@ckeditor/ckeditor5-restricted-editing 41.3.0-alpha.1 → 41.3.0-alpha.3

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.
Files changed (120) hide show
  1. package/dist/translations/ar.d.ts +8 -0
  2. package/dist/translations/ar.js +5 -0
  3. package/dist/translations/az.d.ts +8 -0
  4. package/dist/translations/az.js +5 -0
  5. package/dist/translations/bg.d.ts +8 -0
  6. package/dist/translations/bg.js +5 -0
  7. package/dist/translations/bn.d.ts +8 -0
  8. package/dist/translations/bn.js +5 -0
  9. package/dist/translations/ca.d.ts +8 -0
  10. package/dist/translations/ca.js +5 -0
  11. package/dist/translations/cs.d.ts +8 -0
  12. package/dist/translations/cs.js +5 -0
  13. package/dist/translations/da.d.ts +8 -0
  14. package/dist/translations/da.js +5 -0
  15. package/dist/translations/de-ch.d.ts +8 -0
  16. package/dist/translations/de-ch.js +5 -0
  17. package/dist/translations/de.d.ts +8 -0
  18. package/dist/translations/de.js +5 -0
  19. package/dist/translations/el.d.ts +8 -0
  20. package/dist/translations/el.js +5 -0
  21. package/dist/translations/en-au.d.ts +8 -0
  22. package/dist/translations/en-au.js +5 -0
  23. package/dist/translations/en-gb.d.ts +8 -0
  24. package/dist/translations/en-gb.js +5 -0
  25. package/dist/translations/en.d.ts +8 -0
  26. package/dist/translations/en.js +5 -0
  27. package/dist/translations/es.d.ts +8 -0
  28. package/dist/translations/es.js +5 -0
  29. package/dist/translations/et.d.ts +8 -0
  30. package/dist/translations/et.js +5 -0
  31. package/dist/translations/fa.d.ts +8 -0
  32. package/dist/translations/fa.js +5 -0
  33. package/dist/translations/fi.d.ts +8 -0
  34. package/dist/translations/fi.js +5 -0
  35. package/dist/translations/fr.d.ts +8 -0
  36. package/dist/translations/fr.js +5 -0
  37. package/dist/translations/gl.d.ts +8 -0
  38. package/dist/translations/gl.js +5 -0
  39. package/dist/translations/he.d.ts +8 -0
  40. package/dist/translations/he.js +5 -0
  41. package/dist/translations/hi.d.ts +8 -0
  42. package/dist/translations/hi.js +5 -0
  43. package/dist/translations/hr.d.ts +8 -0
  44. package/dist/translations/hr.js +5 -0
  45. package/dist/translations/hu.d.ts +8 -0
  46. package/dist/translations/hu.js +5 -0
  47. package/dist/translations/id.d.ts +8 -0
  48. package/dist/translations/id.js +5 -0
  49. package/dist/translations/it.d.ts +8 -0
  50. package/dist/translations/it.js +5 -0
  51. package/dist/translations/ja.d.ts +8 -0
  52. package/dist/translations/ja.js +5 -0
  53. package/dist/translations/ko.d.ts +8 -0
  54. package/dist/translations/ko.js +5 -0
  55. package/dist/translations/ku.d.ts +8 -0
  56. package/dist/translations/ku.js +5 -0
  57. package/dist/translations/lt.d.ts +8 -0
  58. package/dist/translations/lt.js +5 -0
  59. package/dist/translations/lv.d.ts +8 -0
  60. package/dist/translations/lv.js +5 -0
  61. package/dist/translations/ms.d.ts +8 -0
  62. package/dist/translations/ms.js +5 -0
  63. package/dist/translations/nl.d.ts +8 -0
  64. package/dist/translations/nl.js +5 -0
  65. package/dist/translations/no.d.ts +8 -0
  66. package/dist/translations/no.js +5 -0
  67. package/dist/translations/pl.d.ts +8 -0
  68. package/dist/translations/pl.js +5 -0
  69. package/dist/translations/pt-br.d.ts +8 -0
  70. package/dist/translations/pt-br.js +5 -0
  71. package/dist/translations/pt.d.ts +8 -0
  72. package/dist/translations/pt.js +5 -0
  73. package/dist/translations/ro.d.ts +8 -0
  74. package/dist/translations/ro.js +5 -0
  75. package/dist/translations/ru.d.ts +8 -0
  76. package/dist/translations/ru.js +5 -0
  77. package/dist/translations/sk.d.ts +8 -0
  78. package/dist/translations/sk.js +5 -0
  79. package/dist/translations/sq.d.ts +8 -0
  80. package/dist/translations/sq.js +5 -0
  81. package/dist/translations/sr-latn.d.ts +8 -0
  82. package/dist/translations/sr-latn.js +5 -0
  83. package/dist/translations/sr.d.ts +8 -0
  84. package/dist/translations/sr.js +5 -0
  85. package/dist/translations/sv.d.ts +8 -0
  86. package/dist/translations/sv.js +5 -0
  87. package/dist/translations/th.d.ts +8 -0
  88. package/dist/translations/th.js +5 -0
  89. package/dist/translations/tk.d.ts +8 -0
  90. package/dist/translations/tk.js +5 -0
  91. package/dist/translations/tr.d.ts +8 -0
  92. package/dist/translations/tr.js +5 -0
  93. package/dist/translations/uk.d.ts +8 -0
  94. package/dist/translations/uk.js +5 -0
  95. package/dist/translations/ur.d.ts +8 -0
  96. package/dist/translations/ur.js +5 -0
  97. package/dist/translations/uz.d.ts +8 -0
  98. package/dist/translations/uz.js +5 -0
  99. package/dist/translations/vi.d.ts +8 -0
  100. package/dist/translations/vi.js +5 -0
  101. package/dist/translations/zh-cn.d.ts +8 -0
  102. package/dist/translations/zh-cn.js +5 -0
  103. package/dist/translations/zh.d.ts +8 -0
  104. package/dist/translations/zh.js +5 -0
  105. package/dist/types/augmentation.d.ts +4 -0
  106. package/dist/types/index.d.ts +4 -0
  107. package/dist/types/restrictededitingconfig.d.ts +4 -0
  108. package/dist/types/restrictededitingexceptioncommand.d.ts +4 -0
  109. package/dist/types/restrictededitingmode/converters.d.ts +4 -0
  110. package/dist/types/restrictededitingmode/utils.d.ts +4 -0
  111. package/dist/types/restrictededitingmode.d.ts +4 -0
  112. package/dist/types/restrictededitingmodeediting.d.ts +4 -0
  113. package/dist/types/restrictededitingmodenavigationcommand.d.ts +4 -0
  114. package/dist/types/restrictededitingmodeui.d.ts +4 -0
  115. package/dist/types/standardeditingmode.d.ts +4 -0
  116. package/dist/types/standardeditingmodeediting.d.ts +4 -0
  117. package/dist/types/standardeditingmodeui.d.ts +4 -0
  118. package/package.json +2 -2
  119. package/dist/index.js +0 -977
  120. package/dist/index.js.map +0 -1
package/dist/index.js DELETED
@@ -1,977 +0,0 @@
1
- /**
2
- * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
3
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
- */
5
- import { Command, Plugin } from '@ckeditor/ckeditor5-core/dist/index.js';
6
- import { Matcher } from '@ckeditor/ckeditor5-engine/dist/index.js';
7
- import { createDropdown, addListToDropdown, ViewModel, ButtonView } from '@ckeditor/ckeditor5-ui/dist/index.js';
8
- import { Collection } from '@ckeditor/ckeditor5-utils/dist/index.js';
9
-
10
- /**
11
- * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
12
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
13
- */
14
- /**
15
- * The command that allows navigation across the exceptions in the edited document.
16
- */
17
- class RestrictedEditingModeNavigationCommand extends Command {
18
- /**
19
- * Creates an instance of the command.
20
- *
21
- * @param editor The editor instance.
22
- * @param direction The direction that the command works.
23
- */
24
- constructor(editor, direction) {
25
- super(editor);
26
- // It does not affect data so should be enabled in read-only mode and in restricted editing mode.
27
- this.affectsData = false;
28
- this._direction = direction;
29
- }
30
- /**
31
- * @inheritDoc
32
- */
33
- refresh() {
34
- this.isEnabled = this._checkEnabled();
35
- }
36
- /**
37
- * Executes the command.
38
- *
39
- * @fires execute
40
- */
41
- execute() {
42
- const position = getNearestExceptionRange(this.editor.model, this._direction);
43
- if (!position) {
44
- return;
45
- }
46
- this.editor.model.change(writer => {
47
- writer.setSelection(position);
48
- });
49
- }
50
- /**
51
- * Checks whether the command can be enabled in the current context.
52
- *
53
- * @returns Whether the command should be enabled.
54
- */
55
- _checkEnabled() {
56
- return !!getNearestExceptionRange(this.editor.model, this._direction);
57
- }
58
- }
59
- /**
60
- * Returns the range of the exception marker closest to the last position of the model selection.
61
- */
62
- function getNearestExceptionRange(model, direction) {
63
- const selection = model.document.selection;
64
- const selectionPosition = selection.getFirstPosition();
65
- const markerRanges = [];
66
- // Get all exception marker positions that start after/before the selection position.
67
- for (const marker of model.markers.getMarkersGroup('restrictedEditingException')) {
68
- const markerRange = marker.getRange();
69
- // Checking parent because there two positions <paragraph>foo^</paragraph><paragraph>^bar</paragraph>
70
- // are touching but they will represent different markers.
71
- const isMarkerRangeTouching = selectionPosition.isTouching(markerRange.start) && selectionPosition.hasSameParentAs(markerRange.start) ||
72
- selectionPosition.isTouching(markerRange.end) && selectionPosition.hasSameParentAs(markerRange.end);
73
- // <paragraph>foo <marker≥b[]ar</marker> baz</paragraph>
74
- // <paragraph>foo <marker≥b[ar</marker> ba]z</paragraph>
75
- // <paragraph>foo <marker≥bar</marker>[] baz</paragraph>
76
- // <paragraph>foo []<marker≥bar</marker> baz</paragraph>
77
- if (markerRange.containsPosition(selectionPosition) || isMarkerRangeTouching) {
78
- continue;
79
- }
80
- if (direction === 'forward' && markerRange.start.isAfter(selectionPosition)) {
81
- markerRanges.push(markerRange);
82
- }
83
- else if (direction === 'backward' && markerRange.end.isBefore(selectionPosition)) {
84
- markerRanges.push(markerRange);
85
- }
86
- }
87
- if (!markerRanges.length) {
88
- return;
89
- }
90
- // Get the marker closest to the selection position among many. To know that, we need to sort
91
- // them first.
92
- return markerRanges
93
- .sort((rangeA, rangeB) => {
94
- if (direction === 'forward') {
95
- return rangeA.start.isAfter(rangeB.start) ? 1 : -1;
96
- }
97
- else {
98
- return rangeA.start.isBefore(rangeB.start) ? 1 : -1;
99
- }
100
- })
101
- .shift();
102
- }
103
-
104
- /**
105
- * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
106
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
107
- */
108
- /**
109
- * @module restricted-editing/restrictededitingmode/utils
110
- */
111
- /**
112
- * Returns a single "restricted-editing-exception" marker at a given position. Contrary to
113
- * {@link module:engine/model/markercollection~MarkerCollection#getMarkersAtPosition}, it returnd a marker also when the postion is
114
- * equal to one of the marker's start or end positions.
115
- */
116
- function getMarkerAtPosition(editor, position) {
117
- for (const marker of editor.model.markers) {
118
- const markerRange = marker.getRange();
119
- if (isPositionInRangeBoundaries(markerRange, position)) {
120
- if (marker.name.startsWith('restrictedEditingException:')) {
121
- return marker;
122
- }
123
- }
124
- }
125
- }
126
- /**
127
- * Checks if the position is fully contained in the range. Positions equal to range start or end are considered "in".
128
- */
129
- function isPositionInRangeBoundaries(range, position) {
130
- return (range.containsPosition(position) ||
131
- range.end.isEqual(position) ||
132
- range.start.isEqual(position));
133
- }
134
- /**
135
- * Checks if the selection is fully contained in the marker. Positions on marker boundaries are considered "in".
136
- *
137
- * ```xml
138
- * <marker>[]foo</marker> -> true
139
- * <marker>f[oo]</marker> -> true
140
- * <marker>f[oo</marker> ba]r -> false
141
- * <marker>foo</marker> []bar -> false
142
- * ```
143
- */
144
- function isSelectionInMarker(selection, marker) {
145
- if (!marker) {
146
- return false;
147
- }
148
- const markerRange = marker.getRange();
149
- if (selection.isCollapsed) {
150
- return isPositionInRangeBoundaries(markerRange, selection.focus);
151
- }
152
- return markerRange.containsRange(selection.getFirstRange(), true);
153
- }
154
-
155
- /**
156
- * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
157
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
158
- */
159
- const HIGHLIGHT_CLASS = 'restricted-editing-exception_selected';
160
- /**
161
- * Adds a visual highlight style to a restricted editing exception that the selection is anchored to.
162
- *
163
- * The highlight is turned on by adding the `.restricted-editing-exception_selected` class to the
164
- * exception in the view:
165
- *
166
- * * The class is removed before the conversion starts, as callbacks added with the `'highest'` priority
167
- * to {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher} events.
168
- * * The class is added in the view post-fixer, after other changes in the model tree are converted to the view.
169
- *
170
- * This way, adding and removing the highlight does not interfere with conversion.
171
- */
172
- function setupExceptionHighlighting(editor) {
173
- const view = editor.editing.view;
174
- const model = editor.model;
175
- const highlightedMarkers = new Set();
176
- // Adding the class.
177
- view.document.registerPostFixer((writer) => {
178
- const modelSelection = model.document.selection;
179
- const marker = getMarkerAtPosition(editor, modelSelection.anchor);
180
- if (!marker) {
181
- return false;
182
- }
183
- for (const viewElement of editor.editing.mapper.markerNameToElements(marker.name)) {
184
- writer.addClass(HIGHLIGHT_CLASS, viewElement);
185
- highlightedMarkers.add(viewElement);
186
- }
187
- return false;
188
- });
189
- // Removing the class.
190
- editor.conversion.for('editingDowncast').add(dispatcher => {
191
- // Make sure the highlight is removed on every possible event, before conversion is started.
192
- dispatcher.on('insert', removeHighlight, { priority: 'highest' });
193
- dispatcher.on('remove', removeHighlight, { priority: 'highest' });
194
- dispatcher.on('attribute', removeHighlight, { priority: 'highest' });
195
- dispatcher.on('cleanSelection', removeHighlight);
196
- function removeHighlight() {
197
- view.change(writer => {
198
- for (const item of highlightedMarkers.values()) {
199
- writer.removeClass(HIGHLIGHT_CLASS, item);
200
- highlightedMarkers.delete(item);
201
- }
202
- });
203
- }
204
- });
205
- }
206
- /**
207
- * A post-fixer that prevents removing a collapsed marker from the document.
208
- */
209
- function resurrectCollapsedMarkerPostFixer(editor) {
210
- // This post-fixer shouldn't be necessary after https://github.com/ckeditor/ckeditor5/issues/5778.
211
- return writer => {
212
- let changeApplied = false;
213
- for (const { name, data } of editor.model.document.differ.getChangedMarkers()) {
214
- if (name.startsWith('restrictedEditingException') && data.newRange && data.newRange.root.rootName == '$graveyard') {
215
- writer.updateMarker(name, {
216
- range: writer.createRange(writer.createPositionAt(data.oldRange.start))
217
- });
218
- changeApplied = true;
219
- }
220
- }
221
- return changeApplied;
222
- };
223
- }
224
- /**
225
- * A post-fixer that extends a marker when the user types on its boundaries.
226
- */
227
- function extendMarkerOnTypingPostFixer(editor) {
228
- // This post-fixer shouldn't be necessary after https://github.com/ckeditor/ckeditor5/issues/5778.
229
- return writer => {
230
- let changeApplied = false;
231
- for (const change of editor.model.document.differ.getChanges()) {
232
- if (change.type == 'insert' && change.name == '$text') {
233
- changeApplied = _tryExtendMarkerStart(editor, change.position, change.length, writer) || changeApplied;
234
- changeApplied = _tryExtendMarkedEnd(editor, change.position, change.length, writer) || changeApplied;
235
- }
236
- }
237
- return changeApplied;
238
- };
239
- }
240
- /**
241
- * A view highlight-to-marker conversion helper.
242
- *
243
- * @param config Conversion configuration.
244
- */
245
- function upcastHighlightToMarker(config) {
246
- return (dispatcher) => dispatcher.on('element:span', (evt, data, conversionApi) => {
247
- const { writer } = conversionApi;
248
- const matcher = new Matcher(config.view);
249
- const matcherResult = matcher.match(data.viewItem);
250
- // If there is no match, this callback should not do anything.
251
- if (!matcherResult) {
252
- return;
253
- }
254
- const match = matcherResult.match;
255
- // Force consuming element's name (taken from upcast helpers elementToElement converter).
256
- match.name = true;
257
- const { modelRange: convertedChildrenRange } = conversionApi.convertChildren(data.viewItem, data.modelCursor);
258
- conversionApi.consumable.consume(data.viewItem, match);
259
- const markerName = config.model();
260
- const fakeMarkerStart = writer.createElement('$marker', { 'data-name': markerName });
261
- const fakeMarkerEnd = writer.createElement('$marker', { 'data-name': markerName });
262
- // Insert in reverse order to use converter content positions directly (without recalculating).
263
- writer.insert(fakeMarkerEnd, convertedChildrenRange.end);
264
- writer.insert(fakeMarkerStart, convertedChildrenRange.start);
265
- data.modelRange = writer.createRange(writer.createPositionBefore(fakeMarkerStart), writer.createPositionAfter(fakeMarkerEnd));
266
- data.modelCursor = data.modelRange.end;
267
- });
268
- }
269
- /**
270
- * Extend marker if change detected on marker's start position.
271
- */
272
- function _tryExtendMarkerStart(editor, position, length, writer) {
273
- const markerAtStart = getMarkerAtPosition(editor, position.getShiftedBy(length));
274
- if (markerAtStart && markerAtStart.getStart().isEqual(position.getShiftedBy(length))) {
275
- writer.updateMarker(markerAtStart, {
276
- range: writer.createRange(markerAtStart.getStart().getShiftedBy(-length), markerAtStart.getEnd())
277
- });
278
- return true;
279
- }
280
- return false;
281
- }
282
- /**
283
- * Extend marker if change detected on marker's end position.
284
- */
285
- function _tryExtendMarkedEnd(editor, position, length, writer) {
286
- const markerAtEnd = getMarkerAtPosition(editor, position);
287
- if (markerAtEnd && markerAtEnd.getEnd().isEqual(position)) {
288
- writer.updateMarker(markerAtEnd, {
289
- range: writer.createRange(markerAtEnd.getStart(), markerAtEnd.getEnd().getShiftedBy(length))
290
- });
291
- return true;
292
- }
293
- return false;
294
- }
295
-
296
- /**
297
- * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
298
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
299
- */
300
- /**
301
- * @module restricted-editing/restrictededitingmodeediting
302
- */
303
- const COMMAND_FORCE_DISABLE_ID = 'RestrictedEditingMode';
304
- /**
305
- * The restricted editing mode editing feature.
306
- *
307
- * * It introduces the exception marker group that renders to `<span>` elements with the `restricted-editing-exception` CSS class.
308
- * * It registers the `'goToPreviousRestrictedEditingException'` and `'goToNextRestrictedEditingException'` commands.
309
- * * It also enables highlighting exception markers that are selected.
310
- */
311
- class RestrictedEditingModeEditing extends Plugin {
312
- /**
313
- * @inheritDoc
314
- */
315
- static get pluginName() {
316
- return 'RestrictedEditingModeEditing';
317
- }
318
- /**
319
- * @inheritDoc
320
- */
321
- constructor(editor) {
322
- super(editor);
323
- editor.config.define('restrictedEditing', {
324
- allowedCommands: ['bold', 'italic', 'link', 'unlink'],
325
- allowedAttributes: ['bold', 'italic', 'linkHref']
326
- });
327
- this._alwaysEnabled = new Set(['undo', 'redo']);
328
- this._allowedInException = new Set(['input', 'insertText', 'delete', 'deleteForward']);
329
- }
330
- /**
331
- * @inheritDoc
332
- */
333
- init() {
334
- const editor = this.editor;
335
- const editingView = editor.editing.view;
336
- const allowedCommands = editor.config.get('restrictedEditing.allowedCommands');
337
- allowedCommands.forEach(commandName => this._allowedInException.add(commandName));
338
- this._setupConversion();
339
- this._setupCommandsToggling();
340
- this._setupRestrictions();
341
- // Commands & keystrokes that allow navigation in the content.
342
- editor.commands.add('goToPreviousRestrictedEditingException', new RestrictedEditingModeNavigationCommand(editor, 'backward'));
343
- editor.commands.add('goToNextRestrictedEditingException', new RestrictedEditingModeNavigationCommand(editor, 'forward'));
344
- editor.keystrokes.set('Tab', getCommandExecuter(editor, 'goToNextRestrictedEditingException'));
345
- editor.keystrokes.set('Shift+Tab', getCommandExecuter(editor, 'goToPreviousRestrictedEditingException'));
346
- editor.keystrokes.set('Ctrl+A', getSelectAllHandler(editor));
347
- editingView.change(writer => {
348
- for (const root of editingView.document.roots) {
349
- writer.addClass('ck-restricted-editing_mode_restricted', root);
350
- }
351
- });
352
- }
353
- /**
354
- * Makes the given command always enabled in the restricted editing mode (regardless
355
- * of selection location).
356
- *
357
- * To enable some commands in non-restricted areas of the content use
358
- * {@link module:restricted-editing/restrictededitingconfig~RestrictedEditingConfig#allowedCommands} configuration option.
359
- *
360
- * @param commandName Name of the command to enable.
361
- */
362
- enableCommand(commandName) {
363
- const command = this.editor.commands.get(commandName);
364
- command.clearForceDisabled(COMMAND_FORCE_DISABLE_ID);
365
- this._alwaysEnabled.add(commandName);
366
- }
367
- /**
368
- * Sets up the restricted mode editing conversion:
369
- *
370
- * * ucpast & downcast converters,
371
- * * marker highlighting in the edting area,
372
- * * marker post-fixers.
373
- */
374
- _setupConversion() {
375
- const editor = this.editor;
376
- const model = editor.model;
377
- const doc = model.document;
378
- // The restricted editing does not attach additional data to the zones so there's no need for smarter markers managing.
379
- // Also, the markers will only be created when loading the data.
380
- let markerNumber = 0;
381
- editor.conversion.for('upcast').add(upcastHighlightToMarker({
382
- view: {
383
- name: 'span',
384
- classes: 'restricted-editing-exception'
385
- },
386
- model: () => {
387
- markerNumber++; // Starting from restrictedEditingException:1 marker.
388
- return `restrictedEditingException:${markerNumber}`;
389
- }
390
- }));
391
- // Currently the marker helpers are tied to other use-cases and do not render a collapsed marker as highlight.
392
- // That's why there are 2 downcast converters for them:
393
- // 1. The default marker-to-highlight will wrap selected text with `<span>`.
394
- editor.conversion.for('downcast').markerToHighlight({
395
- model: 'restrictedEditingException',
396
- // Use callback to return new object every time new marker instance is created - otherwise it will be seen as the same marker.
397
- view: () => {
398
- return {
399
- name: 'span',
400
- classes: 'restricted-editing-exception',
401
- priority: -10
402
- };
403
- }
404
- });
405
- // 2. But for collapsed marker we need to render it as an element.
406
- // Additionally the editing pipeline should always display a collapsed marker.
407
- editor.conversion.for('editingDowncast').markerToElement({
408
- model: 'restrictedEditingException',
409
- view: (markerData, { writer }) => {
410
- return writer.createUIElement('span', {
411
- class: 'restricted-editing-exception restricted-editing-exception_collapsed'
412
- });
413
- }
414
- });
415
- editor.conversion.for('dataDowncast').markerToElement({
416
- model: 'restrictedEditingException',
417
- view: (markerData, { writer }) => {
418
- return writer.createEmptyElement('span', {
419
- class: 'restricted-editing-exception'
420
- });
421
- }
422
- });
423
- doc.registerPostFixer(extendMarkerOnTypingPostFixer(editor));
424
- doc.registerPostFixer(resurrectCollapsedMarkerPostFixer(editor));
425
- doc.registerPostFixer(ensureNewMarkerIsFlatPostFixer(editor));
426
- setupExceptionHighlighting(editor);
427
- }
428
- /**
429
- * Setups additional editing restrictions beyond command toggling:
430
- *
431
- * * delete content range trimming
432
- * * disabling input command outside exception marker
433
- * * restricting clipboard holder to text only
434
- * * restricting text attributes in content
435
- */
436
- _setupRestrictions() {
437
- const editor = this.editor;
438
- const model = editor.model;
439
- const selection = model.document.selection;
440
- const viewDoc = editor.editing.view.document;
441
- const clipboard = editor.plugins.get('ClipboardPipeline');
442
- this.listenTo(model, 'deleteContent', restrictDeleteContent(editor), { priority: 'high' });
443
- const insertTextCommand = editor.commands.get('insertText');
444
- // The restricted editing might be configured without insert text support - ie allow only bolding or removing text.
445
- // This check is bit synthetic since only tests are used this way.
446
- if (insertTextCommand) {
447
- this.listenTo(insertTextCommand, 'execute', disallowInputExecForWrongRange(editor), { priority: 'high' });
448
- }
449
- // Block clipboard outside exception marker on paste and drop.
450
- this.listenTo(clipboard, 'contentInsertion', evt => {
451
- if (!isRangeInsideSingleMarker(editor, selection.getFirstRange())) {
452
- evt.stop();
453
- }
454
- });
455
- // Block clipboard outside exception marker on cut.
456
- this.listenTo(viewDoc, 'clipboardOutput', (evt, data) => {
457
- if (data.method == 'cut' && !isRangeInsideSingleMarker(editor, selection.getFirstRange())) {
458
- evt.stop();
459
- }
460
- }, { priority: 'high' });
461
- const allowedAttributes = editor.config.get('restrictedEditing.allowedAttributes');
462
- model.schema.addAttributeCheck(onlyAllowAttributesFromList(allowedAttributes));
463
- model.schema.addChildCheck(allowTextOnlyInClipboardHolder());
464
- }
465
- /**
466
- * Sets up the command toggling which enables or disables commands based on the user selection.
467
- */
468
- _setupCommandsToggling() {
469
- const editor = this.editor;
470
- const model = editor.model;
471
- const doc = model.document;
472
- this._disableCommands();
473
- this.listenTo(doc.selection, 'change', this._checkCommands.bind(this));
474
- this.listenTo(doc, 'change:data', this._checkCommands.bind(this));
475
- }
476
- /**
477
- * Checks if commands should be enabled or disabled based on the current selection.
478
- */
479
- _checkCommands() {
480
- const editor = this.editor;
481
- const selection = editor.model.document.selection;
482
- if (selection.rangeCount > 1) {
483
- this._disableCommands();
484
- return;
485
- }
486
- const marker = getMarkerAtPosition(editor, selection.focus);
487
- this._disableCommands();
488
- if (isSelectionInMarker(selection, marker)) {
489
- this._enableCommands(marker);
490
- }
491
- }
492
- /**
493
- * Enables commands in non-restricted regions.
494
- */
495
- _enableCommands(marker) {
496
- const editor = this.editor;
497
- for (const [commandName, command] of editor.commands) {
498
- if (!command.affectsData || this._alwaysEnabled.has(commandName)) {
499
- continue;
500
- }
501
- // Enable ony those commands that are allowed in the exception marker.
502
- if (!this._allowedInException.has(commandName)) {
503
- continue;
504
- }
505
- // Do not enable 'delete' and 'deleteForward' commands on the exception marker boundaries.
506
- if (isDeleteCommandOnMarkerBoundaries(commandName, editor.model.document.selection, marker.getRange())) {
507
- continue;
508
- }
509
- command.clearForceDisabled(COMMAND_FORCE_DISABLE_ID);
510
- }
511
- }
512
- /**
513
- * Disables commands outside non-restricted regions.
514
- */
515
- _disableCommands() {
516
- const editor = this.editor;
517
- for (const [commandName, command] of editor.commands) {
518
- if (!command.affectsData || this._alwaysEnabled.has(commandName)) {
519
- continue;
520
- }
521
- command.forceDisabled(COMMAND_FORCE_DISABLE_ID);
522
- }
523
- }
524
- }
525
- /**
526
- * Helper method for executing enabled commands only.
527
- */
528
- function getCommandExecuter(editor, commandName) {
529
- return (_, cancel) => {
530
- const command = editor.commands.get(commandName);
531
- if (command.isEnabled) {
532
- editor.execute(commandName);
533
- cancel();
534
- }
535
- };
536
- }
537
- /**
538
- * Helper for handling Ctrl+A keydown behaviour.
539
- */
540
- function getSelectAllHandler(editor) {
541
- return (_, cancel) => {
542
- const model = editor.model;
543
- const selection = editor.model.document.selection;
544
- const marker = getMarkerAtPosition(editor, selection.focus);
545
- if (!marker) {
546
- return;
547
- }
548
- // If selection range is inside a restricted editing exception, select text only within the exception.
549
- //
550
- // Note: Second Ctrl+A press is also blocked and it won't select the entire text in the editor.
551
- const selectionRange = selection.getFirstRange();
552
- const markerRange = marker.getRange();
553
- if (markerRange.containsRange(selectionRange, true) || selection.isCollapsed) {
554
- cancel();
555
- model.change(writer => {
556
- writer.setSelection(marker.getRange());
557
- });
558
- }
559
- };
560
- }
561
- /**
562
- * Additional rule for enabling "delete" and "deleteForward" commands if selection is on range boundaries:
563
- *
564
- * Does not allow to enable command when selection focus is:
565
- * - is on marker start - "delete" - to prevent removing content before marker
566
- * - is on marker end - "deleteForward" - to prevent removing content after marker
567
- */
568
- function isDeleteCommandOnMarkerBoundaries(commandName, selection, markerRange) {
569
- if (commandName == 'delete' && markerRange.start.isEqual(selection.focus)) {
570
- return true;
571
- }
572
- // Only for collapsed selection - non-collapsed selection that extends over a marker is handled elsewhere.
573
- if (commandName == 'deleteForward' && selection.isCollapsed && markerRange.end.isEqual(selection.focus)) {
574
- return true;
575
- }
576
- return false;
577
- }
578
- /**
579
- * Ensures that model.deleteContent() does not delete outside exception markers ranges.
580
- *
581
- * The enforced restrictions are:
582
- * - only execute deleteContent() inside exception markers
583
- * - restrict passed selection to exception marker
584
- */
585
- function restrictDeleteContent(editor) {
586
- return (evt, args) => {
587
- const [selection] = args;
588
- const marker = getMarkerAtPosition(editor, selection.focus) || getMarkerAtPosition(editor, selection.anchor);
589
- // Stop method execution if marker was not found at selection focus.
590
- if (!marker) {
591
- evt.stop();
592
- return;
593
- }
594
- // Collapsed selection inside exception marker does not require fixing.
595
- if (selection.isCollapsed) {
596
- return;
597
- }
598
- // Shrink the selection to the range inside exception marker.
599
- const allowedToDelete = marker.getRange().getIntersection(selection.getFirstRange());
600
- // Some features uses selection passed to model.deleteContent() to set the selection afterwards. For this we need to properly modify
601
- // either the document selection using change block...
602
- if (selection.is('documentSelection')) {
603
- editor.model.change(writer => {
604
- writer.setSelection(allowedToDelete);
605
- });
606
- }
607
- // ... or by modifying passed selection instance directly.
608
- else {
609
- selection.setTo(allowedToDelete);
610
- }
611
- };
612
- }
613
- /**
614
- * Ensures that input command is executed with a range that is inside exception marker.
615
- *
616
- * This restriction is due to fact that using native spell check changes text outside exception marker.
617
- */
618
- function disallowInputExecForWrongRange(editor) {
619
- return (evt, args) => {
620
- const [options] = args;
621
- const { range } = options;
622
- // Only check "input" command executed with a range value.
623
- // Selection might be set in exception marker but passed range might point elsewhere.
624
- if (!range) {
625
- return;
626
- }
627
- if (!isRangeInsideSingleMarker(editor, range)) {
628
- evt.stop();
629
- }
630
- };
631
- }
632
- function isRangeInsideSingleMarker(editor, range) {
633
- const markerAtStart = getMarkerAtPosition(editor, range.start);
634
- const markerAtEnd = getMarkerAtPosition(editor, range.end);
635
- return markerAtStart && markerAtEnd && markerAtEnd === markerAtStart;
636
- }
637
- /**
638
- * Checks if new marker range is flat. Non-flat ranges might appear during upcast conversion in nested structures, ie tables.
639
- *
640
- * Note: This marker fixer only consider case which is possible to create using StandardEditing mode plugin.
641
- * Markers created by developer in the data might break in many other ways.
642
- *
643
- * See #6003.
644
- */
645
- function ensureNewMarkerIsFlatPostFixer(editor) {
646
- return writer => {
647
- let changeApplied = false;
648
- const changedMarkers = editor.model.document.differ.getChangedMarkers();
649
- for (const { data, name } of changedMarkers) {
650
- if (!name.startsWith('restrictedEditingException')) {
651
- continue;
652
- }
653
- const newRange = data.newRange;
654
- if (!data.oldRange && !newRange.isFlat) {
655
- const start = newRange.start;
656
- const end = newRange.end;
657
- const startIsHigherInTree = start.path.length > end.path.length;
658
- const fixedStart = startIsHigherInTree ? newRange.start : writer.createPositionAt(end.parent, 0);
659
- const fixedEnd = startIsHigherInTree ? writer.createPositionAt(start.parent, 'end') : newRange.end;
660
- writer.updateMarker(name, {
661
- range: writer.createRange(fixedStart, fixedEnd)
662
- });
663
- changeApplied = true;
664
- }
665
- }
666
- return changeApplied;
667
- };
668
- }
669
- function onlyAllowAttributesFromList(allowedAttributes) {
670
- return (context, attributeName) => {
671
- if (context.startsWith('$clipboardHolder')) {
672
- return allowedAttributes.includes(attributeName);
673
- }
674
- };
675
- }
676
- function allowTextOnlyInClipboardHolder() {
677
- return (context, childDefinition) => {
678
- if (context.startsWith('$clipboardHolder')) {
679
- return childDefinition.name === '$text';
680
- }
681
- };
682
- }
683
-
684
- var lockIcon = "<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M15.5 6.5a3.5 3.5 0 0 1 3.495 3.308L19 10v2a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1h-7a1 1 0 0 1-1-1v-5a1 1 0 0 1 1-1v-2l.005-.192A3.5 3.5 0 0 1 15.5 6.5zm0 7.5a.5.5 0 0 0-.492.41L15 14.5v2a.5.5 0 0 0 .992.09L16 16.5v-2a.5.5 0 0 0-.5-.5zm0-6a2 2 0 0 0-2 2v2h4v-2a2 2 0 0 0-2-2zm-9.25 8a.75.75 0 1 1 0 1.5H.75a.75.75 0 1 1 0-1.5h5.5zm0-5a.75.75 0 1 1 0 1.5H.75a.75.75 0 1 1 0-1.5h5.5zm3-5a.75.75 0 0 1 0 1.5H.75a.75.75 0 0 1 0-1.5h8.5zm6-5a.75.75 0 1 1 0 1.5H.75a.75.75 0 0 1 0-1.5h14.5z\"/></svg>";
685
-
686
- /**
687
- * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
688
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
689
- */
690
- /**
691
- * @module restricted-editing/restrictededitingmodeui
692
- */
693
- /**
694
- * The restricted editing mode UI feature.
695
- *
696
- * It introduces the `'restrictedEditing'` dropdown that offers tools to navigate between exceptions across
697
- * the document.
698
- */
699
- class RestrictedEditingModeUI extends Plugin {
700
- /**
701
- * @inheritDoc
702
- */
703
- static get pluginName() {
704
- return 'RestrictedEditingModeUI';
705
- }
706
- /**
707
- * @inheritDoc
708
- */
709
- init() {
710
- const editor = this.editor;
711
- const t = editor.t;
712
- editor.ui.componentFactory.add('restrictedEditing', locale => {
713
- const dropdownView = createDropdown(locale);
714
- const listItems = new Collection();
715
- listItems.add(this._getButtonDefinition('goToPreviousRestrictedEditingException', t('Previous editable region'), 'Shift+Tab'));
716
- listItems.add(this._getButtonDefinition('goToNextRestrictedEditingException', t('Next editable region'), 'Tab'));
717
- addListToDropdown(dropdownView, listItems);
718
- dropdownView.buttonView.set({
719
- label: t('Navigate editable regions'),
720
- icon: lockIcon,
721
- tooltip: true,
722
- isEnabled: true,
723
- isOn: false
724
- });
725
- this.listenTo(dropdownView, 'execute', evt => {
726
- const { _commandName } = evt.source;
727
- editor.execute(_commandName);
728
- editor.editing.view.focus();
729
- });
730
- return dropdownView;
731
- });
732
- }
733
- /**
734
- * Returns a definition of the navigation button to be used in the dropdown.
735
-
736
- * @param commandName The name of the command that the button represents.
737
- * @param label The translated label of the button.
738
- * @param keystroke The button keystroke.
739
- */
740
- _getButtonDefinition(commandName, label, keystroke) {
741
- const editor = this.editor;
742
- const command = editor.commands.get(commandName);
743
- const definition = {
744
- type: 'button',
745
- model: new ViewModel({
746
- label,
747
- withText: true,
748
- keystroke,
749
- withKeystroke: true,
750
- _commandName: commandName
751
- })
752
- };
753
- definition.model.bind('isEnabled').to(command, 'isEnabled');
754
- return definition;
755
- }
756
- }
757
-
758
- /**
759
- * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
760
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
761
- */
762
- /**
763
- * @module restricted-editing/restrictededitingmode
764
- */
765
- /**
766
- * The restricted editing mode plugin.
767
- *
768
- * This is a "glue" plugin which loads the following plugins:
769
- *
770
- * * The {@link module:restricted-editing/restrictededitingmodeediting~RestrictedEditingModeEditing restricted mode editing feature}.
771
- * * The {@link module:restricted-editing/restrictededitingmodeui~RestrictedEditingModeUI restricted mode UI feature}.
772
- */
773
- class RestrictedEditingMode extends Plugin {
774
- /**
775
- * @inheritDoc
776
- */
777
- static get pluginName() {
778
- return 'RestrictedEditingMode';
779
- }
780
- /**
781
- * @inheritDoc
782
- */
783
- static get requires() {
784
- return [RestrictedEditingModeEditing, RestrictedEditingModeUI];
785
- }
786
- }
787
-
788
- /**
789
- * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
790
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
791
- */
792
- /**
793
- * @module restricted-editing/restrictededitingexceptioncommand
794
- */
795
- /**
796
- * The command that toggles exceptions from the restricted editing on text.
797
- */
798
- class RestrictedEditingExceptionCommand extends Command {
799
- /**
800
- * @inheritDoc
801
- */
802
- refresh() {
803
- const model = this.editor.model;
804
- const doc = model.document;
805
- this.value = !!doc.selection.getAttribute('restrictedEditingException');
806
- this.isEnabled = model.schema.checkAttributeInSelection(doc.selection, 'restrictedEditingException');
807
- }
808
- /**
809
- * @inheritDoc
810
- */
811
- execute(options = {}) {
812
- const model = this.editor.model;
813
- const document = model.document;
814
- const selection = document.selection;
815
- const valueToSet = (options.forceValue === undefined) ? !this.value : options.forceValue;
816
- model.change(writer => {
817
- const ranges = model.schema.getValidRanges(selection.getRanges(), 'restrictedEditingException');
818
- if (selection.isCollapsed) {
819
- if (valueToSet) {
820
- writer.setSelectionAttribute('restrictedEditingException', valueToSet);
821
- }
822
- else {
823
- const isSameException = (value) => {
824
- return value.item.getAttribute('restrictedEditingException') === this.value;
825
- };
826
- const focus = selection.focus;
827
- const exceptionStart = focus.getLastMatchingPosition(isSameException, { direction: 'backward' });
828
- const exceptionEnd = focus.getLastMatchingPosition(isSameException);
829
- writer.removeSelectionAttribute('restrictedEditingException');
830
- if (!(focus.isEqual(exceptionStart) || focus.isEqual(exceptionEnd))) {
831
- writer.removeAttribute('restrictedEditingException', writer.createRange(exceptionStart, exceptionEnd));
832
- }
833
- }
834
- }
835
- else {
836
- for (const range of ranges) {
837
- if (valueToSet) {
838
- writer.setAttribute('restrictedEditingException', valueToSet, range);
839
- }
840
- else {
841
- writer.removeAttribute('restrictedEditingException', range);
842
- }
843
- }
844
- }
845
- });
846
- }
847
- }
848
-
849
- /**
850
- * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
851
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
852
- */
853
- /**
854
- * @module restricted-editing/standardeditingmodeediting
855
- */
856
- /**
857
- * The standard editing mode editing feature.
858
- *
859
- * * It introduces the `restrictedEditingException` text attribute that is rendered as
860
- * a `<span>` element with the `restricted-editing-exception` CSS class.
861
- * * It registers the `'restrictedEditingException'` command.
862
- */
863
- class StandardEditingModeEditing extends Plugin {
864
- /**
865
- * @inheritDoc
866
- */
867
- static get pluginName() {
868
- return 'StandardEditingModeEditing';
869
- }
870
- /**
871
- * @inheritDoc
872
- */
873
- init() {
874
- const editor = this.editor;
875
- editor.model.schema.extend('$text', { allowAttributes: ['restrictedEditingException'] });
876
- editor.conversion.for('upcast').elementToAttribute({
877
- model: 'restrictedEditingException',
878
- view: {
879
- name: 'span',
880
- classes: 'restricted-editing-exception'
881
- }
882
- });
883
- editor.conversion.for('downcast').attributeToElement({
884
- model: 'restrictedEditingException',
885
- view: (modelAttributeValue, { writer }) => {
886
- if (modelAttributeValue) {
887
- // Make the restricted editing <span> outer-most in the view.
888
- return writer.createAttributeElement('span', { class: 'restricted-editing-exception' }, { priority: -10 });
889
- }
890
- }
891
- });
892
- editor.commands.add('restrictedEditingException', new RestrictedEditingExceptionCommand(editor));
893
- editor.editing.view.change(writer => {
894
- for (const root of editor.editing.view.document.roots) {
895
- writer.addClass('ck-restricted-editing_mode_standard', root);
896
- }
897
- });
898
- }
899
- }
900
-
901
- var unlockIcon = "<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M6.25 16a.75.75 0 1 1 0 1.5H.75a.75.75 0 1 1 0-1.5h5.5zm0-5a.75.75 0 1 1 0 1.5H.75a.75.75 0 1 1 0-1.5h5.5zm3-5a.75.75 0 0 1 0 1.5H.75a.75.75 0 0 1 0-1.5h8.5zm6-5a.75.75 0 1 1 0 1.5H.75a.75.75 0 0 1 0-1.5h14.5zm.25 5.5a3.5 3.5 0 0 1 3.143 1.959.75.75 0 0 1-1.36.636A2 2 0 0 0 13.5 10v2H19a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1h-7a1 1 0 0 1-1-1v-5a1 1 0 0 1 1-1v-2l.005-.192A3.5 3.5 0 0 1 15.5 6.5zm0 7.5a.5.5 0 0 0-.492.41L15 14.5v2a.5.5 0 0 0 .992.09L16 16.5v-2a.5.5 0 0 0-.5-.5z\"/></svg>";
902
-
903
- /**
904
- * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
905
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
906
- */
907
- /**
908
- * @module restricted-editing/standardeditingmodeui
909
- */
910
- /**
911
- * The standard editing mode UI feature.
912
- *
913
- * It introduces the `'restrictedEditingException'` button that marks text as unrestricted for editing.
914
- */
915
- class StandardEditingModeUI extends Plugin {
916
- /**
917
- * @inheritDoc
918
- */
919
- static get pluginName() {
920
- return 'StandardEditingModeUI';
921
- }
922
- /**
923
- * @inheritDoc
924
- */
925
- init() {
926
- const editor = this.editor;
927
- const t = editor.t;
928
- editor.ui.componentFactory.add('restrictedEditingException', locale => {
929
- const command = editor.commands.get('restrictedEditingException');
930
- const view = new ButtonView(locale);
931
- view.set({
932
- icon: unlockIcon,
933
- tooltip: true,
934
- isToggleable: true
935
- });
936
- view.bind('isOn', 'isEnabled').to(command, 'value', 'isEnabled');
937
- view.bind('label').to(command, 'value', value => {
938
- return value ? t('Disable editing') : t('Enable editing');
939
- });
940
- this.listenTo(view, 'execute', () => {
941
- editor.execute('restrictedEditingException');
942
- editor.editing.view.focus();
943
- });
944
- return view;
945
- });
946
- }
947
- }
948
-
949
- /**
950
- * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
951
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
952
- */
953
- /**
954
- * @module restricted-editing/standardeditingmode
955
- */
956
- /**
957
- * The standard editing mode plugin.
958
- *
959
- * This is a "glue" plugin that loads the following plugins:
960
- *
961
- * * The {@link module:restricted-editing/standardeditingmodeediting~StandardEditingModeEditing standard mode editing feature}.
962
- * * The {@link module:restricted-editing/standardeditingmodeui~StandardEditingModeUI standard mode UI feature}.
963
- */
964
- class StandardEditingMode extends Plugin {
965
- /**
966
- * @inheritDoc
967
- */
968
- static get pluginName() {
969
- return 'StandardEditingMode';
970
- }
971
- static get requires() {
972
- return [StandardEditingModeEditing, StandardEditingModeUI];
973
- }
974
- }
975
-
976
- export { RestrictedEditingMode, RestrictedEditingModeEditing, RestrictedEditingModeUI, StandardEditingMode, StandardEditingModeEditing, StandardEditingModeUI };
977
- //# sourceMappingURL=index.js.map