UNPKG

6.55 kBJavaScriptView Raw
1/**
2 * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3 * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4 */
5/**
6 * @module link/utils/automaticdecorators
7 */
8import { toMap } from 'ckeditor5/src/utils';
9/**
10 * Helper class that ties together all {@link module:link/linkconfig~LinkDecoratorAutomaticDefinition} and provides
11 * the {@link module:engine/conversion/downcasthelpers~DowncastHelpers#attributeToElement downcast dispatchers} for them.
12 */
13export default class AutomaticDecorators {
14 constructor() {
15 /**
16 * Stores the definition of {@link module:link/linkconfig~LinkDecoratorAutomaticDefinition automatic decorators}.
17 * This data is used as a source for a downcast dispatcher to create a proper conversion to output data.
18 */
19 this._definitions = new Set();
20 }
21 /**
22 * Gives information about the number of decorators stored in the {@link module:link/utils/automaticdecorators~AutomaticDecorators}
23 * instance.
24 */
25 get length() {
26 return this._definitions.size;
27 }
28 /**
29 * Adds automatic decorator objects or an array with them to be used during downcasting.
30 *
31 * @param item A configuration object of automatic rules for decorating links. It might also be an array of such objects.
32 */
33 add(item) {
34 if (Array.isArray(item)) {
35 item.forEach(item => this._definitions.add(item));
36 }
37 else {
38 this._definitions.add(item);
39 }
40 }
41 /**
42 * Provides the conversion helper used in the {@link module:engine/conversion/downcasthelpers~DowncastHelpers#add} method.
43 *
44 * @returns A dispatcher function used as conversion helper in {@link module:engine/conversion/downcasthelpers~DowncastHelpers#add}.
45 */
46 getDispatcher() {
47 return dispatcher => {
48 dispatcher.on('attribute:linkHref', (evt, data, conversionApi) => {
49 // There is only test as this behavior decorates links and
50 // it is run before dispatcher which actually consumes this node.
51 // This allows on writing own dispatcher with highest priority,
52 // which blocks both native converter and this additional decoration.
53 if (!conversionApi.consumable.test(data.item, 'attribute:linkHref')) {
54 return;
55 }
56 // Automatic decorators for block links are handled e.g. in LinkImageEditing.
57 if (!(data.item.is('selection') || conversionApi.schema.isInline(data.item))) {
58 return;
59 }
60 const viewWriter = conversionApi.writer;
61 const viewSelection = viewWriter.document.selection;
62 for (const item of this._definitions) {
63 const viewElement = viewWriter.createAttributeElement('a', item.attributes, {
64 priority: 5
65 });
66 if (item.classes) {
67 viewWriter.addClass(item.classes, viewElement);
68 }
69 for (const key in item.styles) {
70 viewWriter.setStyle(key, item.styles[key], viewElement);
71 }
72 viewWriter.setCustomProperty('link', true, viewElement);
73 if (item.callback(data.attributeNewValue)) {
74 if (data.item.is('selection')) {
75 viewWriter.wrap(viewSelection.getFirstRange(), viewElement);
76 }
77 else {
78 viewWriter.wrap(conversionApi.mapper.toViewRange(data.range), viewElement);
79 }
80 }
81 else {
82 viewWriter.unwrap(conversionApi.mapper.toViewRange(data.range), viewElement);
83 }
84 }
85 }, { priority: 'high' });
86 };
87 }
88 /**
89 * Provides the conversion helper used in the {@link module:engine/conversion/downcasthelpers~DowncastHelpers#add} method
90 * when linking images.
91 *
92 * @returns A dispatcher function used as conversion helper in {@link module:engine/conversion/downcasthelpers~DowncastHelpers#add}.
93 */
94 getDispatcherForLinkedImage() {
95 return dispatcher => {
96 dispatcher.on('attribute:linkHref:imageBlock', (evt, data, { writer, mapper }) => {
97 const viewFigure = mapper.toViewElement(data.item);
98 const linkInImage = Array.from(viewFigure.getChildren())
99 .find((child) => child.is('element', 'a'));
100 for (const item of this._definitions) {
101 const attributes = toMap(item.attributes);
102 if (item.callback(data.attributeNewValue)) {
103 for (const [key, val] of attributes) {
104 // Left for backward compatibility. Since v30 decorator should
105 // accept `classes` and `styles` separately from `attributes`.
106 if (key === 'class') {
107 writer.addClass(val, linkInImage);
108 }
109 else {
110 writer.setAttribute(key, val, linkInImage);
111 }
112 }
113 if (item.classes) {
114 writer.addClass(item.classes, linkInImage);
115 }
116 for (const key in item.styles) {
117 writer.setStyle(key, item.styles[key], linkInImage);
118 }
119 }
120 else {
121 for (const [key, val] of attributes) {
122 if (key === 'class') {
123 writer.removeClass(val, linkInImage);
124 }
125 else {
126 writer.removeAttribute(key, linkInImage);
127 }
128 }
129 if (item.classes) {
130 writer.removeClass(item.classes, linkInImage);
131 }
132 for (const key in item.styles) {
133 writer.removeStyle(key, linkInImage);
134 }
135 }
136 }
137 });
138 };
139 }
140}