Security update for Core, with self-updated composer
[yaffs-website] / web / core / modules / editor / js / editor.formattedTextEditor.es6.js
1 /**
2  * @file
3  * Text editor-based in-place editor for formatted text content in Drupal.
4  *
5  * Depends on editor.module. Works with any (WYSIWYG) editor that implements the
6  * editor.js API, including the optional attachInlineEditor() and onChange()
7  * methods.
8  * For example, assuming that a hypothetical editor's name was "Magical Editor"
9  * and its editor.js API implementation lived at Drupal.editors.magical, this
10  * JavaScript would use:
11  *  - Drupal.editors.magical.attachInlineEditor()
12  */
13
14 (function ($, Drupal, drupalSettings, _) {
15   Drupal.quickedit.editors.editor = Drupal.quickedit.EditorView.extend(/** @lends Drupal.quickedit.editors.editor# */{
16
17     /**
18      * The text format for this field.
19      *
20      * @type {string}
21      */
22     textFormat: null,
23
24     /**
25      * Indicates whether this text format has transformations.
26      *
27      * @type {bool}
28      */
29     textFormatHasTransformations: null,
30
31     /**
32      * Stores a reference to the text editor object for this field.
33      *
34      * @type {Drupal.quickedit.EditorModel}
35      */
36     textEditor: null,
37
38     /**
39      * Stores the textual DOM element that is being in-place edited.
40      *
41      * @type {jQuery}
42      */
43     $textElement: null,
44
45     /**
46      * @constructs
47      *
48      * @augments Drupal.quickedit.EditorView
49      *
50      * @param {object} options
51      *   Options for the editor view.
52      */
53     initialize(options) {
54       Drupal.quickedit.EditorView.prototype.initialize.call(this, options);
55
56       const metadata = Drupal.quickedit.metadata.get(this.fieldModel.get('fieldID'), 'custom');
57       this.textFormat = drupalSettings.editor.formats[metadata.format];
58       this.textFormatHasTransformations = metadata.formatHasTransformations;
59       this.textEditor = Drupal.editors[this.textFormat.editor];
60
61       // Store the actual value of this field. We'll need this to restore the
62       // original value when the user discards his modifications.
63       const $fieldItems = this.$el.find('.quickedit-field');
64       if ($fieldItems.length) {
65         this.$textElement = $fieldItems.eq(0);
66       }
67       else {
68         this.$textElement = this.$el;
69       }
70       this.model.set('originalValue', this.$textElement.html());
71     },
72
73     /**
74      * @inheritdoc
75      *
76      * @return {jQuery}
77      *   The text element edited.
78      */
79     getEditedElement() {
80       return this.$textElement;
81     },
82
83     /**
84      * @inheritdoc
85      *
86      * @param {object} fieldModel
87      *   The field model.
88      * @param {string} state
89      *   The current state.
90      */
91     stateChange(fieldModel, state) {
92       const editorModel = this.model;
93       const from = fieldModel.previous('state');
94       const to = state;
95       switch (to) {
96         case 'inactive':
97           break;
98
99         case 'candidate':
100           // Detach the text editor when entering the 'candidate' state from one
101           // of the states where it could have been attached.
102           if (from !== 'inactive' && from !== 'highlighted') {
103             this.textEditor.detach(this.$textElement.get(0), this.textFormat);
104           }
105           // A field model's editor view revert() method is invoked when an
106           // 'active' field becomes a 'candidate' field. But, in the case of
107           // this in-place editor, the content will have been *replaced* if the
108           // text format has transformation filters. Therefore, if we stop
109           // in-place editing this entity, revert explicitly.
110           if (from === 'active' && this.textFormatHasTransformations) {
111             this.revert();
112           }
113           if (from === 'invalid') {
114             this.removeValidationErrors();
115           }
116           break;
117
118         case 'highlighted':
119           break;
120
121         case 'activating':
122           // When transformation filters have been applied to the formatted text
123           // of this field, then we'll need to load a re-formatted version of it
124           // without the transformation filters.
125           if (this.textFormatHasTransformations) {
126             const $textElement = this.$textElement;
127             this._getUntransformedText((untransformedText) => {
128               $textElement.html(untransformedText);
129               fieldModel.set('state', 'active');
130             });
131           }
132           // When no transformation filters have been applied: start WYSIWYG
133           // editing immediately!
134           else {
135             // Defer updating the model until the current state change has
136             // propagated, to not trigger a nested state change event.
137             _.defer(() => {
138               fieldModel.set('state', 'active');
139             });
140           }
141           break;
142
143         case 'active':
144           var textElement = this.$textElement.get(0);
145           var toolbarView = fieldModel.toolbarView;
146           this.textEditor.attachInlineEditor(
147             textElement,
148             this.textFormat,
149             toolbarView.getMainWysiwygToolgroupId(),
150             toolbarView.getFloatedWysiwygToolgroupId(),
151           );
152           // Set the state to 'changed' whenever the content has changed.
153           this.textEditor.onChange(textElement, (htmlText) => {
154             editorModel.set('currentValue', htmlText);
155             fieldModel.set('state', 'changed');
156           });
157           break;
158
159         case 'changed':
160           break;
161
162         case 'saving':
163           if (from === 'invalid') {
164             this.removeValidationErrors();
165           }
166           this.save();
167           break;
168
169         case 'saved':
170           break;
171
172         case 'invalid':
173           this.showValidationErrors();
174           break;
175       }
176     },
177
178     /**
179      * @inheritdoc
180      *
181      * @return {object}
182      *   The sttings for the quick edit UI.
183      */
184     getQuickEditUISettings() {
185       return { padding: true, unifiedToolbar: true, fullWidthToolbar: true, popup: false };
186     },
187
188     /**
189      * @inheritdoc
190      */
191     revert() {
192       this.$textElement.html(this.model.get('originalValue'));
193     },
194
195     /**
196      * Loads untransformed text for this field.
197      *
198      * More accurately: it re-filters formatted text to exclude transformation
199      * filters used by the text format.
200      *
201      * @param {function} callback
202      *   A callback function that will receive the untransformed text.
203      *
204      * @see \Drupal\editor\Ajax\GetUntransformedTextCommand
205      */
206     _getUntransformedText(callback) {
207       const fieldID = this.fieldModel.get('fieldID');
208
209       // Create a Drupal.ajax instance to load the form.
210       const textLoaderAjax = Drupal.ajax({
211         url: Drupal.quickedit.util.buildUrl(fieldID, Drupal.url('editor/!entity_type/!id/!field_name/!langcode/!view_mode')),
212         submit: { nocssjs: true },
213       });
214
215       // Implement a scoped editorGetUntransformedText AJAX command: calls the
216       // callback.
217       textLoaderAjax.commands.editorGetUntransformedText = function (ajax, response, status) {
218         callback(response.data);
219       };
220
221       // This will ensure our scoped editorGetUntransformedText AJAX command
222       // gets called.
223       textLoaderAjax.execute();
224     },
225
226   });
227 }(jQuery, Drupal, drupalSettings, _));