@bigbinary/neeto-editor 0.1.15 → 0.2.1
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 +37 -14
- package/package.json +18 -4
- package/src/App.js +5 -28
- package/src/Common/Avatar.js +168 -0
- package/src/Common/Button.js +95 -0
- package/src/Common/CodeBlock.js +11 -0
- package/src/Common/Description.js +8 -0
- package/src/Common/Dropdown/index.js +122 -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/Input.js +70 -0
- package/src/Common/Label.js +45 -0
- package/src/Common/ListItems.js +17 -0
- package/src/Common/Modal.js +91 -0
- package/src/Common/Tab.js +79 -0
- package/src/Common/ToolTip.js +37 -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 +183 -0
- package/src/Editor/CustomExtensions/Image/LinkUploader/URLForm.js +39 -0
- package/src/Editor/CustomExtensions/Image/LocalUploader.js +21 -0
- package/src/Editor/CustomExtensions/Image/ProgressBar.js +34 -0
- package/src/Editor/CustomExtensions/Image/Uploader.js +72 -31
- package/src/Editor/CustomExtensions/Image/constants.js +5 -0
- package/src/Editor/CustomExtensions/Mention/ExtensionConfig.js +66 -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 +200 -155
- 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 +88 -0
- package/src/Editor/index.js +59 -41
- package/src/constants/regexp.js +1 -0
- package/src/examples/constants.js +95 -0
- package/src/examples/index.js +186 -0
- package/src/hooks/useOutsideClick.js +19 -0
- package/src/hooks/useTabBar.js +9 -0
- package/src/index.scss +32 -12
- package/src/styles/abstracts/_mixins.scss +20 -0
- package/src/styles/abstracts/_neeto-ui-variables.scss +107 -0
- package/src/styles/abstracts/_variables.scss +13 -0
- package/src/styles/components/_avatar.scss +105 -0
- package/src/styles/components/_button.scss +161 -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} +33 -7
- package/src/styles/components/_fixed-menu.scss +17 -0
- package/src/styles/components/_image-uploader.scss +109 -0
- package/src/styles/components/_input.scss +165 -0
- package/src/styles/components/_tab.scss +74 -0
- package/src/styles/components/_tooltip.scss +152 -0
- package/src/utils/common.js +13 -0
- package/webpack.config.js +7 -0
- package/webpack.dev.config.js +7 -0
- package/public/logo192.png +0 -0
- package/public/logo512.png +0 -0
- package/src/logo.svg +0 -1
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import classnames from "classnames";
|
|
3
|
+
import { Info } from "@bigbinary/neeto-icons";
|
|
4
|
+
|
|
5
|
+
import Tooltip from "common/ToolTip";
|
|
6
|
+
|
|
7
|
+
const Label = ({
|
|
8
|
+
children,
|
|
9
|
+
className = "",
|
|
10
|
+
required = false,
|
|
11
|
+
helpIconProps = null,
|
|
12
|
+
...otherProps
|
|
13
|
+
}) => {
|
|
14
|
+
const {
|
|
15
|
+
onClick,
|
|
16
|
+
icon,
|
|
17
|
+
tooltipProps,
|
|
18
|
+
className: helpIconClassName,
|
|
19
|
+
...otherHelpIconProps
|
|
20
|
+
} = helpIconProps || {};
|
|
21
|
+
const HelpIcon = icon || Info;
|
|
22
|
+
return (
|
|
23
|
+
<label
|
|
24
|
+
className={classnames("neeto-ui-label flex items-center", className)}
|
|
25
|
+
{...otherProps}
|
|
26
|
+
>
|
|
27
|
+
{children}
|
|
28
|
+
{required && <span aria-hidden>*</span>}
|
|
29
|
+
{helpIconProps && (
|
|
30
|
+
<Tooltip {...tooltipProps} disabled={!tooltipProps}>
|
|
31
|
+
<span
|
|
32
|
+
className={classnames("ml-1", {
|
|
33
|
+
[helpIconClassName]: helpIconClassName,
|
|
34
|
+
})}
|
|
35
|
+
onClick={onClick}
|
|
36
|
+
>
|
|
37
|
+
<HelpIcon size={16} {...otherHelpIconProps} />
|
|
38
|
+
</span>
|
|
39
|
+
</Tooltip>
|
|
40
|
+
)}
|
|
41
|
+
</label>
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export default Label;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
const ListItems = ({ items, ordered }) => {
|
|
4
|
+
const itemElements = items.map((item, index) => (
|
|
5
|
+
<li key={item.key || index}>{item}</li>
|
|
6
|
+
));
|
|
7
|
+
|
|
8
|
+
const className = "font-serif text-lg list-inside";
|
|
9
|
+
|
|
10
|
+
return ordered ? (
|
|
11
|
+
<ol className={`${className} list-decimal`}>{itemElements}</ol>
|
|
12
|
+
) : (
|
|
13
|
+
<ul className={`${className} list-disc`}>{itemElements}</ul>
|
|
14
|
+
);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export default ListItems;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
const Modal = ({
|
|
4
|
+
isVisible,
|
|
5
|
+
onClose,
|
|
6
|
+
title,
|
|
7
|
+
buttonTextPrimary,
|
|
8
|
+
buttonTextSecondary,
|
|
9
|
+
content,
|
|
10
|
+
children = null,
|
|
11
|
+
}) => {
|
|
12
|
+
if (!isVisible) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<div
|
|
18
|
+
className="fixed inset-0 z-10 overflow-y-auto"
|
|
19
|
+
aria-labelledby="modal-title"
|
|
20
|
+
role="dialog"
|
|
21
|
+
aria-modal="true"
|
|
22
|
+
>
|
|
23
|
+
<div
|
|
24
|
+
className="flex items-end justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:block sm:p-0"
|
|
25
|
+
onClick={onClose}
|
|
26
|
+
>
|
|
27
|
+
<div
|
|
28
|
+
className="fixed inset-0 transition-opacity bg-gray-500 bg-opacity-50"
|
|
29
|
+
aria-hidden="true"
|
|
30
|
+
></div>
|
|
31
|
+
|
|
32
|
+
<span
|
|
33
|
+
className="hidden sm:inline-block sm:align-middle sm:h-screen"
|
|
34
|
+
aria-hidden="true"
|
|
35
|
+
>
|
|
36
|
+
​
|
|
37
|
+
</span>
|
|
38
|
+
|
|
39
|
+
<div
|
|
40
|
+
className="inline-block overflow-hidden text-left align-bottom transition-all transform bg-white rounded shadow-xl sm:my-8 sm:align-middle sm:max-w-lg"
|
|
41
|
+
onClick={(e) => e.stopPropagation()}
|
|
42
|
+
>
|
|
43
|
+
{title || content ? (
|
|
44
|
+
<div className="px-4 pt-5 pb-4 bg-white sm:p-6 sm:pb-4">
|
|
45
|
+
<div className="sm:flex sm:items-start">
|
|
46
|
+
<div className="mt-3 text-center sm:mt-0 sm:text-left">
|
|
47
|
+
{title ? (
|
|
48
|
+
<h3
|
|
49
|
+
className="text-lg font-medium leading-6 text-gray-900"
|
|
50
|
+
id="modal-title"
|
|
51
|
+
>
|
|
52
|
+
{title}
|
|
53
|
+
</h3>
|
|
54
|
+
) : null}
|
|
55
|
+
{content ? (
|
|
56
|
+
<div className="mt-2">
|
|
57
|
+
<p className="text-sm text-gray-500">{content}</p>
|
|
58
|
+
</div>
|
|
59
|
+
) : null}
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
) : null}
|
|
64
|
+
{children}
|
|
65
|
+
{buttonTextPrimary || buttonTextSecondary ? (
|
|
66
|
+
<div className="px-4 py-3 bg-gray-50 sm:px-6 sm:flex sm:flex-row-reverse">
|
|
67
|
+
{buttonTextPrimary ? (
|
|
68
|
+
<button
|
|
69
|
+
type="button"
|
|
70
|
+
className="inline-flex justify-center w-full px-4 py-2 text-base font-medium text-white bg-red-600 border border-transparent rounded-md shadow-sm hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"
|
|
71
|
+
>
|
|
72
|
+
{buttonTextPrimary}
|
|
73
|
+
</button>
|
|
74
|
+
) : null}
|
|
75
|
+
{buttonTextSecondary ? (
|
|
76
|
+
<button
|
|
77
|
+
type="button"
|
|
78
|
+
className="inline-flex justify-center w-full px-4 py-2 mt-3 text-base font-medium text-gray-700 bg-white border border-gray-300 rounded-md shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
|
|
79
|
+
>
|
|
80
|
+
{buttonTextSecondary}
|
|
81
|
+
</button>
|
|
82
|
+
) : null}
|
|
83
|
+
</div>
|
|
84
|
+
) : null}
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export default Modal;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { NavLink } from "react-router-dom";
|
|
3
|
+
import classnames from "classnames";
|
|
4
|
+
|
|
5
|
+
const noop = () => {};
|
|
6
|
+
|
|
7
|
+
const TAB_SIZES = { large: "large", default: "default" };
|
|
8
|
+
|
|
9
|
+
const Tab = ({
|
|
10
|
+
children,
|
|
11
|
+
size,
|
|
12
|
+
noUnderline,
|
|
13
|
+
className = "",
|
|
14
|
+
...otherProps
|
|
15
|
+
}) => {
|
|
16
|
+
return (
|
|
17
|
+
<div
|
|
18
|
+
className={classnames(
|
|
19
|
+
{
|
|
20
|
+
"neeto-ui-tab__wrapper flex": true,
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"neeto-ui-tab__wrapper--size-large": size === TAB_SIZES.large,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"neeto-ui-tab__wrapper--underline-none": noUnderline,
|
|
27
|
+
},
|
|
28
|
+
[className]
|
|
29
|
+
)}
|
|
30
|
+
data-cy="tab-container"
|
|
31
|
+
{...otherProps}
|
|
32
|
+
>
|
|
33
|
+
{children}
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const Item = ({
|
|
39
|
+
active,
|
|
40
|
+
className = "",
|
|
41
|
+
children,
|
|
42
|
+
icon = null,
|
|
43
|
+
onClick = noop,
|
|
44
|
+
activeClassName = "",
|
|
45
|
+
...otherProps
|
|
46
|
+
}) => {
|
|
47
|
+
let Parent = "button";
|
|
48
|
+
let Icon =
|
|
49
|
+
typeof icon == "string"
|
|
50
|
+
? () => <i className={icon} data-cy="tab-item-icon" />
|
|
51
|
+
: icon || React.Fragment;
|
|
52
|
+
|
|
53
|
+
if (activeClassName) {
|
|
54
|
+
Parent = NavLink;
|
|
55
|
+
}
|
|
56
|
+
return (
|
|
57
|
+
<Parent
|
|
58
|
+
className={classnames(
|
|
59
|
+
[
|
|
60
|
+
"neeto-ui-tab flex items-center justify-center select-none",
|
|
61
|
+
className,
|
|
62
|
+
],
|
|
63
|
+
{
|
|
64
|
+
active: active,
|
|
65
|
+
}
|
|
66
|
+
)}
|
|
67
|
+
onClick={onClick}
|
|
68
|
+
data-cy="tab-item"
|
|
69
|
+
{...otherProps}
|
|
70
|
+
>
|
|
71
|
+
{icon && <Icon className="neeto-ui-tab__icon" />}
|
|
72
|
+
{children}
|
|
73
|
+
</Parent>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
Tab.Item = Item;
|
|
78
|
+
|
|
79
|
+
export default Tab;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import Tippy from "@tippyjs/react";
|
|
3
|
+
import { followCursor } from "tippy.js";
|
|
4
|
+
|
|
5
|
+
const Tooltip = ({
|
|
6
|
+
content,
|
|
7
|
+
children,
|
|
8
|
+
theme = "dark",
|
|
9
|
+
disabled = false,
|
|
10
|
+
placement, // Remove this prop once this prop is migrated to position in all neeto products
|
|
11
|
+
position = "auto",
|
|
12
|
+
interactive = false,
|
|
13
|
+
...otherProps
|
|
14
|
+
}) => {
|
|
15
|
+
return (
|
|
16
|
+
<Tippy
|
|
17
|
+
role="tooltip"
|
|
18
|
+
theme={theme}
|
|
19
|
+
content={content}
|
|
20
|
+
arrow={
|
|
21
|
+
"<svg width='10' height='5' viewBox='0 0 10 5' fill='none' xmlns='http://www.w3.org/2000/svg'><path d='M10 5H0.926697L3.95208 1.63847C4.74227 0.760478 6.11722 0.754951 6.91445 1.62656L10 5Z' /></svg>"
|
|
22
|
+
}
|
|
23
|
+
disabled={disabled}
|
|
24
|
+
animation={"scale-subtle"}
|
|
25
|
+
placement={placement || position}
|
|
26
|
+
plugins={[followCursor]}
|
|
27
|
+
interactive={interactive}
|
|
28
|
+
duration={[100, 200]}
|
|
29
|
+
zIndex={100001}
|
|
30
|
+
{...otherProps}
|
|
31
|
+
>
|
|
32
|
+
{children}
|
|
33
|
+
</Tippy>
|
|
34
|
+
);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export default Tooltip;
|
|
@@ -2,13 +2,15 @@ import React from "react";
|
|
|
2
2
|
import classnames from "classnames";
|
|
3
3
|
import { BubbleMenu } from "@tiptap/react";
|
|
4
4
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
} from "
|
|
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";
|
|
12
14
|
|
|
13
15
|
export default function index({ editor, formatterOptions }) {
|
|
14
16
|
if (!editor) {
|
|
@@ -16,25 +18,25 @@ export default function index({ editor, formatterOptions }) {
|
|
|
16
18
|
}
|
|
17
19
|
const options = [
|
|
18
20
|
{
|
|
19
|
-
Icon:
|
|
21
|
+
Icon: TextBold,
|
|
20
22
|
command: () => editor.chain().focus().toggleBold().run(),
|
|
21
23
|
active: editor.isActive("bold"),
|
|
22
24
|
optionName: "bold",
|
|
23
25
|
},
|
|
24
26
|
{
|
|
25
|
-
Icon:
|
|
27
|
+
Icon: TextItalic,
|
|
26
28
|
command: () => editor.chain().focus().toggleItalic().run(),
|
|
27
29
|
active: editor.isActive("italic"),
|
|
28
30
|
optionName: "italic",
|
|
29
31
|
},
|
|
30
32
|
{
|
|
31
|
-
Icon:
|
|
33
|
+
Icon: TextCross,
|
|
32
34
|
command: () => editor.chain().focus().toggleStrike().run(),
|
|
33
35
|
active: editor.isActive("strike"),
|
|
34
36
|
optionName: "strike",
|
|
35
37
|
},
|
|
36
38
|
{
|
|
37
|
-
Icon:
|
|
39
|
+
Icon: Link,
|
|
38
40
|
command: () => {
|
|
39
41
|
if (editor.isActive("link")) {
|
|
40
42
|
editor.chain().focus().unsetLink().run();
|
|
@@ -47,39 +49,44 @@ export default function index({ editor, formatterOptions }) {
|
|
|
47
49
|
optionName: "link",
|
|
48
50
|
},
|
|
49
51
|
{
|
|
50
|
-
Icon:
|
|
52
|
+
Icon: Code,
|
|
51
53
|
command: () => editor.chain().focus().toggleCode().run(),
|
|
52
54
|
active: editor.isActive("code"),
|
|
53
55
|
optionName: "code",
|
|
54
56
|
},
|
|
55
57
|
{
|
|
56
|
-
Icon:
|
|
58
|
+
Icon: Highlight,
|
|
57
59
|
command: () => editor.chain().focus().toggleHighlight().run(),
|
|
58
60
|
active: editor.isActive("highlight"),
|
|
59
61
|
optionName: "highlight",
|
|
60
62
|
},
|
|
61
63
|
];
|
|
62
64
|
return (
|
|
63
|
-
<BubbleMenu
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
65
|
+
<BubbleMenu
|
|
66
|
+
editor={editor}
|
|
67
|
+
tippyOptions={{ arrow: roundArrow }}
|
|
68
|
+
className="relative flex overflow-hidden 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
|
+
))}
|
|
71
75
|
</BubbleMenu>
|
|
72
76
|
);
|
|
73
77
|
}
|
|
74
78
|
|
|
75
79
|
const Option = ({ Icon, command, active, iconSize }) => (
|
|
76
80
|
<div
|
|
77
|
-
className={classnames(
|
|
78
|
-
"
|
|
79
|
-
|
|
80
|
-
|
|
81
|
+
className={classnames(
|
|
82
|
+
"p-3 cursor-pointer editor-command-list--item transition-colors",
|
|
83
|
+
{
|
|
84
|
+
"text-gray-400": !active,
|
|
85
|
+
"text-white": active,
|
|
86
|
+
}
|
|
87
|
+
)}
|
|
81
88
|
onClick={command}
|
|
82
89
|
>
|
|
83
|
-
<Icon size={iconSize ||
|
|
90
|
+
<Icon size={iconSize || 24} />
|
|
84
91
|
</div>
|
|
85
92
|
);
|
|
@@ -3,9 +3,11 @@ import { Node, mergeAttributes } from "@tiptap/core";
|
|
|
3
3
|
export default Node.create({
|
|
4
4
|
name: "external-video",
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
addOptions() {
|
|
7
|
+
return {
|
|
8
|
+
inline: false,
|
|
9
|
+
HTMLAttributes: {},
|
|
10
|
+
};
|
|
9
11
|
},
|
|
10
12
|
|
|
11
13
|
inline() {
|
|
@@ -0,0 +1,32 @@
|
|
|
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="h-full 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)} key={value}>
|
|
25
|
+
{label}
|
|
26
|
+
</li>
|
|
27
|
+
))}
|
|
28
|
+
</Dropdown>
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export default FontSizeOption;
|
|
@@ -0,0 +1,29 @@
|
|
|
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;
|
|
@@ -0,0 +1,183 @@
|
|
|
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;
|
|
@@ -0,0 +1,39 @@
|
|
|
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;
|