godo.js/src-js/godo-menus.mjs

140 lines
3.5 KiB
JavaScript

class Menu {
constructor (menu, editorView) {
this.items = menu.items
this.editorView = editorView
this.name = menu.name
this.el = document.createElement('div')
this.el.className = `godo-${this.name}-menu`
this.el.wrapper = document.createElement('div')
this.el.wrapper.className = 'menuicons'
for (const item in this.items) {
this.el.wrapper.appendChild(this.items[item].dom)
}
this.el.appendChild(this.el.wrapper)
this.update(editorView, null)
this.el.addEventListener('mouseup', e => {
e.preventDefault()
for (const item in this.items) {
const {dom, run} = this.items[item]
if (dom.contains(e.target)) {
run(editorView.state, editorView.dispatch, editorView)
}
}
})
}
update (view, lastState) {
let state = view.state
if (lastState && lastState.doc.eq(state.doc) && lastState.selection.eq(state.selection)) {
return
}
if (this.name === 'marks') {
this.updateMarks(view, state)
}
if (this.name === 'blocks') {
this.udpateBlocks(view, state)
}
}
setMenuItem (menuItem, isActive, isDisabled = true) {
if (isActive) {
menuItem.classList.add('active')
} else {
menuItem.classList.remove('active')
}
menuItem.disabled = isDisabled
}
udpateBlocks (view, state) {
for (const item in this.items) {
const {dom, run, type, active, options} = this.items[item]
const activeDefault = () => {
let {$from, to} = state.selection
return to <= $from.end() && $from.parent.hasMarkup(type, options)
}
const isActive = (active) ? active(state) : activeDefault()
const isDisabled = !run(state, null, view)
this.setMenuItem(dom, isActive, isDisabled)
}
}
updateMarks (view, state) {
let $sel = state.selection
const $cursor = $sel.$cursor
const ranges = $sel.ranges
let getIsActive
// if single cursor
if ($cursor && $cursor.marks()) {
const activeMarks = $cursor.marks().map(m => m.type.name)
getIsActive = (type) => activeMarks.includes(type.name)
this.el.classList.add('fade')
// else = select range
} else {
const range = ranges[0]
if (range && ranges.length === 1){
getIsActive = (type) => state.doc.rangeHasMark(range.$from.pos, range.$to.pos, type)
this.el.classList.remove('fade')
}
}
for (const item in this.items) {
const {dom, type, run} = this.items[item]
const isActive = getIsActive(type)
let isDisabled = !run(state, null, view)
this.setMenuItem(dom, isActive, isDisabled)
}
// move marks nav above selection
let {from, to} = $sel
let start = view.coordsAtPos(from)
let end = view.coordsAtPos(to)
let left = Math.max((start.left + end.left) / 2, start.left + 3)
this.el.style.transform = `translate(${left}px, ${end.bottom}px)`
// if (view.hasFocus()) this.el.hidden = false;
this.el.hidden = !view.hasFocus()
}
destroy () { this.el.remove() }
}
function blocks (components, schema) {
let menu = {
name: 'blocks',
items: {},
}
for (const component of components) {
if (component.blocksMenu) {
Object.assign(menu.items, component.blocksMenu(schema))
}
}
return menu
}
function marks (components, schema) {
let menu = {
name: 'marks',
items: {},
}
for (const component of components) {
if (component.marksMenu) {
Object.assign(menu.items, component.marksMenu(schema))
}
}
return menu
}
export { Menu, blocks, marks }