@ckeditor/ckeditor5-engine 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/LICENSE.md +1 -1
- package/README.md +1 -1
- package/package.json +24 -21
- package/src/controller/datacontroller.js +50 -1
- package/src/conversion/downcasthelpers.js +21 -18
- package/src/conversion/upcasthelpers.js +10 -17
- package/src/dataprocessor/htmldataprocessor.js +13 -16
- package/src/dataprocessor/xmldataprocessor.js +15 -19
- package/src/dev-utils/view.js +21 -3
- package/src/index.js +1 -0
- package/src/model/markercollection.js +5 -4
- package/src/model/range.js +4 -3
- package/src/model/schema.js +28 -24
- package/src/model/selection.js +1 -1
- package/src/model/utils/deletecontent.js +3 -3
- package/src/model/utils/selection-post-fixer.js +10 -2
- package/src/view/document.js +12 -0
- package/src/view/domconverter.js +286 -73
- package/src/view/matcher.js +89 -13
- package/src/view/observer/selectionobserver.js +48 -1
- package/src/view/rawelement.js +3 -2
- package/src/view/renderer.js +103 -39
- package/src/view/styles/border.js +10 -10
- package/src/view/styles/utils.js +5 -0
- package/src/view/uielement.js +5 -2
- package/src/view/view.js +1 -1
- package/theme/renderer.css +9 -0
- package/CHANGELOG.md +0 -823
package/src/view/matcher.js
CHANGED
|
@@ -235,7 +235,7 @@ function isElementMatching( element, pattern ) {
|
|
|
235
235
|
function matchName( pattern, name ) {
|
|
236
236
|
// If pattern is provided as RegExp - test against this regexp.
|
|
237
237
|
if ( pattern instanceof RegExp ) {
|
|
238
|
-
return
|
|
238
|
+
return !!name.match( pattern );
|
|
239
239
|
}
|
|
240
240
|
|
|
241
241
|
return pattern === name;
|
|
@@ -288,18 +288,12 @@ function matchName( pattern, name ) {
|
|
|
288
288
|
// ]
|
|
289
289
|
//
|
|
290
290
|
// @param {Object} patterns Object with information about attributes to match.
|
|
291
|
-
// @param {
|
|
292
|
-
//
|
|
293
|
-
// [
|
|
294
|
-
// [ 'src', 'https://example.com' ],
|
|
295
|
-
// [ 'rel', 'nofollow' ]
|
|
296
|
-
// ]
|
|
297
|
-
//
|
|
291
|
+
// @param {Iterable.<String>} keys Attribute, style or class keys.
|
|
298
292
|
// @param {Function} valueGetter A function providing value for a given item key.
|
|
299
293
|
// @returns {Array|null} Returns array with matched attribute names or `null` if no attributes were matched.
|
|
300
|
-
function matchPatterns( patterns,
|
|
294
|
+
function matchPatterns( patterns, keys, valueGetter ) {
|
|
301
295
|
const normalizedPatterns = normalizePatterns( patterns );
|
|
302
|
-
const normalizedItems = Array.from(
|
|
296
|
+
const normalizedItems = Array.from( keys );
|
|
303
297
|
const match = [];
|
|
304
298
|
|
|
305
299
|
normalizedPatterns.forEach( ( [ patternKey, patternValue ] ) => {
|
|
@@ -400,7 +394,7 @@ function normalizePatterns( patterns ) {
|
|
|
400
394
|
function isKeyMatched( patternKey, itemKey ) {
|
|
401
395
|
return patternKey === true ||
|
|
402
396
|
patternKey === itemKey ||
|
|
403
|
-
patternKey instanceof RegExp &&
|
|
397
|
+
patternKey instanceof RegExp && itemKey.match( patternKey );
|
|
404
398
|
}
|
|
405
399
|
|
|
406
400
|
// @param {String|RegExp} patternValue A pattern representing a value we want to match.
|
|
@@ -414,7 +408,11 @@ function isValueMatched( patternValue, itemKey, valueGetter ) {
|
|
|
414
408
|
|
|
415
409
|
const itemValue = valueGetter( itemKey );
|
|
416
410
|
|
|
417
|
-
|
|
411
|
+
// For now, the reducers are not returning the full tree of properties.
|
|
412
|
+
// Casting to string preserves the old behavior until the root cause is fixed.
|
|
413
|
+
// More can be found in https://github.com/ckeditor/ckeditor5/issues/10399.
|
|
414
|
+
return patternValue === itemValue ||
|
|
415
|
+
patternValue instanceof RegExp && !!String( itemValue ).match( patternValue );
|
|
418
416
|
}
|
|
419
417
|
|
|
420
418
|
// Checks if attributes of provided element can be matched against provided patterns.
|
|
@@ -424,7 +422,25 @@ function isValueMatched( patternValue, itemKey, valueGetter ) {
|
|
|
424
422
|
// @param {module:engine/view/element~Element} element Element which attributes will be tested.
|
|
425
423
|
// @returns {Array|null} Returns array with matched attribute names or `null` if no attributes were matched.
|
|
426
424
|
function matchAttributes( patterns, element ) {
|
|
427
|
-
|
|
425
|
+
const attributeKeys = new Set( element.getAttributeKeys() );
|
|
426
|
+
|
|
427
|
+
// `style` and `class` attribute keys are deprecated. Only allow them in object pattern
|
|
428
|
+
// for backward compatibility.
|
|
429
|
+
if ( isPlainObject( patterns ) ) {
|
|
430
|
+
if ( patterns.style !== undefined ) {
|
|
431
|
+
// Documented at the end of matcher.js.
|
|
432
|
+
logWarning( 'matcher-pattern-deprecated-attributes-style-key', patterns );
|
|
433
|
+
}
|
|
434
|
+
if ( patterns.class !== undefined ) {
|
|
435
|
+
// Documented at the end of matcher.js.
|
|
436
|
+
logWarning( 'matcher-pattern-deprecated-attributes-class-key', patterns );
|
|
437
|
+
}
|
|
438
|
+
} else {
|
|
439
|
+
attributeKeys.delete( 'style' );
|
|
440
|
+
attributeKeys.delete( 'class' );
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
return matchPatterns( patterns, attributeKeys, key => element.getAttribute( key ) );
|
|
428
444
|
}
|
|
429
445
|
|
|
430
446
|
// Checks if classes of provided element can be matched against provided patterns.
|
|
@@ -698,3 +714,63 @@ function matchStyles( patterns, element ) {
|
|
|
698
714
|
* @param {Object} pattern Pattern with missing properties.
|
|
699
715
|
* @error matcher-pattern-missing-key-or-value
|
|
700
716
|
*/
|
|
717
|
+
|
|
718
|
+
/**
|
|
719
|
+
* The key-value matcher pattern for `attributes` option is using deprecated `style` key.
|
|
720
|
+
*
|
|
721
|
+
* Use `styles` matcher pattern option instead:
|
|
722
|
+
*
|
|
723
|
+
* // Instead of:
|
|
724
|
+
* const pattern = {
|
|
725
|
+
* attributes: {
|
|
726
|
+
* key1: 'value1',
|
|
727
|
+
* key2: 'value2',
|
|
728
|
+
* style: /^border.*$/
|
|
729
|
+
* }
|
|
730
|
+
* }
|
|
731
|
+
*
|
|
732
|
+
* // Use:
|
|
733
|
+
* const pattern = {
|
|
734
|
+
* attributes: {
|
|
735
|
+
* key1: 'value1',
|
|
736
|
+
* key2: 'value2'
|
|
737
|
+
* },
|
|
738
|
+
* styles: /^border.*$/
|
|
739
|
+
* }
|
|
740
|
+
*
|
|
741
|
+
* Refer to the {@glink builds/guides/migration/migration-to-29##migration-to-ckeditor-5-v2910 Migration to v29.1.0} guide
|
|
742
|
+
* and {@link module:engine/view/matcher~MatcherPattern} documentation.
|
|
743
|
+
*
|
|
744
|
+
* @param {Object} pattern Pattern with missing properties.
|
|
745
|
+
* @error matcher-pattern-deprecated-attributes-style-key
|
|
746
|
+
*/
|
|
747
|
+
|
|
748
|
+
/**
|
|
749
|
+
* The key-value matcher pattern for `attributes` option is using deprecated `class` key.
|
|
750
|
+
*
|
|
751
|
+
* Use `classes` matcher pattern option instead:
|
|
752
|
+
*
|
|
753
|
+
* // Instead of:
|
|
754
|
+
* const pattern = {
|
|
755
|
+
* attributes: {
|
|
756
|
+
* key1: 'value1',
|
|
757
|
+
* key2: 'value2',
|
|
758
|
+
* class: 'foobar'
|
|
759
|
+
* }
|
|
760
|
+
* }
|
|
761
|
+
*
|
|
762
|
+
* // Use:
|
|
763
|
+
* const pattern = {
|
|
764
|
+
* attributes: {
|
|
765
|
+
* key1: 'value1',
|
|
766
|
+
* key2: 'value2'
|
|
767
|
+
* },
|
|
768
|
+
* classes: 'foobar'
|
|
769
|
+
* }
|
|
770
|
+
*
|
|
771
|
+
* Refer to the {@glink builds/guides/migration/migration-to-29##migration-to-ckeditor-5-v2910 Migration to v29.1.0} guide
|
|
772
|
+
* and the {@link module:engine/view/matcher~MatcherPattern} documentation.
|
|
773
|
+
*
|
|
774
|
+
* @param {Object} pattern Pattern with missing properties.
|
|
775
|
+
* @error matcher-pattern-deprecated-attributes-class-key
|
|
776
|
+
*/
|
|
@@ -20,6 +20,8 @@ import { debounce } from 'lodash-es';
|
|
|
20
20
|
* {@link module:engine/view/document~Document#event:selectionChange} event only if a selection change was the only change in the document
|
|
21
21
|
* and the DOM selection is different then the view selection.
|
|
22
22
|
*
|
|
23
|
+
* This observer also manages the {@link module:engine/view/document~Document#isSelecting} property of the view document.
|
|
24
|
+
*
|
|
23
25
|
* Note that this observer is attached by the {@link module:engine/view/view~View} and is available by default.
|
|
24
26
|
*
|
|
25
27
|
* @see module:engine/view/observer/mutationobserver~MutationObserver
|
|
@@ -78,8 +80,26 @@ export default class SelectionObserver extends Observer {
|
|
|
78
80
|
*/
|
|
79
81
|
this._fireSelectionChangeDoneDebounced = debounce( data => this.document.fire( 'selectionChangeDone', data ), 200 );
|
|
80
82
|
|
|
83
|
+
/**
|
|
84
|
+
* When called, starts clearing the {@link #_loopbackCounter} counter in intervals of time. When the number of selection
|
|
85
|
+
* changes exceeds a certain limit within the interval of time, the observer will not fire `selectionChange` but warn about
|
|
86
|
+
* possible infinite selection loop.
|
|
87
|
+
*
|
|
88
|
+
* @private
|
|
89
|
+
* @member {Number} #_clearInfiniteLoopInterval
|
|
90
|
+
*/
|
|
81
91
|
this._clearInfiniteLoopInterval = setInterval( () => this._clearInfiniteLoop(), 1000 );
|
|
82
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Unlocks the `isSelecting` state of the view document in case the selection observer did not record this fact
|
|
95
|
+
* correctly (for whatever the reason). It is a safeguard (paranoid check) that returns document to the normal state
|
|
96
|
+
* after a certain period of time (debounced, postponed by each selectionchange event).
|
|
97
|
+
*
|
|
98
|
+
* @private
|
|
99
|
+
* @method #_documentIsSelectingInactivityTimeoutDebounced
|
|
100
|
+
*/
|
|
101
|
+
this._documentIsSelectingInactivityTimeoutDebounced = debounce( () => ( this.document.isSelecting = false ), 5000 );
|
|
102
|
+
|
|
83
103
|
/**
|
|
84
104
|
* Private property to check if the code does not enter infinite loop.
|
|
85
105
|
*
|
|
@@ -95,13 +115,39 @@ export default class SelectionObserver extends Observer {
|
|
|
95
115
|
observe( domElement ) {
|
|
96
116
|
const domDocument = domElement.ownerDocument;
|
|
97
117
|
|
|
98
|
-
|
|
118
|
+
const startDocumentIsSelecting = () => {
|
|
119
|
+
this.document.isSelecting = true;
|
|
120
|
+
|
|
121
|
+
// Let's activate the safety timeout each time the document enters the "is selecting" state.
|
|
122
|
+
this._documentIsSelectingInactivityTimeoutDebounced();
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const endDocumentIsSelecting = () => {
|
|
126
|
+
this.document.isSelecting = false;
|
|
127
|
+
|
|
128
|
+
// The safety timeout can be canceled when the document leaves the "is selecting" state.
|
|
129
|
+
this._documentIsSelectingInactivityTimeoutDebounced.cancel();
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// The document has the "is selecting" state while the user keeps making (extending) the selection
|
|
133
|
+
// (e.g. by holding the mouse button and moving the cursor). The state resets when they either released
|
|
134
|
+
// the mouse button or interrupted the process by pressing or releasing any key.
|
|
135
|
+
this.listenTo( domElement, 'selectstart', startDocumentIsSelecting, { priority: 'highest' } );
|
|
136
|
+
this.listenTo( domElement, 'keydown', endDocumentIsSelecting, { priority: 'highest' } );
|
|
137
|
+
this.listenTo( domElement, 'keyup', endDocumentIsSelecting, { priority: 'highest' } );
|
|
138
|
+
|
|
139
|
+
// Add document-wide listeners only once. This method could be called for multiple editing roots.
|
|
99
140
|
if ( this._documents.has( domDocument ) ) {
|
|
100
141
|
return;
|
|
101
142
|
}
|
|
102
143
|
|
|
144
|
+
this.listenTo( domDocument, 'mouseup', endDocumentIsSelecting, { priority: 'highest' } );
|
|
103
145
|
this.listenTo( domDocument, 'selectionchange', ( evt, domEvent ) => {
|
|
104
146
|
this._handleSelectionChange( domEvent, domDocument );
|
|
147
|
+
|
|
148
|
+
// Defer the safety timeout when the selection changes (e.g. the user keeps extending the selection
|
|
149
|
+
// using their mouse).
|
|
150
|
+
this._documentIsSelectingInactivityTimeoutDebounced();
|
|
105
151
|
} );
|
|
106
152
|
|
|
107
153
|
this._documents.add( domDocument );
|
|
@@ -115,6 +161,7 @@ export default class SelectionObserver extends Observer {
|
|
|
115
161
|
|
|
116
162
|
clearInterval( this._clearInfiniteLoopInterval );
|
|
117
163
|
this._fireSelectionChangeDoneDebounced.cancel();
|
|
164
|
+
this._documentIsSelectingInactivityTimeoutDebounced.cancel();
|
|
118
165
|
}
|
|
119
166
|
|
|
120
167
|
/**
|
package/src/view/rawelement.js
CHANGED
|
@@ -131,12 +131,13 @@ export default class RawElement extends Element {
|
|
|
131
131
|
*
|
|
132
132
|
* const myRawElement = downcastWriter.createRawElement( 'div' );
|
|
133
133
|
*
|
|
134
|
-
* myRawElement.render = function( domElement ) {
|
|
135
|
-
*
|
|
134
|
+
* myRawElement.render = function( domElement, domConverter ) {
|
|
135
|
+
* domConverter.setContentOf( domElement, '<b>This is the raw content of myRawElement.</b>' );
|
|
136
136
|
* };
|
|
137
137
|
*
|
|
138
138
|
* @method #render
|
|
139
139
|
* @param {HTMLElement} domElement The native DOM element representing the raw view element.
|
|
140
|
+
* @param {module:engine/view/domconverter~DomConverter} domConverter Instance of the DomConverter used to optimize the output.
|
|
140
141
|
*/
|
|
141
142
|
}
|
|
142
143
|
|
package/src/view/renderer.js
CHANGED
|
@@ -24,6 +24,8 @@ import isNode from '@ckeditor/ckeditor5-utils/src/dom/isnode';
|
|
|
24
24
|
import fastDiff from '@ckeditor/ckeditor5-utils/src/fastdiff';
|
|
25
25
|
import env from '@ckeditor/ckeditor5-utils/src/env';
|
|
26
26
|
|
|
27
|
+
import '../../theme/renderer.css';
|
|
28
|
+
|
|
27
29
|
/**
|
|
28
30
|
* Renderer is responsible for updating the DOM structure and the DOM selection based on
|
|
29
31
|
* the {@link module:engine/view/renderer~Renderer#markToSync information about updated view nodes}.
|
|
@@ -98,8 +100,34 @@ export default class Renderer {
|
|
|
98
100
|
* this is set to `false`.
|
|
99
101
|
*
|
|
100
102
|
* @member {Boolean}
|
|
103
|
+
* @observable
|
|
104
|
+
*/
|
|
105
|
+
this.set( 'isFocused', false );
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Indicates whether the user is making a selection in the document (e.g. holding the mouse button and moving the cursor).
|
|
109
|
+
* When they stop selecting, the property goes back to `false`.
|
|
110
|
+
*
|
|
111
|
+
* Note: In some browsers, the renderer will stop rendering the selection and inline fillers while the user is making
|
|
112
|
+
* a selection to avoid glitches in DOM selection
|
|
113
|
+
* (https://github.com/ckeditor/ckeditor5/issues/10562, https://github.com/ckeditor/ckeditor5/issues/10723).
|
|
114
|
+
*
|
|
115
|
+
* @member {Boolean}
|
|
116
|
+
* @observable
|
|
101
117
|
*/
|
|
102
|
-
this.
|
|
118
|
+
this.set( 'isSelecting', false );
|
|
119
|
+
|
|
120
|
+
// Rendering the selection and inline filler manipulation should be postponed in (non-Android) Blink until the user finishes
|
|
121
|
+
// creating the selection in DOM to avoid accidental selection collapsing
|
|
122
|
+
// (https://github.com/ckeditor/ckeditor5/issues/10562, https://github.com/ckeditor/ckeditor5/issues/10723).
|
|
123
|
+
// When the user stops, selecting, all pending changes should be rendered ASAP, though.
|
|
124
|
+
if ( env.isBlink && !env.isAndroid ) {
|
|
125
|
+
this.on( 'change:isSelecting', () => {
|
|
126
|
+
if ( !this.isSelecting ) {
|
|
127
|
+
this.render();
|
|
128
|
+
}
|
|
129
|
+
} );
|
|
130
|
+
}
|
|
103
131
|
|
|
104
132
|
/**
|
|
105
133
|
* The text node in which the inline filler was rendered.
|
|
@@ -170,29 +198,41 @@ export default class Renderer {
|
|
|
170
198
|
*/
|
|
171
199
|
render() {
|
|
172
200
|
let inlineFillerPosition;
|
|
201
|
+
const isInlineFillerRenderingPossible = env.isBlink && !env.isAndroid ? !this.isSelecting : true;
|
|
173
202
|
|
|
174
203
|
// Refresh mappings.
|
|
175
204
|
for ( const element of this.markedChildren ) {
|
|
176
205
|
this._updateChildrenMappings( element );
|
|
177
206
|
}
|
|
178
207
|
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
// (
|
|
182
|
-
if (
|
|
183
|
-
|
|
184
|
-
|
|
208
|
+
// Don't manipulate inline fillers while the selection is being made in (non-Android) Blink to prevent accidental
|
|
209
|
+
// DOM selection collapsing
|
|
210
|
+
// (https://github.com/ckeditor/ckeditor5/issues/10562, https://github.com/ckeditor/ckeditor5/issues/10723).
|
|
211
|
+
if ( isInlineFillerRenderingPossible ) {
|
|
212
|
+
// There was inline filler rendered in the DOM but it's not
|
|
213
|
+
// at the selection position any more, so we can remove it
|
|
214
|
+
// (cause even if it's needed, it must be placed in another location).
|
|
215
|
+
if ( this._inlineFiller && !this._isSelectionInInlineFiller() ) {
|
|
216
|
+
this._removeInlineFiller();
|
|
217
|
+
}
|
|
185
218
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
219
|
+
// If we've got the filler, let's try to guess its position in the view.
|
|
220
|
+
if ( this._inlineFiller ) {
|
|
221
|
+
inlineFillerPosition = this._getInlineFillerPosition();
|
|
222
|
+
}
|
|
223
|
+
// Otherwise, if it's needed, create it at the selection position.
|
|
224
|
+
else if ( this._needsInlineFillerAtSelection() ) {
|
|
225
|
+
inlineFillerPosition = this.selection.getFirstPosition();
|
|
193
226
|
|
|
194
|
-
|
|
195
|
-
|
|
227
|
+
// Do not use `markToSync` so it will be added even if the parent is already added.
|
|
228
|
+
this.markedChildren.add( inlineFillerPosition.parent );
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// Paranoid check: we make sure the inline filler has any parent so it can be mapped to view position
|
|
232
|
+
// by DomConverter.
|
|
233
|
+
else if ( this._inlineFiller && this._inlineFiller.parentNode ) {
|
|
234
|
+
// While the user is making selection, preserve the inline filler at its original position.
|
|
235
|
+
inlineFillerPosition = this.domConverter.domPositionToView( this._inlineFiller );
|
|
196
236
|
}
|
|
197
237
|
|
|
198
238
|
for ( const element of this.markedAttributes ) {
|
|
@@ -209,26 +249,30 @@ export default class Renderer {
|
|
|
209
249
|
}
|
|
210
250
|
}
|
|
211
251
|
|
|
212
|
-
// Check whether the inline filler is required and where it really is in the DOM.
|
|
213
|
-
//
|
|
214
|
-
//
|
|
215
|
-
//
|
|
216
|
-
//
|
|
217
|
-
//
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
252
|
+
// * Check whether the inline filler is required and where it really is in the DOM.
|
|
253
|
+
// At this point in most cases it will be in the DOM, but there are exceptions.
|
|
254
|
+
// For example, if the inline filler was deep in the created DOM structure, it will not be created.
|
|
255
|
+
// Similarly, if it was removed at the beginning of this function and then neither text nor children were updated,
|
|
256
|
+
// it will not be present. Fix those and similar scenarios.
|
|
257
|
+
// * Don't manipulate inline fillers while the selection is being made in (non-Android) Blink to prevent accidental
|
|
258
|
+
// DOM selection collapsing
|
|
259
|
+
// (https://github.com/ckeditor/ckeditor5/issues/10562, https://github.com/ckeditor/ckeditor5/issues/10723).
|
|
260
|
+
if ( isInlineFillerRenderingPossible ) {
|
|
261
|
+
if ( inlineFillerPosition ) {
|
|
262
|
+
const fillerDomPosition = this.domConverter.viewPositionToDom( inlineFillerPosition );
|
|
263
|
+
const domDocument = fillerDomPosition.parent.ownerDocument;
|
|
264
|
+
|
|
265
|
+
if ( !startsWithFiller( fillerDomPosition.parent ) ) {
|
|
266
|
+
// Filler has not been created at filler position. Create it now.
|
|
267
|
+
this._inlineFiller = addInlineFiller( domDocument, fillerDomPosition.parent, fillerDomPosition.offset );
|
|
268
|
+
} else {
|
|
269
|
+
// Filler has been found, save it.
|
|
270
|
+
this._inlineFiller = fillerDomPosition.parent;
|
|
271
|
+
}
|
|
225
272
|
} else {
|
|
226
|
-
//
|
|
227
|
-
this._inlineFiller =
|
|
273
|
+
// There is no filler needed.
|
|
274
|
+
this._inlineFiller = null;
|
|
228
275
|
}
|
|
229
|
-
} else {
|
|
230
|
-
// There is no filler needed.
|
|
231
|
-
this._inlineFiller = null;
|
|
232
276
|
}
|
|
233
277
|
|
|
234
278
|
// First focus the new editing host, then update the selection.
|
|
@@ -261,8 +305,8 @@ export default class Renderer {
|
|
|
261
305
|
|
|
262
306
|
// Removing nodes from the DOM as we iterate can cause `actualDomChildren`
|
|
263
307
|
// (which is a live-updating `NodeList`) to get out of sync with the
|
|
264
|
-
// indices that we compute as we iterate over `actions
|
|
265
|
-
// incorrect element mappings.
|
|
308
|
+
// indices that we compute as we iterate over `actions`.
|
|
309
|
+
// This would produce incorrect element mappings.
|
|
266
310
|
//
|
|
267
311
|
// Converting live list to an array to make the list static.
|
|
268
312
|
const actualDomChildren = Array.from(
|
|
@@ -401,7 +445,7 @@ export default class Renderer {
|
|
|
401
445
|
}
|
|
402
446
|
|
|
403
447
|
if ( isInlineFiller( domFillerNode ) ) {
|
|
404
|
-
domFillerNode.
|
|
448
|
+
domFillerNode.remove();
|
|
405
449
|
} else {
|
|
406
450
|
domFillerNode.data = domFillerNode.data.substr( INLINE_FILLER_LENGTH );
|
|
407
451
|
}
|
|
@@ -511,11 +555,23 @@ export default class Renderer {
|
|
|
511
555
|
|
|
512
556
|
// Add or overwrite attributes.
|
|
513
557
|
for ( const key of viewAttrKeys ) {
|
|
514
|
-
|
|
558
|
+
const value = viewElement.getAttribute( key );
|
|
559
|
+
|
|
560
|
+
if ( !this.domConverter.shouldRenderAttribute( key, value ) ) {
|
|
561
|
+
domElement.removeAttribute( key );
|
|
562
|
+
} else {
|
|
563
|
+
domElement.setAttribute( key, value );
|
|
564
|
+
}
|
|
515
565
|
}
|
|
516
566
|
|
|
517
567
|
// Remove from DOM attributes which do not exists in the view.
|
|
518
568
|
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
|
+
// All other attributes not present in the DOM should be removed.
|
|
519
575
|
if ( !viewElement.hasAttribute( key ) ) {
|
|
520
576
|
domElement.removeAttribute( key );
|
|
521
577
|
}
|
|
@@ -543,7 +599,7 @@ export default class Renderer {
|
|
|
543
599
|
const inlineFillerPosition = options.inlineFillerPosition;
|
|
544
600
|
const actualDomChildren = this.domConverter.mapViewToDom( viewElement ).childNodes;
|
|
545
601
|
const expectedDomChildren = Array.from(
|
|
546
|
-
this.domConverter.viewChildrenToDom( viewElement, domElement.ownerDocument, { bind: true
|
|
602
|
+
this.domConverter.viewChildrenToDom( viewElement, domElement.ownerDocument, { bind: true } )
|
|
547
603
|
);
|
|
548
604
|
|
|
549
605
|
// Inline filler element has to be created as it is present in the DOM, but not in the view. It is required
|
|
@@ -684,6 +740,14 @@ export default class Renderer {
|
|
|
684
740
|
* @private
|
|
685
741
|
*/
|
|
686
742
|
_updateSelection() {
|
|
743
|
+
// Block updating DOM selection in (non-Android) Blink while the user is selecting to prevent accidental selection collapsing.
|
|
744
|
+
// 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).
|
|
746
|
+
// https://github.com/ckeditor/ckeditor5/issues/10562, https://github.com/ckeditor/ckeditor5/issues/10723
|
|
747
|
+
if ( env.isBlink && !env.isAndroid && this.isSelecting && !this.markedChildren.size ) {
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
|
|
687
751
|
// If there is no selection - remove DOM and fake selections.
|
|
688
752
|
if ( this.selection.rangeCount === 0 ) {
|
|
689
753
|
this._removeDomSelection();
|
|
@@ -239,26 +239,26 @@ function normalizeBorderShorthand( string ) {
|
|
|
239
239
|
//
|
|
240
240
|
// It tries to produce the most optimal output for the specified styles.
|
|
241
241
|
//
|
|
242
|
-
// For border style:
|
|
242
|
+
// For a border style:
|
|
243
243
|
//
|
|
244
244
|
// style: {top: "solid", bottom: "solid", right: "solid", left: "solid"}
|
|
245
245
|
//
|
|
246
|
-
// It will produce: `border-style: solid
|
|
247
|
-
// For border style and color:
|
|
246
|
+
// It will produce: `border-style: solid`.
|
|
247
|
+
// For a border style and color:
|
|
248
248
|
//
|
|
249
249
|
// color: {top: "#ff0", bottom: "#ff0", right: "#ff0", left: "#ff0"}
|
|
250
250
|
// style: {top: "solid", bottom: "solid", right: "solid", left: "solid"}
|
|
251
251
|
//
|
|
252
|
-
// It will produce: `border-color: #ff0; border-style: solid
|
|
252
|
+
// It will produce: `border-color: #ff0; border-style: solid`.
|
|
253
253
|
// If all border parameters are specified:
|
|
254
254
|
//
|
|
255
255
|
// color: {top: "#ff0", bottom: "#ff0", right: "#ff0", left: "#ff0"}
|
|
256
256
|
// style: {top: "solid", bottom: "solid", right: "solid", left: "solid"}
|
|
257
257
|
// width: {top: "2px", bottom: "2px", right: "2px", left: "2px"}
|
|
258
258
|
//
|
|
259
|
-
// It will combine everything into
|
|
259
|
+
// It will combine everything into a single property: `border: 2px solid #ff0`.
|
|
260
260
|
//
|
|
261
|
-
//
|
|
261
|
+
// The definitions are merged only if all border selectors have the same values.
|
|
262
262
|
//
|
|
263
263
|
// @returns {Function}
|
|
264
264
|
function getBorderReducer() {
|
|
@@ -295,7 +295,7 @@ function getBorderReducer() {
|
|
|
295
295
|
return reducedStyleTypes;
|
|
296
296
|
}, [] );
|
|
297
297
|
|
|
298
|
-
// The reduced properties (by type) and all
|
|
298
|
+
// The reduced properties (by type) and all that remains that could not be reduced.
|
|
299
299
|
return [
|
|
300
300
|
...reducedStyleTypes,
|
|
301
301
|
...reduceBorderPosition( topStyles, 'top' ),
|
|
@@ -318,10 +318,10 @@ function getBorderPositionReducer( which ) {
|
|
|
318
318
|
return value => reduceBorderPosition( value, which );
|
|
319
319
|
}
|
|
320
320
|
|
|
321
|
-
// Returns an array with reduced border styles depending on specified values.
|
|
321
|
+
// Returns an array with reduced border styles depending on the specified values.
|
|
322
322
|
//
|
|
323
|
-
// If all (width, style, color)
|
|
324
|
-
// merged into
|
|
323
|
+
// If all border properties (width, style, color) are specified, the returned selector will be
|
|
324
|
+
// merged into a group: `border-*: [width] [style] [color]`.
|
|
325
325
|
//
|
|
326
326
|
// Otherwise, the specific definitions will be returned: `border-(width|style|color)-*: [value]`.
|
|
327
327
|
//
|
package/src/view/styles/utils.js
CHANGED
|
@@ -36,6 +36,11 @@ const COLOR_NAMES = new Set( [
|
|
|
36
36
|
'papayawhip', 'peachpuff', 'peru', 'pink', 'plum', 'powderblue', 'rosybrown', 'royalblue', 'saddlebrown', 'salmon',
|
|
37
37
|
'sandybrown', 'seagreen', 'seashell', 'sienna', 'skyblue', 'slateblue', 'slategray', 'slategrey', 'snow',
|
|
38
38
|
'springgreen', 'steelblue', 'tan', 'thistle', 'tomato', 'turquoise', 'violet', 'wheat', 'whitesmoke', 'yellowgreen',
|
|
39
|
+
// CSS Color Module Level 3 (System Colors)
|
|
40
|
+
'activeborder', 'activecaption', 'appworkspace', 'background', 'buttonface', 'buttonhighlight', 'buttonshadow',
|
|
41
|
+
'buttontext', 'captiontext', 'graytext', 'highlight', 'highlighttext', 'inactiveborder', 'inactivecaption',
|
|
42
|
+
'inactivecaptiontext', 'infobackground', 'infotext', 'menu', 'menutext', 'scrollbar', 'threeddarkshadow',
|
|
43
|
+
'threedface', 'threedhighlight', 'threedlightshadow', 'threedshadow', 'window', 'windowframe', 'windowtext',
|
|
39
44
|
// CSS Color Module Level 4
|
|
40
45
|
'rebeccapurple',
|
|
41
46
|
// Keywords
|
package/src/view/uielement.js
CHANGED
|
@@ -126,9 +126,10 @@ export default class UIElement extends Element {
|
|
|
126
126
|
* Do not use inheritance to create custom rendering method, replace `render()` method instead:
|
|
127
127
|
*
|
|
128
128
|
* const myUIElement = downcastWriter.createUIElement( 'span' );
|
|
129
|
-
* myUIElement.render = function( domDocument ) {
|
|
129
|
+
* myUIElement.render = function( domDocument, domConverter ) {
|
|
130
130
|
* const domElement = this.toDomElement( domDocument );
|
|
131
|
-
*
|
|
131
|
+
*
|
|
132
|
+
* domConverter.setContentOf( domElement, '<b>this is ui element</b>' );
|
|
132
133
|
*
|
|
133
134
|
* return domElement;
|
|
134
135
|
* };
|
|
@@ -138,9 +139,11 @@ export default class UIElement extends Element {
|
|
|
138
139
|
* after rendering your UI element.
|
|
139
140
|
*
|
|
140
141
|
* @param {Document} domDocument
|
|
142
|
+
* @param {module:engine/view/domconverter~DomConverter} domConverter Instance of the DomConverter used to optimize the output.
|
|
141
143
|
* @returns {HTMLElement}
|
|
142
144
|
*/
|
|
143
145
|
render( domDocument ) {
|
|
146
|
+
// Provide basic, default output.
|
|
144
147
|
return this.toDomElement( domDocument );
|
|
145
148
|
}
|
|
146
149
|
|
package/src/view/view.js
CHANGED
|
@@ -116,7 +116,7 @@ export default class View {
|
|
|
116
116
|
* @type {module:engine/view/renderer~Renderer}
|
|
117
117
|
*/
|
|
118
118
|
this._renderer = new Renderer( this.domConverter, this.document.selection );
|
|
119
|
-
this._renderer.bind( 'isFocused' ).to( this.document );
|
|
119
|
+
this._renderer.bind( 'isFocused', 'isSelecting' ).to( this.document );
|
|
120
120
|
|
|
121
121
|
/**
|
|
122
122
|
* A DOM root attributes cache. It saves the initial values of DOM root attributes before the DOM element
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* 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
|
+
/* Elements marked by the Renderer as hidden should be invisible in the editor. */
|
|
7
|
+
.ck.ck-editor__editable span[data-ck-hidden] {
|
|
8
|
+
display: none;
|
|
9
|
+
}
|