diff --git a/app/javascript/editor/editor.ts b/app/javascript/editor/editor.ts
index 7d42e6e7..ef3d315f 100644
--- a/app/javascript/editor/editor.ts
+++ b/app/javascript/editor/editor.ts
@@ -1,17 +1,10 @@
import { storeContent, restoreContent } from 'editor/storage'
-import { isDirectChild, moveChildren, safeGetSelection, safeGetRangeAt } from 'editor/utils'
+import { isDirectChild, moveChildren, safeGetSelection, safeGetRangeAt, setAuxiliaryToolbar } 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'
import { setupButtons as setupParentBlocksButtons } from 'editor/types/parentBlocks'
-
-export interface Editor {
- editorEl: HTMLElement,
- toolbarEl: HTMLElement,
- contentEl: HTMLElement,
- wordAlertEl: HTMLElement,
- htmlEl: HTMLTextAreaElement,
-}
+import { setupAuxiliaryToolbar as setupLinkAuxiliaryToolbar } from 'editor/types/link'
// Esta funcion corrije errores que pueden haber como:
// * que un nodo que no tiene 'text' permitido no tenga children (se les
@@ -50,7 +43,7 @@ function fixContent (editor: Editor, node: Element = editor.contentEl): void {
if (getValidChildren(node, type).length == 0) {
if (typeof type.handleEmpty !== 'string') {
- const el = type.handleEmpty.create()
+ const el = type.handleEmpty.create(editor)
// mover cosas que pueden haber
// por ejemplo: cuando convertís a un
, queda texto fuera del li que
// creamos acá
@@ -129,25 +122,66 @@ function routine (editor: Editor): void {
}
}
+export interface Editor {
+ editorEl: HTMLElement,
+ toolbarEl: HTMLElement,
+ toolbar: {
+ auxiliary: {
+ mark: {
+ parentEl: HTMLElement,
+ colorEl: HTMLInputElement,
+ },
+ multimedia: {
+ parentEl: HTMLElement,
+ fileEl: HTMLInputElement,
+ uploadEl: HTMLButtonElement,
+ altEl: HTMLInputElement,
+ },
+ link: {
+ parentEl: HTMLElement,
+ urlEl: HTMLInputElement,
+ },
+ },
+ },
+ contentEl: HTMLElement,
+ wordAlertEl: HTMLElement,
+ htmlEl: HTMLTextAreaElement,
+}
+
+function getSel(parentEl: HTMLElement, selector: string): T {
+ const el = parentEl.querySelector(selector)
+ if (!el) throw new Error(`No pude encontrar un componente \`${selector}\``)
+ return el
+}
+
function setupEditor (editorEl: HTMLElement): void {
// XXX: ¡Esto afecta a todo el documento! ¿Quizás usar un iframe para el editor?
document.execCommand('defaultParagraphSeparator', false, 'p')
- const toolbarEl = editorEl.querySelector('.editor-toolbar')
- if (!toolbarEl) throw new Error('No pude encontrar .editor-toolbar')
- const contentEl = editorEl.querySelector('.editor-content')
- if (!contentEl) throw new Error('No pude encontrar .editor-content')
- const wordAlertEl = editorEl.querySelector('.editor-aviso-word')
- if (!wordAlertEl) throw new Error('No pude encontrar .editor-aviso-word')
- const htmlEl = editorEl.querySelector('textarea')
- if (!htmlEl) throw new Error('No pude encontrar el textarea para el HTML')
-
const editor: Editor = {
editorEl,
- toolbarEl,
- contentEl,
- wordAlertEl,
- htmlEl,
+ toolbarEl: getSel(editorEl, '.editor-toolbar'),
+ toolbar: {
+ auxiliary: {
+ mark: {
+ parentEl: getSel(editorEl, '[data-editor-auxiliary=mark]'),
+ colorEl: getSel(editorEl, '[data-editor-auxiliary=mark] [name=mark-color]'),
+ },
+ multimedia: {
+ parentEl: getSel(editorEl, '[data-editor-auxiliary=multimedia]'),
+ fileEl: getSel(editorEl, '[data-editor-auxiliary=multimedia] [name=multimedia-file]'),
+ uploadEl: getSel(editorEl, '[data-editor-auxiliary=multimedia] [name=multimedia-file-upload]'),
+ altEl: getSel(editorEl, '[data-editor-auxiliary=multimedia] [name=multimedia-alt]'),
+ },
+ link: {
+ parentEl: getSel(editorEl, '[data-editor-auxiliary=link]'),
+ urlEl: getSel(editorEl, '[data-editor-auxiliary=link] [name=link-url]'),
+ },
+ },
+ },
+ contentEl: getSel(editorEl, '.editor-content'),
+ wordAlertEl: getSel(editorEl, '.editor-aviso-word'),
+ htmlEl: getSel(editorEl, 'textarea'),
}
console.debug('iniciando editor', editor)
@@ -165,7 +199,7 @@ function setupEditor (editorEl: HTMLElement): void {
// Setup routine listeners
const observer = new MutationObserver(() => routine(editor))
- observer.observe(contentEl, {
+ observer.observe(editor.contentEl, {
childList: true,
attributes: true,
subtree: true,
@@ -174,19 +208,43 @@ function setupEditor (editorEl: HTMLElement): void {
document.addEventListener("selectionchange", () => routine(editor))
+ // Capture onClick
+ editor.contentEl.addEventListener('click', event => {
+ const target = event.target! as Element
+ const type = getType(target)
+ if (!type || !type.type.onClick) {
+ setAuxiliaryToolbar(editor, null)
+ const selectedEl = editor.contentEl.querySelector('[data-editor-selected]')
+ if (selectedEl) delete (selectedEl as HTMLElement).dataset.editorSelected
+ return true
+ }
+ type.type.onClick(editor, target)
+ return false
+ }, true)
+
+ // Clean seleted
+ const selectedEl = editor.contentEl.querySelector('[data-editor-selected]')
+ if (selectedEl) delete (selectedEl as HTMLElement).dataset.editorSelected
+
// Setup botones
setupMarksButtons(editor)
setupBlocksButtons(editor)
setupParentBlocksButtons(editor)
+ setupLinkAuxiliaryToolbar(editor)
+
// Finally...
routine(editor)
}
document.addEventListener("turbolinks:load", () => {
- for (const editorEl of document.querySelectorAll('.editor')) {
- if (!editorEl.querySelector('.editor-toolbar')) continue
-
- setupEditor(editorEl)
+ for (const editorEl of document.querySelectorAll('.editor[data-editor]')) {
+ try {
+ setupEditor(editorEl)
+ } catch (error) {
+ //alert(`No pude iniciar el editor: ${error}`)
+ // TODO: mostrar error
+ console.error('no se pudo iniciar el editor, error completo', error)
+ }
}
})
diff --git a/app/javascript/editor/types.ts b/app/javascript/editor/types.ts
index 91dfb4ee..6cc74fdd 100644
--- a/app/javascript/editor/types.ts
+++ b/app/javascript/editor/types.ts
@@ -1,7 +1,8 @@
+import { Editor } from 'editor/editor'
import { marks } from 'editor/types/marks'
import { blocks, li, EditorBlock } from 'editor/types/blocks'
import { parentBlocks } from 'editor/types/parentBlocks'
-import { blockNames } from 'editor/utils'
+import { blockNames, parentBlockNames } from 'editor/utils'
export interface EditorNode {
selector: string,
@@ -18,7 +19,9 @@ export interface EditorNode {
// ej: ul: { handleNothing: li }
handleEmpty: 'do-nothing' | 'remove' | EditorBlock,
- create: () => HTMLElement,
+ create: (editor: Editor) => HTMLElement,
+
+ onClick?: (editor: Editor, target: Element) => void,
}
export const types: { [propName: string]: EditorNode } = {
@@ -28,7 +31,7 @@ export const types: { [propName: string]: EditorNode } = {
...parentBlocks,
contentEl: {
selector: '.editor-content',
- allowedChildren: [...blockNames, ...Object.keys(parentBlocks)],
+ allowedChildren: [...blockNames, ...parentBlockNames],
handleEmpty: blocks.paragraph,
create: () => { throw new Error('se intentó crear contentEl') }
},
diff --git a/app/javascript/editor/types/blocks.ts b/app/javascript/editor/types/blocks.ts
index 55a75020..b3bee4e6 100644
--- a/app/javascript/editor/types/blocks.ts
+++ b/app/javascript/editor/types/blocks.ts
@@ -82,7 +82,7 @@ export function setupButtons (editor: Editor): void {
? blocks.paragraph
: type
- const el = replacementType.create()
+ const el = replacementType.create(editor)
moveChildren(blockEl, el, null)
parentEl.replaceChild(el, blockEl)
sel.collapse(el)
diff --git a/app/javascript/editor/types/link.ts b/app/javascript/editor/types/link.ts
new file mode 100644
index 00000000..62090ae7
--- /dev/null
+++ b/app/javascript/editor/types/link.ts
@@ -0,0 +1,32 @@
+import { Editor } from 'editor/editor'
+import { EditorNode } from 'editor/types'
+import { markNames, setAuxiliaryToolbar } from 'editor/utils'
+
+export const link: EditorNode = {
+ selector: 'a',
+ allowedChildren: [...markNames.filter(n => n !== 'link'), 'text'],
+ handleEmpty: 'remove',
+ create: () => document.createElement('a'),
+ onClick (editor, el) {
+ if (!(el instanceof HTMLAnchorElement))
+ throw new Error('oh no')
+ el.dataset.editorSelected = ''
+ editor.toolbar.auxiliary.link.urlEl.value = el.href
+ setAuxiliaryToolbar(editor, editor.toolbar.auxiliary.link.parentEl)
+ }
+}
+
+export function setupAuxiliaryToolbar (editor: Editor): void {
+ editor.toolbar.auxiliary.link.urlEl.addEventListener('input', event => {
+ const url = editor.toolbar.auxiliary.link.urlEl.value
+ const selectedEl = editor.contentEl
+ .querySelector('a[data-editor-selected]')
+ if (!selectedEl)
+ throw new Error('No pude encontrar el link para setear el enlace')
+
+ selectedEl.href = url
+ })
+ editor.toolbar.auxiliary.link.urlEl.addEventListener('keydown', event => {
+ if (event.keyCode == 13) event.preventDefault()
+ })
+}
diff --git a/app/javascript/editor/types/marks.ts b/app/javascript/editor/types/marks.ts
index 8fb97ab6..ac3e73b2 100644
--- a/app/javascript/editor/types/marks.ts
+++ b/app/javascript/editor/types/marks.ts
@@ -4,7 +4,9 @@ import {
safeGetSelection, safeGetRangeAt,
moveChildren,
markNames,
+ setAuxiliaryToolbar,
} from 'editor/utils'
+import { link } from 'editor/types/link'
function makeMark (name: string, tag: string): EditorNode {
return {
@@ -24,7 +26,7 @@ export const marks: { [propName: string]: EditorNode } = {
sub: makeMark('sub', 'sub'),
super: makeMark('super', 'sup'),
mark: makeMark('mark', 'mark'),
- link: makeMark('link', 'a'),
+ link,
}
function recursiveFilterSelection (
@@ -78,7 +80,7 @@ export function setupButtons (editor: Editor): void {
// TODO: mostrar error
return console.error("No puedo marcar cosas a través de distintos bloques!")
- const tagEl = type.create()
+ const tagEl = type.create(editor)
tagEl.appendChild(range.extractContents())
diff --git a/app/javascript/editor/types/parentBlocks.ts b/app/javascript/editor/types/parentBlocks.ts
index 090d0ea9..346ac3b0 100644
--- a/app/javascript/editor/types/parentBlocks.ts
+++ b/app/javascript/editor/types/parentBlocks.ts
@@ -76,7 +76,7 @@ export function setupButtons (editor: Editor): void {
if (!parentEl)
throw new Error('no')
- const replacementEl = type.create()
+ const replacementEl = type.create(editor)
if (parentEl == editor.contentEl) {
// no está en un parentBlock
editor.contentEl.insertBefore(replacementEl, blockEl)
diff --git a/app/javascript/editor/utils.ts b/app/javascript/editor/utils.ts
index 0710bddd..fc9e0871 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', 'a']
+export const markNames = ['bold', 'italic', 'deleted', 'underline', 'sub', 'super', 'mark', 'link']
export const parentBlockNames = ['left', 'center', 'right']
export function moveChildren (from: Element, to: Element, toRef: Node | null) {
@@ -64,3 +64,10 @@ export function splitNode (node: Element, range: Range): [SplitNode, SplitNode]
return [left, right]
}
+
+export function setAuxiliaryToolbar (editor: Editor, bar: HTMLElement | null): void {
+ for (const { parentEl } of Object.values(editor.toolbar.auxiliary)) {
+ delete parentEl.dataset.editorAuxiliaryActive
+ }
+ if (bar) bar.dataset.editorAuxiliaryActive = 'active'
+}
diff --git a/app/views/posts/attributes/_content.haml b/app/views/posts/attributes/_content.haml
index c6faf7ea..08cd8a58 100644
--- a/app/views/posts/attributes/_content.haml
+++ b/app/views/posts/attributes/_content.haml
@@ -3,7 +3,7 @@
= render 'posts/attribute_feedback',
post: post, attribute: attribute, metadata: metadata
- .editor{ id: attribute }
+ .editor{ id: attribute, data: { editor: '' } }
-# Esto es para luego decirle al navegador que se olvide estas cosas.
= hidden_field_tag 'storage_keys[]', "#{request.original_url}##{attribute}", data: { target: 'storage-key' }
.alert.alert-info
@@ -40,24 +40,24 @@
HAML cringe
TODO: generar IDs para labels
.editor-auxiliary-toolbar.mt-1.scrollbar-black{ data: { 'editor_auxiliary_toolbar': '' } }
- %form.form-group{ data: { editor: { auxiliary: 'mark' } } }
+ .form-group{ data: { editor: { auxiliary: 'mark' } } }
%label{ for: 'mark-color' }= t('editor.color')
- %input.form-control{ type: 'color', data: { prop: 'mark-color' } }/
+ %input.form-control{ type: 'color', name: 'mark-color' }/
- %form{ data: { editor: { auxiliary: 'multimedia' } } }
+ %div{ data: { editor: { auxiliary: 'multimedia' } } }
.row
.col-12.col-lg.form-group.d-flex.align-items-end
.custom-file
- %input.custom-file-input{ type: 'file', data: { prop: 'multimedia-file' }, }/
+ %input.custom-file-input{ type: 'file', name: 'multimedia-file' }/
%label.custom-file-label{ for: 'multimedia-file' }= t('editor.file.multimedia')
- %button.btn{ type: 'button', data: { prop: 'multimedia-file-upload' }, }= t('editor.file.multimedia-upload')
+ %button.btn{ type: 'button', name: 'multimedia-file-upload' }= t('editor.file.multimedia-upload')
.col-12.col-lg.form-group
%label{ for: 'multimedia-alt' }= t('editor.description')
- %input.form-control{ type: 'text', data: { prop: 'multimedia-alt' } }/
+ %input.form-control{ type: 'text', name: 'multimedia-alt' }/
- %form.form-group{ data: { editor: { auxiliary: 'link' } } }
- %label{ for: 'a-href' }= t('editor.url')
- %input.form-control{ type: 'url', data: { prop: 'a-href' } }/
+ .form-group{ data: { editor: { auxiliary: 'link' } } }
+ %label{ for: 'link-url' }= t('editor.url')
+ %input.form-control{ type: 'url', name: 'link-url' }/
.editor-aviso-word.alert.alert-info
%p= t('editor.word')