diff --git a/core/modules/ckeditor/ckeditor.admin.inc b/core/modules/ckeditor/ckeditor.admin.inc index 0dff9f5..7e6bda7 100644 --- a/core/modules/ckeditor/ckeditor.admin.inc +++ b/core/modules/ckeditor/ckeditor.admin.inc @@ -57,7 +57,7 @@ function theme_ckeditor_settings_toolbar($variables) { $value = $button['image_alternative' . $rtl]; } elseif (isset($button['image'])) { - $value = theme('image', array('uri' => $button['image' . $rtl], 'title' => $button['label'])); + $value = '' . theme('image', array('uri' => $button['image' . $rtl], 'title' => $button['label'])) . ''; } else { $value = '?'; diff --git a/core/modules/ckeditor/ckeditor.module b/core/modules/ckeditor/ckeditor.module index 2b1358f..c2c20c8 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/css/ckeditor.admin.css b/core/modules/ckeditor/css/ckeditor.admin.css index 272e3ea..41af514 100644 --- a/core/modules/ckeditor/css/ckeditor.admin.css +++ b/core/modules/ckeditor/css/ckeditor.admin.css @@ -85,8 +85,10 @@ ul.ckeditor-buttons li .cke-icon-only { text-indent: -9999px; width: 16px; } -ul.ckeditor-buttons li a:focus { +ul.ckeditor-buttons li a:focus, +ul.ckeditor-multiple-buttons li a:focus { z-index: 11; /* Ensure focused buttons show their outline on all sides. */ + outline: 1px dotted #333; } ul.ckeditor-buttons li:first-child a { border-top-left-radius: 2px; /* LTR */ diff --git a/core/modules/ckeditor/js/ckeditor.js b/core/modules/ckeditor/js/ckeditor.js index b8318be..928f149 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) { diff --git a/core/modules/ckeditor/js/plugins/drupalimage/image.png b/core/modules/ckeditor/js/plugins/drupalimage/image.png new file mode 100644 index 0000000..ee73637 --- /dev/null +++ b/core/modules/ckeditor/js/plugins/drupalimage/image.png @@ -0,0 +1,5 @@ +PNG + + IHDRabKGD pHYs  tIME 6FPtEXtCommentCreated with GIMPWuIDAT8œj"Q{#24Z8ba!mM*i2sX4bE^N,!ȁ/9C%$HZ-Dd  \. +JkԓR("Qw#<} <3l6 d2y Cc< ""$"o`~WT*T8-ύF?[|'IfQjuTqvZv\8wWEgZ5Nu;Tj l6;W`!1ҞSׯ굤PIENDB` \ No newline at end of file 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..5f9a2f7 --- /dev/null +++ b/core/modules/ckeditor/js/plugins/drupalimage/plugin.js @@ -0,0 +1,127 @@ +/** + * @file + * Drupal Image plugin. + */ + +(function ($, Drupal, drupalSettings, CKEDITOR) { + +"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: drupalSettings.basePath + drupalSettings.ckeditor.modulePath + '/js/plugins/drupalimage/image.png' + }); + + // Register the image command. + editor.addCommand('image', { + allowedContent: 'img[alt,src,width,height,class,id,lang,longdesc,title]', + requiredContent: 'img[alt,src,width,height]', + exec: function (editor) { + var imageElement = getSelectedImage(editor); + var imageDOMElement = null; + var existingValues = {}; + if (imageElement && imageElement.$) { + imageDOMElement = imageElement.$; + + // Width and height are populated by actual dimensions. + existingValues.width = imageDOMElement ? imageDOMElement.width : ''; + existingValues.height = imageDOMElement ? imageDOMElement.height : ''; + // Populate all other attributes by their specified attribute values. + var attribute = null; + for (var key = 0; key < imageDOMElement.attributes.length; key++) { + attribute = imageDOMElement.attributes.item(key); + existingValues[attribute.nodeName.toLowerCase()] = attribute.nodeValue; + } + } + + 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); + } + } + } + } + }; + var modalSettings = { + dialogClass: 'editor-image-dialog', + width: 500 + }; + Drupal.editor.modalOpen(editor.config.drupalImage_dialogUrl, existingValues, saveCallback, modalSettings); + }, + modes: { wysiwyg : 1 }, + canUndo: true + }); + + // Double clicking an image opens its properties. + editor.on('doubleclick', function(event) { + var element = event.data.element; + if (element.is('img') && !element.data('cke-realelement') && !element.isReadOnly()) { + editor.getCommand('image').exec(); + } + }); + + // 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 }; + } + }); + } + } +}); + +/** + * Finds an img tag anywhere in the current editor selection. + */ +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; + } +} + +})(jQuery, Drupal, drupalSettings, CKEDITOR); diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginBase.php b/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginBase.php index 4050be1..445d9c4 100644 --- a/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginBase.php +++ b/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginBase.php @@ -39,4 +39,17 @@ function isInternal() { return FALSE; } + /** + * Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::getDependencies(). + */ + function getDependencies(Editor $editor) { + 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..58c83ac 100644 --- a/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginInterface.php +++ b/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginInterface.php @@ -41,6 +41,31 @@ public function isInternal(); /** + * Returns a list of plugins this plugin requires. + * + * @param \Drupal\editor\Plugin\Core\Entity\Editor $editor + * A configured text editor object. + * @return array + * An unindexed array of plugin names this plugin requires. Each plugin is + * is identified by its annotated ID. + */ + public function getDependencies(Editor $editor); + + /** + * 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. + * + * @param \Drupal\editor\Plugin\Core\Entity\Editor $editor + * A configured text editor object. + * @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 5500241..4b65665 100644 --- a/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginManager.php +++ b/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginManager.php @@ -63,6 +63,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); @@ -72,19 +73,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($editor), $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..ea9e80d --- /dev/null +++ b/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/ckeditor/plugin/DrupalImage.php @@ -0,0 +1,63 @@ + url('editor/image/nojs/' . $editor->format), + ); + } + + /** + * Implements \Drupal\ckeditor\Plugin\CKEditorPluginButtonsInterface::getButtons(). + */ + public function getButtons() { + return array( + 'DrupalImage' => 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 6c8fd54..a67bf79 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 @@ -47,7 +47,7 @@ public function getConfig(Editor $editor) { $config = array( 'customConfig' => '', // Don't load CKEditor's config.js file. 'pasteFromWordPromptCleanup' => TRUE, - 'removeDialogTabs' => 'image:Link;image:advanced;link:advanced', + 'removeDialogTabs' => 'link:advanced', 'resize_dir' => 'vertical', 'keystrokes' => array( // 0x11000 is CKEDITOR.CTRL, see http://docs.ckeditor.com/#!/api/CKEDITOR-property-CTRL. @@ -194,11 +194,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/editor/editor/CKEditor.php b/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/editor/editor/CKEditor.php index 57b857d..87c4469 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,6 +124,8 @@ 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, // Configure CKEditor to not load styles.js. The StylesCombo plugin will // set stylesSet according to the user's settings, if the "Styles" button @@ -145,9 +147,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 non-internal 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/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorPluginManagerTest.php b/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorPluginManagerTest.php index b7383d7..7bebb2f 100644 --- a/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorPluginManagerTest.php +++ b/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorPluginManagerTest.php @@ -69,7 +69,7 @@ function testEnabledPlugins() { // Case 1: no CKEditor plugins. $definitions = array_keys($this->manager->getDefinitions()); sort($definitions); - $this->assertIdentical(array('internal', 'stylescombo'), $definitions, 'No CKEditor plugins found besides the built-in ones.'); + $this->assertIdentical(array('drupalimage', 'internal', 'stylescombo'), $definitions, 'No CKEditor plugins found besides the built-in ones.'); $this->assertIdentical(array(), $this->manager->getEnabledPlugins($editor), 'Only built-in plugins are enabled.'); $this->assertIdentical(array('internal' => NULL), $this->manager->getEnabledPlugins($editor, TRUE), 'Only the "internal" plugin is enabled.'); @@ -83,7 +83,7 @@ function testEnabledPlugins() { // Case 2: CKEditor plugins are available. $plugin_ids = array_keys($this->manager->getDefinitions()); sort($plugin_ids); - $this->assertIdentical(array('internal', 'llama', 'llama_button', 'llama_contextual', 'llama_contextual_and_button', 'stylescombo'), $plugin_ids, 'Additional CKEditor plugins found.'); + $this->assertIdentical(array('drupalimage', 'internal', 'llama', 'llama_button', 'llama_contextual', 'llama_contextual_and_button', 'stylescombo'), $plugin_ids, 'Additional CKEditor plugins found.'); $this->assertIdentical(array(), $this->manager->getEnabledPlugins($editor), 'Only the internal plugins are enabled.'); $this->assertIdentical(array('internal' => NULL), $this->manager->getEnabledPlugins($editor, TRUE), 'Only the "internal" plugin is enabled.'); diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorTest.php b/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorTest.php index db4dbec..febcb5d 100644 --- a/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorTest.php +++ b/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorTest.php @@ -79,6 +79,7 @@ function testGetJSSettings() { 'toolbar' => $this->getDefaultToolbarConfig(), 'contentsCss' => $this->getDefaultContentsCssConfig(), 'extraPlugins' => '', + 'removePlugins' => 'image', 'language' => 'en', 'stylesSet' => FALSE, 'drupalExternalPlugins' => array(), @@ -98,6 +99,7 @@ function testGetJSSettings() { $expected_config['toolbar'][] = '/'; $expected_config['format_tags'] = 'p;h4;h5;h6'; $expected_config['extraPlugins'] = 'llama_contextual,llama_contextual_and_button'; + $expected_config['removePlugins'] = 'image'; $expected_config['drupalExternalPlugins']['llama_contextual'] = file_create_url('core/modules/ckeditor/tests/modules/js/llama_contextual.js'); $expected_config['drupalExternalPlugins']['llama_contextual_and_button'] = file_create_url('core/modules/ckeditor/tests/modules/js/llama_contextual_and_button.js'); $expected_config['contentsCss'][] = file_create_url('core/modules/ckeditor/tests/modules/ckeditor_test.css'); @@ -224,7 +226,7 @@ protected function getDefaultInternalConfig() { return array( 'customConfig' => '', 'pasteFromWordPromptCleanup' => TRUE, - 'removeDialogTabs' => 'image:Link;image:advanced;link:advanced', + 'removeDialogTabs' => 'link:advanced', 'resize_dir' => 'vertical', 'keystrokes' => array(array(0x110000 + 75, 'link'), array(0x110000 + 76, NULL)), ); diff --git a/core/modules/ckeditor/tests/modules/lib/Drupal/ckeditor_test/Plugin/ckeditor/plugin/Llama.php b/core/modules/ckeditor/tests/modules/lib/Drupal/ckeditor_test/Plugin/ckeditor/plugin/Llama.php index 21d6cf1..76431f6 100644 --- a/core/modules/ckeditor/tests/modules/lib/Drupal/ckeditor_test/Plugin/ckeditor/plugin/Llama.php +++ b/core/modules/ckeditor/tests/modules/lib/Drupal/ckeditor_test/Plugin/ckeditor/plugin/Llama.php @@ -41,6 +41,20 @@ function isInternal() { } /** + * Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::getDependencies(). + */ + function getDependencies(Editor $editor) { + return array(); + } + + /** + * Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::getLibraries(). + */ + function getLibraries(Editor $editor) { + return array(); + } + + /** * Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::getFile(). */ function getFile() { @@ -48,7 +62,7 @@ function getFile() { } /** - * Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::getButtons(). + * Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::getConfig(). */ public function getConfig(Editor $editor) { return array(); diff --git a/core/modules/editor/css/editor.dialog.css b/core/modules/editor/css/editor.dialog.css new file mode 100644 index 0000000..4f71f1a --- /dev/null +++ b/core/modules/editor/css/editor.dialog.css @@ -0,0 +1,8 @@ +/** + * @file + * Styles for text editors. + */ +.editor-image-dialog { + width: 80%; + max-width: 500px; +} diff --git a/core/modules/editor/editor.module b/core/modules/editor/editor.module index 6db6210..8a42eef 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 @@ -79,6 +95,25 @@ function editor_library_info() { ), ); + $libraries['drupal.editor-dialog'] = array( + 'title' => 'Text Editor Dialogs', + 'version' => VERSION, + 'js' => array( + $path . '/js/editor.dialog.js' => array(), + ), + 'css' => array( + $path . '/css/editor.dialog.css' => array(), + ), + '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..f380073 --- /dev/null +++ b/core/modules/editor/editor.pages.inc @@ -0,0 +1,138 @@ +' . theme('status_messages') . $output . ''; + + $response = new AjaxResponse(); + if ($form['#title']) { + $response->addCommand(new EditorModalTitle($form['#title'])); + } + $response->addCommand(new HtmlCommand('#editor-modal', $output)); + + return $response; +} + +/** + * Form callback; Display the form for inserting/editing an image. + */ +function editor_image_form(array $form, array $form_state, FilterFormat $format) { + $input = $form_state['input']['editor_object']; + + $form['#tree'] = TRUE; + + // The #title attribute on the form will be set as the modal title. + $form['#title'] = isset($input['src']) ? t('Update image') : t('Insert image'); + + // 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'] : '', + '#maxlength' => 2048, + '#required' => TRUE, + ); + + $form['attributes']['alt'] = array( + '#title' => t('Alternative text'), + '#type' => 'textfield', + '#default_value' => isset($input['alt']) ? $input['alt'] : '', + '#maxlength' => 2048, + ); + $form['dimensions'] = array( + '#type' => 'item', + '#title' => t('Image size'), + '#field_prefix' => '
', + '#field_suffix' => '
', + ); + $form['dimensions']['width'] = array( + '#title' => t('Width'), + '#title_display' => 'invisible', + '#type' => 'number', + '#default_value' => isset($input['width']) ? $input['width'] : '', + '#size' => 8, + '#maxlength' => 8, + '#min' => 1, + '#max' => 99999, + '#placeholder' => 'width', + '#field_suffix' => ' x ', + '#parents' => array('attributes', 'width'), + ); + $form['dimensions']['height'] = array( + '#title' => t('Height'), + '#title_display' => 'invisible', + '#type' => 'number', + '#default_value' => isset($input['height']) ? $input['height'] : '', + '#size' => 8, + '#maxlength' => 8, + '#min' => 1, + '#max' => 99999, + '#placeholder' => 'height', + '#field_suffix' => 'pixels', + '#parents' => array('attributes', 'height'), + ); + + $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(array $form, array $form_state) { + $response = new AjaxResponse(); + + if (form_get_errors()) { + $output = drupal_render($form); + $output = '
' . theme('status_messages') . $output . '
'; + $response->addCommand(new HtmlCommand('#editor-modal', $output)); + } + else { + $response->addCommand(new EditorModalSave($form_state['values'])); + } + return $response; +} diff --git a/core/modules/editor/js/editor.dialog.js b/core/modules/editor/js/editor.dialog.js new file mode 100644 index 0000000..d32cbe7 --- /dev/null +++ b/core/modules/editor/js/editor.dialog.js @@ -0,0 +1,81 @@ +/** + * @todo D8: remove this when http://drupal.org/node/1870764 lands + */ + +(function ($, Drupal) { + +"use strict"; + +/** + * 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, values, callback, callerSettings) { + // 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 = {}; + $.extend(modalSettings, { + modal: true + }, callerSettings); + + $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); +}; + +/** + * Command to set the title of the current modal dialog. + */ +Drupal.ajax.prototype.commands.editorModalTitle = function (ajax, response, status) { + if (Drupal.settings.editor.activeModal) { + Drupal.settings.editor.activeModal.dialog('option', 'title', response.title); + } +}; + +})(jQuery, Drupal); diff --git a/core/modules/editor/js/editor.js b/core/modules/editor/js/editor.js index 32da290..a615711 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,15 +94,43 @@ Drupal.behaviors.editor = { } }; -Drupal.editorAttach = function (field, format) { - if (format.editor) { - Drupal.editors[format.editor].attach(field, format); - } -}; +Drupal.editor = { + + /** + * Attaches an editor to a field with a certain active text format. + * + * @param DOMNode field + * A textual field. + * @param format + * An object depending on the field's active text format, with the following + * keys: + * - format: A text format ID. + * - editor: The plugin ID of the corresponding text editor. + * - editorSettings: An object containing text editor settings. + */ + attach: function (field, format) { + if (format.editor) { + Drupal.editors[format.editor].attach(field, format); + } + }, -Drupal.editorDetach = function (field, format, trigger) { - if (format.editor) { - Drupal.editors[format.editor].detach(field, format, trigger); + /** + * Detaches an editor from a field with a certain trigger. + * + * @param DOMNode field + * A textual field. + * @param format + * See Drupal.editor.attach(). + * @param string trigger + * See Drupal.detachBehaviors(). + * + * @see Drupal.editor.attach() + * @see Drupal.detachBehaviors() + */ + detach: function (field, format, trigger) { + if (format.editor) { + Drupal.editors[format.editor].detach(field, format, trigger); + } } }; 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/Ajax/EditorModalTitle.php b/core/modules/editor/lib/Drupal/editor/Ajax/EditorModalTitle.php new file mode 100644 index 0000000..13cf7f0 --- /dev/null +++ b/core/modules/editor/lib/Drupal/editor/Ajax/EditorModalTitle.php @@ -0,0 +1,44 @@ +title = $title; + } + + /** + * Implements \Drupal\Core\Ajax\CommandInterface::render(). + */ + public function render() { + return array( + 'command' => 'editorModalTitle', + 'title' => $this->title, + ); + } + +} diff --git a/core/modules/system/system.module b/core/modules/system/system.module index 2c51ae0..e53cee4 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -1235,7 +1235,7 @@ function system_library_info() { 'website' => 'http://api.drupal.org/api/group/ajax/8', 'version' => VERSION, 'js' => array( - 'core/misc/ajax.js' => array('group' => JS_LIBRARY, 'weight' => 2), + 'core/misc/ajax.js' => array('group' => JS_LIBRARY), ), 'dependencies' => array( array('system', 'jquery'),