-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
/
tableproperties.ts
236 lines (204 loc) · 6.41 KB
/
tableproperties.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
/**
* @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 table/converters/tableproperties
*/
import type { Conversion, ViewElement } from 'ckeditor5/src/engine';
/**
* Conversion helper for upcasting attributes using normalized styles.
*
* @param options.modelAttribute The attribute to set.
* @param options.styleName The style name to convert.
* @param options.viewElement The view element name that should be converted.
* @param options.defaultValue The default value for the specified `modelAttribute`.
* @param options.shouldUpcast The function which returns `true` if style should be upcasted from this element.
*/
export function upcastStyleToAttribute(
conversion: Conversion,
options: {
modelAttribute: string;
styleName: string;
viewElement: string | RegExp;
defaultValue: string;
reduceBoxSides?: boolean;
shouldUpcast?: ( viewElement: ViewElement ) => boolean;
}
): void {
const {
modelAttribute,
styleName,
viewElement,
defaultValue,
reduceBoxSides = false,
shouldUpcast = () => true
} = options;
conversion.for( 'upcast' ).attributeToAttribute( {
view: {
name: viewElement,
styles: {
[ styleName ]: /[\s\S]+/
}
},
model: {
key: modelAttribute,
value: ( viewElement: ViewElement ) => {
if ( !shouldUpcast( viewElement ) ) {
return;
}
const normalized = viewElement.getNormalizedStyle( styleName ) as Record<Side, string>;
const value = reduceBoxSides ? reduceBoxSidesValue( normalized ) : normalized;
if ( defaultValue !== value ) {
return value;
}
}
}
} );
}
export interface StyleValues {
color: string;
style: string;
width: string;
}
/**
* Conversion helper for upcasting border styles for view elements.
*
* @param defaultBorder The default border values.
* @param defaultBorder.color The default `borderColor` value.
* @param defaultBorder.style The default `borderStyle` value.
* @param defaultBorder.width The default `borderWidth` value.
*/
export function upcastBorderStyles(
conversion: Conversion,
viewElementName: string,
modelAttributes: StyleValues,
defaultBorder: StyleValues
): void {
conversion.for( 'upcast' ).add( dispatcher => dispatcher.on( 'element:' + viewElementName, ( evt, data, conversionApi ) => {
// If the element was not converted by element-to-element converter,
// we should not try to convert the style. See #8393.
if ( !data.modelRange ) {
return;
}
// Check the most detailed properties. These will be always set directly or
// when using the "group" properties like: `border-(top|right|bottom|left)` or `border`.
const stylesToConsume = [
'border-top-width',
'border-top-color',
'border-top-style',
'border-bottom-width',
'border-bottom-color',
'border-bottom-style',
'border-right-width',
'border-right-color',
'border-right-style',
'border-left-width',
'border-left-color',
'border-left-style'
].filter( styleName => data.viewItem.hasStyle( styleName ) );
if ( !stylesToConsume.length ) {
return;
}
const matcherPattern = {
styles: stylesToConsume
};
// Try to consume appropriate values from consumable values list.
if ( !conversionApi.consumable.test( data.viewItem, matcherPattern ) ) {
return;
}
const modelElement = [ ...data.modelRange.getItems( { shallow: true } ) ].pop();
conversionApi.consumable.consume( data.viewItem, matcherPattern );
const normalizedBorder = {
style: data.viewItem.getNormalizedStyle( 'border-style' ),
color: data.viewItem.getNormalizedStyle( 'border-color' ),
width: data.viewItem.getNormalizedStyle( 'border-width' )
};
const reducedBorder = {
style: reduceBoxSidesValue( normalizedBorder.style ),
color: reduceBoxSidesValue( normalizedBorder.color ),
width: reduceBoxSidesValue( normalizedBorder.width )
};
if ( reducedBorder.style !== defaultBorder.style ) {
conversionApi.writer.setAttribute( modelAttributes.style, reducedBorder.style, modelElement );
}
if ( reducedBorder.color !== defaultBorder.color ) {
conversionApi.writer.setAttribute( modelAttributes.color, reducedBorder.color, modelElement );
}
if ( reducedBorder.width !== defaultBorder.width ) {
conversionApi.writer.setAttribute( modelAttributes.width, reducedBorder.width, modelElement );
}
} ) );
}
/**
* Conversion helper for downcasting an attribute to a style.
*/
export function downcastAttributeToStyle(
conversion: Conversion,
options: {
modelElement: string;
modelAttribute: string;
styleName: string;
}
): void {
const { modelElement, modelAttribute, styleName } = options;
conversion.for( 'downcast' ).attributeToAttribute( {
model: {
name: modelElement,
key: modelAttribute
},
view: modelAttributeValue => ( {
key: 'style',
value: {
[ styleName ]: modelAttributeValue
}
} )
} );
}
/**
* Conversion helper for downcasting attributes from the model table to a view table (not to `<figure>`).
*/
export function downcastTableAttribute(
conversion: Conversion,
options: {
modelAttribute: string;
styleName: string;
}
): void {
const { modelAttribute, styleName } = options;
conversion.for( 'downcast' ).add( dispatcher => dispatcher.on( `attribute:${ modelAttribute }:table`, ( evt, data, conversionApi ) => {
const { item, attributeNewValue } = data;
const { mapper, writer } = conversionApi;
if ( !conversionApi.consumable.consume( data.item, evt.name ) ) {
return;
}
const table = [ ...mapper.toViewElement( item ).getChildren() ].find( child => child.is( 'element', 'table' ) );
if ( attributeNewValue ) {
writer.setStyle( styleName, attributeNewValue, table );
} else {
writer.removeStyle( styleName, table );
}
} ) );
}
type Side = 'top' | 'right' | 'bottom' | 'left';
type Style = Record<Side, string>;
/**
* Reduces the full top, right, bottom, left object to a single string if all sides are equal.
* Returns original style otherwise.
*/
function reduceBoxSidesValue( style?: Style ): undefined | string | Style {
if ( !style ) {
return;
}
const sides: Array<Side> = [ 'top', 'right', 'bottom', 'left' ];
const allSidesDefined = sides.every( side => style[ side ] );
if ( !allSidesDefined ) {
return style;
}
const topSideStyle = style.top;
const allSidesEqual = sides.every( side => style[ side ] === topSideStyle );
if ( !allSidesEqual ) {
return style;
}
return topSideStyle;
}