@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.
- package/README.md +2 -2
- package/build/html-support.js +5 -0
- package/build/translations/de.js +1 -0
- package/build/translations/gl.js +1 -0
- package/build/translations/hu.js +1 -0
- package/build/translations/it.js +1 -0
- package/build/translations/nl.js +1 -0
- package/build/translations/pl.js +1 -0
- package/build/translations/ru.js +1 -0
- package/build/translations/sr-latn.js +1 -0
- package/build/translations/sr.js +1 -0
- package/ckeditor5-metadata.json +53 -0
- package/lang/translations/de.po +21 -0
- package/lang/translations/gl.po +21 -0
- package/lang/translations/hu.po +21 -0
- package/lang/translations/it.po +21 -0
- package/lang/translations/nl.po +21 -0
- package/lang/translations/pl.po +21 -0
- package/lang/translations/ru.po +21 -0
- package/lang/translations/sr-latn.po +21 -0
- package/lang/translations/sr.po +21 -0
- package/package.json +36 -29
- package/src/converters.js +2 -20
- package/src/datafilter.js +5 -21
- package/src/dataschema.js +49 -1
- package/src/generalhtmlsupport.js +25 -11
- package/src/htmlcomment.js +250 -0
- package/src/index.js +1 -0
- package/src/integrations/codeblock.js +7 -3
- package/src/integrations/dualcontent.js +138 -0
- package/src/integrations/heading.js +61 -0
- package/src/integrations/image.js +193 -0
- package/src/integrations/mediaembed.js +128 -0
- package/src/integrations/table.js +146 -0
- package/src/schemadefinitions.js +50 -25
- package/CHANGELOG.md +0 -4
package/src/datafilter.js
CHANGED
|
@@ -14,8 +14,6 @@ import { Matcher } from 'ckeditor5/src/engine';
|
|
|
14
14
|
import { priorities, CKEditorError } from 'ckeditor5/src/utils';
|
|
15
15
|
import { Widget } from 'ckeditor5/src/widget';
|
|
16
16
|
import {
|
|
17
|
-
disallowedAttributesConverter,
|
|
18
|
-
|
|
19
17
|
viewToModelObjectConverter,
|
|
20
18
|
toObjectWidgetConverter,
|
|
21
19
|
createObjectView,
|
|
@@ -210,7 +208,7 @@ export default class DataFilter extends Plugin {
|
|
|
210
208
|
}
|
|
211
209
|
|
|
212
210
|
/**
|
|
213
|
-
* Matches and consumes allowed view attributes.
|
|
211
|
+
* Matches and consumes allowed and disallowed view attributes and returns the allowed ones.
|
|
214
212
|
*
|
|
215
213
|
* @protected
|
|
216
214
|
* @param {module:engine/view/element~Element} viewElement
|
|
@@ -221,22 +219,11 @@ export default class DataFilter extends Plugin {
|
|
|
221
219
|
* @returns {Array.<String>} result.classes Set with matched class names.
|
|
222
220
|
*/
|
|
223
221
|
_consumeAllowedAttributes( viewElement, conversionApi ) {
|
|
224
|
-
|
|
225
|
-
|
|
222
|
+
// Make sure that the disabled attributes are handled before the allowed attributes are called.
|
|
223
|
+
// For example, for block images the <figure> converter triggers conversion for <img> first and then for other elements, i.e. <a>.
|
|
224
|
+
consumeAttributes( viewElement, conversionApi, this._disallowedAttributes );
|
|
226
225
|
|
|
227
|
-
|
|
228
|
-
* Matches and consumes disallowed view attributes.
|
|
229
|
-
*
|
|
230
|
-
* @protected
|
|
231
|
-
* @param {module:engine/view/element~Element} viewElement
|
|
232
|
-
* @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi
|
|
233
|
-
* @returns {Object} [result]
|
|
234
|
-
* @returns {Object} result.attributes Set with matched attribute names.
|
|
235
|
-
* @returns {Object} result.styles Set with matched style names.
|
|
236
|
-
* @returns {Array.<String>} result.classes Set with matched class names.
|
|
237
|
-
*/
|
|
238
|
-
_consumeDisallowedAttributes( viewElement, conversionApi ) {
|
|
239
|
-
return consumeAttributes( viewElement, conversionApi, this._disallowedAttributes );
|
|
226
|
+
return consumeAttributes( viewElement, conversionApi, this._allowedAttributes );
|
|
240
227
|
}
|
|
241
228
|
|
|
242
229
|
/**
|
|
@@ -335,7 +322,6 @@ export default class DataFilter extends Plugin {
|
|
|
335
322
|
name: viewName
|
|
336
323
|
} );
|
|
337
324
|
|
|
338
|
-
conversion.for( 'upcast' ).add( disallowedAttributesConverter( definition, this ) );
|
|
339
325
|
conversion.for( 'upcast' ).elementToElement( {
|
|
340
326
|
view: viewName,
|
|
341
327
|
model: viewToModelObjectConverter( definition ),
|
|
@@ -400,7 +386,6 @@ export default class DataFilter extends Plugin {
|
|
|
400
386
|
allowAttributes: 'htmlAttributes'
|
|
401
387
|
} );
|
|
402
388
|
|
|
403
|
-
conversion.for( 'upcast' ).add( disallowedAttributesConverter( definition, this ) );
|
|
404
389
|
conversion.for( 'upcast' ).add( viewToModelBlockAttributeConverter( definition, this ) );
|
|
405
390
|
conversion.for( 'downcast' ).add( modelToViewBlockAttributeConverter( definition ) );
|
|
406
391
|
}
|
|
@@ -427,7 +412,6 @@ export default class DataFilter extends Plugin {
|
|
|
427
412
|
schema.setAttributeProperties( attributeKey, definition.attributeProperties );
|
|
428
413
|
}
|
|
429
414
|
|
|
430
|
-
conversion.for( 'upcast' ).add( disallowedAttributesConverter( definition, this ) );
|
|
431
415
|
conversion.for( 'upcast' ).add( viewToAttributeInlineConverter( definition, this ) );
|
|
432
416
|
|
|
433
417
|
conversion.for( 'downcast' ).attributeToElement( {
|
package/src/dataschema.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { Plugin } from 'ckeditor5/src/core';
|
|
11
11
|
import { toArray } from 'ckeditor5/src/utils';
|
|
12
12
|
import defaultConfig from './schemadefinitions';
|
|
13
|
+
import { mergeWith } from 'lodash-es';
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Holds representation of the extended HTML document type definitions to be used by the
|
|
@@ -93,6 +94,30 @@ export default class DataSchema extends Plugin {
|
|
|
93
94
|
this._definitions.set( definition.model, { ...definition, isInline: true } );
|
|
94
95
|
}
|
|
95
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Updates schema definition describing block element with new properties.
|
|
99
|
+
*
|
|
100
|
+
* Creates new scheme if it doesn't exist.
|
|
101
|
+
* Array properties are concatenated with original values.
|
|
102
|
+
*
|
|
103
|
+
* @param {module:html-support/dataschema~DataSchemaBlockElementDefinition} definition Definition update.
|
|
104
|
+
*/
|
|
105
|
+
extendBlockElement( definition ) {
|
|
106
|
+
this._extendDefinition( { ...definition, isBlock: true } );
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Updates schema definition describing inline element with new properties.
|
|
111
|
+
*
|
|
112
|
+
* Creates new scheme if it doesn't exist.
|
|
113
|
+
* Array properties are concatenated with original values.
|
|
114
|
+
*
|
|
115
|
+
* @param {module:html-support/dataschema~DataSchemaInlineElementDefinition} definition Definition update.
|
|
116
|
+
*/
|
|
117
|
+
extendInlineElement( definition ) {
|
|
118
|
+
this._extendDefinition( { ...definition, isInline: true } );
|
|
119
|
+
}
|
|
120
|
+
|
|
96
121
|
/**
|
|
97
122
|
* Returns all definitions matching the given view name.
|
|
98
123
|
*
|
|
@@ -155,6 +180,25 @@ export default class DataSchema extends Plugin {
|
|
|
155
180
|
}
|
|
156
181
|
}
|
|
157
182
|
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Updates schema definition with new properties.
|
|
186
|
+
*
|
|
187
|
+
* Creates new scheme if it doesn't exist.
|
|
188
|
+
* Array properties are concatenated with original values.
|
|
189
|
+
*
|
|
190
|
+
* @private
|
|
191
|
+
* @param {module:html-support/dataschema~DataSchemaDefinition} definition Definition update.
|
|
192
|
+
*/
|
|
193
|
+
_extendDefinition( definition ) {
|
|
194
|
+
const currentDefinition = this._definitions.get( definition.model );
|
|
195
|
+
|
|
196
|
+
const mergedDefinition = mergeWith( {}, currentDefinition, definition, ( target, source ) => {
|
|
197
|
+
return Array.isArray( target ) ? target.concat( source ) : undefined;
|
|
198
|
+
} );
|
|
199
|
+
|
|
200
|
+
this._definitions.set( definition.model, mergedDefinition );
|
|
201
|
+
}
|
|
158
202
|
}
|
|
159
203
|
|
|
160
204
|
// Test view name against the given pattern.
|
|
@@ -191,6 +235,10 @@ function testViewName( pattern, viewName ) {
|
|
|
191
235
|
* @typedef {Object} module:html-support/dataschema~DataSchemaBlockElementDefinition
|
|
192
236
|
* @property {Boolean} isBlock Indicates that the definition describes block element.
|
|
193
237
|
* Set by {@link module:html-support/dataschema~DataSchema#registerBlockElement} method.
|
|
238
|
+
* @property {String} [paragraphLikeModel] Should be used when an element can behave both as a sectioning element (e.g. article) and
|
|
239
|
+
* element accepting only inline content (e.g. paragraph).
|
|
240
|
+
* If an element contains only inline content, this option will be used as a model
|
|
241
|
+
* name.
|
|
194
242
|
* @extends module:html-support/dataschema~DataSchemaDefinition
|
|
195
243
|
*/
|
|
196
244
|
|
|
@@ -199,7 +247,7 @@ function testViewName( pattern, viewName ) {
|
|
|
199
247
|
*
|
|
200
248
|
* @typedef {Object} module:html-support/dataschema~DataSchemaInlineElementDefinition
|
|
201
249
|
* @property {module:engine/model/schema~AttributeProperties} [attributeProperties] Additional metadata describing the model attribute.
|
|
202
|
-
* @property {Boolean} isInline Indicates that the definition
|
|
250
|
+
* @property {Boolean} isInline Indicates that the definition describes inline element.
|
|
203
251
|
* @property {Number} [priority] Element priority. Decides in what order elements are wrapped by
|
|
204
252
|
* {@link module:engine/view/downcastwriter~DowncastWriter}.
|
|
205
253
|
* Set by {@link module:html-support/dataschema~DataSchema#registerInlineElement} method.
|
|
@@ -8,8 +8,14 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { Plugin } from 'ckeditor5/src/core';
|
|
11
|
+
|
|
11
12
|
import DataFilter from './datafilter';
|
|
12
|
-
import
|
|
13
|
+
import CodeBlockElementSupport from './integrations/codeblock';
|
|
14
|
+
import DualContentModelElementSupport from './integrations/dualcontent';
|
|
15
|
+
import HeadingElementSupport from './integrations/heading';
|
|
16
|
+
import ImageElementSupport from './integrations/image';
|
|
17
|
+
import MediaEmbedElementSupport from './integrations/mediaembed';
|
|
18
|
+
import TableElementSupport from './integrations/table';
|
|
13
19
|
|
|
14
20
|
/**
|
|
15
21
|
* The General HTML Support feature.
|
|
@@ -27,24 +33,32 @@ export default class GeneralHtmlSupport extends Plugin {
|
|
|
27
33
|
return 'GeneralHtmlSupport';
|
|
28
34
|
}
|
|
29
35
|
|
|
30
|
-
init() {
|
|
31
|
-
const editor = this.editor;
|
|
32
|
-
const dataFilter = editor.plugins.get( DataFilter );
|
|
33
|
-
|
|
34
|
-
// Load the filtering configuration.
|
|
35
|
-
dataFilter.loadAllowedConfig( editor.config.get( 'htmlSupport.allow' ) || [] );
|
|
36
|
-
dataFilter.loadDisallowedConfig( editor.config.get( 'htmlSupport.disallow' ) || [] );
|
|
37
|
-
}
|
|
38
|
-
|
|
39
36
|
/**
|
|
40
37
|
* @inheritDoc
|
|
41
38
|
*/
|
|
42
39
|
static get requires() {
|
|
43
40
|
return [
|
|
44
41
|
DataFilter,
|
|
45
|
-
|
|
42
|
+
CodeBlockElementSupport,
|
|
43
|
+
DualContentModelElementSupport,
|
|
44
|
+
HeadingElementSupport,
|
|
45
|
+
ImageElementSupport,
|
|
46
|
+
MediaEmbedElementSupport,
|
|
47
|
+
TableElementSupport
|
|
46
48
|
];
|
|
47
49
|
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @inheritDoc
|
|
53
|
+
*/
|
|
54
|
+
init() {
|
|
55
|
+
const editor = this.editor;
|
|
56
|
+
const dataFilter = editor.plugins.get( DataFilter );
|
|
57
|
+
|
|
58
|
+
// Load the filtering configuration.
|
|
59
|
+
dataFilter.loadAllowedConfig( editor.config.get( 'htmlSupport.allow' ) || [] );
|
|
60
|
+
dataFilter.loadDisallowedConfig( editor.config.get( 'htmlSupport.disallow' ) || [] );
|
|
61
|
+
}
|
|
48
62
|
}
|
|
49
63
|
|
|
50
64
|
/**
|
|
@@ -0,0 +1,250 @@
|
|
|
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/htmlcomment
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Plugin } from 'ckeditor5/src/core';
|
|
11
|
+
import { uid } from 'ckeditor5/src/utils';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The HTML comment feature. It preserves the HTML comments (`<!-- -->`) in the editor data.
|
|
15
|
+
*
|
|
16
|
+
* For a detailed overview, check the {@glink features/general-html-support#html-comments HTML comment feature documentation}.
|
|
17
|
+
*
|
|
18
|
+
* @extends module:core/plugin~Plugin
|
|
19
|
+
*/
|
|
20
|
+
export default class HtmlComment extends Plugin {
|
|
21
|
+
/**
|
|
22
|
+
* @inheritDoc
|
|
23
|
+
*/
|
|
24
|
+
static get pluginName() {
|
|
25
|
+
return 'HtmlComment';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @inheritDoc
|
|
30
|
+
*/
|
|
31
|
+
init() {
|
|
32
|
+
const editor = this.editor;
|
|
33
|
+
|
|
34
|
+
// Allow storing comment's content as the $root attribute with the name `$comment:<unique id>`.
|
|
35
|
+
editor.model.schema.addAttributeCheck( ( context, attributeName ) => {
|
|
36
|
+
if ( context.endsWith( '$root' ) && attributeName.startsWith( '$comment' ) ) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
} );
|
|
40
|
+
|
|
41
|
+
// Convert the `$comment` view element to `$comment:<unique id>` marker and store its content (the comment itself) as a $root
|
|
42
|
+
// attribute. The comment content is needed in the `dataDowncast` pipeline to re-create the comment node.
|
|
43
|
+
editor.conversion.for( 'upcast' ).elementToMarker( {
|
|
44
|
+
view: '$comment',
|
|
45
|
+
model: ( viewElement, { writer } ) => {
|
|
46
|
+
const root = this.editor.model.document.getRoot();
|
|
47
|
+
const commentContent = viewElement.getCustomProperty( '$rawContent' );
|
|
48
|
+
const markerName = `$comment:${ uid() }`;
|
|
49
|
+
|
|
50
|
+
writer.setAttribute( markerName, commentContent, root );
|
|
51
|
+
|
|
52
|
+
return markerName;
|
|
53
|
+
}
|
|
54
|
+
} );
|
|
55
|
+
|
|
56
|
+
// Convert the `$comment` marker to `$comment` UI element with `$rawContent` custom property containing the comment content.
|
|
57
|
+
editor.conversion.for( 'dataDowncast' ).markerToElement( {
|
|
58
|
+
model: '$comment',
|
|
59
|
+
view: ( modelElement, { writer } ) => {
|
|
60
|
+
const root = this.editor.model.document.getRoot();
|
|
61
|
+
const markerName = modelElement.markerName;
|
|
62
|
+
const commentContent = root.getAttribute( markerName );
|
|
63
|
+
const comment = writer.createUIElement( '$comment' );
|
|
64
|
+
|
|
65
|
+
writer.setCustomProperty( '$rawContent', commentContent, comment );
|
|
66
|
+
|
|
67
|
+
return comment;
|
|
68
|
+
}
|
|
69
|
+
} );
|
|
70
|
+
|
|
71
|
+
// Remove comments' markers and their corresponding $root attributes, which are no longer present.
|
|
72
|
+
editor.model.document.registerPostFixer( writer => {
|
|
73
|
+
const root = editor.model.document.getRoot();
|
|
74
|
+
|
|
75
|
+
const changedMarkers = editor.model.document.differ.getChangedMarkers();
|
|
76
|
+
|
|
77
|
+
const changedCommentMarkers = changedMarkers.filter( marker => {
|
|
78
|
+
return marker.name.startsWith( '$comment' );
|
|
79
|
+
} );
|
|
80
|
+
|
|
81
|
+
const removedCommentMarkers = changedCommentMarkers.filter( marker => {
|
|
82
|
+
const newRange = marker.data.newRange;
|
|
83
|
+
|
|
84
|
+
return newRange && newRange.root.rootName === '$graveyard';
|
|
85
|
+
} );
|
|
86
|
+
|
|
87
|
+
if ( removedCommentMarkers.length === 0 ) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
for ( const marker of removedCommentMarkers ) {
|
|
92
|
+
writer.removeMarker( marker.name );
|
|
93
|
+
writer.removeAttribute( marker.name, root );
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return true;
|
|
97
|
+
} );
|
|
98
|
+
|
|
99
|
+
// Delete all comment markers from the document before setting new data.
|
|
100
|
+
editor.data.on( 'set', () => {
|
|
101
|
+
for ( const commentMarker of editor.model.markers.getMarkersGroup( '$comment' ) ) {
|
|
102
|
+
this.removeHtmlComment( commentMarker.name );
|
|
103
|
+
}
|
|
104
|
+
}, { priority: 'high' } );
|
|
105
|
+
|
|
106
|
+
// Delete all comment markers that are within a removed range.
|
|
107
|
+
// Delete all comment markers at the limit element boundaries if the whole content of the limit element is removed.
|
|
108
|
+
editor.model.on( 'deleteContent', ( evt, [ selection ] ) => {
|
|
109
|
+
for ( const range of selection.getRanges() ) {
|
|
110
|
+
const limitElement = editor.model.schema.getLimitElement( range );
|
|
111
|
+
const firstPosition = editor.model.createPositionAt( limitElement, 0 );
|
|
112
|
+
const lastPosition = editor.model.createPositionAt( limitElement, 'end' );
|
|
113
|
+
|
|
114
|
+
let affectedCommentIDs;
|
|
115
|
+
|
|
116
|
+
if ( firstPosition.isTouching( range.start ) && lastPosition.isTouching( range.end ) ) {
|
|
117
|
+
affectedCommentIDs = this.getHtmlCommentsInRange( editor.model.createRange( firstPosition, lastPosition ) );
|
|
118
|
+
} else {
|
|
119
|
+
affectedCommentIDs = this.getHtmlCommentsInRange( range, { skipBoundaries: true } );
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
for ( const commentMarkerID of affectedCommentIDs ) {
|
|
123
|
+
this.removeHtmlComment( commentMarkerID );
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}, { priority: 'high' } );
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Creates an HTML comment on the specified position and returns its ID.
|
|
131
|
+
*
|
|
132
|
+
* *Note*: If two comments are created at the same position, the second comment will be inserted before the first one.
|
|
133
|
+
*
|
|
134
|
+
* @param {module:engine/model/position~Position} position
|
|
135
|
+
* @param {String} content
|
|
136
|
+
* @returns {String} Comment ID. This ID can be later used to e.g. remove the comment from the content.
|
|
137
|
+
*/
|
|
138
|
+
createHtmlComment( position, content ) {
|
|
139
|
+
const id = uid();
|
|
140
|
+
const editor = this.editor;
|
|
141
|
+
const model = editor.model;
|
|
142
|
+
const root = model.document.getRoot();
|
|
143
|
+
const markerName = `$comment:${ id }`;
|
|
144
|
+
|
|
145
|
+
return model.change( writer => {
|
|
146
|
+
const range = writer.createRange( position );
|
|
147
|
+
|
|
148
|
+
writer.addMarker( markerName, {
|
|
149
|
+
usingOperation: true,
|
|
150
|
+
affectsData: true,
|
|
151
|
+
range
|
|
152
|
+
} );
|
|
153
|
+
|
|
154
|
+
writer.setAttribute( markerName, content, root );
|
|
155
|
+
|
|
156
|
+
return markerName;
|
|
157
|
+
} );
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Removes an HTML comment with the given comment ID.
|
|
162
|
+
*
|
|
163
|
+
* It does nothing and returns `false` if the comment with the given ID does not exist.
|
|
164
|
+
* Otherwise it removes the comment and returns `true`.
|
|
165
|
+
*
|
|
166
|
+
* Note that a comment can be removed also by removing the content around the comment.
|
|
167
|
+
*
|
|
168
|
+
* @param {String} commentID The ID of the comment to be removed.
|
|
169
|
+
* @returns {Boolean} `true` when the comment with the given ID was removed, `false` otherwise.
|
|
170
|
+
*/
|
|
171
|
+
removeHtmlComment( commentID ) {
|
|
172
|
+
const editor = this.editor;
|
|
173
|
+
const root = editor.model.document.getRoot();
|
|
174
|
+
|
|
175
|
+
const marker = editor.model.markers.get( commentID );
|
|
176
|
+
|
|
177
|
+
if ( !marker ) {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
editor.model.change( writer => {
|
|
182
|
+
writer.removeMarker( marker );
|
|
183
|
+
writer.removeAttribute( commentID, root );
|
|
184
|
+
} );
|
|
185
|
+
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Gets the HTML comment data for the comment with a given ID.
|
|
191
|
+
*
|
|
192
|
+
* Returns `null` if the comment does not exist.
|
|
193
|
+
*
|
|
194
|
+
* @param {String} commentID
|
|
195
|
+
* @returns {module:html-support/htmlcomment~HtmlCommentData}
|
|
196
|
+
*/
|
|
197
|
+
getHtmlCommentData( commentID ) {
|
|
198
|
+
const editor = this.editor;
|
|
199
|
+
const marker = editor.model.markers.get( commentID );
|
|
200
|
+
const root = editor.model.document.getRoot();
|
|
201
|
+
|
|
202
|
+
if ( !marker ) {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
content: root.getAttribute( commentID ),
|
|
208
|
+
position: marker.getStart()
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Gets all HTML comments in the given range.
|
|
214
|
+
*
|
|
215
|
+
* By default it includes comments at the range boundaries.
|
|
216
|
+
*
|
|
217
|
+
* @param {module:engine/model/range~Range} range
|
|
218
|
+
* @param {Object} [options]
|
|
219
|
+
* @param {Boolean} [options.skipBoundaries=false] When set to `true` the range boundaries will be skipped.
|
|
220
|
+
* @returns {Array.<String>} HTML comment IDs
|
|
221
|
+
*/
|
|
222
|
+
getHtmlCommentsInRange( range, { skipBoundaries = false } = {} ) {
|
|
223
|
+
const includeBoundaries = !skipBoundaries;
|
|
224
|
+
|
|
225
|
+
// Unfortunately, MarkerCollection#getMarkersAtPosition() filters out collapsed markers.
|
|
226
|
+
return Array.from( this.editor.model.markers.getMarkersGroup( '$comment' ) )
|
|
227
|
+
.filter( marker => isCommentMarkerInRange( marker, range ) )
|
|
228
|
+
.map( marker => marker.name );
|
|
229
|
+
|
|
230
|
+
function isCommentMarkerInRange( commentMarker, range ) {
|
|
231
|
+
const position = commentMarker.getRange().start;
|
|
232
|
+
|
|
233
|
+
return (
|
|
234
|
+
( position.isAfter( range.start ) || ( includeBoundaries && position.isEqual( range.start ) ) ) &&
|
|
235
|
+
( position.isBefore( range.end ) || ( includeBoundaries && position.isEqual( range.end ) ) )
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* An interface for the HTML comments data.
|
|
243
|
+
*
|
|
244
|
+
* It consists of the {@link module:engine/model/position~Position `position`} and `content`.
|
|
245
|
+
*
|
|
246
|
+
* @typedef {Object} module:html-support/htmlcomment~HtmlCommentData
|
|
247
|
+
*
|
|
248
|
+
* @property {module:engine/model/position~Position} position
|
|
249
|
+
* @property {String} content
|
|
250
|
+
*/
|
package/src/index.js
CHANGED
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { Plugin } from 'ckeditor5/src/core';
|
|
11
|
-
import { disallowedAttributesConverter } from '../converters';
|
|
12
11
|
import { setViewAttributes } from '../conversionutils.js';
|
|
13
12
|
|
|
14
13
|
import DataFilter from '../datafilter';
|
|
@@ -18,11 +17,17 @@ import DataFilter from '../datafilter';
|
|
|
18
17
|
*
|
|
19
18
|
* @extends module:core/plugin~Plugin
|
|
20
19
|
*/
|
|
21
|
-
export default class
|
|
20
|
+
export default class CodeBlockElementSupport extends Plugin {
|
|
21
|
+
/**
|
|
22
|
+
* @inheritDoc
|
|
23
|
+
*/
|
|
22
24
|
static get requires() {
|
|
23
25
|
return [ DataFilter ];
|
|
24
26
|
}
|
|
25
27
|
|
|
28
|
+
/**
|
|
29
|
+
* @inheritDoc
|
|
30
|
+
*/
|
|
26
31
|
init() {
|
|
27
32
|
if ( !this.editor.plugins.has( 'CodeBlockEditing' ) ) {
|
|
28
33
|
return;
|
|
@@ -44,7 +49,6 @@ export default class CodeBlockHtmlSupport extends Plugin {
|
|
|
44
49
|
allowAttributes: [ 'htmlAttributes', 'htmlContentAttributes' ]
|
|
45
50
|
} );
|
|
46
51
|
|
|
47
|
-
conversion.for( 'upcast' ).add( disallowedAttributesConverter( definition, dataFilter ) );
|
|
48
52
|
conversion.for( 'upcast' ).add( viewToModelCodeBlockAttributeConverter( dataFilter ) );
|
|
49
53
|
conversion.for( 'downcast' ).add( modelToViewCodeBlockAttributeConverter() );
|
|
50
54
|
|
|
@@ -0,0 +1,138 @@
|
|
|
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/dualcontent
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Plugin } from 'ckeditor5/src/core';
|
|
11
|
+
import { priorities } from 'ckeditor5/src/utils';
|
|
12
|
+
import {
|
|
13
|
+
modelToViewBlockAttributeConverter,
|
|
14
|
+
viewToModelBlockAttributeConverter
|
|
15
|
+
} from '../converters';
|
|
16
|
+
|
|
17
|
+
import DataFilter from '../datafilter';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Provides the General HTML Support integration for elements which can behave like sectioning element (e.g. article) or
|
|
21
|
+
* element accepting only inline content (e.g. paragraph).
|
|
22
|
+
*
|
|
23
|
+
* The distinction between this two content models is important for choosing correct schema model and proper content conversion.
|
|
24
|
+
* As an example, it ensures that:
|
|
25
|
+
*
|
|
26
|
+
* * children elements paragraphing is enabled for sectioning elements only,
|
|
27
|
+
* * element and its content can be correctly handled by editing view (splitting and merging elements),
|
|
28
|
+
* * model element HTML is semantically correct and easier to work with.
|
|
29
|
+
*
|
|
30
|
+
* If element contains any block element, it will be treated as a sectioning element and registered using
|
|
31
|
+
* {@link module:html-support/dataschema~DataSchemaDefinition#model} and
|
|
32
|
+
* {@link module:html-support/dataschema~DataSchemaDefinition#modelSchema} in editor schema.
|
|
33
|
+
* Otherwise, it will be registered under {@link module:html-support/dataschema~DataSchemaBlockElementDefinition#paragraphLikeModel} model
|
|
34
|
+
* name with model schema accepting only inline content (inheriting from `$block`).
|
|
35
|
+
*
|
|
36
|
+
* @extends module:core/plugin~Plugin
|
|
37
|
+
*/
|
|
38
|
+
export default class DualContentModelElementSupport extends Plugin {
|
|
39
|
+
/**
|
|
40
|
+
* @inheritDoc
|
|
41
|
+
*/
|
|
42
|
+
static get requires() {
|
|
43
|
+
return [ DataFilter ];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @inheritDoc
|
|
48
|
+
*/
|
|
49
|
+
init() {
|
|
50
|
+
const dataFilter = this.editor.plugins.get( DataFilter );
|
|
51
|
+
|
|
52
|
+
dataFilter.on( 'register', ( evt, definition ) => {
|
|
53
|
+
const editor = this.editor;
|
|
54
|
+
const schema = editor.model.schema;
|
|
55
|
+
const conversion = editor.conversion;
|
|
56
|
+
|
|
57
|
+
if ( !definition.paragraphLikeModel ) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Can only apply to newly registered features.
|
|
62
|
+
if ( schema.isRegistered( definition.model ) || schema.isRegistered( definition.paragraphLikeModel ) ) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const paragraphLikeModelDefinition = {
|
|
67
|
+
model: definition.paragraphLikeModel,
|
|
68
|
+
view: definition.view
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
schema.register( definition.model, definition.modelSchema );
|
|
72
|
+
schema.register( paragraphLikeModelDefinition.model, {
|
|
73
|
+
inheritAllFrom: '$block'
|
|
74
|
+
} );
|
|
75
|
+
|
|
76
|
+
conversion.for( 'upcast' ).elementToElement( {
|
|
77
|
+
view: definition.view,
|
|
78
|
+
model: ( viewElement, { writer } ) => {
|
|
79
|
+
if ( this._hasBlockContent( viewElement ) ) {
|
|
80
|
+
return writer.createElement( definition.model );
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return writer.createElement( paragraphLikeModelDefinition.model );
|
|
84
|
+
},
|
|
85
|
+
// With a `low` priority, `paragraph` plugin auto-paragraphing mechanism is executed. Make sure
|
|
86
|
+
// this listener is called before it. If not, some elements will be transformed into a paragraph.
|
|
87
|
+
converterPriority: priorities.get( 'low' ) + 1
|
|
88
|
+
} );
|
|
89
|
+
|
|
90
|
+
conversion.for( 'downcast' ).elementToElement( {
|
|
91
|
+
view: definition.view,
|
|
92
|
+
model: definition.model
|
|
93
|
+
} );
|
|
94
|
+
this._addAttributeConversion( definition );
|
|
95
|
+
|
|
96
|
+
conversion.for( 'downcast' ).elementToElement( {
|
|
97
|
+
view: paragraphLikeModelDefinition.view,
|
|
98
|
+
model: paragraphLikeModelDefinition.model
|
|
99
|
+
} );
|
|
100
|
+
this._addAttributeConversion( paragraphLikeModelDefinition );
|
|
101
|
+
|
|
102
|
+
evt.stop();
|
|
103
|
+
} );
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Checks whether the given view element includes any other block element.
|
|
108
|
+
*
|
|
109
|
+
* @private
|
|
110
|
+
* @param {module:engine/view/element~Element} viewElement
|
|
111
|
+
* @returns {Boolean}
|
|
112
|
+
*/
|
|
113
|
+
_hasBlockContent( viewElement ) {
|
|
114
|
+
const blockElements = this.editor.editing.view.domConverter.blockElements;
|
|
115
|
+
|
|
116
|
+
return Array.from( viewElement.getChildren() )
|
|
117
|
+
.some( node => blockElements.includes( node.name ) );
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Adds attribute filtering conversion for the given data schema.
|
|
122
|
+
*
|
|
123
|
+
* @private
|
|
124
|
+
* @param {module:html-support/dataschema~DataSchemaBlockElementDefinition} definition
|
|
125
|
+
*/
|
|
126
|
+
_addAttributeConversion( definition ) {
|
|
127
|
+
const editor = this.editor;
|
|
128
|
+
const conversion = editor.conversion;
|
|
129
|
+
const dataFilter = editor.plugins.get( DataFilter );
|
|
130
|
+
|
|
131
|
+
editor.model.schema.extend( definition.model, {
|
|
132
|
+
allowAttributes: 'htmlAttributes'
|
|
133
|
+
} );
|
|
134
|
+
|
|
135
|
+
conversion.for( 'upcast' ).add( viewToModelBlockAttributeConverter( definition, dataFilter ) );
|
|
136
|
+
conversion.for( 'downcast' ).add( modelToViewBlockAttributeConverter( definition ) );
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
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/heading
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Plugin } from 'ckeditor5/src/core';
|
|
11
|
+
|
|
12
|
+
import DataSchema from '../dataschema';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Provides the General HTML Support integration with {@link module:heading/heading~Heading Heading} feature.
|
|
16
|
+
*
|
|
17
|
+
* @extends module:core/plugin~Plugin
|
|
18
|
+
*/
|
|
19
|
+
export default class HeadingElementSupport extends Plugin {
|
|
20
|
+
/**
|
|
21
|
+
* @inheritDoc
|
|
22
|
+
*/
|
|
23
|
+
static get requires() {
|
|
24
|
+
return [ DataSchema ];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @inheritDoc
|
|
29
|
+
*/
|
|
30
|
+
init() {
|
|
31
|
+
const editor = this.editor;
|
|
32
|
+
|
|
33
|
+
if ( !editor.plugins.has( 'HeadingEditing' ) ) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const dataSchema = editor.plugins.get( DataSchema );
|
|
38
|
+
const options = editor.config.get( 'heading.options' );
|
|
39
|
+
const headerModels = [];
|
|
40
|
+
|
|
41
|
+
// We are registering all elements supported by HeadingEditing
|
|
42
|
+
// to enable custom attributes for those elements.
|
|
43
|
+
for ( const option of options ) {
|
|
44
|
+
if ( 'model' in option && 'view' in option ) {
|
|
45
|
+
dataSchema.registerBlockElement( {
|
|
46
|
+
view: option.view,
|
|
47
|
+
model: option.model
|
|
48
|
+
} );
|
|
49
|
+
|
|
50
|
+
headerModels.push( option.model );
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
dataSchema.extendBlockElement( {
|
|
55
|
+
model: 'htmlHgroup',
|
|
56
|
+
modelSchema: {
|
|
57
|
+
allowChildren: headerModels
|
|
58
|
+
}
|
|
59
|
+
} );
|
|
60
|
+
}
|
|
61
|
+
}
|