import {ContentMenu} from "fwtoolkit" import type {ContentMenuInit} from "fwtoolkit/content_menu" import {Plugin, PluginKey, Selection} from "prosemirror-state" import type {Node} from "prosemirror-model" import type {EditorState} from "prosemirror-state" import type {EditorView, NodeView} from "prosemirror-view" import {WRITE_ROLES} from "..//index.js" import type {Editor} from "../types.js" const key = new PluginKey("table") class TableView implements NodeView { node: Node view: EditorView getPos: () => number options: {editor: Editor} dom: HTMLElement contentDOM: HTMLTableElement 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( `table-${node.attrs.width}`, `table-${node.attrs.aligned}`, "content-container" ) this.dom.id = node.attrs.id as string this.menuButton = document.createElement("button") this.menuButton.classList.add("fw-content-menu-btn") this.menuButton.innerHTML = '' this.dom.appendChild(this.menuButton) const dom = document.createElement("table") if ((node.attrs.track as unknown[])?.length) { dom.dataset.track = JSON.stringify(node.attrs.track) } dom.id = node.attrs.id as string dom.dataset.width = node.attrs.width as string dom.dataset.aligned = node.attrs.aligned as string dom.dataset.layout = node.attrs.layout as string ;(dom as unknown as {class: string}).class = `table-${node.attrs.width} table-${node.attrs.aligned} table-${node.attrs.layout}` dom.dataset.category = node.attrs.category as string if (!node.attrs.caption) { dom.dataset.captionHidden = "true" } this.contentDOM = dom this.dom.appendChild(this.contentDOM) } stopEvent(event: Event) { let stopped = false if ( event.type === "mousedown" && event.composedPath().includes(this.menuButton) ) { stopped = true const mouseEvent = event as MouseEvent if (!isSelectedTableClicked(this.view.state, this.getPos())) { const tr = this.view.state.tr const $pos = this.view.state.doc.resolve(this.getPos()) const selection = Selection.findFrom($pos, 1, true) if (!selection) { return stopped } tr.setSelection(selection) this.view.dispatch(tr) } const contentMenu = new ContentMenu({ menu: this.options.editor.menu.tableMenuModel 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() } return stopped } } class TableCaptionView 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("caption") this.dom.innerHTML = '' this.contentDOM = this.dom.lastElementChild as HTMLElement } } const isSelectedTableClicked = (state: EditorState, pos: number) => { const pathArr = (state.selection.$head as unknown as {path: (Node | number)[]}).path for (let i = 0; i < pathArr.length; i++) { const item = pathArr[i] if ( typeof item !== "number" && "type" in item && item.type.name === "table" && pathArr[i - 1] === pos ) { return true } } return false } export const tablePlugin = (options: {editor: Editor}) => new Plugin({ key, state: { init(_config, _state) { const self = this as unknown as { spec: {props: {nodeViews: Record}} } if ( WRITE_ROLES.includes(options.editor.docInfo.access_rights!) ) { self.spec.props.nodeViews["table"] = ( node: Node, view: EditorView, getPos: () => number ) => new TableView(node, view, getPos, options) } self.spec.props.nodeViews["table_caption"] = ( node: Node, view: EditorView, getPos: () => number ) => new TableCaptionView(node, view, getPos, options) return {} }, apply(_tr, prev) { return prev } }, props: { nodeViews: {} } })