Projet

Général

Profil

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

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

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

    
205
      return content;
206
    },
207

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

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

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

    
236
      // Move attributes from the file info array to the placeholder element.
237
      if (info.attributes) {
238
        $.each(Drupal.settings.media.wysiwyg_allowed_attributes, function(i, a) {
239
          if (info.attributes[a]) {
240
            element.attr(a, info.attributes[a]);
241
          }
242
          else if (element.attr(a)) {
243
            // If the element has the attribute, but the value is empty, be
244
            // sure to clear it.
245
            element.removeAttr(a);
246
          }
247
        });
248
        delete(info.attributes);
249

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

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

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

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

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

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

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

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

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

    
307
      return element;
308
    },
309

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
499
})(jQuery);