@ckeditor/ckeditor5-editor-multi-root 37.1.0 → 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.
@@ -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 r=e[i];if(void 0!==r)return r.exports;var s=e[i]={exports:{}};return t[i](s,s.exports,o),s.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:()=>I});var t=o(704),e=o(209),r=o(434),s=o(273),n=o(492);class a extends s.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 s.EditorUIView{constructor(t,e,o,i={}){super(t),this._editingView=e,this.toolbar=new s.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 s.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 l=function(t){return null!=t&&"object"==typeof t};const d="object"==typeof global&&global&&global.Object===Object&&global;var u="object"==typeof self&&self&&self.Object===Object&&self;const h=(d||u||Function("return this")()).Symbol;var b=Object.prototype,f=b.hasOwnProperty,m=b.toString,g=h?h.toStringTag:void 0;const p=function(t){var e=f.call(t,g),o=t[g];try{t[g]=void 0;var i=!0}catch(t){}var r=m.call(t);return i&&(e?t[g]=o:delete t[g]),r};var v=Object.prototype.toString;const E=function(t){return v.call(t)};var y=h?h.toStringTag:void 0;const w=function(t){return null==t?void 0===t?"[object Undefined]":"[object Null]":y&&y in Object(t)?p(t):E(t)};const j=function(t,e){return function(o){return t(e(o))}}(Object.getPrototypeOf,Object);var O=Function.prototype,R=Object.prototype,x=O.toString,C=R.hasOwnProperty,F=x.call(Object);const T=function(t){if(!l(t)||"[object Object]"!=w(t))return!1;var e=j(t);if(null===e)return!0;var o=C.call(e,"constructor")&&e.constructor;return"function"==typeof o&&o instanceof o&&x.call(o)==F};const _=function(t){return l(t)&&1===t.nodeType&&!T(t)};var D=Object.defineProperty,P=Object.defineProperties,A=Object.getOwnPropertyDescriptors,S=Object.getOwnPropertySymbols,k=Object.prototype.hasOwnProperty,K=Object.prototype.propertyIsEnumerable,N=(t,e,o)=>e in t?D(t,e,{enumerable:!0,configurable:!0,writable:!0,value:o}):t[e]=o,W=(t,e)=>{for(var o in e||(e={}))k.call(e,o)&&N(t,o,e[o]);if(S)for(var o of S(e))K.call(e,o)&&N(t,o,e[o]);return t};class I extends((0,t.DataApiMixin)(t.Editor)){constructor(o,i={}){const r=Object.keys(o),s=0===r.length||"string"==typeof o[r[0]];if(s&&void 0!==i.initialData)throw new e.CKEditorError("editor-create-initial-data",null);if(super(i),this._registeredRootsAttributesKeys=new Set,this.sourceElements=s?{}:o,void 0===this.config.get("initialData")){const t={};for(const i of r)t[i]=U(n=o[i])?(0,e.getDataFromElement)(n):n;this.config.set("initialData",t)}var n;if(!s)for(const e of r)(0,t.secureSourceElement)(this,o[e]);for(const t of r)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(!r.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,r]of Object.entries(i))null!==r&&e.setAttribute(o,r,t)}}))}))}const l={shouldToolbarGroupWhenFull:!this.config.get("toolbar.shouldNotGroupWhenFull"),editableElements:s?void 0:o},d=new c(this.locale,this.editing.view,r,l);this.ui=new a(this,d),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)}}))}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:r=!1}={}){const s=this.data,n=this._registeredRootsAttributesKeys;function a(r){const a=r.addRoot(t,i);e&&r.insert(s.parse(e,a),a,0);for(const t of Object.keys(o))n.add(t),r.setAttribute(t,o[t],a)}r?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=W({},t),P(o,A({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 r of e)t[o][r]=i.hasAttribute(r)?i.getAttribute(r):null}return t}static create(t,o={}){return new Promise((i=>{for(const o of Object.values(t))if(U(o)&&"TEXTAREA"===o.tagName)throw new e.CKEditorError("editor-wrong-element",null);const r=new this(t,o);i(r.initPlugins().then((()=>r.ui.init())).then((()=>(r._verifyRootsWithInitialData(),r.data.init(r.config.get("initialData"))))).then((()=>r.fire("ready"))).then((()=>r)))}))}_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 U(t){return _(t)}I.Context=t.Context,I.EditorWatchdog=r.EditorWatchdog,I.ContextWatchdog=r.ContextWatchdog})(),(window.CKEditor5=window.CKEditor5||{}).editorMultiRoot=i})();
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": "37.1.0",
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": "^37.1.0",
14
+ "ckeditor5": "^38.0.0-rc.0",
15
15
  "lodash-es": "^4.17.15"
16
16
  },
17
17
  "devDependencies": {
18
- "@ckeditor/ckeditor5-basic-styles": "^37.1.0",
19
- "@ckeditor/ckeditor5-core": "^37.1.0",
18
+ "@ckeditor/ckeditor5-basic-styles": "^38.0.0-rc.0",
19
+ "@ckeditor/ckeditor5-core": "^38.0.0-rc.0",
20
20
  "@ckeditor/ckeditor5-dev-utils": "^37.0.0",
21
- "@ckeditor/ckeditor5-engine": "^37.1.0",
22
- "@ckeditor/ckeditor5-enter": "^37.1.0",
23
- "@ckeditor/ckeditor5-heading": "^37.1.0",
24
- "@ckeditor/ckeditor5-paragraph": "^37.1.0",
25
- "@ckeditor/ckeditor5-theme-lark": "^37.1.0",
26
- "@ckeditor/ckeditor5-typing": "^37.1.0",
27
- "@ckeditor/ckeditor5-ui": "^37.1.0",
28
- "@ckeditor/ckeditor5-undo": "^37.1.0",
29
- "@ckeditor/ckeditor5-utils": "^37.1.0",
30
- "@ckeditor/ckeditor5-watchdog": "^37.1.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"
@@ -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
  *
@@ -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);
@@ -145,6 +168,28 @@ export default class MultiRootEditor extends DataApiMixin(Editor) {
145
168
  }
146
169
  }
147
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' });
148
193
  }
149
194
  /**
150
195
  * Destroys the editor instance, releasing all resources used by it.
@@ -405,6 +450,86 @@ export default class MultiRootEditor extends DataApiMixin(Editor) {
405
450
  }
406
451
  return rootsAttributes;
407
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
+ }
408
533
  /**
409
534
  * Creates a new multi-root editor instance.
410
535
  *
@@ -570,14 +695,15 @@ export default class MultiRootEditor extends DataApiMixin(Editor) {
570
695
  for (const rootName of this.model.document.getRootNames()) {
571
696
  if (!(rootName in initialData)) {
572
697
  /**
573
- * Editor roots do not match {@link module:core/editor/editorconfig~EditorConfig#initialData `initialData` configuration}.
698
+ * Editor roots do not match the
699
+ * {@link module:core/editor/editorconfig~EditorConfig#initialData `initialData` configuration}.
574
700
  *
575
- * This can happen for one of the two reasons:
701
+ * This may happen for one of the two reasons:
576
702
  *
577
- * * Configuration error. `sourceElementsOrData` parameter in
703
+ * * Configuration error. The `sourceElementsOrData` parameter in
578
704
  * {@link module:editor-multi-root/multirooteditor~MultiRootEditor.create `MultiRootEditor.create()`} contains different
579
705
  * roots than {@link module:core/editor/editorconfig~EditorConfig#initialData `initialData` configuration}.
580
- * * As the editor was initialized, {@link module:core/editor/editorconfig~EditorConfig#initialData `initialData`}
706
+ * * As the editor was initialized, the {@link module:core/editor/editorconfig~EditorConfig#initialData `initialData`}
581
707
  * configuration value or the state of the editor roots has been changed.
582
708
  *
583
709
  * @error multi-root-editor-root-initial-data-mismatch