@bigbinary/neeto-editor 0.1.17 → 0.2.0
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 +16 -14
- package/package.json +12 -1
- package/src/App.js +5 -28
- package/src/Common/Avatar.js +168 -0
- package/src/Common/CodeBlock.js +11 -0
- package/src/Common/Description.js +12 -0
- package/src/Common/Dropdown/index.js +118 -0
- package/src/Common/Heading.js +13 -0
- package/src/Common/HighlightText.js +7 -0
- package/src/Common/Icons/HashtagFilled.js +59 -0
- package/src/Common/Icons/TextColor.js +35 -0
- package/src/Common/Icons/index.js +2 -0
- package/src/Common/ListItems.js +17 -0
- package/src/Editor/CustomExtensions/BubbleMenu/index.js +33 -26
- package/src/Editor/CustomExtensions/Embeds.js +5 -3
- package/src/Editor/CustomExtensions/FixedMenu/FontSizeOption.js +32 -0
- package/src/Editor/CustomExtensions/FixedMenu/TextColorOption.js +29 -0
- package/src/Editor/CustomExtensions/FixedMenu/constants.js +3 -0
- package/src/Editor/CustomExtensions/FixedMenu/index.js +186 -0
- package/src/Editor/CustomExtensions/Mention/ExtensionConfig.js +67 -0
- package/src/Editor/CustomExtensions/Mention/MentionList.js +96 -0
- package/src/Editor/CustomExtensions/Mention/helpers.js +23 -0
- package/src/Editor/CustomExtensions/Placeholder/ExtensionConfig.js +81 -0
- package/src/Editor/CustomExtensions/Placeholder/helpers.js +18 -0
- package/src/Editor/CustomExtensions/SlashCommands/Commands.js +5 -10
- package/src/Editor/CustomExtensions/SlashCommands/CommandsList.js +15 -16
- package/src/Editor/CustomExtensions/SlashCommands/ExtensionConfig.js +191 -150
- package/src/Editor/CustomExtensions/Variable/ExtensionConfig.js +208 -0
- package/src/Editor/CustomExtensions/Variable/VariableList.js +45 -0
- package/src/Editor/CustomExtensions/Variable/VariableSuggestion.js +20 -0
- package/src/Editor/CustomExtensions/Variable/helpers.js +31 -0
- package/src/Editor/CustomExtensions/Variable/index.js +35 -0
- package/src/Editor/CustomExtensions/useCustomExtensions.js +87 -0
- package/src/Editor/index.js +42 -37
- package/src/examples/constants.js +95 -0
- package/src/examples/index.js +186 -0
- package/src/hooks/useOutsideClick.js +19 -0
- package/src/index.scss +27 -12
- package/src/styles/abstracts/_mixins.scss +20 -0
- package/src/styles/abstracts/_neeto-ui-variables.scss +36 -0
- package/src/styles/abstracts/_variables.scss +10 -0
- package/src/styles/components/_avatar.scss +105 -0
- package/src/styles/components/_codeblock.scss +16 -0
- package/src/{Editor/styles/CommandsList.scss → styles/components/_command-list.scss} +12 -1
- package/src/styles/components/_dropdown.scss +69 -0
- package/src/styles/components/_editor-variables.scss +12 -0
- package/src/{Editor/styles/EditorStyles.scss → styles/components/_editor.scss} +29 -7
- package/src/styles/components/_fixed-menu.scss +13 -0
- package/src/utils/common.js +13 -0
- package/public/logo192.png +0 -0
- package/public/logo512.png +0 -0
- package/src/logo.svg +0 -1
|
@@ -1,164 +1,205 @@
|
|
|
1
1
|
import sharedState from "../../sharedState";
|
|
2
|
-
import
|
|
2
|
+
import { Extension } from "@tiptap/core";
|
|
3
3
|
import CommandsList from "./CommandsList";
|
|
4
|
+
import { PluginKey } from "prosemirror-state";
|
|
5
|
+
import Suggestion from "@tiptap/suggestion";
|
|
4
6
|
import tippy from "tippy.js";
|
|
5
7
|
import { ReactRenderer } from "@tiptap/react";
|
|
6
8
|
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
} from "
|
|
9
|
+
Paragraph,
|
|
10
|
+
TextH1,
|
|
11
|
+
TextH2,
|
|
12
|
+
ListDot,
|
|
13
|
+
ListNumber,
|
|
14
|
+
Blockquote,
|
|
15
|
+
Image,
|
|
16
|
+
Video,
|
|
17
|
+
Code,
|
|
18
|
+
} from "@bigbinary/neeto-icons";
|
|
17
19
|
|
|
18
|
-
export
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
.focus()
|
|
33
|
-
.deleteRange(range)
|
|
34
|
-
.setNode("paragraph")
|
|
35
|
-
.run();
|
|
36
|
-
},
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
title: "H1",
|
|
40
|
-
description: "Add a big heading",
|
|
41
|
-
Icon: BsTypeH1,
|
|
42
|
-
command: ({ editor, range }) => {
|
|
43
|
-
editor
|
|
44
|
-
.chain()
|
|
45
|
-
.focus()
|
|
46
|
-
.deleteRange(range)
|
|
47
|
-
.setNode("heading", { level: 1 })
|
|
48
|
-
.run();
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
title: "H2",
|
|
53
|
-
description: "Add a sub-heading",
|
|
54
|
-
Icon: BsTypeH2,
|
|
55
|
-
command: ({ editor, range }) => {
|
|
56
|
-
editor
|
|
57
|
-
.chain()
|
|
58
|
-
.focus()
|
|
59
|
-
.deleteRange(range)
|
|
60
|
-
.setNode("heading", { level: 2 })
|
|
61
|
-
.run();
|
|
62
|
-
},
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
title: "Numbered list",
|
|
66
|
-
description: "Add a list with numbering",
|
|
67
|
-
Icon: BsListOl,
|
|
68
|
-
command: ({ editor, range }) => {
|
|
69
|
-
editor.chain().focus().deleteRange(range).toggleOrderedList().run();
|
|
70
|
-
},
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
title: "Bulleted list",
|
|
74
|
-
description: "Add an list bullets",
|
|
75
|
-
Icon: BsListUl,
|
|
76
|
-
command: ({ editor, range }) => {
|
|
77
|
-
editor.chain().focus().deleteRange(range).toggleBulletList().run();
|
|
78
|
-
},
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
title: "Blockquote",
|
|
82
|
-
description: "Add a quote",
|
|
83
|
-
Icon: BsBlockquoteLeft,
|
|
84
|
-
command: ({ editor, range }) => {
|
|
85
|
-
editor.chain().focus().deleteRange(range).toggleBlockquote().run();
|
|
86
|
-
},
|
|
87
|
-
},
|
|
88
|
-
{
|
|
89
|
-
title: "Image",
|
|
90
|
-
description: "Add an image",
|
|
91
|
-
Icon: BsCardImage,
|
|
92
|
-
command: ({ editor, range }) => {
|
|
93
|
-
sharedState.showImageUpload = true;
|
|
94
|
-
sharedState.range = range;
|
|
95
|
-
editor.chain().focus().deleteRange(range).run();
|
|
96
|
-
},
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
title: "Youtube/Vimeo",
|
|
100
|
-
description: "Embed a video from major services",
|
|
101
|
-
Icon: BsFillCameraVideoFill,
|
|
102
|
-
command: ({ editor, range }) => {
|
|
103
|
-
const embedURL = prompt("Please enter Youtube/Vimeo embed URL");
|
|
104
|
-
editor
|
|
105
|
-
.chain()
|
|
106
|
-
.focus()
|
|
107
|
-
.deleteRange(range)
|
|
108
|
-
.setExternalVideo({ src: embedURL })
|
|
109
|
-
.run();
|
|
110
|
-
},
|
|
20
|
+
export const CommandsPluginKey = new PluginKey("commands");
|
|
21
|
+
|
|
22
|
+
export default Extension.create({
|
|
23
|
+
addOptions() {
|
|
24
|
+
return {
|
|
25
|
+
HTMLAttributes: {
|
|
26
|
+
class: "commands",
|
|
27
|
+
},
|
|
28
|
+
suggestion: {
|
|
29
|
+
char: "/",
|
|
30
|
+
startOfLine: false,
|
|
31
|
+
pluginKey: CommandsPluginKey,
|
|
32
|
+
command: ({ editor, range, props }) => {
|
|
33
|
+
props.command({ editor, range });
|
|
111
34
|
},
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
35
|
+
|
|
36
|
+
items: () => {
|
|
37
|
+
return [
|
|
38
|
+
{
|
|
39
|
+
title: "Paragraph",
|
|
40
|
+
description: "Add a plain text block",
|
|
41
|
+
Icon: Paragraph,
|
|
42
|
+
command: ({ editor, range }) => {
|
|
43
|
+
editor
|
|
44
|
+
.chain()
|
|
45
|
+
.focus()
|
|
46
|
+
.deleteRange(range)
|
|
47
|
+
.setNode("paragraph")
|
|
48
|
+
.run();
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
title: "H1",
|
|
53
|
+
description: "Add a big heading",
|
|
54
|
+
Icon: TextH1,
|
|
55
|
+
command: ({ editor, range }) => {
|
|
56
|
+
editor
|
|
57
|
+
.chain()
|
|
58
|
+
.focus()
|
|
59
|
+
.deleteRange(range)
|
|
60
|
+
.setNode("heading", { level: 1 })
|
|
61
|
+
.run();
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
title: "H2",
|
|
66
|
+
description: "Add a sub-heading",
|
|
67
|
+
Icon: TextH2,
|
|
68
|
+
command: ({ editor, range }) => {
|
|
69
|
+
editor
|
|
70
|
+
.chain()
|
|
71
|
+
.focus()
|
|
72
|
+
.deleteRange(range)
|
|
73
|
+
.setNode("heading", { level: 2 })
|
|
74
|
+
.run();
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
title: "Numbered list",
|
|
79
|
+
description: "Add a list with numbering",
|
|
80
|
+
Icon: ListNumber,
|
|
81
|
+
command: ({ editor, range }) => {
|
|
82
|
+
editor
|
|
83
|
+
.chain()
|
|
84
|
+
.focus()
|
|
85
|
+
.deleteRange(range)
|
|
86
|
+
.toggleOrderedList()
|
|
87
|
+
.run();
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
title: "Bulleted list",
|
|
92
|
+
description: "Add an list bullets",
|
|
93
|
+
Icon: ListDot,
|
|
94
|
+
command: ({ editor, range }) => {
|
|
95
|
+
editor
|
|
96
|
+
.chain()
|
|
97
|
+
.focus()
|
|
98
|
+
.deleteRange(range)
|
|
99
|
+
.toggleBulletList()
|
|
100
|
+
.run();
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
title: "Blockquote",
|
|
105
|
+
description: "Add a quote",
|
|
106
|
+
Icon: Blockquote,
|
|
107
|
+
command: ({ editor, range }) => {
|
|
108
|
+
editor
|
|
109
|
+
.chain()
|
|
110
|
+
.focus()
|
|
111
|
+
.deleteRange(range)
|
|
112
|
+
.toggleBlockquote()
|
|
113
|
+
.run();
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
title: "Image",
|
|
118
|
+
description: "Add an image",
|
|
119
|
+
Icon: Image,
|
|
120
|
+
command: ({ editor, range }) => {
|
|
121
|
+
sharedState.showImageUpload = true;
|
|
122
|
+
sharedState.range = range;
|
|
123
|
+
editor.chain().focus().deleteRange(range).run();
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
title: "Youtube/Vimeo",
|
|
128
|
+
description: "Embed a video from major services",
|
|
129
|
+
Icon: Video,
|
|
130
|
+
command: ({ editor, range }) => {
|
|
131
|
+
const embedURL = prompt("Please enter Youtube/Vimeo embed URL");
|
|
132
|
+
editor
|
|
133
|
+
.chain()
|
|
134
|
+
.focus()
|
|
135
|
+
.deleteRange(range)
|
|
136
|
+
.setExternalVideo({ src: embedURL })
|
|
137
|
+
.run();
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
title: "Code block",
|
|
142
|
+
description: "Add a code block with syntax highlighting",
|
|
143
|
+
Icon: Code,
|
|
144
|
+
command: ({ editor, range }) => {
|
|
145
|
+
editor
|
|
146
|
+
.chain()
|
|
147
|
+
.focus()
|
|
148
|
+
.deleteRange(range)
|
|
149
|
+
.toggleCodeBlock()
|
|
150
|
+
.run();
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
];
|
|
119
154
|
},
|
|
120
|
-
]
|
|
121
|
-
.filter((item) =>
|
|
122
|
-
item.title.toLowerCase().startsWith(query.toLowerCase())
|
|
123
|
-
)
|
|
124
|
-
.slice(0, 10);
|
|
125
|
-
},
|
|
126
|
-
render: () => {
|
|
127
|
-
let reactRenderer;
|
|
128
|
-
let popup;
|
|
129
155
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
props,
|
|
134
|
-
editor: props.editor,
|
|
135
|
-
});
|
|
156
|
+
render: () => {
|
|
157
|
+
let reactRenderer;
|
|
158
|
+
let popup;
|
|
136
159
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
trigger: "manual",
|
|
144
|
-
placement: "bottom-start",
|
|
145
|
-
});
|
|
146
|
-
},
|
|
147
|
-
onUpdate(props) {
|
|
148
|
-
reactRenderer.updateProps(props);
|
|
160
|
+
return {
|
|
161
|
+
onStart: (props) => {
|
|
162
|
+
reactRenderer = new ReactRenderer(CommandsList, {
|
|
163
|
+
props,
|
|
164
|
+
editor: props.editor,
|
|
165
|
+
});
|
|
149
166
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
167
|
+
popup = tippy("body", {
|
|
168
|
+
getReferenceClientRect: props.clientRect,
|
|
169
|
+
appendTo: () => document.body,
|
|
170
|
+
content: reactRenderer.element,
|
|
171
|
+
showOnCreate: true,
|
|
172
|
+
interactive: true,
|
|
173
|
+
trigger: "manual",
|
|
174
|
+
placement: "bottom-start",
|
|
175
|
+
});
|
|
176
|
+
},
|
|
177
|
+
onUpdate(props) {
|
|
178
|
+
reactRenderer.updateProps(props);
|
|
179
|
+
|
|
180
|
+
popup[0].setProps({
|
|
181
|
+
getReferenceClientRect: props.clientRect,
|
|
182
|
+
});
|
|
183
|
+
},
|
|
184
|
+
onKeyDown(props) {
|
|
185
|
+
return reactRenderer.ref?.onKeyDown(props);
|
|
186
|
+
},
|
|
187
|
+
onExit() {
|
|
188
|
+
popup[0].destroy();
|
|
189
|
+
reactRenderer.destroy();
|
|
190
|
+
},
|
|
191
|
+
};
|
|
160
192
|
},
|
|
161
|
-
}
|
|
162
|
-
}
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
addProseMirrorPlugins() {
|
|
198
|
+
return [
|
|
199
|
+
Suggestion({
|
|
200
|
+
editor: this.editor,
|
|
201
|
+
...this.options.suggestion,
|
|
202
|
+
}),
|
|
203
|
+
];
|
|
163
204
|
},
|
|
164
205
|
});
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import tippy from "tippy.js";
|
|
2
|
+
import Suggestion from "@tiptap/suggestion";
|
|
3
|
+
import { Node, mergeAttributes } from "@tiptap/core";
|
|
4
|
+
import { ReactRenderer } from "@tiptap/react";
|
|
5
|
+
import { PluginKey } from "prosemirror-state";
|
|
6
|
+
|
|
7
|
+
import VariableSuggestion from "./VariableSuggestion";
|
|
8
|
+
|
|
9
|
+
export const VariablePluginKey = new PluginKey("variables");
|
|
10
|
+
|
|
11
|
+
const Variable = Node.create({
|
|
12
|
+
name: "variable",
|
|
13
|
+
|
|
14
|
+
addOptions() {
|
|
15
|
+
return {
|
|
16
|
+
HTMLAttributes: { class: "variable__text" },
|
|
17
|
+
charOpen: "{{",
|
|
18
|
+
charClose: "}}",
|
|
19
|
+
renderLabel({ options, node }) {
|
|
20
|
+
return `${options.charOpen}${node.attrs.label || node.attrs.id}${
|
|
21
|
+
options.charClose
|
|
22
|
+
}`;
|
|
23
|
+
},
|
|
24
|
+
suggestion: {},
|
|
25
|
+
};
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
group: "inline",
|
|
29
|
+
|
|
30
|
+
inline: true,
|
|
31
|
+
|
|
32
|
+
selectable: false,
|
|
33
|
+
|
|
34
|
+
atom: true,
|
|
35
|
+
|
|
36
|
+
addAttributes() {
|
|
37
|
+
return {
|
|
38
|
+
id: {
|
|
39
|
+
default: null,
|
|
40
|
+
parseHTML: (element) => element.getAttribute("data-id"),
|
|
41
|
+
renderHTML: (attributes) => {
|
|
42
|
+
if (!attributes.id) {
|
|
43
|
+
return {};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
"data-id": attributes.id,
|
|
48
|
+
};
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
label: {
|
|
53
|
+
default: null,
|
|
54
|
+
parseHTML: (element) => element.getAttribute("data-label"),
|
|
55
|
+
renderHTML: (attributes) => {
|
|
56
|
+
if (!attributes.label) {
|
|
57
|
+
return {};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
"data-label": attributes.label,
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
parseHTML() {
|
|
69
|
+
return [
|
|
70
|
+
{
|
|
71
|
+
tag: "span[data-variable]",
|
|
72
|
+
},
|
|
73
|
+
];
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
renderHTML({ node, HTMLAttributes }) {
|
|
77
|
+
return [
|
|
78
|
+
"span",
|
|
79
|
+
mergeAttributes(
|
|
80
|
+
{ "data-variable": "" },
|
|
81
|
+
this.options.HTMLAttributes,
|
|
82
|
+
HTMLAttributes
|
|
83
|
+
),
|
|
84
|
+
this.options.renderLabel({
|
|
85
|
+
options: this.options,
|
|
86
|
+
node,
|
|
87
|
+
}),
|
|
88
|
+
];
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
renderText({ node }) {
|
|
92
|
+
return this.options.renderLabel({
|
|
93
|
+
options: this.options,
|
|
94
|
+
node,
|
|
95
|
+
});
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
addCommands() {
|
|
99
|
+
return {
|
|
100
|
+
setVariable:
|
|
101
|
+
(attributes) =>
|
|
102
|
+
({ chain }) => {
|
|
103
|
+
chain()
|
|
104
|
+
.focus()
|
|
105
|
+
.insertContent([
|
|
106
|
+
{
|
|
107
|
+
type: this.name,
|
|
108
|
+
attrs: attributes,
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
type: "text",
|
|
112
|
+
text: " ",
|
|
113
|
+
},
|
|
114
|
+
])
|
|
115
|
+
.run();
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
addKeyboardShortcuts() {
|
|
121
|
+
return {
|
|
122
|
+
Backspace: () =>
|
|
123
|
+
this.editor.commands.command(({ tr, state }) => {
|
|
124
|
+
let isVariable = false;
|
|
125
|
+
const { selection } = state;
|
|
126
|
+
const { empty, anchor } = selection;
|
|
127
|
+
|
|
128
|
+
if (!empty) {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
state.doc.nodesBetween(anchor - 1, anchor, (node, pos) => {
|
|
133
|
+
if (node.type.name === this.name) {
|
|
134
|
+
isVariable = true;
|
|
135
|
+
tr.insertText("", pos, pos + node.nodeSize);
|
|
136
|
+
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
return isVariable;
|
|
142
|
+
}),
|
|
143
|
+
};
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
addProseMirrorPlugins() {
|
|
147
|
+
return [
|
|
148
|
+
Suggestion({
|
|
149
|
+
editor: this.editor,
|
|
150
|
+
...this.options.suggestion,
|
|
151
|
+
}),
|
|
152
|
+
];
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const suggestionConfig = {
|
|
157
|
+
char: "{{",
|
|
158
|
+
startOfLine: false,
|
|
159
|
+
pluginKey: VariablePluginKey,
|
|
160
|
+
command: ({ editor, range, props }) => {
|
|
161
|
+
editor.chain().focus().deleteRange(range).setVariable(props).run();
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
items: () => [],
|
|
165
|
+
render: () => {
|
|
166
|
+
let reactRenderer;
|
|
167
|
+
let popup;
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
onStart: (props) => {
|
|
171
|
+
reactRenderer = new ReactRenderer(VariableSuggestion, {
|
|
172
|
+
props,
|
|
173
|
+
editor: props.editor,
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
popup = tippy("body", {
|
|
177
|
+
getReferenceClientRect: props.clientRect,
|
|
178
|
+
appendTo: () => document.body,
|
|
179
|
+
content: reactRenderer.element,
|
|
180
|
+
showOnCreate: true,
|
|
181
|
+
interactive: true,
|
|
182
|
+
trigger: "manual",
|
|
183
|
+
placement: "bottom-start",
|
|
184
|
+
});
|
|
185
|
+
},
|
|
186
|
+
onUpdate(props) {
|
|
187
|
+
reactRenderer.updateProps(props);
|
|
188
|
+
|
|
189
|
+
popup[0].setProps({
|
|
190
|
+
getReferenceClientRect: props.clientRect,
|
|
191
|
+
});
|
|
192
|
+
},
|
|
193
|
+
|
|
194
|
+
onExit() {
|
|
195
|
+
popup[0].destroy();
|
|
196
|
+
reactRenderer.destroy();
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
export default {
|
|
203
|
+
configure: ({ suggestion = {} }) => {
|
|
204
|
+
return Variable.configure({
|
|
205
|
+
suggestion: { ...suggestionConfig, ...suggestion },
|
|
206
|
+
});
|
|
207
|
+
},
|
|
208
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
import { parseVariables } from "./helpers";
|
|
4
|
+
|
|
5
|
+
const VariableList = ({ onClickVariable, variables }) => {
|
|
6
|
+
const parsedVariables = parseVariables(variables);
|
|
7
|
+
|
|
8
|
+
if (!(variables && variables.length)) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<div className="p-3 bg-white">
|
|
14
|
+
{parsedVariables.map(({ label, variables }) => (
|
|
15
|
+
<VariableCategory
|
|
16
|
+
key={label}
|
|
17
|
+
title={label}
|
|
18
|
+
variables={variables}
|
|
19
|
+
onClickItem={onClickVariable}
|
|
20
|
+
/>
|
|
21
|
+
))}
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default VariableList;
|
|
27
|
+
|
|
28
|
+
const VariableCategory = ({ title, variables, onClickItem }) => {
|
|
29
|
+
return (
|
|
30
|
+
<div className="my-3">
|
|
31
|
+
{title ? <p className="text-xs font-semibold">{title}</p> : null}
|
|
32
|
+
<div className="mt-1 space-x-2">
|
|
33
|
+
{variables.map((item) => (
|
|
34
|
+
<button
|
|
35
|
+
onClick={() => onClickItem(item)}
|
|
36
|
+
key={`${item.label}--${item.value}`}
|
|
37
|
+
className="px-2 py-1 text-xs text-white bg-blue-400 rounded-sm"
|
|
38
|
+
>
|
|
39
|
+
{item.label}
|
|
40
|
+
</button>
|
|
41
|
+
))}
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
);
|
|
45
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
import VariableList from "./VariableList";
|
|
4
|
+
|
|
5
|
+
const VariableSuggestion = ({ items, command }) => {
|
|
6
|
+
return (
|
|
7
|
+
<div className="shadow-md">
|
|
8
|
+
<VariableList
|
|
9
|
+
variables={items}
|
|
10
|
+
onClickVariable={(variable) => {
|
|
11
|
+
const { category_key, key } = variable;
|
|
12
|
+
const variableName = category_key ? `${category_key}.${key}` : key;
|
|
13
|
+
command({ label: variableName, id: variableName });
|
|
14
|
+
}}
|
|
15
|
+
/>
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default VariableSuggestion;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/* Helper function that accepts an array of variables and injects category to each item in the group
|
|
2
|
+
And also performs grouping of indvidual variables under common category as 'Others'. */
|
|
3
|
+
|
|
4
|
+
export const parseVariables = (variableArr = []) => {
|
|
5
|
+
const uncategorized = [];
|
|
6
|
+
const groupedVariables = [];
|
|
7
|
+
variableArr.forEach((variable) => {
|
|
8
|
+
const { category_key, category_label, variables } = variable;
|
|
9
|
+
if (category_key && variables) {
|
|
10
|
+
const parsedVariables = variables.map((categoryVariable) => ({
|
|
11
|
+
...categoryVariable,
|
|
12
|
+
category_key,
|
|
13
|
+
}));
|
|
14
|
+
groupedVariables.push({
|
|
15
|
+
label: category_label,
|
|
16
|
+
variables: parsedVariables,
|
|
17
|
+
});
|
|
18
|
+
} else uncategorized.push(variable);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
/* If there are other categorised variables already present, group all uncategorized variables under title 'Others'.
|
|
22
|
+
otherwise, if all available variables are uncategorised, do not render 'Others' title */
|
|
23
|
+
|
|
24
|
+
if (uncategorized.length) {
|
|
25
|
+
groupedVariables.push({
|
|
26
|
+
label: groupedVariables.length ? "Others" : null,
|
|
27
|
+
variables: uncategorized,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
return groupedVariables;
|
|
31
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
import VariableList from "./VariableList";
|
|
4
|
+
import Dropdown from "../../../Common/Dropdown";
|
|
5
|
+
import { HashtagFilled } from "../../../Common/Icons";
|
|
6
|
+
|
|
7
|
+
import { MENU_ICON_SIZE } from "../FixedMenu/constants";
|
|
8
|
+
|
|
9
|
+
const Variables = ({ editor, variables }) => {
|
|
10
|
+
const handleClickItem = (item) => {
|
|
11
|
+
const { category_key, key } = item;
|
|
12
|
+
const variableName = category_key ? `${category_key}.${key}` : key;
|
|
13
|
+
editor.chain().focus().setVariable({ label: variableName }).run();
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
if (!(variables && variables.length)) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<Dropdown
|
|
22
|
+
customTarget={() => (
|
|
23
|
+
<button className="relative p-3 editor-fixed-menu--item variable-selection-popup">
|
|
24
|
+
<HashtagFilled size={MENU_ICON_SIZE} />
|
|
25
|
+
</button>
|
|
26
|
+
)}
|
|
27
|
+
>
|
|
28
|
+
<div className="items-container">
|
|
29
|
+
<VariableList onClickVariable={handleClickItem} variables={variables} />
|
|
30
|
+
</div>
|
|
31
|
+
</Dropdown>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export default Variables;
|