Independent sigpro vs sigpro-ui
All checks were successful
Deploy Docs to Synology / deploy (push) Successful in 3s

This commit is contained in:
2026-05-04 16:39:57 +02:00
parent 817de6a0ee
commit e6b172efa1
26 changed files with 1596 additions and 2368 deletions

250
dist/sigpro-ui.editor.esm.js vendored Normal file
View File

@@ -0,0 +1,250 @@
// src/editor.js
import { $, isFunc, h } from "./sigpro.js";
// src/helpers.js
var val = (val2) => typeof val2 === "function" ? val2() : val2;
var cls = (...classes) => classes.filter(Boolean).join(" ").trim();
// src/editor.js
var Editor = (p) => {
const { value, class: extraClass } = p;
let editorRef = null;
let savedRange = null;
const isSource = $(false);
const source = $("");
const count = $(0);
const refreshTick = $(0);
const showEmojis = $(false);
const emojis = ["\uD83D\uDE00", "\uD83D\uDE0A", "\uD83D\uDE09", "\uD83E\uDDD0", "\uD83D\uDE2E", "\uD83E\uDD14", "\uD83D\uDE05", "\uD83D\uDE02", "\uD83D\uDE0D", "\uD83D\uDE18", "\uD83E\uDD70", "\uD83D\uDC4D", "\uD83D\uDC4E", "\uD83D\uDC4C", "\uD83E\uDD1D", "\uD83E\uDD1E", "\uD83D\uDC4B", "\uD83D\uDC4F", "\uD83D\uDE4C", "\uD83D\uDE4F", "\uD83D\uDCAA", "☝️", "\uD83D\uDC47", "\uD83D\uDC48", "\uD83D\uDC49", "\uD83D\uDD95", "✅", "⚠️", "\uD83D\uDE80", "\uD83D\uDCE2", "✉️", "❤️"];
const saveSelection = () => {
const sel = window.getSelection();
if (sel.getRangeAt && sel.rangeCount)
savedRange = sel.getRangeAt(0);
};
const restoreSelection = () => {
if (savedRange) {
const sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(savedRange);
}
};
const triggerRefresh = () => {
refreshTick(refreshTick() + 1);
if (editorRef)
count(editorRef.innerText.length);
};
const notify = () => {
if (!editorRef)
return;
const html = editorRef.innerHTML;
if (isFunc(value))
value(html);
else
p.onchange?.(html);
triggerRefresh();
};
const exec = (cmd, val2 = null) => {
if (!editorRef)
return;
editorRef.focus();
if (savedRange)
restoreSelection();
document.execCommand(cmd, false, val2);
savedRange = null;
notify();
};
const openLightbox = (src) => {
const overlay = document.createElement("div");
overlay.style = `position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.9);z-index:9999;display:flex;align-items:center;justify-content:center;cursor:zoom-out;`;
const img = document.createElement("img");
img.src = src;
img.style = `max-width:95%;max-height:95%;box-shadow:0 0 30px rgba(0,0,0,0.5);border-radius:4px;`;
overlay.onclick = () => document.body.removeChild(overlay);
overlay.appendChild(img);
document.body.appendChild(overlay);
};
const handleUpload = (file) => {
if (!file)
return;
const reader = new FileReader;
reader.onload = (re) => {
if (file.type.startsWith("image/")) {
const imgHtml = `<div style="display:inline-block; resize:both; overflow:hidden; vertical-align:bottom; line-height:0; width:200px; height:auto; border:1px dashed #ccc; padding:2px; cursor:pointer;" class="resizable-img-container"><img src="${re.target.result}" style="width:100%; height:100%; object-fit:contain; pointer-events:none;"></div>&nbsp;`;
exec("insertHTML", imgHtml);
} else {
const linkHtml = `<a href="${re.target.result}" download="${file.name}" contenteditable="false" style="display:inline-flex; align-items:center; gap:5px; padding:4px 8px; border:1px solid #ccc; border-radius:4px; background:#f9f9f9; text-decoration:none; color:#333; font-size:12px; margin:2px; cursor:pointer;"><span class="icon-[lucide--paperclip] w-3 h-3"></span>${file.name}</a>&nbsp;`;
exec("insertHTML", linkHtml);
}
};
reader.readAsDataURL(file);
};
const queryState = (cmd, val2 = null) => {
refreshTick();
if (!editorRef || isSource())
return false;
try {
if (cmd === "formatBlock") {
let node = window.getSelection().getRangeAt(0).commonAncestorContainer;
while (node && node !== editorRef) {
if (node.nodeType === 1 && node.tagName === val2)
return true;
node = node.parentNode;
}
return false;
}
return document.queryCommandState(cmd);
} catch (e) {
return false;
}
};
const toolbar = h("div", { class: "flex flex-wrap items-center gap-1 p-2 border-b border-base-300 bg-base-200 sticky top-0 z-20" }, [
h("div", { class: "flex flex-wrap gap-1 flex-1 items-center" }, [
h("button", { type: "button", class: () => `btn btn-ghost btn-xs ${queryState("bold") ? "btn-active bg-primary/20" : ""}`, onclick: () => exec("bold") }, h("span", { class: "icon-[lucide--bold]" })),
h("button", { type: "button", class: () => `btn btn-ghost btn-xs ${queryState("italic") ? "btn-active bg-primary/20" : ""}`, onclick: () => exec("italic") }, h("span", { class: "icon-[lucide--italic]" })),
h("button", { type: "button", class: () => `btn btn-ghost btn-xs ${queryState("underline") ? "btn-active bg-primary/20" : ""}`, onclick: () => exec("underline") }, h("span", { class: "icon-[lucide--underline]" })),
h("input", { type: "color", class: "w-5 h-5 p-0 border-0 bg-transparent cursor-pointer", oninput: (e) => exec("foreColor", e.target.value) }),
h("span", { class: "w-px h-5 bg-base-300 mx-1" }),
h("button", {
type: "button",
class: "btn btn-ghost btn-xs",
onclick: () => exec("justifyLeft")
}, h("span", { class: "icon-[lucide--align-left]" })),
h("button", {
type: "button",
class: "btn btn-ghost btn-xs",
onclick: () => exec("justifyCenter")
}, h("span", { class: "icon-[lucide--align-center]" })),
h("button", {
type: "button",
class: "btn btn-ghost btn-xs",
onclick: () => exec("justifyRight")
}, h("span", { class: "icon-[lucide--align-right]" })),
h("span", { class: "w-px h-5 bg-base-300 mx-1" }),
h("button", { type: "button", class: "btn btn-ghost btn-xs", onclick: () => exec("insertUnorderedList") }, h("span", { class: "icon-[lucide--list]" })),
h("button", { type: "button", class: "btn btn-ghost btn-xs", onclick: () => exec("insertOrderedList") }, h("span", { class: "icon-[lucide--list-ordered]" })),
h("button", { type: "button", class: "btn btn-ghost btn-xs", onclick: () => exec("outdent") }, h("span", { class: "icon-[lucide--indent-decrease]" })),
h("button", { type: "button", class: "btn btn-ghost btn-xs", onclick: () => exec("indent") }, h("span", { class: "icon-[lucide--indent-increase]" })),
h("button", { type: "button", class: () => `btn btn-ghost btn-xs ${queryState("formatBlock", "BLOCKQUOTE") ? "btn-active" : ""}`, onclick: () => exec("formatBlock", queryState("formatBlock", "BLOCKQUOTE") ? "P" : "BLOCKQUOTE") }, h("span", { class: "icon-[lucide--quote]" })),
h("span", { class: "w-px h-5 bg-base-300 mx-1" }),
h("button", { type: "button", class: "btn btn-ghost btn-xs", onclick: () => {
const url = window.prompt("URL:");
if (url)
exec("createLink", url);
} }, h("span", { class: "icon-[lucide--link]" })),
h("button", { type: "button", class: "btn btn-ghost btn-xs", onclick: () => {
const input = document.createElement("input");
input.type = "file";
input.onchange = (e) => handleUpload(e.target.files[0]);
input.click();
} }, h("span", { class: "icon-[lucide--paperclip]" })),
h("div", { class: "relative" }, [
h("button", { type: "button", class: "btn btn-ghost btn-xs", onclick: (e) => {
e.stopPropagation();
saveSelection();
showEmojis(!showEmojis());
} }, h("span", { class: "icon-[lucide--smile]" })),
h("div", { class: "absolute top-full left-0 mt-1 p-2 bg-base-100 border border-base-300 shadow-xl rounded-box w-52 z-50 flex flex-wrap gap-1", style: () => showEmojis() ? "display:flex" : "display:none" }, emojis.map((emo) => h("span", { class: "cursor-pointer hover:bg-base-200 p-1 rounded text-lg", onclick: (e) => {
e.stopPropagation();
exec("insertText", emo);
showEmojis(false);
} }, emo)))
]),
h("span", { class: "w-px h-5 bg-base-300 mx-1" }),
h("button", { type: "button", class: "btn btn-ghost btn-xs", onclick: () => exec("undo") }, h("span", { class: "icon-[lucide--undo-2]" })),
h("button", { type: "button", class: "btn btn-ghost btn-xs", onclick: () => exec("redo") }, h("span", { class: "icon-[lucide--redo-2]" }))
]),
h("button", { type: "button", class: () => `btn btn-ghost btn-xs ${isSource() ? "btn-active" : ""}`, onclick: () => {
if (!isSource())
source(editorRef?.innerHTML || "");
else if (editorRef) {
editorRef.innerHTML = source();
notify();
}
isSource(!isSource());
} }, h("span", { class: "icon-[lucide--code-2]" }))
]);
if (typeof document !== "undefined" && !document.getElementById("editor-styles")) {
const style = document.createElement("style");
style.id = "editor-styles";
style.textContent = `
[contenteditable="true"] div,
[contenteditable="true"] p {
margin: 0;
padding: 0;
}
`;
document.head.appendChild(style);
}
return h("div", { class: cls("border border-base-300 rounded-box bg-base-100 overflow-hidden shadow-sm flex flex-col", extraClass) }, [
toolbar,
h("div", { class: "relative flex-1 flex flex-col", onclick: () => showEmojis(false) }, [
h("div", {
ref: (el) => {
if (!editorRef && el) {
editorRef = el;
el.innerHTML = val(value) || "";
document.execCommand("defaultParagraphSeparator", false, "br");
el.addEventListener("click", (e) => {
const container = e.target.closest(".resizable-img-container");
if (container) {
const img = container.querySelector("img");
if (img)
openLightbox(img.src);
}
});
}
},
style: () => `min-height:22rem;${isSource() ? "display:none" : ""}`,
class: "p-4 outline-none text-base-content leading-relaxed [&>div]:m-0 [&>p]:m-0 [&>div]:min-h-[1em] [&_.resizable-img-container]:hover:border-primary [&_blockquote]:border-l-4 [&_blockquote]:border-base-300 [&_blockquote]:pl-4 [&_blockquote]:italic [&_ul]:list-disc [&_ul]:pl-8 [&_ol]:list-decimal [&_ol]:pl-8",
contenteditable: "true",
oninput: notify,
onkeydown: (e) => {
if (e.key === "Tab") {
e.preventDefault();
exec("indent");
}
},
onkeyup: () => {
triggerRefresh();
saveSelection();
},
onclick: (e) => {
triggerRefresh();
saveSelection();
e.stopPropagation();
},
onmouseup: () => {
notify();
saveSelection();
},
onpaste: (e) => {
e.preventDefault();
const text = e.clipboardData.getData("text/plain");
exec("insertText", text);
},
ondragover: (e) => e.preventDefault(),
ondrop: (e) => {
e.preventDefault();
handleUpload(e.dataTransfer.files[0]);
}
}),
h("textarea", {
class: "w-full flex-1 min-h-[22rem] p-4 outline-none font-mono text-sm bg-base-200 border-0",
style: () => isSource() ? "" : "display:none",
value: source,
oninput: (e) => {
source(e.target.value);
if (editorRef)
editorRef.innerHTML = e.target.value;
p.onchange?.(e.target.value);
}
})
]),
h("div", { class: "px-3 py-1 border-t border-base-300 bg-base-100/50 text-[10px] text-right text-base-content/60 italic" }, [
h("span", () => `${count()}`)
])
]);
};
export {
Editor
};