This commit is contained in:
2026-05-14 13:41:32 +02:00
parent 1349d431e9
commit 0b3eb0159f
11 changed files with 67366 additions and 5 deletions

19
src/sigpro.editor.js Normal file
View File

@@ -0,0 +1,19 @@
const { $, isF } = window.SigPro
export const Editor = p => {
let { value: v, class: x } = p, i = $(0), s = $(""), c = $(0), t = $(0), o = $(0), S = () => window.getSelection().rangeCount ? window.getSelection().getRangeAt(0) : 0, R, r, E = "😀 😊 😉 🧐 😮 🤔 😅 😂 😍 😘 🥰 👍 👎 👌 🤝 🤞 👋 👏 🙌 🙏 💪 ☝️ 👇 👈 👉 🖕 ✅ ⚠️ 🚀 📢 ✉️ ❤️".split(" "),
U = () => { t(t() + 1); R && c(R.innerText.length) }, N = () => { if (R) { let H = R.innerHTML; isF(v) ? v(H) : p.onchange?.(H); U() } },
X = (m, k = null) => { R?.focus(); if (r) { let w = window.getSelection(); w.removeAllRanges(); w.addRange(r) } document.execCommand(m, 0, k); r = 0; N() },
q = (k, Z = null) => { t(); if (!R || i()) return 0; try { if (k == 'formatBlock') { let n = window.getSelection().getRangeAt(0).commonAncestorContainer; while (n && n !== R) { if (n.tagName === Z) return 1; n = n.parentNode } return 0 } return document.queryCommandState(k) } catch (e) { return 0 } },
hU = f => { if (f) { let d = new FileReader; d.onload = e => { let I = f.type.startsWith('image/'), z = e.target.result; X("insertHTML", I ? div({ style: "display:inline-block;resize:both;overflow:hidden;vertical-align:bottom;width:200px;border:1px dashed #ccc;padding:2px;cursor:pointer", class: "resizable-img-container" }, [img({ src: z, style: "width:100%;height:100%;object-fit:contain;pointer-events:none" })]) : a({ href: z, download: f.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" }), f.name])) }; d.readAsDataURL(f) } },
B = (I, m, k) => button({ type: "button", class: () => `btn btn-ghost btn-xs ${q(m, k) ? 'btn-active bg-primary/20' : ''}`, onclick: () => typeof m == 'function' ? m() : X(m, k) }, [span({ class: `icon-[lucide--${I}]` })]);
return div({ class: `border border-base-300 rounded-box bg-base-100 overflow-hidden flex flex-col ${x||""}` }, [
div({ class: "flex flex-wrap items-center gap-1 p-2 border-b border-base-300 bg-base-200 sticky top-0 z-20" }, [
div({ class: "flex flex-wrap gap-1 flex-1" }, [B("bold", "bold"), B("italic", "italic"), B("underline", "underline"), input({ type: "color", class: "w-5 h-5 p-0 bg-transparent cursor-pointer", oninput: e => X("foreColor", e.target.value) }), span({ class: "w-px h-5 bg-base-300 mx-1" }), B("align-left", "justifyLeft"), B("align-center", "justifyCenter"), B("align-right", "justifyRight"), span({ class: "w-px h-5 bg-base-300 mx-1" }), B("list", "insertUnorderedList"), B("list-ordered", "insertOrderedList"), B("indent-decrease", "outdent"), B("indent-increase", "indent"), B("quote", () => X("formatBlock", q('formatBlock', 'BLOCKQUOTE') ? 'P' : 'BLOCKQUOTE'), 'BLOCKQUOTE'), span({ class: "w-px h-5 bg-base-300 mx-1" }), B("link", () => { let u = prompt('URL:'); u && X("createLink", u) }), B("paperclip", () => { let I = document.createElement('input'); I.type = 'file'; I.onchange = e => hU(e.target.files[0]); I.click() }), div({ class: "relative" }, [B("smile", () => { r = S(); o(!o()) }), div({ class: "absolute top-full left-0 mt-1 p-2 bg-base-100 border shadow-xl rounded-box w-52 z-50 flex flex-wrap gap-1", style: () => o() ? "" : "display:none" }, E.map(e => span({ class: "cursor-pointer p-1 text-lg", onclick: () => { X("insertText", e); o(0) } }, e)))]), B("undo-2", "undo"), B("redo-2", "redo")]), B("code-2", () => { if (!i()) s(R?.innerHTML || ""); else if (R) { R.innerHTML = s(); N() } i(!i()) })]),
div({ class: "relative flex-1 flex flex-col", onclick: () => o(0) }, [
div({ ref: l => { if (l && !R) { R = l; l.innerHTML = (isF(v) ? v() : v) || ""; document.execCommand("defaultParagraphSeparator", 0, "br"); l.onclick = e => { let c = e.target.closest('.resizable-img-container'); if (c) { let I = c.querySelector('img'); I && (k => { let O = document.createElement('div'); O.style = "position:fixed;top:0;left:0;width:100%;height:100%;background:#000e;z-index:9999;display:flex;align-items:center;justify-content:center;cursor:zoom-out"; O.onclick = () => O.remove(); let M = document.createElement('img'); M.src = k; M.style = "max-width:95%;max-height:95%"; O.appendChild(M); document.body.appendChild(O) })(I.src) } } } }, style: () => `min-height:22rem;${i() ? '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: N, onkeydown: e => e.key === 'Tab' && (e.preventDefault(), X("indent")), onkeyup: () => { U(); r = S() }, onmouseup: N, onpaste: e => { e.preventDefault(); X('insertText', e.clipboardData.getData('text/plain')) }, ondrop: e => { e.preventDefault(); hU(e.dataTransfer.files[0]) }, ondragover: e => e.preventDefault() }),
textarea({ class: "w-full flex-1 min-h-[22rem] p-4 font-mono text-sm bg-base-200 border-0", style: () => i() ? '' : 'display:none', value: s, oninput: e => { s(e.target.value); if (R) R.innerHTML = e.target.value; p.onchange?.(e.target.value) } })]),
div({ class: "px-3 py-1 border-t text-[10px] text-right opacity-60" }, [span(() => c())])
])
}