Security update for Core, with self-updated composer
[yaffs-website] / web / core / modules / editor / js / editor.formattedTextEditor.es6.js
diff --git a/web/core/modules/editor/js/editor.formattedTextEditor.es6.js b/web/core/modules/editor/js/editor.formattedTextEditor.es6.js
new file mode 100644 (file)
index 0000000..3005a38
--- /dev/null
@@ -0,0 +1,227 @@
+/**
+ * @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':
+          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, (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 sttings 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, _));