src/ts/richtext/menu.ts (76 lines of code) (raw):
import { toggleMark } from "prosemirror-commands";
import { MenuElement, MenuItem, MenuItemSpec } from "prosemirror-menu";
import { MarkType, NodeType, Schema } from "prosemirror-model";
import { Command, EditorState } from "prosemirror-state";
import { isInNode, toggleBulletListCommand } from "./utils/listsHelpers";
import { linkItemCommand, unlinkItemCommand } from "./utils/command-helpers";
export const cmdItem = (cmd: Command, options: Partial<MenuItemSpec> & { title: string }) => {
const passedOptions = {
label: options.title,
run: cmd,
...options
};
passedOptions[options.enable ? "enable" : "select"] = state => cmd(state);
return new MenuItem(passedOptions);
};
export const markActive = (state: EditorState, type: MarkType) => {
const {from, $from, to, empty} = state.selection;
if (empty) return type.isInSet(state.storedMarks || $from.marks());
else return state.doc.rangeHasMark(from, to, type);
};
export const markItem = (markType: MarkType, options: Partial<MenuItemSpec> & { title: string } ) => {
const passedOptions: Partial<MenuItemSpec> & { title: string } = {
active: (state: EditorState) => { return !!markActive(state, markType); },
enable: () => true,
...options
};
return cmdItem(toggleMark(markType), passedOptions);
};
const linkItem = (markType: MarkType, options: Partial<MenuItemSpec> & { title: string }) => {
const passedOptions: MenuItemSpec = {
active: (state) => { return !!markActive(state, markType); },
enable: (state) => { return !state.selection.empty; },
run: linkItemCommand(markType),
label: options.title,
...options
};
return new MenuItem(passedOptions);
};
const unlinkItem = (markType: MarkType, options: Partial<MenuItemSpec> & { title: string }) => {
const passedOptions: MenuItemSpec = {
active: (state) => { return !!markActive(state, markType); },
enable: (state) => { return !!markActive(state, markType); },
run: unlinkItemCommand(markType),
label: options.title,
...options
};
return new MenuItem(passedOptions);
};
const wrapListItem = (schema: Schema) => (nodeType: NodeType, options: Partial<MenuItemSpec> & { title: string }) => {
const passedOptions: Partial<MenuItemSpec> & { title: string } = {
active: (state) => { return isInNode(state, nodeType);},
...options
};
return cmdItem(toggleBulletListCommand(schema), passedOptions);
};
export const buildMenuItems = (schema: Schema) => {
const markMenu: MenuElement[] = [];
if (schema.marks.strong){
markMenu.push(markItem(schema.marks.strong, {title: 'Bold', label: "Bold" }));
}
if (schema.marks.em){
markMenu.push(markItem(schema.marks.em, {title: 'Italic', label: "Italic" }));
}
if (schema.marks.link){
markMenu.push(linkItem(schema.marks.link, {title: 'Link', label: "Link" }));
markMenu.push(unlinkItem(schema.marks.link, {title: 'Unlink', label: "Unlink" }));
}
if (schema.nodes.bullet_list && schema.nodes.list_item){
markMenu.push(wrapListItem(schema)(schema.nodes.bullet_list, {
title: "Bullet list",
label: "Bullet List",
enable: () => true
}));
}
return [markMenu];
};