.../includes/media_wysiwyg.filter.inc | 16 +- modules/media_wysiwyg/js/media_wysiwyg.filter.js | 189 ++++++++++++-------- modules/media_wysiwyg/js/wysiwyg-media.js | 1 + 3 files changed, 127 insertions(+), 79 deletions(-) diff --git a/modules/media_wysiwyg/includes/media_wysiwyg.filter.inc b/modules/media_wysiwyg/includes/media_wysiwyg.filter.inc index 8cc99c9..2b0fecf 100644 --- a/modules/media_wysiwyg/includes/media_wysiwyg.filter.inc +++ b/modules/media_wysiwyg/includes/media_wysiwyg.filter.inc @@ -166,18 +166,18 @@ function media_wysiwyg_token_to_markup($match, $wysiwyg = FALSE) { if ($wysiwyg) { $settings['wysiwyg'] = $wysiwyg; // If sending markup to a WYSIWYG, we need to pass the file infomation so - // that a inline macro can be generated when the WYSIWYG is detached. - // The WYSIWYG plugin is expecting this information in the format of a - // urlencoded JSON string stored in the data-file_info attribute of the - // element. + // that an inline macro can be generated when the WYSIWYG is detached. + // The WYSIWYG plugin is expecting this information in the + // Drupal.settings.mediaDataMap variable. $element = media_wysiwyg_get_file_without_label($file, $tag_info['view_mode'], $settings); - $data = drupal_json_encode(array( + $data = array( 'type' => 'media', - 'fid' => $file->fid, + 'fid' => $file->fid, 'view_mode' => $tag_info['view_mode'], 'link_text' => $tag_info['link_text'], - )); - $element['#attributes']['data-file_info'] = urlencode($data); + ); + drupal_add_js(array('mediaDataMap' => array($file->fid => $data)), 'setting'); + $element['#attributes']['data-fid'] = $file->fid; $element['#attributes']['class'][] = 'media-element'; } else { diff --git a/modules/media_wysiwyg/js/media_wysiwyg.filter.js b/modules/media_wysiwyg/js/media_wysiwyg.filter.js index 7de50af..002805e 100644 --- a/modules/media_wysiwyg/js/media_wysiwyg.filter.js +++ b/modules/media_wysiwyg/js/media_wysiwyg.filter.js @@ -15,64 +15,84 @@ */ replaceTokenWithPlaceholder: function(content) { Drupal.media.filter.ensure_tagmap() - var tagmap = Drupal.settings.tagmap, - matches = content.match(/\[\[.*?\]\]/g), - media_definition; + var matches = content.match(/\[\[.*?"type":"media".*?\]\]/g); if (matches) { - for (var macro in tagmap) { - // We cant use indexOf because of IE. - var index = $.inArray(macro, matches); - if (index !== -1) { - 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); + for (var i = 0; i < matches.length; i++) { + var match = matches[i]; + + // Check if the macro exists in the tagmap. This ensures backwards + // compatibility with existing media and is moderately more efficient + // than re-building the element. + var media = Drupal.settings.tagmap[match]; + var media_json = match.replace('[[', '').replace(']]', ''); + + // Ensure that the media JSON is valid. + try { + var media_definition = JSON.parse(media_json); + } + catch (err) { + // @todo: error logging. + // Content should be returned to prevent an empty editor. + return content; + } - content = content.replace(macro, markup); - } + // Re-build the media if the macro has changed from the tagmap. + if (!media && media_definition.fid) { + Drupal.media.filter.ensureSourceMap(); + var source = Drupal.settings.mediaSourceMap[media_definition.fid]; + media = document.createElement(source.tagName); + media.src = source.src; } + + // Apply attributes. + var element = Drupal.media.filter.create_element(media, media_definition); + var markup = Drupal.media.filter.outerHTML(element); + + // Use split and join to replace all instances of macro with markup. + content = content.split(match).join(markup); } } + return content; }, /** - * Replaces the placeholders for html editing with the media tokens to store. - * @param content + * Replaces media elements with tokens. + * + * @param content (string) + * The markup within the wysiwyg instance. */ replacePlaceholderWithToken: function(content) { Drupal.media.filter.ensure_tagmap(); - // Convert all xhtml markup to html for reliable matching/replacing. - content = content.replace(/[\s]\/\>/g, '>'); - // Re-build the macros in case any element has changed in the editor. - $('.media-element', content).each(function(i, element) { - var markup = Drupal.media.filter.outerHTML($(element)); - macro = Drupal.media.filter.create_macro($(element)); + // Rewrite the tagmap in case any of the macros have changed. + Drupal.settings.tagmap = {}; - // Store the macro => html for more efficient rendering in - // replaceTokenWithPlaceholder(). - Drupal.settings.tagmap[macro] = markup; - // Replace the media element with its macro. - content = content.replace(markup, macro); - }); + // Wrap the content to be able to use replaceWith() and html(). + content = $('
').append(content); + var media = $('.media-element', content); - return content; + if (media.length) { + // Replace all media elements with their respective macros. + media.replaceWith(function() { + var el = $(this), + macro = Drupal.media.filter.create_macro(el); + + // Store the markup for more efficient rendering later. + // @see replaceTokenWidthPlaceholder() + Drupal.settings.tagmap[macro] = Drupal.media.filter.outerHTML(el); + + return macro; + }); + } + + return content.html(); }, /** - * Serializes file information as a url-encoded JSON object and stores it as a - * data attribute on the html 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. @@ -80,13 +100,19 @@ * 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. + 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); + // Parse out link wrappers. They will be re-applied when the image is + // rendered on the front-end. + if (element.is('a')) { + element = element.children(); + } + // Move attributes from the file info array to the placeholder element. if (info.attributes) { $.each(Drupal.settings.media.wysiwyg_allowed_attributes, function(i, a) { @@ -95,17 +121,28 @@ } }); delete(info.attributes); + + // Store information to rebuild the element later, if necessary. + Drupal.media.filter.ensureSourceMap(); + Drupal.settings.mediaSourceMap[info.fid] = { + tagName: element[0].tagName, + src: element[0].src + } } - // 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. + // Store the data in the data map. + Drupal.media.filter.ensureDataMap(); + Drupal.settings.mediaDataMap[info.fid] = info; + + // Store the fid in the DOM to retrieve the data from the info map. + element.attr('data-fid', info.fid); + + // Add media-element class so we can find markup element later. var classes = ['media-element']; - if(info.view_mode){ + if (info.view_mode) { classes.push('file-' + info.view_mode.replace(/_/g, '-')); } element.addClass(classes.join(' ')); @@ -131,35 +168,32 @@ * Extract the file info from a WYSIWYG placeholder element as JSON. * * @param element (jQuery object) - * A media element with attached serialized file info. + * A media element with associated file info via a file id (fid). */ extract_file_info: function (element) { - var file_json = $.data(element, 'file_info') || element.data('file_info'), - file_info, - value; + var fid, file_info, value; - try { - file_info = JSON.parse(decodeURIComponent(file_json)); - } - catch (err) { - file_info = null; - } + if (fid = element.data('fid')) { + Drupal.media.filter.ensureDataMap(); - if (file_info) { - file_info.attributes = {}; + if (file_info = Drupal.settings.mediaDataMap[fid]) { + file_info.attributes = {}; - // Extract whitelisted attributes. - $.each(Drupal.settings.media.wysiwyg_allowed_attributes, function(i, a) { - if (value = element.attr(a)) { - file_info.attributes[a] = value; - } - }); - delete(file_info.attributes['data-file_info']); + $.each(Drupal.settings.media.wysiwyg_allowed_attributes, function(i, a) { + if (value = element.attr(a)) { + // Replace " by \" to avoid error with JSON format. + if (typeof value == 'string') { + value = value.replace('"', '\\"'); + } + file_info.attributes[a] = value; + } + }); + } } - return file_info; }, + /** * Gets the HTML content of an element. * @@ -186,10 +220,6 @@ // Store macro/markup in the tagmap. Drupal.media.filter.ensure_tagmap(); - var i = 1; - for (var key in Drupal.settings.tagmap) { - i++; - } Drupal.settings.tagmap[macro] = markup; // Return the html code to insert in an editor and use it with @@ -198,6 +228,22 @@ }, /** + * Ensures the src tracking has been initialized and returns it. + */ + ensureSourceMap: function() { + Drupal.settings.mediaSourceMap = Drupal.settings.mediaSourceMap || {}; + return Drupal.settings.mediaSourceMap; + }, + + /** + * Ensures the data tracking has been initialized and returns it. + */ + ensureDataMap: function() { + Drupal.settings.mediaDataMap = Drupal.settings.mediaDataMap || {}; + return Drupal.settings.mediaDataMap; + }, + + /** * Ensures the tag map has been initialized and returns it. */ ensure_tagmap: function () { @@ -205,4 +251,5 @@ return Drupal.settings.tagmap; } } + })(jQuery); diff --git a/modules/media_wysiwyg/js/wysiwyg-media.js b/modules/media_wysiwyg/js/wysiwyg-media.js index ac5bf4d..af99097 100644 --- a/modules/media_wysiwyg/js/wysiwyg-media.js +++ b/modules/media_wysiwyg/js/wysiwyg-media.js @@ -22,6 +22,7 @@ Drupal.wysiwyg.plugins.media = { isNode: function(node) { return $(node).is('img.media-element'); }, + /** * Execute the button. *