@bigbinary/neeto-editor 0.2.1 → 0.2.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/index.js +8 -8
- package/package.json +1 -1
- package/webpack.config.js +4 -3
- package/webpack.dev.config.js +4 -3
- package/src/App.js +0 -8
- package/src/Common/Avatar.js +0 -168
- package/src/Common/Button.js +0 -95
- package/src/Common/CodeBlock.js +0 -11
- package/src/Common/Description.js +0 -8
- package/src/Common/Dropdown/index.js +0 -122
- package/src/Common/Heading.js +0 -13
- package/src/Common/HighlightText.js +0 -7
- package/src/Common/Icons/HashtagFilled.js +0 -59
- package/src/Common/Icons/TextColor.js +0 -35
- package/src/Common/Icons/index.js +0 -2
- package/src/Common/Input.js +0 -70
- package/src/Common/Label.js +0 -45
- package/src/Common/ListItems.js +0 -17
- package/src/Common/Modal.js +0 -91
- package/src/Common/Tab.js +0 -79
- package/src/Common/ToolTip.js +0 -37
- package/src/Editor/CustomExtensions/BubbleMenu/index.js +0 -92
- package/src/Editor/CustomExtensions/CodeBlock/CodeBlockComponent.js +0 -12
- package/src/Editor/CustomExtensions/CodeBlock/ExtensionConfig.js +0 -10
- package/src/Editor/CustomExtensions/Embeds.js +0 -72
- package/src/Editor/CustomExtensions/FixedMenu/FontSizeOption.js +0 -32
- package/src/Editor/CustomExtensions/FixedMenu/TextColorOption.js +0 -29
- package/src/Editor/CustomExtensions/FixedMenu/constants.js +0 -3
- package/src/Editor/CustomExtensions/FixedMenu/index.js +0 -183
- package/src/Editor/CustomExtensions/Image/ExtensionConfig.js +0 -47
- package/src/Editor/CustomExtensions/Image/LinkUploader/URLForm.js +0 -39
- package/src/Editor/CustomExtensions/Image/LocalUploader.js +0 -21
- package/src/Editor/CustomExtensions/Image/ProgressBar.js +0 -34
- package/src/Editor/CustomExtensions/Image/Uploader.js +0 -82
- package/src/Editor/CustomExtensions/Image/constants.js +0 -5
- package/src/Editor/CustomExtensions/Mention/ExtensionConfig.js +0 -66
- package/src/Editor/CustomExtensions/Mention/MentionList.js +0 -96
- package/src/Editor/CustomExtensions/Mention/helpers.js +0 -23
- package/src/Editor/CustomExtensions/Placeholder/ExtensionConfig.js +0 -81
- package/src/Editor/CustomExtensions/Placeholder/helpers.js +0 -18
- package/src/Editor/CustomExtensions/SlashCommands/Commands.js +0 -20
- package/src/Editor/CustomExtensions/SlashCommands/CommandsList.js +0 -109
- package/src/Editor/CustomExtensions/SlashCommands/ExtensionConfig.js +0 -209
- package/src/Editor/CustomExtensions/Variable/ExtensionConfig.js +0 -208
- package/src/Editor/CustomExtensions/Variable/VariableList.js +0 -45
- package/src/Editor/CustomExtensions/Variable/VariableSuggestion.js +0 -20
- package/src/Editor/CustomExtensions/Variable/helpers.js +0 -31
- package/src/Editor/CustomExtensions/Variable/index.js +0 -35
- package/src/Editor/CustomExtensions/useCustomExtensions.js +0 -88
- package/src/Editor/index.js +0 -104
- package/src/Editor/sharedState.js +0 -8
- package/src/constants/regexp.js +0 -1
- package/src/examples/constants.js +0 -95
- package/src/examples/index.js +0 -186
- package/src/hooks/useOutsideClick.js +0 -19
- package/src/hooks/useTabBar.js +0 -9
- package/src/index.js +0 -10
- package/src/index.scss +0 -46
- package/src/styles/abstracts/_mixins.scss +0 -20
- package/src/styles/abstracts/_neeto-ui-variables.scss +0 -107
- package/src/styles/abstracts/_variables.scss +0 -13
- package/src/styles/components/_avatar.scss +0 -105
- package/src/styles/components/_button.scss +0 -161
- package/src/styles/components/_codeblock.scss +0 -16
- package/src/styles/components/_command-list.scss +0 -19
- package/src/styles/components/_dropdown.scss +0 -69
- package/src/styles/components/_editor-variables.scss +0 -12
- package/src/styles/components/_editor.scss +0 -102
- package/src/styles/components/_fixed-menu.scss +0 -17
- package/src/styles/components/_image-uploader.scss +0 -109
- package/src/styles/components/_input.scss +0 -165
- package/src/styles/components/_tab.scss +0 -74
- package/src/styles/components/_tooltip.scss +0 -152
- package/src/utils/common.js +0 -13
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import {
|
|
3
|
-
TextBold,
|
|
4
|
-
TextItalic,
|
|
5
|
-
Underline,
|
|
6
|
-
TextCross,
|
|
7
|
-
Link,
|
|
8
|
-
Code,
|
|
9
|
-
ListDot,
|
|
10
|
-
ListNumber,
|
|
11
|
-
Image,
|
|
12
|
-
Quote,
|
|
13
|
-
Undo,
|
|
14
|
-
Redo,
|
|
15
|
-
} from "@bigbinary/neeto-icons";
|
|
16
|
-
|
|
17
|
-
import TextColorOption from "./TextColorOption";
|
|
18
|
-
import FontSizeOption from "./FontSizeOption";
|
|
19
|
-
|
|
20
|
-
import { ICON_COLOR_ACTIVE, ICON_COLOR_INACTIVE } from "./constants";
|
|
21
|
-
import Variables from "../Variable";
|
|
22
|
-
|
|
23
|
-
const FixedMenu = ({ editor, variables, setImageUploadVisible }) => {
|
|
24
|
-
if (!editor) {
|
|
25
|
-
return null;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const fontStyleOptions = [
|
|
29
|
-
{
|
|
30
|
-
Icon: TextBold,
|
|
31
|
-
command: () => editor.chain().focus().toggleBold().run(),
|
|
32
|
-
active: editor.isActive("bold"),
|
|
33
|
-
optionName: "bold",
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
Icon: TextItalic,
|
|
37
|
-
command: () => editor.chain().focus().toggleItalic().run(),
|
|
38
|
-
active: editor.isActive("italic"),
|
|
39
|
-
optionName: "italic",
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
Icon: Underline,
|
|
43
|
-
command: () => editor.chain().focus().toggleUnderline().run(),
|
|
44
|
-
active: editor.isActive("underline"),
|
|
45
|
-
optionName: "underline",
|
|
46
|
-
size: 27,
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
Icon: TextCross,
|
|
50
|
-
command: () => editor.chain().focus().toggleStrike().run(),
|
|
51
|
-
active: editor.isActive("strike"),
|
|
52
|
-
optionName: "strike",
|
|
53
|
-
},
|
|
54
|
-
];
|
|
55
|
-
|
|
56
|
-
const blockStyleOptions = [
|
|
57
|
-
{
|
|
58
|
-
Icon: Link,
|
|
59
|
-
command: () => {
|
|
60
|
-
if (editor.isActive("link")) {
|
|
61
|
-
editor.chain().focus().unsetLink().run();
|
|
62
|
-
} else {
|
|
63
|
-
const url = window.prompt("Please enter your URL");
|
|
64
|
-
editor.chain().focus().setLink({ href: url }).run();
|
|
65
|
-
}
|
|
66
|
-
},
|
|
67
|
-
active: editor.isActive("link"),
|
|
68
|
-
optionName: "link",
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
Icon: Quote,
|
|
72
|
-
command: () => editor.chain().focus().toggleBlockquote().run(),
|
|
73
|
-
active: editor.isActive("blockquote"),
|
|
74
|
-
optionName: "block-quote",
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
Icon: Code,
|
|
78
|
-
command: () => editor.chain().focus().toggleCode().run(),
|
|
79
|
-
active: editor.isActive("code"),
|
|
80
|
-
optionName: "code",
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
Icon: Image,
|
|
84
|
-
command: () => setImageUploadVisible(true),
|
|
85
|
-
optionName: "image-upload",
|
|
86
|
-
},
|
|
87
|
-
];
|
|
88
|
-
|
|
89
|
-
const listStyleOptions = [
|
|
90
|
-
{
|
|
91
|
-
Icon: ListDot,
|
|
92
|
-
command: () => editor.chain().focus().toggleBulletList().run(),
|
|
93
|
-
active: editor.isActive("bulletList"),
|
|
94
|
-
optionName: "bullet-list",
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
Icon: ListNumber,
|
|
98
|
-
command: () => editor.chain().focus().toggleOrderedList().run(),
|
|
99
|
-
active: editor.isActive("orderedList"),
|
|
100
|
-
optionName: "ordered-list",
|
|
101
|
-
},
|
|
102
|
-
];
|
|
103
|
-
|
|
104
|
-
const editorOptions = [
|
|
105
|
-
{
|
|
106
|
-
Icon: Undo,
|
|
107
|
-
command: () => editor.chain().focus().undo().run(),
|
|
108
|
-
optionName: "undo",
|
|
109
|
-
disabled: !editor.can().undo(),
|
|
110
|
-
active: editor.can().undo(),
|
|
111
|
-
},
|
|
112
|
-
{
|
|
113
|
-
Icon: Redo,
|
|
114
|
-
command: () => editor.chain().focus().redo().run(),
|
|
115
|
-
optionName: "redo",
|
|
116
|
-
disabled: !editor.can().redo(),
|
|
117
|
-
active: editor.can().redo(),
|
|
118
|
-
},
|
|
119
|
-
];
|
|
120
|
-
|
|
121
|
-
const handleTextSizeChange = (value) => {
|
|
122
|
-
switch (value) {
|
|
123
|
-
case "large": {
|
|
124
|
-
editor.chain().focus().setHeading({ level: 2 }).run();
|
|
125
|
-
break;
|
|
126
|
-
}
|
|
127
|
-
case "medium": {
|
|
128
|
-
editor.chain().focus().setHeading({ level: 3 }).run();
|
|
129
|
-
break;
|
|
130
|
-
}
|
|
131
|
-
case "normal": {
|
|
132
|
-
editor.chain().focus().setParagraph().run();
|
|
133
|
-
break;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
const renderOptionButton = ({
|
|
139
|
-
Icon,
|
|
140
|
-
command,
|
|
141
|
-
active,
|
|
142
|
-
optionName,
|
|
143
|
-
disabled,
|
|
144
|
-
...rest
|
|
145
|
-
}) => (
|
|
146
|
-
<button
|
|
147
|
-
disabled={disabled}
|
|
148
|
-
onClick={command}
|
|
149
|
-
key={optionName}
|
|
150
|
-
className="p-3 transition-colors cursor-pointer editor-fixed-menu--item"
|
|
151
|
-
>
|
|
152
|
-
<Icon
|
|
153
|
-
color={active ? ICON_COLOR_ACTIVE : ICON_COLOR_INACTIVE}
|
|
154
|
-
{...rest}
|
|
155
|
-
/>
|
|
156
|
-
</button>
|
|
157
|
-
);
|
|
158
|
-
|
|
159
|
-
return (
|
|
160
|
-
<div className="flex space-x-6 border-t border-l border-r editor-fixed-menu--root">
|
|
161
|
-
<div className="flex">
|
|
162
|
-
<TextColorOption
|
|
163
|
-
color={editor.getAttributes("textStyle").color}
|
|
164
|
-
onChange={(color) => editor.chain().focus().setColor(color).run()}
|
|
165
|
-
/>
|
|
166
|
-
<FontSizeOption onChange={handleTextSizeChange} />
|
|
167
|
-
{fontStyleOptions.map(renderOptionButton)}
|
|
168
|
-
</div>
|
|
169
|
-
{[blockStyleOptions, listStyleOptions, editorOptions].map(
|
|
170
|
-
(optionGroup, index) => (
|
|
171
|
-
<div className="flex" key={index}>
|
|
172
|
-
{optionGroup.map(renderOptionButton)}
|
|
173
|
-
</div>
|
|
174
|
-
)
|
|
175
|
-
)}
|
|
176
|
-
<div className="flex justify-end flex-1">
|
|
177
|
-
<Variables editor={editor} variables={variables} />
|
|
178
|
-
</div>
|
|
179
|
-
</div>
|
|
180
|
-
);
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
export default FixedMenu;
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import Image from "@tiptap/extension-image";
|
|
2
|
-
import { mergeAttributes } from "@tiptap/core";
|
|
3
|
-
|
|
4
|
-
export default Image.extend({
|
|
5
|
-
name: "image",
|
|
6
|
-
|
|
7
|
-
addAttributes() {
|
|
8
|
-
return {
|
|
9
|
-
...Image.config.addAttributes(),
|
|
10
|
-
size: {
|
|
11
|
-
default: "small",
|
|
12
|
-
rendered: false,
|
|
13
|
-
},
|
|
14
|
-
float: {
|
|
15
|
-
default: "none",
|
|
16
|
-
rendered: false,
|
|
17
|
-
},
|
|
18
|
-
};
|
|
19
|
-
},
|
|
20
|
-
|
|
21
|
-
addCommands() {
|
|
22
|
-
return {
|
|
23
|
-
setImage:
|
|
24
|
-
(options) =>
|
|
25
|
-
({ tr, commands }) => {
|
|
26
|
-
if (tr.selection?.node?.type?.name == "image") {
|
|
27
|
-
return commands.updateAttributes("image", options);
|
|
28
|
-
} else {
|
|
29
|
-
return commands.insertContent({
|
|
30
|
-
type: this.name,
|
|
31
|
-
attrs: options,
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
},
|
|
35
|
-
};
|
|
36
|
-
},
|
|
37
|
-
|
|
38
|
-
renderHTML({ node, HTMLAttributes }) {
|
|
39
|
-
HTMLAttributes.class = " image-" + node.attrs.size;
|
|
40
|
-
HTMLAttributes.class += " image-float-" + node.attrs.float;
|
|
41
|
-
|
|
42
|
-
return [
|
|
43
|
-
"img",
|
|
44
|
-
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
|
|
45
|
-
];
|
|
46
|
-
},
|
|
47
|
-
});
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import React, { useState } from "react";
|
|
2
|
-
|
|
3
|
-
import Input from "common/Input";
|
|
4
|
-
import Button from "common/Button";
|
|
5
|
-
|
|
6
|
-
import { UrlRegExp } from "../../../../constants/regexp";
|
|
7
|
-
|
|
8
|
-
const URLField = ({ onSubmit }) => {
|
|
9
|
-
const [urlString, setUrlString] = useState("");
|
|
10
|
-
const [error, setError] = useState("");
|
|
11
|
-
|
|
12
|
-
return (
|
|
13
|
-
<form
|
|
14
|
-
onSubmit={(e) => {
|
|
15
|
-
e.preventDefault();
|
|
16
|
-
if (UrlRegExp.test(urlString)) {
|
|
17
|
-
onSubmit(urlString);
|
|
18
|
-
} else {
|
|
19
|
-
setError("Please provide a valid image url");
|
|
20
|
-
}
|
|
21
|
-
}}
|
|
22
|
-
className="flex flex-col items-center justify-center flex-1 mx-10"
|
|
23
|
-
>
|
|
24
|
-
<div className="flex-row w-full mb-4">
|
|
25
|
-
<Input
|
|
26
|
-
name="url"
|
|
27
|
-
value={urlString}
|
|
28
|
-
placeholder="Paste the image link"
|
|
29
|
-
onFocus={() => setError("")}
|
|
30
|
-
error={error}
|
|
31
|
-
onChange={({ target: { value } }) => setUrlString(value)}
|
|
32
|
-
/>
|
|
33
|
-
</div>
|
|
34
|
-
<Button type="submit" label="Upload Image" />
|
|
35
|
-
</form>
|
|
36
|
-
);
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
export default URLField;
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
|
|
3
|
-
import { DragDrop } from "@uppy/react";
|
|
4
|
-
|
|
5
|
-
const LocalUploader = ({ uppy }) => {
|
|
6
|
-
return (
|
|
7
|
-
<DragDrop
|
|
8
|
-
note="Max. File Size: 5MB"
|
|
9
|
-
uppy={uppy}
|
|
10
|
-
locale={{
|
|
11
|
-
strings: {
|
|
12
|
-
dropHereOr: "Drop your file(s) here or %{browse}",
|
|
13
|
-
browse: "Browse",
|
|
14
|
-
},
|
|
15
|
-
}}
|
|
16
|
-
className="local-upload__root"
|
|
17
|
-
/>
|
|
18
|
-
);
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export default LocalUploader;
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect } from "react";
|
|
2
|
-
|
|
3
|
-
import { Close } from "@bigbinary/neeto-icons";
|
|
4
|
-
|
|
5
|
-
const ProgressBar = ({ uppy }) => {
|
|
6
|
-
const [progress, setProgress] = useState(0);
|
|
7
|
-
|
|
8
|
-
useEffect(() => {
|
|
9
|
-
uppy.on("progress", setProgress);
|
|
10
|
-
}, [uppy]);
|
|
11
|
-
|
|
12
|
-
const progressPercentage = `${progress}%`;
|
|
13
|
-
|
|
14
|
-
return (
|
|
15
|
-
<div className="progress-bar__root">
|
|
16
|
-
<div className="flex items-center justify-between">
|
|
17
|
-
<span className="progress-bar__percent-text">{progressPercentage}</span>
|
|
18
|
-
<button onClick={uppy.cancelAll}>
|
|
19
|
-
<Close size={16} />
|
|
20
|
-
</button>
|
|
21
|
-
</div>
|
|
22
|
-
<div className="progress-bar__indicator">
|
|
23
|
-
<div className="flex h-full">
|
|
24
|
-
<div
|
|
25
|
-
style={{ width: progressPercentage }}
|
|
26
|
-
className="flex flex-col justify-center text-center text-white shadow-none whitespace-nowrap progress-bar__indicator-inner"
|
|
27
|
-
></div>
|
|
28
|
-
</div>
|
|
29
|
-
</div>
|
|
30
|
-
</div>
|
|
31
|
-
);
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
export default ProgressBar;
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import React, { useState, useMemo } from "react";
|
|
2
|
-
import { view } from "@risingstack/react-easy-state";
|
|
3
|
-
import Uppy from "@uppy/core";
|
|
4
|
-
import XHRUpload from "@uppy/xhr-upload";
|
|
5
|
-
import Modal from "common/Modal";
|
|
6
|
-
import Tab from "common/Tab";
|
|
7
|
-
import useTabBar from "hooks/useTabBar";
|
|
8
|
-
|
|
9
|
-
import LocalUploader from "./LocalUploader";
|
|
10
|
-
import ProgressBar from "./ProgressBar";
|
|
11
|
-
|
|
12
|
-
import URLForm from "./LinkUploader/URLForm";
|
|
13
|
-
|
|
14
|
-
import { IMAGE_UPLOAD_OPTIONS } from "./constants";
|
|
15
|
-
|
|
16
|
-
const ImageUpload = ({ editor, imageUploadUrl, isVisible, setIsVisible }) => {
|
|
17
|
-
const [isUploading, setIsUploading] = useState(false);
|
|
18
|
-
const [activeTab, setActiveTab] = useTabBar(IMAGE_UPLOAD_OPTIONS);
|
|
19
|
-
|
|
20
|
-
const uppy = useMemo(
|
|
21
|
-
() =>
|
|
22
|
-
new Uppy({
|
|
23
|
-
allowMultipleUploads: false,
|
|
24
|
-
autoProceed: true,
|
|
25
|
-
debug: true,
|
|
26
|
-
})
|
|
27
|
-
.use(XHRUpload, {
|
|
28
|
-
endpoint: imageUploadUrl || "/api/v1/direct_uploads",
|
|
29
|
-
formData: true,
|
|
30
|
-
fieldName: "blob",
|
|
31
|
-
})
|
|
32
|
-
.on("upload", () => setIsUploading(true))
|
|
33
|
-
.on("upload-success", (file, response) => {
|
|
34
|
-
const url = response.body.imageURL;
|
|
35
|
-
editor.chain().focus().setImage({ src: url }).run();
|
|
36
|
-
setIsVisible(false);
|
|
37
|
-
})
|
|
38
|
-
.on("cancel-all", () => setIsUploading(false))
|
|
39
|
-
.on("complete", () => setIsUploading(false)),
|
|
40
|
-
[editor]
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
const handleUrlFormSubmit = (url) => {
|
|
44
|
-
editor.chain().focus().setImage({ src: url }).run();
|
|
45
|
-
setIsVisible(false);
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
return (
|
|
49
|
-
<Modal isVisible={isVisible} onClose={() => setIsVisible(false)}>
|
|
50
|
-
<div className="image-uploader__root">
|
|
51
|
-
<Tab>
|
|
52
|
-
{IMAGE_UPLOAD_OPTIONS.map((option) => (
|
|
53
|
-
<Tab.Item
|
|
54
|
-
active={activeTab === option.key}
|
|
55
|
-
onClick={() => setActiveTab(option)}
|
|
56
|
-
>
|
|
57
|
-
{option.title}
|
|
58
|
-
</Tab.Item>
|
|
59
|
-
))}
|
|
60
|
-
</Tab>
|
|
61
|
-
|
|
62
|
-
<div className="image-uploader__content">
|
|
63
|
-
{isUploading ? (
|
|
64
|
-
<div className="flex flex-col items-center justify-center flex-1 text-center">
|
|
65
|
-
<span className="label--primary">Uploading...</span>
|
|
66
|
-
<span className="label--secondary">
|
|
67
|
-
{uppy.getFiles()[0]?.name}
|
|
68
|
-
</span>
|
|
69
|
-
<ProgressBar uppy={uppy} />
|
|
70
|
-
</div>
|
|
71
|
-
) : activeTab === "local" ? (
|
|
72
|
-
<LocalUploader uppy={uppy} />
|
|
73
|
-
) : activeTab === "link" ? (
|
|
74
|
-
<URLForm onSubmit={handleUrlFormSubmit} />
|
|
75
|
-
) : null}
|
|
76
|
-
</div>
|
|
77
|
-
</div>
|
|
78
|
-
</Modal>
|
|
79
|
-
);
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
export default view(ImageUpload);
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import Mention from "@tiptap/extension-mention";
|
|
2
|
-
import tippy from "tippy.js";
|
|
3
|
-
import { ReactRenderer } from "@tiptap/react";
|
|
4
|
-
|
|
5
|
-
import { MentionList } from "./MentionList";
|
|
6
|
-
|
|
7
|
-
import { createMentionSuggestions } from "./helpers";
|
|
8
|
-
|
|
9
|
-
const suggestion = {
|
|
10
|
-
render: () => {
|
|
11
|
-
let reactRenderer;
|
|
12
|
-
let popup;
|
|
13
|
-
|
|
14
|
-
return {
|
|
15
|
-
onStart: (props) => {
|
|
16
|
-
reactRenderer = new ReactRenderer(MentionList, {
|
|
17
|
-
props,
|
|
18
|
-
editor: props.editor,
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
popup = tippy("body", {
|
|
22
|
-
getReferenceClientRect: props.clientRect,
|
|
23
|
-
appendTo: () => document.body,
|
|
24
|
-
content: reactRenderer.element,
|
|
25
|
-
showOnCreate: true,
|
|
26
|
-
interactive: true,
|
|
27
|
-
trigger: "manual",
|
|
28
|
-
placement: "bottom-start",
|
|
29
|
-
});
|
|
30
|
-
},
|
|
31
|
-
|
|
32
|
-
onUpdate(props) {
|
|
33
|
-
reactRenderer.updateProps(props);
|
|
34
|
-
|
|
35
|
-
popup[0].setProps({
|
|
36
|
-
getReferenceClientRect: props.clientRect,
|
|
37
|
-
});
|
|
38
|
-
},
|
|
39
|
-
|
|
40
|
-
onKeyDown(props) {
|
|
41
|
-
if (props.event.key === "Escape") {
|
|
42
|
-
popup[0].hide();
|
|
43
|
-
|
|
44
|
-
return true;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return reactRenderer.ref?.onKeyDown(props);
|
|
48
|
-
},
|
|
49
|
-
|
|
50
|
-
onExit() {
|
|
51
|
-
popup[0].destroy();
|
|
52
|
-
reactRenderer.destroy();
|
|
53
|
-
},
|
|
54
|
-
};
|
|
55
|
-
},
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
export default {
|
|
59
|
-
configure: ({ suggestion: suggestionConfig = {}, ...otherConfig }) =>
|
|
60
|
-
Mention.configure({
|
|
61
|
-
...otherConfig,
|
|
62
|
-
suggestion: { ...suggestion, ...suggestionConfig },
|
|
63
|
-
}),
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
export { createMentionSuggestions };
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import classNames from "classnames";
|
|
3
|
-
|
|
4
|
-
import Avatar from "common/Avatar";
|
|
5
|
-
|
|
6
|
-
export class MentionList extends React.Component {
|
|
7
|
-
state = { selectedIndex: 0 };
|
|
8
|
-
|
|
9
|
-
componentDidUpdate(prevProps) {
|
|
10
|
-
const { items } = this.props;
|
|
11
|
-
if (items !== prevProps.items) {
|
|
12
|
-
this.setState({ selectedIndex: 0 });
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
selectItem = (index) => {
|
|
17
|
-
const { items, command } = this.props;
|
|
18
|
-
const item = items[index];
|
|
19
|
-
|
|
20
|
-
if (item) {
|
|
21
|
-
command({ label: item.label, id: item.key });
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
upHandler = () => {
|
|
26
|
-
const { items } = this.props;
|
|
27
|
-
this.setState((prevState) => {
|
|
28
|
-
const { selectedIndex } = prevState;
|
|
29
|
-
const nextSelectedIndex =
|
|
30
|
-
(selectedIndex + items.length - 1) % items.length;
|
|
31
|
-
return {
|
|
32
|
-
selectedIndex: nextSelectedIndex,
|
|
33
|
-
};
|
|
34
|
-
});
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
downHandler = () => {
|
|
38
|
-
const { items } = this.props;
|
|
39
|
-
this.setState((prevState) => {
|
|
40
|
-
const { selectedIndex } = prevState;
|
|
41
|
-
const nextSelectedIndex = (selectedIndex + 1) % items.length;
|
|
42
|
-
return {
|
|
43
|
-
selectedIndex: nextSelectedIndex,
|
|
44
|
-
};
|
|
45
|
-
});
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
enterHandler = () => {
|
|
49
|
-
const { selectedIndex } = this.state;
|
|
50
|
-
this.selectItem(selectedIndex);
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
onKeyDown = ({ event }) => {
|
|
54
|
-
const keyDownHandlers = {
|
|
55
|
-
ArrowUp: this.upHandler,
|
|
56
|
-
ArrowDown: this.downHandler,
|
|
57
|
-
Enter: this.enterHandler,
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
if (keyDownHandlers.hasOwnProperty(event.key)) {
|
|
61
|
-
keyDownHandlers[event.key]();
|
|
62
|
-
return true;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return false;
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
render() {
|
|
69
|
-
const { selectedIndex } = this.state;
|
|
70
|
-
const { items } = this.props;
|
|
71
|
-
|
|
72
|
-
const containerClassName =
|
|
73
|
-
"relative p-2 space-y-1 overflow-hidden rounded shadow editor-command-list--root";
|
|
74
|
-
const itemClassName =
|
|
75
|
-
"flex items-center w-full px-4 py-2 transition-all duration-100 ease-in-out cursor-pointer text-xs text-white rounded editor-command-list--item";
|
|
76
|
-
|
|
77
|
-
return (
|
|
78
|
-
<div className={containerClassName}>
|
|
79
|
-
{items.map(({ label, imageUrl, showImage }, index) => (
|
|
80
|
-
<button
|
|
81
|
-
className={classNames(itemClassName, {
|
|
82
|
-
selected_item: index === selectedIndex,
|
|
83
|
-
})}
|
|
84
|
-
key={label}
|
|
85
|
-
onClick={() => this.selectItem(index)}
|
|
86
|
-
>
|
|
87
|
-
{showImage ? (
|
|
88
|
-
<Avatar user={{ name: label, imageUrl }} className="mr-2" />
|
|
89
|
-
) : null}
|
|
90
|
-
<span>{label}</span>
|
|
91
|
-
</button>
|
|
92
|
-
))}
|
|
93
|
-
</div>
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
export const createMentionSuggestions = (
|
|
2
|
-
items = [],
|
|
3
|
-
{ limit = 5, showImage = false } = {}
|
|
4
|
-
) => {
|
|
5
|
-
const allSuggestions = items.map((item) => {
|
|
6
|
-
let suggestionObj;
|
|
7
|
-
if (typeof item === "string") {
|
|
8
|
-
suggestionObj = { key: item, label: item };
|
|
9
|
-
} else if (typeof item === "object") {
|
|
10
|
-
suggestionObj = { ...item };
|
|
11
|
-
}
|
|
12
|
-
suggestionObj.showImage = showImage;
|
|
13
|
-
|
|
14
|
-
return suggestionObj;
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
return ({ query }) =>
|
|
18
|
-
allSuggestions
|
|
19
|
-
.filter((suggestion) =>
|
|
20
|
-
suggestion.label.toLowerCase().startsWith(query.toLowerCase())
|
|
21
|
-
)
|
|
22
|
-
.slice(0, limit);
|
|
23
|
-
};
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { Extension } from "@tiptap/core";
|
|
2
|
-
import { Decoration, DecorationSet } from "prosemirror-view";
|
|
3
|
-
import { Plugin } from "prosemirror-state";
|
|
4
|
-
|
|
5
|
-
import { placeholderGenerator } from "./helpers";
|
|
6
|
-
|
|
7
|
-
export default Extension.create({
|
|
8
|
-
name: "placeholder",
|
|
9
|
-
|
|
10
|
-
addOptions() {
|
|
11
|
-
return {
|
|
12
|
-
excludeNodeTypes: ["variable"],
|
|
13
|
-
emptyEditorClass: "is-editor-empty",
|
|
14
|
-
emptyNodeClass: "is-empty",
|
|
15
|
-
placeholder: "Write something …",
|
|
16
|
-
showOnlyWhenEditable: true,
|
|
17
|
-
showOnlyCurrent: true,
|
|
18
|
-
includeChildren: false,
|
|
19
|
-
};
|
|
20
|
-
},
|
|
21
|
-
|
|
22
|
-
addProseMirrorPlugins() {
|
|
23
|
-
return [
|
|
24
|
-
new Plugin({
|
|
25
|
-
props: {
|
|
26
|
-
decorations: ({ doc, selection }) => {
|
|
27
|
-
const active =
|
|
28
|
-
this.editor.isEditable || !this.options.showOnlyWhenEditable;
|
|
29
|
-
const { anchor } = selection;
|
|
30
|
-
const decorations = [];
|
|
31
|
-
|
|
32
|
-
if (!active) {
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
doc.descendants((node, pos) => {
|
|
37
|
-
const hasAnchor = anchor >= pos && anchor <= pos + node.nodeSize;
|
|
38
|
-
const isEmpty = !node.isLeaf && !node.childCount;
|
|
39
|
-
|
|
40
|
-
const isExcluded = this.options.excludeNodeTypes.includes(
|
|
41
|
-
node.type.name
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
if (
|
|
45
|
-
(hasAnchor || !this.options.showOnlyCurrent) &&
|
|
46
|
-
!isExcluded &&
|
|
47
|
-
isEmpty
|
|
48
|
-
) {
|
|
49
|
-
const classes = [this.options.emptyNodeClass];
|
|
50
|
-
|
|
51
|
-
if (this.editor.isEmpty) {
|
|
52
|
-
classes.push(this.options.emptyEditorClass);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const decoration = Decoration.node(pos, pos + node.nodeSize, {
|
|
56
|
-
class: classes.join(" "),
|
|
57
|
-
"data-placeholder":
|
|
58
|
-
typeof this.options.placeholder === "function"
|
|
59
|
-
? this.options.placeholder({
|
|
60
|
-
editor: this.editor,
|
|
61
|
-
node,
|
|
62
|
-
pos,
|
|
63
|
-
})
|
|
64
|
-
: this.options.placeholder,
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
decorations.push(decoration);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return this.options.includeChildren;
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
return DecorationSet.create(doc, decorations);
|
|
74
|
-
},
|
|
75
|
-
},
|
|
76
|
-
}),
|
|
77
|
-
];
|
|
78
|
-
},
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
export { placeholderGenerator };
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import isPlainObject from "lodash.isplainobject";
|
|
2
|
-
|
|
3
|
-
export const placeholderGenerator = (placeholder) => {
|
|
4
|
-
const type = typeof placeholder;
|
|
5
|
-
|
|
6
|
-
if (type === "string" || type === "funtion") {
|
|
7
|
-
return placeholder;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
if (isPlainObject(placeholder)) {
|
|
11
|
-
return ({ node }) => {
|
|
12
|
-
const { name } = node.type;
|
|
13
|
-
return placeholder[name];
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
return "";
|
|
18
|
-
};
|