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.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 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/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..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/image.png b/core/modules/ckeditor/js/plugins/drupalimage/image.png new file mode 100644 index 0000000..3e07ac8 --- /dev/null +++ b/core/modules/ckeditor/js/plugins/drupalimage/image.png @@ -0,0 +1,5 @@ +PNG + + IHDRabKGD pHYs B(xIDAT8˝NQwۋn4\DHHn ih ӄHRF))@ $`6sN +&H;3sFϱbADx TDQDDŽa)ʲɲz_U=Aeczjv]1FPD_L9 E(0`EDj(!<`oo{9a!f8x||h{r-s:TՃ[IUx|hZ( +(fUhTv}ʊc'"D䃈|6kڏ`ollH&"'j5&nw/ٙ< zɲ͹(~N/..OAEqΡcxxx憳3L |zxLUU$IBUUVuMLlKKKx H9$a<`8ALjioC2qb%tEXtdate:create2012-11-07T11:32:21+01:0050G%tEXtdate:modify2012-11-07T11:32:21+01:00htEXtSoftwarewww.inkscape.org<IENDB` \ 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..28705c1 --- /dev/null +++ b/core/modules/ckeditor/js/plugins/drupalimage/plugin.js @@ -0,0 +1,269 @@ +/** + * @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(); + } + } + } + + } + }; + var modalSettings = { + title: Drupal.t('Insert/Edit Image'), + dialogClass: 'editor-image-dialog', + width: 500 + } + Drupal.editor.modalOpen(Drupal.settings.basePath + 'editor/image/nojs/' + editor.config.drupal.format, existingValues, saveCallback, modalSettings); + }, + 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(); + } + }); + } + } + } +}); + +/** + * 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; + } +} + +/** + * Given an image element, get the alignment based on precedence of display. + * + * Matching browsers, this order is: + * 1) inline style + * 2) class style + * 3) align attribute + * + * @return string|false + * A string "left", "center", "right", or "block". Or false if the image is + * not aligned at all. + */ +function getImageAlignment (element) { + var align = element.getStyle('float'); + if (align == 'inherit' || align == 'none') { + align = false; + } + + 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; +} + +/** + * Set an image alignment using classes if available, inline style otherwise. + * + * @param img + * CKEditor element for the image. + * @param value + * One of the following string values: "left", "center", "right", or "block". + * @return boolean + * Returns true if the alignment has been changed, false otherwise. + */ +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..71d93a4 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,24 @@ 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(); + } + + /** + * Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::getConfig(). + */ + function getConfig(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..4988893 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(). @@ -47,7 +48,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 +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/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorLoadingTest.php b/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorLoadingTest.php index 308906a..755a505 100644 --- a/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorLoadingTest.php +++ b/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorLoadingTest.php @@ -90,6 +90,7 @@ function testLoading() { $ckeditor_plugin = drupal_container()->get('plugin.manager.editor')->createInstance('ckeditor'); $editor = entity_load('editor', 'filtered_html'); $expected = array('formats' => array('filtered_html' => array( + 'format' => 'filtered_html', 'editor' => 'ckeditor', 'editorSettings' => $ckeditor_plugin->getJSSettings($editor), ))); @@ -116,6 +117,7 @@ function testLoading() { $this->drupalGet('node/add/article'); list($settings, $editor_settings_present, $editor_js_present, $body, $format_selector) = $this->getThingsToCheck(); $expected = array('formats' => array('filtered_html' => array( + 'format' => 'filtered_html', 'editor' => 'ckeditor', 'editorSettings' => $ckeditor_plugin->getJSSettings($editor), ))); diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorPluginManagerTest.php b/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorPluginManagerTest.php index fc56910..f68b6eb 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.'); @@ -82,7 +82,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 a71a725..b66e19d 100644 --- a/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorTest.php +++ b/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorTest.php @@ -79,9 +79,12 @@ function testGetJSSettings() { 'toolbar' => $this->getDefaultToolbarConfig(), 'contentsCss' => $this->getDefaultContentsCssConfig(), 'extraPlugins' => '', + 'removePlugins' => 'image', 'language' => 'en', + 'allowedContent' => TRUE, 'drupalExternalPlugins' => array(), ); + debug($this->ckeditor->getJSSettings($editor)); $this->assertIdentical($expected_config, $this->ckeditor->getJSSettings($editor), 'Generated JS settings are correct for default configuration.'); // Customize the configuration: add button, have two contextually enabled @@ -97,6 +100,8 @@ 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['allowedContent'] = TRUE; $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'); @@ -227,7 +232,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 d2e0732..029099e 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 @@ -7,8 +7,8 @@ namespace Drupal\ckeditor_test\Plugin\ckeditor\plugin; +use Drupal\ckeditor\CKEditorPluginBase; use Drupal\ckeditor\CKEditorPluginInterface; -use Drupal\Component\Plugin\PluginBase; use Drupal\Core\Annotation\Plugin; use Drupal\Core\Annotation\Translation; use Drupal\editor\Plugin\Core\Entity\Editor; @@ -31,14 +31,7 @@ * module = "ckeditor_test" * ) */ -class Llama extends PluginBase implements CKEditorPluginInterface { - - /** - * Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::isInternal(). - */ - function isInternal() { - return FALSE; - } +class Llama extends CKEditorPluginBase { /** * Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::getFile(). @@ -47,11 +40,4 @@ function getFile() { return drupal_get_path('module', 'ckeditor_test') . '/js/llama.js'; } - /** - * Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::getButtons(). - */ - public function getConfig(Editor $editor) { - return array(); - } - } diff --git a/core/modules/editor/css/editor.css b/core/modules/editor/css/editor.css new file mode 100644 index 0000000..4f71f1a --- /dev/null +++ b/core/modules/editor/css/editor.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..52f0c47 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,10 @@ function editor_library_info() { 'title' => 'Text Editor', 'version' => VERSION, 'js' => array( - $path . '/js/editor.js' => array(), + $path . '/js/editor.js' => array('group' => JS_DEFAULT), + ), + 'css' => array( + $path . '/css/editor.css' => array('group' => CSS_DEFAULT), ), 'dependencies' => array( array('system', 'jquery'), @@ -79,6 +98,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..70a8d13 --- /dev/null +++ b/core/modules/editor/editor.pages.inc @@ -0,0 +1,118 @@ +' . 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']['alt'] = array( + '#title' => t('Alternative text'), + '#type' => 'textfield', + '#default_value' => isset($input['alt']) ? $input['alt'] : '', + ); + $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..e6b1693 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, 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); +} + })(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), ); diff --git a/core/modules/system/tests/modules/ajax_test/ajax_test.info b/core/modules/system/tests/modules/ajax_test/ajax_test.info index dda7f55..1a65b5a 100644 --- a/core/modules/system/tests/modules/ajax_test/ajax_test.info +++ b/core/modules/system/tests/modules/ajax_test/ajax_test.info @@ -3,4 +3,4 @@ description = Support module for AJAX framework tests. package = Testing version = VERSION core = 8.x -hidden = TRUE +;hidden = TRUE