import * as ActiveStorage from "@rails/activestorage"; import { Editor } from "editor/editor"; import { EditorNode, getValidParentInSelection } from "editor/types"; import { safeGetSelection, safeGetRangeAt, markNames, parentBlockNames, setAuxiliaryToolbar, clearSelected, } from "editor/utils"; function uploadFile(file: File): Promise { return new Promise((resolve, reject) => { const upload = new ActiveStorage.DirectUpload( file, origin + "/rails/active_storage/direct_uploads" ); upload.create((error: any, blob: any) => { if (error) { reject(error); } else { const url = `${origin}/rails/active_storage/blobs/${blob.signed_id}/${blob.filename}`; resolve(url); } }); }); } function getAlt(multimediaInnerEl: HTMLElement): string | null { switch (multimediaInnerEl.tagName) { case "VIDEO": case "AUDIO": return multimediaInnerEl.getAttribute("aria-label"); case "IMG": return (multimediaInnerEl as HTMLImageElement).alt; case "IFRAME": return multimediaInnerEl.title; default: throw new Error("no pude conseguir el alt"); } } function setAlt(multimediaInnerEl: HTMLElement, value: string): void { switch (multimediaInnerEl.tagName) { case "VIDEO": case "AUDIO": multimediaInnerEl.setAttribute("aria-label", value); break; case "IMG": (multimediaInnerEl as HTMLImageElement).alt = value; break; case "IFRAME": multimediaInnerEl.title = value; break; default: throw new Error("no pude setear el alt"); } } function select(editor: Editor, el: HTMLElement): void { clearSelected(editor); el.dataset.editorSelected = ""; const innerEl = el.querySelector("[data-multimedia-inner]"); if (!innerEl) throw new Error("No hay multimedia válida"); if (innerEl.tagName === "P") { editor.toolbar.auxiliary.multimedia.altEl.value = ""; editor.toolbar.auxiliary.multimedia.altEl.disabled = true; } else { editor.toolbar.auxiliary.multimedia.altEl.value = getAlt(innerEl) || ""; editor.toolbar.auxiliary.multimedia.altEl.disabled = false; } setAuxiliaryToolbar(editor, editor.toolbar.auxiliary.multimedia.parentEl); } export const multimedia: EditorNode = { selector: "figure[data-multimedia]", allowedChildren: "ignore-children", handleEmpty: "remove", create: () => { const figureEl = document.createElement("figure"); figureEl.dataset.multimedia = ""; figureEl.contentEditable = "false"; const placeholderEl = document.createElement("p"); placeholderEl.dataset.multimediaInner = ""; // TODO i18n placeholderEl.append("¡Clickeame para subir un archivo!"); figureEl.appendChild(placeholderEl); const descriptionEl = document.createElement("figcaption"); descriptionEl.contentEditable = "true"; // TODO i18n descriptionEl.append("Escribí acá la descripción del archivo."); figureEl.appendChild(descriptionEl); return figureEl; }, onClick(editor, el) { if (!(el instanceof HTMLElement)) throw new Error("oh no"); select(editor, el); }, }; function createElementWithFile(url: string, type: string): HTMLElement { if (type.match(/^image\/.+$/)) { const el = document.createElement("img"); el.dataset.multimediaInner = ""; el.src = url; return el; } else if (type.match(/^video\/.+$/)) { const el = document.createElement("video"); el.controls = true; el.dataset.multimediaInner = ""; el.src = url; return el; } else if (type.match(/^audio\/.+$/)) { const el = document.createElement("audio"); el.controls = true; el.dataset.multimediaInner = ""; el.src = url; return el; } else if (type.match(/^application\/pdf$/)) { const el = document.createElement("iframe"); el.dataset.multimediaInner = ""; el.src = url; return el; } else { // TODO: chequear si el archivo es válido antes de subir throw new Error("Tipo de archivo no reconocido"); } } export function setupAuxiliaryToolbar(editor: Editor): void { editor.toolbar.auxiliary.multimedia.uploadEl.addEventListener( "click", (event) => { const files = editor.toolbar.auxiliary.multimedia.fileEl.files; if (!files || !files.length) throw new Error("no hay archivos para subir"); const file = files[0]; const selectedEl = editor.contentEl.querySelector( "figure[data-editor-selected]" ); if (!selectedEl) throw new Error("No pude encontrar el elemento para setear el archivo"); selectedEl.dataset.editorLoading = ""; uploadFile(file) .then((url) => { const innerEl = selectedEl.querySelector("[data-multimedia-inner]"); if (!innerEl) throw new Error("No hay multimedia a reemplazar"); const el = createElementWithFile(url, file.type); setAlt(el, editor.toolbar.auxiliary.multimedia.altEl.value); selectedEl.replaceChild(el, innerEl); select(editor, selectedEl); delete selectedEl.dataset.editorError; }) .catch((err) => { console.error(err); // TODO: mostrar error selectedEl.dataset.editorError = ""; }) .finally(() => { delete selectedEl.dataset.editorLoading; }); } ); editor.toolbar.auxiliary.multimedia.removeEl.addEventListener( "click", (event) => { const selectedEl = editor.contentEl.querySelector( "figure[data-editor-selected]" ); if (!selectedEl) throw new Error("No pude encontrar el elemento para borrar"); selectedEl.parentElement?.removeChild(selectedEl); setAuxiliaryToolbar(editor, null); } ); editor.toolbar.auxiliary.multimedia.altEl.addEventListener( "input", (event) => { const selectedEl = editor.contentEl.querySelector( "figure[data-editor-selected]" ); if (!selectedEl) throw new Error("No pude encontrar el multimedia para setear el alt"); const innerEl = selectedEl.querySelector( "[data-multimedia-inner]" ); if (!innerEl) throw new Error("No hay multimedia a para setear el alt"); setAlt(innerEl, editor.toolbar.auxiliary.multimedia.altEl.value); } ); editor.toolbar.auxiliary.multimedia.altEl.addEventListener( "keydown", (event) => { if (event.keyCode == 13) event.preventDefault(); } ); } export function setupButtons(editor: Editor): void { const buttonEl = editor.toolbarEl.querySelector( '[data-editor-button="multimedia"]' ); if (!buttonEl) throw new Error("No encontre el botón de multimedia"); buttonEl.addEventListener("click", (event) => { event.preventDefault(); const list = getValidParentInSelection({ editor, type: "multimedia" }); const el = multimedia.create(editor); list[0].insertBefore(el, list[1].nextElementSibling); select(editor, el); return false; }); }