Projet

Général

Profil

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

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

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
      // Attempt to override the link_title if the user has chosen to do this.
295
      info.link_text = this.overrideLinkTitle(info);
296
      // Apply link_text if present.
297
      if (info.link_text) {
298
        $('a', element).html(info.link_text);
299
      }
300

    
301
      return element;
302
    },
303

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

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

    
332
      if (fid = element.data('fid')) {
333
        Drupal.media.filter.ensureDataMap();
334

    
335
        if (file_info = Drupal.settings.mediaDataMap[fid]) {
336
          file_info.attributes = {};
337

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

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

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

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

    
376
      return Drupal.media.filter.syncAttributesToFields(file_info);
377
    },
378

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

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

    
403
      // Store macro/markup in the tagmap.
404
      Drupal.media.filter.ensure_tagmap();
405
      Drupal.settings.tagmap[macro] = markup;
406

    
407
      // Return the html code to insert in an editor and use it with
408
      // replacePlaceholderWithToken()
409
      return markup;
410
    },
411

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

    
420
    /**
421
     * Ensures the data tracking has been initialized and returns it.
422
     */
423
    ensureDataMap: function() {
424
      Drupal.settings.mediaDataMap = Drupal.settings.mediaDataMap || {};
425
      return Drupal.settings.mediaDataMap;
426
    },
427

    
428
    /**
429
     * Ensures the tag map has been initialized and returns it.
430
     */
431
    ensure_tagmap: function () {
432
      Drupal.settings.tagmap = Drupal.settings.tagmap || {};
433
      return Drupal.settings.tagmap;
434
    },
435

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

    
453
        if (typeof(file.fields[file_title_field_machine_name]) != 'undefined' && file.fields[file_title_field_machine_name] != '') {
454
          return file.fields[file_title_field_machine_name];
455
        }
456
        else {
457
          return file.link_text;
458
        }
459
      }
460
      else {
461
        return file.link_text;
462
      }
463
    },
464

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

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

    
493
})(jQuery);