ckeditor5-phoenix 1.1.0 → 1.2.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 +68 -64
- package/dist/index.mjs.map +1 -1
- package/dist/src/hooks/editor/editor.d.ts.map +1 -1
- package/dist/test-utils/editor/create-editor-html-element.d.ts +2 -1
- package/dist/test-utils/editor/create-editor-html-element.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/hooks/editor/editor.test.ts +67 -4
- package/src/hooks/editor/editor.ts +24 -10
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var v=Object.create;var k=Object.defineProperty;var I=Object.getOwnPropertyDescriptor;var A=Object.getOwnPropertyNames;var C=Object.getPrototypeOf,H=Object.prototype.hasOwnProperty;var R=(n,t,e,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of A(t))!H.call(n,o)&&o!==e&&k(n,o,{get:()=>t[o],enumerable:!(i=I(t,o))||i.enumerable});return n};var b=(n,t,e)=>(e=n!=null?v(C(n)):{},R(t||!n||!n.__esModule?k(e,"default",{value:n,enumerable:!0}):e,n));Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function f(n,t){let e=null;return(...i)=>{e&&clearTimeout(e),e=setTimeout(()=>{t(...i)},n)}}class g{el;liveSocket;pushEvent;pushEventTo;handleEvent}function E(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 P(n,t){const e=Object.entries(n).map(([i,o])=>[i,t(o,i)]);return Object.fromEntries(e)}function S(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 V 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 m=this.el.querySelector("[data-cke-editable-content]"),c=a.view.createEditable(i,m);a.addEditable(c),u.view.forceRender(),r&&O(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 $=E(V);function O(n,t,e){const i=()=>{n.value=t.getData({rootName:e})};t.model.document.on("change:data",f(100,i)),i()}function y(n){return["inline","classic","balloon","decoupled"].includes(n)}async function T(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 j(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 p(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 w=["inline","classic","balloon","decoupled","multiroot"];function N(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(!w.includes(e))throw new Error(`Invalid editor type: ${e}. Must be one of: ${w.join(", ")}.`);return{type:e,config:i,license:o}}function U(n,t){const{editing:e}=n;e.view.change(i=>{i.setStyle("height",`${t}px`,e.view.document.getRoot())})}class x extends g{editorPromise=null;get attrs(){const t={editorId:this.el.getAttribute("id"),preset:N(this.el),editableHeight:S(this.el.getAttribute("cke-editable-height")),pushEvents:this.el.getAttribute("cke-push-events")!==null};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,pushEvents:o}=this.attrs,{type:r,license:s,config:{plugins:a,...u}}=t,h=await T(r),m=q(e,r),c=await h.create(m,{...u,initialData:B(e,r),licenseKey:s.key,plugins:await j(a)});if(o&&this.setupContentPush(e,c),this.handleEvent("ckeditor5:set-data",({data:d})=>{c.setData(d)}),y(r)){const d=document.getElementById(`${e}_input`);d&&M(d,c),i&&U(c,i)}return c}setupContentPush(t,e){const i=()=>{this.pushEvent("ckeditor5:change",{editorId:t,data:D(e)})};e.model.document.on("change:data",f(250,i)),i()}}function D(n){return n.model.document.getRootNames().reduce((e,i)=>(e[i]=n.getData({rootName:i}),e),Object.create({}))}function M(n,t){const e=()=>{n.value=t.getData()};t.model.document.on("change:data",f(250,e)),K(n)?.addEventListener("submit",e),e()}function K(n){return n.closest("form")}function q(n,t){if(y(t))return document.getElementById(`${n}_editor`);const e=p(n);return P(e,({content:i})=>i)}function B(n,t){if(t==="decoupled"){const i=p(n).main?.initialValue;if(i)return i}if(y(t))return document.getElementById(n)?.getAttribute("cke-initial-value")||"";const e=p(n);return P(e,({initialValue:i})=>i)}const F=E(x);class L 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=_(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 _(n){switch(n){case"toolbar":return"toolbar";case"menubar":return"menuBarView";default:return null}}const z=E(L),G={CKEditor5:F,CKEditable:$,CKUIPart:z};exports.EditorsRegistry=l;exports.Hooks=G;
|
|
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 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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"}
|
|
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 pushEvents: this.el.getAttribute('cke-push-events') !== null,\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, pushEvents } = 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 (pushEvents) {\n this.setupContentPush(editorId, editor);\n }\n\n // Handle incoming data from the server.\n this.handleEvent('ckeditor5:set-data', ({ data }) => {\n editor.setData(data);\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 * Setups the content push event for the editor.\n */\n private setupContentPush(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 editor.model.document.on('change:data', debounce(250, pushContentChange));\n pushContentChange();\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 getParentFormElement(input)?.addEventListener('submit', sync);\n\n sync();\n}\n\n/**\n * Gets the parent form element of the given HTML element.\n *\n * @param element The HTML element to find the parent form for.\n * @returns The parent form element or null if not found.\n */\nfunction getParentFormElement(element: HTMLElement) {\n return element.closest('form') as HTMLFormElement | null;\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","pushEvents","Constructor","rootEditables","getInitialRootsContentElements","getInitialRootsValues","data","syncEditorToInput","pushContentChange","getEditorRootsValues","getParentFormElement","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 p(n, t) {
|
|
2
2
|
let e = null;
|
|
3
3
|
return (...i) => {
|
|
4
4
|
e && clearTimeout(e), e = setTimeout(() => {
|
|
@@ -6,7 +6,7 @@ function b(n, t) {
|
|
|
6
6
|
}, n);
|
|
7
7
|
};
|
|
8
8
|
}
|
|
9
|
-
class
|
|
9
|
+
class f {
|
|
10
10
|
/**
|
|
11
11
|
* The DOM element the hook is attached to.
|
|
12
12
|
* It includes an `instance` property to hold the hook instance.
|
|
@@ -38,7 +38,7 @@ class p {
|
|
|
38
38
|
*/
|
|
39
39
|
handleEvent;
|
|
40
40
|
}
|
|
41
|
-
function
|
|
41
|
+
function g(n) {
|
|
42
42
|
return {
|
|
43
43
|
/**
|
|
44
44
|
* The mounted lifecycle callback for the LiveView hook object.
|
|
@@ -78,14 +78,14 @@ 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 w(n) {
|
|
82
82
|
if (n === null)
|
|
83
83
|
return null;
|
|
84
84
|
const t = Number.parseInt(n, 10);
|
|
85
85
|
return Number.isNaN(t) ? null : t;
|
|
86
86
|
}
|
|
87
|
-
class
|
|
88
|
-
static the = new
|
|
87
|
+
class c {
|
|
88
|
+
static the = new c();
|
|
89
89
|
/**
|
|
90
90
|
* Map of registered editors.
|
|
91
91
|
*/
|
|
@@ -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 P extends f {
|
|
178
178
|
/**
|
|
179
179
|
* The name of the hook.
|
|
180
180
|
*/
|
|
@@ -201,7 +201,7 @@ class w extends p {
|
|
|
201
201
|
*/
|
|
202
202
|
async mounted() {
|
|
203
203
|
const { editableId: t, editorId: e, rootName: i, initialValue: o } = this.attrs, r = this.el.querySelector(`#${t}_input`);
|
|
204
|
-
this.mountedPromise =
|
|
204
|
+
this.mountedPromise = c.the.execute(e, (s) => {
|
|
205
205
|
const { ui: a, editing: u, model: h } = s;
|
|
206
206
|
if (h.document.getRoot(i))
|
|
207
207
|
return;
|
|
@@ -209,8 +209,8 @@ class w extends p {
|
|
|
209
209
|
isUndoable: !1,
|
|
210
210
|
data: o
|
|
211
211
|
});
|
|
212
|
-
const
|
|
213
|
-
a.addEditable(
|
|
212
|
+
const m = this.el.querySelector("[data-cke-editable-content]"), l = a.view.createEditable(i, m);
|
|
213
|
+
a.addEditable(l), u.view.forceRender(), r && I(r, s, i);
|
|
214
214
|
});
|
|
215
215
|
}
|
|
216
216
|
/**
|
|
@@ -218,23 +218,23 @@ class w extends p {
|
|
|
218
218
|
*/
|
|
219
219
|
async destroyed() {
|
|
220
220
|
const { editorId: t, rootName: e } = this.attrs;
|
|
221
|
-
this.el.style.display = "none", await this.mountedPromise, this.mountedPromise = null, await
|
|
221
|
+
this.el.style.display = "none", await this.mountedPromise, this.mountedPromise = null, await c.the.execute(t, (i) => {
|
|
222
222
|
const o = i.model.document.getRoot(e);
|
|
223
223
|
o && (i.detachEditable(o), i.detachRoot(e, !1));
|
|
224
224
|
});
|
|
225
225
|
}
|
|
226
226
|
}
|
|
227
|
-
const
|
|
228
|
-
function
|
|
227
|
+
const v = g(P);
|
|
228
|
+
function I(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", p(100, i)), i();
|
|
233
233
|
}
|
|
234
|
-
function
|
|
234
|
+
function E(n) {
|
|
235
235
|
return ["inline", "classic", "balloon", "decoupled"].includes(n);
|
|
236
236
|
}
|
|
237
|
-
async function
|
|
237
|
+
async function A(n) {
|
|
238
238
|
const t = await import("ckeditor5"), i = {
|
|
239
239
|
inline: t.InlineEditor,
|
|
240
240
|
balloon: t.BalloonEditor,
|
|
@@ -246,7 +246,7 @@ async function I(n) {
|
|
|
246
246
|
throw new Error(`Unsupported editor type: ${n}`);
|
|
247
247
|
return i;
|
|
248
248
|
}
|
|
249
|
-
async function
|
|
249
|
+
async function C(n) {
|
|
250
250
|
const t = await import("ckeditor5");
|
|
251
251
|
let e = null;
|
|
252
252
|
const i = n.map(async (o) => {
|
|
@@ -266,7 +266,7 @@ async function A(n) {
|
|
|
266
266
|
});
|
|
267
267
|
return Promise.all(i);
|
|
268
268
|
}
|
|
269
|
-
function
|
|
269
|
+
function b(n) {
|
|
270
270
|
const t = document.querySelectorAll(
|
|
271
271
|
[
|
|
272
272
|
`[data-cke-editor-id="${n}"][data-cke-editable-root-name]`,
|
|
@@ -284,29 +284,29 @@ function m(n) {
|
|
|
284
284
|
};
|
|
285
285
|
}, /* @__PURE__ */ Object.create({}));
|
|
286
286
|
}
|
|
287
|
-
const
|
|
288
|
-
function
|
|
287
|
+
const k = ["inline", "classic", "balloon", "decoupled", "multiroot"];
|
|
288
|
+
function V(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 (!k.includes(e))
|
|
296
|
+
throw new Error(`Invalid editor type: ${e}. Must be one of: ${k.join(", ")}.`);
|
|
297
297
|
return {
|
|
298
298
|
type: e,
|
|
299
299
|
config: i,
|
|
300
300
|
license: o
|
|
301
301
|
};
|
|
302
302
|
}
|
|
303
|
-
function
|
|
303
|
+
function H(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 R extends f {
|
|
310
310
|
/**
|
|
311
311
|
* The name of the hook.
|
|
312
312
|
*/
|
|
@@ -317,8 +317,9 @@ class V extends p {
|
|
|
317
317
|
get attrs() {
|
|
318
318
|
const t = {
|
|
319
319
|
editorId: this.el.getAttribute("id"),
|
|
320
|
-
preset:
|
|
321
|
-
editableHeight:
|
|
320
|
+
preset: V(this.el),
|
|
321
|
+
editableHeight: w(this.el.getAttribute("cke-editable-height")),
|
|
322
|
+
pushEvents: this.el.getAttribute("cke-push-events") !== null
|
|
322
323
|
};
|
|
323
324
|
return Object.defineProperty(this, "attrs", {
|
|
324
325
|
value: t,
|
|
@@ -331,80 +332,83 @@ class V extends p {
|
|
|
331
332
|
* Mounts the editor component.
|
|
332
333
|
*/
|
|
333
334
|
async mounted() {
|
|
334
|
-
return this.editorPromise = this.createEditor(),
|
|
335
|
+
return this.editorPromise = this.createEditor(), c.the.register(this.attrs.editorId, await this.editorPromise), this;
|
|
335
336
|
}
|
|
336
337
|
/**
|
|
337
338
|
* Destroys the editor instance when the component is destroyed.
|
|
338
339
|
* This is important to prevent memory leaks and ensure that the editor is properly cleaned up.
|
|
339
340
|
*/
|
|
340
341
|
async destroyed() {
|
|
341
|
-
this.el.style.display = "none", (await this.editorPromise)?.destroy(), this.editorPromise = null,
|
|
342
|
+
this.el.style.display = "none", (await this.editorPromise)?.destroy(), this.editorPromise = null, c.the.unregister(this.attrs.editorId);
|
|
342
343
|
}
|
|
343
344
|
/**
|
|
344
345
|
* Creates the CKEditor instance.
|
|
345
346
|
*/
|
|
346
347
|
async createEditor() {
|
|
347
|
-
const { preset: t, editorId: e, editableHeight: i } = this.attrs, { type:
|
|
348
|
-
|
|
348
|
+
const { preset: t, editorId: e, editableHeight: i, pushEvents: o } = this.attrs, { type: r, license: s, config: { plugins: a, ...u } } = t, h = await A(r), m = O(e, r), l = await h.create(
|
|
349
|
+
m,
|
|
349
350
|
{
|
|
350
|
-
...
|
|
351
|
-
initialData:
|
|
352
|
-
licenseKey:
|
|
353
|
-
plugins: await
|
|
351
|
+
...u,
|
|
352
|
+
initialData: T(e, r),
|
|
353
|
+
licenseKey: s.key,
|
|
354
|
+
plugins: await C(a)
|
|
354
355
|
}
|
|
355
356
|
);
|
|
356
|
-
if (this.
|
|
357
|
+
if (o && this.setupContentPush(e, l), this.handleEvent("ckeditor5:set-data", ({ data: d }) => {
|
|
358
|
+
l.setData(d);
|
|
359
|
+
}), E(r)) {
|
|
357
360
|
const d = document.getElementById(`${e}_input`);
|
|
358
|
-
d &&
|
|
361
|
+
d && S(d, l), i && H(l, i);
|
|
359
362
|
}
|
|
360
|
-
return
|
|
363
|
+
return l;
|
|
361
364
|
}
|
|
362
365
|
/**
|
|
363
|
-
*
|
|
366
|
+
* Setups the content push event for the editor.
|
|
364
367
|
*/
|
|
365
|
-
|
|
368
|
+
setupContentPush(t, e) {
|
|
366
369
|
const i = () => {
|
|
367
370
|
this.pushEvent(
|
|
368
371
|
"ckeditor5:change",
|
|
369
372
|
{
|
|
370
373
|
editorId: t,
|
|
371
|
-
data:
|
|
374
|
+
data: $(e)
|
|
372
375
|
}
|
|
373
376
|
);
|
|
374
377
|
};
|
|
375
|
-
e.model.document.on("change:data",
|
|
376
|
-
e.setData(o);
|
|
377
|
-
});
|
|
378
|
+
e.model.document.on("change:data", p(250, i)), i();
|
|
378
379
|
}
|
|
379
380
|
}
|
|
380
|
-
function
|
|
381
|
+
function $(n) {
|
|
381
382
|
return n.model.document.getRootNames().reduce((e, i) => (e[i] = n.getData({ rootName: i }), e), /* @__PURE__ */ Object.create({}));
|
|
382
383
|
}
|
|
383
|
-
function
|
|
384
|
+
function S(n, t) {
|
|
384
385
|
const e = () => {
|
|
385
386
|
n.value = t.getData();
|
|
386
387
|
};
|
|
387
|
-
t.model.document.on("change:data",
|
|
388
|
+
t.model.document.on("change:data", p(250, e)), N(n)?.addEventListener("submit", e), e();
|
|
389
|
+
}
|
|
390
|
+
function N(n) {
|
|
391
|
+
return n.closest("form");
|
|
388
392
|
}
|
|
389
|
-
function
|
|
390
|
-
if (
|
|
393
|
+
function O(n, t) {
|
|
394
|
+
if (E(t))
|
|
391
395
|
return document.getElementById(`${n}_editor`);
|
|
392
|
-
const e =
|
|
396
|
+
const e = b(n);
|
|
393
397
|
return y(e, ({ content: i }) => i);
|
|
394
398
|
}
|
|
395
|
-
function
|
|
399
|
+
function T(n, t) {
|
|
396
400
|
if (t === "decoupled") {
|
|
397
|
-
const i =
|
|
401
|
+
const i = b(n).main?.initialValue;
|
|
398
402
|
if (i)
|
|
399
403
|
return i;
|
|
400
404
|
}
|
|
401
|
-
if (
|
|
405
|
+
if (E(t))
|
|
402
406
|
return document.getElementById(n)?.getAttribute("cke-initial-value") || "";
|
|
403
|
-
const e =
|
|
407
|
+
const e = b(n);
|
|
404
408
|
return y(e, ({ initialValue: i }) => i);
|
|
405
409
|
}
|
|
406
|
-
const
|
|
407
|
-
class
|
|
410
|
+
const j = g(R);
|
|
411
|
+
class x extends f {
|
|
408
412
|
/**
|
|
409
413
|
* The name of the hook.
|
|
410
414
|
*/
|
|
@@ -429,8 +433,8 @@ class T extends p {
|
|
|
429
433
|
*/
|
|
430
434
|
async mounted() {
|
|
431
435
|
const { editorId: t, name: e } = this.attrs;
|
|
432
|
-
this.mountedPromise =
|
|
433
|
-
const { ui: o } = i, r =
|
|
436
|
+
this.mountedPromise = c.the.execute(t, (i) => {
|
|
437
|
+
const { ui: o } = i, r = U(e), s = o.view[r];
|
|
434
438
|
if (!s) {
|
|
435
439
|
console.error(`Unknown UI part name: "${e}". Supported names are "toolbar" and "menubar".`);
|
|
436
440
|
return;
|
|
@@ -445,7 +449,7 @@ class T extends p {
|
|
|
445
449
|
this.el.style.display = "none", await this.mountedPromise, this.mountedPromise = null, this.el.innerHTML = "";
|
|
446
450
|
}
|
|
447
451
|
}
|
|
448
|
-
function
|
|
452
|
+
function U(n) {
|
|
449
453
|
switch (n) {
|
|
450
454
|
case "toolbar":
|
|
451
455
|
return "toolbar";
|
|
@@ -455,13 +459,13 @@ function j(n) {
|
|
|
455
459
|
return null;
|
|
456
460
|
}
|
|
457
461
|
}
|
|
458
|
-
const
|
|
459
|
-
CKEditor5:
|
|
460
|
-
CKEditable:
|
|
461
|
-
CKUIPart:
|
|
462
|
+
const D = g(x), K = {
|
|
463
|
+
CKEditor5: j,
|
|
464
|
+
CKEditable: v,
|
|
465
|
+
CKUIPart: D
|
|
462
466
|
};
|
|
463
467
|
export {
|
|
464
|
-
|
|
465
|
-
|
|
468
|
+
c as EditorsRegistry,
|
|
469
|
+
K as Hooks
|
|
466
470
|
};
|
|
467
471
|
//# sourceMappingURL=index.mjs.map
|
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 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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"}
|
|
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 pushEvents: this.el.getAttribute('cke-push-events') !== null,\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, pushEvents } = 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 (pushEvents) {\n this.setupContentPush(editorId, editor);\n }\n\n // Handle incoming data from the server.\n this.handleEvent('ckeditor5:set-data', ({ data }) => {\n editor.setData(data);\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 * Setups the content push event for the editor.\n */\n private setupContentPush(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 editor.model.document.on('change:data', debounce(250, pushContentChange));\n pushContentChange();\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 getParentFormElement(input)?.addEventListener('submit', sync);\n\n sync();\n}\n\n/**\n * Gets the parent form element of the given HTML element.\n *\n * @param element The HTML element to find the parent form for.\n * @returns The parent form element or null if not found.\n */\nfunction getParentFormElement(element: HTMLElement) {\n return element.closest('form') as HTMLFormElement | null;\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","pushEvents","Constructor","rootEditables","getInitialRootsContentElements","getInitialRootsValues","data","syncEditorToInput","pushContentChange","getEditorRootsValues","getParentFormElement","editables","mainEditableValue","EditorHook","UIPartHookImpl","uiViewName","mapUIPartView","uiPart","UIPartHook","Hooks"],"mappings":"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"}
|
|
@@ -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":"AAwOA;;GAEG;AACH,eAAO,MAAM,UAAU,kGAA2B,CAAC"}
|
|
@@ -2,12 +2,13 @@ import { EditorPreset } from '../../src/hooks/editor/typings';
|
|
|
2
2
|
/**
|
|
3
3
|
* Creates a CKEditor HTML structure for testing.
|
|
4
4
|
*/
|
|
5
|
-
export declare function createEditorHtmlElement({ id, preset, initialValue, editableHeight, withInput, hookAttrs, }?: {
|
|
5
|
+
export declare function createEditorHtmlElement({ id, preset, initialValue, editableHeight, withInput, pushEvents, hookAttrs, }?: {
|
|
6
6
|
id?: string;
|
|
7
7
|
preset?: EditorPreset;
|
|
8
8
|
initialValue?: string | null;
|
|
9
9
|
editableHeight?: number | null;
|
|
10
10
|
withInput?: boolean;
|
|
11
|
+
pushEvents?: boolean;
|
|
11
12
|
hookAttrs?: Record<string, string>;
|
|
12
13
|
}): HTMLDivElement;
|
|
13
14
|
//# sourceMappingURL=create-editor-html-element.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-editor-html-element.d.ts","sourceRoot":"","sources":["../../../test-utils/editor/create-editor-html-element.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAKnE;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,EACE,EAAkB,EAClB,MAA6B,EAC7B,YAAoC,EACpC,cAAqB,EACrB,SAAiB,EACjB,SAAS,GACV,GAAE;IACD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC/B,
|
|
1
|
+
{"version":3,"file":"create-editor-html-element.d.ts","sourceRoot":"","sources":["../../../test-utils/editor/create-editor-html-element.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAKnE;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,EACE,EAAkB,EAClB,MAA6B,EAC7B,YAAoC,EACpC,cAAqB,EACrB,SAAiB,EACjB,UAAkB,EAClB,SAAS,GACV,GAAE;IACD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC/B,kBA0BP"}
|
package/package.json
CHANGED
|
@@ -256,6 +256,40 @@ describe('editor hook', () => {
|
|
|
256
256
|
vi.useFakeTimers();
|
|
257
257
|
});
|
|
258
258
|
|
|
259
|
+
it('should sync editor data with the input on parent form submit', async () => {
|
|
260
|
+
const initialValue = `<p>Initial content</p>`;
|
|
261
|
+
const hookElement = createEditorHtmlElement({
|
|
262
|
+
initialValue,
|
|
263
|
+
withInput: true,
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// Create a form and append the editor element to it
|
|
267
|
+
const form = document.createElement('form');
|
|
268
|
+
form.appendChild(hookElement);
|
|
269
|
+
document.body.appendChild(form);
|
|
270
|
+
|
|
271
|
+
EditorHook.mounted.call({ el: hookElement });
|
|
272
|
+
await waitForTestEditor();
|
|
273
|
+
|
|
274
|
+
const input = getTestEditorInput();
|
|
275
|
+
expect(input.value).toBe(initialValue);
|
|
276
|
+
|
|
277
|
+
// Change editor data
|
|
278
|
+
const newValue = '<p>Changed by user</p>';
|
|
279
|
+
const editor = await waitForTestEditor();
|
|
280
|
+
editor.setData(newValue);
|
|
281
|
+
|
|
282
|
+
// Input should not be updated yet (debounced)
|
|
283
|
+
expect(input.value).toBe(initialValue);
|
|
284
|
+
|
|
285
|
+
// Simulate form submit
|
|
286
|
+
const submitEvent = new Event('submit');
|
|
287
|
+
form.dispatchEvent(submitEvent);
|
|
288
|
+
|
|
289
|
+
// Now input should be synced immediately
|
|
290
|
+
expect(input.value).toBe(newValue);
|
|
291
|
+
});
|
|
292
|
+
|
|
259
293
|
afterEach(() => {
|
|
260
294
|
vi.useRealTimers();
|
|
261
295
|
});
|
|
@@ -323,8 +357,11 @@ describe('editor hook', () => {
|
|
|
323
357
|
vi.useRealTimers();
|
|
324
358
|
});
|
|
325
359
|
|
|
326
|
-
it('should push event to the server after changing data', async () => {
|
|
327
|
-
const hookElement = createEditorHtmlElement(
|
|
360
|
+
it('should push event to the server after changing data when `push events` enabled', async () => {
|
|
361
|
+
const hookElement = createEditorHtmlElement({
|
|
362
|
+
pushEvents: true,
|
|
363
|
+
});
|
|
364
|
+
|
|
328
365
|
const pushSpy = vi.fn();
|
|
329
366
|
|
|
330
367
|
document.body.appendChild(hookElement);
|
|
@@ -348,7 +385,7 @@ describe('editor hook', () => {
|
|
|
348
385
|
undefined,
|
|
349
386
|
);
|
|
350
387
|
|
|
351
|
-
//
|
|
388
|
+
// Check if component responds to changes
|
|
352
389
|
editor.setData('<p>New content</p>');
|
|
353
390
|
|
|
354
391
|
await vi.advanceTimersByTimeAsync(500);
|
|
@@ -366,8 +403,34 @@ describe('editor hook', () => {
|
|
|
366
403
|
);
|
|
367
404
|
});
|
|
368
405
|
|
|
369
|
-
it('should
|
|
406
|
+
it('should not push event to the server if `push events` is not enabled', async () => {
|
|
370
407
|
const hookElement = createEditorHtmlElement();
|
|
408
|
+
|
|
409
|
+
const pushSpy = vi.fn();
|
|
410
|
+
|
|
411
|
+
document.body.appendChild(hookElement);
|
|
412
|
+
EditorHook.mounted.call({
|
|
413
|
+
el: hookElement,
|
|
414
|
+
pushEvent: pushSpy,
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
const editor = await waitForTestEditor();
|
|
418
|
+
|
|
419
|
+
expect(pushSpy).not.toHaveBeenCalled();
|
|
420
|
+
|
|
421
|
+
// Check if component responds to changes
|
|
422
|
+
editor.setData('<p>New content</p>');
|
|
423
|
+
|
|
424
|
+
await vi.advanceTimersByTimeAsync(500);
|
|
425
|
+
|
|
426
|
+
expect(pushSpy).not.toHaveBeenCalled();
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
it('should handle incoming data from the server', async () => {
|
|
430
|
+
const hookElement = createEditorHtmlElement({
|
|
431
|
+
pushEvents: true,
|
|
432
|
+
});
|
|
433
|
+
|
|
371
434
|
const handleEventSpy = vi.fn();
|
|
372
435
|
|
|
373
436
|
document.body.appendChild(hookElement);
|
|
@@ -38,6 +38,7 @@ class EditorHookImpl extends ClassHook {
|
|
|
38
38
|
editorId: this.el.getAttribute('id')!,
|
|
39
39
|
preset: readPresetOrThrow(this.el),
|
|
40
40
|
editableHeight: parseIntIfNotNull(this.el.getAttribute('cke-editable-height')),
|
|
41
|
+
pushEvents: this.el.getAttribute('cke-push-events') !== null,
|
|
41
42
|
};
|
|
42
43
|
|
|
43
44
|
Object.defineProperty(this, 'attrs', {
|
|
@@ -80,7 +81,7 @@ class EditorHookImpl extends ClassHook {
|
|
|
80
81
|
* Creates the CKEditor instance.
|
|
81
82
|
*/
|
|
82
83
|
private async createEditor() {
|
|
83
|
-
const { preset, editorId, editableHeight } = this.attrs;
|
|
84
|
+
const { preset, editorId, editableHeight, pushEvents } = this.attrs;
|
|
84
85
|
const { type, license, config: { plugins, ...config } } = preset;
|
|
85
86
|
|
|
86
87
|
const Constructor = await loadEditorConstructor(type);
|
|
@@ -96,7 +97,14 @@ class EditorHookImpl extends ClassHook {
|
|
|
96
97
|
},
|
|
97
98
|
);
|
|
98
99
|
|
|
99
|
-
|
|
100
|
+
if (pushEvents) {
|
|
101
|
+
this.setupContentPush(editorId, editor);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Handle incoming data from the server.
|
|
105
|
+
this.handleEvent('ckeditor5:set-data', ({ data }) => {
|
|
106
|
+
editor.setData(data);
|
|
107
|
+
});
|
|
100
108
|
|
|
101
109
|
if (isSingleEditingLikeEditor(type)) {
|
|
102
110
|
const input = document.getElementById(`${editorId}_input`) as HTMLInputElement | null;
|
|
@@ -114,9 +122,9 @@ class EditorHookImpl extends ClassHook {
|
|
|
114
122
|
};
|
|
115
123
|
|
|
116
124
|
/**
|
|
117
|
-
*
|
|
125
|
+
* Setups the content push event for the editor.
|
|
118
126
|
*/
|
|
119
|
-
private
|
|
127
|
+
private setupContentPush(editorId: EditorId, editor: Editor) {
|
|
120
128
|
const pushContentChange = () => {
|
|
121
129
|
this.pushEvent(
|
|
122
130
|
'ckeditor5:change',
|
|
@@ -127,14 +135,8 @@ class EditorHookImpl extends ClassHook {
|
|
|
127
135
|
);
|
|
128
136
|
};
|
|
129
137
|
|
|
130
|
-
// Send content changes to the server.
|
|
131
138
|
editor.model.document.on('change:data', debounce(250, pushContentChange));
|
|
132
139
|
pushContentChange();
|
|
133
|
-
|
|
134
|
-
// Handle incoming data from the server.
|
|
135
|
-
this.handleEvent('ckeditor5:set-data', ({ data }) => {
|
|
136
|
-
editor.setData(data);
|
|
137
|
-
});
|
|
138
140
|
}
|
|
139
141
|
}
|
|
140
142
|
|
|
@@ -165,9 +167,21 @@ function syncEditorToInput(input: HTMLInputElement, editor: Editor) {
|
|
|
165
167
|
};
|
|
166
168
|
|
|
167
169
|
editor.model.document.on('change:data', debounce(250, sync));
|
|
170
|
+
getParentFormElement(input)?.addEventListener('submit', sync);
|
|
171
|
+
|
|
168
172
|
sync();
|
|
169
173
|
}
|
|
170
174
|
|
|
175
|
+
/**
|
|
176
|
+
* Gets the parent form element of the given HTML element.
|
|
177
|
+
*
|
|
178
|
+
* @param element The HTML element to find the parent form for.
|
|
179
|
+
* @returns The parent form element or null if not found.
|
|
180
|
+
*/
|
|
181
|
+
function getParentFormElement(element: HTMLElement) {
|
|
182
|
+
return element.closest('form') as HTMLFormElement | null;
|
|
183
|
+
}
|
|
184
|
+
|
|
171
185
|
/**
|
|
172
186
|
* Gets the initial root elements for the editor based on its type.
|
|
173
187
|
*
|