
| Current Path : /var/www/html/rocksensor1/web/modules/contrib/ckeditor/js/plugins/drupalimage/ |
Linux ift1.ift-informatik.de 5.4.0-216-generic #236-Ubuntu SMP Fri Apr 11 19:53:21 UTC 2025 x86_64 |
| Current File : /var/www/html/rocksensor1/web/modules/contrib/ckeditor/js/plugins/drupalimage/plugin.es6.js |
/**
* @file
* Drupal Image plugin.
*
* This alters the existing CKEditor image2 widget plugin to:
* - require a data-entity-type and a data-entity-uuid attribute (which Drupal
* uses to track where images are being used)
* - use a Drupal-native dialog (that is in fact just an alterable Drupal form
* like any other) instead of CKEditor's own dialogs.
*
* @see \Drupal\editor\Form\EditorImageDialog
*
* @ignore
*/
(function ($, Drupal, CKEDITOR) {
/**
* Gets the focused widget, if of the type specific for this plugin.
*
* @param {CKEDITOR.editor} editor
* A CKEditor instance.
*
* @return {?CKEDITOR.plugins.widget}
* The focused image2 widget instance, or null.
*/
function getFocusedWidget(editor) {
const widget = editor.widgets.focused;
if (widget && widget.name === 'image') {
return widget;
}
return null;
}
/**
* Integrates the drupalimage widget with the drupallink plugin.
*
* Makes images linkable.
*
* @param {CKEDITOR.editor} editor
* A CKEditor instance.
*/
function linkCommandIntegrator(editor) {
// Nothing to integrate with if the drupallink plugin is not loaded.
if (!editor.plugins.drupallink) {
return;
}
CKEDITOR.plugins.drupallink.registerLinkableWidget('image');
// Override default behavior of 'drupalunlink' command.
editor.getCommand('drupalunlink').on('exec', function (evt) {
const widget = getFocusedWidget(editor);
// Override 'drupalunlink' only when link truly belongs to the widget. If
// wrapped inline widget in a link, let default unlink work.
// @see https://dev.ckeditor.com/ticket/11814
if (!widget || !widget.parts.link) {
return;
}
widget.setData('link', null);
// Selection (which is fake) may not change if unlinked image in focused
// widget, i.e. if captioned image. Let's refresh command state manually
// here.
this.refresh(editor, editor.elementPath());
evt.cancel();
});
// Override default refresh of 'drupalunlink' command.
editor.getCommand('drupalunlink').on('refresh', function (evt) {
const widget = getFocusedWidget(editor);
if (!widget) {
return;
}
// Note that widget may be wrapped in a link, which
// does not belong to that widget (#11814).
this.setState(
widget.data.link || widget.wrapper.getAscendant('a')
? CKEDITOR.TRISTATE_OFF
: CKEDITOR.TRISTATE_DISABLED,
);
evt.cancel();
});
}
CKEDITOR.plugins.add('drupalimage', {
requires: 'image2',
icons: 'drupalimage',
hidpi: true,
beforeInit(editor) {
// Override the image2 widget definition to require and handle the
// additional data-entity-type and data-entity-uuid attributes.
editor.on('widgetDefinition', (event) => {
const widgetDefinition = event.data;
if (widgetDefinition.name !== 'image') {
return;
}
// First, convert requiredContent & allowedContent from the string
// format that image2 uses for both to formats that are better suited
// for extending, so that both this basic drupalimage plugin and Drupal
// modules can easily extend it.
// @see http://docs.ckeditor.com/#!/api/CKEDITOR.filter.allowedContentRules
// Mapped from image2's allowedContent. Unlike image2, we don't allow
// <figure>, <figcaption>, <div> or <p> in our downcast, so we omit
// those. For the <img> tag, we list all attributes it lists, but omit
// the classes, because the listed classes are for alignment, and for
// alignment we use the data-align attribute.
widgetDefinition.allowedContent = {
img: {
attributes: {
'!src': true,
'!alt': true,
width: true,
height: true,
},
classes: {},
},
};
// Mapped from image2's requiredContent: "img[src,alt]". This does not
// use the object format unlike above, but a CKEDITOR.style instance,
// because requiredContent does not support the object format.
// @see https://www.drupal.org/node/2585173#comment-10456981
widgetDefinition.requiredContent = new CKEDITOR.style({
element: 'img',
attributes: {
src: '',
alt: '',
},
});
// Extend requiredContent & allowedContent.
// CKEDITOR.style is an immutable object: we cannot modify its
// definition to extend requiredContent. Hence we get the definition,
// modify it, and pass it to a new CKEDITOR.style instance.
const requiredContent =
widgetDefinition.requiredContent.getDefinition();
requiredContent.attributes['data-entity-type'] = '';
requiredContent.attributes['data-entity-uuid'] = '';
widgetDefinition.requiredContent = new CKEDITOR.style(requiredContent);
widgetDefinition.allowedContent.img.attributes[
'!data-entity-type'
] = true;
widgetDefinition.allowedContent.img.attributes[
'!data-entity-uuid'
] = true;
// Override downcast(): since we only accept <img> in our upcast method,
// the element is already correct. We only need to update the element's
// data-entity-uuid attribute.
widgetDefinition.downcast = function (element) {
element.attributes['data-entity-type'] =
this.data['data-entity-type'];
element.attributes['data-entity-uuid'] =
this.data['data-entity-uuid'];
};
// We want to upcast <img> elements to a DOM structure required by the
// image2 widget; we only accept an <img> tag, and that <img> tag MAY
// have a data-entity-type and a data-entity-uuid attribute.
widgetDefinition.upcast = function (element, data) {
if (element.name !== 'img') {
return;
}
// Don't initialize on pasted fake objects.
if (element.attributes['data-cke-realelement']) {
return;
}
// Parse the data-entity-type attribute.
data['data-entity-type'] = element.attributes['data-entity-type'];
// Parse the data-entity-uuid attribute.
data['data-entity-uuid'] = element.attributes['data-entity-uuid'];
return element;
};
// Overrides default implementation. Used to populate the "classes"
// property of the widget's "data" property, which is used for the
// "widget styles" functionality
// (http://docs.ckeditor.com/#!/guide/dev_styles-section-widget-styles).
// Is applied to whatever the main element of the widget is (<figure> or
// <img>). The classes in image2_captionedClass are always added due to
// a bug in CKEditor. In the case of drupalimage, we don't ever want to
// add that class, because the widget template already contains it.
// @see http://dev.ckeditor.com/ticket/13888
// @see https://www.drupal.org/node/2268941
const originalGetClasses = widgetDefinition.getClasses;
widgetDefinition.getClasses = function () {
const classes = originalGetClasses.call(this);
const captionedClasses = (
this.editor.config.image2_captionedClass || ''
).split(/\s+/);
if (captionedClasses.length && classes) {
for (let i = 0; i < captionedClasses.length; i++) {
if (captionedClasses[i] in classes) {
delete classes[captionedClasses[i]];
}
}
}
return classes;
};
// Protected; keys of the widget data to be sent to the Drupal dialog.
// Keys in the hash are the keys for image2's data, values are the keys
// that the Drupal dialog uses.
widgetDefinition._mapDataToDialog = {
src: 'src',
alt: 'alt',
width: 'width',
height: 'height',
'data-entity-type': 'data-entity-type',
'data-entity-uuid': 'data-entity-uuid',
};
// Protected; transforms widget's data object to the format used by the
// \Drupal\editor\Form\EditorImageDialog dialog, keeping only the data
// listed in widgetDefinition._dataForDialog.
widgetDefinition._dataToDialogValues = function (data) {
const dialogValues = {};
const map = widgetDefinition._mapDataToDialog;
Object.keys(widgetDefinition._mapDataToDialog).forEach((key) => {
dialogValues[map[key]] = data[key];
});
return dialogValues;
};
// Protected; the inverse of _dataToDialogValues.
widgetDefinition._dialogValuesToData = function (dialogReturnValues) {
const data = {};
const map = widgetDefinition._mapDataToDialog;
Object.keys(widgetDefinition._mapDataToDialog).forEach((key) => {
if (dialogReturnValues.hasOwnProperty(map[key])) {
data[key] = dialogReturnValues[map[key]];
}
});
return data;
};
// Protected; creates Drupal dialog save callback.
widgetDefinition._createDialogSaveCallback = function (editor, widget) {
return function (dialogReturnValues) {
const firstEdit = !widget.ready;
// Dialog may have blurred the widget. Re-focus it first.
if (!firstEdit) {
widget.focus();
}
editor.fire('saveSnapshot');
// Pass `true` so DocumentFragment will also be returned.
const container = widget.wrapper.getParent(true);
const image = widget.parts.image;
// Set the updated widget data, after the necessary conversions from
// the dialog's return values.
// Note: on widget#setData this widget instance might be destroyed.
const data = widgetDefinition._dialogValuesToData(
dialogReturnValues.attributes,
);
widget.setData(data);
// Retrieve the widget once again. It could've been destroyed
// when shifting state, so might deal with a new instance.
widget = editor.widgets.getByElement(image);
// It's first edit, just after widget instance creation, but before
// it was inserted into DOM. So we need to retrieve the widget
// wrapper from inside the DocumentFragment which we cached above
// and finalize other things (like ready event and flag).
if (firstEdit) {
editor.widgets.finalizeCreation(container);
}
setTimeout(() => {
// (Re-)focus the widget.
widget.focus();
// Save snapshot for undo support.
editor.fire('saveSnapshot');
});
return widget;
};
};
const originalInit = widgetDefinition.init;
widgetDefinition.init = function () {
originalInit.call(this);
// Update data.link object with attributes if the link has been
// discovered.
// @see plugins/image2/plugin.js/init() in CKEditor; this is similar.
if (this.parts.link) {
this.setData(
'link',
CKEDITOR.plugins.image2.getLinkAttributesParser()(
editor,
this.parts.link,
),
);
}
};
});
// Add a widget#edit listener to every instance of image2 widget in order
// to handle its editing with a Drupal-native dialog.
// This includes also a case just after the image was created
// and dialog should be opened for it for the first time.
editor.widgets.on('instanceCreated', (event) => {
const widget = event.data;
if (widget.name !== 'image') {
return;
}
widget.on('edit', (event) => {
// Cancel edit event to break image2's dialog binding
// (and also to prevent automatic insertion before opening dialog).
event.cancel();
// Open drupalimage dialog.
editor.execCommand('editdrupalimage', {
existingValues: widget.definition._dataToDialogValues(widget.data),
saveCallback: widget.definition._createDialogSaveCallback(
editor,
widget,
),
// 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.
dialogTitle: widget.data.src
? editor.config.drupalImage_dialogTitleEdit
: editor.config.drupalImage_dialogTitleAdd,
});
});
});
// Register the "editdrupalimage" command, which essentially just replaces
// the "image" command's CKEditor dialog with a Drupal-native dialog.
editor.addCommand('editdrupalimage', {
allowedContent:
'img[alt,!src,width,height,!data-entity-type,!data-entity-uuid]',
requiredContent: 'img[alt,src,data-entity-type,data-entity-uuid]',
modes: { wysiwyg: 1 },
canUndo: true,
exec(editor, data) {
const dialogSettings = {
title: data.dialogTitle,
dialogClass: 'editor-image-dialog',
};
Drupal.ckeditor.openDialog(
editor,
Drupal.url(`editor/dialog/image/${editor.config.drupal.format}`),
data.existingValues,
data.saveCallback,
dialogSettings,
);
},
});
// Register the toolbar button.
if (editor.ui.addButton) {
editor.ui.addButton('DrupalImage', {
label: Drupal.t('Image'),
// Note that we use the original image2 command!
command: 'image',
});
}
},
afterInit(editor) {
linkCommandIntegrator(editor);
},
});
// Override image2's integration with the official CKEditor link plugin:
// integrate with the drupallink plugin instead.
CKEDITOR.plugins.image2.getLinkAttributesParser = function () {
return CKEDITOR.plugins.drupallink.parseLinkAttributes;
};
CKEDITOR.plugins.image2.getLinkAttributesGetter = function () {
return CKEDITOR.plugins.drupallink.getLinkAttributes;
};
// Expose an API for other plugins to interact with drupalimage widgets.
CKEDITOR.plugins.drupalimage = {
getFocusedWidget,
};
})(jQuery, Drupal, CKEDITOR);