@ckeditor/ckeditor5-html-support 33.0.0 → 34.2.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.
@@ -43,6 +43,13 @@ export default class DualContentModelElementSupport extends Plugin {
43
43
  return [ DataFilter ];
44
44
  }
45
45
 
46
+ /**
47
+ * @inheritDoc
48
+ */
49
+ static get pluginName() {
50
+ return 'DualContentModelElementSupport';
51
+ }
52
+
46
53
  /**
47
54
  * @inheritDoc
48
55
  */
@@ -111,10 +118,19 @@ export default class DualContentModelElementSupport extends Plugin {
111
118
  * @returns {Boolean}
112
119
  */
113
120
  _hasBlockContent( viewElement ) {
114
- const blockElements = this.editor.editing.view.domConverter.blockElements;
121
+ const view = this.editor.editing.view;
122
+ const blockElements = view.domConverter.blockElements;
123
+
124
+ // Traversing the viewElement subtree looking for block elements.
125
+ // Especially for the cases like <div><a href="#"><p>foo</p></a></div>.
126
+ // https://github.com/ckeditor/ckeditor5/issues/11513
127
+ for ( const viewItem of view.createRangeIn( viewElement ).getItems() ) {
128
+ if ( viewItem.is( 'element' ) && blockElements.includes( viewItem.name ) ) {
129
+ return true;
130
+ }
131
+ }
115
132
 
116
- return Array.from( viewElement.getChildren() )
117
- .some( node => blockElements.includes( node.name ) );
133
+ return false;
118
134
  }
119
135
 
120
136
  /**
@@ -24,6 +24,13 @@ export default class HeadingElementSupport extends Plugin {
24
24
  return [ DataSchema ];
25
25
  }
26
26
 
27
+ /**
28
+ * @inheritDoc
29
+ */
30
+ static get pluginName() {
31
+ return 'HeadingElementSupport';
32
+ }
33
+
27
34
  /**
28
35
  * @inheritDoc
29
36
  */
@@ -10,7 +10,10 @@
10
10
  import { Plugin } from 'ckeditor5/src/core';
11
11
 
12
12
  import DataFilter from '../datafilter';
13
- import { setViewAttributes } from '../conversionutils.js';
13
+ import {
14
+ setViewAttributes,
15
+ updateViewAttributes
16
+ } from '../conversionutils.js';
14
17
 
15
18
  /**
16
19
  * Provides the General HTML Support integration with the {@link module:image/image~Image Image} feature.
@@ -25,6 +28,13 @@ export default class ImageElementSupport extends Plugin {
25
28
  return [ DataFilter ];
26
29
  }
27
30
 
31
+ /**
32
+ * @inheritDoc
33
+ */
34
+ static get pluginName() {
35
+ return 'ImageElementSupport';
36
+ }
37
+
28
38
  /**
29
39
  * @inheritDoc
30
40
  */
@@ -40,6 +50,10 @@ export default class ImageElementSupport extends Plugin {
40
50
  const conversion = editor.conversion;
41
51
  const dataFilter = editor.plugins.get( DataFilter );
42
52
 
53
+ dataFilter.on( 'register:figure', () => {
54
+ conversion.for( 'upcast' ).add( viewToModelFigureAttributeConverter( dataFilter ) );
55
+ } );
56
+
43
57
  dataFilter.on( 'register:img', ( evt, definition ) => {
44
58
  if ( definition.model !== 'imageBlock' && definition.model !== 'imageInline' ) {
45
59
  return;
@@ -84,36 +98,55 @@ export default class ImageElementSupport extends Plugin {
84
98
  function viewToModelImageAttributeConverter( dataFilter ) {
85
99
  return dispatcher => {
86
100
  dispatcher.on( 'element:img', ( evt, data, conversionApi ) => {
101
+ if ( !data.modelRange ) {
102
+ return;
103
+ }
104
+
87
105
  const viewImageElement = data.viewItem;
88
106
  const viewContainerElement = viewImageElement.parent;
89
107
 
90
108
  preserveElementAttributes( viewImageElement, 'htmlAttributes' );
91
109
 
92
- if ( viewContainerElement.is( 'element', 'figure' ) ) {
93
- preserveElementAttributes( viewContainerElement, 'htmlFigureAttributes' );
94
- } else if ( viewContainerElement.is( 'element', 'a' ) ) {
110
+ if ( viewContainerElement.is( 'element', 'a' ) ) {
95
111
  preserveLinkAttributes( viewContainerElement );
96
112
  }
97
113
 
98
114
  function preserveElementAttributes( viewElement, attributeName ) {
99
- const viewAttributes = dataFilter._consumeAllowedAttributes( viewElement, conversionApi );
115
+ const viewAttributes = dataFilter.processViewAttributes( viewElement, conversionApi );
100
116
 
101
117
  if ( viewAttributes ) {
102
118
  conversionApi.writer.setAttribute( attributeName, viewAttributes, data.modelRange );
103
119
  }
104
120
  }
105
121
 
106
- // For a block image, we want to preserve the attributes on our own.
107
- // The inline image attributes will be handled by the GHS automatically.
108
122
  function preserveLinkAttributes( viewContainerElement ) {
109
123
  if ( data.modelRange && data.modelRange.getContainedElement().is( 'element', 'imageBlock' ) ) {
110
124
  preserveElementAttributes( viewContainerElement, 'htmlLinkAttributes' );
111
125
  }
126
+ }
127
+ }, { priority: 'low' } );
128
+ };
129
+ }
112
130
 
113
- // If we're in a link, then the `<figure>` element should be one level higher.
114
- if ( viewContainerElement.parent.is( 'element', 'figure' ) ) {
115
- preserveElementAttributes( viewContainerElement.parent, 'htmlFigureAttributes' );
116
- }
131
+ // View-to-model conversion helper preserving allowed attributes on {@link module:image/image~Image Image}
132
+ // feature model element from figure view element.
133
+ //
134
+ // @private
135
+ // @param {module:html-support/datafilter~DataFilter} dataFilter
136
+ // @returns {Function} Returns a conversion callback.
137
+ function viewToModelFigureAttributeConverter( dataFilter ) {
138
+ return dispatcher => {
139
+ dispatcher.on( 'element:figure', ( evt, data, conversionApi ) => {
140
+ const viewFigureElement = data.viewItem;
141
+
142
+ if ( !data.modelRange || !viewFigureElement.hasClass( 'image' ) ) {
143
+ return;
144
+ }
145
+
146
+ const viewAttributes = dataFilter.processViewAttributes( viewFigureElement, conversionApi );
147
+
148
+ if ( viewAttributes ) {
149
+ conversionApi.writer.setAttribute( 'htmlFigureAttributes', viewAttributes, data.modelRange );
117
150
  }
118
151
  }, { priority: 'low' } );
119
152
  };
@@ -130,7 +163,7 @@ function modelToViewImageAttributeConverter() {
130
163
 
131
164
  addBlockAttributeConversion( 'img', 'htmlAttributes' );
132
165
  addBlockAttributeConversion( 'figure', 'htmlFigureAttributes' );
133
- addBlockImageLinkAttributeConversion();
166
+ addBlockAttributeConversion( 'a', 'htmlLinkAttributes' );
134
167
 
135
168
  function addInlineAttributeConversion( attributeName ) {
136
169
  dispatcher.on( `attribute:${ attributeName }:imageInline`, ( evt, data, conversionApi ) => {
@@ -138,38 +171,42 @@ function modelToViewImageAttributeConverter() {
138
171
  return;
139
172
  }
140
173
 
174
+ const { attributeOldValue, attributeNewValue } = data;
141
175
  const viewElement = conversionApi.mapper.toViewElement( data.item );
142
176
 
143
- setViewAttributes( conversionApi.writer, data.attributeNewValue, viewElement );
177
+ updateViewAttributes( conversionApi.writer, attributeOldValue, attributeNewValue, viewElement );
144
178
  }, { priority: 'low' } );
145
179
  }
146
180
 
147
181
  function addBlockAttributeConversion( elementName, attributeName ) {
148
182
  dispatcher.on( `attribute:${ attributeName }:imageBlock`, ( evt, data, conversionApi ) => {
149
- if ( !conversionApi.consumable.consume( data.item, evt.name ) ) {
183
+ if ( !conversionApi.consumable.test( data.item, evt.name ) ) {
150
184
  return;
151
185
  }
152
186
 
187
+ const { attributeOldValue, attributeNewValue } = data;
153
188
  const containerElement = conversionApi.mapper.toViewElement( data.item );
154
189
  const viewElement = getDescendantElement( conversionApi.writer, containerElement, elementName );
155
190
 
156
- setViewAttributes( conversionApi.writer, data.attributeNewValue, viewElement );
191
+ if ( viewElement ) {
192
+ updateViewAttributes( conversionApi.writer, attributeOldValue, attributeNewValue, viewElement );
193
+ conversionApi.consumable.consume( data.item, evt.name );
194
+ }
157
195
  }, { priority: 'low' } );
158
- }
159
196
 
160
- // To have a link element in the view, we need to attach a converter to the `linkHref` attribute.
161
- // Doing this directly on `htmlLinkAttributes` will fail, as the link wrapper is not yet called at that moment.
162
- function addBlockImageLinkAttributeConversion( ) {
163
- dispatcher.on( 'attribute:linkHref:imageBlock', ( evt, data, conversionApi ) => {
164
- if ( !conversionApi.consumable.consume( data.item, 'attribute:htmlLinkAttributes:imageBlock' ) ) {
165
- return;
166
- }
197
+ if ( elementName === 'a' ) {
198
+ // To have a link element in the view, we need to attach a converter to the `linkHref` attribute as well.
199
+ dispatcher.on( 'attribute:linkHref:imageBlock', ( evt, data, conversionApi ) => {
200
+ if ( !conversionApi.consumable.consume( data.item, 'attribute:htmlLinkAttributes:imageBlock' ) ) {
201
+ return;
202
+ }
167
203
 
168
- const containerElement = conversionApi.mapper.toViewElement( data.item );
169
- const viewElement = getDescendantElement( conversionApi.writer, containerElement, 'a' );
204
+ const containerElement = conversionApi.mapper.toViewElement( data.item );
205
+ const viewElement = getDescendantElement( conversionApi.writer, containerElement, 'a' );
170
206
 
171
- setViewAttributes( conversionApi.writer, data.item.getAttribute( 'htmlLinkAttributes' ), viewElement );
172
- }, { priority: 'low' } );
207
+ setViewAttributes( conversionApi.writer, data.item.getAttribute( 'htmlLinkAttributes' ), viewElement );
208
+ }, { priority: 'low' } );
209
+ }
173
210
  }
174
211
  };
175
212
  }
@@ -9,9 +9,9 @@
9
9
 
10
10
  import { Plugin } from 'ckeditor5/src/core';
11
11
 
12
- import { setViewAttributes } from '../conversionutils.js';
13
12
  import DataFilter from '../datafilter';
14
13
  import DataSchema from '../dataschema';
14
+ import { updateViewAttributes } from '../conversionutils.js';
15
15
 
16
16
  /**
17
17
  * Provides the General HTML Support integration with {@link module:media-embed/mediaembed~MediaEmbed Media Embed} feature.
@@ -19,10 +19,23 @@ import DataSchema from '../dataschema';
19
19
  * @extends module:core/plugin~Plugin
20
20
  */
21
21
  export default class MediaEmbedElementSupport extends Plugin {
22
+ /**
23
+ * @inheritDoc
24
+ */
22
25
  static get requires() {
23
26
  return [ DataFilter ];
24
27
  }
25
28
 
29
+ /**
30
+ * @inheritDoc
31
+ */
32
+ static get pluginName() {
33
+ return 'MediaEmbedElementSupport';
34
+ }
35
+
36
+ /**
37
+ * @inheritDoc
38
+ */
26
39
  init() {
27
40
  const editor = this.editor;
28
41
 
@@ -44,6 +57,10 @@ export default class MediaEmbedElementSupport extends Plugin {
44
57
  view: mediaElementName
45
58
  } );
46
59
 
60
+ dataFilter.on( 'register:figure', ( ) => {
61
+ conversion.for( 'upcast' ).add( viewToModelFigureAttributesConverter( dataFilter ) );
62
+ } );
63
+
47
64
  dataFilter.on( `register:${ mediaElementName }`, ( evt, definition ) => {
48
65
  if ( definition.model !== 'media' ) {
49
66
  return;
@@ -71,16 +88,11 @@ function viewToModelMediaAttributesConverter( dataFilter, mediaElementName ) {
71
88
 
72
89
  function upcastMedia( evt, data, conversionApi ) {
73
90
  const viewMediaElement = data.viewItem;
74
- const viewParent = viewMediaElement.parent;
75
91
 
76
92
  preserveElementAttributes( viewMediaElement, 'htmlAttributes' );
77
93
 
78
- if ( viewParent.is( 'element', 'figure' ) && viewParent.hasClass( 'media' ) ) {
79
- preserveElementAttributes( viewParent, 'htmlFigureAttributes' );
80
- }
81
-
82
94
  function preserveElementAttributes( viewElement, attributeName ) {
83
- const viewAttributes = dataFilter._consumeAllowedAttributes( viewElement, conversionApi );
95
+ const viewAttributes = dataFilter.processViewAttributes( viewElement, conversionApi );
84
96
 
85
97
  if ( viewAttributes ) {
86
98
  conversionApi.writer.setAttribute( attributeName, viewAttributes, data.modelRange );
@@ -89,6 +101,30 @@ function viewToModelMediaAttributesConverter( dataFilter, mediaElementName ) {
89
101
  }
90
102
  }
91
103
 
104
+ // View-to-model conversion helper preserving allowed attributes on {@link module:media-embed/mediaembed~MediaEmbed MediaEmbed}
105
+ // feature model element from figure view element.
106
+ //
107
+ // @private
108
+ // @param {module:html-support/datafilter~DataFilter} dataFilter
109
+ // @returns {Function} Returns a conversion callback.
110
+ function viewToModelFigureAttributesConverter( dataFilter ) {
111
+ return dispatcher => {
112
+ dispatcher.on( 'element:figure', ( evt, data, conversionApi ) => {
113
+ const viewFigureElement = data.viewItem;
114
+
115
+ if ( !data.modelRange || !viewFigureElement.hasClass( 'media' ) ) {
116
+ return;
117
+ }
118
+
119
+ const viewAttributes = dataFilter.processViewAttributes( viewFigureElement, conversionApi );
120
+
121
+ if ( viewAttributes ) {
122
+ conversionApi.writer.setAttribute( 'htmlFigureAttributes', viewAttributes, data.modelRange );
123
+ }
124
+ }, { priority: 'low' } );
125
+ };
126
+ }
127
+
92
128
  function modelToViewMediaAttributeConverter( mediaElementName ) {
93
129
  return dispatcher => {
94
130
  addAttributeConversionDispatcherHandler( mediaElementName, 'htmlAttributes' );
@@ -100,10 +136,11 @@ function modelToViewMediaAttributeConverter( mediaElementName ) {
100
136
  return;
101
137
  }
102
138
 
139
+ const { attributeOldValue, attributeNewValue } = data;
103
140
  const containerElement = conversionApi.mapper.toViewElement( data.item );
104
141
  const viewElement = getDescendantElement( conversionApi.writer, containerElement, elementName );
105
142
 
106
- setViewAttributes( conversionApi.writer, data.attributeNewValue, viewElement );
143
+ updateViewAttributes( conversionApi.writer, attributeOldValue, attributeNewValue, viewElement );
107
144
  } );
108
145
  }
109
146
  };
@@ -30,6 +30,13 @@ export default class ScriptElementSupport extends Plugin {
30
30
  return [ DataFilter ];
31
31
  }
32
32
 
33
+ /**
34
+ * @inheritDoc
35
+ */
36
+ static get pluginName() {
37
+ return 'ScriptElementSupport';
38
+ }
39
+
33
40
  /**
34
41
  * @inheritDoc
35
42
  */
@@ -30,6 +30,13 @@ export default class StyleElementSupport extends Plugin {
30
30
  return [ DataFilter ];
31
31
  }
32
32
 
33
+ /**
34
+ * @inheritDoc
35
+ */
36
+ static get pluginName() {
37
+ return 'StyleElementSupport';
38
+ }
39
+
33
40
  /**
34
41
  * @inheritDoc
35
42
  */
@@ -9,7 +9,6 @@
9
9
 
10
10
  import { Plugin } from 'ckeditor5/src/core';
11
11
  import { setViewAttributes } from '../conversionutils.js';
12
-
13
12
  import DataFilter from '../datafilter';
14
13
 
15
14
  /**
@@ -25,6 +24,13 @@ export default class TableElementSupport extends Plugin {
25
24
  return [ DataFilter ];
26
25
  }
27
26
 
27
+ /**
28
+ * @inheritDoc
29
+ */
30
+ static get pluginName() {
31
+ return 'TableElementSupport';
32
+ }
33
+
28
34
  /**
29
35
  * @inheritDoc
30
36
  */
@@ -39,6 +45,10 @@ export default class TableElementSupport extends Plugin {
39
45
  const conversion = editor.conversion;
40
46
  const dataFilter = editor.plugins.get( DataFilter );
41
47
 
48
+ dataFilter.on( 'register:figure', ( ) => {
49
+ conversion.for( 'upcast' ).add( viewToModelFigureAttributeConverter( dataFilter ) );
50
+ } );
51
+
42
52
  dataFilter.on( 'register:table', ( evt, definition ) => {
43
53
  if ( definition.model !== 'table' ) {
44
54
  return;
@@ -74,11 +84,6 @@ function viewToModelTableAttributeConverter( dataFilter ) {
74
84
 
75
85
  preserveElementAttributes( viewTableElement, 'htmlAttributes' );
76
86
 
77
- const viewFigureElement = viewTableElement.parent;
78
- if ( viewFigureElement.is( 'element', 'figure' ) ) {
79
- preserveElementAttributes( viewFigureElement, 'htmlFigureAttributes' );
80
- }
81
-
82
87
  for ( const childNode of viewTableElement.getChildren() ) {
83
88
  if ( childNode.is( 'element', 'thead' ) ) {
84
89
  preserveElementAttributes( childNode, 'htmlTheadAttributes' );
@@ -90,12 +95,36 @@ function viewToModelTableAttributeConverter( dataFilter ) {
90
95
  }
91
96
 
92
97
  function preserveElementAttributes( viewElement, attributeName ) {
93
- const viewAttributes = dataFilter._consumeAllowedAttributes( viewElement, conversionApi );
98
+ const viewAttributes = dataFilter.processViewAttributes( viewElement, conversionApi );
94
99
 
95
100
  if ( viewAttributes ) {
96
101
  conversionApi.writer.setAttribute( attributeName, viewAttributes, data.modelRange );
97
102
  }
98
103
  }
104
+ } );
105
+ };
106
+ }
107
+
108
+ // View-to-model conversion helper preserving allowed attributes on {@link module:table/table~Table Table}
109
+ // feature model element from figure view element.
110
+ //
111
+ // @private
112
+ // @param {module:html-support/datafilter~DataFilter} dataFilter
113
+ // @returns {Function} Returns a conversion callback.
114
+ function viewToModelFigureAttributeConverter( dataFilter ) {
115
+ return dispatcher => {
116
+ dispatcher.on( 'element:figure', ( evt, data, conversionApi ) => {
117
+ const viewFigureElement = data.viewItem;
118
+
119
+ if ( !data.modelRange || !viewFigureElement.hasClass( 'table' ) ) {
120
+ return;
121
+ }
122
+
123
+ const viewAttributes = dataFilter.processViewAttributes( viewFigureElement, conversionApi );
124
+
125
+ if ( viewAttributes ) {
126
+ conversionApi.writer.setAttribute( 'htmlFigureAttributes', viewAttributes, data.modelRange );
127
+ }
99
128
  }, { priority: 'low' } );
100
129
  };
101
130
  }