@ckeditor/ckeditor5-engine 31.0.0 → 31.1.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/package.json +22 -22
- package/src/controller/datacontroller.js +6 -6
- package/src/dev-utils/view.js +6 -6
- package/src/model/utils/selection-post-fixer.js +36 -29
- package/src/view/domconverter.js +142 -32
- package/src/view/downcastwriter.js +32 -1
- package/src/view/element.js +28 -0
- package/src/view/matcher.js +14 -13
- package/src/view/observer/selectionobserver.js +2 -2
- package/src/view/renderer.js +4 -15
- package/theme/renderer.css +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ckeditor/ckeditor5-engine",
|
|
3
|
-
"version": "31.
|
|
3
|
+
"version": "31.1.0",
|
|
4
4
|
"description": "The editing engine of CKEditor 5 – the best browser-based rich text editor.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"wysiwyg",
|
|
@@ -23,30 +23,30 @@
|
|
|
23
23
|
],
|
|
24
24
|
"main": "src/index.js",
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@ckeditor/ckeditor5-utils": "^31.
|
|
26
|
+
"@ckeditor/ckeditor5-utils": "^31.1.0",
|
|
27
27
|
"lodash-es": "^4.17.15"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"@ckeditor/ckeditor5-basic-styles": "^31.
|
|
31
|
-
"@ckeditor/ckeditor5-block-quote": "^31.
|
|
32
|
-
"@ckeditor/ckeditor5-clipboard": "^31.
|
|
33
|
-
"@ckeditor/ckeditor5-cloud-services": "^31.
|
|
34
|
-
"@ckeditor/ckeditor5-core": "^31.
|
|
35
|
-
"@ckeditor/ckeditor5-editor-classic": "^31.
|
|
36
|
-
"@ckeditor/ckeditor5-enter": "^31.
|
|
37
|
-
"@ckeditor/ckeditor5-essentials": "^31.
|
|
38
|
-
"@ckeditor/ckeditor5-heading": "^31.
|
|
39
|
-
"@ckeditor/ckeditor5-image": "^31.
|
|
40
|
-
"@ckeditor/ckeditor5-link": "^31.
|
|
41
|
-
"@ckeditor/ckeditor5-list": "^31.
|
|
42
|
-
"@ckeditor/ckeditor5-mention": "^31.
|
|
43
|
-
"@ckeditor/ckeditor5-paragraph": "^31.
|
|
44
|
-
"@ckeditor/ckeditor5-table": "^31.
|
|
45
|
-
"@ckeditor/ckeditor5-theme-lark": "^31.
|
|
46
|
-
"@ckeditor/ckeditor5-typing": "^31.
|
|
47
|
-
"@ckeditor/ckeditor5-ui": "^31.
|
|
48
|
-
"@ckeditor/ckeditor5-undo": "^31.
|
|
49
|
-
"@ckeditor/ckeditor5-widget": "^31.
|
|
30
|
+
"@ckeditor/ckeditor5-basic-styles": "^31.1.0",
|
|
31
|
+
"@ckeditor/ckeditor5-block-quote": "^31.1.0",
|
|
32
|
+
"@ckeditor/ckeditor5-clipboard": "^31.1.0",
|
|
33
|
+
"@ckeditor/ckeditor5-cloud-services": "^31.1.0",
|
|
34
|
+
"@ckeditor/ckeditor5-core": "^31.1.0",
|
|
35
|
+
"@ckeditor/ckeditor5-editor-classic": "^31.1.0",
|
|
36
|
+
"@ckeditor/ckeditor5-enter": "^31.1.0",
|
|
37
|
+
"@ckeditor/ckeditor5-essentials": "^31.1.0",
|
|
38
|
+
"@ckeditor/ckeditor5-heading": "^31.1.0",
|
|
39
|
+
"@ckeditor/ckeditor5-image": "^31.1.0",
|
|
40
|
+
"@ckeditor/ckeditor5-link": "^31.1.0",
|
|
41
|
+
"@ckeditor/ckeditor5-list": "^31.1.0",
|
|
42
|
+
"@ckeditor/ckeditor5-mention": "^31.1.0",
|
|
43
|
+
"@ckeditor/ckeditor5-paragraph": "^31.1.0",
|
|
44
|
+
"@ckeditor/ckeditor5-table": "^31.1.0",
|
|
45
|
+
"@ckeditor/ckeditor5-theme-lark": "^31.1.0",
|
|
46
|
+
"@ckeditor/ckeditor5-typing": "^31.1.0",
|
|
47
|
+
"@ckeditor/ckeditor5-ui": "^31.1.0",
|
|
48
|
+
"@ckeditor/ckeditor5-undo": "^31.1.0",
|
|
49
|
+
"@ckeditor/ckeditor5-widget": "^31.1.0",
|
|
50
50
|
"webpack": "^4.43.0",
|
|
51
51
|
"webpack-cli": "^3.3.11"
|
|
52
52
|
},
|
|
@@ -527,7 +527,7 @@ export default class DataController {
|
|
|
527
527
|
*/
|
|
528
528
|
|
|
529
529
|
/**
|
|
530
|
-
* Event fired after {@link #get get() method} has been run.
|
|
530
|
+
* Event fired after the {@link #get get() method} has been run.
|
|
531
531
|
*
|
|
532
532
|
* The `get` event is fired by decorated {@link #get} method.
|
|
533
533
|
* See {@link module:utils/observablemixin~ObservableMixin#decorate} for more information and samples.
|
|
@@ -570,18 +570,18 @@ function _getMarkersRelativeToElement( element ) {
|
|
|
570
570
|
}
|
|
571
571
|
}
|
|
572
572
|
|
|
573
|
-
// Sort the markers in a stable fashion to ensure that the order
|
|
573
|
+
// Sort the markers in a stable fashion to ensure that the order in which they are
|
|
574
574
|
// added to the model's marker collection does not affect how they are
|
|
575
|
-
// downcast. One particular use case that we
|
|
575
|
+
// downcast. One particular use case that we are targeting here, is one where
|
|
576
576
|
// two markers are adjacent but not overlapping, such as an insertion/deletion
|
|
577
|
-
// suggestion pair
|
|
577
|
+
// suggestion pair representing the replacement of a range of text. In this
|
|
578
578
|
// case, putting the markers in DOM order causes the first marker's end to be
|
|
579
579
|
// serialized right after the second marker's start, while putting the markers
|
|
580
580
|
// in reverse DOM order causes it to be right before the second marker's
|
|
581
|
-
// start. So, we sort in a way that ensures non-intersecting ranges are in
|
|
581
|
+
// start. So, we sort these in a way that ensures non-intersecting ranges are in
|
|
582
582
|
// reverse DOM order, and intersecting ranges are in something approximating
|
|
583
583
|
// reverse DOM order (since reverse DOM order doesn't have a precise meaning
|
|
584
|
-
// when working with
|
|
584
|
+
// when working with intersecting ranges).
|
|
585
585
|
return result.sort( ( [ n1, r1 ], [ n2, r2 ] ) => {
|
|
586
586
|
if ( r1.end.compareWith( r2.start ) !== 'after' ) {
|
|
587
587
|
// m1.end <= m2.start -- m1 is entirely <= m2
|
package/src/dev-utils/view.js
CHANGED
|
@@ -67,8 +67,8 @@ const domConverterStub = {
|
|
|
67
67
|
* @param {Boolean} [options.renderRawElements=false] When set to `true`, the inner content of each
|
|
68
68
|
* {@link module:engine/view/rawelement~RawElement} will be printed.
|
|
69
69
|
* @param {Object} [options.domConverter=null] When set to an actual {@link module:engine/view/domconverter~DomConverter DomConverter}
|
|
70
|
-
* instance it lets the conversion go through exactly the same flow the editing view is going,
|
|
71
|
-
* filtering. Otherwise the simple stub is used.
|
|
70
|
+
* instance, it lets the conversion go through exactly the same flow the editing view is going through,
|
|
71
|
+
* i.e. with view data filtering. Otherwise the simple stub is used.
|
|
72
72
|
* @returns {String} The stringified data.
|
|
73
73
|
*/
|
|
74
74
|
export function getData( view, options = {} ) {
|
|
@@ -253,8 +253,8 @@ setData._parse = parse;
|
|
|
253
253
|
* @param {Boolean} [options.renderRawElements=false] When set to `true`, the inner content of each
|
|
254
254
|
* {@link module:engine/view/rawelement~RawElement} will be printed.
|
|
255
255
|
* @param {Object} [options.domConverter={}] When set to an actual {@link module:engine/view/domconverter~DomConverter DomConverter}
|
|
256
|
-
* instance it lets the conversion go through exactly the same flow the editing view is going,
|
|
257
|
-
* filtering. Otherwise the simple stub is used.
|
|
256
|
+
* instance, it lets the conversion go through exactly the same flow the editing view is going through,
|
|
257
|
+
* i.e. with view data filtering. Otherwise the simple stub is used.
|
|
258
258
|
* @returns {String} An HTML-like string representing the view.
|
|
259
259
|
*/
|
|
260
260
|
export function stringify( node, selectionOrPositionOrRange = null, options = {} ) {
|
|
@@ -645,8 +645,8 @@ class ViewStringify {
|
|
|
645
645
|
* {@link module:engine/view/uielement~UIElement} will be printed.
|
|
646
646
|
* @param {Boolean} [options.renderRawElements=false] When set to `true`, the inner content of each
|
|
647
647
|
* @param {Object} [options.domConverter={}] When set to an actual {@link module:engine/view/domconverter~DomConverter DomConverter}
|
|
648
|
-
* instance it lets the conversion go through exactly the same flow the editing view is going,
|
|
649
|
-
* filtering. Otherwise the simple stub is used.
|
|
648
|
+
* instance, it lets the conversion go through exactly the same flow the editing view is going through,
|
|
649
|
+
* i.e. with view data filtering. Otherwise the simple stub is used.
|
|
650
650
|
* {@link module:engine/view/rawelement~RawElement} will be printed.
|
|
651
651
|
*/
|
|
652
652
|
constructor( root, selection, options ) {
|
|
@@ -128,10 +128,10 @@ function tryFixingCollapsedRange( range, schema ) {
|
|
|
128
128
|
|
|
129
129
|
const nearestSelectionRange = schema.getNearestSelectionRange( originalPosition );
|
|
130
130
|
|
|
131
|
-
// This might be null
|
|
131
|
+
// This might be null, i.e. when the editor data is empty or the selection is inside a limit element
|
|
132
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
|
+
// 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
|
|
135
135
|
if ( !nearestSelectionRange ) {
|
|
136
136
|
const ancestorObject = originalPosition.getAncestors().reverse().find( item => schema.isObject( item ) );
|
|
137
137
|
|
|
@@ -263,35 +263,42 @@ function checkSelectionOnNonLimitElements( start, end, schema ) {
|
|
|
263
263
|
return startIsOnBlock || endIsOnBlock;
|
|
264
264
|
}
|
|
265
265
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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
|
+
}
|
|
292
295
|
}
|
|
296
|
+
|
|
297
|
+
currentRangeIndex++;
|
|
293
298
|
}
|
|
294
299
|
|
|
300
|
+
const nonIntersectingRanges = rangesToMerge.filter( ( _, index ) => !rangeIndexesToRemove.has( index ) );
|
|
301
|
+
|
|
295
302
|
return nonIntersectingRanges;
|
|
296
303
|
}
|
|
297
304
|
|
package/src/view/domconverter.js
CHANGED
|
@@ -24,6 +24,7 @@ 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,6 +32,8 @@ import isText from '@ckeditor/ckeditor5-utils/src/dom/istext';
|
|
|
31
32
|
const BR_FILLER_REF = BR_FILLER( document ); // eslint-disable-line new-cap
|
|
32
33
|
const NBSP_FILLER_REF = NBSP_FILLER( document ); // eslint-disable-line new-cap
|
|
33
34
|
const MARKED_NBSP_FILLER_REF = MARKED_NBSP_FILLER( document ); // eslint-disable-line new-cap
|
|
35
|
+
const UNSAFE_ATTRIBUTE_NAME_PREFIX = 'data-ck-unsafe-attribute-';
|
|
36
|
+
const UNSAFE_ELEMENT_REPLACEMENT_ATTRIBUTE = 'data-ck-unsafe-element';
|
|
34
37
|
|
|
35
38
|
/**
|
|
36
39
|
* `DomConverter` is a set of tools to do transformations between DOM nodes and view nodes. It also handles
|
|
@@ -72,14 +75,6 @@ export default class DomConverter {
|
|
|
72
75
|
*/
|
|
73
76
|
this.renderingMode = options.renderingMode || 'editing';
|
|
74
77
|
|
|
75
|
-
/**
|
|
76
|
-
* Main switch for new rendering approach in the editing view.
|
|
77
|
-
*
|
|
78
|
-
* @protected
|
|
79
|
-
* @member {Boolean}
|
|
80
|
-
*/
|
|
81
|
-
this.experimentalRenderingMode = false;
|
|
82
|
-
|
|
83
78
|
/**
|
|
84
79
|
* The mode of a block filler used by the DOM converter.
|
|
85
80
|
*
|
|
@@ -242,21 +237,47 @@ export default class DomConverter {
|
|
|
242
237
|
}
|
|
243
238
|
|
|
244
239
|
/**
|
|
245
|
-
* Decides whether given pair of attribute key and value should be passed further down the pipeline.
|
|
240
|
+
* Decides whether a given pair of attribute key and value should be passed further down the pipeline.
|
|
246
241
|
*
|
|
247
242
|
* @param {String} attributeKey
|
|
248
243
|
* @param {String} attributeValue
|
|
244
|
+
* @param {String} elementName Element name in lower case.
|
|
249
245
|
* @returns {Boolean}
|
|
250
246
|
*/
|
|
251
|
-
shouldRenderAttribute( attributeKey, attributeValue ) {
|
|
252
|
-
if (
|
|
247
|
+
shouldRenderAttribute( attributeKey, attributeValue, elementName ) {
|
|
248
|
+
if ( this.renderingMode === 'data' ) {
|
|
253
249
|
return true;
|
|
254
250
|
}
|
|
255
251
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
252
|
+
attributeKey = attributeKey.toLowerCase();
|
|
253
|
+
|
|
254
|
+
if ( attributeKey.startsWith( 'on' ) ) {
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (
|
|
259
|
+
attributeKey === 'srcdoc' &&
|
|
260
|
+
attributeValue.match( /\bon\S+\s*=|javascript:|<\s*\/*script/i )
|
|
261
|
+
) {
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (
|
|
266
|
+
elementName === 'img' &&
|
|
267
|
+
( attributeKey === 'src' || attributeKey === 'srcset' )
|
|
268
|
+
) {
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if ( elementName === 'source' && attributeKey === 'srcset' ) {
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if ( attributeValue.match( /^\s*(javascript:|data:(image\/svg|text\/x?html))/i ) ) {
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return true;
|
|
260
281
|
}
|
|
261
282
|
|
|
262
283
|
/**
|
|
@@ -267,7 +288,7 @@ export default class DomConverter {
|
|
|
267
288
|
*/
|
|
268
289
|
setContentOf( domElement, html ) {
|
|
269
290
|
// For data pipeline we pass the HTML as-is.
|
|
270
|
-
if (
|
|
291
|
+
if ( this.renderingMode === 'data' ) {
|
|
271
292
|
domElement.innerHTML = html;
|
|
272
293
|
|
|
273
294
|
return;
|
|
@@ -294,17 +315,15 @@ export default class DomConverter {
|
|
|
294
315
|
for ( const currentNode of nodes ) {
|
|
295
316
|
// Go through nodes to remove those that are prohibited in editing pipeline.
|
|
296
317
|
for ( const attributeName of currentNode.getAttributeNames() ) {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
if ( !this.shouldRenderAttribute( attributeName, attributeValue ) ) {
|
|
300
|
-
currentNode.removeAttribute( attributeName );
|
|
301
|
-
}
|
|
318
|
+
this.setDomElementAttribute( currentNode, attributeName, currentNode.getAttribute( attributeName ) );
|
|
302
319
|
}
|
|
303
320
|
|
|
304
321
|
const elementName = currentNode.tagName.toLowerCase();
|
|
305
322
|
|
|
306
323
|
// There are certain nodes, that should be renamed to <span> in editing pipeline.
|
|
307
324
|
if ( this._shouldRenameElement( elementName ) ) {
|
|
325
|
+
logWarning( 'domconverter-unsafe-element-detected', { unsafeElement: currentNode } );
|
|
326
|
+
|
|
308
327
|
currentNode.replaceWith( this._createReplacementDomElement( elementName, currentNode ) );
|
|
309
328
|
}
|
|
310
329
|
}
|
|
@@ -364,6 +383,8 @@ export default class DomConverter {
|
|
|
364
383
|
} else {
|
|
365
384
|
// Create DOM element.
|
|
366
385
|
if ( this._shouldRenameElement( viewNode.name ) ) {
|
|
386
|
+
logWarning( 'domconverter-unsafe-element-detected', { unsafeElement: viewNode } );
|
|
387
|
+
|
|
367
388
|
domElement = this._createReplacementDomElement( viewNode.name );
|
|
368
389
|
} else if ( viewNode.hasAttribute( 'xmlns' ) ) {
|
|
369
390
|
domElement = domDocument.createElementNS( viewNode.getAttribute( 'xmlns' ), viewNode.name );
|
|
@@ -383,13 +404,7 @@ export default class DomConverter {
|
|
|
383
404
|
|
|
384
405
|
// Copy element's attributes.
|
|
385
406
|
for ( const key of viewNode.getAttributeKeys() ) {
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
if ( !this.shouldRenderAttribute( key, value ) ) {
|
|
389
|
-
continue;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
domElement.setAttribute( key, value );
|
|
407
|
+
this.setDomElementAttribute( domElement, key, viewNode.getAttribute( key ), viewNode );
|
|
393
408
|
}
|
|
394
409
|
}
|
|
395
410
|
|
|
@@ -403,6 +418,60 @@ export default class DomConverter {
|
|
|
403
418
|
}
|
|
404
419
|
}
|
|
405
420
|
|
|
421
|
+
/**
|
|
422
|
+
* Sets the attribute on a DOM element.
|
|
423
|
+
*
|
|
424
|
+
* **Note**: To remove the attribute, use {@link #removeDomElementAttribute}.
|
|
425
|
+
*
|
|
426
|
+
* @param {HTMLElement} domElement The DOM element the attribute should be set on.
|
|
427
|
+
* @param {String} key The name of the attribute
|
|
428
|
+
* @param {String} value The value of the attribute
|
|
429
|
+
* @param {module:engine/view/element~Element} [relatedViewElement] The view element related to the `domElement` (if there is any).
|
|
430
|
+
* It helps decide whether the attribute set is unsafe. For instance, view elements created via
|
|
431
|
+
* {@link module:engine/view/downcastwriter~DowncastWriter} methods can allow certain attributes that would normally be filtered out.
|
|
432
|
+
*/
|
|
433
|
+
setDomElementAttribute( domElement, key, value, relatedViewElement = null ) {
|
|
434
|
+
const shouldRenderAttribute = this.shouldRenderAttribute( key, value, domElement.tagName.toLowerCase() ) ||
|
|
435
|
+
relatedViewElement && relatedViewElement.shouldRenderUnsafeAttribute( key );
|
|
436
|
+
|
|
437
|
+
if ( !shouldRenderAttribute ) {
|
|
438
|
+
logWarning( 'domconverter-unsafe-attribute-detected', { domElement, key, value } );
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// The old value was safe but the new value is unsafe.
|
|
442
|
+
if ( domElement.hasAttribute( key ) && !shouldRenderAttribute ) {
|
|
443
|
+
domElement.removeAttribute( key );
|
|
444
|
+
}
|
|
445
|
+
// The old value was unsafe (but prefixed) but the new value will be safe (will be unprefixed).
|
|
446
|
+
else if ( domElement.hasAttribute( UNSAFE_ATTRIBUTE_NAME_PREFIX + key ) && shouldRenderAttribute ) {
|
|
447
|
+
domElement.removeAttribute( UNSAFE_ATTRIBUTE_NAME_PREFIX + key );
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// If the attribute should not be rendered, rename it (instead of removing) to give developers some idea of what
|
|
451
|
+
// is going on (https://github.com/ckeditor/ckeditor5/issues/10801).
|
|
452
|
+
domElement.setAttribute( shouldRenderAttribute ? key : UNSAFE_ATTRIBUTE_NAME_PREFIX + key, value );
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Removes an attribute from a DOM element.
|
|
457
|
+
*
|
|
458
|
+
* **Note**: To set the attribute, use {@link #setDomElementAttribute}.
|
|
459
|
+
*
|
|
460
|
+
* @param {HTMLElement} domElement The DOM element the attribute should be removed from.
|
|
461
|
+
* @param {String} key The name of the attribute.
|
|
462
|
+
*/
|
|
463
|
+
removeDomElementAttribute( domElement, key ) {
|
|
464
|
+
// See #_createReplacementDomElement() to learn what this is.
|
|
465
|
+
if ( key == UNSAFE_ELEMENT_REPLACEMENT_ATTRIBUTE ) {
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
domElement.removeAttribute( key );
|
|
470
|
+
|
|
471
|
+
// See setDomElementAttribute() to learn what this is.
|
|
472
|
+
domElement.removeAttribute( UNSAFE_ATTRIBUTE_NAME_PREFIX + key );
|
|
473
|
+
}
|
|
474
|
+
|
|
406
475
|
/**
|
|
407
476
|
* Converts children of the view element to DOM using the
|
|
408
477
|
* {@link module:engine/view/domconverter~DomConverter#viewToDom} method.
|
|
@@ -1479,18 +1548,18 @@ export default class DomConverter {
|
|
|
1479
1548
|
}
|
|
1480
1549
|
|
|
1481
1550
|
/**
|
|
1482
|
-
* Checks whether given element name should be renamed in a current rendering mode.
|
|
1551
|
+
* Checks whether a given element name should be renamed in a current rendering mode.
|
|
1483
1552
|
*
|
|
1484
1553
|
* @private
|
|
1485
1554
|
* @param {String} elementName The name of view element.
|
|
1486
1555
|
* @returns {Boolean}
|
|
1487
1556
|
*/
|
|
1488
1557
|
_shouldRenameElement( elementName ) {
|
|
1489
|
-
return this.
|
|
1558
|
+
return this.renderingMode == 'editing' && elementName.toLowerCase() == 'script';
|
|
1490
1559
|
}
|
|
1491
1560
|
|
|
1492
1561
|
/**
|
|
1493
|
-
* Return a <span> element with special attribute holding the name of the original element.
|
|
1562
|
+
* Return a <span> element with a special attribute holding the name of the original element.
|
|
1494
1563
|
* Optionally, copy all the attributes of the original element if that element is provided.
|
|
1495
1564
|
*
|
|
1496
1565
|
* @private
|
|
@@ -1502,7 +1571,7 @@ export default class DomConverter {
|
|
|
1502
1571
|
const newDomElement = document.createElement( 'span' );
|
|
1503
1572
|
|
|
1504
1573
|
// Mark the span replacing a script as hidden.
|
|
1505
|
-
newDomElement.setAttribute(
|
|
1574
|
+
newDomElement.setAttribute( UNSAFE_ELEMENT_REPLACEMENT_ATTRIBUTE, elementName );
|
|
1506
1575
|
|
|
1507
1576
|
if ( originalDomElement ) {
|
|
1508
1577
|
while ( originalDomElement.firstChild ) {
|
|
@@ -1578,3 +1647,44 @@ function hasBlockParent( domNode, blockElements ) {
|
|
|
1578
1647
|
*
|
|
1579
1648
|
* @typedef {String} module:engine/view/filler~BlockFillerMode
|
|
1580
1649
|
*/
|
|
1650
|
+
|
|
1651
|
+
/**
|
|
1652
|
+
* The {@link module:engine/view/domconverter~DomConverter} detected a `<script>` element that may disrupt the
|
|
1653
|
+
* {@glink framework/guides/architecture/editing-engine#editing-pipeline editing pipeline} of the editor. To avoid this,
|
|
1654
|
+
* the `<script>` element was renamed to `<span data-ck-unsafe-element="script"></span>`.
|
|
1655
|
+
*
|
|
1656
|
+
* @error domconverter-unsafe-element-detected
|
|
1657
|
+
* @param {module:engine/model/element~Element|HTMLElement} unsafeElement The editing view or DOM element
|
|
1658
|
+
* that was renamed.
|
|
1659
|
+
*/
|
|
1660
|
+
|
|
1661
|
+
/**
|
|
1662
|
+
* The {@link module:engine/view/domconverter~DomConverter} detected an interactive attribute in the
|
|
1663
|
+
* {@glink framework/guides/architecture/editing-engine#editing-pipeline editing pipeline}. For the best
|
|
1664
|
+
* editing experience, the attribute was renamed to `data-ck-unsafe-attribute-[original attribute name]`.
|
|
1665
|
+
*
|
|
1666
|
+
* If you are the author of the plugin that generated this attribute and you want it to be preserved
|
|
1667
|
+
* in the editing pipeline, you can configure this when creating the element
|
|
1668
|
+
* using {@link module:engine/view/downcastwriter~DowncastWriter} during the
|
|
1669
|
+
* {@glink framework/guides/architecture/editing-engine#conversion model–view conversion}. Methods such as
|
|
1670
|
+
* {@link module:engine/view/downcastwriter~DowncastWriter#createContainerElement},
|
|
1671
|
+
* {@link module:engine/view/downcastwriter~DowncastWriter#createAttributeElement}, or
|
|
1672
|
+
* {@link module:engine/view/downcastwriter~DowncastWriter#createEmptyElement}
|
|
1673
|
+
* accept an option that will disable filtering of specific attributes:
|
|
1674
|
+
*
|
|
1675
|
+
* const paragraph = writer.createContainerElement( 'p',
|
|
1676
|
+
* {
|
|
1677
|
+
* class: 'clickable-paragraph',
|
|
1678
|
+
* onclick: 'alert( "Paragraph clicked!" )'
|
|
1679
|
+
* },
|
|
1680
|
+
* {
|
|
1681
|
+
* // Make sure the "onclick" attribute will pass through.
|
|
1682
|
+
* renderUnsafeAttributes: [ 'onclick' ]
|
|
1683
|
+
* }
|
|
1684
|
+
* );
|
|
1685
|
+
*
|
|
1686
|
+
* @error domconverter-unsafe-attribute-detected
|
|
1687
|
+
* @param {HTMLElement} domElement The DOM element the attribute was set on.
|
|
1688
|
+
* @param {String} key The original name of the attribute
|
|
1689
|
+
* @param {String} value The value of the original attribute
|
|
1690
|
+
*/
|
|
@@ -186,6 +186,8 @@ export default class DowncastWriter {
|
|
|
186
186
|
* @param {Object} [options] Element's options.
|
|
187
187
|
* @param {Number} [options.priority] Element's {@link module:engine/view/attributeelement~AttributeElement#priority priority}.
|
|
188
188
|
* @param {Number|String} [options.id] Element's {@link module:engine/view/attributeelement~AttributeElement#id id}.
|
|
189
|
+
* @param {Array.<String>} [options.renderUnsafeAttributes] A list of attribute names that should be rendered in the editing
|
|
190
|
+
* pipeline even though they would normally be filtered out by unsafe attribute detection mechanisms.
|
|
189
191
|
* @returns {module:engine/view/attributeelement~AttributeElement} Created element.
|
|
190
192
|
*/
|
|
191
193
|
createAttributeElement( name, attributes, options = {} ) {
|
|
@@ -199,6 +201,10 @@ export default class DowncastWriter {
|
|
|
199
201
|
attributeElement._id = options.id;
|
|
200
202
|
}
|
|
201
203
|
|
|
204
|
+
if ( options.renderUnsafeAttributes ) {
|
|
205
|
+
attributeElement._unsafeAttributesToRender.push( ...options.renderUnsafeAttributes );
|
|
206
|
+
}
|
|
207
|
+
|
|
202
208
|
return attributeElement;
|
|
203
209
|
}
|
|
204
210
|
|
|
@@ -222,6 +228,8 @@ export default class DowncastWriter {
|
|
|
222
228
|
* @param {Boolean} [options.isAllowedInsideAttributeElement=false] Whether an element is
|
|
223
229
|
* {@link module:engine/view/element~Element#isAllowedInsideAttributeElement allowed inside an AttributeElement} and can be wrapped
|
|
224
230
|
* with {@link module:engine/view/attributeelement~AttributeElement} by {@link module:engine/view/downcastwriter~DowncastWriter}.
|
|
231
|
+
* @param {Array.<String>} [options.renderUnsafeAttributes] A list of attribute names that should be rendered in the editing
|
|
232
|
+
* pipeline even though they would normally be filtered out by unsafe attribute detection mechanisms.
|
|
225
233
|
* @returns {module:engine/view/containerelement~ContainerElement} Created element.
|
|
226
234
|
*/
|
|
227
235
|
createContainerElement( name, attributes, options = {} ) {
|
|
@@ -231,6 +239,10 @@ export default class DowncastWriter {
|
|
|
231
239
|
containerElement._isAllowedInsideAttributeElement = options.isAllowedInsideAttributeElement;
|
|
232
240
|
}
|
|
233
241
|
|
|
242
|
+
if ( options.renderUnsafeAttributes ) {
|
|
243
|
+
containerElement._unsafeAttributesToRender.push( ...options.renderUnsafeAttributes );
|
|
244
|
+
}
|
|
245
|
+
|
|
234
246
|
return containerElement;
|
|
235
247
|
}
|
|
236
248
|
|
|
@@ -245,12 +257,19 @@ export default class DowncastWriter {
|
|
|
245
257
|
*
|
|
246
258
|
* @param {String} name Name of the element.
|
|
247
259
|
* @param {Object} [attributes] Elements attributes.
|
|
260
|
+
* @param {Object} [options] Element's options.
|
|
261
|
+
* @param {Array.<String>} [options.renderUnsafeAttributes] A list of attribute names that should be rendered in the editing
|
|
262
|
+
* pipeline even though they would normally be filtered out by unsafe attribute detection mechanisms.
|
|
248
263
|
* @returns {module:engine/view/editableelement~EditableElement} Created element.
|
|
249
264
|
*/
|
|
250
|
-
createEditableElement( name, attributes ) {
|
|
265
|
+
createEditableElement( name, attributes, options = {} ) {
|
|
251
266
|
const editableElement = new EditableElement( this.document, name, attributes );
|
|
252
267
|
editableElement._document = this.document;
|
|
253
268
|
|
|
269
|
+
if ( options.renderUnsafeAttributes ) {
|
|
270
|
+
editableElement._unsafeAttributesToRender.push( ...options.renderUnsafeAttributes );
|
|
271
|
+
}
|
|
272
|
+
|
|
254
273
|
return editableElement;
|
|
255
274
|
}
|
|
256
275
|
|
|
@@ -266,6 +285,8 @@ export default class DowncastWriter {
|
|
|
266
285
|
* @param {Boolean} [options.isAllowedInsideAttributeElement=true] Whether an element is
|
|
267
286
|
* {@link module:engine/view/element~Element#isAllowedInsideAttributeElement allowed inside an AttributeElement} and can be wrapped
|
|
268
287
|
* with {@link module:engine/view/attributeelement~AttributeElement} by {@link module:engine/view/downcastwriter~DowncastWriter}.
|
|
288
|
+
* @param {Array.<String>} [options.renderUnsafeAttributes] A list of attribute names that should be rendered in the editing
|
|
289
|
+
* pipeline even though they would normally be filtered out by unsafe attribute detection mechanisms.
|
|
269
290
|
* @returns {module:engine/view/emptyelement~EmptyElement} Created element.
|
|
270
291
|
*/
|
|
271
292
|
createEmptyElement( name, attributes, options = {} ) {
|
|
@@ -275,6 +296,10 @@ export default class DowncastWriter {
|
|
|
275
296
|
emptyElement._isAllowedInsideAttributeElement = options.isAllowedInsideAttributeElement;
|
|
276
297
|
}
|
|
277
298
|
|
|
299
|
+
if ( options.renderUnsafeAttributes ) {
|
|
300
|
+
emptyElement._unsafeAttributesToRender.push( ...options.renderUnsafeAttributes );
|
|
301
|
+
}
|
|
302
|
+
|
|
278
303
|
return emptyElement;
|
|
279
304
|
}
|
|
280
305
|
|
|
@@ -347,6 +372,8 @@ export default class DowncastWriter {
|
|
|
347
372
|
* @param {Boolean} [options.isAllowedInsideAttributeElement=true] Whether an element is
|
|
348
373
|
* {@link module:engine/view/element~Element#isAllowedInsideAttributeElement allowed inside an AttributeElement} and can be wrapped
|
|
349
374
|
* with {@link module:engine/view/attributeelement~AttributeElement} by {@link module:engine/view/downcastwriter~DowncastWriter}.
|
|
375
|
+
* @param {Array.<String>} [options.renderUnsafeAttributes] A list of attribute names that should be rendered in the editing
|
|
376
|
+
* pipeline even though they would normally be filtered out by unsafe attribute detection mechanisms.
|
|
350
377
|
* @returns {module:engine/view/rawelement~RawElement} The created element.
|
|
351
378
|
*/
|
|
352
379
|
createRawElement( name, attributes, renderFunction, options = {} ) {
|
|
@@ -358,6 +385,10 @@ export default class DowncastWriter {
|
|
|
358
385
|
rawElement._isAllowedInsideAttributeElement = options.isAllowedInsideAttributeElement;
|
|
359
386
|
}
|
|
360
387
|
|
|
388
|
+
if ( options.renderUnsafeAttributes ) {
|
|
389
|
+
rawElement._unsafeAttributesToRender.push( ...options.renderUnsafeAttributes );
|
|
390
|
+
}
|
|
391
|
+
|
|
361
392
|
return rawElement;
|
|
362
393
|
}
|
|
363
394
|
|
package/src/view/element.js
CHANGED
|
@@ -138,6 +138,21 @@ export default class Element extends Node {
|
|
|
138
138
|
* @member {Boolean}
|
|
139
139
|
*/
|
|
140
140
|
this._isAllowedInsideAttributeElement = false;
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* A list of attribute names that should be rendered in the editing pipeline even though filtering mechanisms
|
|
144
|
+
* implemented in the {@link module:engine/view/domconverter~DomConverter} (for instance,
|
|
145
|
+
* {@link module:engine/view/domconverter~DomConverter#shouldRenderAttribute}) would filter them out.
|
|
146
|
+
*
|
|
147
|
+
* These attributes can be specified as an option when the element is created by
|
|
148
|
+
* the {@link module:engine/view/downcastwriter~DowncastWriter}. To check whether an unsafe an attribute should
|
|
149
|
+
* be permitted, use the {@link #shouldRenderUnsafeAttribute} method.
|
|
150
|
+
*
|
|
151
|
+
* @private
|
|
152
|
+
* @readonly
|
|
153
|
+
* @member {Array.<String>}
|
|
154
|
+
*/
|
|
155
|
+
this._unsafeAttributesToRender = [];
|
|
141
156
|
}
|
|
142
157
|
|
|
143
158
|
/**
|
|
@@ -572,6 +587,19 @@ export default class Element extends Node {
|
|
|
572
587
|
( attributes == '' ? '' : ` ${ attributes }` );
|
|
573
588
|
}
|
|
574
589
|
|
|
590
|
+
/**
|
|
591
|
+
* Decides whether an unsafe attribute is whitelisted and should be rendered in the editing pipeline even though filtering mechanisms
|
|
592
|
+
* like {@link module:engine/view/domconverter~DomConverter#shouldRenderAttribute} say it should not.
|
|
593
|
+
*
|
|
594
|
+
* Unsafe attribute names can be specified when creating an element via {@link module:engine/view/downcastwriter~DowncastWriter}.
|
|
595
|
+
*
|
|
596
|
+
* @param {String} attributeName The name of the attribute to be checked.
|
|
597
|
+
* @returns {Boolean}
|
|
598
|
+
*/
|
|
599
|
+
shouldRenderUnsafeAttribute( attributeName ) {
|
|
600
|
+
return this._unsafeAttributesToRender.includes( attributeName );
|
|
601
|
+
}
|
|
602
|
+
|
|
575
603
|
/**
|
|
576
604
|
* Clones provided element.
|
|
577
605
|
*
|
package/src/view/matcher.js
CHANGED
|
@@ -531,7 +531,7 @@ function matchStyles( patterns, element ) {
|
|
|
531
531
|
* name: 'figure',
|
|
532
532
|
* attributes: [
|
|
533
533
|
* 'title', // Match `title` attribute (can be empty).
|
|
534
|
-
* /^data
|
|
534
|
+
* /^data-*$/ // Match attributes starting with `data-` e.g. `data-foo` with any value (can be empty).
|
|
535
535
|
* ]
|
|
536
536
|
* };
|
|
537
537
|
*
|
|
@@ -541,7 +541,8 @@ function matchStyles( patterns, element ) {
|
|
|
541
541
|
* attributes: [
|
|
542
542
|
* {
|
|
543
543
|
* key: 'type', // Match `type` as an attribute key.
|
|
544
|
-
* value: /^(text|number|date)$/
|
|
544
|
+
* value: /^(text|number|date)$/ // Match `text`, `number` or `date` values.
|
|
545
|
+
* },
|
|
545
546
|
* {
|
|
546
547
|
* key: /^data-.*$/, // Match attributes starting with `data-` e.g. `data-foo`.
|
|
547
548
|
* value: true // Match any value (can be empty).
|
|
@@ -572,7 +573,7 @@ function matchStyles( patterns, element ) {
|
|
|
572
573
|
* // Match view element which has matching styles (Object).
|
|
573
574
|
* const pattern = {
|
|
574
575
|
* name: 'p',
|
|
575
|
-
*
|
|
576
|
+
* styles: {
|
|
576
577
|
* color: /rgb\((\d{1,3}), (\d{1,3}), (\d{1,3})\)/, // Match `color` in RGB format only.
|
|
577
578
|
* 'font-weight': 600, // Match `font-weight` only if it's `600`.
|
|
578
579
|
* 'text-decoration': true // Match any text decoration.
|
|
@@ -582,19 +583,20 @@ function matchStyles( patterns, element ) {
|
|
|
582
583
|
* // Match view element which has matching styles (Array).
|
|
583
584
|
* const pattern = {
|
|
584
585
|
* name: 'p',
|
|
585
|
-
*
|
|
586
|
+
* styles: [
|
|
586
587
|
* 'color', // Match `color` with any value.
|
|
587
|
-
* /^border
|
|
588
|
+
* /^border.*$/ // Match all border properties.
|
|
588
589
|
* ]
|
|
589
590
|
* };
|
|
590
591
|
*
|
|
591
592
|
* // Match view element which has matching styles (key-value pairs).
|
|
592
593
|
* const pattern = {
|
|
593
594
|
* name: 'p',
|
|
594
|
-
*
|
|
595
|
+
* styles: [
|
|
595
596
|
* {
|
|
596
|
-
* key: 'color',
|
|
597
|
-
* value: /rgb\((\d{1,3}), (\d{1,3}), (\d{1,3})\)
|
|
597
|
+
* key: 'color', // Match `color` as an property key.
|
|
598
|
+
* value: /rgb\((\d{1,3}), (\d{1,3}), (\d{1,3})\)/ // Match RGB format only.
|
|
599
|
+
* },
|
|
598
600
|
* {
|
|
599
601
|
* key: /^border.*$/, // Match any border style.
|
|
600
602
|
* value: true // Match any value.
|
|
@@ -647,6 +649,7 @@ function matchStyles( patterns, element ) {
|
|
|
647
649
|
* {
|
|
648
650
|
* key: 'image', // Match `image` class.
|
|
649
651
|
* value: true
|
|
652
|
+
* },
|
|
650
653
|
* {
|
|
651
654
|
* key: /^image-side-(left|right)$/, // Match `image-side-left` or `image-side-right` class.
|
|
652
655
|
* value: true
|
|
@@ -700,11 +703,9 @@ function matchStyles( patterns, element ) {
|
|
|
700
703
|
* @typedef {String|RegExp|Object|Function} module:engine/view/matcher~MatcherPattern
|
|
701
704
|
*
|
|
702
705
|
* @property {String|RegExp} [name] View element name to match.
|
|
703
|
-
* @property {String|RegExp|Array.<String|RegExp>} [classes] View element's
|
|
704
|
-
* @property {Object} [styles]
|
|
705
|
-
*
|
|
706
|
-
* @property {Object} [attributes] Object with key-value pairs representing attributes to match.
|
|
707
|
-
* Each object key represents attribute name. Value can be given as `String` or `RegExp`.
|
|
706
|
+
* @property {Boolean|String|RegExp|Object|Array.<String|RegExp|Object>} [classes] View element's classes to match.
|
|
707
|
+
* @property {Boolean|String|RegExp|Object|Array.<String|RegExp|Object>} [styles] View element's styles to match.
|
|
708
|
+
* @property {Boolean|String|RegExp|Object|Array.<String|RegExp|Object>} [attributes] View element's attributes to match.
|
|
708
709
|
*/
|
|
709
710
|
|
|
710
711
|
/**
|
|
@@ -81,7 +81,7 @@ export default class SelectionObserver extends Observer {
|
|
|
81
81
|
this._fireSelectionChangeDoneDebounced = debounce( data => this.document.fire( 'selectionChangeDone', data ), 200 );
|
|
82
82
|
|
|
83
83
|
/**
|
|
84
|
-
* When called, starts clearing the {@link #_loopbackCounter} counter in intervals
|
|
84
|
+
* When called, starts clearing the {@link #_loopbackCounter} counter in time intervals. When the number of selection
|
|
85
85
|
* changes exceeds a certain limit within the interval of time, the observer will not fire `selectionChange` but warn about
|
|
86
86
|
* possible infinite selection loop.
|
|
87
87
|
*
|
|
@@ -92,7 +92,7 @@ export default class SelectionObserver extends Observer {
|
|
|
92
92
|
|
|
93
93
|
/**
|
|
94
94
|
* Unlocks the `isSelecting` state of the view document in case the selection observer did not record this fact
|
|
95
|
-
* correctly (for whatever
|
|
95
|
+
* correctly (for whatever reason). It is a safeguard (paranoid check), that returns document to the normal state
|
|
96
96
|
* after a certain period of time (debounced, postponed by each selectionchange event).
|
|
97
97
|
*
|
|
98
98
|
* @private
|
package/src/view/renderer.js
CHANGED
|
@@ -120,7 +120,7 @@ export default class Renderer {
|
|
|
120
120
|
// Rendering the selection and inline filler manipulation should be postponed in (non-Android) Blink until the user finishes
|
|
121
121
|
// creating the selection in DOM to avoid accidental selection collapsing
|
|
122
122
|
// (https://github.com/ckeditor/ckeditor5/issues/10562, https://github.com/ckeditor/ckeditor5/issues/10723).
|
|
123
|
-
// When the user stops
|
|
123
|
+
// When the user stops selecting, all pending changes should be rendered ASAP, though.
|
|
124
124
|
if ( env.isBlink && !env.isAndroid ) {
|
|
125
125
|
this.on( 'change:isSelecting', () => {
|
|
126
126
|
if ( !this.isSelecting ) {
|
|
@@ -555,25 +555,14 @@ export default class Renderer {
|
|
|
555
555
|
|
|
556
556
|
// Add or overwrite attributes.
|
|
557
557
|
for ( const key of viewAttrKeys ) {
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
if ( !this.domConverter.shouldRenderAttribute( key, value ) ) {
|
|
561
|
-
domElement.removeAttribute( key );
|
|
562
|
-
} else {
|
|
563
|
-
domElement.setAttribute( key, value );
|
|
564
|
-
}
|
|
558
|
+
this.domConverter.setDomElementAttribute( domElement, key, viewElement.getAttribute( key ), viewElement );
|
|
565
559
|
}
|
|
566
560
|
|
|
567
561
|
// Remove from DOM attributes which do not exists in the view.
|
|
568
562
|
for ( const key of domAttrKeys ) {
|
|
569
|
-
// Do not remove attributes on `script` elements with special data attributes `data-ck-hidden`.
|
|
570
|
-
if ( viewElement.name === 'script' && key === 'data-ck-hidden' ) {
|
|
571
|
-
continue;
|
|
572
|
-
}
|
|
573
|
-
|
|
574
563
|
// All other attributes not present in the DOM should be removed.
|
|
575
564
|
if ( !viewElement.hasAttribute( key ) ) {
|
|
576
|
-
|
|
565
|
+
this.domConverter.removeDomElementAttribute( domElement, key );
|
|
577
566
|
}
|
|
578
567
|
}
|
|
579
568
|
}
|
|
@@ -742,7 +731,7 @@ export default class Renderer {
|
|
|
742
731
|
_updateSelection() {
|
|
743
732
|
// Block updating DOM selection in (non-Android) Blink while the user is selecting to prevent accidental selection collapsing.
|
|
744
733
|
// Note: Structural changes in DOM must trigger selection rendering, though. Nodes the selection was anchored
|
|
745
|
-
// to may disappear in DOM which would break the selection (e.g. in real-time collaboration scenarios).
|
|
734
|
+
// to, may disappear in DOM which would break the selection (e.g. in real-time collaboration scenarios).
|
|
746
735
|
// https://github.com/ckeditor/ckeditor5/issues/10562, https://github.com/ckeditor/ckeditor5/issues/10723
|
|
747
736
|
if ( env.isBlink && !env.isAndroid && this.isSelecting && !this.markedChildren.size ) {
|
|
748
737
|
return;
|