Update to Drupal 8.0.0-beta15. For more information, see: https://www.drupal.org/node/2563023

This commit is contained in:
Pantheon Automation 2015-09-04 13:20:09 -07:00 committed by Greg Anderson
parent 2720a9ec4b
commit f3791f1da3
1898 changed files with 54300 additions and 11481 deletions

View file

@ -7,7 +7,6 @@
use Drupal\Component\Utility\Html;
use Drupal\Core\Template\Attribute;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Language\LanguageInterface;
/**
@ -66,10 +65,10 @@ function template_preprocess_ckeditor_settings_toolbar(&$variables) {
$build_button_item = function($button, $rtl) {
// Value of the button item.
if (isset($button['image_alternative' . $rtl])) {
$value = SafeMarkup::set($button['image_alternative' . $rtl]);
$value = $button['image_alternative' . $rtl];
}
elseif (isset($button['image_alternative'])) {
$value = SafeMarkup::set($button['image_alternative']);
$value = $button['image_alternative'];
}
elseif (isset($button['image'])) {
$value = array(

View file

@ -73,3 +73,5 @@ drupal.ckeditor.stylescombo.admin:
- core/jquery.once
- core/drupal.vertical-tabs
- core/drupalSettings
# Ensure to run after ckeditor/drupal.ckeditor.admin.
- ckeditor/drupal.ckeditor.admin

View file

@ -16,7 +16,7 @@ function ckeditor_help($route_name, RouteMatchInterface $route_match) {
case 'help.page.ckeditor':
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('The CKEditor module provides a visual text editor and adds a toolbar to text fields. Users can use buttons to format content and to create semantically correct and valid HTML. The CKEditor module uses the framework provided by the <a href="!text_editor">Text Editor module</a>. It requires JavaScript to be enabled in the browser. For more information, see <a href="!doc_url">the online documentation for the CKEditor module</a> and the <a href="!cke_url">CKEditor website</a>.', array( '!doc_url' => 'https://www.drupal.org/documentation/modules/ckeditor', '!cke_url' => 'http://ckeditor.com', '!text_editor' => \Drupal::url('help.page', array('name' => 'editor')))) . '</p>';
$output .= '<p>' . t('The CKEditor module provides a highly-accessible, highly-usable visual text editor and adds a toolbar to text fields. Users can use buttons to format content and to create semantically correct and valid HTML. The CKEditor module uses the framework provided by the <a href="!text_editor">Text Editor module</a>. It requires JavaScript to be enabled in the browser. For more information, see <a href="!doc_url">the online documentation for the CKEditor module</a> and the <a href="!cke_url">CKEditor website</a>.', array( '!doc_url' => 'https://www.drupal.org/documentation/modules/ckeditor', '!cke_url' => 'http://ckeditor.com', '!text_editor' => \Drupal::url('help.page', array('name' => 'editor')))) . '</p>';
$output .= '<h3>' . t('Uses') . '</h3>';
$output .= '<dl>';
$output .= '<dt>' . t('Enabling CKEditor for individual text formats') . '</dt>';
@ -27,6 +27,10 @@ function ckeditor_help($route_name, RouteMatchInterface $route_match) {
$output .= '<dd>' . t('CKEditor only allow users to format content in accordance with the filter configuration of the specific text format. If a text format excludes certain HTML tags, the corresponding toolbar buttons are not displayed to users when they edit a text field in this format. For more information see the <a href="!filter">Filter help page</a>.', array('!filter' => \Drupal::url('help.page', array('name' => 'filter')))) . '</dd>';
$output .= '<dt>' . t('Toggling between formatted text and HTML source') . '</dt>';
$output .= '<dd>' . t('If the <em>Source</em> button is available in the toolbar, users can click this button to disable the visual editor and edit the HTML source directly. After toggling back, the visual editor uses the allowed HTML tags to format the text — independent of whether buttons for these tags are available in the toolbar. If the text format is set to <em>limit the use of HTML tags</em>, then all excluded tags will be stripped out of the HTML source when the user toggles back to the text editor.') . '</dd>';
$output .= '<dt>' . t('Accessibility features') . '</dt>';
$output .= '<dd>' . t('The built in WYSIWYG editor (CKEditor) comes with a number of <a href="!features">accessibility features</a>. CKEditor comes with built in <a href="!shortcuts">keyboard shortcuts</a>, which can be beneficial for both power users and keyboard only users.', array('!features' => 'http://docs.ckeditor.com/#!/guide/dev_a11y', '!shortcuts' => 'http://docs.ckeditor.com/#!/guide/dev_shortcuts')) . '</dd>';
$output .= '<dt>' . t('Generating accessible content') . '</dt>';
$output .= '<dd>' . t('HTML tables can be created with both table headers as well as caption/summary elements. Alt text is required by default on images added through CKEditor (note that this can be overridden). Semantic HTML5 figure/figcaption are available to add captions to images.') . '</dd>';
$output .= '</dl>';
return $output;
}
@ -96,78 +100,3 @@ function _ckeditor_theme_css($theme = NULL) {
}
return $css;
}
/**
* Implements hook_ENTITY_TYPE_insert() for 'filter_format'.
*
* Recalculates the 'format_tags' CKEditor setting when a text format is added.
*
* @see \Drupal\ckeditor\Plugin\CKEditorPlugin\Internal::generateFormatTagsSetting()
* @see ckeditor_rebuild()
*/
function ckeditor_filter_format_insert() {
ckeditor_rebuild();
}
/**
* Implements hook_ENTITY_TYPE_update() for 'filter_format'.
*
* Recalculates the 'format_tags' CKEditor setting when a text format changes.
*
* @see \Drupal\ckeditor\Plugin\CKEditorPlugin\Internal::generateFormatTagsSetting()
* @see ckeditor_rebuild()
*/
function ckeditor_filter_format_update() {
ckeditor_rebuild();
}
/**
* Implements hook_rebuild().
*
* Calculates the 'format_tags' CKEditor setting for each text format.
*
* If this wouldn't happen in hook_rebuild(), then the first drupal_render()
* call that occurs for a page that contains a #type 'text_format' element will
* cause the CKEditor::getJSSettings() to be called, which will cause
* Internal::generateFormatTagsSetting() to be called, which calls
* check_markup(), which finally calls drupal_render() non-recursively, because
* a filter might add placeholders to replace.
* This would be a root call inside a root call, which breaks the stack-based
* logic for bubbling rendering metadata.
* Therefore this pre-calculates the needed values, and hence performs the
* check_markup() calls outside of a drupal_render() call tree.
*
* @see \Drupal\ckeditor\Plugin\CKEditorPlugin\Internal::generateFormatTagsSetting()
* @see ckeditor_filter_format_insert()
* @see ckeditor_filter_format_update()
*/
function ckeditor_rebuild() {
/** @var \Drupal\filter\FilterFormatInterface[] $formats */
$formats = filter_formats();
foreach ($formats as $format) {
$key = 'ckeditor_internal_format_tags:' . $format->id();
// The <p> tag is always allowed — HTML without <p> tags is nonsensical.
$format_tags = array('p');
// Given the list of possible format tags, automatically determine whether
// the current text format allows this tag, and thus whether it should show
// up in the "Format" dropdown.
$possible_format_tags = array('h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'pre');
foreach ($possible_format_tags as $tag) {
$input = '<' . $tag . '>TEST</' . $tag . '>';
$output = trim(check_markup($input, $format->id()));
if ($input == $output) {
$format_tags[] = $tag;
}
}
$format_tags = implode(';', $format_tags);
// Cache the "format_tags" configuration. This cache item is infinitely
// valid; it only changes whenever the text format is changed, which is
// guaranteed by the hook_ENTITY_TYPE_update() and hook_ENTITY_TYPE_insert()
// hook implementations.
\Drupal::state()->set($key, $format_tags);
}
}

View file

@ -10,7 +10,14 @@
Drupal.ckeditor = Drupal.ckeditor || {};
/**
* Sets config behaviour and creates config views for the CKEditor toolbar.
*
* @type {Drupal~behavior}
*
* @prop {Drupal~behaviorAttach} attach
* Attaches admin behaviour to the CKEditor buttons.
* @prop {Drupal~behaviorDetach} detach
* Detaches admin behaviour from the CKEditor buttons on 'unload'.
*/
Drupal.behaviors.ckeditorAdmin = {
attach: function (context) {
@ -18,14 +25,14 @@
var $configurationForm = $(context).find('.ckeditor-toolbar-configuration').once('ckeditor-configuration');
if ($configurationForm.length) {
var $textarea = $configurationForm
// Hide the textarea that contains the serialized representation of the
// CKEditor configuration.
// Hide the textarea that contains the serialized representation of
// the CKEditor configuration.
.find('.form-item-editor-settings-toolbar-button-groups')
.hide()
// Return the textarea child node from this expression.
.find('textarea');
// The HTML for the CKEditor configuration is assembled on the server and
// The HTML for the CKEditor configuration is assembled on the server
// and sent to the client as a serialized DOM fragment.
$configurationForm.append(drupalSettings.ckeditor.toolbarAdmin);
@ -50,15 +57,16 @@
}
},
detach: function (context, settings, trigger) {
// Early-return if the trigger for detachment is something else than unload.
// Early-return if the trigger for detachment is something else than
// unload.
if (trigger !== 'unload') {
return;
}
// We're detaching because CKEditor as text editor has been disabled; this
// really means that all CKEditor toolbar buttons have been removed. Hence,
// all editor features will be removed, so any reactions from filters will
// be undone.
// really means that all CKEditor toolbar buttons have been removed.
// Hence,all editor features will be removed, so any reactions from
// filters will be undone.
var $configurationForm = $(context).find('.ckeditor-toolbar-configuration').findOnce('ckeditor-configuration');
if ($configurationForm.length && Drupal.ckeditor.models && Drupal.ckeditor.models.Model) {
var config = Drupal.ckeditor.models.Model.toJSON().activeEditorConfig;
@ -93,20 +101,21 @@
models: {},
/**
* Translates a change in CKEditor config DOM structure into the config model.
* Translates changes in CKEditor config DOM structure to the config model.
*
* If the button is moved within an existing group, the DOM structure is simply
* translated to a configuration model. If the button is moved into a new group
* placeholder, then a process is launched to name that group before the button
* move is translated into configuration.
* If the button is moved within an existing group, the DOM structure is
* simply translated to a configuration model. If the button is moved into a
* new group placeholder, then a process is launched to name that group
* before the button move is translated into configuration.
*
* @param {Backbone.View} view
* The Backbone View that invoked this function.
* @param {jQuery} $button
* A jQuery set that contains an li element that wraps a button element.
* @param {function} callback
* A callback to invoke after the button group naming modal dialog has been
* closed.
* A callback to invoke after the button group naming modal dialog has
* been closed.
*
*/
registerButtonMove: function (view, $button, callback) {
var $group = $button.closest('.ckeditor-toolbar-group');
@ -127,10 +136,11 @@
},
/**
* Translates a change in CKEditor config DOM structure into the config model.
* Translates changes in CKEditor config DOM structure to the config model.
*
* Each row has a placeholder group at the end of the row. A user may not move
* an existing button group past the placeholder group at the end of a row.
* Each row has a placeholder group at the end of the row. A user may not
* move an existing button group past the placeholder group at the end of a
* row.
*
* @param {Backbone.View} view
* The Backbone View that invoked this function.
@ -155,15 +165,15 @@
},
/**
* Opens a Drupal dialog with a form for changing the title of a button group.
* Opens a dialog with a form for changing the title of a button group.
*
* @param {Backbone.View} view
* The Backbone View that invoked this function.
* @param {jQuery} $group
* A jQuery set that contains an li element that wraps a group of buttons.
* @param {function} callback
* A callback to invoke after the button group naming modal dialog has been
* closed.
* A callback to invoke after the button group naming modal dialog has
* been closed.
*/
openGroupNameDialog: function (view, $group, callback) {
callback = callback || function () {};
@ -172,8 +182,8 @@
* Validates the string provided as a button group title.
*
* @param {HTMLElement} form
* The form DOM element that contains the input with the new button group
* title string.
* The form DOM element that contains the input with the new button
* group title string.
*
* @return {bool}
* Returns true when an error exists, otherwise returns false.
@ -200,8 +210,8 @@
* @param {string} action
* The dialog action chosen by the user: 'apply' or 'cancel'.
* @param {HTMLElement} form
* The form DOM element that contains the input with the new button group
* title string.
* The form DOM element that contains the input with the new button
* group title string.
*/
function closeDialog(action, form) {
@ -211,7 +221,8 @@
function shutdown() {
dialog.close(action);
// The processing marker can be deleted since the dialog has been closed.
// The processing marker can be deleted since the dialog has been
// closed.
delete view.isProcessing;
}
@ -219,13 +230,14 @@
* Applies a string as the name of a CKEditor button group.
*
* @param {jQuery} $group
* A jQuery set that contains an li element that wraps a group of buttons.
* A jQuery set that contains an li element that wraps a group of
* buttons.
* @param {string} name
* The new name of the CKEditor button group.
*/
function namePlaceholderGroup($group, name) {
// If it's currently still a placeholder, then that means we're creating
// a new group, and we must do some extra work.
// If it's currently still a placeholder, then that means we're
// creating a new group, and we must do some extra work.
if ($group.hasClass('placeholder')) {
// Remove all whitespace from the name, lowercase it and ensure
// HTML-safe encoding, then use this as the group ID for CKEditor
@ -338,12 +350,13 @@
$(event.target).remove();
}
});
// A modal dialog is used because the user must provide a button group name
// or cancel the button placement before taking any other action.
// A modal dialog is used because the user must provide a button group
// name or cancel the button placement before taking any other action.
dialog.showModal();
$(document.querySelector('.ckeditor-name-toolbar-group').querySelector('input'))
// When editing, set the "group name" input in the form to the current value.
// When editing, set the "group name" input in the form to the current
// value.
.attr('value', $group.attr('data-drupal-ckeditor-toolbar-group-name'))
// Focus on the "group name" input in the form.
.trigger('focus');
@ -355,6 +368,9 @@
* Automatically shows/hides settings of buttons-only CKEditor plugins.
*
* @type {Drupal~behavior}
*
* @prop {Drupal~behaviorAttach} attach
* Attaches show/hide behaviour to Plugin Settings buttons.
*/
Drupal.behaviors.ckeditorAdminButtonPluginSettings = {
attach: function (context) {
@ -374,10 +390,11 @@
$this.data('ckeditorButtonPluginSettingsActiveButtons', []);
});
// Whenever a button is added or removed, check if we should show or hide
// the corresponding plugin settings. (Note that upon initialization, each
// button that already is part of the toolbar still is considered "added",
// hence it also works correctly for buttons that were added previously.)
// Whenever a button is added or removed, check if we should show or
// hide the corresponding plugin settings. (Note that upon
// initialization, each button that already is part of the toolbar still
// is considered "added", hence it also works correctly for buttons that
// were added previously.)
$context
.find('.ckeditor-toolbar-active')
.off('CKEditorToolbarChanged.ckeditorAdminPluginSettings')
@ -428,6 +445,7 @@
* Themes a blank CKEditor row.
*
* @return {string}
* A HTML string for a CKEditor row.
*/
Drupal.theme.ckeditorRow = function () {
return '<li class="ckeditor-row placeholder" role="group"><ul class="ckeditor-toolbar-groups clearfix"></ul></li>';
@ -437,6 +455,7 @@
* Themes a blank CKEditor button group.
*
* @return {string}
* A HTML string for a CKEditor button group.
*/
Drupal.theme.ckeditorToolbarGroup = function () {
var group = '';
@ -451,6 +470,7 @@
* Themes a form for changing the title of a CKEditor button group.
*
* @return {string}
* A HTML string for the form for the title of a CKEditor button group.
*/
Drupal.theme.ckeditorButtonGroupNameForm = function () {
return '<form><input name="group-name" required="required"></form>';
@ -460,6 +480,7 @@
* Themes a button that will toggle the button group names in active config.
*
* @return {string}
* A HTML string for the button to toggle group names.
*/
Drupal.theme.ckeditorButtonGroupNamesToggle = function () {
return '<a class="ckeditor-groupnames-toggle" role="button" aria-pressed="false"></a>';
@ -469,6 +490,7 @@
* Themes a button that will prompt the user to name a new button group.
*
* @return {string}
* A HTML string for the button to create a name for a new button group.
*/
Drupal.theme.ckeditorNewButtonGroup = function () {
return '<li class="ckeditor-add-new-group"><button role="button" aria-label="' + Drupal.t('Add a CKEditor button group to the end of this row.') + '">' + Drupal.t('Add group') + '</button></li>';

View file

@ -11,6 +11,9 @@
* Provides the summary for the "drupalimage" plugin settings vertical tab.
*
* @type {Drupal~behavior}
*
* @prop {Drupal~behaviorAttach} attach
* Attaches summary behaviour to the "drupalimage" settings vertical tab.
*/
Drupal.behaviors.ckeditorDrupalImageSettingsSummary = {
attach: function () {

View file

@ -16,9 +16,12 @@
* Editor attach callback.
*
* @param {HTMLElement} element
* The element to attach the editor to.
* @param {string} format
* The text format for the editor.
*
* @return {bool}
* Whether the call to `CKEDITOR.replace()` created an editor or not.
*/
attach: function (element, format) {
this._loadExternalPlugins(format);
@ -46,10 +49,15 @@
* Editor detach callback.
*
* @param {HTMLElement} element
* The element to detach the editor from.
* @param {string} format
* The text format used for the editor.
* @param {string} trigger
* The event trigger for the detach.
*
* @return {bool}
* Whether the call to `CKEDITOR.dom.element.get(element).getEditor()`
* found an editor or not.
*/
detach: function (element, format, trigger) {
var editor = CKEDITOR.dom.element.get(element).getEditor();
@ -66,11 +74,16 @@
},
/**
* Reacts on a change in the editor element.
*
* @param {HTMLElement} element
* The element where the change occured.
* @param {function} callback
* Callback called with the value of the editor.
*
* @return {bool}
* Whether the call to `CKEDITOR.dom.element.get(element).getEditor()`
* found an editor or not.
*/
onChange: function (element, callback) {
var editor = CKEDITOR.dom.element.get(element).getEditor();
@ -83,13 +96,19 @@
},
/**
* Attaches an inline editor to a DOM element.
*
* @param {HTMLElement} element
* The element to attach the editor to.
* @param {object} format
* @param {string} mainToolbarId
* @param {string} floatedToolbarId
* The text format used in the editor.
* @param {string} [mainToolbarId]
* The id attribute for the main editor toolbar, if any.
* @param {string} [floatedToolbarId]
* The id attribute for the floated editor toolbar, if any.
*
* @return {bool}
* Whether the call to `CKEDITOR.replace()` created an editor or not.
*/
attachInlineEditor: function (element, format, mainToolbarId, floatedToolbarId) {
this._loadExternalPlugins(format);
@ -143,7 +162,10 @@
},
/**
* Loads the required external plugins for the editor.
*
* @param {object} format
* The text format used in the editor.
*/
_loadExternalPlugins: function (format) {
var externalPlugins = format.editorSettings.drupalExternalPlugins;
@ -213,7 +235,7 @@
dialogType: 'modal',
selector: '.ckeditor-dialog-loading-link',
url: url,
progress: {'type': 'throbber'},
progress: {type: 'throbber'},
submit: {
editor_object: existingValues
}

View file

@ -1,6 +1,6 @@
/**
* @file
* CKEditor SylesCombo admin behavior.
* CKEditor StylesCombo admin behavior.
*/
(function ($, Drupal, drupalSettings) {
@ -11,21 +11,25 @@
* Ensures that the "stylescombo" button's metadata remains up-to-date.
*
* Triggers the CKEditorPluginSettingsChanged event whenever the "stylescombo"
* plugin settings change, to ensure that the corresponding feature metadata is
* immediately updated i.e. ensure that HTML tags and classes entered here are
* known to be "required", which may affect filter settings.
* plugin settings change, to ensure that the corresponding feature metadata
* is immediately updated i.e. ensure that HTML tags and classes entered
* here are known to be "required", which may affect filter settings.
*
* @type {Drupal~behavior}
*
* @prop {Drupal~behaviorAttach} attach
* Attaches admin behaviour to the "stylescombo" button.
*/
Drupal.behaviors.ckeditorStylesComboSettings = {
attach: function (context) {
var $context = $(context);
// React to changes in the list of user-defined styles: calculate the new
// stylesSet setting up to 2 times per second, and if it is different, fire
// the CKEditorPluginSettingsChanged event with the updated parts of the
// CKEditor configuration. (This will, in turn, cause the hidden CKEditor
// instance to be updated and a drupalEditorFeatureModified event to fire.)
// stylesSet setting up to 2 times per second, and if it is different,
// fire the CKEditorPluginSettingsChanged event with the updated parts of
// the CKEditor configuration. (This will, in turn, cause the hidden
// CKEditor instance to be updated and a drupalEditorFeatureModified event
// to fire.)
var $ckeditorActiveToolbar = $context
.find('.ckeditor-toolbar-configuration')
.find('.ckeditor-toolbar-active');
@ -49,9 +53,9 @@
*
* @see \Drupal\ckeditor\Plugin\ckeditor\plugin\StylesCombo::generateStylesSetSetting()
*
* Note that this is a more forgiving implementation than the PHP version: the
* parsing works identically, but instead of failing on invalid styles, we
* just ignore those.
* Note that this is a more forgiving implementation than the PHP version:
* the parsing works identically, but instead of failing on invalid styles,
* we just ignore those.
*
* @param {string} styles
* The "styles" setting.
@ -88,7 +92,7 @@
// Build the data structure CKEditor's stylescombo plugin expects.
// @see http://docs.cksource.com/CKEditor_3.x/Developers_Guide/Styles
stylesSet.push({
attributes: {'class': classes.join(' ')},
attributes: {class: classes.join(' ')},
element: element,
name: label
});
@ -102,6 +106,9 @@
* Provides the summary for the "stylescombo" plugin settings vertical tab.
*
* @type {Drupal~behavior}
*
* @prop {Drupal~behaviorAttach} attach
* Attaches summary behaviour to the plugin settings vertical tab.
*/
Drupal.behaviors.ckeditorStylesComboSettingsSummary = {
attach: function () {

View file

@ -47,6 +47,11 @@
*/
hiddenEditorConfig: null,
/**
* A hash that maps buttons to features.
*/
buttonsToFeatures: null,
/**
* A hash, keyed by a feature name, that details CKEditor plugin features.
*/

View file

@ -131,10 +131,10 @@
// when shifting state, so might deal with a new instance.
widget = editor.widgets.getByElement(image);
// It's first edit, just after widget instance creation, but before it was
// inserted into DOM. So we need to retrieve the widget wrapper from
// inside the DocumentFragment which we cached above and finalize other
// things (like ready event and flag).
// It's first edit, just after widget instance creation, but before
// it was inserted into DOM. So we need to retrieve the widget
// wrapper from inside the DocumentFragment which we cached above
// and finalize other things (like ready event and flag).
if (firstEdit) {
editor.widgets.finalizeCreation(container);
}

View file

@ -52,7 +52,7 @@
// Override requiredContent & allowedContent.
widgetDefinition.requiredContent = 'img[alt,src,width,height,data-entity-type,data-entity-uuid,data-align,data-caption]';
widgetDefinition.allowedContent.img.attributes += ',data-align,data-caption';
widgetDefinition.allowedContent.img.attributes += ',!data-align,!data-caption';
// Override allowedContent setting for the 'caption' nested editable.
// This must match what caption_filter enforces.
@ -213,7 +213,8 @@
}
};
};
}, null, null, 20); // Low priority to ensure drupalimage's event handler runs first.
// Low priority to ensure drupalimage's event handler runs first.
}, null, null, 20);
}
});
@ -224,9 +225,12 @@
* children in DFS order.
*
* @param {CKEDITOR.htmlParser.element} element
* The element to search.
* @param {string} name
* The element name to search for.
*
* @return {CKEDITOR.htmlParser.element}
* @return {?CKEDITOR.htmlParser.element}
* The found element, or null.
*/
function findElementByName(element, name) {
if (element.name === name) {

View file

@ -32,14 +32,14 @@
for (var attrIndex = 0; attrIndex < linkDOMElement.attributes.length; attrIndex++) {
attribute = linkDOMElement.attributes.item(attrIndex);
attributeName = attribute.nodeName.toLowerCase();
// Don't consider data-cke-saved- attributes; they're just there to
// work around browser quirks.
// Don't consider data-cke-saved- attributes; they're just there
// to work around browser quirks.
if (attributeName.substring(0, 15) === 'data-cke-saved-') {
continue;
}
// Store the value for this attribute, unless there's a
// data-cke-saved- alternative for it, which will contain the quirk-
// free, original value.
// data-cke-saved- alternative for it, which will contain the
// quirk-free, original value.
existingValues[attributeName] = linkElement.data('cke-saved-' + attributeName) || attribute.nodeValue;
}
}
@ -97,8 +97,8 @@
editor.fire('saveSnapshot');
};
// Drupal.t() will not work inside CKEditor plugins because CKEditor
// loads the JavaScript file instead of Drupal. Pull translated strings
// from the plugin settings that are translated server-side.
// loads the JavaScript file instead of Drupal. Pull translated
// strings from the plugin settings that are translated server-side.
var dialogSettings = {
title: linkElement ? editor.config.drupalLink_dialogTitleEdit : editor.config.drupalLink_dialogTitleAdd,
dialogClass: 'editor-link-dialog'
@ -210,8 +210,11 @@
* [<a href="#"><b>li]nk</b></a>
*
* @param {CKEDITOR.editor} editor
* The CKEditor editor object
*
* @return {?HTMLElement}
* The selected link element, or null.
*
* @return {?bool}
*/
function getSelectedLink(editor) {
var selection = editor.getSelection();

View file

@ -1,6 +1,7 @@
/**
* @file
* A Backbone View that provides the aural view of CKEditor toolbar configuration.
* A Backbone View that provides the aural view of CKEditor toolbar
* configuration.
*/
(function (Drupal, Backbone, $) {
@ -37,6 +38,7 @@
* Calls announce on buttons and groups when their position is changed.
*
* @param {Drupal.ckeditor.ConfigurationModel} model
* The ckeditor configuration model.
* @param {bool} isDirty
* A model attribute that indicates if the changed toolbar configuration
* has been stored or not.
@ -62,6 +64,7 @@
* Handles the focus event of elements in the active and available toolbars.
*
* @param {jQuery.Event} event
* The focus event that was triggered.
*/
onFocus: function (event) {
event.stopPropagation();
@ -171,6 +174,7 @@
* Provides help information when a button is clicked.
*
* @param {jQuery.Event} event
* The click event for the button click.
*/
announceButtonHelp: function (event) {
var $link = $(event.currentTarget);
@ -199,6 +203,7 @@
* Provides help information when a separator is clicked.
*
* @param {jQuery.Event} event
* The click event for the separator click.
*/
announceSeparatorHelp: function (event) {
var $link = $(event.currentTarget);

View file

@ -179,17 +179,24 @@
CKEFeatureRulesMap[name].push(rule);
}
// Now convert these to Drupal.EditorFeature objects.
// Now convert these to Drupal.EditorFeature objects. And track which
// buttons are mapped to which features.
// @see getFeatureForButton()
var features = {};
var buttonsToFeatures = {};
for (var featureName in CKEFeatureRulesMap) {
if (CKEFeatureRulesMap.hasOwnProperty(featureName)) {
var feature = new Drupal.EditorFeature(featureName);
convertCKERulesToEditorFeature(feature, CKEFeatureRulesMap[featureName]);
features[featureName] = feature;
var command = e.editor.getCommand(featureName);
if (command) {
buttonsToFeatures[command.uiItems[0].name] = featureName;
}
}
}
callback(features);
callback(features, buttonsToFeatures);
}
});
},
@ -213,7 +220,7 @@
// Get a Drupal.editorFeature object that contains all metadata for
// the feature that was just added or removed. Not every feature has
// such metadata.
var featureName = button.toLowerCase();
var featureName = this.model.get('buttonsToFeatures')[button.toLowerCase()];
var featuresMetadata = this.model.get('featuresMetadata');
if (!featuresMetadata[featureName]) {
featuresMetadata[featureName] = new Drupal.EditorFeature(featureName);
@ -227,9 +234,18 @@
*
* @param {object} features
* A map of {@link Drupal.EditorFeature} objects.
* @param {object} buttonsToFeatures
* Object containing the button-to-feature mapping.
*
* @see Drupal.ckeditor.ControllerView#getFeatureForButton
*/
disableFeaturesDisallowedByFilters: function (features) {
disableFeaturesDisallowedByFilters: function (features, buttonsToFeatures) {
this.model.set('featuresMetadata', features);
// Store the button-to-feature mapping. Needs to happen only once, because
// the same buttons continue to have the same features; only the rules for
// specific features may change.
// @see getFeatureForButton()
this.model.set('buttonsToFeatures', buttonsToFeatures);
// Ensure that toolbar configuration changes are broadcast.
this.broadcastConfigurationChanges(this.$el);
@ -271,7 +287,7 @@
.detach()
.appendTo('.ckeditor-toolbar-disabled > .ckeditor-toolbar-available > ul');
// Update the toolbar value field.
this.model.set({'isDirty': true}, {broadcast: false});
this.model.set({isDirty: true}, {broadcast: false});
}
}
},

View file

@ -1,6 +1,6 @@
/**
* @file
* A Backbone View that provides the aural view of CKEditor keyboard UX configuration.
* Backbone View providing the aural view of CKEditor keyboard UX configuration.
*/
(function (Drupal, Backbone, $) {
@ -32,6 +32,7 @@
* Handles keypresses on a CKEditor configuration button.
*
* @param {jQuery.Event} event
* The keypress event triggered.
*/
onPressButton: function (event) {
var upDownKeys = [
@ -69,10 +70,11 @@
var $originalGroup = $group;
var dir;
// Move available buttons between their container and the active toolbar.
// Move available buttons between their container and the active
// toolbar.
if (containerType === 'source') {
// Move the button to the active toolbar configuration when the down or
// up keys are pressed.
// Move the button to the active toolbar configuration when the down
// or up keys are pressed.
if (_.indexOf([40, 63233], event.keyCode) > -1) {
// Move the button to the first row, first button group index
// position.
@ -142,8 +144,8 @@
}
// Move dividers between their container and the active toolbar.
else if (containerType === 'dividers') {
// Move the button to the active toolbar configuration when the down or
// up keys are pressed.
// Move the button to the active toolbar configuration when the down
// or up keys are pressed.
if (_.indexOf([40, 63233], event.keyCode) > -1) {
// Move the button to the first row, first button group index
// position.
@ -169,8 +171,8 @@
else {
view.$el.find('.ui-sortable').sortable('refresh');
}
// Refocus the target button so that the user can continue from a known
// place.
// Refocus the target button so that the user can continue from a
// known place.
$target.trigger('focus');
});
@ -183,6 +185,7 @@
* Handles keypresses on a CKEditor configuration group.
*
* @param {jQuery.Event} event
* The keypress event triggered.
*/
onPressGroup: function (event) {
var upDownKeys = [

View file

@ -34,12 +34,17 @@
},
/**
* Render function for rendering the toolbar configuration.
*
* @param {*} model
* Model used for the view.
* @param {string} [value]
* The value that was changed.
* @param {object} changedAttributes
* The attributes that was changed.
*
* @return {Drupal.ckeditor.VisualView}
* The {@link Drupal.ckeditor.VisualView} object.
*/
render: function (model, value, changedAttributes) {
this.insertPlaceholders();
@ -65,6 +70,7 @@
* Handles clicks to a button group name.
*
* @param {jQuery.Event} event
* The click event on the button group.
*/
onGroupNameClick: function (event) {
var $group = $(event.currentTarget).closest('.ckeditor-toolbar-group');
@ -78,6 +84,7 @@
* Handles clicks on the button group names toggle button.
*
* @param {jQuery.Event} event
* The click event on the toggle button.
*/
onGroupNamesToggleClick: function (event) {
this.model.set('groupNamesVisible', !this.model.get('groupNamesVisible'));
@ -88,6 +95,7 @@
* Prompts the user to provide a name for a new button group; inserts it.
*
* @param {jQuery.Event} event
* The event of the button click.
*/
onAddGroupButtonClick: function (event) {
@ -120,6 +128,7 @@
* Handles jQuery Sortable stop sort of a button group.
*
* @param {jQuery.Event} event
* The event triggered on the group drag.
* @param {object} ui
* A jQuery.ui.sortable argument that contains information about the
* elements involved in the sort action.
@ -138,6 +147,7 @@
* Handles jQuery Sortable start sort of a button.
*
* @param {jQuery.Event} event
* The event triggered on the group drag.
* @param {object} ui
* A jQuery.ui.sortable argument that contains information about the
* elements involved in the sort action.
@ -153,6 +163,7 @@
* Handles jQuery Sortable stop sort of a button.
*
* @param {jQuery.Event} event
* The event triggered on the button drag.
* @param {object} ui
* A jQuery.ui.sortable argument that contains information about the
* elements involved in the sort action.

View file

@ -127,7 +127,18 @@ class Internal extends CKEditorPluginBase implements ContainerFactoryPluginInter
*/
public function getButtons() {
$button = function($name, $direction = 'ltr') {
return '<a href="#" class="cke-icon-only cke_' . $direction . '" role="button" title="' . $name . '" aria-label="' . $name . '"><span class="cke_button_icon cke_button__' . str_replace(' ', '', $name) . '_icon">' . $name . '</span></a>';
// In the markup below, we mostly use the name (which may include spaces),
// but in one spot we use it as a CSS class, so strip spaces.
$class_name = str_replace(' ', '', $name);
return [
'#type' => 'inline_template',
'#template' => '<a href="#" class="cke-icon-only cke_{{ direction }}" role="button" title="{{ name }}" aria-label="{{ name }}"><span class="cke_button_icon cke_button__{{ classname }}_icon">{{ name }}</span></a>',
'#context' => [
'direction' => $direction,
'name' => $name,
'classname' => $class_name,
],
];
};
return array(
@ -256,7 +267,13 @@ class Internal extends CKEditorPluginBase implements ContainerFactoryPluginInter
),
'Format' => array(
'label' => t('HTML block format'),
'image_alternative' => '<a href="#" role="button" aria-label="' . t('Format') . '"><span class="ckeditor-button-dropdown">' . t('Format') . '<span class="ckeditor-button-arrow"></span></span></a>',
'image_alternative' => [
'#type' => 'inline_template',
'#template' => '<a href="#" role="button" aria-label="{{ format_text }}"><span class="ckeditor-button-dropdown">{{ format_text }}<span class="ckeditor-button-arrow"></span></span></a>',
'#context' => [
'format_text' => t('Format'),
],
],
),
// "table" plugin.
'Table' => array(
@ -282,7 +299,13 @@ class Internal extends CKEditorPluginBase implements ContainerFactoryPluginInter
// No plugin, separator "button" for toolbar builder UI use only.
'-' => array(
'label' => t('Separator'),
'image_alternative' => '<a href="#" role="button" aria-label="' . t('Button separator') . '" class="ckeditor-separator"></a>',
'image_alternative' => [
'#type' => 'inline_template',
'#template' => '<a href="#" role="button" aria-label="{{ button_separator_text }}" class="ckeditor-separator"></a>',
'#context' => [
'button_separator_text' => t('Button separator'),
],
],
'attributes' => array(
'class' => array('ckeditor-button-separator'),
'data-drupal-ckeditor-type' => 'separator',
@ -302,10 +325,6 @@ class Internal extends CKEditorPluginBase implements ContainerFactoryPluginInter
*
* @return array
* An array containing the "format_tags" configuration.
*
* @see ckeditor_rebuild()
* @see ckeditor_filter_format_insert()
* @see ckeditor_filter_format_update()
*/
protected function generateFormatTagsSetting(Editor $editor) {
// When no text format is associated yet, assume no tag is allowed.
@ -315,9 +334,35 @@ class Internal extends CKEditorPluginBase implements ContainerFactoryPluginInter
}
$format = $editor->getFilterFormat();
// The <p> tag is always allowed — HTML without <p> tags is nonsensical.
$default = 'p';
return \Drupal::state()->get('ckeditor_internal_format_tags:' . $format->id(), $default);
$cid = 'ckeditor_internal_format_tags:' . $format->id();
if ($cached = $this->cache->get($cid)) {
$format_tags = $cached->data;
}
else {
// The <p> tag is always allowed — HTML without <p> tags is nonsensical.
$format_tags = ['p'];
// Given the list of possible format tags, automatically determine whether
// the current text format allows this tag, and thus whether it should show
// up in the "Format" dropdown.
$possible_format_tags = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'pre'];
foreach ($possible_format_tags as $tag) {
$input = '<' . $tag . '>TEST</' . $tag . '>';
$output = trim(check_markup($input, $editor->id()));
if ($input == $output) {
$format_tags[] = $tag;
}
}
$format_tags = implode(';', $format_tags);
// Cache the "format_tags" configuration. This cache item is infinitely
// valid; it only changes whenever the text format is changed, hence it's
// tagged with the text format's cache tag.
$this->cache->set($cid, $format_tags, Cache::PERMANENT, $format->getCacheTags());
}
return $format_tags;
}
/**

View file

@ -59,7 +59,13 @@ class StylesCombo extends CKEditorPluginBase implements CKEditorPluginConfigurab
return array(
'Styles' => array(
'label' => t('Font style'),
'image_alternative' => '<a href="#" role="button" aria-label="' . t('Styles') . '"><span class="ckeditor-button-dropdown">' . t('Styles') . '<span class="ckeditor-button-arrow"></span></span></a>',
'image_alternative' => [
'#type' => 'inline_template',
'#template' => '<a href="#" role="button" aria-label="{{ styles_text }}"><span class="ckeditor-button-dropdown">{{ styles_text }}<span class="ckeditor-button-arrow"></span></span></a>',
'#context' => [
'styles_text' => t('Styles'),
],
],
),
);
}

View file

@ -331,10 +331,12 @@ class CKEditor extends EditorBase implements ContainerFactoryPluginInterface {
if (empty($langcodes)) {
$langcodes = array();
// Collect languages included with CKEditor based on file listing.
$ckeditor_languages = new \GlobIterator(\Drupal::root() . '/core/assets/vendor/ckeditor/lang/*.js');
foreach ($ckeditor_languages as $language_file) {
$langcode = $language_file->getBasename('.js');
$langcodes[$langcode] = $langcode;
$files = scandir('core/assets/vendor/ckeditor/lang');
foreach ($files as $file) {
if ($file[0] !== '.' && fnmatch('*.js', $file)) {
$langcode = basename($file, '.js');
$langcodes[$langcode] = $langcode;
}
}
\Drupal::cache()->set('ckeditor.langcodes', $langcodes);
}
@ -414,7 +416,7 @@ class CKEditor extends EditorBase implements ContainerFactoryPluginInterface {
public function buildContentsCssJSSetting(EditorEntity $editor) {
$css = array(
drupal_get_path('module', 'ckeditor') . '/css/ckeditor-iframe.css',
drupal_get_path('module', 'system') . '/css/system.module.css',
drupal_get_path('module', 'system') . '/css/components/align.module.css',
);
$this->moduleHandler->alter('ckeditor_css', $css, $editor);
$css = array_merge($css, _ckeditor_theme_css());

View file

@ -7,6 +7,7 @@
namespace Drupal\ckeditor\Tests;
use Drupal\Component\Serialization\Json;
use Drupal\editor\Entity\Editor;
use Drupal\filter\FilterFormatInterface;
use Drupal\simpletest\WebTestBase;
@ -176,6 +177,22 @@ class CKEditorAdminTest extends WebTestBase {
$this->assertTrue($editor instanceof Editor, 'An Editor config entity exists.');
$this->assertIdentical($expected_settings, $editor->getSettings(), 'The Editor config entity has the correct settings.');
// Check that the markup we're setting for the toolbar buttons (actually in
// JavaScript's drupalSettings, and Unicode-escaped) is correctly rendered.
$this->drupalGet('admin/config/content/formats/manage/filtered_html');
// Create function to encode HTML as we expect it in drupalSettings.
$json_encode = function($html) {
return trim(Json::encode($html), '"');
};
// Check the Button separator.
$this->assertRaw($json_encode('<li data-drupal-ckeditor-button-name="-" class="ckeditor-button-separator ckeditor-multiple-button" data-drupal-ckeditor-type="separator"><a href="#" role="button" aria-label="Button separator" class="ckeditor-separator"></a></li>'));
// Check the Format dropdown.
$this->assertRaw($json_encode('<li data-drupal-ckeditor-button-name="Format" class="ckeditor-button"><a href="#" role="button" aria-label="Format"><span class="ckeditor-button-dropdown">Format<span class="ckeditor-button-arrow"></span></span></a></li>'));
// Check the Styles dropdown.
$this->assertRaw($json_encode('<li data-drupal-ckeditor-button-name="Styles" class="ckeditor-button"><a href="#" role="button" aria-label="Styles"><span class="ckeditor-button-dropdown">Styles<span class="ckeditor-button-arrow"></span></span></a></li>'));
// Check strikethrough.
$this->assertRaw($json_encode('<li data-drupal-ckeditor-button-name="Strike" class="ckeditor-button"><a href="#" class="cke-icon-only cke_ltr" role="button" title="strike" aria-label="strike"><span class="cke_button_icon cke_button__strike_icon">strike</span></a></li>'));
// Now enable the ckeditor_test module, which provides one configurable
// CKEditor plugin — this should not affect the Editor config entity.
\Drupal::service('module_installer')->install(array('ckeditor_test'));

View file

@ -54,7 +54,7 @@ class CKEditorTest extends KernelTestBase {
'filter_html' => array(
'status' => 1,
'settings' => array(
'allowed_html' => '<h4> <h5> <h6> <p> <br> <strong> <a>',
'allowed_html' => '<h2> <h3> <h4> <h5> <h6> <p> <br> <strong> <a>',
)
),
),
@ -103,9 +103,6 @@ class CKEditorTest extends KernelTestBase {
$this->container->get('plugin.manager.editor')->clearCachedDefinitions();
$this->ckeditor = $this->container->get('plugin.manager.editor')->createInstance('ckeditor');
$this->container->get('plugin.manager.ckeditor.plugin')->clearCachedDefinitions();
// KernelTestBase::enableModules() unfortunately doesn't invoke
// hook_rebuild() just like a "real" Drupal site would. Do it manually.
\Drupal::moduleHandler()->invoke('ckeditor', 'rebuild');
$settings = $editor->getSettings();
$settings['toolbar']['rows'][0][0]['items'][] = 'Strike';
$settings['toolbar']['rows'][0][0]['items'][] = 'Format';
@ -113,7 +110,7 @@ class CKEditorTest extends KernelTestBase {
$editor->save();
$expected_config['toolbar'][0]['items'][] = 'Strike';
$expected_config['toolbar'][0]['items'][] = 'Format';
$expected_config['format_tags'] = 'p;h4;h5;h6';
$expected_config['format_tags'] = 'p;h2;h3;h4;h5;h6';
$expected_config['extraPlugins'] .= ',llama_contextual,llama_contextual_and_button';
$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');
@ -129,7 +126,7 @@ class CKEditorTest extends KernelTestBase {
$expected_config['allowedContent']['pre'] = array('attributes' => TRUE, 'styles' => FALSE, 'classes' => TRUE);
$expected_config['allowedContent']['h3'] = array('attributes' => TRUE, 'styles' => FALSE, 'classes' => TRUE);
$expected_config['format_tags'] = 'p;h3;h4;h5;h6;pre';
$expected_config['format_tags'] = 'p;h2;h3;h4;h5;h6;pre';
$this->assertIdentical($expected_config, $this->ckeditor->getJSSettings($editor), 'Generated JS settings are correct for customized configuration.');
// Disable the filter_html filter: allow *all *tags.
@ -209,11 +206,6 @@ class CKEditorTest extends KernelTestBase {
$expected_config['format_tags'] = 'p';
ksort($expected_config);
$this->assertIdentical($expected_config, $this->ckeditor->getJSSettings($editor), 'Generated JS settings are correct for customized configuration.');
// Assert that we're robust enough to withstand people messing with State
// manually.
\Drupal::state()->delete('ckeditor_internal_format_tags:' . $format->id());
$this->assertIdentical($expected_config, $this->ckeditor->getJSSettings($editor), 'Even when somebody manually deleted the key-value pair in State with the pre-calculated format_tags setting, it returns "p" — because the <p> tag is always allowed.');
}
/**
@ -267,7 +259,6 @@ class CKEditorTest extends KernelTestBase {
$this->config('system.theme')->set('default', 'bartik')->save();
$expected[] = file_create_url('core/themes/bartik/css/base/elements.css');
$expected[] = file_create_url('core/themes/bartik/css/components/captions.css');
$expected[] = file_create_url('core/themes/bartik/css/components/content.css');
$expected[] = file_create_url('core/themes/bartik/css/components/table.css');
$this->assertIdentical($expected, $this->ckeditor->buildContentsCssJSSetting($editor), '"contentsCss" configuration part of JS settings built correctly while a theme providing a CKEditor stylesheet exists.');
}
@ -289,7 +280,7 @@ class CKEditorTest extends KernelTestBase {
$settings = $editor->getSettings();
$settings['toolbar']['rows'][0][0]['items'][] = 'Format';
$editor->setSettings($settings);
$expected['format_tags'] = 'p;h4;h5;h6';
$expected['format_tags'] = 'p;h2;h3;h4;h5;h6';
$this->assertEqual($expected, $internal_plugin->getConfig($editor), '"Internal" plugin configuration built correctly for customized toolbar.');
}
@ -430,6 +421,8 @@ class CKEditorTest extends KernelTestBase {
protected function getDefaultAllowedContentConfig() {
return array(
'h2' => array('attributes' => TRUE, 'styles' => FALSE, 'classes' => TRUE),
'h3' => array('attributes' => TRUE, 'styles' => FALSE, 'classes' => TRUE),
'h4' => array('attributes' => TRUE, 'styles' => FALSE, 'classes' => TRUE),
'h5' => array('attributes' => TRUE, 'styles' => FALSE, 'classes' => TRUE),
'h6' => array('attributes' => TRUE, 'styles' => FALSE, 'classes' => TRUE),
@ -475,7 +468,7 @@ class CKEditorTest extends KernelTestBase {
protected function getDefaultContentsCssConfig() {
return array(
file_create_url('core/modules/ckeditor/css/ckeditor-iframe.css'),
file_create_url('core/modules/system/css/system.module.css'),
file_create_url('core/modules/system/css/components/align.module.css'),
);
}