import { h, $ } from "sigpro" import { get, cls, isFn } from "./sigpro-helpers.js" export const 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 = ["๐", "๐", "๐", "๐ง", "๐ฎ", "๐ค", "๐ ", "๐", "๐", "๐", "๐ฅฐ", "๐", "๐", "๐", "๐ค", "๐ค", "๐", "๐", "๐", "๐", "๐ช", "โ๏ธ", "๐", "๐", "๐", "๐", "โ ", "โ ๏ธ", "๐", "๐ข", "โ๏ธ", "โค๏ธ"] 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 (isFn(value)) value(html) else p.onchange?.(html) triggerRefresh() } const exec = (cmd, val = null) => { if (!editorRef) return editorRef.focus() if (savedRange) restoreSelection() document.execCommand(cmd, false, val) 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 = `
` exec("insertHTML", imgHtml) } else { const linkHtml = `${file.name} ` exec("insertHTML", linkHtml) } } reader.readAsDataURL(file) } const queryState = (cmd, val = 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 === val) 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" }, [ // GRUPO 1: ESTILOS 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]" })), // --- Botรณn Alineaciรณn Centro --- h("button", { type: "button", class: "btn btn-ghost btn-xs", onclick: () => exec("justifyCenter") }, h("span", { class: "icon-[lucide--align-center]" })), // --- Botรณn Alineaciรณn Derecha --- 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" }), // GRUPO 2: LISTAS Y PรRRAFO 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" }), // GRUPO 3: INSERTAR 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]" })), // EMOJIS 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" }), // GRUPO 4: UTILIDADES 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 = get(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()} caracteres`) ]) ]) }