sutty/app/javascript/editor/types/multimedia.ts

221 lines
6.6 KiB
TypeScript
Raw Normal View History

2021-02-14 16:01:41 +00:00
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<string> {
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<HTMLElement>('[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')
2021-02-14 16:01:41 +00:00
const file = files[0]
const selectedEl = editor.contentEl
.querySelector<HTMLElement>('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<HTMLElement>('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<HTMLAnchorElement>('figure[data-editor-selected]')
if (!selectedEl)
throw new Error('No pude encontrar el multimedia para setear el alt')
const innerEl = selectedEl.querySelector<HTMLElement>('[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
})
}