Projet

Général

Profil

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

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

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 = Drupal.settings.mediaSourceMap[media_definition.fid];
47
            media = document.createElement(source.tagName);
48
            media.src = source.src;
49
            media.innerHTML = source.innerHTML;
50
          }
51

    
52
          // Apply attributes.
53
          var element = Drupal.media.filter.create_element(media, media_definition);
54
          var markup  = Drupal.media.filter.outerHTML(element);
55

    
56
          // Use split and join to replace all instances of macro with markup.
57
          content = content.split(match).join(markup);
58
        }
59
      }
60

    
61
      return content;
62
    },
63

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

    
90
      for (var field in options) {
91
        // If the field is set to false, use an empty string for output.
92
        options[field] = options[field] === false ? '' : options[field];
93
        //if (field.match(/^field_file_image_alt_text/)) {
94
        if (field.match(new RegExp('^' + Drupal.settings.media.img_alt_field))) {
95
          attributes.alt = options[field];
96
          if (includeFieldID) {
97
            attributes.altField = field;
98
          }
99
        }
100

    
101
        //if (field.match(/^field_file_image_title_text/)) {
102
        if (field.match(new RegExp('^' + Drupal.settings.media.img_title_field))) {
103
          attributes.title = options[field];
104
          if (includeFieldID) {
105
            attributes.titleField = field;
106
          }
107
        }
108
      }
109

    
110
      return attributes;
111
    },
112

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

    
146
      // If the title attribute has changed, ensure the title field is updated.
147
      var titleAttr = file_info.attributes.title || false;
148
      if (fields.titleField && (titleAttr !== fields.title)) {
149
        file_info.fields[fields.titleField] = titleAttr;
150
      }
151

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

    
158
      return file_info;
159
    },
160

    
161
    /**
162
     * Replaces media elements with tokens.
163
     *
164
     * @param content (string)
165
     *   The markup within the wysiwyg instance.
166
     */
167
    replacePlaceholderWithToken: function(content) {
168
      Drupal.media.filter.ensure_tagmap();
169

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

    
206
      return content;
207
    },
208

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

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

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

    
238
      // Move attributes from the file info array to the placeholder element.
239
      if (info.attributes) {
240
        $.each(Drupal.settings.media.wysiwyg_allowed_attributes, function(i, a) {
241
          if (info.attributes[a]) {
242
            element.attr(a, $('<textarea />').html(info.attributes[a]).text());
243
          }
244
        });
245
        delete(info.attributes);
246

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

    
256
      info.type = info.type || "media";
257

    
258
      // Store the data in the data map.
259
      Drupal.media.filter.ensureDataMap();
260
      Drupal.settings.mediaDataMap[info.fid] = info;
261

    
262
      // Store the fid in the DOM to retrieve the data from the info map.
263
      element.attr('data-fid', info.fid);
264

    
265
      // Add data-media-element attribute so we can find the markup element later.
266
      element.attr('data-media-element', '1')
267

    
268
      var classes = ['media-element'];
269
      if (info.view_mode) {
270
        // Remove any existing view mode classes.
271
        element.removeClass (function (index, css) {
272
          return (css.match (/\bfile-\S+/g) || []).join(' ');
273
        });
274
        classes.push('file-' + info.view_mode.replace(/_/g, '-'));
275
      }
276
      element.addClass(classes.join(' '));
277

    
278
      // Apply link_text if present.
279
      if (info.link_text) {
280
        $('a', element).html(info.link_text);
281
      }
282

    
283
      return element;
284
    },
285

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

    
304
    /**
305
     * Extract the file info from a WYSIWYG placeholder element as JSON.
306
     *
307
     * @param element (jQuery object)
308
     *    A media element with associated file info via a file id (fid).
309
     */
310
    extract_file_info: function (element) {
311
      var fid, file_info, value;
312

    
313
      if (fid = element.data('fid')) {
314
        Drupal.media.filter.ensureDataMap();
315

    
316
        if (file_info = Drupal.settings.mediaDataMap[fid]) {
317
          file_info.attributes = {};
318

    
319
          $.each(Drupal.settings.media.wysiwyg_allowed_attributes, function(i, a) {
320
            if (value = element.attr(a)) {
321
              // Replace &quot; by \" to avoid error with JSON format.
322
              if (typeof value == 'string') {
323
                value = value.replace('&quot;', '\\"');
324
              }
325
              file_info.attributes[a] = value;
326
            }
327
          });
328

    
329
          // Extract the link text, if there is any.
330
          file_info.link_text = element.find('a').html();
331
        }
332
      }
333

    
334
      return Drupal.media.filter.syncAttributesToFields(file_info);
335
    },
336

    
337
    /**
338
     * Gets the HTML content of an element.
339
     *
340
     * @param element (jQuery object)
341
     */
342
    outerHTML: function (element) {
343
      return element[0].outerHTML || $('<div>').append(element.eq(0).clone()).html();
344
    },
345

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

    
361
      // Store macro/markup in the tagmap.
362
      Drupal.media.filter.ensure_tagmap();
363
      Drupal.settings.tagmap[macro] = markup;
364

    
365
      // Return the html code to insert in an editor and use it with
366
      // replacePlaceholderWithToken()
367
      return markup;
368
    },
369

    
370
    /**
371
     * Ensures the src tracking has been initialized and returns it.
372
     */
373
    ensureSourceMap: function() {
374
      Drupal.settings.mediaSourceMap = Drupal.settings.mediaSourceMap || {};
375
      return Drupal.settings.mediaSourceMap;
376
    },
377

    
378
    /**
379
     * Ensures the data tracking has been initialized and returns it.
380
     */
381
    ensureDataMap: function() {
382
      Drupal.settings.mediaDataMap = Drupal.settings.mediaDataMap || {};
383
      return Drupal.settings.mediaDataMap;
384
    },
385

    
386
    /**
387
     * Ensures the tag map has been initialized and returns it.
388
     */
389
    ensure_tagmap: function () {
390
      Drupal.settings.tagmap = Drupal.settings.tagmap || {};
391
      return Drupal.settings.tagmap;
392
    }
393
  }
394

    
395
})(jQuery);