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

207 lines
6.3 KiB
TypeScript
Raw Normal View History

2021-04-07 20:45:27 +00:00
import * as ActiveStorage from '@rails/activestorage'
2021-02-14 16:01:41 +00:00
import { Editor } from 'editor/editor'
import { EditorNode, getValidParentInSelection } from 'editor/types'
2021-02-14 16:01:41 +00:00
import {
safeGetSelection, safeGetRangeAt,
markNames, parentBlockNames,
setAuxiliaryToolbar, clearSelected,
2021-02-14 16:01:41 +00:00
} from 'editor/utils'
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')
}
}
function select (editor: Editor, el: HTMLElement): void {
clearSelected(editor)
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 = "";
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)
}
2021-02-14 16:01:41 +00:00
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)
2021-02-14 16:01:41 +00:00
},
}
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)
select(editor, selectedEl)
2021-02-14 16:01:41 +00:00
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 list = getValidParentInSelection({ editor, type: 'multimedia' })
2021-02-14 16:01:41 +00:00
const el = multimedia.create(editor)
list[0].insertBefore(el, list[1].nextElementSibling)
select(editor, el)
2021-02-14 16:01:41 +00:00
return false
})
}