diff --git a/core/lib/Drupal/Core/Ajax/AjaxResponse.php b/core/lib/Drupal/Core/Ajax/AjaxResponse.php index 83124e8..1231f36 100644 --- a/core/lib/Drupal/Core/Ajax/AjaxResponse.php +++ b/core/lib/Drupal/Core/Ajax/AjaxResponse.php @@ -133,7 +133,7 @@ protected function ajaxRender(Request $request) { $scripts = drupal_add_js(); if (!empty($scripts['settings'])) { $settings = drupal_merge_js_settings($scripts['settings']['data']); - $this->addCommand(new SettingsCommand($settings, TRUE)); + $this->addCommand(new SettingsCommand($settings, TRUE), TRUE); } $commands = $this->commands; diff --git a/core/modules/ckeditor/ckeditor.module b/core/modules/ckeditor/ckeditor.module index c566f3a..d9f4808 100644 --- a/core/modules/ckeditor/ckeditor.module +++ b/core/modules/ckeditor/ckeditor.module @@ -20,7 +20,7 @@ function ckeditor_library_info() { 'title' => 'Drupal behavior to enable CKEditor on textareas.', 'version' => VERSION, 'js' => array( - $module_path . '/js/ckeditor.js' => array(), + $module_path . '/js/ckeditor.js' => array('group' => JS_DEFAULT), array('data' => $settings, 'type' => 'setting'), ), 'dependencies' => array( diff --git a/core/modules/ckeditor/js/ckeditor.js b/core/modules/ckeditor/js/ckeditor.js index b8318be..b931698 100644 --- a/core/modules/ckeditor/js/ckeditor.js +++ b/core/modules/ckeditor/js/ckeditor.js @@ -5,7 +5,7 @@ Drupal.editors.ckeditor = { attach: function (element, format) { - var externalPlugins = format.editorSettings.externalPlugins; + var externalPlugins = format.editorSettings.drupalExternalPlugins; // Register and load additional CKEditor plugins as necessary. if (externalPlugins) { for (var pluginName in externalPlugins) { @@ -15,6 +15,10 @@ Drupal.editors.ckeditor = { } delete format.editorSettings.drupalExternalPlugins; } + // Save settings that are Drupal-specific into the editor config. + format.editorSettings.drupal = { + format: format.format + } return !!CKEDITOR.replace(element, format.editorSettings); }, diff --git a/core/modules/ckeditor/js/plugins/drupalimage/plugin.js b/core/modules/ckeditor/js/plugins/drupalimage/plugin.js new file mode 100644 index 0000000..9b920be --- /dev/null +++ b/core/modules/ckeditor/js/plugins/drupalimage/plugin.js @@ -0,0 +1,242 @@ +/** + * @file Drupal Image plugin. + */ + +(function ($, Drupal, drupalSettings) { + +"use strict"; + +CKEDITOR.plugins.add('drupalimage', { + init: function (editor) { + var pluginName = 'drupalimage'; + + // Register the toolbar button. + editor.ui.addButton('DrupalImage', { + label: editor.lang.common.image, + command: 'image', + icon: Drupal.settings.basePath + Drupal.settings.ckeditor.modulePath + '/js/plugins/drupalimage/image.png', + }); + + // Register the image command. + editor.addCommand('image', { + allowedContent: 'img[class,id,lang,longdesc,title]', + requiredContent: 'img[alt,src,width,height,class]', + exec: function (editor) { + var imageElement = getSelectedImage(editor); + var imageDOMElement = null; + if (imageElement && imageElement.$) { + imageDOMElement = imageElement.$; + } + var existingValues = { + src: imageDOMElement && imageDOMElement.attributes.src ? imageDOMElement.attributes.src.value : '', + width: imageDOMElement ? imageDOMElement.width : '', + height: imageDOMElement ? imageDOMElement.height : '', + }; + var saveCallback = function(returnValues) { + // Create a new image element if needed. + if (!imageElement && returnValues['attributes']['src']) { + imageElement = editor.document.createElement('img'); + imageElement.setAttribute('alt', ''); + editor.insertElement(imageElement); + } + // Delete the image if the src was removed. + if (imageElement && !returnValues['attributes']['src']) { + imageElement.remove(); + } + // Update the image properties. + else { + for (var key in returnValues['attributes']) { + if (returnValues['attributes'].hasOwnProperty(key)) { + // Update the property if a value is specified. + if (returnValues['attributes'][key].length > 0) { + imageElement.setAttribute(key, returnValues['attributes'][key]); + } + // Delete the property if set to an empty string. + else { + imageElement.removeAttribute(key); + } + } + } + + // Set image alignment. + if (returnValues['alignment'] !== existingValues['alignment']) { + // Set a new alignment. + if (returnValues['alignment']) { + var alignCommand = editor.getCommand('justify' + returnValues['alignment']); + if (alignCommand.state === CKEDITOR.TRISTATE_OFF) { + alignCommand.exec(); + } + } + // Remove alignment. + else if (existingValues['alignment']) { + var alignCommand = editor.getCommand('justify' + existingValues['alignment']); + if (alignCommand.state === CKEDITOR.TRISTATE_ON) { + alignCommand.exec(); + } + } + } + + } + }; + Drupal.editor.modalOpen(Drupal.settings.basePath + 'editor/image/nojs/' + editor.config.drupal.format, Drupal.t('Insert/Edit Image'), existingValues, saveCallback); + }, + modes: { wysiwyg : 1 }, + canUndo: true + }); + + // If the "menu" plugin is loaded, register the menu items. + if (editor.addMenuItems) { + editor.addMenuItems({ + image: { + label: editor.lang.image.menu, + command : 'image', + group: 'image' + } + }); + } + + // If the "contextmenu" plugin is loaded, register the listeners. + if (editor.contextMenu) { + editor.contextMenu.addListener(function (element, selection) { + if (getSelectedImage(editor, element)) { + return { image: CKEDITOR.TRISTATE_OFF }; + } + }); + } + }, + afterInit: function (editor) { + // Customize the behavior of the alignment commands. (#7430) + setupAlignCommand('left'); + setupAlignCommand('right'); + setupAlignCommand('center'); + setupAlignCommand('block'); + + function setupAlignCommand (value) { + var command = editor.getCommand('justify' + value); + if (command) { + command.on( 'exec', function(evt) { + var img = getSelectedImage(editor), align; + if (img) { + // Remove the state of the previous alignment button. + previousAlignment = getImageAlignment(img); + if (previousAlignment) { + var previousCommand = editor.getCommand('justify' + previousAlignment); + if ( previousCommand.state === CKEDITOR.TRISTATE_ON) { + previousCommand.setState(CKEDITOR.TRISTATE_OFF); + } + } + // Set alignment and activate the current alignment button. + if (setImageAlignment(img, value)) { + command.setState(CKEDITOR.TRISTATE_ON); + } + evt.cancel(); + } + }); + + command.on('refresh', function(evt) { + var img = getSelectedImage(editor), align; + if (img) { + align = getImageAlignment(img); + + this.setState( + (align == value) ? CKEDITOR.TRISTATE_ON : + (CKEDITOR.config.drupalimage_justifyClasses || value == 'right' || value == 'left' ) ? CKEDITOR.TRISTATE_OFF : + CKEDITOR.TRISTATE_DISABLED); + + evt.cancel(); + } + }); + } + } + } +}); + +function getSelectedImage (editor, element) { + if (!element) { + var sel = editor.getSelection(); + var selectedText = sel.getSelectedText().replace(/^\s\s*/, '').replace(/\s\s*$/, ''); + var isElement = sel.getType() === CKEDITOR.SELECTION_ELEMENT; + var isEmptySelection = sel.getType() === CKEDITOR.SELECTION_TEXT && selectedText.length === 0; + element = (isElement || isEmptySelection) && sel.getSelectedElement(); + } + + if (element && element.is('img') && !element.data('cke-realelement') && !element.isReadOnly()) { + return element; + } +} + +function getImageAlignment (element) { + // Get alignment based on precedence of display used in browsers: + // 1) inline style, 2) class style, then 3) align attribute. + var align = element.getStyle('float'); + + if (align == 'inherit' || align == 'none') { + align = 0; + } + + if (!align && CKEDITOR.config.drupalimage_justifyClasses) { + var justifyClasses = CKEDITOR.config.drupalimage_justifyClasses; + var justifyNames = ['left', 'center', 'right', 'block']; + for (var classPosition = 0; classPosition < 4; classPosition++) { + if (element.hasClass(justifyClasses[classPosition])) { + align = justifyNames[classPosition]; + } + } + } + + if (!align) { + align = element.getAttribute('align'); + } + + return align; +} + +function setImageAlignment (img, value) { + align = getImageAlignment(img); + if (CKEDITOR.config.drupalimage_justifyClasses) { + var justifyClasses = CKEDITOR.config.drupalimage_justifyClasses; + var justifyNames = [ 'left', 'center', 'right', 'block' ]; + var justifyOldPosition = CKEDITOR.tools.indexOf( justifyNames, align ); + var justifyOldClassName = justifyOldPosition === -1 ? null : justifyClasses[justifyOldPosition]; + var justifyNewPosition = CKEDITOR.tools.indexOf( justifyNames, value ); + var justifyNewClassName = justifyNewPosition === -1 ? null : justifyClasses[justifyNewPosition]; + } + + // If this image is already aligned, remove existing alignment. + if (align) { + img.removeStyle('float'); + img.removeAttribute('align'); + if (justifyOldClassName) { + img.removeClass(justifyOldClassName); + } + } + + // If changing the alignment to a new value, set the new style. + if (value && align !== value) { + if (justifyNewClassName) { + img.addClass(justifyNewClassName); + } + else { + img.setStyle('float', value); + } + } + + return align !== value; +} + +})(jQuery, Drupal, drupalSettings); + +/** + * List of classes to use for aligning images. Each class will be used when + * an image is selected and the normal justify toolbar buttons are clicked. The + * array of classes should contain 4 members, in the following order: left, + * center, right, justify. If the list of classes is null, CSS style attributes + * will be used instead. + * + * @type Array + * @default true + * @example + * // Disable the list of classes and use styles instead. + * config.drupalimage_justifyClasses = null; + */ +CKEDITOR.config.drupalimage_justifyClasses = [ 'align-left', 'align-center', 'align-right', 'full-width' ]; diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginBase.php b/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginBase.php index 4050be1..d1fc68f 100644 --- a/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginBase.php +++ b/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginBase.php @@ -30,7 +30,7 @@ * @see CKEditorPluginContextualInterface * @see CKEditorPluginConfigurableInterface */ -abstract class CKEditorPluginBase extends PluginBase implements CKEditorPluginInterface, CKEditorPluginButtonsInterface { +abstract class CKEditorPluginBase extends PluginBase implements CKEditorPluginInterface { /** * Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::isInternal(). @@ -39,4 +39,17 @@ function isInternal() { return FALSE; } + /** + * Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::getDependencies(). + */ + function getDependencies() { + return array(); + } + + /** + * Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::getLibraries(). + */ + function getLibraries(Editor $editor) { + return array(); + } } diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginInterface.php b/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginInterface.php index 601cafb..c4cd4a5 100644 --- a/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginInterface.php +++ b/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginInterface.php @@ -41,6 +41,27 @@ public function isInternal(); /** + * Returns a list of plugins this plugin requires. + * + * @return array + * An unindexed array of plugin names this plugin requires. Each plugin is + * is identified by its annotated ID. + */ + public function getDependencies(); + + /** + * Returns a list of libraries this plugin requires. + * + * These libraries will be attached to the text_format element on which the + * editor is being loaded. + * + * @return array + * An array of libraries suitable for usage in a render API #attached + * property. + */ + public function getLibraries(Editor $editor); + + /** * Returns the Drupal root-relative file path to the plugin JavaScript file. * * Note: this does not use a Drupal library because this uses CKEditor's API, diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginManager.php b/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginManager.php index 57a3a4b..ae797b1 100644 --- a/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginManager.php +++ b/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginManager.php @@ -61,6 +61,7 @@ public function getEnabledPlugins(Editor $editor, $include_internal_plugins = FA $plugins = array_keys($this->getDefinitions()); $toolbar_buttons = array_unique(NestedArray::mergeDeepArray($editor->settings['toolbar']['buttons'])); $enabled_plugins = array(); + $additional_plugins = array(); foreach ($plugins as $plugin_id) { $plugin = $this->createInstance($plugin_id); @@ -70,19 +71,29 @@ public function getEnabledPlugins(Editor $editor, $include_internal_plugins = FA } $enabled = FALSE; + // Enable this plugin if it provides a button that has been enabled. if ($plugin instanceof CKEditorPluginButtonsInterface) { $plugin_buttons = array_keys($plugin->getButtons()); $enabled = (count(array_intersect($toolbar_buttons, $plugin_buttons)) > 0); } + // Otherwise enable this plugin if it declares itself as enabled. if (!$enabled && $plugin instanceof CKEditorPluginContextualInterface) { $enabled = $plugin->isEnabled($editor); } if ($enabled) { $enabled_plugins[$plugin_id] = ($plugin->isInternal()) ? NULL : $plugin->getFile(); + // Check if this plugin has dependencies that also need to be enabled. + $additional_plugins = array_merge($additional_plugins, array_diff($plugin->getDependencies(), $additional_plugins)); } } + // Add the list of dependent plugins. + foreach ($additional_plugins as $plugin_id) { + $plugin = $this->createInstance($plugin_id); + $enabled_plugins[$plugin_id] = ($plugin->isInternal()) ? NULL : $plugin->getFile(); + } + // Always return plugins in the same order. asort($enabled_plugins); diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/ckeditor/plugin/DrupalImage.php b/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/ckeditor/plugin/DrupalImage.php new file mode 100644 index 0000000..bec31af --- /dev/null +++ b/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/ckeditor/plugin/DrupalImage.php @@ -0,0 +1,62 @@ + array( + 'label' => t('Image'), + 'image' => drupal_get_path('module', 'ckeditor') . '/js/plugins/drupalimage/image.png', + ), + ); + } + +} diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/ckeditor/plugin/Internal.php b/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/ckeditor/plugin/Internal.php index 86fe66f..16e58c6 100644 --- a/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/ckeditor/plugin/Internal.php +++ b/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/ckeditor/plugin/Internal.php @@ -8,6 +8,7 @@ namespace Drupal\ckeditor\Plugin\ckeditor\plugin; use Drupal\ckeditor\CKEditorPluginBase; +use Drupal\ckeditor\CKEditorPluginButtonsInterface; use Drupal\Component\Utility\NestedArray; use Drupal\Core\Annotation\Plugin; use Drupal\Core\Annotation\Translation; @@ -22,7 +23,7 @@ * module = "ckeditor" * ) */ -class Internal extends CKEditorPluginBase { +class Internal extends CKEditorPluginBase implements CKEditorPluginButtonsInterface { /** * Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::isInternal(). @@ -194,11 +195,6 @@ public function getButtons() { 'label' => t('HTML block format'), 'image_alternative' => '' . t('Format') . '', ), - // "image" plugin. - 'Image' => array( - 'label' => t('Image'), - 'image_alternative' => $button('image'), - ), // "table" plugin. 'Table' => array( 'label' => t('Table'), diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/ckeditor/plugin/StylesCombo.php b/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/ckeditor/plugin/StylesCombo.php index 9f09a11..ec00a9a 100644 --- a/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/ckeditor/plugin/StylesCombo.php +++ b/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/ckeditor/plugin/StylesCombo.php @@ -8,6 +8,7 @@ namespace Drupal\ckeditor\Plugin\ckeditor\plugin; use Drupal\ckeditor\CKEditorPluginBase; +use Drupal\ckeditor\CKEditorPluginButtonsInterface; use Drupal\ckeditor\CKEditorPluginConfigurableInterface; use Drupal\Component\Utility\NestedArray; use Drupal\Core\Annotation\Plugin; @@ -23,7 +24,7 @@ * module = "ckeditor" * ) */ -class StylesCombo extends CKEditorPluginBase implements CKEditorPluginConfigurableInterface { +class StylesCombo extends CKEditorPluginBase implements CKEditorPluginButtonsInterface, CKEditorPluginConfigurableInterface { /** * Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::isInternal(). diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/editor/editor/CKEditor.php b/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/editor/editor/CKEditor.php index e4c0948..07c6a90 100644 --- a/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/editor/editor/CKEditor.php +++ b/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/editor/editor/CKEditor.php @@ -124,7 +124,10 @@ public function getJSSettings(Editor $editor) { 'toolbar' => $this->buildToolbarJSSetting($editor), 'contentsCss' => $this->buildContentsCssJSSetting($editor), 'extraPlugins' => implode(',', array_keys($external_plugins)), + // @todo: Remove default image plugin entirely from Drupal CKEditor build. + 'removePlugins' => 'image', 'language' => $language_interface->langcode, + 'allowedContent' => true, ); // Finally, set Drupal-specific CKEditor settings. @@ -139,9 +142,18 @@ public function getJSSettings(Editor $editor) { * Implements \Drupal\editor\Plugin\EditorInterface::getLibraries(). */ public function getLibraries(Editor $editor) { - return array( + // The main CKEditor library. + $libraries = array( array('ckeditor', 'drupal.ckeditor'), ); + // Add any libraries needed by individual plugins. + $manager = drupal_container()->get('plugin.manager.ckeditor.plugin'); + $external_plugins = $manager->getEnabledPlugins($editor); + foreach ($external_plugins as $plugin_id => $file) { + $plugin = $manager->createInstance($plugin_id); + $libraries = array_merge($libraries, $plugin->getLibraries($editor)); + } + return $libraries; } /** diff --git a/core/modules/editor/editor.module b/core/modules/editor/editor.module index 6db6210..9519cc7 100644 --- a/core/modules/editor/editor.module +++ b/core/modules/editor/editor.module @@ -34,6 +34,22 @@ function editor_help($path, $arg) { } /** + * Implements hook_menu(). + */ +function editor_menu() { + $items['editor/image/%/%filter_format'] = array( + 'title' => 'Insert image', + 'description' => 'Displays the modal dialog for inserting or editing an image in a text editor.', + 'page callback' => 'editor_image_modal', + 'page arguments' => array(2, 3), + 'access callback' => 'filter_access', + 'access arguments' => array(3), + 'file' => 'editor.pages.inc', + ); + return $items; +} + +/** * Implements hook_menu_alter(). * * Rewrites the menu entries for filter module that relate to the configuration @@ -69,7 +85,7 @@ function editor_library_info() { 'title' => 'Text Editor', 'version' => VERSION, 'js' => array( - $path . '/js/editor.js' => array(), + $path . '/js/editor.js' => array('group' => JS_DEFAULT), ), 'dependencies' => array( array('system', 'jquery'), @@ -79,6 +95,22 @@ function editor_library_info() { ), ); + // This library ensures that the Drupal AJAX and jQuery UI libraries are + // loaded for dialogs. The JS/CSS for dialogs is included directly in the + // drupal.editor library. + $libraries['drupal.editor-dialog'] = array( + 'title' => 'Text Editor Dialogs', + 'version' => VERSION, + 'dependencies' => array( + array('system', 'jquery'), + array('system', 'drupal'), + array('system', 'drupalSettings'), + array('system', 'drupal.ajax'), + array('system', 'jquery.ui.dialog'), + array('editor', 'drupal.editor'), + ), + ); + return $libraries; } diff --git a/core/modules/editor/editor.pages.inc b/core/modules/editor/editor.pages.inc new file mode 100644 index 0000000..6fc1499 --- /dev/null +++ b/core/modules/editor/editor.pages.inc @@ -0,0 +1,113 @@ +' . theme('status_messages') . $output . ''; + $javascript = drupal_add_js(); + + $response = new AjaxResponse(); + $response->addCommand(new HtmlCommand('#editor-modal', $output)); + + return $response; +} + +/** + * Form callback; Display the form for inserting/editing an image. + */ +function editor_image_form($form, $form_state, $format) { + $input = $form_state['input']['editor_object']; + + $form['#tree'] = TRUE; + + // Everything under the "attributes" key is merged directly into the + // generate img tag's attributes. + $form['attributes']['src'] = array( + '#title' => t('URL'), + '#type' => 'textfield', + '#default_value' => isset($input['src']) ? $input['src'] : '', + ); + $form['attributes']['width'] = array( + '#title' => t('Width'), + '#type' => 'textfield', + '#default_value' => isset($input['width']) ? $input['width'] : '', + '#size' => 8, + '#maxlength' => 8, + '#field_suffix' => ' ' . t('px'), + ); + $form['attributes']['height'] = array( + '#title' => t('Height'), + '#type' => 'textfield', + '#default_value' => isset($input['height']) ? $input['height'] : '', + '#size' => 8, + '#maxlength' => 8, + '#field_suffix' => ' ' . t('px'), + ); + + // @todo: Postponed on http://drupal.org/node/1907418. + /* + $form['alignment'] = array( + '#title' => t('Alignment'), + '#type' => 'select', + '#options' => array( + '' => t('None'), + 'left' => t('Left'), + 'right' => t('Right'), + 'center' => t('Center'), + 'block' => t('Full width'), + ), + ); + */ + + $form['modal_actions'] = array( + '#type' => 'actions', + ); + $form['modal_actions']['save_modal'] = array( + '#type' => 'submit', + '#value' => t('Save'), + // No regular submit-handler. This form only works via JavaScript. + '#submit' => array(), + '#ajax' => array( + 'callback' => 'editor_image_form_submit', + ), + ); + + return $form; +} + +/** + * Submit handler for editor_image_form_submit(). + */ +function editor_image_form_submit($form, $form_state) { + $response = new AjaxResponse(); + $response->addCommand(new EditorModalSave($form_state['values'])); + return $response; +} diff --git a/core/modules/editor/js/editor.js b/core/modules/editor/js/editor.js index 32da290..cf08eaf 100644 --- a/core/modules/editor/js/editor.js +++ b/core/modules/editor/js/editor.js @@ -31,7 +31,7 @@ Drupal.behaviors.editor = { // Directly attach this editor, if the text format is enabled. if (settings.editor.formats[activeFormatID]) { - Drupal.editorAttach(field, settings.editor.formats[activeFormatID]); + Drupal.editor.attach(field, settings.editor.formats[activeFormatID]); } // Attach onChange handler to text format selector element. @@ -46,11 +46,11 @@ Drupal.behaviors.editor = { // Detach the current editor (if any) and attach a new editor. if (settings.editor.formats[activeFormatID]) { - Drupal.editorDetach(field, settings.editor.formats[activeFormatID]); + Drupal.editor.detach(field, settings.editor.formats[activeFormatID]); } activeFormatID = newFormatID; if (settings.editor.formats[activeFormatID]) { - Drupal.editorAttach(field, settings.editor.formats[activeFormatID]); + Drupal.editor.attach(field, settings.editor.formats[activeFormatID]); } }); } @@ -60,7 +60,7 @@ Drupal.behaviors.editor = { if (event.isDefaultPrevented()) { return; } - Drupal.editorDetach(field, settings.editor.formats[activeFormatID], 'serialize'); + Drupal.editor.detach(field, settings.editor.formats[activeFormatID], 'serialize'); }); }); }, @@ -84,7 +84,7 @@ Drupal.behaviors.editor = { var activeFormatID = $this.val(); var field = behavior.findFieldForFormatSelector($this); - Drupal.editorDetach(field, settings.editor.formats[activeFormatID], trigger); + Drupal.editor.detach(field, settings.editor.formats[activeFormatID], trigger); }); }, @@ -94,16 +94,87 @@ Drupal.behaviors.editor = { } }; -Drupal.editorAttach = function (field, format) { +Drupal.editor = {}; + +/** + * Attach an editor to a field. + */ +Drupal.editor.attach = function (field, format) { if (format.editor) { Drupal.editors[format.editor].attach(field, format); } }; -Drupal.editorDetach = function (field, format, trigger) { +/** + * Remove an editor from a field. + */ +Drupal.editor.detach = function (field, format, trigger) { if (format.editor) { Drupal.editors[format.editor].detach(field, format, trigger); } }; +/** + * Open a Drupal-based modal dialog. + * + * @param url + * The URL of the page that will provide the modal contents. + * @param title + * The title of the modal dialog. + * @param values + * Existing values to be populated into the form. + * @param callback + * When this dialog is completed, this function will receive the returned + * data. + */ +Drupal.editor.modalOpen = function (url, title, values, callback) { + // Remove any existing modal. + var $modal = $('#editor-modal'); + if ($modal.length) { + Drupal.detachBehaviors($modal[0]); + $modal.remove(); + } + // Create a new modal dialog container. + $modal = $('
').hide().appendTo('body'); + var $ajaxElement = $('').attr('href', url); + var modalSettings = { + modal: true, + title: title + } + + $modal.data('saveCallback', callback); + $modal.html($ajaxElement); + $modal.dialog(modalSettings); + + // Perform the AJAX request to load the form. + var base = 'editor-modal'; + var ajaxSettings = { + event: 'click', + submit: { + js: true, + editor_object: values, + } + } + + Drupal.ajax[base] = new Drupal.ajax(base, $ajaxElement[0], ajaxSettings); + $ajaxElement.triggerHandler('click'); + + Drupal.settings.editor.activeModal = $modal; +} + +/** + * Close a modal dialog and save its returned configuration + */ +Drupal.editor.modalClose = function (data) { + Drupal.settings.editor.activeModal.data('saveCallback')(data); + Drupal.settings.editor.activeModal.dialog('close'); +} + +/** + * Command to close and save an open modal dialog. + */ +Drupal.ajax.prototype.commands.editorModalSave = function (ajax, response, status) { + Drupal.editor.modalClose(response.values); +} + })(jQuery, Drupal); diff --git a/core/modules/editor/lib/Drupal/editor/Ajax/EditorModalSave.php b/core/modules/editor/lib/Drupal/editor/Ajax/EditorModalSave.php new file mode 100644 index 0000000..f84b7ec --- /dev/null +++ b/core/modules/editor/lib/Drupal/editor/Ajax/EditorModalSave.php @@ -0,0 +1,45 @@ +values = $values; + } + + /** + * Implements \Drupal\Core\Ajax\CommandInterface::render(). + */ + public function render() { + return array( + 'command' => 'editorModalSave', + 'values' => $this->values, + ); + } + +} diff --git a/core/modules/editor/lib/Drupal/editor/Plugin/EditorManager.php b/core/modules/editor/lib/Drupal/editor/Plugin/EditorManager.php index 74a5eab..671b8d9 100644 --- a/core/modules/editor/lib/Drupal/editor/Plugin/EditorManager.php +++ b/core/modules/editor/lib/Drupal/editor/Plugin/EditorManager.php @@ -72,6 +72,7 @@ public function getAttachments(array $format_ids) { // JavaScript settings. $settings[$format_id] = array( + 'format' => $format_id, 'editor' => $editor->editor, 'editorSettings' => $plugin->getJSSettings($editor), );