Projet

Général

Profil

Paste
Télécharger (18,1 ko) Statistiques
| Branche: | Révision:

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

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
          else if (element.attr(a)) {
242
            // If the element has the attribute, but the value is empty, be
243
            // sure to clear it.
244
            element.removeAttr(a);
245
          }
246
        });
247
        delete(info.attributes);
248

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

    
258
      info.type = info.type || "media";
259

    
260
      // Store the data in the data map.
261
      Drupal.media.filter.ensureDataMap();
262

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

    
274
      Drupal.settings.mediaDataMap[info.fid] = info;
275

    
276
      // Store the fid in the DOM to retrieve the data from the info map.
277
      element.attr('data-fid', info.fid);
278

    
279
      // Add data-media-element attribute so we can find the markup element later.
280
      element.attr('data-media-element', '1')
281

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

    
299
      // Attempt to override the link_title if the user has chosen to do this.
300
      info.link_text = this.overrideLinkTitle(info);
301
      // Apply link_text if present.
302
      if (info.link_text) {
303
        $('a', element).html(info.link_text);
304
      }
305

    
306
      return element;
307
    },
308

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

    
328
    /**
329
     * Extract the file info from a WYSIWYG placeholder element as JSON.
330
     *
331
     * @param element (jQuery object)
332
     *    A media element with associated file info via a file id (fid).
333
     */
334
    extract_file_info: function (element) {
335
      var fid, file_info, value, delta;
336

    
337
      if (fid = element.data('fid')) {
338
        Drupal.media.filter.ensureDataMap();
339

    
340
        if (file_info = Drupal.settings.mediaDataMap[fid]) {
341
          file_info.attributes = {};
342

    
343
          $.each(Drupal.settings.media.wysiwyg_allowed_attributes, function(i, a) {
344
            if (value = element.attr(a)) {
345
              // Replace &quot; by \" to avoid error with JSON format.
346
              if (typeof value == 'string') {
347
                value = value.replace('&quot;', '\\"');
348
              }
349
              file_info.attributes[a] = value;
350
            }
351
          });
352

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

    
356
          // When a file is embedded, its fields can be overridden. To allow for
357
          // the edge case where the same file is embedded multiple times with
358
          // different field overrides, we look for a data-delta attribute on
359
          // the element, and use that to decide which set of data in the
360
          // "field_deltas" property to use.
361
          if (delta = element.data('delta')) {
362
            if (file_info.field_deltas && file_info.field_deltas[delta]) {
363
              file_info.fields = file_info.field_deltas[delta];
364

    
365
              // Also look for an overridden view mode, aka "format".
366
              // Check for existance of fields to make it backward compatible.
367
              if (file_info.fields && file_info.fields.format && file_info.view_mode) {
368
                file_info.view_mode = file_info.fields.format;
369
              }
370
            }
371
          }
372
        }
373
        else {
374
          return false;
375
        }
376
      }
377
      else {
378
        return false;
379
      }
380

    
381
      return Drupal.media.filter.syncAttributesToFields(file_info);
382
    },
383

    
384
    /**
385
     * Gets the HTML content of an element.
386
     *
387
     * @param element (jQuery object)
388
     */
389
    outerHTML: function (element) {
390
      return element[0].outerHTML || $('<div>').append(element.eq(0).clone()).html();
391
    },
392

    
393
    /**
394
     * Gets the wrapped HTML content of an element to insert into the wysiwyg.
395
     *
396
     * It also registers the element in the tag map so that the token
397
     * replacement works.
398
     *
399
     * @param element (jQuery object) The element to insert.
400
     *
401
     * @see Drupal.media.filter.replacePlaceholderWithToken()
402
     */
403
    getWysiwygHTML: function (element) {
404
      // Create the markup and the macro.
405
      var markup = Drupal.media.filter.outerHTML(element),
406
        macro = Drupal.media.filter.create_macro(element);
407

    
408
      // Store macro/markup in the tagmap.
409
      Drupal.media.filter.ensure_tagmap();
410
      Drupal.settings.tagmap[macro] = markup;
411

    
412
      // Return the html code to insert in an editor and use it with
413
      // replacePlaceholderWithToken()
414
      return markup;
415
    },
416

    
417
    /**
418
     * Ensures the src tracking has been initialized and returns it.
419
     */
420
    ensureSourceMap: function() {
421
      Drupal.settings.mediaSourceMap = Drupal.settings.mediaSourceMap || {};
422
      return Drupal.settings.mediaSourceMap;
423
    },
424

    
425
    /**
426
     * Ensures the data tracking has been initialized and returns it.
427
     */
428
    ensureDataMap: function() {
429
      Drupal.settings.mediaDataMap = Drupal.settings.mediaDataMap || {};
430
      return Drupal.settings.mediaDataMap;
431
    },
432

    
433
    /**
434
     * Ensures the tag map has been initialized and returns it.
435
     */
436
    ensure_tagmap: function () {
437
      Drupal.settings.tagmap = Drupal.settings.tagmap || {};
438
      return Drupal.settings.tagmap;
439
    },
440

    
441
    /**
442
     * Return the overridden link title based on the file_entity title field
443
     * set.
444
     * @param file the file object.
445
     * @returns the overridden link_title or the existing link text if no
446
     * overridden.
447
     */
448
    overrideLinkTitle: function(file) {
449
      var file_title_field = Drupal.settings.media.img_title_field.replace('field_', '');
450
      var file_title_field_machine_name = '';
451
      if (typeof(file.fields) != 'undefined') {
452
        jQuery.each(file.fields, function(field, fieldValue) {
453
          if (field.indexOf(file_title_field) != -1) {
454
            file_title_field_machine_name = field;
455
          }
456
        });
457

    
458
        if (typeof(file.fields[file_title_field_machine_name]) != 'undefined' && file.fields[file_title_field_machine_name] != '') {
459
          return file.fields[file_title_field_machine_name];
460
        }
461
        else {
462
          return file.link_text;
463
        }
464
      }
465
      else {
466
        return file.link_text;
467
      }
468
    },
469

    
470
    /**
471
     * Generates a unique "delta" for each embedding of a particular file.
472
     */
473
    fileEmbedDelta: function(fid, element) {
474
      // Ensure we have an object to track our deltas.
475
      Drupal.settings.mediaDeltas = Drupal.settings.mediaDeltas || {};
476
      Drupal.settings.maxMediaDelta = Drupal.settings.maxMediaDelta || 0;
477

    
478
      // Check to see if the element already has one.
479
      if (element && element.data('delta')) {
480
        var existingDelta = element.data('delta');
481
        // If so, make sure that it is being tracked in mediaDeltas. If we're
482
        // going to create new deltas later on, make sure they do not overwrite
483
        // other mediaDeltas.
484
        if (!Drupal.settings.mediaDeltas[existingDelta]) {
485
          Drupal.settings.mediaDeltas[existingDelta] = fid;
486
          Drupal.settings.maxMediaDelta = Math.max(Drupal.settings.maxMediaDelta, existingDelta);
487
        }
488
        return existingDelta;
489
      }
490
      // Otherwise, generate a new one.
491
      var newDelta = Drupal.settings.maxMediaDelta + 1;
492
      Drupal.settings.mediaDeltas[newDelta] = fid;
493
      Drupal.settings.maxMediaDelta = newDelta;
494
      return newDelta;
495
    }
496
  }
497

    
498
})(jQuery);