Projet

Général

Profil

Paste
Télécharger (13,9 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / media / modules / media_wysiwyg / js / media_wysiwyg.filter.js @ da542b7b

1
/**
2
 *  @file
3
 *  File with utilities to handle media in html editing.
4
 */
5
(function ($) {
6

    
7
  Drupal.media = Drupal.media || {};
8
  /**
9
   * Utility to deal with media tokens / placeholders.
10
   */
11
  Drupal.media.filter = {
12
    /**
13
     * Replaces media tokens with the placeholders for html editing.
14
     * @param content
15
     */
16
    replaceTokenWithPlaceholder: function(content) {
17
      Drupal.media.filter.ensure_tagmap();
18
      var matches = content.match(/\[\[.*?\]\]/g);
19

    
20
      if (matches) {
21
        for (var i = 0; i < matches.length; i++) {
22
          var match = matches[i];
23
          if (match.indexOf('"type":"media"') == -1) {
24
            continue;
25
          }
26

    
27
          // Check if the macro exists in the tagmap. This ensures backwards
28
          // compatibility with existing media and is moderately more efficient
29
          // than re-building the element.
30
          var media = Drupal.settings.tagmap[match];
31
          var media_json = match.replace('[[', '').replace(']]', '');
32

    
33
          // Ensure that the media JSON is valid.
34
          try {
35
            var media_definition = JSON.parse(media_json);
36
          }
37
          catch (err) {
38
            // @todo: error logging.
39
            // Content should be returned to prevent an empty editor.
40
            return content;
41
          }
42

    
43
          // Re-build the media if the macro has changed from the tagmap.
44
          if (!media && media_definition.fid) {
45
            Drupal.media.filter.ensureSourceMap();
46
            var source;
47
            if (source = Drupal.settings.mediaSourceMap[media_definition.fid]) {
48
              media = document.createElement(source.tagName);
49
              media.src = source.src;
50
              media.innerHTML = source.innerHTML;
51
            }
52
            else {
53
              // If the media element can't be found, leave it in to be resolved
54
              // by the user later.
55
              continue;
56
            }
57
          }
58

    
59
          // Apply attributes.
60
          var element = Drupal.media.filter.create_element(media, media_definition);
61
          var markup  = Drupal.media.filter.outerHTML(element);
62

    
63
          // Use split and join to replace all instances of macro with markup.
64
          content = content.split(match).join(markup);
65
        }
66
      }
67

    
68
      return content;
69
    },
70

    
71
    /**
72
     * Returns alt and title field attribute data from the corresponding fields.
73
     *
74
     * Specifically looks for file_entity module's file_image_alt_text and
75
     * file_image_title_text fields as those are by default used to store
76
     * override values for image alt and title attributes.
77
     *
78
     * @param options (array)
79
     *   Options passed through a popup form submission.
80
     * @param includeFieldID (bool)
81
     *   If set, the returned object will have extra keys with the IDs of the
82
     *   found fields.
83
     *
84
     * If the alt or title fields were not found, their keys will be excluded
85
     * from the returned array.
86
     *
87
     * @return
88
     *   An object with the following keys:
89
     *   - alt: The value of the alt field.
90
     *   - altField: The id of the alt field.
91
     *   - title: The value of the title field.
92
     *   - titleField: The id of the title field.
93
     */
94
    parseAttributeFields: function(options, includeFieldID) {
95
      var attributes = {};
96

    
97
      for (var field in options) {
98
        // If the field is set to false, use an empty string for output.
99
        options[field] = options[field] === false ? '' : options[field];
100
        //if (field.match(/^field_file_image_alt_text/)) {
101
        if (field.match(new RegExp('^' + Drupal.settings.media.img_alt_field))) {
102
          attributes.alt = options[field];
103
          if (includeFieldID) {
104
            attributes.altField = field;
105
          }
106
        }
107

    
108
        //if (field.match(/^field_file_image_title_text/)) {
109
        if (field.match(new RegExp('^' + Drupal.settings.media.img_title_field))) {
110
          attributes.title = options[field];
111
          if (includeFieldID) {
112
            attributes.titleField = field;
113
          }
114
        }
115
      }
116

    
117
      return attributes;
118
    },
119

    
120
    /**
121
     * Ensures changes made to fielded attributes are done on the fields too.
122
     *
123
     * This should be called when creating a macro tag from a placeholder.
124
     *
125
     * Changed made to attributes represented by fields are synced back to the
126
     * corresponding fields, if they exist. The alt/title attribute
127
     * values encoded in the macro will override the alt/title field values (set
128
     * in the Media dialog) during rendering of both WYSIWYG placeholders and
129
     * the final file entity on the server. Syncing makes changes applied to a
130
     * placeholder's alt/title attribute using native WYSIWYG tools visible in
131
     * the fields shown in the Media dialog.
132
     *
133
     * The reverse should be done when creating a placeholder from a macro tag
134
     * so changes made in the Media dialog are reflected in the placeholder's
135
     * alt and title attributes or the values there become stale and the change
136
     * appears uneffective.
137
     *
138
     * @param file_info (object)
139
     *   A JSON decoded object of the file being inserted/updated.
140
     */
141
    syncAttributesToFields: function(file_info) {
142
      if (!file_info) {
143
        file_info = {};
144
      }
145
      if (!file_info.attributes) {
146
        file_info.attributes = {};
147
      }
148
      if (!file_info.fields) {
149
        file_info.fields = {};
150
      }
151
      var fields = Drupal.media.filter.parseAttributeFields(file_info.fields, true);
152

    
153
      // If the title attribute has changed, ensure the title field is updated.
154
      var titleAttr = file_info.attributes.title || false;
155
      if (fields.titleField && (titleAttr !== fields.title)) {
156
        file_info.fields[fields.titleField] = titleAttr;
157
      }
158

    
159
      // If the alt attribute has changed, ensure the alt field is updated.
160
      var altAttr = file_info.attributes.alt || false;
161
      if (fields.altField && (altAttr !== fields.alt)) {
162
        file_info.fields[fields.altField] = altAttr;
163
      }
164

    
165
      return file_info;
166
    },
167

    
168
    /**
169
     * Replaces media elements with tokens.
170
     *
171
     * @param content (string)
172
     *   The markup within the wysiwyg instance.
173
     */
174
    replacePlaceholderWithToken: function(content) {
175
      Drupal.media.filter.ensure_tagmap();
176

    
177
      // Replace all media placeholders with their JSON macro representations.
178
      //
179
      // There are issues with using jQuery to parse the WYSIWYG content (see
180
      // http://drupal.org/node/1280758), and parsing HTML with regular
181
      // expressions is a terrible idea (see http://stackoverflow.com/a/1732454/854985)
182
      //
183
      // WYSIWYG editors act wacky with complex placeholder markup anyway, so an
184
      // image is the most reliable and most usable anyway: images can be moved by
185
      // dragging and dropping, and can be resized using interactive handles.
186
      //
187
      // Media requests a WYSIWYG place holder rendering of the file by passing
188
      // the wysiwyg => 1 flag in the settings array when calling
189
      // media_get_file_without_label().
190
      //
191
      // Finds the media-element class.
192
      var classRegex = 'class=[\'"][^\'"]*?media-element';
193
      // Image tag with the media-element class.
194
      var regex = '<img[^>]+' + classRegex + '[^>]*?>';
195
      // Or a span with the media-element class (used for documents).
196
      // \S\s catches any character, including a linebreak; JavaScript does not
197
      // have a dotall flag.
198
      regex += '|<span[^>]+' + classRegex + '[^>]*?>[\\S\\s]+?</span>';
199
      var matches = content.match(RegExp(regex, 'gi'));
200
      if (matches) {
201
        for (i = 0; i < matches.length; i++) {
202
          var markup = matches[i];
203
          var macro = Drupal.media.filter.create_macro($(markup));
204
          // If we have a truthy response, store the macro and perform the
205
          // replacement.
206
          if (macro) {
207
            Drupal.settings.tagmap[macro] = markup;
208
            content = content.replace(markup, macro);
209
          }
210
        }
211
      }
212

    
213
      return content;
214
    },
215

    
216
    /**
217
     * Serializes file information as a url-encoded JSON object and stores it
218
     * as a data attribute on the html element.
219
     *
220
     * @param html (string)
221
     *    A html element to be used to represent the inserted media element.
222
     * @param info (object)
223
     *    A object containing the media file information (fid, view_mode, etc).
224
     */
225
    create_element: function (html, info) {
226
      if ($('<div>').append(html).text().length === html.length) {
227
        // Element is not an html tag. Surround it in a span element so we can
228
        // pass the file attributes.
229
        html = '<span>' + html + '</span>';
230
      }
231
      var element = $(html);
232

    
233
      // Parse out link wrappers. They will be re-applied when the image is
234
      // rendered on the front-end.
235
      if (element.is('a')) {
236
        element = element.children();
237
      }
238

    
239
      // Extract attributes represented by fields and use those values to keep
240
      // them in sync, usually alt and title.
241
      var attributes = Drupal.media.filter.parseAttributeFields(info.fields);
242
      info.attributes = $.extend(info.attributes, attributes);
243

    
244
      // Move attributes from the file info array to the placeholder element.
245
      if (info.attributes) {
246
        $.each(Drupal.settings.media.wysiwyg_allowed_attributes, function(i, a) {
247
          if (info.attributes[a]) {
248
            element.attr(a, $('<textarea />').html(info.attributes[a]).text());
249
          }
250
        });
251
        delete(info.attributes);
252

    
253
        // Store information to rebuild the element later, if necessary.
254
        Drupal.media.filter.ensureSourceMap();
255
        Drupal.settings.mediaSourceMap[info.fid] = {
256
          tagName: element[0].tagName,
257
          src: element[0].src,
258
          innerHTML: element[0].innerHTML
259
        }
260
      }
261

    
262
      info.type = info.type || "media";
263

    
264
      // Store the data in the data map.
265
      Drupal.media.filter.ensureDataMap();
266
      Drupal.settings.mediaDataMap[info.fid] = info;
267

    
268
      // Store the fid in the DOM to retrieve the data from the info map.
269
      element.attr('data-fid', info.fid);
270

    
271
      // Add data-media-element attribute so we can find the markup element later.
272
      element.attr('data-media-element', '1')
273

    
274
      var classes = ['media-element'];
275
      if (info.view_mode) {
276
        // Remove any existing view mode classes.
277
        element.removeClass (function (index, css) {
278
          return (css.match (/\bfile-\S+/g) || []).join(' ');
279
        });
280
        classes.push('file-' + info.view_mode.replace(/_/g, '-'));
281
      }
282
      element.addClass(classes.join(' '));
283

    
284
      // Apply link_text if present.
285
      if (info.link_text) {
286
        $('a', element).html(info.link_text);
287
      }
288

    
289
      return element;
290
    },
291

    
292
    /**
293
     * Create a macro representation of the inserted media element.
294
     *
295
     * @param element (jQuery object)
296
     *    A media element with attached serialized file info.
297
     */
298
    create_macro: function (element) {
299
      var file_info = Drupal.media.filter.extract_file_info(element);
300
      if (file_info) {
301
        if (typeof file_info.link_text == 'string') {
302
          // Make sure the link_text-html-tags are properly escaped.
303
          file_info.link_text = file_info.link_text.replace(/</g, '&lt;').replace(/>/g, '&gt;');
304
        }
305
        return '[[' + JSON.stringify(file_info) + ']]';
306
      }
307
      return false;
308
    },
309

    
310
    /**
311
     * Extract the file info from a WYSIWYG placeholder element as JSON.
312
     *
313
     * @param element (jQuery object)
314
     *    A media element with associated file info via a file id (fid).
315
     */
316
    extract_file_info: function (element) {
317
      var fid, file_info, value;
318

    
319
      if (fid = element.data('fid')) {
320
        Drupal.media.filter.ensureDataMap();
321

    
322
        if (file_info = Drupal.settings.mediaDataMap[fid]) {
323
          file_info.attributes = {};
324

    
325
          $.each(Drupal.settings.media.wysiwyg_allowed_attributes, function(i, a) {
326
            if (value = element.attr(a)) {
327
              // Replace &quot; by \" to avoid error with JSON format.
328
              if (typeof value == 'string') {
329
                value = value.replace('&quot;', '\\"');
330
              }
331
              file_info.attributes[a] = value;
332
            }
333
          });
334

    
335
          // Extract the link text, if there is any.
336
          file_info.link_text = element.find('a').html();
337
        }
338
      }
339

    
340
      return Drupal.media.filter.syncAttributesToFields(file_info);
341
    },
342

    
343
    /**
344
     * Gets the HTML content of an element.
345
     *
346
     * @param element (jQuery object)
347
     */
348
    outerHTML: function (element) {
349
      return element[0].outerHTML || $('<div>').append(element.eq(0).clone()).html();
350
    },
351

    
352
    /**
353
     * Gets the wrapped HTML content of an element to insert into the wysiwyg.
354
     *
355
     * It also registers the element in the tag map so that the token
356
     * replacement works.
357
     *
358
     * @param element (jQuery object) The element to insert.
359
     *
360
     * @see Drupal.media.filter.replacePlaceholderWithToken()
361
     */
362
    getWysiwygHTML: function (element) {
363
      // Create the markup and the macro.
364
      var markup = Drupal.media.filter.outerHTML(element),
365
        macro = Drupal.media.filter.create_macro(element);
366

    
367
      // Store macro/markup in the tagmap.
368
      Drupal.media.filter.ensure_tagmap();
369
      Drupal.settings.tagmap[macro] = markup;
370

    
371
      // Return the html code to insert in an editor and use it with
372
      // replacePlaceholderWithToken()
373
      return markup;
374
    },
375

    
376
    /**
377
     * Ensures the src tracking has been initialized and returns it.
378
     */
379
    ensureSourceMap: function() {
380
      Drupal.settings.mediaSourceMap = Drupal.settings.mediaSourceMap || {};
381
      return Drupal.settings.mediaSourceMap;
382
    },
383

    
384
    /**
385
     * Ensures the data tracking has been initialized and returns it.
386
     */
387
    ensureDataMap: function() {
388
      Drupal.settings.mediaDataMap = Drupal.settings.mediaDataMap || {};
389
      return Drupal.settings.mediaDataMap;
390
    },
391

    
392
    /**
393
     * Ensures the tag map has been initialized and returns it.
394
     */
395
    ensure_tagmap: function () {
396
      Drupal.settings.tagmap = Drupal.settings.tagmap || {};
397
      return Drupal.settings.tagmap;
398
    }
399
  }
400

    
401
})(jQuery);