diff --git a/core/modules/ckeditor5/ckeditor5.libraries.yml b/core/modules/ckeditor5/ckeditor5.libraries.yml index f458a46ea4..d0908635c0 100644 --- a/core/modules/ckeditor5/ckeditor5.libraries.yml +++ b/core/modules/ckeditor5/ckeditor5.libraries.yml @@ -29,6 +29,8 @@ drupal.ckeditor5: - editor/drupal.editor - ckeditor5/drupal.ckeditor5.quickedit-temporary-work-around - ckeditor5/drupal.ckeditor5.stylesheets + - core/drupalSettings + - core/drupal.message # Library used for dynamically loading CKEditor 5 stylesheets from the default # front end theme. @@ -95,6 +97,8 @@ drupal.ckeditor5.filter.admin: - core/drupal.message - core/once - core/drupal.ajax + - core/drupalSettings + - core/drupal.message admin: js: diff --git a/core/modules/ckeditor5/ckeditor5.module b/core/modules/ckeditor5/ckeditor5.module index fa4ec4c2ce..c57091491b 100644 --- a/core/modules/ckeditor5/ckeditor5.module +++ b/core/modules/ckeditor5/ckeditor5.module @@ -355,6 +355,38 @@ function ckeditor5_library_info_alter(&$libraries, $extension) { }, array_flip($css)), ], ]; + + $admin_theme = \Drupal::configFactory()->get('system.theme')->get('admin'); + $admin_theme_info = \Drupal::service('theme_handler')->listInfo()[$admin_theme]->info; + + $opt_out_ckeditor5_stylesheets = (isset($admin_theme_info['ckeditor5-stylesheets']) && $admin_theme_info['ckeditor5-info'] !== FALSE); + + // Check if: + // - We are on an admin page. + // - No CKEditor 5 stylesheets were discovered. + // - The admin theme has not explicitly opted out of CKEditor 5 stylesheets + // by setting its ckeditor5-stylesheets property to FALSE. + // If the following are all true, we check if any ckeditor_stylesheets are + // being added to the default theme. If so, then a warning is generated to + // make users aware that stylesheets they expect to load with their editor + // may not be present. + if (\Drupal::service('router.admin_context')->isAdminRoute() && empty($css) && !$opt_out_ckeditor5_stylesheets) { + $default_theme = \Drupal::configFactory()->get('system.theme')->get('default'); + $default_theme_info = \Drupal::service('theme_handler')->listInfo()[$default_theme]->info; + $ckeditor4_stylesheets = $default_theme_info['ckeditor_stylesheets'] ?? []; + + $base_theme = $default_theme_info['base_theme'] ?? FALSE; + while ($base_theme) { + $base_theme_info = \Drupal::service('theme_handler')->listInfo()[$base_theme]->info; + $base_theme_ckeditor4_stylesheets = $base_theme_info['ckeditor_stylesheets'] ?? []; + $ckeditor4_stylesheets = array_merge($ckeditor4_stylesheets, $base_theme_ckeditor4_stylesheets); + $base_theme = $base_theme_info['base_theme'] ?? FALSE; + } + if (!empty($ckeditor4_stylesheets)) { + $libraries['drupal.ckeditor5']['drupalSettings']['ckeditor5']['ckeditor4_stylesheets'] = $ckeditor4_stylesheets; + $libraries['drupal.ckeditor5.filter.admin']['drupalSettings']['ckeditor5']['ckeditor4_stylesheets'] = $ckeditor4_stylesheets; + } + } } if ($extension === 'core') { diff --git a/core/modules/ckeditor5/js/ckeditor5.es6.js b/core/modules/ckeditor5/js/ckeditor5.es6.js index 6716e8d2c0..a21b543a79 100644 --- a/core/modules/ckeditor5/js/ckeditor5.es6.js +++ b/core/modules/ckeditor5/js/ckeditor5.es6.js @@ -3,7 +3,7 @@ * CKEditor 5 implementation of {@link Drupal.editors} API. */ /* global CKEditor5 */ -((Drupal, debounce, CKEditor5, $) => { +((Drupal, debounce, CKEditor5, $, drupalSettings) => { /** * The CKEDITOR instances. * @@ -354,6 +354,23 @@ // eslint-disable-next-line no-console console.error(error); }); + + if ( + drupalSettings.ckeditor5 && + drupalSettings.ckeditor5.ckeditor4_stylesheets + ) { + const editorMessageContainer = document.createElement('div'); + element.parentNode.insertBefore(editorMessageContainer, element); + const editorMessages = new Drupal.Message(editorMessageContainer); + editorMessages.add( + Drupal.t( + 'There are CKEditor 4 stylesheets that may need CKeditor 5 equivalents.', + ), + { + type: 'warning', + }, + ); + } }, /** @@ -580,4 +597,4 @@ Drupal.ckeditor5.saveCallback = null; } }); -})(Drupal, Drupal.debounce, CKEditor5, jQuery); +})(Drupal, Drupal.debounce, CKEditor5, jQuery, drupalSettings); diff --git a/core/modules/ckeditor5/js/ckeditor5.filter.admin.es6.js b/core/modules/ckeditor5/js/ckeditor5.filter.admin.es6.js index 69b8643905..7735acb179 100644 --- a/core/modules/ckeditor5/js/ckeditor5.filter.admin.es6.js +++ b/core/modules/ckeditor5/js/ckeditor5.filter.admin.es6.js @@ -3,7 +3,7 @@ * Provides Text Editor UI improvements specific to CKEditor 5. */ -((Drupal, once) => { +((Drupal, once, drupalSettings) => { Drupal.behaviors.allowedTagsListener = { attach: function attach(context) { once( @@ -111,6 +111,113 @@ }, }; + Drupal.behaviors.ckEditor5StylesheetsWarn = { + attach: function attach() { + const editorSelect = once( + 'editor-select-stylesheet-warning', + document.querySelector( + '#filter-format-edit-form #edit-editor-editor, #filter-format-add-form #edit-editor-editor', + ), + ); + + if ( + typeof editorSelect[0] !== 'undefined' && + drupalSettings.ckeditor5 && + drupalSettings.ckeditor5.ckeditor4_stylesheets + ) { + const select = editorSelect[0]; + + // Add a container for messages above the text format select element. + const selectMessageContainer = document.createElement('div'); + select.parentNode.insertBefore(selectMessageContainer, select); + const selectMessages = new Drupal.Message(selectMessageContainer); + const editorSettings = document.querySelector( + '#editor-settings-wrapper', + ); + + /** + * Adds a ckeditor_stylesheets warning to the message container. + */ + const ck5Warning = () => { + selectMessages.add( + Drupal.t( + 'There are CKEditor 4 stylesheets without equivalents: @stylesheets', + { + '@stylesheets': + drupalSettings.ckeditor5.ckeditor4_stylesheets.join(', '), + }, + ), + { + type: 'warning', + }, + ); + }; + + /** + * Adds a warning if the selected editor is ckeditor5, otherwise clears + * the message container. + */ + const updateWarningStatus = () => { + if ( + select.value === 'ckeditor5' && + !select.classList.contains('error') + ) { + ck5Warning(); + } else { + editorSettings.hidden = false; + selectMessages.clear(); + } + }; + + const selectChangeHandler = () => { + // Declare the observer first so the observer callback can access it. + let editorSelectObserver = null; + + /** + * MutationObserver callback for the editor select. + * + * This listens for the removal 'disabled' attribute on the