@ckeditor/ckeditor5-typing 35.2.1 → 35.3.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 +31 -18
- package/src/delete.js +68 -123
- package/src/deletecommand.js +205 -242
- package/src/deleteobserver.js +205 -112
- package/src/index.js +0 -6
- package/src/input.js +125 -28
- package/src/inserttextcommand.js +96 -0
- package/src/inserttextobserver.js +104 -0
- package/src/texttransformation.js +174 -384
- package/src/textwatcher.js +131 -171
- package/src/twostepcaretmovement.js +300 -341
- package/src/typing.js +9 -43
- package/src/typingconfig.js +5 -0
- package/src/utils/changebuffer.js +142 -151
- package/src/utils/findattributerange.js +12 -24
- package/src/utils/getlasttextline.js +11 -29
- package/src/utils/inlinehighlight.js +38 -52
- package/src/inputcommand.js +0 -100
- package/src/utils/injecttypingmutationshandling.js +0 -331
- package/src/utils/injectunsafekeystrokeshandling.js +0 -189
- package/src/utils/utils.js +0 -104
package/src/typing.js
CHANGED
|
@@ -2,15 +2,12 @@
|
|
|
2
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
|
-
|
|
6
5
|
/**
|
|
7
6
|
* @module typing/typing
|
|
8
7
|
*/
|
|
9
|
-
|
|
10
8
|
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
|
|
11
9
|
import Input from './input';
|
|
12
10
|
import Delete from './delete';
|
|
13
|
-
|
|
14
11
|
/**
|
|
15
12
|
* The typing feature. It handles typing.
|
|
16
13
|
*
|
|
@@ -20,44 +17,13 @@ import Delete from './delete';
|
|
|
20
17
|
* @extends module:core/plugin~Plugin
|
|
21
18
|
*/
|
|
22
19
|
export default class Typing extends Plugin {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
20
|
+
static get requires() {
|
|
21
|
+
return [Input, Delete];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* @inheritDoc
|
|
25
|
+
*/
|
|
26
|
+
static get pluginName() {
|
|
27
|
+
return 'Typing';
|
|
28
|
+
}
|
|
33
29
|
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* The configuration of the typing features. Used by the features from the `@ckeditor/ckeditor5-typing` package.
|
|
37
|
-
*
|
|
38
|
-
* Read more in {@link module:typing/typing~TypingConfig}.
|
|
39
|
-
*
|
|
40
|
-
* @member {module:typing/typing~TypingConfig} module:core/editor/editorconfig~EditorConfig#typing
|
|
41
|
-
*/
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* The configuration of the typing features. Used by the typing features in `@ckeditor/ckeditor5-typing` package.
|
|
45
|
-
*
|
|
46
|
-
* ClassicEditor
|
|
47
|
-
* .create( editorElement, {
|
|
48
|
-
* typing: ... // Typing feature options.
|
|
49
|
-
* } )
|
|
50
|
-
* .then( ... )
|
|
51
|
-
* .catch( ... );
|
|
52
|
-
*
|
|
53
|
-
* See {@link module:core/editor/editorconfig~EditorConfig all editor options}.
|
|
54
|
-
*
|
|
55
|
-
* @interface TypingConfig
|
|
56
|
-
*/
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* The granularity of undo/redo for typing and deleting. The value `20` means (more or less) that a new undo step
|
|
60
|
-
* is created every 20 characters are inserted or deleted.
|
|
61
|
-
*
|
|
62
|
-
* @member {Number} [module:typing/typing~TypingConfig#undoStep=20]
|
|
63
|
-
*/
|
|
@@ -2,11 +2,6 @@
|
|
|
2
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
|
-
|
|
6
|
-
/**
|
|
7
|
-
* @module typing/utils/changebuffer
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
5
|
/**
|
|
11
6
|
* Change buffer allows to group atomic changes (like characters that have been typed) into
|
|
12
7
|
* {@link module:engine/model/batch~Batch batches}.
|
|
@@ -26,150 +21,146 @@
|
|
|
26
21
|
*
|
|
27
22
|
*/
|
|
28
23
|
export default class ChangeBuffer {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
this._batch = null;
|
|
172
|
-
this.size = 0;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
24
|
+
/**
|
|
25
|
+
* Creates a new instance of the change buffer.
|
|
26
|
+
*
|
|
27
|
+
* @param {module:engine/model/model~Model} model
|
|
28
|
+
* @param {Number} [limit=20] The maximum number of atomic changes which can be contained in one batch.
|
|
29
|
+
*/
|
|
30
|
+
constructor(model, limit = 20) {
|
|
31
|
+
this._batch = null;
|
|
32
|
+
/**
|
|
33
|
+
* The model instance.
|
|
34
|
+
*
|
|
35
|
+
* @readonly
|
|
36
|
+
* @member {module:engine/model/model~Model} #model
|
|
37
|
+
*/
|
|
38
|
+
this.model = model;
|
|
39
|
+
/**
|
|
40
|
+
* The number of atomic changes in the buffer. Once it exceeds the {@link #limit},
|
|
41
|
+
* the {@link #batch batch} is set to a new one.
|
|
42
|
+
*
|
|
43
|
+
* @readonly
|
|
44
|
+
* @member {Number} #size
|
|
45
|
+
*/
|
|
46
|
+
this._size = 0;
|
|
47
|
+
/**
|
|
48
|
+
* The maximum number of atomic changes which can be contained in one batch.
|
|
49
|
+
*
|
|
50
|
+
* @readonly
|
|
51
|
+
* @member {Number} #limit
|
|
52
|
+
*/
|
|
53
|
+
this.limit = limit;
|
|
54
|
+
/**
|
|
55
|
+
* Whether the buffer is locked. A locked buffer cannot be reset unless it gets unlocked.
|
|
56
|
+
*
|
|
57
|
+
* @readonly
|
|
58
|
+
* @member {Boolean} #isLocked
|
|
59
|
+
*/
|
|
60
|
+
this._isLocked = false;
|
|
61
|
+
// The function to be called in order to notify the buffer about batches which appeared in the document.
|
|
62
|
+
// The callback will check whether it is a new batch and in that case the buffer will be flushed.
|
|
63
|
+
//
|
|
64
|
+
// The reason why the buffer needs to be flushed whenever a new batch appears is that the changes added afterwards
|
|
65
|
+
// should be added to a new batch. For instance, when the user types, then inserts an image, and then types again,
|
|
66
|
+
// the characters typed after inserting the image should be added to a different batch than the characters typed before.
|
|
67
|
+
this._changeCallback = (evt, batch) => {
|
|
68
|
+
if (batch.isLocal && batch.isUndoable && batch !== this._batch) {
|
|
69
|
+
this._reset(true);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
this._selectionChangeCallback = () => {
|
|
73
|
+
this._reset();
|
|
74
|
+
};
|
|
75
|
+
this.model.document.on('change', this._changeCallback);
|
|
76
|
+
this.model.document.selection.on('change:range', this._selectionChangeCallback);
|
|
77
|
+
this.model.document.selection.on('change:attribute', this._selectionChangeCallback);
|
|
78
|
+
/**
|
|
79
|
+
* The current batch instance.
|
|
80
|
+
*
|
|
81
|
+
* @private
|
|
82
|
+
* @member #_batch
|
|
83
|
+
*/
|
|
84
|
+
/**
|
|
85
|
+
* The callback to document the change event which later needs to be removed.
|
|
86
|
+
*
|
|
87
|
+
* @private
|
|
88
|
+
* @member #_changeCallback
|
|
89
|
+
*/
|
|
90
|
+
/**
|
|
91
|
+
* The callback to document selection `change:attribute` and `change:range` events which resets the buffer.
|
|
92
|
+
*
|
|
93
|
+
* @private
|
|
94
|
+
* @member #_selectionChangeCallback
|
|
95
|
+
*/
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* The current batch to which a feature should add its operations. Once the {@link #size}
|
|
99
|
+
* is reached or exceeds the {@link #limit}, the batch is set to a new instance and the size is reset.
|
|
100
|
+
*
|
|
101
|
+
* @type {module:engine/model/batch~Batch}
|
|
102
|
+
*/
|
|
103
|
+
get batch() {
|
|
104
|
+
if (!this._batch) {
|
|
105
|
+
this._batch = this.model.createBatch({ isTyping: true });
|
|
106
|
+
}
|
|
107
|
+
return this._batch;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* The number of atomic changes in the buffer. Once it exceeds the {@link #limit},
|
|
111
|
+
* the {@link #batch batch} is set to a new one.
|
|
112
|
+
*/
|
|
113
|
+
get size() {
|
|
114
|
+
return this._size;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* The input number of changes into the buffer. Once the {@link #size} is
|
|
118
|
+
* reached or exceeds the {@link #limit}, the batch is set to a new instance and the size is reset.
|
|
119
|
+
*
|
|
120
|
+
* @param {Number} changeCount The number of atomic changes to input.
|
|
121
|
+
*/
|
|
122
|
+
input(changeCount) {
|
|
123
|
+
this._size += changeCount;
|
|
124
|
+
if (this._size >= this.limit) {
|
|
125
|
+
this._reset(true);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Whether the buffer is locked. A locked buffer cannot be reset unless it gets unlocked.
|
|
130
|
+
*/
|
|
131
|
+
get isLocked() {
|
|
132
|
+
return this._isLocked;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Locks the buffer.
|
|
136
|
+
*/
|
|
137
|
+
lock() {
|
|
138
|
+
this._isLocked = true;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Unlocks the buffer.
|
|
142
|
+
*/
|
|
143
|
+
unlock() {
|
|
144
|
+
this._isLocked = false;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Destroys the buffer.
|
|
148
|
+
*/
|
|
149
|
+
destroy() {
|
|
150
|
+
this.model.document.off('change', this._changeCallback);
|
|
151
|
+
this.model.document.selection.off('change:range', this._selectionChangeCallback);
|
|
152
|
+
this.model.document.selection.off('change:attribute', this._selectionChangeCallback);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Resets the change buffer.
|
|
156
|
+
*
|
|
157
|
+
* @private
|
|
158
|
+
* @param {Boolean} [ignoreLock] Whether internal lock {@link #isLocked} should be ignored.
|
|
159
|
+
*/
|
|
160
|
+
_reset(ignoreLock = false) {
|
|
161
|
+
if (!this.isLocked || ignoreLock) {
|
|
162
|
+
this._batch = null;
|
|
163
|
+
this._size = 0;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
175
166
|
}
|
|
@@ -2,11 +2,6 @@
|
|
|
2
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
|
-
|
|
6
|
-
/**
|
|
7
|
-
* @module typing/utils/findattributerange
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
5
|
/**
|
|
11
6
|
* Returns a model range that covers all consecutive nodes with the same `attributeName` and its `value`
|
|
12
7
|
* that intersect the given `position`.
|
|
@@ -20,13 +15,9 @@
|
|
|
20
15
|
* @param {module:engine/model/model~Model} model The model instance.
|
|
21
16
|
* @returns {module:engine/model/range~Range} The link range.
|
|
22
17
|
*/
|
|
23
|
-
export default function findAttributeRange(
|
|
24
|
-
|
|
25
|
-
_findBound( position, attributeName, value, true, model ),
|
|
26
|
-
_findBound( position, attributeName, value, false, model )
|
|
27
|
-
);
|
|
18
|
+
export default function findAttributeRange(position, attributeName, value, model) {
|
|
19
|
+
return model.createRange(_findBound(position, attributeName, value, true, model), _findBound(position, attributeName, value, false, model));
|
|
28
20
|
}
|
|
29
|
-
|
|
30
21
|
// Walks forward or backward (depends on the `lookBack` flag), node by node, as long as they have the same attribute value
|
|
31
22
|
// and returns a position just before or after (depends on the `lookBack` flag) the last matched node.
|
|
32
23
|
//
|
|
@@ -35,17 +26,14 @@ export default function findAttributeRange( position, attributeName, value, mode
|
|
|
35
26
|
// @param {String} value The attribute value.
|
|
36
27
|
// @param {Boolean} lookBack Whether the walk direction is forward (`false`) or backward (`true`).
|
|
37
28
|
// @returns {module:engine/model/position~Position} The position just before the last matched node.
|
|
38
|
-
function _findBound(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return lastNode ? model.createPositionAt( lastNode, lookBack ? 'before' : 'after' ) : position;
|
|
29
|
+
function _findBound(position, attributeName, value, lookBack, model) {
|
|
30
|
+
// Get node before or after position (depends on `lookBack` flag).
|
|
31
|
+
// When position is inside text node then start searching from text node.
|
|
32
|
+
let node = position.textNode || (lookBack ? position.nodeBefore : position.nodeAfter);
|
|
33
|
+
let lastNode = null;
|
|
34
|
+
while (node && node.getAttribute(attributeName) == value) {
|
|
35
|
+
lastNode = node;
|
|
36
|
+
node = lookBack ? node.previousSibling : node.nextSibling;
|
|
37
|
+
}
|
|
38
|
+
return lastNode ? model.createPositionAt(lastNode, lookBack ? 'before' : 'after') : position;
|
|
51
39
|
}
|
|
@@ -2,11 +2,6 @@
|
|
|
2
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
|
-
|
|
6
|
-
/**
|
|
7
|
-
* @module typing/utils/getlasttextline
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
5
|
/**
|
|
11
6
|
* Returns the last text line from the given range.
|
|
12
7
|
*
|
|
@@ -33,28 +28,15 @@
|
|
|
33
28
|
* @param {module:engine/model/model~Model} model
|
|
34
29
|
* @returns {module:typing/utils/getlasttextline~LastTextLineData}
|
|
35
30
|
*/
|
|
36
|
-
export default function getLastTextLine(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
return rangeText + node.data;
|
|
48
|
-
}, '' );
|
|
49
|
-
|
|
50
|
-
return { text, range: model.createRange( start, range.end ) };
|
|
31
|
+
export default function getLastTextLine(range, model) {
|
|
32
|
+
let start = range.start;
|
|
33
|
+
const text = Array.from(range.getItems()).reduce((rangeText, node) => {
|
|
34
|
+
// Trim text to a last occurrence of an inline element and update range start.
|
|
35
|
+
if (!(node.is('$text') || node.is('$textProxy'))) {
|
|
36
|
+
start = model.createPositionAfter(node);
|
|
37
|
+
return '';
|
|
38
|
+
}
|
|
39
|
+
return rangeText + node.data;
|
|
40
|
+
}, '');
|
|
41
|
+
return { text, range: model.createRange(start, range.end) };
|
|
51
42
|
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* The value returned by {@link module:typing/utils/getlasttextline~getLastTextLine}.
|
|
55
|
-
*
|
|
56
|
-
* @typedef {Object} module:typing/utils/getlasttextline~LastTextLineData
|
|
57
|
-
*
|
|
58
|
-
* @property {String} text The text from the text nodes in the last text line.
|
|
59
|
-
* @property {module:engine/model/range~Range} range The range set on the text nodes in the last text line.
|
|
60
|
-
*/
|
|
@@ -2,13 +2,10 @@
|
|
|
2
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
|
-
|
|
6
5
|
import findAttributeRange from './findattributerange';
|
|
7
|
-
|
|
8
6
|
/**
|
|
9
7
|
* @module typing/utils/inlinehighlight
|
|
10
8
|
*/
|
|
11
|
-
|
|
12
9
|
/**
|
|
13
10
|
* Adds a visual highlight style to an attribute element in which the selection is anchored.
|
|
14
11
|
* Together with two-step caret movement, they indicate that the user is typing inside the element.
|
|
@@ -34,53 +31,42 @@ import findAttributeRange from './findattributerange';
|
|
|
34
31
|
* @param {String} tagName The tagName of a view item.
|
|
35
32
|
* @param {String} className The class name to apply in the view.
|
|
36
33
|
*/
|
|
37
|
-
export default function inlineHighlight(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
dispatcher.on( 'selection', removeHighlight, { priority: 'highest' } );
|
|
76
|
-
|
|
77
|
-
function removeHighlight() {
|
|
78
|
-
view.change( writer => {
|
|
79
|
-
for ( const item of highlightedElements.values() ) {
|
|
80
|
-
writer.removeClass( className, item );
|
|
81
|
-
highlightedElements.delete( item );
|
|
82
|
-
}
|
|
83
|
-
} );
|
|
84
|
-
}
|
|
85
|
-
} );
|
|
34
|
+
export default function inlineHighlight(editor, attributeName, tagName, className) {
|
|
35
|
+
const view = editor.editing.view;
|
|
36
|
+
const highlightedElements = new Set();
|
|
37
|
+
// Adding the class.
|
|
38
|
+
view.document.registerPostFixer(writer => {
|
|
39
|
+
const selection = editor.model.document.selection;
|
|
40
|
+
let changed = false;
|
|
41
|
+
if (selection.hasAttribute(attributeName)) {
|
|
42
|
+
const modelRange = findAttributeRange(selection.getFirstPosition(), attributeName, selection.getAttribute(attributeName), editor.model);
|
|
43
|
+
const viewRange = editor.editing.mapper.toViewRange(modelRange);
|
|
44
|
+
// There might be multiple view elements in the `viewRange`, for example, when the `a` element is
|
|
45
|
+
// broken by a UIElement.
|
|
46
|
+
for (const item of viewRange.getItems()) {
|
|
47
|
+
if (item.is('element', tagName) && !item.hasClass(className)) {
|
|
48
|
+
writer.addClass(className, item);
|
|
49
|
+
highlightedElements.add(item);
|
|
50
|
+
changed = true;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return changed;
|
|
55
|
+
});
|
|
56
|
+
// Removing the class.
|
|
57
|
+
editor.conversion.for('editingDowncast').add(dispatcher => {
|
|
58
|
+
// Make sure the highlight is removed on every possible event, before conversion is started.
|
|
59
|
+
dispatcher.on('insert', removeHighlight, { priority: 'highest' });
|
|
60
|
+
dispatcher.on('remove', removeHighlight, { priority: 'highest' });
|
|
61
|
+
dispatcher.on('attribute', removeHighlight, { priority: 'highest' });
|
|
62
|
+
dispatcher.on('selection', removeHighlight, { priority: 'highest' });
|
|
63
|
+
function removeHighlight() {
|
|
64
|
+
view.change(writer => {
|
|
65
|
+
for (const item of highlightedElements.values()) {
|
|
66
|
+
writer.removeClass(className, item);
|
|
67
|
+
highlightedElements.delete(item);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
});
|
|
86
72
|
}
|