@ckeditor/ckeditor5-engine 29.2.0 → 32.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/LICENSE.md +2 -2
- package/package.json +25 -23
- package/src/controller/datacontroller.js +60 -12
- package/src/controller/editingcontroller.js +1 -1
- package/src/conversion/conversion.js +1 -1
- package/src/conversion/conversionhelpers.js +1 -1
- package/src/conversion/downcastdispatcher.js +1 -1
- package/src/conversion/downcasthelpers.js +1 -1
- package/src/conversion/mapper.js +1 -1
- package/src/conversion/modelconsumable.js +1 -1
- package/src/conversion/upcastdispatcher.js +1 -1
- package/src/conversion/upcasthelpers.js +1 -1
- package/src/conversion/viewconsumable.js +1 -1
- package/src/dataprocessor/basichtmlwriter.js +1 -1
- package/src/dataprocessor/dataprocessor.jsdoc +1 -1
- package/src/dataprocessor/htmldataprocessor.js +18 -43
- package/src/dataprocessor/htmlwriter.js +1 -1
- package/src/dataprocessor/xmldataprocessor.js +13 -17
- package/src/dev-utils/model.js +3 -3
- package/src/dev-utils/operationreplayer.js +1 -1
- package/src/dev-utils/utils.js +1 -1
- package/src/dev-utils/view.js +22 -4
- package/src/index.js +1 -1
- package/src/model/batch.js +77 -10
- package/src/model/differ.js +1 -1
- package/src/model/document.js +1 -1
- package/src/model/documentfragment.js +1 -1
- package/src/model/documentselection.js +1 -1
- package/src/model/element.js +1 -1
- package/src/model/history.js +1 -1
- package/src/model/item.jsdoc +1 -1
- package/src/model/liveposition.js +1 -1
- package/src/model/liverange.js +1 -1
- package/src/model/markercollection.js +6 -5
- package/src/model/model.js +17 -9
- package/src/model/node.js +1 -1
- package/src/model/nodelist.js +1 -1
- package/src/model/operation/attributeoperation.js +1 -1
- package/src/model/operation/detachoperation.js +1 -1
- package/src/model/operation/insertoperation.js +1 -1
- package/src/model/operation/markeroperation.js +1 -1
- package/src/model/operation/mergeoperation.js +1 -1
- package/src/model/operation/moveoperation.js +1 -1
- package/src/model/operation/nooperation.js +1 -1
- package/src/model/operation/operation.js +1 -1
- package/src/model/operation/operationfactory.js +1 -1
- package/src/model/operation/renameoperation.js +1 -1
- package/src/model/operation/rootattributeoperation.js +1 -1
- package/src/model/operation/splitoperation.js +1 -1
- package/src/model/operation/transform.js +1 -1
- package/src/model/operation/utils.js +1 -1
- package/src/model/position.js +1 -1
- package/src/model/range.js +2 -2
- package/src/model/rootelement.js +1 -1
- package/src/model/schema.js +1 -1
- package/src/model/selection.js +1 -1
- package/src/model/text.js +1 -1
- package/src/model/textproxy.js +1 -1
- package/src/model/treewalker.js +1 -1
- package/src/model/utils/autoparagraphing.js +1 -1
- package/src/model/utils/deletecontent.js +1 -1
- package/src/model/utils/getselectedcontent.js +1 -1
- package/src/model/utils/insertcontent.js +1 -1
- package/src/model/utils/modifyselection.js +1 -1
- package/src/model/utils/selection-post-fixer.js +44 -29
- package/src/model/writer.js +1 -1
- package/src/view/attributeelement.js +1 -1
- package/src/view/containerelement.js +1 -1
- package/src/view/document.js +13 -1
- package/src/view/documentfragment.js +1 -1
- package/src/view/documentselection.js +1 -1
- package/src/view/domconverter.js +268 -24
- package/src/view/downcastwriter.js +33 -2
- package/src/view/editableelement.js +1 -1
- package/src/view/element.js +29 -1
- package/src/view/elementdefinition.jsdoc +1 -1
- package/src/view/emptyelement.js +1 -1
- package/src/view/filler.js +1 -1
- package/src/view/item.jsdoc +1 -1
- package/src/view/matcher.js +22 -17
- package/src/view/node.js +1 -1
- package/src/view/observer/arrowkeysobserver.js +1 -1
- package/src/view/observer/bubblingemittermixin.js +1 -1
- package/src/view/observer/bubblingeventinfo.js +1 -1
- package/src/view/observer/clickobserver.js +1 -1
- package/src/view/observer/compositionobserver.js +1 -1
- package/src/view/observer/domeventdata.js +1 -1
- package/src/view/observer/domeventobserver.js +1 -1
- package/src/view/observer/fakeselectionobserver.js +1 -1
- package/src/view/observer/focusobserver.js +1 -1
- package/src/view/observer/inputobserver.js +1 -1
- package/src/view/observer/keyobserver.js +1 -1
- package/src/view/observer/mouseobserver.js +1 -1
- package/src/view/observer/mutationobserver.js +1 -1
- package/src/view/observer/observer.js +1 -1
- package/src/view/observer/selectionobserver.js +49 -2
- package/src/view/placeholder.js +1 -1
- package/src/view/position.js +1 -1
- package/src/view/range.js +1 -1
- package/src/view/rawelement.js +4 -3
- package/src/view/renderer.js +94 -40
- package/src/view/rooteditableelement.js +1 -1
- package/src/view/selection.js +1 -1
- package/src/view/styles/background.js +1 -1
- package/src/view/styles/border.js +1 -1
- package/src/view/styles/margin.js +1 -1
- package/src/view/styles/padding.js +1 -1
- package/src/view/styles/utils.js +6 -1
- package/src/view/stylesmap.js +1 -1
- package/src/view/text.js +1 -1
- package/src/view/textproxy.js +1 -1
- package/src/view/treewalker.js +1 -1
- package/src/view/uielement.js +6 -3
- package/src/view/upcastwriter.js +1 -1
- package/src/view/view.js +2 -2
- package/theme/placeholder.css +1 -1
- package/theme/renderer.css +9 -0
- package/CHANGELOG.md +0 -823
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @license Copyright (c) 2003-
|
|
2
|
+
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
3
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
4
4
|
*/
|
|
5
5
|
|
|
@@ -128,9 +128,17 @@ function tryFixingCollapsedRange( range, schema ) {
|
|
|
128
128
|
|
|
129
129
|
const nearestSelectionRange = schema.getNearestSelectionRange( originalPosition );
|
|
130
130
|
|
|
131
|
-
// This might be null
|
|
132
|
-
//
|
|
131
|
+
// This might be null, i.e. when the editor data is empty or the selection is inside a limit element
|
|
132
|
+
// that doesn't allow text inside.
|
|
133
|
+
// In the first case, there is no need to fix the selection range.
|
|
134
|
+
// In the second, let's go up to the outer selectable element
|
|
133
135
|
if ( !nearestSelectionRange ) {
|
|
136
|
+
const ancestorObject = originalPosition.getAncestors().reverse().find( item => schema.isObject( item ) );
|
|
137
|
+
|
|
138
|
+
if ( ancestorObject ) {
|
|
139
|
+
return Range._createOn( ancestorObject );
|
|
140
|
+
}
|
|
141
|
+
|
|
134
142
|
return null;
|
|
135
143
|
}
|
|
136
144
|
|
|
@@ -255,35 +263,42 @@ function checkSelectionOnNonLimitElements( start, end, schema ) {
|
|
|
255
263
|
return startIsOnBlock || endIsOnBlock;
|
|
256
264
|
}
|
|
257
265
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
266
|
+
/**
|
|
267
|
+
* Returns a minimal non-intersecting array of ranges without duplicates.
|
|
268
|
+
*
|
|
269
|
+
* @param {Array.<module:engine/model/range~Range>} Ranges to merge.
|
|
270
|
+
* @returns {Array.<module:engine/model/range~Range>} Array of unique and nonIntersecting ranges.
|
|
271
|
+
*/
|
|
272
|
+
export function mergeIntersectingRanges( ranges ) {
|
|
273
|
+
const rangesToMerge = [ ...ranges ];
|
|
274
|
+
const rangeIndexesToRemove = new Set();
|
|
275
|
+
let currentRangeIndex = 1;
|
|
276
|
+
|
|
277
|
+
while ( currentRangeIndex < rangesToMerge.length ) {
|
|
278
|
+
const currentRange = rangesToMerge[ currentRangeIndex ];
|
|
279
|
+
const previousRanges = rangesToMerge.slice( 0, currentRangeIndex );
|
|
280
|
+
|
|
281
|
+
for ( const [ previousRangeIndex, previousRange ] of previousRanges.entries() ) {
|
|
282
|
+
if ( rangeIndexesToRemove.has( previousRangeIndex ) ) {
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if ( currentRange.isEqual( previousRange ) ) {
|
|
287
|
+
rangeIndexesToRemove.add( previousRangeIndex );
|
|
288
|
+
} else if ( currentRange.isIntersecting( previousRange ) ) {
|
|
289
|
+
rangeIndexesToRemove.add( previousRangeIndex );
|
|
290
|
+
rangeIndexesToRemove.add( currentRangeIndex );
|
|
291
|
+
|
|
292
|
+
const mergedRange = currentRange.getJoined( previousRange );
|
|
293
|
+
rangesToMerge.push( mergedRange );
|
|
294
|
+
}
|
|
284
295
|
}
|
|
296
|
+
|
|
297
|
+
currentRangeIndex++;
|
|
285
298
|
}
|
|
286
299
|
|
|
300
|
+
const nonIntersectingRanges = rangesToMerge.filter( ( _, index ) => !rangeIndexesToRemove.has( index ) );
|
|
301
|
+
|
|
287
302
|
return nonIntersectingRanges;
|
|
288
303
|
}
|
|
289
304
|
|
package/src/model/writer.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @license Copyright (c) 2003-
|
|
2
|
+
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
3
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
4
4
|
*/
|
|
5
5
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @license Copyright (c) 2003-
|
|
2
|
+
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
3
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
4
4
|
*/
|
|
5
5
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @license Copyright (c) 2003-
|
|
2
|
+
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
3
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
4
4
|
*/
|
|
5
5
|
|
package/src/view/document.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @license Copyright (c) 2003-
|
|
2
|
+
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
3
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
4
4
|
*/
|
|
5
5
|
|
|
@@ -80,6 +80,18 @@ export default class Document {
|
|
|
80
80
|
*/
|
|
81
81
|
this.set( 'isFocused', false );
|
|
82
82
|
|
|
83
|
+
/**
|
|
84
|
+
* `true` while the user is making a selection in the document (e.g. holding the mouse button and moving the cursor).
|
|
85
|
+
* When they stop selecting, the property goes back to `false`.
|
|
86
|
+
*
|
|
87
|
+
* This property is updated by the {@link module:engine/view/observer/selectionobserver~SelectionObserver}.
|
|
88
|
+
*
|
|
89
|
+
* @readonly
|
|
90
|
+
* @observable
|
|
91
|
+
* @member {Boolean} module:engine/view/document~Document#isSelecting
|
|
92
|
+
*/
|
|
93
|
+
this.set( 'isSelecting', false );
|
|
94
|
+
|
|
83
95
|
/**
|
|
84
96
|
* True if composition is in progress inside the document.
|
|
85
97
|
*
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @license Copyright (c) 2003-
|
|
2
|
+
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
3
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
4
4
|
*/
|
|
5
5
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @license Copyright (c) 2003-
|
|
2
|
+
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
3
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
4
4
|
*/
|
|
5
5
|
|
package/src/view/domconverter.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @license Copyright (c) 2003-
|
|
2
|
+
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
3
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
4
4
|
*/
|
|
5
5
|
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* @module engine/view/domconverter
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
/* globals document, Node, Text */
|
|
10
|
+
/* globals document, Node, NodeFilter, DOMParser, Text */
|
|
11
11
|
|
|
12
12
|
import ViewText from './text';
|
|
13
13
|
import ViewElement from './element';
|
|
@@ -24,13 +24,17 @@ import {
|
|
|
24
24
|
} from './filler';
|
|
25
25
|
|
|
26
26
|
import global from '@ckeditor/ckeditor5-utils/src/dom/global';
|
|
27
|
+
import { logWarning } from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
|
|
27
28
|
import indexOf from '@ckeditor/ckeditor5-utils/src/dom/indexof';
|
|
28
29
|
import getAncestors from '@ckeditor/ckeditor5-utils/src/dom/getancestors';
|
|
29
30
|
import isText from '@ckeditor/ckeditor5-utils/src/dom/istext';
|
|
31
|
+
import isComment from '@ckeditor/ckeditor5-utils/src/dom/iscomment';
|
|
30
32
|
|
|
31
33
|
const BR_FILLER_REF = BR_FILLER( document ); // eslint-disable-line new-cap
|
|
32
34
|
const NBSP_FILLER_REF = NBSP_FILLER( document ); // eslint-disable-line new-cap
|
|
33
35
|
const MARKED_NBSP_FILLER_REF = MARKED_NBSP_FILLER( document ); // eslint-disable-line new-cap
|
|
36
|
+
const UNSAFE_ATTRIBUTE_NAME_PREFIX = 'data-ck-unsafe-attribute-';
|
|
37
|
+
const UNSAFE_ELEMENT_REPLACEMENT_ATTRIBUTE = 'data-ck-unsafe-element';
|
|
34
38
|
|
|
35
39
|
/**
|
|
36
40
|
* `DomConverter` is a set of tools to do transformations between DOM nodes and view nodes. It also handles
|
|
@@ -51,7 +55,12 @@ export default class DomConverter {
|
|
|
51
55
|
*
|
|
52
56
|
* @param {module:engine/view/document~Document} document The view document instance.
|
|
53
57
|
* @param {Object} options An object with configuration options.
|
|
54
|
-
* @param {module:engine/view/filler~BlockFillerMode} [options.blockFillerMode
|
|
58
|
+
* @param {module:engine/view/filler~BlockFillerMode} [options.blockFillerMode] The type of the block filler to use.
|
|
59
|
+
* Default value depends on the options.renderingMode:
|
|
60
|
+
* 'nbsp' when options.renderingMode == 'data',
|
|
61
|
+
* 'br' when options.renderingMode == 'editing'.
|
|
62
|
+
* @param {'data'|'editing'} [options.renderingMode='editing'] Whether to leave the View-to-DOM conversion result unchanged
|
|
63
|
+
* or improve editing experience by filtering out interactive data.
|
|
55
64
|
*/
|
|
56
65
|
constructor( document, options = {} ) {
|
|
57
66
|
/**
|
|
@@ -60,12 +69,19 @@ export default class DomConverter {
|
|
|
60
69
|
*/
|
|
61
70
|
this.document = document;
|
|
62
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Whether to leave the View-to-DOM conversion result unchanged or improve editing experience by filtering out interactive data.
|
|
74
|
+
*
|
|
75
|
+
* @member {'data'|'editing'} module:engine/view/domconverter~DomConverter#renderingMode
|
|
76
|
+
*/
|
|
77
|
+
this.renderingMode = options.renderingMode || 'editing';
|
|
78
|
+
|
|
63
79
|
/**
|
|
64
80
|
* The mode of a block filler used by the DOM converter.
|
|
65
81
|
*
|
|
66
82
|
* @member {'br'|'nbsp'|'markedNbsp'} module:engine/view/domconverter~DomConverter#blockFillerMode
|
|
67
83
|
*/
|
|
68
|
-
this.blockFillerMode = options.blockFillerMode || 'br';
|
|
84
|
+
this.blockFillerMode = options.blockFillerMode || ( this.renderingMode === 'editing' ? 'br' : 'nbsp' );
|
|
69
85
|
|
|
70
86
|
/**
|
|
71
87
|
* Elements which are considered pre-formatted elements.
|
|
@@ -221,6 +237,106 @@ export default class DomConverter {
|
|
|
221
237
|
this._viewToDomMapping.set( viewFragment, domFragment );
|
|
222
238
|
}
|
|
223
239
|
|
|
240
|
+
/**
|
|
241
|
+
* Decides whether a given pair of attribute key and value should be passed further down the pipeline.
|
|
242
|
+
*
|
|
243
|
+
* @param {String} attributeKey
|
|
244
|
+
* @param {String} attributeValue
|
|
245
|
+
* @param {String} elementName Element name in lower case.
|
|
246
|
+
* @returns {Boolean}
|
|
247
|
+
*/
|
|
248
|
+
shouldRenderAttribute( attributeKey, attributeValue, elementName ) {
|
|
249
|
+
if ( this.renderingMode === 'data' ) {
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
attributeKey = attributeKey.toLowerCase();
|
|
254
|
+
|
|
255
|
+
if ( attributeKey.startsWith( 'on' ) ) {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (
|
|
260
|
+
attributeKey === 'srcdoc' &&
|
|
261
|
+
attributeValue.match( /\bon\S+\s*=|javascript:|<\s*\/*script/i )
|
|
262
|
+
) {
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (
|
|
267
|
+
elementName === 'img' &&
|
|
268
|
+
( attributeKey === 'src' || attributeKey === 'srcset' )
|
|
269
|
+
) {
|
|
270
|
+
return true;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if ( elementName === 'source' && attributeKey === 'srcset' ) {
|
|
274
|
+
return true;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if ( attributeValue.match( /^\s*(javascript:|data:(image\/svg|text\/x?html))/i ) ) {
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Set `domElement`'s content using provided `html` argument. Apply necessary filtering for the editing pipeline.
|
|
286
|
+
*
|
|
287
|
+
* @param {Element} domElement DOM element that should have `html` set as its content.
|
|
288
|
+
* @param {String} html Textual representation of the HTML that will be set on `domElement`.
|
|
289
|
+
*/
|
|
290
|
+
setContentOf( domElement, html ) {
|
|
291
|
+
// For data pipeline we pass the HTML as-is.
|
|
292
|
+
if ( this.renderingMode === 'data' ) {
|
|
293
|
+
domElement.innerHTML = html;
|
|
294
|
+
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const document = new DOMParser().parseFromString( html, 'text/html' );
|
|
299
|
+
const fragment = document.createDocumentFragment();
|
|
300
|
+
const bodyChildNodes = document.body.childNodes;
|
|
301
|
+
|
|
302
|
+
while ( bodyChildNodes.length > 0 ) {
|
|
303
|
+
fragment.appendChild( bodyChildNodes[ 0 ] );
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const treeWalker = document.createTreeWalker( fragment, NodeFilter.SHOW_ELEMENT );
|
|
307
|
+
const nodes = [];
|
|
308
|
+
|
|
309
|
+
let currentNode;
|
|
310
|
+
|
|
311
|
+
// eslint-disable-next-line no-cond-assign
|
|
312
|
+
while ( currentNode = treeWalker.nextNode() ) {
|
|
313
|
+
nodes.push( currentNode );
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
for ( const currentNode of nodes ) {
|
|
317
|
+
// Go through nodes to remove those that are prohibited in editing pipeline.
|
|
318
|
+
for ( const attributeName of currentNode.getAttributeNames() ) {
|
|
319
|
+
this.setDomElementAttribute( currentNode, attributeName, currentNode.getAttribute( attributeName ) );
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const elementName = currentNode.tagName.toLowerCase();
|
|
323
|
+
|
|
324
|
+
// There are certain nodes, that should be renamed to <span> in editing pipeline.
|
|
325
|
+
if ( this._shouldRenameElement( elementName ) ) {
|
|
326
|
+
logWarning( 'domconverter-unsafe-element-detected', { unsafeElement: currentNode } );
|
|
327
|
+
|
|
328
|
+
currentNode.replaceWith( this._createReplacementDomElement( elementName, currentNode ) );
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Empty the target element.
|
|
333
|
+
while ( domElement.firstChild ) {
|
|
334
|
+
domElement.firstChild.remove();
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
domElement.append( fragment );
|
|
338
|
+
}
|
|
339
|
+
|
|
224
340
|
/**
|
|
225
341
|
* Converts the view to the DOM. For all text nodes, not bound elements and document fragments new items will
|
|
226
342
|
* be created. For bound elements and document fragments the method will return corresponding items.
|
|
@@ -257,7 +373,7 @@ export default class DomConverter {
|
|
|
257
373
|
domElement = domDocument.createComment( viewNode.getCustomProperty( '$rawContent' ) );
|
|
258
374
|
} else {
|
|
259
375
|
// UIElement has its own render() method (see #799).
|
|
260
|
-
domElement = viewNode.render( domDocument );
|
|
376
|
+
domElement = viewNode.render( domDocument, this );
|
|
261
377
|
}
|
|
262
378
|
|
|
263
379
|
if ( options.bind ) {
|
|
@@ -267,7 +383,11 @@ export default class DomConverter {
|
|
|
267
383
|
return domElement;
|
|
268
384
|
} else {
|
|
269
385
|
// Create DOM element.
|
|
270
|
-
if (
|
|
386
|
+
if ( this._shouldRenameElement( viewNode.name ) ) {
|
|
387
|
+
logWarning( 'domconverter-unsafe-element-detected', { unsafeElement: viewNode } );
|
|
388
|
+
|
|
389
|
+
domElement = this._createReplacementDomElement( viewNode.name );
|
|
390
|
+
} else if ( viewNode.hasAttribute( 'xmlns' ) ) {
|
|
271
391
|
domElement = domDocument.createElementNS( viewNode.getAttribute( 'xmlns' ), viewNode.name );
|
|
272
392
|
} else {
|
|
273
393
|
domElement = domDocument.createElement( viewNode.name );
|
|
@@ -276,7 +396,7 @@ export default class DomConverter {
|
|
|
276
396
|
// RawElement take care of their children in RawElement#render() method which can be customized
|
|
277
397
|
// (see https://github.com/ckeditor/ckeditor5/issues/4469).
|
|
278
398
|
if ( viewNode.is( 'rawElement' ) ) {
|
|
279
|
-
viewNode.render( domElement );
|
|
399
|
+
viewNode.render( domElement, this );
|
|
280
400
|
}
|
|
281
401
|
|
|
282
402
|
if ( options.bind ) {
|
|
@@ -285,7 +405,7 @@ export default class DomConverter {
|
|
|
285
405
|
|
|
286
406
|
// Copy element's attributes.
|
|
287
407
|
for ( const key of viewNode.getAttributeKeys() ) {
|
|
288
|
-
|
|
408
|
+
this.setDomElementAttribute( domElement, key, viewNode.getAttribute( key ), viewNode );
|
|
289
409
|
}
|
|
290
410
|
}
|
|
291
411
|
|
|
@@ -299,6 +419,60 @@ export default class DomConverter {
|
|
|
299
419
|
}
|
|
300
420
|
}
|
|
301
421
|
|
|
422
|
+
/**
|
|
423
|
+
* Sets the attribute on a DOM element.
|
|
424
|
+
*
|
|
425
|
+
* **Note**: To remove the attribute, use {@link #removeDomElementAttribute}.
|
|
426
|
+
*
|
|
427
|
+
* @param {HTMLElement} domElement The DOM element the attribute should be set on.
|
|
428
|
+
* @param {String} key The name of the attribute.
|
|
429
|
+
* @param {String} value The value of the attribute.
|
|
430
|
+
* @param {module:engine/view/element~Element} [relatedViewElement] The view element related to the `domElement` (if there is any).
|
|
431
|
+
* It helps decide whether the attribute set is unsafe. For instance, view elements created via
|
|
432
|
+
* {@link module:engine/view/downcastwriter~DowncastWriter} methods can allow certain attributes that would normally be filtered out.
|
|
433
|
+
*/
|
|
434
|
+
setDomElementAttribute( domElement, key, value, relatedViewElement = null ) {
|
|
435
|
+
const shouldRenderAttribute = this.shouldRenderAttribute( key, value, domElement.tagName.toLowerCase() ) ||
|
|
436
|
+
relatedViewElement && relatedViewElement.shouldRenderUnsafeAttribute( key );
|
|
437
|
+
|
|
438
|
+
if ( !shouldRenderAttribute ) {
|
|
439
|
+
logWarning( 'domconverter-unsafe-attribute-detected', { domElement, key, value } );
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// The old value was safe but the new value is unsafe.
|
|
443
|
+
if ( domElement.hasAttribute( key ) && !shouldRenderAttribute ) {
|
|
444
|
+
domElement.removeAttribute( key );
|
|
445
|
+
}
|
|
446
|
+
// The old value was unsafe (but prefixed) but the new value will be safe (will be unprefixed).
|
|
447
|
+
else if ( domElement.hasAttribute( UNSAFE_ATTRIBUTE_NAME_PREFIX + key ) && shouldRenderAttribute ) {
|
|
448
|
+
domElement.removeAttribute( UNSAFE_ATTRIBUTE_NAME_PREFIX + key );
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// If the attribute should not be rendered, rename it (instead of removing) to give developers some idea of what
|
|
452
|
+
// is going on (https://github.com/ckeditor/ckeditor5/issues/10801).
|
|
453
|
+
domElement.setAttribute( shouldRenderAttribute ? key : UNSAFE_ATTRIBUTE_NAME_PREFIX + key, value );
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Removes an attribute from a DOM element.
|
|
458
|
+
*
|
|
459
|
+
* **Note**: To set the attribute, use {@link #setDomElementAttribute}.
|
|
460
|
+
*
|
|
461
|
+
* @param {HTMLElement} domElement The DOM element the attribute should be removed from.
|
|
462
|
+
* @param {String} key The name of the attribute.
|
|
463
|
+
*/
|
|
464
|
+
removeDomElementAttribute( domElement, key ) {
|
|
465
|
+
// See #_createReplacementDomElement() to learn what this is.
|
|
466
|
+
if ( key == UNSAFE_ELEMENT_REPLACEMENT_ATTRIBUTE ) {
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
domElement.removeAttribute( key );
|
|
471
|
+
|
|
472
|
+
// See setDomElementAttribute() to learn what this is.
|
|
473
|
+
domElement.removeAttribute( UNSAFE_ATTRIBUTE_NAME_PREFIX + key );
|
|
474
|
+
}
|
|
475
|
+
|
|
302
476
|
/**
|
|
303
477
|
* Converts children of the view element to DOM using the
|
|
304
478
|
* {@link module:engine/view/domconverter~DomConverter#viewToDom} method.
|
|
@@ -444,7 +618,7 @@ export default class DomConverter {
|
|
|
444
618
|
return hostElement;
|
|
445
619
|
}
|
|
446
620
|
|
|
447
|
-
if (
|
|
621
|
+
if ( isComment( domNode ) && options.skipComments ) {
|
|
448
622
|
return null;
|
|
449
623
|
}
|
|
450
624
|
|
|
@@ -489,8 +663,8 @@ export default class DomConverter {
|
|
|
489
663
|
|
|
490
664
|
// Treat this element's content as a raw data if it was registered as such.
|
|
491
665
|
// Comment node is also treated as an element with raw data.
|
|
492
|
-
if ( this._isViewElementWithRawContent( viewElement, options ) ||
|
|
493
|
-
const rawContent =
|
|
666
|
+
if ( this._isViewElementWithRawContent( viewElement, options ) || isComment( domNode ) ) {
|
|
667
|
+
const rawContent = isComment( domNode ) ? domNode.data : domNode.innerHTML;
|
|
494
668
|
|
|
495
669
|
viewElement._setCustomProperty( '$rawContent', rawContent );
|
|
496
670
|
|
|
@@ -603,10 +777,10 @@ export default class DomConverter {
|
|
|
603
777
|
* If structures are too different and it is not possible to find corresponding position then `null` will be returned.
|
|
604
778
|
*
|
|
605
779
|
* @param {Node} domParent DOM position parent.
|
|
606
|
-
* @param {Number} domOffset DOM position offset.
|
|
780
|
+
* @param {Number} [domOffset=0] DOM position offset. You can skip it when converting the inline filler node.
|
|
607
781
|
* @returns {module:engine/view/position~Position} viewPosition View position.
|
|
608
782
|
*/
|
|
609
|
-
domPositionToView( domParent, domOffset ) {
|
|
783
|
+
domPositionToView( domParent, domOffset = 0 ) {
|
|
610
784
|
if ( this.isBlockFiller( domParent ) ) {
|
|
611
785
|
return this.domPositionToView( domParent.parentNode, indexOf( domParent ) );
|
|
612
786
|
}
|
|
@@ -859,16 +1033,6 @@ export default class DomConverter {
|
|
|
859
1033
|
return node && node.nodeType == Node.DOCUMENT_FRAGMENT_NODE;
|
|
860
1034
|
}
|
|
861
1035
|
|
|
862
|
-
/**
|
|
863
|
-
* Returns `true` when `node.nodeType` equals `Node.COMMENT_NODE`.
|
|
864
|
-
*
|
|
865
|
-
* @param {Node} node Node to check.
|
|
866
|
-
* @returns {Boolean}
|
|
867
|
-
*/
|
|
868
|
-
isComment( node ) {
|
|
869
|
-
return node && node.nodeType == Node.COMMENT_NODE;
|
|
870
|
-
}
|
|
871
|
-
|
|
872
1036
|
/**
|
|
873
1037
|
* Checks if the node is an instance of the block filler for this DOM converter.
|
|
874
1038
|
*
|
|
@@ -1353,7 +1517,7 @@ export default class DomConverter {
|
|
|
1353
1517
|
* @returns {Element}
|
|
1354
1518
|
*/
|
|
1355
1519
|
_createViewElement( node, options ) {
|
|
1356
|
-
if (
|
|
1520
|
+
if ( isComment( node ) ) {
|
|
1357
1521
|
return new ViewUIElement( this.document, '$comment' );
|
|
1358
1522
|
}
|
|
1359
1523
|
|
|
@@ -1373,6 +1537,45 @@ export default class DomConverter {
|
|
|
1373
1537
|
_isViewElementWithRawContent( viewElement, options ) {
|
|
1374
1538
|
return options.withChildren !== false && this._rawContentElementMatcher.match( viewElement );
|
|
1375
1539
|
}
|
|
1540
|
+
|
|
1541
|
+
/**
|
|
1542
|
+
* Checks whether a given element name should be renamed in a current rendering mode.
|
|
1543
|
+
*
|
|
1544
|
+
* @private
|
|
1545
|
+
* @param {String} elementName The name of view element.
|
|
1546
|
+
* @returns {Boolean}
|
|
1547
|
+
*/
|
|
1548
|
+
_shouldRenameElement( elementName ) {
|
|
1549
|
+
return this.renderingMode == 'editing' && elementName.toLowerCase() == 'script';
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
/**
|
|
1553
|
+
* Return a <span> element with a special attribute holding the name of the original element.
|
|
1554
|
+
* Optionally, copy all the attributes of the original element if that element is provided.
|
|
1555
|
+
*
|
|
1556
|
+
* @private
|
|
1557
|
+
* @param {String} elementName The name of view element.
|
|
1558
|
+
* @param {Element} [originalDomElement] The original DOM element to copy attributes and content from.
|
|
1559
|
+
* @returns {Element}
|
|
1560
|
+
*/
|
|
1561
|
+
_createReplacementDomElement( elementName, originalDomElement = null ) {
|
|
1562
|
+
const newDomElement = document.createElement( 'span' );
|
|
1563
|
+
|
|
1564
|
+
// Mark the span replacing a script as hidden.
|
|
1565
|
+
newDomElement.setAttribute( UNSAFE_ELEMENT_REPLACEMENT_ATTRIBUTE, elementName );
|
|
1566
|
+
|
|
1567
|
+
if ( originalDomElement ) {
|
|
1568
|
+
while ( originalDomElement.firstChild ) {
|
|
1569
|
+
newDomElement.appendChild( originalDomElement.firstChild );
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
for ( const attributeName of originalDomElement.getAttributeNames() ) {
|
|
1573
|
+
newDomElement.setAttribute( attributeName, originalDomElement.getAttribute( attributeName ) );
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
return newDomElement;
|
|
1578
|
+
}
|
|
1376
1579
|
}
|
|
1377
1580
|
|
|
1378
1581
|
// Helper function.
|
|
@@ -1435,3 +1638,44 @@ function hasBlockParent( domNode, blockElements ) {
|
|
|
1435
1638
|
*
|
|
1436
1639
|
* @typedef {String} module:engine/view/filler~BlockFillerMode
|
|
1437
1640
|
*/
|
|
1641
|
+
|
|
1642
|
+
/**
|
|
1643
|
+
* The {@link module:engine/view/domconverter~DomConverter} detected a `<script>` element that may disrupt the
|
|
1644
|
+
* {@glink framework/guides/architecture/editing-engine#editing-pipeline editing pipeline} of the editor. To avoid this,
|
|
1645
|
+
* the `<script>` element was renamed to `<span data-ck-unsafe-element="script"></span>`.
|
|
1646
|
+
*
|
|
1647
|
+
* @error domconverter-unsafe-element-detected
|
|
1648
|
+
* @param {module:engine/model/element~Element|HTMLElement} unsafeElement The editing view or DOM element
|
|
1649
|
+
* that was renamed.
|
|
1650
|
+
*/
|
|
1651
|
+
|
|
1652
|
+
/**
|
|
1653
|
+
* The {@link module:engine/view/domconverter~DomConverter} detected an interactive attribute in the
|
|
1654
|
+
* {@glink framework/guides/architecture/editing-engine#editing-pipeline editing pipeline}. For the best
|
|
1655
|
+
* editing experience, the attribute was renamed to `data-ck-unsafe-attribute-[original attribute name]`.
|
|
1656
|
+
*
|
|
1657
|
+
* If you are the author of the plugin that generated this attribute and you want it to be preserved
|
|
1658
|
+
* in the editing pipeline, you can configure this when creating the element
|
|
1659
|
+
* using {@link module:engine/view/downcastwriter~DowncastWriter} during the
|
|
1660
|
+
* {@glink framework/guides/architecture/editing-engine#conversion model–view conversion}. Methods such as
|
|
1661
|
+
* {@link module:engine/view/downcastwriter~DowncastWriter#createContainerElement},
|
|
1662
|
+
* {@link module:engine/view/downcastwriter~DowncastWriter#createAttributeElement}, or
|
|
1663
|
+
* {@link module:engine/view/downcastwriter~DowncastWriter#createEmptyElement}
|
|
1664
|
+
* accept an option that will disable filtering of specific attributes:
|
|
1665
|
+
*
|
|
1666
|
+
* const paragraph = writer.createContainerElement( 'p',
|
|
1667
|
+
* {
|
|
1668
|
+
* class: 'clickable-paragraph',
|
|
1669
|
+
* onclick: 'alert( "Paragraph clicked!" )'
|
|
1670
|
+
* },
|
|
1671
|
+
* {
|
|
1672
|
+
* // Make sure the "onclick" attribute will pass through.
|
|
1673
|
+
* renderUnsafeAttributes: [ 'onclick' ]
|
|
1674
|
+
* }
|
|
1675
|
+
* );
|
|
1676
|
+
*
|
|
1677
|
+
* @error domconverter-unsafe-attribute-detected
|
|
1678
|
+
* @param {HTMLElement} domElement The DOM element the attribute was set on.
|
|
1679
|
+
* @param {String} key The original name of the attribute
|
|
1680
|
+
* @param {String} value The value of the original attribute
|
|
1681
|
+
*/
|