@ckeditor/ckeditor5-link
Version:
Link feature for CKEditor 5.
141 lines (140 loc) • 6.55 kB
JavaScript
/**
* @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module link/utils/automaticdecorators
*/
import { toMap } from 'ckeditor5/src/utils';
/**
* Helper class that ties together all {@link module:link/linkconfig~LinkDecoratorAutomaticDefinition} and provides
* the {@link module:engine/conversion/downcasthelpers~DowncastHelpers#attributeToElement downcast dispatchers} for them.
*/
export default class AutomaticDecorators {
constructor() {
/**
* Stores the definition of {@link module:link/linkconfig~LinkDecoratorAutomaticDefinition automatic decorators}.
* This data is used as a source for a downcast dispatcher to create a proper conversion to output data.
*/
this._definitions = new Set();
}
/**
* Gives information about the number of decorators stored in the {@link module:link/utils/automaticdecorators~AutomaticDecorators}
* instance.
*/
get length() {
return this._definitions.size;
}
/**
* Adds automatic decorator objects or an array with them to be used during downcasting.
*
* @param item A configuration object of automatic rules for decorating links. It might also be an array of such objects.
*/
add(item) {
if (Array.isArray(item)) {
item.forEach(item => this._definitions.add(item));
}
else {
this._definitions.add(item);
}
}
/**
* Provides the conversion helper used in the {@link module:engine/conversion/downcasthelpers~DowncastHelpers#add} method.
*
* @returns A dispatcher function used as conversion helper in {@link module:engine/conversion/downcasthelpers~DowncastHelpers#add}.
*/
getDispatcher() {
return dispatcher => {
dispatcher.on('attribute:linkHref', (evt, data, conversionApi) => {
// There is only test as this behavior decorates links and
// it is run before dispatcher which actually consumes this node.
// This allows on writing own dispatcher with highest priority,
// which blocks both native converter and this additional decoration.
if (!conversionApi.consumable.test(data.item, 'attribute:linkHref')) {
return;
}
// Automatic decorators for block links are handled e.g. in LinkImageEditing.
if (!(data.item.is('selection') || conversionApi.schema.isInline(data.item))) {
return;
}
const viewWriter = conversionApi.writer;
const viewSelection = viewWriter.document.selection;
for (const item of this._definitions) {
const viewElement = viewWriter.createAttributeElement('a', item.attributes, {
priority: 5
});
if (item.classes) {
viewWriter.addClass(item.classes, viewElement);
}
for (const key in item.styles) {
viewWriter.setStyle(key, item.styles[key], viewElement);
}
viewWriter.setCustomProperty('link', true, viewElement);
if (item.callback(data.attributeNewValue)) {
if (data.item.is('selection')) {
viewWriter.wrap(viewSelection.getFirstRange(), viewElement);
}
else {
viewWriter.wrap(conversionApi.mapper.toViewRange(data.range), viewElement);
}
}
else {
viewWriter.unwrap(conversionApi.mapper.toViewRange(data.range), viewElement);
}
}
}, { priority: 'high' });
};
}
/**
* Provides the conversion helper used in the {@link module:engine/conversion/downcasthelpers~DowncastHelpers#add} method
* when linking images.
*
* @returns A dispatcher function used as conversion helper in {@link module:engine/conversion/downcasthelpers~DowncastHelpers#add}.
*/
getDispatcherForLinkedImage() {
return dispatcher => {
dispatcher.on('attribute:linkHref:imageBlock', (evt, data, { writer, mapper }) => {
const viewFigure = mapper.toViewElement(data.item);
const linkInImage = Array.from(viewFigure.getChildren())
.find((child) => child.is('element', 'a'));
for (const item of this._definitions) {
const attributes = toMap(item.attributes);
if (item.callback(data.attributeNewValue)) {
for (const [key, val] of attributes) {
// Left for backward compatibility. Since v30 decorator should
// accept `classes` and `styles` separately from `attributes`.
if (key === 'class') {
writer.addClass(val, linkInImage);
}
else {
writer.setAttribute(key, val, linkInImage);
}
}
if (item.classes) {
writer.addClass(item.classes, linkInImage);
}
for (const key in item.styles) {
writer.setStyle(key, item.styles[key], linkInImage);
}
}
else {
for (const [key, val] of attributes) {
if (key === 'class') {
writer.removeClass(val, linkInImage);
}
else {
writer.removeAttribute(key, linkInImage);
}
}
if (item.classes) {
writer.removeClass(item.classes, linkInImage);
}
for (const key in item.styles) {
writer.removeStyle(key, linkInImage);
}
}
}
});
};
}
}