@ckeditor/ckeditor5-restricted-editing 48.2.0-alpha.7 → 48.3.0-alpha.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/dist/augmentation.d.ts +29 -29
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +19 -19
- package/dist/index.js +1346 -1545
- package/dist/index.js.map +1 -1
- package/dist/restrictededitingconfig.d.ts +58 -58
- package/dist/restrictededitingexceptionautocommand.d.ts +34 -34
- package/dist/restrictededitingexceptionblockcommand.d.ts +52 -52
- package/dist/restrictededitingexceptioncommand.d.ts +23 -23
- package/dist/restrictededitingmode/converters.d.ts +36 -36
- package/dist/restrictededitingmode/utils.d.ts +32 -32
- package/dist/restrictededitingmode.d.ts +28 -28
- package/dist/restrictededitingmodeediting.d.ts +95 -95
- package/dist/restrictededitingmodenavigationcommand.d.ts +37 -37
- package/dist/restrictededitingmodeui.d.ts +41 -41
- package/dist/standardeditingmode.d.ts +25 -25
- package/dist/standardeditingmodeediting.d.ts +24 -24
- package/dist/standardeditingmodeui.d.ts +26 -26
- package/package.json +8 -8
package/dist/index.js
CHANGED
|
@@ -2,1616 +2,1417 @@
|
|
|
2
2
|
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
3
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
4
4
|
*/
|
|
5
|
-
import { Command, Plugin } from
|
|
6
|
-
import {
|
|
7
|
-
import { Matcher } from
|
|
8
|
-
import { IconContentLock, IconContentUnlock } from
|
|
9
|
-
import {
|
|
5
|
+
import { Command, Plugin } from "@ckeditor/ckeditor5-core";
|
|
6
|
+
import { Collection, count, first, getCode, parseKeystroke } from "@ckeditor/ckeditor5-utils";
|
|
7
|
+
import { Matcher } from "@ckeditor/ckeditor5-engine";
|
|
8
|
+
import { IconContentLock, IconContentUnlock } from "@ckeditor/ckeditor5-icons";
|
|
9
|
+
import { ButtonView, MenuBarMenuListItemButtonView, MenuBarMenuListItemView, MenuBarMenuListView, MenuBarMenuView, UIModel, addListToDropdown, addToolbarToDropdown, createDropdown } from "@ckeditor/ckeditor5-ui";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if (marker.name.startsWith('restrictedEditingException:')) {
|
|
27
|
-
return marker;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
12
|
+
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
13
|
+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Returns a single "restricted-editing-exception" marker at a given position. Contrary to
|
|
17
|
+
* {@link module:engine/model/markercollection~MarkerCollection#getMarkersAtPosition}, it returns a marker also when the position is
|
|
18
|
+
* equal to one of the marker's start or end positions.
|
|
19
|
+
*
|
|
20
|
+
* @internal
|
|
21
|
+
*/
|
|
22
|
+
function getMarkerAtPosition(editor, position) {
|
|
23
|
+
for (const marker of editor.model.markers) if (isPositionInRangeBoundaries(getExceptionRange(marker, editor.model), position)) {
|
|
24
|
+
if (marker.name.startsWith("restrictedEditingException:")) return marker;
|
|
25
|
+
}
|
|
31
26
|
}
|
|
32
27
|
/**
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
28
|
+
* Checks if the position is fully contained in the range. Positions equal to range start or end are considered "in".
|
|
29
|
+
*
|
|
30
|
+
* @internal
|
|
31
|
+
*/
|
|
32
|
+
function isPositionInRangeBoundaries(range, position) {
|
|
33
|
+
return range.containsPosition(position) || range.end.isEqual(position) || range.start.isEqual(position);
|
|
38
34
|
}
|
|
39
35
|
/**
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
return isPositionInRangeBoundaries(markerRange, selection.focus);
|
|
57
|
-
}
|
|
58
|
-
return markerRange.containsRange(selection.getFirstRange(), true);
|
|
36
|
+
* Checks if the selection is fully contained in the marker. Positions on marker boundaries are considered "in".
|
|
37
|
+
*
|
|
38
|
+
* ```xml
|
|
39
|
+
* <marker>[]foo</marker> -> true
|
|
40
|
+
* <marker>f[oo]</marker> -> true
|
|
41
|
+
* <marker>f[oo</marker> ba]r -> false
|
|
42
|
+
* <marker>foo</marker> []bar -> false
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* @internal
|
|
46
|
+
*/
|
|
47
|
+
function isSelectionInMarker(selection, model, marker) {
|
|
48
|
+
if (!marker) return false;
|
|
49
|
+
const markerRange = getExceptionRange(marker, model);
|
|
50
|
+
if (selection.isCollapsed) return isPositionInRangeBoundaries(markerRange, selection.focus);
|
|
51
|
+
return markerRange.containsRange(selection.getFirstRange(), true);
|
|
59
52
|
}
|
|
60
53
|
/**
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
return markerRange;
|
|
54
|
+
* Returns the marker range asjusted to the inside of exception wrapper element if needed.
|
|
55
|
+
*
|
|
56
|
+
* @internal
|
|
57
|
+
*/
|
|
58
|
+
function getExceptionRange(marker, model) {
|
|
59
|
+
const markerRange = marker.getRange();
|
|
60
|
+
const wrapperElement = markerRange.getContainedElement();
|
|
61
|
+
if (wrapperElement && wrapperElement.is("element", "restrictedEditingException")) return model.createRangeIn(wrapperElement);
|
|
62
|
+
return markerRange;
|
|
71
63
|
}
|
|
72
64
|
|
|
73
65
|
/**
|
|
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
|
-
|
|
66
|
+
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
67
|
+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
68
|
+
*/
|
|
69
|
+
/**
|
|
70
|
+
* The command that allows navigation across the exceptions in the edited document.
|
|
71
|
+
*/
|
|
72
|
+
var RestrictedEditingModeNavigationCommand = class extends Command {
|
|
73
|
+
/**
|
|
74
|
+
* The direction of the command.
|
|
75
|
+
*/
|
|
76
|
+
_direction;
|
|
77
|
+
/**
|
|
78
|
+
* Creates an instance of the command.
|
|
79
|
+
*
|
|
80
|
+
* @param editor The editor instance.
|
|
81
|
+
* @param direction The direction that the command works.
|
|
82
|
+
*/
|
|
83
|
+
constructor(editor, direction) {
|
|
84
|
+
super(editor);
|
|
85
|
+
this.affectsData = false;
|
|
86
|
+
this._direction = direction;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* @inheritDoc
|
|
90
|
+
*/
|
|
91
|
+
refresh() {
|
|
92
|
+
this.isEnabled = this._checkEnabled();
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Executes the command.
|
|
96
|
+
*
|
|
97
|
+
* @fires execute
|
|
98
|
+
*/
|
|
99
|
+
execute() {
|
|
100
|
+
const position = getNearestExceptionRange(this.editor.model, this._direction);
|
|
101
|
+
if (!position) return;
|
|
102
|
+
this.editor.model.change((writer) => {
|
|
103
|
+
writer.setSelection(position);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Checks whether the command can be enabled in the current context.
|
|
108
|
+
*
|
|
109
|
+
* @returns Whether the command should be enabled.
|
|
110
|
+
*/
|
|
111
|
+
_checkEnabled() {
|
|
112
|
+
return !!getNearestExceptionRange(this.editor.model, this._direction);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
116
115
|
/**
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
if (direction === 'forward' && markerRange.start.isAfter(selectionPosition)) {
|
|
136
|
-
markerRanges.push(markerRange);
|
|
137
|
-
} else if (direction === 'backward' && markerRange.end.isBefore(selectionPosition)) {
|
|
138
|
-
markerRanges.push(markerRange);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
if (!markerRanges.length) {
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
// Get the marker closest to the selection position among many. To know that, we need to sort
|
|
145
|
-
// them first.
|
|
146
|
-
return markerRanges.sort((rangeA, rangeB)=>{
|
|
147
|
-
if (direction === 'forward') {
|
|
148
|
-
return rangeA.start.isAfter(rangeB.start) ? 1 : -1;
|
|
149
|
-
} else {
|
|
150
|
-
return rangeA.start.isBefore(rangeB.start) ? 1 : -1;
|
|
151
|
-
}
|
|
152
|
-
}).shift();
|
|
116
|
+
* Returns the range of the exception marker closest to the last position of the model selection.
|
|
117
|
+
*/
|
|
118
|
+
function getNearestExceptionRange(model, direction) {
|
|
119
|
+
const selectionPosition = model.document.selection.getFirstPosition();
|
|
120
|
+
const markerRanges = [];
|
|
121
|
+
for (const marker of model.markers.getMarkersGroup("restrictedEditingException")) {
|
|
122
|
+
const markerRange = getExceptionRange(marker, model);
|
|
123
|
+
const isMarkerRangeTouching = selectionPosition.isTouching(markerRange.start) && selectionPosition.hasSameParentAs(markerRange.start) || selectionPosition.isTouching(markerRange.end) && selectionPosition.hasSameParentAs(markerRange.end);
|
|
124
|
+
if (markerRange.containsPosition(selectionPosition) || isMarkerRangeTouching) continue;
|
|
125
|
+
if (direction === "forward" && markerRange.start.isAfter(selectionPosition)) markerRanges.push(markerRange);
|
|
126
|
+
else if (direction === "backward" && markerRange.end.isBefore(selectionPosition)) markerRanges.push(markerRange);
|
|
127
|
+
}
|
|
128
|
+
if (!markerRanges.length) return;
|
|
129
|
+
return markerRanges.sort((rangeA, rangeB) => {
|
|
130
|
+
if (direction === "forward") return rangeA.start.isAfter(rangeB.start) ? 1 : -1;
|
|
131
|
+
else return rangeA.start.isBefore(rangeB.start) ? 1 : -1;
|
|
132
|
+
}).shift();
|
|
153
133
|
}
|
|
154
134
|
|
|
155
|
-
const HIGHLIGHT_CLASS = 'restricted-editing-exception_selected';
|
|
156
135
|
/**
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
*
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
for (const item of highlightedMarkers.values()){
|
|
209
|
-
writer.removeClass(HIGHLIGHT_CLASS, item);
|
|
210
|
-
highlightedMarkers.delete(item);
|
|
211
|
-
}
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
});
|
|
136
|
+
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
137
|
+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
138
|
+
*/
|
|
139
|
+
const HIGHLIGHT_CLASS = "restricted-editing-exception_selected";
|
|
140
|
+
/**
|
|
141
|
+
* Adds a visual highlight style to a restricted editing exception that the selection is anchored to.
|
|
142
|
+
*
|
|
143
|
+
* The highlight is turned on by adding the `.restricted-editing-exception_selected` class to the
|
|
144
|
+
* exception in the view:
|
|
145
|
+
*
|
|
146
|
+
* * The class is removed before the conversion starts, as callbacks added with the `'highest'` priority
|
|
147
|
+
* to {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher} events.
|
|
148
|
+
* * The class is added in the view post-fixer, after other changes in the model tree are converted to the view.
|
|
149
|
+
*
|
|
150
|
+
* This way, adding and removing the highlight does not interfere with conversion.
|
|
151
|
+
*
|
|
152
|
+
* @internal
|
|
153
|
+
*/
|
|
154
|
+
function setupExceptionHighlighting(editor) {
|
|
155
|
+
const view = editor.editing.view;
|
|
156
|
+
const model = editor.model;
|
|
157
|
+
const highlightedMarkers = /* @__PURE__ */ new Set();
|
|
158
|
+
view.document.registerPostFixer((writer) => {
|
|
159
|
+
const modelSelection = model.document.selection;
|
|
160
|
+
const marker = getMarkerAtPosition(editor, modelSelection.anchor);
|
|
161
|
+
if (!marker) return false;
|
|
162
|
+
const modelWrapperElement = marker.getRange().getContainedElement();
|
|
163
|
+
if (modelWrapperElement && modelWrapperElement.is("element", "restrictedEditingException")) {
|
|
164
|
+
const viewElement = editor.editing.mapper.toViewElement(modelWrapperElement);
|
|
165
|
+
writer.addClass(HIGHLIGHT_CLASS, viewElement);
|
|
166
|
+
highlightedMarkers.add(viewElement);
|
|
167
|
+
} else for (const viewElement of editor.editing.mapper.markerNameToElements(marker.name)) {
|
|
168
|
+
writer.addClass(HIGHLIGHT_CLASS, viewElement);
|
|
169
|
+
highlightedMarkers.add(viewElement);
|
|
170
|
+
}
|
|
171
|
+
return false;
|
|
172
|
+
});
|
|
173
|
+
editor.conversion.for("editingDowncast").add((dispatcher) => {
|
|
174
|
+
dispatcher.on("insert", removeHighlight, { priority: "highest" });
|
|
175
|
+
dispatcher.on("remove", removeHighlight, { priority: "highest" });
|
|
176
|
+
dispatcher.on("attribute", removeHighlight, { priority: "highest" });
|
|
177
|
+
dispatcher.on("cleanSelection", removeHighlight);
|
|
178
|
+
function removeHighlight() {
|
|
179
|
+
view.change((writer) => {
|
|
180
|
+
for (const item of highlightedMarkers.values()) {
|
|
181
|
+
writer.removeClass(HIGHLIGHT_CLASS, item);
|
|
182
|
+
highlightedMarkers.delete(item);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
});
|
|
215
187
|
}
|
|
216
188
|
/**
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
return changeApplied;
|
|
233
|
-
};
|
|
189
|
+
* A post-fixer that prevents removing a collapsed marker from the document.
|
|
190
|
+
*
|
|
191
|
+
* @internal
|
|
192
|
+
*/
|
|
193
|
+
function resurrectCollapsedMarkerPostFixer(editor) {
|
|
194
|
+
return (writer) => {
|
|
195
|
+
let changeApplied = false;
|
|
196
|
+
for (const { name, data } of editor.model.document.differ.getChangedMarkers()) if (name.startsWith("restrictedEditingException") && data.newRange && data.newRange.root.rootName == "$graveyard") {
|
|
197
|
+
writer.updateMarker(name, { range: writer.createRange(writer.createPositionAt(data.oldRange.start)) });
|
|
198
|
+
changeApplied = true;
|
|
199
|
+
}
|
|
200
|
+
return changeApplied;
|
|
201
|
+
};
|
|
234
202
|
}
|
|
235
203
|
/**
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
return changeApplied;
|
|
251
|
-
};
|
|
204
|
+
* A post-fixer that extends a marker when the user types on its boundaries.
|
|
205
|
+
*
|
|
206
|
+
* @internal
|
|
207
|
+
*/
|
|
208
|
+
function extendMarkerOnTypingPostFixer(editor) {
|
|
209
|
+
return (writer) => {
|
|
210
|
+
let changeApplied = false;
|
|
211
|
+
const schema = editor.model.schema;
|
|
212
|
+
for (const change of editor.model.document.differ.getChanges()) if (change.type == "insert" && schema.checkChild("$block", change.name)) {
|
|
213
|
+
changeApplied = _tryExtendMarkerStart(editor, change.position, change.length, writer) || changeApplied;
|
|
214
|
+
changeApplied = _tryExtendMarkedEnd(editor, change.position, change.length, writer) || changeApplied;
|
|
215
|
+
}
|
|
216
|
+
return changeApplied;
|
|
217
|
+
};
|
|
252
218
|
}
|
|
253
219
|
/**
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
'data-name': markerName
|
|
291
|
-
});
|
|
292
|
-
if (wrapperElement) {
|
|
293
|
-
writer.insert(fakeMarkerStart, writer.createPositionBefore(wrapperElement));
|
|
294
|
-
writer.insert(fakeMarkerEnd, writer.createPositionAfter(wrapperElement));
|
|
295
|
-
} else {
|
|
296
|
-
// Insert in reverse order to use converter content positions directly (without recalculating).
|
|
297
|
-
writer.insert(fakeMarkerEnd, convertedChildrenRange.end);
|
|
298
|
-
writer.insert(fakeMarkerStart, convertedChildrenRange.start);
|
|
299
|
-
}
|
|
300
|
-
data.modelRange = writer.createRange(writer.createPositionBefore(fakeMarkerStart), writer.createPositionAfter(fakeMarkerEnd));
|
|
301
|
-
data.modelCursor = data.modelRange.end;
|
|
302
|
-
});
|
|
220
|
+
* A view highlight-to-marker conversion helper.
|
|
221
|
+
*
|
|
222
|
+
* @param config Conversion configuration.
|
|
223
|
+
* @internal
|
|
224
|
+
*/
|
|
225
|
+
function upcastHighlightToMarker(config) {
|
|
226
|
+
return (dispatcher) => dispatcher.on("element", (evt, data, conversionApi) => {
|
|
227
|
+
const { writer } = conversionApi;
|
|
228
|
+
const matcherResult = new Matcher(config.view).match(data.viewItem);
|
|
229
|
+
if (!matcherResult) return;
|
|
230
|
+
const match = matcherResult.match;
|
|
231
|
+
match.name = true;
|
|
232
|
+
if (!conversionApi.consumable.test(data.viewItem, match)) return;
|
|
233
|
+
let position = data.modelCursor;
|
|
234
|
+
let wrapperElement = null;
|
|
235
|
+
if (config.useWrapperElement) {
|
|
236
|
+
if (!conversionApi.schema.checkChild(position, "restrictedEditingException")) return;
|
|
237
|
+
wrapperElement = writer.createElement("restrictedEditingException");
|
|
238
|
+
writer.insert(wrapperElement, position);
|
|
239
|
+
position = writer.createPositionAt(wrapperElement, 0);
|
|
240
|
+
}
|
|
241
|
+
const { modelRange: convertedChildrenRange } = conversionApi.convertChildren(data.viewItem, position);
|
|
242
|
+
conversionApi.consumable.consume(data.viewItem, match);
|
|
243
|
+
const markerName = config.model();
|
|
244
|
+
const fakeMarkerStart = writer.createElement("$marker", { "data-name": markerName });
|
|
245
|
+
const fakeMarkerEnd = writer.createElement("$marker", { "data-name": markerName });
|
|
246
|
+
if (wrapperElement) {
|
|
247
|
+
writer.insert(fakeMarkerStart, writer.createPositionBefore(wrapperElement));
|
|
248
|
+
writer.insert(fakeMarkerEnd, writer.createPositionAfter(wrapperElement));
|
|
249
|
+
} else {
|
|
250
|
+
writer.insert(fakeMarkerEnd, convertedChildrenRange.end);
|
|
251
|
+
writer.insert(fakeMarkerStart, convertedChildrenRange.start);
|
|
252
|
+
}
|
|
253
|
+
data.modelRange = writer.createRange(writer.createPositionBefore(fakeMarkerStart), writer.createPositionAfter(fakeMarkerEnd));
|
|
254
|
+
data.modelCursor = data.modelRange.end;
|
|
255
|
+
});
|
|
303
256
|
}
|
|
304
257
|
/**
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
return false;
|
|
258
|
+
* Extend marker if change detected on marker's start position.
|
|
259
|
+
*/
|
|
260
|
+
function _tryExtendMarkerStart(editor, position, length, writer) {
|
|
261
|
+
const markerAtStart = getMarkerAtPosition(editor, position.getShiftedBy(length));
|
|
262
|
+
if (markerAtStart && markerAtStart.getStart().isEqual(position.getShiftedBy(length))) {
|
|
263
|
+
writer.updateMarker(markerAtStart, { range: writer.createRange(markerAtStart.getStart().getShiftedBy(-length), markerAtStart.getEnd()) });
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
return false;
|
|
315
267
|
}
|
|
316
268
|
/**
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
return false;
|
|
269
|
+
* Extend marker if change detected on marker's end position.
|
|
270
|
+
*/
|
|
271
|
+
function _tryExtendMarkedEnd(editor, position, length, writer) {
|
|
272
|
+
const markerAtEnd = getMarkerAtPosition(editor, position);
|
|
273
|
+
if (markerAtEnd && markerAtEnd.getEnd().isEqual(position)) {
|
|
274
|
+
writer.updateMarker(markerAtEnd, { range: writer.createRange(markerAtEnd.getStart(), markerAtEnd.getEnd().getShiftedBy(length)) });
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
277
|
+
return false;
|
|
327
278
|
}
|
|
328
279
|
|
|
329
|
-
const COMMAND_FORCE_DISABLE_ID = 'RestrictedEditingMode';
|
|
330
280
|
/**
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
});
|
|
635
|
-
// Do not allow pasting/dropping block exception wrapper.
|
|
636
|
-
model.schema.addChildCheck((context)=>{
|
|
637
|
-
if (context.startsWith('$clipboardHolder')) {
|
|
638
|
-
return false;
|
|
639
|
-
}
|
|
640
|
-
}, 'restrictedEditingException');
|
|
641
|
-
}
|
|
642
|
-
/**
|
|
643
|
-
* Sets up the command toggling which enables or disables commands based on the user selection.
|
|
644
|
-
*/ _setupCommandsToggling() {
|
|
645
|
-
const editor = this.editor;
|
|
646
|
-
const model = editor.model;
|
|
647
|
-
const doc = model.document;
|
|
648
|
-
this._disableCommands();
|
|
649
|
-
this.listenTo(doc.selection, 'change', this._checkCommands.bind(this));
|
|
650
|
-
this.listenTo(doc, 'change:data', this._checkCommands.bind(this));
|
|
651
|
-
}
|
|
652
|
-
/**
|
|
653
|
-
* Checks if commands should be enabled or disabled based on the current selection.
|
|
654
|
-
*/ _checkCommands() {
|
|
655
|
-
const editor = this.editor;
|
|
656
|
-
const selection = editor.model.document.selection;
|
|
657
|
-
if (selection.rangeCount > 1) {
|
|
658
|
-
this._disableCommands();
|
|
659
|
-
return;
|
|
660
|
-
}
|
|
661
|
-
const marker = getMarkerAtPosition(editor, selection.focus);
|
|
662
|
-
this._disableCommands();
|
|
663
|
-
if (isSelectionInMarker(selection, editor.model, marker)) {
|
|
664
|
-
this._enableCommands(marker);
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
/**
|
|
668
|
-
* Enables commands in non-restricted regions.
|
|
669
|
-
*/ _enableCommands(marker) {
|
|
670
|
-
const editor = this.editor;
|
|
671
|
-
const selection = editor.model.document.selection;
|
|
672
|
-
for (const [commandName, command] of editor.commands){
|
|
673
|
-
if (!command.affectsData || this._alwaysEnabled.has(commandName)) {
|
|
674
|
-
continue;
|
|
675
|
-
}
|
|
676
|
-
// Enable ony those commands that are allowed in the exception marker.
|
|
677
|
-
// In block exceptions all commands are enabled.
|
|
678
|
-
if (!marker.name.startsWith('restrictedEditingException:block:') && !this._allowedInException.has(commandName)) {
|
|
679
|
-
continue;
|
|
680
|
-
}
|
|
681
|
-
// Do not enable 'delete' and 'deleteForward' commands on the exception marker boundaries.
|
|
682
|
-
if (isDeleteCommandOnMarkerBoundaries(commandName, selection, getExceptionRange(marker, editor.model))) {
|
|
683
|
-
continue;
|
|
684
|
-
}
|
|
685
|
-
command.clearForceDisabled(COMMAND_FORCE_DISABLE_ID);
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
/**
|
|
689
|
-
* Disables commands outside non-restricted regions.
|
|
690
|
-
*/ _disableCommands() {
|
|
691
|
-
const editor = this.editor;
|
|
692
|
-
for (const [commandName, command] of editor.commands){
|
|
693
|
-
if (!command.affectsData || this._alwaysEnabled.has(commandName)) {
|
|
694
|
-
continue;
|
|
695
|
-
}
|
|
696
|
-
command.forceDisabled(COMMAND_FORCE_DISABLE_ID);
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
}
|
|
281
|
+
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
282
|
+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
283
|
+
*/
|
|
284
|
+
/**
|
|
285
|
+
* @module restricted-editing/restrictededitingmodeediting
|
|
286
|
+
*/
|
|
287
|
+
const COMMAND_FORCE_DISABLE_ID = "RestrictedEditingMode";
|
|
288
|
+
/**
|
|
289
|
+
* The restricted editing mode editing feature.
|
|
290
|
+
*
|
|
291
|
+
* * It introduces the exception marker group that renders to `<span>` elements with the `restricted-editing-exception` CSS class.
|
|
292
|
+
* * It registers the `'goToPreviousRestrictedEditingException'` and `'goToNextRestrictedEditingException'` commands.
|
|
293
|
+
* * It also enables highlighting exception markers that are selected.
|
|
294
|
+
*/
|
|
295
|
+
var RestrictedEditingModeEditing = class extends Plugin {
|
|
296
|
+
/**
|
|
297
|
+
* Command names that are enabled outside the non-restricted regions.
|
|
298
|
+
*/
|
|
299
|
+
_alwaysEnabled;
|
|
300
|
+
/**
|
|
301
|
+
* Commands allowed in non-restricted areas.
|
|
302
|
+
*
|
|
303
|
+
* Commands always enabled combine typing feature commands: `'input'`, `'insertText'`, `'delete'`, and `'deleteForward'` with
|
|
304
|
+
* commands defined in the feature configuration.
|
|
305
|
+
*/
|
|
306
|
+
_allowedInException;
|
|
307
|
+
/**
|
|
308
|
+
* @inheritDoc
|
|
309
|
+
*/
|
|
310
|
+
static get pluginName() {
|
|
311
|
+
return "RestrictedEditingModeEditing";
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* @inheritDoc
|
|
315
|
+
* @internal
|
|
316
|
+
*/
|
|
317
|
+
static get licenseFeatureCode() {
|
|
318
|
+
return "RED";
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* @inheritDoc
|
|
322
|
+
*/
|
|
323
|
+
static get isOfficialPlugin() {
|
|
324
|
+
return true;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* @inheritDoc
|
|
328
|
+
*/
|
|
329
|
+
static get isPremiumPlugin() {
|
|
330
|
+
return true;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* @inheritDoc
|
|
334
|
+
*/
|
|
335
|
+
constructor(editor) {
|
|
336
|
+
super(editor);
|
|
337
|
+
editor.config.define("restrictedEditing", {
|
|
338
|
+
allowedCommands: [
|
|
339
|
+
"bold",
|
|
340
|
+
"italic",
|
|
341
|
+
"link",
|
|
342
|
+
"unlink"
|
|
343
|
+
],
|
|
344
|
+
allowedAttributes: [
|
|
345
|
+
"bold",
|
|
346
|
+
"italic",
|
|
347
|
+
"linkHref"
|
|
348
|
+
]
|
|
349
|
+
});
|
|
350
|
+
this._alwaysEnabled = new Set(["undo", "redo"]);
|
|
351
|
+
this._allowedInException = new Set([
|
|
352
|
+
"input",
|
|
353
|
+
"insertText",
|
|
354
|
+
"delete",
|
|
355
|
+
"deleteForward"
|
|
356
|
+
]);
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* @inheritDoc
|
|
360
|
+
*/
|
|
361
|
+
init() {
|
|
362
|
+
const editor = this.editor;
|
|
363
|
+
const editingView = editor.editing.view;
|
|
364
|
+
editor.config.get("restrictedEditing.allowedCommands").forEach((commandName) => this._allowedInException.add(commandName));
|
|
365
|
+
this._setupSchema();
|
|
366
|
+
this._setupConversion();
|
|
367
|
+
this._setupCommandsToggling();
|
|
368
|
+
this._setupRestrictions();
|
|
369
|
+
editor.commands.add("goToPreviousRestrictedEditingException", new RestrictedEditingModeNavigationCommand(editor, "backward"));
|
|
370
|
+
editor.commands.add("goToNextRestrictedEditingException", new RestrictedEditingModeNavigationCommand(editor, "forward"));
|
|
371
|
+
this.listenTo(editingView.document, "tab", (evt, data) => {
|
|
372
|
+
const commandName = !data.shiftKey ? "goToNextRestrictedEditingException" : "goToPreviousRestrictedEditingException";
|
|
373
|
+
if (editor.commands.get(commandName).isEnabled) {
|
|
374
|
+
editor.execute(commandName);
|
|
375
|
+
data.preventDefault();
|
|
376
|
+
data.stopPropagation();
|
|
377
|
+
}
|
|
378
|
+
evt.stop();
|
|
379
|
+
}, { context: "$capture" });
|
|
380
|
+
this.listenTo(editingView.document, "keydown", getSelectAllHandler(editor), { priority: "high" });
|
|
381
|
+
editingView.change((writer) => {
|
|
382
|
+
for (const root of editingView.document.roots) writer.addClass("ck-restricted-editing_mode_restricted", root);
|
|
383
|
+
});
|
|
384
|
+
editor.data.on("set", () => {
|
|
385
|
+
editor.model.change((writer) => {
|
|
386
|
+
for (const marker of editor.model.markers.getMarkersGroup("restrictedEditingException")) writer.removeMarker(marker.name);
|
|
387
|
+
});
|
|
388
|
+
}, { priority: "high" });
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Makes the given command always enabled in the restricted editing mode (regardless
|
|
392
|
+
* of selection location).
|
|
393
|
+
*
|
|
394
|
+
* To enable some commands in non-restricted areas of the content use
|
|
395
|
+
* {@link module:restricted-editing/restrictededitingconfig~RestrictedEditingConfig#allowedCommands} configuration option.
|
|
396
|
+
*
|
|
397
|
+
* @param commandName Name of the command to enable.
|
|
398
|
+
*/
|
|
399
|
+
enableCommand(commandName) {
|
|
400
|
+
this.editor.commands.get(commandName).clearForceDisabled(COMMAND_FORCE_DISABLE_ID);
|
|
401
|
+
this._alwaysEnabled.add(commandName);
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Registers block exception wrapper in the schema.
|
|
405
|
+
*/
|
|
406
|
+
_setupSchema() {
|
|
407
|
+
this.editor.model.schema.register("restrictedEditingException", {
|
|
408
|
+
allowWhere: "$block",
|
|
409
|
+
allowContentOf: "$container",
|
|
410
|
+
isLimit: true
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Sets up the restricted mode editing conversion:
|
|
415
|
+
*
|
|
416
|
+
* * ucpast & downcast converters,
|
|
417
|
+
* * marker highlighting in the edting area,
|
|
418
|
+
* * marker post-fixers.
|
|
419
|
+
*/
|
|
420
|
+
_setupConversion() {
|
|
421
|
+
const editor = this.editor;
|
|
422
|
+
const doc = editor.model.document;
|
|
423
|
+
let markerNumber = 0;
|
|
424
|
+
editor.conversion.for("upcast").add(upcastHighlightToMarker({
|
|
425
|
+
view: {
|
|
426
|
+
name: "span",
|
|
427
|
+
classes: "restricted-editing-exception"
|
|
428
|
+
},
|
|
429
|
+
model: () => {
|
|
430
|
+
markerNumber++;
|
|
431
|
+
return `restrictedEditingException:inline:${markerNumber}`;
|
|
432
|
+
}
|
|
433
|
+
}));
|
|
434
|
+
editor.conversion.for("upcast").add(upcastHighlightToMarker({
|
|
435
|
+
view: {
|
|
436
|
+
name: "div",
|
|
437
|
+
classes: "restricted-editing-exception"
|
|
438
|
+
},
|
|
439
|
+
model: () => {
|
|
440
|
+
markerNumber++;
|
|
441
|
+
return `restrictedEditingException:block:${markerNumber}`;
|
|
442
|
+
},
|
|
443
|
+
useWrapperElement: true
|
|
444
|
+
}));
|
|
445
|
+
editor.conversion.for("downcast").elementToElement({
|
|
446
|
+
model: "restrictedEditingException",
|
|
447
|
+
view: {
|
|
448
|
+
name: "div",
|
|
449
|
+
classes: "restricted-editing-exception"
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
editor.conversion.for("downcast").add((dispatcher) => {
|
|
453
|
+
dispatcher.on("addMarker:restrictedEditingException:inline", (evt, data, conversionApi) => {
|
|
454
|
+
if (!data.item) return;
|
|
455
|
+
if (data.item.is("selection") || !conversionApi.schema.isInline(data.item)) return;
|
|
456
|
+
if (!conversionApi.consumable.consume(data.item, evt.name)) return;
|
|
457
|
+
const viewWriter = conversionApi.writer;
|
|
458
|
+
const viewElement = viewWriter.createAttributeElement("span", { class: "restricted-editing-exception" }, {
|
|
459
|
+
id: data.markerName,
|
|
460
|
+
priority: -10
|
|
461
|
+
});
|
|
462
|
+
const viewRange = conversionApi.mapper.toViewRange(data.range);
|
|
463
|
+
const rangeAfterWrap = viewWriter.wrap(viewRange, viewElement);
|
|
464
|
+
for (const element of rangeAfterWrap.getItems()) if (element.is("attributeElement") && element.isSimilar(viewElement)) {
|
|
465
|
+
conversionApi.mapper.bindElementToMarker(element, data.markerName);
|
|
466
|
+
break;
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
});
|
|
470
|
+
editor.conversion.for("downcast").markerToHighlight({
|
|
471
|
+
model: "restrictedEditingException:inline",
|
|
472
|
+
view: () => {
|
|
473
|
+
return {
|
|
474
|
+
name: "span",
|
|
475
|
+
classes: "restricted-editing-exception",
|
|
476
|
+
priority: -10
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
editor.conversion.for("editingDowncast").markerToElement({
|
|
481
|
+
model: "restrictedEditingException:inline",
|
|
482
|
+
view: (markerData, { writer }) => {
|
|
483
|
+
return writer.createUIElement("span", { class: "restricted-editing-exception restricted-editing-exception_collapsed" });
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
editor.conversion.for("dataDowncast").markerToElement({
|
|
487
|
+
model: "restrictedEditingException:inline",
|
|
488
|
+
view: (markerData, { writer }) => {
|
|
489
|
+
return writer.createEmptyElement("span", { class: "restricted-editing-exception" });
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
doc.registerPostFixer(extendMarkerOnTypingPostFixer(editor));
|
|
493
|
+
doc.registerPostFixer(resurrectCollapsedMarkerPostFixer(editor));
|
|
494
|
+
doc.registerPostFixer(ensureNewMarkerIsFlatPostFixer(editor));
|
|
495
|
+
setupExceptionHighlighting(editor);
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Setups additional editing restrictions beyond command toggling:
|
|
499
|
+
*
|
|
500
|
+
* * delete content range trimming
|
|
501
|
+
* * disabling input command outside exception marker
|
|
502
|
+
* * restricting clipboard holder to text only
|
|
503
|
+
* * restricting text attributes in content
|
|
504
|
+
*/
|
|
505
|
+
_setupRestrictions() {
|
|
506
|
+
const editor = this.editor;
|
|
507
|
+
const model = editor.model;
|
|
508
|
+
const selection = model.document.selection;
|
|
509
|
+
const viewDoc = editor.editing.view.document;
|
|
510
|
+
const clipboard = editor.plugins.get("ClipboardPipeline");
|
|
511
|
+
this.listenTo(model, "deleteContent", restrictDeleteContent(editor), { priority: "high" });
|
|
512
|
+
const insertTextCommand = editor.commands.get("insertText");
|
|
513
|
+
if (insertTextCommand) this.listenTo(insertTextCommand, "execute", disallowInputExecForWrongRange(editor), { priority: "high" });
|
|
514
|
+
this.listenTo(clipboard, "contentInsertion", (evt, data) => {
|
|
515
|
+
if (!isRangeInsideSingleMarker(editor, selection.getFirstRange())) evt.stop();
|
|
516
|
+
const marker = getMarkerAtPosition(editor, selection.focus);
|
|
517
|
+
if (marker && marker.name.startsWith("restrictedEditingException:inline:")) {
|
|
518
|
+
const allowedAttributes = editor.config.get("restrictedEditing.allowedAttributes");
|
|
519
|
+
model.change((writer) => {
|
|
520
|
+
const content = writer.createDocumentFragment();
|
|
521
|
+
const textNodes = Array.from(writer.createRangeIn(data.content).getItems()).filter((node) => node.is("$textProxy"));
|
|
522
|
+
for (const item of textNodes) {
|
|
523
|
+
for (const attr of item.getAttributeKeys()) if (!allowedAttributes.includes(attr)) writer.removeAttribute(attr, item);
|
|
524
|
+
writer.append(item, content);
|
|
525
|
+
}
|
|
526
|
+
data.content = content;
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
});
|
|
530
|
+
this.listenTo(viewDoc, "clipboardOutput", (evt, data) => {
|
|
531
|
+
if (data.method == "cut" && !isRangeInsideSingleMarker(editor, selection.getFirstRange())) evt.stop();
|
|
532
|
+
}, { priority: "high" });
|
|
533
|
+
model.schema.addChildCheck((context) => {
|
|
534
|
+
if (context.startsWith("$clipboardHolder")) return false;
|
|
535
|
+
}, "restrictedEditingException");
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Sets up the command toggling which enables or disables commands based on the user selection.
|
|
539
|
+
*/
|
|
540
|
+
_setupCommandsToggling() {
|
|
541
|
+
const doc = this.editor.model.document;
|
|
542
|
+
this._disableCommands();
|
|
543
|
+
this.listenTo(doc.selection, "change", this._checkCommands.bind(this));
|
|
544
|
+
this.listenTo(doc, "change:data", this._checkCommands.bind(this));
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* Checks if commands should be enabled or disabled based on the current selection.
|
|
548
|
+
*/
|
|
549
|
+
_checkCommands() {
|
|
550
|
+
const editor = this.editor;
|
|
551
|
+
const selection = editor.model.document.selection;
|
|
552
|
+
if (selection.rangeCount > 1) {
|
|
553
|
+
this._disableCommands();
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
const marker = getMarkerAtPosition(editor, selection.focus);
|
|
557
|
+
this._disableCommands();
|
|
558
|
+
if (isSelectionInMarker(selection, editor.model, marker)) this._enableCommands(marker);
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Enables commands in non-restricted regions.
|
|
562
|
+
*/
|
|
563
|
+
_enableCommands(marker) {
|
|
564
|
+
const editor = this.editor;
|
|
565
|
+
const selection = editor.model.document.selection;
|
|
566
|
+
for (const [commandName, command] of editor.commands) {
|
|
567
|
+
if (!command.affectsData || this._alwaysEnabled.has(commandName)) continue;
|
|
568
|
+
if (!marker.name.startsWith("restrictedEditingException:block:") && !this._allowedInException.has(commandName)) continue;
|
|
569
|
+
if (isDeleteCommandOnMarkerBoundaries(commandName, selection, getExceptionRange(marker, editor.model))) continue;
|
|
570
|
+
command.clearForceDisabled(COMMAND_FORCE_DISABLE_ID);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Disables commands outside non-restricted regions.
|
|
575
|
+
*/
|
|
576
|
+
_disableCommands() {
|
|
577
|
+
const editor = this.editor;
|
|
578
|
+
for (const [commandName, command] of editor.commands) {
|
|
579
|
+
if (!command.affectsData || this._alwaysEnabled.has(commandName)) continue;
|
|
580
|
+
command.forceDisabled(COMMAND_FORCE_DISABLE_ID);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
};
|
|
700
584
|
/**
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
domEventData.stopPropagation();
|
|
722
|
-
model.change((writer)=>{
|
|
723
|
-
writer.setSelection(markerRange);
|
|
724
|
-
});
|
|
725
|
-
}
|
|
726
|
-
};
|
|
585
|
+
* Helper for handling Ctrl+A keydown behaviour.
|
|
586
|
+
*/
|
|
587
|
+
function getSelectAllHandler(editor) {
|
|
588
|
+
return (eventInfo, domEventData) => {
|
|
589
|
+
if (getCode(domEventData) != parseKeystroke("Ctrl+A")) return;
|
|
590
|
+
const model = editor.model;
|
|
591
|
+
const selection = editor.model.document.selection;
|
|
592
|
+
const marker = getMarkerAtPosition(editor, selection.focus);
|
|
593
|
+
if (!marker) return;
|
|
594
|
+
const selectionRange = selection.getFirstRange();
|
|
595
|
+
const markerRange = getExceptionRange(marker, editor.model);
|
|
596
|
+
if (markerRange.containsRange(selectionRange, true) || selection.isCollapsed) {
|
|
597
|
+
eventInfo.stop();
|
|
598
|
+
domEventData.preventDefault();
|
|
599
|
+
domEventData.stopPropagation();
|
|
600
|
+
model.change((writer) => {
|
|
601
|
+
writer.setSelection(markerRange);
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
};
|
|
727
605
|
}
|
|
728
606
|
/**
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
if (commandName == 'deleteForward' && selection.isCollapsed && markerRange.end.isTouching(selection.focus)) {
|
|
740
|
-
return true;
|
|
741
|
-
}
|
|
742
|
-
return false;
|
|
607
|
+
* Additional rule for enabling "delete" and "deleteForward" commands if selection is on range boundaries:
|
|
608
|
+
*
|
|
609
|
+
* Does not allow to enable command when selection focus is:
|
|
610
|
+
* - is on marker start - "delete" - to prevent removing content before marker
|
|
611
|
+
* - is on marker end - "deleteForward" - to prevent removing content after marker
|
|
612
|
+
*/
|
|
613
|
+
function isDeleteCommandOnMarkerBoundaries(commandName, selection, markerRange) {
|
|
614
|
+
if (commandName == "delete" && selection.isCollapsed && markerRange.start.isTouching(selection.focus)) return true;
|
|
615
|
+
if (commandName == "deleteForward" && selection.isCollapsed && markerRange.end.isTouching(selection.focus)) return true;
|
|
616
|
+
return false;
|
|
743
617
|
}
|
|
744
618
|
/**
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
// either the document selection using change block...
|
|
767
|
-
if (selection.is('documentSelection')) {
|
|
768
|
-
editor.model.change((writer)=>{
|
|
769
|
-
writer.setSelection(allowedToDelete);
|
|
770
|
-
});
|
|
771
|
-
} else {
|
|
772
|
-
selection.setTo(allowedToDelete);
|
|
773
|
-
}
|
|
774
|
-
};
|
|
619
|
+
* Ensures that model.deleteContent() does not delete outside exception markers ranges.
|
|
620
|
+
*
|
|
621
|
+
* The enforced restrictions are:
|
|
622
|
+
* - only execute deleteContent() inside exception markers
|
|
623
|
+
* - restrict passed selection to exception marker
|
|
624
|
+
*/
|
|
625
|
+
function restrictDeleteContent(editor) {
|
|
626
|
+
return (evt, args) => {
|
|
627
|
+
const [selection] = args;
|
|
628
|
+
const marker = getMarkerAtPosition(editor, selection.focus) || getMarkerAtPosition(editor, selection.anchor);
|
|
629
|
+
if (!marker) {
|
|
630
|
+
evt.stop();
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
if (selection.isCollapsed) return;
|
|
634
|
+
const allowedToDelete = getExceptionRange(marker, editor.model).getIntersection(selection.getFirstRange());
|
|
635
|
+
if (selection.is("documentSelection")) editor.model.change((writer) => {
|
|
636
|
+
writer.setSelection(allowedToDelete);
|
|
637
|
+
});
|
|
638
|
+
else selection.setTo(allowedToDelete);
|
|
639
|
+
};
|
|
775
640
|
}
|
|
776
641
|
/**
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
}
|
|
789
|
-
if (!isRangeInsideSingleMarker(editor, range)) {
|
|
790
|
-
evt.stop();
|
|
791
|
-
}
|
|
792
|
-
};
|
|
642
|
+
* Ensures that input command is executed with a range that is inside exception marker.
|
|
643
|
+
*
|
|
644
|
+
* This restriction is due to fact that using native spell check changes text outside exception marker.
|
|
645
|
+
*/
|
|
646
|
+
function disallowInputExecForWrongRange(editor) {
|
|
647
|
+
return (evt, args) => {
|
|
648
|
+
const [options] = args;
|
|
649
|
+
const { range } = options;
|
|
650
|
+
if (!range) return;
|
|
651
|
+
if (!isRangeInsideSingleMarker(editor, range)) evt.stop();
|
|
652
|
+
};
|
|
793
653
|
}
|
|
794
654
|
function isRangeInsideSingleMarker(editor, range) {
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
655
|
+
const markerAtStart = getMarkerAtPosition(editor, range.start);
|
|
656
|
+
const markerAtEnd = getMarkerAtPosition(editor, range.end);
|
|
657
|
+
return markerAtStart && markerAtEnd && markerAtEnd === markerAtStart;
|
|
798
658
|
}
|
|
799
659
|
/**
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
}
|
|
827
|
-
return changeApplied;
|
|
828
|
-
};
|
|
660
|
+
* Checks if new marker range is flat. Non-flat ranges might appear during upcast conversion in nested structures, ie tables.
|
|
661
|
+
*
|
|
662
|
+
* Note: This marker fixer only consider case which is possible to create using StandardEditing mode plugin.
|
|
663
|
+
* Markers created by developer in the data might break in many other ways.
|
|
664
|
+
*
|
|
665
|
+
* See https://github.com/ckeditor/ckeditor5/issues/6003.
|
|
666
|
+
*/
|
|
667
|
+
function ensureNewMarkerIsFlatPostFixer(editor) {
|
|
668
|
+
return (writer) => {
|
|
669
|
+
let changeApplied = false;
|
|
670
|
+
const changedMarkers = editor.model.document.differ.getChangedMarkers();
|
|
671
|
+
for (const { data, name } of changedMarkers) {
|
|
672
|
+
if (!name.startsWith("restrictedEditingException")) continue;
|
|
673
|
+
const newRange = data.newRange;
|
|
674
|
+
if (!data.oldRange && !newRange.isFlat) {
|
|
675
|
+
const start = newRange.start;
|
|
676
|
+
const end = newRange.end;
|
|
677
|
+
const startIsHigherInTree = start.path.length > end.path.length;
|
|
678
|
+
const fixedStart = startIsHigherInTree ? newRange.start : writer.createPositionAt(end.parent, 0);
|
|
679
|
+
const fixedEnd = startIsHigherInTree ? writer.createPositionAt(start.parent, "end") : newRange.end;
|
|
680
|
+
writer.updateMarker(name, { range: writer.createRange(fixedStart, fixedEnd) });
|
|
681
|
+
changeApplied = true;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
return changeApplied;
|
|
685
|
+
};
|
|
829
686
|
}
|
|
830
687
|
|
|
831
688
|
/**
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
689
|
+
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
690
|
+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
691
|
+
*/
|
|
692
|
+
/**
|
|
693
|
+
* @module restricted-editing/restrictededitingmodeui
|
|
694
|
+
*/
|
|
695
|
+
/**
|
|
696
|
+
* The restricted editing mode UI feature.
|
|
697
|
+
*
|
|
698
|
+
* It introduces the `'restrictedEditing'` dropdown that offers tools to navigate between exceptions across
|
|
699
|
+
* the document.
|
|
700
|
+
*/
|
|
701
|
+
var RestrictedEditingModeUI = class extends Plugin {
|
|
702
|
+
/**
|
|
703
|
+
* @inheritDoc
|
|
704
|
+
*/
|
|
705
|
+
static get pluginName() {
|
|
706
|
+
return "RestrictedEditingModeUI";
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* @inheritDoc
|
|
710
|
+
*/
|
|
711
|
+
static get isOfficialPlugin() {
|
|
712
|
+
return true;
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* @inheritDoc
|
|
716
|
+
*/
|
|
717
|
+
init() {
|
|
718
|
+
const editor = this.editor;
|
|
719
|
+
const t = editor.t;
|
|
720
|
+
editor.ui.componentFactory.add("restrictedEditing", (locale) => {
|
|
721
|
+
const dropdownView = createDropdown(locale);
|
|
722
|
+
const listItems = new Collection();
|
|
723
|
+
this._getButtonDefinitions().forEach(({ commandName, label, keystroke }) => {
|
|
724
|
+
listItems.add(this._getButtonDefinition(commandName, label, keystroke));
|
|
725
|
+
});
|
|
726
|
+
addListToDropdown(dropdownView, listItems, { role: "menu" });
|
|
727
|
+
dropdownView.buttonView.set({
|
|
728
|
+
label: t("Navigate editable regions"),
|
|
729
|
+
icon: IconContentLock,
|
|
730
|
+
tooltip: true,
|
|
731
|
+
isEnabled: true,
|
|
732
|
+
isOn: false
|
|
733
|
+
});
|
|
734
|
+
this.listenTo(dropdownView, "execute", (evt) => {
|
|
735
|
+
const { _commandName } = evt.source;
|
|
736
|
+
editor.execute(_commandName);
|
|
737
|
+
editor.editing.view.focus();
|
|
738
|
+
});
|
|
739
|
+
return dropdownView;
|
|
740
|
+
});
|
|
741
|
+
editor.ui.componentFactory.add("menuBar:restrictedEditing", (locale) => {
|
|
742
|
+
const menuView = new MenuBarMenuView(locale);
|
|
743
|
+
const listView = new MenuBarMenuListView(locale);
|
|
744
|
+
listView.set({
|
|
745
|
+
ariaLabel: t("Navigate editable regions"),
|
|
746
|
+
role: "menu"
|
|
747
|
+
});
|
|
748
|
+
menuView.buttonView.set({
|
|
749
|
+
label: t("Navigate editable regions"),
|
|
750
|
+
icon: IconContentLock
|
|
751
|
+
});
|
|
752
|
+
menuView.panelView.children.add(listView);
|
|
753
|
+
this._getButtonDefinitions().forEach(({ commandName, label, keystroke }) => {
|
|
754
|
+
const listItemView = new MenuBarMenuListItemView(locale, menuView);
|
|
755
|
+
const buttonView = this._createMenuBarButton(label, commandName, keystroke);
|
|
756
|
+
buttonView.delegate("execute").to(menuView);
|
|
757
|
+
listItemView.children.add(buttonView);
|
|
758
|
+
listView.items.add(listItemView);
|
|
759
|
+
});
|
|
760
|
+
return menuView;
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Creates a button for restricted editing command to use in menu bar.
|
|
765
|
+
*/
|
|
766
|
+
_createMenuBarButton(label, commandName, keystroke) {
|
|
767
|
+
const editor = this.editor;
|
|
768
|
+
const command = editor.commands.get(commandName);
|
|
769
|
+
const view = new MenuBarMenuListItemButtonView(editor.locale);
|
|
770
|
+
view.set({
|
|
771
|
+
label,
|
|
772
|
+
keystroke,
|
|
773
|
+
isEnabled: true,
|
|
774
|
+
isOn: false
|
|
775
|
+
});
|
|
776
|
+
view.bind("isEnabled").to(command);
|
|
777
|
+
this.listenTo(view, "execute", () => {
|
|
778
|
+
editor.execute(commandName);
|
|
779
|
+
editor.editing.view.focus();
|
|
780
|
+
});
|
|
781
|
+
return view;
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* Returns a definition of the navigation button to be used in the dropdown.
|
|
785
|
+
*
|
|
786
|
+
* @param commandName The name of the command that the button represents.
|
|
787
|
+
* @param label The translated label of the button.
|
|
788
|
+
* @param keystroke The button keystroke.
|
|
789
|
+
*/
|
|
790
|
+
_getButtonDefinition(commandName, label, keystroke) {
|
|
791
|
+
const command = this.editor.commands.get(commandName);
|
|
792
|
+
const definition = {
|
|
793
|
+
type: "button",
|
|
794
|
+
model: new UIModel({
|
|
795
|
+
label,
|
|
796
|
+
withText: true,
|
|
797
|
+
keystroke,
|
|
798
|
+
withKeystroke: true,
|
|
799
|
+
role: "menuitem",
|
|
800
|
+
_commandName: commandName
|
|
801
|
+
})
|
|
802
|
+
};
|
|
803
|
+
definition.model.bind("isEnabled").to(command, "isEnabled");
|
|
804
|
+
return definition;
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Returns definitions for UI buttons.
|
|
808
|
+
*
|
|
809
|
+
* @internal
|
|
810
|
+
*/
|
|
811
|
+
_getButtonDefinitions() {
|
|
812
|
+
const t = this.editor.locale.t;
|
|
813
|
+
return [{
|
|
814
|
+
commandName: "goToPreviousRestrictedEditingException",
|
|
815
|
+
label: t("Previous editable region"),
|
|
816
|
+
keystroke: "Shift+Tab"
|
|
817
|
+
}, {
|
|
818
|
+
commandName: "goToNextRestrictedEditingException",
|
|
819
|
+
label: t("Next editable region"),
|
|
820
|
+
keystroke: "Tab"
|
|
821
|
+
}];
|
|
822
|
+
}
|
|
823
|
+
};
|
|
960
824
|
|
|
961
825
|
/**
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
826
|
+
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
827
|
+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
828
|
+
*/
|
|
829
|
+
/**
|
|
830
|
+
* @module restricted-editing/restrictededitingmode
|
|
831
|
+
*/
|
|
832
|
+
/**
|
|
833
|
+
* The restricted editing mode plugin.
|
|
834
|
+
*
|
|
835
|
+
* This is a "glue" plugin which loads the following plugins:
|
|
836
|
+
*
|
|
837
|
+
* * The {@link module:restricted-editing/restrictededitingmodeediting~RestrictedEditingModeEditing restricted mode editing feature}.
|
|
838
|
+
* * The {@link module:restricted-editing/restrictededitingmodeui~RestrictedEditingModeUI restricted mode UI feature}.
|
|
839
|
+
*/
|
|
840
|
+
var RestrictedEditingMode = class extends Plugin {
|
|
841
|
+
/**
|
|
842
|
+
* @inheritDoc
|
|
843
|
+
*/
|
|
844
|
+
static get pluginName() {
|
|
845
|
+
return "RestrictedEditingMode";
|
|
846
|
+
}
|
|
847
|
+
/**
|
|
848
|
+
* @inheritDoc
|
|
849
|
+
*/
|
|
850
|
+
static get isOfficialPlugin() {
|
|
851
|
+
return true;
|
|
852
|
+
}
|
|
853
|
+
/**
|
|
854
|
+
* @inheritDoc
|
|
855
|
+
*/
|
|
856
|
+
static get requires() {
|
|
857
|
+
return [RestrictedEditingModeEditing, RestrictedEditingModeUI];
|
|
858
|
+
}
|
|
859
|
+
};
|
|
988
860
|
|
|
989
861
|
/**
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
}
|
|
1035
|
-
});
|
|
1036
|
-
}
|
|
1037
|
-
}
|
|
862
|
+
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
863
|
+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
864
|
+
*/
|
|
865
|
+
/**
|
|
866
|
+
* @module restricted-editing/restrictededitingexceptioncommand
|
|
867
|
+
*/
|
|
868
|
+
/**
|
|
869
|
+
* The command that toggles exceptions from the restricted editing on text.
|
|
870
|
+
*/
|
|
871
|
+
var RestrictedEditingExceptionCommand = class extends Command {
|
|
872
|
+
/**
|
|
873
|
+
* @inheritDoc
|
|
874
|
+
*/
|
|
875
|
+
refresh() {
|
|
876
|
+
const model = this.editor.model;
|
|
877
|
+
const doc = model.document;
|
|
878
|
+
this.value = !!doc.selection.getAttribute("restrictedEditingException");
|
|
879
|
+
this.isEnabled = model.schema.checkAttributeInSelection(doc.selection, "restrictedEditingException");
|
|
880
|
+
}
|
|
881
|
+
/**
|
|
882
|
+
* @inheritDoc
|
|
883
|
+
*/
|
|
884
|
+
execute(options = {}) {
|
|
885
|
+
const model = this.editor.model;
|
|
886
|
+
const selection = model.document.selection;
|
|
887
|
+
const valueToSet = options.forceValue === void 0 ? !this.value : options.forceValue;
|
|
888
|
+
model.change((writer) => {
|
|
889
|
+
const ranges = model.schema.getValidRanges(selection.getRanges(), "restrictedEditingException");
|
|
890
|
+
if (selection.isCollapsed) if (valueToSet) writer.setSelectionAttribute("restrictedEditingException", valueToSet);
|
|
891
|
+
else {
|
|
892
|
+
const isSameException = (value) => {
|
|
893
|
+
return value.item.getAttribute("restrictedEditingException") === this.value;
|
|
894
|
+
};
|
|
895
|
+
const focus = selection.focus;
|
|
896
|
+
const exceptionStart = focus.getLastMatchingPosition(isSameException, { direction: "backward" });
|
|
897
|
+
const exceptionEnd = focus.getLastMatchingPosition(isSameException);
|
|
898
|
+
writer.removeSelectionAttribute("restrictedEditingException");
|
|
899
|
+
if (!(focus.isEqual(exceptionStart) || focus.isEqual(exceptionEnd))) writer.removeAttribute("restrictedEditingException", writer.createRange(exceptionStart, exceptionEnd));
|
|
900
|
+
}
|
|
901
|
+
else for (const range of ranges) if (valueToSet) writer.setAttribute("restrictedEditingException", valueToSet, range);
|
|
902
|
+
else writer.removeAttribute("restrictedEditingException", range);
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
};
|
|
1038
906
|
|
|
1039
907
|
/**
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
// Merge subsequent exceptions.
|
|
1161
|
-
exceptionsToMerge.reduce((currentException, nextException)=>{
|
|
1162
|
-
if (currentException.nextSibling == nextException) {
|
|
1163
|
-
writer.merge(writer.createPositionAfter(currentException));
|
|
1164
|
-
return currentException;
|
|
1165
|
-
}
|
|
1166
|
-
return nextException;
|
|
1167
|
-
});
|
|
1168
|
-
// Remove inline exceptions from block exception.
|
|
1169
|
-
schema.removeDisallowedAttributes(blocks, writer);
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
908
|
+
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
909
|
+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
910
|
+
*/
|
|
911
|
+
/**
|
|
912
|
+
* @module restricted-editing/restrictededitingexceptionblockcommand
|
|
913
|
+
*/
|
|
914
|
+
/**
|
|
915
|
+
* The command that toggles exception blocks for the restricted editing.
|
|
916
|
+
*/
|
|
917
|
+
var RestrictedEditingExceptionBlockCommand = class extends Command {
|
|
918
|
+
/**
|
|
919
|
+
* @inheritDoc
|
|
920
|
+
*/
|
|
921
|
+
refresh() {
|
|
922
|
+
this.value = this._getValue();
|
|
923
|
+
this.isEnabled = this._checkEnabled();
|
|
924
|
+
}
|
|
925
|
+
/**
|
|
926
|
+
* Wraps or unwraps the selected blocks with non-restricted area.
|
|
927
|
+
*
|
|
928
|
+
* @fires execute
|
|
929
|
+
* @param options Command options.
|
|
930
|
+
* @param options.forceValue If set, it will force the command behavior. If `true`, the command will apply a block exception,
|
|
931
|
+
* otherwise the command will remove the block exception. If not set, the command will act basing on its current value.
|
|
932
|
+
*/
|
|
933
|
+
execute(options = {}) {
|
|
934
|
+
const model = this.editor.model;
|
|
935
|
+
const schema = model.schema;
|
|
936
|
+
const selection = model.document.selection;
|
|
937
|
+
const blocks = Array.from(selection.getSelectedBlocks());
|
|
938
|
+
const value = options.forceValue === void 0 ? !this.value : options.forceValue;
|
|
939
|
+
model.change((writer) => {
|
|
940
|
+
if (!value) {
|
|
941
|
+
const blocksToUnwrap = blocks.map((block) => {
|
|
942
|
+
return findExceptionContentBlock(block);
|
|
943
|
+
}).filter((exception) => !!exception);
|
|
944
|
+
this._removeException(writer, blocksToUnwrap);
|
|
945
|
+
} else {
|
|
946
|
+
const blocksToWrap = blocks.filter((block) => {
|
|
947
|
+
return findException(block) || checkCanBeWrapped(schema, block);
|
|
948
|
+
});
|
|
949
|
+
this._applyException(writer, blocksToWrap);
|
|
950
|
+
}
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
/**
|
|
954
|
+
* Checks the command's {@link #value}.
|
|
955
|
+
*/
|
|
956
|
+
_getValue() {
|
|
957
|
+
const selection = this.editor.model.document.selection;
|
|
958
|
+
const firstBlock = first(selection.getSelectedBlocks());
|
|
959
|
+
return !!(firstBlock && findException(firstBlock));
|
|
960
|
+
}
|
|
961
|
+
/**
|
|
962
|
+
* Checks whether the command can be enabled in the current context.
|
|
963
|
+
*
|
|
964
|
+
* @returns Whether the command should be enabled.
|
|
965
|
+
*/
|
|
966
|
+
_checkEnabled() {
|
|
967
|
+
if (this.value) return true;
|
|
968
|
+
const selection = this.editor.model.document.selection;
|
|
969
|
+
const schema = this.editor.model.schema;
|
|
970
|
+
const firstBlock = first(selection.getSelectedBlocks());
|
|
971
|
+
if (!firstBlock) return false;
|
|
972
|
+
return checkCanBeWrapped(schema, firstBlock);
|
|
973
|
+
}
|
|
974
|
+
/**
|
|
975
|
+
* Unwraps the exception from given blocks.
|
|
976
|
+
*
|
|
977
|
+
* If blocks which are supposed to be unwrapped are in the middle of an exception,
|
|
978
|
+
* start it or end it, then the exception will be split (if needed) and the blocks
|
|
979
|
+
* will be moved out of it, so other exception blocks remained wrapped.
|
|
980
|
+
*/
|
|
981
|
+
_removeException(writer, blocks) {
|
|
982
|
+
getRangesOfBlockGroups(writer, blocks).reverse().forEach((groupRange) => {
|
|
983
|
+
if (groupRange.start.isAtStart && groupRange.end.isAtEnd) {
|
|
984
|
+
writer.unwrap(groupRange.start.parent);
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
if (groupRange.start.isAtStart) {
|
|
988
|
+
const positionBefore = writer.createPositionBefore(groupRange.start.parent);
|
|
989
|
+
writer.move(groupRange, positionBefore);
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
if (!groupRange.end.isAtEnd) writer.split(groupRange.end);
|
|
993
|
+
const positionAfter = writer.createPositionAfter(groupRange.end.parent);
|
|
994
|
+
writer.move(groupRange, positionAfter);
|
|
995
|
+
});
|
|
996
|
+
}
|
|
997
|
+
/**
|
|
998
|
+
* Applies the exception to given blocks.
|
|
999
|
+
*/
|
|
1000
|
+
_applyException(writer, blocks) {
|
|
1001
|
+
const schema = this.editor.model.schema;
|
|
1002
|
+
const exceptionsToMerge = [];
|
|
1003
|
+
getRangesOfBlockGroups(writer, blocks).reverse().forEach((groupRange) => {
|
|
1004
|
+
let exception = findException(groupRange.start);
|
|
1005
|
+
if (!exception) {
|
|
1006
|
+
exception = writer.createElement("restrictedEditingException");
|
|
1007
|
+
writer.wrap(groupRange, exception);
|
|
1008
|
+
}
|
|
1009
|
+
exceptionsToMerge.push(exception);
|
|
1010
|
+
});
|
|
1011
|
+
exceptionsToMerge.reverse();
|
|
1012
|
+
if (exceptionsToMerge.length) {
|
|
1013
|
+
const previousSibling = exceptionsToMerge.at(0).previousSibling;
|
|
1014
|
+
const nextSibling = exceptionsToMerge.at(-1).nextSibling;
|
|
1015
|
+
if (previousSibling?.is("element", "restrictedEditingException")) exceptionsToMerge.unshift(previousSibling);
|
|
1016
|
+
if (nextSibling?.is("element", "restrictedEditingException")) exceptionsToMerge.push(nextSibling);
|
|
1017
|
+
}
|
|
1018
|
+
exceptionsToMerge.reduce((currentException, nextException) => {
|
|
1019
|
+
if (currentException.nextSibling == nextException) {
|
|
1020
|
+
writer.merge(writer.createPositionAfter(currentException));
|
|
1021
|
+
return currentException;
|
|
1022
|
+
}
|
|
1023
|
+
return nextException;
|
|
1024
|
+
});
|
|
1025
|
+
schema.removeDisallowedAttributes(blocks, writer);
|
|
1026
|
+
}
|
|
1027
|
+
};
|
|
1172
1028
|
function findException(elementOrPosition) {
|
|
1173
|
-
|
|
1174
|
-
includeSelf: true
|
|
1175
|
-
});
|
|
1029
|
+
return elementOrPosition.findAncestor("restrictedEditingException", { includeSelf: true });
|
|
1176
1030
|
}
|
|
1177
1031
|
function findExceptionContentBlock(element) {
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
}
|
|
1185
|
-
return null;
|
|
1032
|
+
let node = element;
|
|
1033
|
+
while (node.parent) {
|
|
1034
|
+
if (node.parent.name == "restrictedEditingException") return node;
|
|
1035
|
+
node = node.parent;
|
|
1036
|
+
}
|
|
1037
|
+
return null;
|
|
1186
1038
|
}
|
|
1187
1039
|
/**
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
return ranges;
|
|
1040
|
+
* Returns a minimal array of ranges containing groups of subsequent blocks.
|
|
1041
|
+
*
|
|
1042
|
+
* content: abcdefgh
|
|
1043
|
+
* blocks: [ a, b, d, f, g, h ]
|
|
1044
|
+
* output ranges: [ab]c[d]e[fgh]
|
|
1045
|
+
*/
|
|
1046
|
+
function getRangesOfBlockGroups(writer, blocks) {
|
|
1047
|
+
let startPosition;
|
|
1048
|
+
let i = 0;
|
|
1049
|
+
const ranges = [];
|
|
1050
|
+
while (i < blocks.length) {
|
|
1051
|
+
const block = blocks[i];
|
|
1052
|
+
const nextBlock = blocks[i + 1];
|
|
1053
|
+
if (!startPosition) startPosition = writer.createPositionBefore(block);
|
|
1054
|
+
if (!nextBlock || block.nextSibling != nextBlock) {
|
|
1055
|
+
ranges.push(writer.createRange(startPosition, writer.createPositionAfter(block)));
|
|
1056
|
+
startPosition = null;
|
|
1057
|
+
}
|
|
1058
|
+
i++;
|
|
1059
|
+
}
|
|
1060
|
+
return ranges;
|
|
1210
1061
|
}
|
|
1211
1062
|
/**
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
// Is block allowed inside block exception.
|
|
1220
|
-
if (!schema.checkChild(parentContext.push('restrictedEditingException'), block)) {
|
|
1221
|
-
return false;
|
|
1222
|
-
}
|
|
1223
|
-
return true;
|
|
1063
|
+
* Checks whether exception can wrap the block.
|
|
1064
|
+
*/
|
|
1065
|
+
function checkCanBeWrapped(schema, block) {
|
|
1066
|
+
const parentContext = schema.createContext(block.parent);
|
|
1067
|
+
if (!schema.checkChild(parentContext, "restrictedEditingException")) return false;
|
|
1068
|
+
if (!schema.checkChild(parentContext.push("restrictedEditingException"), block)) return false;
|
|
1069
|
+
return true;
|
|
1224
1070
|
}
|
|
1225
1071
|
|
|
1226
1072
|
/**
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1073
|
+
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
1074
|
+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
1075
|
+
*/
|
|
1076
|
+
/**
|
|
1077
|
+
* @module restricted-editing/restrictededitingexceptionautocommand
|
|
1078
|
+
*/
|
|
1079
|
+
/**
|
|
1080
|
+
* The command that toggles exceptions from the restricted editing on text or on blocks.
|
|
1081
|
+
*/
|
|
1082
|
+
var RestrictedEditingExceptionAutoCommand = class extends Command {
|
|
1083
|
+
/**
|
|
1084
|
+
* The inline exception command.
|
|
1085
|
+
*/
|
|
1086
|
+
_inlineCommand;
|
|
1087
|
+
/**
|
|
1088
|
+
* The block exception command.
|
|
1089
|
+
*/
|
|
1090
|
+
_blockCommand;
|
|
1091
|
+
/**
|
|
1092
|
+
* @inheritDoc
|
|
1093
|
+
*/
|
|
1094
|
+
constructor(editor) {
|
|
1095
|
+
super(editor);
|
|
1096
|
+
this._inlineCommand = editor.commands.get("restrictedEditingException");
|
|
1097
|
+
this._blockCommand = editor.commands.get("restrictedEditingExceptionBlock");
|
|
1098
|
+
}
|
|
1099
|
+
/**
|
|
1100
|
+
* @inheritDoc
|
|
1101
|
+
*/
|
|
1102
|
+
refresh() {
|
|
1103
|
+
this.value = this._inlineCommand.value || this._blockCommand.value;
|
|
1104
|
+
this.isEnabled = this._inlineCommand.isEnabled || this._blockCommand.isEnabled;
|
|
1105
|
+
}
|
|
1106
|
+
/**
|
|
1107
|
+
* @inheritDoc
|
|
1108
|
+
*/
|
|
1109
|
+
execute() {
|
|
1110
|
+
const editor = this.editor;
|
|
1111
|
+
if (this._inlineCommand.value) editor.execute("restrictedEditingException");
|
|
1112
|
+
else if (this._blockCommand.value) editor.execute("restrictedEditingExceptionBlock");
|
|
1113
|
+
else if (this._inlineCommand.isEnabled && !this._blockCommand.isEnabled) editor.execute("restrictedEditingException");
|
|
1114
|
+
else if (this._blockCommand.isEnabled && !this._inlineCommand.isEnabled) editor.execute("restrictedEditingExceptionBlock");
|
|
1115
|
+
else {
|
|
1116
|
+
const schema = editor.model.schema;
|
|
1117
|
+
const selection = editor.model.document.selection;
|
|
1118
|
+
const selectedElement = selection.getSelectedElement();
|
|
1119
|
+
if (selection.isCollapsed || selectedElement && schema.isObject(selectedElement) && schema.isBlock(selectedElement) || count(selection.getSelectedBlocks()) > 1) editor.execute("restrictedEditingExceptionBlock");
|
|
1120
|
+
else editor.execute("restrictedEditingException");
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
};
|
|
1273
1124
|
|
|
1274
1125
|
/**
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
model: 'restrictedEditingException',
|
|
1390
|
-
view: {
|
|
1391
|
-
name: 'div',
|
|
1392
|
-
classes: 'restricted-editing-exception'
|
|
1393
|
-
}
|
|
1394
|
-
});
|
|
1395
|
-
registerFallbackUpcastConverter(editor);
|
|
1396
|
-
editor.conversion.for('downcast').attributeToElement({
|
|
1397
|
-
model: 'restrictedEditingException',
|
|
1398
|
-
view: (modelAttributeValue, { writer })=>{
|
|
1399
|
-
if (modelAttributeValue) {
|
|
1400
|
-
// Make the restricted editing <span> outer-most in the view.
|
|
1401
|
-
return writer.createAttributeElement('span', {
|
|
1402
|
-
class: 'restricted-editing-exception'
|
|
1403
|
-
}, {
|
|
1404
|
-
priority: -10
|
|
1405
|
-
});
|
|
1406
|
-
}
|
|
1407
|
-
}
|
|
1408
|
-
}).elementToElement({
|
|
1409
|
-
model: 'restrictedEditingException',
|
|
1410
|
-
view: {
|
|
1411
|
-
name: 'div',
|
|
1412
|
-
classes: 'restricted-editing-exception'
|
|
1413
|
-
}
|
|
1414
|
-
});
|
|
1415
|
-
editor.commands.add('restrictedEditingException', new RestrictedEditingExceptionCommand(editor));
|
|
1416
|
-
editor.commands.add('restrictedEditingExceptionBlock', new RestrictedEditingExceptionBlockCommand(editor));
|
|
1417
|
-
editor.commands.add('restrictedEditingExceptionAuto', new RestrictedEditingExceptionAutoCommand(editor));
|
|
1418
|
-
editor.editing.view.change((writer)=>{
|
|
1419
|
-
for (const root of editor.editing.view.document.roots){
|
|
1420
|
-
writer.addClass('ck-restricted-editing_mode_standard', root);
|
|
1421
|
-
}
|
|
1422
|
-
});
|
|
1423
|
-
}
|
|
1424
|
-
}
|
|
1126
|
+
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
1127
|
+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
1128
|
+
*/
|
|
1129
|
+
/**
|
|
1130
|
+
* @module restricted-editing/standardeditingmodeediting
|
|
1131
|
+
*/
|
|
1132
|
+
/**
|
|
1133
|
+
* The standard editing mode editing feature.
|
|
1134
|
+
*
|
|
1135
|
+
* * It introduces the `restrictedEditingException` text attribute that is rendered as
|
|
1136
|
+
* a `<span>` element with the `restricted-editing-exception` CSS class.
|
|
1137
|
+
* * It registers the `'restrictedEditingException'` command.
|
|
1138
|
+
*/
|
|
1139
|
+
var StandardEditingModeEditing = class extends Plugin {
|
|
1140
|
+
/**
|
|
1141
|
+
* @inheritDoc
|
|
1142
|
+
*/
|
|
1143
|
+
static get pluginName() {
|
|
1144
|
+
return "StandardEditingModeEditing";
|
|
1145
|
+
}
|
|
1146
|
+
/**
|
|
1147
|
+
* @inheritDoc
|
|
1148
|
+
*/
|
|
1149
|
+
static get isOfficialPlugin() {
|
|
1150
|
+
return true;
|
|
1151
|
+
}
|
|
1152
|
+
/**
|
|
1153
|
+
* @inheritDoc
|
|
1154
|
+
*/
|
|
1155
|
+
init() {
|
|
1156
|
+
const editor = this.editor;
|
|
1157
|
+
const schema = editor.model.schema;
|
|
1158
|
+
schema.extend("$text", { allowAttributes: ["restrictedEditingException"] });
|
|
1159
|
+
schema.register("restrictedEditingException", {
|
|
1160
|
+
allowWhere: "$container",
|
|
1161
|
+
allowContentOf: "$container"
|
|
1162
|
+
});
|
|
1163
|
+
schema.addChildCheck((context) => {
|
|
1164
|
+
for (const item of context) if (item.name == "restrictedEditingException") return false;
|
|
1165
|
+
}, "restrictedEditingException");
|
|
1166
|
+
schema.addAttributeCheck((context) => {
|
|
1167
|
+
for (const item of context) if (item.name == "restrictedEditingException") return false;
|
|
1168
|
+
}, "restrictedEditingException");
|
|
1169
|
+
editor.model.document.registerPostFixer((writer) => {
|
|
1170
|
+
const changes = editor.model.document.differ.getChanges();
|
|
1171
|
+
const unwrap = /* @__PURE__ */ new Set();
|
|
1172
|
+
const remove = /* @__PURE__ */ new Set();
|
|
1173
|
+
const merge = /* @__PURE__ */ new Set();
|
|
1174
|
+
let changed = false;
|
|
1175
|
+
for (const entry of changes) if (entry.type == "insert") {
|
|
1176
|
+
const range = writer.createRange(entry.position, entry.position.getShiftedBy(entry.length));
|
|
1177
|
+
for (const child of range.getItems()) if (child.is("element", "restrictedEditingException")) if (!schema.checkChild(writer.createPositionBefore(child), child)) unwrap.add(child);
|
|
1178
|
+
else if (child.isEmpty) remove.add(child);
|
|
1179
|
+
else merge.add(child);
|
|
1180
|
+
else if (child.is("$textProxy") && child.hasAttribute("restrictedEditingException") && !schema.checkAttribute(child, "restrictedEditingException")) {
|
|
1181
|
+
writer.removeAttribute("restrictedEditingException", child);
|
|
1182
|
+
changed = true;
|
|
1183
|
+
}
|
|
1184
|
+
} else if (entry.type == "remove") {
|
|
1185
|
+
const parent = entry.position.parent;
|
|
1186
|
+
if (parent.is("element", "restrictedEditingException") && parent.isEmpty) remove.add(parent);
|
|
1187
|
+
for (const child of parent.getChildren()) if (child.is("element", "restrictedEditingException")) merge.add(child);
|
|
1188
|
+
}
|
|
1189
|
+
for (const child of unwrap) {
|
|
1190
|
+
writer.unwrap(child);
|
|
1191
|
+
changed = true;
|
|
1192
|
+
}
|
|
1193
|
+
for (const child of remove) {
|
|
1194
|
+
writer.remove(child);
|
|
1195
|
+
changed = true;
|
|
1196
|
+
}
|
|
1197
|
+
for (const child of merge) {
|
|
1198
|
+
if (child.root.rootName == "$graveyard") continue;
|
|
1199
|
+
const nodeBefore = child.previousSibling;
|
|
1200
|
+
const nodeAfter = child.nextSibling;
|
|
1201
|
+
if (nodeBefore && nodeBefore.is("element", "restrictedEditingException")) writer.merge(writer.createPositionBefore(child));
|
|
1202
|
+
if (nodeAfter && nodeAfter.is("element", "restrictedEditingException")) writer.merge(writer.createPositionAfter(child));
|
|
1203
|
+
}
|
|
1204
|
+
return changed;
|
|
1205
|
+
});
|
|
1206
|
+
editor.conversion.for("upcast").elementToAttribute({
|
|
1207
|
+
model: "restrictedEditingException",
|
|
1208
|
+
view: {
|
|
1209
|
+
name: "span",
|
|
1210
|
+
classes: "restricted-editing-exception"
|
|
1211
|
+
}
|
|
1212
|
+
}).elementToElement({
|
|
1213
|
+
model: "restrictedEditingException",
|
|
1214
|
+
view: {
|
|
1215
|
+
name: "div",
|
|
1216
|
+
classes: "restricted-editing-exception"
|
|
1217
|
+
}
|
|
1218
|
+
});
|
|
1219
|
+
registerFallbackUpcastConverter(editor);
|
|
1220
|
+
editor.conversion.for("downcast").attributeToElement({
|
|
1221
|
+
model: "restrictedEditingException",
|
|
1222
|
+
view: (modelAttributeValue, { writer }) => {
|
|
1223
|
+
if (modelAttributeValue) return writer.createAttributeElement("span", { class: "restricted-editing-exception" }, { priority: -10 });
|
|
1224
|
+
}
|
|
1225
|
+
}).elementToElement({
|
|
1226
|
+
model: "restrictedEditingException",
|
|
1227
|
+
view: {
|
|
1228
|
+
name: "div",
|
|
1229
|
+
classes: "restricted-editing-exception"
|
|
1230
|
+
}
|
|
1231
|
+
});
|
|
1232
|
+
editor.commands.add("restrictedEditingException", new RestrictedEditingExceptionCommand(editor));
|
|
1233
|
+
editor.commands.add("restrictedEditingExceptionBlock", new RestrictedEditingExceptionBlockCommand(editor));
|
|
1234
|
+
editor.commands.add("restrictedEditingExceptionAuto", new RestrictedEditingExceptionAutoCommand(editor));
|
|
1235
|
+
editor.editing.view.change((writer) => {
|
|
1236
|
+
for (const root of editor.editing.view.document.roots) writer.addClass("ck-restricted-editing_mode_standard", root);
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
1239
|
+
};
|
|
1425
1240
|
/**
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
});
|
|
1445
|
-
if (!conversionApi.safeInsert(modelText, data.modelCursor)) {
|
|
1446
|
-
return;
|
|
1447
|
-
}
|
|
1448
|
-
conversionApi.consumable.consume(data.viewItem, match);
|
|
1449
|
-
data.modelRange = conversionApi.writer.createRange(data.modelCursor, data.modelCursor.getShiftedBy(modelText.offsetSize));
|
|
1450
|
-
data.modelCursor = data.modelRange.end;
|
|
1451
|
-
}, {
|
|
1452
|
-
priority: 'low'
|
|
1453
|
-
}));
|
|
1241
|
+
* Fallback upcast converter for empty exception span inside a table cell.
|
|
1242
|
+
*/
|
|
1243
|
+
function registerFallbackUpcastConverter(editor) {
|
|
1244
|
+
const matcher = new Matcher({
|
|
1245
|
+
name: "span",
|
|
1246
|
+
classes: "restricted-editing-exception"
|
|
1247
|
+
});
|
|
1248
|
+
editor.conversion.for("upcast").add((dispatcher) => dispatcher.on("element:span", (evt, data, conversionApi) => {
|
|
1249
|
+
const matcherResult = matcher.match(data.viewItem);
|
|
1250
|
+
if (!matcherResult) return;
|
|
1251
|
+
const match = matcherResult.match;
|
|
1252
|
+
if (!conversionApi.consumable.test(data.viewItem, match)) return;
|
|
1253
|
+
const modelText = conversionApi.writer.createText(" ", { restrictedEditingException: true });
|
|
1254
|
+
if (!conversionApi.safeInsert(modelText, data.modelCursor)) return;
|
|
1255
|
+
conversionApi.consumable.consume(data.viewItem, match);
|
|
1256
|
+
data.modelRange = conversionApi.writer.createRange(data.modelCursor, data.modelCursor.getShiftedBy(modelText.offsetSize));
|
|
1257
|
+
data.modelCursor = data.modelRange.end;
|
|
1258
|
+
}, { priority: "low" }));
|
|
1454
1259
|
}
|
|
1455
1260
|
|
|
1456
1261
|
/**
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
view.bind('label').to(command, 'value', (value)=>{
|
|
1574
|
-
return value ? t('Disable block editing') : t('Enable block editing');
|
|
1575
|
-
});
|
|
1576
|
-
} else {
|
|
1577
|
-
view.bind('label').to(command, 'value', (value)=>{
|
|
1578
|
-
return value ? t('Disable inline editing') : t('Enable inline editing');
|
|
1579
|
-
});
|
|
1580
|
-
}
|
|
1581
|
-
// Execute the command.
|
|
1582
|
-
this.listenTo(view, 'execute', ()=>{
|
|
1583
|
-
editor.execute(commandName);
|
|
1584
|
-
editor.editing.view.focus();
|
|
1585
|
-
});
|
|
1586
|
-
return view;
|
|
1587
|
-
}
|
|
1588
|
-
}
|
|
1262
|
+
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
1263
|
+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
1264
|
+
*/
|
|
1265
|
+
/**
|
|
1266
|
+
* @module restricted-editing/standardeditingmodeui
|
|
1267
|
+
*/
|
|
1268
|
+
/**
|
|
1269
|
+
* The standard editing mode UI feature.
|
|
1270
|
+
*
|
|
1271
|
+
* It introduces the `'restrictedEditingException'` button that marks text as unrestricted for editing.
|
|
1272
|
+
*/
|
|
1273
|
+
var StandardEditingModeUI = class extends Plugin {
|
|
1274
|
+
/**
|
|
1275
|
+
* @inheritDoc
|
|
1276
|
+
*/
|
|
1277
|
+
static get pluginName() {
|
|
1278
|
+
return "StandardEditingModeUI";
|
|
1279
|
+
}
|
|
1280
|
+
/**
|
|
1281
|
+
* @inheritDoc
|
|
1282
|
+
*/
|
|
1283
|
+
static get isOfficialPlugin() {
|
|
1284
|
+
return true;
|
|
1285
|
+
}
|
|
1286
|
+
/**
|
|
1287
|
+
* @inheritDoc
|
|
1288
|
+
*/
|
|
1289
|
+
init() {
|
|
1290
|
+
const editor = this.editor;
|
|
1291
|
+
const componentFactory = editor.ui.componentFactory;
|
|
1292
|
+
componentFactory.add("restrictedEditingException:dropdown", (locale) => {
|
|
1293
|
+
const dropdownView = createDropdown(locale);
|
|
1294
|
+
const t = locale.t;
|
|
1295
|
+
const buttons = [componentFactory.create("restrictedEditingException:inline"), componentFactory.create("restrictedEditingException:block")];
|
|
1296
|
+
for (const button of buttons) button.set({
|
|
1297
|
+
withText: true,
|
|
1298
|
+
tooltip: false
|
|
1299
|
+
});
|
|
1300
|
+
addToolbarToDropdown(dropdownView, buttons, {
|
|
1301
|
+
enableActiveItemFocusOnDropdownOpen: true,
|
|
1302
|
+
isVertical: true,
|
|
1303
|
+
ariaLabel: t("Enable editing")
|
|
1304
|
+
});
|
|
1305
|
+
dropdownView.buttonView.set({
|
|
1306
|
+
label: t("Enable editing"),
|
|
1307
|
+
icon: IconContentUnlock,
|
|
1308
|
+
tooltip: true
|
|
1309
|
+
});
|
|
1310
|
+
dropdownView.extendTemplate({ attributes: { class: "ck-restricted-editing-dropdown" } });
|
|
1311
|
+
dropdownView.bind("isEnabled").toMany(buttons, "isEnabled", (...areEnabled) => {
|
|
1312
|
+
return areEnabled.some((isEnabled) => isEnabled);
|
|
1313
|
+
});
|
|
1314
|
+
this.listenTo(dropdownView, "execute", () => {
|
|
1315
|
+
editor.editing.view.focus();
|
|
1316
|
+
});
|
|
1317
|
+
return dropdownView;
|
|
1318
|
+
});
|
|
1319
|
+
componentFactory.add("restrictedEditingException:inline", () => {
|
|
1320
|
+
const button = this._createButton("restrictedEditingException", ButtonView);
|
|
1321
|
+
button.set({ tooltip: true });
|
|
1322
|
+
return button;
|
|
1323
|
+
});
|
|
1324
|
+
componentFactory.add("restrictedEditingException:block", () => {
|
|
1325
|
+
const button = this._createButton("restrictedEditingExceptionBlock", ButtonView);
|
|
1326
|
+
button.set({ tooltip: true });
|
|
1327
|
+
return button;
|
|
1328
|
+
});
|
|
1329
|
+
componentFactory.add("menuBar:restrictedEditingException:inline", () => {
|
|
1330
|
+
return this._createButton("restrictedEditingException", MenuBarMenuListItemButtonView);
|
|
1331
|
+
});
|
|
1332
|
+
componentFactory.add("menuBar:restrictedEditingException:block", () => {
|
|
1333
|
+
return this._createButton("restrictedEditingExceptionBlock", MenuBarMenuListItemButtonView);
|
|
1334
|
+
});
|
|
1335
|
+
componentFactory.add("restrictedEditingException:auto", () => {
|
|
1336
|
+
const button = this._createButton("restrictedEditingExceptionAuto", ButtonView);
|
|
1337
|
+
button.set({ tooltip: true });
|
|
1338
|
+
return button;
|
|
1339
|
+
});
|
|
1340
|
+
componentFactory.add("menuBar:restrictedEditingException:auto", () => {
|
|
1341
|
+
return this._createButton("restrictedEditingExceptionAuto", MenuBarMenuListItemButtonView);
|
|
1342
|
+
});
|
|
1343
|
+
componentFactory.add("restrictedEditingException", () => {
|
|
1344
|
+
return componentFactory.create("restrictedEditingException:inline");
|
|
1345
|
+
});
|
|
1346
|
+
componentFactory.add("menuBar:restrictedEditingException", () => {
|
|
1347
|
+
return componentFactory.create("menuBar:restrictedEditingException:inline");
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
/**
|
|
1351
|
+
* Creates a button for restricted editing exception command to use either in toolbar or in menu bar.
|
|
1352
|
+
*/
|
|
1353
|
+
_createButton(commandName, ButtonClass) {
|
|
1354
|
+
const editor = this.editor;
|
|
1355
|
+
const locale = editor.locale;
|
|
1356
|
+
const command = this.editor.commands.get(commandName);
|
|
1357
|
+
const view = new ButtonClass(locale);
|
|
1358
|
+
const t = locale.t;
|
|
1359
|
+
view.icon = IconContentUnlock;
|
|
1360
|
+
view.isToggleable = true;
|
|
1361
|
+
view.bind("isOn", "isEnabled").to(command, "value", "isEnabled");
|
|
1362
|
+
if (commandName == "restrictedEditingExceptionAuto") view.bind("label").to(command, "value", (value) => {
|
|
1363
|
+
return value ? t("Disable editing") : t("Enable editing");
|
|
1364
|
+
});
|
|
1365
|
+
else if (commandName == "restrictedEditingExceptionBlock") view.bind("label").to(command, "value", (value) => {
|
|
1366
|
+
return value ? t("Disable block editing") : t("Enable block editing");
|
|
1367
|
+
});
|
|
1368
|
+
else view.bind("label").to(command, "value", (value) => {
|
|
1369
|
+
return value ? t("Disable inline editing") : t("Enable inline editing");
|
|
1370
|
+
});
|
|
1371
|
+
this.listenTo(view, "execute", () => {
|
|
1372
|
+
editor.execute(commandName);
|
|
1373
|
+
editor.editing.view.focus();
|
|
1374
|
+
});
|
|
1375
|
+
return view;
|
|
1376
|
+
}
|
|
1377
|
+
};
|
|
1589
1378
|
|
|
1590
1379
|
/**
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1380
|
+
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
1381
|
+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
1382
|
+
*/
|
|
1383
|
+
/**
|
|
1384
|
+
* @module restricted-editing/standardeditingmode
|
|
1385
|
+
*/
|
|
1386
|
+
/**
|
|
1387
|
+
* The standard editing mode plugin.
|
|
1388
|
+
*
|
|
1389
|
+
* This is a "glue" plugin that loads the following plugins:
|
|
1390
|
+
*
|
|
1391
|
+
* * The {@link module:restricted-editing/standardeditingmodeediting~StandardEditingModeEditing standard mode editing feature}.
|
|
1392
|
+
* * The {@link module:restricted-editing/standardeditingmodeui~StandardEditingModeUI standard mode UI feature}.
|
|
1393
|
+
*/
|
|
1394
|
+
var StandardEditingMode = class extends Plugin {
|
|
1395
|
+
/**
|
|
1396
|
+
* @inheritDoc
|
|
1397
|
+
*/
|
|
1398
|
+
static get pluginName() {
|
|
1399
|
+
return "StandardEditingMode";
|
|
1400
|
+
}
|
|
1401
|
+
/**
|
|
1402
|
+
* @inheritDoc
|
|
1403
|
+
*/
|
|
1404
|
+
static get isOfficialPlugin() {
|
|
1405
|
+
return true;
|
|
1406
|
+
}
|
|
1407
|
+
static get requires() {
|
|
1408
|
+
return [StandardEditingModeEditing, StandardEditingModeUI];
|
|
1409
|
+
}
|
|
1410
|
+
};
|
|
1411
|
+
|
|
1412
|
+
/**
|
|
1413
|
+
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
1414
|
+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
1415
|
+
*/
|
|
1615
1416
|
|
|
1616
1417
|
export { RestrictedEditingExceptionAutoCommand, RestrictedEditingExceptionBlockCommand, RestrictedEditingExceptionCommand, RestrictedEditingMode, RestrictedEditingModeEditing, RestrictedEditingModeNavigationCommand, RestrictedEditingModeUI, StandardEditingMode, StandardEditingModeEditing, StandardEditingModeUI, extendMarkerOnTypingPostFixer as _extendRestrictedEditingMarkerOnTypingPostFixer, getMarkerAtPosition as _getRestrictedEditingMarkerAtPosition, isPositionInRangeBoundaries as _isRestrictedEditingPositionInRangeBoundaries, isSelectionInMarker as _isRestrictedEditingSelectionInMarker, resurrectCollapsedMarkerPostFixer as _resurrectRestrictedEditingCollapsedMarkerPostFixer, setupExceptionHighlighting as _setupRestrictedEditingExceptionHighlighting, upcastHighlightToMarker as _upcastRestrictedEditingHighlightToMarker };
|
|
1617
|
-
//# sourceMappingURL=index.js.map
|
|
1418
|
+
//# sourceMappingURL=index.js.map
|