import { Editor } from 'editor/editor' import { EditorNode, getType } from 'editor/types' import { safeGetSelection, safeGetRangeAt, markNames, parentBlockNames, setAuxiliaryToolbar, } from 'editor/utils' // TODO: tener ActiveStorage como import así no hacemos hacks declare global { const ActiveStorage: any } 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') } } 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') 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 = getAlt(innerEl) || '' setAuxiliaryToolbar(editor, editor.toolbar.auxiliary.multimedia.parentEl) }, } 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) 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 sel = safeGetSelection(editor) if (!sel) return const range = safeGetRangeAt(sel) if (!range) return let blockEl = sel.anchorNode while (true) { if (!blockEl) throw new Error('WTF') if (!blockEl.parentElement) throw new Error('No pude encontrar contentEl!') let type = getType(blockEl.parentElement) if (!type) throw new Error('La selección está en algo que no es un type!') if (type.typeName === 'contentEl' || parentBlockNames.includes(type.typeName) ) break blockEl = blockEl.parentElement } if (!(blockEl instanceof Element)) throw new Error('La selección no está en un elemento!') const parentEl = blockEl.parentElement if (!parentEl) throw new Error('Inesperado') const el = multimedia.create(editor) parentEl.insertBefore(el, blockEl.nextElementSibling) return false }) }