diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/Editor/CKEditor.php b/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/Editor/CKEditor.php index 03f37e1..29691e8 100644 --- a/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/Editor/CKEditor.php +++ b/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/Editor/CKEditor.php @@ -23,6 +23,7 @@ * @Editor( * id = "ckeditor", * label = @Translation("CKEditor"), + * supports_content_filtering = TRUE, * supports_inline_editing = TRUE * ) */ diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorLoadingTest.php b/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorLoadingTest.php index 039ee24..e97c7e7 100644 --- a/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorLoadingTest.php +++ b/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorLoadingTest.php @@ -103,6 +103,7 @@ function testLoading() { 'format' => 'filtered_html', 'editor' => 'ckeditor', 'editorSettings' => $ckeditor_plugin->getJSSettings($editor), + 'editorSupportsContentFiltering' => TRUE, ))); $this->assertTrue($editor_settings_present, "Text Editor module's JavaScript settings are on the page."); $this->assertIdentical($expected, $settings['editor'], "Text Editor module's JavaScript settings on the page are correct."); @@ -129,6 +130,7 @@ function testLoading() { 'format' => 'filtered_html', 'editor' => 'ckeditor', 'editorSettings' => $ckeditor_plugin->getJSSettings($editor), + 'editorSupportsContentFiltering' => TRUE, ))); $this->assertTrue($editor_settings_present, "Text Editor module's JavaScript settings are on the page."); $this->assertIdentical($expected, $settings['editor'], "Text Editor module's JavaScript settings on the page are correct."); diff --git a/core/modules/editor/editor.module b/core/modules/editor/editor.module index affe711..9e05d99 100644 --- a/core/modules/editor/editor.module +++ b/core/modules/editor/editor.module @@ -93,6 +93,7 @@ function editor_library_info() { array('system', 'drupal'), array('system', 'drupalSettings'), array('system', 'jquery.once'), + array('system', 'drupal.dialog'), ), ); diff --git a/core/modules/editor/js/editor.js b/core/modules/editor/js/editor.js index c1cdcbc..62733f4 100644 --- a/core/modules/editor/js/editor.js +++ b/core/modules/editor/js/editor.js @@ -3,11 +3,118 @@ * Attaches behavior for the Editor module. */ -(function ($, Drupal) { +(function ($, Drupal, drupalSettings) { "use strict"; /** + * Finds the text area field associated with the given text format selector. + * + * @param jQuery $formatSelector + * A text format selector DOM element. + * + * @return DOM + * The text area DOM element. + */ +function findFieldForFormatSelector ($formatSelector) { + var field_id = $formatSelector.attr('data-editor-for'); + return $('#' + field_id).get(0); +} + +/** + * Changes the text editor on the text area for the given text format selector. + * + * @param jQuery $formatSelector + * A text format selector DOM element. + * @param String activeFormatID + * The currently active text format; its associated text editor will be + * detached. + * @param String newFormatID + * The text format we're changing to; its associated text editor will be + * attached. + */ +function changeTextEditor ($formatSelector, activeFormatID, newFormatID) { + var field = findFieldForFormatSelector($formatSelector); + // Detach the current editor (if any) and attach a new editor. + if (drupalSettings.editor.formats[activeFormatID]) { + Drupal.editorDetach(field, drupalSettings.editor.formats[activeFormatID]); + } + activeFormatID = newFormatID; + if (drupalSettings.editor.formats[activeFormatID]) { + Drupal.editorAttach(field, drupalSettings.editor.formats[activeFormatID]); + } + $formatSelector.attr('data-editor-active-text-format', newFormatID); +} + +/** + * Handles changes in text format. + * + * @param jQuery.Event event + */ +function onTextFormatChange (event) { + var $select = $(event.target); + var activeFormatID = $select.attr('data-editor-active-text-format'); + var newFormatID = $select.val(); + + // Prevent double-attaching if the change event is triggered manually. + if (newFormatID === activeFormatID) { + return; + } + + // When changing to a text format that has a text editor associated + // with it that supports content filtering, then first ask for + // confirmation, because switching text formats might cause certain + // markup to be stripped away. + if (drupalSettings.editor.formats[newFormatID] && drupalSettings.editor.formats[newFormatID].editorSupportsContentFiltering) { + var message = Drupal.t('Changing the text format to %text_format will permanently remove content that is not allowed in that text format.<br><br>Save your changes before switching the text format to avoid losing data.', { + '%text_format': $select.find('option:selected').text() + }); + var confirmationDialog = Drupal.dialog('<div>' + message + '</div>', { + title: Drupal.t('Change text format?'), + dialogClass: 'editor-change-text-format-modal', + resizable: false, + buttons: [ + { + text: Drupal.t('Continue'), + 'class': 'button button--primary', + click: function () { + changeTextEditor($select, activeFormatID, newFormatID); + confirmationDialog.close(); + } + }, + { + text: Drupal.t('Cancel'), + 'class': 'button', + click: function () { + // Restore the active format ID: cancel changing text format. We cannot + // simply call event.preventDefault() because jQuery's change event is + // only triggered after the change has already been accepted. + $select.val(activeFormatID); + confirmationDialog.close(); + } + } + ], + // Prevent this modal from being closed without the user making a choice + // as per http://stackoverflow.com/a/5438771. + closeOnEscape: false, + create: function () { + $(this).parent().find('.ui-dialog-titlebar-close').remove(); + }, + beforeClose: false, + close: function (event) { + // Automatically destroy the DOM element that was used for the dialog. + $(event.target).remove(); + } + }); + + confirmationDialog.showModal(); + } + else { + changeTextEditor($select, activeFormatID, newFormatID); + } +} + +/** * Initialize an empty object for editors to place their attachment code. */ Drupal.editors = {}; @@ -22,12 +129,11 @@ Drupal.behaviors.editor = { return; } - var $context = $(context); - var behavior = this; - $context.find('.editor').once('editor', function () { + $(context).find('.editor').once('editor', function () { var $this = $(this); var activeFormatID = $this.val(); - var field = behavior.findFieldForFormatSelector($this); + $this.attr('data-editor-active-text-format', activeFormatID); + var field = findFieldForFormatSelector($this); // Directly attach this editor, if the text format is enabled. if (settings.editor.formats[activeFormatID]) { @@ -36,23 +142,7 @@ Drupal.behaviors.editor = { // Attach onChange handler to text format selector element. if ($this.is('select')) { - $this.on('change.editorAttach', function () { - var newFormatID = $this.val(); - - // Prevent double-attaching if the change event is triggered manually. - if (newFormatID === activeFormatID) { - return; - } - - // Detach the current editor (if any) and attach a new editor. - if (settings.editor.formats[activeFormatID]) { - Drupal.editorDetach(field, settings.editor.formats[activeFormatID]); - } - activeFormatID = newFormatID; - if (settings.editor.formats[activeFormatID]) { - Drupal.editorAttach(field, settings.editor.formats[activeFormatID]); - } - }); + $this.on('change.editorAttach', onTextFormatChange); } // Detach any editor when the containing form is submitted. $this.parents('form').on('submit', function (event) { @@ -81,20 +171,14 @@ Drupal.behaviors.editor = { editors = $(context).find('.editor').removeOnce('editor'); } - var behavior = this; editors.each(function () { var $this = $(this); var activeFormatID = $this.val(); - var field = behavior.findFieldForFormatSelector($this); + var field = findFieldForFormatSelector($this); if (activeFormatID in settings.editor.formats) { Drupal.editorDetach(field, settings.editor.formats[activeFormatID], trigger); } }); - }, - - findFieldForFormatSelector: function ($formatSelector) { - var field_id = $formatSelector.attr('data-editor-for'); - return $('#' + field_id).get(0); } }; @@ -133,4 +217,4 @@ Drupal.editorDetach = function (field, format, trigger) { } }; -})(jQuery, Drupal); +})(jQuery, Drupal, drupalSettings); diff --git a/core/modules/editor/lib/Drupal/editor/Annotation/Editor.php b/core/modules/editor/lib/Drupal/editor/Annotation/Editor.php index fd9b049..be126b4 100644 --- a/core/modules/editor/lib/Drupal/editor/Annotation/Editor.php +++ b/core/modules/editor/lib/Drupal/editor/Annotation/Editor.php @@ -33,6 +33,13 @@ class Editor extends Plugin { public $label; /** + * Whether the editor supports "allowed content only" filtering. + * + * @var boolean + */ + public $supports_content_filtering; + + /** * Whether the editor supports the inline editing provided by the Edit module. * * @var boolean diff --git a/core/modules/editor/lib/Drupal/editor/Plugin/EditorBase.php b/core/modules/editor/lib/Drupal/editor/Plugin/EditorBase.php index 5610d33..20a4169 100644 --- a/core/modules/editor/lib/Drupal/editor/Plugin/EditorBase.php +++ b/core/modules/editor/lib/Drupal/editor/Plugin/EditorBase.php @@ -24,6 +24,8 @@ * - id: The unique, system-wide identifier of the text editor. Typically named * the same as the editor library. * - label: The human-readable name of the text editor, translated. + * - supports_content_filtering: Whether the editor supports "allowed content + * only" filtering. * - supports_inline_editing: Whether the editor supports the inline editing * provided by the Edit module. * @@ -33,6 +35,7 @@ * @Editor( * id = "myeditor", * label = @Translation("My Editor"), + * supports_content_filtering = FALSE, * supports_inline_editing = FALSE * ) * @endcode diff --git a/core/modules/editor/lib/Drupal/editor/Plugin/EditorManager.php b/core/modules/editor/lib/Drupal/editor/Plugin/EditorManager.php index fcc2076..558cfde 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) { } $plugin = $this->createInstance($editor->editor); + $plugin_definition = $plugin->getPluginDefinition(); // Libraries. $attachments['library'] = array_merge($attachments['library'], $plugin->getLibraries($editor)); @@ -81,6 +82,7 @@ public function getAttachments(array $format_ids) { 'format' => $format_id, 'editor' => $editor->editor, 'editorSettings' => $plugin->getJSSettings($editor), + 'editorSupportsContentFiltering' => $plugin_definition['supports_content_filtering'], ); } diff --git a/core/modules/editor/lib/Drupal/editor/Tests/EditorLoadingTest.php b/core/modules/editor/lib/Drupal/editor/Tests/EditorLoadingTest.php index 640530e..ffbf9a8 100644 --- a/core/modules/editor/lib/Drupal/editor/Tests/EditorLoadingTest.php +++ b/core/modules/editor/lib/Drupal/editor/Tests/EditorLoadingTest.php @@ -96,6 +96,7 @@ public function testLoading() { 'format' => 'full_html', 'editor' => 'unicorn', 'editorSettings' => array('ponyModeEnabled' => TRUE), + 'editorSupportsContentFiltering' => TRUE, ))); $this->assertTrue($editor_settings_present, "Text Editor module's JavaScript settings are on the page."); $this->assertIdentical($expected, $settings['editor'], "Text Editor module's JavaScript settings on the page are correct."); @@ -123,6 +124,7 @@ public function testLoading() { 'format' => 'plain_text', 'editor' => 'unicorn', 'editorSettings' => array('ponyModeEnabled' => TRUE), + 'editorSupportsContentFiltering' => TRUE, ))); $this->assertTrue($editor_settings_present, "Text Editor module's JavaScript settings are on the page."); $this->assertIdentical($expected, $settings['editor'], "Text Editor module's JavaScript settings on the page are correct."); diff --git a/core/modules/editor/lib/Drupal/editor/Tests/EditorManagerTest.php b/core/modules/editor/lib/Drupal/editor/Tests/EditorManagerTest.php index 5f2ac0a..78140c7 100644 --- a/core/modules/editor/lib/Drupal/editor/Tests/EditorManagerTest.php +++ b/core/modules/editor/lib/Drupal/editor/Tests/EditorManagerTest.php @@ -104,6 +104,7 @@ public function testManager() { 'format' => 'full_html', 'editor' => 'unicorn', 'editorSettings' => $unicorn_plugin->getJSSettings($editor), + 'editorSupportsContentFiltering' => TRUE, ) ))) ) diff --git a/core/modules/editor/tests/modules/lib/Drupal/editor_test/Plugin/Editor/UnicornEditor.php b/core/modules/editor/tests/modules/lib/Drupal/editor_test/Plugin/Editor/UnicornEditor.php index 8944cee..e6b34cd 100644 --- a/core/modules/editor/tests/modules/lib/Drupal/editor_test/Plugin/Editor/UnicornEditor.php +++ b/core/modules/editor/tests/modules/lib/Drupal/editor_test/Plugin/Editor/UnicornEditor.php @@ -16,6 +16,7 @@ * @Editor( * id = "unicorn", * label = @Translation("Unicorn Editor"), + * supports_content_filtering = TRUE, * supports_inline_editing = TRUE * ) */