prosemirror-slash-menu-react 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,137 @@
1
+ # prosemirror-slash-menu-react
2
+
3
+ ![made by Emergence Engineering](https://emergence-engineering.com/ee-logo.svg)
4
+
5
+ [**Made by Emergence-Engineering**](https://emergence-engineering.com/)
6
+
7
+ A UI package used together with [prosemirror-slash-menu](https://github.com/emergence-engineering/prosemirror-slash-menu) to display the menu with react.
8
+
9
+ By Horváth Áron & [Viktor Váczi](https://emergence-engineering.com/cv/viktor) at [Emergence Engineering](https://emergence-engineering.com/)
10
+
11
+ Try it out at <https://emergence-engineering.com/blog/prosemirror-slash-menu>
12
+ ![alt text](https://github.com/emergence-engineering/prosemirror-slash-menu-react/blob/main/public/prosemirror-slash-menu.gif?raw=true)
13
+
14
+ # Features
15
+
16
+ - Displaying `prosemirror-slash-menu` with react
17
+ - Menu positioning at the cursor position
18
+ - Displaying the menu upwards in case of overflow
19
+ - Default styling
20
+ - Custom styling with css classnames
21
+ - Outside click handling
22
+
23
+ # Behavior
24
+
25
+ You can open the menu with the `/` key in an empty paragraph or after a space and you can filter the elements just by typing, or you can navigate with the keyboard.
26
+ For exact behaviour description checkout [prosemirror-slash-menu](https://github.com/emergence-engineering/prosemirror-slash-menu).
27
+
28
+
29
+ # Installation and Usage
30
+ Install from npm with:
31
+
32
+ `npm install prosemirror-slash-menu-react`
33
+
34
+ Usage in the app:
35
+
36
+ ```tsx
37
+ import React, { useEffect, useRef, useState } from "react";
38
+ import { exampleSetup } from "prosemirror-example-setup";
39
+ import { EditorState } from "prosemirror-state";
40
+ import { EditorView } from "prosemirror-view";
41
+ import schema from "./schema";
42
+ import { SlashMenuPlugin } from "prosemirror-slash-menu";
43
+ import {
44
+ defaultElements,
45
+ defaultIcons,
46
+ Icons,
47
+ SlashMenuReact,
48
+ } from "prosemirror-slash-menu-react";
49
+
50
+ const ProseMirrorSlashMenuDemo = () => {
51
+ const [pmState, setPmState] = useState<EditorState>();
52
+ const [editorView, setEditorView] = useState<EditorView>();
53
+ const editorRef = useRef<HTMLDivElement>(null);
54
+ useEffect(() => {
55
+ if (!editorRef.current) return;
56
+ const state = EditorState.create({
57
+ doc: schema.nodeFromJSON({
58
+ content: [
59
+ {
60
+ content: [
61
+ {
62
+ text: "Type '/' after a space to open the menu. ",
63
+ type: "text",
64
+ },
65
+ ],
66
+ type: "paragraph",
67
+ },
68
+ ],
69
+ type: "doc",
70
+ }),
71
+ plugins: [
72
+ SlashMenuPlugin(defaultElements),
73
+ ...exampleSetup({
74
+ schema,
75
+ }),
76
+ ],
77
+ });
78
+ const view: EditorView = new EditorView(editorRef.current, {
79
+ state,
80
+ dispatchTransaction: (tr) => {
81
+ try {
82
+ const newState = view.state.apply(tr);
83
+ view.updateState(newState);
84
+ setPmState(newState);
85
+ } catch (e) {}
86
+ },
87
+ });
88
+ setEditorView(view);
89
+ return () => {
90
+ view && view.destroy();
91
+ };
92
+ }, [editorRef]);
93
+ return (
94
+ <>
95
+ <div ref={editorRef} id="editor" />
96
+ {pmState && editorView && (
97
+ <SlashMenuReact
98
+ icons={{
99
+ [Icons.HeaderMenu]: defaultIcons.H1Icon,
100
+ [Icons.Level1]: defaultIcons.H1Icon,
101
+ [Icons.Level2]: defaultIcons.H2Icon,
102
+ [Icons.Level3]: defaultIcons.H3Icon,
103
+ [Icons.Bold]: defaultIcons.BoldIcon,
104
+ [Icons.Italic]: defaultIcons.ItalicIcon,
105
+ [Icons.Code]: defaultIcons.CodeIcon,
106
+ [Icons.Link]: defaultIcons.LinkIcon,
107
+ }}
108
+ editorState={pmState}
109
+ editorView={editorView}
110
+ />
111
+ )}
112
+ </>
113
+ );
114
+ };
115
+ ```
116
+
117
+
118
+ # Styling
119
+
120
+ To use the basic styling you can import `menu-style.css` into your project. If you want to use your own styling you can override the following classnames.
121
+
122
+ - `menu-display-root` root div for the menu
123
+ - `menu-element-wrapper` root of menu elements
124
+ - `menu-element-selected` classname that is added alongside `menu-element-wrapper` when an element is selected
125
+ - `menu-element-icon` if icon is provided for the element it's rendered in this div
126
+ - `menu-element-label` label of the menu element
127
+ - `menu-placeholder` when there is no matching items for the filter, this is displayed with the text "No matching items"
128
+ - `menu-filter-wrapper` root of the filter display, positioned above the menu by default
129
+ - `menu-filter` the filter text
130
+ - `submenu-label` The label of the submenu is shown above the menu elements when its opened
131
+
132
+ # Props
133
+
134
+ - `editorState` prosemirrors editor state
135
+ - `editorView` prosemirror editor view
136
+ - `icons` Optional, if you want to provide icons for your menu elements. Type of `{[key: string]: FC}` where the key is the id of the menu element and the value is a `FunctionComponent` that renders the icon
137
+ - `subMenuIcon` Optional icon for submenu label. By default, when a submenu is open an arrow is displayed indicating that the user is in a subMenu, it can be replaced with a react node of your choice
package/dist/index.css ADDED
@@ -0,0 +1,60 @@
1
+ .menu-display-root {
2
+ position: absolute;
3
+ display: flex;
4
+ flex-direction: column;
5
+ height: 150px;
6
+ width: 200px;
7
+ z-index: 100;
8
+ overflow: scroll;
9
+ border-radius: 0.3rem;
10
+ background-color: #fafafa;
11
+ border: 2px solid #dddddd;
12
+ }
13
+
14
+ .menu-element-wrapper {
15
+ display: flex;
16
+ border-radius: 0.3rem;
17
+ padding: 0.2rem 0.5rem;
18
+ }
19
+
20
+ .menu-element-selected {
21
+ background-color: #f1f1f1;
22
+ }
23
+
24
+ .menu-element-icon {
25
+ width: 23px;
26
+ height: auto;
27
+ display: flex;
28
+ align-items: center;
29
+ }
30
+
31
+ .menu-element-label {
32
+ color: black;
33
+ display: flex;
34
+ align-items: center;
35
+ margin-left: 0.5rem;
36
+ }
37
+
38
+ .menu-placeholder {
39
+ color: #aaaaaa;
40
+ text-align: center;
41
+ padding-top: 1rem;
42
+ }
43
+
44
+ .menu-filter-wrapper {
45
+ display: flex;
46
+ background-color: transparent;
47
+ padding: 0.2rem 0.5rem;
48
+ position: absolute;
49
+ top: -1.5rem;
50
+ }
51
+
52
+ .menu-filter {
53
+ color: #aaaaaa;
54
+ font-style: italic;
55
+ }
56
+
57
+
58
+ .submenu-label{
59
+ margin-left: 0.5rem;
60
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./slashMenuReact/display";
2
+ export * from "./slashMenuReact/defaults";
@@ -0,0 +1,60 @@
1
+ .menu-display-root {
2
+ position: absolute;
3
+ display: flex;
4
+ flex-direction: column;
5
+ height: 150px;
6
+ width: 200px;
7
+ z-index: 100;
8
+ overflow: scroll;
9
+ border-radius: 0.3rem;
10
+ background-color: #fafafa;
11
+ border: 2px solid #dddddd;
12
+ }
13
+
14
+ .menu-element-wrapper {
15
+ display: flex;
16
+ border-radius: 0.3rem;
17
+ padding: 0.2rem 0.5rem;
18
+ }
19
+
20
+ .menu-element-selected {
21
+ background-color: #f1f1f1;
22
+ }
23
+
24
+ .menu-element-icon {
25
+ width: 23px;
26
+ height: auto;
27
+ display: flex;
28
+ align-items: center;
29
+ }
30
+
31
+ .menu-element-label {
32
+ color: black;
33
+ display: flex;
34
+ align-items: center;
35
+ margin-left: 0.5rem;
36
+ }
37
+
38
+ .menu-placeholder {
39
+ color: #aaaaaa;
40
+ text-align: center;
41
+ padding-top: 1rem;
42
+ }
43
+
44
+ .menu-filter-wrapper {
45
+ display: flex;
46
+ background-color: transparent;
47
+ padding: 0.2rem 0.5rem;
48
+ position: absolute;
49
+ top: -1.5rem;
50
+ }
51
+
52
+ .menu-filter {
53
+ color: #aaaaaa;
54
+ font-style: italic;
55
+ }
56
+
57
+
58
+ .submenu-label{
59
+ margin-left: 0.5rem;
60
+ }
@@ -0,0 +1,297 @@
1
+ import React, { useEffect, useMemo, useRef } from 'react';
2
+ import { getElementById, SlashMenuKey, dispatchWithMeta, SlashMetaTypes } from 'prosemirror-slash-menu';
3
+ import { usePopper } from 'react-popper';
4
+ import { setBlockType, toggleMark } from 'prosemirror-commands';
5
+
6
+ const getElements = (state) => {
7
+ const { subMenuId, filteredElements } = state;
8
+ if (!subMenuId) {
9
+ return filteredElements;
10
+ }
11
+ const subMenu = getElementById(subMenuId, state);
12
+ if (subMenu && subMenu.type === "submenu") {
13
+ return subMenu.elements;
14
+ }
15
+ };
16
+
17
+ const ListItem = ({ menuState, id, Icon, idx, label }) => {
18
+ useEffect(() => {
19
+ const element = document.getElementById(id);
20
+ if (!element)
21
+ return;
22
+ if (id === menuState.selected) {
23
+ element.classList.add("menu-element-selected");
24
+ return;
25
+ }
26
+ if (element.classList.contains("menu-element-selected")) {
27
+ element.classList.remove("menu-element-selected");
28
+ }
29
+ }, [menuState.selected]);
30
+ return (React.createElement("div", { className: "menu-element-wrapper", id: id, key: `${id}-${idx}` },
31
+ Icon ? (React.createElement("div", { className: "menu-element-icon" },
32
+ React.createElement(Icon, null))) : null,
33
+ React.createElement("div", { className: "menu-element-label" }, label)));
34
+ };
35
+
36
+ var Icons;
37
+ (function (Icons) {
38
+ Icons["HeaderMenu"] = "HeaderMenu";
39
+ Icons["Level1"] = "Level1";
40
+ Icons["Level2"] = "Level2";
41
+ Icons["Level3"] = "Level3";
42
+ Icons["Bold"] = "Bold";
43
+ Icons["Italic"] = "Italic";
44
+ Icons["Link"] = "Link";
45
+ Icons["Code"] = "Code";
46
+ })(Icons || (Icons = {}));
47
+ const H1Command = {
48
+ id: Icons.Level1,
49
+ label: "H1",
50
+ type: "command",
51
+ command: (view) => {
52
+ setBlockType(view.state.schema.nodes.heading, { level: 1 })(view.state, view.dispatch, view);
53
+ },
54
+ available: (view) => true,
55
+ };
56
+ const H2Command = {
57
+ id: Icons.Level2,
58
+ label: "H2",
59
+ type: "command",
60
+ command: (view) => {
61
+ setBlockType(view.state.schema.nodes.heading, { level: 2 })(view.state, view.dispatch, view);
62
+ },
63
+ available: (view) => true,
64
+ };
65
+ const H3Command = {
66
+ id: Icons.Level3,
67
+ label: "H3",
68
+ type: "command",
69
+ command: (view) => {
70
+ setBlockType(view.state.schema.nodes.heading, { level: 3 })(view.state, view.dispatch, view);
71
+ },
72
+ available: (view) => true,
73
+ };
74
+ const BoldCommand = {
75
+ id: Icons.Bold,
76
+ label: "Bold",
77
+ type: "command",
78
+ command: (view) => {
79
+ const markType = view.state.schema.marks.strong;
80
+ toggleMark(markType)(view.state, view.dispatch, view);
81
+ },
82
+ available: (view) => true,
83
+ };
84
+ const ItalicCommand = {
85
+ id: Icons.Italic,
86
+ label: "Italic",
87
+ type: "command",
88
+ command: (view) => {
89
+ const markType = view.state.schema.marks.em;
90
+ toggleMark(markType)(view.state, view.dispatch, view);
91
+ },
92
+ available: (view) => true,
93
+ };
94
+ const CodeCommand = {
95
+ id: Icons.Code,
96
+ label: "Code",
97
+ type: "command",
98
+ command: (view) => {
99
+ const markType = view.state.schema.marks.code;
100
+ toggleMark(markType)(view.state, view.dispatch, view);
101
+ },
102
+ available: (view) => true,
103
+ };
104
+ const LinkCommand = {
105
+ id: Icons.Link,
106
+ label: "Link",
107
+ type: "command",
108
+ command: (view) => {
109
+ const markType = view.state.schema.marks.link;
110
+ toggleMark(markType)(view.state, view.dispatch, view);
111
+ },
112
+ available: (view) => true,
113
+ };
114
+ const HeadingsMenu = {
115
+ id: Icons.HeaderMenu,
116
+ label: "Headings",
117
+ type: "submenu",
118
+ available: (view) => true,
119
+ elements: [H1Command, H2Command, H3Command],
120
+ };
121
+ const defaultElements = [
122
+ HeadingsMenu,
123
+ BoldCommand,
124
+ ItalicCommand,
125
+ CodeCommand,
126
+ LinkCommand,
127
+ ];
128
+ const H1Icon = () => (React.createElement("svg", { width: "36", height: "36", viewBox: "0 0 36 36", fill: "none", xmlns: "http://www.w3.org/2000/svg" },
129
+ React.createElement("path", { d: "M17 17H11V12C11 11.4477 10.5523 11 10 11C9.44772 11 9 11.4477 9 12V24C9 24.5523 9.44772 25 10 25C10.5523 25 11 24.5523 11 24V19H17V24C17 24.5523 17.4477 25 18 25C18.5523 25 19 24.5523 19 24V12C19 11.4477 18.5523 11 18 11C17.4477 11 17 11.4477 17 12V17Z", fill: "#050038" }),
130
+ React.createElement("path", { d: "M26 25C26.5523 25 27 24.5523 27 24V12C27 11.4477 26.5523 11 26 11H23C22.4477 11 22 11.4477 22 12C22 12.5523 22.4477 13 23 13H25V24C25 24.5523 25.4477 25 26 25Z", fill: "#050038" })));
131
+ const H2Icon = () => (React.createElement("svg", { width: "36", height: "36", viewBox: "0 0 36 36", fill: "none", xmlns: "http://www.w3.org/2000/svg" },
132
+ React.createElement("path", { d: "M16 17H11V13C11 12.4477 10.5523 12 10 12C9.44772 12 9 12.4477 9 13V23C9 23.5523 9.44772 24 10 24C10.5523 24 11 23.5523 11 23V19H16V23C16 23.5523 16.4477 24 17 24C17.5523 24 18 23.5523 18 23V13C18 12.4477 17.5523 12 17 12C16.4477 12 16 12.4477 16 13V17Z", fill: "#050038" }),
133
+ React.createElement("path", { d: "M26 22H22V19H26C26.5523 19 27 18.5523 27 18V13C27 12.4477 26.5523 12 26 12H21C20.4477 12 20 12.4477 20 13C20 13.5523 20.4477 14 21 14H25V17H21C20.4477 17 20 17.4477 20 18V23C20 23.5523 20.4477 24 21 24H26C26.5523 24 27 23.5523 27 23C27 22.4477 26.5523 22 26 22Z", fill: "#050038" })));
134
+ const H3Icon = () => (React.createElement("svg", { width: "36", height: "36", viewBox: "0 0 36 36", fill: "none", xmlns: "http://www.w3.org/2000/svg" },
135
+ React.createElement("path", { d: "M12 17H16V14C16 13.4477 16.4477 13 17 13C17.5523 13 18 13.4477 18 14V22C18 22.5523 17.5523 23 17 23C16.4477 23 16 22.5523 16 22V19H12V22C12 22.5523 11.5523 23 11 23C10.4477 23 10 22.5523 10 22V14C10 13.4477 10.4477 13 11 13C11.5523 13 12 13.4477 12 14V17Z", fill: "#050038" }),
136
+ React.createElement("path", { d: "M20 14C20 13.4477 20.4477 13 21 13H25C25.5523 13 26 13.4477 26 14V22C26 22.5523 25.5523 23 25 23H21C20.4477 23 20 22.5523 20 22C20 21.4477 20.4477 21 21 21H24V19H21C20.4477 19 20 18.5523 20 18C20 17.4477 20.4477 17 21 17H24V15H21C20.4477 15 20 14.5523 20 14Z", fill: "#050038" })));
137
+ const ItalicIcon = () => (React.createElement("svg", { width: "25", height: "24", viewBox: "0 0 25 24", fill: "none", xmlns: "http://www.w3.org/2000/svg" },
138
+ React.createElement("path", { d: "M8.38197 18L14.382 6H11.5C10.9477 6 10.5 5.55228 10.5 5C10.5 4.44772 10.9477 4 11.5 4H19.5C20.0523 4 20.5 4.44772 20.5 5C20.5 5.55228 20.0523 6 19.5 6H16.618L10.618 18H13.5C14.0523 18 14.5 18.4477 14.5 19C14.5 19.5523 14.0523 20 13.5 20H5.5C4.94772 20 4.5 19.5523 4.5 19C4.5 18.4477 4.94772 18 5.5 18H8.38197Z", fill: "#050038" })));
139
+ const BoldIcon = () => (React.createElement("svg", { width: "25", height: "24", viewBox: "0 0 25 24", fill: "none", xmlns: "http://www.w3.org/2000/svg" },
140
+ React.createElement("path", { "fill-rule": "evenodd", "clip-rule": "evenodd", d: "M19.5 15C19.5 17.7614 17.2614 20 14.5 20H8.5C7.94772 20 7.5 19.5523 7.5 19V5C7.5 4.44772 7.94772 4 8.5 4H14C16.4853 4 18.5 6.01472 18.5 8.5C18.5 9.4786 18.1876 10.3842 17.6572 11.1226C18.7818 12.0395 19.5 13.4359 19.5 15ZM10.5 10H14C14.8284 10 15.5 9.32843 15.5 8.5C15.5 7.67157 14.8284 7 14 7H10.5V10ZM10.5 17V13H14.5C15.6046 13 16.5 13.8954 16.5 15C16.5 16.1046 15.6046 17 14.5 17H10.5Z", fill: "#050038" })));
141
+ const ArrowLeft = () => (React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", className: "feather feather-arrow-left" },
142
+ React.createElement("line", { x1: "19", y1: "12", x2: "5", y2: "12" }),
143
+ React.createElement("polyline", { points: "12 19 5 12 12 5" })));
144
+ const ArrowRight = () => (React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", className: "feather feather-arrow-right" },
145
+ React.createElement("line", { x1: "5", y1: "12", x2: "19", y2: "12" }),
146
+ React.createElement("polyline", { points: "12 5 19 12 12 19" })));
147
+ const CodeIcon = () => (React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", className: "feather feather-code" },
148
+ React.createElement("polyline", { points: "16 18 22 12 16 6" }),
149
+ React.createElement("polyline", { points: "8 6 2 12 8 18" })));
150
+ const LinkIcon = () => (React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", className: "feather feather-link-2" },
151
+ React.createElement("path", { d: "M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3" }),
152
+ React.createElement("line", { x1: "8", y1: "12", x2: "16", y2: "12" })));
153
+ const defaultIcons = {
154
+ H1Icon,
155
+ H2Icon,
156
+ H3Icon,
157
+ LinkIcon,
158
+ BoldIcon,
159
+ CodeIcon,
160
+ ItalicIcon,
161
+ ArrowLeft,
162
+ ArrowRight,
163
+ };
164
+
165
+ const SlashMenuReact = ({ editorState, editorView, icons, subMenuIcon, }) => {
166
+ const menuState = useMemo(() => {
167
+ if (!editorState)
168
+ return;
169
+ return SlashMenuKey.getState(editorState);
170
+ }, [editorState]);
171
+ const elements = useMemo(() => {
172
+ if (!menuState)
173
+ return;
174
+ return getElements(menuState);
175
+ }, [menuState]);
176
+ const rootRef = useRef(null);
177
+ useEffect(() => {
178
+ if (!rootRef)
179
+ return;
180
+ const outsideClickHandler = (event) => {
181
+ if (rootRef.current &&
182
+ (!event.target ||
183
+ !(event.target instanceof Node) ||
184
+ !rootRef.current.contains(event === null || event === void 0 ? void 0 : event.target))) {
185
+ dispatchWithMeta(editorView, SlashMenuKey, {
186
+ type: SlashMetaTypes.close,
187
+ });
188
+ }
189
+ };
190
+ document.addEventListener("mousedown", outsideClickHandler);
191
+ return () => {
192
+ document.removeEventListener("mousedown", outsideClickHandler);
193
+ };
194
+ }, [rootRef]);
195
+ const [popperElement, setPopperElement] = React.useState(null);
196
+ const virtualReference = useMemo(() => {
197
+ var _a;
198
+ const domNode = (_a = editorView.domAtPos(editorState.selection.to)) === null || _a === void 0 ? void 0 : _a.node;
199
+ const cursorLeft = editorView.coordsAtPos(editorState.selection.to).left;
200
+ if (!(domNode instanceof HTMLElement))
201
+ return;
202
+ const { top, height } = domNode.getBoundingClientRect();
203
+ return {
204
+ getBoundingClientRect() {
205
+ return {
206
+ top: top,
207
+ right: cursorLeft,
208
+ bottom: top,
209
+ left: cursorLeft,
210
+ width: 0,
211
+ height: height,
212
+ x: cursorLeft,
213
+ y: top,
214
+ toJSON: () => JSON.stringify({
215
+ top: top,
216
+ right: cursorLeft,
217
+ bottom: top,
218
+ left: cursorLeft,
219
+ width: 0,
220
+ height: height,
221
+ x: cursorLeft,
222
+ y: top,
223
+ }),
224
+ };
225
+ },
226
+ };
227
+ }, [editorState, window.scrollY]);
228
+ const { styles, attributes } = usePopper(virtualReference, popperElement, {
229
+ modifiers: [
230
+ { name: "flip", enabled: true },
231
+ {
232
+ name: "preventOverflow",
233
+ options: {
234
+ mainAxis: false,
235
+ },
236
+ },
237
+ ],
238
+ });
239
+ useEffect(() => {
240
+ if (!menuState)
241
+ return;
242
+ const element = document.getElementById(menuState.selected);
243
+ if (!element || !rootRef.current)
244
+ return;
245
+ const isTopElement = menuState.selected === menuState.filteredElements[0].id;
246
+ if (isTopElement) {
247
+ rootRef.current.scrollTop = 0;
248
+ return;
249
+ }
250
+ const height = element.clientHeight +
251
+ parseInt(window.getComputedStyle(element).getPropertyValue("margin-top")) +
252
+ parseInt(window.getComputedStyle(element).getPropertyValue("margin-bottom")) +
253
+ parseInt(window.getComputedStyle(element).getPropertyValue("padding-top")) +
254
+ parseInt(window.getComputedStyle(element).getPropertyValue("padding-bottom"));
255
+ const { bottom, top } = element.getBoundingClientRect();
256
+ const containerRect = rootRef.current.getBoundingClientRect();
257
+ const scrollUp = top - height < containerRect.top;
258
+ const visible = scrollUp
259
+ ? top - containerRect.top > height
260
+ : !(bottom > containerRect.bottom);
261
+ if (!visible) {
262
+ if (scrollUp) {
263
+ rootRef.current.scrollTop = element.offsetTop - height / 2;
264
+ }
265
+ else {
266
+ rootRef.current.scrollTop =
267
+ element.offsetTop - containerRect.height + height + height / 4;
268
+ }
269
+ }
270
+ }, [menuState]);
271
+ useEffect(() => {
272
+ if (rootRef.current === null) {
273
+ return;
274
+ }
275
+ rootRef.current.scrollTop = 0;
276
+ }, [menuState === null || menuState === void 0 ? void 0 : menuState.filteredElements]);
277
+ const subMenuLabel = useMemo(() => {
278
+ var _a;
279
+ if (menuState === null || menuState === void 0 ? void 0 : menuState.subMenuId) {
280
+ return (_a = getElementById(menuState.subMenuId, menuState)) === null || _a === void 0 ? void 0 : _a.label;
281
+ }
282
+ }, [menuState]);
283
+ return (React.createElement(React.Fragment, null, (menuState === null || menuState === void 0 ? void 0 : menuState.open) ? (React.createElement("div", Object.assign({
284
+ // @ts-ignore
285
+ ref: setPopperElement, style: Object.assign({}, styles.popper) }, attributes.popper),
286
+ menuState.filter ? (React.createElement("div", { className: "menu-filter-wrapper" },
287
+ React.createElement("div", { id: "menu-filter", className: "menu-filter" }, menuState.filter))) : null,
288
+ React.createElement("div", { id: "slashDisplay", ref: rootRef, className: "menu-display-root" },
289
+ menuState.subMenuId ? (React.createElement("div", { className: "menu-element-wrapper" },
290
+ React.createElement("div", { className: "menu-element-icon" }, subMenuIcon || defaultIcons.ArrowLeft()),
291
+ React.createElement("div", { className: "submenu-label" }, subMenuLabel))) : null, elements === null || elements === void 0 ? void 0 :
292
+ elements.map((el, idx) => (React.createElement(ListItem, { key: el.id, menuState: menuState, id: el.id, Icon: icons === null || icons === void 0 ? void 0 : icons[el.id], idx: idx, label: el.label }))),
293
+ (elements === null || elements === void 0 ? void 0 : elements.length) === 0 ? (React.createElement("div", { className: "menu-placeholder" }, "No matching items")) : null))) : null));
294
+ };
295
+
296
+ export { Icons, SlashMenuReact, defaultElements, defaultIcons };
297
+ //# sourceMappingURL=index.es.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.es.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
package/dist/index.js ADDED
@@ -0,0 +1,307 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var React = require('react');
6
+ var prosemirrorSlashMenu = require('prosemirror-slash-menu');
7
+ var reactPopper = require('react-popper');
8
+ var prosemirrorCommands = require('prosemirror-commands');
9
+
10
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
11
+
12
+ var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
13
+
14
+ const getElements = (state) => {
15
+ const { subMenuId, filteredElements } = state;
16
+ if (!subMenuId) {
17
+ return filteredElements;
18
+ }
19
+ const subMenu = prosemirrorSlashMenu.getElementById(subMenuId, state);
20
+ if (subMenu && subMenu.type === "submenu") {
21
+ return subMenu.elements;
22
+ }
23
+ };
24
+
25
+ const ListItem = ({ menuState, id, Icon, idx, label }) => {
26
+ React.useEffect(() => {
27
+ const element = document.getElementById(id);
28
+ if (!element)
29
+ return;
30
+ if (id === menuState.selected) {
31
+ element.classList.add("menu-element-selected");
32
+ return;
33
+ }
34
+ if (element.classList.contains("menu-element-selected")) {
35
+ element.classList.remove("menu-element-selected");
36
+ }
37
+ }, [menuState.selected]);
38
+ return (React__default["default"].createElement("div", { className: "menu-element-wrapper", id: id, key: `${id}-${idx}` },
39
+ Icon ? (React__default["default"].createElement("div", { className: "menu-element-icon" },
40
+ React__default["default"].createElement(Icon, null))) : null,
41
+ React__default["default"].createElement("div", { className: "menu-element-label" }, label)));
42
+ };
43
+
44
+ exports.Icons = void 0;
45
+ (function (Icons) {
46
+ Icons["HeaderMenu"] = "HeaderMenu";
47
+ Icons["Level1"] = "Level1";
48
+ Icons["Level2"] = "Level2";
49
+ Icons["Level3"] = "Level3";
50
+ Icons["Bold"] = "Bold";
51
+ Icons["Italic"] = "Italic";
52
+ Icons["Link"] = "Link";
53
+ Icons["Code"] = "Code";
54
+ })(exports.Icons || (exports.Icons = {}));
55
+ const H1Command = {
56
+ id: exports.Icons.Level1,
57
+ label: "H1",
58
+ type: "command",
59
+ command: (view) => {
60
+ prosemirrorCommands.setBlockType(view.state.schema.nodes.heading, { level: 1 })(view.state, view.dispatch, view);
61
+ },
62
+ available: (view) => true,
63
+ };
64
+ const H2Command = {
65
+ id: exports.Icons.Level2,
66
+ label: "H2",
67
+ type: "command",
68
+ command: (view) => {
69
+ prosemirrorCommands.setBlockType(view.state.schema.nodes.heading, { level: 2 })(view.state, view.dispatch, view);
70
+ },
71
+ available: (view) => true,
72
+ };
73
+ const H3Command = {
74
+ id: exports.Icons.Level3,
75
+ label: "H3",
76
+ type: "command",
77
+ command: (view) => {
78
+ prosemirrorCommands.setBlockType(view.state.schema.nodes.heading, { level: 3 })(view.state, view.dispatch, view);
79
+ },
80
+ available: (view) => true,
81
+ };
82
+ const BoldCommand = {
83
+ id: exports.Icons.Bold,
84
+ label: "Bold",
85
+ type: "command",
86
+ command: (view) => {
87
+ const markType = view.state.schema.marks.strong;
88
+ prosemirrorCommands.toggleMark(markType)(view.state, view.dispatch, view);
89
+ },
90
+ available: (view) => true,
91
+ };
92
+ const ItalicCommand = {
93
+ id: exports.Icons.Italic,
94
+ label: "Italic",
95
+ type: "command",
96
+ command: (view) => {
97
+ const markType = view.state.schema.marks.em;
98
+ prosemirrorCommands.toggleMark(markType)(view.state, view.dispatch, view);
99
+ },
100
+ available: (view) => true,
101
+ };
102
+ const CodeCommand = {
103
+ id: exports.Icons.Code,
104
+ label: "Code",
105
+ type: "command",
106
+ command: (view) => {
107
+ const markType = view.state.schema.marks.code;
108
+ prosemirrorCommands.toggleMark(markType)(view.state, view.dispatch, view);
109
+ },
110
+ available: (view) => true,
111
+ };
112
+ const LinkCommand = {
113
+ id: exports.Icons.Link,
114
+ label: "Link",
115
+ type: "command",
116
+ command: (view) => {
117
+ const markType = view.state.schema.marks.link;
118
+ prosemirrorCommands.toggleMark(markType)(view.state, view.dispatch, view);
119
+ },
120
+ available: (view) => true,
121
+ };
122
+ const HeadingsMenu = {
123
+ id: exports.Icons.HeaderMenu,
124
+ label: "Headings",
125
+ type: "submenu",
126
+ available: (view) => true,
127
+ elements: [H1Command, H2Command, H3Command],
128
+ };
129
+ const defaultElements = [
130
+ HeadingsMenu,
131
+ BoldCommand,
132
+ ItalicCommand,
133
+ CodeCommand,
134
+ LinkCommand,
135
+ ];
136
+ const H1Icon = () => (React__default["default"].createElement("svg", { width: "36", height: "36", viewBox: "0 0 36 36", fill: "none", xmlns: "http://www.w3.org/2000/svg" },
137
+ React__default["default"].createElement("path", { d: "M17 17H11V12C11 11.4477 10.5523 11 10 11C9.44772 11 9 11.4477 9 12V24C9 24.5523 9.44772 25 10 25C10.5523 25 11 24.5523 11 24V19H17V24C17 24.5523 17.4477 25 18 25C18.5523 25 19 24.5523 19 24V12C19 11.4477 18.5523 11 18 11C17.4477 11 17 11.4477 17 12V17Z", fill: "#050038" }),
138
+ React__default["default"].createElement("path", { d: "M26 25C26.5523 25 27 24.5523 27 24V12C27 11.4477 26.5523 11 26 11H23C22.4477 11 22 11.4477 22 12C22 12.5523 22.4477 13 23 13H25V24C25 24.5523 25.4477 25 26 25Z", fill: "#050038" })));
139
+ const H2Icon = () => (React__default["default"].createElement("svg", { width: "36", height: "36", viewBox: "0 0 36 36", fill: "none", xmlns: "http://www.w3.org/2000/svg" },
140
+ React__default["default"].createElement("path", { d: "M16 17H11V13C11 12.4477 10.5523 12 10 12C9.44772 12 9 12.4477 9 13V23C9 23.5523 9.44772 24 10 24C10.5523 24 11 23.5523 11 23V19H16V23C16 23.5523 16.4477 24 17 24C17.5523 24 18 23.5523 18 23V13C18 12.4477 17.5523 12 17 12C16.4477 12 16 12.4477 16 13V17Z", fill: "#050038" }),
141
+ React__default["default"].createElement("path", { d: "M26 22H22V19H26C26.5523 19 27 18.5523 27 18V13C27 12.4477 26.5523 12 26 12H21C20.4477 12 20 12.4477 20 13C20 13.5523 20.4477 14 21 14H25V17H21C20.4477 17 20 17.4477 20 18V23C20 23.5523 20.4477 24 21 24H26C26.5523 24 27 23.5523 27 23C27 22.4477 26.5523 22 26 22Z", fill: "#050038" })));
142
+ const H3Icon = () => (React__default["default"].createElement("svg", { width: "36", height: "36", viewBox: "0 0 36 36", fill: "none", xmlns: "http://www.w3.org/2000/svg" },
143
+ React__default["default"].createElement("path", { d: "M12 17H16V14C16 13.4477 16.4477 13 17 13C17.5523 13 18 13.4477 18 14V22C18 22.5523 17.5523 23 17 23C16.4477 23 16 22.5523 16 22V19H12V22C12 22.5523 11.5523 23 11 23C10.4477 23 10 22.5523 10 22V14C10 13.4477 10.4477 13 11 13C11.5523 13 12 13.4477 12 14V17Z", fill: "#050038" }),
144
+ React__default["default"].createElement("path", { d: "M20 14C20 13.4477 20.4477 13 21 13H25C25.5523 13 26 13.4477 26 14V22C26 22.5523 25.5523 23 25 23H21C20.4477 23 20 22.5523 20 22C20 21.4477 20.4477 21 21 21H24V19H21C20.4477 19 20 18.5523 20 18C20 17.4477 20.4477 17 21 17H24V15H21C20.4477 15 20 14.5523 20 14Z", fill: "#050038" })));
145
+ const ItalicIcon = () => (React__default["default"].createElement("svg", { width: "25", height: "24", viewBox: "0 0 25 24", fill: "none", xmlns: "http://www.w3.org/2000/svg" },
146
+ React__default["default"].createElement("path", { d: "M8.38197 18L14.382 6H11.5C10.9477 6 10.5 5.55228 10.5 5C10.5 4.44772 10.9477 4 11.5 4H19.5C20.0523 4 20.5 4.44772 20.5 5C20.5 5.55228 20.0523 6 19.5 6H16.618L10.618 18H13.5C14.0523 18 14.5 18.4477 14.5 19C14.5 19.5523 14.0523 20 13.5 20H5.5C4.94772 20 4.5 19.5523 4.5 19C4.5 18.4477 4.94772 18 5.5 18H8.38197Z", fill: "#050038" })));
147
+ const BoldIcon = () => (React__default["default"].createElement("svg", { width: "25", height: "24", viewBox: "0 0 25 24", fill: "none", xmlns: "http://www.w3.org/2000/svg" },
148
+ React__default["default"].createElement("path", { "fill-rule": "evenodd", "clip-rule": "evenodd", d: "M19.5 15C19.5 17.7614 17.2614 20 14.5 20H8.5C7.94772 20 7.5 19.5523 7.5 19V5C7.5 4.44772 7.94772 4 8.5 4H14C16.4853 4 18.5 6.01472 18.5 8.5C18.5 9.4786 18.1876 10.3842 17.6572 11.1226C18.7818 12.0395 19.5 13.4359 19.5 15ZM10.5 10H14C14.8284 10 15.5 9.32843 15.5 8.5C15.5 7.67157 14.8284 7 14 7H10.5V10ZM10.5 17V13H14.5C15.6046 13 16.5 13.8954 16.5 15C16.5 16.1046 15.6046 17 14.5 17H10.5Z", fill: "#050038" })));
149
+ const ArrowLeft = () => (React__default["default"].createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", className: "feather feather-arrow-left" },
150
+ React__default["default"].createElement("line", { x1: "19", y1: "12", x2: "5", y2: "12" }),
151
+ React__default["default"].createElement("polyline", { points: "12 19 5 12 12 5" })));
152
+ const ArrowRight = () => (React__default["default"].createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", className: "feather feather-arrow-right" },
153
+ React__default["default"].createElement("line", { x1: "5", y1: "12", x2: "19", y2: "12" }),
154
+ React__default["default"].createElement("polyline", { points: "12 5 19 12 12 19" })));
155
+ const CodeIcon = () => (React__default["default"].createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", className: "feather feather-code" },
156
+ React__default["default"].createElement("polyline", { points: "16 18 22 12 16 6" }),
157
+ React__default["default"].createElement("polyline", { points: "8 6 2 12 8 18" })));
158
+ const LinkIcon = () => (React__default["default"].createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", className: "feather feather-link-2" },
159
+ React__default["default"].createElement("path", { d: "M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3" }),
160
+ React__default["default"].createElement("line", { x1: "8", y1: "12", x2: "16", y2: "12" })));
161
+ const defaultIcons = {
162
+ H1Icon,
163
+ H2Icon,
164
+ H3Icon,
165
+ LinkIcon,
166
+ BoldIcon,
167
+ CodeIcon,
168
+ ItalicIcon,
169
+ ArrowLeft,
170
+ ArrowRight,
171
+ };
172
+
173
+ const SlashMenuReact = ({ editorState, editorView, icons, subMenuIcon, }) => {
174
+ const menuState = React.useMemo(() => {
175
+ if (!editorState)
176
+ return;
177
+ return prosemirrorSlashMenu.SlashMenuKey.getState(editorState);
178
+ }, [editorState]);
179
+ const elements = React.useMemo(() => {
180
+ if (!menuState)
181
+ return;
182
+ return getElements(menuState);
183
+ }, [menuState]);
184
+ const rootRef = React.useRef(null);
185
+ React.useEffect(() => {
186
+ if (!rootRef)
187
+ return;
188
+ const outsideClickHandler = (event) => {
189
+ if (rootRef.current &&
190
+ (!event.target ||
191
+ !(event.target instanceof Node) ||
192
+ !rootRef.current.contains(event === null || event === void 0 ? void 0 : event.target))) {
193
+ prosemirrorSlashMenu.dispatchWithMeta(editorView, prosemirrorSlashMenu.SlashMenuKey, {
194
+ type: prosemirrorSlashMenu.SlashMetaTypes.close,
195
+ });
196
+ }
197
+ };
198
+ document.addEventListener("mousedown", outsideClickHandler);
199
+ return () => {
200
+ document.removeEventListener("mousedown", outsideClickHandler);
201
+ };
202
+ }, [rootRef]);
203
+ const [popperElement, setPopperElement] = React__default["default"].useState(null);
204
+ const virtualReference = React.useMemo(() => {
205
+ var _a;
206
+ const domNode = (_a = editorView.domAtPos(editorState.selection.to)) === null || _a === void 0 ? void 0 : _a.node;
207
+ const cursorLeft = editorView.coordsAtPos(editorState.selection.to).left;
208
+ if (!(domNode instanceof HTMLElement))
209
+ return;
210
+ const { top, height } = domNode.getBoundingClientRect();
211
+ return {
212
+ getBoundingClientRect() {
213
+ return {
214
+ top: top,
215
+ right: cursorLeft,
216
+ bottom: top,
217
+ left: cursorLeft,
218
+ width: 0,
219
+ height: height,
220
+ x: cursorLeft,
221
+ y: top,
222
+ toJSON: () => JSON.stringify({
223
+ top: top,
224
+ right: cursorLeft,
225
+ bottom: top,
226
+ left: cursorLeft,
227
+ width: 0,
228
+ height: height,
229
+ x: cursorLeft,
230
+ y: top,
231
+ }),
232
+ };
233
+ },
234
+ };
235
+ }, [editorState, window.scrollY]);
236
+ const { styles, attributes } = reactPopper.usePopper(virtualReference, popperElement, {
237
+ modifiers: [
238
+ { name: "flip", enabled: true },
239
+ {
240
+ name: "preventOverflow",
241
+ options: {
242
+ mainAxis: false,
243
+ },
244
+ },
245
+ ],
246
+ });
247
+ React.useEffect(() => {
248
+ if (!menuState)
249
+ return;
250
+ const element = document.getElementById(menuState.selected);
251
+ if (!element || !rootRef.current)
252
+ return;
253
+ const isTopElement = menuState.selected === menuState.filteredElements[0].id;
254
+ if (isTopElement) {
255
+ rootRef.current.scrollTop = 0;
256
+ return;
257
+ }
258
+ const height = element.clientHeight +
259
+ parseInt(window.getComputedStyle(element).getPropertyValue("margin-top")) +
260
+ parseInt(window.getComputedStyle(element).getPropertyValue("margin-bottom")) +
261
+ parseInt(window.getComputedStyle(element).getPropertyValue("padding-top")) +
262
+ parseInt(window.getComputedStyle(element).getPropertyValue("padding-bottom"));
263
+ const { bottom, top } = element.getBoundingClientRect();
264
+ const containerRect = rootRef.current.getBoundingClientRect();
265
+ const scrollUp = top - height < containerRect.top;
266
+ const visible = scrollUp
267
+ ? top - containerRect.top > height
268
+ : !(bottom > containerRect.bottom);
269
+ if (!visible) {
270
+ if (scrollUp) {
271
+ rootRef.current.scrollTop = element.offsetTop - height / 2;
272
+ }
273
+ else {
274
+ rootRef.current.scrollTop =
275
+ element.offsetTop - containerRect.height + height + height / 4;
276
+ }
277
+ }
278
+ }, [menuState]);
279
+ React.useEffect(() => {
280
+ if (rootRef.current === null) {
281
+ return;
282
+ }
283
+ rootRef.current.scrollTop = 0;
284
+ }, [menuState === null || menuState === void 0 ? void 0 : menuState.filteredElements]);
285
+ const subMenuLabel = React.useMemo(() => {
286
+ var _a;
287
+ if (menuState === null || menuState === void 0 ? void 0 : menuState.subMenuId) {
288
+ return (_a = prosemirrorSlashMenu.getElementById(menuState.subMenuId, menuState)) === null || _a === void 0 ? void 0 : _a.label;
289
+ }
290
+ }, [menuState]);
291
+ return (React__default["default"].createElement(React__default["default"].Fragment, null, (menuState === null || menuState === void 0 ? void 0 : menuState.open) ? (React__default["default"].createElement("div", Object.assign({
292
+ // @ts-ignore
293
+ ref: setPopperElement, style: Object.assign({}, styles.popper) }, attributes.popper),
294
+ menuState.filter ? (React__default["default"].createElement("div", { className: "menu-filter-wrapper" },
295
+ React__default["default"].createElement("div", { id: "menu-filter", className: "menu-filter" }, menuState.filter))) : null,
296
+ React__default["default"].createElement("div", { id: "slashDisplay", ref: rootRef, className: "menu-display-root" },
297
+ menuState.subMenuId ? (React__default["default"].createElement("div", { className: "menu-element-wrapper" },
298
+ React__default["default"].createElement("div", { className: "menu-element-icon" }, subMenuIcon || defaultIcons.ArrowLeft()),
299
+ React__default["default"].createElement("div", { className: "submenu-label" }, subMenuLabel))) : null, elements === null || elements === void 0 ? void 0 :
300
+ elements.map((el, idx) => (React__default["default"].createElement(ListItem, { key: el.id, menuState: menuState, id: el.id, Icon: icons === null || icons === void 0 ? void 0 : icons[el.id], idx: idx, label: el.label }))),
301
+ (elements === null || elements === void 0 ? void 0 : elements.length) === 0 ? (React__default["default"].createElement("div", { className: "menu-placeholder" }, "No matching items")) : null))) : null));
302
+ };
303
+
304
+ exports.SlashMenuReact = SlashMenuReact;
305
+ exports.defaultElements = defaultElements;
306
+ exports.defaultIcons = defaultIcons;
307
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -0,0 +1,9 @@
1
+ import { FC } from "react";
2
+ import { SlashMenuState } from "prosemirror-slash-menu";
3
+ export declare const ListItem: FC<{
4
+ menuState: SlashMenuState;
5
+ id: string;
6
+ Icon?: FC;
7
+ idx: number;
8
+ label: string;
9
+ }>;
@@ -0,0 +1,11 @@
1
+ import { SlashMenuState } from "prosemirror-slash-menu";
2
+ export declare enum Icons {
3
+ "Level1" = "Level1",
4
+ "Level2" = "Level2",
5
+ "Level3" = "Level3",
6
+ "Bold" = "Bold",
7
+ "Italic" = "Italic",
8
+ "Link" = "Link",
9
+ "Code" = "Code"
10
+ }
11
+ export declare const defaultConfig: Partial<SlashMenuState>;
@@ -0,0 +1,17 @@
1
+ import { FC } from "react";
2
+ import { EditorState } from "prosemirror-state";
3
+ import { EditorView } from "prosemirror-view";
4
+ export interface SlashMenuDisplayConfig {
5
+ height: number;
6
+ minHeight: number;
7
+ overflowPadding: number;
8
+ }
9
+ export interface SlashMenuProps {
10
+ editorState: EditorState;
11
+ editorView: EditorView;
12
+ config: SlashMenuDisplayConfig;
13
+ icons?: {
14
+ [key: string]: FC;
15
+ };
16
+ }
17
+ export declare const SlashMenuDisplay: FC<SlashMenuProps>;
@@ -0,0 +1,11 @@
1
+ import React from "react";
2
+ export declare const HeadingIcon: () => React.JSX.Element;
3
+ export declare const H1Icon: () => React.JSX.Element;
4
+ export declare const H2Icon: () => React.JSX.Element;
5
+ export declare const H3Icon: () => React.JSX.Element;
6
+ export declare const ItalicIcon: () => React.JSX.Element;
7
+ export declare const BoldIcon: () => React.JSX.Element;
8
+ export declare const ArrowLeft: () => React.JSX.Element;
9
+ export declare const ArrowRight: () => React.JSX.Element;
10
+ export declare const CodeIcon: () => React.JSX.Element;
11
+ export declare const Link: () => React.JSX.Element;
@@ -0,0 +1,2 @@
1
+ import { SlashMenuState } from "prosemirror-slash-menu";
2
+ export declare const getElements: (state: SlashMenuState) => import("prosemirror-slash-menu").MenuElement[] | undefined;
@@ -0,0 +1,10 @@
1
+ import { FC } from "react";
2
+ import { SlashMenuState } from "prosemirror-slash-menu";
3
+ import "./styles/menu-style.css";
4
+ export declare const ListItem: FC<{
5
+ menuState: SlashMenuState;
6
+ id: string;
7
+ Icon?: FC;
8
+ idx: number;
9
+ label: string;
10
+ }>;
@@ -0,0 +1,24 @@
1
+ import { CommandItem, SubMenu } from "prosemirror-slash-menu";
2
+ export declare enum Icons {
3
+ "HeaderMenu" = "HeaderMenu",
4
+ "Level1" = "Level1",
5
+ "Level2" = "Level2",
6
+ "Level3" = "Level3",
7
+ "Bold" = "Bold",
8
+ "Italic" = "Italic",
9
+ "Link" = "Link",
10
+ "Code" = "Code"
11
+ }
12
+ export declare const defaultElements: (CommandItem | SubMenu)[];
13
+ import React from "react";
14
+ export declare const defaultIcons: {
15
+ H1Icon: () => React.JSX.Element;
16
+ H2Icon: () => React.JSX.Element;
17
+ H3Icon: () => React.JSX.Element;
18
+ LinkIcon: () => React.JSX.Element;
19
+ BoldIcon: () => React.JSX.Element;
20
+ CodeIcon: () => React.JSX.Element;
21
+ ItalicIcon: () => React.JSX.Element;
22
+ ArrowLeft: () => React.JSX.Element;
23
+ ArrowRight: () => React.JSX.Element;
24
+ };
@@ -0,0 +1,13 @@
1
+ import { FC, ReactNode } from "react";
2
+ import { EditorState } from "prosemirror-state";
3
+ import { EditorView } from "prosemirror-view";
4
+ import "./styles/menu-style.css";
5
+ export interface SlashMenuProps {
6
+ editorState: EditorState;
7
+ editorView: EditorView;
8
+ icons?: {
9
+ [key: string]: FC;
10
+ };
11
+ subMenuIcon?: ReactNode;
12
+ }
13
+ export declare const SlashMenuReact: FC<SlashMenuProps>;
@@ -0,0 +1,2 @@
1
+ import { SlashMenuState } from "prosemirror-slash-menu";
2
+ export declare const getElements: (state: SlashMenuState) => import("prosemirror-slash-menu").MenuElement[] | undefined;
@@ -0,0 +1,60 @@
1
+ .menu-display-root {
2
+ position: absolute;
3
+ display: flex;
4
+ flex-direction: column;
5
+ height: 150px;
6
+ width: 200px;
7
+ z-index: 100;
8
+ overflow: scroll;
9
+ border-radius: 0.3rem;
10
+ background-color: #fafafa;
11
+ border: 2px solid #dddddd;
12
+ }
13
+
14
+ .menu-element-wrapper {
15
+ display: flex;
16
+ border-radius: 0.3rem;
17
+ padding: 0.2rem 0.5rem;
18
+ }
19
+
20
+ .menu-element-selected {
21
+ background-color: #f1f1f1;
22
+ }
23
+
24
+ .menu-element-icon {
25
+ width: 23px;
26
+ height: auto;
27
+ display: flex;
28
+ align-items: center;
29
+ }
30
+
31
+ .menu-element-label {
32
+ color: black;
33
+ display: flex;
34
+ align-items: center;
35
+ margin-left: 0.5rem;
36
+ }
37
+
38
+ .menu-placeholder {
39
+ color: #aaaaaa;
40
+ text-align: center;
41
+ padding-top: 1rem;
42
+ }
43
+
44
+ .menu-filter-wrapper {
45
+ display: flex;
46
+ background-color: transparent;
47
+ padding: 0.2rem 0.5rem;
48
+ position: absolute;
49
+ top: -1.5rem;
50
+ }
51
+
52
+ .menu-filter {
53
+ color: #aaaaaa;
54
+ font-style: italic;
55
+ }
56
+
57
+
58
+ .submenu-label{
59
+ margin-left: 0.5rem;
60
+ }
package/package.json ADDED
@@ -0,0 +1,83 @@
1
+ {
2
+ "name": "prosemirror-slash-menu-react",
3
+ "version": "0.0.2",
4
+ "description": "Implementation of prosemirror-slash-menu in react",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.es.js",
7
+ "types": "dist/index.d.ts",
8
+ "dependencies": {
9
+ "@popperjs/core": "^2.11.8",
10
+ "@rollup/plugin-commonjs": "^25.0.0",
11
+ "@rollup/plugin-sucrase": "^5.0.1",
12
+ "@rollup/plugin-typescript": "^11.1.1",
13
+ "@testing-library/jest-dom": "^5.16.5",
14
+ "@testing-library/react": "^13.4.0",
15
+ "@testing-library/user-event": "^13.5.0",
16
+ "@types/jest": "^27.5.2",
17
+ "@types/node": "^16.18.34",
18
+ "@types/react": "^18.2.9",
19
+ "@types/react-dom": "^18.2.4",
20
+ "prettier": "^2.8.8",
21
+ "prosemirror-commands": "^1.5.2",
22
+ "prosemirror-schema-basic": "^1.2.2",
23
+ "prosemirror-slash-menu": "^0.1.0",
24
+ "prosemirror-view": "^1.31.4",
25
+ "react": "^18.2.0",
26
+ "react-dom": "^18.2.0",
27
+ "react-popper": "^2.3.0",
28
+ "react-scripts": "5.0.1",
29
+ "rollup-plugin-import-css": "^3.2.1",
30
+ "rollup-plugin-peer-deps-external": "^2.2.4",
31
+ "rollup-plugin-postcss": "^4.0.2",
32
+ "typescript": "^4.9.5",
33
+ "web-vitals": "^2.1.4"
34
+ },
35
+ "scripts": {
36
+ "start": "react-scripts start",
37
+ "test": "react-scripts test",
38
+ "eject": "react-scripts eject",
39
+ "build": "rollup -c --bundleConfigAsCjs",
40
+ "yalc:watch": "nodemon --watch dist --exec 'yalc push'",
41
+ "dev:watch": "npm-run-all --parallel dev yalc:watch"
42
+ },
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "git+https://github.com/emergence-engineering/prosemirror-slash-menu-react.git"
46
+ },
47
+ "files": [
48
+ "dist/**/*"
49
+ ],
50
+ "author": "Emergence Engineering",
51
+ "keywords": [
52
+ "ProseMirror",
53
+ "React",
54
+ "slash",
55
+ "menu"
56
+ ],
57
+ "license": "ISC",
58
+ "bugs": {
59
+ "url": "https://github.com/emergence-engineering/prosemirror-slash-menu-react/issues"
60
+ },
61
+ "homepage": "https://github.com/emergence-engineering/prosemirror-slash-menu-react#readme",
62
+ "eslintConfig": {
63
+ "extends": [
64
+ "react-app",
65
+ "react-app/jest"
66
+ ]
67
+ },
68
+ "devDependencies": {
69
+ "@babel/core": "^7.22.1",
70
+ "@babel/preset-env": "^7.22.4",
71
+ "@babel/preset-react": "^7.22.3",
72
+ "@rollup/plugin-babel": "^6.0.3",
73
+ "@rollup/plugin-node-resolve": "^15.1.0",
74
+ "nodemon": "^2.0.22",
75
+ "rollup": "^2.79.1",
76
+ "rollup-plugin-babel": "^4.4.0",
77
+ "rollup-plugin-copy": "^3.4.0",
78
+ "rollup-plugin-typescript2": "^0.34.1"
79
+ },
80
+ "peerDependencies": {
81
+ "react": "^18.2.0"
82
+ }
83
+ }