You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
122 lines
3.3 KiB
122 lines
3.3 KiB
![]()
4 years ago
|
/* eslint-disable no-cond-assign */
|
||
|
/* eslint-disable no-plusplus */
|
||
|
import { undoItem, redoItem, icons, MenuItem } from 'prosemirror-menu';
|
||
|
import { toggleMark } from 'prosemirror-commands';
|
||
|
import { wrapInList } from 'prosemirror-schema-list';
|
||
|
import { openPrompt } from './prompt';
|
||
|
import { TextField } from './TextField';
|
||
|
|
||
|
// Helpers to create specific types of items
|
||
|
|
||
|
function cmdItem(cmd, options) {
|
||
|
let passedOptions = {
|
||
|
label: options.title,
|
||
|
run: cmd,
|
||
|
};
|
||
|
Object.keys(options).reduce((acc, optionKey) => {
|
||
|
acc[optionKey] = options[optionKey];
|
||
|
return acc;
|
||
|
}, passedOptions);
|
||
|
if ((!options.enable || options.enable === true) && !options.select)
|
||
|
passedOptions[options.enable ? 'enable' : 'select'] = state => cmd(state);
|
||
|
|
||
|
return new MenuItem(passedOptions);
|
||
|
}
|
||
|
|
||
|
function markActive(state, type) {
|
||
|
let { from, $from, to, empty } = state.selection;
|
||
|
if (empty) return type.isInSet(state.storedMarks || $from.marks());
|
||
|
return state.doc.rangeHasMark(from, to, type);
|
||
|
}
|
||
|
|
||
|
function markItem(markType, options) {
|
||
|
let passedOptions = {
|
||
|
active(state) {
|
||
|
return markActive(state, markType);
|
||
|
},
|
||
|
enable: true,
|
||
|
};
|
||
|
Object.keys(options).reduce((acc, optionKey) => {
|
||
|
acc[optionKey] = options[optionKey];
|
||
|
return acc;
|
||
|
}, passedOptions);
|
||
|
return cmdItem(toggleMark(markType), passedOptions);
|
||
|
}
|
||
|
|
||
|
function linkItem(markType) {
|
||
|
return new MenuItem({
|
||
|
title: 'Add or remove link',
|
||
|
icon: icons.link,
|
||
|
active(state) {
|
||
|
return markActive(state, markType);
|
||
|
},
|
||
|
enable(state) {
|
||
|
return !state.selection.empty;
|
||
|
},
|
||
|
run(state, dispatch, view) {
|
||
|
if (markActive(state, markType)) {
|
||
|
toggleMark(markType)(state, dispatch);
|
||
|
return true;
|
||
|
}
|
||
|
openPrompt({
|
||
|
title: 'Create a link',
|
||
|
fields: {
|
||
|
href: new TextField({
|
||
|
label: 'https://example.com',
|
||
|
class: 'small',
|
||
|
required: true,
|
||
|
}),
|
||
|
},
|
||
|
callback(attrs) {
|
||
|
toggleMark(markType, attrs)(view.state, view.dispatch);
|
||
|
view.focus();
|
||
|
},
|
||
|
});
|
||
|
return false;
|
||
|
},
|
||
|
});
|
||
|
}
|
||
|
|
||
|
function wrapListItem(nodeType, options) {
|
||
|
return cmdItem(wrapInList(nodeType, options.attrs), options);
|
||
|
}
|
||
|
|
||
|
export function buildMenuItems(schema) {
|
||
|
let r = {};
|
||
|
let type;
|
||
|
if ((type = schema.marks.strong))
|
||
|
r.toggleStrong = markItem(type, {
|
||
|
title: 'Toggle strong style',
|
||
|
icon: icons.strong,
|
||
|
});
|
||
|
if ((type = schema.marks.em))
|
||
|
r.toggleEm = markItem(type, { title: 'Toggle emphasis', icon: icons.em });
|
||
|
if ((type = schema.marks.code))
|
||
|
r.toggleCode = markItem(type, {
|
||
|
title: 'Toggle code font',
|
||
|
icon: icons.code,
|
||
|
});
|
||
|
if ((type = schema.marks.link)) r.toggleLink = linkItem(type);
|
||
|
|
||
|
if ((type = schema.nodes.bullet_list))
|
||
|
r.wrapBulletList = wrapListItem(type, {
|
||
|
title: 'Wrap in bullet list',
|
||
|
icon: icons.bulletList,
|
||
|
});
|
||
|
if ((type = schema.nodes.ordered_list))
|
||
|
r.wrapOrderedList = wrapListItem(type, {
|
||
|
title: 'Wrap in ordered list',
|
||
|
icon: icons.orderedList,
|
||
|
});
|
||
|
|
||
|
let cut = arr => arr.filter(x => x);
|
||
|
|
||
|
r.inlineMenu = [
|
||
|
cut([r.toggleStrong, r.toggleEm, r.toggleCode, r.toggleLink]),
|
||
|
];
|
||
|
r.blockMenu = [cut([r.wrapBulletList, r.wrapOrderedList])];
|
||
|
r.fullMenu = r.inlineMenu.concat([[undoItem, redoItem]], r.blockMenu);
|
||
|
|
||
|
return r;
|
||
|
}
|