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