d4296fffc98919a4eb44eab2531549bc6bc28756
[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(
16     /** @lends Drupal.quickedit.editors.editor# */ {
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(
57           this.fieldModel.get('fieldID'),
58           'custom',
59         );
60         this.textFormat = drupalSettings.editor.formats[metadata.format];
61         this.textFormatHasTransformations = metadata.formatHasTransformations;
62         this.textEditor = Drupal.editors[this.textFormat.editor];
63
64         // Store the actual value of this field. We'll need this to restore the
65         // original value when the user discards his modifications.
66         const $fieldItems = this.$el.find('.quickedit-field');
67         if ($fieldItems.length) {
68           this.$textElement = $fieldItems.eq(0);
69         } else {
70           this.$textElement = this.$el;
71         }
72         this.model.set('originalValue', this.$textElement.html());
73       },
74
75       /**
76        * @inheritdoc
77        *
78        * @return {jQuery}
79        *   The text element edited.
80        */
81       getEditedElement() {
82         return this.$textElement;
83       },
84
85       /**
86        * @inheritdoc
87        *
88        * @param {object} fieldModel
89        *   The field model.
90        * @param {string} state
91        *   The current state.
92        */
93       stateChange(fieldModel, state) {
94         const editorModel = this.model;
95         const from = fieldModel.previous('state');
96         const to = state;
97         switch (to) {
98           case 'inactive':
99             break;
100
101           case 'candidate':
102             // Detach the text editor when entering the 'candidate' state from one
103             // of the states where it could have been attached.
104             if (from !== 'inactive' && from !== 'highlighted') {
105               this.textEditor.detach(this.$textElement.get(0), this.textFormat);
106             }
107             // A field model's editor view revert() method is invoked when an
108             // 'active' field becomes a 'candidate' field. But, in the case of
109             // this in-place editor, the content will have been *replaced* if the
110             // text format has transformation filters. Therefore, if we stop
111             // in-place editing this entity, revert explicitly.
112             if (from === 'active' && this.textFormatHasTransformations) {
113               this.revert();
114             }
115             if (from === 'invalid') {
116               this.removeValidationErrors();
117             }
118             break;
119
120           case 'highlighted':
121             break;
122
123           case 'activating':
124             // When transformation filters have been applied to the formatted text
125             // of this field, then we'll need to load a re-formatted version of it
126             // without the transformation filters.
127             if (this.textFormatHasTransformations) {
128               const $textElement = this.$textElement;
129               this._getUntransformedText(untransformedText => {
130                 $textElement.html(untransformedText);
131                 fieldModel.set('state', 'active');
132               });
133             }
134             // When no transformation filters have been applied: start WYSIWYG
135             // editing immediately!
136             else {
137               // Defer updating the model until the current state change has
138               // propagated, to not trigger a nested state change event.
139               _.defer(() => {
140                 fieldModel.set('state', 'active');
141               });
142             }
143             break;
144
145           case 'active': {
146             const textElement = this.$textElement.get(0);
147             const toolbarView = fieldModel.toolbarView;
148             this.textEditor.attachInlineEditor(
149               textElement,
150               this.textFormat,
151               toolbarView.getMainWysiwygToolgroupId(),
152               toolbarView.getFloatedWysiwygToolgroupId(),
153             );
154             // Set the state to 'changed' whenever the content has changed.
155             this.textEditor.onChange(textElement, htmlText => {
156               editorModel.set('currentValue', htmlText);
157               fieldModel.set('state', 'changed');
158             });
159             break;
160           }
161
162           case 'changed':
163             break;
164
165           case 'saving':
166             if (from === 'invalid') {
167               this.removeValidationErrors();
168             }
169             this.save();
170             break;
171
172           case 'saved':
173             break;
174
175           case 'invalid':
176             this.showValidationErrors();
177             break;
178         }
179       },
180
181       /**
182        * @inheritdoc
183        *
184        * @return {object}
185        *   The settings for the quick edit UI.
186        */
187       getQuickEditUISettings() {
188         return {
189           padding: true,
190           unifiedToolbar: true,
191           fullWidthToolbar: true,
192           popup: false,
193         };
194       },
195
196       /**
197        * @inheritdoc
198        */
199       revert() {
200         this.$textElement.html(this.model.get('originalValue'));
201       },
202
203       /**
204        * Loads untransformed text for this field.
205        *
206        * More accurately: it re-filters formatted text to exclude transformation
207        * filters used by the text format.
208        *
209        * @param {function} callback
210        *   A callback function that will receive the untransformed text.
211        *
212        * @see \Drupal\editor\Ajax\GetUntransformedTextCommand
213        */
214       _getUntransformedText(callback) {
215         const fieldID = this.fieldModel.get('fieldID');
216
217         // Create a Drupal.ajax instance to load the form.
218         const textLoaderAjax = Drupal.ajax({
219           url: Drupal.quickedit.util.buildUrl(
220             fieldID,
221             Drupal.url(
222               'editor/!entity_type/!id/!field_name/!langcode/!view_mode',
223             ),
224           ),
225           submit: { nocssjs: true },
226         });
227
228         // Implement a scoped editorGetUntransformedText AJAX command: calls the
229         // callback.
230         textLoaderAjax.commands.editorGetUntransformedText = function(
231           ajax,
232           response,
233           status,
234         ) {
235           callback(response.data);
236         };
237
238         // This will ensure our scoped editorGetUntransformedText AJAX command
239         // gets called.
240         textLoaderAjax.execute();
241       },
242     },
243   );
244 })(jQuery, Drupal, drupalSettings, _);