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