@ckeditor/ckeditor5-html-support 37.1.0 → 38.0.0-rc.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 (64) hide show
  1. package/build/html-support.js +1 -1
  2. package/lang/translations/ar.po +1 -1
  3. package/lang/translations/bg.po +1 -1
  4. package/lang/translations/bn.po +1 -1
  5. package/lang/translations/ca.po +1 -1
  6. package/lang/translations/cs.po +1 -1
  7. package/lang/translations/da.po +1 -1
  8. package/lang/translations/de.po +1 -1
  9. package/lang/translations/el.po +1 -1
  10. package/lang/translations/en-au.po +1 -1
  11. package/lang/translations/es.po +1 -1
  12. package/lang/translations/et.po +1 -1
  13. package/lang/translations/fi.po +1 -1
  14. package/lang/translations/fr.po +1 -1
  15. package/lang/translations/gl.po +1 -1
  16. package/lang/translations/he.po +1 -1
  17. package/lang/translations/hi.po +1 -1
  18. package/lang/translations/hr.po +1 -1
  19. package/lang/translations/hu.po +1 -1
  20. package/lang/translations/id.po +1 -1
  21. package/lang/translations/it.po +1 -1
  22. package/lang/translations/ja.po +1 -1
  23. package/lang/translations/jv.po +1 -1
  24. package/lang/translations/ko.po +1 -1
  25. package/lang/translations/lt.po +1 -1
  26. package/lang/translations/lv.po +1 -1
  27. package/lang/translations/ms.po +1 -1
  28. package/lang/translations/nl.po +1 -1
  29. package/lang/translations/no.po +1 -1
  30. package/lang/translations/pl.po +1 -1
  31. package/lang/translations/pt-br.po +1 -1
  32. package/lang/translations/pt.po +1 -1
  33. package/lang/translations/ro.po +1 -1
  34. package/lang/translations/ru.po +1 -1
  35. package/lang/translations/sk.po +1 -1
  36. package/lang/translations/sr-latn.po +1 -1
  37. package/lang/translations/sr.po +1 -1
  38. package/lang/translations/sv.po +1 -1
  39. package/lang/translations/th.po +1 -1
  40. package/lang/translations/tr.po +1 -1
  41. package/lang/translations/uk.po +1 -1
  42. package/lang/translations/ur.po +1 -1
  43. package/lang/translations/vi.po +1 -1
  44. package/lang/translations/zh-cn.po +1 -1
  45. package/lang/translations/zh.po +1 -1
  46. package/package.json +34 -33
  47. package/src/converters.js +1 -1
  48. package/src/datafilter.d.ts +5 -1
  49. package/src/datafilter.js +38 -33
  50. package/src/dataschema.d.ts +12 -2
  51. package/src/dataschema.js +47 -23
  52. package/src/generalhtmlsupport.d.ts +5 -6
  53. package/src/generalhtmlsupport.js +13 -54
  54. package/src/integrations/codeblock.js +1 -1
  55. package/src/integrations/customelement.js +1 -1
  56. package/src/integrations/documentlist.js +1 -1
  57. package/src/integrations/heading.d.ts +10 -1
  58. package/src/integrations/heading.js +24 -4
  59. package/src/integrations/image.js +1 -1
  60. package/src/integrations/mediaembed.js +1 -1
  61. package/src/integrations/table.js +38 -3
  62. package/src/schemadefinitions.js +82 -24
  63. package/src/{conversionutils.d.ts → utils.d.ts} +21 -2
  64. package/src/{conversionutils.js → utils.js} +43 -0
@@ -18,6 +18,7 @@ import TableElementSupport from './integrations/table';
18
18
  import StyleElementSupport from './integrations/style';
19
19
  import DocumentListElementSupport from './integrations/documentlist';
20
20
  import CustomElementSupport from './integrations/customelement';
21
+ import { modifyGhsAttribute } from './utils';
21
22
  /**
22
23
  * The General HTML Support feature.
23
24
  *
@@ -62,16 +63,15 @@ export default class GeneralHtmlSupport extends Plugin {
62
63
  /**
63
64
  * Returns a GHS model attribute name related to a given view element name.
64
65
  *
66
+ * @internal
65
67
  * @param viewElementName A view element name.
66
68
  */
67
69
  getGhsAttributeNameForElement(viewElementName) {
68
70
  const dataSchema = this.editor.plugins.get('DataSchema');
69
71
  const definitions = Array.from(dataSchema.getDefinitionsForView(viewElementName, false));
70
- if (definitions &&
71
- definitions.length &&
72
- definitions[0].isInline &&
73
- !definitions[0].isObject) {
74
- return definitions[0].model;
72
+ const inlineDefinition = definitions.find(definition => (definition.isInline && !definitions[0].isObject));
73
+ if (inlineDefinition) {
74
+ return inlineDefinition.model;
75
75
  }
76
76
  return 'htmlAttributes';
77
77
  }
@@ -202,7 +202,10 @@ export default class GeneralHtmlSupport extends Plugin {
202
202
  * Returns an iterator over an items in the selectable that accept given GHS attribute.
203
203
  */
204
204
  function* getItemsToUpdateGhsAttribute(model, selectable, ghsAttributeName) {
205
- if (selectable.is('documentSelection') && selectable.isCollapsed) {
205
+ if (!selectable) {
206
+ return;
207
+ }
208
+ if (!(Symbol.iterator in selectable) && selectable.is('documentSelection') && selectable.isCollapsed) {
206
209
  if (model.schema.checkAttributeInSelection(selectable, ghsAttributeName)) {
207
210
  yield selectable;
208
211
  }
@@ -217,7 +220,10 @@ function* getItemsToUpdateGhsAttribute(model, selectable, ghsAttributeName) {
217
220
  * Translates a given selectable to an iterable of ranges.
218
221
  */
219
222
  function getValidRangesForSelectable(model, selectable, ghsAttributeName) {
220
- if (selectable.is('node') || selectable.is('$text') || selectable.is('$textProxy')) {
223
+ if (!(Symbol.iterator in selectable) &&
224
+ (selectable.is('node') ||
225
+ selectable.is('$text') ||
226
+ selectable.is('$textProxy'))) {
221
227
  if (model.schema.checkAttribute(selectable, ghsAttributeName)) {
222
228
  return [model.createRangeOn(selectable)];
223
229
  }
@@ -229,50 +235,3 @@ function getValidRangesForSelectable(model, selectable, ghsAttributeName) {
229
235
  return model.schema.getValidRanges(model.createSelection(selectable).getRanges(), ghsAttributeName);
230
236
  }
231
237
  }
232
- /**
233
- * Updates a GHS attribute on a specified item.
234
- * @param callback That receives a map or set as an argument and should modify it (add or remove entries).
235
- */
236
- function modifyGhsAttribute(writer, item, ghsAttributeName, subject, callback) {
237
- const oldValue = item.getAttribute(ghsAttributeName);
238
- const newValue = {};
239
- for (const kind of ['attributes', 'styles', 'classes']) {
240
- // Properties other than `subject` should be assigned from `oldValue`.
241
- if (kind != subject) {
242
- if (oldValue && oldValue[kind]) {
243
- newValue[kind] = oldValue[kind];
244
- }
245
- continue;
246
- }
247
- // `callback` should be applied on property [`subject`].
248
- if (subject == 'classes') {
249
- const values = new Set(oldValue && oldValue.classes || []);
250
- callback(values);
251
- if (values.size) {
252
- newValue[kind] = Array.from(values);
253
- }
254
- continue;
255
- }
256
- const values = new Map(Object.entries(oldValue && oldValue[kind] || {}));
257
- callback(values);
258
- if (values.size) {
259
- newValue[kind] = Object.fromEntries(values);
260
- }
261
- }
262
- if (Object.keys(newValue).length) {
263
- if (item.is('documentSelection')) {
264
- writer.setSelectionAttribute(ghsAttributeName, newValue);
265
- }
266
- else {
267
- writer.setAttribute(ghsAttributeName, newValue, item);
268
- }
269
- }
270
- else if (oldValue) {
271
- if (item.is('documentSelection')) {
272
- writer.removeSelectionAttribute(ghsAttributeName);
273
- }
274
- else {
275
- writer.removeAttribute(ghsAttributeName, item);
276
- }
277
- }
278
- }
@@ -3,7 +3,7 @@
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
4
  */
5
5
  import { Plugin } from 'ckeditor5/src/core';
6
- import { updateViewAttributes } from '../conversionutils';
6
+ import { updateViewAttributes } from '../utils';
7
7
  import DataFilter from '../datafilter';
8
8
  /**
9
9
  * Provides the General HTML Support integration with {@link module:code-block/codeblock~CodeBlock Code Block} feature.
@@ -10,7 +10,7 @@ import { Plugin } from 'ckeditor5/src/core';
10
10
  import { UpcastWriter } from 'ckeditor5/src/engine';
11
11
  import DataSchema from '../dataschema';
12
12
  import DataFilter from '../datafilter';
13
- import { setViewAttributes } from '../conversionutils';
13
+ import { setViewAttributes } from '../utils';
14
14
  /**
15
15
  * Provides the General HTML Support for custom elements (not registered in the {@link module:html-support/dataschema~DataSchema}).
16
16
  */
@@ -7,7 +7,7 @@
7
7
  */
8
8
  import { isEqual } from 'lodash-es';
9
9
  import { Plugin } from 'ckeditor5/src/core';
10
- import { setViewAttributes } from '../conversionutils';
10
+ import { setViewAttributes } from '../utils';
11
11
  import DataFilter from '../datafilter';
12
12
  /**
13
13
  * Provides the General HTML Support integration with the {@link module:list/documentlist~DocumentList Document List} feature.
@@ -6,6 +6,7 @@
6
6
  * @module html-support/integrations/heading
7
7
  */
8
8
  import { Plugin } from 'ckeditor5/src/core';
9
+ import { Enter } from 'ckeditor5/src/enter';
9
10
  import DataSchema from '../dataschema';
10
11
  /**
11
12
  * Provides the General HTML Support integration with {@link module:heading/heading~Heading Heading} feature.
@@ -14,7 +15,7 @@ export default class HeadingElementSupport extends Plugin {
14
15
  /**
15
16
  * @inheritDoc
16
17
  */
17
- static get requires(): readonly [typeof DataSchema];
18
+ static get requires(): readonly [typeof DataSchema, typeof Enter];
18
19
  /**
19
20
  * @inheritDoc
20
21
  */
@@ -23,4 +24,12 @@ export default class HeadingElementSupport extends Plugin {
23
24
  * @inheritDoc
24
25
  */
25
26
  init(): void;
27
+ /**
28
+ * Registers all elements supported by HeadingEditing to enable custom attributes for those elements.
29
+ */
30
+ private registerHeadingElements;
31
+ /**
32
+ * Removes css classes from "htmlAttributes" of new paragraph created when hitting "enter" in heading.
33
+ */
34
+ private removeClassesOnEnter;
26
35
  }
@@ -6,7 +6,9 @@
6
6
  * @module html-support/integrations/heading
7
7
  */
8
8
  import { Plugin } from 'ckeditor5/src/core';
9
+ import { Enter } from 'ckeditor5/src/enter';
9
10
  import DataSchema from '../dataschema';
11
+ import { modifyGhsAttribute } from '../utils';
10
12
  /**
11
13
  * Provides the General HTML Support integration with {@link module:heading/heading~Heading Heading} feature.
12
14
  */
@@ -15,7 +17,7 @@ export default class HeadingElementSupport extends Plugin {
15
17
  * @inheritDoc
16
18
  */
17
19
  static get requires() {
18
- return [DataSchema];
20
+ return [DataSchema, Enter];
19
21
  }
20
22
  /**
21
23
  * @inheritDoc
@@ -31,11 +33,16 @@ export default class HeadingElementSupport extends Plugin {
31
33
  if (!editor.plugins.has('HeadingEditing')) {
32
34
  return;
33
35
  }
34
- const dataSchema = editor.plugins.get(DataSchema);
35
36
  const options = editor.config.get('heading.options');
37
+ this.registerHeadingElements(editor, options);
38
+ this.removeClassesOnEnter(editor, options);
39
+ }
40
+ /**
41
+ * Registers all elements supported by HeadingEditing to enable custom attributes for those elements.
42
+ */
43
+ registerHeadingElements(editor, options) {
44
+ const dataSchema = editor.plugins.get(DataSchema);
36
45
  const headerModels = [];
37
- // We are registering all elements supported by HeadingEditing
38
- // to enable custom attributes for those elements.
39
46
  for (const option of options) {
40
47
  if ('model' in option && 'view' in option) {
41
48
  dataSchema.registerBlockElement({
@@ -52,4 +59,17 @@ export default class HeadingElementSupport extends Plugin {
52
59
  }
53
60
  });
54
61
  }
62
+ /**
63
+ * Removes css classes from "htmlAttributes" of new paragraph created when hitting "enter" in heading.
64
+ */
65
+ removeClassesOnEnter(editor, options) {
66
+ const enterCommand = editor.commands.get('enter');
67
+ this.listenTo(enterCommand, 'afterExecute', (evt, data) => {
68
+ const positionParent = editor.model.document.selection.getFirstPosition().parent;
69
+ const isHeading = options.some(option => positionParent.is('element', option.model));
70
+ if (isHeading && positionParent.childCount === 0) {
71
+ modifyGhsAttribute(data.writer, positionParent, 'htmlAttributes', 'classes', classes => classes.clear());
72
+ }
73
+ });
74
+ }
55
75
  }
@@ -7,7 +7,7 @@
7
7
  */
8
8
  import { Plugin } from 'ckeditor5/src/core';
9
9
  import DataFilter from '../datafilter';
10
- import { setViewAttributes, updateViewAttributes } from '../conversionutils';
10
+ import { setViewAttributes, updateViewAttributes } from '../utils';
11
11
  import { getDescendantElement } from './integrationutils';
12
12
  /**
13
13
  * Provides the General HTML Support integration with the {@link module:image/image~Image Image} feature.
@@ -8,7 +8,7 @@
8
8
  import { Plugin } from 'ckeditor5/src/core';
9
9
  import DataFilter from '../datafilter';
10
10
  import DataSchema from '../dataschema';
11
- import { updateViewAttributes } from '../conversionutils';
11
+ import { updateViewAttributes } from '../utils';
12
12
  import { getDescendantElement } from './integrationutils';
13
13
  /**
14
14
  * Provides the General HTML Support integration with {@link module:media-embed/mediaembed~MediaEmbed Media Embed} feature.
@@ -3,7 +3,7 @@
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
4
  */
5
5
  import { Plugin } from 'ckeditor5/src/core';
6
- import { setViewAttributes } from '../conversionutils';
6
+ import { updateViewAttributes } from '../utils';
7
7
  import DataFilter from '../datafilter';
8
8
  import { getDescendantElement } from './integrationutils';
9
9
  /**
@@ -33,6 +33,7 @@ export default class TableElementSupport extends Plugin {
33
33
  const schema = editor.model.schema;
34
34
  const conversion = editor.conversion;
35
35
  const dataFilter = editor.plugins.get(DataFilter);
36
+ const tableUtils = editor.plugins.get('TableUtils');
36
37
  dataFilter.on('register:figure', () => {
37
38
  conversion.for('upcast').add(viewToModelFigureAttributeConverter(dataFilter));
38
39
  });
@@ -50,10 +51,37 @@ export default class TableElementSupport extends Plugin {
50
51
  });
51
52
  conversion.for('upcast').add(viewToModelTableAttributeConverter(dataFilter));
52
53
  conversion.for('downcast').add(modelToViewTableAttributeConverter());
54
+ editor.model.document.registerPostFixer(createHeadingRowsPostFixer(editor.model, tableUtils));
53
55
  evt.stop();
54
56
  });
55
57
  }
56
58
  }
59
+ /**
60
+ * Creates a model post-fixer for thead and tbody GHS related attributes.
61
+ */
62
+ function createHeadingRowsPostFixer(model, tableUtils) {
63
+ return writer => {
64
+ const changes = model.document.differ.getChanges();
65
+ let wasFixed = false;
66
+ for (const change of changes) {
67
+ if (change.type != 'attribute' || change.attributeKey != 'headingRows') {
68
+ continue;
69
+ }
70
+ const table = change.range.start.nodeAfter;
71
+ const hasTHeadAttributes = table.getAttribute('htmlTheadAttributes');
72
+ const hasTBodyAttributes = table.getAttribute('htmlTbodyAttributes');
73
+ if (hasTHeadAttributes && !change.attributeNewValue) {
74
+ writer.removeAttribute('htmlTheadAttributes', table);
75
+ wasFixed = true;
76
+ }
77
+ else if (hasTBodyAttributes && change.attributeNewValue == tableUtils.getRows(table)) {
78
+ writer.removeAttribute('htmlTbodyAttributes', table);
79
+ wasFixed = true;
80
+ }
81
+ }
82
+ return wasFixed;
83
+ };
84
+ }
57
85
  /**
58
86
  * View-to-model conversion helper preserving allowed attributes on {@link module:table/table~Table Table}
59
87
  * feature model element.
@@ -63,6 +91,9 @@ export default class TableElementSupport extends Plugin {
63
91
  function viewToModelTableAttributeConverter(dataFilter) {
64
92
  return (dispatcher) => {
65
93
  dispatcher.on('element:table', (evt, data, conversionApi) => {
94
+ if (!data.modelRange) {
95
+ return;
96
+ }
66
97
  const viewTableElement = data.viewItem;
67
98
  preserveElementAttributes(viewTableElement, 'htmlAttributes');
68
99
  for (const childNode of viewTableElement.getChildren()) {
@@ -116,12 +147,16 @@ function modelToViewTableAttributeConverter() {
116
147
  addAttributeConversionDispatcherHandler('tbody', 'htmlTbodyAttributes');
117
148
  function addAttributeConversionDispatcherHandler(elementName, attributeName) {
118
149
  dispatcher.on(`attribute:${attributeName}:table`, (evt, data, conversionApi) => {
119
- if (!conversionApi.consumable.consume(data.item, evt.name)) {
150
+ if (!conversionApi.consumable.test(data.item, evt.name)) {
120
151
  return;
121
152
  }
122
153
  const containerElement = conversionApi.mapper.toViewElement(data.item);
123
154
  const viewElement = getDescendantElement(conversionApi.writer, containerElement, elementName);
124
- setViewAttributes(conversionApi.writer, data.attributeNewValue, viewElement);
155
+ if (!viewElement) {
156
+ return;
157
+ }
158
+ conversionApi.consumable.consume(data.item, evt.name);
159
+ updateViewAttributes(conversionApi.writer, data.attributeOldValue, data.attributeNewValue, viewElement);
125
160
  });
126
161
  }
127
162
  };
@@ -46,7 +46,7 @@
46
46
  // noscript
47
47
  export default {
48
48
  block: [
49
- // Existing features
49
+ // Existing features.
50
50
  {
51
51
  model: 'codeBlock',
52
52
  view: 'pre'
@@ -111,7 +111,7 @@ export default {
111
111
  model: 'imageInline',
112
112
  view: 'img'
113
113
  },
114
- // Compatibility features
114
+ // Compatibility features.
115
115
  {
116
116
  model: 'htmlP',
117
117
  view: 'p',
@@ -509,102 +509,153 @@ export default {
509
509
  }
510
510
  ],
511
511
  inline: [
512
+ // Existing features (attribute set on an existing model element).
513
+ {
514
+ model: 'htmlLiAttributes',
515
+ view: 'li',
516
+ appliesToBlock: true
517
+ },
518
+ {
519
+ model: 'htmlListAttributes',
520
+ view: 'ol',
521
+ appliesToBlock: true
522
+ },
523
+ {
524
+ model: 'htmlListAttributes',
525
+ view: 'ul',
526
+ appliesToBlock: true
527
+ },
528
+ {
529
+ model: 'htmlFigureAttributes',
530
+ view: 'figure',
531
+ appliesToBlock: 'table'
532
+ },
533
+ {
534
+ model: 'htmlTheadAttributes',
535
+ view: 'thead',
536
+ appliesToBlock: 'table'
537
+ },
538
+ {
539
+ model: 'htmlTbodyAttributes',
540
+ view: 'tbody',
541
+ appliesToBlock: 'table'
542
+ },
543
+ {
544
+ model: 'htmlFigureAttributes',
545
+ view: 'figure',
546
+ appliesToBlock: 'imageBlock'
547
+ },
548
+ // Compatibility features.
512
549
  {
513
550
  model: 'htmlAcronym',
514
551
  view: 'acronym',
515
552
  attributeProperties: {
516
- copyOnEnter: true
553
+ copyOnEnter: true,
554
+ isFormatting: true
517
555
  }
518
556
  },
519
557
  {
520
558
  model: 'htmlTt',
521
559
  view: 'tt',
522
560
  attributeProperties: {
523
- copyOnEnter: true
561
+ copyOnEnter: true,
562
+ isFormatting: true
524
563
  }
525
564
  },
526
565
  {
527
566
  model: 'htmlFont',
528
567
  view: 'font',
529
568
  attributeProperties: {
530
- copyOnEnter: true
569
+ copyOnEnter: true,
570
+ isFormatting: true
531
571
  }
532
572
  },
533
573
  {
534
574
  model: 'htmlTime',
535
575
  view: 'time',
536
576
  attributeProperties: {
537
- copyOnEnter: true
577
+ copyOnEnter: true,
578
+ isFormatting: true
538
579
  }
539
580
  },
540
581
  {
541
582
  model: 'htmlVar',
542
583
  view: 'var',
543
584
  attributeProperties: {
544
- copyOnEnter: true
585
+ copyOnEnter: true,
586
+ isFormatting: true
545
587
  }
546
588
  },
547
589
  {
548
590
  model: 'htmlBig',
549
591
  view: 'big',
550
592
  attributeProperties: {
551
- copyOnEnter: true
593
+ copyOnEnter: true,
594
+ isFormatting: true
552
595
  }
553
596
  },
554
597
  {
555
598
  model: 'htmlSmall',
556
599
  view: 'small',
557
600
  attributeProperties: {
558
- copyOnEnter: true
601
+ copyOnEnter: true,
602
+ isFormatting: true
559
603
  }
560
604
  },
561
605
  {
562
606
  model: 'htmlSamp',
563
607
  view: 'samp',
564
608
  attributeProperties: {
565
- copyOnEnter: true
609
+ copyOnEnter: true,
610
+ isFormatting: true
566
611
  }
567
612
  },
568
613
  {
569
614
  model: 'htmlQ',
570
615
  view: 'q',
571
616
  attributeProperties: {
572
- copyOnEnter: true
617
+ copyOnEnter: true,
618
+ isFormatting: true
573
619
  }
574
620
  },
575
621
  {
576
622
  model: 'htmlOutput',
577
623
  view: 'output',
578
624
  attributeProperties: {
579
- copyOnEnter: true
625
+ copyOnEnter: true,
626
+ isFormatting: true
580
627
  }
581
628
  },
582
629
  {
583
630
  model: 'htmlKbd',
584
631
  view: 'kbd',
585
632
  attributeProperties: {
586
- copyOnEnter: true
633
+ copyOnEnter: true,
634
+ isFormatting: true
587
635
  }
588
636
  },
589
637
  {
590
638
  model: 'htmlBdi',
591
639
  view: 'bdi',
592
640
  attributeProperties: {
593
- copyOnEnter: true
641
+ copyOnEnter: true,
642
+ isFormatting: true
594
643
  }
595
644
  },
596
645
  {
597
646
  model: 'htmlBdo',
598
647
  view: 'bdo',
599
648
  attributeProperties: {
600
- copyOnEnter: true
649
+ copyOnEnter: true,
650
+ isFormatting: true
601
651
  }
602
652
  },
603
653
  {
604
654
  model: 'htmlAbbr',
605
655
  view: 'abbr',
606
656
  attributeProperties: {
607
- copyOnEnter: true
657
+ copyOnEnter: true,
658
+ isFormatting: true
608
659
  }
609
660
  },
610
661
  {
@@ -667,7 +718,8 @@ export default {
667
718
  view: 'del',
668
719
  coupledAttribute: 'strikethrough',
669
720
  attributeProperties: {
670
- copyOnEnter: true
721
+ copyOnEnter: true,
722
+ isFormatting: true
671
723
  }
672
724
  },
673
725
  // TODO According to HTML-spec can behave as div-like element, although CKE4 only handles it as an inline element.
@@ -675,7 +727,8 @@ export default {
675
727
  model: 'htmlIns',
676
728
  view: 'ins',
677
729
  attributeProperties: {
678
- copyOnEnter: true
730
+ copyOnEnter: true,
731
+ isFormatting: true
679
732
  }
680
733
  },
681
734
  {
@@ -718,38 +771,43 @@ export default {
718
771
  model: 'htmlMark',
719
772
  view: 'mark',
720
773
  attributeProperties: {
721
- copyOnEnter: true
774
+ copyOnEnter: true,
775
+ isFormatting: true
722
776
  }
723
777
  },
724
778
  {
725
779
  model: 'htmlSpan',
726
780
  view: 'span',
727
781
  attributeProperties: {
728
- copyOnEnter: true
782
+ copyOnEnter: true,
783
+ isFormatting: true
729
784
  }
730
785
  },
731
786
  {
732
787
  model: 'htmlCite',
733
788
  view: 'cite',
734
789
  attributeProperties: {
735
- copyOnEnter: true
790
+ copyOnEnter: true,
791
+ isFormatting: true
736
792
  }
737
793
  },
738
794
  {
739
795
  model: 'htmlLabel',
740
796
  view: 'label',
741
797
  attributeProperties: {
742
- copyOnEnter: true
798
+ copyOnEnter: true,
799
+ isFormatting: true
743
800
  }
744
801
  },
745
802
  {
746
803
  model: 'htmlDfn',
747
804
  view: 'dfn',
748
805
  attributeProperties: {
749
- copyOnEnter: true
806
+ copyOnEnter: true,
807
+ isFormatting: true
750
808
  }
751
809
  },
752
- // Objects
810
+ // Objects.
753
811
  {
754
812
  model: 'htmlObject',
755
813
  view: 'object',
@@ -3,9 +3,9 @@
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
4
  */
5
5
  /**
6
- * @module html-support/conversionutils
6
+ * @module html-support/utils
7
7
  */
8
- import type { DowncastWriter, ViewElement } from 'ckeditor5/src/engine';
8
+ import type { DocumentSelection, DowncastWriter, Item, ViewElement, Writer } from 'ckeditor5/src/engine';
9
9
  export interface GHSViewAttributes {
10
10
  attributes?: Record<string, unknown>;
11
11
  classes?: Array<string>;
@@ -40,3 +40,22 @@ export declare function removeViewAttributes(writer: DowncastWriter, viewAttribu
40
40
  * Merges view element attribute objects.
41
41
  */
42
42
  export declare function mergeViewElementAttributes(target: GHSViewAttributes, source: GHSViewAttributes): GHSViewAttributes;
43
+ type ModifyGhsAttributesCallback = (t: Map<string, unknown>) => void;
44
+ type ModifyGhsClassesCallback = (t: Set<string>) => void;
45
+ type ModifyGhsStylesCallback = (t: Map<string, string>) => void;
46
+ /**
47
+ * Updates a GHS attribute on a specified item.
48
+ * @param callback That receives a map as an argument and should modify it (add or remove entries).
49
+ */
50
+ export declare function modifyGhsAttribute(writer: Writer, item: Item | DocumentSelection, ghsAttributeName: string, subject: 'attributes', callback: ModifyGhsAttributesCallback): void;
51
+ /**
52
+ * Updates a GHS attribute on a specified item.
53
+ * @param callback That receives a set as an argument and should modify it (add or remove entries).
54
+ */
55
+ export declare function modifyGhsAttribute(writer: Writer, item: Item | DocumentSelection, ghsAttributeName: string, subject: 'classes', callback: ModifyGhsClassesCallback): void;
56
+ /**
57
+ * Updates a GHS attribute on a specified item.
58
+ * @param callback That receives a map as an argument and should modify it (add or remove entries).
59
+ */
60
+ export declare function modifyGhsAttribute(writer: Writer, item: Item | DocumentSelection, ghsAttributeName: string, subject: 'styles', callback: ModifyGhsStylesCallback): void;
61
+ export {};
@@ -79,3 +79,46 @@ export function mergeViewElementAttributes(target, source) {
79
79
  }
80
80
  return result;
81
81
  }
82
+ export function modifyGhsAttribute(writer, item, ghsAttributeName, subject, callback) {
83
+ const oldValue = item.getAttribute(ghsAttributeName);
84
+ const newValue = {};
85
+ for (const kind of ['attributes', 'styles', 'classes']) {
86
+ // Properties other than `subject` should be assigned from `oldValue`.
87
+ if (kind != subject) {
88
+ if (oldValue && oldValue[kind]) {
89
+ newValue[kind] = oldValue[kind];
90
+ }
91
+ continue;
92
+ }
93
+ // `callback` should be applied on property [`subject`].
94
+ if (subject == 'classes') {
95
+ const values = new Set(oldValue && oldValue.classes || []);
96
+ callback(values);
97
+ if (values.size) {
98
+ newValue[kind] = Array.from(values);
99
+ }
100
+ continue;
101
+ }
102
+ const values = new Map(Object.entries(oldValue && oldValue[kind] || {}));
103
+ callback(values);
104
+ if (values.size) {
105
+ newValue[kind] = Object.fromEntries(values);
106
+ }
107
+ }
108
+ if (Object.keys(newValue).length) {
109
+ if (item.is('documentSelection')) {
110
+ writer.setSelectionAttribute(ghsAttributeName, newValue);
111
+ }
112
+ else {
113
+ writer.setAttribute(ghsAttributeName, newValue, item);
114
+ }
115
+ }
116
+ else if (oldValue) {
117
+ if (item.is('documentSelection')) {
118
+ writer.removeSelectionAttribute(ghsAttributeName);
119
+ }
120
+ else {
121
+ writer.removeAttribute(ghsAttributeName, item);
122
+ }
123
+ }
124
+ }