140 lines
3.5 KiB
JavaScript
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 }
|