@ckeditor/ckeditor5-style 41.3.1 → 41.4.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/dist/index-content.css +4 -0
  2. package/dist/index-editor.css +25 -0
  3. package/dist/index.css +35 -0
  4. package/dist/index.css.map +1 -0
  5. package/dist/index.js +1213 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/translations/ar.d.ts +8 -0
  8. package/dist/translations/ar.js +5 -0
  9. package/dist/translations/bg.d.ts +8 -0
  10. package/dist/translations/bg.js +5 -0
  11. package/dist/translations/bn.d.ts +8 -0
  12. package/dist/translations/bn.js +5 -0
  13. package/dist/translations/ca.d.ts +8 -0
  14. package/dist/translations/ca.js +5 -0
  15. package/dist/translations/cs.d.ts +8 -0
  16. package/dist/translations/cs.js +5 -0
  17. package/dist/translations/da.d.ts +8 -0
  18. package/dist/translations/da.js +5 -0
  19. package/dist/translations/de.d.ts +8 -0
  20. package/dist/translations/de.js +5 -0
  21. package/dist/translations/el.d.ts +8 -0
  22. package/dist/translations/el.js +5 -0
  23. package/dist/translations/en-au.d.ts +8 -0
  24. package/dist/translations/en-au.js +5 -0
  25. package/dist/translations/en.d.ts +8 -0
  26. package/dist/translations/en.js +5 -0
  27. package/dist/translations/es.d.ts +8 -0
  28. package/dist/translations/es.js +5 -0
  29. package/dist/translations/et.d.ts +8 -0
  30. package/dist/translations/et.js +5 -0
  31. package/dist/translations/fi.d.ts +8 -0
  32. package/dist/translations/fi.js +5 -0
  33. package/dist/translations/fr.d.ts +8 -0
  34. package/dist/translations/fr.js +5 -0
  35. package/dist/translations/gl.d.ts +8 -0
  36. package/dist/translations/gl.js +5 -0
  37. package/dist/translations/he.d.ts +8 -0
  38. package/dist/translations/he.js +5 -0
  39. package/dist/translations/hi.d.ts +8 -0
  40. package/dist/translations/hi.js +5 -0
  41. package/dist/translations/hr.d.ts +8 -0
  42. package/dist/translations/hr.js +5 -0
  43. package/dist/translations/hu.d.ts +8 -0
  44. package/dist/translations/hu.js +5 -0
  45. package/dist/translations/id.d.ts +8 -0
  46. package/dist/translations/id.js +5 -0
  47. package/dist/translations/it.d.ts +8 -0
  48. package/dist/translations/it.js +5 -0
  49. package/dist/translations/ja.d.ts +8 -0
  50. package/dist/translations/ja.js +5 -0
  51. package/dist/translations/ko.d.ts +8 -0
  52. package/dist/translations/ko.js +5 -0
  53. package/dist/translations/lt.d.ts +8 -0
  54. package/dist/translations/lt.js +5 -0
  55. package/dist/translations/lv.d.ts +8 -0
  56. package/dist/translations/lv.js +5 -0
  57. package/dist/translations/ms.d.ts +8 -0
  58. package/dist/translations/ms.js +5 -0
  59. package/dist/translations/nl.d.ts +8 -0
  60. package/dist/translations/nl.js +5 -0
  61. package/dist/translations/no.d.ts +8 -0
  62. package/dist/translations/no.js +5 -0
  63. package/dist/translations/pl.d.ts +8 -0
  64. package/dist/translations/pl.js +5 -0
  65. package/dist/translations/pt-br.d.ts +8 -0
  66. package/dist/translations/pt-br.js +5 -0
  67. package/dist/translations/pt.d.ts +8 -0
  68. package/dist/translations/pt.js +5 -0
  69. package/dist/translations/ro.d.ts +8 -0
  70. package/dist/translations/ro.js +5 -0
  71. package/dist/translations/ru.d.ts +8 -0
  72. package/dist/translations/ru.js +5 -0
  73. package/dist/translations/sk.d.ts +8 -0
  74. package/dist/translations/sk.js +5 -0
  75. package/dist/translations/sr-latn.d.ts +8 -0
  76. package/dist/translations/sr-latn.js +5 -0
  77. package/dist/translations/sr.d.ts +8 -0
  78. package/dist/translations/sr.js +5 -0
  79. package/dist/translations/sv.d.ts +8 -0
  80. package/dist/translations/sv.js +5 -0
  81. package/dist/translations/th.d.ts +8 -0
  82. package/dist/translations/th.js +5 -0
  83. package/dist/translations/tr.d.ts +8 -0
  84. package/dist/translations/tr.js +5 -0
  85. package/dist/translations/ug.d.ts +8 -0
  86. package/dist/translations/ug.js +5 -0
  87. package/dist/translations/uk.d.ts +8 -0
  88. package/dist/translations/uk.js +5 -0
  89. package/dist/translations/ur.d.ts +8 -0
  90. package/dist/translations/ur.js +5 -0
  91. package/dist/translations/vi.d.ts +8 -0
  92. package/dist/translations/vi.js +5 -0
  93. package/dist/translations/zh-cn.d.ts +8 -0
  94. package/dist/translations/zh-cn.js +5 -0
  95. package/dist/translations/zh.d.ts +8 -0
  96. package/dist/translations/zh.js +5 -0
  97. package/dist/types/augmentation.d.ts +28 -0
  98. package/dist/types/index.d.ts +18 -0
  99. package/dist/types/integrations/link.d.ts +41 -0
  100. package/dist/types/integrations/list.d.ts +46 -0
  101. package/dist/types/integrations/table.d.ts +53 -0
  102. package/dist/types/style.d.ts +30 -0
  103. package/dist/types/stylecommand.d.ts +86 -0
  104. package/dist/types/styleconfig.d.ts +91 -0
  105. package/dist/types/styleediting.d.ts +37 -0
  106. package/dist/types/styleui.d.ts +34 -0
  107. package/dist/types/styleutils.d.ts +142 -0
  108. package/dist/types/ui/stylegridbuttonview.d.ts +38 -0
  109. package/dist/types/ui/stylegridview.d.ts +76 -0
  110. package/dist/types/ui/stylegroupview.d.ts +39 -0
  111. package/dist/types/ui/stylepanelview.d.ts +93 -0
  112. package/package.json +3 -2
package/dist/index.js ADDED
@@ -0,0 +1,1213 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2024, 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
+ import { Plugin, Command } from '@ckeditor/ckeditor5-core/dist/index.js';
6
+ import { ButtonView, View, addKeyboardHandlingForGrid, LabelView, ViewCollection, FocusCycler, createDropdown } from '@ckeditor/ckeditor5-ui/dist/index.js';
7
+ import { FocusTracker, KeystrokeHandler, first, logWarning } from '@ckeditor/ckeditor5-utils/dist/index.js';
8
+ import { isObject } from 'lodash-es';
9
+ import { findAttributeRange, findAttributeRangeBound } from '@ckeditor/ckeditor5-typing/dist/index.js';
10
+
11
+ class StyleGridButtonView extends ButtonView {
12
+ /**
13
+ * Creates the view representing the preview of the style.
14
+ */ _createPreview() {
15
+ const previewView = new View(this.locale);
16
+ previewView.setTemplate({
17
+ tag: 'div',
18
+ attributes: {
19
+ class: [
20
+ 'ck',
21
+ 'ck-reset_all-excluded',
22
+ 'ck-style-grid__button__preview',
23
+ 'ck-content'
24
+ ],
25
+ // The preview "AaBbCcDdEeFfGgHhIiJj" should not be read by screen readers because it is purely presentational.
26
+ 'aria-hidden': 'true'
27
+ },
28
+ children: [
29
+ this.styleDefinition.previewTemplate
30
+ ]
31
+ });
32
+ return previewView;
33
+ }
34
+ /**
35
+ * Creates an instance of the {@link module:style/ui/stylegridbuttonview~StyleGridButtonView} class.
36
+ *
37
+ * @param locale The localization services instance.
38
+ * @param styleDefinition Definition of the style.
39
+ */ constructor(locale, styleDefinition){
40
+ super(locale);
41
+ this.styleDefinition = styleDefinition;
42
+ this.previewView = this._createPreview();
43
+ this.set({
44
+ label: styleDefinition.name,
45
+ class: 'ck-style-grid__button',
46
+ withText: true
47
+ });
48
+ this.extendTemplate({
49
+ attributes: {
50
+ role: 'option'
51
+ }
52
+ });
53
+ this.children.add(this.previewView, 0);
54
+ }
55
+ }
56
+
57
+ class StyleGridView extends View {
58
+ /**
59
+ * @inheritDoc
60
+ */ render() {
61
+ super.render();
62
+ for (const child of this.children){
63
+ this.focusTracker.add(child.element);
64
+ }
65
+ addKeyboardHandlingForGrid({
66
+ keystrokeHandler: this.keystrokes,
67
+ focusTracker: this.focusTracker,
68
+ gridItems: this.children,
69
+ numberOfColumns: 3,
70
+ uiLanguageDirection: this.locale && this.locale.uiLanguageDirection
71
+ });
72
+ // Start listening for the keystrokes coming from the grid view.
73
+ this.keystrokes.listenTo(this.element);
74
+ }
75
+ /**
76
+ * Focuses the first style button in the grid.
77
+ */ focus() {
78
+ this.children.first.focus();
79
+ }
80
+ /**
81
+ * @inheritDoc
82
+ */ destroy() {
83
+ super.destroy();
84
+ this.focusTracker.destroy();
85
+ this.keystrokes.destroy();
86
+ }
87
+ /**
88
+ * Creates an instance of the {@link module:style/ui/stylegridview~StyleGridView} class.
89
+ *
90
+ * @param locale The localization services instance.
91
+ * @param styleDefinitions Definitions of the styles.
92
+ */ constructor(locale, styleDefinitions){
93
+ super(locale);
94
+ this.focusTracker = new FocusTracker();
95
+ this.keystrokes = new KeystrokeHandler();
96
+ this.set('activeStyles', []);
97
+ this.set('enabledStyles', []);
98
+ this.children = this.createCollection();
99
+ this.children.delegate('execute').to(this);
100
+ for (const definition of styleDefinitions){
101
+ const gridTileView = new StyleGridButtonView(locale, definition);
102
+ this.children.add(gridTileView);
103
+ }
104
+ this.on('change:activeStyles', ()=>{
105
+ for (const child of this.children){
106
+ child.isOn = this.activeStyles.includes(child.styleDefinition.name);
107
+ }
108
+ });
109
+ this.on('change:enabledStyles', ()=>{
110
+ for (const child of this.children){
111
+ child.isEnabled = this.enabledStyles.includes(child.styleDefinition.name);
112
+ }
113
+ });
114
+ this.setTemplate({
115
+ tag: 'div',
116
+ attributes: {
117
+ class: [
118
+ 'ck',
119
+ 'ck-style-grid'
120
+ ],
121
+ role: 'listbox'
122
+ },
123
+ children: this.children
124
+ });
125
+ }
126
+ }
127
+
128
+ class StyleGroupView extends View {
129
+ /**
130
+ * Creates an instance of the {@link module:style/ui/stylegroupview~StyleGroupView} class.
131
+ *
132
+ * @param locale The localization services instance.
133
+ * @param label The localized label of the group.
134
+ * @param styleDefinitions Definitions of the styles in the group.
135
+ */ constructor(locale, label, styleDefinitions){
136
+ super(locale);
137
+ this.labelView = new LabelView(locale);
138
+ this.labelView.text = label;
139
+ this.gridView = new StyleGridView(locale, styleDefinitions);
140
+ this.setTemplate({
141
+ tag: 'div',
142
+ attributes: {
143
+ class: [
144
+ 'ck',
145
+ 'ck-style-panel__style-group'
146
+ ],
147
+ role: 'group',
148
+ 'aria-labelledby': this.labelView.id
149
+ },
150
+ children: [
151
+ this.labelView,
152
+ this.gridView
153
+ ]
154
+ });
155
+ }
156
+ }
157
+
158
+ class StylePanelView extends View {
159
+ /**
160
+ * @inheritDoc
161
+ */ render() {
162
+ super.render();
163
+ // Register the views as focusable.
164
+ this._focusables.add(this.blockStylesGroupView.gridView);
165
+ this._focusables.add(this.inlineStylesGroupView.gridView);
166
+ // Register the views in the focus tracker.
167
+ this.focusTracker.add(this.blockStylesGroupView.gridView.element);
168
+ this.focusTracker.add(this.inlineStylesGroupView.gridView.element);
169
+ this.keystrokes.listenTo(this.element);
170
+ }
171
+ /**
172
+ * Focuses the first focusable element in the panel.
173
+ */ focus() {
174
+ this._focusCycler.focusFirst();
175
+ }
176
+ /**
177
+ * Focuses the last focusable element in the panel.
178
+ */ focusLast() {
179
+ this._focusCycler.focusLast();
180
+ }
181
+ /**
182
+ * Creates an instance of the {@link module:style/ui/stylegroupview~StyleGroupView} class.
183
+ *
184
+ * @param locale The localization services instance.
185
+ * @param styleDefinitions Normalized definitions of the styles.
186
+ */ constructor(locale, styleDefinitions){
187
+ super(locale);
188
+ const t = locale.t;
189
+ this.focusTracker = new FocusTracker();
190
+ this.keystrokes = new KeystrokeHandler();
191
+ this.children = this.createCollection();
192
+ this.blockStylesGroupView = new StyleGroupView(locale, t('Block styles'), styleDefinitions.block);
193
+ this.inlineStylesGroupView = new StyleGroupView(locale, t('Text styles'), styleDefinitions.inline);
194
+ this.set('activeStyles', []);
195
+ this.set('enabledStyles', []);
196
+ this._focusables = new ViewCollection();
197
+ this._focusCycler = new FocusCycler({
198
+ focusables: this._focusables,
199
+ focusTracker: this.focusTracker,
200
+ keystrokeHandler: this.keystrokes,
201
+ actions: {
202
+ // Navigate style groups backwards using the <kbd>Shift</kbd> + <kbd>Tab</kbd> keystroke.
203
+ focusPrevious: [
204
+ 'shift + tab'
205
+ ],
206
+ // Navigate style groups forward using the <kbd>Tab</kbd> key.
207
+ focusNext: [
208
+ 'tab'
209
+ ]
210
+ }
211
+ });
212
+ if (styleDefinitions.block.length) {
213
+ this.children.add(this.blockStylesGroupView);
214
+ }
215
+ if (styleDefinitions.inline.length) {
216
+ this.children.add(this.inlineStylesGroupView);
217
+ }
218
+ this.blockStylesGroupView.gridView.delegate('execute').to(this);
219
+ this.inlineStylesGroupView.gridView.delegate('execute').to(this);
220
+ this.blockStylesGroupView.gridView.bind('activeStyles', 'enabledStyles').to(this, 'activeStyles', 'enabledStyles');
221
+ this.inlineStylesGroupView.gridView.bind('activeStyles', 'enabledStyles').to(this, 'activeStyles', 'enabledStyles');
222
+ this.setTemplate({
223
+ tag: 'div',
224
+ attributes: {
225
+ class: [
226
+ 'ck',
227
+ 'ck-style-panel'
228
+ ]
229
+ },
230
+ children: this.children
231
+ });
232
+ }
233
+ }
234
+
235
+ // These are intermediate element names that can't be rendered as style preview because they don't make sense standalone.
236
+ const NON_PREVIEWABLE_ELEMENT_NAMES = [
237
+ 'caption',
238
+ 'colgroup',
239
+ 'dd',
240
+ 'dt',
241
+ 'figcaption',
242
+ 'legend',
243
+ 'li',
244
+ 'optgroup',
245
+ 'option',
246
+ 'rp',
247
+ 'rt',
248
+ 'summary',
249
+ 'tbody',
250
+ 'td',
251
+ 'tfoot',
252
+ 'th',
253
+ 'thead',
254
+ 'tr'
255
+ ];
256
+ class StyleUtils extends Plugin {
257
+ /**
258
+ * @inheritDoc
259
+ */ static get pluginName() {
260
+ return 'StyleUtils';
261
+ }
262
+ /**
263
+ * @inheritDoc
264
+ */ init() {
265
+ this._htmlSupport = this.editor.plugins.get('GeneralHtmlSupport');
266
+ }
267
+ /**
268
+ * Normalizes {@link module:style/styleconfig~StyleConfig#definitions} in the configuration of the styles feature.
269
+ * The structure of normalized styles looks as follows:
270
+ *
271
+ * ```ts
272
+ * {
273
+ * block: [
274
+ * <module:style/style~StyleDefinition>,
275
+ * <module:style/style~StyleDefinition>,
276
+ * ...
277
+ * ],
278
+ * inline: [
279
+ * <module:style/style~StyleDefinition>,
280
+ * <module:style/style~StyleDefinition>,
281
+ * ...
282
+ * ]
283
+ * }
284
+ * ```
285
+ *
286
+ * @returns An object with normalized style definitions grouped into `block` and `inline` categories (arrays).
287
+ */ normalizeConfig(dataSchema, styleDefinitions = []) {
288
+ const normalizedDefinitions = {
289
+ block: [],
290
+ inline: []
291
+ };
292
+ for (const definition of styleDefinitions){
293
+ const modelElements = [];
294
+ const ghsAttributes = [];
295
+ for (const ghsDefinition of dataSchema.getDefinitionsForView(definition.element)){
296
+ const appliesToBlock = 'appliesToBlock' in ghsDefinition ? ghsDefinition.appliesToBlock : false;
297
+ if (ghsDefinition.isBlock || appliesToBlock) {
298
+ if (typeof appliesToBlock == 'string') {
299
+ modelElements.push(appliesToBlock);
300
+ } else if (ghsDefinition.isBlock) {
301
+ const ghsBlockDefinition = ghsDefinition;
302
+ modelElements.push(ghsDefinition.model);
303
+ if (ghsBlockDefinition.paragraphLikeModel) {
304
+ modelElements.push(ghsBlockDefinition.paragraphLikeModel);
305
+ }
306
+ }
307
+ } else {
308
+ ghsAttributes.push(ghsDefinition.model);
309
+ }
310
+ }
311
+ const previewTemplate = this.getStylePreview(definition, [
312
+ {
313
+ text: 'AaBbCcDdEeFfGgHhIiJj'
314
+ }
315
+ ]);
316
+ if (modelElements.length) {
317
+ normalizedDefinitions.block.push({
318
+ ...definition,
319
+ previewTemplate,
320
+ modelElements,
321
+ isBlock: true
322
+ });
323
+ } else {
324
+ normalizedDefinitions.inline.push({
325
+ ...definition,
326
+ previewTemplate,
327
+ ghsAttributes
328
+ });
329
+ }
330
+ }
331
+ return normalizedDefinitions;
332
+ }
333
+ /**
334
+ * Verifies if the given style is applicable to the provided block element.
335
+ *
336
+ * @internal
337
+ */ isStyleEnabledForBlock(definition, block) {
338
+ const model = this.editor.model;
339
+ const attributeName = this._htmlSupport.getGhsAttributeNameForElement(definition.element);
340
+ if (!model.schema.checkAttribute(block, attributeName)) {
341
+ return false;
342
+ }
343
+ return definition.modelElements.includes(block.name);
344
+ }
345
+ /**
346
+ * Returns true if the given style is applied to the specified block element.
347
+ *
348
+ * @internal
349
+ */ isStyleActiveForBlock(definition, block) {
350
+ const attributeName = this._htmlSupport.getGhsAttributeNameForElement(definition.element);
351
+ const ghsAttributeValue = block.getAttribute(attributeName);
352
+ return this.hasAllClasses(ghsAttributeValue, definition.classes);
353
+ }
354
+ /**
355
+ * Returns an array of block elements that style should be applied to.
356
+ *
357
+ * @internal
358
+ */ getAffectedBlocks(definition, block) {
359
+ if (definition.modelElements.includes(block.name)) {
360
+ return [
361
+ block
362
+ ];
363
+ }
364
+ return null;
365
+ }
366
+ /**
367
+ * Verifies if the given style is applicable to the provided document selection.
368
+ *
369
+ * @internal
370
+ */ isStyleEnabledForInlineSelection(definition, selection) {
371
+ const model = this.editor.model;
372
+ for (const ghsAttributeName of definition.ghsAttributes){
373
+ if (model.schema.checkAttributeInSelection(selection, ghsAttributeName)) {
374
+ return true;
375
+ }
376
+ }
377
+ return false;
378
+ }
379
+ /**
380
+ * Returns true if the given style is applied to the specified document selection.
381
+ *
382
+ * @internal
383
+ */ isStyleActiveForInlineSelection(definition, selection) {
384
+ for (const ghsAttributeName of definition.ghsAttributes){
385
+ const ghsAttributeValue = this._getValueFromFirstAllowedNode(selection, ghsAttributeName);
386
+ if (this.hasAllClasses(ghsAttributeValue, definition.classes)) {
387
+ return true;
388
+ }
389
+ }
390
+ return false;
391
+ }
392
+ /**
393
+ * Returns a selectable that given style should be applied to.
394
+ *
395
+ * @internal
396
+ */ getAffectedInlineSelectable(definition, selection) {
397
+ return selection;
398
+ }
399
+ /**
400
+ * Returns the `TemplateDefinition` used by styles dropdown to render style preview.
401
+ *
402
+ * @internal
403
+ */ getStylePreview(definition, children) {
404
+ const { element, classes } = definition;
405
+ return {
406
+ tag: isPreviewable(element) ? element : 'div',
407
+ attributes: {
408
+ class: classes
409
+ },
410
+ children
411
+ };
412
+ }
413
+ /**
414
+ * Verifies if all classes are present in the given GHS attribute.
415
+ *
416
+ * @internal
417
+ */ hasAllClasses(ghsAttributeValue, classes) {
418
+ return isObject(ghsAttributeValue) && hasClassesProperty(ghsAttributeValue) && classes.every((className)=>ghsAttributeValue.classes.includes(className));
419
+ }
420
+ /**
421
+ * This is where the styles feature configures the GHS feature. This method translates normalized
422
+ * {@link module:style/styleconfig~StyleDefinition style definitions} to
423
+ * {@link module:engine/view/matcher~MatcherObjectPattern matcher patterns} and feeds them to the GHS
424
+ * {@link module:html-support/datafilter~DataFilter} plugin.
425
+ *
426
+ * @internal
427
+ */ configureGHSDataFilter({ block, inline }) {
428
+ const ghsDataFilter = this.editor.plugins.get('DataFilter');
429
+ ghsDataFilter.loadAllowedConfig(block.map(normalizedStyleDefinitionToMatcherPattern));
430
+ ghsDataFilter.loadAllowedConfig(inline.map(normalizedStyleDefinitionToMatcherPattern));
431
+ }
432
+ /**
433
+ * Checks the attribute value of the first node in the selection that allows the attribute.
434
+ * For the collapsed selection, returns the selection attribute.
435
+ *
436
+ * @param selection The document selection.
437
+ * @param attributeName Name of the GHS attribute.
438
+ * @returns The attribute value.
439
+ */ _getValueFromFirstAllowedNode(selection, attributeName) {
440
+ const model = this.editor.model;
441
+ const schema = model.schema;
442
+ if (selection.isCollapsed) {
443
+ return selection.getAttribute(attributeName);
444
+ }
445
+ for (const range of selection.getRanges()){
446
+ for (const item of range.getItems()){
447
+ if (schema.checkAttribute(item, attributeName)) {
448
+ return item.getAttribute(attributeName);
449
+ }
450
+ }
451
+ }
452
+ return null;
453
+ }
454
+ /**
455
+ * @inheritDoc
456
+ */ constructor(editor){
457
+ super(editor);
458
+ this.decorate('isStyleEnabledForBlock');
459
+ this.decorate('isStyleActiveForBlock');
460
+ this.decorate('getAffectedBlocks');
461
+ this.decorate('isStyleEnabledForInlineSelection');
462
+ this.decorate('isStyleActiveForInlineSelection');
463
+ this.decorate('getAffectedInlineSelectable');
464
+ this.decorate('getStylePreview');
465
+ this.decorate('configureGHSDataFilter');
466
+ }
467
+ }
468
+ /**
469
+ * Checks if given object has `classes` property which is an array.
470
+ *
471
+ * @param obj Object to check.
472
+ */ function hasClassesProperty(obj) {
473
+ return Boolean(obj.classes) && Array.isArray(obj.classes);
474
+ }
475
+ /**
476
+ * Decides whether an element should be created in the preview or a substitute `<div>` should
477
+ * be used instead. This avoids previewing a standalone `<td>`, `<li>`, etc. without a parent.
478
+ *
479
+ * @param elementName Name of the element
480
+ * @returns Boolean indicating whether the element can be rendered.
481
+ */ function isPreviewable(elementName) {
482
+ return !NON_PREVIEWABLE_ELEMENT_NAMES.includes(elementName);
483
+ }
484
+ /**
485
+ * Translates a normalized style definition to a view matcher pattern.
486
+ */ function normalizedStyleDefinitionToMatcherPattern({ element, classes }) {
487
+ return {
488
+ name: element,
489
+ classes
490
+ };
491
+ }
492
+
493
+ class StyleUI extends Plugin {
494
+ /**
495
+ * @inheritDoc
496
+ */ static get pluginName() {
497
+ return 'StyleUI';
498
+ }
499
+ /**
500
+ * @inheritDoc
501
+ */ static get requires() {
502
+ return [
503
+ StyleUtils
504
+ ];
505
+ }
506
+ /**
507
+ * @inheritDoc
508
+ */ init() {
509
+ const editor = this.editor;
510
+ const dataSchema = editor.plugins.get('DataSchema');
511
+ const styleUtils = editor.plugins.get('StyleUtils');
512
+ const styleDefinitions = editor.config.get('style.definitions');
513
+ const normalizedStyleDefinitions = styleUtils.normalizeConfig(dataSchema, styleDefinitions);
514
+ // Add the dropdown to the component factory.
515
+ editor.ui.componentFactory.add('style', (locale)=>{
516
+ const t = locale.t;
517
+ const dropdown = createDropdown(locale);
518
+ const styleCommand = editor.commands.get('style');
519
+ dropdown.once('change:isOpen', ()=>{
520
+ const panelView = new StylePanelView(locale, normalizedStyleDefinitions);
521
+ // Put the styles panel is the dropdown.
522
+ dropdown.panelView.children.add(panelView);
523
+ // Close the dropdown when a style is selected in the styles panel.
524
+ panelView.delegate('execute').to(dropdown);
525
+ // Bind the state of the styles panel to the command.
526
+ panelView.bind('activeStyles').to(styleCommand, 'value');
527
+ panelView.bind('enabledStyles').to(styleCommand, 'enabledStyles');
528
+ });
529
+ // The entire dropdown will be disabled together with the command (e.g. when the editor goes read-only).
530
+ dropdown.bind('isEnabled').to(styleCommand);
531
+ // This dropdown has no icon. It displays text label depending on the selection.
532
+ dropdown.buttonView.withText = true;
533
+ // The label of the dropdown is dynamic and depends on how many styles are active at a time.
534
+ dropdown.buttonView.bind('label').to(styleCommand, 'value', (value)=>{
535
+ if (value.length > 1) {
536
+ return t('Multiple styles');
537
+ } else if (value.length === 1) {
538
+ return value[0];
539
+ } else {
540
+ return t('Styles');
541
+ }
542
+ });
543
+ // The dropdown has a static CSS class for easy customization. There's another CSS class
544
+ // that gets displayed when multiple styles are active at a time allowing visual customization of
545
+ // the label.
546
+ dropdown.bind('class').to(styleCommand, 'value', (value)=>{
547
+ const classes = [
548
+ 'ck-style-dropdown'
549
+ ];
550
+ if (value.length > 1) {
551
+ classes.push('ck-style-dropdown_multiple-active');
552
+ }
553
+ return classes.join(' ');
554
+ });
555
+ // Execute the command when a style is selected in the styles panel.
556
+ // Also focus the editable after executing the command.
557
+ // It overrides a default behaviour where the focus is moved to the dropdown button (#12125).
558
+ dropdown.on('execute', (evt)=>{
559
+ editor.execute('style', {
560
+ styleName: evt.source.styleDefinition.name
561
+ });
562
+ editor.editing.view.focus();
563
+ });
564
+ return dropdown;
565
+ });
566
+ }
567
+ }
568
+
569
+ class StyleCommand extends Command {
570
+ /**
571
+ * @inheritDoc
572
+ */ refresh() {
573
+ const model = this.editor.model;
574
+ const selection = model.document.selection;
575
+ const value = new Set();
576
+ const enabledStyles = new Set();
577
+ // Inline styles.
578
+ for (const definition of this._styleDefinitions.inline){
579
+ // Check if this inline style is enabled.
580
+ if (this._styleUtils.isStyleEnabledForInlineSelection(definition, selection)) {
581
+ enabledStyles.add(definition.name);
582
+ }
583
+ // Check if this inline style is active.
584
+ if (this._styleUtils.isStyleActiveForInlineSelection(definition, selection)) {
585
+ value.add(definition.name);
586
+ }
587
+ }
588
+ // Block styles.
589
+ const firstBlock = first(selection.getSelectedBlocks()) || selection.getFirstPosition().parent;
590
+ if (firstBlock) {
591
+ const ancestorBlocks = firstBlock.getAncestors({
592
+ includeSelf: true,
593
+ parentFirst: true
594
+ });
595
+ for (const block of ancestorBlocks){
596
+ if (block.is('rootElement')) {
597
+ break;
598
+ }
599
+ for (const definition of this._styleDefinitions.block){
600
+ // Check if this block style is enabled.
601
+ if (!this._styleUtils.isStyleEnabledForBlock(definition, block)) {
602
+ continue;
603
+ }
604
+ enabledStyles.add(definition.name);
605
+ // Check if this block style is active.
606
+ if (this._styleUtils.isStyleActiveForBlock(definition, block)) {
607
+ value.add(definition.name);
608
+ }
609
+ }
610
+ // E.g. reached a model table when the selection is in a cell. The command should not modify
611
+ // ancestors of a table.
612
+ if (model.schema.isObject(block)) {
613
+ break;
614
+ }
615
+ }
616
+ }
617
+ this.enabledStyles = Array.from(enabledStyles).sort();
618
+ this.isEnabled = this.enabledStyles.length > 0;
619
+ this.value = this.isEnabled ? Array.from(value).sort() : [];
620
+ }
621
+ /**
622
+ * Executes the command &ndash; applies the style classes to the selection or removes it from the selection.
623
+ *
624
+ * If the command value already contains the requested style, it will remove the style classes. Otherwise, it will set it.
625
+ *
626
+ * The execution result differs, depending on the {@link module:engine/model/document~Document#selection} and the
627
+ * style type (inline or block):
628
+ *
629
+ * * When applying inline styles:
630
+ * * If the selection is on a range, the command applies the style classes to all nodes in that range.
631
+ * * If the selection is collapsed in a non-empty node, the command applies the style classes to the
632
+ * {@link module:engine/model/document~Document#selection}.
633
+ *
634
+ * * When applying block styles:
635
+ * * If the selection is on a range, the command applies the style classes to the nearest block parent element.
636
+ *
637
+ * @fires execute
638
+ * @param options Command options.
639
+ * @param options.styleName Style name matching the one defined in the
640
+ * {@link module:style/styleconfig~StyleConfig#definitions configuration}.
641
+ * @param options.forceValue Whether the command should add given style (`true`) or remove it (`false`) from the selection.
642
+ * If not set (default), the command will toggle the style basing on the first selected node. Note, that this will not force
643
+ * setting a style on an element that cannot receive given style.
644
+ */ execute({ styleName, forceValue }) {
645
+ if (!this.enabledStyles.includes(styleName)) {
646
+ /**
647
+ * Style command can be executed only with a correct style name.
648
+ *
649
+ * This warning may be caused by:
650
+ *
651
+ * * passing a name that is not specified in the {@link module:style/styleconfig~StyleConfig#definitions configuration}
652
+ * (e.g. a CSS class name),
653
+ * * when trying to apply a style that is not allowed on a given element.
654
+ *
655
+ * @error style-command-executed-with-incorrect-style-name
656
+ */ logWarning('style-command-executed-with-incorrect-style-name');
657
+ return;
658
+ }
659
+ const model = this.editor.model;
660
+ const selection = model.document.selection;
661
+ const htmlSupport = this.editor.plugins.get('GeneralHtmlSupport');
662
+ const allDefinitions = [
663
+ ...this._styleDefinitions.inline,
664
+ ...this._styleDefinitions.block
665
+ ];
666
+ const activeDefinitions = allDefinitions.filter(({ name })=>this.value.includes(name));
667
+ const definition = allDefinitions.find(({ name })=>name == styleName);
668
+ const shouldAddStyle = forceValue === undefined ? !this.value.includes(definition.name) : forceValue;
669
+ model.change(()=>{
670
+ let selectables;
671
+ if (isBlockStyleDefinition(definition)) {
672
+ selectables = this._findAffectedBlocks(getBlocksFromSelection(selection), definition);
673
+ } else {
674
+ selectables = [
675
+ this._styleUtils.getAffectedInlineSelectable(definition, selection)
676
+ ];
677
+ }
678
+ for (const selectable of selectables){
679
+ if (shouldAddStyle) {
680
+ htmlSupport.addModelHtmlClass(definition.element, definition.classes, selectable);
681
+ } else {
682
+ htmlSupport.removeModelHtmlClass(definition.element, getDefinitionExclusiveClasses(activeDefinitions, definition), selectable);
683
+ }
684
+ }
685
+ });
686
+ }
687
+ /**
688
+ * Returns a set of elements that should be affected by the block-style change.
689
+ */ _findAffectedBlocks(selectedBlocks, definition) {
690
+ const blocks = new Set();
691
+ for (const selectedBlock of selectedBlocks){
692
+ const ancestorBlocks = selectedBlock.getAncestors({
693
+ includeSelf: true,
694
+ parentFirst: true
695
+ });
696
+ for (const block of ancestorBlocks){
697
+ if (block.is('rootElement')) {
698
+ break;
699
+ }
700
+ const affectedBlocks = this._styleUtils.getAffectedBlocks(definition, block);
701
+ if (affectedBlocks) {
702
+ for (const affectedBlock of affectedBlocks){
703
+ blocks.add(affectedBlock);
704
+ }
705
+ break;
706
+ }
707
+ }
708
+ }
709
+ return blocks;
710
+ }
711
+ /**
712
+ * Creates an instance of the command.
713
+ *
714
+ * @param editor Editor on which this command will be used.
715
+ * @param styleDefinitions Normalized definitions of the styles.
716
+ */ constructor(editor, styleDefinitions){
717
+ super(editor);
718
+ this.set('value', []);
719
+ this.set('enabledStyles', []);
720
+ this._styleDefinitions = styleDefinitions;
721
+ this._styleUtils = this.editor.plugins.get(StyleUtils);
722
+ }
723
+ }
724
+ /**
725
+ * Returns classes that are defined only in the supplied definition and not in any other active definition. It's used
726
+ * to ensure that classes used by other definitions are preserved when a style is removed. See #11748.
727
+ *
728
+ * @param activeDefinitions All currently active definitions affecting selected element(s).
729
+ * @param definition Definition whose classes will be compared with all other active definition classes.
730
+ * @returns Array of classes exclusive to the supplied definition.
731
+ */ function getDefinitionExclusiveClasses(activeDefinitions, definition) {
732
+ return activeDefinitions.reduce((classes, currentDefinition)=>{
733
+ if (currentDefinition.name === definition.name) {
734
+ return classes;
735
+ }
736
+ return classes.filter((className)=>!currentDefinition.classes.includes(className));
737
+ }, definition.classes);
738
+ }
739
+ /**
740
+ * Checks if provided style definition is of type block.
741
+ */ function isBlockStyleDefinition(definition) {
742
+ return 'isBlock' in definition;
743
+ }
744
+ /**
745
+ * Gets block elements from selection. If there are none, returns first selected element.
746
+ * @param selection Current document's selection.
747
+ * @returns Selected blocks if there are any, first selected element otherwise.
748
+ */ function getBlocksFromSelection(selection) {
749
+ const blocks = Array.from(selection.getSelectedBlocks());
750
+ if (blocks.length) {
751
+ return blocks;
752
+ }
753
+ return [
754
+ selection.getFirstPosition().parent
755
+ ];
756
+ }
757
+
758
+ class ListStyleSupport extends Plugin {
759
+ /**
760
+ * @inheritDoc
761
+ */ static get pluginName() {
762
+ return 'ListStyleSupport';
763
+ }
764
+ /**
765
+ * @inheritDoc
766
+ */ static get requires() {
767
+ return [
768
+ StyleUtils,
769
+ 'GeneralHtmlSupport'
770
+ ];
771
+ }
772
+ /**
773
+ * @inheritDoc
774
+ */ init() {
775
+ const editor = this.editor;
776
+ if (!editor.plugins.has('ListEditing')) {
777
+ return;
778
+ }
779
+ this._styleUtils = editor.plugins.get(StyleUtils);
780
+ this._listUtils = this.editor.plugins.get('ListUtils');
781
+ this._htmlSupport = this.editor.plugins.get('GeneralHtmlSupport');
782
+ this.listenTo(this._styleUtils, 'isStyleEnabledForBlock', (evt, [definition, block])=>{
783
+ if (this._isStyleEnabledForBlock(definition, block)) {
784
+ evt.return = true;
785
+ evt.stop();
786
+ }
787
+ }, {
788
+ priority: 'high'
789
+ });
790
+ this.listenTo(this._styleUtils, 'isStyleActiveForBlock', (evt, [definition, block])=>{
791
+ if (this._isStyleActiveForBlock(definition, block)) {
792
+ evt.return = true;
793
+ evt.stop();
794
+ }
795
+ }, {
796
+ priority: 'high'
797
+ });
798
+ this.listenTo(this._styleUtils, 'getAffectedBlocks', (evt, [definition, block])=>{
799
+ const blocks = this._getAffectedBlocks(definition, block);
800
+ if (blocks) {
801
+ evt.return = blocks;
802
+ evt.stop();
803
+ }
804
+ }, {
805
+ priority: 'high'
806
+ });
807
+ this.listenTo(this._styleUtils, 'getStylePreview', (evt, [definition, children])=>{
808
+ const templateDefinition = this._getStylePreview(definition, children);
809
+ if (templateDefinition) {
810
+ evt.return = templateDefinition;
811
+ evt.stop();
812
+ }
813
+ }, {
814
+ priority: 'high'
815
+ });
816
+ }
817
+ /**
818
+ * Verifies if the given style is applicable to the provided block element.
819
+ */ _isStyleEnabledForBlock(definition, block) {
820
+ const model = this.editor.model;
821
+ if (![
822
+ 'ol',
823
+ 'ul',
824
+ 'li'
825
+ ].includes(definition.element)) {
826
+ return false;
827
+ }
828
+ if (!this._listUtils.isListItemBlock(block)) {
829
+ return false;
830
+ }
831
+ const attributeName = this._htmlSupport.getGhsAttributeNameForElement(definition.element);
832
+ if (definition.element == 'ol' || definition.element == 'ul') {
833
+ if (!model.schema.checkAttribute(block, attributeName)) {
834
+ return false;
835
+ }
836
+ const isNumbered = this._listUtils.isNumberedListType(block.getAttribute('listType'));
837
+ const viewElementName = isNumbered ? 'ol' : 'ul';
838
+ return definition.element == viewElementName;
839
+ } else {
840
+ return model.schema.checkAttribute(block, attributeName);
841
+ }
842
+ }
843
+ /**
844
+ * Returns true if the given style is applied to the specified block element.
845
+ */ _isStyleActiveForBlock(definition, block) {
846
+ const attributeName = this._htmlSupport.getGhsAttributeNameForElement(definition.element);
847
+ const ghsAttributeValue = block.getAttribute(attributeName);
848
+ return this._styleUtils.hasAllClasses(ghsAttributeValue, definition.classes);
849
+ }
850
+ /**
851
+ * Returns an array of block elements that style should be applied to.
852
+ */ _getAffectedBlocks(definition, block) {
853
+ if (!this._isStyleEnabledForBlock(definition, block)) {
854
+ return null;
855
+ }
856
+ if (definition.element == 'li') {
857
+ return this._listUtils.expandListBlocksToCompleteItems(block, {
858
+ withNested: false
859
+ });
860
+ } else {
861
+ return this._listUtils.expandListBlocksToCompleteList(block);
862
+ }
863
+ }
864
+ /**
865
+ * Returns a view template definition for the style preview.
866
+ */ _getStylePreview(definition, children) {
867
+ const { element, classes } = definition;
868
+ if (element == 'ol' || element == 'ul') {
869
+ return {
870
+ tag: element,
871
+ attributes: {
872
+ class: classes
873
+ },
874
+ children: [
875
+ {
876
+ tag: 'li',
877
+ children
878
+ }
879
+ ]
880
+ };
881
+ } else if (element == 'li') {
882
+ return {
883
+ tag: 'ol',
884
+ children: [
885
+ {
886
+ tag: element,
887
+ attributes: {
888
+ class: classes
889
+ },
890
+ children
891
+ }
892
+ ]
893
+ };
894
+ }
895
+ return null;
896
+ }
897
+ }
898
+
899
+ class TableStyleSupport extends Plugin {
900
+ /**
901
+ * @inheritDoc
902
+ */ static get pluginName() {
903
+ return 'TableStyleSupport';
904
+ }
905
+ /**
906
+ * @inheritDoc
907
+ */ static get requires() {
908
+ return [
909
+ StyleUtils
910
+ ];
911
+ }
912
+ /**
913
+ * @inheritDoc
914
+ */ init() {
915
+ const editor = this.editor;
916
+ if (!editor.plugins.has('TableEditing')) {
917
+ return;
918
+ }
919
+ this._styleUtils = editor.plugins.get(StyleUtils);
920
+ this._tableUtils = this.editor.plugins.get('TableUtils');
921
+ this.listenTo(this._styleUtils, 'isStyleEnabledForBlock', (evt, [definition, block])=>{
922
+ if (this._isApplicable(definition, block)) {
923
+ evt.return = this._isStyleEnabledForBlock(definition, block);
924
+ evt.stop();
925
+ }
926
+ }, {
927
+ priority: 'high'
928
+ });
929
+ this.listenTo(this._styleUtils, 'getAffectedBlocks', (evt, [definition, block])=>{
930
+ if (this._isApplicable(definition, block)) {
931
+ evt.return = this._getAffectedBlocks(definition, block);
932
+ evt.stop();
933
+ }
934
+ }, {
935
+ priority: 'high'
936
+ });
937
+ this.listenTo(this._styleUtils, 'configureGHSDataFilter', (evt, [{ block }])=>{
938
+ const ghsDataFilter = this.editor.plugins.get('DataFilter');
939
+ ghsDataFilter.loadAllowedConfig(block.filter((definition)=>definition.element == 'figcaption').map((definition)=>({
940
+ name: 'caption',
941
+ classes: definition.classes
942
+ })));
943
+ });
944
+ }
945
+ /**
946
+ * Checks if this plugin's custom logic should be applied for defintion-block pair.
947
+ *
948
+ * @param definition Style definition that is being considered.
949
+ * @param block Block element to check if should be styled.
950
+ * @returns True if the defintion-block pair meet the plugin criteria, false otherwise.
951
+ */ _isApplicable(definition, block) {
952
+ if ([
953
+ 'td',
954
+ 'th'
955
+ ].includes(definition.element)) {
956
+ return block.name == 'tableCell';
957
+ }
958
+ if ([
959
+ 'thead',
960
+ 'tbody'
961
+ ].includes(definition.element)) {
962
+ return block.name == 'table';
963
+ }
964
+ return false;
965
+ }
966
+ /**
967
+ * Checks if the style definition should be applied to selected block.
968
+ *
969
+ * @param definition Style definition that is being considered.
970
+ * @param block Block element to check if should be styled.
971
+ * @returns True if the block should be style with the style description, false otherwise.
972
+ */ _isStyleEnabledForBlock(definition, block) {
973
+ if ([
974
+ 'td',
975
+ 'th'
976
+ ].includes(definition.element)) {
977
+ const location = this._tableUtils.getCellLocation(block);
978
+ const tableRow = block.parent;
979
+ const table = tableRow.parent;
980
+ const headingRows = table.getAttribute('headingRows') || 0;
981
+ const headingColumns = table.getAttribute('headingColumns') || 0;
982
+ const isHeadingCell = location.row < headingRows || location.column < headingColumns;
983
+ if (definition.element == 'th') {
984
+ return isHeadingCell;
985
+ } else {
986
+ return !isHeadingCell;
987
+ }
988
+ }
989
+ if ([
990
+ 'thead',
991
+ 'tbody'
992
+ ].includes(definition.element)) {
993
+ const headingRows = block.getAttribute('headingRows') || 0;
994
+ if (definition.element == 'thead') {
995
+ return headingRows > 0;
996
+ } else {
997
+ return headingRows < this._tableUtils.getRows(block);
998
+ }
999
+ }
1000
+ /* istanbul ignore next -- @preserve */ return false;
1001
+ }
1002
+ /**
1003
+ * Gets all blocks that the style should be applied to.
1004
+ *
1005
+ * @param definition Style definition that is being considered.
1006
+ * @param block A block element from selection.
1007
+ * @returns An array with the block that was passed as an argument if meets the criteria, null otherwise.
1008
+ */ _getAffectedBlocks(definition, block) {
1009
+ if (!this._isStyleEnabledForBlock(definition, block)) {
1010
+ return null;
1011
+ }
1012
+ return [
1013
+ block
1014
+ ];
1015
+ }
1016
+ }
1017
+
1018
+ class LinkStyleSupport extends Plugin {
1019
+ /**
1020
+ * @inheritDoc
1021
+ */ static get pluginName() {
1022
+ return 'LinkStyleSupport';
1023
+ }
1024
+ /**
1025
+ * @inheritDoc
1026
+ */ static get requires() {
1027
+ return [
1028
+ StyleUtils,
1029
+ 'GeneralHtmlSupport'
1030
+ ];
1031
+ }
1032
+ /**
1033
+ * @inheritDoc
1034
+ */ init() {
1035
+ const editor = this.editor;
1036
+ if (!editor.plugins.has('LinkEditing')) {
1037
+ return;
1038
+ }
1039
+ this._styleUtils = editor.plugins.get(StyleUtils);
1040
+ this._htmlSupport = this.editor.plugins.get('GeneralHtmlSupport');
1041
+ this.listenTo(this._styleUtils, 'isStyleEnabledForInlineSelection', (evt, [definition, selection])=>{
1042
+ if (definition.element == 'a') {
1043
+ evt.return = this._isStyleEnabled(definition, selection);
1044
+ evt.stop();
1045
+ }
1046
+ }, {
1047
+ priority: 'high'
1048
+ });
1049
+ this.listenTo(this._styleUtils, 'isStyleActiveForInlineSelection', (evt, [definition, selection])=>{
1050
+ if (definition.element == 'a') {
1051
+ evt.return = this._isStyleActive(definition, selection);
1052
+ evt.stop();
1053
+ }
1054
+ }, {
1055
+ priority: 'high'
1056
+ });
1057
+ this.listenTo(this._styleUtils, 'getAffectedInlineSelectable', (evt, [definition, selection])=>{
1058
+ if (definition.element != 'a') {
1059
+ return;
1060
+ }
1061
+ const selectable = this._getAffectedSelectable(definition, selection);
1062
+ if (selectable) {
1063
+ evt.return = selectable;
1064
+ evt.stop();
1065
+ }
1066
+ }, {
1067
+ priority: 'high'
1068
+ });
1069
+ }
1070
+ /**
1071
+ * Verifies if the given style is applicable to the provided document selection.
1072
+ */ _isStyleEnabled(definition, selection) {
1073
+ const model = this.editor.model;
1074
+ // Handle collapsed selection.
1075
+ if (selection.isCollapsed) {
1076
+ return selection.hasAttribute('linkHref');
1077
+ }
1078
+ // Non-collapsed selection.
1079
+ for (const range of selection.getRanges()){
1080
+ for (const item of range.getItems()){
1081
+ if ((item.is('$textProxy') || model.schema.isInline(item)) && item.hasAttribute('linkHref')) {
1082
+ return true;
1083
+ }
1084
+ }
1085
+ }
1086
+ return false;
1087
+ }
1088
+ /**
1089
+ * Returns true if the given style is applied to the specified document selection.
1090
+ */ _isStyleActive(definition, selection) {
1091
+ const model = this.editor.model;
1092
+ const attributeName = this._htmlSupport.getGhsAttributeNameForElement(definition.element);
1093
+ // Handle collapsed selection.
1094
+ if (selection.isCollapsed) {
1095
+ if (selection.hasAttribute('linkHref')) {
1096
+ const ghsAttributeValue = selection.getAttribute(attributeName);
1097
+ if (this._styleUtils.hasAllClasses(ghsAttributeValue, definition.classes)) {
1098
+ return true;
1099
+ }
1100
+ }
1101
+ return false;
1102
+ }
1103
+ // Non-collapsed selection.
1104
+ for (const range of selection.getRanges()){
1105
+ for (const item of range.getItems()){
1106
+ if ((item.is('$textProxy') || model.schema.isInline(item)) && item.hasAttribute('linkHref')) {
1107
+ const ghsAttributeValue = item.getAttribute(attributeName);
1108
+ return this._styleUtils.hasAllClasses(ghsAttributeValue, definition.classes);
1109
+ }
1110
+ }
1111
+ }
1112
+ return false;
1113
+ }
1114
+ /**
1115
+ * Returns a selectable that given style should be applied to.
1116
+ */ _getAffectedSelectable(definition, selection) {
1117
+ const model = this.editor.model;
1118
+ // Handle collapsed selection.
1119
+ if (selection.isCollapsed) {
1120
+ const linkHref = selection.getAttribute('linkHref');
1121
+ return findAttributeRange(selection.getFirstPosition(), 'linkHref', linkHref, model);
1122
+ }
1123
+ // Non-collapsed selection.
1124
+ const ranges = [];
1125
+ for (const range of selection.getRanges()){
1126
+ // First expand range to include the whole link.
1127
+ const expandedRange = model.createRange(expandAttributePosition(range.start, 'linkHref', true, model), expandAttributePosition(range.end, 'linkHref', false, model));
1128
+ // Pick only ranges on links.
1129
+ for (const item of expandedRange.getItems()){
1130
+ if ((item.is('$textProxy') || model.schema.isInline(item)) && item.hasAttribute('linkHref')) {
1131
+ ranges.push(this.editor.model.createRangeOn(item));
1132
+ }
1133
+ }
1134
+ }
1135
+ // Make sure that we have a continuous range on a link
1136
+ // (not split between text nodes with mixed attributes like bold etc.)
1137
+ return normalizeRanges(ranges);
1138
+ }
1139
+ }
1140
+ /**
1141
+ * Walks forward or backward (depends on the `lookBack` flag), node by node, as long as they have the same attribute value
1142
+ * and returns a position just before or after (depends on the `lookBack` flag) the last matched node.
1143
+ */ function expandAttributePosition(position, attributeName, lookBack, model) {
1144
+ const referenceNode = position.textNode || (lookBack ? position.nodeAfter : position.nodeBefore);
1145
+ if (!referenceNode || !referenceNode.hasAttribute(attributeName)) {
1146
+ return position;
1147
+ }
1148
+ const attributeValue = referenceNode.getAttribute(attributeName);
1149
+ return findAttributeRangeBound(position, attributeName, attributeValue, lookBack, model);
1150
+ }
1151
+ /**
1152
+ * Normalizes list of ranges by joining intersecting or "touching" ranges.
1153
+ *
1154
+ * Note: It assumes that ranges are sorted.
1155
+ */ function normalizeRanges(ranges) {
1156
+ for(let i = 1; i < ranges.length; i++){
1157
+ const joinedRange = ranges[i - 1].getJoined(ranges[i]);
1158
+ if (joinedRange) {
1159
+ // Replace the ranges on the list with the new joined range.
1160
+ ranges.splice(--i, 2, joinedRange);
1161
+ }
1162
+ }
1163
+ return ranges;
1164
+ }
1165
+
1166
+ class StyleEditing extends Plugin {
1167
+ /**
1168
+ * @inheritDoc
1169
+ */ static get pluginName() {
1170
+ return 'StyleEditing';
1171
+ }
1172
+ /**
1173
+ * @inheritDoc
1174
+ */ static get requires() {
1175
+ return [
1176
+ 'GeneralHtmlSupport',
1177
+ StyleUtils,
1178
+ ListStyleSupport,
1179
+ TableStyleSupport,
1180
+ LinkStyleSupport
1181
+ ];
1182
+ }
1183
+ /**
1184
+ * @inheritDoc
1185
+ */ init() {
1186
+ const editor = this.editor;
1187
+ const dataSchema = editor.plugins.get('DataSchema');
1188
+ const styleUtils = editor.plugins.get('StyleUtils');
1189
+ const styleDefinitions = editor.config.get('style.definitions');
1190
+ const normalizedStyleDefinitions = styleUtils.normalizeConfig(dataSchema, styleDefinitions);
1191
+ editor.commands.add('style', new StyleCommand(editor, normalizedStyleDefinitions));
1192
+ styleUtils.configureGHSDataFilter(normalizedStyleDefinitions);
1193
+ }
1194
+ }
1195
+
1196
+ class Style extends Plugin {
1197
+ /**
1198
+ * @inheritDoc
1199
+ */ static get pluginName() {
1200
+ return 'Style';
1201
+ }
1202
+ /**
1203
+ * @inheritDoc
1204
+ */ static get requires() {
1205
+ return [
1206
+ StyleEditing,
1207
+ StyleUI
1208
+ ];
1209
+ }
1210
+ }
1211
+
1212
+ export { Style, StyleEditing, StyleUI, StyleUtils };
1213
+ //# sourceMappingURL=index.js.map