mirror of
https://0xacab.org/sutty/sutty
synced 2024-11-17 02:36:23 +00:00
Deprecar el editor
This commit is contained in:
parent
ffa2c80bf1
commit
6467a265d3
1 changed files with 0 additions and 313 deletions
|
@ -1,320 +1,7 @@
|
||||||
import { storeContent, restoreContent, forgetContent } from "editor/storage";
|
|
||||||
import {
|
|
||||||
isDirectChild,
|
|
||||||
moveChildren,
|
|
||||||
safeGetSelection,
|
|
||||||
safeGetRangeAt,
|
|
||||||
setAuxiliaryToolbar,
|
|
||||||
parentBlockNames,
|
|
||||||
clearSelected,
|
|
||||||
} from "editor/utils";
|
|
||||||
import { types, getValidChildren, getType } from "editor/types";
|
|
||||||
import { setupButtons as setupMarksButtons } from "editor/types/marks";
|
|
||||||
import { setupButtons as setupBlocksButtons } from "editor/types/blocks";
|
|
||||||
import { setupButtons as setupParentBlocksButtons } from "editor/types/parentBlocks";
|
|
||||||
import { setupAuxiliaryToolbar as setupLinkAuxiliaryToolbar } from "editor/types/link";
|
|
||||||
import {
|
|
||||||
setupAuxiliaryToolbar as setupMultimediaAuxiliaryToolbar,
|
|
||||||
setupButtons as setupMultimediaButtons,
|
|
||||||
} from "editor/types/multimedia";
|
|
||||||
import { setupAuxiliaryToolbar as setupMarkAuxiliaryToolbar } from "editor/types/mark";
|
|
||||||
|
|
||||||
/// @ts-ignore
|
/// @ts-ignore
|
||||||
import SuttyEditor from "@suttyweb/editor";
|
import SuttyEditor from "@suttyweb/editor";
|
||||||
import "@suttyweb/editor/dist/style.css";
|
import "@suttyweb/editor/dist/style.css";
|
||||||
|
|
||||||
// Esta funcion corrije errores que pueden haber como:
|
|
||||||
// * que un nodo que no tiene 'text' permitido no tenga children (se les
|
|
||||||
// inserta un allowedChildren[0])
|
|
||||||
// * TODO: que haya una imágen sin <figure> o que no esté como bloque (se ponen
|
|
||||||
// después del bloque en el que están como bloque de por si)
|
|
||||||
// * convierte <i> y <b> en <em> y <strong>
|
|
||||||
// Lo hace para que siga la estructura del documento y que no se borren por
|
|
||||||
// cleanContent luego.
|
|
||||||
function fixContent(editor: Editor, node: Element = editor.contentEl): void {
|
|
||||||
if (node.tagName === "SCRIPT" || node.tagName === "STYLE") {
|
|
||||||
node.parentElement?.removeChild(node);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node.tagName === "I") {
|
|
||||||
const el = document.createElement("em");
|
|
||||||
moveChildren(node, el, null);
|
|
||||||
node.parentElement?.replaceChild(el, node);
|
|
||||||
node = el;
|
|
||||||
}
|
|
||||||
if (node.tagName === "B") {
|
|
||||||
const el = document.createElement("strong");
|
|
||||||
moveChildren(node, el, null);
|
|
||||||
node.parentElement?.replaceChild(el, node);
|
|
||||||
node = el;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node instanceof HTMLImageElement) {
|
|
||||||
node.dataset.multimediaInner = "";
|
|
||||||
const figureEl = types.multimedia.create(editor);
|
|
||||||
|
|
||||||
let targetEl = node.parentElement;
|
|
||||||
if (!targetEl) throw new Error("No encontré lx objetivo");
|
|
||||||
while (true) {
|
|
||||||
const type = getType(targetEl);
|
|
||||||
if (!type) throw new Error("lx objetivo tiene tipo");
|
|
||||||
if (type.type.allowedChildren.includes("multimedia")) break;
|
|
||||||
if (!targetEl.parentElement) throw new Error("No encontré lx objetivo");
|
|
||||||
targetEl = targetEl.parentElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
let parentEl = [...targetEl.childNodes].find((el) => el.contains(node));
|
|
||||||
if (!parentEl) throw new Error("no encontré lx pariente");
|
|
||||||
targetEl.insertBefore(figureEl, parentEl);
|
|
||||||
|
|
||||||
const innerEl = figureEl.querySelector("[data-multimedia-inner]");
|
|
||||||
if (!innerEl) throw new Error("Raro.");
|
|
||||||
figureEl.replaceChild(node, innerEl);
|
|
||||||
|
|
||||||
node = figureEl;
|
|
||||||
}
|
|
||||||
|
|
||||||
const _type = getType(node);
|
|
||||||
if (!_type) return;
|
|
||||||
|
|
||||||
const { typeName, type } = _type;
|
|
||||||
|
|
||||||
if (type.allowedChildren !== "ignore-children") {
|
|
||||||
const sel = safeGetSelection(editor);
|
|
||||||
const range = sel && safeGetRangeAt(sel);
|
|
||||||
|
|
||||||
if (getValidChildren(node, type).length == 0) {
|
|
||||||
if (typeof type.handleEmpty !== "string") {
|
|
||||||
const el = type.handleEmpty.create(editor);
|
|
||||||
// mover cosas que pueden haber
|
|
||||||
// por ejemplo: cuando convertís a un <ul>, queda texto fuera del li que
|
|
||||||
// creamos acá
|
|
||||||
moveChildren(node, el, null);
|
|
||||||
node.appendChild(el);
|
|
||||||
if (range?.intersectsNode(node)) sel?.collapse(el);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const child of node.childNodes) {
|
|
||||||
if (!(child instanceof Element)) continue;
|
|
||||||
fixContent(editor, child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Esta funcion hace que los elementos del editor sigan la estructura.
|
|
||||||
// TODO: nos falta borrar atributos (style, y básicamente cualquier otra cosa)
|
|
||||||
// Edge cases:
|
|
||||||
// * no borramos los <br> por que se requieren para que los navegadores
|
|
||||||
// funcionen bien al escribir. no se deberían mostrar de todas maneras
|
|
||||||
function cleanContent(editor: Editor, node: Element = editor.contentEl): void {
|
|
||||||
const _type = getType(node);
|
|
||||||
if (!_type) {
|
|
||||||
node.parentElement?.removeChild(node);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { type } = _type;
|
|
||||||
|
|
||||||
if (type.allowedChildren !== "ignore-children") {
|
|
||||||
for (const child of node.childNodes) {
|
|
||||||
if (
|
|
||||||
child.nodeType === Node.TEXT_NODE &&
|
|
||||||
!type.allowedChildren.includes("text")
|
|
||||||
) {
|
|
||||||
node.removeChild(child);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(child instanceof Element)) continue;
|
|
||||||
|
|
||||||
const childType = getType(child);
|
|
||||||
if (childType?.typeName === "br") continue;
|
|
||||||
if (!childType || !type.allowedChildren.includes(childType.typeName)) {
|
|
||||||
// XXX: esto extrae las cosas de adentro para que no sea destructivo
|
|
||||||
moveChildren(child, node, child);
|
|
||||||
node.removeChild(child);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanContent(editor, child);
|
|
||||||
}
|
|
||||||
|
|
||||||
// solo contar children válido para ese nodo
|
|
||||||
const validChildrenLength = getValidChildren(node, type).length;
|
|
||||||
|
|
||||||
const sel = safeGetSelection(editor);
|
|
||||||
const range = sel && safeGetRangeAt(sel);
|
|
||||||
if (
|
|
||||||
type.handleEmpty === "remove" &&
|
|
||||||
validChildrenLength == 0
|
|
||||||
//&& (!range || !range.intersectsNode(node))
|
|
||||||
) {
|
|
||||||
node.parentNode?.removeChild(node);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function routine(editor: Editor): void {
|
|
||||||
try {
|
|
||||||
fixContent(editor);
|
|
||||||
cleanContent(editor);
|
|
||||||
storeContent(editor);
|
|
||||||
|
|
||||||
editor.htmlEl.value = editor.contentEl.innerHTML;
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Hubo un problema corriendo la rutina", editor, error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Editor {
|
|
||||||
editorEl: HTMLElement;
|
|
||||||
toolbarEl: HTMLElement;
|
|
||||||
toolbar: {
|
|
||||||
auxiliary: {
|
|
||||||
mark: {
|
|
||||||
parentEl: HTMLElement;
|
|
||||||
colorEl: HTMLInputElement;
|
|
||||||
textColorEl: HTMLInputElement;
|
|
||||||
};
|
|
||||||
multimedia: {
|
|
||||||
parentEl: HTMLElement;
|
|
||||||
fileEl: HTMLInputElement;
|
|
||||||
uploadEl: HTMLButtonElement;
|
|
||||||
altEl: HTMLInputElement;
|
|
||||||
removeEl: HTMLButtonElement;
|
|
||||||
};
|
|
||||||
link: {
|
|
||||||
parentEl: HTMLElement;
|
|
||||||
urlEl: HTMLInputElement;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
contentEl: HTMLElement;
|
|
||||||
wordAlertEl: HTMLElement;
|
|
||||||
htmlEl: HTMLTextAreaElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSel<T extends Element>(parentEl: HTMLElement, selector: string): T {
|
|
||||||
const el = parentEl.querySelector<T>(selector);
|
|
||||||
if (!el) throw new Error(`No pude encontrar un componente \`${selector}\``);
|
|
||||||
return el;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setupEditor(editorEl: HTMLElement): void {
|
|
||||||
// XXX: ¡Esto afecta a todo el documento! ¿Quizás usar un iframe para el editor?
|
|
||||||
document.execCommand("defaultParagraphSeparator", false, "p");
|
|
||||||
|
|
||||||
const editor: Editor = {
|
|
||||||
editorEl,
|
|
||||||
toolbarEl: getSel(editorEl, ".editor-toolbar"),
|
|
||||||
toolbar: {
|
|
||||||
auxiliary: {
|
|
||||||
mark: {
|
|
||||||
parentEl: getSel(editorEl, "[data-editor-auxiliary=mark]"),
|
|
||||||
colorEl: getSel(
|
|
||||||
editorEl,
|
|
||||||
"[data-editor-auxiliary=mark] [name=mark-color]"
|
|
||||||
),
|
|
||||||
textColorEl: getSel(
|
|
||||||
editorEl,
|
|
||||||
"[data-editor-auxiliary=mark] [name=mark-text-color]"
|
|
||||||
),
|
|
||||||
},
|
|
||||||
multimedia: {
|
|
||||||
parentEl: getSel(editorEl, "[data-editor-auxiliary=multimedia]"),
|
|
||||||
fileEl: getSel(
|
|
||||||
editorEl,
|
|
||||||
"[data-editor-auxiliary=multimedia] [name=multimedia-file]"
|
|
||||||
),
|
|
||||||
uploadEl: getSel(
|
|
||||||
editorEl,
|
|
||||||
"[data-editor-auxiliary=multimedia] [name=multimedia-file-upload]"
|
|
||||||
),
|
|
||||||
altEl: getSel(
|
|
||||||
editorEl,
|
|
||||||
"[data-editor-auxiliary=multimedia] [name=multimedia-alt]"
|
|
||||||
),
|
|
||||||
removeEl: getSel(
|
|
||||||
editorEl,
|
|
||||||
"[data-editor-auxiliary=multimedia] [name=multimedia-remove]"
|
|
||||||
),
|
|
||||||
},
|
|
||||||
link: {
|
|
||||||
parentEl: getSel(editorEl, "[data-editor-auxiliary=link]"),
|
|
||||||
urlEl: getSel(
|
|
||||||
editorEl,
|
|
||||||
"[data-editor-auxiliary=link] [name=link-url]"
|
|
||||||
),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
contentEl: getSel(editorEl, ".editor-content"),
|
|
||||||
wordAlertEl: getSel(editorEl, ".editor-aviso-word"),
|
|
||||||
htmlEl: getSel(editorEl, "textarea"),
|
|
||||||
};
|
|
||||||
console.debug("iniciando editor", editor);
|
|
||||||
|
|
||||||
// Recuperar el contenido si hay algo guardado, si tuviéramos un campo
|
|
||||||
// de última edición podríamos saber si el artículo fue editado
|
|
||||||
// después o la versión local es la última.
|
|
||||||
//
|
|
||||||
// TODO: Preguntar si se lo quiere recuperar.
|
|
||||||
restoreContent(editor);
|
|
||||||
|
|
||||||
// Word alert
|
|
||||||
editor.contentEl.addEventListener("paste", () => {
|
|
||||||
editor.wordAlertEl.style.display = "block";
|
|
||||||
});
|
|
||||||
|
|
||||||
// Setup routine listeners
|
|
||||||
const observer = new MutationObserver(() => routine(editor));
|
|
||||||
observer.observe(editor.contentEl, {
|
|
||||||
childList: true,
|
|
||||||
attributes: true,
|
|
||||||
subtree: true,
|
|
||||||
characterData: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener("selectionchange", () => routine(editor));
|
|
||||||
|
|
||||||
// Capture onClick
|
|
||||||
editor.contentEl.addEventListener(
|
|
||||||
"click",
|
|
||||||
(event) => {
|
|
||||||
const target = event.target! as Element;
|
|
||||||
const type = getType(target);
|
|
||||||
if (!type || !type.type.onClick) {
|
|
||||||
setAuxiliaryToolbar(editor, null);
|
|
||||||
clearSelected(editor);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
type.type.onClick(editor, target);
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
// Clean seleted
|
|
||||||
const selectedEl = editor.contentEl.querySelector("[data-editor-selected]");
|
|
||||||
if (selectedEl) delete (selectedEl as HTMLElement).dataset.editorSelected;
|
|
||||||
|
|
||||||
// Setup botones
|
|
||||||
setupMarksButtons(editor);
|
|
||||||
setupBlocksButtons(editor);
|
|
||||||
setupParentBlocksButtons(editor);
|
|
||||||
setupMultimediaButtons(editor);
|
|
||||||
|
|
||||||
setupLinkAuxiliaryToolbar(editor);
|
|
||||||
setupMultimediaAuxiliaryToolbar(editor);
|
|
||||||
setupMarkAuxiliaryToolbar(editor);
|
|
||||||
|
|
||||||
// Finally...
|
|
||||||
routine(editor);
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener("turbolinks:load", () => {
|
document.addEventListener("turbolinks:load", () => {
|
||||||
const flash = document.querySelector<HTMLElement>(".js-flash");
|
const flash = document.querySelector<HTMLElement>(".js-flash");
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue