ckeditor5-phoenix 1.0.8 → 1.1.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/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +59 -39
- package/dist/index.mjs.map +1 -1
- package/dist/src/hooks/editor/editor.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/hooks/editor/editor.test.ts +72 -0
- package/src/hooks/editor/editor.ts +44 -1
- package/src/shared/hook.ts +3 -3
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var P=Object.create;var
|
|
1
|
+
"use strict";var P=Object.create;var y=Object.defineProperty;var v=Object.getOwnPropertyDescriptor;var I=Object.getOwnPropertyNames;var A=Object.getPrototypeOf,C=Object.prototype.hasOwnProperty;var S=(n,t,e,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of I(t))!C.call(n,o)&&o!==e&&y(n,o,{get:()=>t[o],enumerable:!(i=v(t,o))||i.enumerable});return n};var m=(n,t,e)=>(e=n!=null?P(A(n)):{},S(t||!n||!n.__esModule?y(e,"default",{value:n,enumerable:!0}):e,n));Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function p(n,t){let e=null;return(...i)=>{e&&clearTimeout(e),e=setTimeout(()=>{t(...i)},n)}}class g{el;liveSocket;pushEvent;pushEventTo;handleEvent}function f(n){return{mounted(){const t=new n;this.el.instance=t,t.el=this.el,t.liveSocket=this.liveSocket,t.pushEvent=(e,i,o)=>this.pushEvent?.(e,i,o),t.pushEventTo=(e,i,o,r)=>this.pushEventTo?.(e,i,o,r),t.handleEvent=(e,i)=>this.handleEvent?.(e,i),t.mounted?.()},beforeUpdate(){this.el.instance.beforeUpdate?.()},destroyed(){this.el.instance.destroyed?.()},disconnected(){this.el.instance.disconnected?.()},reconnected(){this.el.instance.reconnected?.()}}}function w(n,t){const e=Object.entries(n).map(([i,o])=>[i,t(o,i)]);return Object.fromEntries(e)}function H(n){if(n===null)return null;const t=Number.parseInt(n,10);return Number.isNaN(t)?null:t}class l{static the=new l;editors=new Map;callbacks=new Map;constructor(){}execute(t,e){const{callbacks:i,editors:o}=this,r=o.get(t);return r?Promise.resolve(e(r)):new Promise(s=>{const a=async u=>s(await e(u));this.callbacks.has(t)||i.set(t,[]),i.set(t,[...i.get(t),a])})}register(t,e){const{editors:i,callbacks:o}=this,r=o.get(t);if(i.has(t))throw new Error(`Editor with ID "${t}" is already registered.`);i.set(t,e),r&&(r.forEach(s=>s(e)),o.delete(t)),this.editors.size===1&&this.register(null,e)}unregister(t){const{editors:e,callbacks:i}=this;if(!e.has(t))throw new Error(`Editor with ID "${t}" is not registered.`);t&&this.editors.get(null)===e.get(t)&&this.unregister(null),e.delete(t),i.delete(t)}getEditors(){return Array.from(this.editors.values())}hasEditor(t){return this.editors.has(t)}waitForEditor(t){return this.execute(t,e=>e)}async destroyAllEditors(){const t=Array.from(this.editors.values()).map(e=>e.destroy());this.editors.clear(),this.callbacks.clear(),await Promise.all(t)}}class R extends g{mountedPromise=null;get attrs(){const t={editableId:this.el.getAttribute("id"),editorId:this.el.getAttribute("data-cke-editor-id")||null,rootName:this.el.getAttribute("data-cke-editable-root-name"),initialValue:this.el.getAttribute("data-cke-editable-initial-value")||""};return Object.defineProperty(this,"attrs",{value:t,writable:!1,configurable:!1,enumerable:!0}),t}async mounted(){const{editableId:t,editorId:e,rootName:i,initialValue:o}=this.attrs,r=this.el.querySelector(`#${t}_input`);this.mountedPromise=l.the.execute(e,s=>{const{ui:a,editing:u,model:h}=s;if(h.document.getRoot(i))return;s.addRoot(i,{isUndoable:!1,data:o});const c=this.el.querySelector("[data-cke-editable-content]"),d=a.view.createEditable(i,c);a.addEditable(d),u.view.forceRender(),r&&$(r,s,i)})}async destroyed(){const{editorId:t,rootName:e}=this.attrs;this.el.style.display="none",await this.mountedPromise,this.mountedPromise=null,await l.the.execute(t,i=>{const o=i.model.document.getRoot(e);o&&(i.detachEditable(o),i.detachRoot(e,!1))})}}const V=f(R);function $(n,t,e){const i=()=>{n.value=t.getData({rootName:e})};t.model.document.on("change:data",p(100,i)),i()}function E(n){return["inline","classic","balloon","decoupled"].includes(n)}async function O(n){const t=await import("ckeditor5"),i={inline:t.InlineEditor,balloon:t.BalloonEditor,classic:t.ClassicEditor,decoupled:t.DecoupledEditor,multiroot:t.MultiRootEditor}[n];if(!i)throw new Error(`Unsupported editor type: ${n}`);return i}async function T(n){const t=await import("ckeditor5");let e=null;const i=n.map(async o=>{const{[o]:r}=t;if(r)return r;if(!e)try{e=await import("ckeditor5-premium-features")}catch(a){console.error(`Failed to load premium package: ${a}`)}const{[o]:s}=e||{};if(s)return s;throw new Error(`Plugin "${o}" not found in base or premium packages.`)});return Promise.all(i)}function b(n){const t=document.querySelectorAll([`[data-cke-editor-id="${n}"][data-cke-editable-root-name]`,"[data-cke-editable-root-name]:not([data-cke-editor-id])"].join(", "));return Array.from(t).reduce((e,i)=>{const o=i.getAttribute("data-cke-editable-root-name"),r=i.getAttribute("data-cke-editable-initial-value")||"",s=i.querySelector("[data-cke-editable-content]");return!o||!s?e:{...e,[o]:{content:s,initialValue:r}}},Object.create({}))}const k=["inline","classic","balloon","decoupled","multiroot"];function j(n){const t=n.getAttribute("cke-preset");if(!t)throw new Error('CKEditor5 hook requires a "cke-preset" attribute on the element.');const{type:e,config:i,license:o}=JSON.parse(t);if(!e||!i||!o)throw new Error('CKEditor5 hook configuration must include "editor", "config", and "license" properties.');if(!k.includes(e))throw new Error(`Invalid editor type: ${e}. Must be one of: ${k.join(", ")}.`);return{type:e,config:i,license:o}}function N(n,t){const{editing:e}=n;e.view.change(i=>{i.setStyle("height",`${t}px`,e.view.document.getRoot())})}class U extends g{editorPromise=null;get attrs(){const t={editorId:this.el.getAttribute("id"),preset:j(this.el),editableHeight:H(this.el.getAttribute("cke-editable-height"))};return Object.defineProperty(this,"attrs",{value:t,writable:!1,configurable:!1,enumerable:!0}),t}async mounted(){return this.editorPromise=this.createEditor(),l.the.register(this.attrs.editorId,await this.editorPromise),this}async destroyed(){this.el.style.display="none",(await this.editorPromise)?.destroy(),this.editorPromise=null,l.the.unregister(this.attrs.editorId)}async createEditor(){const{preset:t,editorId:e,editableHeight:i}=this.attrs,{type:o,license:r,config:{plugins:s,...a}}=t,u=await O(o),h=M(e,o),c=await u.create(h,{...a,initialData:K(e,o),licenseKey:r.key,plugins:await T(s)});if(this.setupContentSync(e,c),E(o)){const d=document.getElementById(`${e}_input`);d&&D(d,c),i&&N(c,i)}return c}setupContentSync(t,e){const i=()=>{this.pushEvent("ckeditor5:change",{editorId:t,data:x(e)})};e.model.document.on("change:data",p(250,i)),i(),this.handleEvent("ckeditor5:set-data",({data:o})=>{e.setData(o)})}}function x(n){return n.model.document.getRootNames().reduce((e,i)=>(e[i]=n.getData({rootName:i}),e),Object.create({}))}function D(n,t){const e=()=>{n.value=t.getData()};t.model.document.on("change:data",p(250,e)),e()}function M(n,t){if(E(t))return document.getElementById(`${n}_editor`);const e=b(n);return w(e,({content:i})=>i)}function K(n,t){if(t==="decoupled"){const i=b(n).main?.initialValue;if(i)return i}if(E(t))return document.getElementById(n)?.getAttribute("cke-initial-value")||"";const e=b(n);return w(e,({initialValue:i})=>i)}const q=f(U);class B extends g{mountedPromise=null;get attrs(){const t={editorId:this.el.getAttribute("data-cke-editor-id")||null,name:this.el.getAttribute("data-cke-ui-part-name")};return Object.defineProperty(this,"attrs",{value:t,writable:!1,configurable:!1,enumerable:!0}),t}async mounted(){const{editorId:t,name:e}=this.attrs;this.mountedPromise=l.the.execute(t,i=>{const{ui:o}=i,r=F(e),s=o.view[r];if(!s){console.error(`Unknown UI part name: "${e}". Supported names are "toolbar" and "menubar".`);return}this.el.appendChild(s.element)})}async destroyed(){this.el.style.display="none",await this.mountedPromise,this.mountedPromise=null,this.el.innerHTML=""}}function F(n){switch(n){case"toolbar":return"toolbar";case"menubar":return"menuBarView";default:return null}}const _=f(B),L={CKEditor5:q,CKEditable:V,CKUIPart:_};exports.EditorsRegistry=l;exports.Hooks=L;
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":["../src/shared/debounce.ts","../src/shared/hook.ts","../src/shared/map-object-values.ts","../src/shared/parse-int-if-not-null.ts","../src/hooks/editor/editors-registry.ts","../src/hooks/editable.ts","../src/hooks/editor/utils/is-single-editing-like-editor.ts","../src/hooks/editor/utils/load-editor-constructor.ts","../src/hooks/editor/utils/load-editor-plugins.ts","../src/hooks/editor/utils/query-all-editor-editables.ts","../src/hooks/editor/typings.ts","../src/hooks/editor/utils/read-preset-or-throw.ts","../src/hooks/editor/utils/set-editor-editable-height.ts","../src/hooks/editor/editor.ts","../src/hooks/ui-part.ts","../src/hooks/index.ts"],"sourcesContent":["export function debounce<T extends (...args: any[]) => any>(\n delay: number,\n callback: T,\n): (...args: Parameters<T>) => void {\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n\n return (...args: Parameters<T>): void => {\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n\n timeoutId = setTimeout(() => {\n callback(...args);\n }, delay);\n };\n}\n","import type { Hook, LiveSocket } from 'phoenix_live_view';\n\nimport type { RequiredBy } from '../types';\n\n/**\n * An abstract class that provides a class-based API for creating Phoenix LiveView hooks.\n *\n * This class defines the structure and lifecycle methods of a hook, which can be extended\n * to implement custom client-side behavior that integrates with LiveView.\n */\nexport abstract class ClassHook {\n /**\n * The DOM element the hook is attached to.\n * It includes an `instance` property to hold the hook instance.\n */\n el: HTMLElement & { instance: Hook; };\n\n /**\n * The LiveView socket instance, providing connection to the server.\n */\n liveSocket: LiveSocket;\n\n /**\n * Pushes an event from the client to the LiveView server process.\n * @param _event The name of the event.\n * @param _payload The data to send with the event.\n * @param _callback An optional function to be called with the server's reply.\n */\n pushEvent!: (\n _event: string,\n _payload: any,\n _callback?: (reply: any, ref: number) => void,\n ) => void;\n\n /**\n * Pushes an event to another hook on the page.\n * @param _selector The CSS selector of the target element with the hook.\n * @param _event The name of the event.\n * @param _payload The data to send with the event.\n * @param _callback An optional function to be called with the reply.\n */\n pushEventTo!: (\n _selector: string,\n _event: string,\n _payload: any,\n _callback?: (reply: any, ref: number) => void,\n ) => void;\n\n /**\n * Registers a handler for an event pushed from the server.\n * @param _event The name of the event to handle.\n * @param _callback The function to execute when the event is received.\n */\n handleEvent!: (\n _event: string,\n _callback: (payload: any) => void,\n ) => void;\n\n /**\n * Called when the hook has been mounted to the DOM.\n * This is the ideal place for initialization code.\n */\n abstract mounted(): void;\n\n /**\n * Called when the element has been removed from the DOM.\n * Perfect for cleanup tasks.\n */\n abstract destroyed(): void;\n\n /**\n * Called before the element is updated by a LiveView patch.\n */\n beforeUpdate?(): void;\n\n /**\n * Called when the client has disconnected from the server.\n */\n disconnected?(): void;\n\n /**\n * Called when the client has reconnected to the server.\n */\n reconnected?(): void;\n}\n\n/**\n * A factory function that adapts a class-based hook to the object-based API expected by Phoenix LiveView.\n *\n * @param constructor The constructor of the class that extends the `Hook` abstract class.\n */\nexport function makeHook(constructor: new () => ClassHook): RequiredBy<Hook<any>, 'mounted' | 'destroyed'> {\n return {\n /**\n * The mounted lifecycle callback for the LiveView hook object.\n * It creates an instance of the user-defined hook class and sets up the necessary properties and methods.\n */\n mounted(this: any) {\n const instance = new constructor();\n\n this.el.instance = instance;\n\n instance.el = this.el;\n instance.liveSocket = this.liveSocket;\n\n instance.pushEvent = (event, payload, callback) => this.pushEvent(event, payload, callback);\n instance.pushEventTo = (selector, event, payload, callback) => this.pushEventTo(selector, event, payload, callback);\n instance.handleEvent = (event, callback) => this.handleEvent(event, callback);\n\n instance.mounted?.();\n },\n\n /**\n * The beforeUpdate lifecycle callback that delegates to the hook instance.\n */\n beforeUpdate(this: any) {\n this.el.instance.beforeUpdate?.();\n },\n\n /**\n * The destroyed lifecycle callback that delegates to the hook instance.\n */\n destroyed(this: any) {\n this.el.instance.destroyed?.();\n },\n\n /**\n * The disconnected lifecycle callback that delegates to the hook instance.\n */\n disconnected(this: any) {\n this.el.instance.disconnected?.();\n },\n\n /**\n * The reconnected lifecycle callback that delegates to the hook instance.\n */\n reconnected(this: any) {\n this.el.instance.reconnected?.();\n },\n };\n}\n","/**\n * Maps the values of an object using a provided mapper function.\n *\n * @param obj The object whose values will be mapped.\n * @param mapper A function that takes a value and its key, and returns a new value.\n * @template T The type of the original values in the object.\n * @template U The type of the new values in the object.\n * @returns A new object with the same keys as the original, but with values transformed by\n */\nexport function mapObjectValues<T, U>(\n obj: Record<string, T>,\n mapper: (value: T, key: string) => U,\n): Record<string, U> {\n const mappedEntries = Object\n .entries(obj)\n .map(([key, value]) => [key, mapper(value, key)] as const);\n\n return Object.fromEntries(mappedEntries);\n}\n","export function parseIntIfNotNull(value: string | null): number | null {\n if (value === null) {\n return null;\n }\n\n const parsed = Number.parseInt(value, 10);\n\n return Number.isNaN(parsed) ? null : parsed;\n}\n","import type { Editor } from 'ckeditor5';\n\nimport type { EditorId } from './typings';\n\n/**\n * Allows other hooks to communicate with specific editors.\n * It provides a way to register editors and execute callbacks on them when they are available.\n */\nexport class EditorsRegistry {\n static readonly the = new EditorsRegistry();\n\n /**\n * Map of registered editors.\n */\n private readonly editors = new Map<EditorId | null, Editor>();\n\n /**\n * Map of callbacks that are waiting for an editor to be registered.\n */\n private readonly callbacks = new Map<EditorId | null, EditorCallback<any>[]>();\n\n /**\n * Private constructor to enforce singleton pattern.\n */\n private constructor() {}\n\n /**\n * Executes a function on an editor.\n * If the editor is not yet registered, it will wait for it to be registered.\n *\n * @param editorId The ID of the editor.\n * @param fn The function to execute.\n * @returns A promise that resolves with the result of the function.\n */\n execute<T, E extends Editor = Editor>(editorId: EditorId | null, fn: (editor: E) => T): Promise<Awaited<T>> {\n const { callbacks, editors } = this;\n const editor = editors.get(editorId);\n\n if (editor) {\n return Promise.resolve(fn(editor as E));\n }\n\n return new Promise((resolve) => {\n const callback = async (editor: E) => resolve(await fn(editor));\n\n if (!this.callbacks.has(editorId)) {\n callbacks.set(editorId, []);\n }\n\n callbacks.set(editorId, [\n ...callbacks.get(editorId)!,\n callback,\n ]);\n });\n }\n\n /**\n * Registers an editor.\n *\n * @param editorId The ID of the editor.\n * @param editor The editor instance.\n */\n register(editorId: EditorId | null, editor: Editor): void {\n const { editors, callbacks } = this;\n const callbacksForEditor = callbacks.get(editorId);\n\n if (editors.has(editorId)) {\n throw new Error(`Editor with ID \"${editorId}\" is already registered.`);\n }\n\n editors.set(editorId, editor);\n\n if (callbacksForEditor) {\n callbacksForEditor.forEach(callback => callback(editor));\n callbacks.delete(editorId);\n }\n\n // Register the first editor as the default editor.\n // This is useful for editables that do not specify an editor ID.\n if (this.editors.size === 1) {\n this.register(null, editor);\n }\n }\n\n /**\n * Un-registers an editor.\n *\n * @param editorId The ID of the editor.\n */\n unregister(editorId: EditorId | null): void {\n const { editors, callbacks } = this;\n\n if (!editors.has(editorId)) {\n throw new Error(`Editor with ID \"${editorId}\" is not registered.`);\n }\n\n if (editorId && this.editors.get(null) === editors.get(editorId)) {\n this.unregister(null);\n }\n\n editors.delete(editorId);\n callbacks.delete(editorId);\n }\n\n /**\n * Gets all registered editors.\n */\n getEditors(): Editor[] {\n return Array.from(this.editors.values());\n }\n\n /**\n * Checks if an editor with the given ID is registered.\n *\n * @param editorId The ID of the editor.\n * @returns `true` if the editor is registered, `false` otherwise.\n */\n hasEditor(editorId: EditorId | null): boolean {\n return this.editors.has(editorId);\n }\n\n /**\n * Gets a promise that resolves with the editor instance for the given ID.\n * If the editor is not registered yet, it will wait for it to be registered.\n *\n * @param editorId The ID of the editor.\n * @returns A promise that resolves with the editor instance.\n */\n waitForEditor<E extends Editor>(editorId: EditorId | null): Promise<E> {\n return this.execute(editorId, editor => editor as E);\n }\n\n /**\n * Destroys all registered editors and clears the registry.\n * This will call the `destroy` method on each editor.\n */\n async destroyAllEditors() {\n const promises = (\n Array\n .from(this.editors.values())\n .map(editor => editor.destroy())\n );\n\n this.editors.clear();\n this.callbacks.clear();\n\n await Promise.all(promises);\n }\n}\n\n/**\n * Callback type for editor operations.\n */\ntype EditorCallback<E extends Editor = Editor> = (editor: E) => void;\n","import type { MultiRootEditor } from 'ckeditor5';\n\nimport { ClassHook, debounce, makeHook } from '../shared';\nimport { EditorsRegistry } from './editor/editors-registry';\n\n/**\n * Editable hook for Phoenix LiveView. It allows you to create editables for multi-root editors.\n */\nclass EditableHookImpl extends ClassHook {\n /**\n * The name of the hook.\n */\n private mountedPromise: Promise<void> | null = null;\n\n /**\n * Attributes for the editable instance.\n */\n private get attrs() {\n const value = {\n editableId: this.el.getAttribute('id')!,\n editorId: this.el.getAttribute('data-cke-editor-id') || null,\n rootName: this.el.getAttribute('data-cke-editable-root-name')!,\n initialValue: this.el.getAttribute('data-cke-editable-initial-value') || '',\n };\n\n Object.defineProperty(this, 'attrs', {\n value,\n writable: false,\n configurable: false,\n enumerable: true,\n });\n\n return value;\n }\n\n /**\n * Mounts the editable component.\n */\n override async mounted() {\n const { editableId, editorId, rootName, initialValue } = this.attrs;\n const input = this.el.querySelector<HTMLInputElement>(`#${editableId}_input`);\n\n // If the editor is not registered yet, we will wait for it to be registered.\n this.mountedPromise = EditorsRegistry.the.execute(editorId, (editor: MultiRootEditor) => {\n const { ui, editing, model } = editor;\n\n if (model.document.getRoot(rootName)) {\n return;\n }\n\n editor.addRoot(rootName, {\n isUndoable: false,\n data: initialValue,\n });\n\n const contentElement = this.el.querySelector('[data-cke-editable-content]') as HTMLElement | null;\n const editable = ui.view.createEditable(rootName, contentElement!);\n\n ui.addEditable(editable);\n editing.view.forceRender();\n\n if (input) {\n syncEditorRootToInput(input, editor, rootName);\n }\n });\n }\n\n /**\n * Destroys the editable component. Unmounts root from the editor.\n */\n override async destroyed() {\n const { editorId, rootName } = this.attrs;\n\n // Let's hide the element during destruction to prevent flickering.\n this.el.style.display = 'none';\n\n // Let's wait for the mounted promise to resolve before proceeding with destruction.\n await this.mountedPromise;\n this.mountedPromise = null;\n\n // Unmount root from the editor.\n await EditorsRegistry.the.execute(editorId, (editor: MultiRootEditor) => {\n const root = editor.model.document.getRoot(rootName);\n\n if (root) {\n editor.detachEditable(root);\n editor.detachRoot(rootName, false);\n }\n });\n }\n}\n\n/**\n * Phoenix LiveView hook for CKEditor 5 editable elements.\n */\nexport const EditableHook = makeHook(EditableHookImpl);\n\n/**\n * Synchronizes the editor's root data to the corresponding input element.\n * This is used to keep the input value in sync with the editor's content.\n *\n * @param input - The input element to synchronize with the editor.\n * @param editor - The CKEditor instance.\n * @param rootName - The name of the root to synchronize.\n */\nfunction syncEditorRootToInput(input: HTMLInputElement, editor: MultiRootEditor, rootName: string) {\n const sync = () => {\n input.value = editor.getData({ rootName });\n };\n\n editor.model.document.on('change:data', debounce(100, sync));\n sync();\n}\n","import type { EditorType } from '../typings';\n\n/**\n * Checks if the given editor type is one of the single editing-like editors.\n *\n * @param editorType - The type of the editor to check.\n * @returns `true` if the editor type is 'inline', 'classic', or 'balloon', otherwise `false`.\n */\nexport function isSingleEditingLikeEditor(editorType: EditorType): boolean {\n return ['inline', 'classic', 'balloon', 'decoupled'].includes(editorType);\n}\n","import type { EditorType } from '../typings';\n\n/**\n * Returns the constructor for the specified CKEditor5 editor type.\n *\n * @param type - The type of the editor to load.\n * @returns A promise that resolves to the editor constructor.\n */\nexport async function loadEditorConstructor(type: EditorType) {\n const PKG = await import('ckeditor5');\n\n const editorMap = {\n inline: PKG.InlineEditor,\n balloon: PKG.BalloonEditor,\n classic: PKG.ClassicEditor,\n decoupled: PKG.DecoupledEditor,\n multiroot: PKG.MultiRootEditor,\n } as const;\n\n const EditorConstructor = editorMap[type];\n\n if (!EditorConstructor) {\n throw new Error(`Unsupported editor type: ${type}`);\n }\n\n return EditorConstructor;\n}\n","import type { PluginConstructor } from 'ckeditor5';\n\nimport type { EditorPlugin } from '../typings';\n\n/**\n * Loads CKEditor plugins from base and premium packages.\n * First tries to load from the base 'ckeditor5' package, then falls back to 'ckeditor5-premium-features'.\n *\n * @param plugins - Array of plugin names to load\n * @returns Promise that resolves to an array of loaded Plugin instances\n * @throws Error if a plugin is not found in either package\n */\nexport async function loadEditorPlugins(plugins: EditorPlugin[]): Promise<PluginConstructor[]> {\n const basePackage: Record<string, any> = await import('ckeditor5');\n let premiumPackage: Record<string, any> | null = null;\n\n const loaders = plugins.map(async (plugin) => {\n // Let's first try to load the plugin from the base package.\n // Coverage is disabled due to Vitest issues with mocking dynamic imports.\n\n /* v8 ignore start */\n const { [plugin]: basePkgImport } = basePackage;\n\n if (basePkgImport) {\n return basePkgImport as PluginConstructor;\n }\n\n // Plugin not found in base package, try premium package.\n if (!premiumPackage) {\n try {\n premiumPackage = await import('ckeditor5-premium-features');\n }\n catch (error) {\n console.error(`Failed to load premium package: ${error}`);\n }\n }\n\n const { [plugin]: premiumPkgImport } = premiumPackage || {};\n\n if (premiumPkgImport) {\n return premiumPkgImport as PluginConstructor;\n }\n\n // Plugin not found in either package, throw an error.\n throw new Error(`Plugin \"${plugin}\" not found in base or premium packages.`);\n /* v8 ignore end */\n });\n\n return Promise.all(loaders);\n}\n","import type { EditorId } from '../typings';\n\n/**\n * Queries all editable elements within a specific editor instance.\n *\n * @param editorId The ID of the editor to query.\n * @returns An object mapping editable names to their corresponding elements and initial values.\n */\nexport function queryAllEditorEditables(editorId: EditorId): Record<string, EditableItem> {\n const iterator = document.querySelectorAll<HTMLElement>(\n [\n `[data-cke-editor-id=\"${editorId}\"][data-cke-editable-root-name]`,\n '[data-cke-editable-root-name]:not([data-cke-editor-id])',\n ]\n .join(', '),\n );\n\n return (\n Array\n .from(iterator)\n .reduce<Record<string, EditableItem>>((acc, element) => {\n const name = element.getAttribute('data-cke-editable-root-name');\n const initialValue = element.getAttribute('data-cke-editable-initial-value') || '';\n const content = element.querySelector('[data-cke-editable-content]') as HTMLElement;\n\n if (!name || !content) {\n return acc;\n }\n\n return {\n ...acc,\n [name]: {\n content,\n initialValue,\n },\n };\n }, Object.create({}))\n );\n}\n\n/**\n * Type representing an editable item within an editor.\n */\nexport type EditableItem = {\n content: HTMLElement;\n initialValue: string;\n};\n","/**\n * List of supported CKEditor5 editor types.\n */\nexport const EDITOR_TYPES = ['inline', 'classic', 'balloon', 'decoupled', 'multiroot'] as const;\n\n/**\n * Represents a unique identifier for a CKEditor5 editor instance.\n * This is typically the ID of the HTML element that the editor is attached to.\n */\nexport type EditorId = string;\n\n/**\n * Defines editor type supported by CKEditor5. It must match list of available\n * editor types specified in `preset/parser.ex` file.\n */\nexport type EditorType = (typeof EDITOR_TYPES)[number];\n\n/**\n * Represents a CKEditor5 plugin as a string identifier.\n */\nexport type EditorPlugin = string;\n\n/**\n * Configuration object for CKEditor5 editor instance.\n */\nexport type EditorConfig = {\n /**\n * Array of plugin identifiers to be loaded by the editor.\n */\n plugins: EditorPlugin[];\n\n /**\n * Other configuration options are flexible and can be any key-value pairs.\n */\n [key: string]: any;\n};\n\n/**\n * Represents a license key for CKEditor5.\n */\nexport type EditorLicense = {\n key: string;\n};\n\n/**\n * Configuration object for the CKEditor5 hook.\n */\nexport type EditorPreset = {\n /**\n * The type of CKEditor5 editor to use.\n * Must be one of the predefined types: 'inline', 'classic', 'balloon', 'decoupled', or 'multiroot'.\n */\n type: EditorType;\n\n /**\n * The configuration object for the CKEditor5 editor.\n * This should match the configuration expected by CKEditor5.\n */\n config: EditorConfig;\n\n /**\n * The license key for CKEditor5.\n * This is required for using CKEditor5 with a valid license.\n */\n license: EditorLicense;\n\n /**\n * Optional height for the editor, if applicable.\n * This can be used to set a specific height for the editor instance.\n */\n editableHeight?: number;\n};\n","import type { EditorPreset } from '../typings';\n\nimport { EDITOR_TYPES } from '../typings';\n\n/**\n * Reads the hook configuration from the element's attribute and parses it as JSON.\n *\n * @param element - The HTML element that contains the hook configuration.\n * @returns The parsed hook configuration.\n */\nexport function readPresetOrThrow(element: HTMLElement): EditorPreset {\n const attributeValue = element.getAttribute('cke-preset');\n\n if (!attributeValue) {\n throw new Error('CKEditor5 hook requires a \"cke-preset\" attribute on the element.');\n }\n\n const { type, config, license } = JSON.parse(attributeValue);\n\n if (!type || !config || !license) {\n throw new Error('CKEditor5 hook configuration must include \"editor\", \"config\", and \"license\" properties.');\n }\n\n if (!EDITOR_TYPES.includes(type)) {\n throw new Error(`Invalid editor type: ${type}. Must be one of: ${EDITOR_TYPES.join(', ')}.`);\n }\n\n return {\n type,\n config,\n license,\n };\n}\n","import type { Editor } from 'ckeditor5';\n\n/**\n * Sets the height of the editable area in the CKEditor instance.\n *\n * @param instance - The CKEditor instance to modify.\n * @param height - The height in pixels to set for the editable area.\n */\nexport function setEditorEditableHeight(instance: Editor, height: number): void {\n const { editing } = instance;\n\n editing.view.change((writer) => {\n writer.setStyle('height', `${height}px`, editing.view.document.getRoot()!);\n });\n}\n","import type { Editor } from 'ckeditor5';\n\nimport type { EditorId, EditorType } from './typings';\n\nimport {\n debounce,\n mapObjectValues,\n parseIntIfNotNull,\n} from '../../shared';\nimport { ClassHook, makeHook } from '../../shared/hook';\nimport { EditorsRegistry } from './editors-registry';\nimport {\n isSingleEditingLikeEditor,\n loadEditorConstructor,\n loadEditorPlugins,\n queryAllEditorEditables,\n readPresetOrThrow,\n setEditorEditableHeight,\n} from './utils';\n\n/**\n * Editor hook for Phoenix LiveView.\n *\n * This class is a hook that can be used with Phoenix LiveView to integrate\n * the CKEditor 5 WYSIWYG editor.\n */\nclass EditorHookImpl extends ClassHook {\n /**\n * The name of the hook.\n */\n private editorPromise: Promise<Editor> | null = null;\n\n /**\n * Attributes for the editor instance.\n */\n private get attrs() {\n const value = {\n editorId: this.el.getAttribute('id')!,\n preset: readPresetOrThrow(this.el),\n editableHeight: parseIntIfNotNull(this.el.getAttribute('cke-editable-height')),\n };\n\n Object.defineProperty(this, 'attrs', {\n value,\n writable: false,\n configurable: false,\n enumerable: true,\n });\n\n return value;\n }\n\n /**\n * Mounts the editor component.\n */\n override async mounted() {\n this.editorPromise = this.createEditor();\n\n EditorsRegistry.the.register(this.attrs.editorId, await this.editorPromise);\n }\n\n /**\n * Destroys the editor instance when the component is destroyed.\n * This is important to prevent memory leaks and ensure that the editor is properly cleaned up.\n */\n override async destroyed() {\n // Let's hide the element during destruction to prevent flickering.\n this.el.style.display = 'none';\n\n // Let's wait for the mounted promise to resolve before proceeding with destruction.\n (await this.editorPromise)?.destroy();\n this.editorPromise = null;\n\n EditorsRegistry.the.unregister(this.attrs.editorId);\n }\n\n /**\n * Creates the CKEditor instance.\n */\n private createEditor = async () => {\n const { preset, editorId, editableHeight } = this.attrs;\n const { type, license, config: { plugins, ...config } } = preset;\n\n const Constructor = await loadEditorConstructor(type);\n const rootEditables = getInitialRootsContentElements(editorId, type);\n\n const editor = await Constructor.create(\n rootEditables as any,\n {\n ...config,\n initialData: getInitialRootsValues(editorId, type),\n licenseKey: license.key,\n plugins: await loadEditorPlugins(plugins),\n },\n );\n\n if (isSingleEditingLikeEditor(type)) {\n const input = document.getElementById(`${editorId}_input`) as HTMLInputElement | null;\n\n if (input) {\n syncEditorToInput(input, editor);\n }\n\n if (editableHeight) {\n setEditorEditableHeight(editor, editableHeight);\n }\n }\n\n return editor;\n };\n}\n\n/**\n * Synchronizes the editor's content with a hidden input field.\n *\n * @param input The input element to synchronize with the editor.\n * @param editor The CKEditor instance.\n */\nfunction syncEditorToInput(input: HTMLInputElement, editor: Editor) {\n const sync = () => {\n input.value = editor.getData();\n };\n\n editor.model.document.on('change:data', debounce(250, sync));\n sync();\n}\n\n/**\n * Gets the initial root elements for the editor based on its type.\n *\n * @param editorId The editor's ID.\n * @param type The type of the editor.\n * @returns The root element(s) for the editor.\n */\nfunction getInitialRootsContentElements(editorId: EditorId, type: EditorType) {\n if (isSingleEditingLikeEditor(type)) {\n return document.getElementById(`${editorId}_editor`)!;\n }\n\n const editables = queryAllEditorEditables(editorId);\n\n return mapObjectValues(editables, ({ content }) => content);\n}\n\n/**\n * Gets the initial data for the roots of the editor. If the editor is a single editing-like editor,\n * it retrieves the initial value from the element's attribute. Otherwise, it returns an object mapping\n * editable names to their initial values.\n *\n * @param editorId The editor's ID.\n * @param type The type of the editor.\n * @returns The initial values for the editor's roots.\n */\nfunction getInitialRootsValues(editorId: EditorId, type: EditorType) {\n // If the editor is decoupled, the initial value might be specified in the `main` editable.\n if (type === 'decoupled') {\n const mainEditableValue = queryAllEditorEditables(editorId)['main']?.initialValue;\n\n if (mainEditableValue) {\n return mainEditableValue;\n }\n }\n\n // Let's check initial value assigned to the editor element.\n if (isSingleEditingLikeEditor(type)) {\n const initialValue = document.getElementById(editorId)?.getAttribute('cke-initial-value') || '';\n\n return initialValue;\n }\n\n const editables = queryAllEditorEditables(editorId);\n\n return mapObjectValues(editables, ({ initialValue }) => initialValue);\n}\n\n/**\n * Phoenix LiveView hook for CKEditor 5.\n */\nexport const EditorHook = makeHook(EditorHookImpl);\n","import { ClassHook, makeHook } from '../shared';\nimport { EditorsRegistry } from './editor/editors-registry';\n\n/**\n * UI Part hook for Phoenix LiveView. It allows you to create UI parts for multi-root editors.\n */\nclass UIPartHookImpl extends ClassHook {\n /**\n * The name of the hook.\n */\n private mountedPromise: Promise<void> | null = null;\n\n /**\n * Attributes for the editable instance.\n */\n private get attrs() {\n const value = {\n editorId: this.el.getAttribute('data-cke-editor-id') || null,\n name: this.el.getAttribute('data-cke-ui-part-name')!,\n };\n\n Object.defineProperty(this, 'attrs', {\n value,\n writable: false,\n configurable: false,\n enumerable: true,\n });\n\n return value;\n }\n\n /**\n * Mounts the editable component.\n */\n override async mounted() {\n const { editorId, name } = this.attrs;\n\n // If the editor is not registered yet, we will wait for it to be registered.\n this.mountedPromise = EditorsRegistry.the.execute(editorId, (editor) => {\n const { ui } = editor;\n\n const uiViewName = mapUIPartView(name);\n const uiPart = (ui.view as any)[uiViewName!];\n\n if (!uiPart) {\n console.error(`Unknown UI part name: \"${name}\". Supported names are \"toolbar\" and \"menubar\".`);\n return;\n }\n\n this.el.appendChild(uiPart.element);\n });\n }\n\n /**\n * Destroys the editable component. Unmounts root from the editor.\n */\n override async destroyed() {\n // Let's hide the element during destruction to prevent flickering.\n this.el.style.display = 'none';\n\n // Let's wait for the mounted promise to resolve before proceeding with destruction.\n await this.mountedPromise;\n this.mountedPromise = null;\n\n // Unmount all UI parts from the editor.\n this.el.innerHTML = '';\n }\n}\n\n/**\n * Maps the UI part name to the corresponding view in the editor.\n */\nfunction mapUIPartView(name: string): string | null {\n switch (name) {\n case 'toolbar':\n return 'toolbar';\n\n case 'menubar':\n return 'menuBarView';\n\n default:\n return null;\n }\n}\n\n/**\n * Phoenix LiveView hook for CKEditor 5 UI parts.\n */\nexport const UIPartHook = makeHook(UIPartHookImpl);\n","import { EditableHook } from './editable';\nimport { EditorHook } from './editor';\nimport { UIPartHook } from './ui-part';\n\nexport const Hooks = {\n CKEditor5: EditorHook,\n CKEditable: EditableHook,\n CKUIPart: UIPartHook,\n};\n"],"names":["debounce","delay","callback","timeoutId","args","ClassHook","makeHook","constructor","instance","event","payload","selector","mapObjectValues","obj","mapper","mappedEntries","key","value","parseIntIfNotNull","parsed","EditorsRegistry","editorId","fn","callbacks","editors","editor","resolve","callbacksForEditor","promises","EditableHookImpl","editableId","rootName","initialValue","input","ui","editing","model","contentElement","editable","syncEditorRootToInput","root","EditableHook","sync","isSingleEditingLikeEditor","editorType","loadEditorConstructor","type","PKG","EditorConstructor","loadEditorPlugins","plugins","basePackage","premiumPackage","loaders","plugin","basePkgImport","error","premiumPkgImport","queryAllEditorEditables","iterator","acc","element","name","content","EDITOR_TYPES","readPresetOrThrow","attributeValue","config","license","setEditorEditableHeight","height","writer","EditorHookImpl","preset","editableHeight","Constructor","rootEditables","getInitialRootsContentElements","getInitialRootsValues","syncEditorToInput","editables","mainEditableValue","EditorHook","UIPartHookImpl","uiViewName","mapUIPartView","uiPart","UIPartHook","Hooks"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../src/shared/debounce.ts","../src/shared/hook.ts","../src/shared/map-object-values.ts","../src/shared/parse-int-if-not-null.ts","../src/hooks/editor/editors-registry.ts","../src/hooks/editable.ts","../src/hooks/editor/utils/is-single-editing-like-editor.ts","../src/hooks/editor/utils/load-editor-constructor.ts","../src/hooks/editor/utils/load-editor-plugins.ts","../src/hooks/editor/utils/query-all-editor-editables.ts","../src/hooks/editor/typings.ts","../src/hooks/editor/utils/read-preset-or-throw.ts","../src/hooks/editor/utils/set-editor-editable-height.ts","../src/hooks/editor/editor.ts","../src/hooks/ui-part.ts","../src/hooks/index.ts"],"sourcesContent":["export function debounce<T extends (...args: any[]) => any>(\n delay: number,\n callback: T,\n): (...args: Parameters<T>) => void {\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n\n return (...args: Parameters<T>): void => {\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n\n timeoutId = setTimeout(() => {\n callback(...args);\n }, delay);\n };\n}\n","import type { Hook, LiveSocket } from 'phoenix_live_view';\n\nimport type { RequiredBy } from '../types';\n\n/**\n * An abstract class that provides a class-based API for creating Phoenix LiveView hooks.\n *\n * This class defines the structure and lifecycle methods of a hook, which can be extended\n * to implement custom client-side behavior that integrates with LiveView.\n */\nexport abstract class ClassHook {\n /**\n * The DOM element the hook is attached to.\n * It includes an `instance` property to hold the hook instance.\n */\n el: HTMLElement & { instance: Hook; };\n\n /**\n * The LiveView socket instance, providing connection to the server.\n */\n liveSocket: LiveSocket;\n\n /**\n * Pushes an event from the client to the LiveView server process.\n * @param _event The name of the event.\n * @param _payload The data to send with the event.\n * @param _callback An optional function to be called with the server's reply.\n */\n pushEvent!: (\n _event: string,\n _payload: any,\n _callback?: (reply: any, ref: number) => void,\n ) => void;\n\n /**\n * Pushes an event to another hook on the page.\n * @param _selector The CSS selector of the target element with the hook.\n * @param _event The name of the event.\n * @param _payload The data to send with the event.\n * @param _callback An optional function to be called with the reply.\n */\n pushEventTo!: (\n _selector: string,\n _event: string,\n _payload: any,\n _callback?: (reply: any, ref: number) => void,\n ) => void;\n\n /**\n * Registers a handler for an event pushed from the server.\n * @param _event The name of the event to handle.\n * @param _callback The function to execute when the event is received.\n */\n handleEvent!: (\n _event: string,\n _callback: (payload: any) => void,\n ) => void;\n\n /**\n * Called when the hook has been mounted to the DOM.\n * This is the ideal place for initialization code.\n */\n abstract mounted(): void;\n\n /**\n * Called when the element has been removed from the DOM.\n * Perfect for cleanup tasks.\n */\n abstract destroyed(): void;\n\n /**\n * Called before the element is updated by a LiveView patch.\n */\n beforeUpdate?(): void;\n\n /**\n * Called when the client has disconnected from the server.\n */\n disconnected?(): void;\n\n /**\n * Called when the client has reconnected to the server.\n */\n reconnected?(): void;\n}\n\n/**\n * A factory function that adapts a class-based hook to the object-based API expected by Phoenix LiveView.\n *\n * @param constructor The constructor of the class that extends the `Hook` abstract class.\n */\nexport function makeHook(constructor: new () => ClassHook): RequiredBy<Hook<any>, 'mounted' | 'destroyed'> {\n return {\n /**\n * The mounted lifecycle callback for the LiveView hook object.\n * It creates an instance of the user-defined hook class and sets up the necessary properties and methods.\n */\n mounted(this: any) {\n const instance = new constructor();\n\n this.el.instance = instance;\n\n instance.el = this.el;\n instance.liveSocket = this.liveSocket;\n\n instance.pushEvent = (event, payload, callback) => this.pushEvent?.(event, payload, callback);\n instance.pushEventTo = (selector, event, payload, callback) => this.pushEventTo?.(selector, event, payload, callback);\n instance.handleEvent = (event, callback) => this.handleEvent?.(event, callback);\n\n instance.mounted?.();\n },\n\n /**\n * The beforeUpdate lifecycle callback that delegates to the hook instance.\n */\n beforeUpdate(this: any) {\n this.el.instance.beforeUpdate?.();\n },\n\n /**\n * The destroyed lifecycle callback that delegates to the hook instance.\n */\n destroyed(this: any) {\n this.el.instance.destroyed?.();\n },\n\n /**\n * The disconnected lifecycle callback that delegates to the hook instance.\n */\n disconnected(this: any) {\n this.el.instance.disconnected?.();\n },\n\n /**\n * The reconnected lifecycle callback that delegates to the hook instance.\n */\n reconnected(this: any) {\n this.el.instance.reconnected?.();\n },\n };\n}\n","/**\n * Maps the values of an object using a provided mapper function.\n *\n * @param obj The object whose values will be mapped.\n * @param mapper A function that takes a value and its key, and returns a new value.\n * @template T The type of the original values in the object.\n * @template U The type of the new values in the object.\n * @returns A new object with the same keys as the original, but with values transformed by\n */\nexport function mapObjectValues<T, U>(\n obj: Record<string, T>,\n mapper: (value: T, key: string) => U,\n): Record<string, U> {\n const mappedEntries = Object\n .entries(obj)\n .map(([key, value]) => [key, mapper(value, key)] as const);\n\n return Object.fromEntries(mappedEntries);\n}\n","export function parseIntIfNotNull(value: string | null): number | null {\n if (value === null) {\n return null;\n }\n\n const parsed = Number.parseInt(value, 10);\n\n return Number.isNaN(parsed) ? null : parsed;\n}\n","import type { Editor } from 'ckeditor5';\n\nimport type { EditorId } from './typings';\n\n/**\n * Allows other hooks to communicate with specific editors.\n * It provides a way to register editors and execute callbacks on them when they are available.\n */\nexport class EditorsRegistry {\n static readonly the = new EditorsRegistry();\n\n /**\n * Map of registered editors.\n */\n private readonly editors = new Map<EditorId | null, Editor>();\n\n /**\n * Map of callbacks that are waiting for an editor to be registered.\n */\n private readonly callbacks = new Map<EditorId | null, EditorCallback<any>[]>();\n\n /**\n * Private constructor to enforce singleton pattern.\n */\n private constructor() {}\n\n /**\n * Executes a function on an editor.\n * If the editor is not yet registered, it will wait for it to be registered.\n *\n * @param editorId The ID of the editor.\n * @param fn The function to execute.\n * @returns A promise that resolves with the result of the function.\n */\n execute<T, E extends Editor = Editor>(editorId: EditorId | null, fn: (editor: E) => T): Promise<Awaited<T>> {\n const { callbacks, editors } = this;\n const editor = editors.get(editorId);\n\n if (editor) {\n return Promise.resolve(fn(editor as E));\n }\n\n return new Promise((resolve) => {\n const callback = async (editor: E) => resolve(await fn(editor));\n\n if (!this.callbacks.has(editorId)) {\n callbacks.set(editorId, []);\n }\n\n callbacks.set(editorId, [\n ...callbacks.get(editorId)!,\n callback,\n ]);\n });\n }\n\n /**\n * Registers an editor.\n *\n * @param editorId The ID of the editor.\n * @param editor The editor instance.\n */\n register(editorId: EditorId | null, editor: Editor): void {\n const { editors, callbacks } = this;\n const callbacksForEditor = callbacks.get(editorId);\n\n if (editors.has(editorId)) {\n throw new Error(`Editor with ID \"${editorId}\" is already registered.`);\n }\n\n editors.set(editorId, editor);\n\n if (callbacksForEditor) {\n callbacksForEditor.forEach(callback => callback(editor));\n callbacks.delete(editorId);\n }\n\n // Register the first editor as the default editor.\n // This is useful for editables that do not specify an editor ID.\n if (this.editors.size === 1) {\n this.register(null, editor);\n }\n }\n\n /**\n * Un-registers an editor.\n *\n * @param editorId The ID of the editor.\n */\n unregister(editorId: EditorId | null): void {\n const { editors, callbacks } = this;\n\n if (!editors.has(editorId)) {\n throw new Error(`Editor with ID \"${editorId}\" is not registered.`);\n }\n\n if (editorId && this.editors.get(null) === editors.get(editorId)) {\n this.unregister(null);\n }\n\n editors.delete(editorId);\n callbacks.delete(editorId);\n }\n\n /**\n * Gets all registered editors.\n */\n getEditors(): Editor[] {\n return Array.from(this.editors.values());\n }\n\n /**\n * Checks if an editor with the given ID is registered.\n *\n * @param editorId The ID of the editor.\n * @returns `true` if the editor is registered, `false` otherwise.\n */\n hasEditor(editorId: EditorId | null): boolean {\n return this.editors.has(editorId);\n }\n\n /**\n * Gets a promise that resolves with the editor instance for the given ID.\n * If the editor is not registered yet, it will wait for it to be registered.\n *\n * @param editorId The ID of the editor.\n * @returns A promise that resolves with the editor instance.\n */\n waitForEditor<E extends Editor>(editorId: EditorId | null): Promise<E> {\n return this.execute(editorId, editor => editor as E);\n }\n\n /**\n * Destroys all registered editors and clears the registry.\n * This will call the `destroy` method on each editor.\n */\n async destroyAllEditors() {\n const promises = (\n Array\n .from(this.editors.values())\n .map(editor => editor.destroy())\n );\n\n this.editors.clear();\n this.callbacks.clear();\n\n await Promise.all(promises);\n }\n}\n\n/**\n * Callback type for editor operations.\n */\ntype EditorCallback<E extends Editor = Editor> = (editor: E) => void;\n","import type { MultiRootEditor } from 'ckeditor5';\n\nimport { ClassHook, debounce, makeHook } from '../shared';\nimport { EditorsRegistry } from './editor/editors-registry';\n\n/**\n * Editable hook for Phoenix LiveView. It allows you to create editables for multi-root editors.\n */\nclass EditableHookImpl extends ClassHook {\n /**\n * The name of the hook.\n */\n private mountedPromise: Promise<void> | null = null;\n\n /**\n * Attributes for the editable instance.\n */\n private get attrs() {\n const value = {\n editableId: this.el.getAttribute('id')!,\n editorId: this.el.getAttribute('data-cke-editor-id') || null,\n rootName: this.el.getAttribute('data-cke-editable-root-name')!,\n initialValue: this.el.getAttribute('data-cke-editable-initial-value') || '',\n };\n\n Object.defineProperty(this, 'attrs', {\n value,\n writable: false,\n configurable: false,\n enumerable: true,\n });\n\n return value;\n }\n\n /**\n * Mounts the editable component.\n */\n override async mounted() {\n const { editableId, editorId, rootName, initialValue } = this.attrs;\n const input = this.el.querySelector<HTMLInputElement>(`#${editableId}_input`);\n\n // If the editor is not registered yet, we will wait for it to be registered.\n this.mountedPromise = EditorsRegistry.the.execute(editorId, (editor: MultiRootEditor) => {\n const { ui, editing, model } = editor;\n\n if (model.document.getRoot(rootName)) {\n return;\n }\n\n editor.addRoot(rootName, {\n isUndoable: false,\n data: initialValue,\n });\n\n const contentElement = this.el.querySelector('[data-cke-editable-content]') as HTMLElement | null;\n const editable = ui.view.createEditable(rootName, contentElement!);\n\n ui.addEditable(editable);\n editing.view.forceRender();\n\n if (input) {\n syncEditorRootToInput(input, editor, rootName);\n }\n });\n }\n\n /**\n * Destroys the editable component. Unmounts root from the editor.\n */\n override async destroyed() {\n const { editorId, rootName } = this.attrs;\n\n // Let's hide the element during destruction to prevent flickering.\n this.el.style.display = 'none';\n\n // Let's wait for the mounted promise to resolve before proceeding with destruction.\n await this.mountedPromise;\n this.mountedPromise = null;\n\n // Unmount root from the editor.\n await EditorsRegistry.the.execute(editorId, (editor: MultiRootEditor) => {\n const root = editor.model.document.getRoot(rootName);\n\n if (root) {\n editor.detachEditable(root);\n editor.detachRoot(rootName, false);\n }\n });\n }\n}\n\n/**\n * Phoenix LiveView hook for CKEditor 5 editable elements.\n */\nexport const EditableHook = makeHook(EditableHookImpl);\n\n/**\n * Synchronizes the editor's root data to the corresponding input element.\n * This is used to keep the input value in sync with the editor's content.\n *\n * @param input - The input element to synchronize with the editor.\n * @param editor - The CKEditor instance.\n * @param rootName - The name of the root to synchronize.\n */\nfunction syncEditorRootToInput(input: HTMLInputElement, editor: MultiRootEditor, rootName: string) {\n const sync = () => {\n input.value = editor.getData({ rootName });\n };\n\n editor.model.document.on('change:data', debounce(100, sync));\n sync();\n}\n","import type { EditorType } from '../typings';\n\n/**\n * Checks if the given editor type is one of the single editing-like editors.\n *\n * @param editorType - The type of the editor to check.\n * @returns `true` if the editor type is 'inline', 'classic', or 'balloon', otherwise `false`.\n */\nexport function isSingleEditingLikeEditor(editorType: EditorType): boolean {\n return ['inline', 'classic', 'balloon', 'decoupled'].includes(editorType);\n}\n","import type { EditorType } from '../typings';\n\n/**\n * Returns the constructor for the specified CKEditor5 editor type.\n *\n * @param type - The type of the editor to load.\n * @returns A promise that resolves to the editor constructor.\n */\nexport async function loadEditorConstructor(type: EditorType) {\n const PKG = await import('ckeditor5');\n\n const editorMap = {\n inline: PKG.InlineEditor,\n balloon: PKG.BalloonEditor,\n classic: PKG.ClassicEditor,\n decoupled: PKG.DecoupledEditor,\n multiroot: PKG.MultiRootEditor,\n } as const;\n\n const EditorConstructor = editorMap[type];\n\n if (!EditorConstructor) {\n throw new Error(`Unsupported editor type: ${type}`);\n }\n\n return EditorConstructor;\n}\n","import type { PluginConstructor } from 'ckeditor5';\n\nimport type { EditorPlugin } from '../typings';\n\n/**\n * Loads CKEditor plugins from base and premium packages.\n * First tries to load from the base 'ckeditor5' package, then falls back to 'ckeditor5-premium-features'.\n *\n * @param plugins - Array of plugin names to load\n * @returns Promise that resolves to an array of loaded Plugin instances\n * @throws Error if a plugin is not found in either package\n */\nexport async function loadEditorPlugins(plugins: EditorPlugin[]): Promise<PluginConstructor[]> {\n const basePackage: Record<string, any> = await import('ckeditor5');\n let premiumPackage: Record<string, any> | null = null;\n\n const loaders = plugins.map(async (plugin) => {\n // Let's first try to load the plugin from the base package.\n // Coverage is disabled due to Vitest issues with mocking dynamic imports.\n\n /* v8 ignore start */\n const { [plugin]: basePkgImport } = basePackage;\n\n if (basePkgImport) {\n return basePkgImport as PluginConstructor;\n }\n\n // Plugin not found in base package, try premium package.\n if (!premiumPackage) {\n try {\n premiumPackage = await import('ckeditor5-premium-features');\n }\n catch (error) {\n console.error(`Failed to load premium package: ${error}`);\n }\n }\n\n const { [plugin]: premiumPkgImport } = premiumPackage || {};\n\n if (premiumPkgImport) {\n return premiumPkgImport as PluginConstructor;\n }\n\n // Plugin not found in either package, throw an error.\n throw new Error(`Plugin \"${plugin}\" not found in base or premium packages.`);\n /* v8 ignore end */\n });\n\n return Promise.all(loaders);\n}\n","import type { EditorId } from '../typings';\n\n/**\n * Queries all editable elements within a specific editor instance.\n *\n * @param editorId The ID of the editor to query.\n * @returns An object mapping editable names to their corresponding elements and initial values.\n */\nexport function queryAllEditorEditables(editorId: EditorId): Record<string, EditableItem> {\n const iterator = document.querySelectorAll<HTMLElement>(\n [\n `[data-cke-editor-id=\"${editorId}\"][data-cke-editable-root-name]`,\n '[data-cke-editable-root-name]:not([data-cke-editor-id])',\n ]\n .join(', '),\n );\n\n return (\n Array\n .from(iterator)\n .reduce<Record<string, EditableItem>>((acc, element) => {\n const name = element.getAttribute('data-cke-editable-root-name');\n const initialValue = element.getAttribute('data-cke-editable-initial-value') || '';\n const content = element.querySelector('[data-cke-editable-content]') as HTMLElement;\n\n if (!name || !content) {\n return acc;\n }\n\n return {\n ...acc,\n [name]: {\n content,\n initialValue,\n },\n };\n }, Object.create({}))\n );\n}\n\n/**\n * Type representing an editable item within an editor.\n */\nexport type EditableItem = {\n content: HTMLElement;\n initialValue: string;\n};\n","/**\n * List of supported CKEditor5 editor types.\n */\nexport const EDITOR_TYPES = ['inline', 'classic', 'balloon', 'decoupled', 'multiroot'] as const;\n\n/**\n * Represents a unique identifier for a CKEditor5 editor instance.\n * This is typically the ID of the HTML element that the editor is attached to.\n */\nexport type EditorId = string;\n\n/**\n * Defines editor type supported by CKEditor5. It must match list of available\n * editor types specified in `preset/parser.ex` file.\n */\nexport type EditorType = (typeof EDITOR_TYPES)[number];\n\n/**\n * Represents a CKEditor5 plugin as a string identifier.\n */\nexport type EditorPlugin = string;\n\n/**\n * Configuration object for CKEditor5 editor instance.\n */\nexport type EditorConfig = {\n /**\n * Array of plugin identifiers to be loaded by the editor.\n */\n plugins: EditorPlugin[];\n\n /**\n * Other configuration options are flexible and can be any key-value pairs.\n */\n [key: string]: any;\n};\n\n/**\n * Represents a license key for CKEditor5.\n */\nexport type EditorLicense = {\n key: string;\n};\n\n/**\n * Configuration object for the CKEditor5 hook.\n */\nexport type EditorPreset = {\n /**\n * The type of CKEditor5 editor to use.\n * Must be one of the predefined types: 'inline', 'classic', 'balloon', 'decoupled', or 'multiroot'.\n */\n type: EditorType;\n\n /**\n * The configuration object for the CKEditor5 editor.\n * This should match the configuration expected by CKEditor5.\n */\n config: EditorConfig;\n\n /**\n * The license key for CKEditor5.\n * This is required for using CKEditor5 with a valid license.\n */\n license: EditorLicense;\n\n /**\n * Optional height for the editor, if applicable.\n * This can be used to set a specific height for the editor instance.\n */\n editableHeight?: number;\n};\n","import type { EditorPreset } from '../typings';\n\nimport { EDITOR_TYPES } from '../typings';\n\n/**\n * Reads the hook configuration from the element's attribute and parses it as JSON.\n *\n * @param element - The HTML element that contains the hook configuration.\n * @returns The parsed hook configuration.\n */\nexport function readPresetOrThrow(element: HTMLElement): EditorPreset {\n const attributeValue = element.getAttribute('cke-preset');\n\n if (!attributeValue) {\n throw new Error('CKEditor5 hook requires a \"cke-preset\" attribute on the element.');\n }\n\n const { type, config, license } = JSON.parse(attributeValue);\n\n if (!type || !config || !license) {\n throw new Error('CKEditor5 hook configuration must include \"editor\", \"config\", and \"license\" properties.');\n }\n\n if (!EDITOR_TYPES.includes(type)) {\n throw new Error(`Invalid editor type: ${type}. Must be one of: ${EDITOR_TYPES.join(', ')}.`);\n }\n\n return {\n type,\n config,\n license,\n };\n}\n","import type { Editor } from 'ckeditor5';\n\n/**\n * Sets the height of the editable area in the CKEditor instance.\n *\n * @param instance - The CKEditor instance to modify.\n * @param height - The height in pixels to set for the editable area.\n */\nexport function setEditorEditableHeight(instance: Editor, height: number): void {\n const { editing } = instance;\n\n editing.view.change((writer) => {\n writer.setStyle('height', `${height}px`, editing.view.document.getRoot()!);\n });\n}\n","import type { Editor } from 'ckeditor5';\n\nimport type { EditorId, EditorType } from './typings';\n\nimport {\n debounce,\n mapObjectValues,\n parseIntIfNotNull,\n} from '../../shared';\nimport { ClassHook, makeHook } from '../../shared/hook';\nimport { EditorsRegistry } from './editors-registry';\nimport {\n isSingleEditingLikeEditor,\n loadEditorConstructor,\n loadEditorPlugins,\n queryAllEditorEditables,\n readPresetOrThrow,\n setEditorEditableHeight,\n} from './utils';\n\n/**\n * Editor hook for Phoenix LiveView.\n *\n * This class is a hook that can be used with Phoenix LiveView to integrate\n * the CKEditor 5 WYSIWYG editor.\n */\nclass EditorHookImpl extends ClassHook {\n /**\n * The name of the hook.\n */\n private editorPromise: Promise<Editor> | null = null;\n\n /**\n * Attributes for the editor instance.\n */\n private get attrs() {\n const value = {\n editorId: this.el.getAttribute('id')!,\n preset: readPresetOrThrow(this.el),\n editableHeight: parseIntIfNotNull(this.el.getAttribute('cke-editable-height')),\n };\n\n Object.defineProperty(this, 'attrs', {\n value,\n writable: false,\n configurable: false,\n enumerable: true,\n });\n\n return value;\n }\n\n /**\n * Mounts the editor component.\n */\n override async mounted() {\n this.editorPromise = this.createEditor();\n\n EditorsRegistry.the.register(this.attrs.editorId, await this.editorPromise);\n\n return this;\n }\n\n /**\n * Destroys the editor instance when the component is destroyed.\n * This is important to prevent memory leaks and ensure that the editor is properly cleaned up.\n */\n override async destroyed() {\n // Let's hide the element during destruction to prevent flickering.\n this.el.style.display = 'none';\n\n // Let's wait for the mounted promise to resolve before proceeding with destruction.\n (await this.editorPromise)?.destroy();\n this.editorPromise = null;\n\n EditorsRegistry.the.unregister(this.attrs.editorId);\n }\n\n /**\n * Creates the CKEditor instance.\n */\n private async createEditor() {\n const { preset, editorId, editableHeight } = this.attrs;\n const { type, license, config: { plugins, ...config } } = preset;\n\n const Constructor = await loadEditorConstructor(type);\n const rootEditables = getInitialRootsContentElements(editorId, type);\n\n const editor = await Constructor.create(\n rootEditables as any,\n {\n ...config,\n initialData: getInitialRootsValues(editorId, type),\n licenseKey: license.key,\n plugins: await loadEditorPlugins(plugins),\n },\n );\n\n this.setupContentSync(editorId, editor);\n\n if (isSingleEditingLikeEditor(type)) {\n const input = document.getElementById(`${editorId}_input`) as HTMLInputElement | null;\n\n if (input) {\n syncEditorToInput(input, editor);\n }\n\n if (editableHeight) {\n setEditorEditableHeight(editor, editableHeight);\n }\n }\n\n return editor;\n };\n\n /**\n * Sets up the content synchronization for the editor.\n */\n private setupContentSync(editorId: EditorId, editor: Editor) {\n const pushContentChange = () => {\n this.pushEvent(\n 'ckeditor5:change',\n {\n editorId,\n data: getEditorRootsValues(editor),\n },\n );\n };\n\n // Send content changes to the server.\n editor.model.document.on('change:data', debounce(250, pushContentChange));\n pushContentChange();\n\n // Handle incoming data from the server.\n this.handleEvent('ckeditor5:set-data', ({ data }) => {\n editor.setData(data);\n });\n }\n}\n\n/**\n * Gets the values of the editor's roots.\n *\n * @param editor The CKEditor instance.\n * @returns An object mapping root names to their content.\n */\nfunction getEditorRootsValues(editor: Editor) {\n const roots = editor.model.document.getRootNames();\n\n return roots.reduce<Record<string, string>>((acc, rootName) => {\n acc[rootName] = editor.getData({ rootName });\n return acc;\n }, Object.create({}));\n}\n\n/**\n * Synchronizes the editor's content with a hidden input field.\n *\n * @param input The input element to synchronize with the editor.\n * @param editor The CKEditor instance.\n */\nfunction syncEditorToInput(input: HTMLInputElement, editor: Editor) {\n const sync = () => {\n input.value = editor.getData();\n };\n\n editor.model.document.on('change:data', debounce(250, sync));\n sync();\n}\n\n/**\n * Gets the initial root elements for the editor based on its type.\n *\n * @param editorId The editor's ID.\n * @param type The type of the editor.\n * @returns The root element(s) for the editor.\n */\nfunction getInitialRootsContentElements(editorId: EditorId, type: EditorType) {\n if (isSingleEditingLikeEditor(type)) {\n return document.getElementById(`${editorId}_editor`)!;\n }\n\n const editables = queryAllEditorEditables(editorId);\n\n return mapObjectValues(editables, ({ content }) => content);\n}\n\n/**\n * Gets the initial data for the roots of the editor. If the editor is a single editing-like editor,\n * it retrieves the initial value from the element's attribute. Otherwise, it returns an object mapping\n * editable names to their initial values.\n *\n * @param editorId The editor's ID.\n * @param type The type of the editor.\n * @returns The initial values for the editor's roots.\n */\nfunction getInitialRootsValues(editorId: EditorId, type: EditorType) {\n // If the editor is decoupled, the initial value might be specified in the `main` editable.\n if (type === 'decoupled') {\n const mainEditableValue = queryAllEditorEditables(editorId)['main']?.initialValue;\n\n if (mainEditableValue) {\n return mainEditableValue;\n }\n }\n\n // Let's check initial value assigned to the editor element.\n if (isSingleEditingLikeEditor(type)) {\n const initialValue = document.getElementById(editorId)?.getAttribute('cke-initial-value') || '';\n\n return initialValue;\n }\n\n const editables = queryAllEditorEditables(editorId);\n\n return mapObjectValues(editables, ({ initialValue }) => initialValue);\n}\n\n/**\n * Phoenix LiveView hook for CKEditor 5.\n */\nexport const EditorHook = makeHook(EditorHookImpl);\n","import { ClassHook, makeHook } from '../shared';\nimport { EditorsRegistry } from './editor/editors-registry';\n\n/**\n * UI Part hook for Phoenix LiveView. It allows you to create UI parts for multi-root editors.\n */\nclass UIPartHookImpl extends ClassHook {\n /**\n * The name of the hook.\n */\n private mountedPromise: Promise<void> | null = null;\n\n /**\n * Attributes for the editable instance.\n */\n private get attrs() {\n const value = {\n editorId: this.el.getAttribute('data-cke-editor-id') || null,\n name: this.el.getAttribute('data-cke-ui-part-name')!,\n };\n\n Object.defineProperty(this, 'attrs', {\n value,\n writable: false,\n configurable: false,\n enumerable: true,\n });\n\n return value;\n }\n\n /**\n * Mounts the editable component.\n */\n override async mounted() {\n const { editorId, name } = this.attrs;\n\n // If the editor is not registered yet, we will wait for it to be registered.\n this.mountedPromise = EditorsRegistry.the.execute(editorId, (editor) => {\n const { ui } = editor;\n\n const uiViewName = mapUIPartView(name);\n const uiPart = (ui.view as any)[uiViewName!];\n\n if (!uiPart) {\n console.error(`Unknown UI part name: \"${name}\". Supported names are \"toolbar\" and \"menubar\".`);\n return;\n }\n\n this.el.appendChild(uiPart.element);\n });\n }\n\n /**\n * Destroys the editable component. Unmounts root from the editor.\n */\n override async destroyed() {\n // Let's hide the element during destruction to prevent flickering.\n this.el.style.display = 'none';\n\n // Let's wait for the mounted promise to resolve before proceeding with destruction.\n await this.mountedPromise;\n this.mountedPromise = null;\n\n // Unmount all UI parts from the editor.\n this.el.innerHTML = '';\n }\n}\n\n/**\n * Maps the UI part name to the corresponding view in the editor.\n */\nfunction mapUIPartView(name: string): string | null {\n switch (name) {\n case 'toolbar':\n return 'toolbar';\n\n case 'menubar':\n return 'menuBarView';\n\n default:\n return null;\n }\n}\n\n/**\n * Phoenix LiveView hook for CKEditor 5 UI parts.\n */\nexport const UIPartHook = makeHook(UIPartHookImpl);\n","import { EditableHook } from './editable';\nimport { EditorHook } from './editor';\nimport { UIPartHook } from './ui-part';\n\nexport const Hooks = {\n CKEditor5: EditorHook,\n CKEditable: EditableHook,\n CKUIPart: UIPartHook,\n};\n"],"names":["debounce","delay","callback","timeoutId","args","ClassHook","makeHook","constructor","instance","event","payload","selector","mapObjectValues","obj","mapper","mappedEntries","key","value","parseIntIfNotNull","parsed","EditorsRegistry","editorId","fn","callbacks","editors","editor","resolve","callbacksForEditor","promises","EditableHookImpl","editableId","rootName","initialValue","input","ui","editing","model","contentElement","editable","syncEditorRootToInput","root","EditableHook","sync","isSingleEditingLikeEditor","editorType","loadEditorConstructor","type","PKG","EditorConstructor","loadEditorPlugins","plugins","basePackage","premiumPackage","loaders","plugin","basePkgImport","error","premiumPkgImport","queryAllEditorEditables","iterator","acc","element","name","content","EDITOR_TYPES","readPresetOrThrow","attributeValue","config","license","setEditorEditableHeight","height","writer","EditorHookImpl","preset","editableHeight","Constructor","rootEditables","getInitialRootsContentElements","getInitialRootsValues","syncEditorToInput","pushContentChange","getEditorRootsValues","data","editables","mainEditableValue","EditorHook","UIPartHookImpl","uiViewName","mapUIPartView","uiPart","UIPartHook","Hooks"],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
function
|
|
1
|
+
function b(n, t) {
|
|
2
2
|
let e = null;
|
|
3
3
|
return (...i) => {
|
|
4
4
|
e && clearTimeout(e), e = setTimeout(() => {
|
|
@@ -6,7 +6,7 @@ function E(n, t) {
|
|
|
6
6
|
}, n);
|
|
7
7
|
};
|
|
8
8
|
}
|
|
9
|
-
class
|
|
9
|
+
class p {
|
|
10
10
|
/**
|
|
11
11
|
* The DOM element the hook is attached to.
|
|
12
12
|
* It includes an `instance` property to hold the hook instance.
|
|
@@ -46,7 +46,7 @@ function f(n) {
|
|
|
46
46
|
*/
|
|
47
47
|
mounted() {
|
|
48
48
|
const t = new n();
|
|
49
|
-
this.el.instance = t, t.el = this.el, t.liveSocket = this.liveSocket, t.pushEvent = (e, i, o) => this.pushEvent(e, i, o), t.pushEventTo = (e, i, o, r) => this.pushEventTo(e, i, o, r), t.handleEvent = (e, i) => this.handleEvent(e, i), t.mounted?.();
|
|
49
|
+
this.el.instance = t, t.el = this.el, t.liveSocket = this.liveSocket, t.pushEvent = (e, i, o) => this.pushEvent?.(e, i, o), t.pushEventTo = (e, i, o, r) => this.pushEventTo?.(e, i, o, r), t.handleEvent = (e, i) => this.handleEvent?.(e, i), t.mounted?.();
|
|
50
50
|
},
|
|
51
51
|
/**
|
|
52
52
|
* The beforeUpdate lifecycle callback that delegates to the hook instance.
|
|
@@ -78,7 +78,7 @@ function y(n, t) {
|
|
|
78
78
|
const e = Object.entries(n).map(([i, o]) => [i, t(o, i)]);
|
|
79
79
|
return Object.fromEntries(e);
|
|
80
80
|
}
|
|
81
|
-
function
|
|
81
|
+
function k(n) {
|
|
82
82
|
if (n === null)
|
|
83
83
|
return null;
|
|
84
84
|
const t = Number.parseInt(n, 10);
|
|
@@ -110,7 +110,7 @@ class l {
|
|
|
110
110
|
execute(t, e) {
|
|
111
111
|
const { callbacks: i, editors: o } = this, r = o.get(t);
|
|
112
112
|
return r ? Promise.resolve(e(r)) : new Promise((s) => {
|
|
113
|
-
const a = async (
|
|
113
|
+
const a = async (u) => s(await e(u));
|
|
114
114
|
this.callbacks.has(t) || i.set(t, []), i.set(t, [
|
|
115
115
|
...i.get(t),
|
|
116
116
|
a
|
|
@@ -174,7 +174,7 @@ class l {
|
|
|
174
174
|
this.editors.clear(), this.callbacks.clear(), await Promise.all(t);
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
|
-
class
|
|
177
|
+
class w extends p {
|
|
178
178
|
/**
|
|
179
179
|
* The name of the hook.
|
|
180
180
|
*/
|
|
@@ -202,15 +202,15 @@ class k extends b {
|
|
|
202
202
|
async mounted() {
|
|
203
203
|
const { editableId: t, editorId: e, rootName: i, initialValue: o } = this.attrs, r = this.el.querySelector(`#${t}_input`);
|
|
204
204
|
this.mountedPromise = l.the.execute(e, (s) => {
|
|
205
|
-
const { ui: a, editing:
|
|
205
|
+
const { ui: a, editing: u, model: h } = s;
|
|
206
206
|
if (h.document.getRoot(i))
|
|
207
207
|
return;
|
|
208
208
|
s.addRoot(i, {
|
|
209
209
|
isUndoable: !1,
|
|
210
210
|
data: o
|
|
211
211
|
});
|
|
212
|
-
const
|
|
213
|
-
a.addEditable(d),
|
|
212
|
+
const c = this.el.querySelector("[data-cke-editable-content]"), d = a.view.createEditable(i, c);
|
|
213
|
+
a.addEditable(d), u.view.forceRender(), r && v(r, s, i);
|
|
214
214
|
});
|
|
215
215
|
}
|
|
216
216
|
/**
|
|
@@ -224,14 +224,14 @@ class k extends b {
|
|
|
224
224
|
});
|
|
225
225
|
}
|
|
226
226
|
}
|
|
227
|
-
const P = f(
|
|
227
|
+
const P = f(w);
|
|
228
228
|
function v(n, t, e) {
|
|
229
229
|
const i = () => {
|
|
230
230
|
n.value = t.getData({ rootName: e });
|
|
231
231
|
};
|
|
232
|
-
t.model.document.on("change:data",
|
|
232
|
+
t.model.document.on("change:data", b(100, i)), i();
|
|
233
233
|
}
|
|
234
|
-
function
|
|
234
|
+
function g(n) {
|
|
235
235
|
return ["inline", "classic", "balloon", "decoupled"].includes(n);
|
|
236
236
|
}
|
|
237
237
|
async function I(n) {
|
|
@@ -284,29 +284,29 @@ function m(n) {
|
|
|
284
284
|
};
|
|
285
285
|
}, /* @__PURE__ */ Object.create({}));
|
|
286
286
|
}
|
|
287
|
-
const
|
|
288
|
-
function
|
|
287
|
+
const E = ["inline", "classic", "balloon", "decoupled", "multiroot"];
|
|
288
|
+
function C(n) {
|
|
289
289
|
const t = n.getAttribute("cke-preset");
|
|
290
290
|
if (!t)
|
|
291
291
|
throw new Error('CKEditor5 hook requires a "cke-preset" attribute on the element.');
|
|
292
292
|
const { type: e, config: i, license: o } = JSON.parse(t);
|
|
293
293
|
if (!e || !i || !o)
|
|
294
294
|
throw new Error('CKEditor5 hook configuration must include "editor", "config", and "license" properties.');
|
|
295
|
-
if (!
|
|
296
|
-
throw new Error(`Invalid editor type: ${e}. Must be one of: ${
|
|
295
|
+
if (!E.includes(e))
|
|
296
|
+
throw new Error(`Invalid editor type: ${e}. Must be one of: ${E.join(", ")}.`);
|
|
297
297
|
return {
|
|
298
298
|
type: e,
|
|
299
299
|
config: i,
|
|
300
300
|
license: o
|
|
301
301
|
};
|
|
302
302
|
}
|
|
303
|
-
function
|
|
303
|
+
function S(n, t) {
|
|
304
304
|
const { editing: e } = n;
|
|
305
305
|
e.view.change((i) => {
|
|
306
306
|
i.setStyle("height", `${t}px`, e.view.document.getRoot());
|
|
307
307
|
});
|
|
308
308
|
}
|
|
309
|
-
class
|
|
309
|
+
class V extends p {
|
|
310
310
|
/**
|
|
311
311
|
* The name of the hook.
|
|
312
312
|
*/
|
|
@@ -317,8 +317,8 @@ class $ extends b {
|
|
|
317
317
|
get attrs() {
|
|
318
318
|
const t = {
|
|
319
319
|
editorId: this.el.getAttribute("id"),
|
|
320
|
-
preset:
|
|
321
|
-
editableHeight:
|
|
320
|
+
preset: C(this.el),
|
|
321
|
+
editableHeight: k(this.el.getAttribute("cke-editable-height"))
|
|
322
322
|
};
|
|
323
323
|
return Object.defineProperty(this, "attrs", {
|
|
324
324
|
value: t,
|
|
@@ -331,7 +331,7 @@ class $ extends b {
|
|
|
331
331
|
* Mounts the editor component.
|
|
332
332
|
*/
|
|
333
333
|
async mounted() {
|
|
334
|
-
this.editorPromise = this.createEditor(), l.the.register(this.attrs.editorId, await this.editorPromise);
|
|
334
|
+
return this.editorPromise = this.createEditor(), l.the.register(this.attrs.editorId, await this.editorPromise), this;
|
|
335
335
|
}
|
|
336
336
|
/**
|
|
337
337
|
* Destroys the editor instance when the component is destroyed.
|
|
@@ -343,8 +343,8 @@ class $ extends b {
|
|
|
343
343
|
/**
|
|
344
344
|
* Creates the CKEditor instance.
|
|
345
345
|
*/
|
|
346
|
-
|
|
347
|
-
const { preset: t, editorId: e, editableHeight: i } = this.attrs, { type: o, license: r, config: { plugins: s, ...a } } = t,
|
|
346
|
+
async createEditor() {
|
|
347
|
+
const { preset: t, editorId: e, editableHeight: i } = this.attrs, { type: o, license: r, config: { plugins: s, ...a } } = t, u = await I(o), h = $(e, o), c = await u.create(
|
|
348
348
|
h,
|
|
349
349
|
{
|
|
350
350
|
...a,
|
|
@@ -353,21 +353,41 @@ class $ extends b {
|
|
|
353
353
|
plugins: await A(s)
|
|
354
354
|
}
|
|
355
355
|
);
|
|
356
|
-
if (
|
|
356
|
+
if (this.setupContentSync(e, c), g(o)) {
|
|
357
357
|
const d = document.getElementById(`${e}_input`);
|
|
358
|
-
d &&
|
|
358
|
+
d && R(d, c), i && S(c, i);
|
|
359
359
|
}
|
|
360
|
-
return
|
|
361
|
-
}
|
|
360
|
+
return c;
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Sets up the content synchronization for the editor.
|
|
364
|
+
*/
|
|
365
|
+
setupContentSync(t, e) {
|
|
366
|
+
const i = () => {
|
|
367
|
+
this.pushEvent(
|
|
368
|
+
"ckeditor5:change",
|
|
369
|
+
{
|
|
370
|
+
editorId: t,
|
|
371
|
+
data: H(e)
|
|
372
|
+
}
|
|
373
|
+
);
|
|
374
|
+
};
|
|
375
|
+
e.model.document.on("change:data", b(250, i)), i(), this.handleEvent("ckeditor5:set-data", ({ data: o }) => {
|
|
376
|
+
e.setData(o);
|
|
377
|
+
});
|
|
378
|
+
}
|
|
362
379
|
}
|
|
363
|
-
function
|
|
380
|
+
function H(n) {
|
|
381
|
+
return n.model.document.getRootNames().reduce((e, i) => (e[i] = n.getData({ rootName: i }), e), /* @__PURE__ */ Object.create({}));
|
|
382
|
+
}
|
|
383
|
+
function R(n, t) {
|
|
364
384
|
const e = () => {
|
|
365
385
|
n.value = t.getData();
|
|
366
386
|
};
|
|
367
|
-
t.model.document.on("change:data",
|
|
387
|
+
t.model.document.on("change:data", b(250, e)), e();
|
|
368
388
|
}
|
|
369
|
-
function
|
|
370
|
-
if (
|
|
389
|
+
function $(n, t) {
|
|
390
|
+
if (g(t))
|
|
371
391
|
return document.getElementById(`${n}_editor`);
|
|
372
392
|
const e = m(n);
|
|
373
393
|
return y(e, ({ content: i }) => i);
|
|
@@ -378,13 +398,13 @@ function N(n, t) {
|
|
|
378
398
|
if (i)
|
|
379
399
|
return i;
|
|
380
400
|
}
|
|
381
|
-
if (
|
|
401
|
+
if (g(t))
|
|
382
402
|
return document.getElementById(n)?.getAttribute("cke-initial-value") || "";
|
|
383
403
|
const e = m(n);
|
|
384
404
|
return y(e, ({ initialValue: i }) => i);
|
|
385
405
|
}
|
|
386
|
-
const
|
|
387
|
-
class T extends
|
|
406
|
+
const O = f(V);
|
|
407
|
+
class T extends p {
|
|
388
408
|
/**
|
|
389
409
|
* The name of the hook.
|
|
390
410
|
*/
|
|
@@ -410,7 +430,7 @@ class T extends b {
|
|
|
410
430
|
async mounted() {
|
|
411
431
|
const { editorId: t, name: e } = this.attrs;
|
|
412
432
|
this.mountedPromise = l.the.execute(t, (i) => {
|
|
413
|
-
const { ui: o } = i, r =
|
|
433
|
+
const { ui: o } = i, r = j(e), s = o.view[r];
|
|
414
434
|
if (!s) {
|
|
415
435
|
console.error(`Unknown UI part name: "${e}". Supported names are "toolbar" and "menubar".`);
|
|
416
436
|
return;
|
|
@@ -425,7 +445,7 @@ class T extends b {
|
|
|
425
445
|
this.el.style.display = "none", await this.mountedPromise, this.mountedPromise = null, this.el.innerHTML = "";
|
|
426
446
|
}
|
|
427
447
|
}
|
|
428
|
-
function
|
|
448
|
+
function j(n) {
|
|
429
449
|
switch (n) {
|
|
430
450
|
case "toolbar":
|
|
431
451
|
return "toolbar";
|
|
@@ -435,10 +455,10 @@ function x(n) {
|
|
|
435
455
|
return null;
|
|
436
456
|
}
|
|
437
457
|
}
|
|
438
|
-
const
|
|
439
|
-
CKEditor5:
|
|
458
|
+
const x = f(T), U = {
|
|
459
|
+
CKEditor5: O,
|
|
440
460
|
CKEditable: P,
|
|
441
|
-
CKUIPart:
|
|
461
|
+
CKUIPart: x
|
|
442
462
|
};
|
|
443
463
|
export {
|
|
444
464
|
l as EditorsRegistry,
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":["../src/shared/debounce.ts","../src/shared/hook.ts","../src/shared/map-object-values.ts","../src/shared/parse-int-if-not-null.ts","../src/hooks/editor/editors-registry.ts","../src/hooks/editable.ts","../src/hooks/editor/utils/is-single-editing-like-editor.ts","../src/hooks/editor/utils/load-editor-constructor.ts","../src/hooks/editor/utils/load-editor-plugins.ts","../src/hooks/editor/utils/query-all-editor-editables.ts","../src/hooks/editor/typings.ts","../src/hooks/editor/utils/read-preset-or-throw.ts","../src/hooks/editor/utils/set-editor-editable-height.ts","../src/hooks/editor/editor.ts","../src/hooks/ui-part.ts","../src/hooks/index.ts"],"sourcesContent":["export function debounce<T extends (...args: any[]) => any>(\n delay: number,\n callback: T,\n): (...args: Parameters<T>) => void {\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n\n return (...args: Parameters<T>): void => {\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n\n timeoutId = setTimeout(() => {\n callback(...args);\n }, delay);\n };\n}\n","import type { Hook, LiveSocket } from 'phoenix_live_view';\n\nimport type { RequiredBy } from '../types';\n\n/**\n * An abstract class that provides a class-based API for creating Phoenix LiveView hooks.\n *\n * This class defines the structure and lifecycle methods of a hook, which can be extended\n * to implement custom client-side behavior that integrates with LiveView.\n */\nexport abstract class ClassHook {\n /**\n * The DOM element the hook is attached to.\n * It includes an `instance` property to hold the hook instance.\n */\n el: HTMLElement & { instance: Hook; };\n\n /**\n * The LiveView socket instance, providing connection to the server.\n */\n liveSocket: LiveSocket;\n\n /**\n * Pushes an event from the client to the LiveView server process.\n * @param _event The name of the event.\n * @param _payload The data to send with the event.\n * @param _callback An optional function to be called with the server's reply.\n */\n pushEvent!: (\n _event: string,\n _payload: any,\n _callback?: (reply: any, ref: number) => void,\n ) => void;\n\n /**\n * Pushes an event to another hook on the page.\n * @param _selector The CSS selector of the target element with the hook.\n * @param _event The name of the event.\n * @param _payload The data to send with the event.\n * @param _callback An optional function to be called with the reply.\n */\n pushEventTo!: (\n _selector: string,\n _event: string,\n _payload: any,\n _callback?: (reply: any, ref: number) => void,\n ) => void;\n\n /**\n * Registers a handler for an event pushed from the server.\n * @param _event The name of the event to handle.\n * @param _callback The function to execute when the event is received.\n */\n handleEvent!: (\n _event: string,\n _callback: (payload: any) => void,\n ) => void;\n\n /**\n * Called when the hook has been mounted to the DOM.\n * This is the ideal place for initialization code.\n */\n abstract mounted(): void;\n\n /**\n * Called when the element has been removed from the DOM.\n * Perfect for cleanup tasks.\n */\n abstract destroyed(): void;\n\n /**\n * Called before the element is updated by a LiveView patch.\n */\n beforeUpdate?(): void;\n\n /**\n * Called when the client has disconnected from the server.\n */\n disconnected?(): void;\n\n /**\n * Called when the client has reconnected to the server.\n */\n reconnected?(): void;\n}\n\n/**\n * A factory function that adapts a class-based hook to the object-based API expected by Phoenix LiveView.\n *\n * @param constructor The constructor of the class that extends the `Hook` abstract class.\n */\nexport function makeHook(constructor: new () => ClassHook): RequiredBy<Hook<any>, 'mounted' | 'destroyed'> {\n return {\n /**\n * The mounted lifecycle callback for the LiveView hook object.\n * It creates an instance of the user-defined hook class and sets up the necessary properties and methods.\n */\n mounted(this: any) {\n const instance = new constructor();\n\n this.el.instance = instance;\n\n instance.el = this.el;\n instance.liveSocket = this.liveSocket;\n\n instance.pushEvent = (event, payload, callback) => this.pushEvent(event, payload, callback);\n instance.pushEventTo = (selector, event, payload, callback) => this.pushEventTo(selector, event, payload, callback);\n instance.handleEvent = (event, callback) => this.handleEvent(event, callback);\n\n instance.mounted?.();\n },\n\n /**\n * The beforeUpdate lifecycle callback that delegates to the hook instance.\n */\n beforeUpdate(this: any) {\n this.el.instance.beforeUpdate?.();\n },\n\n /**\n * The destroyed lifecycle callback that delegates to the hook instance.\n */\n destroyed(this: any) {\n this.el.instance.destroyed?.();\n },\n\n /**\n * The disconnected lifecycle callback that delegates to the hook instance.\n */\n disconnected(this: any) {\n this.el.instance.disconnected?.();\n },\n\n /**\n * The reconnected lifecycle callback that delegates to the hook instance.\n */\n reconnected(this: any) {\n this.el.instance.reconnected?.();\n },\n };\n}\n","/**\n * Maps the values of an object using a provided mapper function.\n *\n * @param obj The object whose values will be mapped.\n * @param mapper A function that takes a value and its key, and returns a new value.\n * @template T The type of the original values in the object.\n * @template U The type of the new values in the object.\n * @returns A new object with the same keys as the original, but with values transformed by\n */\nexport function mapObjectValues<T, U>(\n obj: Record<string, T>,\n mapper: (value: T, key: string) => U,\n): Record<string, U> {\n const mappedEntries = Object\n .entries(obj)\n .map(([key, value]) => [key, mapper(value, key)] as const);\n\n return Object.fromEntries(mappedEntries);\n}\n","export function parseIntIfNotNull(value: string | null): number | null {\n if (value === null) {\n return null;\n }\n\n const parsed = Number.parseInt(value, 10);\n\n return Number.isNaN(parsed) ? null : parsed;\n}\n","import type { Editor } from 'ckeditor5';\n\nimport type { EditorId } from './typings';\n\n/**\n * Allows other hooks to communicate with specific editors.\n * It provides a way to register editors and execute callbacks on them when they are available.\n */\nexport class EditorsRegistry {\n static readonly the = new EditorsRegistry();\n\n /**\n * Map of registered editors.\n */\n private readonly editors = new Map<EditorId | null, Editor>();\n\n /**\n * Map of callbacks that are waiting for an editor to be registered.\n */\n private readonly callbacks = new Map<EditorId | null, EditorCallback<any>[]>();\n\n /**\n * Private constructor to enforce singleton pattern.\n */\n private constructor() {}\n\n /**\n * Executes a function on an editor.\n * If the editor is not yet registered, it will wait for it to be registered.\n *\n * @param editorId The ID of the editor.\n * @param fn The function to execute.\n * @returns A promise that resolves with the result of the function.\n */\n execute<T, E extends Editor = Editor>(editorId: EditorId | null, fn: (editor: E) => T): Promise<Awaited<T>> {\n const { callbacks, editors } = this;\n const editor = editors.get(editorId);\n\n if (editor) {\n return Promise.resolve(fn(editor as E));\n }\n\n return new Promise((resolve) => {\n const callback = async (editor: E) => resolve(await fn(editor));\n\n if (!this.callbacks.has(editorId)) {\n callbacks.set(editorId, []);\n }\n\n callbacks.set(editorId, [\n ...callbacks.get(editorId)!,\n callback,\n ]);\n });\n }\n\n /**\n * Registers an editor.\n *\n * @param editorId The ID of the editor.\n * @param editor The editor instance.\n */\n register(editorId: EditorId | null, editor: Editor): void {\n const { editors, callbacks } = this;\n const callbacksForEditor = callbacks.get(editorId);\n\n if (editors.has(editorId)) {\n throw new Error(`Editor with ID \"${editorId}\" is already registered.`);\n }\n\n editors.set(editorId, editor);\n\n if (callbacksForEditor) {\n callbacksForEditor.forEach(callback => callback(editor));\n callbacks.delete(editorId);\n }\n\n // Register the first editor as the default editor.\n // This is useful for editables that do not specify an editor ID.\n if (this.editors.size === 1) {\n this.register(null, editor);\n }\n }\n\n /**\n * Un-registers an editor.\n *\n * @param editorId The ID of the editor.\n */\n unregister(editorId: EditorId | null): void {\n const { editors, callbacks } = this;\n\n if (!editors.has(editorId)) {\n throw new Error(`Editor with ID \"${editorId}\" is not registered.`);\n }\n\n if (editorId && this.editors.get(null) === editors.get(editorId)) {\n this.unregister(null);\n }\n\n editors.delete(editorId);\n callbacks.delete(editorId);\n }\n\n /**\n * Gets all registered editors.\n */\n getEditors(): Editor[] {\n return Array.from(this.editors.values());\n }\n\n /**\n * Checks if an editor with the given ID is registered.\n *\n * @param editorId The ID of the editor.\n * @returns `true` if the editor is registered, `false` otherwise.\n */\n hasEditor(editorId: EditorId | null): boolean {\n return this.editors.has(editorId);\n }\n\n /**\n * Gets a promise that resolves with the editor instance for the given ID.\n * If the editor is not registered yet, it will wait for it to be registered.\n *\n * @param editorId The ID of the editor.\n * @returns A promise that resolves with the editor instance.\n */\n waitForEditor<E extends Editor>(editorId: EditorId | null): Promise<E> {\n return this.execute(editorId, editor => editor as E);\n }\n\n /**\n * Destroys all registered editors and clears the registry.\n * This will call the `destroy` method on each editor.\n */\n async destroyAllEditors() {\n const promises = (\n Array\n .from(this.editors.values())\n .map(editor => editor.destroy())\n );\n\n this.editors.clear();\n this.callbacks.clear();\n\n await Promise.all(promises);\n }\n}\n\n/**\n * Callback type for editor operations.\n */\ntype EditorCallback<E extends Editor = Editor> = (editor: E) => void;\n","import type { MultiRootEditor } from 'ckeditor5';\n\nimport { ClassHook, debounce, makeHook } from '../shared';\nimport { EditorsRegistry } from './editor/editors-registry';\n\n/**\n * Editable hook for Phoenix LiveView. It allows you to create editables for multi-root editors.\n */\nclass EditableHookImpl extends ClassHook {\n /**\n * The name of the hook.\n */\n private mountedPromise: Promise<void> | null = null;\n\n /**\n * Attributes for the editable instance.\n */\n private get attrs() {\n const value = {\n editableId: this.el.getAttribute('id')!,\n editorId: this.el.getAttribute('data-cke-editor-id') || null,\n rootName: this.el.getAttribute('data-cke-editable-root-name')!,\n initialValue: this.el.getAttribute('data-cke-editable-initial-value') || '',\n };\n\n Object.defineProperty(this, 'attrs', {\n value,\n writable: false,\n configurable: false,\n enumerable: true,\n });\n\n return value;\n }\n\n /**\n * Mounts the editable component.\n */\n override async mounted() {\n const { editableId, editorId, rootName, initialValue } = this.attrs;\n const input = this.el.querySelector<HTMLInputElement>(`#${editableId}_input`);\n\n // If the editor is not registered yet, we will wait for it to be registered.\n this.mountedPromise = EditorsRegistry.the.execute(editorId, (editor: MultiRootEditor) => {\n const { ui, editing, model } = editor;\n\n if (model.document.getRoot(rootName)) {\n return;\n }\n\n editor.addRoot(rootName, {\n isUndoable: false,\n data: initialValue,\n });\n\n const contentElement = this.el.querySelector('[data-cke-editable-content]') as HTMLElement | null;\n const editable = ui.view.createEditable(rootName, contentElement!);\n\n ui.addEditable(editable);\n editing.view.forceRender();\n\n if (input) {\n syncEditorRootToInput(input, editor, rootName);\n }\n });\n }\n\n /**\n * Destroys the editable component. Unmounts root from the editor.\n */\n override async destroyed() {\n const { editorId, rootName } = this.attrs;\n\n // Let's hide the element during destruction to prevent flickering.\n this.el.style.display = 'none';\n\n // Let's wait for the mounted promise to resolve before proceeding with destruction.\n await this.mountedPromise;\n this.mountedPromise = null;\n\n // Unmount root from the editor.\n await EditorsRegistry.the.execute(editorId, (editor: MultiRootEditor) => {\n const root = editor.model.document.getRoot(rootName);\n\n if (root) {\n editor.detachEditable(root);\n editor.detachRoot(rootName, false);\n }\n });\n }\n}\n\n/**\n * Phoenix LiveView hook for CKEditor 5 editable elements.\n */\nexport const EditableHook = makeHook(EditableHookImpl);\n\n/**\n * Synchronizes the editor's root data to the corresponding input element.\n * This is used to keep the input value in sync with the editor's content.\n *\n * @param input - The input element to synchronize with the editor.\n * @param editor - The CKEditor instance.\n * @param rootName - The name of the root to synchronize.\n */\nfunction syncEditorRootToInput(input: HTMLInputElement, editor: MultiRootEditor, rootName: string) {\n const sync = () => {\n input.value = editor.getData({ rootName });\n };\n\n editor.model.document.on('change:data', debounce(100, sync));\n sync();\n}\n","import type { EditorType } from '../typings';\n\n/**\n * Checks if the given editor type is one of the single editing-like editors.\n *\n * @param editorType - The type of the editor to check.\n * @returns `true` if the editor type is 'inline', 'classic', or 'balloon', otherwise `false`.\n */\nexport function isSingleEditingLikeEditor(editorType: EditorType): boolean {\n return ['inline', 'classic', 'balloon', 'decoupled'].includes(editorType);\n}\n","import type { EditorType } from '../typings';\n\n/**\n * Returns the constructor for the specified CKEditor5 editor type.\n *\n * @param type - The type of the editor to load.\n * @returns A promise that resolves to the editor constructor.\n */\nexport async function loadEditorConstructor(type: EditorType) {\n const PKG = await import('ckeditor5');\n\n const editorMap = {\n inline: PKG.InlineEditor,\n balloon: PKG.BalloonEditor,\n classic: PKG.ClassicEditor,\n decoupled: PKG.DecoupledEditor,\n multiroot: PKG.MultiRootEditor,\n } as const;\n\n const EditorConstructor = editorMap[type];\n\n if (!EditorConstructor) {\n throw new Error(`Unsupported editor type: ${type}`);\n }\n\n return EditorConstructor;\n}\n","import type { PluginConstructor } from 'ckeditor5';\n\nimport type { EditorPlugin } from '../typings';\n\n/**\n * Loads CKEditor plugins from base and premium packages.\n * First tries to load from the base 'ckeditor5' package, then falls back to 'ckeditor5-premium-features'.\n *\n * @param plugins - Array of plugin names to load\n * @returns Promise that resolves to an array of loaded Plugin instances\n * @throws Error if a plugin is not found in either package\n */\nexport async function loadEditorPlugins(plugins: EditorPlugin[]): Promise<PluginConstructor[]> {\n const basePackage: Record<string, any> = await import('ckeditor5');\n let premiumPackage: Record<string, any> | null = null;\n\n const loaders = plugins.map(async (plugin) => {\n // Let's first try to load the plugin from the base package.\n // Coverage is disabled due to Vitest issues with mocking dynamic imports.\n\n /* v8 ignore start */\n const { [plugin]: basePkgImport } = basePackage;\n\n if (basePkgImport) {\n return basePkgImport as PluginConstructor;\n }\n\n // Plugin not found in base package, try premium package.\n if (!premiumPackage) {\n try {\n premiumPackage = await import('ckeditor5-premium-features');\n }\n catch (error) {\n console.error(`Failed to load premium package: ${error}`);\n }\n }\n\n const { [plugin]: premiumPkgImport } = premiumPackage || {};\n\n if (premiumPkgImport) {\n return premiumPkgImport as PluginConstructor;\n }\n\n // Plugin not found in either package, throw an error.\n throw new Error(`Plugin \"${plugin}\" not found in base or premium packages.`);\n /* v8 ignore end */\n });\n\n return Promise.all(loaders);\n}\n","import type { EditorId } from '../typings';\n\n/**\n * Queries all editable elements within a specific editor instance.\n *\n * @param editorId The ID of the editor to query.\n * @returns An object mapping editable names to their corresponding elements and initial values.\n */\nexport function queryAllEditorEditables(editorId: EditorId): Record<string, EditableItem> {\n const iterator = document.querySelectorAll<HTMLElement>(\n [\n `[data-cke-editor-id=\"${editorId}\"][data-cke-editable-root-name]`,\n '[data-cke-editable-root-name]:not([data-cke-editor-id])',\n ]\n .join(', '),\n );\n\n return (\n Array\n .from(iterator)\n .reduce<Record<string, EditableItem>>((acc, element) => {\n const name = element.getAttribute('data-cke-editable-root-name');\n const initialValue = element.getAttribute('data-cke-editable-initial-value') || '';\n const content = element.querySelector('[data-cke-editable-content]') as HTMLElement;\n\n if (!name || !content) {\n return acc;\n }\n\n return {\n ...acc,\n [name]: {\n content,\n initialValue,\n },\n };\n }, Object.create({}))\n );\n}\n\n/**\n * Type representing an editable item within an editor.\n */\nexport type EditableItem = {\n content: HTMLElement;\n initialValue: string;\n};\n","/**\n * List of supported CKEditor5 editor types.\n */\nexport const EDITOR_TYPES = ['inline', 'classic', 'balloon', 'decoupled', 'multiroot'] as const;\n\n/**\n * Represents a unique identifier for a CKEditor5 editor instance.\n * This is typically the ID of the HTML element that the editor is attached to.\n */\nexport type EditorId = string;\n\n/**\n * Defines editor type supported by CKEditor5. It must match list of available\n * editor types specified in `preset/parser.ex` file.\n */\nexport type EditorType = (typeof EDITOR_TYPES)[number];\n\n/**\n * Represents a CKEditor5 plugin as a string identifier.\n */\nexport type EditorPlugin = string;\n\n/**\n * Configuration object for CKEditor5 editor instance.\n */\nexport type EditorConfig = {\n /**\n * Array of plugin identifiers to be loaded by the editor.\n */\n plugins: EditorPlugin[];\n\n /**\n * Other configuration options are flexible and can be any key-value pairs.\n */\n [key: string]: any;\n};\n\n/**\n * Represents a license key for CKEditor5.\n */\nexport type EditorLicense = {\n key: string;\n};\n\n/**\n * Configuration object for the CKEditor5 hook.\n */\nexport type EditorPreset = {\n /**\n * The type of CKEditor5 editor to use.\n * Must be one of the predefined types: 'inline', 'classic', 'balloon', 'decoupled', or 'multiroot'.\n */\n type: EditorType;\n\n /**\n * The configuration object for the CKEditor5 editor.\n * This should match the configuration expected by CKEditor5.\n */\n config: EditorConfig;\n\n /**\n * The license key for CKEditor5.\n * This is required for using CKEditor5 with a valid license.\n */\n license: EditorLicense;\n\n /**\n * Optional height for the editor, if applicable.\n * This can be used to set a specific height for the editor instance.\n */\n editableHeight?: number;\n};\n","import type { EditorPreset } from '../typings';\n\nimport { EDITOR_TYPES } from '../typings';\n\n/**\n * Reads the hook configuration from the element's attribute and parses it as JSON.\n *\n * @param element - The HTML element that contains the hook configuration.\n * @returns The parsed hook configuration.\n */\nexport function readPresetOrThrow(element: HTMLElement): EditorPreset {\n const attributeValue = element.getAttribute('cke-preset');\n\n if (!attributeValue) {\n throw new Error('CKEditor5 hook requires a \"cke-preset\" attribute on the element.');\n }\n\n const { type, config, license } = JSON.parse(attributeValue);\n\n if (!type || !config || !license) {\n throw new Error('CKEditor5 hook configuration must include \"editor\", \"config\", and \"license\" properties.');\n }\n\n if (!EDITOR_TYPES.includes(type)) {\n throw new Error(`Invalid editor type: ${type}. Must be one of: ${EDITOR_TYPES.join(', ')}.`);\n }\n\n return {\n type,\n config,\n license,\n };\n}\n","import type { Editor } from 'ckeditor5';\n\n/**\n * Sets the height of the editable area in the CKEditor instance.\n *\n * @param instance - The CKEditor instance to modify.\n * @param height - The height in pixels to set for the editable area.\n */\nexport function setEditorEditableHeight(instance: Editor, height: number): void {\n const { editing } = instance;\n\n editing.view.change((writer) => {\n writer.setStyle('height', `${height}px`, editing.view.document.getRoot()!);\n });\n}\n","import type { Editor } from 'ckeditor5';\n\nimport type { EditorId, EditorType } from './typings';\n\nimport {\n debounce,\n mapObjectValues,\n parseIntIfNotNull,\n} from '../../shared';\nimport { ClassHook, makeHook } from '../../shared/hook';\nimport { EditorsRegistry } from './editors-registry';\nimport {\n isSingleEditingLikeEditor,\n loadEditorConstructor,\n loadEditorPlugins,\n queryAllEditorEditables,\n readPresetOrThrow,\n setEditorEditableHeight,\n} from './utils';\n\n/**\n * Editor hook for Phoenix LiveView.\n *\n * This class is a hook that can be used with Phoenix LiveView to integrate\n * the CKEditor 5 WYSIWYG editor.\n */\nclass EditorHookImpl extends ClassHook {\n /**\n * The name of the hook.\n */\n private editorPromise: Promise<Editor> | null = null;\n\n /**\n * Attributes for the editor instance.\n */\n private get attrs() {\n const value = {\n editorId: this.el.getAttribute('id')!,\n preset: readPresetOrThrow(this.el),\n editableHeight: parseIntIfNotNull(this.el.getAttribute('cke-editable-height')),\n };\n\n Object.defineProperty(this, 'attrs', {\n value,\n writable: false,\n configurable: false,\n enumerable: true,\n });\n\n return value;\n }\n\n /**\n * Mounts the editor component.\n */\n override async mounted() {\n this.editorPromise = this.createEditor();\n\n EditorsRegistry.the.register(this.attrs.editorId, await this.editorPromise);\n }\n\n /**\n * Destroys the editor instance when the component is destroyed.\n * This is important to prevent memory leaks and ensure that the editor is properly cleaned up.\n */\n override async destroyed() {\n // Let's hide the element during destruction to prevent flickering.\n this.el.style.display = 'none';\n\n // Let's wait for the mounted promise to resolve before proceeding with destruction.\n (await this.editorPromise)?.destroy();\n this.editorPromise = null;\n\n EditorsRegistry.the.unregister(this.attrs.editorId);\n }\n\n /**\n * Creates the CKEditor instance.\n */\n private createEditor = async () => {\n const { preset, editorId, editableHeight } = this.attrs;\n const { type, license, config: { plugins, ...config } } = preset;\n\n const Constructor = await loadEditorConstructor(type);\n const rootEditables = getInitialRootsContentElements(editorId, type);\n\n const editor = await Constructor.create(\n rootEditables as any,\n {\n ...config,\n initialData: getInitialRootsValues(editorId, type),\n licenseKey: license.key,\n plugins: await loadEditorPlugins(plugins),\n },\n );\n\n if (isSingleEditingLikeEditor(type)) {\n const input = document.getElementById(`${editorId}_input`) as HTMLInputElement | null;\n\n if (input) {\n syncEditorToInput(input, editor);\n }\n\n if (editableHeight) {\n setEditorEditableHeight(editor, editableHeight);\n }\n }\n\n return editor;\n };\n}\n\n/**\n * Synchronizes the editor's content with a hidden input field.\n *\n * @param input The input element to synchronize with the editor.\n * @param editor The CKEditor instance.\n */\nfunction syncEditorToInput(input: HTMLInputElement, editor: Editor) {\n const sync = () => {\n input.value = editor.getData();\n };\n\n editor.model.document.on('change:data', debounce(250, sync));\n sync();\n}\n\n/**\n * Gets the initial root elements for the editor based on its type.\n *\n * @param editorId The editor's ID.\n * @param type The type of the editor.\n * @returns The root element(s) for the editor.\n */\nfunction getInitialRootsContentElements(editorId: EditorId, type: EditorType) {\n if (isSingleEditingLikeEditor(type)) {\n return document.getElementById(`${editorId}_editor`)!;\n }\n\n const editables = queryAllEditorEditables(editorId);\n\n return mapObjectValues(editables, ({ content }) => content);\n}\n\n/**\n * Gets the initial data for the roots of the editor. If the editor is a single editing-like editor,\n * it retrieves the initial value from the element's attribute. Otherwise, it returns an object mapping\n * editable names to their initial values.\n *\n * @param editorId The editor's ID.\n * @param type The type of the editor.\n * @returns The initial values for the editor's roots.\n */\nfunction getInitialRootsValues(editorId: EditorId, type: EditorType) {\n // If the editor is decoupled, the initial value might be specified in the `main` editable.\n if (type === 'decoupled') {\n const mainEditableValue = queryAllEditorEditables(editorId)['main']?.initialValue;\n\n if (mainEditableValue) {\n return mainEditableValue;\n }\n }\n\n // Let's check initial value assigned to the editor element.\n if (isSingleEditingLikeEditor(type)) {\n const initialValue = document.getElementById(editorId)?.getAttribute('cke-initial-value') || '';\n\n return initialValue;\n }\n\n const editables = queryAllEditorEditables(editorId);\n\n return mapObjectValues(editables, ({ initialValue }) => initialValue);\n}\n\n/**\n * Phoenix LiveView hook for CKEditor 5.\n */\nexport const EditorHook = makeHook(EditorHookImpl);\n","import { ClassHook, makeHook } from '../shared';\nimport { EditorsRegistry } from './editor/editors-registry';\n\n/**\n * UI Part hook for Phoenix LiveView. It allows you to create UI parts for multi-root editors.\n */\nclass UIPartHookImpl extends ClassHook {\n /**\n * The name of the hook.\n */\n private mountedPromise: Promise<void> | null = null;\n\n /**\n * Attributes for the editable instance.\n */\n private get attrs() {\n const value = {\n editorId: this.el.getAttribute('data-cke-editor-id') || null,\n name: this.el.getAttribute('data-cke-ui-part-name')!,\n };\n\n Object.defineProperty(this, 'attrs', {\n value,\n writable: false,\n configurable: false,\n enumerable: true,\n });\n\n return value;\n }\n\n /**\n * Mounts the editable component.\n */\n override async mounted() {\n const { editorId, name } = this.attrs;\n\n // If the editor is not registered yet, we will wait for it to be registered.\n this.mountedPromise = EditorsRegistry.the.execute(editorId, (editor) => {\n const { ui } = editor;\n\n const uiViewName = mapUIPartView(name);\n const uiPart = (ui.view as any)[uiViewName!];\n\n if (!uiPart) {\n console.error(`Unknown UI part name: \"${name}\". Supported names are \"toolbar\" and \"menubar\".`);\n return;\n }\n\n this.el.appendChild(uiPart.element);\n });\n }\n\n /**\n * Destroys the editable component. Unmounts root from the editor.\n */\n override async destroyed() {\n // Let's hide the element during destruction to prevent flickering.\n this.el.style.display = 'none';\n\n // Let's wait for the mounted promise to resolve before proceeding with destruction.\n await this.mountedPromise;\n this.mountedPromise = null;\n\n // Unmount all UI parts from the editor.\n this.el.innerHTML = '';\n }\n}\n\n/**\n * Maps the UI part name to the corresponding view in the editor.\n */\nfunction mapUIPartView(name: string): string | null {\n switch (name) {\n case 'toolbar':\n return 'toolbar';\n\n case 'menubar':\n return 'menuBarView';\n\n default:\n return null;\n }\n}\n\n/**\n * Phoenix LiveView hook for CKEditor 5 UI parts.\n */\nexport const UIPartHook = makeHook(UIPartHookImpl);\n","import { EditableHook } from './editable';\nimport { EditorHook } from './editor';\nimport { UIPartHook } from './ui-part';\n\nexport const Hooks = {\n CKEditor5: EditorHook,\n CKEditable: EditableHook,\n CKUIPart: UIPartHook,\n};\n"],"names":["debounce","delay","callback","timeoutId","args","ClassHook","makeHook","constructor","instance","event","payload","selector","mapObjectValues","obj","mapper","mappedEntries","key","value","parseIntIfNotNull","parsed","EditorsRegistry","editorId","fn","callbacks","editors","editor","resolve","callbacksForEditor","promises","EditableHookImpl","editableId","rootName","initialValue","input","ui","editing","model","contentElement","editable","syncEditorRootToInput","root","EditableHook","sync","isSingleEditingLikeEditor","editorType","loadEditorConstructor","type","PKG","EditorConstructor","loadEditorPlugins","plugins","basePackage","premiumPackage","loaders","plugin","basePkgImport","error","premiumPkgImport","queryAllEditorEditables","iterator","acc","element","name","content","EDITOR_TYPES","readPresetOrThrow","attributeValue","config","license","setEditorEditableHeight","height","writer","EditorHookImpl","preset","editableHeight","Constructor","rootEditables","getInitialRootsContentElements","getInitialRootsValues","syncEditorToInput","editables","mainEditableValue","EditorHook","UIPartHookImpl","uiViewName","mapUIPartView","uiPart","UIPartHook","Hooks"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":["../src/shared/debounce.ts","../src/shared/hook.ts","../src/shared/map-object-values.ts","../src/shared/parse-int-if-not-null.ts","../src/hooks/editor/editors-registry.ts","../src/hooks/editable.ts","../src/hooks/editor/utils/is-single-editing-like-editor.ts","../src/hooks/editor/utils/load-editor-constructor.ts","../src/hooks/editor/utils/load-editor-plugins.ts","../src/hooks/editor/utils/query-all-editor-editables.ts","../src/hooks/editor/typings.ts","../src/hooks/editor/utils/read-preset-or-throw.ts","../src/hooks/editor/utils/set-editor-editable-height.ts","../src/hooks/editor/editor.ts","../src/hooks/ui-part.ts","../src/hooks/index.ts"],"sourcesContent":["export function debounce<T extends (...args: any[]) => any>(\n delay: number,\n callback: T,\n): (...args: Parameters<T>) => void {\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n\n return (...args: Parameters<T>): void => {\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n\n timeoutId = setTimeout(() => {\n callback(...args);\n }, delay);\n };\n}\n","import type { Hook, LiveSocket } from 'phoenix_live_view';\n\nimport type { RequiredBy } from '../types';\n\n/**\n * An abstract class that provides a class-based API for creating Phoenix LiveView hooks.\n *\n * This class defines the structure and lifecycle methods of a hook, which can be extended\n * to implement custom client-side behavior that integrates with LiveView.\n */\nexport abstract class ClassHook {\n /**\n * The DOM element the hook is attached to.\n * It includes an `instance` property to hold the hook instance.\n */\n el: HTMLElement & { instance: Hook; };\n\n /**\n * The LiveView socket instance, providing connection to the server.\n */\n liveSocket: LiveSocket;\n\n /**\n * Pushes an event from the client to the LiveView server process.\n * @param _event The name of the event.\n * @param _payload The data to send with the event.\n * @param _callback An optional function to be called with the server's reply.\n */\n pushEvent!: (\n _event: string,\n _payload: any,\n _callback?: (reply: any, ref: number) => void,\n ) => void;\n\n /**\n * Pushes an event to another hook on the page.\n * @param _selector The CSS selector of the target element with the hook.\n * @param _event The name of the event.\n * @param _payload The data to send with the event.\n * @param _callback An optional function to be called with the reply.\n */\n pushEventTo!: (\n _selector: string,\n _event: string,\n _payload: any,\n _callback?: (reply: any, ref: number) => void,\n ) => void;\n\n /**\n * Registers a handler for an event pushed from the server.\n * @param _event The name of the event to handle.\n * @param _callback The function to execute when the event is received.\n */\n handleEvent!: (\n _event: string,\n _callback: (payload: any) => void,\n ) => void;\n\n /**\n * Called when the hook has been mounted to the DOM.\n * This is the ideal place for initialization code.\n */\n abstract mounted(): void;\n\n /**\n * Called when the element has been removed from the DOM.\n * Perfect for cleanup tasks.\n */\n abstract destroyed(): void;\n\n /**\n * Called before the element is updated by a LiveView patch.\n */\n beforeUpdate?(): void;\n\n /**\n * Called when the client has disconnected from the server.\n */\n disconnected?(): void;\n\n /**\n * Called when the client has reconnected to the server.\n */\n reconnected?(): void;\n}\n\n/**\n * A factory function that adapts a class-based hook to the object-based API expected by Phoenix LiveView.\n *\n * @param constructor The constructor of the class that extends the `Hook` abstract class.\n */\nexport function makeHook(constructor: new () => ClassHook): RequiredBy<Hook<any>, 'mounted' | 'destroyed'> {\n return {\n /**\n * The mounted lifecycle callback for the LiveView hook object.\n * It creates an instance of the user-defined hook class and sets up the necessary properties and methods.\n */\n mounted(this: any) {\n const instance = new constructor();\n\n this.el.instance = instance;\n\n instance.el = this.el;\n instance.liveSocket = this.liveSocket;\n\n instance.pushEvent = (event, payload, callback) => this.pushEvent?.(event, payload, callback);\n instance.pushEventTo = (selector, event, payload, callback) => this.pushEventTo?.(selector, event, payload, callback);\n instance.handleEvent = (event, callback) => this.handleEvent?.(event, callback);\n\n instance.mounted?.();\n },\n\n /**\n * The beforeUpdate lifecycle callback that delegates to the hook instance.\n */\n beforeUpdate(this: any) {\n this.el.instance.beforeUpdate?.();\n },\n\n /**\n * The destroyed lifecycle callback that delegates to the hook instance.\n */\n destroyed(this: any) {\n this.el.instance.destroyed?.();\n },\n\n /**\n * The disconnected lifecycle callback that delegates to the hook instance.\n */\n disconnected(this: any) {\n this.el.instance.disconnected?.();\n },\n\n /**\n * The reconnected lifecycle callback that delegates to the hook instance.\n */\n reconnected(this: any) {\n this.el.instance.reconnected?.();\n },\n };\n}\n","/**\n * Maps the values of an object using a provided mapper function.\n *\n * @param obj The object whose values will be mapped.\n * @param mapper A function that takes a value and its key, and returns a new value.\n * @template T The type of the original values in the object.\n * @template U The type of the new values in the object.\n * @returns A new object with the same keys as the original, but with values transformed by\n */\nexport function mapObjectValues<T, U>(\n obj: Record<string, T>,\n mapper: (value: T, key: string) => U,\n): Record<string, U> {\n const mappedEntries = Object\n .entries(obj)\n .map(([key, value]) => [key, mapper(value, key)] as const);\n\n return Object.fromEntries(mappedEntries);\n}\n","export function parseIntIfNotNull(value: string | null): number | null {\n if (value === null) {\n return null;\n }\n\n const parsed = Number.parseInt(value, 10);\n\n return Number.isNaN(parsed) ? null : parsed;\n}\n","import type { Editor } from 'ckeditor5';\n\nimport type { EditorId } from './typings';\n\n/**\n * Allows other hooks to communicate with specific editors.\n * It provides a way to register editors and execute callbacks on them when they are available.\n */\nexport class EditorsRegistry {\n static readonly the = new EditorsRegistry();\n\n /**\n * Map of registered editors.\n */\n private readonly editors = new Map<EditorId | null, Editor>();\n\n /**\n * Map of callbacks that are waiting for an editor to be registered.\n */\n private readonly callbacks = new Map<EditorId | null, EditorCallback<any>[]>();\n\n /**\n * Private constructor to enforce singleton pattern.\n */\n private constructor() {}\n\n /**\n * Executes a function on an editor.\n * If the editor is not yet registered, it will wait for it to be registered.\n *\n * @param editorId The ID of the editor.\n * @param fn The function to execute.\n * @returns A promise that resolves with the result of the function.\n */\n execute<T, E extends Editor = Editor>(editorId: EditorId | null, fn: (editor: E) => T): Promise<Awaited<T>> {\n const { callbacks, editors } = this;\n const editor = editors.get(editorId);\n\n if (editor) {\n return Promise.resolve(fn(editor as E));\n }\n\n return new Promise((resolve) => {\n const callback = async (editor: E) => resolve(await fn(editor));\n\n if (!this.callbacks.has(editorId)) {\n callbacks.set(editorId, []);\n }\n\n callbacks.set(editorId, [\n ...callbacks.get(editorId)!,\n callback,\n ]);\n });\n }\n\n /**\n * Registers an editor.\n *\n * @param editorId The ID of the editor.\n * @param editor The editor instance.\n */\n register(editorId: EditorId | null, editor: Editor): void {\n const { editors, callbacks } = this;\n const callbacksForEditor = callbacks.get(editorId);\n\n if (editors.has(editorId)) {\n throw new Error(`Editor with ID \"${editorId}\" is already registered.`);\n }\n\n editors.set(editorId, editor);\n\n if (callbacksForEditor) {\n callbacksForEditor.forEach(callback => callback(editor));\n callbacks.delete(editorId);\n }\n\n // Register the first editor as the default editor.\n // This is useful for editables that do not specify an editor ID.\n if (this.editors.size === 1) {\n this.register(null, editor);\n }\n }\n\n /**\n * Un-registers an editor.\n *\n * @param editorId The ID of the editor.\n */\n unregister(editorId: EditorId | null): void {\n const { editors, callbacks } = this;\n\n if (!editors.has(editorId)) {\n throw new Error(`Editor with ID \"${editorId}\" is not registered.`);\n }\n\n if (editorId && this.editors.get(null) === editors.get(editorId)) {\n this.unregister(null);\n }\n\n editors.delete(editorId);\n callbacks.delete(editorId);\n }\n\n /**\n * Gets all registered editors.\n */\n getEditors(): Editor[] {\n return Array.from(this.editors.values());\n }\n\n /**\n * Checks if an editor with the given ID is registered.\n *\n * @param editorId The ID of the editor.\n * @returns `true` if the editor is registered, `false` otherwise.\n */\n hasEditor(editorId: EditorId | null): boolean {\n return this.editors.has(editorId);\n }\n\n /**\n * Gets a promise that resolves with the editor instance for the given ID.\n * If the editor is not registered yet, it will wait for it to be registered.\n *\n * @param editorId The ID of the editor.\n * @returns A promise that resolves with the editor instance.\n */\n waitForEditor<E extends Editor>(editorId: EditorId | null): Promise<E> {\n return this.execute(editorId, editor => editor as E);\n }\n\n /**\n * Destroys all registered editors and clears the registry.\n * This will call the `destroy` method on each editor.\n */\n async destroyAllEditors() {\n const promises = (\n Array\n .from(this.editors.values())\n .map(editor => editor.destroy())\n );\n\n this.editors.clear();\n this.callbacks.clear();\n\n await Promise.all(promises);\n }\n}\n\n/**\n * Callback type for editor operations.\n */\ntype EditorCallback<E extends Editor = Editor> = (editor: E) => void;\n","import type { MultiRootEditor } from 'ckeditor5';\n\nimport { ClassHook, debounce, makeHook } from '../shared';\nimport { EditorsRegistry } from './editor/editors-registry';\n\n/**\n * Editable hook for Phoenix LiveView. It allows you to create editables for multi-root editors.\n */\nclass EditableHookImpl extends ClassHook {\n /**\n * The name of the hook.\n */\n private mountedPromise: Promise<void> | null = null;\n\n /**\n * Attributes for the editable instance.\n */\n private get attrs() {\n const value = {\n editableId: this.el.getAttribute('id')!,\n editorId: this.el.getAttribute('data-cke-editor-id') || null,\n rootName: this.el.getAttribute('data-cke-editable-root-name')!,\n initialValue: this.el.getAttribute('data-cke-editable-initial-value') || '',\n };\n\n Object.defineProperty(this, 'attrs', {\n value,\n writable: false,\n configurable: false,\n enumerable: true,\n });\n\n return value;\n }\n\n /**\n * Mounts the editable component.\n */\n override async mounted() {\n const { editableId, editorId, rootName, initialValue } = this.attrs;\n const input = this.el.querySelector<HTMLInputElement>(`#${editableId}_input`);\n\n // If the editor is not registered yet, we will wait for it to be registered.\n this.mountedPromise = EditorsRegistry.the.execute(editorId, (editor: MultiRootEditor) => {\n const { ui, editing, model } = editor;\n\n if (model.document.getRoot(rootName)) {\n return;\n }\n\n editor.addRoot(rootName, {\n isUndoable: false,\n data: initialValue,\n });\n\n const contentElement = this.el.querySelector('[data-cke-editable-content]') as HTMLElement | null;\n const editable = ui.view.createEditable(rootName, contentElement!);\n\n ui.addEditable(editable);\n editing.view.forceRender();\n\n if (input) {\n syncEditorRootToInput(input, editor, rootName);\n }\n });\n }\n\n /**\n * Destroys the editable component. Unmounts root from the editor.\n */\n override async destroyed() {\n const { editorId, rootName } = this.attrs;\n\n // Let's hide the element during destruction to prevent flickering.\n this.el.style.display = 'none';\n\n // Let's wait for the mounted promise to resolve before proceeding with destruction.\n await this.mountedPromise;\n this.mountedPromise = null;\n\n // Unmount root from the editor.\n await EditorsRegistry.the.execute(editorId, (editor: MultiRootEditor) => {\n const root = editor.model.document.getRoot(rootName);\n\n if (root) {\n editor.detachEditable(root);\n editor.detachRoot(rootName, false);\n }\n });\n }\n}\n\n/**\n * Phoenix LiveView hook for CKEditor 5 editable elements.\n */\nexport const EditableHook = makeHook(EditableHookImpl);\n\n/**\n * Synchronizes the editor's root data to the corresponding input element.\n * This is used to keep the input value in sync with the editor's content.\n *\n * @param input - The input element to synchronize with the editor.\n * @param editor - The CKEditor instance.\n * @param rootName - The name of the root to synchronize.\n */\nfunction syncEditorRootToInput(input: HTMLInputElement, editor: MultiRootEditor, rootName: string) {\n const sync = () => {\n input.value = editor.getData({ rootName });\n };\n\n editor.model.document.on('change:data', debounce(100, sync));\n sync();\n}\n","import type { EditorType } from '../typings';\n\n/**\n * Checks if the given editor type is one of the single editing-like editors.\n *\n * @param editorType - The type of the editor to check.\n * @returns `true` if the editor type is 'inline', 'classic', or 'balloon', otherwise `false`.\n */\nexport function isSingleEditingLikeEditor(editorType: EditorType): boolean {\n return ['inline', 'classic', 'balloon', 'decoupled'].includes(editorType);\n}\n","import type { EditorType } from '../typings';\n\n/**\n * Returns the constructor for the specified CKEditor5 editor type.\n *\n * @param type - The type of the editor to load.\n * @returns A promise that resolves to the editor constructor.\n */\nexport async function loadEditorConstructor(type: EditorType) {\n const PKG = await import('ckeditor5');\n\n const editorMap = {\n inline: PKG.InlineEditor,\n balloon: PKG.BalloonEditor,\n classic: PKG.ClassicEditor,\n decoupled: PKG.DecoupledEditor,\n multiroot: PKG.MultiRootEditor,\n } as const;\n\n const EditorConstructor = editorMap[type];\n\n if (!EditorConstructor) {\n throw new Error(`Unsupported editor type: ${type}`);\n }\n\n return EditorConstructor;\n}\n","import type { PluginConstructor } from 'ckeditor5';\n\nimport type { EditorPlugin } from '../typings';\n\n/**\n * Loads CKEditor plugins from base and premium packages.\n * First tries to load from the base 'ckeditor5' package, then falls back to 'ckeditor5-premium-features'.\n *\n * @param plugins - Array of plugin names to load\n * @returns Promise that resolves to an array of loaded Plugin instances\n * @throws Error if a plugin is not found in either package\n */\nexport async function loadEditorPlugins(plugins: EditorPlugin[]): Promise<PluginConstructor[]> {\n const basePackage: Record<string, any> = await import('ckeditor5');\n let premiumPackage: Record<string, any> | null = null;\n\n const loaders = plugins.map(async (plugin) => {\n // Let's first try to load the plugin from the base package.\n // Coverage is disabled due to Vitest issues with mocking dynamic imports.\n\n /* v8 ignore start */\n const { [plugin]: basePkgImport } = basePackage;\n\n if (basePkgImport) {\n return basePkgImport as PluginConstructor;\n }\n\n // Plugin not found in base package, try premium package.\n if (!premiumPackage) {\n try {\n premiumPackage = await import('ckeditor5-premium-features');\n }\n catch (error) {\n console.error(`Failed to load premium package: ${error}`);\n }\n }\n\n const { [plugin]: premiumPkgImport } = premiumPackage || {};\n\n if (premiumPkgImport) {\n return premiumPkgImport as PluginConstructor;\n }\n\n // Plugin not found in either package, throw an error.\n throw new Error(`Plugin \"${plugin}\" not found in base or premium packages.`);\n /* v8 ignore end */\n });\n\n return Promise.all(loaders);\n}\n","import type { EditorId } from '../typings';\n\n/**\n * Queries all editable elements within a specific editor instance.\n *\n * @param editorId The ID of the editor to query.\n * @returns An object mapping editable names to their corresponding elements and initial values.\n */\nexport function queryAllEditorEditables(editorId: EditorId): Record<string, EditableItem> {\n const iterator = document.querySelectorAll<HTMLElement>(\n [\n `[data-cke-editor-id=\"${editorId}\"][data-cke-editable-root-name]`,\n '[data-cke-editable-root-name]:not([data-cke-editor-id])',\n ]\n .join(', '),\n );\n\n return (\n Array\n .from(iterator)\n .reduce<Record<string, EditableItem>>((acc, element) => {\n const name = element.getAttribute('data-cke-editable-root-name');\n const initialValue = element.getAttribute('data-cke-editable-initial-value') || '';\n const content = element.querySelector('[data-cke-editable-content]') as HTMLElement;\n\n if (!name || !content) {\n return acc;\n }\n\n return {\n ...acc,\n [name]: {\n content,\n initialValue,\n },\n };\n }, Object.create({}))\n );\n}\n\n/**\n * Type representing an editable item within an editor.\n */\nexport type EditableItem = {\n content: HTMLElement;\n initialValue: string;\n};\n","/**\n * List of supported CKEditor5 editor types.\n */\nexport const EDITOR_TYPES = ['inline', 'classic', 'balloon', 'decoupled', 'multiroot'] as const;\n\n/**\n * Represents a unique identifier for a CKEditor5 editor instance.\n * This is typically the ID of the HTML element that the editor is attached to.\n */\nexport type EditorId = string;\n\n/**\n * Defines editor type supported by CKEditor5. It must match list of available\n * editor types specified in `preset/parser.ex` file.\n */\nexport type EditorType = (typeof EDITOR_TYPES)[number];\n\n/**\n * Represents a CKEditor5 plugin as a string identifier.\n */\nexport type EditorPlugin = string;\n\n/**\n * Configuration object for CKEditor5 editor instance.\n */\nexport type EditorConfig = {\n /**\n * Array of plugin identifiers to be loaded by the editor.\n */\n plugins: EditorPlugin[];\n\n /**\n * Other configuration options are flexible and can be any key-value pairs.\n */\n [key: string]: any;\n};\n\n/**\n * Represents a license key for CKEditor5.\n */\nexport type EditorLicense = {\n key: string;\n};\n\n/**\n * Configuration object for the CKEditor5 hook.\n */\nexport type EditorPreset = {\n /**\n * The type of CKEditor5 editor to use.\n * Must be one of the predefined types: 'inline', 'classic', 'balloon', 'decoupled', or 'multiroot'.\n */\n type: EditorType;\n\n /**\n * The configuration object for the CKEditor5 editor.\n * This should match the configuration expected by CKEditor5.\n */\n config: EditorConfig;\n\n /**\n * The license key for CKEditor5.\n * This is required for using CKEditor5 with a valid license.\n */\n license: EditorLicense;\n\n /**\n * Optional height for the editor, if applicable.\n * This can be used to set a specific height for the editor instance.\n */\n editableHeight?: number;\n};\n","import type { EditorPreset } from '../typings';\n\nimport { EDITOR_TYPES } from '../typings';\n\n/**\n * Reads the hook configuration from the element's attribute and parses it as JSON.\n *\n * @param element - The HTML element that contains the hook configuration.\n * @returns The parsed hook configuration.\n */\nexport function readPresetOrThrow(element: HTMLElement): EditorPreset {\n const attributeValue = element.getAttribute('cke-preset');\n\n if (!attributeValue) {\n throw new Error('CKEditor5 hook requires a \"cke-preset\" attribute on the element.');\n }\n\n const { type, config, license } = JSON.parse(attributeValue);\n\n if (!type || !config || !license) {\n throw new Error('CKEditor5 hook configuration must include \"editor\", \"config\", and \"license\" properties.');\n }\n\n if (!EDITOR_TYPES.includes(type)) {\n throw new Error(`Invalid editor type: ${type}. Must be one of: ${EDITOR_TYPES.join(', ')}.`);\n }\n\n return {\n type,\n config,\n license,\n };\n}\n","import type { Editor } from 'ckeditor5';\n\n/**\n * Sets the height of the editable area in the CKEditor instance.\n *\n * @param instance - The CKEditor instance to modify.\n * @param height - The height in pixels to set for the editable area.\n */\nexport function setEditorEditableHeight(instance: Editor, height: number): void {\n const { editing } = instance;\n\n editing.view.change((writer) => {\n writer.setStyle('height', `${height}px`, editing.view.document.getRoot()!);\n });\n}\n","import type { Editor } from 'ckeditor5';\n\nimport type { EditorId, EditorType } from './typings';\n\nimport {\n debounce,\n mapObjectValues,\n parseIntIfNotNull,\n} from '../../shared';\nimport { ClassHook, makeHook } from '../../shared/hook';\nimport { EditorsRegistry } from './editors-registry';\nimport {\n isSingleEditingLikeEditor,\n loadEditorConstructor,\n loadEditorPlugins,\n queryAllEditorEditables,\n readPresetOrThrow,\n setEditorEditableHeight,\n} from './utils';\n\n/**\n * Editor hook for Phoenix LiveView.\n *\n * This class is a hook that can be used with Phoenix LiveView to integrate\n * the CKEditor 5 WYSIWYG editor.\n */\nclass EditorHookImpl extends ClassHook {\n /**\n * The name of the hook.\n */\n private editorPromise: Promise<Editor> | null = null;\n\n /**\n * Attributes for the editor instance.\n */\n private get attrs() {\n const value = {\n editorId: this.el.getAttribute('id')!,\n preset: readPresetOrThrow(this.el),\n editableHeight: parseIntIfNotNull(this.el.getAttribute('cke-editable-height')),\n };\n\n Object.defineProperty(this, 'attrs', {\n value,\n writable: false,\n configurable: false,\n enumerable: true,\n });\n\n return value;\n }\n\n /**\n * Mounts the editor component.\n */\n override async mounted() {\n this.editorPromise = this.createEditor();\n\n EditorsRegistry.the.register(this.attrs.editorId, await this.editorPromise);\n\n return this;\n }\n\n /**\n * Destroys the editor instance when the component is destroyed.\n * This is important to prevent memory leaks and ensure that the editor is properly cleaned up.\n */\n override async destroyed() {\n // Let's hide the element during destruction to prevent flickering.\n this.el.style.display = 'none';\n\n // Let's wait for the mounted promise to resolve before proceeding with destruction.\n (await this.editorPromise)?.destroy();\n this.editorPromise = null;\n\n EditorsRegistry.the.unregister(this.attrs.editorId);\n }\n\n /**\n * Creates the CKEditor instance.\n */\n private async createEditor() {\n const { preset, editorId, editableHeight } = this.attrs;\n const { type, license, config: { plugins, ...config } } = preset;\n\n const Constructor = await loadEditorConstructor(type);\n const rootEditables = getInitialRootsContentElements(editorId, type);\n\n const editor = await Constructor.create(\n rootEditables as any,\n {\n ...config,\n initialData: getInitialRootsValues(editorId, type),\n licenseKey: license.key,\n plugins: await loadEditorPlugins(plugins),\n },\n );\n\n this.setupContentSync(editorId, editor);\n\n if (isSingleEditingLikeEditor(type)) {\n const input = document.getElementById(`${editorId}_input`) as HTMLInputElement | null;\n\n if (input) {\n syncEditorToInput(input, editor);\n }\n\n if (editableHeight) {\n setEditorEditableHeight(editor, editableHeight);\n }\n }\n\n return editor;\n };\n\n /**\n * Sets up the content synchronization for the editor.\n */\n private setupContentSync(editorId: EditorId, editor: Editor) {\n const pushContentChange = () => {\n this.pushEvent(\n 'ckeditor5:change',\n {\n editorId,\n data: getEditorRootsValues(editor),\n },\n );\n };\n\n // Send content changes to the server.\n editor.model.document.on('change:data', debounce(250, pushContentChange));\n pushContentChange();\n\n // Handle incoming data from the server.\n this.handleEvent('ckeditor5:set-data', ({ data }) => {\n editor.setData(data);\n });\n }\n}\n\n/**\n * Gets the values of the editor's roots.\n *\n * @param editor The CKEditor instance.\n * @returns An object mapping root names to their content.\n */\nfunction getEditorRootsValues(editor: Editor) {\n const roots = editor.model.document.getRootNames();\n\n return roots.reduce<Record<string, string>>((acc, rootName) => {\n acc[rootName] = editor.getData({ rootName });\n return acc;\n }, Object.create({}));\n}\n\n/**\n * Synchronizes the editor's content with a hidden input field.\n *\n * @param input The input element to synchronize with the editor.\n * @param editor The CKEditor instance.\n */\nfunction syncEditorToInput(input: HTMLInputElement, editor: Editor) {\n const sync = () => {\n input.value = editor.getData();\n };\n\n editor.model.document.on('change:data', debounce(250, sync));\n sync();\n}\n\n/**\n * Gets the initial root elements for the editor based on its type.\n *\n * @param editorId The editor's ID.\n * @param type The type of the editor.\n * @returns The root element(s) for the editor.\n */\nfunction getInitialRootsContentElements(editorId: EditorId, type: EditorType) {\n if (isSingleEditingLikeEditor(type)) {\n return document.getElementById(`${editorId}_editor`)!;\n }\n\n const editables = queryAllEditorEditables(editorId);\n\n return mapObjectValues(editables, ({ content }) => content);\n}\n\n/**\n * Gets the initial data for the roots of the editor. If the editor is a single editing-like editor,\n * it retrieves the initial value from the element's attribute. Otherwise, it returns an object mapping\n * editable names to their initial values.\n *\n * @param editorId The editor's ID.\n * @param type The type of the editor.\n * @returns The initial values for the editor's roots.\n */\nfunction getInitialRootsValues(editorId: EditorId, type: EditorType) {\n // If the editor is decoupled, the initial value might be specified in the `main` editable.\n if (type === 'decoupled') {\n const mainEditableValue = queryAllEditorEditables(editorId)['main']?.initialValue;\n\n if (mainEditableValue) {\n return mainEditableValue;\n }\n }\n\n // Let's check initial value assigned to the editor element.\n if (isSingleEditingLikeEditor(type)) {\n const initialValue = document.getElementById(editorId)?.getAttribute('cke-initial-value') || '';\n\n return initialValue;\n }\n\n const editables = queryAllEditorEditables(editorId);\n\n return mapObjectValues(editables, ({ initialValue }) => initialValue);\n}\n\n/**\n * Phoenix LiveView hook for CKEditor 5.\n */\nexport const EditorHook = makeHook(EditorHookImpl);\n","import { ClassHook, makeHook } from '../shared';\nimport { EditorsRegistry } from './editor/editors-registry';\n\n/**\n * UI Part hook for Phoenix LiveView. It allows you to create UI parts for multi-root editors.\n */\nclass UIPartHookImpl extends ClassHook {\n /**\n * The name of the hook.\n */\n private mountedPromise: Promise<void> | null = null;\n\n /**\n * Attributes for the editable instance.\n */\n private get attrs() {\n const value = {\n editorId: this.el.getAttribute('data-cke-editor-id') || null,\n name: this.el.getAttribute('data-cke-ui-part-name')!,\n };\n\n Object.defineProperty(this, 'attrs', {\n value,\n writable: false,\n configurable: false,\n enumerable: true,\n });\n\n return value;\n }\n\n /**\n * Mounts the editable component.\n */\n override async mounted() {\n const { editorId, name } = this.attrs;\n\n // If the editor is not registered yet, we will wait for it to be registered.\n this.mountedPromise = EditorsRegistry.the.execute(editorId, (editor) => {\n const { ui } = editor;\n\n const uiViewName = mapUIPartView(name);\n const uiPart = (ui.view as any)[uiViewName!];\n\n if (!uiPart) {\n console.error(`Unknown UI part name: \"${name}\". Supported names are \"toolbar\" and \"menubar\".`);\n return;\n }\n\n this.el.appendChild(uiPart.element);\n });\n }\n\n /**\n * Destroys the editable component. Unmounts root from the editor.\n */\n override async destroyed() {\n // Let's hide the element during destruction to prevent flickering.\n this.el.style.display = 'none';\n\n // Let's wait for the mounted promise to resolve before proceeding with destruction.\n await this.mountedPromise;\n this.mountedPromise = null;\n\n // Unmount all UI parts from the editor.\n this.el.innerHTML = '';\n }\n}\n\n/**\n * Maps the UI part name to the corresponding view in the editor.\n */\nfunction mapUIPartView(name: string): string | null {\n switch (name) {\n case 'toolbar':\n return 'toolbar';\n\n case 'menubar':\n return 'menuBarView';\n\n default:\n return null;\n }\n}\n\n/**\n * Phoenix LiveView hook for CKEditor 5 UI parts.\n */\nexport const UIPartHook = makeHook(UIPartHookImpl);\n","import { EditableHook } from './editable';\nimport { EditorHook } from './editor';\nimport { UIPartHook } from './ui-part';\n\nexport const Hooks = {\n CKEditor5: EditorHook,\n CKEditable: EditableHook,\n CKUIPart: UIPartHook,\n};\n"],"names":["debounce","delay","callback","timeoutId","args","ClassHook","makeHook","constructor","instance","event","payload","selector","mapObjectValues","obj","mapper","mappedEntries","key","value","parseIntIfNotNull","parsed","EditorsRegistry","editorId","fn","callbacks","editors","editor","resolve","callbacksForEditor","promises","EditableHookImpl","editableId","rootName","initialValue","input","ui","editing","model","contentElement","editable","syncEditorRootToInput","root","EditableHook","sync","isSingleEditingLikeEditor","editorType","loadEditorConstructor","type","PKG","EditorConstructor","loadEditorPlugins","plugins","basePackage","premiumPackage","loaders","plugin","basePkgImport","error","premiumPkgImport","queryAllEditorEditables","iterator","acc","element","name","content","EDITOR_TYPES","readPresetOrThrow","attributeValue","config","license","setEditorEditableHeight","height","writer","EditorHookImpl","preset","editableHeight","Constructor","rootEditables","getInitialRootsContentElements","getInitialRootsValues","syncEditorToInput","pushContentChange","getEditorRootsValues","data","editables","mainEditableValue","EditorHook","UIPartHookImpl","uiViewName","mapUIPartView","uiPart","UIPartHook","Hooks"],"mappings":"AAAO,SAASA,EACdC,GACAC,GACkC;AAClC,MAAIC,IAAkD;AAEtD,SAAO,IAAIC,MAA8B;AACvC,IAAID,KACF,aAAaA,CAAS,GAGxBA,IAAY,WAAW,MAAM;AAC3B,MAAAD,EAAS,GAAGE,CAAI;AAAA,IAClB,GAAGH,CAAK;AAAA,EACV;AACF;ACLO,MAAeI,EAAU;AAAA;AAAA;AAAA;AAAA;AAAA,EAK9B;AAAA;AAAA;AAAA;AAAA,EAKA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA;AA+BF;AAOO,SAASC,EAASC,GAAkF;AACzG,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA,IAKL,UAAmB;AACjB,YAAMC,IAAW,IAAID,EAAA;AAErB,WAAK,GAAG,WAAWC,GAEnBA,EAAS,KAAK,KAAK,IACnBA,EAAS,aAAa,KAAK,YAE3BA,EAAS,YAAY,CAACC,GAAOC,GAASR,MAAa,KAAK,YAAYO,GAAOC,GAASR,CAAQ,GAC5FM,EAAS,cAAc,CAACG,GAAUF,GAAOC,GAASR,MAAa,KAAK,cAAcS,GAAUF,GAAOC,GAASR,CAAQ,GACpHM,EAAS,cAAc,CAACC,GAAOP,MAAa,KAAK,cAAcO,GAAOP,CAAQ,GAE9EM,EAAS,UAAA;AAAA,IACX;AAAA;AAAA;AAAA;AAAA,IAKA,eAAwB;AACtB,WAAK,GAAG,SAAS,eAAA;AAAA,IACnB;AAAA;AAAA;AAAA;AAAA,IAKA,YAAqB;AACnB,WAAK,GAAG,SAAS,YAAA;AAAA,IACnB;AAAA;AAAA;AAAA;AAAA,IAKA,eAAwB;AACtB,WAAK,GAAG,SAAS,eAAA;AAAA,IACnB;AAAA;AAAA;AAAA;AAAA,IAKA,cAAuB;AACrB,WAAK,GAAG,SAAS,cAAA;AAAA,IACnB;AAAA,EAAA;AAEJ;ACnIO,SAASI,EACdC,GACAC,GACmB;AACnB,QAAMC,IAAgB,OACnB,QAAQF,CAAG,EACX,IAAI,CAAC,CAACG,GAAKC,CAAK,MAAM,CAACD,GAAKF,EAAOG,GAAOD,CAAG,CAAC,CAAU;AAE3D,SAAO,OAAO,YAAYD,CAAa;AACzC;AClBO,SAASG,EAAkBD,GAAqC;AACrE,MAAIA,MAAU;AACZ,WAAO;AAGT,QAAME,IAAS,OAAO,SAASF,GAAO,EAAE;AAExC,SAAO,OAAO,MAAME,CAAM,IAAI,OAAOA;AACvC;ACAO,MAAMC,EAAgB;AAAA,EAC3B,OAAgB,MAAM,IAAIA,EAAA;AAAA;AAAA;AAAA;AAAA,EAKT,8BAAc,IAAA;AAAA;AAAA;AAAA;AAAA,EAKd,gCAAgB,IAAA;AAAA;AAAA;AAAA;AAAA,EAKzB,cAAc;AAAA,EAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUvB,QAAsCC,GAA2BC,GAA2C;AAC1G,UAAM,EAAE,WAAAC,GAAW,SAAAC,EAAA,IAAY,MACzBC,IAASD,EAAQ,IAAIH,CAAQ;AAEnC,WAAII,IACK,QAAQ,QAAQH,EAAGG,CAAW,CAAC,IAGjC,IAAI,QAAQ,CAACC,MAAY;AAC9B,YAAMxB,IAAW,OAAOuB,MAAcC,EAAQ,MAAMJ,EAAGG,CAAM,CAAC;AAE9D,MAAK,KAAK,UAAU,IAAIJ,CAAQ,KAC9BE,EAAU,IAAIF,GAAU,EAAE,GAG5BE,EAAU,IAAIF,GAAU;AAAA,QACtB,GAAGE,EAAU,IAAIF,CAAQ;AAAA,QACzBnB;AAAA,MAAA,CACD;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,SAASmB,GAA2BI,GAAsB;AACxD,UAAM,EAAE,SAAAD,GAAS,WAAAD,EAAA,IAAc,MACzBI,IAAqBJ,EAAU,IAAIF,CAAQ;AAEjD,QAAIG,EAAQ,IAAIH,CAAQ;AACtB,YAAM,IAAI,MAAM,mBAAmBA,CAAQ,0BAA0B;AAGvE,IAAAG,EAAQ,IAAIH,GAAUI,CAAM,GAExBE,MACFA,EAAmB,QAAQ,CAAAzB,MAAYA,EAASuB,CAAM,CAAC,GACvDF,EAAU,OAAOF,CAAQ,IAKvB,KAAK,QAAQ,SAAS,KACxB,KAAK,SAAS,MAAMI,CAAM;AAAA,EAE9B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,WAAWJ,GAAiC;AAC1C,UAAM,EAAE,SAAAG,GAAS,WAAAD,EAAA,IAAc;AAE/B,QAAI,CAACC,EAAQ,IAAIH,CAAQ;AACvB,YAAM,IAAI,MAAM,mBAAmBA,CAAQ,sBAAsB;AAGnE,IAAIA,KAAY,KAAK,QAAQ,IAAI,IAAI,MAAMG,EAAQ,IAAIH,CAAQ,KAC7D,KAAK,WAAW,IAAI,GAGtBG,EAAQ,OAAOH,CAAQ,GACvBE,EAAU,OAAOF,CAAQ;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA,EAKA,aAAuB;AACrB,WAAO,MAAM,KAAK,KAAK,QAAQ,QAAQ;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,UAAUA,GAAoC;AAC5C,WAAO,KAAK,QAAQ,IAAIA,CAAQ;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,cAAgCA,GAAuC;AACrE,WAAO,KAAK,QAAQA,GAAU,CAAAI,MAAUA,CAAW;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,oBAAoB;AACxB,UAAMG,IACJ,MACG,KAAK,KAAK,QAAQ,QAAQ,EAC1B,IAAI,CAAAH,MAAUA,EAAO,QAAA,CAAS;AAGnC,SAAK,QAAQ,MAAA,GACb,KAAK,UAAU,MAAA,GAEf,MAAM,QAAQ,IAAIG,CAAQ;AAAA,EAC5B;AACF;AC5IA,MAAMC,UAAyBxB,EAAU;AAAA;AAAA;AAAA;AAAA,EAI/B,iBAAuC;AAAA;AAAA;AAAA;AAAA,EAK/C,IAAY,QAAQ;AAClB,UAAMY,IAAQ;AAAA,MACZ,YAAY,KAAK,GAAG,aAAa,IAAI;AAAA,MACrC,UAAU,KAAK,GAAG,aAAa,oBAAoB,KAAK;AAAA,MACxD,UAAU,KAAK,GAAG,aAAa,6BAA6B;AAAA,MAC5D,cAAc,KAAK,GAAG,aAAa,iCAAiC,KAAK;AAAA,IAAA;AAG3E,kBAAO,eAAe,MAAM,SAAS;AAAA,MACnC,OAAAA;AAAA,MACA,UAAU;AAAA,MACV,cAAc;AAAA,MACd,YAAY;AAAA,IAAA,CACb,GAEMA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAe,UAAU;AACvB,UAAM,EAAE,YAAAa,GAAY,UAAAT,GAAU,UAAAU,GAAU,cAAAC,EAAA,IAAiB,KAAK,OACxDC,IAAQ,KAAK,GAAG,cAAgC,IAAIH,CAAU,QAAQ;AAG5E,SAAK,iBAAiBV,EAAgB,IAAI,QAAQC,GAAU,CAACI,MAA4B;AACvF,YAAM,EAAE,IAAAS,GAAI,SAAAC,GAAS,OAAAC,EAAA,IAAUX;AAE/B,UAAIW,EAAM,SAAS,QAAQL,CAAQ;AACjC;AAGF,MAAAN,EAAO,QAAQM,GAAU;AAAA,QACvB,YAAY;AAAA,QACZ,MAAMC;AAAA,MAAA,CACP;AAED,YAAMK,IAAiB,KAAK,GAAG,cAAc,6BAA6B,GACpEC,IAAWJ,EAAG,KAAK,eAAeH,GAAUM,CAAe;AAEjE,MAAAH,EAAG,YAAYI,CAAQ,GACvBH,EAAQ,KAAK,YAAA,GAETF,KACFM,EAAsBN,GAAOR,GAAQM,CAAQ;AAAA,IAEjD,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAe,YAAY;AACzB,UAAM,EAAE,UAAAV,GAAU,UAAAU,EAAA,IAAa,KAAK;AAGpC,SAAK,GAAG,MAAM,UAAU,QAGxB,MAAM,KAAK,gBACX,KAAK,iBAAiB,MAGtB,MAAMX,EAAgB,IAAI,QAAQC,GAAU,CAACI,MAA4B;AACvE,YAAMe,IAAOf,EAAO,MAAM,SAAS,QAAQM,CAAQ;AAEnD,MAAIS,MACFf,EAAO,eAAee,CAAI,GAC1Bf,EAAO,WAAWM,GAAU,EAAK;AAAA,IAErC,CAAC;AAAA,EACH;AACF;AAKO,MAAMU,IAAenC,EAASuB,CAAgB;AAUrD,SAASU,EAAsBN,GAAyBR,GAAyBM,GAAkB;AACjG,QAAMW,IAAO,MAAM;AACjB,IAAAT,EAAM,QAAQR,EAAO,QAAQ,EAAE,UAAAM,GAAU;AAAA,EAC3C;AAEA,EAAAN,EAAO,MAAM,SAAS,GAAG,eAAezB,EAAS,KAAK0C,CAAI,CAAC,GAC3DA,EAAA;AACF;ACxGO,SAASC,EAA0BC,GAAiC;AACzE,SAAO,CAAC,UAAU,WAAW,WAAW,WAAW,EAAE,SAASA,CAAU;AAC1E;ACFA,eAAsBC,EAAsBC,GAAkB;AAC5D,QAAMC,IAAM,MAAM,OAAO,WAAW,GAU9BC,IARY;AAAA,IAChB,QAAQD,EAAI;AAAA,IACZ,SAASA,EAAI;AAAA,IACb,SAASA,EAAI;AAAA,IACb,WAAWA,EAAI;AAAA,IACf,WAAWA,EAAI;AAAA,EAAA,EAGmBD,CAAI;AAExC,MAAI,CAACE;AACH,UAAM,IAAI,MAAM,4BAA4BF,CAAI,EAAE;AAGpD,SAAOE;AACT;ACdA,eAAsBC,EAAkBC,GAAuD;AAC7F,QAAMC,IAAmC,MAAM,OAAO,WAAW;AACjE,MAAIC,IAA6C;AAEjD,QAAMC,IAAUH,EAAQ,IAAI,OAAOI,MAAW;AAK5C,UAAM,EAAE,CAACA,CAAM,GAAGC,MAAkBJ;AAEpC,QAAII;AACF,aAAOA;AAIT,QAAI,CAACH;AACH,UAAI;AACF,QAAAA,IAAiB,MAAM,OAAO,4BAA4B;AAAA,MAC5D,SACOI,GAAO;AACZ,gBAAQ,MAAM,mCAAmCA,CAAK,EAAE;AAAA,MAC1D;AAGF,UAAM,EAAE,CAACF,CAAM,GAAGG,EAAA,IAAqBL,KAAkB,CAAA;AAEzD,QAAIK;AACF,aAAOA;AAIT,UAAM,IAAI,MAAM,WAAWH,CAAM,0CAA0C;AAAA,EAE7E,CAAC;AAED,SAAO,QAAQ,IAAID,CAAO;AAC5B;ACzCO,SAASK,EAAwBrC,GAAkD;AACxF,QAAMsC,IAAW,SAAS;AAAA,IACxB;AAAA,MACE,wBAAwBtC,CAAQ;AAAA,MAChC;AAAA,IAAA,EAEC,KAAK,IAAI;AAAA,EAAA;AAGd,SACE,MACG,KAAKsC,CAAQ,EACb,OAAqC,CAACC,GAAKC,MAAY;AACtD,UAAMC,IAAOD,EAAQ,aAAa,6BAA6B,GACzD7B,IAAe6B,EAAQ,aAAa,iCAAiC,KAAK,IAC1EE,IAAUF,EAAQ,cAAc,6BAA6B;AAEnE,WAAI,CAACC,KAAQ,CAACC,IACLH,IAGF;AAAA,MACL,GAAGA;AAAA,MACH,CAACE,CAAI,GAAG;AAAA,QACN,SAAAC;AAAA,QACA,cAAA/B;AAAA,MAAA;AAAA,IACF;AAAA,EAEJ,GAAG,uBAAO,OAAO,CAAA,CAAE,CAAC;AAE1B;ACnCO,MAAMgC,IAAe,CAAC,UAAU,WAAW,WAAW,aAAa,WAAW;ACO9E,SAASC,EAAkBJ,GAAoC;AACpE,QAAMK,IAAiBL,EAAQ,aAAa,YAAY;AAExD,MAAI,CAACK;AACH,UAAM,IAAI,MAAM,kEAAkE;AAGpF,QAAM,EAAE,MAAApB,GAAM,QAAAqB,GAAQ,SAAAC,MAAY,KAAK,MAAMF,CAAc;AAE3D,MAAI,CAACpB,KAAQ,CAACqB,KAAU,CAACC;AACvB,UAAM,IAAI,MAAM,yFAAyF;AAG3G,MAAI,CAACJ,EAAa,SAASlB,CAAI;AAC7B,UAAM,IAAI,MAAM,wBAAwBA,CAAI,qBAAqBkB,EAAa,KAAK,IAAI,CAAC,GAAG;AAG7F,SAAO;AAAA,IACL,MAAAlB;AAAA,IACA,QAAAqB;AAAA,IACA,SAAAC;AAAA,EAAA;AAEJ;ACxBO,SAASC,EAAwB7D,GAAkB8D,GAAsB;AAC9E,QAAM,EAAE,SAAAnC,MAAY3B;AAEpB,EAAA2B,EAAQ,KAAK,OAAO,CAACoC,MAAW;AAC9B,IAAAA,EAAO,SAAS,UAAU,GAAGD,CAAM,MAAMnC,EAAQ,KAAK,SAAS,QAAA,CAAU;AAAA,EAC3E,CAAC;AACH;ACYA,MAAMqC,UAAuBnE,EAAU;AAAA;AAAA;AAAA;AAAA,EAI7B,gBAAwC;AAAA;AAAA;AAAA;AAAA,EAKhD,IAAY,QAAQ;AAClB,UAAMY,IAAQ;AAAA,MACZ,UAAU,KAAK,GAAG,aAAa,IAAI;AAAA,MACnC,QAAQgD,EAAkB,KAAK,EAAE;AAAA,MACjC,gBAAgB/C,EAAkB,KAAK,GAAG,aAAa,qBAAqB,CAAC;AAAA,IAAA;AAG/E,kBAAO,eAAe,MAAM,SAAS;AAAA,MACnC,OAAAD;AAAA,MACA,UAAU;AAAA,MACV,cAAc;AAAA,MACd,YAAY;AAAA,IAAA,CACb,GAEMA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAe,UAAU;AACvB,gBAAK,gBAAgB,KAAK,aAAA,GAE1BG,EAAgB,IAAI,SAAS,KAAK,MAAM,UAAU,MAAM,KAAK,aAAa,GAEnE;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAe,YAAY;AAEzB,SAAK,GAAG,MAAM,UAAU,SAGvB,MAAM,KAAK,gBAAgB,QAAA,GAC5B,KAAK,gBAAgB,MAErBA,EAAgB,IAAI,WAAW,KAAK,MAAM,QAAQ;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,eAAe;AAC3B,UAAM,EAAE,QAAAqD,GAAQ,UAAApD,GAAU,gBAAAqD,EAAA,IAAmB,KAAK,OAC5C,EAAE,MAAA5B,GAAM,SAAAsB,GAAS,QAAQ,EAAE,SAAAlB,GAAS,GAAGiB,EAAA,EAAO,IAAMM,GAEpDE,IAAc,MAAM9B,EAAsBC,CAAI,GAC9C8B,IAAgBC,EAA+BxD,GAAUyB,CAAI,GAE7DrB,IAAS,MAAMkD,EAAY;AAAA,MAC/BC;AAAA,MACA;AAAA,QACE,GAAGT;AAAA,QACH,aAAaW,EAAsBzD,GAAUyB,CAAI;AAAA,QACjD,YAAYsB,EAAQ;AAAA,QACpB,SAAS,MAAMnB,EAAkBC,CAAO;AAAA,MAAA;AAAA,IAC1C;AAKF,QAFA,KAAK,iBAAiB7B,GAAUI,CAAM,GAElCkB,EAA0BG,CAAI,GAAG;AACnC,YAAMb,IAAQ,SAAS,eAAe,GAAGZ,CAAQ,QAAQ;AAEzD,MAAIY,KACF8C,EAAkB9C,GAAOR,CAAM,GAG7BiD,KACFL,EAAwB5C,GAAQiD,CAAc;AAAA,IAElD;AAEA,WAAOjD;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,iBAAiBJ,GAAoBI,GAAgB;AAC3D,UAAMuD,IAAoB,MAAM;AAC9B,WAAK;AAAA,QACH;AAAA,QACA;AAAA,UACE,UAAA3D;AAAA,UACA,MAAM4D,EAAqBxD,CAAM;AAAA,QAAA;AAAA,MACnC;AAAA,IAEJ;AAGA,IAAAA,EAAO,MAAM,SAAS,GAAG,eAAezB,EAAS,KAAKgF,CAAiB,CAAC,GACxEA,EAAA,GAGA,KAAK,YAAY,sBAAsB,CAAC,EAAE,MAAAE,QAAW;AACnD,MAAAzD,EAAO,QAAQyD,CAAI;AAAA,IACrB,CAAC;AAAA,EACH;AACF;AAQA,SAASD,EAAqBxD,GAAgB;AAG5C,SAFcA,EAAO,MAAM,SAAS,aAAA,EAEvB,OAA+B,CAACmC,GAAK7B,OAChD6B,EAAI7B,CAAQ,IAAIN,EAAO,QAAQ,EAAE,UAAAM,GAAU,GACpC6B,IACN,uBAAO,OAAO,CAAA,CAAE,CAAC;AACtB;AAQA,SAASmB,EAAkB9C,GAAyBR,GAAgB;AAClE,QAAMiB,IAAO,MAAM;AACjB,IAAAT,EAAM,QAAQR,EAAO,QAAA;AAAA,EACvB;AAEA,EAAAA,EAAO,MAAM,SAAS,GAAG,eAAezB,EAAS,KAAK0C,CAAI,CAAC,GAC3DA,EAAA;AACF;AASA,SAASmC,EAA+BxD,GAAoByB,GAAkB;AAC5E,MAAIH,EAA0BG,CAAI;AAChC,WAAO,SAAS,eAAe,GAAGzB,CAAQ,SAAS;AAGrD,QAAM8D,IAAYzB,EAAwBrC,CAAQ;AAElD,SAAOT,EAAgBuE,GAAW,CAAC,EAAE,SAAApB,EAAA,MAAcA,CAAO;AAC5D;AAWA,SAASe,EAAsBzD,GAAoByB,GAAkB;AAEnE,MAAIA,MAAS,aAAa;AACxB,UAAMsC,IAAoB1B,EAAwBrC,CAAQ,EAAE,MAAS;AAErE,QAAI+D;AACF,aAAOA;AAAA,EAEX;AAGA,MAAIzC,EAA0BG,CAAI;AAGhC,WAFqB,SAAS,eAAezB,CAAQ,GAAG,aAAa,mBAAmB,KAAK;AAK/F,QAAM8D,IAAYzB,EAAwBrC,CAAQ;AAElD,SAAOT,EAAgBuE,GAAW,CAAC,EAAE,cAAAnD,EAAA,MAAmBA,CAAY;AACtE;AAKO,MAAMqD,IAAa/E,EAASkE,CAAc;ACvNjD,MAAMc,UAAuBjF,EAAU;AAAA;AAAA;AAAA;AAAA,EAI7B,iBAAuC;AAAA;AAAA;AAAA;AAAA,EAK/C,IAAY,QAAQ;AAClB,UAAMY,IAAQ;AAAA,MACZ,UAAU,KAAK,GAAG,aAAa,oBAAoB,KAAK;AAAA,MACxD,MAAM,KAAK,GAAG,aAAa,uBAAuB;AAAA,IAAA;AAGpD,kBAAO,eAAe,MAAM,SAAS;AAAA,MACnC,OAAAA;AAAA,MACA,UAAU;AAAA,MACV,cAAc;AAAA,MACd,YAAY;AAAA,IAAA,CACb,GAEMA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAe,UAAU;AACvB,UAAM,EAAE,UAAAI,GAAU,MAAAyC,EAAA,IAAS,KAAK;AAGhC,SAAK,iBAAiB1C,EAAgB,IAAI,QAAQC,GAAU,CAACI,MAAW;AACtE,YAAM,EAAE,IAAAS,MAAOT,GAET8D,IAAaC,EAAc1B,CAAI,GAC/B2B,IAAUvD,EAAG,KAAaqD,CAAW;AAE3C,UAAI,CAACE,GAAQ;AACX,gBAAQ,MAAM,0BAA0B3B,CAAI,iDAAiD;AAC7F;AAAA,MACF;AAEA,WAAK,GAAG,YAAY2B,EAAO,OAAO;AAAA,IACpC,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAe,YAAY;AAEzB,SAAK,GAAG,MAAM,UAAU,QAGxB,MAAM,KAAK,gBACX,KAAK,iBAAiB,MAGtB,KAAK,GAAG,YAAY;AAAA,EACtB;AACF;AAKA,SAASD,EAAc1B,GAA6B;AAClD,UAAQA,GAAA;AAAA,IACN,KAAK;AACH,aAAO;AAAA,IAET,KAAK;AACH,aAAO;AAAA,IAET;AACE,aAAO;AAAA,EAAA;AAEb;AAKO,MAAM4B,IAAapF,EAASgF,CAAc,GCpFpCK,IAAQ;AAAA,EACnB,WAAWN;AAAA,EACX,YAAY5C;AAAA,EACZ,UAAUiD;AACZ;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../../../../src/hooks/editor/editor.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../../../../src/hooks/editor/editor.ts"],"names":[],"mappings":"AA0NA;;GAEG;AACH,eAAO,MAAM,UAAU,kGAA2B,CAAC"}
|
package/package.json
CHANGED
|
@@ -314,6 +314,78 @@ describe('editor hook', () => {
|
|
|
314
314
|
});
|
|
315
315
|
});
|
|
316
316
|
|
|
317
|
+
describe('socket events', () => {
|
|
318
|
+
beforeEach(() => {
|
|
319
|
+
vi.useFakeTimers();
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
afterEach(() => {
|
|
323
|
+
vi.useRealTimers();
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it('should push event to the server after changing data', async () => {
|
|
327
|
+
const hookElement = createEditorHtmlElement();
|
|
328
|
+
const pushSpy = vi.fn();
|
|
329
|
+
|
|
330
|
+
document.body.appendChild(hookElement);
|
|
331
|
+
EditorHook.mounted.call({
|
|
332
|
+
el: hookElement,
|
|
333
|
+
pushEvent: pushSpy,
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
const editor = await waitForTestEditor();
|
|
337
|
+
|
|
338
|
+
// First call after mount
|
|
339
|
+
expect(pushSpy).toHaveBeenCalledTimes(1);
|
|
340
|
+
expect(pushSpy).toHaveBeenCalledWith(
|
|
341
|
+
'ckeditor5:change',
|
|
342
|
+
{
|
|
343
|
+
editorId: hookElement.id,
|
|
344
|
+
data: {
|
|
345
|
+
main: '<p>Test content</p>',
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
undefined,
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
// CHeck if component responds to changes
|
|
352
|
+
editor.setData('<p>New content</p>');
|
|
353
|
+
|
|
354
|
+
await vi.advanceTimersByTimeAsync(500);
|
|
355
|
+
|
|
356
|
+
expect(pushSpy).toHaveBeenCalledTimes(2);
|
|
357
|
+
expect(pushSpy).toHaveBeenCalledWith(
|
|
358
|
+
'ckeditor5:change',
|
|
359
|
+
{
|
|
360
|
+
editorId: hookElement.id,
|
|
361
|
+
data: {
|
|
362
|
+
main: '<p>New content</p>',
|
|
363
|
+
},
|
|
364
|
+
},
|
|
365
|
+
undefined,
|
|
366
|
+
);
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it('should handle incoming data from the server', async () => {
|
|
370
|
+
const hookElement = createEditorHtmlElement();
|
|
371
|
+
const handleEventSpy = vi.fn();
|
|
372
|
+
|
|
373
|
+
document.body.appendChild(hookElement);
|
|
374
|
+
EditorHook.mounted.call({
|
|
375
|
+
el: hookElement,
|
|
376
|
+
handleEvent: handleEventSpy,
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
const editor = await waitForTestEditor();
|
|
380
|
+
|
|
381
|
+
// Simulate server event
|
|
382
|
+
const dataFromServer = '<p>Content from server</p>';
|
|
383
|
+
handleEventSpy.mock.calls[0];
|
|
384
|
+
|
|
385
|
+
expect(editor.getData()).toBe(dataFromServer);
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
|
|
317
389
|
describe('`cke-editable-height` attribute', () => {
|
|
318
390
|
it('should set the height of the editable area', async () => {
|
|
319
391
|
const editableHeight = 255;
|
|
@@ -57,6 +57,8 @@ class EditorHookImpl extends ClassHook {
|
|
|
57
57
|
this.editorPromise = this.createEditor();
|
|
58
58
|
|
|
59
59
|
EditorsRegistry.the.register(this.attrs.editorId, await this.editorPromise);
|
|
60
|
+
|
|
61
|
+
return this;
|
|
60
62
|
}
|
|
61
63
|
|
|
62
64
|
/**
|
|
@@ -77,7 +79,7 @@ class EditorHookImpl extends ClassHook {
|
|
|
77
79
|
/**
|
|
78
80
|
* Creates the CKEditor instance.
|
|
79
81
|
*/
|
|
80
|
-
private
|
|
82
|
+
private async createEditor() {
|
|
81
83
|
const { preset, editorId, editableHeight } = this.attrs;
|
|
82
84
|
const { type, license, config: { plugins, ...config } } = preset;
|
|
83
85
|
|
|
@@ -94,6 +96,8 @@ class EditorHookImpl extends ClassHook {
|
|
|
94
96
|
},
|
|
95
97
|
);
|
|
96
98
|
|
|
99
|
+
this.setupContentSync(editorId, editor);
|
|
100
|
+
|
|
97
101
|
if (isSingleEditingLikeEditor(type)) {
|
|
98
102
|
const input = document.getElementById(`${editorId}_input`) as HTMLInputElement | null;
|
|
99
103
|
|
|
@@ -108,6 +112,45 @@ class EditorHookImpl extends ClassHook {
|
|
|
108
112
|
|
|
109
113
|
return editor;
|
|
110
114
|
};
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Sets up the content synchronization for the editor.
|
|
118
|
+
*/
|
|
119
|
+
private setupContentSync(editorId: EditorId, editor: Editor) {
|
|
120
|
+
const pushContentChange = () => {
|
|
121
|
+
this.pushEvent(
|
|
122
|
+
'ckeditor5:change',
|
|
123
|
+
{
|
|
124
|
+
editorId,
|
|
125
|
+
data: getEditorRootsValues(editor),
|
|
126
|
+
},
|
|
127
|
+
);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// Send content changes to the server.
|
|
131
|
+
editor.model.document.on('change:data', debounce(250, pushContentChange));
|
|
132
|
+
pushContentChange();
|
|
133
|
+
|
|
134
|
+
// Handle incoming data from the server.
|
|
135
|
+
this.handleEvent('ckeditor5:set-data', ({ data }) => {
|
|
136
|
+
editor.setData(data);
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Gets the values of the editor's roots.
|
|
143
|
+
*
|
|
144
|
+
* @param editor The CKEditor instance.
|
|
145
|
+
* @returns An object mapping root names to their content.
|
|
146
|
+
*/
|
|
147
|
+
function getEditorRootsValues(editor: Editor) {
|
|
148
|
+
const roots = editor.model.document.getRootNames();
|
|
149
|
+
|
|
150
|
+
return roots.reduce<Record<string, string>>((acc, rootName) => {
|
|
151
|
+
acc[rootName] = editor.getData({ rootName });
|
|
152
|
+
return acc;
|
|
153
|
+
}, Object.create({}));
|
|
111
154
|
}
|
|
112
155
|
|
|
113
156
|
/**
|
package/src/shared/hook.ts
CHANGED
|
@@ -103,9 +103,9 @@ export function makeHook(constructor: new () => ClassHook): RequiredBy<Hook<any>
|
|
|
103
103
|
instance.el = this.el;
|
|
104
104
|
instance.liveSocket = this.liveSocket;
|
|
105
105
|
|
|
106
|
-
instance.pushEvent = (event, payload, callback) => this.pushEvent(event, payload, callback);
|
|
107
|
-
instance.pushEventTo = (selector, event, payload, callback) => this.pushEventTo(selector, event, payload, callback);
|
|
108
|
-
instance.handleEvent = (event, callback) => this.handleEvent(event, callback);
|
|
106
|
+
instance.pushEvent = (event, payload, callback) => this.pushEvent?.(event, payload, callback);
|
|
107
|
+
instance.pushEventTo = (selector, event, payload, callback) => this.pushEventTo?.(selector, event, payload, callback);
|
|
108
|
+
instance.handleEvent = (event, callback) => this.handleEvent?.(event, callback);
|
|
109
109
|
|
|
110
110
|
instance.mounted?.();
|
|
111
111
|
},
|