Security update for Core, with self-updated composer
[yaffs-website] / web / core / modules / ckeditor / js / plugins / drupallink / plugin.es6.js
diff --git a/web/core/modules/ckeditor/js/plugins/drupallink/plugin.es6.js b/web/core/modules/ckeditor/js/plugins/drupallink/plugin.es6.js
new file mode 100644 (file)
index 0000000..7444fcc
--- /dev/null
@@ -0,0 +1,300 @@
+/**
+ * @file
+ * Drupal Link plugin.
+ *
+ * @ignore
+ */
+
+(function ($, Drupal, drupalSettings, CKEDITOR) {
+  function parseAttributes(editor, element) {
+    const parsedAttributes = {};
+
+    const domElement = element.$;
+    let attribute;
+    let attributeName;
+    for (let attrIndex = 0; attrIndex < domElement.attributes.length; attrIndex++) {
+      attribute = domElement.attributes.item(attrIndex);
+      attributeName = attribute.nodeName.toLowerCase();
+      // Ignore data-cke-* attributes; they're CKEditor internals.
+      if (attributeName.indexOf('data-cke-') === 0) {
+        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.
+      parsedAttributes[attributeName] = element.data(`cke-saved-${attributeName}`) || attribute.nodeValue;
+    }
+
+    // Remove any cke_* classes.
+    if (parsedAttributes.class) {
+      parsedAttributes.class = CKEDITOR.tools.trim(parsedAttributes.class.replace(/cke_\S+/, ''));
+    }
+
+    return parsedAttributes;
+  }
+
+  function getAttributes(editor, data) {
+    const set = {};
+    for (const attributeName in data) {
+      if (data.hasOwnProperty(attributeName)) {
+        set[attributeName] = data[attributeName];
+      }
+    }
+
+    // CKEditor tracks the *actual* saved href in a data-cke-saved-* attribute
+    // to work around browser quirks. We need to update it.
+    set['data-cke-saved-href'] = set.href;
+
+    // Remove all attributes which are not currently set.
+    const removed = {};
+    for (const s in set) {
+      if (set.hasOwnProperty(s)) {
+        delete removed[s];
+      }
+    }
+
+    return {
+      set,
+      removed: CKEDITOR.tools.objectKeys(removed),
+    };
+  }
+
+  CKEDITOR.plugins.add('drupallink', {
+    icons: 'drupallink,drupalunlink',
+    hidpi: true,
+
+    init(editor) {
+      // Add the commands for link and unlink.
+      editor.addCommand('drupallink', {
+        allowedContent: {
+          a: {
+            attributes: {
+              '!href': true,
+            },
+            classes: {},
+          },
+        },
+        requiredContent: new CKEDITOR.style({
+          element: 'a',
+          attributes: {
+            href: '',
+          },
+        }),
+        modes: { wysiwyg: 1 },
+        canUndo: true,
+        exec(editor) {
+          const drupalImageUtils = CKEDITOR.plugins.drupalimage;
+          const focusedImageWidget = drupalImageUtils && drupalImageUtils.getFocusedWidget(editor);
+          let linkElement = getSelectedLink(editor);
+
+          // Set existing values based on selected element.
+          let existingValues = {};
+          if (linkElement && linkElement.$) {
+            existingValues = parseAttributes(editor, linkElement);
+          }
+          // Or, if an image widget is focused, we're editing a link wrapping
+          // an image widget.
+          else if (focusedImageWidget && focusedImageWidget.data.link) {
+            existingValues = CKEDITOR.tools.clone(focusedImageWidget.data.link);
+          }
+
+          // Prepare a save callback to be used upon saving the dialog.
+          const saveCallback = function (returnValues) {
+            // If an image widget is focused, we're not editing an independent
+            // link, but we're wrapping an image widget in a link.
+            if (focusedImageWidget) {
+              focusedImageWidget.setData('link', CKEDITOR.tools.extend(returnValues.attributes, focusedImageWidget.data.link));
+              editor.fire('saveSnapshot');
+              return;
+            }
+
+            editor.fire('saveSnapshot');
+
+            // Create a new link element if needed.
+            if (!linkElement && returnValues.attributes.href) {
+              const selection = editor.getSelection();
+              const range = selection.getRanges(1)[0];
+
+              // Use link URL as text with a collapsed cursor.
+              if (range.collapsed) {
+                // Shorten mailto URLs to just the email address.
+                const text = new CKEDITOR.dom.text(returnValues.attributes.href.replace(/^mailto:/, ''), editor.document);
+                range.insertNode(text);
+                range.selectNodeContents(text);
+              }
+
+              // Create the new link by applying a style to the new text.
+              const style = new CKEDITOR.style({ element: 'a', attributes: returnValues.attributes });
+              style.type = CKEDITOR.STYLE_INLINE;
+              style.applyToRange(range);
+              range.select();
+
+              // Set the link so individual properties may be set below.
+              linkElement = getSelectedLink(editor);
+            }
+            // Update the link properties.
+            else if (linkElement) {
+              for (const attrName in returnValues.attributes) {
+                if (returnValues.attributes.hasOwnProperty(attrName)) {
+                  // Update the property if a value is specified.
+                  if (returnValues.attributes[attrName].length > 0) {
+                    const value = returnValues.attributes[attrName];
+                    linkElement.data(`cke-saved-${attrName}`, value);
+                    linkElement.setAttribute(attrName, value);
+                  }
+                  // Delete the property if set to an empty string.
+                  else {
+                    linkElement.removeAttribute(attrName);
+                  }
+                }
+              }
+            }
+
+            // Save snapshot for undo support.
+            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.
+          const dialogSettings = {
+            title: linkElement ? editor.config.drupalLink_dialogTitleEdit : editor.config.drupalLink_dialogTitleAdd,
+            dialogClass: 'editor-link-dialog',
+          };
+
+          // Open the dialog for the edit form.
+          Drupal.ckeditor.openDialog(editor, Drupal.url(`editor/dialog/link/${editor.config.drupal.format}`), existingValues, saveCallback, dialogSettings);
+        },
+      });
+      editor.addCommand('drupalunlink', {
+        contextSensitive: 1,
+        startDisabled: 1,
+        requiredContent: new CKEDITOR.style({
+          element: 'a',
+          attributes: {
+            href: '',
+          },
+        }),
+        exec(editor) {
+          const style = new CKEDITOR.style({ element: 'a', type: CKEDITOR.STYLE_INLINE, alwaysRemoveElement: 1 });
+          editor.removeStyle(style);
+        },
+        refresh(editor, path) {
+          const element = path.lastElement && path.lastElement.getAscendant('a', true);
+          if (element && element.getName() === 'a' && element.getAttribute('href') && element.getChildCount()) {
+            this.setState(CKEDITOR.TRISTATE_OFF);
+          }
+          else {
+            this.setState(CKEDITOR.TRISTATE_DISABLED);
+          }
+        },
+      });
+
+      // CTRL + K.
+      editor.setKeystroke(CKEDITOR.CTRL + 75, 'drupallink');
+
+      // Add buttons for link and unlink.
+      if (editor.ui.addButton) {
+        editor.ui.addButton('DrupalLink', {
+          label: Drupal.t('Link'),
+          command: 'drupallink',
+        });
+        editor.ui.addButton('DrupalUnlink', {
+          label: Drupal.t('Unlink'),
+          command: 'drupalunlink',
+        });
+      }
+
+      editor.on('doubleclick', (evt) => {
+        const element = getSelectedLink(editor) || evt.data.element;
+
+        if (!element.isReadOnly()) {
+          if (element.is('a')) {
+            editor.getSelection().selectElement(element);
+            editor.getCommand('drupallink').exec();
+          }
+        }
+      });
+
+      // If the "menu" plugin is loaded, register the menu items.
+      if (editor.addMenuItems) {
+        editor.addMenuItems({
+          link: {
+            label: Drupal.t('Edit Link'),
+            command: 'drupallink',
+            group: 'link',
+            order: 1,
+          },
+
+          unlink: {
+            label: Drupal.t('Unlink'),
+            command: 'drupalunlink',
+            group: 'link',
+            order: 5,
+          },
+        });
+      }
+
+      // If the "contextmenu" plugin is loaded, register the listeners.
+      if (editor.contextMenu) {
+        editor.contextMenu.addListener((element, selection) => {
+          if (!element || element.isReadOnly()) {
+            return null;
+          }
+          const anchor = getSelectedLink(editor);
+          if (!anchor) {
+            return null;
+          }
+
+          let menu = {};
+          if (anchor.getAttribute('href') && anchor.getChildCount()) {
+            menu = { link: CKEDITOR.TRISTATE_OFF, unlink: CKEDITOR.TRISTATE_OFF };
+          }
+          return menu;
+        });
+      }
+    },
+  });
+
+  /**
+   * Get the surrounding link element of current selection.
+   *
+   * The following selection will all return the link element.
+   *
+   * @example
+   *  <a href="#">li^nk</a>
+   *  <a href="#">[link]</a>
+   *  text[<a href="#">link]</a>
+   *  <a href="#">li[nk</a>]
+   *  [<b><a href="#">li]nk</a></b>]
+   *  [<a href="#"><b>li]nk</b></a>
+   *
+   * @param {CKEDITOR.editor} editor
+   *   The CKEditor editor object
+   *
+   * @return {?HTMLElement}
+   *   The selected link element, or null.
+   *
+   */
+  function getSelectedLink(editor) {
+    const selection = editor.getSelection();
+    const selectedElement = selection.getSelectedElement();
+    if (selectedElement && selectedElement.is('a')) {
+      return selectedElement;
+    }
+
+    const range = selection.getRanges(true)[0];
+
+    if (range) {
+      range.shrink(CKEDITOR.SHRINK_TEXT);
+      return editor.elementPath(range.getCommonAncestor()).contains('a', 1);
+    }
+    return null;
+  }
+
+  // Expose an API for other plugins to interact with drupallink widgets.
+  // (Compatible with the official CKEditor link plugin's API:
+  // http://dev.ckeditor.com/ticket/13885.)
+  CKEDITOR.plugins.drupallink = {
+    parseLinkAttributes: parseAttributes,
+    getLinkAttributes: getAttributes,
+  };
+}(jQuery, Drupal, drupalSettings, CKEDITOR));