(function($) { CKEDITOR.disableAutoInline = true; // Exclude every id starting with 'cke_' in ajax_html_ids during AJAX requests. Drupal.wysiwyg.excludeIdSelectors.wysiwyg_ckeditor = ['[id^="cke_"]']; // Keeps track of private instance data. var instanceMap; /** * Initialize the editor library. * * This method is called once the first time a library is needed. If new * WYSIWYG fields are added later, update() will be called instead. * * @param settings * An object containing editor settings for each input format. * @param pluginInfo * An object containing global plugin configuration. */ Drupal.wysiwyg.editor.init.ckeditor = function(settings, pluginInfo) { instanceMap = {}; // Manually set the cache-busting string to the same value as Drupal. if (Drupal.settings.wysiwyg.ckeditor.hasOwnProperty('timestamp')) { CKEDITOR.timestamp = Drupal.settings.wysiwyg.ckeditor.timestamp; } // Nothing to do here other than register new plugins etc. Drupal.wysiwyg.editor.update.ckeditor(settings, pluginInfo); }; /** * Update the editor library when new settings are available. * * This method is called instead of init() when at least one new WYSIWYG field * has been added to the document and the library has already been initialized. * * $param settings * An object containing editor settings for each input format. * $param pluginInfo * An object containing global plugin configuration. */ Drupal.wysiwyg.editor.update.ckeditor = function(settings, pluginInfo) { // Register native external plugins. // Array syntax required; 'native' is a predefined token in JavaScript. for (var pluginId in pluginInfo['native']) { if (pluginInfo['native'].hasOwnProperty(pluginId) && (!CKEDITOR.plugins.externals || !CKEDITOR.plugins.externals[pluginId])) { var plugin = pluginInfo['native'][pluginId]; CKEDITOR.plugins.addExternal(pluginId, plugin.path, plugin.fileName); } } // Build and register Drupal plugin wrappers. for (var pluginId in pluginInfo.drupal) { if (pluginInfo.drupal.hasOwnProperty(pluginId) && (!CKEDITOR.plugins.registered || !CKEDITOR.plugins.registered[pluginId])) { Drupal.wysiwyg.editor.instance.ckeditor.addPlugin(pluginId, pluginInfo.drupal[pluginId]); } } // Register Font styles (versions 3.2.1 and above). for (var format in settings) { if (settings[format].stylesSet && (!CKEDITOR.stylesSet || !CKEDITOR.stylesSet.registered[format])) { CKEDITOR.stylesSet.add(format, settings[format].stylesSet); } } }; /** * Attach this editor to a target element. */ Drupal.wysiwyg.editor.attach.ckeditor = function(context, params, settings) { // Apply editor instance settings. CKEDITOR.config.customConfig = ''; if (!settings.height) { settings.height = $('#' + params.field).height(); } // Handler for any change-related event. function changed(ev) { instanceMap[ev.editor.name].contentsChanged(); } settings.on = { // Versions 4.x has a change event, 3.x does not. change: function (ev) { changed(ev); }, contentDom: function (ev) { var version_parts = CKEDITOR.version.split('.'); if (version_parts[0] === '3' || (version_parts[0] === '4' && parseInt(version_parts[1], 10) < 2)) { ev.editor.on('key', function (ev) { // Do not capture modifiers. if (ev.data.ctrlKey || ev.data.metaKey) return; var keyCode = ev.data.keyCode; // Filter out movement keys and related. if (keyCode == 8 || keyCode == 13 || keyCode == 32 || (keyCode >= 46 && keyCode <= 90) || (keyCode >= 96 && keyCode <= 111) || (keyCode >= 186 && keyCode <= 222) || keyCode == 229) { changed(ev); } }); ev.editor.on('paste', changed); ev.editor.on('saveSnapshot', function (ev) { if (instanceMap[ev.editor.name].firstSaveSnapshot) { // The first save snapshot event is triggered when the editor is // focused and before anything has changed. instanceMap[ev.editor.name].firstSaveSnapshot = false; return; } changed(ev); }); } }, instanceReady: function(ev) { var editor = ev.editor; // Get a list of block, list and table tags from CKEditor's XHTML DTD. // @see http://docs.cksource.com/CKEditor_3.x/Developers_Guide/Output_Formatting. var dtd = CKEDITOR.dtd; var tags = CKEDITOR.tools.extend({}, dtd.$block, dtd.$listItem, dtd.$tableContent); // Set source formatting rules for each listed tag except
. // Linebreaks can be inserted before or after opening and closing tags. if (settings.simple_source_formatting) { // Mimic FCKeditor output, by breaking lines between tags. for (var tag in tags) { if (tag == 'pre') { continue; } this.dataProcessor.writer.setRules(tag, { indent: true, breakBeforeOpen: true, breakAfterOpen: false, breakBeforeClose: false, breakAfterClose: true }); } } else { // CKEditor adds default formatting to
, so we want to remove that // here too. tags.br = 1; // No indents or linebreaks; for (var tag in tags) { if (tag == 'pre') { continue; } this.dataProcessor.writer.setRules(tag, { indent: false, breakBeforeOpen: false, breakAfterOpen: false, breakBeforeClose: false, breakAfterClose: false }); } } }, pluginsLoaded: function(ev) { var wysiwygInstance = instanceMap[this.name]; var enabledPlugins = wysiwygInstance.pluginInfo.instances.drupal; // Override the conversion methods to let Drupal plugins modify the data. var editor = ev.editor; if (editor.dataProcessor && enabledPlugins) { editor.dataProcessor.toHtml = CKEDITOR.tools.override(editor.dataProcessor.toHtml, function(originalToHtml) { // Convert raw data for display in WYSIWYG mode. return function(data, fixForBody) { for (var plugin in enabledPlugins) { if (typeof Drupal.wysiwyg.plugins[plugin].attach == 'function') { data = Drupal.wysiwyg.plugins[plugin].attach(data, wysiwygInstance.pluginInfo.global.drupal[plugin], editor.name); data = wysiwygInstance.prepareContent(data); } } return originalToHtml.call(this, data, fixForBody); }; }); editor.dataProcessor.toDataFormat = CKEDITOR.tools.override(editor.dataProcessor.toDataFormat, function(originalToDataFormat) { // Convert WYSIWYG mode content to raw data. return function(data, fixForBody) { data = originalToDataFormat.call(this, data, fixForBody); for (var plugin in enabledPlugins) { if (typeof Drupal.wysiwyg.plugins[plugin].detach == 'function') { data = Drupal.wysiwyg.plugins[plugin].detach(data, wysiwygInstance.pluginInfo.global.drupal[plugin], editor.name); } } return data; }; }); } }, selectionChange: function (event) { var wysiwygInstance = instanceMap[this.name]; var enabledPlugins = wysiwygInstance.pluginInfo.instances.drupal; for (var name in enabledPlugins) { var plugin = Drupal.wysiwyg.plugins[name]; if ($.isFunction(plugin.isNode)) { var node = event.data.selection.getSelectedElement(); var state = plugin.isNode(node ? node.$ : null) ? CKEDITOR.TRISTATE_ON : CKEDITOR.TRISTATE_OFF; event.editor.getCommand(name).setState(state); } } }, focus: function(ev) { Drupal.wysiwyg.activeId = ev.editor.name; }, afterCommandExec: function(ev) { // Fix Drupal toolbar obscuring editor toolbar in fullscreen mode. if (ev.data.name != 'maximize') { return; } if (ev.data.command.state == CKEDITOR.TRISTATE_ON) { Drupal.wysiwyg.utilities.onFullscreenEnter(); } else { Drupal.wysiwyg.utilities.onFullscreenExit(); } }, destroy: function (event) { // Free our reference to the private instance to not risk memory leaks. delete instanceMap[this.name]; } }; instanceMap[params.field] = this; // Attach editor. var editorInstance = CKEDITOR.replace(params.field, settings); }; /** * Detach a single editor instance. */ Drupal.wysiwyg.editor.detach.ckeditor = function (context, params, trigger) { var method = (trigger == 'serialize') ? 'updateElement' : 'destroy'; var instance = CKEDITOR.instances[params.field]; if (!instance) { return; } instance[method](); }; Drupal.wysiwyg.editor.instance.ckeditor = { // Flag indicating if the first save snapshot event has fired. firstSaveSnapshot: true, addPlugin: function (pluginName, pluginSettings) { CKEDITOR.plugins.add(pluginName, { // Wrap Drupal plugin in a proxy plugin. init: function(editor) { if (pluginSettings.css) { editor.on('mode', function(ev) { if (ev.editor.mode == 'wysiwyg') { // Inject CSS files directly into the editing area head tag. var iframe = $('#cke_contents_' + ev.editor.name + ' iframe, #' + ev.editor.id + '_contents iframe'); $('head', iframe.eq(0).contents()).append(''); } }); } if (typeof Drupal.wysiwyg.plugins[pluginName].invoke == 'function') { var pluginCommand = { exec: function (editor) { var data = { format: 'html', node: null, content: '' }; var selection = editor.getSelection(); if (selection) { data.node = selection.getSelectedElement(); if (data.node) { data.node = data.node.$; } if (selection.getType() == CKEDITOR.SELECTION_TEXT) { if (selection.getSelectedText) { data.content = selection.getSelectedText(); } else { // Pre v3.6.1. if (CKEDITOR.env.ie) { data.content = selection.getNative().createRange().text; } else { data.content = selection.getNative().toString(); } } } else if (data.node) { // content is supposed to contain the "outerHTML". data.content = data.node.parentNode.innerHTML; } } Drupal.wysiwyg.plugins[pluginName].invoke(data, pluginSettings, editor.name); } }; editor.addCommand(pluginName, pluginCommand); } editor.ui.addButton(pluginName, { label: pluginSettings.title, command: pluginName, icon: pluginSettings.icon }); // @todo Add button state handling. } }); }, prepareContent: function(content) { // @todo Don't know if we need this yet. return content; }, insert: function(content) { content = this.prepareContent(content); if (CKEDITOR.version.split('.')[0] === '3' && (CKEDITOR.env.webkit || CKEDITOR.env.chrome || CKEDITOR.env.opera || CKEDITOR.env.safari)) { // Works around a WebKit bug which removes wrapper elements. // @see https://drupal.org/node/1927968 var tmp = new CKEDITOR.dom.element('div'), children, skip = 0, item; tmp.setHtml(content); children = tmp.getChildren(); skip = 0; while (children.count() > skip) { item = children.getItem(skip); switch(item.type) { case 1: CKEDITOR.instances[this.field].insertElement(item); break; case 3: CKEDITOR.instances[this.field].insertText(item.getText()); skip++; break; case 8: CKEDITOR.instances[this.field].insertHtml(item.getOuterHtml()); skip++; break; default: skip++; } } } else { CKEDITOR.instances[this.field].insertHtml(content); } }, setContent: function (content) { CKEDITOR.instances[this.field].setData(content); }, getContent: function () { return CKEDITOR.instances[this.field].getData(); }, isFullscreen: function () { var cmd = CKEDITOR.instances[this.field].commands.maximize; return !!(cmd && cmd.state == CKEDITOR.TRISTATE_ON); } }; })(jQuery); ; (function ($) { /** * Wysiwyg plugin button implementation for NodeEmbed plugin. */ Drupal.wysiwyg.plugins.node_embed = { /** * Return whether the passed node belongs to this plugin. * * @param node * The currently focused DOM element in the editor content. */ isNode: function(node) { return ($(node).is('img.wysiwyg-node_embed')); }, /** * Execute the button. * * @param data * An object containing data about the current selection: * - format: 'html' when the passed data is HTML content, 'text' when the * passed data is plain-text content. * - node: When 'format' is 'html', the focused DOM element in the editor. * - content: The textual representation of the focused/selected editor * content. * @param settings * The plugin settings, as provided in the plugin's PHP include file. * @param instanceId * The ID of the current editor instance. */ invoke: function(data, settings, instanceId) { // Show the node selection Dialog. var iframeSrc = Drupal.settings.basePath + 'ckeditor-node-embed'; var dialogMarkup = '' + '' + '' + ''; var dialog = $(dialogMarkup).dialog({ autoOpen: false, height: 470, width: 650, modal: true, dialogClass: 'node_embed_dialog' }); $(dialog).bind('dialogclose', function(event, ui) { $(this).remove(); }); $('#buttonEmbedFromDialog').click(function(e) { // Set or updated whenever a node is selected. var node_id = window.currentActiveNid; if (node_id != null && node_id != "" ) { dialog.editor_content = '[[nid:' + node_id + ']]'; } if (window.currentActiveNid && window.currentActiveNid != "") { var edit_content = "[[nid:" + window.currentActiveNid + "]]"; if (data.format == 'html') { var content = ' '; } else { var content = edit_content; } // Write the content to the editor. if (typeof content != 'undefined') { Drupal.wysiwyg.instances[instanceId].insert(content); } } $(dialog).dialog('close'); }); $('#buttonCancelDialog').click(function(e) { $(dialog).dialog('close'); }); $(dialog).dialog('open'); } }; })(jQuery); ; (function ($) { // @todo Array syntax required; 'break' is a predefined token in JavaScript. Drupal.wysiwyg.plugins['break'] = { /** * Return whether the passed node belongs to this plugin. */ isNode: function(node) { return ($(node).is('img.wysiwyg-break')); }, /** * Execute the button. */ invoke: function(data, settings, instanceId) { if (data.format == 'html') { // Prevent duplicating a teaser break. if ($(data.node).is('img.wysiwyg-break')) { return; } var content = this._getPlaceholder(settings); } else { // Prevent duplicating a teaser break. // @todo data.content is the selection only; needs access to complete content. if (data.content.match(//)) { return; } var content = ''; } if (typeof content != 'undefined') { Drupal.wysiwyg.instances[instanceId].insert(content); } }, /** * Replace all tags with images. */ attach: function(content, settings, instanceId) { content = content.replace(//g, this._getPlaceholder(settings)); return content; }, /** * Replace images with tags in content upon detaching editor. */ detach: function(content, settings, instanceId) { var $content = $('' + content + ''); // No .outerHTML() in jQuery :( // #404532: document.createComment() required or IE will strip the comment. // #474908: IE 8 breaks when using jQuery methods to replace the elements. // @todo Add a generic implementation for all Drupal plugins for this. $.each($('img.wysiwyg-break', $content), function (i, elem) { elem.parentNode.insertBefore(document.createComment('break'), elem); elem.parentNode.removeChild(elem); }); return $content.html(); }, /** * Helper function to return a HTML placeholder. */ _getPlaceholder: function (settings) { return ''; } }; })(jQuery); ; (function ($) { Drupal.behaviors.textarea = { attach: function (context, settings) { $('.form-textarea-wrapper.resizable', context).once('textarea', function () { var staticOffset = null; var textarea = $(this).addClass('resizable-textarea').find('textarea'); var grippie = $('').mousedown(startDrag); grippie.insertAfter(textarea); function startDrag(e) { staticOffset = textarea.height() - e.pageY; textarea.css('opacity', 0.25); $(document).mousemove(performDrag).mouseup(endDrag); return false; } function performDrag(e) { textarea.height(Math.max(32, staticOffset + e.pageY) + 'px'); return false; } function endDrag(e) { $(document).unbind('mousemove', performDrag).unbind('mouseup', endDrag); textarea.css('opacity', 1); } }); } }; })(jQuery); ; /** * @file: Popup dialog interfaces for the media project. * * Drupal.media.popups.mediaBrowser * Launches the media browser which allows users to pick a piece of media. * * Drupal.media.popups.mediaStyleSelector * Launches the style selection form where the user can choose what * format/style they want their media in. */ (function ($) { namespace('Drupal.media.popups'); /** * Media browser popup. Creates a media browser dialog. * * @param {function} * onSelect Callback for when dialog is closed, received (Array media, Object * extra); * @param {Object} * globalOptions Global options that will get passed upon initialization of * the browser. @see Drupal.media.popups.mediaBrowser.getDefaults(); * @param {Object} * pluginOptions Options for specific plugins. These are passed to the plugin * upon initialization. If a function is passed here as a callback, it is * obviously not passed, but is accessible to the plugin in * Drupal.settings.variables. Example: * pluginOptions = {library: {url_include_patterns:'/foo/bar'}}; * @param {Object} * widgetOptions Options controlling the appearance and behavior of the modal * dialog. @see Drupal.media.popups.mediaBrowser.getDefaults(); */ Drupal.media.popups.mediaBrowser = function (onSelect, globalOptions, pluginOptions, widgetOptions) { // Get default dialog options. var options = Drupal.media.popups.mediaBrowser.getDefaults(); // Add global, plugin and widget options. options.global = $.extend({}, options.global, globalOptions); options.plugins = pluginOptions; options.widget = $.extend({}, options.widget, widgetOptions); // Find the URL of the modal iFrame. var browserSrc = options.widget.src; if ($.isArray(browserSrc) && browserSrc.length) { browserSrc = browserSrc[browserSrc.length - 1]; } // Create an array of parameters to send along to the iFrame. var params = {}; // Add global field widget settings and plugin information. $.extend(params, options.global); params.plugins = options.plugins; // Append the list of parameters to the iFrame URL as query parameters. browserSrc += '&' + $.param(params); // Create an iFrame with the iFrame URL. var mediaIframe = Drupal.media.popups.getPopupIframe(browserSrc, 'mediaBrowser'); // Attach an onLoad event. mediaIframe.bind('load', options, options.widget.onLoad); // Create an array of Dialog options. var dialogOptions = options.dialog; // Setup the dialog buttons. var ok = Drupal.t('OK'); var notSelected = Drupal.t('You have not selected anything!'); dialogOptions.buttons[ok] = function () { // Find the current file selection. var selected = this.contentWindow.Drupal.media.browser.selectedMedia; // Alert the user if a selection has yet to be made. if (selected.length < 1) { alert(notSelected); return; } // Select the file. onSelect(selected); // Close the dialog. $(this).dialog('close'); }; // Create a jQuery UI dialog with the given options. var dialog = mediaIframe.dialog(dialogOptions); // Allow the dialog to react to re-sizing, scrolling, etc. Drupal.media.popups.sizeDialog(dialog); Drupal.media.popups.resizeDialog(dialog); Drupal.media.popups.scrollDialog(dialog); Drupal.media.popups.overlayDisplace(dialog.parents(".ui-dialog")); return mediaIframe; }; /** * Retrieves a list of default settings for the media browser. * * @return * An array of default settings. */ Drupal.media.popups.mediaBrowser.getDefaults = function () { return { global: { types: [], // Types to allow, defaults to all. enabledPlugins: [] // If provided, a list of plugins which should be enabled. }, widget: { // Settings for the actual iFrame which is launched. src: Drupal.settings.media.browserUrl, // Src of the media browser (if you want to totally override it) onLoad: Drupal.media.popups.mediaBrowser.mediaBrowserOnLoad // Onload function when iFrame loads. }, dialog: Drupal.media.popups.getDialogOptions() }; }; /** * Sets up the iFrame buttons. */ Drupal.media.popups.mediaBrowser.mediaBrowserOnLoad = function (e) { var options = e.data; // Ensure that the iFrame is defined. if (typeof this.contentWindow.Drupal.media === 'undefined' || typeof this.contentWindow.Drupal.media.browser === 'undefined') { return; } // Check if a selection has been made and press the 'ok' button. if (this.contentWindow.Drupal.media.browser.selectedMedia.length > 0) { var ok = Drupal.t('OK'); var ok_func = $(this).dialog('option', 'buttons')[ok]; ok_func.call(this); return; } }; /** * Finalizes the selection of a file. * * Alerts the user if a selection has yet to be made, triggers the file * selection and closes the modal dialog. */ Drupal.media.popups.mediaBrowser.finalizeSelection = function () { // Find the current file selection. var selected = this.contentWindow.Drupal.media.browser.selectedMedia; // Alert the user if a selection has yet to be made. if (selected.length < 1) { alert(notSelected); return; } // Select the file. onSelect(selected); // Close the dialog. $(this).dialog('close'); }; /** * Style chooser Popup. Creates a dialog for a user to choose a media style. * * @param mediaFile * The mediaFile you are requesting this formatting form for. * @todo: should this be fid? That's actually all we need now. * * @param Function * onSubmit Function to be called when the user chooses a media style. Takes * one parameter (Object formattedMedia). * * @param Object * options Options for the mediaStyleChooser dialog. */ Drupal.media.popups.mediaStyleSelector = function (mediaFile, onSelect, options) { var defaults = Drupal.media.popups.mediaStyleSelector.getDefaults(); // @todo: remove this awful hack :( if (typeof defaults.src === 'string' ) { defaults.src = defaults.src.replace('-media_id-', mediaFile.fid) + '&fields=' + encodeURIComponent(JSON.stringify(mediaFile.fields)); } else { var src = defaults.src.shift(); defaults.src.unshift(src); defaults.src = src.replace('-media_id-', mediaFile.fid) + '&fields=' + encodeURIComponent(JSON.stringify(mediaFile.fields)); } options = $.extend({}, defaults, options); // Create an iFrame with the iFrame URL. var mediaIframe = Drupal.media.popups.getPopupIframe(options.src, 'mediaStyleSelector'); // Attach an onLoad event. mediaIframe.bind('load', options, options.onLoad); // Create an array of Dialog options. var dialogOptions = Drupal.media.popups.getDialogOptions(); // Setup the dialog buttons. var ok = Drupal.t('OK'); var notSelected = Drupal.t('Very sorry, there was an unknown error embedding media.'); dialogOptions.buttons[ok] = function () { // Find the current file selection. var formattedMedia = this.contentWindow.Drupal.media.formatForm.getFormattedMedia(); formattedMedia.options = $.extend({}, mediaFile.attributes, formattedMedia.options); // Alert the user if a selection has yet to be made. if (!formattedMedia) { alert(notSelected); return; } // Select the file. onSelect(formattedMedia); // Close the dialog. $(this).dialog('close'); }; // Create a jQuery UI dialog with the given options. var dialog = mediaIframe.dialog(dialogOptions); // Allow the dialog to react to re-sizing, scrolling, etc. Drupal.media.popups.sizeDialog(dialog); Drupal.media.popups.resizeDialog(dialog); Drupal.media.popups.scrollDialog(dialog); Drupal.media.popups.overlayDisplace(dialog.parents(".ui-dialog")); return mediaIframe; }; Drupal.media.popups.mediaStyleSelector.mediaBrowserOnLoad = function (e) { }; Drupal.media.popups.mediaStyleSelector.getDefaults = function () { return { src: Drupal.settings.media.styleSelectorUrl, onLoad: Drupal.media.popups.mediaStyleSelector.mediaBrowserOnLoad }; }; /** * Style chooser Popup. Creates a dialog for a user to choose a media style. * * @param mediaFile * The mediaFile you are requesting this formatting form for. * @todo: should this be fid? That's actually all we need now. * * @param Function * onSubmit Function to be called when the user chooses a media style. Takes * one parameter (Object formattedMedia). * * @param Object * options Options for the mediaStyleChooser dialog. */ Drupal.media.popups.mediaFieldEditor = function (fid, onSelect, options) { var defaults = Drupal.media.popups.mediaFieldEditor.getDefaults(); // @todo: remove this awful hack :( defaults.src = defaults.src.replace('-media_id-', fid); options = $.extend({}, defaults, options); // Create an iFrame with the iFrame URL. var mediaIframe = Drupal.media.popups.getPopupIframe(options.src, 'mediaFieldEditor'); // Attach an onLoad event. mediaIframe.bind('load', options, options.onLoad); // Create an array of Dialog options. var dialogOptions = Drupal.media.popups.getDialogOptions(); // Setup the dialog buttons. var ok = Drupal.t('OK'); var notSelected = Drupal.t('Very sorry, there was an unknown error embedding media.'); dialogOptions.buttons[ok] = function () { // Find the current file selection. var formattedMedia = this.contentWindow.Drupal.media.formatForm.getFormattedMedia(); // Alert the user if a selection has yet to be made. if (!formattedMedia) { alert(notSelected); return; } // Select the file. onSelect(formattedMedia); // Close the dialog. $(this).dialog('close'); }; // Create a jQuery UI dialog with the given options. var dialog = mediaIframe.dialog(dialogOptions); // Allow the dialog to react to re-sizing, scrolling, etc. Drupal.media.popups.sizeDialog(dialog); Drupal.media.popups.resizeDialog(dialog); Drupal.media.popups.scrollDialog(dialog); Drupal.media.popups.overlayDisplace(dialog); return mediaIframe; }; Drupal.media.popups.mediaFieldEditor.mediaBrowserOnLoad = function (e) { }; Drupal.media.popups.mediaFieldEditor.getDefaults = function () { return { // @todo: do this for real src: '/media/-media_id-/edit?render=media-popup', onLoad: Drupal.media.popups.mediaFieldEditor.mediaBrowserOnLoad }; }; /** * Generic functions to both the media-browser and style selector. */ /** * Returns the commonly used options for the dialog. */ Drupal.media.popups.getDialogOptions = function () { return { title: Drupal.t('Media browser'), buttons: {}, dialogClass: Drupal.settings.media.dialogOptions.dialogclass, modal: Drupal.settings.media.dialogOptions.modal, draggable: Drupal.settings.media.dialogOptions.draggable, resizable: Drupal.settings.media.dialogOptions.resizable, minWidth: Drupal.settings.media.dialogOptions.minwidth, width: Drupal.settings.media.dialogOptions.width, height: Drupal.settings.media.dialogOptions.height, position: Drupal.settings.media.dialogOptions.position, overlay: { backgroundColor: Drupal.settings.media.dialogOptions.overlay.backgroundcolor, opacity: Drupal.settings.media.dialogOptions.overlay.opacity }, zIndex: Drupal.settings.media.dialogOptions.zindex, close: function (event, ui) { var elem = $(event.target); var id = elem.attr('id'); if(id == 'mediaStyleSelector') { $(this).dialog("destroy"); $('#mediaStyleSelector').remove(); } else { $(this).dialog("destroy"); $('#mediaBrowser').remove(); } } }; }; /** * Get an iframe to serve as the dialog's contents. Common to both plugins. */ Drupal.media.popups.getPopupIframe = function (src, id, options) { var defaults = {width: '100%', scrolling: 'auto'}; var options = $.extend({}, defaults, options); return $('') .attr('src', src) .attr('width', options.width) .attr('id', id) .attr('scrolling', options.scrolling); }; Drupal.media.popups.overlayDisplace = function (dialog) { if (parent.window.Drupal.overlay && jQuery.isFunction(parent.window.Drupal.overlay.getDisplacement)) { var overlayDisplace = parent.window.Drupal.overlay.getDisplacement('top'); if (dialog.offset().top < overlayDisplace) { dialog.css('top', overlayDisplace); } } } /** * Size the dialog when it is first loaded and keep it centered when scrolling. * * @param jQuery dialogElement * The element which has .dialog() attached to it. */ Drupal.media.popups.sizeDialog = function (dialogElement) { if (!dialogElement.is(':visible')) { return; } var windowWidth = $(window).width(); var dialogWidth = windowWidth * 0.8; var windowHeight = $(window).height(); var dialogHeight = windowHeight * 0.8; dialogElement.dialog("option", "width", dialogWidth); dialogElement.dialog("option", "height", dialogHeight); dialogElement.dialog("option", "position", 'center'); $('.media-modal-frame').width('100%'); } /** * Resize the dialog when the window changes. * * @param jQuery dialogElement * The element which has .dialog() attached to it. */ Drupal.media.popups.resizeDialog = function (dialogElement) { $(window).resize(function() { Drupal.media.popups.sizeDialog(dialogElement); }); } /** * Keeps the dialog centered when the window is scrolled. * * @param jQuery dialogElement * The element which has .dialog() attached to it. */ Drupal.media.popups.scrollDialog = function (dialogElement) { // Keep the dialog window centered when scrolling. $(window).scroll(function() { if (!dialogElement.is(':visible')) { return; } dialogElement.dialog("option", "position", 'center'); }); } })(jQuery); ; (function ($) { /** * Automatically display the guidelines of the selected text format. */ Drupal.behaviors.filterGuidelines = { attach: function (context) { $('.filter-guidelines', context).once('filter-guidelines') .find(':header').hide() .closest('.filter-wrapper').find('select.filter-list') .bind('change', function () { $(this).closest('.filter-wrapper') .find('.filter-guidelines-item').hide() .siblings('.filter-guidelines-' + this.value).show(); }) .change(); } }; })(jQuery); ; /** * @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) { Drupal.media.filter.ensure_tagmap(); var matches = content.match(/\[\[.*?\]\]/g); if (matches) { for (var i = 0; i < matches.length; i++) { var match = matches[i]; if (match.indexOf('"type":"media"') == -1) { continue; } // 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; } // Re-build the media if the macro has changed from the tagmap. if (!media && media_definition.fid) { Drupal.media.filter.ensureSourceMap(); var source; if (source = Drupal.settings.mediaSourceMap[media_definition.fid]) { media = document.createElement(source.tagName); media.src = source.src; media.innerHTML = source.innerHTML; } else { // If the media element can't be found, leave it in to be resolved // by the user later. continue; } } // 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; }, /** * Returns alt and title field attribute data from the corresponding fields. * * Specifically looks for file_entity module's file_image_alt_text and * file_image_title_text fields as those are by default used to store * override values for image alt and title attributes. * * @param options (array) * Options passed through a popup form submission. * @param includeFieldID (bool) * If set, the returned object will have extra keys with the IDs of the * found fields. * * If the alt or title fields were not found, their keys will be excluded * from the returned array. * * @return * An object with the following keys: * - alt: The value of the alt field. * - altField: The id of the alt field. * - title: The value of the title field. * - titleField: The id of the title field. */ parseAttributeFields: function(options, includeFieldID) { var attributes = {}; for (var field in options) { // If the field is set to false, use an empty string for output. options[field] = options[field] === false ? '' : options[field]; //if (field.match(/^field_file_image_alt_text/)) { if (field.match(new RegExp('^' + Drupal.settings.media.img_alt_field))) { attributes.alt = options[field]; if (includeFieldID) { attributes.altField = field; } } //if (field.match(/^field_file_image_title_text/)) { if (field.match(new RegExp('^' + Drupal.settings.media.img_title_field))) { attributes.title = options[field]; if (includeFieldID) { attributes.titleField = field; } } } return attributes; }, /** * Ensures changes made to fielded attributes are done on the fields too. * * This should be called when creating a macro tag from a placeholder. * * Changed made to attributes represented by fields are synced back to the * corresponding fields, if they exist. The alt/title attribute * values encoded in the macro will override the alt/title field values (set * in the Media dialog) during rendering of both WYSIWYG placeholders and * the final file entity on the server. Syncing makes changes applied to a * placeholder's alt/title attribute using native WYSIWYG tools visible in * the fields shown in the Media dialog. * * The reverse should be done when creating a placeholder from a macro tag * so changes made in the Media dialog are reflected in the placeholder's * alt and title attributes or the values there become stale and the change * appears uneffective. * * @param file_info (object) * A JSON decoded object of the file being inserted/updated. */ syncAttributesToFields: function(file_info) { if (!file_info) { file_info = {}; } if (!file_info.attributes) { file_info.attributes = {}; } if (!file_info.fields) { file_info.fields = {}; } var fields = Drupal.media.filter.parseAttributeFields(file_info.fields, true); // If the title attribute has changed, ensure the title field is updated. var titleAttr = file_info.attributes.title || false; if (fields.titleField && (titleAttr !== fields.title)) { file_info.fields[fields.titleField] = titleAttr; } // If the alt attribute has changed, ensure the alt field is updated. var altAttr = file_info.attributes.alt || false; if (fields.altField && (altAttr !== fields.alt)) { file_info.fields[fields.altField] = altAttr; } return file_info; }, /** * Replaces media elements with tokens. * * @param content (string) * The markup within the wysiwyg instance. */ replacePlaceholderWithToken: function(content) { Drupal.media.filter.ensure_tagmap(); // Locate and process all the media placeholders in the WYSIWYG content. var contentElements = $(''); // TODO: once baseline jQuery is 1.8+, switch to using $.parseHTML(content) contentElements.get(0).innerHTML = content; var mediaElements = contentElements.find('.media-element'); if (mediaElements) { $(mediaElements).each(function (i) { // Attempt to derive a JSON macro representation of the media placeholder. // Note: Drupal 7 ships with JQuery 1.4.4, which allows $(this).attr('outerHTML') to retrieve the eement's HTML, // but many sites use JQuery update to increate this to 1.6+, which insists on $(this).prop('outerHTML). // Until the minimum jQuery is >= 1.6, we need to do this the old-school way. // See http://stackoverflow.com/questions/2419749/get-selected-elements-outer-html var markup = $(this).get(0).outerHTML; if (markup === undefined) { // Browser does not support outerHTML DOM property. Use the more expensive clone method instead. markup = $(this).clone().wrap('').parent().html(); } var macro = Drupal.media.filter.create_macro($(markup)); if (macro) { // Replace the placeholder with the macro in the parsed content. // (Can't just replace the string section, because the outerHTML may be subtly different, // depending on the browser. Parsing tends to convert to , for instance.) Drupal.settings.tagmap[macro] = markup; $(this).replaceWith(macro); } }); content = $(contentElements).html(); } return content; }, /** * 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); // Parse out link wrappers. They will be re-applied when the image is // rendered on the front-end. if (element.is('a') && element.find('img').length) { element = element.children(); } // Extract attributes represented by fields and use those values to keep // them in sync, usually alt and title. var attributes = Drupal.media.filter.parseAttributeFields(info.fields); info.attributes = $.extend(info.attributes, attributes); // Move attributes from the file info array to the placeholder element. if (info.attributes) { $.each(Drupal.settings.media.wysiwyg_allowed_attributes, function(i, a) { if (info.attributes[a]) { element.attr(a, info.attributes[a]); } else if (element.attr(a)) { // If the element has the attribute, but the value is empty, be // sure to clear it. element.removeAttr(a); } }); 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, innerHTML: element[0].innerHTML } } info.type = info.type || "media"; // Store the data in the data map. Drupal.media.filter.ensureDataMap(); // Generate a "delta" to allow for multiple embeddings of the same file. var delta = Drupal.media.filter.fileEmbedDelta(info.fid, element); if (Drupal.settings.mediaDataMap[info.fid]) { info.field_deltas = Drupal.settings.mediaDataMap[info.fid].field_deltas || {}; } else { info.field_deltas = {}; } info.field_deltas[delta] = info.fields; element.attr('data-delta', delta); 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 data-media-element attribute so we can find the markup element later. element.attr('data-media-element', '1') var classes = ['media-element']; if (info.view_mode) { // Remove any existing view mode classes. element.removeClass (function (index, css) { return (css.match (/\bfile-\S+/g) || []).join(' '); }); classes.push('file-' + info.view_mode.replace(/_/g, '-')); } // Check for alignment info, after removing any existing alignment class. element.removeClass (function (index, css) { return (css.match (/\bmedia-wysiwyg-align-\S+/g) || []).join(' '); }); if (info.fields && info.fields.alignment) { classes.push('media-wysiwyg-align-' + info.fields.alignment); } element.addClass(classes.join(' ')); // Attempt to override the link_title if the user has chosen to do this. info.link_text = this.overrideLinkTitle(info); // Apply link_text if present. if ((info.link_text) && (!info.fields || !info.fields.external_url || info.fields.external_url.length === 0)) { $('a', element).html(info.link_text); } 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) { if (typeof file_info.link_text == 'string') { file_info.link_text = this.overrideLinkTitle(file_info); // Make sure the link_text-html-tags are properly escaped. file_info.link_text = file_info.link_text.replace(//g, '>'); } 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 associated file info via a file id (fid). */ extract_file_info: function (element) { var fid, file_info, value, delta; if (fid = element.data('fid')) { Drupal.media.filter.ensureDataMap(); if (file_info = Drupal.settings.mediaDataMap[fid]) { file_info.attributes = {}; $.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; } }); // Extract the link text, if there is any. file_info.link_text = (Drupal.settings.mediaDoLinkText) ? element.find('a:not(:has(img))').html() : false; // When a file is embedded, its fields can be overridden. To allow for // the edge case where the same file is embedded multiple times with // different field overrides, we look for a data-delta attribute on // the element, and use that to decide which set of data in the // "field_deltas" property to use. if (delta = element.data('delta')) { if (file_info.field_deltas && file_info.field_deltas[delta]) { file_info.fields = file_info.field_deltas[delta]; // Also look for an overridden view mode, aka "format". // Check for existance of fields to make it backward compatible. if (file_info.fields && file_info.fields.format && file_info.view_mode) { file_info.view_mode = file_info.fields.format; } } } } else { return false; } } else { return false; } return Drupal.media.filter.syncAttributesToFields(file_info); }, /** * Gets the HTML content of an element. * * @param element (jQuery object) */ outerHTML: function (element) { return element[0].outerHTML || $('').append(element.eq(0).clone()).html(); }, /** * Gets the wrapped HTML content of an element to insert into the wysiwyg. * * It also registers the element in the tag map so that the token * replacement works. * * @param element (jQuery object) The element to insert. * * @see Drupal.media.filter.replacePlaceholderWithToken() */ getWysiwygHTML: function (element) { // Create the markup and the macro. var markup = Drupal.media.filter.outerHTML(element), macro = Drupal.media.filter.create_macro(element); // Store macro/markup in the tagmap. Drupal.media.filter.ensure_tagmap(); Drupal.settings.tagmap[macro] = markup; // Return the html code to insert in an editor and use it with // replacePlaceholderWithToken() return markup; }, /** * 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 () { Drupal.settings.tagmap = Drupal.settings.tagmap || {}; return Drupal.settings.tagmap; }, /** * Return the overridden link title based on the file_entity title field * set. * @param file the file object. * @returns the overridden link_title or the existing link text if no * overridden. */ overrideLinkTitle: function(file) { var file_title_field = Drupal.settings.media.img_title_field.replace('field_', ''); var file_title_field_machine_name = ''; if (typeof(file.fields) != 'undefined') { jQuery.each(file.fields, function(field, fieldValue) { if (field.indexOf(file_title_field) != -1) { file_title_field_machine_name = field; } }); if (typeof(file.fields[file_title_field_machine_name]) != 'undefined' && file.fields[file_title_field_machine_name] != '') { return file.fields[file_title_field_machine_name]; } else { return file.link_text; } } else { return file.link_text; } }, /** * Generates a unique "delta" for each embedding of a particular file. */ fileEmbedDelta: function(fid, element) { // Ensure we have an object to track our deltas. Drupal.settings.mediaDeltas = Drupal.settings.mediaDeltas || {}; Drupal.settings.maxMediaDelta = Drupal.settings.maxMediaDelta || 0; // Check to see if the element already has one. if (element && element.data('delta')) { var existingDelta = element.data('delta'); // If so, make sure that it is being tracked in mediaDeltas. If we're // going to create new deltas later on, make sure they do not overwrite // other mediaDeltas. if (!Drupal.settings.mediaDeltas[existingDelta]) { Drupal.settings.mediaDeltas[existingDelta] = fid; Drupal.settings.maxMediaDelta = Math.max(Drupal.settings.maxMediaDelta, existingDelta); } return existingDelta; } // Otherwise, generate a new one. var newDelta = Drupal.settings.maxMediaDelta + 1; Drupal.settings.mediaDeltas[newDelta] = fid; Drupal.settings.maxMediaDelta = newDelta; return newDelta; } } })(jQuery); ; /** * @file * Attaches behaviors for the Chosen module. */ (function($) { Drupal.behaviors.chosen = { attach: function(context, settings) { settings.chosen = settings.chosen || Drupal.settings.chosen; // Prepare selector and add unwantend selectors. var selector = settings.chosen.selector; // Function to prepare all the options together for the chosen() call. var getElementOptions = function (element) { var options = $.extend({}, settings.chosen.options); // The width default option is considered the minimum width, so this // must be evaluated for every option. if (settings.chosen.minimum_width > 0) { if ($(element).width() < settings.chosen.minimum_width) { options.width = settings.chosen.minimum_width + 'px'; } else { options.width = $(element).width() + 'px'; } } // Some field widgets have cardinality, so we must respect that. // @see chosen_pre_render_select() if ($(element).attr('multiple') && $(element).data('cardinality')) { options.max_selected_options = $(element).data('cardinality'); } return options; }; // Process elements that have opted-in for Chosen. // @todo Remove support for the deprecated chosen-widget class. $('select.chosen-enable, select.chosen-widget', context).once('chosen', function() { options = getElementOptions(this); $(this).chosen(options); }); $(selector, context) // Disabled on: // - Field UI // - WYSIWYG elements // - Tabledrag weights // - Elements that have opted-out of Chosen // - Elements already processed by Chosen. .not('#field-ui-field-overview-form select, #field-ui-display-overview-form select, .wysiwyg, .draggable select[name$="[weight]"], .draggable select[name$="[position]"], .chosen-disable, .chosen-processed') .filter(function() { // Filter out select widgets that do not meet the minimum number of // options. var minOptions = $(this).attr('multiple') ? settings.chosen.minimum_multiple : settings.chosen.minimum_single; if (!minOptions) { // Zero value means no minimum. return true; } else { return $(this).find('option').length >= minOptions; } }) .once('chosen', function() { options = getElementOptions(this); $(this).chosen(options); }); } }; })(jQuery); ;