From 117fa84cc48dadb409e2e17ecd5f3525b8ecc9c2 Mon Sep 17 00:00:00 2001 From: void Date: Fri, 26 Mar 2021 15:31:36 +0000 Subject: [PATCH 1/6] editor: WIP: --- app/assets/stylesheets/editor.scss | 2 +- app/javascript/editor/types/marks.ts | 1 + app/javascript/editor/utils.ts | 2 +- app/models/metadata_template.rb | 2 +- app/views/posts/attributes/_content.haml | 3 +++ config/locales/en.yml | 1 + config/locales/es.yml | 1 + 7 files changed, 9 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/editor.scss b/app/assets/stylesheets/editor.scss index c7b50db4..5d218c7e 100644 --- a/app/assets/stylesheets/editor.scss +++ b/app/assets/stylesheets/editor.scss @@ -61,7 +61,7 @@ .editor-content { min-height: 480px; p, h1, h2, h3, h4, h5, h6, ul, li, figcaption { outline: #ccc solid thin; } - strong, em, del, u, sub, sup { background: #0002; } + strong, em, del, u, sub, sup, small { background: #0002; } a { background: #13fefe50; } [data-editor-selected] { outline: #f206f9 solid thick; } } diff --git a/app/javascript/editor/types/marks.ts b/app/javascript/editor/types/marks.ts index ac2493a8..3790c749 100644 --- a/app/javascript/editor/types/marks.ts +++ b/app/javascript/editor/types/marks.ts @@ -27,6 +27,7 @@ export const marks: { [propName: string]: EditorNode } = { super: makeMark('super', 'sup'), mark, link, + small: makeMark('small', 'small'), } function recursiveFilterSelection ( diff --git a/app/javascript/editor/utils.ts b/app/javascript/editor/utils.ts index fd3b0821..7ac4c186 100644 --- a/app/javascript/editor/utils.ts +++ b/app/javascript/editor/utils.ts @@ -1,7 +1,7 @@ import { Editor } from 'editor/editor' export const blockNames = ['paragraph', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'unordered_list', 'ordered_list'] -export const markNames = ['bold', 'italic', 'deleted', 'underline', 'sub', 'super', 'mark', 'link'] +export const markNames = ['bold', 'italic', 'deleted', 'underline', 'sub', 'super', 'mark', 'link', 'small'] export const parentBlockNames = ['left', 'center', 'right'] export function moveChildren (from: Element, to: Element, toRef: Node | null) { diff --git a/app/models/metadata_template.rb b/app/models/metadata_template.rb index c8ac0df7..3d3eeef7 100644 --- a/app/models/metadata_template.rb +++ b/app/models/metadata_template.rb @@ -178,7 +178,7 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type, def allowed_tags @allowed_tags ||= %w[strong em del u mark p h1 h2 h3 h4 h5 h6 ul ol li img iframe audio video div figure - figcaption a].freeze + figcaption a small].freeze end # Decifra el valor diff --git a/app/views/posts/attributes/_content.haml b/app/views/posts/attributes/_content.haml index 50da7e9b..235dca30 100644 --- a/app/views/posts/attributes/_content.haml +++ b/app/views/posts/attributes/_content.haml @@ -47,6 +47,9 @@ %button.btn{ type: 'button', title: t('editor.sub'), data: { editor_button: 'mark-sub' } }> %i.fa.fa-fw.fa-subscript> %span.sr-only>= t('editor.sub') + %button.btn{ type: 'button', title: t('editor.small'), data: { editor_button: 'mark-small' } }> + %i.fa.fa-fw.fa-subscript> + %span.sr-only>= t('editor.small') %button.btn.mr-0{ type: 'button', title: t('editor.h1'), data: { editor_button: 'block-h1' } }> %i.fa.fa-fw.fa-heading> 1 diff --git a/config/locales/en.yml b/config/locales/en.yml index 4a843018..b657a156 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -584,6 +584,7 @@ en: link: Link super: Superscript sub: Subscript + small: Small h1: Heading 1 h2: Heading 2 h3: Heading 3 diff --git a/config/locales/es.yml b/config/locales/es.yml index 62c3f8ed..1893f4f1 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -596,6 +596,7 @@ es: mark: Resaltado super: Índice sub: Subíndice + small: Chico link: Vínculo h1: Título 1 h2: Título 2 From 2d8507369bc59bb0758384afa4b42dc034b37125 Mon Sep 17 00:00:00 2001 From: void Date: Fri, 26 Mar 2021 15:57:07 +0000 Subject: [PATCH 2/6] editor: mantener text nodes solo cuando tienen texto --- app/javascript/editor/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/editor/types.ts b/app/javascript/editor/types.ts index 3c215a85..0e75081e 100644 --- a/app/javascript/editor/types.ts +++ b/app/javascript/editor/types.ts @@ -62,7 +62,7 @@ export function getValidChildren (node: Element, type: EditorNode): Node[] { return [...node.childNodes].filter(n => { // si permite texto y esto es un texto, es válido if (n.nodeType === Node.TEXT_NODE) - return type.allowedChildren.includes('text') + return type.allowedChildren.includes('text') && n.textContent?.length // si no es un elemento, no es válido if (!(n instanceof Element)) From 3b9237d87010ff7fa999699f33b560f87eb67863 Mon Sep 17 00:00:00 2001 From: void Date: Fri, 26 Mar 2021 15:57:47 +0000 Subject: [PATCH 3/6] fix: editor: guardar y --- app/models/metadata_template.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/metadata_template.rb b/app/models/metadata_template.rb index 3d3eeef7..bf4ac181 100644 --- a/app/models/metadata_template.rb +++ b/app/models/metadata_template.rb @@ -178,7 +178,7 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type, def allowed_tags @allowed_tags ||= %w[strong em del u mark p h1 h2 h3 h4 h5 h6 ul ol li img iframe audio video div figure - figcaption a small].freeze + figcaption a sub sup small].freeze end # Decifra el valor From b11346074af6590f68f57197079e4109a451b152 Mon Sep 17 00:00:00 2001 From: void Date: Sat, 27 Mar 2021 18:44:24 +0000 Subject: [PATCH 4/6] =?UTF-8?q?editor:=20desrepetir=20c=C3=B3digo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit esto me huele a que podría introducir bugs pero espero que no. --- app/javascript/editor/types.ts | 50 ++++++++++++++++++++- app/javascript/editor/types/blocks.ts | 38 +++++----------- app/javascript/editor/types/multimedia.ts | 31 ++----------- app/javascript/editor/types/parentBlocks.ts | 40 ++++------------- 4 files changed, 71 insertions(+), 88 deletions(-) diff --git a/app/javascript/editor/types.ts b/app/javascript/editor/types.ts index 0e75081e..9bce8097 100644 --- a/app/javascript/editor/types.ts +++ b/app/javascript/editor/types.ts @@ -3,7 +3,7 @@ import { marks } from 'editor/types/marks' import { blocks, li, EditorBlock } from 'editor/types/blocks' import { parentBlocks } from 'editor/types/parentBlocks' import { multimedia } from 'editor/types/multimedia' -import { blockNames, parentBlockNames } from 'editor/utils' +import { blockNames, parentBlockNames, safeGetRangeAt, safeGetSelection } from 'editor/utils' export interface EditorNode { selector: string, @@ -56,6 +56,54 @@ export function getType (node: Element): { typeName: string, type: EditorNode } return null } +// encuentra el primer pariente que pueda tener al type, y retorna un array +// donde +// array[0] = elemento que matchea el type +// array[array.len - 1] = primer elemento seleccionado +export function getValidParentInSelection (args: { + editor: Editor, + type: string, +}): Element[] { + const sel = safeGetSelection(args.editor) + if (!sel) throw new Error('No se donde insertar esto') + const range = safeGetRangeAt(sel) + if (!range) throw new Error('No se donde insertar esto') + + let list: Element[] = [] + + if (!sel.anchorNode) { + throw new Error('No se donde insertar esto') + } else if (sel.anchorNode instanceof Element) { + list = [sel.anchorNode] + } else if (sel.anchorNode.parentElement) { + list = [sel.anchorNode.parentElement] + } else { + throw new Error('No se donde insertar esto') + } + + while (true) { + const el = list[0] + if (!args.editor.contentEl.contains(el) + && el != args.editor.contentEl) + throw new Error('No se donde insertar esto') + const type = getType(el) + + if (type) { + //if (type.typeName === 'contentEl') break + //if (parentBlockNames.includes(type.typeName)) break + if ((type.type.allowedChildren instanceof Array) + && type.type.allowedChildren.includes(args.type)) break + } + if (el.parentElement) { + list = [el.parentElement, ...list] + } else { + throw new Error('No se donde insertar esto') + } + } + + return list +} + export function getValidChildren (node: Element, type: EditorNode): Node[] { if (type.allowedChildren === 'ignore-children') throw new Error('se llamó a getValidChildren con un type que no lo permite!') diff --git a/app/javascript/editor/types/blocks.ts b/app/javascript/editor/types/blocks.ts index 28f6384e..52ad157a 100644 --- a/app/javascript/editor/types/blocks.ts +++ b/app/javascript/editor/types/blocks.ts @@ -2,9 +2,9 @@ import { Editor } from 'editor/editor' import { safeGetSelection, safeGetRangeAt, moveChildren, - markNames, parentBlockNames, + markNames, blockNames, parentBlockNames, } from 'editor/utils' -import { EditorNode, getType } from 'editor/types' +import { EditorNode, getType, getValidParentInSelection } from 'editor/types' export interface EditorBlock extends EditorNode { } @@ -49,40 +49,22 @@ export function setupButtons (editor: Editor): void { buttonEl.addEventListener("click", event => { event.preventDefault() - const sel = safeGetSelection(editor) - if (!sel) return - const range = safeGetRangeAt(sel) - if (!range) return + const list = getValidParentInSelection({ editor, type: name }) - 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 + // No borrar cosas como multimedia + if (blockNames.indexOf(getType(list[1])!.typeName) === -1) { + return } - 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') - - let replacementType = blockEl.matches(type.selector) + let replacementType = list[1].matches(type.selector) ? blocks.paragraph : type const el = replacementType.create(editor) replacementType.onClick && replacementType.onClick(editor, el) - moveChildren(blockEl, el, null) - parentEl.replaceChild(el, blockEl) - sel.collapse(el) + moveChildren(list[1], el, null) + list[0].replaceChild(el, list[1]) + window.getSelection()?.collapse(el) return false }) diff --git a/app/javascript/editor/types/multimedia.ts b/app/javascript/editor/types/multimedia.ts index 7fdc0cac..7ebf8e88 100644 --- a/app/javascript/editor/types/multimedia.ts +++ b/app/javascript/editor/types/multimedia.ts @@ -1,5 +1,5 @@ import { Editor } from 'editor/editor' -import { EditorNode, getType } from 'editor/types' +import { EditorNode, getValidParentInSelection } from 'editor/types' import { safeGetSelection, safeGetRangeAt, markNames, parentBlockNames, @@ -192,34 +192,11 @@ export function setupButtons (editor: Editor): void { 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 list = getValidParentInSelection({ editor, type: 'multimedia' }) const el = multimedia.create(editor) - multimedia.onClick!(editor, el) - parentEl.insertBefore(el, blockEl.nextElementSibling) + list[0].insertBefore(el, list[1].nextElementSibling) + select(editor, el) return false }) diff --git a/app/javascript/editor/types/parentBlocks.ts b/app/javascript/editor/types/parentBlocks.ts index 9f780424..55a8c3d8 100644 --- a/app/javascript/editor/types/parentBlocks.ts +++ b/app/javascript/editor/types/parentBlocks.ts @@ -4,7 +4,7 @@ import { moveChildren, blockNames, parentBlockNames, } from 'editor/utils' -import { EditorNode, getType } from 'editor/types' +import { EditorNode, getType, getValidParentInSelection } from 'editor/types' function makeParentBlock (tag: string, create: EditorNode["create"]): EditorNode { return { @@ -45,48 +45,24 @@ export function setupButtons (editor: Editor): void { buttonEl.addEventListener("click", event => { event.preventDefault() - const sel = safeGetSelection(editor) - if (!sel) return - const range = safeGetRangeAt(sel) - if (!range) return - // TODO: Esto solo mueve el bloque en el que está el final de la selección // (anchorNode). quizás lo podemos hacer al revés (iterar desde contentEl // para encontrar los bloques que están seleccionados y moverlos/cambiarles // el parentBlock) - 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('no') + const list = getValidParentInSelection({ editor, type: name }) const replacementEl = type.create(editor) - if (parentEl == editor.contentEl) { + if (list[0] == editor.contentEl) { // no está en un parentBlock - editor.contentEl.insertBefore(replacementEl, blockEl) - replacementEl.appendChild(blockEl) + editor.contentEl.insertBefore(replacementEl, list[1]) + replacementEl.appendChild(list[1]) } else { // está en un parentBlock - moveChildren(parentEl, replacementEl, null) - editor.contentEl.replaceChild(replacementEl, parentEl) + moveChildren(list[0], replacementEl, null) + editor.contentEl.replaceChild(replacementEl, list[0]) } - sel.collapse(replacementEl) + window.getSelection()?.collapse(replacementEl) return false }) From f25e93ad4bfb825185d96909e6e724a4d1f9c626 Mon Sep 17 00:00:00 2001 From: void Date: Sat, 27 Mar 2021 18:45:46 +0000 Subject: [PATCH 5/6] editor: chore: estilo --- app/javascript/editor/editor.ts | 5 ++++- app/javascript/editor/types.ts | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/javascript/editor/editor.ts b/app/javascript/editor/editor.ts index 6bc18df6..167d40db 100644 --- a/app/javascript/editor/editor.ts +++ b/app/javascript/editor/editor.ts @@ -1,5 +1,8 @@ import { storeContent, restoreContent, forgetContent } from 'editor/storage' -import { isDirectChild, moveChildren, safeGetSelection, safeGetRangeAt, setAuxiliaryToolbar, parentBlockNames, clearSelected } from 'editor/utils' +import { + isDirectChild, moveChildren, safeGetSelection, safeGetRangeAt, + setAuxiliaryToolbar, parentBlockNames, clearSelected, +} from 'editor/utils' import { types, getValidChildren, getType } from 'editor/types' import { setupButtons as setupMarksButtons } from 'editor/types/marks' import { setupButtons as setupBlocksButtons } from 'editor/types/blocks' diff --git a/app/javascript/editor/types.ts b/app/javascript/editor/types.ts index 9bce8097..8034e3e4 100644 --- a/app/javascript/editor/types.ts +++ b/app/javascript/editor/types.ts @@ -51,8 +51,11 @@ export const types: { [propName: string]: EditorNode } = { export function getType (node: Element): { typeName: string, type: EditorNode } | null { for (let [typeName, type] of Object.entries(types)) { - if (node.matches(type.selector)) return { typeName, type } + if (node.matches(type.selector)) { + return { typeName, type } + } } + return null } From ff532c7658f28c38a25202e39b9c8c56a8b81cf7 Mon Sep 17 00:00:00 2001 From: void Date: Wed, 7 Apr 2021 20:18:36 +0000 Subject: [PATCH 6/6] arreglar imports en typescript --- tsconfig.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tsconfig.json b/tsconfig.json index 1cb735ac..f3d55442 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,6 +2,11 @@ "compilerOptions": { "target": "es6", "module": "es2015", + "moduleResolution": "node", + "baseUrl": ".", + "paths": { + "*": ["node_modules/*", "app/javascript/*"] + }, "strict": true, "esModuleInterop": true, "skipLibCheck": true,