@ckeditor/ckeditor5-restricted-editing 47.1.0-alpha.2 → 47.2.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
* @module restricted-editing/restrictededitingmodeediting
|
|
7
7
|
*/
|
|
8
8
|
import { Plugin } from 'ckeditor5/src/core.js';
|
|
9
|
+
import { getCode, parseKeystroke } from 'ckeditor5/src/utils.js';
|
|
9
10
|
import { RestrictedEditingModeNavigationCommand } from './restrictededitingmodenavigationcommand.js';
|
|
11
|
+
import { getMarkerAtPosition, isSelectionInMarker, getExceptionRange } from './restrictededitingmode/utils.js';
|
|
10
12
|
import { extendMarkerOnTypingPostFixer, resurrectCollapsedMarkerPostFixer, setupExceptionHighlighting, upcastHighlightToMarker } from './restrictededitingmode/converters.js';
|
|
11
|
-
import { getMarkerAtPosition, isSelectionInMarker } from './restrictededitingmode/utils.js';
|
|
12
13
|
const COMMAND_FORCE_DISABLE_ID = 'RestrictedEditingMode';
|
|
13
14
|
/**
|
|
14
15
|
* The restricted editing mode editing feature.
|
|
@@ -74,6 +75,7 @@ export class RestrictedEditingModeEditing extends Plugin {
|
|
|
74
75
|
const editingView = editor.editing.view;
|
|
75
76
|
const allowedCommands = editor.config.get('restrictedEditing.allowedCommands');
|
|
76
77
|
allowedCommands.forEach(commandName => this._allowedInException.add(commandName));
|
|
78
|
+
this._setupSchema();
|
|
77
79
|
this._setupConversion();
|
|
78
80
|
this._setupCommandsToggling();
|
|
79
81
|
this._setupRestrictions();
|
|
@@ -92,7 +94,7 @@ export class RestrictedEditingModeEditing extends Plugin {
|
|
|
92
94
|
// Stop the event bubbling in the editor: no more callbacks will be executed for this keystroke.
|
|
93
95
|
evt.stop();
|
|
94
96
|
}, { context: '$capture' });
|
|
95
|
-
|
|
97
|
+
this.listenTo(editingView.document, 'keydown', getSelectAllHandler(editor), { priority: 'high' });
|
|
96
98
|
editingView.change(writer => {
|
|
97
99
|
for (const root of editingView.document.roots) {
|
|
98
100
|
writer.addClass('ck-restricted-editing_mode_restricted', root);
|
|
@@ -123,6 +125,17 @@ export class RestrictedEditingModeEditing extends Plugin {
|
|
|
123
125
|
command.clearForceDisabled(COMMAND_FORCE_DISABLE_ID);
|
|
124
126
|
this._alwaysEnabled.add(commandName);
|
|
125
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* Registers block exception wrapper in the schema.
|
|
130
|
+
*/
|
|
131
|
+
_setupSchema() {
|
|
132
|
+
const schema = this.editor.model.schema;
|
|
133
|
+
schema.register('restrictedEditingException', {
|
|
134
|
+
allowWhere: '$block',
|
|
135
|
+
allowContentOf: '$container',
|
|
136
|
+
isLimit: true
|
|
137
|
+
});
|
|
138
|
+
}
|
|
126
139
|
/**
|
|
127
140
|
* Sets up the restricted mode editing conversion:
|
|
128
141
|
*
|
|
@@ -144,16 +157,35 @@ export class RestrictedEditingModeEditing extends Plugin {
|
|
|
144
157
|
},
|
|
145
158
|
model: () => {
|
|
146
159
|
markerNumber++; // Starting from restrictedEditingException:1 marker.
|
|
147
|
-
return `restrictedEditingException:${markerNumber}`;
|
|
160
|
+
return `restrictedEditingException:inline:${markerNumber}`;
|
|
148
161
|
}
|
|
149
162
|
}));
|
|
163
|
+
editor.conversion.for('upcast').add(upcastHighlightToMarker({
|
|
164
|
+
view: {
|
|
165
|
+
name: 'div',
|
|
166
|
+
classes: 'restricted-editing-exception'
|
|
167
|
+
},
|
|
168
|
+
model: () => {
|
|
169
|
+
markerNumber++; // Starting from restrictedEditingException:1 marker.
|
|
170
|
+
return `restrictedEditingException:block:${markerNumber}`;
|
|
171
|
+
},
|
|
172
|
+
useWrapperElement: true
|
|
173
|
+
}));
|
|
174
|
+
// Block exception wrapper.
|
|
175
|
+
editor.conversion.for('downcast').elementToElement({
|
|
176
|
+
model: 'restrictedEditingException',
|
|
177
|
+
view: {
|
|
178
|
+
name: 'div',
|
|
179
|
+
classes: 'restricted-editing-exception'
|
|
180
|
+
}
|
|
181
|
+
});
|
|
150
182
|
// Currently the marker helpers are tied to other use-cases and do not render a collapsed marker as highlight.
|
|
151
183
|
// Also, markerToHighlight cannot convert marker on an inline object. It handles only text and widgets,
|
|
152
184
|
// but it is not a case in the data pipeline. That's why there are 3 downcast converters for them:
|
|
153
185
|
//
|
|
154
186
|
// 1. The custom inline item (text or inline object) converter (but not the selection).
|
|
155
187
|
editor.conversion.for('downcast').add(dispatcher => {
|
|
156
|
-
dispatcher.on('addMarker:restrictedEditingException', (evt, data, conversionApi) => {
|
|
188
|
+
dispatcher.on('addMarker:restrictedEditingException:inline', (evt, data, conversionApi) => {
|
|
157
189
|
// Only convert per-item conversion.
|
|
158
190
|
if (!data.item) {
|
|
159
191
|
return;
|
|
@@ -186,7 +218,7 @@ export class RestrictedEditingModeEditing extends Plugin {
|
|
|
186
218
|
});
|
|
187
219
|
// 2. The marker-to-highlight converter for the document selection.
|
|
188
220
|
editor.conversion.for('downcast').markerToHighlight({
|
|
189
|
-
model: 'restrictedEditingException',
|
|
221
|
+
model: 'restrictedEditingException:inline',
|
|
190
222
|
// Use callback to return new object every time new marker instance is created - otherwise it will be seen as the same marker.
|
|
191
223
|
view: () => {
|
|
192
224
|
return {
|
|
@@ -199,7 +231,7 @@ export class RestrictedEditingModeEditing extends Plugin {
|
|
|
199
231
|
// 3. And for collapsed marker we need to render it as an element.
|
|
200
232
|
// Additionally, the editing pipeline should always display a collapsed marker.
|
|
201
233
|
editor.conversion.for('editingDowncast').markerToElement({
|
|
202
|
-
model: 'restrictedEditingException',
|
|
234
|
+
model: 'restrictedEditingException:inline',
|
|
203
235
|
view: (markerData, { writer }) => {
|
|
204
236
|
return writer.createUIElement('span', {
|
|
205
237
|
class: 'restricted-editing-exception restricted-editing-exception_collapsed'
|
|
@@ -207,7 +239,7 @@ export class RestrictedEditingModeEditing extends Plugin {
|
|
|
207
239
|
}
|
|
208
240
|
});
|
|
209
241
|
editor.conversion.for('dataDowncast').markerToElement({
|
|
210
|
-
model: 'restrictedEditingException',
|
|
242
|
+
model: 'restrictedEditingException:inline',
|
|
211
243
|
view: (markerData, { writer }) => {
|
|
212
244
|
return writer.createEmptyElement('span', {
|
|
213
245
|
class: 'restricted-editing-exception'
|
|
@@ -241,10 +273,29 @@ export class RestrictedEditingModeEditing extends Plugin {
|
|
|
241
273
|
this.listenTo(insertTextCommand, 'execute', disallowInputExecForWrongRange(editor), { priority: 'high' });
|
|
242
274
|
}
|
|
243
275
|
// Block clipboard outside exception marker on paste and drop.
|
|
244
|
-
this.listenTo(clipboard, 'contentInsertion', evt => {
|
|
276
|
+
this.listenTo(clipboard, 'contentInsertion', (evt, data) => {
|
|
245
277
|
if (!isRangeInsideSingleMarker(editor, selection.getFirstRange())) {
|
|
246
278
|
evt.stop();
|
|
247
279
|
}
|
|
280
|
+
const marker = getMarkerAtPosition(editor, selection.focus);
|
|
281
|
+
// Reduce content pasted into inline exception to text nodes only. Also strip not allowed attributes.
|
|
282
|
+
if (marker && marker.name.startsWith('restrictedEditingException:inline:')) {
|
|
283
|
+
const allowedAttributes = editor.config.get('restrictedEditing.allowedAttributes');
|
|
284
|
+
model.change(writer => {
|
|
285
|
+
const content = writer.createDocumentFragment();
|
|
286
|
+
const textNodes = Array.from(writer.createRangeIn(data.content).getItems())
|
|
287
|
+
.filter(node => node.is('$textProxy'));
|
|
288
|
+
for (const item of textNodes) {
|
|
289
|
+
for (const attr of item.getAttributeKeys()) {
|
|
290
|
+
if (!allowedAttributes.includes(attr)) {
|
|
291
|
+
writer.removeAttribute(attr, item);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
writer.append(item, content);
|
|
295
|
+
}
|
|
296
|
+
data.content = content;
|
|
297
|
+
});
|
|
298
|
+
}
|
|
248
299
|
});
|
|
249
300
|
// Block clipboard outside exception marker on cut.
|
|
250
301
|
this.listenTo(viewDoc, 'clipboardOutput', (evt, data) => {
|
|
@@ -252,9 +303,12 @@ export class RestrictedEditingModeEditing extends Plugin {
|
|
|
252
303
|
evt.stop();
|
|
253
304
|
}
|
|
254
305
|
}, { priority: 'high' });
|
|
255
|
-
|
|
256
|
-
model.schema.
|
|
257
|
-
|
|
306
|
+
// Do not allow pasting/dropping block exception wrapper.
|
|
307
|
+
model.schema.addChildCheck(context => {
|
|
308
|
+
if (context.startsWith('$clipboardHolder')) {
|
|
309
|
+
return false;
|
|
310
|
+
}
|
|
311
|
+
}, 'restrictedEditingException');
|
|
258
312
|
}
|
|
259
313
|
/**
|
|
260
314
|
* Sets up the command toggling which enables or disables commands based on the user selection.
|
|
@@ -279,7 +333,7 @@ export class RestrictedEditingModeEditing extends Plugin {
|
|
|
279
333
|
}
|
|
280
334
|
const marker = getMarkerAtPosition(editor, selection.focus);
|
|
281
335
|
this._disableCommands();
|
|
282
|
-
if (isSelectionInMarker(selection, marker)) {
|
|
336
|
+
if (isSelectionInMarker(selection, editor.model, marker)) {
|
|
283
337
|
this._enableCommands(marker);
|
|
284
338
|
}
|
|
285
339
|
}
|
|
@@ -288,16 +342,19 @@ export class RestrictedEditingModeEditing extends Plugin {
|
|
|
288
342
|
*/
|
|
289
343
|
_enableCommands(marker) {
|
|
290
344
|
const editor = this.editor;
|
|
345
|
+
const selection = editor.model.document.selection;
|
|
291
346
|
for (const [commandName, command] of editor.commands) {
|
|
292
347
|
if (!command.affectsData || this._alwaysEnabled.has(commandName)) {
|
|
293
348
|
continue;
|
|
294
349
|
}
|
|
295
350
|
// Enable ony those commands that are allowed in the exception marker.
|
|
296
|
-
|
|
351
|
+
// In block exceptions all commands are enabled.
|
|
352
|
+
if (!marker.name.startsWith('restrictedEditingException:block:') &&
|
|
353
|
+
!this._allowedInException.has(commandName)) {
|
|
297
354
|
continue;
|
|
298
355
|
}
|
|
299
356
|
// Do not enable 'delete' and 'deleteForward' commands on the exception marker boundaries.
|
|
300
|
-
if (isDeleteCommandOnMarkerBoundaries(commandName,
|
|
357
|
+
if (isDeleteCommandOnMarkerBoundaries(commandName, selection, getExceptionRange(marker, editor.model))) {
|
|
301
358
|
continue;
|
|
302
359
|
}
|
|
303
360
|
command.clearForceDisabled(COMMAND_FORCE_DISABLE_ID);
|
|
@@ -320,7 +377,10 @@ export class RestrictedEditingModeEditing extends Plugin {
|
|
|
320
377
|
* Helper for handling Ctrl+A keydown behaviour.
|
|
321
378
|
*/
|
|
322
379
|
function getSelectAllHandler(editor) {
|
|
323
|
-
return (
|
|
380
|
+
return (eventInfo, domEventData) => {
|
|
381
|
+
if (getCode(domEventData) != parseKeystroke('Ctrl+A')) {
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
324
384
|
const model = editor.model;
|
|
325
385
|
const selection = editor.model.document.selection;
|
|
326
386
|
const marker = getMarkerAtPosition(editor, selection.focus);
|
|
@@ -331,11 +391,13 @@ function getSelectAllHandler(editor) {
|
|
|
331
391
|
//
|
|
332
392
|
// Note: Second Ctrl+A press is also blocked and it won't select the entire text in the editor.
|
|
333
393
|
const selectionRange = selection.getFirstRange();
|
|
334
|
-
const markerRange = marker.
|
|
394
|
+
const markerRange = getExceptionRange(marker, editor.model);
|
|
335
395
|
if (markerRange.containsRange(selectionRange, true) || selection.isCollapsed) {
|
|
336
|
-
|
|
396
|
+
eventInfo.stop();
|
|
397
|
+
domEventData.preventDefault();
|
|
398
|
+
domEventData.stopPropagation();
|
|
337
399
|
model.change(writer => {
|
|
338
|
-
writer.setSelection(
|
|
400
|
+
writer.setSelection(markerRange);
|
|
339
401
|
});
|
|
340
402
|
}
|
|
341
403
|
};
|
|
@@ -348,11 +410,11 @@ function getSelectAllHandler(editor) {
|
|
|
348
410
|
* - is on marker end - "deleteForward" - to prevent removing content after marker
|
|
349
411
|
*/
|
|
350
412
|
function isDeleteCommandOnMarkerBoundaries(commandName, selection, markerRange) {
|
|
351
|
-
if (commandName == 'delete' && markerRange.start.
|
|
413
|
+
if (commandName == 'delete' && selection.isCollapsed && markerRange.start.isTouching(selection.focus)) {
|
|
352
414
|
return true;
|
|
353
415
|
}
|
|
354
416
|
// Only for collapsed selection - non-collapsed selection that extends over a marker is handled elsewhere.
|
|
355
|
-
if (commandName == 'deleteForward' && selection.isCollapsed && markerRange.end.
|
|
417
|
+
if (commandName == 'deleteForward' && selection.isCollapsed && markerRange.end.isTouching(selection.focus)) {
|
|
356
418
|
return true;
|
|
357
419
|
}
|
|
358
420
|
return false;
|
|
@@ -378,7 +440,7 @@ function restrictDeleteContent(editor) {
|
|
|
378
440
|
return;
|
|
379
441
|
}
|
|
380
442
|
// Shrink the selection to the range inside exception marker.
|
|
381
|
-
const allowedToDelete = marker.
|
|
443
|
+
const allowedToDelete = getExceptionRange(marker, editor.model).getIntersection(selection.getFirstRange());
|
|
382
444
|
// Some features uses selection passed to model.deleteContent() to set the selection afterwards. For this we need to properly modify
|
|
383
445
|
// either the document selection using change block...
|
|
384
446
|
if (selection.is('documentSelection')) {
|
|
@@ -448,17 +510,3 @@ function ensureNewMarkerIsFlatPostFixer(editor) {
|
|
|
448
510
|
return changeApplied;
|
|
449
511
|
};
|
|
450
512
|
}
|
|
451
|
-
function onlyAllowAttributesFromList(allowedAttributes) {
|
|
452
|
-
return (context, attributeName) => {
|
|
453
|
-
if (context.startsWith('$clipboardHolder')) {
|
|
454
|
-
return allowedAttributes.includes(attributeName);
|
|
455
|
-
}
|
|
456
|
-
};
|
|
457
|
-
}
|
|
458
|
-
function allowTextOnlyInClipboardHolder() {
|
|
459
|
-
return (context, childDefinition) => {
|
|
460
|
-
if (context.startsWith('$clipboardHolder')) {
|
|
461
|
-
return childDefinition.name === '$text';
|
|
462
|
-
}
|
|
463
|
-
};
|
|
464
|
-
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
4
4
|
*/
|
|
5
5
|
import { Command } from 'ckeditor5/src/core.js';
|
|
6
|
+
import { getExceptionRange } from './restrictededitingmode/utils.js';
|
|
6
7
|
/**
|
|
7
8
|
* The command that allows navigation across the exceptions in the edited document.
|
|
8
9
|
*/
|
|
@@ -61,7 +62,7 @@ function getNearestExceptionRange(model, direction) {
|
|
|
61
62
|
const markerRanges = [];
|
|
62
63
|
// Get all exception marker positions that start after/before the selection position.
|
|
63
64
|
for (const marker of model.markers.getMarkersGroup('restrictedEditingException')) {
|
|
64
|
-
const markerRange = marker
|
|
65
|
+
const markerRange = getExceptionRange(marker, model);
|
|
65
66
|
// Checking parent because there two positions <paragraph>foo^</paragraph><paragraph>^bar</paragraph>
|
|
66
67
|
// are touching but they will represent different markers.
|
|
67
68
|
const isMarkerRangeTouching = selectionPosition.isTouching(markerRange.start) && selectionPosition.hasSameParentAs(markerRange.start) ||
|
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
* @module restricted-editing/standardeditingmodeediting
|
|
7
7
|
*/
|
|
8
8
|
import { Plugin } from 'ckeditor5/src/core.js';
|
|
9
|
+
import { Matcher } from 'ckeditor5/src/engine.js';
|
|
9
10
|
import { RestrictedEditingExceptionCommand } from './restrictededitingexceptioncommand.js';
|
|
11
|
+
import { RestrictedEditingExceptionBlockCommand } from './restrictededitingexceptionblockcommand.js';
|
|
10
12
|
/**
|
|
11
13
|
* The standard editing mode editing feature.
|
|
12
14
|
*
|
|
@@ -32,15 +34,113 @@ export class StandardEditingModeEditing extends Plugin {
|
|
|
32
34
|
*/
|
|
33
35
|
init() {
|
|
34
36
|
const editor = this.editor;
|
|
35
|
-
editor.model.schema
|
|
36
|
-
|
|
37
|
+
const schema = editor.model.schema;
|
|
38
|
+
schema.extend('$text', { allowAttributes: ['restrictedEditingException'] });
|
|
39
|
+
schema.register('restrictedEditingException', {
|
|
40
|
+
allowWhere: '$container',
|
|
41
|
+
allowContentOf: '$container'
|
|
42
|
+
});
|
|
43
|
+
// Don't allow nesting of block exceptions.
|
|
44
|
+
schema.addChildCheck(context => {
|
|
45
|
+
for (const item of context) {
|
|
46
|
+
if (item.name == 'restrictedEditingException') {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}, 'restrictedEditingException');
|
|
51
|
+
// Don't allow nesting inline exceptions inside block exceptions.
|
|
52
|
+
schema.addAttributeCheck(context => {
|
|
53
|
+
for (const item of context) {
|
|
54
|
+
if (item.name == 'restrictedEditingException') {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}, 'restrictedEditingException');
|
|
59
|
+
// Post-fixer to ensure proper structure.
|
|
60
|
+
editor.model.document.registerPostFixer(writer => {
|
|
61
|
+
const changes = editor.model.document.differ.getChanges();
|
|
62
|
+
const unwrap = new Set();
|
|
63
|
+
const remove = new Set();
|
|
64
|
+
const merge = new Set();
|
|
65
|
+
let changed = false;
|
|
66
|
+
for (const entry of changes) {
|
|
67
|
+
if (entry.type == 'insert') {
|
|
68
|
+
const range = writer.createRange(entry.position, entry.position.getShiftedBy(entry.length));
|
|
69
|
+
for (const child of range.getItems()) {
|
|
70
|
+
if (child.is('element', 'restrictedEditingException')) {
|
|
71
|
+
// Make sure that block exception is not nested or added in invalid place.
|
|
72
|
+
if (!schema.checkChild(writer.createPositionBefore(child), child)) {
|
|
73
|
+
unwrap.add(child);
|
|
74
|
+
}
|
|
75
|
+
else if (child.isEmpty) {
|
|
76
|
+
remove.add(child);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
merge.add(child);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
else if (child.is('$textProxy') &&
|
|
83
|
+
child.hasAttribute('restrictedEditingException') &&
|
|
84
|
+
!schema.checkAttribute(child, 'restrictedEditingException')) {
|
|
85
|
+
writer.removeAttribute('restrictedEditingException', child);
|
|
86
|
+
changed = true;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else if (entry.type == 'remove') {
|
|
91
|
+
const parent = entry.position.parent;
|
|
92
|
+
if (parent.is('element', 'restrictedEditingException') && parent.isEmpty) {
|
|
93
|
+
remove.add(parent);
|
|
94
|
+
}
|
|
95
|
+
// Verify if some block exceptions are siblings now after element removed between.
|
|
96
|
+
for (const child of parent.getChildren()) {
|
|
97
|
+
if (child.is('element', 'restrictedEditingException')) {
|
|
98
|
+
merge.add(child);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
for (const child of unwrap) {
|
|
104
|
+
writer.unwrap(child);
|
|
105
|
+
changed = true;
|
|
106
|
+
}
|
|
107
|
+
for (const child of remove) {
|
|
108
|
+
writer.remove(child);
|
|
109
|
+
changed = true;
|
|
110
|
+
}
|
|
111
|
+
for (const child of merge) {
|
|
112
|
+
if (child.root.rootName == '$graveyard') {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const nodeBefore = child.previousSibling;
|
|
116
|
+
const nodeAfter = child.nextSibling;
|
|
117
|
+
if (nodeBefore && nodeBefore.is('element', 'restrictedEditingException')) {
|
|
118
|
+
writer.merge(writer.createPositionBefore(child));
|
|
119
|
+
}
|
|
120
|
+
if (nodeAfter && nodeAfter.is('element', 'restrictedEditingException')) {
|
|
121
|
+
writer.merge(writer.createPositionAfter(child));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return changed;
|
|
125
|
+
});
|
|
126
|
+
editor.conversion.for('upcast')
|
|
127
|
+
.elementToAttribute({
|
|
37
128
|
model: 'restrictedEditingException',
|
|
38
129
|
view: {
|
|
39
130
|
name: 'span',
|
|
40
131
|
classes: 'restricted-editing-exception'
|
|
41
132
|
}
|
|
133
|
+
})
|
|
134
|
+
.elementToElement({
|
|
135
|
+
model: 'restrictedEditingException',
|
|
136
|
+
view: {
|
|
137
|
+
name: 'div',
|
|
138
|
+
classes: 'restricted-editing-exception'
|
|
139
|
+
}
|
|
42
140
|
});
|
|
43
|
-
editor
|
|
141
|
+
registerFallbackUpcastConverter(editor);
|
|
142
|
+
editor.conversion.for('downcast')
|
|
143
|
+
.attributeToElement({
|
|
44
144
|
model: 'restrictedEditingException',
|
|
45
145
|
view: (modelAttributeValue, { writer }) => {
|
|
46
146
|
if (modelAttributeValue) {
|
|
@@ -48,8 +148,16 @@ export class StandardEditingModeEditing extends Plugin {
|
|
|
48
148
|
return writer.createAttributeElement('span', { class: 'restricted-editing-exception' }, { priority: -10 });
|
|
49
149
|
}
|
|
50
150
|
}
|
|
151
|
+
})
|
|
152
|
+
.elementToElement({
|
|
153
|
+
model: 'restrictedEditingException',
|
|
154
|
+
view: {
|
|
155
|
+
name: 'div',
|
|
156
|
+
classes: 'restricted-editing-exception'
|
|
157
|
+
}
|
|
51
158
|
});
|
|
52
159
|
editor.commands.add('restrictedEditingException', new RestrictedEditingExceptionCommand(editor));
|
|
160
|
+
editor.commands.add('restrictedEditingExceptionBlock', new RestrictedEditingExceptionBlockCommand(editor));
|
|
53
161
|
editor.editing.view.change(writer => {
|
|
54
162
|
for (const root of editor.editing.view.document.roots) {
|
|
55
163
|
writer.addClass('ck-restricted-editing_mode_standard', root);
|
|
@@ -57,3 +165,27 @@ export class StandardEditingModeEditing extends Plugin {
|
|
|
57
165
|
});
|
|
58
166
|
}
|
|
59
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* Fallback upcast converter for empty exception span inside a table cell.
|
|
170
|
+
*/
|
|
171
|
+
function registerFallbackUpcastConverter(editor) {
|
|
172
|
+
const matcher = new Matcher({ name: 'span', classes: 'restricted-editing-exception' });
|
|
173
|
+
// See: https://github.com/ckeditor/ckeditor5/issues/16376.
|
|
174
|
+
editor.conversion.for('upcast').add(dispatcher => dispatcher.on('element:span', (evt, data, conversionApi) => {
|
|
175
|
+
const matcherResult = matcher.match(data.viewItem);
|
|
176
|
+
if (!matcherResult) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const match = matcherResult.match;
|
|
180
|
+
if (!conversionApi.consumable.test(data.viewItem, match)) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const modelText = conversionApi.writer.createText(' ', { restrictedEditingException: true });
|
|
184
|
+
if (!conversionApi.safeInsert(modelText, data.modelCursor)) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
conversionApi.consumable.consume(data.viewItem, match);
|
|
188
|
+
data.modelRange = conversionApi.writer.createRange(data.modelCursor, data.modelCursor.getShiftedBy(modelText.offsetSize));
|
|
189
|
+
data.modelCursor = data.modelRange.end;
|
|
190
|
+
}, { priority: 'low' }));
|
|
191
|
+
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { Plugin } from 'ckeditor5/src/core.js';
|
|
9
9
|
import { IconContentUnlock } from 'ckeditor5/src/icons.js';
|
|
10
|
-
import { ButtonView, MenuBarMenuListItemButtonView } from 'ckeditor5/src/ui.js';
|
|
10
|
+
import { ButtonView, MenuBarMenuListItemButtonView, createDropdown, addToolbarToDropdown } from 'ckeditor5/src/ui.js';
|
|
11
11
|
/**
|
|
12
12
|
* The standard editing mode UI feature.
|
|
13
13
|
*
|
|
@@ -31,35 +31,99 @@ export class StandardEditingModeUI extends Plugin {
|
|
|
31
31
|
*/
|
|
32
32
|
init() {
|
|
33
33
|
const editor = this.editor;
|
|
34
|
-
editor.ui.componentFactory
|
|
35
|
-
|
|
34
|
+
const componentFactory = editor.ui.componentFactory;
|
|
35
|
+
componentFactory.add('restrictedEditingException:dropdown', locale => {
|
|
36
|
+
const dropdownView = createDropdown(locale);
|
|
37
|
+
const t = locale.t;
|
|
38
|
+
const buttons = [
|
|
39
|
+
componentFactory.create('restrictedEditingException:inline'),
|
|
40
|
+
componentFactory.create('restrictedEditingException:block')
|
|
41
|
+
];
|
|
42
|
+
for (const button of buttons) {
|
|
43
|
+
button.set({
|
|
44
|
+
withText: true,
|
|
45
|
+
tooltip: false
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
addToolbarToDropdown(dropdownView, buttons, {
|
|
49
|
+
enableActiveItemFocusOnDropdownOpen: true,
|
|
50
|
+
isVertical: true,
|
|
51
|
+
ariaLabel: t('Enable editing')
|
|
52
|
+
});
|
|
53
|
+
dropdownView.buttonView.set({
|
|
54
|
+
label: t('Enable editing'),
|
|
55
|
+
icon: IconContentUnlock,
|
|
56
|
+
tooltip: true
|
|
57
|
+
});
|
|
58
|
+
dropdownView.extendTemplate({
|
|
59
|
+
attributes: {
|
|
60
|
+
class: 'ck-restricted-editing-dropdown'
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
// Enable button if any of the buttons is enabled.
|
|
64
|
+
dropdownView.bind('isEnabled').toMany(buttons, 'isEnabled', (...areEnabled) => {
|
|
65
|
+
return areEnabled.some(isEnabled => isEnabled);
|
|
66
|
+
});
|
|
67
|
+
// Focus the editable after executing the command.
|
|
68
|
+
this.listenTo(dropdownView, 'execute', () => {
|
|
69
|
+
editor.editing.view.focus();
|
|
70
|
+
});
|
|
71
|
+
return dropdownView;
|
|
72
|
+
});
|
|
73
|
+
componentFactory.add('restrictedEditingException:inline', () => {
|
|
74
|
+
const button = this._createButton('restrictedEditingException', ButtonView);
|
|
75
|
+
button.set({
|
|
76
|
+
tooltip: true,
|
|
77
|
+
isToggleable: true
|
|
78
|
+
});
|
|
79
|
+
return button;
|
|
80
|
+
});
|
|
81
|
+
componentFactory.add('restrictedEditingException:block', () => {
|
|
82
|
+
const button = this._createButton('restrictedEditingExceptionBlock', ButtonView);
|
|
36
83
|
button.set({
|
|
37
84
|
tooltip: true,
|
|
38
85
|
isToggleable: true
|
|
39
86
|
});
|
|
40
87
|
return button;
|
|
41
88
|
});
|
|
42
|
-
|
|
43
|
-
return this._createButton(MenuBarMenuListItemButtonView);
|
|
89
|
+
componentFactory.add('menuBar:restrictedEditingException:inline', () => {
|
|
90
|
+
return this._createButton('restrictedEditingException', MenuBarMenuListItemButtonView);
|
|
91
|
+
});
|
|
92
|
+
componentFactory.add('menuBar:restrictedEditingException:block', () => {
|
|
93
|
+
return this._createButton('restrictedEditingExceptionBlock', MenuBarMenuListItemButtonView);
|
|
94
|
+
});
|
|
95
|
+
// Aliases for backward compatibility.
|
|
96
|
+
componentFactory.add('restrictedEditingException', () => {
|
|
97
|
+
return componentFactory.create('restrictedEditingException:inline');
|
|
98
|
+
});
|
|
99
|
+
componentFactory.add('menuBar:restrictedEditingException', () => {
|
|
100
|
+
return componentFactory.create('menuBar:restrictedEditingException:inline');
|
|
44
101
|
});
|
|
45
102
|
}
|
|
46
103
|
/**
|
|
47
104
|
* Creates a button for restricted editing exception command to use either in toolbar or in menu bar.
|
|
48
105
|
*/
|
|
49
|
-
_createButton(ButtonClass) {
|
|
106
|
+
_createButton(commandName, ButtonClass) {
|
|
50
107
|
const editor = this.editor;
|
|
51
108
|
const locale = editor.locale;
|
|
52
|
-
const command = this.editor.commands.get(
|
|
109
|
+
const command = this.editor.commands.get(commandName);
|
|
53
110
|
const view = new ButtonClass(locale);
|
|
54
111
|
const t = locale.t;
|
|
55
112
|
view.icon = IconContentUnlock;
|
|
56
113
|
view.bind('isOn', 'isEnabled').to(command, 'value', 'isEnabled');
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
114
|
+
if (commandName == 'restrictedEditingExceptionBlock') {
|
|
115
|
+
view.bind('label').to(command, 'value', value => {
|
|
116
|
+
return value ? t('Disable block editing') : t('Enable block editing');
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
view.bind('label').to(command, 'value', value => {
|
|
121
|
+
return value ? t('Disable inline editing') : t('Enable inline editing');
|
|
122
|
+
});
|
|
123
|
+
}
|
|
60
124
|
// Execute the command.
|
|
61
125
|
this.listenTo(view, 'execute', () => {
|
|
62
|
-
editor.execute(
|
|
126
|
+
editor.execute(commandName);
|
|
63
127
|
editor.editing.view.focus();
|
|
64
128
|
});
|
|
65
129
|
return view;
|