@ckeditor/ckeditor5-editor-multi-root 38.2.0-alpha.1 → 39.0.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 CHANGED
@@ -4,7 +4,6 @@ CKEditor 5 multi-root editor implementation
4
4
  [![npm version](https://badge.fury.io/js/%40ckeditor%2Fckeditor5-editor-multi-root.svg)](https://www.npmjs.com/package/@ckeditor/ckeditor5-editor-multi-root)
5
5
  [![Coverage Status](https://coveralls.io/repos/github/ckeditor/ckeditor5/badge.svg?branch=master)](https://coveralls.io/github/ckeditor/ckeditor5?branch=master)
6
6
  [![Build Status](https://travis-ci.com/ckeditor/ckeditor5.svg?branch=master)](https://app.travis-ci.com/github/ckeditor/ckeditor5)
7
- ![Dependency Status](https://img.shields.io/librariesio/release/npm/@ckeditor/ckeditor5-editor-multi-root)
8
7
 
9
8
  The multi-root editor implementation for CKEditor 5. Read more about the [multi-root editor build](https://ckeditor.com/docs/ckeditor5/latest/installation/getting-started/predefined-builds.html#multi-root-editor) and see the [demo](https://ckeditor.com/docs/ckeditor5/latest/examples/builds/multi-root-editor.html).
10
9
 
@@ -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:()=>A});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 d 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 c=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 E=Object.prototype.toString;const v=function(t){return E.call(t)};var w=u?u.toStringTag:void 0;const R=function(t){return null==t?void 0===t?"[object Undefined]":"[object Null]":w&&w in Object(t)?y(t):v(t)};const p=function(t,e){return function(o){return t(e(o))}}(Object.getPrototypeOf,Object);var O=Function.prototype,j=Object.prototype,_=O.toString,C=j.hasOwnProperty,k=_.call(Object);const x=function(t){if(!c(t)||"[object Object]"!=R(t))return!1;var e=p(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 T=function(t){return c(t)&&1===t.nodeType&&!x(t)};class A 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]=D(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 c={shouldToolbarGroupWhenFull:!this.config.get("toolbar.shouldNotGroupWhenFull"),editableElements:r?void 0:o},l=new d(this.locale,this.editing.view,s,c);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 o of this.model.document.getRootNames())e[o]=this.data.get({...t,rootName: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(D(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 D(t){return T(t)}A.Context=t.Context,A.EditorWatchdog=s.EditorWatchdog,A.ContextWatchdog=s.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:()=>D});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])}const o=this.editor.editing.view,i=o.document.getRoot(t.name);e&&(i.placeholder=e),(0,n.enablePlaceholder)({view:o,element:i,isDirectHost:!1,keepOnFocus:!0})}}class d 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 l=function(t){return null!=t&&"object"==typeof t};const c="object"==typeof global&&global&&global.Object===Object&&global;var h="object"==typeof self&&self&&self.Object===Object&&self;const u=(c||h||Function("return this")()).Symbol;var f=Object.prototype,m=f.hasOwnProperty,b=f.toString,g=u?u.toStringTag:void 0;const y=function(t){var e=m.call(t,g),o=t[g];try{t[g]=void 0;var i=!0}catch(t){}var s=b.call(t);return i&&(e?t[g]=o:delete t[g]),s};var E=Object.prototype.toString;const R=function(t){return E.call(t)};var v=u?u.toStringTag:void 0;const p=function(t){return null==t?void 0===t?"[object Undefined]":"[object Null]":v&&v in Object(t)?y(t):R(t)};const w=function(t,e){return function(o){return t(e(o))}}(Object.getPrototypeOf,Object);var O=Function.prototype,j=Object.prototype,_=O.toString,C=j.hasOwnProperty,k=_.call(Object);const A=function(t){if(!l(t)||"[object Object]"!=p(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 x=function(t){return l(t)&&1===t.nodeType&&!A(t)};class D 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&&Object.keys(i.initialData).length>0)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]=T(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("lazyRoots"))for(const t of this.config.get("lazyRoots")){this.model.document.createRoot("$root",t)._isLoaded=!1}if(this.config.get("rootsAttributes")){const t=this.config.get("rootsAttributes");for(const[o,i]of Object.entries(t)){if(!this.model.document.getRoot(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 l={shouldToolbarGroupWhenFull:!this.config.get("toolbar.shouldNotGroupWhenFull"),editableElements:r?void 0:o},c=new d(this.locale,this.editing.view,s,l);this.ui=new a(this,c),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;if(this._readOnlyRootLocks.has(e.rootName)){o=!0;break}}o&&(t.return=!1,t.stop())}),{priority:"high"}),this.decorate("loadRoot"),this.on("loadRoot",((t,[o])=>{const i=this.model.document.getRoot(o);if(!i)throw new e.CKEditorError("multi-root-editor-load-root-no-root",this,{rootName:o});i._isLoaded&&((0,e.logWarning)("multi-root-editor-load-root-already-loaded"),t.stop())}),{priority:"highest"})}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}loadRoot(t,{data:e="",attributes:o={}}={}){const i=this.model.document.getRoot(t);this.model.enqueueChange({isUndoable:!1},(t=>{e&&t.insert(this.data.parse(e,i),i,0);for(const e of Object.keys(o))this._registeredRootsAttributesKeys.add(e),t.setAttribute(e,o[e],i);i._isLoaded=!0,this.model.document.differ._bufferRootLoad(i)}))}getFullData(t){const e={};for(const o of this.model.document.getRootNames())e[o]=this.data.get({...t,rootName:o});return e}getRootsAttributes(){const t={};for(const e of this.model.document.getRootNames())t[e]=this.getRootAttributes(e);return t}getRootAttributes(t){const e={},o=this.model.document.getRoot(t);for(const t of this._registeredRootsAttributesKeys)e[t]=o.hasAttribute(t)?o.getAttribute(t):null;return e}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(T(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 T(t){return x(t)}D.Context=t.Context,D.EditorWatchdog=s.EditorWatchdog,D.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": "38.2.0-alpha.1",
3
+ "version": "39.0.0",
4
4
  "description": "Multi-root editor implementation for CKEditor 5.",
5
5
  "keywords": [
6
6
  "ckeditor",
@@ -10,10 +10,9 @@
10
10
  "ckeditor5-dll"
11
11
  ],
12
12
  "main": "src/index.js",
13
- "type": "module",
14
13
  "dependencies": {
15
- "ckeditor5": "38.2.0-alpha.1",
16
- "lodash-es": "^4.17.15"
14
+ "ckeditor5": "39.0.0",
15
+ "lodash-es": "4.17.21"
17
16
  },
18
17
  "engines": {
19
18
  "node": ">=16.0.0",
@@ -2,7 +2,7 @@
2
2
  * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
4
  */
5
- import { type RootAttributes } from './multirooteditor.js';
5
+ import { type RootAttributes } from './multirooteditor';
6
6
  declare module '@ckeditor/ckeditor5-core' {
7
7
  interface EditorConfig {
8
8
  /**
@@ -76,5 +76,15 @@ declare module '@ckeditor/ckeditor5-core' {
76
76
  * ```
77
77
  */
78
78
  rootsAttributes?: Record<string, RootAttributes>;
79
+ /**
80
+ * List of names of all the roots that exist in the document but are not initially loaded by the editor.
81
+ *
82
+ * These roots can be loaded at any time after the editor has been initialized, using
83
+ * {@link module:editor-multi-root/multirooteditor~MultiRootEditor#loadRoot `MultiRootEditor#lazyRoot()`}.
84
+ *
85
+ * This is useful for handling big documents that contain hundreds of roots, or contain very large roots, which may have
86
+ * impact editor performance if loaded all together.
87
+ */
88
+ lazyRoots?: Array<string>;
79
89
  }
80
90
  }
package/src/index.d.ts CHANGED
@@ -5,6 +5,6 @@
5
5
  /**
6
6
  * @module editor-multi-root
7
7
  */
8
- export { default as MultiRootEditor } from './multirooteditor.js';
9
- export type { RootAttributes } from './multirooteditor.js';
10
- import './augmentation.js';
8
+ export { default as MultiRootEditor } from './multirooteditor';
9
+ export type { RootAttributes } from './multirooteditor';
10
+ import './augmentation';
package/src/index.js CHANGED
@@ -5,5 +5,5 @@
5
5
  /**
6
6
  * @module editor-multi-root
7
7
  */
8
- export { default as MultiRootEditor } from './multirooteditor.js';
9
- import './augmentation.js';
8
+ export { default as MultiRootEditor } from './multirooteditor';
9
+ import './augmentation';
@@ -5,11 +5,12 @@
5
5
  /**
6
6
  * @module editor-multi-root/multirooteditor
7
7
  */
8
- import { Editor, Context, type EditorConfig } from 'ckeditor5/src/core.js';
9
- import { ContextWatchdog, EditorWatchdog } from 'ckeditor5/src/watchdog.js';
10
- import MultiRootEditorUI from './multirooteditorui.js';
11
- import { type RootElement } from 'ckeditor5/src/engine.js';
12
- declare const MultiRootEditor_base: import("ckeditor5/src/utils.js").Mixed<typeof Editor, import("ckeditor5/src/core.js").DataApi>;
8
+ import { Editor, Context, type EditorConfig } from 'ckeditor5/src/core';
9
+ import { type DecoratedMethodEvent } from 'ckeditor5/src/utils';
10
+ import { ContextWatchdog, EditorWatchdog } from 'ckeditor5/src/watchdog';
11
+ import MultiRootEditorUI from './multirooteditorui';
12
+ import { type RootElement } from 'ckeditor5/src/engine';
13
+ declare const MultiRootEditor_base: import("ckeditor5/src/utils").Mixed<typeof Editor, import("ckeditor5/src/core").DataApi>;
13
14
  /**
14
15
  * The {@glink installation/getting-started/predefined-builds#multi-root-editor multi-root editor} implementation.
15
16
  *
@@ -241,6 +242,31 @@ export default class MultiRootEditor extends MultiRootEditor_base {
241
242
  * @returns The DOM element that was detached. You may want to remove it from your application DOM structure.
242
243
  */
243
244
  detachEditable(root: RootElement): HTMLElement;
245
+ /**
246
+ * Loads a root that has been previously declared in {@link module:core/editor/editorconfig~EditorConfig#lazyRoots `lazyRoots`}
247
+ * configuration option.
248
+ *
249
+ * Only roots specified in the editor config can be loaded. A root cannot be loaded multiple times. A root cannot be unloaded and
250
+ * loading a root cannot be reverted using the undo feature.
251
+ *
252
+ * When a root becomes loaded, it will be treated by the editor as though it was just added. This, among other, means that all
253
+ * related events and mechanisms will be fired, including {@link ~MultiRootEditor#event:addRoot `addRoot` event},
254
+ * {@link module:engine/model/document~Document#event:change `model.Document` `change` event}, model post-fixers and conversion.
255
+ *
256
+ * Until the root becomes loaded, all above mechanisms are suppressed.
257
+ *
258
+ * This method is {@link module:utils/observablemixin~Observable#decorate decorated}.
259
+ *
260
+ * When this method is used in real-time collaboration environment, its effects become asynchronous as the editor will first synchronize
261
+ * with the remote editing session, before the root is added to the editor.
262
+ *
263
+ * If the root has been already loaded by any other client, the additional data passed in `loadRoot()` parameters will be ignored.
264
+ *
265
+ * @param rootName Name of the root to load.
266
+ * @param options Additional options for the loaded root.
267
+ * @fires loadRoot
268
+ */
269
+ loadRoot(rootName: string, { data, attributes }?: LoadRootOptions): void;
244
270
  /**
245
271
  * Returns the document data for all attached roots.
246
272
  *
@@ -253,12 +279,23 @@ export default class MultiRootEditor extends MultiRootEditor_base {
253
279
  */
254
280
  getFullData(options?: Record<string, unknown>): Record<string, string>;
255
281
  /**
256
- * Returns currently set roots attributes for attributes specified in
257
- * {@link module:core/editor/editorconfig~EditorConfig#rootsAttributes `rootsAttributes`} configuration option.
282
+ * Returns attributes for all attached roots.
283
+ *
284
+ * Note: only attributes specified in {@link module:core/editor/editorconfig~EditorConfig#rootsAttributes `rootsAttributes`}
285
+ * configuration option will be returned.
258
286
  *
259
287
  * @returns Object with roots attributes. Keys are roots names, while values are attributes set on given root.
260
288
  */
261
289
  getRootsAttributes(): Record<string, RootAttributes>;
290
+ /**
291
+ * Returns attributes for the specified root.
292
+ *
293
+ * Note: only attributes specified in {@link module:core/editor/editorconfig~EditorConfig#rootsAttributes `rootsAttributes`}
294
+ * configuration option will be returned.
295
+ *
296
+ * @param rootName
297
+ */
298
+ getRootAttributes(rootName: string): RootAttributes;
262
299
  /**
263
300
  * Switches given editor root to the read-only mode.
264
301
  *
@@ -494,6 +531,16 @@ export type DetachRootEvent = {
494
531
  name: 'detachRoot';
495
532
  args: [root: RootElement];
496
533
  };
534
+ /**
535
+ * Event fired when {@link ~MultiRootEditor#loadRoot} method is called.
536
+ *
537
+ * The {@link ~MultiRootEditor#loadRoot default action of that method} is implemented as a
538
+ * listener to this event, so it can be fully customized by the features.
539
+ *
540
+ * @eventName ~MultiRootEditor#loadRoot
541
+ * @param args The arguments passed to the original method.
542
+ */
543
+ export type LoadRootEvent = DecoratedMethodEvent<MultiRootEditor, 'loadRoot'>;
497
544
  /**
498
545
  * Additional options available when adding a root.
499
546
  */
@@ -515,6 +562,10 @@ export type AddRootOptions = {
515
562
  */
516
563
  isUndoable?: boolean;
517
564
  };
565
+ /**
566
+ * Additional options available when loading a root.
567
+ */
568
+ export type LoadRootOptions = Omit<AddRootOptions, 'elementName' | 'isUndoable'>;
518
569
  /**
519
570
  * Attributes set on a model root element.
520
571
  */
@@ -5,11 +5,11 @@
5
5
  /**
6
6
  * @module editor-multi-root/multirooteditor
7
7
  */
8
- import { Editor, Context, DataApiMixin, secureSourceElement } from 'ckeditor5/src/core.js';
9
- import { CKEditorError, getDataFromElement, setDataInElement } from 'ckeditor5/src/utils.js';
10
- import { ContextWatchdog, EditorWatchdog } from 'ckeditor5/src/watchdog.js';
11
- import MultiRootEditorUI from './multirooteditorui.js';
12
- import MultiRootEditorUIView from './multirooteditoruiview.js';
8
+ import { Editor, Context, DataApiMixin, secureSourceElement } from 'ckeditor5/src/core';
9
+ import { CKEditorError, getDataFromElement, setDataInElement, logWarning } from 'ckeditor5/src/utils';
10
+ import { ContextWatchdog, EditorWatchdog } from 'ckeditor5/src/watchdog';
11
+ import MultiRootEditorUI from './multirooteditorui';
12
+ import MultiRootEditorUIView from './multirooteditoruiview';
13
13
  import { isElement as _isElement } from 'lodash-es';
14
14
  /**
15
15
  * The {@glink installation/getting-started/predefined-builds#multi-root-editor multi-root editor} implementation.
@@ -56,7 +56,7 @@ export default class MultiRootEditor extends DataApiMixin(Editor) {
56
56
  constructor(sourceElementsOrData, config = {}) {
57
57
  const rootNames = Object.keys(sourceElementsOrData);
58
58
  const sourceIsData = rootNames.length === 0 || typeof sourceElementsOrData[rootNames[0]] === 'string';
59
- if (sourceIsData && config.initialData !== undefined) {
59
+ if (sourceIsData && config.initialData !== undefined && Object.keys(config.initialData).length > 0) {
60
60
  // Documented in core/editor/editorconfig.jsdoc.
61
61
  // eslint-disable-next-line ckeditor5-rules/ckeditor-error-message
62
62
  throw new CKEditorError('editor-create-initial-data', null);
@@ -113,10 +113,16 @@ export default class MultiRootEditor extends DataApiMixin(Editor) {
113
113
  // Create root and `UIView` element for each editable container.
114
114
  this.model.document.createRoot('$root', rootName);
115
115
  }
116
+ if (this.config.get('lazyRoots')) {
117
+ for (const rootName of this.config.get('lazyRoots')) {
118
+ const root = this.model.document.createRoot('$root', rootName);
119
+ root._isLoaded = false;
120
+ }
121
+ }
116
122
  if (this.config.get('rootsAttributes')) {
117
123
  const rootsAttributes = this.config.get('rootsAttributes');
118
124
  for (const [rootName, attributes] of Object.entries(rootsAttributes)) {
119
- if (!rootNames.includes(rootName)) {
125
+ if (!this.model.document.getRoot(rootName)) {
120
126
  /**
121
127
  * Trying to set attributes on a non-existing root.
122
128
  *
@@ -177,8 +183,8 @@ export default class MultiRootEditor extends DataApiMixin(Editor) {
177
183
  }
178
184
  let selectionInReadOnlyRoot = false;
179
185
  for (const range of selection.getRanges()) {
180
- const rootName = range.root.rootName;
181
- if (this._readOnlyRootLocks.has(rootName)) {
186
+ const root = range.root;
187
+ if (this._readOnlyRootLocks.has(root.rootName)) {
182
188
  selectionInReadOnlyRoot = true;
183
189
  break;
184
190
  }
@@ -190,6 +196,27 @@ export default class MultiRootEditor extends DataApiMixin(Editor) {
190
196
  evt.stop();
191
197
  }
192
198
  }, { priority: 'high' });
199
+ this.decorate('loadRoot');
200
+ this.on('loadRoot', (evt, [rootName]) => {
201
+ const root = this.model.document.getRoot(rootName);
202
+ if (!root) {
203
+ /**
204
+ * The root to load does not exist.
205
+ *
206
+ * @error multi-root-editor-load-root-no-root
207
+ */
208
+ throw new CKEditorError('multi-root-editor-load-root-no-root', this, { rootName });
209
+ }
210
+ if (root._isLoaded) {
211
+ /**
212
+ * The root to load was already loaded before. The `loadRoot()` call has no effect.
213
+ *
214
+ * @error multi-root-editor-load-root-already-loaded
215
+ */
216
+ logWarning('multi-root-editor-load-root-already-loaded');
217
+ evt.stop();
218
+ }
219
+ }, { priority: 'highest' });
193
220
  }
194
221
  /**
195
222
  * Destroys the editor instance, releasing all resources used by it.
@@ -415,6 +442,45 @@ export default class MultiRootEditor extends DataApiMixin(Editor) {
415
442
  this.ui.view.removeEditable(rootName);
416
443
  return editable.element;
417
444
  }
445
+ /**
446
+ * Loads a root that has been previously declared in {@link module:core/editor/editorconfig~EditorConfig#lazyRoots `lazyRoots`}
447
+ * configuration option.
448
+ *
449
+ * Only roots specified in the editor config can be loaded. A root cannot be loaded multiple times. A root cannot be unloaded and
450
+ * loading a root cannot be reverted using the undo feature.
451
+ *
452
+ * When a root becomes loaded, it will be treated by the editor as though it was just added. This, among other, means that all
453
+ * related events and mechanisms will be fired, including {@link ~MultiRootEditor#event:addRoot `addRoot` event},
454
+ * {@link module:engine/model/document~Document#event:change `model.Document` `change` event}, model post-fixers and conversion.
455
+ *
456
+ * Until the root becomes loaded, all above mechanisms are suppressed.
457
+ *
458
+ * This method is {@link module:utils/observablemixin~Observable#decorate decorated}.
459
+ *
460
+ * When this method is used in real-time collaboration environment, its effects become asynchronous as the editor will first synchronize
461
+ * with the remote editing session, before the root is added to the editor.
462
+ *
463
+ * If the root has been already loaded by any other client, the additional data passed in `loadRoot()` parameters will be ignored.
464
+ *
465
+ * @param rootName Name of the root to load.
466
+ * @param options Additional options for the loaded root.
467
+ * @fires loadRoot
468
+ */
469
+ loadRoot(rootName, { data = '', attributes = {} } = {}) {
470
+ // `root` will be defined as it is guaranteed by a check in a higher priority callback.
471
+ const root = this.model.document.getRoot(rootName);
472
+ this.model.enqueueChange({ isUndoable: false }, writer => {
473
+ if (data) {
474
+ writer.insert(this.data.parse(data, root), root, 0);
475
+ }
476
+ for (const key of Object.keys(attributes)) {
477
+ this._registeredRootsAttributesKeys.add(key);
478
+ writer.setAttribute(key, attributes[key], root);
479
+ }
480
+ root._isLoaded = true;
481
+ this.model.document.differ._bufferRootLoad(root);
482
+ });
483
+ }
418
484
  /**
419
485
  * Returns the document data for all attached roots.
420
486
  *
@@ -433,23 +499,36 @@ export default class MultiRootEditor extends DataApiMixin(Editor) {
433
499
  return data;
434
500
  }
435
501
  /**
436
- * Returns currently set roots attributes for attributes specified in
437
- * {@link module:core/editor/editorconfig~EditorConfig#rootsAttributes `rootsAttributes`} configuration option.
502
+ * Returns attributes for all attached roots.
503
+ *
504
+ * Note: only attributes specified in {@link module:core/editor/editorconfig~EditorConfig#rootsAttributes `rootsAttributes`}
505
+ * configuration option will be returned.
438
506
  *
439
507
  * @returns Object with roots attributes. Keys are roots names, while values are attributes set on given root.
440
508
  */
441
509
  getRootsAttributes() {
442
510
  const rootsAttributes = {};
443
- const keys = Array.from(this._registeredRootsAttributesKeys);
444
511
  for (const rootName of this.model.document.getRootNames()) {
445
- rootsAttributes[rootName] = {};
446
- const root = this.model.document.getRoot(rootName);
447
- for (const key of keys) {
448
- rootsAttributes[rootName][key] = root.hasAttribute(key) ? root.getAttribute(key) : null;
449
- }
512
+ rootsAttributes[rootName] = this.getRootAttributes(rootName);
450
513
  }
451
514
  return rootsAttributes;
452
515
  }
516
+ /**
517
+ * Returns attributes for the specified root.
518
+ *
519
+ * Note: only attributes specified in {@link module:core/editor/editorconfig~EditorConfig#rootsAttributes `rootsAttributes`}
520
+ * configuration option will be returned.
521
+ *
522
+ * @param rootName
523
+ */
524
+ getRootAttributes(rootName) {
525
+ const rootAttributes = {};
526
+ const root = this.model.document.getRoot(rootName);
527
+ for (const key of this._registeredRootsAttributesKeys) {
528
+ rootAttributes[key] = root.hasAttribute(key) ? root.getAttribute(key) : null;
529
+ }
530
+ return rootAttributes;
531
+ }
453
532
  /**
454
533
  * Switches given editor root to the read-only mode.
455
534
  *
@@ -5,9 +5,9 @@
5
5
  /**
6
6
  * @module editor-multi-root/multirooteditorui
7
7
  */
8
- import { type Editor } from 'ckeditor5/src/core.js';
9
- import { EditorUI, type InlineEditableUIView } from 'ckeditor5/src/ui.js';
10
- import type MultiRootEditorUIView from './multirooteditoruiview.js';
8
+ import { type Editor } from 'ckeditor5/src/core';
9
+ import { EditorUI, type InlineEditableUIView } from 'ckeditor5/src/ui';
10
+ import type MultiRootEditorUIView from './multirooteditoruiview';
11
11
  /**
12
12
  * The multi-root editor UI class.
13
13
  */
@@ -64,7 +64,7 @@ export default class MultiRootEditorUI extends EditorUI {
64
64
  */
65
65
  private _initToolbar;
66
66
  /**
67
- * Enables the placeholder text on a given editable, if the placeholder was configured.
67
+ * Enables the placeholder text on a given editable.
68
68
  *
69
69
  * @param editable Editable on which the placeholder should be set.
70
70
  * @param placeholder Placeholder for the editable element. If not set, placeholder value from the
@@ -2,8 +2,8 @@
2
2
  * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
4
  */
5
- import { EditorUI } from 'ckeditor5/src/ui.js';
6
- import { enablePlaceholder } from 'ckeditor5/src/engine.js';
5
+ import { EditorUI } from 'ckeditor5/src/ui';
6
+ import { enablePlaceholder } from 'ckeditor5/src/engine';
7
7
  /**
8
8
  * The multi-root editor UI class.
9
9
  */
@@ -140,7 +140,7 @@ export default class MultiRootEditorUI extends EditorUI {
140
140
  this.addToolbar(view.toolbar);
141
141
  }
142
142
  /**
143
- * Enables the placeholder text on a given editable, if the placeholder was configured.
143
+ * Enables the placeholder text on a given editable.
144
144
  *
145
145
  * @param editable Editable on which the placeholder should be set.
146
146
  * @param placeholder Placeholder for the editable element. If not set, placeholder value from the
@@ -153,15 +153,14 @@ export default class MultiRootEditorUI extends EditorUI {
153
153
  placeholder = typeof configPlaceholder === 'string' ? configPlaceholder : configPlaceholder[editable.name];
154
154
  }
155
155
  }
156
- if (!placeholder) {
157
- return;
158
- }
159
156
  const editingView = this.editor.editing.view;
160
157
  const editingRoot = editingView.document.getRoot(editable.name);
158
+ if (placeholder) {
159
+ editingRoot.placeholder = placeholder;
160
+ }
161
161
  enablePlaceholder({
162
162
  view: editingView,
163
163
  element: editingRoot,
164
- text: placeholder,
165
164
  isDirectHost: false,
166
165
  keepOnFocus: true
167
166
  });
@@ -5,9 +5,9 @@
5
5
  /**
6
6
  * @module editor-multi-root/multirooteditoruiview
7
7
  */
8
- import { EditorUIView, InlineEditableUIView, ToolbarView } from 'ckeditor5/src/ui.js';
9
- import type { Locale } from 'ckeditor5/src/utils.js';
10
- import type { View } from 'ckeditor5/src/engine.js';
8
+ import { EditorUIView, InlineEditableUIView, ToolbarView } from 'ckeditor5/src/ui';
9
+ import type { Locale } from 'ckeditor5/src/utils';
10
+ import type { View } from 'ckeditor5/src/engine';
11
11
  /**
12
12
  * The multi-root editor UI view. It is a virtual view providing an inline
13
13
  * {@link module:editor-multi-root/multirooteditoruiview~MultiRootEditorUIView#editable} and a
@@ -5,7 +5,7 @@
5
5
  /**
6
6
  * @module editor-multi-root/multirooteditoruiview
7
7
  */
8
- import { EditorUIView, InlineEditableUIView, ToolbarView } from 'ckeditor5/src/ui.js';
8
+ import { EditorUIView, InlineEditableUIView, ToolbarView } from 'ckeditor5/src/ui';
9
9
  /**
10
10
  * The multi-root editor UI view. It is a virtual view providing an inline
11
11
  * {@link module:editor-multi-root/multirooteditoruiview~MultiRootEditorUIView#editable} and a