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);