diff --git a/js/media.filter.js b/js/media.filter.js new file mode 100644 index 0000000..8b961c0 --- /dev/null +++ b/js/media.filter.js @@ -0,0 +1,205 @@ +/** + * @file + * File with utilities to handle media in html editing. + */ +(function ($) { + + Drupal.media = Drupal.media || {}; + /** + * Utility to deal with media tokens / placeholders. + */ + Drupal.media.filter = { + /** + * Replaces media tokens with the placeholders for html editing. + * @param content + */ + replaceTokenWithPlaceholder: function(content) { + var tagmap = Drupal.media.filter.tagmap(), + matches = content.match(/\[\[.*?\]\]/g), + media_definition; + + if (matches) { + for (var index in matches) { + var macro = matches[index]; + + if (tagmap[macro]) { + var media_json = macro.replace('[[', '').replace(']]', ''); + + // Make sure that the media JSON is valid. + try { + media_definition = JSON.parse(media_json); + } + catch (err) { + media_definition = null; + } + if (media_definition) { + // Apply attributes. + var element = Drupal.media.filter.create_element(tagmap[macro], media_definition); + var markup = Drupal.media.filter.outerHTML(element); + + content = content.replace(macro, markup); + } + } + else { + debug.debug("Could not find content for " + macro); + } + } + } + return content; + }, + + /** + * Replaces the placeholders for html editing with the media tokens to store. + * @param content + */ + replacePlaceholderWithToken: function(content) { + var tagmap = Drupal.media.filter.tagmap(); + for (var key in tagmap) { + content.replace(tagmap[key], key); + } + return content; + }, + + /** + * Register new element and returns the placeholder markup. + * + * @param formattedMedia a formatted media object as given by the onSubmit + * function of the media Style popup. + * @param fid the file id. + * + * @return The registered element. + */ + registerNewElement: function (formattedMedia, fid) { + var element = Drupal.media.filter.create_element(formattedMedia.html, { + fid: fid, + view_mode: formattedMedia.type, + attributes: formattedMedia.options + }); + + var markup = Drupal.media.filter.outerHTML(element), + macro = Drupal.media.filter.create_macro(element); + + // Store macro/markup pair in the tagmap. + Drupal.media.filter.tagmap(); + Drupal.settings.tagmap[macro] = markup; + + return element; + }, + + /** + * Serializes file information as a url-encoded JSON object and stores it as a + * data attribute on the html element. + * + * @param html (string) + * A html element to be used to represent the inserted media element. + * @param info (object) + * A object containing the media file information (fid, view_mode, etc). + */ + create_element: function (html, info) { + if ($('
').append(html).text().length === html.length) { + // Element is not an html tag. Surround it in a span element + // so we can pass the file attributes. + html = '' + html + ''; + } + var element = $(html); + + // Move attributes from the file info array to the placeholder element. + if (info.attributes) { + $.each(Drupal.media.filter.allowed_attributes(), function(i, a) { + if (info.attributes[a]) { + element.attr(a, info.attributes[a]); + } + }); + delete(info.attributes); + } + + // Important to url-encode the file information as it is being stored in an + // html data attribute. + info.type = info.type || "media"; + element.attr('data-file_info', encodeURI(JSON.stringify(info))); + + // Adding media-element class so we can find markup element later. + var classes = ['media-element']; + + if(info.view_mode){ + classes.push('file-' + info.view_mode.replace(/_/g, '-')); + } + element.addClass(classes.join(' ')); + + return element; + }, + + /** + * Create a macro representation of the inserted media element. + * + * @param element (jQuery object) + * A media element with attached serialized file info. + */ + create_macro: function (element) { + var file_info = Drupal.media.filter.extract_file_info(element); + if (file_info) { + return '[[' + JSON.stringify(file_info) + ']]'; + } + return false; + }, + + /** + * Extract the file info from a WYSIWYG placeholder element as JSON. + * + * @param element (jQuery object) + * A media element with attached serialized file info. + */ + extract_file_info: function (element) { + var file_json = $.data(element, 'file_info') || element.data('file_info'), + file_info, + value; + + try { + file_info = JSON.parse(decodeURIComponent(file_json)); + } + catch (err) { + file_info = null; + } + + if (file_info) { + file_info.attributes = {}; + + // Extract whitelisted attributes. + $.each(Drupal.media.filter.allowed_attributes, function(i, a) { + if (value = element.attr(a)) { + file_info.attributes[a] = value; + } + }); + delete(file_info.attributes['data-file_info']); + } + + return file_info; + }, + + /** + * Gets the HTML content of an element. + * + * @param element (jQuery object) + */ + outerHTML: function (element) { + return $('
').append(element.eq(0).clone()).html(); + }, + + /** + * Ensures the tag map has been initialized and returns it. + */ + tagmap: function () { + Drupal.settings.tagmap = Drupal.settings.tagmap || {}; + return Drupal.settings.tagmap; + }, + + /** + * Ensures the wysiwyg_allowed_attributes and returns it. + * In case of an error the default settings are returned. + */ + allowed_attributes: function () { + Drupal.settings.wysiwyg_allowed_attributes = Drupal.settings.wysiwyg_allowed_attributes || ['height', 'width', 'hspace', 'vspace', 'border', 'align', 'style', 'alt', 'title', 'class', 'id', 'usemap']; + return Drupal.settings.wysiwyg_allowed_attributes; + } + } +})(jQuery); diff --git a/js/wysiwyg-media.js b/js/wysiwyg-media.js index 5de03f9..1dd3480 100644 --- a/js/wysiwyg-media.js +++ b/js/wysiwyg-media.js @@ -42,7 +42,7 @@ Drupal.wysiwyg.plugins.media = { var insert = new InsertMedia(instanceId); if (this.isNode(data.node)) { // Change the view mode for already-inserted media. - var media_file = extract_file_info($(data.node)); + var media_file = Drupal.media.filter.extract_file_info($(data.node)); insert.onSelect([media_file]); } else { @@ -61,39 +61,7 @@ Drupal.wysiwyg.plugins.media = { * that will show in the editor. */ attach: function (content, settings, instanceId) { - ensure_tagmap(); - - var tagmap = Drupal.settings.tagmap, - matches = content.match(/\[\[.*?\]\]/g), - media_definition; - - if (matches) { - for (var index in matches) { - var macro = matches[index]; - - if (tagmap[macro]) { - var media_json = macro.replace('[[', '').replace(']]', ''); - - // Make sure that the media JSON is valid. - try { - media_definition = JSON.parse(media_json); - } - catch (err) { - media_definition = null; - } - if (media_definition) { - // Apply attributes. - var element = create_element(tagmap[macro], media_definition); - var markup = outerHTML(element); - - content = content.replace(macro, markup); - } - } - else { - debug.debug("Could not find content for " + macro); - } - } - } + content = Drupal.media.filter.replaceTokenWithPlaceholder(content); return content; }, @@ -101,44 +69,7 @@ Drupal.wysiwyg.plugins.media = { * Detach function, called when a rich text editor detaches */ detach: function (content, settings, instanceId) { - ensure_tagmap(); - var tagmap = Drupal.settings.tagmap, - i = 0, - markup, - macro; - - // Replace all media placeholders with their JSON macro representations. - // - // There are issues with using jQuery to parse the WYSIWYG content (see - // http://drupal.org/node/1280758), and parsing HTML with regular - // expressions is a terrible idea (see http://stackoverflow.com/a/1732454/854985) - // - // WYSIWYG editors act wacky with complex placeholder markup anyway, so an - // image is the most reliable and most usable anyway: images can be moved by - // dragging and dropping, and can be resized using interactive handles. - // - // Media requests a WYSIWYG place holder rendering of the file by passing - // the wysiwyg => 1 flag in the settings array when calling - // media_get_file_without_label(). - // - // Finds the media-element class. - var classRegex = 'class=[\'"][^\'"]*?media-element'; - // Image tag with the media-element class. - var regex = ']+' + classRegex + '[^>]*?>'; - // Or a span with the media-element class (used for documents). - // \S\s catches any character, including a linebreak; JavaScript does not - // have a dotall flag. - regex += '|]+' + classRegex + '[^>]*?>[\\S\\s]+?'; - var matches = content.match(RegExp(regex, 'gi')); - if (matches) { - for (i = 0; i < matches.length; i++) { - markup = matches[i]; - macro = create_macro($(markup)); - tagmap[macro] = markup; - content = content.replace(markup, macro); - } - } - + content = Drupal.media.filter.replacePlaceholderWithToken(content); return content; } }; diff --git a/wysiwyg_plugins/media.inc b/wysiwyg_plugins/media.inc index 9d0c07c..56ad769 100644 --- a/wysiwyg_plugins/media.inc +++ b/wysiwyg_plugins/media.inc @@ -15,6 +15,9 @@ function media_media_plugin() { // @see http://drupal.org/node/1039076 media_include_browser_js(); + // Add the filter handling. + drupal_add_js(drupal_get_path('module', 'media') . '/js/media.filter.js'); + // Plugin definition. $plugins['media'] = array( 'title' => media_variable_get('wysiwyg_title'),