]> BookStack Code Mirror - bookstack/blob - resources/js/editor/menu/index.js
665d5f9ef654406d6a1e8f35a6c7c95d1d50ca3c
[bookstack] / resources / js / editor / menu / index.js
1 import {
2     MenuItem, Dropdown, DropdownSubmenu, renderGrouped, joinUpItem, liftItem, selectParentNodeItem,
3     undoItem, redoItem, wrapItem, blockTypeItem, setAttrItem, insertBlockBeforeItem,
4 } from "./menu"
5 import {icons} from "./icons";
6 import ColorPickerGrid from "./ColorPickerGrid";
7 import TableCreatorGrid from "./TableCreatorGrid";
8 import {toggleMark} from "prosemirror-commands";
9 import {menuBar} from "./menubar"
10 import schema from "../schema";
11 import {removeMarks} from "../commands";
12
13 import itemAnchorButtonItem from "./item-anchor-button";
14 import itemHtmlSourceButton from "./item-html-source-button";
15
16
17 function cmdItem(cmd, options) {
18     const passedOptions = {
19         label: options.title,
20         run: cmd
21     };
22     for (const prop in options) {
23         passedOptions[prop] = options[prop];
24     }
25     if ((!options.enable || options.enable === true) && !options.select) {
26         passedOptions[options.enable ? "enable" : "select"] = function (state) {
27             return cmd(state);
28         };
29     }
30
31     return new MenuItem(passedOptions)
32 }
33
34 function markActive(state, type) {
35     const ref = state.selection;
36     const from = ref.from;
37     const $from = ref.$from;
38     const to = ref.to;
39     const empty = ref.empty;
40     if (empty) {
41         return type.isInSet(state.storedMarks || $from.marks())
42     } else {
43         return state.doc.rangeHasMark(from, to, type)
44     }
45 }
46
47 function markItem(markType, options) {
48     const passedOptions = {
49         active: function active(state) {
50             return markActive(state, markType)
51         },
52         enable: true
53     };
54     for (const prop in options) {
55         passedOptions[prop] = options[prop];
56     }
57
58     return cmdItem(toggleMark(markType, passedOptions.attrs), passedOptions)
59 }
60
61 const inlineStyles = [
62     markItem(schema.marks.strong, {title: "Bold", icon: icons.strong}),
63     markItem(schema.marks.em, {title: "Italic", icon: icons.em}),
64     markItem(schema.marks.underline, {title: "Underline", icon: icons.underline}),
65     markItem(schema.marks.strike, {title: "Strikethrough", icon: icons.strike}),
66     markItem(schema.marks.superscript, {title: "Superscript", icon: icons.superscript}),
67     markItem(schema.marks.subscript, {title: "Subscript", icon: icons.subscript}),
68 ];
69
70 const formats = [
71     blockTypeItem(schema.nodes.heading, {
72         label: "Header Large",
73         attrs: {level: 2}
74     }),
75     blockTypeItem(schema.nodes.heading, {
76         label: "Header Medium",
77         attrs: {level: 3}
78     }),
79     blockTypeItem(schema.nodes.heading, {
80         label: "Header Small",
81         attrs: {level: 4}
82     }),
83     blockTypeItem(schema.nodes.heading, {
84         label: "Header Tiny",
85         attrs: {level: 5}
86     }),
87     blockTypeItem(schema.nodes.paragraph, {
88         label: "Paragraph",
89         attrs: {}
90     }),
91     markItem(schema.marks.code, {
92         label: "Inline Code",
93         attrs: {}
94     }),
95     new DropdownSubmenu([
96         blockTypeItem(schema.nodes.callout, {
97             label: "Info Callout",
98             attrs: {type: 'info'}
99         }),
100         blockTypeItem(schema.nodes.callout, {
101             label: "Danger Callout",
102             attrs: {type: 'danger'}
103         }),
104         blockTypeItem(schema.nodes.callout, {
105             label: "Success Callout",
106             attrs: {type: 'success'}
107         }),
108         blockTypeItem(schema.nodes.callout, {
109             label: "Warning Callout",
110             attrs: {type: 'warning'}
111         })
112     ], { label: 'Callouts' }),
113 ];
114
115 const alignments = [
116     setAttrItem('align', 'left', {
117         icon: icons.align_left
118     }),
119     setAttrItem('align', 'center', {
120         icon: icons.align_center
121     }),
122     setAttrItem('align', 'right', {
123         icon: icons.align_right
124     }),
125     setAttrItem('align', 'justify', {
126         icon: icons.align_justify
127     }),
128 ];
129
130 const colorOptions = ["#000000","#993300","#333300","#003300","#003366","#000080","#333399","#333333","#800000","#FF6600","#808000","#008000","#008080","#0000FF","#666699","#808080","#FF0000","#FF9900","#99CC00","#339966","#33CCCC","#3366FF","#800080","#999999","#FF00FF","#FFCC00","#FFFF00","#00FF00","#00FFFF","#00CCFF","#993366","#FFFFFF","#FF99CC","#FFCC99","#FFFF99","#CCFFCC","#CCFFFF","#99CCFF","#CC99FF"];
131
132 const colors = [
133     new DropdownSubmenu([
134         new ColorPickerGrid(schema.marks.text_color, 'color', colorOptions),
135     ], {icon: icons.text_color}),
136     new DropdownSubmenu([
137         new ColorPickerGrid(schema.marks.background_color, 'color', colorOptions),
138     ], {icon: icons.background_color}),
139 ];
140
141 const lists = [
142     wrapItem(schema.nodes.bullet_list, {
143         title: "Bullet List",
144         icon: icons.bullet_list,
145     }),
146     wrapItem(schema.nodes.ordered_list, {
147         title: "Ordered List",
148         icon: icons.ordered_list,
149     }),
150 ];
151
152 const inserts = [
153     itemAnchorButtonItem(),
154     insertBlockBeforeItem(schema.nodes.horizontal_rule, {
155         title: "Horizontal Rule",
156         icon: icons.horizontal_rule,
157     }),
158     new DropdownSubmenu([
159         new TableCreatorGrid()
160     ], {icon: icons.table}),
161     itemHtmlSourceButton(),
162 ];
163
164 const utilities = [
165     new MenuItem({
166         title: 'Clear Formatting',
167         icon: icons.format_clear,
168         run: removeMarks(),
169         enable: state => true,
170     }),
171 ];
172
173 const menu = menuBar({
174     floating: false,
175     content: [
176         [undoItem, redoItem],
177         [new DropdownSubmenu(formats, { label: 'Formats' })],
178         inlineStyles,
179         colors,
180         alignments,
181         lists,
182         inserts,
183         utilities,
184     ],
185 });
186
187 export default menu;
188
189 // !! This module defines a number of building blocks for ProseMirror
190 // menus, along with a [menu bar](#menu.menuBar) implementation.
191
192 // MenuElement:: interface
193 // The types defined in this module aren't the only thing you can
194 // display in your menu. Anything that conforms to this interface can
195 // be put into a menu structure.
196 //
197 //   render:: (pm: EditorView) → {dom: dom.Node, update: (EditorState) → bool}
198 //   Render the element for display in the menu. Must return a DOM
199 //   element and a function that can be used to update the element to
200 //   a new state. The `update` function will return false if the
201 //   update hid the entire element.