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

199 lines
5.2 KiB
JavaScript

import {toggleMark, setBlockType, chainCommands} from 'prosemirror-commands'
import {wrapInList, liftListItem} from './godo-additional-commands.mjs'
import getLanguage from './godo-menus-language.mjs'
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('mousedown', 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 linkItem (type) {
return {
dom: icon('a'),
run (state, dispatch) {
let chref = prompt('href')
toggleMark(type, {href: chref})(state, dispatch)
},
type,
}
}
function setHeader (type, level, iconId) {
return {
run: setBlockType(type, {level}),
dom: icon(iconId),
type,
options: {level},
}
}
// Helper function to create menu icons
// id: defined in languageContent
function icon (id) {
let menuicon = document.createElement('button')
menuicon.className = 'menuicon menuicon-' + id
menuicon.setAttribute('type', 'button')
menuicon.title = getLanguage(id).text
menuicon.textContent = getLanguage(id).icon
return menuicon
}
function blocks (schema, hLevels) {
let menu = {
name: 'blocks',
hLevels,
}
let type; let i = {}
if ((type = schema.nodes.heading)) {
let iconId = 'h'
hLevels.forEach((level) => {
i['setHeader' + level] = setHeader(type, level, iconId)
iconId = iconId + 'h'
})
}
if ((type = schema.nodes.paragraph)) {
i.setP = {run: setBlockType(type), dom: icon('p'), type}
}
if ((type = schema.nodes.bullet_list)) {
i.setList = {
run: chainCommands(wrapInList(type), liftListItem(schema.nodes.list_item)),
dom: icon('ul'),
type,
active (state) {
let {$from, $to} = state.selection
let range = $from.blockRange($to)
switch (range.parent.type) {
case type:
case schema.nodes.list_item:
return true
default:
return false
}
},
}
}
menu.items = i
return menu
}
function marks (schema) {
let menu = {
name: 'marks',
}
let type; let i = {}
if ((type = schema.marks.strong)) i.toggleStrong = {run: toggleMark(type), dom: icon('b'), type}
if ((type = schema.marks.em)) i.toggleEm = {run: toggleMark(type), dom: icon('i'), type}
if ((type = schema.marks.link)) i.toggleLink = linkItem(type)
menu.items = i
return menu
}
export { Menu, blocks, marks }