UNPKG

6.41 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 * Conversion helper for upcasting attributes using normalized styles.
7 *
8 * @param options.modelAttribute The attribute to set.
9 * @param options.styleName The style name to convert.
10 * @param options.viewElement The view element name that should be converted.
11 * @param options.defaultValue The default value for the specified `modelAttribute`.
12 * @param options.shouldUpcast The function which returns `true` if style should be upcasted from this element.
13 */
14export function upcastStyleToAttribute(conversion, options) {
15 const { modelAttribute, styleName, viewElement, defaultValue, reduceBoxSides = false, shouldUpcast = () => true } = options;
16 conversion.for('upcast').attributeToAttribute({
17 view: {
18 name: viewElement,
19 styles: {
20 [styleName]: /[\s\S]+/
21 }
22 },
23 model: {
24 key: modelAttribute,
25 value: (viewElement) => {
26 if (!shouldUpcast(viewElement)) {
27 return;
28 }
29 const normalized = viewElement.getNormalizedStyle(styleName);
30 const value = reduceBoxSides ? reduceBoxSidesValue(normalized) : normalized;
31 if (defaultValue !== value) {
32 return value;
33 }
34 }
35 }
36 });
37}
38/**
39 * Conversion helper for upcasting border styles for view elements.
40 *
41 * @param defaultBorder The default border values.
42 * @param defaultBorder.color The default `borderColor` value.
43 * @param defaultBorder.style The default `borderStyle` value.
44 * @param defaultBorder.width The default `borderWidth` value.
45 */
46export function upcastBorderStyles(conversion, viewElementName, modelAttributes, defaultBorder) {
47 conversion.for('upcast').add(dispatcher => dispatcher.on('element:' + viewElementName, (evt, data, conversionApi) => {
48 // If the element was not converted by element-to-element converter,
49 // we should not try to convert the style. See #8393.
50 if (!data.modelRange) {
51 return;
52 }
53 // Check the most detailed properties. These will be always set directly or
54 // when using the "group" properties like: `border-(top|right|bottom|left)` or `border`.
55 const stylesToConsume = [
56 'border-top-width',
57 'border-top-color',
58 'border-top-style',
59 'border-bottom-width',
60 'border-bottom-color',
61 'border-bottom-style',
62 'border-right-width',
63 'border-right-color',
64 'border-right-style',
65 'border-left-width',
66 'border-left-color',
67 'border-left-style'
68 ].filter(styleName => data.viewItem.hasStyle(styleName));
69 if (!stylesToConsume.length) {
70 return;
71 }
72 const matcherPattern = {
73 styles: stylesToConsume
74 };
75 // Try to consume appropriate values from consumable values list.
76 if (!conversionApi.consumable.test(data.viewItem, matcherPattern)) {
77 return;
78 }
79 const modelElement = [...data.modelRange.getItems({ shallow: true })].pop();
80 conversionApi.consumable.consume(data.viewItem, matcherPattern);
81 const normalizedBorder = {
82 style: data.viewItem.getNormalizedStyle('border-style'),
83 color: data.viewItem.getNormalizedStyle('border-color'),
84 width: data.viewItem.getNormalizedStyle('border-width')
85 };
86 const reducedBorder = {
87 style: reduceBoxSidesValue(normalizedBorder.style),
88 color: reduceBoxSidesValue(normalizedBorder.color),
89 width: reduceBoxSidesValue(normalizedBorder.width)
90 };
91 if (reducedBorder.style !== defaultBorder.style) {
92 conversionApi.writer.setAttribute(modelAttributes.style, reducedBorder.style, modelElement);
93 }
94 if (reducedBorder.color !== defaultBorder.color) {
95 conversionApi.writer.setAttribute(modelAttributes.color, reducedBorder.color, modelElement);
96 }
97 if (reducedBorder.width !== defaultBorder.width) {
98 conversionApi.writer.setAttribute(modelAttributes.width, reducedBorder.width, modelElement);
99 }
100 }));
101}
102/**
103 * Conversion helper for downcasting an attribute to a style.
104 */
105export function downcastAttributeToStyle(conversion, options) {
106 const { modelElement, modelAttribute, styleName } = options;
107 conversion.for('downcast').attributeToAttribute({
108 model: {
109 name: modelElement,
110 key: modelAttribute
111 },
112 view: modelAttributeValue => ({
113 key: 'style',
114 value: {
115 [styleName]: modelAttributeValue
116 }
117 })
118 });
119}
120/**
121 * Conversion helper for downcasting attributes from the model table to a view table (not to `<figure>`).
122 */
123export function downcastTableAttribute(conversion, options) {
124 const { modelAttribute, styleName } = options;
125 conversion.for('downcast').add(dispatcher => dispatcher.on(`attribute:${modelAttribute}:table`, (evt, data, conversionApi) => {
126 const { item, attributeNewValue } = data;
127 const { mapper, writer } = conversionApi;
128 if (!conversionApi.consumable.consume(data.item, evt.name)) {
129 return;
130 }
131 const table = [...mapper.toViewElement(item).getChildren()].find(child => child.is('element', 'table'));
132 if (attributeNewValue) {
133 writer.setStyle(styleName, attributeNewValue, table);
134 }
135 else {
136 writer.removeStyle(styleName, table);
137 }
138 }));
139}
140/**
141 * Reduces the full top, right, bottom, left object to a single string if all sides are equal.
142 * Returns original style otherwise.
143 */
144function reduceBoxSidesValue(style) {
145 if (!style) {
146 return;
147 }
148 const sides = ['top', 'right', 'bottom', 'left'];
149 const allSidesDefined = sides.every(side => style[side]);
150 if (!allSidesDefined) {
151 return style;
152 }
153 const topSideStyle = style.top;
154 const allSidesEqual = sides.every(side => style[side] === topSideStyle);
155 if (!allSidesEqual) {
156 return style;
157 }
158 return topSideStyle;
159}