@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/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 '@ckeditor/ckeditor5-core/dist/index.js';
6
- import { getCode, parseKeystroke, Collection, first, count } from '@ckeditor/ckeditor5-utils/dist/index.js';
7
- import { Matcher } from '@ckeditor/ckeditor5-engine/dist/index.js';
8
- import { IconContentLock, IconContentUnlock } from '@ckeditor/ckeditor5-icons/dist/index.js';
9
- import { createDropdown, addListToDropdown, MenuBarMenuView, MenuBarMenuListView, MenuBarMenuListItemView, MenuBarMenuListItemButtonView, UIModel, addToolbarToDropdown, ButtonView } from '@ckeditor/ckeditor5-ui/dist/index.js';
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
- * @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
- * @module restricted-editing/restrictededitingmode/utils
16
- */ /**
17
- * Returns a single "restricted-editing-exception" marker at a given position. Contrary to
18
- * {@link module:engine/model/markercollection~MarkerCollection#getMarkersAtPosition}, it returns a marker also when the position is
19
- * equal to one of the marker's start or end positions.
20
- *
21
- * @internal
22
- */ function getMarkerAtPosition(editor, position) {
23
- for (const marker of editor.model.markers){
24
- const markerRange = getExceptionRange(marker, editor.model);
25
- if (isPositionInRangeBoundaries(markerRange, position)) {
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
- * Checks if the position is fully contained in the range. Positions equal to range start or end are considered "in".
34
- *
35
- * @internal
36
- */ function isPositionInRangeBoundaries(range, position) {
37
- return range.containsPosition(position) || range.end.isEqual(position) || range.start.isEqual(position);
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
- * Checks if the selection is fully contained in the marker. Positions on marker boundaries are considered "in".
41
- *
42
- * ```xml
43
- * <marker>[]foo</marker> -> true
44
- * <marker>f[oo]</marker> -> true
45
- * <marker>f[oo</marker> ba]r -> false
46
- * <marker>foo</marker> []bar -> false
47
- * ```
48
- *
49
- * @internal
50
- */ function isSelectionInMarker(selection, model, marker) {
51
- if (!marker) {
52
- return false;
53
- }
54
- const markerRange = getExceptionRange(marker, model);
55
- if (selection.isCollapsed) {
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
- * Returns the marker range asjusted to the inside of exception wrapper element if needed.
62
- *
63
- * @internal
64
- */ function getExceptionRange(marker, model) {
65
- const markerRange = marker.getRange();
66
- const wrapperElement = markerRange.getContainedElement();
67
- if (wrapperElement && wrapperElement.is('element', 'restrictedEditingException')) {
68
- return model.createRangeIn(wrapperElement);
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
- * The command that allows navigation across the exceptions in the edited document.
75
- */ class RestrictedEditingModeNavigationCommand extends Command {
76
- /**
77
- * The direction of the command.
78
- */ _direction;
79
- /**
80
- * Creates an instance of the command.
81
- *
82
- * @param editor The editor instance.
83
- * @param direction The direction that the command works.
84
- */ constructor(editor, direction){
85
- super(editor);
86
- // It does not affect data so should be enabled in read-only mode and in restricted editing mode.
87
- this.affectsData = false;
88
- this._direction = direction;
89
- }
90
- /**
91
- * @inheritDoc
92
- */ refresh() {
93
- this.isEnabled = this._checkEnabled();
94
- }
95
- /**
96
- * Executes the command.
97
- *
98
- * @fires execute
99
- */ execute() {
100
- const position = getNearestExceptionRange(this.editor.model, this._direction);
101
- if (!position) {
102
- return;
103
- }
104
- this.editor.model.change((writer)=>{
105
- writer.setSelection(position);
106
- });
107
- }
108
- /**
109
- * Checks whether the command can be enabled in the current context.
110
- *
111
- * @returns Whether the command should be enabled.
112
- */ _checkEnabled() {
113
- return !!getNearestExceptionRange(this.editor.model, this._direction);
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
- * Returns the range of the exception marker closest to the last position of the model selection.
118
- */ function getNearestExceptionRange(model, direction) {
119
- const selection = model.document.selection;
120
- const selectionPosition = selection.getFirstPosition();
121
- const markerRanges = [];
122
- // Get all exception marker positions that start after/before the selection position.
123
- for (const marker of model.markers.getMarkersGroup('restrictedEditingException')){
124
- const markerRange = getExceptionRange(marker, model);
125
- // Checking parent because there two positions <paragraph>foo^</paragraph><paragraph>^bar</paragraph>
126
- // are touching but they will represent different markers.
127
- const isMarkerRangeTouching = selectionPosition.isTouching(markerRange.start) && selectionPosition.hasSameParentAs(markerRange.start) || selectionPosition.isTouching(markerRange.end) && selectionPosition.hasSameParentAs(markerRange.end);
128
- // <paragraph>foo <marker≥b[]ar</marker> baz</paragraph>
129
- // <paragraph>foo <marker≥b[ar</marker> ba]z</paragraph>
130
- // <paragraph>foo <marker≥bar</marker>[] baz</paragraph>
131
- // <paragraph>foo []<marker≥bar</marker> baz</paragraph>
132
- if (markerRange.containsPosition(selectionPosition) || isMarkerRangeTouching) {
133
- continue;
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
- * Adds a visual highlight style to a restricted editing exception that the selection is anchored to.
158
- *
159
- * The highlight is turned on by adding the `.restricted-editing-exception_selected` class to the
160
- * exception in the view:
161
- *
162
- * * The class is removed before the conversion starts, as callbacks added with the `'highest'` priority
163
- * to {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher} events.
164
- * * The class is added in the view post-fixer, after other changes in the model tree are converted to the view.
165
- *
166
- * This way, adding and removing the highlight does not interfere with conversion.
167
- *
168
- * @internal
169
- */ function setupExceptionHighlighting(editor) {
170
- const view = editor.editing.view;
171
- const model = editor.model;
172
- const highlightedMarkers = new Set();
173
- // Adding the class.
174
- view.document.registerPostFixer((writer)=>{
175
- const modelSelection = model.document.selection;
176
- const marker = getMarkerAtPosition(editor, modelSelection.anchor);
177
- if (!marker) {
178
- return false;
179
- }
180
- const modelWrapperElement = marker.getRange().getContainedElement();
181
- if (modelWrapperElement && modelWrapperElement.is('element', 'restrictedEditingException')) {
182
- const viewElement = editor.editing.mapper.toViewElement(modelWrapperElement);
183
- writer.addClass(HIGHLIGHT_CLASS, viewElement);
184
- highlightedMarkers.add(viewElement);
185
- } else {
186
- for (const viewElement of editor.editing.mapper.markerNameToElements(marker.name)){
187
- writer.addClass(HIGHLIGHT_CLASS, viewElement);
188
- highlightedMarkers.add(viewElement);
189
- }
190
- }
191
- return false;
192
- });
193
- // Removing the class.
194
- editor.conversion.for('editingDowncast').add((dispatcher)=>{
195
- // Make sure the highlight is removed on every possible event, before conversion is started.
196
- dispatcher.on('insert', removeHighlight, {
197
- priority: 'highest'
198
- });
199
- dispatcher.on('remove', removeHighlight, {
200
- priority: 'highest'
201
- });
202
- dispatcher.on('attribute', removeHighlight, {
203
- priority: 'highest'
204
- });
205
- dispatcher.on('cleanSelection', removeHighlight);
206
- function removeHighlight() {
207
- view.change((writer)=>{
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
- * A post-fixer that prevents removing a collapsed marker from the document.
218
- *
219
- * @internal
220
- */ function resurrectCollapsedMarkerPostFixer(editor) {
221
- // This post-fixer shouldn't be necessary after https://github.com/ckeditor/ckeditor5/issues/5778.
222
- return (writer)=>{
223
- let changeApplied = false;
224
- for (const { name, data } of editor.model.document.differ.getChangedMarkers()){
225
- if (name.startsWith('restrictedEditingException') && data.newRange && data.newRange.root.rootName == '$graveyard') {
226
- writer.updateMarker(name, {
227
- range: writer.createRange(writer.createPositionAt(data.oldRange.start))
228
- });
229
- changeApplied = true;
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
- * A post-fixer that extends a marker when the user types on its boundaries.
237
- *
238
- * @internal
239
- */ function extendMarkerOnTypingPostFixer(editor) {
240
- // This post-fixer shouldn't be necessary after https://github.com/ckeditor/ckeditor5/issues/5778.
241
- return (writer)=>{
242
- let changeApplied = false;
243
- const schema = editor.model.schema;
244
- for (const change of editor.model.document.differ.getChanges()){
245
- if (change.type == 'insert' && schema.checkChild('$block', change.name)) {
246
- changeApplied = _tryExtendMarkerStart(editor, change.position, change.length, writer) || changeApplied;
247
- changeApplied = _tryExtendMarkedEnd(editor, change.position, change.length, writer) || changeApplied;
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
- * A view highlight-to-marker conversion helper.
255
- *
256
- * @param config Conversion configuration.
257
- * @internal
258
- */ function upcastHighlightToMarker(config) {
259
- return (dispatcher)=>dispatcher.on('element', (evt, data, conversionApi)=>{
260
- const { writer } = conversionApi;
261
- const matcher = new Matcher(config.view);
262
- const matcherResult = matcher.match(data.viewItem);
263
- // If there is no match, this callback should not do anything.
264
- if (!matcherResult) {
265
- return;
266
- }
267
- const match = matcherResult.match;
268
- // Force consuming element's name (taken from upcast helpers elementToElement converter).
269
- match.name = true;
270
- if (!conversionApi.consumable.test(data.viewItem, match)) {
271
- return;
272
- }
273
- let position = data.modelCursor;
274
- let wrapperElement = null;
275
- if (config.useWrapperElement) {
276
- if (!conversionApi.schema.checkChild(position, 'restrictedEditingException')) {
277
- return;
278
- }
279
- wrapperElement = writer.createElement('restrictedEditingException');
280
- writer.insert(wrapperElement, position);
281
- position = writer.createPositionAt(wrapperElement, 0);
282
- }
283
- const { modelRange: convertedChildrenRange } = conversionApi.convertChildren(data.viewItem, position);
284
- conversionApi.consumable.consume(data.viewItem, match);
285
- const markerName = config.model();
286
- const fakeMarkerStart = writer.createElement('$marker', {
287
- 'data-name': markerName
288
- });
289
- const fakeMarkerEnd = writer.createElement('$marker', {
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
- * Extend marker if change detected on marker's start position.
306
- */ function _tryExtendMarkerStart(editor, position, length, writer) {
307
- const markerAtStart = getMarkerAtPosition(editor, position.getShiftedBy(length));
308
- if (markerAtStart && markerAtStart.getStart().isEqual(position.getShiftedBy(length))) {
309
- writer.updateMarker(markerAtStart, {
310
- range: writer.createRange(markerAtStart.getStart().getShiftedBy(-length), markerAtStart.getEnd())
311
- });
312
- return true;
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
- * Extend marker if change detected on marker's end position.
318
- */ function _tryExtendMarkedEnd(editor, position, length, writer) {
319
- const markerAtEnd = getMarkerAtPosition(editor, position);
320
- if (markerAtEnd && markerAtEnd.getEnd().isEqual(position)) {
321
- writer.updateMarker(markerAtEnd, {
322
- range: writer.createRange(markerAtEnd.getStart(), markerAtEnd.getEnd().getShiftedBy(length))
323
- });
324
- return true;
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
- * The restricted editing mode editing feature.
332
- *
333
- * * It introduces the exception marker group that renders to `<span>` elements with the `restricted-editing-exception` CSS class.
334
- * * It registers the `'goToPreviousRestrictedEditingException'` and `'goToNextRestrictedEditingException'` commands.
335
- * * It also enables highlighting exception markers that are selected.
336
- */ class RestrictedEditingModeEditing extends Plugin {
337
- /**
338
- * Command names that are enabled outside the non-restricted regions.
339
- */ _alwaysEnabled;
340
- /**
341
- * Commands allowed in non-restricted areas.
342
- *
343
- * Commands always enabled combine typing feature commands: `'input'`, `'insertText'`, `'delete'`, and `'deleteForward'` with
344
- * commands defined in the feature configuration.
345
- */ _allowedInException;
346
- /**
347
- * @inheritDoc
348
- */ static get pluginName() {
349
- return 'RestrictedEditingModeEditing';
350
- }
351
- /**
352
- * @inheritDoc
353
- * @internal
354
- */ static get licenseFeatureCode() {
355
- return 'RED';
356
- }
357
- /**
358
- * @inheritDoc
359
- */ static get isOfficialPlugin() {
360
- return true;
361
- }
362
- /**
363
- * @inheritDoc
364
- */ static get isPremiumPlugin() {
365
- return true;
366
- }
367
- /**
368
- * @inheritDoc
369
- */ constructor(editor){
370
- super(editor);
371
- editor.config.define('restrictedEditing', {
372
- allowedCommands: [
373
- 'bold',
374
- 'italic',
375
- 'link',
376
- 'unlink'
377
- ],
378
- allowedAttributes: [
379
- 'bold',
380
- 'italic',
381
- 'linkHref'
382
- ]
383
- });
384
- this._alwaysEnabled = new Set([
385
- 'undo',
386
- 'redo'
387
- ]);
388
- this._allowedInException = new Set([
389
- 'input',
390
- 'insertText',
391
- 'delete',
392
- 'deleteForward'
393
- ]);
394
- }
395
- /**
396
- * @inheritDoc
397
- */ init() {
398
- const editor = this.editor;
399
- const editingView = editor.editing.view;
400
- const allowedCommands = editor.config.get('restrictedEditing.allowedCommands');
401
- allowedCommands.forEach((commandName)=>this._allowedInException.add(commandName));
402
- this._setupSchema();
403
- this._setupConversion();
404
- this._setupCommandsToggling();
405
- this._setupRestrictions();
406
- // Commands & keystrokes that allow navigation in the content.
407
- editor.commands.add('goToPreviousRestrictedEditingException', new RestrictedEditingModeNavigationCommand(editor, 'backward'));
408
- editor.commands.add('goToNextRestrictedEditingException', new RestrictedEditingModeNavigationCommand(editor, 'forward'));
409
- this.listenTo(editingView.document, 'tab', (evt, data)=>{
410
- const commandName = !data.shiftKey ? 'goToNextRestrictedEditingException' : 'goToPreviousRestrictedEditingException';
411
- const command = editor.commands.get(commandName);
412
- if (command.isEnabled) {
413
- editor.execute(commandName);
414
- // Stop the event in the DOM: no listener in the web page will be triggered by this event.
415
- data.preventDefault();
416
- data.stopPropagation();
417
- }
418
- // Stop the event bubbling in the editor: no more callbacks will be executed for this keystroke.
419
- evt.stop();
420
- }, {
421
- context: '$capture'
422
- });
423
- this.listenTo(editingView.document, 'keydown', getSelectAllHandler(editor), {
424
- priority: 'high'
425
- });
426
- editingView.change((writer)=>{
427
- for (const root of editingView.document.roots){
428
- writer.addClass('ck-restricted-editing_mode_restricted', root);
429
- }
430
- });
431
- // Remove existing restricted editing markers when setting new data to prevent marker resurrection.
432
- // Without this, markers from removed content would be incorrectly restored due to the resurrection mechanism.
433
- // See more: https://github.com/ckeditor/ckeditor5/issues/9646#issuecomment-843064995
434
- editor.data.on('set', ()=>{
435
- editor.model.change((writer)=>{
436
- for (const marker of editor.model.markers.getMarkersGroup('restrictedEditingException')){
437
- writer.removeMarker(marker.name);
438
- }
439
- });
440
- }, {
441
- priority: 'high'
442
- });
443
- }
444
- /**
445
- * Makes the given command always enabled in the restricted editing mode (regardless
446
- * of selection location).
447
- *
448
- * To enable some commands in non-restricted areas of the content use
449
- * {@link module:restricted-editing/restrictededitingconfig~RestrictedEditingConfig#allowedCommands} configuration option.
450
- *
451
- * @param commandName Name of the command to enable.
452
- */ enableCommand(commandName) {
453
- const command = this.editor.commands.get(commandName);
454
- command.clearForceDisabled(COMMAND_FORCE_DISABLE_ID);
455
- this._alwaysEnabled.add(commandName);
456
- }
457
- /**
458
- * Registers block exception wrapper in the schema.
459
- */ _setupSchema() {
460
- const schema = this.editor.model.schema;
461
- schema.register('restrictedEditingException', {
462
- allowWhere: '$block',
463
- allowContentOf: '$container',
464
- isLimit: true
465
- });
466
- }
467
- /**
468
- * Sets up the restricted mode editing conversion:
469
- *
470
- * * ucpast & downcast converters,
471
- * * marker highlighting in the edting area,
472
- * * marker post-fixers.
473
- */ _setupConversion() {
474
- const editor = this.editor;
475
- const model = editor.model;
476
- const doc = model.document;
477
- // The restricted editing does not attach additional data to the zones so there's no need for smarter markers managing.
478
- // Also, the markers will only be created when loading the data.
479
- let markerNumber = 0;
480
- editor.conversion.for('upcast').add(upcastHighlightToMarker({
481
- view: {
482
- name: 'span',
483
- classes: 'restricted-editing-exception'
484
- },
485
- model: ()=>{
486
- markerNumber++; // Starting from restrictedEditingException:1 marker.
487
- return `restrictedEditingException:inline:${markerNumber}`;
488
- }
489
- }));
490
- editor.conversion.for('upcast').add(upcastHighlightToMarker({
491
- view: {
492
- name: 'div',
493
- classes: 'restricted-editing-exception'
494
- },
495
- model: ()=>{
496
- markerNumber++; // Starting from restrictedEditingException:1 marker.
497
- return `restrictedEditingException:block:${markerNumber}`;
498
- },
499
- useWrapperElement: true
500
- }));
501
- // Block exception wrapper.
502
- editor.conversion.for('downcast').elementToElement({
503
- model: 'restrictedEditingException',
504
- view: {
505
- name: 'div',
506
- classes: 'restricted-editing-exception'
507
- }
508
- });
509
- // Currently the marker helpers are tied to other use-cases and do not render a collapsed marker as highlight.
510
- // Also, markerToHighlight cannot convert marker on an inline object. It handles only text and widgets,
511
- // but it is not a case in the data pipeline. That's why there are 3 downcast converters for them:
512
- //
513
- // 1. The custom inline item (text or inline object) converter (but not the selection).
514
- editor.conversion.for('downcast').add((dispatcher)=>{
515
- dispatcher.on('addMarker:restrictedEditingException:inline', (evt, data, conversionApi)=>{
516
- // Only convert per-item conversion.
517
- if (!data.item) {
518
- return;
519
- }
520
- // Do not convert the selection or non-inline items.
521
- if (data.item.is('selection') || !conversionApi.schema.isInline(data.item)) {
522
- return;
523
- }
524
- if (!conversionApi.consumable.consume(data.item, evt.name)) {
525
- return;
526
- }
527
- const viewWriter = conversionApi.writer;
528
- const viewElement = viewWriter.createAttributeElement('span', {
529
- class: 'restricted-editing-exception'
530
- }, {
531
- id: data.markerName,
532
- priority: -10
533
- });
534
- const viewRange = conversionApi.mapper.toViewRange(data.range);
535
- const rangeAfterWrap = viewWriter.wrap(viewRange, viewElement);
536
- for (const element of rangeAfterWrap.getItems()){
537
- if (element.is('attributeElement') && element.isSimilar(viewElement)) {
538
- conversionApi.mapper.bindElementToMarker(element, data.markerName);
539
- break;
540
- }
541
- }
542
- });
543
- });
544
- // 2. The marker-to-highlight converter for the document selection.
545
- editor.conversion.for('downcast').markerToHighlight({
546
- model: 'restrictedEditingException:inline',
547
- // Use callback to return new object every time new marker instance is created - otherwise it will be seen as the same marker.
548
- view: ()=>{
549
- return {
550
- name: 'span',
551
- classes: 'restricted-editing-exception',
552
- priority: -10
553
- };
554
- }
555
- });
556
- // 3. And for collapsed marker we need to render it as an element.
557
- // Additionally, the editing pipeline should always display a collapsed marker.
558
- editor.conversion.for('editingDowncast').markerToElement({
559
- model: 'restrictedEditingException:inline',
560
- view: (markerData, { writer })=>{
561
- return writer.createUIElement('span', {
562
- class: 'restricted-editing-exception restricted-editing-exception_collapsed'
563
- });
564
- }
565
- });
566
- editor.conversion.for('dataDowncast').markerToElement({
567
- model: 'restrictedEditingException:inline',
568
- view: (markerData, { writer })=>{
569
- return writer.createEmptyElement('span', {
570
- class: 'restricted-editing-exception'
571
- });
572
- }
573
- });
574
- doc.registerPostFixer(extendMarkerOnTypingPostFixer(editor));
575
- doc.registerPostFixer(resurrectCollapsedMarkerPostFixer(editor));
576
- doc.registerPostFixer(ensureNewMarkerIsFlatPostFixer(editor));
577
- setupExceptionHighlighting(editor);
578
- }
579
- /**
580
- * Setups additional editing restrictions beyond command toggling:
581
- *
582
- * * delete content range trimming
583
- * * disabling input command outside exception marker
584
- * * restricting clipboard holder to text only
585
- * * restricting text attributes in content
586
- */ _setupRestrictions() {
587
- const editor = this.editor;
588
- const model = editor.model;
589
- const selection = model.document.selection;
590
- const viewDoc = editor.editing.view.document;
591
- const clipboard = editor.plugins.get('ClipboardPipeline');
592
- this.listenTo(model, 'deleteContent', restrictDeleteContent(editor), {
593
- priority: 'high'
594
- });
595
- const insertTextCommand = editor.commands.get('insertText');
596
- // The restricted editing might be configured without insert text support - ie allow only bolding or removing text.
597
- // This check is bit synthetic since only tests are used this way.
598
- if (insertTextCommand) {
599
- this.listenTo(insertTextCommand, 'execute', disallowInputExecForWrongRange(editor), {
600
- priority: 'high'
601
- });
602
- }
603
- // Block clipboard outside exception marker on paste and drop.
604
- this.listenTo(clipboard, 'contentInsertion', (evt, data)=>{
605
- if (!isRangeInsideSingleMarker(editor, selection.getFirstRange())) {
606
- evt.stop();
607
- }
608
- const marker = getMarkerAtPosition(editor, selection.focus);
609
- // Reduce content pasted into inline exception to text nodes only. Also strip not allowed attributes.
610
- if (marker && marker.name.startsWith('restrictedEditingException:inline:')) {
611
- const allowedAttributes = editor.config.get('restrictedEditing.allowedAttributes');
612
- model.change((writer)=>{
613
- const content = writer.createDocumentFragment();
614
- const textNodes = Array.from(writer.createRangeIn(data.content).getItems()).filter((node)=>node.is('$textProxy'));
615
- for (const item of textNodes){
616
- for (const attr of item.getAttributeKeys()){
617
- if (!allowedAttributes.includes(attr)) {
618
- writer.removeAttribute(attr, item);
619
- }
620
- }
621
- writer.append(item, content);
622
- }
623
- data.content = content;
624
- });
625
- }
626
- });
627
- // Block clipboard outside exception marker on cut.
628
- this.listenTo(viewDoc, 'clipboardOutput', (evt, data)=>{
629
- if (data.method == 'cut' && !isRangeInsideSingleMarker(editor, selection.getFirstRange())) {
630
- evt.stop();
631
- }
632
- }, {
633
- priority: 'high'
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
- * Helper for handling Ctrl+A keydown behaviour.
702
- */ function getSelectAllHandler(editor) {
703
- return (eventInfo, domEventData)=>{
704
- if (getCode(domEventData) != parseKeystroke('Ctrl+A')) {
705
- return;
706
- }
707
- const model = editor.model;
708
- const selection = editor.model.document.selection;
709
- const marker = getMarkerAtPosition(editor, selection.focus);
710
- if (!marker) {
711
- return;
712
- }
713
- // If selection range is inside a restricted editing exception, select text only within the exception.
714
- //
715
- // Note: Second Ctrl+A press is also blocked and it won't select the entire text in the editor.
716
- const selectionRange = selection.getFirstRange();
717
- const markerRange = getExceptionRange(marker, editor.model);
718
- if (markerRange.containsRange(selectionRange, true) || selection.isCollapsed) {
719
- eventInfo.stop();
720
- domEventData.preventDefault();
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
- * Additional rule for enabling "delete" and "deleteForward" commands if selection is on range boundaries:
730
- *
731
- * Does not allow to enable command when selection focus is:
732
- * - is on marker start - "delete" - to prevent removing content before marker
733
- * - is on marker end - "deleteForward" - to prevent removing content after marker
734
- */ function isDeleteCommandOnMarkerBoundaries(commandName, selection, markerRange) {
735
- if (commandName == 'delete' && selection.isCollapsed && markerRange.start.isTouching(selection.focus)) {
736
- return true;
737
- }
738
- // Only for collapsed selection - non-collapsed selection that extends over a marker is handled elsewhere.
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
- * Ensures that model.deleteContent() does not delete outside exception markers ranges.
746
- *
747
- * The enforced restrictions are:
748
- * - only execute deleteContent() inside exception markers
749
- * - restrict passed selection to exception marker
750
- */ function restrictDeleteContent(editor) {
751
- return (evt, args)=>{
752
- const [selection] = args;
753
- const marker = getMarkerAtPosition(editor, selection.focus) || getMarkerAtPosition(editor, selection.anchor);
754
- // Stop method execution if marker was not found at selection focus.
755
- if (!marker) {
756
- evt.stop();
757
- return;
758
- }
759
- // Collapsed selection inside exception marker does not require fixing.
760
- if (selection.isCollapsed) {
761
- return;
762
- }
763
- // Shrink the selection to the range inside exception marker.
764
- const allowedToDelete = getExceptionRange(marker, editor.model).getIntersection(selection.getFirstRange());
765
- // Some features uses selection passed to model.deleteContent() to set the selection afterwards. For this we need to properly modify
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
- * Ensures that input command is executed with a range that is inside exception marker.
778
- *
779
- * This restriction is due to fact that using native spell check changes text outside exception marker.
780
- */ function disallowInputExecForWrongRange(editor) {
781
- return (evt, args)=>{
782
- const [options] = args;
783
- const { range } = options;
784
- // Only check "input" command executed with a range value.
785
- // Selection might be set in exception marker but passed range might point elsewhere.
786
- if (!range) {
787
- return;
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
- const markerAtStart = getMarkerAtPosition(editor, range.start);
796
- const markerAtEnd = getMarkerAtPosition(editor, range.end);
797
- return markerAtStart && markerAtEnd && markerAtEnd === markerAtStart;
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
- * Checks if new marker range is flat. Non-flat ranges might appear during upcast conversion in nested structures, ie tables.
801
- *
802
- * Note: This marker fixer only consider case which is possible to create using StandardEditing mode plugin.
803
- * Markers created by developer in the data might break in many other ways.
804
- *
805
- * See https://github.com/ckeditor/ckeditor5/issues/6003.
806
- */ function ensureNewMarkerIsFlatPostFixer(editor) {
807
- return (writer)=>{
808
- let changeApplied = false;
809
- const changedMarkers = editor.model.document.differ.getChangedMarkers();
810
- for (const { data, name } of changedMarkers){
811
- if (!name.startsWith('restrictedEditingException')) {
812
- continue;
813
- }
814
- const newRange = data.newRange;
815
- if (!data.oldRange && !newRange.isFlat) {
816
- const start = newRange.start;
817
- const end = newRange.end;
818
- const startIsHigherInTree = start.path.length > end.path.length;
819
- const fixedStart = startIsHigherInTree ? newRange.start : writer.createPositionAt(end.parent, 0);
820
- const fixedEnd = startIsHigherInTree ? writer.createPositionAt(start.parent, 'end') : newRange.end;
821
- writer.updateMarker(name, {
822
- range: writer.createRange(fixedStart, fixedEnd)
823
- });
824
- changeApplied = true;
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
- * The restricted editing mode UI feature.
833
- *
834
- * It introduces the `'restrictedEditing'` dropdown that offers tools to navigate between exceptions across
835
- * the document.
836
- */ class RestrictedEditingModeUI extends Plugin {
837
- /**
838
- * @inheritDoc
839
- */ static get pluginName() {
840
- return 'RestrictedEditingModeUI';
841
- }
842
- /**
843
- * @inheritDoc
844
- */ static get isOfficialPlugin() {
845
- return true;
846
- }
847
- /**
848
- * @inheritDoc
849
- */ init() {
850
- const editor = this.editor;
851
- const t = editor.t;
852
- editor.ui.componentFactory.add('restrictedEditing', (locale)=>{
853
- const dropdownView = createDropdown(locale);
854
- const listItems = new Collection();
855
- this._getButtonDefinitions().forEach(({ commandName, label, keystroke })=>{
856
- listItems.add(this._getButtonDefinition(commandName, label, keystroke));
857
- });
858
- addListToDropdown(dropdownView, listItems, {
859
- role: 'menu'
860
- });
861
- dropdownView.buttonView.set({
862
- label: t('Navigate editable regions'),
863
- icon: IconContentLock,
864
- tooltip: true,
865
- isEnabled: true,
866
- isOn: false
867
- });
868
- this.listenTo(dropdownView, 'execute', (evt)=>{
869
- const { _commandName } = evt.source;
870
- editor.execute(_commandName);
871
- editor.editing.view.focus();
872
- });
873
- return dropdownView;
874
- });
875
- editor.ui.componentFactory.add('menuBar:restrictedEditing', (locale)=>{
876
- const menuView = new MenuBarMenuView(locale);
877
- const listView = new MenuBarMenuListView(locale);
878
- listView.set({
879
- ariaLabel: t('Navigate editable regions'),
880
- role: 'menu'
881
- });
882
- menuView.buttonView.set({
883
- label: t('Navigate editable regions'),
884
- icon: IconContentLock
885
- });
886
- menuView.panelView.children.add(listView);
887
- this._getButtonDefinitions().forEach(({ commandName, label, keystroke })=>{
888
- const listItemView = new MenuBarMenuListItemView(locale, menuView);
889
- const buttonView = this._createMenuBarButton(label, commandName, keystroke);
890
- buttonView.delegate('execute').to(menuView);
891
- listItemView.children.add(buttonView);
892
- listView.items.add(listItemView);
893
- });
894
- return menuView;
895
- });
896
- }
897
- /**
898
- * Creates a button for restricted editing command to use in menu bar.
899
- */ _createMenuBarButton(label, commandName, keystroke) {
900
- const editor = this.editor;
901
- const command = editor.commands.get(commandName);
902
- const view = new MenuBarMenuListItemButtonView(editor.locale);
903
- view.set({
904
- label,
905
- keystroke,
906
- isEnabled: true,
907
- isOn: false
908
- });
909
- view.bind('isEnabled').to(command);
910
- // Execute the command.
911
- this.listenTo(view, 'execute', ()=>{
912
- editor.execute(commandName);
913
- editor.editing.view.focus();
914
- });
915
- return view;
916
- }
917
- /**
918
- * Returns a definition of the navigation button to be used in the dropdown.
919
- *
920
- * @param commandName The name of the command that the button represents.
921
- * @param label The translated label of the button.
922
- * @param keystroke The button keystroke.
923
- */ _getButtonDefinition(commandName, label, keystroke) {
924
- const editor = this.editor;
925
- const command = editor.commands.get(commandName);
926
- const definition = {
927
- type: 'button',
928
- model: new UIModel({
929
- label,
930
- withText: true,
931
- keystroke,
932
- withKeystroke: true,
933
- role: 'menuitem',
934
- _commandName: commandName
935
- })
936
- };
937
- definition.model.bind('isEnabled').to(command, 'isEnabled');
938
- return definition;
939
- }
940
- /**
941
- * Returns definitions for UI buttons.
942
- *
943
- * @internal
944
- */ _getButtonDefinitions() {
945
- const t = this.editor.locale.t;
946
- return [
947
- {
948
- commandName: 'goToPreviousRestrictedEditingException',
949
- label: t('Previous editable region'),
950
- keystroke: 'Shift+Tab'
951
- },
952
- {
953
- commandName: 'goToNextRestrictedEditingException',
954
- label: t('Next editable region'),
955
- keystroke: 'Tab'
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
- * The restricted editing mode plugin.
963
- *
964
- * This is a "glue" plugin which loads the following plugins:
965
- *
966
- * * The {@link module:restricted-editing/restrictededitingmodeediting~RestrictedEditingModeEditing restricted mode editing feature}.
967
- * * The {@link module:restricted-editing/restrictededitingmodeui~RestrictedEditingModeUI restricted mode UI feature}.
968
- */ class RestrictedEditingMode extends Plugin {
969
- /**
970
- * @inheritDoc
971
- */ static get pluginName() {
972
- return 'RestrictedEditingMode';
973
- }
974
- /**
975
- * @inheritDoc
976
- */ static get isOfficialPlugin() {
977
- return true;
978
- }
979
- /**
980
- * @inheritDoc
981
- */ static get requires() {
982
- return [
983
- RestrictedEditingModeEditing,
984
- RestrictedEditingModeUI
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
- * The command that toggles exceptions from the restricted editing on text.
991
- */ class RestrictedEditingExceptionCommand extends Command {
992
- /**
993
- * @inheritDoc
994
- */ refresh() {
995
- const model = this.editor.model;
996
- const doc = model.document;
997
- this.value = !!doc.selection.getAttribute('restrictedEditingException');
998
- this.isEnabled = model.schema.checkAttributeInSelection(doc.selection, 'restrictedEditingException');
999
- }
1000
- /**
1001
- * @inheritDoc
1002
- */ execute(options = {}) {
1003
- const model = this.editor.model;
1004
- const document = model.document;
1005
- const selection = document.selection;
1006
- const valueToSet = options.forceValue === undefined ? !this.value : options.forceValue;
1007
- model.change((writer)=>{
1008
- const ranges = model.schema.getValidRanges(selection.getRanges(), 'restrictedEditingException');
1009
- if (selection.isCollapsed) {
1010
- if (valueToSet) {
1011
- writer.setSelectionAttribute('restrictedEditingException', valueToSet);
1012
- } else {
1013
- const isSameException = (value)=>{
1014
- return value.item.getAttribute('restrictedEditingException') === this.value;
1015
- };
1016
- const focus = selection.focus;
1017
- const exceptionStart = focus.getLastMatchingPosition(isSameException, {
1018
- direction: 'backward'
1019
- });
1020
- const exceptionEnd = focus.getLastMatchingPosition(isSameException);
1021
- writer.removeSelectionAttribute('restrictedEditingException');
1022
- if (!(focus.isEqual(exceptionStart) || focus.isEqual(exceptionEnd))) {
1023
- writer.removeAttribute('restrictedEditingException', writer.createRange(exceptionStart, exceptionEnd));
1024
- }
1025
- }
1026
- } else {
1027
- for (const range of ranges){
1028
- if (valueToSet) {
1029
- writer.setAttribute('restrictedEditingException', valueToSet, range);
1030
- } else {
1031
- writer.removeAttribute('restrictedEditingException', range);
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
- * The command that toggles exception blocks for the restricted editing.
1041
- */ class RestrictedEditingExceptionBlockCommand extends Command {
1042
- /**
1043
- * @inheritDoc
1044
- */ refresh() {
1045
- this.value = this._getValue();
1046
- this.isEnabled = this._checkEnabled();
1047
- }
1048
- /**
1049
- * Wraps or unwraps the selected blocks with non-restricted area.
1050
- *
1051
- * @fires execute
1052
- * @param options Command options.
1053
- * @param options.forceValue If set, it will force the command behavior. If `true`, the command will apply a block exception,
1054
- * otherwise the command will remove the block exception. If not set, the command will act basing on its current value.
1055
- */ execute(options = {}) {
1056
- const model = this.editor.model;
1057
- const schema = model.schema;
1058
- const selection = model.document.selection;
1059
- const blocks = Array.from(selection.getSelectedBlocks());
1060
- const value = options.forceValue === undefined ? !this.value : options.forceValue;
1061
- model.change((writer)=>{
1062
- if (!value) {
1063
- const blocksToUnwrap = blocks.map((block)=>{
1064
- // Find blocks directly nested inside an exception.
1065
- return findExceptionContentBlock(block);
1066
- }).filter((exception)=>!!exception);
1067
- this._removeException(writer, blocksToUnwrap);
1068
- } else {
1069
- const blocksToWrap = blocks.filter((block)=>{
1070
- // Already wrapped blocks needs to be considered while wrapping too
1071
- // in order to reuse their wrapper elements.
1072
- return findException(block) || checkCanBeWrapped(schema, block);
1073
- });
1074
- this._applyException(writer, blocksToWrap);
1075
- }
1076
- });
1077
- }
1078
- /**
1079
- * Checks the command's {@link #value}.
1080
- */ _getValue() {
1081
- const selection = this.editor.model.document.selection;
1082
- const firstBlock = first(selection.getSelectedBlocks());
1083
- return !!(firstBlock && findException(firstBlock));
1084
- }
1085
- /**
1086
- * Checks whether the command can be enabled in the current context.
1087
- *
1088
- * @returns Whether the command should be enabled.
1089
- */ _checkEnabled() {
1090
- if (this.value) {
1091
- return true;
1092
- }
1093
- const selection = this.editor.model.document.selection;
1094
- const schema = this.editor.model.schema;
1095
- const firstBlock = first(selection.getSelectedBlocks());
1096
- if (!firstBlock) {
1097
- return false;
1098
- }
1099
- return checkCanBeWrapped(schema, firstBlock);
1100
- }
1101
- /**
1102
- * Unwraps the exception from given blocks.
1103
- *
1104
- * If blocks which are supposed to be unwrapped are in the middle of an exception,
1105
- * start it or end it, then the exception will be split (if needed) and the blocks
1106
- * will be moved out of it, so other exception blocks remained wrapped.
1107
- */ _removeException(writer, blocks) {
1108
- // Unwrap all groups of block. Iterate in the reverse order to not break following ranges.
1109
- getRangesOfBlockGroups(writer, blocks).reverse().forEach((groupRange)=>{
1110
- if (groupRange.start.isAtStart && groupRange.end.isAtEnd) {
1111
- writer.unwrap(groupRange.start.parent);
1112
- return;
1113
- }
1114
- // The group of blocks are at the beginning of an exception so let's move them left (out of the exception).
1115
- if (groupRange.start.isAtStart) {
1116
- const positionBefore = writer.createPositionBefore(groupRange.start.parent);
1117
- writer.move(groupRange, positionBefore);
1118
- return;
1119
- }
1120
- // The blocks are in the middle of an exception so we need to split the exception after the last block
1121
- // so we move the items there.
1122
- if (!groupRange.end.isAtEnd) {
1123
- writer.split(groupRange.end);
1124
- }
1125
- // Now we are sure that groupRange.end.isAtEnd is true, so let's move the blocks right.
1126
- const positionAfter = writer.createPositionAfter(groupRange.end.parent);
1127
- writer.move(groupRange, positionAfter);
1128
- });
1129
- }
1130
- /**
1131
- * Applies the exception to given blocks.
1132
- */ _applyException(writer, blocks) {
1133
- const schema = this.editor.model.schema;
1134
- const exceptionsToMerge = [];
1135
- // Wrap all groups of block. Iterate in the reverse order to not break following ranges.
1136
- getRangesOfBlockGroups(writer, blocks).reverse().forEach((groupRange)=>{
1137
- let exception = findException(groupRange.start);
1138
- if (!exception) {
1139
- exception = writer.createElement('restrictedEditingException');
1140
- writer.wrap(groupRange, exception);
1141
- }
1142
- exceptionsToMerge.push(exception);
1143
- });
1144
- // Merge subsequent exception elements. Reverse the order again because this time we want to go through
1145
- // the exception elements in the source order (due to how merge works – it moves the right element's content
1146
- // to the first element and removes the right one. Since we may need to merge a couple of subsequent exception elements
1147
- // we want to keep the reference to the first (furthest left) one.
1148
- exceptionsToMerge.reverse();
1149
- // But first add any neighbouring block exceptions to the list.
1150
- if (exceptionsToMerge.length) {
1151
- const previousSibling = exceptionsToMerge.at(0).previousSibling;
1152
- const nextSibling = exceptionsToMerge.at(-1).nextSibling;
1153
- if (previousSibling?.is('element', 'restrictedEditingException')) {
1154
- exceptionsToMerge.unshift(previousSibling);
1155
- }
1156
- if (nextSibling?.is('element', 'restrictedEditingException')) {
1157
- exceptionsToMerge.push(nextSibling);
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
- return elementOrPosition.findAncestor('restrictedEditingException', {
1174
- includeSelf: true
1175
- });
1029
+ return elementOrPosition.findAncestor("restrictedEditingException", { includeSelf: true });
1176
1030
  }
1177
1031
  function findExceptionContentBlock(element) {
1178
- let node = element;
1179
- while(node.parent){
1180
- if (node.parent.name == 'restrictedEditingException') {
1181
- return node;
1182
- }
1183
- node = node.parent;
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
- * Returns a minimal array of ranges containing groups of subsequent blocks.
1189
- *
1190
- * content: abcdefgh
1191
- * blocks: [ a, b, d, f, g, h ]
1192
- * output ranges: [ab]c[d]e[fgh]
1193
- */ function getRangesOfBlockGroups(writer, blocks) {
1194
- let startPosition;
1195
- let i = 0;
1196
- const ranges = [];
1197
- while(i < blocks.length){
1198
- const block = blocks[i];
1199
- const nextBlock = blocks[i + 1];
1200
- if (!startPosition) {
1201
- startPosition = writer.createPositionBefore(block);
1202
- }
1203
- if (!nextBlock || block.nextSibling != nextBlock) {
1204
- ranges.push(writer.createRange(startPosition, writer.createPositionAfter(block)));
1205
- startPosition = null;
1206
- }
1207
- i++;
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
- * Checks whether exception can wrap the block.
1213
- */ function checkCanBeWrapped(schema, block) {
1214
- const parentContext = schema.createContext(block.parent);
1215
- // Is block exception allowed in parent of block.
1216
- if (!schema.checkChild(parentContext, 'restrictedEditingException')) {
1217
- return false;
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
- * The command that toggles exceptions from the restricted editing on text or on blocks.
1228
- */ class RestrictedEditingExceptionAutoCommand extends Command {
1229
- /**
1230
- * The inline exception command.
1231
- */ _inlineCommand;
1232
- /**
1233
- * The block exception command.
1234
- */ _blockCommand;
1235
- /**
1236
- * @inheritDoc
1237
- */ constructor(editor){
1238
- super(editor);
1239
- this._inlineCommand = editor.commands.get('restrictedEditingException');
1240
- this._blockCommand = editor.commands.get('restrictedEditingExceptionBlock');
1241
- }
1242
- /**
1243
- * @inheritDoc
1244
- */ refresh() {
1245
- this.value = this._inlineCommand.value || this._blockCommand.value;
1246
- this.isEnabled = this._inlineCommand.isEnabled || this._blockCommand.isEnabled;
1247
- }
1248
- /**
1249
- * @inheritDoc
1250
- */ execute() {
1251
- const editor = this.editor;
1252
- // Selection is inside an inline exception.
1253
- if (this._inlineCommand.value) {
1254
- editor.execute('restrictedEditingException');
1255
- } else if (this._blockCommand.value) {
1256
- editor.execute('restrictedEditingExceptionBlock');
1257
- } else if (this._inlineCommand.isEnabled && !this._blockCommand.isEnabled) {
1258
- editor.execute('restrictedEditingException');
1259
- } else if (this._blockCommand.isEnabled && !this._inlineCommand.isEnabled) {
1260
- editor.execute('restrictedEditingExceptionBlock');
1261
- } else {
1262
- const schema = editor.model.schema;
1263
- const selection = editor.model.document.selection;
1264
- const selectedElement = selection.getSelectedElement();
1265
- if (selection.isCollapsed || selectedElement && schema.isObject(selectedElement) && schema.isBlock(selectedElement) || count(selection.getSelectedBlocks()) > 1) {
1266
- editor.execute('restrictedEditingExceptionBlock');
1267
- } else {
1268
- editor.execute('restrictedEditingException');
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
- * The standard editing mode editing feature.
1276
- *
1277
- * * It introduces the `restrictedEditingException` text attribute that is rendered as
1278
- * a `<span>` element with the `restricted-editing-exception` CSS class.
1279
- * * It registers the `'restrictedEditingException'` command.
1280
- */ class StandardEditingModeEditing extends Plugin {
1281
- /**
1282
- * @inheritDoc
1283
- */ static get pluginName() {
1284
- return 'StandardEditingModeEditing';
1285
- }
1286
- /**
1287
- * @inheritDoc
1288
- */ static get isOfficialPlugin() {
1289
- return true;
1290
- }
1291
- /**
1292
- * @inheritDoc
1293
- */ init() {
1294
- const editor = this.editor;
1295
- const schema = editor.model.schema;
1296
- schema.extend('$text', {
1297
- allowAttributes: [
1298
- 'restrictedEditingException'
1299
- ]
1300
- });
1301
- schema.register('restrictedEditingException', {
1302
- allowWhere: '$container',
1303
- allowContentOf: '$container'
1304
- });
1305
- // Don't allow nesting of block exceptions.
1306
- schema.addChildCheck((context)=>{
1307
- for (const item of context){
1308
- if (item.name == 'restrictedEditingException') {
1309
- return false;
1310
- }
1311
- }
1312
- }, 'restrictedEditingException');
1313
- // Don't allow nesting inline exceptions inside block exceptions.
1314
- schema.addAttributeCheck((context)=>{
1315
- for (const item of context){
1316
- if (item.name == 'restrictedEditingException') {
1317
- return false;
1318
- }
1319
- }
1320
- }, 'restrictedEditingException');
1321
- // Post-fixer to ensure proper structure.
1322
- editor.model.document.registerPostFixer((writer)=>{
1323
- const changes = editor.model.document.differ.getChanges();
1324
- const unwrap = new Set();
1325
- const remove = new Set();
1326
- const merge = new Set();
1327
- let changed = false;
1328
- for (const entry of changes){
1329
- if (entry.type == 'insert') {
1330
- const range = writer.createRange(entry.position, entry.position.getShiftedBy(entry.length));
1331
- for (const child of range.getItems()){
1332
- if (child.is('element', 'restrictedEditingException')) {
1333
- // Make sure that block exception is not nested or added in invalid place.
1334
- if (!schema.checkChild(writer.createPositionBefore(child), child)) {
1335
- unwrap.add(child);
1336
- } else if (child.isEmpty) {
1337
- remove.add(child);
1338
- } else {
1339
- merge.add(child);
1340
- }
1341
- } else if (child.is('$textProxy') && child.hasAttribute('restrictedEditingException') && !schema.checkAttribute(child, 'restrictedEditingException')) {
1342
- writer.removeAttribute('restrictedEditingException', child);
1343
- changed = true;
1344
- }
1345
- }
1346
- } else if (entry.type == 'remove') {
1347
- const parent = entry.position.parent;
1348
- if (parent.is('element', 'restrictedEditingException') && parent.isEmpty) {
1349
- remove.add(parent);
1350
- }
1351
- // Verify if some block exceptions are siblings now after element removed between.
1352
- for (const child of parent.getChildren()){
1353
- if (child.is('element', 'restrictedEditingException')) {
1354
- merge.add(child);
1355
- }
1356
- }
1357
- }
1358
- }
1359
- for (const child of unwrap){
1360
- writer.unwrap(child);
1361
- changed = true;
1362
- }
1363
- for (const child of remove){
1364
- writer.remove(child);
1365
- changed = true;
1366
- }
1367
- for (const child of merge){
1368
- if (child.root.rootName == '$graveyard') {
1369
- continue;
1370
- }
1371
- const nodeBefore = child.previousSibling;
1372
- const nodeAfter = child.nextSibling;
1373
- if (nodeBefore && nodeBefore.is('element', 'restrictedEditingException')) {
1374
- writer.merge(writer.createPositionBefore(child));
1375
- }
1376
- if (nodeAfter && nodeAfter.is('element', 'restrictedEditingException')) {
1377
- writer.merge(writer.createPositionAfter(child));
1378
- }
1379
- }
1380
- return changed;
1381
- });
1382
- editor.conversion.for('upcast').elementToAttribute({
1383
- model: 'restrictedEditingException',
1384
- view: {
1385
- name: 'span',
1386
- classes: 'restricted-editing-exception'
1387
- }
1388
- }).elementToElement({
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
- * Fallback upcast converter for empty exception span inside a table cell.
1427
- */ function registerFallbackUpcastConverter(editor) {
1428
- const matcher = new Matcher({
1429
- name: 'span',
1430
- classes: 'restricted-editing-exception'
1431
- });
1432
- // See: https://github.com/ckeditor/ckeditor5/issues/16376.
1433
- editor.conversion.for('upcast').add((dispatcher)=>dispatcher.on('element:span', (evt, data, conversionApi)=>{
1434
- const matcherResult = matcher.match(data.viewItem);
1435
- if (!matcherResult) {
1436
- return;
1437
- }
1438
- const match = matcherResult.match;
1439
- if (!conversionApi.consumable.test(data.viewItem, match)) {
1440
- return;
1441
- }
1442
- const modelText = conversionApi.writer.createText(' ', {
1443
- restrictedEditingException: true
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
- * The standard editing mode UI feature.
1458
- *
1459
- * It introduces the `'restrictedEditingException'` button that marks text as unrestricted for editing.
1460
- */ class StandardEditingModeUI extends Plugin {
1461
- /**
1462
- * @inheritDoc
1463
- */ static get pluginName() {
1464
- return 'StandardEditingModeUI';
1465
- }
1466
- /**
1467
- * @inheritDoc
1468
- */ static get isOfficialPlugin() {
1469
- return true;
1470
- }
1471
- /**
1472
- * @inheritDoc
1473
- */ init() {
1474
- const editor = this.editor;
1475
- const componentFactory = editor.ui.componentFactory;
1476
- // Dropdown button with inline/block buttons.
1477
- componentFactory.add('restrictedEditingException:dropdown', (locale)=>{
1478
- const dropdownView = createDropdown(locale);
1479
- const t = locale.t;
1480
- const buttons = [
1481
- componentFactory.create('restrictedEditingException:inline'),
1482
- componentFactory.create('restrictedEditingException:block')
1483
- ];
1484
- for (const button of buttons){
1485
- button.set({
1486
- withText: true,
1487
- tooltip: false
1488
- });
1489
- }
1490
- addToolbarToDropdown(dropdownView, buttons, {
1491
- enableActiveItemFocusOnDropdownOpen: true,
1492
- isVertical: true,
1493
- ariaLabel: t('Enable editing')
1494
- });
1495
- dropdownView.buttonView.set({
1496
- label: t('Enable editing'),
1497
- icon: IconContentUnlock,
1498
- tooltip: true
1499
- });
1500
- dropdownView.extendTemplate({
1501
- attributes: {
1502
- class: 'ck-restricted-editing-dropdown'
1503
- }
1504
- });
1505
- // Enable button if any of the buttons is enabled.
1506
- dropdownView.bind('isEnabled').toMany(buttons, 'isEnabled', (...areEnabled)=>{
1507
- return areEnabled.some((isEnabled)=>isEnabled);
1508
- });
1509
- // Focus the editable after executing the command.
1510
- this.listenTo(dropdownView, 'execute', ()=>{
1511
- editor.editing.view.focus();
1512
- });
1513
- return dropdownView;
1514
- });
1515
- // Inline exception button.
1516
- componentFactory.add('restrictedEditingException:inline', ()=>{
1517
- const button = this._createButton('restrictedEditingException', ButtonView);
1518
- button.set({
1519
- tooltip: true
1520
- });
1521
- return button;
1522
- });
1523
- // Block exception button.
1524
- componentFactory.add('restrictedEditingException:block', ()=>{
1525
- const button = this._createButton('restrictedEditingExceptionBlock', ButtonView);
1526
- button.set({
1527
- tooltip: true
1528
- });
1529
- return button;
1530
- });
1531
- // Menu bar inline/block buttons.
1532
- componentFactory.add('menuBar:restrictedEditingException:inline', ()=>{
1533
- return this._createButton('restrictedEditingException', MenuBarMenuListItemButtonView);
1534
- });
1535
- componentFactory.add('menuBar:restrictedEditingException:block', ()=>{
1536
- return this._createButton('restrictedEditingExceptionBlock', MenuBarMenuListItemButtonView);
1537
- });
1538
- // Auto button - triggers inline or block exception command.
1539
- componentFactory.add('restrictedEditingException:auto', ()=>{
1540
- const button = this._createButton('restrictedEditingExceptionAuto', ButtonView);
1541
- button.set({
1542
- tooltip: true
1543
- });
1544
- return button;
1545
- });
1546
- componentFactory.add('menuBar:restrictedEditingException:auto', ()=>{
1547
- return this._createButton('restrictedEditingExceptionAuto', MenuBarMenuListItemButtonView);
1548
- });
1549
- // Aliases for backward compatibility.
1550
- componentFactory.add('restrictedEditingException', ()=>{
1551
- return componentFactory.create('restrictedEditingException:inline');
1552
- });
1553
- componentFactory.add('menuBar:restrictedEditingException', ()=>{
1554
- return componentFactory.create('menuBar:restrictedEditingException:inline');
1555
- });
1556
- }
1557
- /**
1558
- * Creates a button for restricted editing exception command to use either in toolbar or in menu bar.
1559
- */ _createButton(commandName, ButtonClass) {
1560
- const editor = this.editor;
1561
- const locale = editor.locale;
1562
- const command = this.editor.commands.get(commandName);
1563
- const view = new ButtonClass(locale);
1564
- const t = locale.t;
1565
- view.icon = IconContentUnlock;
1566
- view.isToggleable = true;
1567
- view.bind('isOn', 'isEnabled').to(command, 'value', 'isEnabled');
1568
- if (commandName == 'restrictedEditingExceptionAuto') {
1569
- view.bind('label').to(command, 'value', (value)=>{
1570
- return value ? t('Disable editing') : t('Enable editing');
1571
- });
1572
- } else if (commandName == 'restrictedEditingExceptionBlock') {
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
- * The standard editing mode plugin.
1592
- *
1593
- * This is a "glue" plugin that loads the following plugins:
1594
- *
1595
- * * The {@link module:restricted-editing/standardeditingmodeediting~StandardEditingModeEditing standard mode editing feature}.
1596
- * * The {@link module:restricted-editing/standardeditingmodeui~StandardEditingModeUI standard mode UI feature}.
1597
- */ class StandardEditingMode extends Plugin {
1598
- /**
1599
- * @inheritDoc
1600
- */ static get pluginName() {
1601
- return 'StandardEditingMode';
1602
- }
1603
- /**
1604
- * @inheritDoc
1605
- */ static get isOfficialPlugin() {
1606
- return true;
1607
- }
1608
- static get requires() {
1609
- return [
1610
- StandardEditingModeEditing,
1611
- StandardEditingModeUI
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