Update Composer, update everything

This commit is contained in:
Oliver Davies 2018-11-23 12:29:20 +00:00
parent ea3e94409f
commit dda5c284b6
19527 changed files with 1135420 additions and 351004 deletions

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,34 @@
/**
* @file
* AJAX commands used by Editor module.
*/
(function($, Drupal) {
/**
* Command to save the contents of an editor-provided modal.
*
* This command does not close the open modal. It should be followed by a
* call to `Drupal.AjaxCommands.prototype.closeDialog`. Editors that are
* integrated with dialogs must independently listen for an
* `editor:dialogsave` event to save the changes into the contents of their
* interface.
*
* @param {Drupal.Ajax} [ajax]
* The Drupal.Ajax object.
* @param {object} response
* The server response from the ajax request.
* @param {Array} response.values
* The values that were saved.
* @param {number} [status]
* The status code from the ajax request.
*
* @fires event:editor:dialogsave
*/
Drupal.AjaxCommands.prototype.editorDialogSave = function(
ajax,
response,
status,
) {
$(window).trigger('editor:dialogsave', [response.values]);
};
})(jQuery, Drupal);

View file

@ -1,34 +1,12 @@
/**
* @file
* AJAX commands used by Editor module.
*/
* DO NOT EDIT THIS FILE.
* See the following change record for more information,
* https://www.drupal.org/node/2815083
* @preserve
**/
(function ($, Drupal) {
'use strict';
/**
* Command to save the contents of an editor-provided modal.
*
* This command does not close the open modal. It should be followed by a
* call to `Drupal.AjaxCommands.prototype.closeDialog`. Editors that are
* integrated with dialogs must independently listen for an
* `editor:dialogsave` event to save the changes into the contents of their
* interface.
*
* @param {Drupal.Ajax} [ajax]
* The Drupal.Ajax object.
* @param {object} response
* The server response from the ajax request.
* @param {Array} response.values
* The values that were saved.
* @param {number} [status]
* The status code from the ajax request.
*
* @fires event:editor:dialogsave
*/
Drupal.AjaxCommands.prototype.editorDialogSave = function (ajax, response, status) {
$(window).trigger('editor:dialogsave', [response.values]);
};
})(jQuery, Drupal);
})(jQuery, Drupal);

View file

@ -0,0 +1,345 @@
/**
* @file
* Attaches behavior for the Editor module.
*/
(function($, Drupal, drupalSettings) {
/**
* Finds the text area field associated with the given text format selector.
*
* @param {jQuery} $formatSelector
* A text format selector DOM element.
*
* @return {HTMLElement}
* The text area DOM element, if it was found.
*/
function findFieldForFormatSelector($formatSelector) {
const fieldId = $formatSelector.attr('data-editor-for');
// This selector will only find text areas in the top-level document. We do
// not support attaching editors on text areas within iframes.
return $(`#${fieldId}`).get(0);
}
/**
* Filter away XSS attack vectors when switching text formats.
*
* @param {HTMLElement} field
* The textarea DOM element.
* @param {object} format
* The text format that's being activated, from
* drupalSettings.editor.formats.
* @param {string} originalFormatID
* The text format ID of the original text format.
* @param {function} callback
* A callback to be called (with no parameters) after the field's value has
* been XSS filtered.
*/
function filterXssWhenSwitching(field, format, originalFormatID, callback) {
// A text editor that already is XSS-safe needs no additional measures.
if (format.editor.isXssSafe) {
callback(field, format);
}
// Otherwise, ensure XSS safety: let the server XSS filter this value.
else {
$.ajax({
url: Drupal.url(`editor/filter_xss/${format.format}`),
type: 'POST',
data: {
value: field.value,
original_format_id: originalFormatID,
},
dataType: 'json',
success(xssFilteredValue) {
// If the server returns false, then no XSS filtering is needed.
if (xssFilteredValue !== false) {
field.value = xssFilteredValue;
}
callback(field, format);
},
});
}
}
/**
* Changes the text editor on a text area.
*
* @param {HTMLElement} field
* The text area DOM element.
* @param {string} newFormatID
* The text format we're changing to; the text editor for the currently
* active text format will be detached, and the text editor for the new text
* format will be attached.
*/
function changeTextEditor(field, newFormatID) {
const previousFormatID = field.getAttribute(
'data-editor-active-text-format',
);
// Detach the current editor (if any) and attach a new editor.
if (drupalSettings.editor.formats[previousFormatID]) {
Drupal.editorDetach(
field,
drupalSettings.editor.formats[previousFormatID],
);
}
// When no text editor is currently active, stop tracking changes.
else {
$(field).off('.editor');
}
// Attach the new text editor (if any).
if (drupalSettings.editor.formats[newFormatID]) {
const format = drupalSettings.editor.formats[newFormatID];
filterXssWhenSwitching(
field,
format,
previousFormatID,
Drupal.editorAttach,
);
}
// Store the new active format.
field.setAttribute('data-editor-active-text-format', newFormatID);
}
/**
* Handles changes in text format.
*
* @param {jQuery.Event} event
* The text format change event.
*/
function onTextFormatChange(event) {
const $select = $(event.target);
const field = event.data.field;
const activeFormatID = field.getAttribute('data-editor-active-text-format');
const 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.
const supportContentFiltering =
drupalSettings.editor.formats[newFormatID] &&
drupalSettings.editor.formats[newFormatID].editorSupportsContentFiltering;
// If there is no content yet, it's always safe to change the text format.
const hasContent = field.value !== '';
if (hasContent && supportContentFiltering) {
const 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(),
},
);
const 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() {
changeTextEditor(field, newFormatID);
confirmationDialog.close();
},
},
{
text: Drupal.t('Cancel'),
class: 'button',
click() {
// 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() {
$(this)
.parent()
.find('.ui-dialog-titlebar-close')
.remove();
},
beforeClose: false,
close(event) {
// Automatically destroy the DOM element that was used for the dialog.
$(event.target).remove();
},
});
confirmationDialog.showModal();
} else {
changeTextEditor(field, newFormatID);
}
}
/**
* Initialize an empty object for editors to place their attachment code.
*
* @namespace
*/
Drupal.editors = {};
/**
* Enables editors on text_format elements.
*
* @type {Drupal~behavior}
*
* @prop {Drupal~behaviorAttach} attach
* Attaches an editor to an input element.
* @prop {Drupal~behaviorDetach} detach
* Detaches an editor from an input element.
*/
Drupal.behaviors.editor = {
attach(context, settings) {
// If there are no editor settings, there are no editors to enable.
if (!settings.editor) {
return;
}
$(context)
.find('[data-editor-for]')
.once('editor')
.each(function() {
const $this = $(this);
const field = findFieldForFormatSelector($this);
// Opt-out if no supported text area was found.
if (!field) {
return;
}
// Store the current active format.
const activeFormatID = $this.val();
field.setAttribute('data-editor-active-text-format', activeFormatID);
// Directly attach this text editor, if the text format is enabled.
if (settings.editor.formats[activeFormatID]) {
// XSS protection for the current text format/editor is performed on
// the server side, so we don't need to do anything special here.
Drupal.editorAttach(field, settings.editor.formats[activeFormatID]);
}
// When there is no text editor for this text format, still track
// changes, because the user has the ability to switch to some text
// editor, otherwise this code would not be executed.
$(field).on('change.editor keypress.editor', () => {
field.setAttribute('data-editor-value-is-changed', 'true');
// Just knowing that the value was changed is enough, stop tracking.
$(field).off('.editor');
});
// Attach onChange handler to text format selector element.
if ($this.is('select')) {
$this.on('change.editorAttach', { field }, onTextFormatChange);
}
// Detach any editor when the containing form is submitted.
$this.parents('form').on('submit', event => {
// Do not detach if the event was canceled.
if (event.isDefaultPrevented()) {
return;
}
// Detach the current editor (if any).
if (settings.editor.formats[activeFormatID]) {
Drupal.editorDetach(
field,
settings.editor.formats[activeFormatID],
'serialize',
);
}
});
});
},
detach(context, settings, trigger) {
let editors;
// The 'serialize' trigger indicates that we should simply update the
// underlying element with the new text, without destroying the editor.
if (trigger === 'serialize') {
// Removing the editor-processed class guarantees that the editor will
// be reattached. Only do this if we're planning to destroy the editor.
editors = $(context)
.find('[data-editor-for]')
.findOnce('editor');
} else {
editors = $(context)
.find('[data-editor-for]')
.removeOnce('editor');
}
editors.each(function() {
const $this = $(this);
const activeFormatID = $this.val();
const field = findFieldForFormatSelector($this);
if (field && activeFormatID in settings.editor.formats) {
Drupal.editorDetach(
field,
settings.editor.formats[activeFormatID],
trigger,
);
}
});
},
};
/**
* Attaches editor behaviors to the field.
*
* @param {HTMLElement} field
* The textarea DOM element.
* @param {object} format
* The text format that's being activated, from
* drupalSettings.editor.formats.
*
* @listens event:change
*
* @fires event:formUpdated
*/
Drupal.editorAttach = function(field, format) {
if (format.editor) {
// Attach the text editor.
Drupal.editors[format.editor].attach(field, format);
// Ensures form.js' 'formUpdated' event is triggered even for changes that
// happen within the text editor.
Drupal.editors[format.editor].onChange(field, () => {
$(field).trigger('formUpdated');
// Keep track of changes, so we know what to do when switching text
// formats and guaranteeing XSS protection.
field.setAttribute('data-editor-value-is-changed', 'true');
});
}
};
/**
* Detaches editor behaviors from the field.
*
* @param {HTMLElement} field
* The textarea DOM element.
* @param {object} format
* The text format that's being activated, from
* drupalSettings.editor.formats.
* @param {string} trigger
* Trigger value from the detach behavior.
*/
Drupal.editorDetach = function(field, format, trigger) {
if (format.editor) {
Drupal.editors[format.editor].detach(field, format, trigger);
// Restore the original value if the user didn't make any changes yet.
if (field.getAttribute('data-editor-value-is-changed') === 'false') {
field.value = field.getAttribute('data-editor-value-original');
}
}
};
})(jQuery, Drupal, drupalSettings);

View file

@ -0,0 +1,244 @@
/**
* @file
* Text editor-based in-place editor for formatted text content in Drupal.
*
* Depends on editor.module. Works with any (WYSIWYG) editor that implements the
* editor.js API, including the optional attachInlineEditor() and onChange()
* methods.
* For example, assuming that a hypothetical editor's name was "Magical Editor"
* and its editor.js API implementation lived at Drupal.editors.magical, this
* JavaScript would use:
* - Drupal.editors.magical.attachInlineEditor()
*/
(function($, Drupal, drupalSettings, _) {
Drupal.quickedit.editors.editor = Drupal.quickedit.EditorView.extend(
/** @lends Drupal.quickedit.editors.editor# */ {
/**
* The text format for this field.
*
* @type {string}
*/
textFormat: null,
/**
* Indicates whether this text format has transformations.
*
* @type {bool}
*/
textFormatHasTransformations: null,
/**
* Stores a reference to the text editor object for this field.
*
* @type {Drupal.quickedit.EditorModel}
*/
textEditor: null,
/**
* Stores the textual DOM element that is being in-place edited.
*
* @type {jQuery}
*/
$textElement: null,
/**
* @constructs
*
* @augments Drupal.quickedit.EditorView
*
* @param {object} options
* Options for the editor view.
*/
initialize(options) {
Drupal.quickedit.EditorView.prototype.initialize.call(this, options);
const metadata = Drupal.quickedit.metadata.get(
this.fieldModel.get('fieldID'),
'custom',
);
this.textFormat = drupalSettings.editor.formats[metadata.format];
this.textFormatHasTransformations = metadata.formatHasTransformations;
this.textEditor = Drupal.editors[this.textFormat.editor];
// Store the actual value of this field. We'll need this to restore the
// original value when the user discards his modifications.
const $fieldItems = this.$el.find('.quickedit-field');
if ($fieldItems.length) {
this.$textElement = $fieldItems.eq(0);
} else {
this.$textElement = this.$el;
}
this.model.set('originalValue', this.$textElement.html());
},
/**
* @inheritdoc
*
* @return {jQuery}
* The text element edited.
*/
getEditedElement() {
return this.$textElement;
},
/**
* @inheritdoc
*
* @param {object} fieldModel
* The field model.
* @param {string} state
* The current state.
*/
stateChange(fieldModel, state) {
const editorModel = this.model;
const from = fieldModel.previous('state');
const to = state;
switch (to) {
case 'inactive':
break;
case 'candidate':
// Detach the text editor when entering the 'candidate' state from one
// of the states where it could have been attached.
if (from !== 'inactive' && from !== 'highlighted') {
this.textEditor.detach(this.$textElement.get(0), this.textFormat);
}
// A field model's editor view revert() method is invoked when an
// 'active' field becomes a 'candidate' field. But, in the case of
// this in-place editor, the content will have been *replaced* if the
// text format has transformation filters. Therefore, if we stop
// in-place editing this entity, revert explicitly.
if (from === 'active' && this.textFormatHasTransformations) {
this.revert();
}
if (from === 'invalid') {
this.removeValidationErrors();
}
break;
case 'highlighted':
break;
case 'activating':
// When transformation filters have been applied to the formatted text
// of this field, then we'll need to load a re-formatted version of it
// without the transformation filters.
if (this.textFormatHasTransformations) {
const $textElement = this.$textElement;
this._getUntransformedText(untransformedText => {
$textElement.html(untransformedText);
fieldModel.set('state', 'active');
});
}
// When no transformation filters have been applied: start WYSIWYG
// editing immediately!
else {
// Defer updating the model until the current state change has
// propagated, to not trigger a nested state change event.
_.defer(() => {
fieldModel.set('state', 'active');
});
}
break;
case 'active': {
const textElement = this.$textElement.get(0);
const toolbarView = fieldModel.toolbarView;
this.textEditor.attachInlineEditor(
textElement,
this.textFormat,
toolbarView.getMainWysiwygToolgroupId(),
toolbarView.getFloatedWysiwygToolgroupId(),
);
// Set the state to 'changed' whenever the content has changed.
this.textEditor.onChange(textElement, htmlText => {
editorModel.set('currentValue', htmlText);
fieldModel.set('state', 'changed');
});
break;
}
case 'changed':
break;
case 'saving':
if (from === 'invalid') {
this.removeValidationErrors();
}
this.save();
break;
case 'saved':
break;
case 'invalid':
this.showValidationErrors();
break;
}
},
/**
* @inheritdoc
*
* @return {object}
* The settings for the quick edit UI.
*/
getQuickEditUISettings() {
return {
padding: true,
unifiedToolbar: true,
fullWidthToolbar: true,
popup: false,
};
},
/**
* @inheritdoc
*/
revert() {
this.$textElement.html(this.model.get('originalValue'));
},
/**
* Loads untransformed text for this field.
*
* More accurately: it re-filters formatted text to exclude transformation
* filters used by the text format.
*
* @param {function} callback
* A callback function that will receive the untransformed text.
*
* @see \Drupal\editor\Ajax\GetUntransformedTextCommand
*/
_getUntransformedText(callback) {
const fieldID = this.fieldModel.get('fieldID');
// Create a Drupal.ajax instance to load the form.
const textLoaderAjax = Drupal.ajax({
url: Drupal.quickedit.util.buildUrl(
fieldID,
Drupal.url(
'editor/!entity_type/!id/!field_name/!langcode/!view_mode',
),
),
submit: { nocssjs: true },
});
// Implement a scoped editorGetUntransformedText AJAX command: calls the
// callback.
textLoaderAjax.commands.editorGetUntransformedText = function(
ajax,
response,
status,
) {
callback(response.data);
};
// This will ensure our scoped editorGetUntransformedText AJAX command
// gets called.
textLoaderAjax.execute();
},
},
);
})(jQuery, Drupal, drupalSettings, _);

View file

@ -1,59 +1,21 @@
/**
* @file
* Text editor-based in-place editor for formatted text content in Drupal.
*
* Depends on editor.module. Works with any (WYSIWYG) editor that implements the
* editor.js API, including the optional attachInlineEditor() and onChange()
* methods.
* For example, assuming that a hypothetical editor's name was "Magical Editor"
* and its editor.js API implementation lived at Drupal.editors.magical, this
* JavaScript would use:
* - Drupal.editors.magical.attachInlineEditor()
*/
* DO NOT EDIT THIS FILE.
* See the following change record for more information,
* https://www.drupal.org/node/2815083
* @preserve
**/
(function ($, Drupal, drupalSettings, _) {
'use strict';
Drupal.quickedit.editors.editor = Drupal.quickedit.EditorView.extend(/** @lends Drupal.quickedit.editors.editor# */{
/**
* The text format for this field.
*
* @type {string}
*/
Drupal.quickedit.editors.editor = Drupal.quickedit.EditorView.extend({
textFormat: null,
/**
* Indicates whether this text format has transformations.
*
* @type {bool}
*/
textFormatHasTransformations: null,
/**
* Stores a reference to the text editor object for this field.
*
* @type {Drupal.quickedit.EditorModel}
*/
textEditor: null,
/**
* Stores the textual DOM element that is being in-place edited.
*
* @type {jQuery}
*/
$textElement: null,
/**
* @constructs
*
* @augments Drupal.quickedit.EditorView
*
* @param {object} options
* Options for the editor view.
*/
initialize: function (options) {
initialize: function initialize(options) {
Drupal.quickedit.EditorView.prototype.initialize.call(this, options);
var metadata = Drupal.quickedit.metadata.get(this.fieldModel.get('fieldID'), 'custom');
@ -61,37 +23,18 @@
this.textFormatHasTransformations = metadata.formatHasTransformations;
this.textEditor = Drupal.editors[this.textFormat.editor];
// Store the actual value of this field. We'll need this to restore the
// original value when the user discards his modifications.
var $fieldItems = this.$el.find('.quickedit-field');
if ($fieldItems.length) {
this.$textElement = $fieldItems.eq(0);
}
else {
} else {
this.$textElement = this.$el;
}
this.model.set('originalValue', this.$textElement.html());
},
/**
* @inheritdoc
*
* @return {jQuery}
* The text element edited.
*/
getEditedElement: function () {
getEditedElement: function getEditedElement() {
return this.$textElement;
},
/**
* @inheritdoc
*
* @param {object} fieldModel
* The field model.
* @param {string} state
* The current state.
*/
stateChange: function (fieldModel, state) {
stateChange: function stateChange(fieldModel, state) {
var editorModel = this.model;
var from = fieldModel.previous('state');
var to = state;
@ -100,16 +43,10 @@
break;
case 'candidate':
// Detach the text editor when entering the 'candidate' state from one
// of the states where it could have been attached.
if (from !== 'inactive' && from !== 'highlighted') {
this.textEditor.detach(this.$textElement.get(0), this.textFormat);
}
// A field model's editor view revert() method is invoked when an
// 'active' field becomes a 'candidate' field. But, in the case of
// this in-place editor, the content will have been *replaced* if the
// text format has transformation filters. Therefore, if we stop
// in-place editing this entity, revert explicitly.
if (from === 'active' && this.textFormatHasTransformations) {
this.revert();
}
@ -122,42 +59,31 @@
break;
case 'activating':
// When transformation filters have been applied to the formatted text
// of this field, then we'll need to load a re-formatted version of it
// without the transformation filters.
if (this.textFormatHasTransformations) {
var $textElement = this.$textElement;
this._getUntransformedText(function (untransformedText) {
$textElement.html(untransformedText);
fieldModel.set('state', 'active');
});
}
// When no transformation filters have been applied: start WYSIWYG
// editing immediately!
else {
// Defer updating the model until the current state change has
// propagated, to not trigger a nested state change event.
_.defer(function () {
fieldModel.set('state', 'active');
});
}
} else {
_.defer(function () {
fieldModel.set('state', 'active');
});
}
break;
case 'active':
var textElement = this.$textElement.get(0);
var toolbarView = fieldModel.toolbarView;
this.textEditor.attachInlineEditor(
textElement,
this.textFormat,
toolbarView.getMainWysiwygToolgroupId(),
toolbarView.getFloatedWysiwygToolgroupId()
);
// Set the state to 'changed' whenever the content has changed.
this.textEditor.onChange(textElement, function (htmlText) {
editorModel.set('currentValue', htmlText);
fieldModel.set('state', 'changed');
});
break;
{
var textElement = this.$textElement.get(0);
var toolbarView = fieldModel.toolbarView;
this.textEditor.attachInlineEditor(textElement, this.textFormat, toolbarView.getMainWysiwygToolgroupId(), toolbarView.getFloatedWysiwygToolgroupId());
this.textEditor.onChange(textElement, function (htmlText) {
editorModel.set('currentValue', htmlText);
fieldModel.set('state', 'changed');
});
break;
}
case 'changed':
break;
@ -177,55 +103,30 @@
break;
}
},
/**
* @inheritdoc
*
* @return {object}
* The sttings for the quick edit UI.
*/
getQuickEditUISettings: function () {
return {padding: true, unifiedToolbar: true, fullWidthToolbar: true, popup: false};
getQuickEditUISettings: function getQuickEditUISettings() {
return {
padding: true,
unifiedToolbar: true,
fullWidthToolbar: true,
popup: false
};
},
/**
* @inheritdoc
*/
revert: function () {
revert: function revert() {
this.$textElement.html(this.model.get('originalValue'));
},
/**
* Loads untransformed text for this field.
*
* More accurately: it re-filters formatted text to exclude transformation
* filters used by the text format.
*
* @param {function} callback
* A callback function that will receive the untransformed text.
*
* @see \Drupal\editor\Ajax\GetUntransformedTextCommand
*/
_getUntransformedText: function (callback) {
_getUntransformedText: function _getUntransformedText(callback) {
var fieldID = this.fieldModel.get('fieldID');
// Create a Drupal.ajax instance to load the form.
var textLoaderAjax = Drupal.ajax({
url: Drupal.quickedit.util.buildUrl(fieldID, Drupal.url('editor/!entity_type/!id/!field_name/!langcode/!view_mode')),
submit: {nocssjs: true}
submit: { nocssjs: true }
});
// Implement a scoped editorGetUntransformedText AJAX command: calls the
// callback.
textLoaderAjax.commands.editorGetUntransformedText = function (ajax, response, status) {
callback(response.data);
};
// This will ensure our scoped editorGetUntransformedText AJAX command
// gets called.
textLoaderAjax.execute();
}
});
})(jQuery, Drupal, drupalSettings, _);
})(jQuery, Drupal, drupalSettings, _);

View file

@ -1,83 +1,68 @@
/**
* @file
* Attaches behavior for the Editor module.
*/
* DO NOT EDIT THIS FILE.
* See the following change record for more information,
* https://www.drupal.org/node/2815083
* @preserve
**/
(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 {HTMLElement}
* The text area DOM element, if it was found.
*/
function findFieldForFormatSelector($formatSelector) {
var field_id = $formatSelector.attr('data-editor-for');
// This selector will only find text areas in the top-level document. We do
// not support attaching editors on text areas within iframes.
return $('#' + field_id).get(0);
var fieldId = $formatSelector.attr('data-editor-for');
return $('#' + fieldId).get(0);
}
function filterXssWhenSwitching(field, format, originalFormatID, callback) {
if (format.editor.isXssSafe) {
callback(field, format);
} else {
$.ajax({
url: Drupal.url('editor/filter_xss/' + format.format),
type: 'POST',
data: {
value: field.value,
original_format_id: originalFormatID
},
dataType: 'json',
success: function success(xssFilteredValue) {
if (xssFilteredValue !== false) {
field.value = xssFilteredValue;
}
callback(field, format);
}
});
}
}
/**
* Changes the text editor on a text area.
*
* @param {HTMLElement} field
* The text area DOM element.
* @param {string} newFormatID
* The text format we're changing to; the text editor for the currently
* active text format will be detached, and the text editor for the new text
* format will be attached.
*/
function changeTextEditor(field, newFormatID) {
var previousFormatID = field.getAttribute('data-editor-active-text-format');
// Detach the current editor (if any) and attach a new editor.
if (drupalSettings.editor.formats[previousFormatID]) {
Drupal.editorDetach(field, drupalSettings.editor.formats[previousFormatID]);
}
// When no text editor is currently active, stop tracking changes.
else {
$(field).off('.editor');
}
} else {
$(field).off('.editor');
}
// Attach the new text editor (if any).
if (drupalSettings.editor.formats[newFormatID]) {
var format = drupalSettings.editor.formats[newFormatID];
filterXssWhenSwitching(field, format, previousFormatID, Drupal.editorAttach);
}
// Store the new active format.
field.setAttribute('data-editor-active-text-format', newFormatID);
}
/**
* Handles changes in text format.
*
* @param {jQuery.Event} event
* The text format change event.
*/
function onTextFormatChange(event) {
var $select = $(event.target);
var field = event.data.field;
var activeFormatID = field.getAttribute('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.
var supportContentFiltering = drupalSettings.editor.formats[newFormatID] && drupalSettings.editor.formats[newFormatID].editorSupportsContentFiltering;
// If there is no content yet, it's always safe to change the text format.
var hasContent = field.value !== '';
if (hasContent && supportContentFiltering) {
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.', {
@ -87,68 +72,43 @@
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(field, 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();
}
buttons: [{
text: Drupal.t('Continue'),
class: 'button button--primary',
click: function click() {
changeTextEditor(field, newFormatID);
confirmationDialog.close();
}
],
// Prevent this modal from being closed without the user making a choice
// as per http://stackoverflow.com/a/5438771.
}, {
text: Drupal.t('Cancel'),
class: 'button',
click: function click() {
$select.val(activeFormatID);
confirmationDialog.close();
}
}],
closeOnEscape: false,
create: function () {
create: function create() {
$(this).parent().find('.ui-dialog-titlebar-close').remove();
},
beforeClose: false,
close: function (event) {
// Automatically destroy the DOM element that was used for the dialog.
close: function close(event) {
$(event.target).remove();
}
});
confirmationDialog.showModal();
}
else {
} else {
changeTextEditor(field, newFormatID);
}
}
/**
* Initialize an empty object for editors to place their attachment code.
*
* @namespace
*/
Drupal.editors = {};
/**
* Enables editors on text_format elements.
*
* @type {Drupal~behavior}
*
* @prop {Drupal~behaviorAttach} attach
* Attaches an editor to an input element.
* @prop {Drupal~behaviorDetach} detach
* Detaches an editor from an input element.
*/
Drupal.behaviors.editor = {
attach: function (context, settings) {
// If there are no editor settings, there are no editors to enable.
attach: function attach(context, settings) {
if (!settings.editor) {
return;
}
@ -157,58 +117,44 @@
var $this = $(this);
var field = findFieldForFormatSelector($this);
// Opt-out if no supported text area was found.
if (!field) {
return;
}
// Store the current active format.
var activeFormatID = $this.val();
field.setAttribute('data-editor-active-text-format', activeFormatID);
// Directly attach this text editor, if the text format is enabled.
if (settings.editor.formats[activeFormatID]) {
// XSS protection for the current text format/editor is performed on
// the server side, so we don't need to do anything special here.
Drupal.editorAttach(field, settings.editor.formats[activeFormatID]);
}
// When there is no text editor for this text format, still track
// changes, because the user has the ability to switch to some text
// editor, otherwise this code would not be executed.
$(field).on('change.editor keypress.editor', function () {
field.setAttribute('data-editor-value-is-changed', 'true');
// Just knowing that the value was changed is enough, stop tracking.
$(field).off('.editor');
});
// Attach onChange handler to text format selector element.
if ($this.is('select')) {
$this.on('change.editorAttach', {field: field}, onTextFormatChange);
$this.on('change.editorAttach', { field: field }, onTextFormatChange);
}
// Detach any editor when the containing form is submitted.
$this.parents('form').on('submit', function (event) {
// Do not detach if the event was canceled.
if (event.isDefaultPrevented()) {
return;
}
// Detach the current editor (if any).
if (settings.editor.formats[activeFormatID]) {
Drupal.editorDetach(field, settings.editor.formats[activeFormatID], 'serialize');
}
});
});
},
detach: function detach(context, settings, trigger) {
var editors = void 0;
detach: function (context, settings, trigger) {
var editors;
// The 'serialize' trigger indicates that we should simply update the
// underlying element with the new text, without destroying the editor.
if (trigger === 'serialize') {
// Removing the editor-processed class guarantees that the editor will
// be reattached. Only do this if we're planning to destroy the editor.
editors = $(context).find('[data-editor-for]').findOnce('editor');
}
else {
} else {
editors = $(context).find('[data-editor-for]').removeOnce('editor');
}
@ -223,96 +169,25 @@
}
};
/**
* Attaches editor behaviors to the field.
*
* @param {HTMLElement} field
* The textarea DOM element.
* @param {object} format
* The text format that's being activated, from
* drupalSettings.editor.formats.
*
* @listens event:change
*
* @fires event:formUpdated
*/
Drupal.editorAttach = function (field, format) {
if (format.editor) {
// Attach the text editor.
Drupal.editors[format.editor].attach(field, format);
// Ensures form.js' 'formUpdated' event is triggered even for changes that
// happen within the text editor.
Drupal.editors[format.editor].onChange(field, function () {
$(field).trigger('formUpdated');
// Keep track of changes, so we know what to do when switching text
// formats and guaranteeing XSS protection.
field.setAttribute('data-editor-value-is-changed', 'true');
});
}
};
/**
* Detaches editor behaviors from the field.
*
* @param {HTMLElement} field
* The textarea DOM element.
* @param {object} format
* The text format that's being activated, from
* drupalSettings.editor.formats.
* @param {string} trigger
* Trigger value from the detach behavior.
*/
Drupal.editorDetach = function (field, format, trigger) {
if (format.editor) {
Drupal.editors[format.editor].detach(field, format, trigger);
// Restore the original value if the user didn't make any changes yet.
if (field.getAttribute('data-editor-value-is-changed') === 'false') {
field.value = field.getAttribute('data-editor-value-original');
}
}
};
/**
* Filter away XSS attack vectors when switching text formats.
*
* @param {HTMLElement} field
* The textarea DOM element.
* @param {object} format
* The text format that's being activated, from
* drupalSettings.editor.formats.
* @param {string} originalFormatID
* The text format ID of the original text format.
* @param {function} callback
* A callback to be called (with no parameters) after the field's value has
* been XSS filtered.
*/
function filterXssWhenSwitching(field, format, originalFormatID, callback) {
// A text editor that already is XSS-safe needs no additional measures.
if (format.editor.isXssSafe) {
callback(field, format);
}
// Otherwise, ensure XSS safety: let the server XSS filter this value.
else {
$.ajax({
url: Drupal.url('editor/filter_xss/' + format.format),
type: 'POST',
data: {
value: field.value,
original_format_id: originalFormatID
},
dataType: 'json',
success: function (xssFilteredValue) {
// If the server returns false, then no XSS filtering is needed.
if (xssFilteredValue !== false) {
field.value = xssFilteredValue;
}
callback(field, format);
}
});
}
}
})(jQuery, Drupal, drupalSettings);
})(jQuery, Drupal, drupalSettings);