import {ContentMenu} from "fwtoolkit"
import type {ContentMenuInit} from "fwtoolkit/content_menu"
import {DOMSerializer, type Node} from "prosemirror-model"
import {NodeSelection, Plugin, PluginKey} from "prosemirror-state"
import type {EditorView, NodeView} from "prosemirror-view"
import type {Editor} from "../types.js"
const key = new PluginKey("figureMenu")
class FigureView implements NodeView {
node: Node
view: EditorView
getPos: () => number
options: {editor: Editor}
dom: HTMLElement
serializer: DOMSerializer
contentDOM: HTMLElement
menuButton: HTMLButtonElement
constructor(
node: Node,
view: EditorView,
getPos: () => number,
options: {editor: Editor}
) {
this.node = node
this.view = view
this.getPos = getPos
this.options = options
this.dom = document.createElement("div")
this.dom.classList.add("figure")
this.serializer = DOMSerializer.fromSchema(node.type.schema)
const contentDOM = this.serializer.serializeNode(this.node) as HTMLElement
contentDOM.classList.forEach((className: string) =>
this.dom.classList.add(className)
)
contentDOM.classList.value = ""
this.dom.appendChild(contentDOM)
this.contentDOM = contentDOM
this.menuButton = document.createElement("button")
this.menuButton.classList.add("figure-menu-btn")
this.menuButton.innerHTML =
''
this.dom.insertBefore(this.menuButton, this.dom.firstChild)
}
stopEvent(event: Event) {
let stopped = false
if (event.type === "mousedown") {
const mouseEvent = event as MouseEvent
const composedPath = mouseEvent.composedPath()
if (composedPath.includes(this.menuButton)) {
stopped = true
const tr = this.view.state.tr
const $pos = this.view.state.doc.resolve(this.getPos())
tr.setSelection(new NodeSelection($pos))
this.view.dispatch(tr)
const contentMenu = new ContentMenu({
menu: this.options.editor.menu.figureMenuModel as ContentMenuInit,
width: 280,
page: this.options.editor,
menuPos: {
X: Number.parseInt(mouseEvent.pageX as unknown as string) + 20,
Y: Number.parseInt(mouseEvent.pageY as unknown as string) - 100
},
onClose: () => {
this.view.focus()
}
})
contentMenu.open()
} else if (
composedPath.includes(this.dom) &&
!composedPath.find(
el =>
el instanceof Element &&
el.matches &&
el.matches("figcaption")
)
) {
stopped = true
const tr = this.view.state.tr
const $pos = this.view.state.doc.resolve(this.getPos())
tr.setSelection(new NodeSelection($pos))
this.view.dispatch(tr)
}
}
return stopped
}
}
class FigureCaptionView implements NodeView {
node: Node
view: EditorView
getPos: () => number
options: {editor: Editor}
dom: HTMLElement
contentDOM: HTMLElement
constructor(
node: Node,
view: EditorView,
getPos: () => number,
options: {editor: Editor}
) {
this.node = node
this.view = view
this.getPos = getPos
this.options = options
this.dom = document.createElement("figcaption")
this.dom.innerHTML = ''
this.contentDOM = this.dom.lastElementChild as HTMLElement
}
}
export const figurePlugin = (options: {editor: Editor}) =>
new Plugin({
key,
state: {
init(_config, _state) {
const self = this as unknown as {
spec: {props: {nodeViews: Record}}
}
if (options.editor.docInfo.access_rights === "write") {
self.spec.props.nodeViews["figure"] = (
node: Node,
view: EditorView,
getPos: () => number
) => new FigureView(node, view, getPos, options)
}
self.spec.props.nodeViews["figure_caption"] = (
node: Node,
view: EditorView,
getPos: () => number
) => new FigureCaptionView(node, view, getPos, options)
return {}
},
apply(_tr, prev) {
return prev
}
},
props: {
nodeViews: {}
}
})