@monolith-forensics/monolith-ui 1.9.1-dev.1 → 1.9.1-dev.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/dist/DropDownMenu/components/MenuItemList.js +32 -12
- package/dist/DropDownMenu/components/StyledInnerItemContainer.js +1 -0
- package/dist/MonolithUIProvider/MonolithUIProvider.d.ts +23 -0
- package/dist/RichTextEditor/Components/BubbleMenu.d.ts +8 -8
- package/dist/RichTextEditor/Components/BubbleMenu.js +195 -93
- package/dist/RichTextEditor/Components/CodeBlockBaseButton.d.ts +18 -0
- package/dist/RichTextEditor/Components/CodeBlockBaseButton.js +6 -0
- package/dist/RichTextEditor/Components/CodeBlockCopyButton.d.ts +9 -0
- package/dist/RichTextEditor/Components/CodeBlockCopyButton.js +42 -0
- package/dist/RichTextEditor/Components/CodeBlockFormatButton.d.ts +10 -0
- package/dist/RichTextEditor/Components/CodeBlockFormatButton.js +60 -0
- package/dist/RichTextEditor/Components/CodeBlockLanguageSelect.d.ts +9 -0
- package/dist/RichTextEditor/Components/CodeBlockLanguageSelect.js +30 -0
- package/dist/RichTextEditor/Components/CodeBlockNodeView.d.ts +3 -0
- package/dist/RichTextEditor/Components/CodeBlockNodeView.js +28 -0
- package/dist/RichTextEditor/Components/CodeBlockWrapButton.d.ts +10 -0
- package/dist/RichTextEditor/Components/CodeBlockWrapButton.js +17 -0
- package/dist/RichTextEditor/Components/LinkEditor.d.ts +8 -0
- package/dist/RichTextEditor/Components/LinkEditor.js +94 -0
- package/dist/RichTextEditor/Enums/Controls.d.ts +5 -1
- package/dist/RichTextEditor/Enums/Controls.js +4 -0
- package/dist/RichTextEditor/Enums/Extensions.d.ts +4 -0
- package/dist/RichTextEditor/Enums/Extensions.js +4 -0
- package/dist/RichTextEditor/Enums/HighlightColors.d.ts +9 -0
- package/dist/RichTextEditor/Enums/HighlightColors.js +10 -0
- package/dist/RichTextEditor/Enums/SlashCommands.d.ts +2 -0
- package/dist/RichTextEditor/Enums/SlashCommands.js +2 -0
- package/dist/RichTextEditor/Extensions/SlashCommandList.js +0 -1
- package/dist/RichTextEditor/Extensions/getSlashCommand.js +25 -1
- package/dist/RichTextEditor/Extensions/getTiptapExtensions.d.ts +10 -2
- package/dist/RichTextEditor/Extensions/getTiptapExtensions.js +158 -31
- package/dist/RichTextEditor/Plugins/ImageActionsPlugin.js +6 -109
- package/dist/RichTextEditor/Plugins/UploadImagesPlugin.js +1 -0
- package/dist/RichTextEditor/RichTextEditor.d.ts +4 -2
- package/dist/RichTextEditor/RichTextEditor.js +323 -13
- package/dist/RichTextEditor/Toolbar/Control.d.ts +6 -2
- package/dist/RichTextEditor/Toolbar/Control.js +13 -6
- package/dist/RichTextEditor/Toolbar/Controls.d.ts +2 -0
- package/dist/RichTextEditor/Toolbar/Controls.js +14 -0
- package/dist/RichTextEditor/Toolbar/ControlsGroup.js +1 -0
- package/dist/RichTextEditor/Toolbar/Toolbar.js +61 -9
- package/dist/RichTextEditor/Utils/codeBlockUtils.d.ts +20 -0
- package/dist/RichTextEditor/Utils/codeBlockUtils.js +137 -0
- package/dist/RichTextEditor/Utils/codeUtils.d.ts +3 -0
- package/dist/RichTextEditor/Utils/codeUtils.js +12 -0
- package/dist/RichTextEditor/Utils/linkUtils.d.ts +19 -0
- package/dist/RichTextEditor/Utils/linkUtils.js +57 -0
- package/dist/theme/variants.js +46 -0
- package/package.json +8 -1
- package/dist/RichTextEditor/Extensions/BubbleMenuExtension.d.ts +0 -7
- package/dist/RichTextEditor/Extensions/BubbleMenuExtension.js +0 -157
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import prettier from "prettier/standalone";
|
|
11
|
+
import * as prettierBabelPlugin from "prettier/plugins/babel";
|
|
12
|
+
import * as prettierEstreePlugin from "prettier/plugins/estree";
|
|
13
|
+
import * as prettierHtmlPlugin from "prettier/plugins/html";
|
|
14
|
+
import * as prettierMarkdownPlugin from "prettier/plugins/markdown";
|
|
15
|
+
import * as prettierPostcssPlugin from "prettier/plugins/postcss";
|
|
16
|
+
import * as prettierTypescriptPlugin from "prettier/plugins/typescript";
|
|
17
|
+
import * as prettierYamlPlugin from "prettier/plugins/yaml";
|
|
18
|
+
export const DEFAULT_CODE_BLOCK_LANGUAGE = "plaintext";
|
|
19
|
+
export const CODE_BLOCK_LANGUAGES = [
|
|
20
|
+
{ label: "Plain Text", value: DEFAULT_CODE_BLOCK_LANGUAGE },
|
|
21
|
+
{ label: "JavaScript", value: "javascript" },
|
|
22
|
+
{ label: "TypeScript", value: "typescript" },
|
|
23
|
+
{ label: "JSX", value: "jsx" },
|
|
24
|
+
{ label: "TSX", value: "tsx" },
|
|
25
|
+
{ label: "HTML", value: "html" },
|
|
26
|
+
{ label: "CSS", value: "css" },
|
|
27
|
+
{ label: "JSON", value: "json" },
|
|
28
|
+
{ label: "Markdown", value: "markdown" },
|
|
29
|
+
{ label: "Bash", value: "bash" },
|
|
30
|
+
{ label: "Python", value: "python" },
|
|
31
|
+
{ label: "SQL", value: "sql" },
|
|
32
|
+
{ label: "YAML", value: "yaml" },
|
|
33
|
+
{ label: "XML", value: "xml" },
|
|
34
|
+
];
|
|
35
|
+
const PRETTIER_PARSERS = {
|
|
36
|
+
javascript: "babel",
|
|
37
|
+
jsx: "babel",
|
|
38
|
+
typescript: "typescript",
|
|
39
|
+
tsx: "typescript",
|
|
40
|
+
html: "html",
|
|
41
|
+
css: "css",
|
|
42
|
+
json: "json",
|
|
43
|
+
markdown: "markdown",
|
|
44
|
+
yaml: "yaml",
|
|
45
|
+
};
|
|
46
|
+
const PRETTIER_PLUGINS = [
|
|
47
|
+
prettierBabelPlugin,
|
|
48
|
+
prettierEstreePlugin,
|
|
49
|
+
prettierHtmlPlugin,
|
|
50
|
+
prettierMarkdownPlugin,
|
|
51
|
+
prettierPostcssPlugin,
|
|
52
|
+
prettierTypescriptPlugin,
|
|
53
|
+
prettierYamlPlugin,
|
|
54
|
+
];
|
|
55
|
+
export const getCodeBlockLanguage = (editor) => {
|
|
56
|
+
var _a;
|
|
57
|
+
return (((_a = editor === null || editor === void 0 ? void 0 : editor.getAttributes("codeBlock")) === null || _a === void 0 ? void 0 : _a.language) ||
|
|
58
|
+
DEFAULT_CODE_BLOCK_LANGUAGE);
|
|
59
|
+
};
|
|
60
|
+
export const getCodeBlockLanguageOption = (language) => {
|
|
61
|
+
return (CODE_BLOCK_LANGUAGES.find((item) => item.value === language) ||
|
|
62
|
+
CODE_BLOCK_LANGUAGES[0]);
|
|
63
|
+
};
|
|
64
|
+
export const canFormatCodeBlockLanguage = (language) => {
|
|
65
|
+
return Boolean(language && PRETTIER_PARSERS[language]);
|
|
66
|
+
};
|
|
67
|
+
export const formatCodeBlockText = (text, language) => __awaiter(void 0, void 0, void 0, function* () {
|
|
68
|
+
const parser = language ? PRETTIER_PARSERS[language] : undefined;
|
|
69
|
+
if (!parser) {
|
|
70
|
+
throw new Error("Code formatting is not supported for this language.");
|
|
71
|
+
}
|
|
72
|
+
const formatted = yield prettier.format(text, {
|
|
73
|
+
parser,
|
|
74
|
+
plugins: PRETTIER_PLUGINS,
|
|
75
|
+
});
|
|
76
|
+
return formatted.replace(/\n$/, "");
|
|
77
|
+
});
|
|
78
|
+
export const hasSyntaxHighlightedCodeBlock = (editor) => {
|
|
79
|
+
return Boolean(editor === null || editor === void 0 ? void 0 : editor.extensionManager.extensions.find((extension) => { var _a; return extension.name === "codeBlock" && ((_a = extension.options) === null || _a === void 0 ? void 0 : _a.lowlight); }));
|
|
80
|
+
};
|
|
81
|
+
export const setCodeBlockLanguage = (editor, language) => {
|
|
82
|
+
if (!(editor === null || editor === void 0 ? void 0 : editor.isActive("codeBlock")))
|
|
83
|
+
return;
|
|
84
|
+
editor.chain().focus().updateAttributes("codeBlock", { language }).run();
|
|
85
|
+
};
|
|
86
|
+
export const getCodeBlockWrap = (editor) => {
|
|
87
|
+
var _a;
|
|
88
|
+
return Boolean((_a = editor === null || editor === void 0 ? void 0 : editor.getAttributes("codeBlock")) === null || _a === void 0 ? void 0 : _a.wrap);
|
|
89
|
+
};
|
|
90
|
+
export const toggleCodeBlockWrap = (editor) => {
|
|
91
|
+
if (!(editor === null || editor === void 0 ? void 0 : editor.isActive("codeBlock")))
|
|
92
|
+
return;
|
|
93
|
+
editor
|
|
94
|
+
.chain()
|
|
95
|
+
.focus()
|
|
96
|
+
.updateAttributes("codeBlock", { wrap: !getCodeBlockWrap(editor) })
|
|
97
|
+
.run();
|
|
98
|
+
};
|
|
99
|
+
export const toggleCodeBlock = (editor) => {
|
|
100
|
+
if (!editor)
|
|
101
|
+
return;
|
|
102
|
+
editor
|
|
103
|
+
.chain()
|
|
104
|
+
.focus()
|
|
105
|
+
.toggleCodeBlock({ language: DEFAULT_CODE_BLOCK_LANGUAGE })
|
|
106
|
+
.run();
|
|
107
|
+
};
|
|
108
|
+
export const getActiveCodeBlockText = (editor) => {
|
|
109
|
+
if (!(editor === null || editor === void 0 ? void 0 : editor.isActive("codeBlock")))
|
|
110
|
+
return "";
|
|
111
|
+
return editor.state.selection.$from.parent.textContent;
|
|
112
|
+
};
|
|
113
|
+
export const replaceCodeBlockContent = (editor, from, to, text) => {
|
|
114
|
+
return editor.commands.command(({ state, tr }) => {
|
|
115
|
+
if (text.length) {
|
|
116
|
+
tr.replaceWith(from, to, state.schema.text(text));
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
tr.delete(from, to);
|
|
120
|
+
}
|
|
121
|
+
return true;
|
|
122
|
+
});
|
|
123
|
+
};
|
|
124
|
+
export const formatActiveCodeBlock = (editor) => __awaiter(void 0, void 0, void 0, function* () {
|
|
125
|
+
if (!(editor === null || editor === void 0 ? void 0 : editor.isActive("codeBlock")))
|
|
126
|
+
return;
|
|
127
|
+
const language = getCodeBlockLanguage(editor);
|
|
128
|
+
const formatted = yield formatCodeBlockText(getActiveCodeBlockText(editor), language);
|
|
129
|
+
replaceCodeBlockContent(editor, editor.state.selection.$from.start(), editor.state.selection.$from.end(), formatted);
|
|
130
|
+
});
|
|
131
|
+
export const copyCodeBlockText = (text) => __awaiter(void 0, void 0, void 0, function* () {
|
|
132
|
+
var _a;
|
|
133
|
+
if (!((_a = navigator.clipboard) === null || _a === void 0 ? void 0 : _a.writeText)) {
|
|
134
|
+
throw new Error("Clipboard copying is not supported by this browser.");
|
|
135
|
+
}
|
|
136
|
+
yield navigator.clipboard.writeText(text);
|
|
137
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const hasInlineCode = (editor) => {
|
|
2
|
+
return Boolean(editor === null || editor === void 0 ? void 0 : editor.extensionManager.extensions.find((extension) => extension.name === "code"));
|
|
3
|
+
};
|
|
4
|
+
export const toggleInlineCode = (editor) => {
|
|
5
|
+
if (!editor || !hasInlineCode(editor))
|
|
6
|
+
return;
|
|
7
|
+
if (editor.isActive("code")) {
|
|
8
|
+
editor.chain().focus().unsetCode().run();
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
editor.chain().focus().unsetAllMarks().setCode().run();
|
|
12
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { EditorState } from "@tiptap/pm/state";
|
|
2
|
+
export declare const LINK_TARGET = "_blank";
|
|
3
|
+
export declare const LINK_REL = "noopener noreferrer nofollow";
|
|
4
|
+
export type NormalizedLink = {
|
|
5
|
+
href: string;
|
|
6
|
+
isValid: boolean;
|
|
7
|
+
};
|
|
8
|
+
export declare const normalizeLinkUrl: (value?: string | null) => NormalizedLink;
|
|
9
|
+
export declare const getLinkAttributes: (href: string) => {
|
|
10
|
+
href: string;
|
|
11
|
+
target: string;
|
|
12
|
+
rel: string;
|
|
13
|
+
class: string;
|
|
14
|
+
};
|
|
15
|
+
export declare const openLink: (href?: string | null) => void;
|
|
16
|
+
export declare const getLinkRangeAtPosition: (state: EditorState, pos: number) => void | import("@tiptap/core").Range;
|
|
17
|
+
export declare const getLinkAttributesAtPosition: (state: EditorState, pos: number) => {
|
|
18
|
+
href?: string;
|
|
19
|
+
} | undefined;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { getMarkRange } from "@tiptap/core";
|
|
2
|
+
export const LINK_TARGET = "_blank";
|
|
3
|
+
export const LINK_REL = "noopener noreferrer nofollow";
|
|
4
|
+
export const normalizeLinkUrl = (value) => {
|
|
5
|
+
const trimmedValue = (value === null || value === void 0 ? void 0 : value.trim()) || "";
|
|
6
|
+
if (!trimmedValue) {
|
|
7
|
+
return { href: "", isValid: false };
|
|
8
|
+
}
|
|
9
|
+
const href = /^[a-z][a-z0-9+.-]*:/i.test(trimmedValue)
|
|
10
|
+
? trimmedValue
|
|
11
|
+
: `https://${trimmedValue}`;
|
|
12
|
+
try {
|
|
13
|
+
const url = new URL(href);
|
|
14
|
+
const isValid = url.protocol === "http:" || url.protocol === "https:";
|
|
15
|
+
return {
|
|
16
|
+
href,
|
|
17
|
+
isValid,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
catch (_a) {
|
|
21
|
+
return {
|
|
22
|
+
href,
|
|
23
|
+
isValid: false,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
export const getLinkAttributes = (href) => ({
|
|
28
|
+
href,
|
|
29
|
+
target: LINK_TARGET,
|
|
30
|
+
rel: LINK_REL,
|
|
31
|
+
class: "editor-link",
|
|
32
|
+
});
|
|
33
|
+
export const openLink = (href) => {
|
|
34
|
+
if (!href)
|
|
35
|
+
return;
|
|
36
|
+
const normalizedLink = normalizeLinkUrl(href);
|
|
37
|
+
if (!normalizedLink.isValid)
|
|
38
|
+
return;
|
|
39
|
+
window.open(normalizedLink.href, "_blank", "noopener,noreferrer");
|
|
40
|
+
};
|
|
41
|
+
export const getLinkRangeAtPosition = (state, pos) => {
|
|
42
|
+
const linkMark = state.schema.marks.link;
|
|
43
|
+
if (!linkMark)
|
|
44
|
+
return;
|
|
45
|
+
return getMarkRange(state.doc.resolve(pos), linkMark);
|
|
46
|
+
};
|
|
47
|
+
export const getLinkAttributesAtPosition = (state, pos) => {
|
|
48
|
+
const linkMark = state.schema.marks.link;
|
|
49
|
+
if (!linkMark)
|
|
50
|
+
return;
|
|
51
|
+
const $pos = state.doc.resolve(pos);
|
|
52
|
+
const nextNode = $pos.parent.childAfter($pos.parentOffset).node;
|
|
53
|
+
const previousNode = $pos.parent.childBefore($pos.parentOffset).node;
|
|
54
|
+
const mark = (nextNode === null || nextNode === void 0 ? void 0 : nextNode.marks.find((mark) => mark.type === linkMark)) ||
|
|
55
|
+
(previousNode === null || previousNode === void 0 ? void 0 : previousNode.marks.find((mark) => mark.type === linkMark));
|
|
56
|
+
return mark === null || mark === void 0 ? void 0 : mark.attrs;
|
|
57
|
+
};
|
package/dist/theme/variants.js
CHANGED
|
@@ -122,6 +122,29 @@ const lightVariant = {
|
|
|
122
122
|
action: {
|
|
123
123
|
hover: "rgba(0, 0, 0, 0.1)",
|
|
124
124
|
},
|
|
125
|
+
codeBlock: {
|
|
126
|
+
background: "#f6f8fa",
|
|
127
|
+
text: "#24292f",
|
|
128
|
+
border: "#d0d7de",
|
|
129
|
+
selection: "#0969da2e",
|
|
130
|
+
syntax: {
|
|
131
|
+
comment: "#6e7781",
|
|
132
|
+
punctuation: "#24292f",
|
|
133
|
+
property: "#0550ae",
|
|
134
|
+
selector: "#116329",
|
|
135
|
+
operator: "#cf222e",
|
|
136
|
+
keyword: "#cf222e",
|
|
137
|
+
string: "#0a3069",
|
|
138
|
+
number: "#0550ae",
|
|
139
|
+
function: "#8250df",
|
|
140
|
+
variable: "#953800",
|
|
141
|
+
tag: "#116329",
|
|
142
|
+
attribute: "#0550ae",
|
|
143
|
+
literal: "#0550ae",
|
|
144
|
+
deleted: "#82071e",
|
|
145
|
+
inserted: "#116329",
|
|
146
|
+
},
|
|
147
|
+
},
|
|
125
148
|
divider: "rgba(0, 0, 0, 0.3)",
|
|
126
149
|
},
|
|
127
150
|
header: {
|
|
@@ -282,6 +305,29 @@ const darkVariant = merge(lightVariant, {
|
|
|
282
305
|
dataGrid: {
|
|
283
306
|
hover: "#2f2f2f",
|
|
284
307
|
},
|
|
308
|
+
codeBlock: {
|
|
309
|
+
background: "#191919",
|
|
310
|
+
text: "#c9d1d9",
|
|
311
|
+
border: "rgba(255, 255, 255, 0.15)",
|
|
312
|
+
selection: "#1f6feb40",
|
|
313
|
+
syntax: {
|
|
314
|
+
comment: "#8b949e",
|
|
315
|
+
punctuation: "#c9d1d9",
|
|
316
|
+
property: "#79c0ff",
|
|
317
|
+
selector: "#7ee787",
|
|
318
|
+
operator: "#ff7b72",
|
|
319
|
+
keyword: "#ff7b72",
|
|
320
|
+
string: "#a5d6ff",
|
|
321
|
+
number: "#79c0ff",
|
|
322
|
+
function: "#d2a8ff",
|
|
323
|
+
variable: "#ffa657",
|
|
324
|
+
tag: "#7ee787",
|
|
325
|
+
attribute: "#79c0ff",
|
|
326
|
+
literal: "#79c0ff",
|
|
327
|
+
deleted: "#ffa198",
|
|
328
|
+
inserted: "#aff5b4",
|
|
329
|
+
},
|
|
330
|
+
},
|
|
285
331
|
},
|
|
286
332
|
header: {
|
|
287
333
|
color: grey[300],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@monolith-forensics/monolith-ui",
|
|
3
|
-
"version": "1.9.1-dev.
|
|
3
|
+
"version": "1.9.1-dev.11",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
6
|
"author": "Matt Danner (Monolith Forensics LLC)",
|
|
@@ -41,9 +41,13 @@
|
|
|
41
41
|
"@radix-ui/react-tooltip": "^1.0.7",
|
|
42
42
|
"@tabler/icons-react": "^3.11.0",
|
|
43
43
|
"@tiptap/core": "3.22.4",
|
|
44
|
+
"@tiptap/extension-code-block": "3.22.4",
|
|
45
|
+
"@tiptap/extension-code-block-lowlight": "3.22.4",
|
|
44
46
|
"@tiptap/extension-color": "3.22.4",
|
|
47
|
+
"@tiptap/extension-highlight": "3.22.4",
|
|
45
48
|
"@tiptap/extension-horizontal-rule": "3.22.4",
|
|
46
49
|
"@tiptap/extension-image": "3.22.4",
|
|
50
|
+
"@tiptap/extension-link": "3.22.4",
|
|
47
51
|
"@tiptap/extension-table": "3.22.4",
|
|
48
52
|
"@tiptap/extension-text-align": "3.22.4",
|
|
49
53
|
"@tiptap/extension-text-style": "3.22.4",
|
|
@@ -58,10 +62,13 @@
|
|
|
58
62
|
"d3-shape": "^3.2.0",
|
|
59
63
|
"deepmerge": "^4.3.1",
|
|
60
64
|
"exceljs": "^4.4.0",
|
|
65
|
+
"highlight.js": "^11.11.1",
|
|
66
|
+
"lowlight": "^3.3.0",
|
|
61
67
|
"lucide-react": "^0.469.0",
|
|
62
68
|
"moment": "^2.29.1",
|
|
63
69
|
"overlayscrollbars": "^2.6.0",
|
|
64
70
|
"overlayscrollbars-react": "^0.5.6",
|
|
71
|
+
"prettier": "^3.8.3",
|
|
65
72
|
"react-dropzone": "^14.2.3",
|
|
66
73
|
"react-icons": "^5.2.1",
|
|
67
74
|
"react-pdf": "^9.1.1",
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import { Extension, isNodeSelection, isTextSelection, posToDOMRect, } from "@tiptap/core";
|
|
2
|
-
import { Plugin, PluginKey } from "@tiptap/pm/state";
|
|
3
|
-
import { ReactRenderer } from "@tiptap/react";
|
|
4
|
-
import BubbleMenuComponent from "../Components/BubbleMenu";
|
|
5
|
-
class Menu {
|
|
6
|
-
constructor({ view, editor, customMenuItems, }) {
|
|
7
|
-
this.mousedownHandler = (event) => {
|
|
8
|
-
this.preventShow = true;
|
|
9
|
-
};
|
|
10
|
-
this.mouseUpHandler = (event) => {
|
|
11
|
-
this.preventShow = false;
|
|
12
|
-
this.update(this.editor.view);
|
|
13
|
-
};
|
|
14
|
-
this.focusHandler = () => {
|
|
15
|
-
// this.editor.commands.setTextSelection({ from: 0, to: 0 });
|
|
16
|
-
// we use `setTimeout` to make sure `selection` is already updated
|
|
17
|
-
setTimeout(() => this.update(this.editor.view));
|
|
18
|
-
};
|
|
19
|
-
this.blurHandler = ({ event }) => {
|
|
20
|
-
var _a;
|
|
21
|
-
if (this.preventShow) {
|
|
22
|
-
this.preventShow = false;
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
if ((event === null || event === void 0 ? void 0 : event.relatedTarget) &&
|
|
26
|
-
((_a = this.floating) === null || _a === void 0 ? void 0 : _a.contains(event === null || event === void 0 ? void 0 : event.relatedTarget))) {
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
// clear text selection
|
|
30
|
-
// this.editor.commands.setTextSelection({ from: 0, to: 0 });
|
|
31
|
-
this.hide();
|
|
32
|
-
};
|
|
33
|
-
this.editor = editor;
|
|
34
|
-
this.view = view;
|
|
35
|
-
this.rect = null;
|
|
36
|
-
this.preventShow = false;
|
|
37
|
-
this.floating = null;
|
|
38
|
-
this.isOpen = false;
|
|
39
|
-
// create and mount react component
|
|
40
|
-
if (!this.component) {
|
|
41
|
-
this.component = new ReactRenderer(BubbleMenuComponent, {
|
|
42
|
-
props: {
|
|
43
|
-
editor: this.editor,
|
|
44
|
-
open: false,
|
|
45
|
-
onOpen: (ref) => {
|
|
46
|
-
this.floating = ref;
|
|
47
|
-
},
|
|
48
|
-
customMenuItems,
|
|
49
|
-
},
|
|
50
|
-
editor: this.editor,
|
|
51
|
-
});
|
|
52
|
-
document.body.appendChild(this.component.element);
|
|
53
|
-
}
|
|
54
|
-
// don't show the bubble during selection of text
|
|
55
|
-
this.view.dom.addEventListener("mousedown", this.mousedownHandler, {
|
|
56
|
-
capture: true,
|
|
57
|
-
});
|
|
58
|
-
this.view.dom.addEventListener("mouseup", this.mouseUpHandler);
|
|
59
|
-
this.editor.on("blur", this.blurHandler);
|
|
60
|
-
this.editor.on("focus", this.focusHandler);
|
|
61
|
-
}
|
|
62
|
-
update(view, oldState) {
|
|
63
|
-
var _a;
|
|
64
|
-
const { state, composing } = view;
|
|
65
|
-
const { doc, selection } = state;
|
|
66
|
-
const { empty, ranges } = selection;
|
|
67
|
-
const from = Math.min(...ranges.map((range) => range.$from.pos));
|
|
68
|
-
const to = Math.max(...ranges.map((range) => range.$to.pos));
|
|
69
|
-
const selectionChanged = !(oldState === null || oldState === void 0 ? void 0 : oldState.selection.eq(view.state.selection));
|
|
70
|
-
const docChanged = !(oldState === null || oldState === void 0 ? void 0 : oldState.doc.eq(view.state.doc));
|
|
71
|
-
const isSame = !selectionChanged && !docChanged;
|
|
72
|
-
if (composing || isSame) {
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
// Sometime check for `empty` is not enough.
|
|
76
|
-
// Doubleclick an empty paragraph returns a node size of 2.
|
|
77
|
-
// So we check also for an empty text size.
|
|
78
|
-
const isEmptyTextBlock = !doc.textBetween(from, to).length && isTextSelection(state.selection);
|
|
79
|
-
// When clicking on a element inside the bubble menu the editor "blur" event
|
|
80
|
-
// is called and the bubble menu item is focussed. In this case we should
|
|
81
|
-
// consider the menu as part of the editor and keep showing the menu
|
|
82
|
-
const isChildOfMenu = (_a = this === null || this === void 0 ? void 0 : this.floating) === null || _a === void 0 ? void 0 : _a.contains(document.activeElement);
|
|
83
|
-
const hasEditorFocus = view.hasFocus() || isChildOfMenu;
|
|
84
|
-
if (!hasEditorFocus ||
|
|
85
|
-
empty ||
|
|
86
|
-
isEmptyTextBlock ||
|
|
87
|
-
!this.editor.isEditable ||
|
|
88
|
-
this.preventShow) {
|
|
89
|
-
this.hide();
|
|
90
|
-
}
|
|
91
|
-
else {
|
|
92
|
-
// only set position when it is not already open
|
|
93
|
-
// otherwise the menu will jump around when the selection changes or text formatting is applied
|
|
94
|
-
if (!this.isOpen) {
|
|
95
|
-
if (isNodeSelection(state.selection)) {
|
|
96
|
-
let node = view.nodeDOM(from);
|
|
97
|
-
const nodeViewWrapper = node.dataset.nodeViewWrapper
|
|
98
|
-
? node
|
|
99
|
-
: node.querySelector("[data-node-view-wrapper]");
|
|
100
|
-
if (nodeViewWrapper) {
|
|
101
|
-
node = nodeViewWrapper.firstChild;
|
|
102
|
-
}
|
|
103
|
-
if (node) {
|
|
104
|
-
this.rect = node.getBoundingClientRect();
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
this.rect = posToDOMRect(view, from, to);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
this.show();
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
show() {
|
|
115
|
-
if (this.component) {
|
|
116
|
-
this.component.updateProps({ open: true, rect: this.rect });
|
|
117
|
-
this.isOpen = true;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
hide() {
|
|
121
|
-
if (this.component) {
|
|
122
|
-
this.component.updateProps({ open: false, rect: null });
|
|
123
|
-
this.isOpen = false;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
destroy() {
|
|
127
|
-
if (this.component) {
|
|
128
|
-
this.view.dom.removeEventListener("mousedown", this.mousedownHandler);
|
|
129
|
-
this.view.dom.removeEventListener("mouseup", this.mouseUpHandler);
|
|
130
|
-
this.component.destroy();
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
const BubbleMenu = Extension.create({
|
|
135
|
-
name: "bubbleMenu",
|
|
136
|
-
addOptions() {
|
|
137
|
-
return {
|
|
138
|
-
bubbleMenuOptions: {},
|
|
139
|
-
};
|
|
140
|
-
},
|
|
141
|
-
addProseMirrorPlugins() {
|
|
142
|
-
return [
|
|
143
|
-
new Plugin({
|
|
144
|
-
key: new PluginKey("bubbleMenu"),
|
|
145
|
-
view: (view) => {
|
|
146
|
-
var _a;
|
|
147
|
-
return new Menu({
|
|
148
|
-
view,
|
|
149
|
-
editor: this.editor,
|
|
150
|
-
customMenuItems: ((_a = this === null || this === void 0 ? void 0 : this.options) === null || _a === void 0 ? void 0 : _a.customMenuItems) || [],
|
|
151
|
-
});
|
|
152
|
-
},
|
|
153
|
-
}),
|
|
154
|
-
];
|
|
155
|
-
},
|
|
156
|
-
});
|
|
157
|
-
export default BubbleMenu;
|