mirror of
https://0xacab.org/sutty/sutty
synced 2024-07-06 00:15:46 +00:00
423 lines
11 KiB
JavaScript
423 lines
11 KiB
JavaScript
function setAuxiliaryToolbar (editorEl, toolbarName) {
|
|
const toolbarEl = editorEl.querySelector(`*[data-editor-auxiliary-toolbar]`)
|
|
for (const otherEl of toolbarEl.childNodes) {
|
|
if (otherEl.nodeType !== Node.ELEMENT_NODE) continue
|
|
otherEl.classList.remove("editor-auxiliary-tool-active")
|
|
}
|
|
if (toolbarName) {
|
|
const auxEl = editorEl.querySelector(`*[data-editor-auxiliary="${toolbarName}"]`)
|
|
auxEl.classList.add("editor-auxiliary-tool-active")
|
|
}
|
|
}
|
|
|
|
const marks = {
|
|
bold: {
|
|
selector: "strong",
|
|
createFn: () => document.createElement("STRONG"),
|
|
},
|
|
italic: {
|
|
selector: "em",
|
|
createFn: () => document.createElement("EM"),
|
|
},
|
|
deleted: {
|
|
selector: "del",
|
|
createFn: () => document.createElement("DEL"),
|
|
},
|
|
underline: {
|
|
selector: "u",
|
|
createFn: () => document.createElement("U"),
|
|
},
|
|
sub: {
|
|
selector: "sub",
|
|
createFn: () => document.createElement("SUB"),
|
|
},
|
|
sup: {
|
|
selector: "sup",
|
|
createFn: () => document.createElement("SUP"),
|
|
},
|
|
mark: {
|
|
selector: "mark",
|
|
createFn: () => document.createElement("MARK"),
|
|
},
|
|
a: {
|
|
selector: "a",
|
|
createFn: () => document.createElement("A"),
|
|
}
|
|
}
|
|
|
|
const tagNameSetFn = tagName => el => {
|
|
const newEl = document.createElement(tagName)
|
|
moveChildren(el, newEl, null)
|
|
el.parentNode.insertBefore(newEl, el)
|
|
el.parentNode.removeChild(el)
|
|
window.getSelection().collapse(newEl, 0)
|
|
}
|
|
|
|
const blocks = {
|
|
p: {
|
|
noButton: true,
|
|
selector: "P",
|
|
setFn: tagNameSetFn("P"),
|
|
},
|
|
h1: {
|
|
selector: "H1",
|
|
setFn: tagNameSetFn("H1"),
|
|
},
|
|
h2: {
|
|
selector: "H2",
|
|
setFn: tagNameSetFn("H2"),
|
|
},
|
|
h3: {
|
|
selector: "H3",
|
|
setFn: tagNameSetFn("H3"),
|
|
},
|
|
h4: {
|
|
selector: "H4",
|
|
setFn: tagNameSetFn("H4"),
|
|
},
|
|
h5: {
|
|
selector: "H5",
|
|
setFn: tagNameSetFn("H5"),
|
|
},
|
|
h6: {
|
|
selector: "H6",
|
|
setFn: tagNameSetFn("H6"),
|
|
},
|
|
ul: {
|
|
selector: "UL",
|
|
setFn: tagNameSetFn("UL"),
|
|
},
|
|
ol: {
|
|
selector: "OL",
|
|
setFn: tagNameSetFn("OL"),
|
|
},
|
|
img: {
|
|
selector: "IMG",
|
|
createFn: editorEl => {
|
|
const el = document.createElement("IMG")
|
|
el.src = "/placeholder.png"
|
|
el.alt = ""
|
|
return el
|
|
},
|
|
},
|
|
figure: {
|
|
selector: "FIGURE",
|
|
noButton: true
|
|
},
|
|
figcaption: {
|
|
selector: "FIGCAPTION",
|
|
noButton: true,
|
|
},
|
|
audio: {
|
|
selector: "AUDIO",
|
|
createFn: editorEl => {
|
|
const el = document.createElement("FIGURE")
|
|
|
|
el.appendChild(document.createElement("AUDIO"))
|
|
el.appendChild(document.createElement("FIGCAPTION"))
|
|
|
|
el.children[0].controls = true
|
|
el.children[1].innerText = "Toca el borde para subir un archivo de audio"
|
|
|
|
return el
|
|
},
|
|
},
|
|
video: {
|
|
selector: "VIDEO",
|
|
createFn: editorEl => {
|
|
const el = document.createElement("VIDEO")
|
|
el.poster = "/placeholder.png"
|
|
// Para poder seleccionar el video tenemos que sacarle los
|
|
// controles, pero queremos poder verlos para reproducir el video.
|
|
// Al hacer click le damos los controles y al salir se los sacamos
|
|
// para poder hacer click de vuelta
|
|
el.addEventListener('click', event => event.target.controls = true)
|
|
el.addEventListener('focusout', event => event.target.controls = false)
|
|
return el
|
|
},
|
|
},
|
|
// PDF
|
|
pdf: {
|
|
selector: "IFRAME",
|
|
createFn: editorEl => {
|
|
const el = document.createElement("FIGURE")
|
|
|
|
el.appendChild(document.createElement("IFRAME"))
|
|
el.appendChild(document.createElement("FIGCAPTION"))
|
|
|
|
el.children[1].innerText = "Toca el borde para subir un archivo PDF"
|
|
|
|
return el
|
|
},
|
|
},
|
|
}
|
|
|
|
const divWithStyleCreateFn = styleFn => () => {
|
|
const el = document.createElement("DIV")
|
|
styleFn(el)
|
|
return el
|
|
}
|
|
|
|
const parentBlocks = {
|
|
left: {
|
|
selector: "div[data-align=left]",
|
|
createFn: divWithStyleCreateFn(el => el.dataset.align = "left"),
|
|
},
|
|
center: {
|
|
selector: "div[data-align=center]",
|
|
createFn: divWithStyleCreateFn(el => el.dataset.align = "center"),
|
|
},
|
|
right: {
|
|
selector: "div[data-align=right]",
|
|
createFn: divWithStyleCreateFn(el => el.dataset.align = "right"),
|
|
},
|
|
}
|
|
|
|
// https://stackoverflow.com/a/3627747
|
|
// TODO: cambiar por una solución más copada
|
|
function rgb2hex(rgb) {
|
|
rgb = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
|
|
function hex(x) {
|
|
return ("0" + parseInt(x).toString(16)).slice(-2);
|
|
}
|
|
return "#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]);
|
|
}
|
|
|
|
const getSelected = contentEl => contentEl.querySelector(".selected")
|
|
|
|
const typesWithProperties = {
|
|
a: {
|
|
selector: marks.a.selector,
|
|
updateInput (el, editorEl) {
|
|
setAuxiliaryToolbar(editorEl, "a")
|
|
|
|
const markAInputEl = editorEl.querySelector(`*[data-prop="a-href"]`)
|
|
markAInputEl.disabled = false
|
|
markAInputEl.value = el.href
|
|
},
|
|
disableInput (editorEl) {
|
|
const markAInputEl = editorEl.querySelector(`*[data-prop="a-href"]`)
|
|
markAInputEl.disabled = true
|
|
markAInputEl.value = ""
|
|
},
|
|
setupInput (editorEl, contentEl) {
|
|
const markAInputEl = editorEl.querySelector(`*[data-prop="a-href"]`)
|
|
markAInputEl.addEventListener("change", event => {
|
|
const markEl = contentEl.querySelector(marks.a.selector + ".selected")
|
|
if (markEl) markEl.href = markAInputEl.value
|
|
}, false)
|
|
}
|
|
},
|
|
mark: {
|
|
selector: marks.mark.selector,
|
|
updateInput (el, editorEl) {
|
|
setAuxiliaryToolbar(editorEl, "mark")
|
|
|
|
const markColorInputEl = editorEl.querySelector(`*[data-prop="mark-color"]`)
|
|
markColorInputEl.disabled = false
|
|
markColorInputEl.value = el.style.backgroundColor ? rgb2hex(el.style.backgroundColor) : "#f206f9"
|
|
},
|
|
disableInput (editorEl) {
|
|
const markColorInputEl = editorEl.querySelector(`*[data-prop="mark-color"]`)
|
|
markColorInputEl.disabled = true
|
|
markColorInputEl.value = "#000000"
|
|
},
|
|
setupInput (editorEl, contentEl) {
|
|
const markColorInputEl = editorEl.querySelector(`*[data-prop="mark-color"]`)
|
|
markColorInputEl.addEventListener("change", event => {
|
|
const markEl = contentEl.querySelector(marks.mark.selector + ".selected")
|
|
if (markEl) markEl.style.backgroundColor = markColorInputEl.value
|
|
}, false)
|
|
},
|
|
},
|
|
img: {
|
|
selector: blocks.img.selector,
|
|
updateInput (el, editorEl) {
|
|
setAuxiliaryToolbar(editorEl, "img")
|
|
|
|
const imgFileEl = editorEl.querySelector(`*[data-prop="img-file"]`)
|
|
imgFileEl.disabled = false
|
|
// XXX: No se puede cambiar el texto, ¡esto puede ser confuso!
|
|
|
|
const imgAltEl = editorEl.querySelector(`*[data-prop="img-alt"]`)
|
|
imgAltEl.disabled = false
|
|
imgAltEl.value = el.alt
|
|
},
|
|
disableInput (editorEl) {
|
|
const imgFileEl = editorEl.querySelector(`*[data-prop="img-file"]`)
|
|
imgFileEl.disabled = true
|
|
|
|
const imgAltEl = editorEl.querySelector(`*[data-prop="img-alt"]`)
|
|
imgAltEl.disabled = true
|
|
imgAltEl.value = ""
|
|
},
|
|
setupInput (editorEl, contentEl) {
|
|
const imgFileEl = editorEl.querySelector(`*[data-prop="img-file"]`)
|
|
imgFileEl.addEventListener("input", event => {
|
|
const imgEl = contentEl.querySelector("img.selected")
|
|
if (!imgEl) return
|
|
|
|
const file = imgFileEl.files[0]
|
|
|
|
imgEl.src = URL.createObjectURL(file)
|
|
imgEl.dataset.editorLoading = true
|
|
uploadFile(file)
|
|
.then(url => {
|
|
imgEl.src = url
|
|
delete imgEl.dataset.editorError
|
|
})
|
|
.catch(err => {
|
|
// TODO: mostrar error
|
|
console.error(err)
|
|
imgEl.dataset.editorError = true
|
|
})
|
|
.finally(() => {
|
|
delete imgEl.dataset.editorLoading
|
|
})
|
|
}, false)
|
|
|
|
const imgAltEl = editorEl.querySelector(`*[data-prop="img-alt"]`)
|
|
imgAltEl.addEventListener("input", event => {
|
|
const imgEl = contentEl.querySelector("img.selected")
|
|
if (imgEl) imgEl.alt = imgAltEl.value
|
|
}, false)
|
|
},
|
|
},
|
|
figure: {
|
|
selector: blocks.figure.selector,
|
|
actualInput (el) {
|
|
// TODO: Cuando tengamos otros iframes hay que seleccionarlos de
|
|
// otra forma.
|
|
const tag = el.children[0].tagName.toLowerCase()
|
|
|
|
return typesWithProperties[(tag === 'iframe' ? 'pdf' : tag)]
|
|
},
|
|
updateInput (el, editorEl) {
|
|
typesWithProperties.figure.actualInput(el).updateInput(el.children[0], editorEl)
|
|
},
|
|
disableInput (editorEl) {},
|
|
setupInput (editorEl, contentEl) {},
|
|
},
|
|
audio: {
|
|
selector: blocks.audio.selector,
|
|
updateInput (el, editorEl) {
|
|
setAuxiliaryToolbar(editorEl, "audio")
|
|
|
|
const audioFileEl = editorEl.querySelector(`*[data-prop="audio-file"]`)
|
|
audioFileEl.disabled = false
|
|
// XXX: No se puede cambiar el texto, ¡esto puede ser confuso!
|
|
},
|
|
disableInput (editorEl) {
|
|
const audioFileEl = editorEl.querySelector(`*[data-prop="audio-file"]`)
|
|
audioFileEl.disabled = true
|
|
},
|
|
setupInput (editorEl, contentEl) {
|
|
const audioFileEl = editorEl.querySelector(`*[data-prop="audio-file"]`)
|
|
audioFileEl.addEventListener("input", event => {
|
|
const figureEl = getSelected(contentEl)
|
|
if (!figureEl) return
|
|
|
|
const file = audioFileEl.files[0]
|
|
|
|
const audioEl = figureEl.querySelector('audio')
|
|
|
|
audioEl.src = URL.createObjectURL(file)
|
|
audioEl.dataset.editorLoading = true
|
|
uploadFile(file)
|
|
.then(url => {
|
|
audioEl.src = url
|
|
delete audioEl.dataset.editorError
|
|
})
|
|
.catch(err => {
|
|
// TODO: mostrar error
|
|
console.error(err)
|
|
audioEl.dataset.editorError = true
|
|
})
|
|
.finally(() => {
|
|
delete audioEl.dataset.editorLoading
|
|
})
|
|
}, false)
|
|
},
|
|
},
|
|
video: {
|
|
selector: blocks.video.selector,
|
|
updateInput (el, editorEl) {
|
|
setAuxiliaryToolbar(editorEl, "video")
|
|
|
|
const videoFileEl = editorEl.querySelector(`*[data-prop="video-file"]`)
|
|
videoFileEl.disabled = false
|
|
// XXX: No se puede cambiar el texto, ¡esto puede ser confuso!
|
|
},
|
|
disableInput (editorEl) {
|
|
const videoFileEl = editorEl.querySelector(`*[data-prop="video-file"]`)
|
|
videoFileEl.disabled = true
|
|
},
|
|
setupInput (editorEl, contentEl) {
|
|
const videoFileEl = editorEl.querySelector(`*[data-prop="video-file"]`)
|
|
videoFileEl.addEventListener("input", event => {
|
|
const videoEl = getSelected(contentEl)
|
|
if (!videoEl) return
|
|
|
|
const file = videoFileEl.files[0]
|
|
|
|
videoEl.poster = ""
|
|
videoEl.src = URL.createObjectURL(file)
|
|
videoEl.dataset.editorLoading = true
|
|
uploadFile(file)
|
|
.then(url => {
|
|
videoEl.src = url
|
|
delete videoEl.dataset.editorError
|
|
})
|
|
.catch(err => {
|
|
// TODO: mostrar error
|
|
console.error(err)
|
|
videoEl.dataset.editorError = true
|
|
})
|
|
.finally(() => {
|
|
delete videoEl.dataset.editorLoading
|
|
})
|
|
}, false)
|
|
},
|
|
},
|
|
pdf: {
|
|
selector: blocks.pdf.selector,
|
|
updateInput (el, editorEl) {
|
|
setAuxiliaryToolbar(editorEl, "pdf")
|
|
|
|
const pdfFileEl = editorEl.querySelector(`*[data-prop="pdf-file"]`)
|
|
pdfFileEl.disabled = false
|
|
// XXX: No se puede cambiar el texto, ¡esto puede ser confuso!
|
|
},
|
|
disableInput (editorEl) {
|
|
const pdfFileEl = editorEl.querySelector(`*[data-prop="pdf-file"]`)
|
|
pdfFileEl.disabled = true
|
|
},
|
|
setupInput (editorEl, contentEl) {
|
|
const pdfFileEl = editorEl.querySelector(`*[data-prop="pdf-file"]`)
|
|
pdfFileEl.addEventListener("input", event => {
|
|
const figureEl = getSelected(contentEl)
|
|
if (!figureEl) return
|
|
|
|
const file = pdfFileEl.files[0]
|
|
const pdfEl = figureEl.children[0]
|
|
|
|
pdfEl.src = URL.createObjectURL(file)
|
|
pdfEl.dataset.editorLoading = true
|
|
uploadFile(file)
|
|
.then(url => {
|
|
pdfEl.src = url
|
|
delete pdfEl.dataset.editorError
|
|
})
|
|
.catch(err => {
|
|
// TODO: mostrar error
|
|
console.error(err)
|
|
pdfEl.dataset.editorError = true
|
|
})
|
|
.finally(() => {
|
|
delete pdfEl.dataset.editorLoading
|
|
})
|
|
}, false)
|
|
},
|
|
},
|
|
}
|