@springmicro/rte 0.1.3 → 0.1.11
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 +57 -4
- package/dist/index.js +7361 -7279
- package/package.json +7 -3
- package/.eslintrc.cjs +0 -18
- package/dist/index.d.ts +0 -10
- package/dist/index.umd.cjs +0 -469
- package/index.html +0 -13
- package/src/App.css +0 -42
- package/src/App.tsx +0 -10
- package/src/contexts/color-context.tsx +0 -53
- package/src/hooks/useSimpleFormik.tsx +0 -74
- package/src/index.css +0 -68
- package/src/index.tsx +0 -3
- package/src/main.tsx +0 -10
- package/src/slate/base-editor.stories.tsx +0 -16
- package/src/slate/base-editor.tsx +0 -116
- package/src/slate/blog-rte.stories.tsx +0 -16
- package/src/slate/blog-rte.tsx +0 -126
- package/src/slate/common/button.tsx +0 -35
- package/src/slate/common/element.tsx +0 -13
- package/src/slate/common/icon.jsx +0 -97
- package/src/slate/components/code-to-text/CodeToTextButton.jsx +0 -19
- package/src/slate/components/code-to-text/HtmlCode.jsx +0 -64
- package/src/slate/components/code-to-text/HtmlContextMenu.jsx +0 -39
- package/src/slate/components/code-to-text/index.jsx +0 -111
- package/src/slate/components/color-picker/color-cursor.stories.tsx +0 -16
- package/src/slate/components/color-picker/color-cursor.tsx +0 -34
- package/src/slate/components/color-picker/color-formats-view.stories.tsx +0 -25
- package/src/slate/components/color-picker/color-formats-view.tsx +0 -115
- package/src/slate/components/color-picker/color-gradient.stories.tsx +0 -48
- package/src/slate/components/color-picker/color-gradient.tsx +0 -128
- package/src/slate/components/color-picker/color-hue.stories.tsx +0 -41
- package/src/slate/components/color-picker/color-hue.tsx +0 -110
- package/src/slate/components/color-picker/color-picker.stories.tsx +0 -25
- package/src/slate/components/color-picker/color-picker.tsx +0 -41
- package/src/slate/components/color-picker/color-popover.stories.tsx +0 -26
- package/src/slate/components/color-picker/color-popover.tsx +0 -58
- package/src/slate/components/color-picker/color-swatch.stories.tsx +0 -16
- package/src/slate/components/color-picker/color-swatch.tsx +0 -76
- package/src/slate/components/color-picker/default-colors.ts +0 -38
- package/src/slate/components/color-picker/slate-color-button.tsx +0 -128
- package/src/slate/components/embed/Embed.jsx +0 -96
- package/src/slate/components/embed/Image.jsx +0 -45
- package/src/slate/components/embed/Video.jsx +0 -65
- package/src/slate/components/equation/Equation.jsx +0 -19
- package/src/slate/components/equation/EquationButton.jsx +0 -68
- package/src/slate/components/id/Id.jsx +0 -57
- package/src/slate/components/image/image.stories.tsx +0 -17
- package/src/slate/components/image/image.tsx +0 -62
- package/src/slate/components/image/insert-image-button.stories.tsx +0 -83
- package/src/slate/components/image/insert-image-button.tsx +0 -132
- package/src/slate/components/image/types.ts +0 -9
- package/src/slate/components/link/Link.jsx +0 -56
- package/src/slate/components/link/LinkButton.tsx +0 -106
- package/src/slate/components/table/Table.jsx +0 -11
- package/src/slate/components/table/TableSelector.jsx +0 -97
- package/src/slate/components/table-context-menu/TableContextMenu.tsx +0 -106
- package/src/slate/custom-types.d.ts +0 -152
- package/src/slate/editor.module.css +0 -226
- package/src/slate/paper-rte.stories.tsx +0 -16
- package/src/slate/paper-rte.tsx +0 -47
- package/src/slate/plugins/withEmbeds.js +0 -33
- package/src/slate/plugins/withEquation.js +0 -8
- package/src/slate/plugins/withImages.ts +0 -69
- package/src/slate/plugins/withLinks.js +0 -9
- package/src/slate/plugins/withTable.js +0 -74
- package/src/slate/serializers/generic.ts +0 -44
- package/src/slate/serializers/types.ts +0 -20
- package/src/slate/toolbar/index.tsx +0 -186
- package/src/slate/toolbar/paper-toolbar.tsx +0 -494
- package/src/slate/toolbar/shortcuts.tsx +0 -77
- package/src/slate/toolbar/toolbar-groups.ts +0 -213
- package/src/slate/types/index.ts +0 -0
- package/src/slate/utils/customHooks/useContextMenu.js +0 -42
- package/src/slate/utils/customHooks/useFormat.js +0 -26
- package/src/slate/utils/customHooks/usePopup.jsx +0 -26
- package/src/slate/utils/customHooks/useResize.js +0 -27
- package/src/slate/utils/embed.js +0 -18
- package/src/slate/utils/equation.js +0 -22
- package/src/slate/utils/index.jsx +0 -267
- package/src/slate/utils/link.js +0 -44
- package/src/slate/utils/p.js +0 -4
- package/src/slate/utils/table.js +0 -131
- package/src/vite-env.d.ts +0 -1
- package/tsconfig.json +0 -32
- package/tsconfig.node.json +0 -10
- package/vite.config.ts +0 -41
|
@@ -1,494 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
MenuList,
|
|
3
|
-
MenuItem,
|
|
4
|
-
Button,
|
|
5
|
-
ButtonGroup,
|
|
6
|
-
Grow,
|
|
7
|
-
Popper,
|
|
8
|
-
ClickAwayListener,
|
|
9
|
-
Paper,
|
|
10
|
-
Box,
|
|
11
|
-
Typography,
|
|
12
|
-
} from "@mui/material";
|
|
13
|
-
import Icon from "../common/icon";
|
|
14
|
-
import * as React from "react";
|
|
15
|
-
import { BaseEditor, Descendant } from "slate";
|
|
16
|
-
import { slateToHtml, SlateToDomConfig } from "slate-serializers";
|
|
17
|
-
import { ParentNode, TextNode } from "../serializers/types";
|
|
18
|
-
import { Element, Node, ChildNode } from "domhandler";
|
|
19
|
-
import { fontFamilyMap, sizeMap } from "../utils";
|
|
20
|
-
|
|
21
|
-
import "html-to-rtf/app/browser/bundle.js";
|
|
22
|
-
|
|
23
|
-
// @ts-ignore
|
|
24
|
-
// dynamic(() => import("html-to-rtf/app/browser/bundle.js"), { ssr: false });
|
|
25
|
-
|
|
26
|
-
export type PaperToolbarProps = {
|
|
27
|
-
value: Descendant[];
|
|
28
|
-
editor: BaseEditor;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
function download(filename: string, text: string, mime: string) {
|
|
32
|
-
let element = document.createElement("a");
|
|
33
|
-
element.setAttribute(
|
|
34
|
-
"href",
|
|
35
|
-
`data:${mime};charset=utf-8,` + encodeURIComponent(text)
|
|
36
|
-
);
|
|
37
|
-
element.setAttribute("download", filename);
|
|
38
|
-
|
|
39
|
-
element.style.display = "none";
|
|
40
|
-
document.body.appendChild(element);
|
|
41
|
-
|
|
42
|
-
element.click();
|
|
43
|
-
|
|
44
|
-
document.body.removeChild(element);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const PAGE_MARGIN_X_INCHES = 0.5;
|
|
48
|
-
const PAGE_MARGIN_Y_INCHES = 1;
|
|
49
|
-
const EXPORT_STYLES =
|
|
50
|
-
"font-family: Helvetica, Arial, sans serif; font-size: 16px;"; // When exporting the margins will be figured out by the PDF renderer, so we don't need to add them here
|
|
51
|
-
const DEFAULT_STYLES =
|
|
52
|
-
EXPORT_STYLES +
|
|
53
|
-
`padding: ${PAGE_MARGIN_X_INCHES}in ${PAGE_MARGIN_Y_INCHES}in; `; // I still want the padding when regularly exporting to HTML
|
|
54
|
-
|
|
55
|
-
// Typescript HATES this code, but it works B)
|
|
56
|
-
// Feel free to refactor it if you want, but I'm pretty sure the problem is that we are using the same "type" but from different libraries
|
|
57
|
-
// so typescript thinks they are different types even though they are the same (at keast for some of them)
|
|
58
|
-
// Default config + lists and colors and tweaks to links and blockquotes
|
|
59
|
-
export const slate_config: SlateToDomConfig = {
|
|
60
|
-
elementMap: {
|
|
61
|
-
p: "p",
|
|
62
|
-
paragraph: "p",
|
|
63
|
-
h1: "h1",
|
|
64
|
-
h2: "h2",
|
|
65
|
-
h3: "h3",
|
|
66
|
-
h4: "h4",
|
|
67
|
-
h5: "h5",
|
|
68
|
-
h6: "h6",
|
|
69
|
-
["table-row"]: "tr",
|
|
70
|
-
["table-cell"]: "td",
|
|
71
|
-
},
|
|
72
|
-
markMap: {
|
|
73
|
-
strikethrough: ["s"],
|
|
74
|
-
bold: ["strong"],
|
|
75
|
-
underline: ["u"],
|
|
76
|
-
italic: ["i"],
|
|
77
|
-
code: ["pre", "code"],
|
|
78
|
-
},
|
|
79
|
-
elementTransforms: {
|
|
80
|
-
// Need indentation in PDF export
|
|
81
|
-
//@ts-ignore
|
|
82
|
-
blockquote: ({ node, children = [] }) => {
|
|
83
|
-
//@ts-ignore
|
|
84
|
-
const p = [new Element("p", {}, children)];
|
|
85
|
-
return new Element("blockquote", {}, p);
|
|
86
|
-
},
|
|
87
|
-
//@ts-ignore
|
|
88
|
-
link: ({ node, children = [] }) => {
|
|
89
|
-
const attributes = {
|
|
90
|
-
target: "",
|
|
91
|
-
};
|
|
92
|
-
attributes.target = node.target;
|
|
93
|
-
return new Element(
|
|
94
|
-
"a",
|
|
95
|
-
{
|
|
96
|
-
href: node.href,
|
|
97
|
-
...attributes,
|
|
98
|
-
},
|
|
99
|
-
children as unknown as ChildNode[] // keep this change in merge
|
|
100
|
-
);
|
|
101
|
-
},
|
|
102
|
-
//@ts-ignore
|
|
103
|
-
unorderedList: ({ node, children = [] }) => {
|
|
104
|
-
const new_children = children.map(
|
|
105
|
-
//@ts-ignore
|
|
106
|
-
(child) => new Element("li", {}, [child])
|
|
107
|
-
);
|
|
108
|
-
return new Element("ul", {}, new_children);
|
|
109
|
-
},
|
|
110
|
-
//@ts-ignore
|
|
111
|
-
orderedList: ({ children = [] }) => {
|
|
112
|
-
const new_children = children.map(
|
|
113
|
-
//@ts-ignore
|
|
114
|
-
(child) => new Element("li", {}, [child])
|
|
115
|
-
);
|
|
116
|
-
return new Element("ol", {}, new_children);
|
|
117
|
-
},
|
|
118
|
-
//@ts-ignore
|
|
119
|
-
image: ({ node }) => {
|
|
120
|
-
const attributes = {
|
|
121
|
-
src: "",
|
|
122
|
-
};
|
|
123
|
-
attributes.src = node.url;
|
|
124
|
-
return new Element("img", attributes, []);
|
|
125
|
-
},
|
|
126
|
-
//@ts-ignore
|
|
127
|
-
alignCenter: ({ node, children = [] }) => {
|
|
128
|
-
const attributes = {
|
|
129
|
-
style: "",
|
|
130
|
-
};
|
|
131
|
-
attributes.style = `text-align: center; display: block;`;
|
|
132
|
-
//@ts-ignore
|
|
133
|
-
return new Element("div", attributes, children);
|
|
134
|
-
},
|
|
135
|
-
//@ts-ignore
|
|
136
|
-
alignRight: ({ node, children = [] }) => {
|
|
137
|
-
const attributes = {
|
|
138
|
-
style: "",
|
|
139
|
-
};
|
|
140
|
-
attributes.style = `text-align: right; display: block;`;
|
|
141
|
-
//@ts-ignore
|
|
142
|
-
return new Element("div", attributes, children);
|
|
143
|
-
},
|
|
144
|
-
//@ts-ignore
|
|
145
|
-
alignLeft: ({ node, children = [] }) => {
|
|
146
|
-
const attributes = {
|
|
147
|
-
style: "",
|
|
148
|
-
};
|
|
149
|
-
attributes.style = `text-align: left; display: block;`;
|
|
150
|
-
//@ts-ignore
|
|
151
|
-
return new Element("div", attributes, children);
|
|
152
|
-
},
|
|
153
|
-
//@ts-ignore
|
|
154
|
-
table: ({ node, children = [] }) => {
|
|
155
|
-
const attributes = {
|
|
156
|
-
border: "",
|
|
157
|
-
};
|
|
158
|
-
attributes.border = `1`;
|
|
159
|
-
//@ts-ignore
|
|
160
|
-
return new Element("table", attributes, children);
|
|
161
|
-
},
|
|
162
|
-
},
|
|
163
|
-
markTransforms: {
|
|
164
|
-
//@ts-ignore
|
|
165
|
-
color: ({ node }) => {
|
|
166
|
-
const attributes = {
|
|
167
|
-
style: "",
|
|
168
|
-
};
|
|
169
|
-
attributes.style = `color: ${node.color}`;
|
|
170
|
-
return new Element("span", attributes, node.children);
|
|
171
|
-
},
|
|
172
|
-
//@ts-ignore
|
|
173
|
-
bgColor: ({ node }) => {
|
|
174
|
-
const attributes = {
|
|
175
|
-
style: "",
|
|
176
|
-
};
|
|
177
|
-
attributes.style = `background-color: ${node.bgColor}`;
|
|
178
|
-
return new Element("span", attributes, node.children);
|
|
179
|
-
},
|
|
180
|
-
//@ts-ignore
|
|
181
|
-
fontFamily: ({ node }) => {
|
|
182
|
-
const attributes = {
|
|
183
|
-
style: "",
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
// I don't know if using this random import is better or worse than just using an if statement for each font
|
|
187
|
-
// on one hand it means one less place to update duplicate data (DRY)
|
|
188
|
-
// on the other hand it's a random import to a random index.jsx file that I don't know the purpose of and having dependencies to random unrelated files is bad
|
|
189
|
-
let typedMap = fontFamilyMap as Record<string, string>;
|
|
190
|
-
|
|
191
|
-
attributes.style = `font-family: ${typedMap[node.fontFamily]}`;
|
|
192
|
-
|
|
193
|
-
return new Element("span", attributes, node.children);
|
|
194
|
-
},
|
|
195
|
-
//@ts-ignore
|
|
196
|
-
fontSize: ({ node }) => {
|
|
197
|
-
const attributes = {
|
|
198
|
-
style: "",
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
// This is the same as above with the fontFamilyMap
|
|
202
|
-
let typedMap = sizeMap as Record<string, string>;
|
|
203
|
-
attributes.style = `font-size: ${typedMap[node.fontSize]}`;
|
|
204
|
-
return new Element("span", attributes, node.children);
|
|
205
|
-
},
|
|
206
|
-
//@ts-ignore
|
|
207
|
-
superscript: ({ node }) => {
|
|
208
|
-
const attributes = {
|
|
209
|
-
style: "",
|
|
210
|
-
};
|
|
211
|
-
attributes.style = `vertical-align: super; font-size: smaller;`;
|
|
212
|
-
return new Element("span", attributes, node.children);
|
|
213
|
-
},
|
|
214
|
-
//@ts-ignore
|
|
215
|
-
subscript: ({ node }) => {
|
|
216
|
-
const attributes = {
|
|
217
|
-
style: "",
|
|
218
|
-
};
|
|
219
|
-
attributes.style = `vertical-align: sub; font-size: smaller;`;
|
|
220
|
-
return new Element("span", attributes, node.children);
|
|
221
|
-
},
|
|
222
|
-
},
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
// Made this wrapper function to make it easier to change the default styles (DRY code!)
|
|
226
|
-
const parseToHTML = (value: Descendant[], styles?: string) => {
|
|
227
|
-
if (styles == undefined) {
|
|
228
|
-
styles = DEFAULT_STYLES;
|
|
229
|
-
}
|
|
230
|
-
return (
|
|
231
|
-
`<div style='${styles}'>` + slateToHtml(value, slate_config) + "</div>"
|
|
232
|
-
);
|
|
233
|
-
};
|
|
234
|
-
|
|
235
|
-
// 🐢 TODO: Figure out how to add submenus, and then add them for the "Export as" option
|
|
236
|
-
const menuOptions = {
|
|
237
|
-
file: {
|
|
238
|
-
"Log html to console and clipboard": (value: Descendant[]) => {
|
|
239
|
-
const html = parseToHTML(value);
|
|
240
|
-
console.log(html);
|
|
241
|
-
navigator.clipboard.writeText(html);
|
|
242
|
-
},
|
|
243
|
-
Divider: "line",
|
|
244
|
-
"Download as .html file": (value: Descendant[]) => {
|
|
245
|
-
const html = parseToHTML(value);
|
|
246
|
-
download("markup.html", html, "text/html");
|
|
247
|
-
},
|
|
248
|
-
// "Download as .md file": (editor: TextNode | ParentNode) => {
|
|
249
|
-
// const markdown = serializeMD(editor)
|
|
250
|
-
// download("markdown.md", markdown, "text/plain")
|
|
251
|
-
// console.log(markdown)
|
|
252
|
-
// },
|
|
253
|
-
"Download as .rtf file": (value: Descendant[]) => {
|
|
254
|
-
// @ts-ignore
|
|
255
|
-
const rtf = window.htmlToRtf(parseToHTML(value));
|
|
256
|
-
download("rich.rtf", rtf, "text/rtf");
|
|
257
|
-
console.log(rtf);
|
|
258
|
-
},
|
|
259
|
-
// Untested because I can't test in Storybook due to CORS, and I do not know where in the app to go to test this 😎
|
|
260
|
-
"Download as .pdf file": (value: Descendant[]) => {
|
|
261
|
-
const htmlContent = parseToHTML(value, EXPORT_STYLES);
|
|
262
|
-
|
|
263
|
-
const response = fetch("http://localhost:3000/api/editor/export-pdf", {
|
|
264
|
-
method: "POST",
|
|
265
|
-
headers: {
|
|
266
|
-
"Content-Type": "application/json",
|
|
267
|
-
},
|
|
268
|
-
body: JSON.stringify({ htmlContent }),
|
|
269
|
-
})
|
|
270
|
-
.then((response) => response.blob())
|
|
271
|
-
.then((blob) => {
|
|
272
|
-
const blobUrl = URL.createObjectURL(blob);
|
|
273
|
-
download("document.pdf", blobUrl, "application/pdf");
|
|
274
|
-
|
|
275
|
-
// Make sure to release the URL object when you're done with it
|
|
276
|
-
URL.revokeObjectURL(blobUrl);
|
|
277
|
-
});
|
|
278
|
-
},
|
|
279
|
-
} as Record<string, Function | string>,
|
|
280
|
-
edit: {
|
|
281
|
-
Undo: () => {},
|
|
282
|
-
Redo: () => {},
|
|
283
|
-
"The names of entries that return line dont matter": "line",
|
|
284
|
-
Cut: () => {},
|
|
285
|
-
Copy: () => {},
|
|
286
|
-
Paste: () => {},
|
|
287
|
-
"These entries are the dividing lines between sections": "line",
|
|
288
|
-
"Select All": () => {},
|
|
289
|
-
Delete: () => {},
|
|
290
|
-
"Maybe this is a bad system, but it's simple to edit on the fly. Once groups are more settled we can hard code them":
|
|
291
|
-
"line",
|
|
292
|
-
"Find and Replace": () => {},
|
|
293
|
-
} as Record<string, Function | string>,
|
|
294
|
-
view: {
|
|
295
|
-
"Toggle Fullscreen": () => {},
|
|
296
|
-
"Toggle Paper View": () => {},
|
|
297
|
-
} as Record<string, Function | string>,
|
|
298
|
-
format: {
|
|
299
|
-
// Having menu options for all of these things we already have buttons for feels redundant, but that's how google docs does it. We can change it later if we want, especially since there's no code for these yet
|
|
300
|
-
"Paper Margins": () => {},
|
|
301
|
-
"Paper Dimensions": () => {},
|
|
302
|
-
Divider1: "line",
|
|
303
|
-
Bold: () => {},
|
|
304
|
-
Italic: () => {},
|
|
305
|
-
Underline: () => {},
|
|
306
|
-
Strikethrough: () => {},
|
|
307
|
-
Divider2: "line",
|
|
308
|
-
Superscript: () => {},
|
|
309
|
-
Subscript: () => {},
|
|
310
|
-
Divider3: "line",
|
|
311
|
-
"Font Family": () => {},
|
|
312
|
-
"Font Size": () => {},
|
|
313
|
-
Divider4: "line",
|
|
314
|
-
"Text Color": () => {},
|
|
315
|
-
"Background Color": () => {},
|
|
316
|
-
Divider5: "line",
|
|
317
|
-
"Align Left": () => {},
|
|
318
|
-
"Align Center": () => {},
|
|
319
|
-
"Align Right": () => {},
|
|
320
|
-
} as Record<string, Function | string>,
|
|
321
|
-
insert: {
|
|
322
|
-
Link: () => {},
|
|
323
|
-
Table: () => {},
|
|
324
|
-
Equation: () => {},
|
|
325
|
-
Image: () => {},
|
|
326
|
-
Code: () => {},
|
|
327
|
-
} as Record<string, Function | string>,
|
|
328
|
-
help: {
|
|
329
|
-
"Keyboard Shortcuts": () => {},
|
|
330
|
-
Divider: "line",
|
|
331
|
-
About: () => {},
|
|
332
|
-
Divider1: "line",
|
|
333
|
-
"Ask Keith": () => {},
|
|
334
|
-
} as Record<string, Function | string>,
|
|
335
|
-
} as Record<string, Record<string, Function | string>>;
|
|
336
|
-
|
|
337
|
-
//These are the options that you shouldn't be able to access if you haven't selected anything
|
|
338
|
-
const selectDependentOptions = ["Cut", "Copy", "Delete"];
|
|
339
|
-
|
|
340
|
-
export function PaperToolbar({ editor, value }: PaperToolbarProps) {
|
|
341
|
-
const menuOpenState = {
|
|
342
|
-
file: false,
|
|
343
|
-
edit: false,
|
|
344
|
-
view: false,
|
|
345
|
-
format: false,
|
|
346
|
-
insert: false,
|
|
347
|
-
help: false,
|
|
348
|
-
} as Record<string, boolean>;
|
|
349
|
-
|
|
350
|
-
const [open, setOpen] = React.useState(menuOpenState);
|
|
351
|
-
const anchorRef = React.useRef<HTMLDivElement>(null);
|
|
352
|
-
const [selectedIndex, setSelectedIndex] = React.useState(1);
|
|
353
|
-
|
|
354
|
-
// const handleClick = () => {
|
|
355
|
-
// console.info(`You clicked ${Object.keys(fileOptions)[selectedIndex]}`)
|
|
356
|
-
// fileOptions[Object.keys(fileOptions)[selectedIndex]](editor)
|
|
357
|
-
// }
|
|
358
|
-
|
|
359
|
-
const handleMenuItemClick = (
|
|
360
|
-
event: React.MouseEvent<HTMLLIElement, MouseEvent>,
|
|
361
|
-
menuOptions: Record<string, Function>,
|
|
362
|
-
index: number
|
|
363
|
-
) => {
|
|
364
|
-
// setSelectedIndex(index)
|
|
365
|
-
const clickedOption = Object.keys(menuOptions)[index];
|
|
366
|
-
console.info(`You clicked ${clickedOption}`);
|
|
367
|
-
console.log(value);
|
|
368
|
-
menuOptions[clickedOption](value);
|
|
369
|
-
setOpen((prevOpen) => ({ ...prevOpen, file: false }));
|
|
370
|
-
};
|
|
371
|
-
|
|
372
|
-
const handleToggle = (menuItem: string) => {
|
|
373
|
-
console.log("You clicked " + menuItem);
|
|
374
|
-
console.log(
|
|
375
|
-
editor.selection?.anchor.offset == editor.selection?.focus.offset
|
|
376
|
-
);
|
|
377
|
-
console.log(editor.selection?.anchor.offset);
|
|
378
|
-
console.log(editor.selection?.focus.offset);
|
|
379
|
-
setOpen((prevOpen) => ({
|
|
380
|
-
...prevOpen,
|
|
381
|
-
[menuItem]: !prevOpen[menuItem] as boolean,
|
|
382
|
-
}));
|
|
383
|
-
};
|
|
384
|
-
|
|
385
|
-
const handleClose = (event: Event, menuItem: string) => {
|
|
386
|
-
if (
|
|
387
|
-
anchorRef.current &&
|
|
388
|
-
anchorRef.current
|
|
389
|
-
.querySelector(`.${menuItem}`)
|
|
390
|
-
?.contains(event.target as HTMLElement)
|
|
391
|
-
) {
|
|
392
|
-
return;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
console.log("You closed " + menuItem);
|
|
396
|
-
setOpen((prevOpen) => ({ ...prevOpen, [menuItem]: false }));
|
|
397
|
-
};
|
|
398
|
-
|
|
399
|
-
return (
|
|
400
|
-
<>
|
|
401
|
-
<ButtonGroup variant="outlined" ref={anchorRef} aria-label="split button">
|
|
402
|
-
{Object.keys(menuOpenState).map((key) => {
|
|
403
|
-
return (
|
|
404
|
-
<Button
|
|
405
|
-
size="small"
|
|
406
|
-
aria-controls={open[key] ? "split-button-menu" : undefined}
|
|
407
|
-
aria-expanded={open[key] ? "true" : undefined}
|
|
408
|
-
aria-haspopup="menu"
|
|
409
|
-
onClick={() => handleToggle(key)}
|
|
410
|
-
sx={{ pr: 1 }}
|
|
411
|
-
className={key}
|
|
412
|
-
key={key}
|
|
413
|
-
>
|
|
414
|
-
{key.charAt(0).toUpperCase() + key.slice(1)}
|
|
415
|
-
<Icon icon="arrowDown" />
|
|
416
|
-
</Button>
|
|
417
|
-
);
|
|
418
|
-
})}
|
|
419
|
-
</ButtonGroup>
|
|
420
|
-
{Object.keys(menuOpenState).map((key) => {
|
|
421
|
-
return (
|
|
422
|
-
<Popper
|
|
423
|
-
sx={{
|
|
424
|
-
zIndex: 1,
|
|
425
|
-
left: 0,
|
|
426
|
-
}}
|
|
427
|
-
open={open[key]}
|
|
428
|
-
anchorEl={anchorRef.current?.querySelector(`.${key}`)}
|
|
429
|
-
role={undefined} //What does this do?
|
|
430
|
-
transition
|
|
431
|
-
disablePortal
|
|
432
|
-
placement="bottom-start"
|
|
433
|
-
key={key}
|
|
434
|
-
>
|
|
435
|
-
{({ TransitionProps, placement }) => (
|
|
436
|
-
<Grow
|
|
437
|
-
{...TransitionProps}
|
|
438
|
-
style={{
|
|
439
|
-
transformOrigin: "center bottom",
|
|
440
|
-
}}
|
|
441
|
-
>
|
|
442
|
-
<Paper>
|
|
443
|
-
<ClickAwayListener
|
|
444
|
-
onClickAway={(event) => handleClose(event, key)}
|
|
445
|
-
>
|
|
446
|
-
<MenuList id="split-button-menu" autoFocusItem>
|
|
447
|
-
{Object.keys(menuOptions[key]).map((option, index) => {
|
|
448
|
-
if (menuOptions[key][option] == "line") {
|
|
449
|
-
// 🐢 -(The hard coded color here should probably come from a theme or something like that?)
|
|
450
|
-
return (
|
|
451
|
-
<Box
|
|
452
|
-
key={option}
|
|
453
|
-
sx={{
|
|
454
|
-
mx: 0,
|
|
455
|
-
my: 0.5,
|
|
456
|
-
borderBottom: "solid 2px #f2f2f2",
|
|
457
|
-
}}
|
|
458
|
-
/>
|
|
459
|
-
);
|
|
460
|
-
}
|
|
461
|
-
return (
|
|
462
|
-
<MenuItem
|
|
463
|
-
key={option}
|
|
464
|
-
disabled={
|
|
465
|
-
selectDependentOptions.includes(option) &&
|
|
466
|
-
editor.selection?.anchor.offset ==
|
|
467
|
-
editor.selection?.focus.offset
|
|
468
|
-
? true
|
|
469
|
-
: false
|
|
470
|
-
}
|
|
471
|
-
// selected={index === selectedIndex}
|
|
472
|
-
onClick={(event) =>
|
|
473
|
-
handleMenuItemClick(
|
|
474
|
-
event,
|
|
475
|
-
menuOptions[key] as Record<string, Function>,
|
|
476
|
-
index
|
|
477
|
-
)
|
|
478
|
-
}
|
|
479
|
-
>
|
|
480
|
-
{option}
|
|
481
|
-
</MenuItem>
|
|
482
|
-
);
|
|
483
|
-
})}
|
|
484
|
-
</MenuList>
|
|
485
|
-
</ClickAwayListener>
|
|
486
|
-
</Paper>
|
|
487
|
-
</Grow>
|
|
488
|
-
)}
|
|
489
|
-
</Popper>
|
|
490
|
-
);
|
|
491
|
-
})}
|
|
492
|
-
</>
|
|
493
|
-
);
|
|
494
|
-
}
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
|
|
3
|
-
const CTRL = "Ctrl";
|
|
4
|
-
const CTRL_SHIFT = "Ctrl+Shift";
|
|
5
|
-
const ALT_SHIFT = "Alt+Shift";
|
|
6
|
-
|
|
7
|
-
const CTRL_USED_BY_EDITOR = [
|
|
8
|
-
"b",
|
|
9
|
-
"i",
|
|
10
|
-
"u",
|
|
11
|
-
".",
|
|
12
|
-
",",
|
|
13
|
-
"1",
|
|
14
|
-
"2",
|
|
15
|
-
"3",
|
|
16
|
-
"k",
|
|
17
|
-
"7",
|
|
18
|
-
"8",
|
|
19
|
-
];
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Modelled directly after Google Docs' shortcut keys.
|
|
23
|
-
* Custom shortcuts unique to us: Headings.
|
|
24
|
-
* @returns [key, combo]
|
|
25
|
-
*/
|
|
26
|
-
export function getShortcutKey(format: string, toPrint?: boolean) {
|
|
27
|
-
let combo = CTRL;
|
|
28
|
-
if (format === "bold") return ["b", combo];
|
|
29
|
-
if (format === "italic") return ["i", combo];
|
|
30
|
-
if (format === "underline") return ["u", combo];
|
|
31
|
-
if (format === "superscript") return [".", combo];
|
|
32
|
-
if (format === "subscript") return [",", combo];
|
|
33
|
-
if (format === "h1") return ["1", combo];
|
|
34
|
-
if (format === "h2") return ["2", combo];
|
|
35
|
-
if (format === "h3") return ["3", combo];
|
|
36
|
-
if (format === "link") return ["k", combo];
|
|
37
|
-
combo = CTRL_SHIFT;
|
|
38
|
-
if (format === "orderedList") return toPrint ? ["7", combo] : ["*", combo];
|
|
39
|
-
if (format === "unorderedList") return toPrint ? ["8", combo] : ["&", combo];
|
|
40
|
-
if (format === "blockquote") return toPrint ? [".", combo] : [">", combo];
|
|
41
|
-
combo = ALT_SHIFT;
|
|
42
|
-
if (format === "strikethrough") return toPrint ? ["5", combo] : ["%", combo];
|
|
43
|
-
return ["", ""];
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export function useShortcut(format: string, action: Function) {
|
|
47
|
-
React.useEffect(() => {
|
|
48
|
-
const [key, combo] = getShortcutKey(format);
|
|
49
|
-
if (key === "") return;
|
|
50
|
-
if ((window as any).keyMappings === undefined) {
|
|
51
|
-
console.log("create keymappings");
|
|
52
|
-
(window as any).keyMappings = {
|
|
53
|
-
[CTRL]: {} as Record<string, Function>,
|
|
54
|
-
[ALT_SHIFT]: {} as Record<string, Function>,
|
|
55
|
-
[CTRL_SHIFT]: {} as Record<string, Function>,
|
|
56
|
-
};
|
|
57
|
-
window.onkeydown = (e) => {
|
|
58
|
-
if (
|
|
59
|
-
(e.ctrlKey && CTRL_USED_BY_EDITOR.includes(e.key)) ||
|
|
60
|
-
(e.altKey && e.shiftKey)
|
|
61
|
-
) {
|
|
62
|
-
e.preventDefault();
|
|
63
|
-
const combo = e.ctrlKey
|
|
64
|
-
? e.shiftKey
|
|
65
|
-
? CTRL_SHIFT
|
|
66
|
-
: CTRL
|
|
67
|
-
: ALT_SHIFT;
|
|
68
|
-
const action = (window as any).keyMappings[combo][e.key];
|
|
69
|
-
if (action) action();
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
if (!(window as any).keyMappings[combo][key]) {
|
|
74
|
-
(window as any).keyMappings[combo][key] = action;
|
|
75
|
-
}
|
|
76
|
-
}, [window]);
|
|
77
|
-
}
|