@ckeditor/ckeditor5-restricted-editing 47.1.0 → 47.2.0-alpha.1
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/README.md +1 -1
- package/build/restricted-editing.js +2 -2
- package/build/translations/af.js +1 -1
- package/build/translations/ar.js +1 -1
- package/build/translations/ast.js +1 -1
- package/build/translations/az.js +1 -1
- package/build/translations/be.js +1 -1
- package/build/translations/bg.js +1 -1
- package/build/translations/bn.js +1 -1
- package/build/translations/bs.js +1 -1
- package/build/translations/ca.js +1 -1
- package/build/translations/cs.js +1 -1
- package/build/translations/da.js +1 -1
- package/build/translations/de-ch.js +1 -1
- package/build/translations/de.js +1 -1
- package/build/translations/el.js +1 -1
- package/build/translations/en-au.js +1 -1
- package/build/translations/en-gb.js +1 -1
- package/build/translations/eo.js +1 -1
- package/build/translations/es-co.js +1 -1
- package/build/translations/es.js +1 -1
- package/build/translations/et.js +1 -1
- package/build/translations/eu.js +1 -1
- package/build/translations/fa.js +1 -1
- package/build/translations/fi.js +1 -1
- package/build/translations/fr.js +1 -1
- package/build/translations/gl.js +1 -1
- package/build/translations/gu.js +1 -1
- package/build/translations/he.js +1 -1
- package/build/translations/hi.js +1 -1
- package/build/translations/hr.js +1 -1
- package/build/translations/hu.js +1 -1
- package/build/translations/hy.js +1 -1
- package/build/translations/id.js +1 -1
- package/build/translations/it.js +1 -1
- package/build/translations/ja.js +1 -1
- package/build/translations/jv.js +1 -1
- package/build/translations/kk.js +1 -1
- package/build/translations/km.js +1 -1
- package/build/translations/kn.js +1 -1
- package/build/translations/ko.js +1 -1
- package/build/translations/ku.js +1 -1
- package/build/translations/lt.js +1 -1
- package/build/translations/lv.js +1 -1
- package/build/translations/ms.js +1 -1
- package/build/translations/nb.js +1 -1
- package/build/translations/ne.js +1 -1
- package/build/translations/nl.js +1 -1
- package/build/translations/no.js +1 -1
- package/build/translations/oc.js +1 -1
- package/build/translations/pl.js +1 -1
- package/build/translations/pt-br.js +1 -1
- package/build/translations/pt.js +1 -1
- package/build/translations/ro.js +1 -1
- package/build/translations/ru.js +1 -1
- package/build/translations/si.js +1 -1
- package/build/translations/sk.js +1 -1
- package/build/translations/sl.js +1 -1
- package/build/translations/sq.js +1 -1
- package/build/translations/sr-latn.js +1 -1
- package/build/translations/sr.js +1 -1
- package/build/translations/sv.js +1 -1
- package/build/translations/th.js +1 -1
- package/build/translations/ti.js +1 -1
- package/build/translations/tk.js +1 -1
- package/build/translations/tr.js +1 -1
- package/build/translations/tt.js +1 -1
- package/build/translations/ug.js +1 -1
- package/build/translations/uk.js +1 -1
- package/build/translations/ur.js +1 -1
- package/build/translations/uz.js +1 -1
- package/build/translations/vi.js +1 -1
- package/build/translations/zh-cn.js +1 -1
- package/build/translations/zh.js +1 -1
- package/ckeditor5-metadata.json +20 -2
- package/dist/index.js +564 -105
- package/dist/index.js.map +1 -1
- package/dist/translations/af.js +1 -1
- package/dist/translations/af.umd.js +1 -1
- package/dist/translations/ar.js +1 -1
- package/dist/translations/ar.umd.js +1 -1
- package/dist/translations/ast.js +1 -1
- package/dist/translations/ast.umd.js +1 -1
- package/dist/translations/az.js +1 -1
- package/dist/translations/az.umd.js +1 -1
- package/dist/translations/be.js +1 -1
- package/dist/translations/be.umd.js +1 -1
- package/dist/translations/bg.js +1 -1
- package/dist/translations/bg.umd.js +1 -1
- package/dist/translations/bn.js +1 -1
- package/dist/translations/bn.umd.js +1 -1
- package/dist/translations/bs.js +1 -1
- package/dist/translations/bs.umd.js +1 -1
- package/dist/translations/ca.js +1 -1
- package/dist/translations/ca.umd.js +1 -1
- package/dist/translations/cs.js +1 -1
- package/dist/translations/cs.umd.js +1 -1
- package/dist/translations/da.js +1 -1
- package/dist/translations/da.umd.js +1 -1
- package/dist/translations/de-ch.js +1 -1
- package/dist/translations/de-ch.umd.js +1 -1
- package/dist/translations/de.js +1 -1
- package/dist/translations/de.umd.js +1 -1
- package/dist/translations/el.js +1 -1
- package/dist/translations/el.umd.js +1 -1
- package/dist/translations/en-au.js +1 -1
- package/dist/translations/en-au.umd.js +1 -1
- package/dist/translations/en-gb.js +1 -1
- package/dist/translations/en-gb.umd.js +1 -1
- package/dist/translations/en.js +1 -1
- package/dist/translations/en.umd.js +1 -1
- package/dist/translations/eo.js +1 -1
- package/dist/translations/eo.umd.js +1 -1
- package/dist/translations/es-co.js +1 -1
- package/dist/translations/es-co.umd.js +1 -1
- package/dist/translations/es.js +1 -1
- package/dist/translations/es.umd.js +1 -1
- package/dist/translations/et.js +1 -1
- package/dist/translations/et.umd.js +1 -1
- package/dist/translations/eu.js +1 -1
- package/dist/translations/eu.umd.js +1 -1
- package/dist/translations/fa.js +1 -1
- package/dist/translations/fa.umd.js +1 -1
- package/dist/translations/fi.js +1 -1
- package/dist/translations/fi.umd.js +1 -1
- package/dist/translations/fr.js +1 -1
- package/dist/translations/fr.umd.js +1 -1
- package/dist/translations/gl.js +1 -1
- package/dist/translations/gl.umd.js +1 -1
- package/dist/translations/gu.js +1 -1
- package/dist/translations/gu.umd.js +1 -1
- package/dist/translations/he.js +1 -1
- package/dist/translations/he.umd.js +1 -1
- package/dist/translations/hi.js +1 -1
- package/dist/translations/hi.umd.js +1 -1
- package/dist/translations/hr.js +1 -1
- package/dist/translations/hr.umd.js +1 -1
- package/dist/translations/hu.js +1 -1
- package/dist/translations/hu.umd.js +1 -1
- package/dist/translations/hy.js +1 -1
- package/dist/translations/hy.umd.js +1 -1
- package/dist/translations/id.js +1 -1
- package/dist/translations/id.umd.js +1 -1
- package/dist/translations/it.js +1 -1
- package/dist/translations/it.umd.js +1 -1
- package/dist/translations/ja.js +1 -1
- package/dist/translations/ja.umd.js +1 -1
- package/dist/translations/jv.js +1 -1
- package/dist/translations/jv.umd.js +1 -1
- package/dist/translations/kk.js +1 -1
- package/dist/translations/kk.umd.js +1 -1
- package/dist/translations/km.js +1 -1
- package/dist/translations/km.umd.js +1 -1
- package/dist/translations/kn.js +1 -1
- package/dist/translations/kn.umd.js +1 -1
- package/dist/translations/ko.js +1 -1
- package/dist/translations/ko.umd.js +1 -1
- package/dist/translations/ku.js +1 -1
- package/dist/translations/ku.umd.js +1 -1
- package/dist/translations/lt.js +1 -1
- package/dist/translations/lt.umd.js +1 -1
- package/dist/translations/lv.js +1 -1
- package/dist/translations/lv.umd.js +1 -1
- package/dist/translations/ms.js +1 -1
- package/dist/translations/ms.umd.js +1 -1
- package/dist/translations/nb.js +1 -1
- package/dist/translations/nb.umd.js +1 -1
- package/dist/translations/ne.js +1 -1
- package/dist/translations/ne.umd.js +1 -1
- package/dist/translations/nl.js +1 -1
- package/dist/translations/nl.umd.js +1 -1
- package/dist/translations/no.js +1 -1
- package/dist/translations/no.umd.js +1 -1
- package/dist/translations/oc.js +1 -1
- package/dist/translations/oc.umd.js +1 -1
- package/dist/translations/pl.js +1 -1
- package/dist/translations/pl.umd.js +1 -1
- package/dist/translations/pt-br.js +1 -1
- package/dist/translations/pt-br.umd.js +1 -1
- package/dist/translations/pt.js +1 -1
- package/dist/translations/pt.umd.js +1 -1
- package/dist/translations/ro.js +1 -1
- package/dist/translations/ro.umd.js +1 -1
- package/dist/translations/ru.js +1 -1
- package/dist/translations/ru.umd.js +1 -1
- package/dist/translations/si.js +1 -1
- package/dist/translations/si.umd.js +1 -1
- package/dist/translations/sk.js +1 -1
- package/dist/translations/sk.umd.js +1 -1
- package/dist/translations/sl.js +1 -1
- package/dist/translations/sl.umd.js +1 -1
- package/dist/translations/sq.js +1 -1
- package/dist/translations/sq.umd.js +1 -1
- package/dist/translations/sr-latn.js +1 -1
- package/dist/translations/sr-latn.umd.js +1 -1
- package/dist/translations/sr.js +1 -1
- package/dist/translations/sr.umd.js +1 -1
- package/dist/translations/sv.js +1 -1
- package/dist/translations/sv.umd.js +1 -1
- package/dist/translations/th.js +1 -1
- package/dist/translations/th.umd.js +1 -1
- package/dist/translations/ti.js +1 -1
- package/dist/translations/ti.umd.js +1 -1
- package/dist/translations/tk.js +1 -1
- package/dist/translations/tk.umd.js +1 -1
- package/dist/translations/tr.js +1 -1
- package/dist/translations/tr.umd.js +1 -1
- package/dist/translations/tt.js +1 -1
- package/dist/translations/tt.umd.js +1 -1
- package/dist/translations/ug.js +1 -1
- package/dist/translations/ug.umd.js +1 -1
- package/dist/translations/uk.js +1 -1
- package/dist/translations/uk.umd.js +1 -1
- package/dist/translations/ur.js +1 -1
- package/dist/translations/ur.umd.js +1 -1
- package/dist/translations/uz.js +1 -1
- package/dist/translations/uz.umd.js +1 -1
- package/dist/translations/vi.js +1 -1
- package/dist/translations/vi.umd.js +1 -1
- package/dist/translations/zh-cn.js +1 -1
- package/dist/translations/zh-cn.umd.js +1 -1
- package/dist/translations/zh.js +1 -1
- package/dist/translations/zh.umd.js +1 -1
- package/lang/contexts.json +4 -1
- package/lang/translations/af.po +16 -4
- package/lang/translations/ar.po +16 -4
- package/lang/translations/ast.po +16 -4
- package/lang/translations/az.po +16 -4
- package/lang/translations/be.po +16 -4
- package/lang/translations/bg.po +16 -4
- package/lang/translations/bn.po +16 -4
- package/lang/translations/bs.po +16 -4
- package/lang/translations/ca.po +16 -4
- package/lang/translations/cs.po +16 -4
- package/lang/translations/da.po +16 -4
- package/lang/translations/de-ch.po +16 -4
- package/lang/translations/de.po +16 -4
- package/lang/translations/el.po +16 -4
- package/lang/translations/en-au.po +16 -4
- package/lang/translations/en-gb.po +16 -4
- package/lang/translations/en.po +16 -4
- package/lang/translations/eo.po +16 -4
- package/lang/translations/es-co.po +16 -4
- package/lang/translations/es.po +16 -4
- package/lang/translations/et.po +16 -4
- package/lang/translations/eu.po +16 -4
- package/lang/translations/fa.po +16 -4
- package/lang/translations/fi.po +16 -4
- package/lang/translations/fr.po +16 -4
- package/lang/translations/gl.po +16 -4
- package/lang/translations/gu.po +16 -4
- package/lang/translations/he.po +16 -4
- package/lang/translations/hi.po +16 -4
- package/lang/translations/hr.po +16 -4
- package/lang/translations/hu.po +16 -4
- package/lang/translations/hy.po +16 -4
- package/lang/translations/id.po +16 -4
- package/lang/translations/it.po +16 -4
- package/lang/translations/ja.po +16 -4
- package/lang/translations/jv.po +16 -4
- package/lang/translations/kk.po +16 -4
- package/lang/translations/km.po +16 -4
- package/lang/translations/kn.po +16 -4
- package/lang/translations/ko.po +16 -4
- package/lang/translations/ku.po +16 -4
- package/lang/translations/lt.po +16 -4
- package/lang/translations/lv.po +16 -4
- package/lang/translations/ms.po +16 -4
- package/lang/translations/nb.po +16 -4
- package/lang/translations/ne.po +16 -4
- package/lang/translations/nl.po +16 -4
- package/lang/translations/no.po +16 -4
- package/lang/translations/oc.po +16 -4
- package/lang/translations/pl.po +16 -4
- package/lang/translations/pt-br.po +16 -4
- package/lang/translations/pt.po +16 -4
- package/lang/translations/ro.po +16 -4
- package/lang/translations/ru.po +16 -4
- package/lang/translations/si.po +16 -4
- package/lang/translations/sk.po +16 -4
- package/lang/translations/sl.po +16 -4
- package/lang/translations/sq.po +16 -4
- package/lang/translations/sr-latn.po +16 -4
- package/lang/translations/sr.po +16 -4
- package/lang/translations/sv.po +16 -4
- package/lang/translations/th.po +16 -4
- package/lang/translations/ti.po +16 -4
- package/lang/translations/tk.po +16 -4
- package/lang/translations/tr.po +16 -4
- package/lang/translations/tt.po +16 -4
- package/lang/translations/ug.po +16 -4
- package/lang/translations/uk.po +16 -4
- package/lang/translations/ur.po +16 -4
- package/lang/translations/uz.po +16 -4
- package/lang/translations/vi.po +16 -4
- package/lang/translations/zh-cn.po +16 -4
- package/lang/translations/zh.po +16 -4
- package/package.json +7 -7
- package/src/augmentation.d.ts +2 -1
- package/src/index.d.ts +1 -0
- package/src/index.js +1 -0
- package/src/restrictededitingconfig.d.ts +2 -2
- package/src/restrictededitingexceptionblockcommand.d.ts +57 -0
- package/src/restrictededitingexceptionblockcommand.js +203 -0
- package/src/restrictededitingmode/converters.d.ts +1 -0
- package/src/restrictededitingmode/converters.js +33 -6
- package/src/restrictededitingmode/utils.d.ts +8 -2
- package/src/restrictededitingmode/utils.js +16 -3
- package/src/restrictededitingmodeediting.d.ts +4 -0
- package/src/restrictededitingmodeediting.js +83 -35
- package/src/restrictededitingmodenavigationcommand.js +2 -1
- package/src/standardeditingmodeediting.js +135 -3
- package/src/standardeditingmodeui.js +75 -11
package/dist/index.js
CHANGED
|
@@ -3,10 +3,72 @@
|
|
|
3
3
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
4
4
|
*/
|
|
5
5
|
import { Command, Plugin } from '@ckeditor/ckeditor5-core/dist/index.js';
|
|
6
|
+
import { getCode, parseKeystroke, Collection, first } from '@ckeditor/ckeditor5-utils/dist/index.js';
|
|
6
7
|
import { Matcher } from '@ckeditor/ckeditor5-engine/dist/index.js';
|
|
7
8
|
import { IconContentLock, IconContentUnlock } from '@ckeditor/ckeditor5-icons/dist/index.js';
|
|
8
|
-
import { createDropdown, addListToDropdown, MenuBarMenuView, MenuBarMenuListView, MenuBarMenuListItemView, MenuBarMenuListItemButtonView, UIModel, ButtonView } from '@ckeditor/ckeditor5-ui/dist/index.js';
|
|
9
|
-
|
|
9
|
+
import { createDropdown, addListToDropdown, MenuBarMenuView, MenuBarMenuListView, MenuBarMenuListItemView, MenuBarMenuListItemButtonView, UIModel, addToolbarToDropdown, ButtonView } from '@ckeditor/ckeditor5-ui/dist/index.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @license Copyright (c) 2003-2025, 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 returnd a marker also when the postion 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
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
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);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
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);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
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;
|
|
71
|
+
}
|
|
10
72
|
|
|
11
73
|
/**
|
|
12
74
|
* The command that allows navigation across the exceptions in the edited document.
|
|
@@ -59,7 +121,7 @@ import { Collection } from '@ckeditor/ckeditor5-utils/dist/index.js';
|
|
|
59
121
|
const markerRanges = [];
|
|
60
122
|
// Get all exception marker positions that start after/before the selection position.
|
|
61
123
|
for (const marker of model.markers.getMarkersGroup('restrictedEditingException')){
|
|
62
|
-
const markerRange = marker
|
|
124
|
+
const markerRange = getExceptionRange(marker, model);
|
|
63
125
|
// Checking parent because there two positions <paragraph>foo^</paragraph><paragraph>^bar</paragraph>
|
|
64
126
|
// are touching but they will represent different markers.
|
|
65
127
|
const isMarkerRangeTouching = selectionPosition.isTouching(markerRange.start) && selectionPosition.hasSameParentAs(markerRange.start) || selectionPosition.isTouching(markerRange.end) && selectionPosition.hasSameParentAs(markerRange.end);
|
|
@@ -90,56 +152,6 @@ import { Collection } from '@ckeditor/ckeditor5-utils/dist/index.js';
|
|
|
90
152
|
}).shift();
|
|
91
153
|
}
|
|
92
154
|
|
|
93
|
-
/**
|
|
94
|
-
* @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
|
|
95
|
-
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
96
|
-
*/ /**
|
|
97
|
-
* @module restricted-editing/restrictededitingmode/utils
|
|
98
|
-
*/ /**
|
|
99
|
-
* Returns a single "restricted-editing-exception" marker at a given position. Contrary to
|
|
100
|
-
* {@link module:engine/model/markercollection~MarkerCollection#getMarkersAtPosition}, it returnd a marker also when the postion is
|
|
101
|
-
* equal to one of the marker's start or end positions.
|
|
102
|
-
*
|
|
103
|
-
* @internal
|
|
104
|
-
*/ function getMarkerAtPosition(editor, position) {
|
|
105
|
-
for (const marker of editor.model.markers){
|
|
106
|
-
const markerRange = marker.getRange();
|
|
107
|
-
if (isPositionInRangeBoundaries(markerRange, position)) {
|
|
108
|
-
if (marker.name.startsWith('restrictedEditingException:')) {
|
|
109
|
-
return marker;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* Checks if the position is fully contained in the range. Positions equal to range start or end are considered "in".
|
|
116
|
-
*
|
|
117
|
-
* @internal
|
|
118
|
-
*/ function isPositionInRangeBoundaries(range, position) {
|
|
119
|
-
return range.containsPosition(position) || range.end.isEqual(position) || range.start.isEqual(position);
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* Checks if the selection is fully contained in the marker. Positions on marker boundaries are considered "in".
|
|
123
|
-
*
|
|
124
|
-
* ```xml
|
|
125
|
-
* <marker>[]foo</marker> -> true
|
|
126
|
-
* <marker>f[oo]</marker> -> true
|
|
127
|
-
* <marker>f[oo</marker> ba]r -> false
|
|
128
|
-
* <marker>foo</marker> []bar -> false
|
|
129
|
-
* ```
|
|
130
|
-
*
|
|
131
|
-
* @internal
|
|
132
|
-
*/ function isSelectionInMarker(selection, marker) {
|
|
133
|
-
if (!marker) {
|
|
134
|
-
return false;
|
|
135
|
-
}
|
|
136
|
-
const markerRange = marker.getRange();
|
|
137
|
-
if (selection.isCollapsed) {
|
|
138
|
-
return isPositionInRangeBoundaries(markerRange, selection.focus);
|
|
139
|
-
}
|
|
140
|
-
return markerRange.containsRange(selection.getFirstRange(), true);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
155
|
const HIGHLIGHT_CLASS = 'restricted-editing-exception_selected';
|
|
144
156
|
/**
|
|
145
157
|
* Adds a visual highlight style to a restricted editing exception that the selection is anchored to.
|
|
@@ -165,9 +177,16 @@ const HIGHLIGHT_CLASS = 'restricted-editing-exception_selected';
|
|
|
165
177
|
if (!marker) {
|
|
166
178
|
return false;
|
|
167
179
|
}
|
|
168
|
-
|
|
180
|
+
const modelWrapperElement = marker.getRange().getContainedElement();
|
|
181
|
+
if (modelWrapperElement && modelWrapperElement.is('element', 'restrictedEditingException')) {
|
|
182
|
+
const viewElement = editor.editing.mapper.toViewElement(modelWrapperElement);
|
|
169
183
|
writer.addClass(HIGHLIGHT_CLASS, viewElement);
|
|
170
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
|
+
}
|
|
171
190
|
}
|
|
172
191
|
return false;
|
|
173
192
|
});
|
|
@@ -237,7 +256,7 @@ const HIGHLIGHT_CLASS = 'restricted-editing-exception_selected';
|
|
|
237
256
|
* @param config Conversion configuration.
|
|
238
257
|
* @internal
|
|
239
258
|
*/ function upcastHighlightToMarker(config) {
|
|
240
|
-
return (dispatcher)=>dispatcher.on('element
|
|
259
|
+
return (dispatcher)=>dispatcher.on('element', (evt, data, conversionApi)=>{
|
|
241
260
|
const { writer } = conversionApi;
|
|
242
261
|
const matcher = new Matcher(config.view);
|
|
243
262
|
const matcherResult = matcher.match(data.viewItem);
|
|
@@ -248,7 +267,20 @@ const HIGHLIGHT_CLASS = 'restricted-editing-exception_selected';
|
|
|
248
267
|
const match = matcherResult.match;
|
|
249
268
|
// Force consuming element's name (taken from upcast helpers elementToElement converter).
|
|
250
269
|
match.name = true;
|
|
251
|
-
|
|
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);
|
|
252
284
|
conversionApi.consumable.consume(data.viewItem, match);
|
|
253
285
|
const markerName = config.model();
|
|
254
286
|
const fakeMarkerStart = writer.createElement('$marker', {
|
|
@@ -257,9 +289,14 @@ const HIGHLIGHT_CLASS = 'restricted-editing-exception_selected';
|
|
|
257
289
|
const fakeMarkerEnd = writer.createElement('$marker', {
|
|
258
290
|
'data-name': markerName
|
|
259
291
|
});
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
+
}
|
|
263
300
|
data.modelRange = writer.createRange(writer.createPositionBefore(fakeMarkerStart), writer.createPositionAfter(fakeMarkerEnd));
|
|
264
301
|
data.modelCursor = data.modelRange.end;
|
|
265
302
|
});
|
|
@@ -362,6 +399,7 @@ const COMMAND_FORCE_DISABLE_ID = 'RestrictedEditingMode';
|
|
|
362
399
|
const editingView = editor.editing.view;
|
|
363
400
|
const allowedCommands = editor.config.get('restrictedEditing.allowedCommands');
|
|
364
401
|
allowedCommands.forEach((commandName)=>this._allowedInException.add(commandName));
|
|
402
|
+
this._setupSchema();
|
|
365
403
|
this._setupConversion();
|
|
366
404
|
this._setupCommandsToggling();
|
|
367
405
|
this._setupRestrictions();
|
|
@@ -382,7 +420,9 @@ const COMMAND_FORCE_DISABLE_ID = 'RestrictedEditingMode';
|
|
|
382
420
|
}, {
|
|
383
421
|
context: '$capture'
|
|
384
422
|
});
|
|
385
|
-
|
|
423
|
+
this.listenTo(editingView.document, 'keydown', getSelectAllHandler(editor), {
|
|
424
|
+
priority: 'high'
|
|
425
|
+
});
|
|
386
426
|
editingView.change((writer)=>{
|
|
387
427
|
for (const root of editingView.document.roots){
|
|
388
428
|
writer.addClass('ck-restricted-editing_mode_restricted', root);
|
|
@@ -414,6 +454,16 @@ const COMMAND_FORCE_DISABLE_ID = 'RestrictedEditingMode';
|
|
|
414
454
|
command.clearForceDisabled(COMMAND_FORCE_DISABLE_ID);
|
|
415
455
|
this._alwaysEnabled.add(commandName);
|
|
416
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
|
+
}
|
|
417
467
|
/**
|
|
418
468
|
* Sets up the restricted mode editing conversion:
|
|
419
469
|
*
|
|
@@ -434,16 +484,35 @@ const COMMAND_FORCE_DISABLE_ID = 'RestrictedEditingMode';
|
|
|
434
484
|
},
|
|
435
485
|
model: ()=>{
|
|
436
486
|
markerNumber++; // Starting from restrictedEditingException:1 marker.
|
|
437
|
-
return `restrictedEditingException:${markerNumber}`;
|
|
487
|
+
return `restrictedEditingException:inline:${markerNumber}`;
|
|
438
488
|
}
|
|
439
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
|
+
});
|
|
440
509
|
// Currently the marker helpers are tied to other use-cases and do not render a collapsed marker as highlight.
|
|
441
510
|
// Also, markerToHighlight cannot convert marker on an inline object. It handles only text and widgets,
|
|
442
511
|
// but it is not a case in the data pipeline. That's why there are 3 downcast converters for them:
|
|
443
512
|
//
|
|
444
513
|
// 1. The custom inline item (text or inline object) converter (but not the selection).
|
|
445
514
|
editor.conversion.for('downcast').add((dispatcher)=>{
|
|
446
|
-
dispatcher.on('addMarker:restrictedEditingException', (evt, data, conversionApi)=>{
|
|
515
|
+
dispatcher.on('addMarker:restrictedEditingException:inline', (evt, data, conversionApi)=>{
|
|
447
516
|
// Only convert per-item conversion.
|
|
448
517
|
if (!data.item) {
|
|
449
518
|
return;
|
|
@@ -474,7 +543,7 @@ const COMMAND_FORCE_DISABLE_ID = 'RestrictedEditingMode';
|
|
|
474
543
|
});
|
|
475
544
|
// 2. The marker-to-highlight converter for the document selection.
|
|
476
545
|
editor.conversion.for('downcast').markerToHighlight({
|
|
477
|
-
model: 'restrictedEditingException',
|
|
546
|
+
model: 'restrictedEditingException:inline',
|
|
478
547
|
// Use callback to return new object every time new marker instance is created - otherwise it will be seen as the same marker.
|
|
479
548
|
view: ()=>{
|
|
480
549
|
return {
|
|
@@ -487,7 +556,7 @@ const COMMAND_FORCE_DISABLE_ID = 'RestrictedEditingMode';
|
|
|
487
556
|
// 3. And for collapsed marker we need to render it as an element.
|
|
488
557
|
// Additionally, the editing pipeline should always display a collapsed marker.
|
|
489
558
|
editor.conversion.for('editingDowncast').markerToElement({
|
|
490
|
-
model: 'restrictedEditingException',
|
|
559
|
+
model: 'restrictedEditingException:inline',
|
|
491
560
|
view: (markerData, { writer })=>{
|
|
492
561
|
return writer.createUIElement('span', {
|
|
493
562
|
class: 'restricted-editing-exception restricted-editing-exception_collapsed'
|
|
@@ -495,7 +564,7 @@ const COMMAND_FORCE_DISABLE_ID = 'RestrictedEditingMode';
|
|
|
495
564
|
}
|
|
496
565
|
});
|
|
497
566
|
editor.conversion.for('dataDowncast').markerToElement({
|
|
498
|
-
model: 'restrictedEditingException',
|
|
567
|
+
model: 'restrictedEditingException:inline',
|
|
499
568
|
view: (markerData, { writer })=>{
|
|
500
569
|
return writer.createEmptyElement('span', {
|
|
501
570
|
class: 'restricted-editing-exception'
|
|
@@ -532,10 +601,28 @@ const COMMAND_FORCE_DISABLE_ID = 'RestrictedEditingMode';
|
|
|
532
601
|
});
|
|
533
602
|
}
|
|
534
603
|
// Block clipboard outside exception marker on paste and drop.
|
|
535
|
-
this.listenTo(clipboard, 'contentInsertion', (evt)=>{
|
|
604
|
+
this.listenTo(clipboard, 'contentInsertion', (evt, data)=>{
|
|
536
605
|
if (!isRangeInsideSingleMarker(editor, selection.getFirstRange())) {
|
|
537
606
|
evt.stop();
|
|
538
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
|
+
}
|
|
539
626
|
});
|
|
540
627
|
// Block clipboard outside exception marker on cut.
|
|
541
628
|
this.listenTo(viewDoc, 'clipboardOutput', (evt, data)=>{
|
|
@@ -545,9 +632,12 @@ const COMMAND_FORCE_DISABLE_ID = 'RestrictedEditingMode';
|
|
|
545
632
|
}, {
|
|
546
633
|
priority: 'high'
|
|
547
634
|
});
|
|
548
|
-
|
|
549
|
-
model.schema.
|
|
550
|
-
|
|
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');
|
|
551
641
|
}
|
|
552
642
|
/**
|
|
553
643
|
* Sets up the command toggling which enables or disables commands based on the user selection.
|
|
@@ -570,7 +660,7 @@ const COMMAND_FORCE_DISABLE_ID = 'RestrictedEditingMode';
|
|
|
570
660
|
}
|
|
571
661
|
const marker = getMarkerAtPosition(editor, selection.focus);
|
|
572
662
|
this._disableCommands();
|
|
573
|
-
if (isSelectionInMarker(selection, marker)) {
|
|
663
|
+
if (isSelectionInMarker(selection, editor.model, marker)) {
|
|
574
664
|
this._enableCommands(marker);
|
|
575
665
|
}
|
|
576
666
|
}
|
|
@@ -578,16 +668,18 @@ const COMMAND_FORCE_DISABLE_ID = 'RestrictedEditingMode';
|
|
|
578
668
|
* Enables commands in non-restricted regions.
|
|
579
669
|
*/ _enableCommands(marker) {
|
|
580
670
|
const editor = this.editor;
|
|
671
|
+
const selection = editor.model.document.selection;
|
|
581
672
|
for (const [commandName, command] of editor.commands){
|
|
582
673
|
if (!command.affectsData || this._alwaysEnabled.has(commandName)) {
|
|
583
674
|
continue;
|
|
584
675
|
}
|
|
585
676
|
// Enable ony those commands that are allowed in the exception marker.
|
|
586
|
-
|
|
677
|
+
// In block exceptions all commands are enabled.
|
|
678
|
+
if (!marker.name.startsWith('restrictedEditingException:block:') && !this._allowedInException.has(commandName)) {
|
|
587
679
|
continue;
|
|
588
680
|
}
|
|
589
681
|
// Do not enable 'delete' and 'deleteForward' commands on the exception marker boundaries.
|
|
590
|
-
if (isDeleteCommandOnMarkerBoundaries(commandName,
|
|
682
|
+
if (isDeleteCommandOnMarkerBoundaries(commandName, selection, getExceptionRange(marker, editor.model))) {
|
|
591
683
|
continue;
|
|
592
684
|
}
|
|
593
685
|
command.clearForceDisabled(COMMAND_FORCE_DISABLE_ID);
|
|
@@ -608,7 +700,10 @@ const COMMAND_FORCE_DISABLE_ID = 'RestrictedEditingMode';
|
|
|
608
700
|
/**
|
|
609
701
|
* Helper for handling Ctrl+A keydown behaviour.
|
|
610
702
|
*/ function getSelectAllHandler(editor) {
|
|
611
|
-
return (
|
|
703
|
+
return (eventInfo, domEventData)=>{
|
|
704
|
+
if (getCode(domEventData) != parseKeystroke('Ctrl+A')) {
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
612
707
|
const model = editor.model;
|
|
613
708
|
const selection = editor.model.document.selection;
|
|
614
709
|
const marker = getMarkerAtPosition(editor, selection.focus);
|
|
@@ -619,11 +714,13 @@ const COMMAND_FORCE_DISABLE_ID = 'RestrictedEditingMode';
|
|
|
619
714
|
//
|
|
620
715
|
// Note: Second Ctrl+A press is also blocked and it won't select the entire text in the editor.
|
|
621
716
|
const selectionRange = selection.getFirstRange();
|
|
622
|
-
const markerRange = marker.
|
|
717
|
+
const markerRange = getExceptionRange(marker, editor.model);
|
|
623
718
|
if (markerRange.containsRange(selectionRange, true) || selection.isCollapsed) {
|
|
624
|
-
|
|
719
|
+
eventInfo.stop();
|
|
720
|
+
domEventData.preventDefault();
|
|
721
|
+
domEventData.stopPropagation();
|
|
625
722
|
model.change((writer)=>{
|
|
626
|
-
writer.setSelection(
|
|
723
|
+
writer.setSelection(markerRange);
|
|
627
724
|
});
|
|
628
725
|
}
|
|
629
726
|
};
|
|
@@ -635,11 +732,11 @@ const COMMAND_FORCE_DISABLE_ID = 'RestrictedEditingMode';
|
|
|
635
732
|
* - is on marker start - "delete" - to prevent removing content before marker
|
|
636
733
|
* - is on marker end - "deleteForward" - to prevent removing content after marker
|
|
637
734
|
*/ function isDeleteCommandOnMarkerBoundaries(commandName, selection, markerRange) {
|
|
638
|
-
if (commandName == 'delete' && markerRange.start.
|
|
735
|
+
if (commandName == 'delete' && selection.isCollapsed && markerRange.start.isTouching(selection.focus)) {
|
|
639
736
|
return true;
|
|
640
737
|
}
|
|
641
738
|
// Only for collapsed selection - non-collapsed selection that extends over a marker is handled elsewhere.
|
|
642
|
-
if (commandName == 'deleteForward' && selection.isCollapsed && markerRange.end.
|
|
739
|
+
if (commandName == 'deleteForward' && selection.isCollapsed && markerRange.end.isTouching(selection.focus)) {
|
|
643
740
|
return true;
|
|
644
741
|
}
|
|
645
742
|
return false;
|
|
@@ -664,7 +761,7 @@ const COMMAND_FORCE_DISABLE_ID = 'RestrictedEditingMode';
|
|
|
664
761
|
return;
|
|
665
762
|
}
|
|
666
763
|
// Shrink the selection to the range inside exception marker.
|
|
667
|
-
const allowedToDelete = marker.
|
|
764
|
+
const allowedToDelete = getExceptionRange(marker, editor.model).getIntersection(selection.getFirstRange());
|
|
668
765
|
// Some features uses selection passed to model.deleteContent() to set the selection afterwards. For this we need to properly modify
|
|
669
766
|
// either the document selection using change block...
|
|
670
767
|
if (selection.is('documentSelection')) {
|
|
@@ -730,20 +827,6 @@ function isRangeInsideSingleMarker(editor, range) {
|
|
|
730
827
|
return changeApplied;
|
|
731
828
|
};
|
|
732
829
|
}
|
|
733
|
-
function onlyAllowAttributesFromList(allowedAttributes) {
|
|
734
|
-
return (context, attributeName)=>{
|
|
735
|
-
if (context.startsWith('$clipboardHolder')) {
|
|
736
|
-
return allowedAttributes.includes(attributeName);
|
|
737
|
-
}
|
|
738
|
-
};
|
|
739
|
-
}
|
|
740
|
-
function allowTextOnlyInClipboardHolder() {
|
|
741
|
-
return (context, childDefinition)=>{
|
|
742
|
-
if (context.startsWith('$clipboardHolder')) {
|
|
743
|
-
return childDefinition.name === '$text';
|
|
744
|
-
}
|
|
745
|
-
};
|
|
746
|
-
}
|
|
747
830
|
|
|
748
831
|
/**
|
|
749
832
|
* The restricted editing mode UI feature.
|
|
@@ -953,6 +1036,193 @@ function allowTextOnlyInClipboardHolder() {
|
|
|
953
1036
|
}
|
|
954
1037
|
}
|
|
955
1038
|
|
|
1039
|
+
/**
|
|
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
|
+
}
|
|
1172
|
+
function findException(elementOrPosition) {
|
|
1173
|
+
return elementOrPosition.findAncestor('restrictedEditingException', {
|
|
1174
|
+
includeSelf: true
|
|
1175
|
+
});
|
|
1176
|
+
}
|
|
1177
|
+
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;
|
|
1186
|
+
}
|
|
1187
|
+
/**
|
|
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;
|
|
1210
|
+
}
|
|
1211
|
+
/**
|
|
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;
|
|
1224
|
+
}
|
|
1225
|
+
|
|
956
1226
|
/**
|
|
957
1227
|
* The standard editing mode editing feature.
|
|
958
1228
|
*
|
|
@@ -974,18 +1244,107 @@ function allowTextOnlyInClipboardHolder() {
|
|
|
974
1244
|
* @inheritDoc
|
|
975
1245
|
*/ init() {
|
|
976
1246
|
const editor = this.editor;
|
|
977
|
-
editor.model.schema
|
|
1247
|
+
const schema = editor.model.schema;
|
|
1248
|
+
schema.extend('$text', {
|
|
978
1249
|
allowAttributes: [
|
|
979
1250
|
'restrictedEditingException'
|
|
980
1251
|
]
|
|
981
1252
|
});
|
|
1253
|
+
schema.register('restrictedEditingException', {
|
|
1254
|
+
allowWhere: '$container',
|
|
1255
|
+
allowContentOf: '$container'
|
|
1256
|
+
});
|
|
1257
|
+
// Don't allow nesting of block exceptions.
|
|
1258
|
+
schema.addChildCheck((context)=>{
|
|
1259
|
+
for (const item of context){
|
|
1260
|
+
if (item.name == 'restrictedEditingException') {
|
|
1261
|
+
return false;
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
}, 'restrictedEditingException');
|
|
1265
|
+
// Don't allow nesting inline exceptions inside block exceptions.
|
|
1266
|
+
schema.addAttributeCheck((context)=>{
|
|
1267
|
+
for (const item of context){
|
|
1268
|
+
if (item.name == 'restrictedEditingException') {
|
|
1269
|
+
return false;
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
}, 'restrictedEditingException');
|
|
1273
|
+
// Post-fixer to ensure proper structure.
|
|
1274
|
+
editor.model.document.registerPostFixer((writer)=>{
|
|
1275
|
+
const changes = editor.model.document.differ.getChanges();
|
|
1276
|
+
const unwrap = new Set();
|
|
1277
|
+
const remove = new Set();
|
|
1278
|
+
const merge = new Set();
|
|
1279
|
+
let changed = false;
|
|
1280
|
+
for (const entry of changes){
|
|
1281
|
+
if (entry.type == 'insert') {
|
|
1282
|
+
const range = writer.createRange(entry.position, entry.position.getShiftedBy(entry.length));
|
|
1283
|
+
for (const child of range.getItems()){
|
|
1284
|
+
if (child.is('element', 'restrictedEditingException')) {
|
|
1285
|
+
// Make sure that block exception is not nested or added in invalid place.
|
|
1286
|
+
if (!schema.checkChild(writer.createPositionBefore(child), child)) {
|
|
1287
|
+
unwrap.add(child);
|
|
1288
|
+
} else if (child.isEmpty) {
|
|
1289
|
+
remove.add(child);
|
|
1290
|
+
} else {
|
|
1291
|
+
merge.add(child);
|
|
1292
|
+
}
|
|
1293
|
+
} else if (child.is('$textProxy') && child.hasAttribute('restrictedEditingException') && !schema.checkAttribute(child, 'restrictedEditingException')) {
|
|
1294
|
+
writer.removeAttribute('restrictedEditingException', child);
|
|
1295
|
+
changed = true;
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
} else if (entry.type == 'remove') {
|
|
1299
|
+
const parent = entry.position.parent;
|
|
1300
|
+
if (parent.is('element', 'restrictedEditingException') && parent.isEmpty) {
|
|
1301
|
+
remove.add(parent);
|
|
1302
|
+
}
|
|
1303
|
+
// Verify if some block exceptions are siblings now after element removed between.
|
|
1304
|
+
for (const child of parent.getChildren()){
|
|
1305
|
+
if (child.is('element', 'restrictedEditingException')) {
|
|
1306
|
+
merge.add(child);
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
for (const child of unwrap){
|
|
1312
|
+
writer.unwrap(child);
|
|
1313
|
+
changed = true;
|
|
1314
|
+
}
|
|
1315
|
+
for (const child of remove){
|
|
1316
|
+
writer.remove(child);
|
|
1317
|
+
changed = true;
|
|
1318
|
+
}
|
|
1319
|
+
for (const child of merge){
|
|
1320
|
+
if (child.root.rootName == '$graveyard') {
|
|
1321
|
+
continue;
|
|
1322
|
+
}
|
|
1323
|
+
const nodeBefore = child.previousSibling;
|
|
1324
|
+
const nodeAfter = child.nextSibling;
|
|
1325
|
+
if (nodeBefore && nodeBefore.is('element', 'restrictedEditingException')) {
|
|
1326
|
+
writer.merge(writer.createPositionBefore(child));
|
|
1327
|
+
}
|
|
1328
|
+
if (nodeAfter && nodeAfter.is('element', 'restrictedEditingException')) {
|
|
1329
|
+
writer.merge(writer.createPositionAfter(child));
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
return changed;
|
|
1333
|
+
});
|
|
982
1334
|
editor.conversion.for('upcast').elementToAttribute({
|
|
983
1335
|
model: 'restrictedEditingException',
|
|
984
1336
|
view: {
|
|
985
1337
|
name: 'span',
|
|
986
1338
|
classes: 'restricted-editing-exception'
|
|
987
1339
|
}
|
|
1340
|
+
}).elementToElement({
|
|
1341
|
+
model: 'restrictedEditingException',
|
|
1342
|
+
view: {
|
|
1343
|
+
name: 'div',
|
|
1344
|
+
classes: 'restricted-editing-exception'
|
|
1345
|
+
}
|
|
988
1346
|
});
|
|
1347
|
+
registerFallbackUpcastConverter(editor);
|
|
989
1348
|
editor.conversion.for('downcast').attributeToElement({
|
|
990
1349
|
model: 'restrictedEditingException',
|
|
991
1350
|
view: (modelAttributeValue, { writer })=>{
|
|
@@ -998,8 +1357,15 @@ function allowTextOnlyInClipboardHolder() {
|
|
|
998
1357
|
});
|
|
999
1358
|
}
|
|
1000
1359
|
}
|
|
1360
|
+
}).elementToElement({
|
|
1361
|
+
model: 'restrictedEditingException',
|
|
1362
|
+
view: {
|
|
1363
|
+
name: 'div',
|
|
1364
|
+
classes: 'restricted-editing-exception'
|
|
1365
|
+
}
|
|
1001
1366
|
});
|
|
1002
1367
|
editor.commands.add('restrictedEditingException', new RestrictedEditingExceptionCommand(editor));
|
|
1368
|
+
editor.commands.add('restrictedEditingExceptionBlock', new RestrictedEditingExceptionBlockCommand(editor));
|
|
1003
1369
|
editor.editing.view.change((writer)=>{
|
|
1004
1370
|
for (const root of editor.editing.view.document.roots){
|
|
1005
1371
|
writer.addClass('ck-restricted-editing_mode_standard', root);
|
|
@@ -1007,6 +1373,36 @@ function allowTextOnlyInClipboardHolder() {
|
|
|
1007
1373
|
});
|
|
1008
1374
|
}
|
|
1009
1375
|
}
|
|
1376
|
+
/**
|
|
1377
|
+
* Fallback upcast converter for empty exception span inside a table cell.
|
|
1378
|
+
*/ function registerFallbackUpcastConverter(editor) {
|
|
1379
|
+
const matcher = new Matcher({
|
|
1380
|
+
name: 'span',
|
|
1381
|
+
classes: 'restricted-editing-exception'
|
|
1382
|
+
});
|
|
1383
|
+
// See: https://github.com/ckeditor/ckeditor5/issues/16376.
|
|
1384
|
+
editor.conversion.for('upcast').add((dispatcher)=>dispatcher.on('element:span', (evt, data, conversionApi)=>{
|
|
1385
|
+
const matcherResult = matcher.match(data.viewItem);
|
|
1386
|
+
if (!matcherResult) {
|
|
1387
|
+
return;
|
|
1388
|
+
}
|
|
1389
|
+
const match = matcherResult.match;
|
|
1390
|
+
if (!conversionApi.consumable.test(data.viewItem, match)) {
|
|
1391
|
+
return;
|
|
1392
|
+
}
|
|
1393
|
+
const modelText = conversionApi.writer.createText(' ', {
|
|
1394
|
+
restrictedEditingException: true
|
|
1395
|
+
});
|
|
1396
|
+
if (!conversionApi.safeInsert(modelText, data.modelCursor)) {
|
|
1397
|
+
return;
|
|
1398
|
+
}
|
|
1399
|
+
conversionApi.consumable.consume(data.viewItem, match);
|
|
1400
|
+
data.modelRange = conversionApi.writer.createRange(data.modelCursor, data.modelCursor.getShiftedBy(modelText.offsetSize));
|
|
1401
|
+
data.modelCursor = data.modelRange.end;
|
|
1402
|
+
}, {
|
|
1403
|
+
priority: 'low'
|
|
1404
|
+
}));
|
|
1405
|
+
}
|
|
1010
1406
|
|
|
1011
1407
|
/**
|
|
1012
1408
|
* The standard editing mode UI feature.
|
|
@@ -1027,34 +1423,97 @@ function allowTextOnlyInClipboardHolder() {
|
|
|
1027
1423
|
* @inheritDoc
|
|
1028
1424
|
*/ init() {
|
|
1029
1425
|
const editor = this.editor;
|
|
1030
|
-
editor.ui.componentFactory
|
|
1031
|
-
|
|
1426
|
+
const componentFactory = editor.ui.componentFactory;
|
|
1427
|
+
componentFactory.add('restrictedEditingException:dropdown', (locale)=>{
|
|
1428
|
+
const dropdownView = createDropdown(locale);
|
|
1429
|
+
const t = locale.t;
|
|
1430
|
+
const buttons = [
|
|
1431
|
+
componentFactory.create('restrictedEditingException:inline'),
|
|
1432
|
+
componentFactory.create('restrictedEditingException:block')
|
|
1433
|
+
];
|
|
1434
|
+
for (const button of buttons){
|
|
1435
|
+
button.set({
|
|
1436
|
+
withText: true,
|
|
1437
|
+
tooltip: false
|
|
1438
|
+
});
|
|
1439
|
+
}
|
|
1440
|
+
addToolbarToDropdown(dropdownView, buttons, {
|
|
1441
|
+
enableActiveItemFocusOnDropdownOpen: true,
|
|
1442
|
+
isVertical: true,
|
|
1443
|
+
ariaLabel: t('Enable editing')
|
|
1444
|
+
});
|
|
1445
|
+
dropdownView.buttonView.set({
|
|
1446
|
+
label: t('Enable editing'),
|
|
1447
|
+
icon: IconContentUnlock,
|
|
1448
|
+
tooltip: true
|
|
1449
|
+
});
|
|
1450
|
+
dropdownView.extendTemplate({
|
|
1451
|
+
attributes: {
|
|
1452
|
+
class: 'ck-restricted-editing-dropdown'
|
|
1453
|
+
}
|
|
1454
|
+
});
|
|
1455
|
+
// Enable button if any of the buttons is enabled.
|
|
1456
|
+
dropdownView.bind('isEnabled').toMany(buttons, 'isEnabled', (...areEnabled)=>{
|
|
1457
|
+
return areEnabled.some((isEnabled)=>isEnabled);
|
|
1458
|
+
});
|
|
1459
|
+
// Focus the editable after executing the command.
|
|
1460
|
+
this.listenTo(dropdownView, 'execute', ()=>{
|
|
1461
|
+
editor.editing.view.focus();
|
|
1462
|
+
});
|
|
1463
|
+
return dropdownView;
|
|
1464
|
+
});
|
|
1465
|
+
componentFactory.add('restrictedEditingException:inline', ()=>{
|
|
1466
|
+
const button = this._createButton('restrictedEditingException', ButtonView);
|
|
1032
1467
|
button.set({
|
|
1033
1468
|
tooltip: true,
|
|
1034
1469
|
isToggleable: true
|
|
1035
1470
|
});
|
|
1036
1471
|
return button;
|
|
1037
1472
|
});
|
|
1038
|
-
|
|
1039
|
-
|
|
1473
|
+
componentFactory.add('restrictedEditingException:block', ()=>{
|
|
1474
|
+
const button = this._createButton('restrictedEditingExceptionBlock', ButtonView);
|
|
1475
|
+
button.set({
|
|
1476
|
+
tooltip: true,
|
|
1477
|
+
isToggleable: true
|
|
1478
|
+
});
|
|
1479
|
+
return button;
|
|
1480
|
+
});
|
|
1481
|
+
componentFactory.add('menuBar:restrictedEditingException:inline', ()=>{
|
|
1482
|
+
return this._createButton('restrictedEditingException', MenuBarMenuListItemButtonView);
|
|
1483
|
+
});
|
|
1484
|
+
componentFactory.add('menuBar:restrictedEditingException:block', ()=>{
|
|
1485
|
+
return this._createButton('restrictedEditingExceptionBlock', MenuBarMenuListItemButtonView);
|
|
1486
|
+
});
|
|
1487
|
+
// Aliases for backward compatibility.
|
|
1488
|
+
componentFactory.add('restrictedEditingException', ()=>{
|
|
1489
|
+
return componentFactory.create('restrictedEditingException:inline');
|
|
1490
|
+
});
|
|
1491
|
+
componentFactory.add('menuBar:restrictedEditingException', ()=>{
|
|
1492
|
+
return componentFactory.create('menuBar:restrictedEditingException:inline');
|
|
1040
1493
|
});
|
|
1041
1494
|
}
|
|
1042
1495
|
/**
|
|
1043
1496
|
* Creates a button for restricted editing exception command to use either in toolbar or in menu bar.
|
|
1044
|
-
*/ _createButton(ButtonClass) {
|
|
1497
|
+
*/ _createButton(commandName, ButtonClass) {
|
|
1045
1498
|
const editor = this.editor;
|
|
1046
1499
|
const locale = editor.locale;
|
|
1047
|
-
const command = this.editor.commands.get(
|
|
1500
|
+
const command = this.editor.commands.get(commandName);
|
|
1048
1501
|
const view = new ButtonClass(locale);
|
|
1049
1502
|
const t = locale.t;
|
|
1050
1503
|
view.icon = IconContentUnlock;
|
|
1051
1504
|
view.bind('isOn', 'isEnabled').to(command, 'value', 'isEnabled');
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1505
|
+
if (commandName == 'restrictedEditingExceptionBlock') {
|
|
1506
|
+
view.bind('label').to(command, 'value', (value)=>{
|
|
1507
|
+
return value ? t('Disable block editing') : t('Enable block editing');
|
|
1508
|
+
});
|
|
1509
|
+
} else {
|
|
1510
|
+
view.bind('label').to(command, 'value', (value)=>{
|
|
1511
|
+
return value ? t('Disable inline editing') : t('Enable inline editing');
|
|
1512
|
+
});
|
|
1513
|
+
}
|
|
1055
1514
|
// Execute the command.
|
|
1056
1515
|
this.listenTo(view, 'execute', ()=>{
|
|
1057
|
-
editor.execute(
|
|
1516
|
+
editor.execute(commandName);
|
|
1058
1517
|
editor.editing.view.focus();
|
|
1059
1518
|
});
|
|
1060
1519
|
return view;
|
|
@@ -1087,5 +1546,5 @@ function allowTextOnlyInClipboardHolder() {
|
|
|
1087
1546
|
}
|
|
1088
1547
|
}
|
|
1089
1548
|
|
|
1090
|
-
export { 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 };
|
|
1549
|
+
export { 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 };
|
|
1091
1550
|
//# sourceMappingURL=index.js.map
|