diff --git a/js/media.filter.js b/js/media.filter.js new file mode 100644 index 0000000..5e60007 --- /dev/null +++ b/js/media.filter.js @@ -0,0 +1,228 @@ +/** + * @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(), + 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(). + var matches = content.match(/]+class=[\'"]([^"']+ )?media-element[^>]*>/gi); + if (matches) { + for (i = 0; i < matches.length; i++) { + markup = matches[i]; + macro = Drupal.media.filter.create_macro($(markup)); + tagmap[macro] = markup; + content = content.replace(markup, macro); + } + } + 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 0c5bdd3..892ecf7 100644 --- a/js/wysiwyg-media.js +++ b/js/wysiwyg-media.js @@ -12,14 +12,6 @@ Drupal.media = Drupal.media || {}; Drupal.wysiwyg.plugins.media = { /** - * Initializes the tag map. - */ - initializeTagMap: function () { - if (typeof Drupal.settings.tagmap == 'undefined') { - Drupal.settings.tagmap = { }; - } - }, - /** * Execute the button. * @TODO: Debug calls from this are never called. What's its function? */ @@ -45,50 +37,38 @@ Drupal.wysiwyg.plugins.media = { return; }, - insertMediaFile: function (mediaFile, viewMode, formattedMedia, options, wysiwygInstance) { + insertMediaFile: function (mediaFile, formattedMedia, ckeditorInstance) { + + // Customization of Drupal.media.filter.registerNewElement(). + var element = Drupal.media.filter.create_element(formattedMedia.html, { + fid: mediaFile.fid, + view_mode: formattedMedia.type, + attributes: formattedMedia.options + }); + + this.forceAttributesIntoClass( + element, + mediaFile.fid, + formattedMedia.type, + formattedMedia.options + ); + + var markup = Drupal.media.filter.outerHTML(element), + macro = Drupal.media.filter.create_macro(element); - this.initializeTagMap(); // @TODO: the folks @ ckeditor have told us that there is no way // to reliably add wrapper divs via normal HTML. // There is some method of adding a "fake element" // But until then, we're just going to embed to img. // This is pretty hacked for now. - // - var imgElement = $(this.stripDivs(formattedMedia)); - this.addImageAttributes(imgElement, mediaFile.fid, viewMode, options); - - var toInsert = this.outerHTML(imgElement); - // Create an inline tag - var inlineTag = Drupal.wysiwyg.plugins.media.createTag(imgElement); - // Add it to the tag map in case the user switches input formats - Drupal.settings.tagmap[inlineTag] = toInsert; - wysiwygInstance.insert(toInsert); - }, + markup = this.stripDivs(markup); - /** - * Gets the HTML content of an element - * - * @param jQuery element - */ - outerHTML: function (element) { - return $('
').append( element.eq(0).clone() ).html(); - }, + // Store macro/markup pair in the tagmap. + Drupal.media.filter.tagmap(); + Drupal.settings.tagmap[macro] = markup; - addImageAttributes: function (imgElement, fid, view_mode, additional) { - // imgElement.attr('fid', fid); - // imgElement.attr('view_mode', view_mode); - // Class so we can find this image later. - imgElement.addClass('media-image'); - this.forceAttributesIntoClass(imgElement, fid, view_mode, additional); - if (additional) { - for (k in additional) { - if (additional.hasOwnProperty(k)) { - if (k === 'attr') { - imgElement.attr(k, additional[k]); - } - } - } - } + // Ensure this also registered in the tag map. + ckeditorInstance.insertHtml(markup); }, /** @@ -108,167 +88,14 @@ Drupal.wysiwyg.plugins.media = { // Check to see if the image tag has divs to strip var stripped = null; if ($(formattedMedia).is('img')) { - stripped = this.outerHTML($(formattedMedia)); + stripped = Drupal.media.filter.outerHTML($(formattedMedia)); } else { - stripped = this.outerHTML($('img', $(formattedMedia))); + stripped = Drupal.media.filter.outerHTML($('img', $(formattedMedia))); } - // This will fail if we pass the img tag without anything wrapping it, like we do when re-enabling WYSIWYG + // This will fail if we pass the img tag without anything wrapping it, like we do when re-enabling ckeditor return stripped; }, - /** - * Attach function, called when a rich text editor loads. - * This finds all [[tags]] and replaces them with the html - * that needs to show in the editor. - * - */ - attach: function (content, settings, instanceId) { - var matches = content.match(/\[\[.*?\]\]/g); - this.initializeTagMap(); - var tagmap = Drupal.settings.tagmap; - if (matches) { - var inlineTag = ""; - for (i = 0; i < matches.length; i++) { - inlineTag = matches[i]; - if (tagmap[inlineTag]) { - // This probably needs some work... - // We need to somehow get the fid propogated here. - // We really want to - var tagContent = tagmap[inlineTag]; - var mediaMarkup = this.stripDivs(tagContent); // THis is
.. - - var _tag = inlineTag; - _tag = _tag.replace('[[',''); - _tag = _tag.replace(']]',''); - try { - mediaObj = JSON.parse(_tag); - } - catch(err) { - mediaObj = null; - } - if(mediaObj) { - var imgElement = $(mediaMarkup); - this.addImageAttributes(imgElement, mediaObj.fid, mediaObj.view_mode); - var toInsert = this.outerHTML(imgElement); - content = content.replace(inlineTag, toInsert); - } - } - else { - debug.debug("Could not find content for " + inlineTag); - } - } - } - return content; - }, - - /** - * Detach function, called when a rich text editor detaches - */ - detach: function (content, settings, instanceId) { - // Replace all Media placeholder images with the appropriate inline json - // string. Using a regular expression instead of jQuery manipulation to - // prevent