Packages
ckeditor5_phoenix
1.2.1
1.28.2
1.28.1
1.28.0
1.27.2
1.27.1
1.27.0
1.26.0
1.25.1
1.25.0
1.24.2
1.24.1
1.24.0
1.23.0
1.22.0
1.21.0
1.20.0
1.19.0
1.18.0
1.17.2
1.17.1
1.17.0
1.16.1
1.16.0
1.15.8
1.15.7
1.15.6
1.15.5
1.15.4
1.15.3
1.15.2
1.15.1
1.15.0
1.14.2
1.14.1
1.14.0
1.13.0
1.12.0
1.11.0
1.10.2
1.10.1
1.10.0
1.9.0
1.8.0
1.7.0
1.6.0
1.5.0
1.4.1
1.4.0
1.3.0
1.2.1
1.2.0
1.1.0
1.0.8
1.0.7
CKEditor 5 integration for Phoenix Framework
Current section
Files
Jump to
Current section
Files
dist/index.mjs
function g(n, t) {
let e = null;
return (...i) => {
e && clearTimeout(e), e = setTimeout(() => {
t(...i);
}, n);
};
}
class f {
/**
* The DOM element the hook is attached to.
* It includes an `instance` property to hold the hook instance.
*/
el;
/**
* The LiveView socket instance, providing connection to the server.
*/
liveSocket;
/**
* Pushes an event from the client to the LiveView server process.
* @param _event The name of the event.
* @param _payload The data to send with the event.
* @param _callback An optional function to be called with the server's reply.
*/
pushEvent;
/**
* Pushes an event to another hook on the page.
* @param _selector The CSS selector of the target element with the hook.
* @param _event The name of the event.
* @param _payload The data to send with the event.
* @param _callback An optional function to be called with the reply.
*/
pushEventTo;
/**
* Registers a handler for an event pushed from the server.
* @param _event The name of the event to handle.
* @param _callback The function to execute when the event is received.
*/
handleEvent;
}
function p(n) {
return {
/**
* The mounted lifecycle callback for the LiveView hook object.
* It creates an instance of the user-defined hook class and sets up the necessary properties and methods.
*/
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?.();
},
/**
* The beforeUpdate lifecycle callback that delegates to the hook instance.
*/
beforeUpdate() {
this.el.instance.beforeUpdate?.();
},
/**
* The destroyed lifecycle callback that delegates to the hook instance.
*/
destroyed() {
this.el.instance.destroyed?.();
},
/**
* The disconnected lifecycle callback that delegates to the hook instance.
*/
disconnected() {
this.el.instance.disconnected?.();
},
/**
* The reconnected lifecycle callback that delegates to the hook instance.
*/
reconnected() {
this.el.instance.reconnected?.();
}
};
}
function y(n, t) {
const e = Object.entries(n).map(([i, o]) => [i, t(o, i)]);
return Object.fromEntries(e);
}
function w(n) {
if (n === null)
return null;
const t = Number.parseInt(n, 10);
return Number.isNaN(t) ? null : t;
}
class c {
static the = new c();
/**
* Map of registered editors.
*/
editors = /* @__PURE__ */ new Map();
/**
* Map of callbacks that are waiting for an editor to be registered.
*/
callbacks = /* @__PURE__ */ new Map();
/**
* Private constructor to enforce singleton pattern.
*/
constructor() {
}
/**
* Executes a function on an editor.
* If the editor is not yet registered, it will wait for it to be registered.
*
* @param editorId The ID of the editor.
* @param fn The function to execute.
* @returns A promise that resolves with the result of the function.
*/
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
]);
});
}
/**
* Registers an editor.
*
* @param editorId The ID of the editor.
* @param editor The editor instance.
*/
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);
}
/**
* Un-registers an editor.
*
* @param editorId The ID of the editor.
*/
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);
}
/**
* Gets all registered editors.
*/
getEditors() {
return Array.from(this.editors.values());
}
/**
* Checks if an editor with the given ID is registered.
*
* @param editorId The ID of the editor.
* @returns `true` if the editor is registered, `false` otherwise.
*/
hasEditor(t) {
return this.editors.has(t);
}
/**
* Gets a promise that resolves with the editor instance for the given ID.
* If the editor is not registered yet, it will wait for it to be registered.
*
* @param editorId The ID of the editor.
* @returns A promise that resolves with the editor instance.
*/
waitForEditor(t) {
return this.execute(t, (e) => e);
}
/**
* Destroys all registered editors and clears the registry.
* This will call the `destroy` method on each editor.
*/
async destroyAllEditors() {
const t = Array.from(this.editors.values()).map((e) => e.destroy());
this.editors.clear(), this.callbacks.clear(), await Promise.all(t);
}
}
class P extends f {
/**
* The name of the hook.
*/
mountedPromise = null;
/**
* Attributes for the editable instance.
*/
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;
}
/**
* Mounts the editable component.
*/
async mounted() {
const { editableId: t, editorId: e, rootName: i, initialValue: o } = this.attrs, r = this.el.querySelector(`#${t}_input`);
this.mountedPromise = c.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]"), l = a.view.createEditable(i, m);
a.addEditable(l), u.view.forceRender(), r && I(r, s, i);
});
}
/**
* Destroys the editable component. Unmounts root from the editor.
*/
async destroyed() {
const { editorId: t, rootName: e } = this.attrs;
this.el.style.display = "none", await this.mountedPromise, this.mountedPromise = null, await c.the.execute(t, (i) => {
const o = i.model.document.getRoot(e);
o && (i.detachEditable(o), i.detachRoot(e, !1));
});
}
}
const v = p(P);
function I(n, t, e) {
const i = () => {
n.value = t.getData({ rootName: e });
};
t.model.document.on("change:data", g(100, i)), i();
}
function E(n) {
return ["inline", "classic", "balloon", "decoupled"].includes(n);
}
async function A(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 C(n) {
const t = await import("ckeditor5");
let e = null;
const i = n.map(async (o) => {
const { [o]: r } = t;
if (r)
return r;
if (!e)
try {
e = await import("ckeditor5-premium-features");
} catch (a) {
console.error(`Failed to load premium package: ${a}`);
}
const { [o]: s } = e || {};
if (s)
return s;
throw new Error(`Plugin "${o}" not found in base or premium packages.`);
});
return Promise.all(i);
}
function b(n) {
const t = document.querySelectorAll(
[
`[data-cke-editor-id="${n}"][data-cke-editable-root-name]`,
"[data-cke-editable-root-name]:not([data-cke-editor-id])"
].join(", ")
);
return Array.from(t).reduce((e, i) => {
const o = i.getAttribute("data-cke-editable-root-name"), r = i.getAttribute("data-cke-editable-initial-value") || "", s = i.querySelector("[data-cke-editable-content]");
return !o || !s ? e : {
...e,
[o]: {
content: s,
initialValue: r
}
};
}, /* @__PURE__ */ Object.create({}));
}
const k = ["inline", "classic", "balloon", "decoupled", "multiroot"];
function V(n) {
const t = n.getAttribute("cke-preset");
if (!t)
throw new Error('CKEditor5 hook requires a "cke-preset" attribute on the element.');
const { type: e, config: i, license: o } = JSON.parse(t);
if (!e || !i || !o)
throw new Error('CKEditor5 hook configuration must include "editor", "config", and "license" properties.');
if (!k.includes(e))
throw new Error(`Invalid editor type: ${e}. Must be one of: ${k.join(", ")}.`);
return {
type: e,
config: i,
license: o
};
}
function H(n, t) {
const { editing: e } = n;
e.view.change((i) => {
i.setStyle("height", `${t}px`, e.view.document.getRoot());
});
}
class R extends f {
/**
* The name of the hook.
*/
editorPromise = null;
/**
* Attributes for the editor instance.
*/
get attrs() {
const t = {
editorId: this.el.getAttribute("id"),
preset: V(this.el),
editableHeight: w(this.el.getAttribute("cke-editable-height")),
changeEvent: this.el.getAttribute("cke-change-event") !== null
};
return Object.defineProperty(this, "attrs", {
value: t,
writable: !1,
configurable: !1,
enumerable: !0
}), t;
}
/**
* Mounts the editor component.
*/
async mounted() {
return this.editorPromise = this.createEditor(), c.the.register(this.attrs.editorId, await this.editorPromise), this;
}
/**
* Destroys the editor instance when the component is destroyed.
* This is important to prevent memory leaks and ensure that the editor is properly cleaned up.
*/
async destroyed() {
this.el.style.display = "none", (await this.editorPromise)?.destroy(), this.editorPromise = null, c.the.unregister(this.attrs.editorId);
}
/**
* Creates the CKEditor instance.
*/
async createEditor() {
const { preset: t, editorId: e, editableHeight: i, changeEvent: o } = this.attrs, { type: r, license: s, config: { plugins: a, ...u } } = t, h = await A(r), m = O(e, r), l = await h.create(
m,
{
...u,
initialData: T(e, r),
licenseKey: s.key,
plugins: await C(a)
}
);
if (o && this.setupContentPush(e, l), this.handleEvent("ckeditor5:set-data", ({ data: d }) => {
l.setData(d);
}), E(r)) {
const d = document.getElementById(`${e}_input`);
d && S(d, l), i && H(l, i);
}
return l;
}
/**
* Setups the content push event for the editor.
*/
setupContentPush(t, e) {
const i = () => {
this.pushEvent(
"ckeditor5:change",
{
editorId: t,
data: $(e)
}
);
};
e.model.document.on("change:data", g(250, i)), i();
}
}
function $(n) {
return n.model.document.getRootNames().reduce((e, i) => (e[i] = n.getData({ rootName: i }), e), /* @__PURE__ */ Object.create({}));
}
function S(n, t) {
const e = () => {
n.value = t.getData();
};
t.model.document.on("change:data", g(250, e)), N(n)?.addEventListener("submit", e), e();
}
function N(n) {
return n.closest("form");
}
function O(n, t) {
if (E(t))
return document.getElementById(`${n}_editor`);
const e = b(n);
return y(e, ({ content: i }) => i);
}
function T(n, t) {
if (t === "decoupled") {
const i = b(n).main?.initialValue;
if (i)
return i;
}
if (E(t))
return document.getElementById(n)?.getAttribute("cke-initial-value") || "";
const e = b(n);
return y(e, ({ initialValue: i }) => i);
}
const j = p(R);
class x extends f {
/**
* The name of the hook.
*/
mountedPromise = null;
/**
* Attributes for the editable instance.
*/
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;
}
/**
* Mounts the editable component.
*/
async mounted() {
const { editorId: t, name: e } = this.attrs;
this.mountedPromise = c.the.execute(t, (i) => {
const { ui: o } = i, r = U(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);
});
}
/**
* Destroys the editable component. Unmounts root from the editor.
*/
async destroyed() {
this.el.style.display = "none", await this.mountedPromise, this.mountedPromise = null, this.el.innerHTML = "";
}
}
function U(n) {
switch (n) {
case "toolbar":
return "toolbar";
case "menubar":
return "menuBarView";
default:
return null;
}
}
const D = p(x), K = {
CKEditor5: j,
CKEditable: v,
CKUIPart: D
};
export {
c as EditorsRegistry,
K as Hooks
};
//# sourceMappingURL=index.mjs.map