@ckeditor/ckeditor5-editor-multi-root 37.0.1 → 38.0.0-rc.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/build/editor-multi-root.js +1 -1
- package/package.json +17 -15
- package/src/multirooteditor.d.ts +53 -0
- package/src/multirooteditor.js +178 -12
@@ -1,4 +1,4 @@
|
|
1
1
|
/*!
|
2
2
|
* @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
|
3
3
|
* For licensing, see LICENSE.md.
|
4
|
-
*/(()=>{var t={704:(t,e,o)=>{t.exports=o(79)("./src/core.js")},492:(t,e,o)=>{t.exports=o(79)("./src/engine.js")},273:(t,e,o)=>{t.exports=o(79)("./src/ui.js")},209:(t,e,o)=>{t.exports=o(79)("./src/utils.js")},434:(t,e,o)=>{t.exports=o(79)("./src/watchdog.js")},79:t=>{"use strict";t.exports=CKEditor5.dll}},e={};function o(i){var s=e[i];if(void 0!==s)return s.exports;var r=e[i]={exports:{}};return t[i](r,r.exports,o),r.exports}o.d=(t,e)=>{for(var i in e)o.o(e,i)&&!o.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:e[i]})},o.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),o.r=t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})};var i={};(()=>{"use strict";o.r(i),o.d(i,{MultiRootEditor:()=>
|
4
|
+
*/(()=>{var t={704:(t,e,o)=>{t.exports=o(79)("./src/core.js")},492:(t,e,o)=>{t.exports=o(79)("./src/engine.js")},273:(t,e,o)=>{t.exports=o(79)("./src/ui.js")},209:(t,e,o)=>{t.exports=o(79)("./src/utils.js")},434:(t,e,o)=>{t.exports=o(79)("./src/watchdog.js")},79:t=>{"use strict";t.exports=CKEditor5.dll}},e={};function o(i){var s=e[i];if(void 0!==s)return s.exports;var r=e[i]={exports:{}};return t[i](r,r.exports,o),r.exports}o.d=(t,e)=>{for(var i in e)o.o(e,i)&&!o.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:e[i]})},o.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),o.r=t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})};var i={};(()=>{"use strict";o.r(i),o.d(i,{MultiRootEditor:()=>W});var t=o(704),e=o(209),s=o(434),r=o(273),n=o(492);class a extends r.EditorUI{constructor(t,e){super(t),this.view=e,this._lastFocusedEditableElement=null}init(){this.view.render(),this.focusTracker.on("change:focusedElement",((t,e,o)=>{for(const t of Object.values(this.view.editables))o===t.element&&(this._lastFocusedEditableElement=t.element)})),this.focusTracker.on("change:isFocused",((t,e,o)=>{o||(this._lastFocusedEditableElement=null)}));for(const t of Object.values(this.view.editables))this.addEditable(t);this._initToolbar(),this.fire("ready")}addEditable(t,e){const o=t.element;this.editor.editing.view.attachDomRoot(o,t.name),this.setEditableElement(t.name,o),t.bind("isFocused").to(this.focusTracker,"isFocused",this.focusTracker,"focusedElement",((t,e)=>!!t&&(e===o||this._lastFocusedEditableElement===o))),this._initPlaceholder(t,e)}removeEditable(t){this.editor.editing.view.detachDomRoot(t.name),t.unbind("isFocused"),this.removeEditableElement(t.name)}destroy(){super.destroy();for(const t of Object.values(this.view.editables))this.removeEditable(t);this.view.destroy()}_initToolbar(){const t=this.editor,e=this.view;e.toolbar.fillFromConfig(t.config.get("toolbar"),this.componentFactory),this.addToolbar(e.toolbar)}_initPlaceholder(t,e){if(!e){const o=this.editor.config.get("placeholder");o&&(e="string"==typeof o?o:o[t.name])}if(!e)return;const o=this.editor.editing.view,i=o.document.getRoot(t.name);(0,n.enablePlaceholder)({view:o,element:i,text:e,isDirectHost:!1,keepOnFocus:!0})}}class c extends r.EditorUIView{constructor(t,e,o,i={}){super(t),this._editingView=e,this.toolbar=new r.ToolbarView(t,{shouldGroupWhenFull:i.shouldToolbarGroupWhenFull}),this.editables={};for(const t of o){const e=i.editableElements?i.editableElements[t]:void 0;this.createEditable(t,e)}this.editable=Object.values(this.editables)[0],this.toolbar.extendTemplate({attributes:{class:["ck-reset_all","ck-rounded-corners"],dir:t.uiLanguageDirection}})}createEditable(t,e){const o=this.locale.t,i=new r.InlineEditableUIView(this.locale,this._editingView,e,{label:t=>o("Rich Text Editor. Editing area: %0",t.name)});return this.editables[t]=i,i.name=t,this.isRendered&&this.registerChild(i),i}removeEditable(t){const e=this.editables[t];this.isRendered&&this.deregisterChild(e),delete this.editables[t],e.destroy()}render(){super.render(),this.registerChild(Object.values(this.editables)),this.registerChild(this.toolbar)}}const d=function(t){return null!=t&&"object"==typeof t};const l="object"==typeof global&&global&&global.Object===Object&&global;var h="object"==typeof self&&self&&self.Object===Object&&self;const u=(l||h||Function("return this")()).Symbol;var f=Object.prototype,b=f.hasOwnProperty,m=f.toString,g=u?u.toStringTag:void 0;const y=function(t){var e=b.call(t,g),o=t[g];try{t[g]=void 0;var i=!0}catch(t){}var s=m.call(t);return i&&(e?t[g]=o:delete t[g]),s};var v=Object.prototype.toString;const E=function(t){return v.call(t)};var p=u?u.toStringTag:void 0;const O=function(t){return null==t?void 0===t?"[object Undefined]":"[object Null]":p&&p in Object(t)?y(t):E(t)};const w=function(t,e){return function(o){return t(e(o))}}(Object.getPrototypeOf,Object);var R=Function.prototype,j=Object.prototype,_=R.toString,C=j.hasOwnProperty,k=_.call(Object);const x=function(t){if(!d(t)||"[object Object]"!=O(t))return!1;var e=w(t);if(null===e)return!0;var o=C.call(e,"constructor")&&e.constructor;return"function"==typeof o&&o instanceof o&&_.call(o)==k};const D=function(t){return d(t)&&1===t.nodeType&&!x(t)};var T=Object.defineProperty,A=Object.defineProperties,F=Object.getOwnPropertyDescriptors,P=Object.getOwnPropertySymbols,S=Object.prototype.hasOwnProperty,K=Object.prototype.propertyIsEnumerable,N=(t,e,o)=>e in t?T(t,e,{enumerable:!0,configurable:!0,writable:!0,value:o}):t[e]=o,I=(t,e)=>{for(var o in e||(e={}))S.call(e,o)&&N(t,o,e[o]);if(P)for(var o of P(e))K.call(e,o)&&N(t,o,e[o]);return t};class W extends((0,t.DataApiMixin)(t.Editor)){constructor(o,i={}){const s=Object.keys(o),r=0===s.length||"string"==typeof o[s[0]];if(r&&void 0!==i.initialData)throw new e.CKEditorError("editor-create-initial-data",null);if(super(i),this._registeredRootsAttributesKeys=new Set,this._readOnlyRootLocks=new Map,this.sourceElements=r?{}:o,void 0===this.config.get("initialData")){const t={};for(const i of s)t[i]=L(n=o[i])?(0,e.getDataFromElement)(n):n;this.config.set("initialData",t)}var n;if(!r)for(const e of s)(0,t.secureSourceElement)(this,o[e]);this.editing.view.document.roots.on("add",((t,e)=>{e.unbind("isReadOnly"),e.bind("isReadOnly").to(this.editing.view.document,"isReadOnly",(t=>t||this._readOnlyRootLocks.has(e.rootName))),e.on("change:isReadOnly",((t,o,i)=>{const s=this.editing.view.createRangeIn(e);for(const t of s.getItems())t.is("editableElement")&&(t.unbind("isReadOnly"),t.isReadOnly=i)}))}));for(const t of s)this.model.document.createRoot("$root",t);if(this.config.get("rootsAttributes")){const t=this.config.get("rootsAttributes");for(const[o,i]of Object.entries(t)){if(!s.includes(o))throw new e.CKEditorError("multi-root-editor-root-attributes-no-root",null);for(const t of Object.keys(i))this._registeredRootsAttributesKeys.add(t)}this.data.on("init",(()=>{this.model.enqueueChange({isUndoable:!1},(e=>{for(const[o,i]of Object.entries(t)){const t=this.model.document.getRoot(o);for(const[o,s]of Object.entries(i))null!==s&&e.setAttribute(o,s,t)}}))}))}const d={shouldToolbarGroupWhenFull:!this.config.get("toolbar.shouldNotGroupWhenFull"),editableElements:r?void 0:o},l=new c(this.locale,this.editing.view,s,d);this.ui=new a(this,l),this.model.document.on("change:data",(()=>{const t=this.model.document.differ.getChangedRoots();for(const e of t){const t=this.model.document.getRoot(e.name);"detached"==e.state&&this.fire("detachRoot",t)}for(const e of t){const t=this.model.document.getRoot(e.name);"attached"==e.state&&this.fire("addRoot",t)}})),this.listenTo(this.model,"canEditAt",((t,[e])=>{if(!e)return;let o=!1;for(const t of e.getRanges()){const e=t.root.rootName;if(this._readOnlyRootLocks.has(e)){o=!0;break}}o&&(t.return=!1,t.stop())}),{priority:"high"})}destroy(){const t=this.config.get("updateSourceElementOnDestroy"),o={};for(const e of Object.keys(this.sourceElements))o[e]=t?this.getData({rootName:e}):"";return this.ui.destroy(),super.destroy().then((()=>{for(const t of Object.keys(this.sourceElements))(0,e.setDataInElement)(this.sourceElements[t],o[t])}))}addRoot(t,{data:e="",attributes:o={},elementName:i="$root",isUndoable:s=!1}={}){const r=this.data,n=this._registeredRootsAttributesKeys;function a(s){const a=s.addRoot(t,i);e&&s.insert(r.parse(e,a),a,0);for(const t of Object.keys(o))n.add(t),s.setAttribute(t,o[t],a)}s?this.model.change(a):this.model.enqueueChange({isUndoable:!1},a)}detachRoot(t,e=!1){e?this.model.change((e=>e.detachRoot(t))):this.model.enqueueChange({isUndoable:!1},(e=>e.detachRoot(t)))}createEditable(t,e){const o=this.ui.view.createEditable(t.rootName);return this.ui.addEditable(o,e),this.editing.view.forceRender(),o.element}detachEditable(t){const e=t.rootName,o=this.ui.view.editables[e];return this.ui.removeEditable(o),this.ui.view.removeEditable(e),o.element}getFullData(t){const e={};for(const i of this.model.document.getRootNames())e[i]=this.data.get((o=I({},t),A(o,F({rootName:i}))));var o;return e}getRootsAttributes(){const t={},e=Array.from(this._registeredRootsAttributesKeys);for(const o of this.model.document.getRootNames()){t[o]={};const i=this.model.document.getRoot(o);for(const s of e)t[o][s]=i.hasAttribute(s)?i.getAttribute(s):null}return t}disableRoot(t,o){if("$graveyard"==t)throw new e.CKEditorError("multi-root-editor-cannot-disable-graveyard-root",this);const i=this._readOnlyRootLocks.get(t);if(i)i.add(o);else{this._readOnlyRootLocks.set(t,new Set([o]));this.editing.view.document.getRoot(t).isReadOnly=!0,Array.from(this.commands.commands()).forEach((t=>t.affectsData&&t.refresh()))}}enableRoot(t,e){const o=this._readOnlyRootLocks.get(t);if(o&&o.has(e))if(1===o.size){this._readOnlyRootLocks.delete(t);this.editing.view.document.getRoot(t).isReadOnly=this.isReadOnly,Array.from(this.commands.commands()).forEach((t=>t.affectsData&&t.refresh()))}else o.delete(e)}static create(t,o={}){return new Promise((i=>{for(const o of Object.values(t))if(L(o)&&"TEXTAREA"===o.tagName)throw new e.CKEditorError("editor-wrong-element",null);const s=new this(t,o);i(s.initPlugins().then((()=>s.ui.init())).then((()=>(s._verifyRootsWithInitialData(),s.data.init(s.config.get("initialData"))))).then((()=>s.fire("ready"))).then((()=>s)))}))}_verifyRootsWithInitialData(){const t=this.config.get("initialData");for(const o of this.model.document.getRootNames())if(!(o in t))throw new e.CKEditorError("multi-root-editor-root-initial-data-mismatch",null);for(const o of Object.keys(t)){const t=this.model.document.getRoot(o);if(!t||!t.isAttached())throw new e.CKEditorError("multi-root-editor-root-initial-data-mismatch",null)}}}function L(t){return D(t)}W.Context=t.Context,W.EditorWatchdog=s.EditorWatchdog,W.ContextWatchdog=s.ContextWatchdog})(),(window.CKEditor5=window.CKEditor5||{}).editorMultiRoot=i})();
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@ckeditor/ckeditor5-editor-multi-root",
|
3
|
-
"version": "
|
3
|
+
"version": "38.0.0-rc.0",
|
4
4
|
"description": "Multi-root editor implementation for CKEditor 5.",
|
5
5
|
"keywords": [
|
6
6
|
"ckeditor",
|
@@ -11,23 +11,25 @@
|
|
11
11
|
],
|
12
12
|
"main": "src/index.js",
|
13
13
|
"dependencies": {
|
14
|
-
"ckeditor5": "^
|
14
|
+
"ckeditor5": "^38.0.0-rc.0",
|
15
15
|
"lodash-es": "^4.17.15"
|
16
16
|
},
|
17
17
|
"devDependencies": {
|
18
|
-
"@ckeditor/ckeditor5-basic-styles": "^
|
19
|
-
"@ckeditor/ckeditor5-core": "^
|
20
|
-
"@ckeditor/ckeditor5-dev-utils": "^
|
21
|
-
"@ckeditor/ckeditor5-
|
22
|
-
"@ckeditor/ckeditor5-
|
23
|
-
"@ckeditor/ckeditor5-
|
24
|
-
"@ckeditor/ckeditor5-
|
25
|
-
"@ckeditor/ckeditor5-
|
26
|
-
"@ckeditor/ckeditor5-
|
27
|
-
"@ckeditor/ckeditor5-
|
28
|
-
"@ckeditor/ckeditor5-
|
29
|
-
"@ckeditor/ckeditor5-
|
30
|
-
"@ckeditor/ckeditor5-
|
18
|
+
"@ckeditor/ckeditor5-basic-styles": "^38.0.0-rc.0",
|
19
|
+
"@ckeditor/ckeditor5-core": "^38.0.0-rc.0",
|
20
|
+
"@ckeditor/ckeditor5-dev-utils": "^37.0.0",
|
21
|
+
"@ckeditor/ckeditor5-essentials": "^38.0.0-rc.0",
|
22
|
+
"@ckeditor/ckeditor5-engine": "^38.0.0-rc.0",
|
23
|
+
"@ckeditor/ckeditor5-enter": "^38.0.0-rc.0",
|
24
|
+
"@ckeditor/ckeditor5-heading": "^38.0.0-rc.0",
|
25
|
+
"@ckeditor/ckeditor5-paragraph": "^38.0.0-rc.0",
|
26
|
+
"@ckeditor/ckeditor5-theme-lark": "^38.0.0-rc.0",
|
27
|
+
"@ckeditor/ckeditor5-table": "^38.0.0-rc.0",
|
28
|
+
"@ckeditor/ckeditor5-typing": "^38.0.0-rc.0",
|
29
|
+
"@ckeditor/ckeditor5-ui": "^38.0.0-rc.0",
|
30
|
+
"@ckeditor/ckeditor5-undo": "^38.0.0-rc.0",
|
31
|
+
"@ckeditor/ckeditor5-utils": "^38.0.0-rc.0",
|
32
|
+
"@ckeditor/ckeditor5-watchdog": "^38.0.0-rc.0",
|
31
33
|
"typescript": "^4.8.4",
|
32
34
|
"webpack": "^5.58.1",
|
33
35
|
"webpack-cli": "^4.9.0"
|
package/src/multirooteditor.d.ts
CHANGED
@@ -54,6 +54,10 @@ export default class MultiRootEditor extends MultiRootEditor_base {
|
|
54
54
|
* config property and should be returned by {@link #getRootsAttributes}.
|
55
55
|
*/
|
56
56
|
private readonly _registeredRootsAttributesKeys;
|
57
|
+
/**
|
58
|
+
* A set of lock IDs for enabling or disabling particular root.
|
59
|
+
*/
|
60
|
+
private readonly _readOnlyRootLocks;
|
57
61
|
/**
|
58
62
|
* Creates an instance of the multi-root editor.
|
59
63
|
*
|
@@ -255,6 +259,51 @@ export default class MultiRootEditor extends MultiRootEditor_base {
|
|
255
259
|
* @returns Object with roots attributes. Keys are roots names, while values are attributes set on given root.
|
256
260
|
*/
|
257
261
|
getRootsAttributes(): Record<string, RootAttributes>;
|
262
|
+
/**
|
263
|
+
* Switches given editor root to the read-only mode.
|
264
|
+
*
|
265
|
+
* In contrary to {@link module:core/editor/editor~Editor#enableReadOnlyMode `enableReadOnlyMode()`}, which switches the whole editor
|
266
|
+
* to the read-only mode, this method turns only a particular root to the read-only mode. This can be useful when you want to prevent
|
267
|
+
* editing only a part of the editor content.
|
268
|
+
*
|
269
|
+
* When you switch a root to the read-only mode, you need provide a unique identifier (`lockId`) that will identify this request. You
|
270
|
+
* will need to provide the same `lockId` when you will want to
|
271
|
+
* {@link module:editor-multi-root/multirooteditor~MultiRootEditor#enableRoot re-enable} the root.
|
272
|
+
*
|
273
|
+
* ```ts
|
274
|
+
* const model = editor.model;
|
275
|
+
* const myRoot = model.document.getRoot( 'myRoot' );
|
276
|
+
*
|
277
|
+
* editor.disableRoot( 'myRoot', 'my-lock' );
|
278
|
+
* model.canEditAt( myRoot ); // `false`
|
279
|
+
*
|
280
|
+
* editor.disableRoot( 'myRoot', 'other-lock' );
|
281
|
+
* editor.disableRoot( 'myRoot', 'other-lock' ); // Multiple locks with the same ID have no effect.
|
282
|
+
* model.canEditAt( myRoot ); // `false`
|
283
|
+
*
|
284
|
+
* editor.enableRoot( 'myRoot', 'my-lock' );
|
285
|
+
* model.canEditAt( myRoot ); // `false`
|
286
|
+
*
|
287
|
+
* editor.enableRoot( 'myRoot', 'other-lock' );
|
288
|
+
* model.canEditAt( myRoot ); // `true`
|
289
|
+
* ```
|
290
|
+
*
|
291
|
+
* See also {@link module:core/editor/editor~Editor#enableReadOnlyMode `Editor#enableReadOnlyMode()`} and
|
292
|
+
* {@link module:editor-multi-root/multirooteditor~MultiRootEditor#enableRoot `MultiRootEditor#enableRoot()`}.
|
293
|
+
*
|
294
|
+
* @param rootName Name of the root to switch to read-only mode.
|
295
|
+
* @param lockId A unique ID for setting the editor to the read-only state.
|
296
|
+
*/
|
297
|
+
disableRoot(rootName: string, lockId: string | symbol): void;
|
298
|
+
/**
|
299
|
+
* Removes given read-only lock from the given root.
|
300
|
+
*
|
301
|
+
* See {@link module:editor-multi-root/multirooteditor~MultiRootEditor#disableRoot `disableRoot()`}.
|
302
|
+
*
|
303
|
+
* @param rootName Name of the root to switch back from the read-only mode.
|
304
|
+
* @param lockId A unique ID for setting the editor to the read-only state.
|
305
|
+
*/
|
306
|
+
enableRoot(rootName: string, lockId: string | symbol): void;
|
258
307
|
/**
|
259
308
|
* Creates a new multi-root editor instance.
|
260
309
|
*
|
@@ -408,6 +457,10 @@ export default class MultiRootEditor extends MultiRootEditor_base {
|
|
408
457
|
* Exposed as static editor field for easier access in editor builds.
|
409
458
|
*/
|
410
459
|
static ContextWatchdog: typeof ContextWatchdog;
|
460
|
+
/**
|
461
|
+
* @internal
|
462
|
+
*/
|
463
|
+
private _verifyRootsWithInitialData;
|
411
464
|
}
|
412
465
|
/**
|
413
466
|
* Fired whenever a root is {@link ~MultiRootEditor#addRoot added or re-added} to the editor model.
|
package/src/multirooteditor.js
CHANGED
@@ -67,6 +67,10 @@ export default class MultiRootEditor extends DataApiMixin(Editor) {
|
|
67
67
|
* config property and should be returned by {@link #getRootsAttributes}.
|
68
68
|
*/
|
69
69
|
this._registeredRootsAttributesKeys = new Set();
|
70
|
+
/**
|
71
|
+
* A set of lock IDs for enabling or disabling particular root.
|
72
|
+
*/
|
73
|
+
this._readOnlyRootLocks = new Map();
|
70
74
|
if (!sourceIsData) {
|
71
75
|
this.sourceElements = sourceElementsOrData;
|
72
76
|
}
|
@@ -86,6 +90,25 @@ export default class MultiRootEditor extends DataApiMixin(Editor) {
|
|
86
90
|
secureSourceElement(this, sourceElementsOrData[rootName]);
|
87
91
|
}
|
88
92
|
}
|
93
|
+
this.editing.view.document.roots.on('add', (evt, viewRoot) => {
|
94
|
+
// Here we change the standard binding of readOnly flag by adding
|
95
|
+
// additional constraint that multi-root has (enabling / disabling particular root).
|
96
|
+
viewRoot.unbind('isReadOnly');
|
97
|
+
viewRoot.bind('isReadOnly').to(this.editing.view.document, 'isReadOnly', isReadOnly => {
|
98
|
+
return isReadOnly || this._readOnlyRootLocks.has(viewRoot.rootName);
|
99
|
+
});
|
100
|
+
// Hacky solution to nested editables.
|
101
|
+
// Nested editables should be managed each separately and do not base on view document or view root.
|
102
|
+
viewRoot.on('change:isReadOnly', (evt, prop, value) => {
|
103
|
+
const viewRange = this.editing.view.createRangeIn(viewRoot);
|
104
|
+
for (const viewItem of viewRange.getItems()) {
|
105
|
+
if (viewItem.is('editableElement')) {
|
106
|
+
viewItem.unbind('isReadOnly');
|
107
|
+
viewItem.isReadOnly = value;
|
108
|
+
}
|
109
|
+
}
|
110
|
+
});
|
111
|
+
});
|
89
112
|
for (const rootName of rootNames) {
|
90
113
|
// Create root and `UIView` element for each editable container.
|
91
114
|
this.model.document.createRoot('$root', rootName);
|
@@ -129,16 +152,44 @@ export default class MultiRootEditor extends DataApiMixin(Editor) {
|
|
129
152
|
this.ui = new MultiRootEditorUI(this, view);
|
130
153
|
this.model.document.on('change:data', () => {
|
131
154
|
const changedRoots = this.model.document.differ.getChangedRoots();
|
155
|
+
// Fire detaches first. If there are multiple roots removed and added in one batch, it should be easier to handle if
|
156
|
+
// changes aren't mixed. Detaching will usually lead to just removing DOM elements. Detaching first will lead to a clean DOM
|
157
|
+
// when new editables are added in `addRoot` event.
|
158
|
+
for (const changes of changedRoots) {
|
159
|
+
const root = this.model.document.getRoot(changes.name);
|
160
|
+
if (changes.state == 'detached') {
|
161
|
+
this.fire('detachRoot', root);
|
162
|
+
}
|
163
|
+
}
|
132
164
|
for (const changes of changedRoots) {
|
133
165
|
const root = this.model.document.getRoot(changes.name);
|
134
166
|
if (changes.state == 'attached') {
|
135
167
|
this.fire('addRoot', root);
|
136
168
|
}
|
137
|
-
else if (changes.state == 'detached') {
|
138
|
-
this.fire('detachRoot', root);
|
139
|
-
}
|
140
169
|
}
|
141
170
|
});
|
171
|
+
// Overwrite `Model#canEditAt()` decorated method.
|
172
|
+
// Check if the provided selection is inside a read-only root. If so, return `false`.
|
173
|
+
this.listenTo(this.model, 'canEditAt', (evt, [selection]) => {
|
174
|
+
// Skip empty selections.
|
175
|
+
if (!selection) {
|
176
|
+
return;
|
177
|
+
}
|
178
|
+
let selectionInReadOnlyRoot = false;
|
179
|
+
for (const range of selection.getRanges()) {
|
180
|
+
const rootName = range.root.rootName;
|
181
|
+
if (this._readOnlyRootLocks.has(rootName)) {
|
182
|
+
selectionInReadOnlyRoot = true;
|
183
|
+
break;
|
184
|
+
}
|
185
|
+
}
|
186
|
+
// If selection is in read-only root, return `false` and prevent further processing.
|
187
|
+
// Otherwise, allow for other callbacks (or default callback) to evaluate.
|
188
|
+
if (selectionInReadOnlyRoot) {
|
189
|
+
evt.return = false;
|
190
|
+
evt.stop();
|
191
|
+
}
|
192
|
+
}, { priority: 'high' });
|
142
193
|
}
|
143
194
|
/**
|
144
195
|
* Destroys the editor instance, releasing all resources used by it.
|
@@ -170,18 +221,14 @@ export default class MultiRootEditor extends DataApiMixin(Editor) {
|
|
170
221
|
// It's safe to assume that the model->view conversion will not work after `super.destroy()`,
|
171
222
|
// same as `ui.getEditableElement()` method will not return editables.
|
172
223
|
const data = {};
|
173
|
-
|
174
|
-
|
175
|
-
data[rootName] = shouldUpdateSourceElement ? this.getData({ rootName }) : '';
|
176
|
-
}
|
224
|
+
for (const rootName of Object.keys(this.sourceElements)) {
|
225
|
+
data[rootName] = shouldUpdateSourceElement ? this.getData({ rootName }) : '';
|
177
226
|
}
|
178
227
|
this.ui.destroy();
|
179
228
|
return super.destroy()
|
180
229
|
.then(() => {
|
181
|
-
|
182
|
-
|
183
|
-
setDataInElement(this.sourceElements[rootName], data[rootName]);
|
184
|
-
}
|
230
|
+
for (const rootName of Object.keys(this.sourceElements)) {
|
231
|
+
setDataInElement(this.sourceElements[rootName], data[rootName]);
|
185
232
|
}
|
186
233
|
});
|
187
234
|
}
|
@@ -403,6 +450,86 @@ export default class MultiRootEditor extends DataApiMixin(Editor) {
|
|
403
450
|
}
|
404
451
|
return rootsAttributes;
|
405
452
|
}
|
453
|
+
/**
|
454
|
+
* Switches given editor root to the read-only mode.
|
455
|
+
*
|
456
|
+
* In contrary to {@link module:core/editor/editor~Editor#enableReadOnlyMode `enableReadOnlyMode()`}, which switches the whole editor
|
457
|
+
* to the read-only mode, this method turns only a particular root to the read-only mode. This can be useful when you want to prevent
|
458
|
+
* editing only a part of the editor content.
|
459
|
+
*
|
460
|
+
* When you switch a root to the read-only mode, you need provide a unique identifier (`lockId`) that will identify this request. You
|
461
|
+
* will need to provide the same `lockId` when you will want to
|
462
|
+
* {@link module:editor-multi-root/multirooteditor~MultiRootEditor#enableRoot re-enable} the root.
|
463
|
+
*
|
464
|
+
* ```ts
|
465
|
+
* const model = editor.model;
|
466
|
+
* const myRoot = model.document.getRoot( 'myRoot' );
|
467
|
+
*
|
468
|
+
* editor.disableRoot( 'myRoot', 'my-lock' );
|
469
|
+
* model.canEditAt( myRoot ); // `false`
|
470
|
+
*
|
471
|
+
* editor.disableRoot( 'myRoot', 'other-lock' );
|
472
|
+
* editor.disableRoot( 'myRoot', 'other-lock' ); // Multiple locks with the same ID have no effect.
|
473
|
+
* model.canEditAt( myRoot ); // `false`
|
474
|
+
*
|
475
|
+
* editor.enableRoot( 'myRoot', 'my-lock' );
|
476
|
+
* model.canEditAt( myRoot ); // `false`
|
477
|
+
*
|
478
|
+
* editor.enableRoot( 'myRoot', 'other-lock' );
|
479
|
+
* model.canEditAt( myRoot ); // `true`
|
480
|
+
* ```
|
481
|
+
*
|
482
|
+
* See also {@link module:core/editor/editor~Editor#enableReadOnlyMode `Editor#enableReadOnlyMode()`} and
|
483
|
+
* {@link module:editor-multi-root/multirooteditor~MultiRootEditor#enableRoot `MultiRootEditor#enableRoot()`}.
|
484
|
+
*
|
485
|
+
* @param rootName Name of the root to switch to read-only mode.
|
486
|
+
* @param lockId A unique ID for setting the editor to the read-only state.
|
487
|
+
*/
|
488
|
+
disableRoot(rootName, lockId) {
|
489
|
+
if (rootName == '$graveyard') {
|
490
|
+
/**
|
491
|
+
* You cannot disable the `$graveyard` root.
|
492
|
+
*
|
493
|
+
* @error multi-root-editor-cannot-disable-graveyard-root
|
494
|
+
*/
|
495
|
+
throw new CKEditorError('multi-root-editor-cannot-disable-graveyard-root', this);
|
496
|
+
}
|
497
|
+
const locksForGivenRoot = this._readOnlyRootLocks.get(rootName);
|
498
|
+
if (locksForGivenRoot) {
|
499
|
+
locksForGivenRoot.add(lockId);
|
500
|
+
}
|
501
|
+
else {
|
502
|
+
this._readOnlyRootLocks.set(rootName, new Set([lockId]));
|
503
|
+
const editableRootElement = this.editing.view.document.getRoot(rootName);
|
504
|
+
editableRootElement.isReadOnly = true;
|
505
|
+
// Since one of the roots has changed read-only state, we need to refresh all commands that affect data.
|
506
|
+
Array.from(this.commands.commands()).forEach(command => command.affectsData && command.refresh());
|
507
|
+
}
|
508
|
+
}
|
509
|
+
/**
|
510
|
+
* Removes given read-only lock from the given root.
|
511
|
+
*
|
512
|
+
* See {@link module:editor-multi-root/multirooteditor~MultiRootEditor#disableRoot `disableRoot()`}.
|
513
|
+
*
|
514
|
+
* @param rootName Name of the root to switch back from the read-only mode.
|
515
|
+
* @param lockId A unique ID for setting the editor to the read-only state.
|
516
|
+
*/
|
517
|
+
enableRoot(rootName, lockId) {
|
518
|
+
const locksForGivenRoot = this._readOnlyRootLocks.get(rootName);
|
519
|
+
if (!locksForGivenRoot || !locksForGivenRoot.has(lockId)) {
|
520
|
+
return;
|
521
|
+
}
|
522
|
+
if (locksForGivenRoot.size === 1) {
|
523
|
+
this._readOnlyRootLocks.delete(rootName);
|
524
|
+
const editableRootElement = this.editing.view.document.getRoot(rootName);
|
525
|
+
editableRootElement.isReadOnly = this.isReadOnly;
|
526
|
+
// Since one of the roots has changed read-only state, we need to refresh all commands that affect data.
|
527
|
+
Array.from(this.commands.commands()).forEach(command => command.affectsData && command.refresh());
|
528
|
+
}
|
529
|
+
else {
|
530
|
+
locksForGivenRoot.delete(lockId);
|
531
|
+
}
|
532
|
+
}
|
406
533
|
/**
|
407
534
|
* Creates a new multi-root editor instance.
|
408
535
|
*
|
@@ -549,11 +676,50 @@ export default class MultiRootEditor extends DataApiMixin(Editor) {
|
|
549
676
|
const editor = new this(sourceElementsOrData, config);
|
550
677
|
resolve(editor.initPlugins()
|
551
678
|
.then(() => editor.ui.init())
|
552
|
-
.then(() =>
|
679
|
+
.then(() => {
|
680
|
+
// This is checked directly before setting the initial data,
|
681
|
+
// as plugins may change `EditorConfig#initialData` value.
|
682
|
+
editor._verifyRootsWithInitialData();
|
683
|
+
return editor.data.init(editor.config.get('initialData'));
|
684
|
+
})
|
553
685
|
.then(() => editor.fire('ready'))
|
554
686
|
.then(() => editor));
|
555
687
|
});
|
556
688
|
}
|
689
|
+
/**
|
690
|
+
* @internal
|
691
|
+
*/
|
692
|
+
_verifyRootsWithInitialData() {
|
693
|
+
const initialData = this.config.get('initialData');
|
694
|
+
// Roots that are not in the initial data.
|
695
|
+
for (const rootName of this.model.document.getRootNames()) {
|
696
|
+
if (!(rootName in initialData)) {
|
697
|
+
/**
|
698
|
+
* Editor roots do not match the
|
699
|
+
* {@link module:core/editor/editorconfig~EditorConfig#initialData `initialData` configuration}.
|
700
|
+
*
|
701
|
+
* This may happen for one of the two reasons:
|
702
|
+
*
|
703
|
+
* * Configuration error. The `sourceElementsOrData` parameter in
|
704
|
+
* {@link module:editor-multi-root/multirooteditor~MultiRootEditor.create `MultiRootEditor.create()`} contains different
|
705
|
+
* roots than {@link module:core/editor/editorconfig~EditorConfig#initialData `initialData` configuration}.
|
706
|
+
* * As the editor was initialized, the {@link module:core/editor/editorconfig~EditorConfig#initialData `initialData`}
|
707
|
+
* configuration value or the state of the editor roots has been changed.
|
708
|
+
*
|
709
|
+
* @error multi-root-editor-root-initial-data-mismatch
|
710
|
+
*/
|
711
|
+
throw new CKEditorError('multi-root-editor-root-initial-data-mismatch', null);
|
712
|
+
}
|
713
|
+
}
|
714
|
+
// Roots that are not in the editor.
|
715
|
+
for (const rootName of Object.keys(initialData)) {
|
716
|
+
const root = this.model.document.getRoot(rootName);
|
717
|
+
if (!root || !root.isAttached()) {
|
718
|
+
// eslint-disable-next-line ckeditor5-rules/ckeditor-error-message
|
719
|
+
throw new CKEditorError('multi-root-editor-root-initial-data-mismatch', null);
|
720
|
+
}
|
721
|
+
}
|
722
|
+
}
|
557
723
|
}
|
558
724
|
/**
|
559
725
|
* The {@link module:core/context~Context} class.
|