@ckeditor/ckeditor5-html-support 29.0.0 → 31.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,193 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+
6
+ /**
7
+ * @module html-support/integrations/image
8
+ */
9
+
10
+ import { Plugin } from 'ckeditor5/src/core';
11
+
12
+ import DataFilter from '../datafilter';
13
+ import { setViewAttributes } from '../conversionutils.js';
14
+
15
+ /**
16
+ * Provides the General HTML Support integration with the {@link module:image/image~Image Image} feature.
17
+ *
18
+ * @extends module:core/plugin~Plugin
19
+ */
20
+ export default class ImageElementSupport extends Plugin {
21
+ /**
22
+ * @inheritDoc
23
+ */
24
+ static get requires() {
25
+ return [ DataFilter ];
26
+ }
27
+
28
+ /**
29
+ * @inheritDoc
30
+ */
31
+ init() {
32
+ const editor = this.editor;
33
+
34
+ // At least one image plugin should be loaded for the integration to work properly.
35
+ if ( !editor.plugins.has( 'ImageInlineEditing' ) && !editor.plugins.has( 'ImageBlockEditing' ) ) {
36
+ return;
37
+ }
38
+
39
+ const schema = editor.model.schema;
40
+ const conversion = editor.conversion;
41
+ const dataFilter = editor.plugins.get( DataFilter );
42
+
43
+ dataFilter.on( 'register:img', ( evt, definition ) => {
44
+ if ( definition.model !== 'imageBlock' && definition.model !== 'imageInline' ) {
45
+ return;
46
+ }
47
+
48
+ if ( schema.isRegistered( 'imageBlock' ) ) {
49
+ schema.extend( 'imageBlock', {
50
+ allowAttributes: [
51
+ 'htmlAttributes',
52
+ // Figure and Link don't have model counterpart.
53
+ // We will preserve attributes on image model element using these attribute keys.
54
+ 'htmlFigureAttributes',
55
+ 'htmlLinkAttributes'
56
+ ]
57
+ } );
58
+ }
59
+
60
+ if ( schema.isRegistered( 'imageInline' ) ) {
61
+ schema.extend( 'imageInline', {
62
+ allowAttributes: [
63
+ // `htmlA` is needed for standard GHS link integration.
64
+ 'htmlA',
65
+ 'htmlAttributes'
66
+ ]
67
+ } );
68
+ }
69
+
70
+ conversion.for( 'upcast' ).add( viewToModelImageAttributeConverter( dataFilter ) );
71
+ conversion.for( 'downcast' ).add( modelToViewImageAttributeConverter() );
72
+
73
+ evt.stop();
74
+ } );
75
+ }
76
+ }
77
+
78
+ // View-to-model conversion helper preserving allowed attributes on the {@link module:image/image~Image Image}
79
+ // feature model element.
80
+ //
81
+ // @private
82
+ // @param {module:html-support/datafilter~DataFilter} dataFilter
83
+ // @returns {Function} Returns a conversion callback.
84
+ function viewToModelImageAttributeConverter( dataFilter ) {
85
+ return dispatcher => {
86
+ dispatcher.on( 'element:img', ( evt, data, conversionApi ) => {
87
+ const viewImageElement = data.viewItem;
88
+ const viewContainerElement = viewImageElement.parent;
89
+
90
+ preserveElementAttributes( viewImageElement, 'htmlAttributes' );
91
+
92
+ if ( viewContainerElement.is( 'element', 'figure' ) ) {
93
+ preserveElementAttributes( viewContainerElement, 'htmlFigureAttributes' );
94
+ } else if ( viewContainerElement.is( 'element', 'a' ) ) {
95
+ preserveLinkAttributes( viewContainerElement );
96
+ }
97
+
98
+ function preserveElementAttributes( viewElement, attributeName ) {
99
+ const viewAttributes = dataFilter._consumeAllowedAttributes( viewElement, conversionApi );
100
+
101
+ if ( viewAttributes ) {
102
+ conversionApi.writer.setAttribute( attributeName, viewAttributes, data.modelRange );
103
+ }
104
+ }
105
+
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
+ function preserveLinkAttributes( viewContainerElement ) {
109
+ if ( data.modelRange && data.modelRange.getContainedElement().is( 'element', 'imageBlock' ) ) {
110
+ preserveElementAttributes( viewContainerElement, 'htmlLinkAttributes' );
111
+ }
112
+
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
+ }
117
+ }
118
+ }, { priority: 'low' } );
119
+ };
120
+ }
121
+
122
+ // A model-to-view conversion helper applying attributes from the {@link module:image/image~Image Image}
123
+ // feature.
124
+ //
125
+ // @private
126
+ // @returns {Function} Returns a conversion callback.
127
+ function modelToViewImageAttributeConverter() {
128
+ return dispatcher => {
129
+ addInlineAttributeConversion( 'htmlAttributes' );
130
+
131
+ addBlockAttributeConversion( 'img', 'htmlAttributes' );
132
+ addBlockAttributeConversion( 'figure', 'htmlFigureAttributes' );
133
+ addBlockImageLinkAttributeConversion();
134
+
135
+ function addInlineAttributeConversion( attributeName ) {
136
+ dispatcher.on( `attribute:${ attributeName }:imageInline`, ( evt, data, conversionApi ) => {
137
+ if ( !conversionApi.consumable.consume( data.item, evt.name ) ) {
138
+ return;
139
+ }
140
+
141
+ const viewElement = conversionApi.mapper.toViewElement( data.item );
142
+
143
+ setViewAttributes( conversionApi.writer, data.attributeNewValue, viewElement );
144
+ }, { priority: 'low' } );
145
+ }
146
+
147
+ function addBlockAttributeConversion( elementName, attributeName ) {
148
+ dispatcher.on( `attribute:${ attributeName }:imageBlock`, ( evt, data, conversionApi ) => {
149
+ if ( !conversionApi.consumable.consume( data.item, evt.name ) ) {
150
+ return;
151
+ }
152
+
153
+ const containerElement = conversionApi.mapper.toViewElement( data.item );
154
+ const viewElement = getDescendantElement( conversionApi.writer, containerElement, elementName );
155
+
156
+ setViewAttributes( conversionApi.writer, data.attributeNewValue, viewElement );
157
+ }, { priority: 'low' } );
158
+ }
159
+
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
+ }
167
+
168
+ const containerElement = conversionApi.mapper.toViewElement( data.item );
169
+ const viewElement = getDescendantElement( conversionApi.writer, containerElement, 'a' );
170
+
171
+ setViewAttributes( conversionApi.writer, data.item.getAttribute( 'htmlLinkAttributes' ), viewElement );
172
+ }, { priority: 'low' } );
173
+ }
174
+ };
175
+ }
176
+
177
+ // Returns the first view element descendant matching the given view name.
178
+ // Includes view element itself.
179
+ //
180
+ // @private
181
+ // @param {module:engine/view/downcastwriter~DowncastWriter} writer
182
+ // @param {module:engine/view/element~Element} containerElement
183
+ // @param {String} elementName
184
+ // @returns {module:engine/view/element~Element|null}
185
+ function getDescendantElement( writer, containerElement, elementName ) {
186
+ const range = writer.createRangeOn( containerElement );
187
+
188
+ for ( const { item } of range.getWalker() ) {
189
+ if ( item.is( 'element', elementName ) ) {
190
+ return item;
191
+ }
192
+ }
193
+ }
@@ -0,0 +1,128 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+
6
+ /**
7
+ * @module html-support/integrations/mediaembed
8
+ */
9
+
10
+ import { Plugin } from 'ckeditor5/src/core';
11
+
12
+ import { setViewAttributes } from '../conversionutils.js';
13
+ import DataFilter from '../datafilter';
14
+ import DataSchema from '../dataschema';
15
+
16
+ /**
17
+ * Provides the General HTML Support integration with {@link module:media-embed/mediaembed~MediaEmbed Media Embed} feature.
18
+ *
19
+ * @extends module:core/plugin~Plugin
20
+ */
21
+ export default class MediaEmbedElementSupport extends Plugin {
22
+ static get requires() {
23
+ return [ DataFilter ];
24
+ }
25
+
26
+ init() {
27
+ const editor = this.editor;
28
+
29
+ // Stop here if MediaEmbed plugin is not provided or the integrator wants to output markup with previews as
30
+ // we do not support filtering previews.
31
+ if ( !editor.plugins.has( 'MediaEmbed' ) || editor.config.get( 'mediaEmbed.previewsInData' ) ) {
32
+ return;
33
+ }
34
+
35
+ const schema = editor.model.schema;
36
+ const conversion = editor.conversion;
37
+ const dataFilter = this.editor.plugins.get( DataFilter );
38
+ const dataSchema = this.editor.plugins.get( DataSchema );
39
+ const mediaElementName = editor.config.get( 'mediaEmbed.elementName' );
40
+
41
+ // Overwrite GHS schema definition for a given elementName.
42
+ dataSchema.registerBlockElement( {
43
+ model: 'media',
44
+ view: mediaElementName
45
+ } );
46
+
47
+ dataFilter.on( `register:${ mediaElementName }`, ( evt, definition ) => {
48
+ if ( definition.model !== 'media' ) {
49
+ return;
50
+ }
51
+
52
+ schema.extend( 'media', {
53
+ allowAttributes: [
54
+ 'htmlAttributes',
55
+ 'htmlFigureAttributes'
56
+ ]
57
+ } );
58
+
59
+ conversion.for( 'upcast' ).add( viewToModelMediaAttributesConverter( dataFilter, mediaElementName ) );
60
+ conversion.for( 'dataDowncast' ).add( modelToViewMediaAttributeConverter( mediaElementName ) );
61
+
62
+ evt.stop();
63
+ } );
64
+ }
65
+ }
66
+
67
+ function viewToModelMediaAttributesConverter( dataFilter, mediaElementName ) {
68
+ return dispatcher => {
69
+ dispatcher.on( `element:${ mediaElementName }`, upcastMedia );
70
+ };
71
+
72
+ function upcastMedia( evt, data, conversionApi ) {
73
+ const viewMediaElement = data.viewItem;
74
+ const viewParent = viewMediaElement.parent;
75
+
76
+ preserveElementAttributes( viewMediaElement, 'htmlAttributes' );
77
+
78
+ if ( viewParent.is( 'element', 'figure' ) && viewParent.hasClass( 'media' ) ) {
79
+ preserveElementAttributes( viewParent, 'htmlFigureAttributes' );
80
+ }
81
+
82
+ function preserveElementAttributes( viewElement, attributeName ) {
83
+ const viewAttributes = dataFilter._consumeAllowedAttributes( viewElement, conversionApi );
84
+
85
+ if ( viewAttributes ) {
86
+ conversionApi.writer.setAttribute( attributeName, viewAttributes, data.modelRange );
87
+ }
88
+ }
89
+ }
90
+ }
91
+
92
+ function modelToViewMediaAttributeConverter( mediaElementName ) {
93
+ return dispatcher => {
94
+ addAttributeConversionDispatcherHandler( mediaElementName, 'htmlAttributes' );
95
+ addAttributeConversionDispatcherHandler( 'figure', 'htmlFigureAttributes' );
96
+
97
+ function addAttributeConversionDispatcherHandler( elementName, attributeName ) {
98
+ dispatcher.on( `attribute:${ attributeName }:media`, ( evt, data, conversionApi ) => {
99
+ if ( !conversionApi.consumable.consume( data.item, evt.name ) ) {
100
+ return;
101
+ }
102
+
103
+ const containerElement = conversionApi.mapper.toViewElement( data.item );
104
+ const viewElement = getDescendantElement( conversionApi.writer, containerElement, elementName );
105
+
106
+ setViewAttributes( conversionApi.writer, data.attributeNewValue, viewElement );
107
+ } );
108
+ }
109
+ };
110
+ }
111
+
112
+ // Returns the first view element descendant matching the given view name.
113
+ // Includes view element itself.
114
+ //
115
+ // @private
116
+ // @param {module:engine/view/downcastwriter~DowncastWriter} writer
117
+ // @param {module:engine/view/element~Element} containerElement
118
+ // @param {String} elementName
119
+ // @returns {module:engine/view/element~Element|null}
120
+ function getDescendantElement( writer, containerElement, elementName ) {
121
+ const range = writer.createRangeOn( containerElement );
122
+
123
+ for ( const { item } of range.getWalker() ) {
124
+ if ( item.is( 'element', elementName ) ) {
125
+ return item;
126
+ }
127
+ }
128
+ }
@@ -0,0 +1,146 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+
6
+ /**
7
+ * @module html-support/integrations/table
8
+ */
9
+
10
+ import { Plugin } from 'ckeditor5/src/core';
11
+ import { setViewAttributes } from '../conversionutils.js';
12
+
13
+ import DataFilter from '../datafilter';
14
+
15
+ /**
16
+ * Provides the General HTML Support integration with {@link module:table/table~Table Table} feature.
17
+ *
18
+ * @extends module:core/plugin~Plugin
19
+ */
20
+ export default class TableElementSupport extends Plugin {
21
+ /**
22
+ * @inheritDoc
23
+ */
24
+ static get requires() {
25
+ return [ DataFilter ];
26
+ }
27
+
28
+ /**
29
+ * @inheritDoc
30
+ */
31
+ init() {
32
+ const editor = this.editor;
33
+
34
+ if ( !editor.plugins.has( 'TableEditing' ) ) {
35
+ return;
36
+ }
37
+
38
+ const schema = editor.model.schema;
39
+ const conversion = editor.conversion;
40
+ const dataFilter = editor.plugins.get( DataFilter );
41
+
42
+ dataFilter.on( 'register:table', ( evt, definition ) => {
43
+ if ( definition.model !== 'table' ) {
44
+ return;
45
+ }
46
+
47
+ schema.extend( 'table', {
48
+ allowAttributes: [
49
+ 'htmlAttributes',
50
+ // Figure, thead and tbody elements don't have model counterparts.
51
+ // We will be preserving attributes on table element using these attribute keys.
52
+ 'htmlFigureAttributes', 'htmlTheadAttributes', 'htmlTbodyAttributes'
53
+ ]
54
+ } );
55
+
56
+ conversion.for( 'upcast' ).add( viewToModelTableAttributeConverter( dataFilter ) );
57
+ conversion.for( 'downcast' ).add( modelToViewTableAttributeConverter() );
58
+
59
+ evt.stop();
60
+ } );
61
+ }
62
+ }
63
+
64
+ // View-to-model conversion helper preserving allowed attributes on {@link module:table/table~Table Table}
65
+ // feature model element.
66
+ //
67
+ // @private
68
+ // @param {module:html-support/datafilter~DataFilter} dataFilter
69
+ // @returns {Function} Returns a conversion callback.
70
+ function viewToModelTableAttributeConverter( dataFilter ) {
71
+ return dispatcher => {
72
+ dispatcher.on( 'element:table', ( evt, data, conversionApi ) => {
73
+ const viewTableElement = data.viewItem;
74
+
75
+ preserveElementAttributes( viewTableElement, 'htmlAttributes' );
76
+
77
+ const viewFigureElement = viewTableElement.parent;
78
+ if ( viewFigureElement.is( 'element', 'figure' ) ) {
79
+ preserveElementAttributes( viewFigureElement, 'htmlFigureAttributes' );
80
+ }
81
+
82
+ for ( const childNode of viewTableElement.getChildren() ) {
83
+ if ( childNode.is( 'element', 'thead' ) ) {
84
+ preserveElementAttributes( childNode, 'htmlTheadAttributes' );
85
+ }
86
+
87
+ if ( childNode.is( 'element', 'tbody' ) ) {
88
+ preserveElementAttributes( childNode, 'htmlTbodyAttributes' );
89
+ }
90
+ }
91
+
92
+ function preserveElementAttributes( viewElement, attributeName ) {
93
+ const viewAttributes = dataFilter._consumeAllowedAttributes( viewElement, conversionApi );
94
+
95
+ if ( viewAttributes ) {
96
+ conversionApi.writer.setAttribute( attributeName, viewAttributes, data.modelRange );
97
+ }
98
+ }
99
+ }, { priority: 'low' } );
100
+ };
101
+ }
102
+
103
+ // Model-to-view conversion helper applying attributes from {@link module:table/table~Table Table}
104
+ // feature.
105
+ //
106
+ // @private
107
+ // @returns {Function} Returns a conversion callback.
108
+ function modelToViewTableAttributeConverter() {
109
+ return dispatcher => {
110
+ addAttributeConversionDispatcherHandler( 'table', 'htmlAttributes' );
111
+ addAttributeConversionDispatcherHandler( 'figure', 'htmlFigureAttributes' );
112
+ addAttributeConversionDispatcherHandler( 'thead', 'htmlTheadAttributes' );
113
+ addAttributeConversionDispatcherHandler( 'tbody', 'htmlTbodyAttributes' );
114
+
115
+ function addAttributeConversionDispatcherHandler( elementName, attributeName ) {
116
+ dispatcher.on( `attribute:${ attributeName }:table`, ( evt, data, conversionApi ) => {
117
+ if ( !conversionApi.consumable.consume( data.item, evt.name ) ) {
118
+ return;
119
+ }
120
+
121
+ const containerElement = conversionApi.mapper.toViewElement( data.item );
122
+ const viewElement = getDescendantElement( conversionApi.writer, containerElement, elementName );
123
+
124
+ setViewAttributes( conversionApi.writer, data.attributeNewValue, viewElement );
125
+ } );
126
+ }
127
+ };
128
+ }
129
+
130
+ // Returns the first view element descendant matching the given view name.
131
+ // Includes view element itself.
132
+ //
133
+ // @private
134
+ // @param {module:engine/view/downcastwriter~DowncastWriter} writer
135
+ // @param {module:engine/view/element~Element} containerElement
136
+ // @param {String} elementName
137
+ // @returns {module:engine/view/element~Element|null}
138
+ function getDescendantElement( writer, containerElement, elementName ) {
139
+ const range = writer.createRangeOn( containerElement );
140
+
141
+ for ( const { item } of range.getWalker() ) {
142
+ if ( item.is( 'element', elementName ) ) {
143
+ return item;
144
+ }
145
+ }
146
+ }
@@ -51,18 +51,6 @@
51
51
  export default {
52
52
  block: [
53
53
  // Existing features
54
- {
55
- model: 'heading1',
56
- view: 'h2'
57
- },
58
- {
59
- model: 'heading2',
60
- view: 'h3'
61
- },
62
- {
63
- model: 'heading3',
64
- view: 'h4'
65
- },
66
54
  {
67
55
  model: 'codeBlock',
68
56
  view: 'pre'
@@ -87,6 +75,38 @@ export default {
87
75
  model: 'rawHtml',
88
76
  view: 'div'
89
77
  },
78
+ {
79
+ model: 'table',
80
+ view: 'table'
81
+ },
82
+ {
83
+ model: 'tableRow',
84
+ view: 'tr'
85
+ },
86
+ {
87
+ model: 'tableCell',
88
+ view: 'td'
89
+ },
90
+ {
91
+ model: 'tableCell',
92
+ view: 'th'
93
+ },
94
+ {
95
+ model: 'caption',
96
+ view: 'caption'
97
+ },
98
+ {
99
+ model: 'caption',
100
+ view: 'figcaption'
101
+ },
102
+ {
103
+ model: 'imageBlock',
104
+ view: 'img'
105
+ },
106
+ {
107
+ model: 'imageInline',
108
+ view: 'img'
109
+ },
90
110
 
91
111
  // Compatibility features
92
112
  {
@@ -232,7 +252,7 @@ export default {
232
252
  }
233
253
  },
234
254
  {
235
- model: 'htmlSumary',
255
+ model: 'htmlSummary',
236
256
  view: 'summary',
237
257
  modelSchema: {
238
258
  allowChildren: '$text',
@@ -240,10 +260,10 @@ export default {
240
260
  isBlock: true
241
261
  }
242
262
  },
243
- // TODO can also include text.
244
263
  {
245
264
  model: 'htmlDiv',
246
265
  view: 'div',
266
+ paragraphLikeModel: 'htmlDivParagraph',
247
267
  modelSchema: {
248
268
  inheritAllFrom: '$htmlSection'
249
269
  }
@@ -294,15 +314,12 @@ export default {
294
314
  view: 'hgroup',
295
315
  modelSchema: {
296
316
  allowChildren: [
297
- 'htmlHeading1',
298
- 'htmlHeading2',
299
- 'htmlHeading3',
300
- 'htmlHeading4',
301
- 'htmlHeading5',
302
- 'htmlHeading6',
303
- 'heading1',
304
- 'heading2',
305
- 'heading3'
317
+ 'htmlH1',
318
+ 'htmlH2',
319
+ 'htmlH3',
320
+ 'htmlH4',
321
+ 'htmlH5',
322
+ 'htmlH6'
306
323
  ],
307
324
  isBlock: true
308
325
  }
@@ -609,7 +626,7 @@ export default {
609
626
  copyOnEnter: true
610
627
  }
611
628
  },
612
- // TODO According to HTML-spec can behave as div-like element, althouth CKE4 only handles it as an inline element.
629
+ // TODO According to HTML-spec can behave as div-like element, although CKE4 only handles it as an inline element.
613
630
  {
614
631
  model: 'htmlDel',
615
632
  view: 'del',
@@ -617,7 +634,7 @@ export default {
617
634
  copyOnEnter: true
618
635
  }
619
636
  },
620
- // TODO According to HTML-spec can behave as div-like element, althouth CKE4 only handles it as an inline element.
637
+ // TODO According to HTML-spec can behave as div-like element, although CKE4 only handles it as an inline element.
621
638
  {
622
639
  model: 'htmlIns',
623
640
  view: 'ins',
@@ -764,6 +781,14 @@ export default {
764
781
  inheritAllFrom: '$htmlObjectInline'
765
782
  }
766
783
  },
784
+ {
785
+ model: 'htmlOembed',
786
+ view: 'oembed',
787
+ isObject: true,
788
+ modelSchema: {
789
+ inheritAllFrom: '$htmlObjectInline'
790
+ }
791
+ },
767
792
  {
768
793
  model: 'htmlAudio',
769
794
  view: 'audio',
package/CHANGELOG.md DELETED
@@ -1,4 +0,0 @@
1
- Changelog
2
- =========
3
-
4
- All changes in the package are documented in the main repository. See: https://github.com/ckeditor/ckeditor5/blob/master/CHANGELOG.md.