From 941118494af253ac88a0e9a1e6e29d0315647cac Mon Sep 17 00:00:00 2001 From: koehr Date: Wed, 1 Apr 2020 19:53:20 +0200 Subject: [PATCH] fix menu sync this loops the search for marks until a block element is found, because marks are nested elements like for example `

bold and italic

`. --- src/components/deck-card-editor.vue | 68 +++++++++++------------------ src/editor/index.ts | 46 ++++++++++++++++--- 2 files changed, 64 insertions(+), 50 deletions(-) diff --git a/src/components/deck-card-editor.vue b/src/components/deck-card-editor.vue index e161209..96f6349 100644 --- a/src/components/deck-card-editor.vue +++ b/src/components/deck-card-editor.vue @@ -29,11 +29,8 @@ import { Component, Prop, Vue } from 'vue-property-decorator' import DeckCardEditorMenu from '@/components/deck-card-editor-menu.vue' import { - elementNameToMenuState, menuActionToCommand, - getElementAndParentName, - marks, - blocks, + getActiveMarksAndBlocks, State, movementKeys, controlSequenceKeys @@ -48,45 +45,33 @@ export default class DeckCardEditor extends Vue { private contentInFocus = false - private menuState: State = { - bold: false, - italic: false, - paragraph: true, - heading1: false, - heading2: false, - heading3: false, - bulletList: false, - spacer: false, - separator: false, - statBlock: false + private defaultMenuState (): State { + return { + bold: false, + italic: false, + paragraph: true, + heading1: false, + heading2: false, + heading3: false, + bulletList: false, + spacer: false, + separator: false, + statBlock: false + } } - private clearMarks () { - marks.forEach(mark => { - this.menuState[mark] = false - }) - } + private menuState = this.defaultMenuState() - private toggleBlock (name: string) { - blocks.forEach(block => { - this.menuState[block] = false - }) - this.menuState[name] = true + private resetMenuState () { + this.menuState = this.defaultMenuState() } - private setMenuState (elementName: string, parentName?: string) { - const stateName = elementNameToMenuState[elementName] - - // marks are always inside a block element - if (marks.indexOf(stateName) >= 0 && parentName) { - const parentStateName = elementNameToMenuState[parentName] - // marks are inclusive like checkboxes - this.menuState[stateName] = true - // but blocks are exclusive like radio buttons - this.toggleBlock(parentStateName) - } else { - this.clearMarks() - this.toggleBlock(stateName) + private setMenuState (marks: string[], block: string) { + this.resetMenuState() + marks.forEach(mark => { this.menuState[mark] = true }) + if (block !== 'paragraph') { + this.menuState.paragraph = false + this.menuState[block] = true } } @@ -105,11 +90,8 @@ export default class DeckCardEditor extends Vue { const sel = window.getSelection()?.focusNode if (!sel) return - const [elementName, parentName] = getElementAndParentName(sel) - console.log('focussed element', elementName, parentName) - if (!elementName) return - - this.setMenuState(elementName, parentName) + const { marks, block } = getActiveMarksAndBlocks(sel as HTMLElement) + this.setMenuState(marks, block) } private syncMenuStateIfFocussed () { diff --git a/src/editor/index.ts b/src/editor/index.ts index 97ea19e..8f23a94 100644 --- a/src/editor/index.ts +++ b/src/editor/index.ts @@ -1,8 +1,9 @@ +import { elementNameToMenuState, marks, blocks } from './constants' + export type State = KV export { movementKeys, controlSequenceKeys, - elementNameToMenuState, marks, blocks } from './constants' @@ -33,10 +34,41 @@ export const menuActionToCommand: KV<() => boolean> = { italic: simpleAction('italic') } -export function getElementAndParentName (el: Node) { - const element = el.nodeName === '#text' ? el.parentElement : el - return [ - element?.nodeName, - element?.parentElement?.nodeName - ] +export function getActiveMarksAndBlocks (el: HTMLElement): { + marks: string[]; + block: string; +} { + let activeBlock = 'paragraph' + const activeMarks: string[] = [] + + const focussedEl = el.nodeName === '#text' ? el.parentElement : el + if (!focussedEl) return { marks: activeMarks, block: activeBlock } + + const focussedState = elementNameToMenuState[focussedEl.nodeName] + if (!focussedState) return { marks: activeMarks, block: activeBlock } + + if (blocks.indexOf(focussedState) >= 0) { + activeBlock = focussedState + return { marks: activeMarks, block: activeBlock } + } + + let wrappingEl = focussedEl.parentElement + let wrappingState: string + + if (marks.indexOf(focussedState) >= 0) { + activeMarks.push(focussedState) + + while (wrappingEl) { + wrappingState = elementNameToMenuState[wrappingEl.nodeName] + if (marks.indexOf(wrappingState) < 0) { + if (blocks.indexOf(wrappingState) >= 0) activeBlock = wrappingState + break + } + + activeMarks.push(wrappingState) + wrappingEl = wrappingEl.parentElement + } + } + + return { marks: activeMarks, block: activeBlock } }