Projet

Général

Profil

Paste
Télécharger (16,4 ko) Statistiques
| Branche: | Révision:

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

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
      // Locate and process all the media placeholders in the WYSIWYG content.
178
      var contentElements = $('<div/>').html(content);  // TODO: once baseline jQuery is 1.8+, switch to using $.parseHTML(content)
179
      var mediaElements = contentElements.find('.media-element');
180
      if (mediaElements) {
181
        $(mediaElements).each(function (i) {
182
          // Attempt to derive a JSON macro representation of the media placeholder.
183
          // Note: Drupal 7 ships with JQuery 1.4.4, which allows $(this).attr('outerHTML') to retrieve the eement's HTML,
184
          // but many sites use JQuery update to increate this to 1.6+, which insists on $(this).prop('outerHTML). 
185
          // Until the minimum jQuery is >= 1.6, we need to do this the old-school way. 
186
          // See http://stackoverflow.com/questions/2419749/get-selected-elements-outer-html
187
          var markup = $(this).get(0).outerHTML;
188
          if (markup === undefined) {
189
            // Browser does not support outerHTML DOM property.  Use the more expensive clone method instead.
190
            markup = $(this).clone().wrap('<div>').parent().html();
191
          }
192
          var macro = Drupal.media.filter.create_macro($(markup));
193
          if (macro) {
194
            // Replace the placeholder with the macro in the parsed content.
195
            // (Can't just replace the string section, because the outerHTML may be subtly different,
196
            // depending on the browser. Parsing tends to convert <img/> to <img>, for instance.)
197
            Drupal.settings.tagmap[macro] = markup;
198
            $(this).replaceWith(macro);
199
          }
200
        });
201
        content = $(contentElements).html();
202
      }
203

    
204
      return content;
205
    },
206

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

    
224
      // Parse out link wrappers. They will be re-applied when the image is
225
      // rendered on the front-end.
226
      if (element.is('a') && element.find('img').length) {
227
        element = element.children();
228
      }
229

    
230
      // Extract attributes represented by fields and use those values to keep
231
      // them in sync, usually alt and title.
232
      var attributes = Drupal.media.filter.parseAttributeFields(info.fields);
233
      info.attributes = $.extend(info.attributes, attributes);
234

    
235
      // Move attributes from the file info array to the placeholder element.
236
      if (info.attributes) {
237
        $.each(Drupal.settings.media.wysiwyg_allowed_attributes, function(i, a) {
238
          if (info.attributes[a]) {
239
            element.attr(a, info.attributes[a]);
240
          }
241
        });
242
        delete(info.attributes);
243

    
244
        // Store information to rebuild the element later, if necessary.
245
        Drupal.media.filter.ensureSourceMap();
246
        Drupal.settings.mediaSourceMap[info.fid] = {
247
          tagName: element[0].tagName,
248
          src: element[0].src,
249
          innerHTML: element[0].innerHTML
250
        }
251
      }
252

    
253
      info.type = info.type || "media";
254

    
255
      // Store the data in the data map.
256
      Drupal.media.filter.ensureDataMap();
257

    
258
      // Generate a "delta" to allow for multiple embeddings of the same file.
259
      var delta = Drupal.media.filter.fileEmbedDelta(info.fid, element);
260
      if (Drupal.settings.mediaDataMap[info.fid]) {
261
        info.field_deltas = Drupal.settings.mediaDataMap[info.fid].field_deltas || {};
262
      }
263
      else {
264
        info.field_deltas = {};
265
      }
266
      info.field_deltas[delta] = info.fields;
267
      element.attr('data-delta', delta);
268

    
269
      Drupal.settings.mediaDataMap[info.fid] = info;
270

    
271
      // Store the fid in the DOM to retrieve the data from the info map.
272
      element.attr('data-fid', info.fid);
273

    
274
      // Add data-media-element attribute so we can find the markup element later.
275
      element.attr('data-media-element', '1')
276

    
277
      var classes = ['media-element'];
278
      if (info.view_mode) {
279
        // Remove any existing view mode classes.
280
        element.removeClass (function (index, css) {
281
          return (css.match (/\bfile-\S+/g) || []).join(' ');
282
        });
283
        classes.push('file-' + info.view_mode.replace(/_/g, '-'));
284
      }
285
      // Check for alignment info, after removing any existing alignment class.
286
      element.removeClass (function (index, css) {
287
        return (css.match (/\bmedia-wysiwyg-align-\S+/g) || []).join(' ');
288
      });
289
      if (info.fields && info.fields.alignment) {
290
        classes.push('media-wysiwyg-align-' + info.fields.alignment);
291
      }
292
      element.addClass(classes.join(' '));
293

    
294
      // Apply link_text if present.
295
      if (info.link_text) {
296
        $('a', element).html(info.link_text);
297
      }
298

    
299
      return element;
300
    },
301

    
302
    /**
303
     * Create a macro representation of the inserted media element.
304
     *
305
     * @param element (jQuery object)
306
     *    A media element with attached serialized file info.
307
     */
308
    create_macro: function (element) {
309
      var file_info = Drupal.media.filter.extract_file_info(element);
310
      if (file_info) {
311
        if (typeof file_info.link_text == 'string') {
312
          // Make sure the link_text-html-tags are properly escaped.
313
          file_info.link_text = file_info.link_text.replace(/</g, '&lt;').replace(/>/g, '&gt;');
314
        }
315
        return '[[' + JSON.stringify(file_info) + ']]';
316
      }
317
      return false;
318
    },
319

    
320
    /**
321
     * Extract the file info from a WYSIWYG placeholder element as JSON.
322
     *
323
     * @param element (jQuery object)
324
     *    A media element with associated file info via a file id (fid).
325
     */
326
    extract_file_info: function (element) {
327
      var fid, file_info, value, delta;
328

    
329
      if (fid = element.data('fid')) {
330
        Drupal.media.filter.ensureDataMap();
331

    
332
        if (file_info = Drupal.settings.mediaDataMap[fid]) {
333
          file_info.attributes = {};
334

    
335
          $.each(Drupal.settings.media.wysiwyg_allowed_attributes, function(i, a) {
336
            if (value = element.attr(a)) {
337
              // Replace &quot; by \" to avoid error with JSON format.
338
              if (typeof value == 'string') {
339
                value = value.replace('&quot;', '\\"');
340
              }
341
              file_info.attributes[a] = value;
342
            }
343
          });
344

    
345
          // Extract the link text, if there is any.
346
          file_info.link_text = (Drupal.settings.mediaDoLinkText) ? element.find('a').html() : false;
347

    
348
          // When a file is embedded, its fields can be overridden. To allow for
349
          // the edge case where the same file is embedded multiple times with
350
          // different field overrides, we look for a data-delta attribute on
351
          // the element, and use that to decide which set of data in the
352
          // "field_deltas" property to use.
353
          if (delta = element.data('delta')) {
354
            if (file_info.field_deltas && file_info.field_deltas[delta]) {
355
              file_info.fields = file_info.field_deltas[delta];
356

    
357
              // Also look for an overridden view mode, aka "format".
358
              // Check for existance of fields to make it backward compatible.
359
              if (file_info.fields && file_info.fields.format && file_info.view_mode) {
360
                file_info.view_mode = file_info.fields.format;
361
              }
362
            }
363
          }
364
        }
365
      }
366

    
367
      return Drupal.media.filter.syncAttributesToFields(file_info);
368
    },
369

    
370
    /**
371
     * Gets the HTML content of an element.
372
     *
373
     * @param element (jQuery object)
374
     */
375
    outerHTML: function (element) {
376
      return element[0].outerHTML || $('<div>').append(element.eq(0).clone()).html();
377
    },
378

    
379
    /**
380
     * Gets the wrapped HTML content of an element to insert into the wysiwyg.
381
     *
382
     * It also registers the element in the tag map so that the token
383
     * replacement works.
384
     *
385
     * @param element (jQuery object) The element to insert.
386
     *
387
     * @see Drupal.media.filter.replacePlaceholderWithToken()
388
     */
389
    getWysiwygHTML: function (element) {
390
      // Create the markup and the macro.
391
      var markup = Drupal.media.filter.outerHTML(element),
392
        macro = Drupal.media.filter.create_macro(element);
393

    
394
      // Store macro/markup in the tagmap.
395
      Drupal.media.filter.ensure_tagmap();
396
      Drupal.settings.tagmap[macro] = markup;
397

    
398
      // Return the html code to insert in an editor and use it with
399
      // replacePlaceholderWithToken()
400
      return markup;
401
    },
402

    
403
    /**
404
     * Ensures the src tracking has been initialized and returns it.
405
     */
406
    ensureSourceMap: function() {
407
      Drupal.settings.mediaSourceMap = Drupal.settings.mediaSourceMap || {};
408
      return Drupal.settings.mediaSourceMap;
409
    },
410

    
411
    /**
412
     * Ensures the data tracking has been initialized and returns it.
413
     */
414
    ensureDataMap: function() {
415
      Drupal.settings.mediaDataMap = Drupal.settings.mediaDataMap || {};
416
      return Drupal.settings.mediaDataMap;
417
    },
418

    
419
    /**
420
     * Ensures the tag map has been initialized and returns it.
421
     */
422
    ensure_tagmap: function () {
423
      Drupal.settings.tagmap = Drupal.settings.tagmap || {};
424
      return Drupal.settings.tagmap;
425
    },
426

    
427
    /**
428
     * Generates a unique "delta" for each embedding of a particular file.
429
     */
430
    fileEmbedDelta: function(fid, element) {
431
      // Ensure we have an object to track our deltas.
432
      Drupal.settings.mediaDeltas = Drupal.settings.mediaDeltas || {};
433

    
434
      // Check to see if the element already has one.
435
      if (element && element.data('delta')) {
436
        var existingDelta = element.data('delta');
437
        // If so, make sure that it is being tracked in mediaDeltas.
438
        if (!Drupal.settings.mediaDeltas[fid]) {
439
          Drupal.settings.mediaDeltas[fid] = existingDelta;
440
        }
441
        return existingDelta;
442
      }
443
      // Otherwise, generate a new one. Arbitrarily start with 1.
444
      var delta = 1;
445
      if (Drupal.settings.mediaDeltas[fid]) {
446
        delta = Drupal.settings.mediaDeltas[fid] + 1;
447
      }
448
      Drupal.settings.mediaDeltas[fid] = delta;
449
      return delta;
450
    }
451
  }
452

    
453
})(jQuery);