From 5f8c3685417cb3f03bb2b641405ca1d7328a8bf5 Mon Sep 17 00:00:00 2001 From: void Date: Mon, 9 Nov 2020 16:07:20 -0300 Subject: [PATCH] parar de usar checkFn, reemplazar con selectores --- app/assets/javascripts/01-types.js | 72 +++++++++++++---------------- app/assets/javascripts/02-editor.js | 62 +++++++++++-------------- app/assets/stylesheets/editor.scss | 3 -- 3 files changed, 61 insertions(+), 76 deletions(-) diff --git a/app/assets/javascripts/01-types.js b/app/assets/javascripts/01-types.js index 660529f..ea6865b 100644 --- a/app/assets/javascripts/01-types.js +++ b/app/assets/javascripts/01-types.js @@ -1,24 +1,22 @@ -// TODO: minimizar complejidad utilizando selectores css/querySelector - const marks = { bold: { - checkFn: el => el.tagName === "STRONG", + selector: "strong", createFn: () => document.createElement("STRONG"), }, italic: { - checkFn: el => el.tagName === "EM", + selector: "em", createFn: () => document.createElement("EM"), }, deleted: { - checkFn: el => el.tagName === "DEL", + selector: "del", createFn: () => document.createElement("DEL"), }, underline: { - checkFn: el => el.tagName === "U", + selector: "u", createFn: () => document.createElement("U"), }, mark: { - checkFn: el => el.tagName === "MARK", + selector: "mark", createFn: () => document.createElement("MARK"), }, } @@ -34,43 +32,43 @@ const tagNameSetFn = tagName => el => { const blocks = { p: { noButton: true, - checkFn: el => el.tagName == "P", + selector: "P", setFn: tagNameSetFn("P"), }, h1: { - checkFn: el => el.tagName == "H1", + selector: "H1", setFn: tagNameSetFn("H1"), }, h2: { - checkFn: el => el.tagName == "H2", + selector: "H2", setFn: tagNameSetFn("H2"), }, h3: { - checkFn: el => el.tagName == "H3", + selector: "H3", setFn: tagNameSetFn("H3"), }, h4: { - checkFn: el => el.tagName == "H4", + selector: "H4", setFn: tagNameSetFn("H4"), }, h5: { - checkFn: el => el.tagName == "H5", + selector: "H5", setFn: tagNameSetFn("H5"), }, h6: { - checkFn: el => el.tagName == "H6", + selector: "H6", setFn: tagNameSetFn("H6"), }, ul: { - checkFn: el => el.tagName == "UL", + selector: "UL", setFn: tagNameSetFn("UL"), }, ol: { - checkFn: el => el.tagName == "OL", + selector: "OL", setFn: tagNameSetFn("OL"), }, img: { - checkFn: el => el.tagName == "IMG", + selector: "IMG", createFn: editorEl => { const el = document.createElement("IMG") el.src = "https://radio.sutty.nl/public/placeholder_992x992.png" @@ -79,7 +77,7 @@ const blocks = { }, }, audio: { - checkFn: el => el.tagName == "AUDIO", + selector: "AUDIO", createFn: editorEl => { const el = document.createElement("AUDIO") el.controls = true @@ -87,7 +85,7 @@ const blocks = { }, }, video: { - checkFn: el => el.tagName == "VIDEO", + selector: "VIDEO", createFn: editorEl => { const el = document.createElement("VIDEO") el.controls = true @@ -96,7 +94,7 @@ const blocks = { }, // PDF pdf: { - checkFn: el => el.tagName == "IFRAME", + selector: "IFRAME", createFn: editorEl => { const el = document.createElement("IFRAME") return el @@ -112,15 +110,15 @@ const divWithStyleCreateFn = styleFn => () => { const parentBlocks = { left: { - checkFn: el => el.tagName === "DIV" && el.dataset.align === "left", + selector: "div[data-align=left]", createFn: divWithStyleCreateFn(el => el.dataset.align = "left"), }, center: { - checkFn: el => el.tagName === "DIV" && el.dataset.align === "center", + selector: "div[data-align=center]", createFn: divWithStyleCreateFn(el => el.dataset.align = "center"), }, right: { - checkFn: el => el.tagName === "DIV" && el.dataset.align === "right", + selector: "div[data-align=right]", createFn: divWithStyleCreateFn(el => el.dataset.align = "right"), }, } @@ -135,12 +133,10 @@ function rgb2hex(rgb) { return "#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]); } -// Encuentra lx primer hijx que esté en la selección que cumpla con checkFn -const findRecursiveChild = (checkFn, node) => { - if (checkFn(node) && window.getSelection().containsNode(node)) return node - for (const child of node.childNodes) { - const result = findRecursiveChild(checkFn, child) - if (result) return result +// Encuentra lx primer hijx de `selector` que esté en la selección dentro de `node` +const findInSelection = (selector, node) => { + for (const child of node.querySelectorAll(selector)) { + if (window.getSelection().containsNode(child)) return child } } @@ -148,7 +144,7 @@ const getSelected = contentEl => contentEl.querySelector(".selected") const typesWithProperties = { mark: { - checkFn: marks.mark.checkFn, + selector: marks.mark.selector, updateInput (el, editorEl) { const markColorInputEl = editorEl.querySelector(`*[data-prop="mark-color"]`) markColorInputEl.disabled = false @@ -162,13 +158,13 @@ const typesWithProperties = { setupInput (editorEl, contentEl) { const markColorInputEl = editorEl.querySelector(`*[data-prop="mark-color"]`) markColorInputEl.addEventListener("change", event => { - const markEl = findRecursiveChild(marks.mark.checkFn, contentEl) + const markEl = findInSelection(marks.mark.selector, contentEl) if (markEl) markEl.style.backgroundColor = markColorInputEl.value }, false) }, }, img: { - checkFn: blocks.img.checkFn, + selector: blocks.img.selector, updateInput (el, editorEl) { const imgFileEl = editorEl.querySelector(`*[data-prop="img-file"]`) imgFileEl.disabled = false @@ -189,8 +185,7 @@ const typesWithProperties = { setupInput (editorEl, contentEl) { const imgFileEl = editorEl.querySelector(`*[data-prop="img-file"]`) imgFileEl.addEventListener("input", event => { - // const imgEl = findRecursiveChild(blocks.img.checkFn, contentEl) - const imgEl = getSelected(contentEl) + const imgEl = contentEl.querySelector("img.selected") if (!imgEl) return const file = imgFileEl.files[0] @@ -214,14 +209,13 @@ const typesWithProperties = { const imgAltEl = editorEl.querySelector(`*[data-prop="img-alt"]`) imgAltEl.addEventListener("input", event => { - // const imgEl = findRecursiveChild(blocks.img.checkFn, contentEl) - const imgEl = getSelected(contentEl) + const imgEl = contentEl.querySelector("img.selected") if (imgEl) imgEl.alt = imgAltEl.value }, false) }, }, audio: { - checkFn: blocks.audio.checkFn, + selector: blocks.audio.selector, updateInput (el, editorEl) { const audioFileEl = editorEl.querySelector(`*[data-prop="audio-file"]`) audioFileEl.disabled = false @@ -258,7 +252,7 @@ const typesWithProperties = { }, }, video: { - checkFn: blocks.video.checkFn, + selector: blocks.video.selector, updateInput (el, editorEl) { const videoFileEl = editorEl.querySelector(`*[data-prop="video-file"]`) videoFileEl.disabled = false @@ -295,7 +289,7 @@ const typesWithProperties = { }, }, pdf: { - checkFn: blocks.pdf.checkFn, + selector: blocks.pdf.selector, updateInput (el, editorEl) { const pdfFileEl = editorEl.querySelector(`*[data-prop="pdf-file"]`) pdfFileEl.disabled = false diff --git a/app/assets/javascripts/02-editor.js b/app/assets/javascripts/02-editor.js index fff979a..b0e9aa1 100644 --- a/app/assets/javascripts/02-editor.js +++ b/app/assets/javascripts/02-editor.js @@ -82,11 +82,10 @@ function setupMarkButton (button, mark, contentEl) { let parentEl = getElementParent(sel.anchorNode) //if (sel.anchorNode == parentEl) parentEl = parentEl.firstChild - const parentChildren = Array.from(parentEl.childNodes) const range = sel.getRangeAt(0) - if (mark.checkFn(parentEl)) { + if (parentEl.matches(mark.selector)) { const [left, right] = splitNode(parentEl, range) right.range.insertNode(range.extractContents()) @@ -96,8 +95,12 @@ function setupMarkButton (button, mark, contentEl) { sel.removeAllRanges() sel.addRange(selectionRange) } else { - for (const child of parentChildren) { - if (mark.checkFn(child) && sel.containsNode(child)) { + for (const child of parentEl.childNodes) { + if ( + (child instanceof Element) + && child.matches(mark.selector) + && sel.containsNode(child) + ) { moveChildren(child, parentEl, child) parentEl.removeChild(child) // TODO: agregar a selección @@ -115,7 +118,7 @@ function setupMarkButton (button, mark, contentEl) { } for (const child of tagEl.childNodes) { - if (mark.checkFn(child)) { + if (child instanceof Element && child.matches(mark.selector)) { moveChildren(child, tagEl, child) tagEl.removeChild(child) } @@ -147,7 +150,7 @@ function setupBlockButton (button, block, contentEl, editorEl) { while (!isDirectChild(contentEl, parentEl)) parentEl = parentEl.parentElement if (block.setFn) { - if (block.checkFn(parentEl)) { + if (parentEl.matches(block.selector)) { tagNameSetFn("P")(parentEl) } else { block.setFn(parentEl) @@ -177,7 +180,7 @@ function setupParentBlockButton (button, parentBlock, contentEl) { let parentEl = sel.anchorNode while (!isDirectChild(contentEl, parentEl)) parentEl = parentEl.parentElement - if (parentBlock.checkFn(parentEl)) { + if (parentEl.matches(parentBlock.selector)) { moveChildren(parentEl, parentEl.parentElement, parentEl) parentEl.parentElement.removeChild(parentEl) } else if (elementIsParentBlock(parentEl)) { @@ -195,7 +198,7 @@ function setupParentBlockButton (button, parentBlock, contentEl) { const elementIsTypes = types => element => { for (const type of Object.values(types)) { - if (type.checkFn(element)) return true + if (element.matches(type.selector)) return true } return false } @@ -288,7 +291,12 @@ function cleanNode (node, contentEl) { child.parentNode.removeChild(child) for (const mark of Object.values(marks)) { - if (mark.checkFn(child) && child.nextSibling && mark.checkFn(child.nextSibling)) { + if ( + child.matches(mark.selector) + && child.nextSibling + && (child.nextSibling instanceof Element) + && child.nextSibling.matches(mark.selector) + ) { moveChildren(child.nextSibling, child, null) child.nextSibling.parentNode.removeChild(child.nextSibling) } @@ -346,39 +354,25 @@ function setupEditor (editorEl) { el.classList.remove("selected-unactive") } + let parentEl = range.commonAncestorContainer + if (parentEl.nodeType !== Node.ELEMENT_TYPE) parentEl = parentEl.parentElement + for (const [name, type] of Object.entries(typesWithProperties)) { let i = 0 - - let result - while (true) { - try { - result = findRecursiveChild( - el => type.checkFn(el) - && !(el.classList.contains("selected") - || el.classList.contains("selected-unactive")), - range.commonAncestorContainer - ) - } catch (err) { - // Permission denied or something... - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Property_access_denied - console.log("Error raro", err) - break - } + const results = parentEl.querySelectorAll(type.selector) - if (result) { + if (results.length) { + for (const el of results) { + if (!sel.containsNode(el)) continue if (i === 0) { - type.updateInput(result, editorEl) - result.classList.add("selected") + type.updateInput(el, editorEl) + el.classList.add("selected") } else { - result.classList.add("selected-unactive") + el.classList.add("selected-unactive") } i++ - } else { - break } - } - - if (i === 0) { + } else { type.disableInput(editorEl) } } diff --git a/app/assets/stylesheets/editor.scss b/app/assets/stylesheets/editor.scss index d5290d4..7cc714f 100644 --- a/app/assets/stylesheets/editor.scss +++ b/app/assets/stylesheets/editor.scss @@ -13,9 +13,6 @@ padding: 0; } - /*.selected, .selected-unactive { - outline-offset: 1pt; - }*/ .selected { outline: #f206f9 solid medium; } .selected-unactive { outline: gray solid medium; }