@ckeditor/ckeditor5-html-support 33.0.0 → 34.0.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.
@@ -0,0 +1 @@
1
+ !function(n){const i=n["en-au"]=n["en-au"]||{};i.dictionary=Object.assign(i.dictionary||{},{"HTML object":"HTML object"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
@@ -0,0 +1 @@
1
+ !function(i){const n=i.hr=i.hr||{};n.dictionary=Object.assign(n.dictionary||{},{"HTML object":"HTML objekt"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
@@ -0,0 +1 @@
1
+ !function(i){const n=i.jv=i.jv||{};n.dictionary=Object.assign(n.dictionary||{},{"HTML object":"Obyek HTML"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
@@ -0,0 +1,21 @@
1
+ # Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
2
+ #
3
+ # !!! IMPORTANT !!!
4
+ #
5
+ # Before you edit this file, please keep in mind that contributing to the project
6
+ # translations is possible ONLY via the Transifex online service.
7
+ #
8
+ # To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
9
+ #
10
+ # To learn more, check out the official contributor's guide:
11
+ # https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
12
+ #
13
+ msgid ""
14
+ msgstr ""
15
+ "Language-Team: English (Australia) (https://www.transifex.com/ckeditor/teams/11143/en_AU/)\n"
16
+ "Language: en_AU\n"
17
+ "Plural-Forms: nplurals=2; plural=(n != 1);\n"
18
+
19
+ msgctxt "A label describing an HTML object widget."
20
+ msgid "HTML object"
21
+ msgstr "HTML object"
@@ -0,0 +1,21 @@
1
+ # Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
2
+ #
3
+ # !!! IMPORTANT !!!
4
+ #
5
+ # Before you edit this file, please keep in mind that contributing to the project
6
+ # translations is possible ONLY via the Transifex online service.
7
+ #
8
+ # To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
9
+ #
10
+ # To learn more, check out the official contributor's guide:
11
+ # https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
12
+ #
13
+ msgid ""
14
+ msgstr ""
15
+ "Language-Team: Croatian (https://www.transifex.com/ckeditor/teams/11143/hr/)\n"
16
+ "Language: hr\n"
17
+ "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
18
+
19
+ msgctxt "A label describing an HTML object widget."
20
+ msgid "HTML object"
21
+ msgstr "HTML objekt"
@@ -0,0 +1,21 @@
1
+ # Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
2
+ #
3
+ # !!! IMPORTANT !!!
4
+ #
5
+ # Before you edit this file, please keep in mind that contributing to the project
6
+ # translations is possible ONLY via the Transifex online service.
7
+ #
8
+ # To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
9
+ #
10
+ # To learn more, check out the official contributor's guide:
11
+ # https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
12
+ #
13
+ msgid ""
14
+ msgstr ""
15
+ "Language-Team: Javanese (https://www.transifex.com/ckeditor/teams/11143/jv/)\n"
16
+ "Language: jv\n"
17
+ "Plural-Forms: nplurals=1; plural=0;\n"
18
+
19
+ msgctxt "A label describing an HTML object widget."
20
+ msgid "HTML object"
21
+ msgstr "Obyek HTML"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ckeditor/ckeditor5-html-support",
3
- "version": "33.0.0",
3
+ "version": "34.0.0",
4
4
  "description": "HTML Support feature for CKEditor 5.",
5
5
  "keywords": [
6
6
  "ckeditor",
@@ -16,39 +16,39 @@
16
16
  ],
17
17
  "main": "src/index.js",
18
18
  "dependencies": {
19
- "ckeditor5": "^33.0.0",
19
+ "ckeditor5": "^34.0.0",
20
20
  "lodash-es": "^4.17.15"
21
21
  },
22
22
  "devDependencies": {
23
- "@ckeditor/ckeditor5-alignment": "^33.0.0",
24
- "@ckeditor/ckeditor5-basic-styles": "^33.0.0",
25
- "@ckeditor/ckeditor5-block-quote": "^33.0.0",
26
- "@ckeditor/ckeditor5-cloud-services": "^33.0.0",
27
- "@ckeditor/ckeditor5-code-block": "^33.0.0",
28
- "@ckeditor/ckeditor5-core": "^33.0.0",
29
- "@ckeditor/ckeditor5-dev-utils": "^28.0.1",
30
- "@ckeditor/ckeditor5-easy-image": "^33.0.0",
31
- "@ckeditor/ckeditor5-editor-classic": "^33.0.0",
32
- "@ckeditor/ckeditor5-engine": "^33.0.0",
33
- "@ckeditor/ckeditor5-enter": "^33.0.0",
34
- "@ckeditor/ckeditor5-essentials": "^33.0.0",
35
- "@ckeditor/ckeditor5-font": "^33.0.0",
36
- "@ckeditor/ckeditor5-heading": "^33.0.0",
37
- "@ckeditor/ckeditor5-highlight": "^33.0.0",
38
- "@ckeditor/ckeditor5-horizontal-line": "^33.0.0",
39
- "@ckeditor/ckeditor5-html-embed": "^33.0.0",
40
- "@ckeditor/ckeditor5-image": "^33.0.0",
41
- "@ckeditor/ckeditor5-indent": "^33.0.0",
42
- "@ckeditor/ckeditor5-link": "^33.0.0",
43
- "@ckeditor/ckeditor5-list": "^33.0.0",
44
- "@ckeditor/ckeditor5-media-embed": "^33.0.0",
45
- "@ckeditor/ckeditor5-page-break": "^33.0.0",
46
- "@ckeditor/ckeditor5-paragraph": "^33.0.0",
47
- "@ckeditor/ckeditor5-paste-from-office": "^33.0.0",
48
- "@ckeditor/ckeditor5-source-editing": "^33.0.0",
49
- "@ckeditor/ckeditor5-table": "^33.0.0",
50
- "@ckeditor/ckeditor5-theme-lark": "^33.0.0",
51
- "@ckeditor/ckeditor5-utils": "^33.0.0",
23
+ "@ckeditor/ckeditor5-alignment": "^34.0.0",
24
+ "@ckeditor/ckeditor5-basic-styles": "^34.0.0",
25
+ "@ckeditor/ckeditor5-block-quote": "^34.0.0",
26
+ "@ckeditor/ckeditor5-cloud-services": "^34.0.0",
27
+ "@ckeditor/ckeditor5-code-block": "^34.0.0",
28
+ "@ckeditor/ckeditor5-core": "^34.0.0",
29
+ "@ckeditor/ckeditor5-dev-utils": "^30.0.0",
30
+ "@ckeditor/ckeditor5-easy-image": "^34.0.0",
31
+ "@ckeditor/ckeditor5-editor-classic": "^34.0.0",
32
+ "@ckeditor/ckeditor5-engine": "^34.0.0",
33
+ "@ckeditor/ckeditor5-enter": "^34.0.0",
34
+ "@ckeditor/ckeditor5-essentials": "^34.0.0",
35
+ "@ckeditor/ckeditor5-font": "^34.0.0",
36
+ "@ckeditor/ckeditor5-heading": "^34.0.0",
37
+ "@ckeditor/ckeditor5-highlight": "^34.0.0",
38
+ "@ckeditor/ckeditor5-horizontal-line": "^34.0.0",
39
+ "@ckeditor/ckeditor5-html-embed": "^34.0.0",
40
+ "@ckeditor/ckeditor5-image": "^34.0.0",
41
+ "@ckeditor/ckeditor5-indent": "^34.0.0",
42
+ "@ckeditor/ckeditor5-link": "^34.0.0",
43
+ "@ckeditor/ckeditor5-list": "^34.0.0",
44
+ "@ckeditor/ckeditor5-media-embed": "^34.0.0",
45
+ "@ckeditor/ckeditor5-page-break": "^34.0.0",
46
+ "@ckeditor/ckeditor5-paragraph": "^34.0.0",
47
+ "@ckeditor/ckeditor5-paste-from-office": "^34.0.0",
48
+ "@ckeditor/ckeditor5-source-editing": "^34.0.0",
49
+ "@ckeditor/ckeditor5-table": "^34.0.0",
50
+ "@ckeditor/ckeditor5-theme-lark": "^34.0.0",
51
+ "@ckeditor/ckeditor5-utils": "^34.0.0",
52
52
  "webpack": "^5.58.1",
53
53
  "webpack-cli": "^4.9.0"
54
54
  },
@@ -10,12 +10,30 @@
10
10
  import { cloneDeep } from 'lodash-es';
11
11
 
12
12
  /**
13
- * Helper function for downcast converter. Sets attributes on the given view element.
13
+ * Helper function for the downcast converter. Updates attributes on the given view element.
14
14
  *
15
- * @param {module:engine/view/downcastwriter~DowncastWriter} writer
16
- * @param {Object} viewAttributes
17
- * @param {module:engine/view/element~Element} viewElement
15
+ * @param {module:engine/view/downcastwriter~DowncastWriter} writer The view writer.
16
+ * @param {Object} oldViewAttributes The previous GHS attribute value.
17
+ * @param {Object} newViewAttributes The current GHS attribute value.
18
+ * @param {module:engine/view/element~Element} viewElement The view element to update.
18
19
  */
20
+ export function updateViewAttributes( writer, oldViewAttributes, newViewAttributes, viewElement ) {
21
+ if ( oldViewAttributes ) {
22
+ removeViewAttributes( writer, oldViewAttributes, viewElement );
23
+ }
24
+
25
+ if ( newViewAttributes ) {
26
+ setViewAttributes( writer, newViewAttributes, viewElement );
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Helper function for the downcast converter. Sets attributes on the given view element.
32
+ *
33
+ * @param {module:engine/view/downcastwriter~DowncastWriter} writer The view writer.
34
+ * @param {Object} viewAttributes The GHS attribute value.
35
+ * @param {module:engine/view/element~Element} viewElement The view element to update.
36
+ */
19
37
  export function setViewAttributes( writer, viewAttributes, viewElement ) {
20
38
  if ( viewAttributes.attributes ) {
21
39
  for ( const [ key, value ] of Object.entries( viewAttributes.attributes ) ) {
@@ -32,6 +50,31 @@ export function setViewAttributes( writer, viewAttributes, viewElement ) {
32
50
  }
33
51
  }
34
52
 
53
+ /**
54
+ * Helper function for the downcast converter. Removes attributes on the given view element.
55
+ *
56
+ * @param {module:engine/view/downcastwriter~DowncastWriter} writer The view writer.
57
+ * @param {Object} viewAttributes The GHS attribute value.
58
+ * @param {module:engine/view/element~Element} viewElement The view element to update.
59
+ */
60
+ export function removeViewAttributes( writer, viewAttributes, viewElement ) {
61
+ if ( viewAttributes.attributes ) {
62
+ for ( const [ key ] of Object.entries( viewAttributes.attributes ) ) {
63
+ writer.removeAttribute( key, viewElement );
64
+ }
65
+ }
66
+
67
+ if ( viewAttributes.styles ) {
68
+ for ( const style of Object.keys( viewAttributes.styles ) ) {
69
+ writer.removeStyle( style, viewElement );
70
+ }
71
+ }
72
+
73
+ if ( viewAttributes.classes ) {
74
+ writer.removeClass( viewAttributes.classes, viewElement );
75
+ }
76
+ }
77
+
35
78
  /**
36
79
  * Merges view element attribute objects.
37
80
  *
@@ -45,7 +88,7 @@ export function mergeViewElementAttributes( target, source ) {
45
88
  for ( const key in source ) {
46
89
  // Merge classes.
47
90
  if ( Array.isArray( source[ key ] ) ) {
48
- result[ key ] = Array.from( new Set( [ ...target[ key ], ...source[ key ] ] ) );
91
+ result[ key ] = Array.from( new Set( [ ...( target[ key ] || [] ), ...source[ key ] ] ) );
49
92
  }
50
93
 
51
94
  // Merge attributes or styles.
package/src/converters.js CHANGED
@@ -8,7 +8,11 @@
8
8
  */
9
9
 
10
10
  import { toWidget } from 'ckeditor5/src/widget';
11
- import { setViewAttributes, mergeViewElementAttributes } from './conversionutils';
11
+ import {
12
+ setViewAttributes,
13
+ mergeViewElementAttributes,
14
+ updateViewAttributes
15
+ } from './conversionutils';
12
16
 
13
17
  /**
14
18
  * View-to-model conversion helper for object elements.
@@ -28,7 +32,7 @@ export function viewToModelObjectConverter( { model: modelName } ) {
28
32
  }
29
33
 
30
34
  /**
31
- * Conversion helper converting object element to HTML object widget.
35
+ * Conversion helper converting an object element to an HTML object widget.
32
36
  *
33
37
  * @param {module:core/editor/editor~Editor} editor
34
38
  * @param {module:html-support/dataschema~DataSchemaInlineElementDefinition} definition
@@ -37,14 +41,15 @@ export function viewToModelObjectConverter( { model: modelName } ) {
37
41
  export function toObjectWidgetConverter( editor, { view: viewName, isInline } ) {
38
42
  const t = editor.t;
39
43
 
40
- return ( modelElement, { writer, consumable } ) => {
44
+ return ( modelElement, { writer } ) => {
41
45
  const widgetLabel = t( 'HTML object' );
42
46
 
43
47
  const viewElement = createObjectView( viewName, modelElement, writer );
48
+ const viewAttributes = modelElement.getAttribute( 'htmlAttributes' );
49
+
44
50
  writer.addClass( 'html-object-embed__content', viewElement );
45
51
 
46
- const viewAttributes = modelElement.getAttribute( 'htmlAttributes' );
47
- if ( viewAttributes && consumable.consume( modelElement, `attribute:htmlAttributes:${ modelElement.name }` ) ) {
52
+ if ( viewAttributes ) {
48
53
  setViewAttributes( writer, viewAttributes, viewElement );
49
54
  }
50
55
 
@@ -55,10 +60,7 @@ export function toObjectWidgetConverter( editor, { view: viewName, isInline } )
55
60
  class: 'html-object-embed',
56
61
  'data-html-object-embed-label': widgetLabel
57
62
  },
58
- viewElement,
59
- {
60
- isAllowedInsideAttributeElement: isInline
61
- }
63
+ viewElement
62
64
  );
63
65
 
64
66
  return toWidget( viewContainer, writer, { widgetLabel } );
@@ -91,6 +93,11 @@ export function viewToAttributeInlineConverter( { view: viewName, model: attribu
91
93
  dispatcher.on( `element:${ viewName }`, ( evt, data, conversionApi ) => {
92
94
  const viewAttributes = dataFilter._consumeAllowedAttributes( data.viewItem, conversionApi );
93
95
 
96
+ // Do not apply the attribute if the element itself is already consumed and there is no view attributes to store.
97
+ if ( !viewAttributes && !conversionApi.consumable.test( data.viewItem, { name: true } ) ) {
98
+ return;
99
+ }
100
+
94
101
  // Since we are converting to attribute we need a range on which we will set the attribute.
95
102
  // If the range is not created yet, we will create it.
96
103
  if ( !data.modelRange ) {
@@ -168,16 +175,15 @@ export function viewToModelBlockAttributeConverter( { view: viewName }, dataFilt
168
175
  export function modelToViewBlockAttributeConverter( { model: modelName } ) {
169
176
  return dispatcher => {
170
177
  dispatcher.on( `attribute:htmlAttributes:${ modelName }`, ( evt, data, conversionApi ) => {
171
- const viewAttributes = data.attributeNewValue;
172
-
173
178
  if ( !conversionApi.consumable.consume( data.item, evt.name ) ) {
174
179
  return;
175
180
  }
176
181
 
182
+ const { attributeOldValue, attributeNewValue } = data;
177
183
  const viewWriter = conversionApi.writer;
178
184
  const viewElement = conversionApi.mapper.toViewElement( data.item );
179
185
 
180
- setViewAttributes( viewWriter, viewAttributes, viewElement );
186
+ updateViewAttributes( viewWriter, attributeOldValue, attributeNewValue, viewElement );
181
187
  } );
182
188
  };
183
189
  }
package/src/datafilter.js CHANGED
@@ -312,6 +312,7 @@ export default class DataFilter extends Plugin {
312
312
 
313
313
  schema.register( modelName, definition.modelSchema );
314
314
 
315
+ /* istanbul ignore next: paranoid check */
315
316
  if ( !viewName ) {
316
317
  return;
317
318
  }
@@ -336,7 +337,12 @@ export default class DataFilter extends Plugin {
336
337
  conversion.for( 'upcast' ).add( viewToModelBlockAttributeConverter( definition, this ) );
337
338
 
338
339
  conversion.for( 'editingDowncast' ).elementToStructure( {
339
- model: modelName,
340
+ model: {
341
+ name: modelName,
342
+ attributes: [
343
+ 'htmlAttributes'
344
+ ]
345
+ },
340
346
  view: toObjectWidgetConverter( editor, definition )
341
347
  } );
342
348
 
@@ -8,6 +8,7 @@
8
8
  */
9
9
 
10
10
  import { Plugin } from 'ckeditor5/src/core';
11
+ import { toArray } from 'ckeditor5/src/utils';
11
12
 
12
13
  import DataFilter from './datafilter';
13
14
  import CodeBlockElementSupport from './integrations/codeblock';
@@ -18,6 +19,7 @@ import MediaEmbedElementSupport from './integrations/mediaembed';
18
19
  import ScriptElementSupport from './integrations/script';
19
20
  import TableElementSupport from './integrations/table';
20
21
  import StyleElementSupport from './integrations/style';
22
+ import DocumentListElementSupport from './integrations/documentlist';
21
23
 
22
24
  /**
23
25
  * The General HTML Support feature.
@@ -48,7 +50,8 @@ export default class GeneralHtmlSupport extends Plugin {
48
50
  MediaEmbedElementSupport,
49
51
  ScriptElementSupport,
50
52
  TableElementSupport,
51
- StyleElementSupport
53
+ StyleElementSupport,
54
+ DocumentListElementSupport
52
55
  ];
53
56
  }
54
57
 
@@ -63,6 +66,231 @@ export default class GeneralHtmlSupport extends Plugin {
63
66
  dataFilter.loadAllowedConfig( editor.config.get( 'htmlSupport.allow' ) || [] );
64
67
  dataFilter.loadDisallowedConfig( editor.config.get( 'htmlSupport.disallow' ) || [] );
65
68
  }
69
+
70
+ /**
71
+ * Returns a GHS model attribute name related to a given view element name.
72
+ *
73
+ * @protected
74
+ * @param {String} viewElementName A view element name.
75
+ * @returns {String}
76
+ */
77
+ getGhsAttributeNameForElement( viewElementName ) {
78
+ const dataSchema = this.editor.plugins.get( 'DataSchema' );
79
+ const definitions = Array.from( dataSchema.getDefinitionsForView( viewElementName, false ) );
80
+
81
+ if ( definitions && definitions.length && definitions[ 0 ].isInline && !definitions[ 0 ].isObject ) {
82
+ return definitions[ 0 ].model;
83
+ }
84
+
85
+ return 'htmlAttributes';
86
+ }
87
+
88
+ /**
89
+ * Updates GHS model attribute for a specified view element name, so it includes a given class name.
90
+ *
91
+ * @protected
92
+ * @param {String} viewElementName A view element name.
93
+ * @param {String|Array.<String>} className The css class to add.
94
+ * @param {module:engine/model/selection~Selectable} selectable The selection or element to update.
95
+ */
96
+ addModelHtmlClass( viewElementName, className, selectable ) {
97
+ const model = this.editor.model;
98
+ const ghsAttributeName = this.getGhsAttributeNameForElement( viewElementName );
99
+
100
+ model.change( writer => {
101
+ for ( const item of getItemsToUpdateGhsAttribute( model, selectable, ghsAttributeName ) ) {
102
+ modifyGhsAttribute( writer, item, ghsAttributeName, 'classes', classes => {
103
+ for ( const value of toArray( className ) ) {
104
+ classes.add( value );
105
+ }
106
+ } );
107
+ }
108
+ } );
109
+ }
110
+
111
+ /**
112
+ * Updates GHS model attribute for a specified view element name, so it does not include a given class name.
113
+ *
114
+ * @protected
115
+ * @param {String} viewElementName A view element name.
116
+ * @param {String|Array.<String>} className The css class to remove.
117
+ * @param {module:engine/model/selection~Selectable} selectable The selection or element to update.
118
+ */
119
+ removeModelHtmlClass( viewElementName, className, selectable ) {
120
+ const model = this.editor.model;
121
+ const ghsAttributeName = this.getGhsAttributeNameForElement( viewElementName );
122
+
123
+ model.change( writer => {
124
+ for ( const item of getItemsToUpdateGhsAttribute( model, selectable, ghsAttributeName ) ) {
125
+ modifyGhsAttribute( writer, item, ghsAttributeName, 'classes', classes => {
126
+ for ( const value of toArray( className ) ) {
127
+ classes.delete( value );
128
+ }
129
+ } );
130
+ }
131
+ } );
132
+ }
133
+
134
+ /**
135
+ * Updates GHS model attribute for a specified view element name, so it includes a given attribute.
136
+ *
137
+ * @protected
138
+ * @param {String} viewElementName A view element name.
139
+ * @param {Object} attributes The object with attributes to set.
140
+ * @param {module:engine/model/selection~Selectable} selectable The selection or element to update.
141
+ */
142
+ setModelHtmlAttributes( viewElementName, attributes, selectable ) {
143
+ const model = this.editor.model;
144
+ const ghsAttributeName = this.getGhsAttributeNameForElement( viewElementName );
145
+
146
+ model.change( writer => {
147
+ for ( const item of getItemsToUpdateGhsAttribute( model, selectable, ghsAttributeName ) ) {
148
+ modifyGhsAttribute( writer, item, ghsAttributeName, 'attributes', attributesMap => {
149
+ for ( const [ key, value ] of Object.entries( attributes ) ) {
150
+ attributesMap.set( key, value );
151
+ }
152
+ } );
153
+ }
154
+ } );
155
+ }
156
+
157
+ /**
158
+ * Updates GHS model attribute for a specified view element name, so it does not include a given attribute.
159
+ *
160
+ * @protected
161
+ * @param {String} viewElementName A view element name.
162
+ * @param {String|Array.<String>} attributeName The attribute name (or names) to remove.
163
+ * @param {module:engine/model/selection~Selectable} selectable The selection or element to update.
164
+ */
165
+ removeModelHtmlAttributes( viewElementName, attributeName, selectable ) {
166
+ const model = this.editor.model;
167
+ const ghsAttributeName = this.getGhsAttributeNameForElement( viewElementName );
168
+
169
+ model.change( writer => {
170
+ for ( const item of getItemsToUpdateGhsAttribute( model, selectable, ghsAttributeName ) ) {
171
+ modifyGhsAttribute( writer, item, ghsAttributeName, 'attributes', attributesMap => {
172
+ for ( const key of toArray( attributeName ) ) {
173
+ attributesMap.delete( key );
174
+ }
175
+ } );
176
+ }
177
+ } );
178
+ }
179
+
180
+ /**
181
+ * Updates GHS model attribute for a specified view element name, so it includes a given style.
182
+ *
183
+ * @protected
184
+ * @param {String} viewElementName A view element name.
185
+ * @param {Object} styles The object with styles to set.
186
+ * @param {module:engine/model/selection~Selectable} selectable The selection or element to update.
187
+ */
188
+ setModelHtmlStyles( viewElementName, styles, selectable ) {
189
+ const model = this.editor.model;
190
+ const ghsAttributeName = this.getGhsAttributeNameForElement( viewElementName );
191
+
192
+ model.change( writer => {
193
+ for ( const item of getItemsToUpdateGhsAttribute( model, selectable, ghsAttributeName ) ) {
194
+ modifyGhsAttribute( writer, item, ghsAttributeName, 'styles', stylesMap => {
195
+ for ( const [ key, value ] of Object.entries( styles ) ) {
196
+ stylesMap.set( key, value );
197
+ }
198
+ } );
199
+ }
200
+ } );
201
+ }
202
+
203
+ /**
204
+ * Updates GHS model attribute for a specified view element name, so it does not include a given style.
205
+ *
206
+ * @protected
207
+ * @param {String} viewElementName A view element name.
208
+ * @param {String|Array.<String>} properties The style (or styles list) to remove.
209
+ * @param {module:engine/model/selection~Selectable} selectable The selection or element to update.
210
+ */
211
+ removeModelHtmlStyles( viewElementName, properties, selectable ) {
212
+ const model = this.editor.model;
213
+ const ghsAttributeName = this.getGhsAttributeNameForElement( viewElementName );
214
+
215
+ model.change( writer => {
216
+ for ( const item of getItemsToUpdateGhsAttribute( model, selectable, ghsAttributeName ) ) {
217
+ modifyGhsAttribute( writer, item, ghsAttributeName, 'styles', stylesMap => {
218
+ for ( const key of toArray( properties ) ) {
219
+ stylesMap.delete( key );
220
+ }
221
+ } );
222
+ }
223
+ } );
224
+ }
225
+ }
226
+
227
+ // Returns an iterator over an items in the selectable that accept given GHS attribute.
228
+ function* getItemsToUpdateGhsAttribute( model, selectable, ghsAttributeName ) {
229
+ if ( selectable.is( 'documentSelection' ) && selectable.isCollapsed ) {
230
+ if ( model.schema.checkAttributeInSelection( selectable, ghsAttributeName ) ) {
231
+ yield selectable;
232
+ }
233
+ } else {
234
+ for ( const range of getValidRangesForSelectable( model, selectable, ghsAttributeName ) ) {
235
+ yield* range.getItems( { shallow: true } );
236
+ }
237
+ }
238
+ }
239
+
240
+ // Translates a given selectable to an iterable of ranges.
241
+ function getValidRangesForSelectable( model, selectable, ghsAttributeName ) {
242
+ if ( selectable.is( 'node' ) || selectable.is( '$text' ) || selectable.is( '$textProxy' ) ) {
243
+ if ( model.schema.checkAttribute( selectable, ghsAttributeName ) ) {
244
+ return [ model.createRangeOn( selectable ) ];
245
+ } else {
246
+ return [];
247
+ }
248
+ } else {
249
+ return model.schema.getValidRanges( model.createSelection( selectable ).getRanges(), ghsAttributeName );
250
+ }
251
+ }
252
+
253
+ // Updates a GHS attribute on a specified item.
254
+ // @param {module:engine/model/writer~Writer} writer
255
+ // @param {module:engine/model/item~Item|module:engine/model/documentselection~DocumentSelection} item
256
+ // @param {String} ghsAttributeName
257
+ // @param {'classes'|'attributes'|'styles'} subject
258
+ // @param {Function} callback That receives a map or set as an argument and should modify it (add or remove entries).
259
+ function modifyGhsAttribute( writer, item, ghsAttributeName, subject, callback ) {
260
+ const oldValue = item.getAttribute( ghsAttributeName );
261
+ const newValue = {};
262
+
263
+ for ( const kind of [ 'attributes', 'styles', 'classes' ] ) {
264
+ if ( kind != subject ) {
265
+ if ( oldValue && oldValue[ kind ] ) {
266
+ newValue[ kind ] = oldValue[ kind ];
267
+ }
268
+ } else {
269
+ const values = kind == 'classes' ?
270
+ new Set( oldValue && oldValue[ kind ] || [] ) :
271
+ new Map( Object.entries( oldValue && oldValue[ kind ] || {} ) );
272
+
273
+ callback( values );
274
+
275
+ if ( values.size ) {
276
+ newValue[ kind ] = kind == 'classes' ? Array.from( values ) : Object.fromEntries( values );
277
+ }
278
+ }
279
+ }
280
+
281
+ if ( Object.keys( newValue ).length ) {
282
+ if ( item.is( 'documentSelection' ) ) {
283
+ writer.setSelectionAttribute( ghsAttributeName, newValue );
284
+ } else {
285
+ writer.setAttribute( ghsAttributeName, newValue, item );
286
+ }
287
+ } else if ( oldValue ) {
288
+ if ( item.is( 'documentSelection' ) ) {
289
+ writer.removeSelectionAttribute( ghsAttributeName );
290
+ } else {
291
+ writer.removeAttribute( ghsAttributeName, item );
292
+ }
293
+ }
66
294
  }
67
295
 
68
296
  /**
@@ -8,7 +8,7 @@
8
8
  */
9
9
 
10
10
  import { Plugin } from 'ckeditor5/src/core';
11
- import { setViewAttributes } from '../conversionutils.js';
11
+ import { updateViewAttributes } from '../conversionutils.js';
12
12
 
13
13
  import DataFilter from '../datafilter';
14
14
 
@@ -101,10 +101,11 @@ function modelToViewCodeBlockAttributeConverter() {
101
101
  return;
102
102
  }
103
103
 
104
+ const { attributeOldValue, attributeNewValue } = data;
104
105
  const viewCodeElement = conversionApi.mapper.toViewElement( data.item );
105
106
  const viewPreElement = viewCodeElement.parent;
106
107
 
107
- setViewAttributes( conversionApi.writer, data.attributeNewValue, viewPreElement );
108
+ updateViewAttributes( conversionApi.writer, attributeOldValue, attributeNewValue, viewPreElement );
108
109
  } );
109
110
 
110
111
  dispatcher.on( 'attribute:htmlContentAttributes:codeBlock', ( evt, data, conversionApi ) => {
@@ -112,9 +113,10 @@ function modelToViewCodeBlockAttributeConverter() {
112
113
  return;
113
114
  }
114
115
 
116
+ const { attributeOldValue, attributeNewValue } = data;
115
117
  const viewCodeElement = conversionApi.mapper.toViewElement( data.item );
116
118
 
117
- setViewAttributes( conversionApi.writer, data.attributeNewValue, viewCodeElement );
119
+ updateViewAttributes( conversionApi.writer, attributeOldValue, attributeNewValue, viewCodeElement );
118
120
  } );
119
121
  };
120
122
  }