61 lines
1.7 KiB
TypeScript
61 lines
1.7 KiB
TypeScript
|
import { Plugin, NodeSelection } from 'prosemirror-state'
|
||
|
import { DecorationSet, Decoration } from 'prosemirror-view'
|
||
|
import type { ProseMirrorExtension } from '../state'
|
||
|
|
||
|
const handleIcon = `
|
||
|
<svg viewBox="0 0 10 10" height="14" width="14">
|
||
|
<path d="M3 2a1 1 0 110-2 1 1 0 010 2zm0 4a1 1 0 110-2 1 1 0 010 2zm0 4a1 1 0 110-2 1 1 0 010 2zm4-8a1 1 0 110-2 1 1 0 010 2zm0 4a1 1 0 110-2 1 1 0 010 2zm0 4a1 1 0 110-2 1 1 0 010 2z"/>
|
||
|
</svg>`
|
||
|
|
||
|
const createDragHandle = () => {
|
||
|
const handle = document.createElement('span')
|
||
|
|
||
|
handle.setAttribute('contenteditable', 'false')
|
||
|
const icon = document.createElement('span')
|
||
|
|
||
|
icon.innerHTML = handleIcon
|
||
|
handle.append(icon)
|
||
|
handle.classList.add('handle')
|
||
|
|
||
|
return handle
|
||
|
}
|
||
|
|
||
|
const handlePlugin = new Plugin({
|
||
|
props: {
|
||
|
decorations(state) {
|
||
|
const decos = []
|
||
|
|
||
|
state.doc.forEach((node, pos) => {
|
||
|
decos.push(
|
||
|
Decoration.widget(pos + 1, createDragHandle),
|
||
|
Decoration.node(pos, pos + node.nodeSize, {
|
||
|
class: 'draggable'
|
||
|
})
|
||
|
)
|
||
|
})
|
||
|
|
||
|
return DecorationSet.create(state.doc, decos)
|
||
|
},
|
||
|
handleDOMEvents: {
|
||
|
mousedown: (editorView, event) => {
|
||
|
const target = event.target as Element
|
||
|
|
||
|
if (target.classList.contains('handle')) {
|
||
|
const pos = editorView.posAtCoords({ left: event.x, top: event.y })
|
||
|
const resolved = editorView.state.doc.resolve(pos.pos)
|
||
|
const tr = editorView.state.tr
|
||
|
|
||
|
tr.setSelection(NodeSelection.create(editorView.state.doc, resolved.before()))
|
||
|
editorView.dispatch(tr)
|
||
|
|
||
|
return false
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
})
|
||
|
|
||
|
export default (): ProseMirrorExtension => ({
|
||
|
plugins: (prev) => [...prev, handlePlugin]
|
||
|
})
|