@bigbinary/neeto-editor 0.2.0 → 0.2.4
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 +28 -7
- package/package.json +7 -5
- package/tailwind.config.js +13 -9
- package/webpack.config.js +9 -1
- package/webpack.dev.config.js +9 -1
- package/public/favicon.ico +0 -0
- package/public/index.html +0 -28
- package/public/robots.txt +0 -3
- package/src/App.js +0 -8
- package/src/Common/Avatar.js +0 -168
- package/src/Common/CodeBlock.js +0 -11
- package/src/Common/Description.js +0 -12
- package/src/Common/Dropdown/index.js +0 -118
- 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/ListItems.js +0 -17
- 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 -186
- package/src/Editor/CustomExtensions/Image/ExtensionConfig.js +0 -47
- package/src/Editor/CustomExtensions/Image/Uploader.js +0 -41
- package/src/Editor/CustomExtensions/Mention/ExtensionConfig.js +0 -67
- 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 -205
- 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 -87
- package/src/Editor/index.js +0 -92
- package/src/Editor/sharedState.js +0 -8
- package/src/examples/constants.js +0 -95
- package/src/examples/index.js +0 -186
- package/src/hooks/useOutsideClick.js +0 -19
- package/src/index.js +0 -10
- package/src/index.scss +0 -41
- package/src/styles/abstracts/_mixins.scss +0 -20
- package/src/styles/abstracts/_neeto-ui-variables.scss +0 -36
- package/src/styles/abstracts/_variables.scss +0 -10
- package/src/styles/components/_avatar.scss +0 -105
- 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 -98
- package/src/styles/components/_fixed-menu.scss +0 -13
- package/src/utils/common.js +0 -13
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import classnames from "classnames";
|
|
3
|
-
import { BubbleMenu } from "@tiptap/react";
|
|
4
|
-
import {
|
|
5
|
-
TextBold,
|
|
6
|
-
TextItalic,
|
|
7
|
-
TextCross,
|
|
8
|
-
Link,
|
|
9
|
-
Code,
|
|
10
|
-
Highlight,
|
|
11
|
-
} from "@bigbinary/neeto-icons";
|
|
12
|
-
import { roundArrow } from "tippy.js";
|
|
13
|
-
import "tippy.js/dist/svg-arrow.css";
|
|
14
|
-
|
|
15
|
-
export default function index({ editor, formatterOptions }) {
|
|
16
|
-
if (!editor) {
|
|
17
|
-
return null;
|
|
18
|
-
}
|
|
19
|
-
const options = [
|
|
20
|
-
{
|
|
21
|
-
Icon: TextBold,
|
|
22
|
-
command: () => editor.chain().focus().toggleBold().run(),
|
|
23
|
-
active: editor.isActive("bold"),
|
|
24
|
-
optionName: "bold",
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
Icon: TextItalic,
|
|
28
|
-
command: () => editor.chain().focus().toggleItalic().run(),
|
|
29
|
-
active: editor.isActive("italic"),
|
|
30
|
-
optionName: "italic",
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
Icon: TextCross,
|
|
34
|
-
command: () => editor.chain().focus().toggleStrike().run(),
|
|
35
|
-
active: editor.isActive("strike"),
|
|
36
|
-
optionName: "strike",
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
Icon: Link,
|
|
40
|
-
command: () => {
|
|
41
|
-
if (editor.isActive("link")) {
|
|
42
|
-
editor.chain().focus().unsetLink().run();
|
|
43
|
-
} else {
|
|
44
|
-
const url = window.prompt("Please enter your URL");
|
|
45
|
-
editor.chain().focus().setLink({ href: url }).run();
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
|
-
active: editor.isActive("link"),
|
|
49
|
-
optionName: "link",
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
Icon: Code,
|
|
53
|
-
command: () => editor.chain().focus().toggleCode().run(),
|
|
54
|
-
active: editor.isActive("code"),
|
|
55
|
-
optionName: "code",
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
Icon: Highlight,
|
|
59
|
-
command: () => editor.chain().focus().toggleHighlight().run(),
|
|
60
|
-
active: editor.isActive("highlight"),
|
|
61
|
-
optionName: "highlight",
|
|
62
|
-
},
|
|
63
|
-
];
|
|
64
|
-
return (
|
|
65
|
-
<BubbleMenu
|
|
66
|
-
editor={editor}
|
|
67
|
-
tippyOptions={{ arrow: roundArrow }}
|
|
68
|
-
className="relative flex rounded shadow editor-command-list--root"
|
|
69
|
-
>
|
|
70
|
-
{options
|
|
71
|
-
.filter(({ optionName }) => formatterOptions.includes(optionName))
|
|
72
|
-
.map((option) => (
|
|
73
|
-
<Option {...option} key={option.optionName} />
|
|
74
|
-
))}
|
|
75
|
-
</BubbleMenu>
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const Option = ({ Icon, command, active, iconSize }) => (
|
|
80
|
-
<div
|
|
81
|
-
className={classnames(
|
|
82
|
-
"p-3 cursor-pointer editor-command-list--item transition-colors rounded",
|
|
83
|
-
{
|
|
84
|
-
"text-gray-400": !active,
|
|
85
|
-
"text-white": active,
|
|
86
|
-
}
|
|
87
|
-
)}
|
|
88
|
-
onClick={command}
|
|
89
|
-
>
|
|
90
|
-
<Icon size={iconSize || 24} />
|
|
91
|
-
</div>
|
|
92
|
-
);
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight";
|
|
2
|
-
import { ReactNodeViewRenderer } from "@tiptap/react";
|
|
3
|
-
import CodeBlockComponent from "./CodeBlockComponent";
|
|
4
|
-
import lowlight from "lowlight";
|
|
5
|
-
|
|
6
|
-
export default CodeBlockLowlight.extend({
|
|
7
|
-
addNodeView() {
|
|
8
|
-
return new ReactNodeViewRenderer(CodeBlockComponent);
|
|
9
|
-
},
|
|
10
|
-
}).configure({ lowlight });
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { Node, mergeAttributes } from "@tiptap/core";
|
|
2
|
-
|
|
3
|
-
export default Node.create({
|
|
4
|
-
name: "external-video",
|
|
5
|
-
|
|
6
|
-
addOptions() {
|
|
7
|
-
return {
|
|
8
|
-
inline: false,
|
|
9
|
-
HTMLAttributes: {},
|
|
10
|
-
};
|
|
11
|
-
},
|
|
12
|
-
|
|
13
|
-
inline() {
|
|
14
|
-
return this.options.inline;
|
|
15
|
-
},
|
|
16
|
-
|
|
17
|
-
group() {
|
|
18
|
-
return this.options.inline ? "inline" : "block";
|
|
19
|
-
},
|
|
20
|
-
|
|
21
|
-
draggable: true,
|
|
22
|
-
|
|
23
|
-
addAttributes() {
|
|
24
|
-
return {
|
|
25
|
-
src: {
|
|
26
|
-
default: null,
|
|
27
|
-
},
|
|
28
|
-
title: {
|
|
29
|
-
default: null,
|
|
30
|
-
},
|
|
31
|
-
frameborder: {
|
|
32
|
-
default: "0",
|
|
33
|
-
},
|
|
34
|
-
allow: {
|
|
35
|
-
default:
|
|
36
|
-
"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture",
|
|
37
|
-
},
|
|
38
|
-
allowfullscreen: {
|
|
39
|
-
default: "allowfullscreen",
|
|
40
|
-
},
|
|
41
|
-
};
|
|
42
|
-
},
|
|
43
|
-
|
|
44
|
-
parseHTML() {
|
|
45
|
-
return [
|
|
46
|
-
{
|
|
47
|
-
tag: "iframe[src]",
|
|
48
|
-
},
|
|
49
|
-
];
|
|
50
|
-
},
|
|
51
|
-
|
|
52
|
-
renderHTML({ HTMLAttributes }) {
|
|
53
|
-
return [
|
|
54
|
-
"div",
|
|
55
|
-
{ class: "video-wrapper" },
|
|
56
|
-
["iframe", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)],
|
|
57
|
-
];
|
|
58
|
-
},
|
|
59
|
-
|
|
60
|
-
addCommands() {
|
|
61
|
-
return {
|
|
62
|
-
setExternalVideo:
|
|
63
|
-
(options) =>
|
|
64
|
-
({ commands }) => {
|
|
65
|
-
return commands.insertContent({
|
|
66
|
-
type: this.name,
|
|
67
|
-
attrs: options,
|
|
68
|
-
});
|
|
69
|
-
},
|
|
70
|
-
};
|
|
71
|
-
},
|
|
72
|
-
});
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
|
|
3
|
-
import { TextSize } from "@bigbinary/neeto-icons";
|
|
4
|
-
|
|
5
|
-
import { ICON_COLOR_ACTIVE, MENU_ICON_SIZE } from "./constants";
|
|
6
|
-
import Dropdown from "../../../Common/Dropdown";
|
|
7
|
-
|
|
8
|
-
const FontSizeOption = ({ onChange }) => {
|
|
9
|
-
const options = [
|
|
10
|
-
{ label: <h2 className="text-xl font-medium">Large</h2>, value: "large" },
|
|
11
|
-
{ label: <h3 className="text-lg">Medium</h3>, value: "medium" },
|
|
12
|
-
{ label: <span className="text-sm">Normal</span>, value: "normal" },
|
|
13
|
-
];
|
|
14
|
-
|
|
15
|
-
return (
|
|
16
|
-
<Dropdown
|
|
17
|
-
customTarget={() => (
|
|
18
|
-
<button className="p-3 transition-colors editor-fixed-menu--item">
|
|
19
|
-
<TextSize size={MENU_ICON_SIZE} color={ICON_COLOR_ACTIVE} />
|
|
20
|
-
</button>
|
|
21
|
-
)}
|
|
22
|
-
>
|
|
23
|
-
{options.map(({ label, className, value }) => (
|
|
24
|
-
<li className={className} onClick={() => onChange(value)}>
|
|
25
|
-
{label}
|
|
26
|
-
</li>
|
|
27
|
-
))}
|
|
28
|
-
</Dropdown>
|
|
29
|
-
);
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
export default FontSizeOption;
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import React, { useRef } from "react";
|
|
2
|
-
|
|
3
|
-
import { TextColor } from "../../../Common/Icons";
|
|
4
|
-
import { ICON_COLOR_ACTIVE, MENU_ICON_SIZE } from "./constants";
|
|
5
|
-
|
|
6
|
-
const TextColorOption = ({ color = "#000", onChange }) => {
|
|
7
|
-
const colorInputRef = useRef();
|
|
8
|
-
|
|
9
|
-
return (
|
|
10
|
-
<div
|
|
11
|
-
onClick={() => colorInputRef.current?.click()}
|
|
12
|
-
className="flex items-center justify-center p-3 transition-colors cursor-pointer editor-fixed-menu--item"
|
|
13
|
-
>
|
|
14
|
-
<TextColor
|
|
15
|
-
size={MENU_ICON_SIZE}
|
|
16
|
-
underlineColor={color}
|
|
17
|
-
color={ICON_COLOR_ACTIVE}
|
|
18
|
-
/>
|
|
19
|
-
<input
|
|
20
|
-
ref={colorInputRef}
|
|
21
|
-
type="color"
|
|
22
|
-
className="invisible w-0 h-0"
|
|
23
|
-
onChange={(event) => onChange && onChange(event.target.value)}
|
|
24
|
-
/>
|
|
25
|
-
</div>
|
|
26
|
-
);
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export default TextColorOption;
|
|
@@ -1,186 +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 sharedState from "../../sharedState";
|
|
22
|
-
import Variables from "../Variable";
|
|
23
|
-
|
|
24
|
-
const FixedMenu = ({ editor, variables }) => {
|
|
25
|
-
if (!editor) {
|
|
26
|
-
return null;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const fontStyleOptions = [
|
|
30
|
-
{
|
|
31
|
-
Icon: TextBold,
|
|
32
|
-
command: () => editor.chain().focus().toggleBold().run(),
|
|
33
|
-
active: editor.isActive("bold"),
|
|
34
|
-
optionName: "bold",
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
Icon: TextItalic,
|
|
38
|
-
command: () => editor.chain().focus().toggleItalic().run(),
|
|
39
|
-
active: editor.isActive("italic"),
|
|
40
|
-
optionName: "italic",
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
Icon: Underline,
|
|
44
|
-
command: () => editor.chain().focus().toggleUnderline().run(),
|
|
45
|
-
active: editor.isActive("underline"),
|
|
46
|
-
optionName: "underline",
|
|
47
|
-
size: 27,
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
Icon: TextCross,
|
|
51
|
-
command: () => editor.chain().focus().toggleStrike().run(),
|
|
52
|
-
active: editor.isActive("strike"),
|
|
53
|
-
optionName: "strike",
|
|
54
|
-
},
|
|
55
|
-
];
|
|
56
|
-
|
|
57
|
-
const blockStyleOptions = [
|
|
58
|
-
{
|
|
59
|
-
Icon: Link,
|
|
60
|
-
command: () => {
|
|
61
|
-
if (editor.isActive("link")) {
|
|
62
|
-
editor.chain().focus().unsetLink().run();
|
|
63
|
-
} else {
|
|
64
|
-
const url = window.prompt("Please enter your URL");
|
|
65
|
-
editor.chain().focus().setLink({ href: url }).run();
|
|
66
|
-
}
|
|
67
|
-
},
|
|
68
|
-
active: editor.isActive("link"),
|
|
69
|
-
optionName: "link",
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
Icon: Quote,
|
|
73
|
-
command: () => editor.chain().focus().toggleBlockquote().run(),
|
|
74
|
-
active: editor.isActive("blockquote"),
|
|
75
|
-
optionName: "block-quote",
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
Icon: Code,
|
|
79
|
-
command: () => editor.chain().focus().toggleCode().run(),
|
|
80
|
-
active: editor.isActive("code"),
|
|
81
|
-
optionName: "code",
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
Icon: Image,
|
|
85
|
-
command: ({ editor, range }) => {
|
|
86
|
-
sharedState.showImageUpload = true;
|
|
87
|
-
sharedState.range = range;
|
|
88
|
-
editor.chain().focus().deleteRange(range).run();
|
|
89
|
-
},
|
|
90
|
-
optionName: "image-upload",
|
|
91
|
-
},
|
|
92
|
-
];
|
|
93
|
-
|
|
94
|
-
const listStyleOptions = [
|
|
95
|
-
{
|
|
96
|
-
Icon: ListDot,
|
|
97
|
-
command: () => editor.chain().focus().toggleBulletList().run(),
|
|
98
|
-
active: editor.isActive("bulletList"),
|
|
99
|
-
optionName: "bullet-list",
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
Icon: ListNumber,
|
|
103
|
-
command: () => editor.chain().focus().toggleOrderedList().run(),
|
|
104
|
-
active: editor.isActive("orderedList"),
|
|
105
|
-
optionName: "ordered-list",
|
|
106
|
-
},
|
|
107
|
-
];
|
|
108
|
-
|
|
109
|
-
const editorOptions = [
|
|
110
|
-
{
|
|
111
|
-
Icon: Undo,
|
|
112
|
-
command: () => editor.chain().focus().undo().run(),
|
|
113
|
-
optionName: "undo",
|
|
114
|
-
disabled: !editor.can().undo(),
|
|
115
|
-
active: editor.can().undo(),
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
Icon: Redo,
|
|
119
|
-
command: () => editor.chain().focus().redo().run(),
|
|
120
|
-
optionName: "redo",
|
|
121
|
-
disabled: !editor.can().redo(),
|
|
122
|
-
active: editor.can().redo(),
|
|
123
|
-
},
|
|
124
|
-
];
|
|
125
|
-
|
|
126
|
-
const handleTextSizeChange = (value) => {
|
|
127
|
-
switch (value) {
|
|
128
|
-
case "large": {
|
|
129
|
-
editor.chain().focus().setHeading({ level: 2 }).run();
|
|
130
|
-
break;
|
|
131
|
-
}
|
|
132
|
-
case "medium": {
|
|
133
|
-
editor.chain().focus().setHeading({ level: 3 }).run();
|
|
134
|
-
break;
|
|
135
|
-
}
|
|
136
|
-
case "normal": {
|
|
137
|
-
editor.chain().focus().setParagraph().run();
|
|
138
|
-
break;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
const renderOptionButton = ({
|
|
144
|
-
Icon,
|
|
145
|
-
command,
|
|
146
|
-
active,
|
|
147
|
-
optionName,
|
|
148
|
-
disabled,
|
|
149
|
-
...rest
|
|
150
|
-
}) => (
|
|
151
|
-
<button
|
|
152
|
-
disabled={disabled}
|
|
153
|
-
onClick={command}
|
|
154
|
-
key={optionName}
|
|
155
|
-
className="p-3 transition-colors cursor-pointer editor-fixed-menu--item"
|
|
156
|
-
>
|
|
157
|
-
<Icon
|
|
158
|
-
color={active ? ICON_COLOR_ACTIVE : ICON_COLOR_INACTIVE}
|
|
159
|
-
{...rest}
|
|
160
|
-
/>
|
|
161
|
-
</button>
|
|
162
|
-
);
|
|
163
|
-
|
|
164
|
-
return (
|
|
165
|
-
<div className="flex items-center space-x-6 border-t border-l border-r editor-fixed-menu--root">
|
|
166
|
-
<div className="flex">
|
|
167
|
-
<TextColorOption
|
|
168
|
-
color={editor.getAttributes("textStyle").color}
|
|
169
|
-
onChange={(color) => editor.chain().focus().setColor(color).run()}
|
|
170
|
-
/>
|
|
171
|
-
<FontSizeOption onChange={handleTextSizeChange} />
|
|
172
|
-
{fontStyleOptions.map(renderOptionButton)}
|
|
173
|
-
</div>
|
|
174
|
-
{[blockStyleOptions, listStyleOptions, editorOptions].map(
|
|
175
|
-
(optionGroup) => (
|
|
176
|
-
<div className="flex">{optionGroup.map(renderOptionButton)}</div>
|
|
177
|
-
)
|
|
178
|
-
)}
|
|
179
|
-
<div className="flex justify-end flex-1">
|
|
180
|
-
<Variables editor={editor} variables={variables} />
|
|
181
|
-
</div>
|
|
182
|
-
</div>
|
|
183
|
-
);
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
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,41 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { view } from "@risingstack/react-easy-state";
|
|
3
|
-
import Uppy from "@uppy/core";
|
|
4
|
-
import { DashboardModal } from "@uppy/react";
|
|
5
|
-
import XHRUpload from "@uppy/xhr-upload";
|
|
6
|
-
import "@uppy/core/dist/style.css";
|
|
7
|
-
import "@uppy/dashboard/dist/style.css";
|
|
8
|
-
import sharedState from "../../sharedState";
|
|
9
|
-
|
|
10
|
-
const ImageUpload = ({ editor, imageUploadUrl }) => {
|
|
11
|
-
const uppy = new Uppy({
|
|
12
|
-
allowMultipleUploads: false,
|
|
13
|
-
autoProceed: true,
|
|
14
|
-
debug: true,
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
uppy.use(XHRUpload, {
|
|
18
|
-
endpoint: imageUploadUrl || "/api/v1/direct_uploads",
|
|
19
|
-
formData: true,
|
|
20
|
-
fieldName: "blob",
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
uppy.on("upload-success", (file, response) => {
|
|
24
|
-
const url = response.body.imageURL;
|
|
25
|
-
editor.chain().focus().setImage({ src: url }).run();
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
return (
|
|
29
|
-
<div>
|
|
30
|
-
<DashboardModal
|
|
31
|
-
uppy={uppy}
|
|
32
|
-
proudlyDisplayPoweredByUppy={false}
|
|
33
|
-
closeModalOnClickOutside
|
|
34
|
-
open={sharedState.showImageUpload}
|
|
35
|
-
onRequestClose={() => (sharedState.showImageUpload = false)}
|
|
36
|
-
/>
|
|
37
|
-
</div>
|
|
38
|
-
);
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
export default view(ImageUpload);
|
|
@@ -1,67 +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
|
-
console.log({ props });
|
|
17
|
-
reactRenderer = new ReactRenderer(MentionList, {
|
|
18
|
-
props,
|
|
19
|
-
editor: props.editor,
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
popup = tippy("body", {
|
|
23
|
-
getReferenceClientRect: props.clientRect,
|
|
24
|
-
appendTo: () => document.body,
|
|
25
|
-
content: reactRenderer.element,
|
|
26
|
-
showOnCreate: true,
|
|
27
|
-
interactive: true,
|
|
28
|
-
trigger: "manual",
|
|
29
|
-
placement: "bottom-start",
|
|
30
|
-
});
|
|
31
|
-
},
|
|
32
|
-
|
|
33
|
-
onUpdate(props) {
|
|
34
|
-
reactRenderer.updateProps(props);
|
|
35
|
-
|
|
36
|
-
popup[0].setProps({
|
|
37
|
-
getReferenceClientRect: props.clientRect,
|
|
38
|
-
});
|
|
39
|
-
},
|
|
40
|
-
|
|
41
|
-
onKeyDown(props) {
|
|
42
|
-
if (props.event.key === "Escape") {
|
|
43
|
-
popup[0].hide();
|
|
44
|
-
|
|
45
|
-
return true;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return reactRenderer.ref?.onKeyDown(props);
|
|
49
|
-
},
|
|
50
|
-
|
|
51
|
-
onExit() {
|
|
52
|
-
popup[0].destroy();
|
|
53
|
-
reactRenderer.destroy();
|
|
54
|
-
},
|
|
55
|
-
};
|
|
56
|
-
},
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
export default {
|
|
60
|
-
configure: ({ suggestion: suggestionConfig = {}, ...otherConfig }) =>
|
|
61
|
-
Mention.configure({
|
|
62
|
-
...otherConfig,
|
|
63
|
-
suggestion: { ...suggestion, ...suggestionConfig },
|
|
64
|
-
}),
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
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
|
-
}
|