fix menu sync

this loops the search for marks until a block element is found, because marks
are nested elements like for example `<p><b><i>bold and italic</i></b></p>`.
own-rich-text-solution
koehr 5 years ago
parent bfa4ef8ba0
commit 8668838238

@ -29,11 +29,8 @@
import { Component, Prop, Vue } from 'vue-property-decorator' import { Component, Prop, Vue } from 'vue-property-decorator'
import DeckCardEditorMenu from '@/components/deck-card-editor-menu.vue' import DeckCardEditorMenu from '@/components/deck-card-editor-menu.vue'
import { import {
elementNameToMenuState,
menuActionToCommand, menuActionToCommand,
getElementAndParentName, getActiveMarksAndBlocks,
marks,
blocks,
State, State,
movementKeys, movementKeys,
controlSequenceKeys controlSequenceKeys
@ -48,7 +45,8 @@ export default class DeckCardEditor extends Vue {
private contentInFocus = false private contentInFocus = false
private menuState: State = { private defaultMenuState (): State {
return {
bold: false, bold: false,
italic: false, italic: false,
paragraph: true, paragraph: true,
@ -60,33 +58,20 @@ export default class DeckCardEditor extends Vue {
separator: false, separator: false,
statBlock: false statBlock: false
} }
private clearMarks () {
marks.forEach(mark => {
this.menuState[mark] = false
})
} }
private toggleBlock (name: string) { private menuState = this.defaultMenuState()
blocks.forEach(block => {
this.menuState[block] = false private resetMenuState () {
}) this.menuState = this.defaultMenuState()
this.menuState[name] = true
} }
private setMenuState (elementName: string, parentName?: string) { private setMenuState (marks: string[], block: string) {
const stateName = elementNameToMenuState[elementName] this.resetMenuState()
marks.forEach(mark => { this.menuState[mark] = true })
// marks are always inside a block element if (block !== 'paragraph') {
if (marks.indexOf(stateName) >= 0 && parentName) { this.menuState.paragraph = false
const parentStateName = elementNameToMenuState[parentName] this.menuState[block] = true
// 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)
} }
} }
@ -105,11 +90,8 @@ export default class DeckCardEditor extends Vue {
const sel = window.getSelection()?.focusNode const sel = window.getSelection()?.focusNode
if (!sel) return if (!sel) return
const [elementName, parentName] = getElementAndParentName(sel) const { marks, block } = getActiveMarksAndBlocks(sel as HTMLElement)
console.log('focussed element', elementName, parentName) this.setMenuState(marks, block)
if (!elementName) return
this.setMenuState(elementName, parentName)
} }
private syncMenuStateIfFocussed () { private syncMenuStateIfFocussed () {

@ -1,8 +1,9 @@
import { elementNameToMenuState, marks, blocks } from './constants'
export type State = KV<boolean> export type State = KV<boolean>
export { export {
movementKeys, movementKeys,
controlSequenceKeys, controlSequenceKeys,
elementNameToMenuState,
marks, marks,
blocks blocks
} from './constants' } from './constants'
@ -33,10 +34,41 @@ export const menuActionToCommand: KV<() => boolean> = {
italic: simpleAction('italic') italic: simpleAction('italic')
} }
export function getElementAndParentName (el: Node) { export function getActiveMarksAndBlocks (el: HTMLElement): {
const element = el.nodeName === '#text' ? el.parentElement : el marks: string[];
return [ block: string;
element?.nodeName, } {
element?.parentElement?.nodeName 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 }
} }

Loading…
Cancel
Save