import { setBlockType, toggleMark, wrapIn } from "prosemirror-commands" import type { EditorView } from "prosemirror-view"; import type { Command, EditorState, Transaction } from "prosemirror-state"; import { Plugin } from "prosemirror-state"; import { schema } from "prosemirror-markdown"; import { publish_post, update_post, last_update, delete_post } from "./sync"; interface MenuButtonOptions { dom: HTMLSpanElement command?: Command } function icon(text: string, name: string, styles?: string) { let button = document.createElement("button"); button.className = "menuicon " + name; button.title = name; button.innerHTML = text; styles ? button.style = styles : ""; return button; } function heading(level: number) { return { command: setBlockType(schema.nodes.heading!, { level }), dom: icon("H" + level, "heading") } } class MenuView { items: MenuButtonOptions[]; editorView: EditorView; dom: HTMLDivElement; constructor(items: MenuButtonOptions[], editorView: EditorView) { this.items = items; this.editorView = editorView; this.dom = document.createElement("div"); items.forEach((item) => this.dom.appendChild(item.dom)) this.dom.className = "editor-toolbar"; this.dom.id = "editor-toolbar"; this.dom.addEventListener("mousedown", e => { e.preventDefault() editorView.focus() items.forEach(({ command, dom }) => { if (!command) return; if (dom.contains(e.target as any)) command(editorView.state, editorView.dispatch, editorView); }) }) } update() { this.items.forEach(({ command, dom }) => { if (!command || dom.classList.contains("save") || dom.classList.contains("publish")) return; let active = command(this.editorView.state, undefined, this.editorView); dom.style.display = active ? "" : "none"; }) } destroy() { this.dom.remove() } } export function menuPlugin(items: MenuButtonOptions[]) { return new Plugin({ view(editorView) { let menuView = new MenuView(items, editorView); editorView.dom.parentNode!.insertBefore(menuView.dom, editorView.dom); return menuView; } }) } const save_icon = ` `; const publish_icon = ` `; const delete_icon = ` ` function setPublishLabel(dom: HTMLButtonElement) { const isPublished = !!last_update.published_at; dom.innerHTML = (isPublished ? "Unpublish " : "Publish ") + publish_icon; } const publishButton = icon("", "publish"); setPublishLabel(publishButton); const publishItem = { dom: publishButton, command: async (_state: EditorState, _dispatch: Transaction, view: EditorView) => { await publish_post(); setPublishLabel(publishButton); return true; } }; export const menu = menuPlugin([ { command: toggleMark(schema.marks.strong!), dom: icon("B", "strong") }, { command: toggleMark(schema.marks.em!), dom: icon("i", "em") }, { command: setBlockType(schema.nodes.paragraph!), dom: icon("ΒΆ", "paragraph") }, heading(1), heading(2), heading(3), { command: wrapIn(schema.nodes.blockquote!), dom: icon(">", "blockquote") }, { dom: icon("Save " + save_icon, "save", "margin-left: auto;"), command: (state, dispatch, view) => { if (dispatch && view) update_post(view.state); return true; // don't hide } }, publishItem as any, { dom: icon("Delete " + delete_icon, "delete", "color: var(--warning);"), command: async (state, dispatch, view) => { if (dispatch && view) await delete_post(); return true; // don't hide } }, ]); setPublishLabel(publishButton);