@flozy/editor 3.3.0 → 3.3.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -31,6 +31,7 @@ import decorators from "./utils/Decorators";
31
31
  import { getTRBLBreakPoints } from "./helper/theme";
32
32
  import { handleInsertLastElement, outsideEditorClickLabel } from "./utils/helper";
33
33
  import useWindowResize from "./hooks/useWindowResize";
34
+ import PopoverAIInput from "./Elements/AI/PopoverAIInput";
34
35
  import { jsx as _jsx } from "react/jsx-runtime";
35
36
  import { jsxs as _jsxs } from "react/jsx-runtime";
36
37
  const Item = /*#__PURE__*/forwardRef(({
@@ -455,7 +456,9 @@ const CommonEditor = /*#__PURE__*/forwardRef((props, ref) => {
455
456
  customProps: customProps,
456
457
  toolbarOptions: toolbarOptions,
457
458
  theme: theme
458
- }) : null, footer && (fullScreen || readOnly) && /*#__PURE__*/_jsx(Typography, {
459
+ }) : null, /*#__PURE__*/_jsx(PopoverAIInput, {
460
+ otherProps: otherProps
461
+ }), footer && (fullScreen || readOnly) && /*#__PURE__*/_jsx(Typography, {
459
462
  sx: {
460
463
  color: "rgb(100, 116, 139)",
461
464
  fontSize: "13px",
@@ -471,7 +474,8 @@ const CommonEditor = /*#__PURE__*/forwardRef((props, ref) => {
471
474
  }), !readOnly ? /*#__PURE__*/_jsx(PopupTool, {
472
475
  onDrawerOpen: onDrawerOpen,
473
476
  theme: theme,
474
- setIsTextSelected: setIsTextSelected
477
+ setIsTextSelected: setIsTextSelected,
478
+ customProps: customProps
475
479
  }) : null]
476
480
  })
477
481
  }), htmlAction.showInput && /*#__PURE__*/_jsx(CodeToText, {
@@ -0,0 +1,157 @@
1
+ import { Box, IconButton, TextareaAutosize, Typography } from "@mui/material";
2
+ import Styles from "./Styles";
3
+ import Icon from "../../common/Icon";
4
+ // import { TbRotateClockwise } from "react-icons/tb";
5
+ // import { FaRegCircleCheck } from "react-icons/fa6";
6
+ import { IoSend } from "react-icons/io5";
7
+ import React, { forwardRef, useEffect, useRef } from "react";
8
+ import WaveLoading from "../../common/WaveLoading";
9
+ import useWindowResize from "../../hooks/useWindowResize";
10
+ import CustomSelect from "./CustomSelect";
11
+ import { editContentOptions, newContentOptions, generatedContentOptions } from "./helper";
12
+ import useClickOutside from "../../hooks/useClickOutside";
13
+ import { useEditorContext } from "../../hooks/useMouseMove";
14
+ import { jsx as _jsx } from "react/jsx-runtime";
15
+ import { jsxs as _jsxs } from "react/jsx-runtime";
16
+ import { Fragment as _Fragment } from "react/jsx-runtime";
17
+ function getProps(openAI, generatedText) {
18
+ const dropDownProps = {
19
+ fromElements: {
20
+ options: newContentOptions,
21
+ show: window.getSelection().toString().length
22
+ },
23
+ fromToolBar: {
24
+ options: editContentOptions,
25
+ show: true
26
+ },
27
+ generatedContent: {
28
+ options: generatedContentOptions,
29
+ show: true
30
+ }
31
+ };
32
+ if (openAI === "fromToolBar" && generatedText?.length) {
33
+ return dropDownProps.generatedContent;
34
+ }
35
+ return dropDownProps[openAI];
36
+ }
37
+ function AIInput({
38
+ onSend,
39
+ loading,
40
+ generatedText,
41
+ openAI,
42
+ inputValue,
43
+ onInputChange,
44
+ onClickOutside
45
+ }, ref) {
46
+ const {
47
+ theme
48
+ } = useEditorContext();
49
+ const classes = Styles(theme);
50
+ const inputRef = useRef();
51
+ const [size] = useWindowResize();
52
+ const inputWrapperRef = useRef();
53
+ const refs = useClickOutside({
54
+ onClickOutside,
55
+ omitIds: ["infinity-select-popover"],
56
+ omitClass: ["MuiModal-backdrop"],
57
+ refCount: 2
58
+ });
59
+ useEffect(() => {
60
+ let timeoutId;
61
+ if (inputRef.current && openAI !== "fromToolBar") {
62
+ timeoutId = setTimeout(() => {
63
+ inputRef.current.focus();
64
+ }, 200);
65
+ }
66
+ return () => {
67
+ clearTimeout(timeoutId);
68
+ };
69
+ }, [openAI]);
70
+ const isSendBtnDisabled = !inputValue || loading;
71
+ const props = getProps(openAI, generatedText) || {};
72
+ const fromToolBar = openAI === "fromToolBar";
73
+ const handleSendBtnClick = () => {
74
+ if (isSendBtnDisabled) {
75
+ return;
76
+ }
77
+ onSend("", {
78
+ replace: true,
79
+ isSendBtn: true
80
+ });
81
+ };
82
+ return /*#__PURE__*/_jsxs(_Fragment, {
83
+ children: [size.device === "xs" ? /*#__PURE__*/_jsx(Box, {
84
+ component: "div",
85
+ ref: refs[1],
86
+ sx: classes.customSelectWrapper,
87
+ children: /*#__PURE__*/_jsx(CustomSelect, {
88
+ ...props,
89
+ onSend: onSend,
90
+ classes: classes
91
+ })
92
+ }) : null, /*#__PURE__*/_jsxs(Box, {
93
+ component: "div",
94
+ sx: classes.aiContainer,
95
+ children: [generatedText ? /*#__PURE__*/_jsx(Typography, {
96
+ sx: classes.generatedText,
97
+ children: generatedText
98
+ }) : null, /*#__PURE__*/_jsxs(Box, {
99
+ component: "form",
100
+ sx: classes.aiInputWrapper,
101
+ onSubmit: e => {
102
+ e.preventDefault();
103
+ },
104
+ ref: refs[0],
105
+ children: [/*#__PURE__*/_jsx("div", {
106
+ className: "icon-container",
107
+ ref: inputWrapperRef,
108
+ children: /*#__PURE__*/_jsx(Icon, {
109
+ icon: "infinityIcon"
110
+ })
111
+ }), loading ? /*#__PURE__*/_jsxs("div", {
112
+ className: "loading-container",
113
+ children: [/*#__PURE__*/_jsx(Typography, {
114
+ variant: "body1",
115
+ children: "Infinity Writing"
116
+ }), /*#__PURE__*/_jsx("div", {
117
+ children: /*#__PURE__*/_jsx(WaveLoading, {})
118
+ })]
119
+ }) : /*#__PURE__*/_jsx(TextareaAutosize, {
120
+ className: "ai-input",
121
+ placeholder: fromToolBar ? "" : "Ask AI to write anything...",
122
+ ref: inputRef,
123
+ value: inputValue,
124
+ onChange: onInputChange,
125
+ disabled: fromToolBar,
126
+ onKeyDown: event => {
127
+ if (event.key === "Enter" && !event.shiftKey) {
128
+ event.preventDefault();
129
+ handleSendBtnClick();
130
+ }
131
+ }
132
+ }), /*#__PURE__*/_jsx(Box, {
133
+ component: "div",
134
+ style: classes.sendIconContainer,
135
+ children: /*#__PURE__*/_jsx(IconButton, {
136
+ sx: isSendBtnDisabled ? classes.sendBtnDisabled : classes.sendBtn,
137
+ onClick: () => handleSendBtnClick(),
138
+ children: /*#__PURE__*/_jsx(IoSend, {
139
+ color: "#fff",
140
+ size: 13
141
+ })
142
+ })
143
+ })]
144
+ })]
145
+ }), size.device === "xs" ? null : /*#__PURE__*/_jsx(Box, {
146
+ component: "div",
147
+ ref: refs[1],
148
+ sx: classes.customSelectWrapper,
149
+ children: /*#__PURE__*/_jsx(CustomSelect, {
150
+ ...props,
151
+ onSend: onSend,
152
+ classes: classes
153
+ })
154
+ })]
155
+ });
156
+ }
157
+ export default /*#__PURE__*/forwardRef(AIInput);
@@ -0,0 +1,101 @@
1
+ import { Box, Button, IconButton, Popover, Typography } from "@mui/material";
2
+ import React, { useRef, useState } from "react";
3
+ import { FaChevronRight } from "react-icons/fa";
4
+ import { jsx as _jsx } from "react/jsx-runtime";
5
+ import { jsxs as _jsxs } from "react/jsx-runtime";
6
+ import { Fragment as _Fragment } from "react/jsx-runtime";
7
+ function CustomSelect({
8
+ classes,
9
+ options,
10
+ onSend,
11
+ show
12
+ }) {
13
+ if (show) {
14
+ return /*#__PURE__*/_jsx(Box, {
15
+ component: "div",
16
+ sx: classes.customSelectContainer,
17
+ children: options?.map((groupOption, index) => {
18
+ const {
19
+ options,
20
+ groupLabel
21
+ } = groupOption;
22
+ return /*#__PURE__*/_jsxs(React.Fragment, {
23
+ children: [groupLabel && /*#__PURE__*/_jsx(Typography, {
24
+ variant: "body2",
25
+ sx: classes.optionHeading,
26
+ children: groupLabel
27
+ }), options?.map((option, i) => {
28
+ return /*#__PURE__*/_jsx(DisplayOption, {
29
+ option: option,
30
+ classes: classes,
31
+ onSend: onSend
32
+ }, i);
33
+ })]
34
+ }, index);
35
+ })
36
+ });
37
+ } else {
38
+ return /*#__PURE__*/_jsx(_Fragment, {});
39
+ }
40
+ }
41
+ export default CustomSelect;
42
+ function DisplayOption({
43
+ option,
44
+ classes,
45
+ onSend
46
+ }) {
47
+ const {
48
+ Icon
49
+ } = option;
50
+ const [open, setOpen] = useState(false);
51
+ const optionRef = useRef();
52
+ return /*#__PURE__*/_jsxs(Box, {
53
+ sx: classes.optionWrapper,
54
+ ref: optionRef,
55
+ children: [/*#__PURE__*/_jsxs(Button, {
56
+ sx: classes.optionBtn,
57
+ onClick: e => {
58
+ e.stopPropagation();
59
+
60
+ // is having child options
61
+ if (option.options?.length) {
62
+ setOpen(e.currentTarget);
63
+ return;
64
+ }
65
+ setOpen(null);
66
+ onSend(option.value, option);
67
+ },
68
+ id: "infinity-select-popover",
69
+ children: [/*#__PURE__*/_jsxs("div", {
70
+ className: "option-label",
71
+ id: "infinity-select-popover",
72
+ children: [Icon && /*#__PURE__*/_jsx(Icon, {}), option.label]
73
+ }), option.options?.length && /*#__PURE__*/_jsx(IconButton, {
74
+ children: /*#__PURE__*/_jsx(FaChevronRight, {
75
+ color: "#94A3B8",
76
+ size: 12
77
+ })
78
+ })]
79
+ }), /*#__PURE__*/_jsx(Popover, {
80
+ open: open && option.options,
81
+ anchorEl: open,
82
+ sx: {
83
+ zIndex: 9001,
84
+ background: "transparent"
85
+ },
86
+ anchorOrigin: {
87
+ vertical: "top",
88
+ horizontal: "right"
89
+ },
90
+ onClose: () => {
91
+ setOpen(null);
92
+ },
93
+ children: /*#__PURE__*/_jsx(CustomSelect, {
94
+ options: option.options,
95
+ onSend: onSend,
96
+ classes: classes,
97
+ show: open
98
+ })
99
+ })]
100
+ });
101
+ }
@@ -0,0 +1,231 @@
1
+ import { useEffect, useRef, useState } from "react";
2
+ import { useEditorContext } from "../../hooks/useMouseMove";
3
+ import Styles from "./Styles";
4
+ import { Box, Fade, Paper, Popper } from "@mui/material";
5
+ import AIInput from "./AIInput";
6
+ import { ReactEditor, useSlate } from "slate-react";
7
+ import { Node, Path, Transforms } from "slate";
8
+ import useWindowResize from "../../hooks/useWindowResize";
9
+ import { MODES } from "./helper";
10
+ import { jsx as _jsx } from "react/jsx-runtime";
11
+ import { jsxs as _jsxs } from "react/jsx-runtime";
12
+ const scrollToAIInput = () => {
13
+ try {
14
+ setTimeout(() => {
15
+ const slateWrapper = document.getElementById("slate-wrapper-scroll-container");
16
+ const selectionRect = window.getSelection().getRangeAt(0).getBoundingClientRect();
17
+ const halfOfWrapper = slateWrapper.clientHeight / 2;
18
+ const selectionScollTop = selectionRect.y + selectionRect.height;
19
+ if (selectionScollTop > halfOfWrapper) {
20
+ // scroll to half of the slateWrapper
21
+ slateWrapper.scrollTo({
22
+ top: slateWrapper.scrollTop + selectionScollTop - halfOfWrapper,
23
+ behavior: "smooth"
24
+ });
25
+ }
26
+ }, 200);
27
+ } catch (err) {
28
+ console.log(err);
29
+ }
30
+ };
31
+ const updateAnchorEl = setAnchorEl => {
32
+ try {
33
+ const selection = window.getSelection();
34
+ if (selection.rangeCount) {
35
+ const domRange = selection.getRangeAt(0);
36
+ const getBoundingClientRect = () => {
37
+ const editorEle = document.querySelector(".ed-section-inner").getBoundingClientRect();
38
+ const caretPos = domRange.getBoundingClientRect();
39
+ return {
40
+ y: caretPos.y,
41
+ height: caretPos.height,
42
+ top: caretPos.top,
43
+ right: caretPos.right,
44
+ bottom: caretPos.bottom,
45
+ x: editorEle.x,
46
+ left: editorEle.left,
47
+ width: editorEle.width
48
+ };
49
+ };
50
+ setAnchorEl({
51
+ getBoundingClientRect
52
+ });
53
+ }
54
+ } catch (err) {
55
+ console.log(err);
56
+ }
57
+ };
58
+ function PopoverAIInput({
59
+ otherProps
60
+ }) {
61
+ const {
62
+ services
63
+ } = otherProps;
64
+ const {
65
+ openAI,
66
+ setOpenAI
67
+ } = useEditorContext();
68
+ const [anchorEl, setAnchorEl] = useState(null);
69
+ const [loading, setLoading] = useState(false);
70
+ const [generatedText, setGeneratedText] = useState("");
71
+ const [inputValue, setInputValue] = useState("");
72
+ const [selectedOption, setSelectedOption] = useState();
73
+ const targetRef = useRef();
74
+ const classes = Styles();
75
+ const editor = useSlate();
76
+ const [size] = useWindowResize();
77
+ const onClickOutside = () => {
78
+ setAnchorEl(null);
79
+ setOpenAI("");
80
+ setGeneratedText("");
81
+ setLoading(false);
82
+ setSelectedOption(null);
83
+ setInputValue("");
84
+ ReactEditor.focus(editor);
85
+ Transforms.deselect(editor);
86
+ };
87
+ const editorElement = document.querySelector(".ed-section-inner");
88
+ useEffect(() => {
89
+ updateAnchorEl(setAnchorEl);
90
+ }, [openAI, editor.selection]);
91
+ useEffect(() => {
92
+ if (openAI === "fromToolBar") {
93
+ scrollToAIInput();
94
+ }
95
+ }, [openAI]);
96
+ const onSend = async (type, option) => {
97
+ if (type === "close") {
98
+ onClickOutside();
99
+ return;
100
+ }
101
+ if (type === "replace_selection") {
102
+ // replace generated text
103
+ Transforms.insertText(editor, generatedText);
104
+ onClickOutside();
105
+ return;
106
+ }
107
+ if (type === "try_again") {
108
+ // resetting the previous option and try again
109
+ option = selectedOption;
110
+ type = selectedOption.value;
111
+ } else {
112
+ setSelectedOption(option);
113
+ }
114
+ setLoading(true);
115
+ const payload = {
116
+ mode: option.mode || 0,
117
+ query: inputValue
118
+ };
119
+ if (option.mode === MODES.translate || option.mode === MODES.rephraseTone) {
120
+ payload.textOptionInput = type;
121
+ }
122
+ if (option.mode) {
123
+ payload.textData = generatedText || window.getSelection().toString();
124
+ }
125
+ const result = await services("infinityAI", payload);
126
+ setLoading(false);
127
+ setInputValue("");
128
+ let {
129
+ data: text
130
+ } = result || {};
131
+ if (!option.replace) {
132
+ if (type === "continue_writing") {
133
+ setGeneratedText(generatedText + text);
134
+ } else {
135
+ setGeneratedText(text);
136
+ }
137
+ return;
138
+ }
139
+ const currentPath = Path.parent(editor.selection.focus.path);
140
+ const nextPath = Path.next(currentPath);
141
+
142
+ // Get the current selection point
143
+ const {
144
+ anchor
145
+ } = editor.selection;
146
+ const {
147
+ path
148
+ } = editor.selection.anchor;
149
+ const {
150
+ text: selectText
151
+ } = Node.get(editor, path);
152
+ const insertInNewLine = option.isSendBtn && selectText?.length || type === "continue_writing";
153
+ if (insertInNewLine) {
154
+ Transforms.insertNodes(editor, {
155
+ type: "paragraph",
156
+ children: [{
157
+ text
158
+ }]
159
+ }, {
160
+ at: nextPath,
161
+ select: true
162
+ });
163
+ } else {
164
+ Transforms.insertText(editor, text);
165
+ }
166
+ const range = {
167
+ ...editor.selection,
168
+ anchor: {
169
+ ...anchor,
170
+ offset: openAI === "fromToolBar" ? anchor.offset : 0
171
+ }
172
+ };
173
+ ReactEditor.focus(editor);
174
+ Transforms.select(editor, range);
175
+ scrollToAIInput();
176
+ };
177
+ const onInputChange = e => {
178
+ setInputValue(e.target.value);
179
+ };
180
+ return /*#__PURE__*/_jsxs("div", {
181
+ children: [size.device === "xs" && openAI ? /*#__PURE__*/_jsx(Box, {
182
+ component: "div",
183
+ sx: classes.mobileAIInputWrapper,
184
+ ref: targetRef,
185
+ children: /*#__PURE__*/_jsx(AIInput, {
186
+ loading: loading,
187
+ onSend: onSend,
188
+ generatedText: generatedText,
189
+ anchorEl: anchorEl,
190
+ openAI: openAI,
191
+ inputValue: inputValue,
192
+ onInputChange: onInputChange,
193
+ onClickOutside: onClickOutside
194
+ })
195
+ }) : /*#__PURE__*/_jsx(Popper, {
196
+ open: Boolean(openAI),
197
+ anchorEl: anchorEl,
198
+ transition: true,
199
+ placement: "bottom-start",
200
+ sx: {
201
+ ...classes.aiPopper,
202
+ width: editorElement?.offsetWidth || 400
203
+ },
204
+ ref: targetRef,
205
+ children: ({
206
+ TransitionProps
207
+ }) => /*#__PURE__*/_jsx(Fade, {
208
+ ...TransitionProps,
209
+ timeout: 350,
210
+ children: /*#__PURE__*/_jsx(Paper, {
211
+ children: /*#__PURE__*/_jsx(AIInput, {
212
+ loading: loading,
213
+ onSend: onSend,
214
+ generatedText: generatedText,
215
+ anchorEl: anchorEl,
216
+ openAI: openAI,
217
+ inputValue: inputValue,
218
+ onInputChange: onInputChange,
219
+ onClickOutside: onClickOutside
220
+ })
221
+ })
222
+ })
223
+ }), openAI ? /*#__PURE__*/_jsx("div", {
224
+ style: {
225
+ height: targetRef?.current?.clientHeight > 250 ? targetRef?.current?.clientHeight : 250,
226
+ background: "transparent"
227
+ }
228
+ }) : null]
229
+ });
230
+ }
231
+ export default PopoverAIInput;
@@ -0,0 +1,149 @@
1
+ const Styles = theme => ({
2
+ aiContainer: {
3
+ background: "#FCFAFF",
4
+ border: "1px solid #8360FD",
5
+ borderRadius: "8px"
6
+ },
7
+ aiInputWrapper: {
8
+ display: "flex",
9
+ alignItems: "baseline",
10
+ justifyContent: "space-between",
11
+ padding: "0px 12px",
12
+ minHeight: "36px",
13
+ position: "relative",
14
+ background: theme?.palette?.editor?.background,
15
+ borderRadius: "7px",
16
+ "& .icon-container": {
17
+ display: "flex",
18
+ alignItems: "center",
19
+ height: "48px"
20
+ },
21
+ "& .ai-input": {
22
+ padding: "14px",
23
+ border: "none",
24
+ outline: "none",
25
+ width: "100%",
26
+ background: "transparent",
27
+ resize: "none",
28
+ alignSelf: "center",
29
+ color: theme?.palette?.editor?.textColor,
30
+ fontSize: "13px !important",
31
+ "&::placeholder": {
32
+ color: "#94A3B8",
33
+ opacity: 1 /* Firefox */
34
+ },
35
+
36
+ "&::-ms-input-placeholder": {
37
+ /* Edge 12-18 */color: "#94A3B8"
38
+ }
39
+ },
40
+ "& .action-btns": {
41
+ display: "flex",
42
+ alignItems: "center",
43
+ gap: "6px",
44
+ "& .MuiTypography-root": {
45
+ color: "#64748B !important",
46
+ fontSize: "14px",
47
+ textTransform: "none"
48
+ },
49
+ "& .btn-wrapper": {
50
+ display: "flex",
51
+ alignItems: "center",
52
+ gap: "6px"
53
+ }
54
+ },
55
+ "& .loading-container": {
56
+ display: "flex",
57
+ alignItems: "center",
58
+ color: "#2563EB !important",
59
+ gap: "12px",
60
+ width: "100%",
61
+ paddingLeft: "14px",
62
+ alignSelf: "center",
63
+ "& .MuiTypography-body1": {
64
+ fontSize: "14px",
65
+ color: "#2563EB !important",
66
+ fontWeight: 500
67
+ }
68
+ }
69
+ },
70
+ sendBtn: {
71
+ background: "linear-gradient(331.11deg, #2563EB 7.11%, #8360FD 88.37%)"
72
+ },
73
+ sendBtnDisabled: {
74
+ background: "#C0C9D6"
75
+ },
76
+ aiPopper: {
77
+ zIndex: 9000,
78
+ "& .MuiPaper-rounded": {
79
+ background: "transparent",
80
+ boxShadow: "none"
81
+ }
82
+ },
83
+ generatedText: {
84
+ margin: "8px",
85
+ maxHeight: "80px",
86
+ overflow: "auto"
87
+ },
88
+ customSelectWrapper: {
89
+ width: "fit-content",
90
+ marginTop: "4px",
91
+ "@media only screen and (max-width: 600px)": {
92
+ marginBottom: "4px"
93
+ }
94
+ },
95
+ customSelectContainer: {
96
+ background: theme?.palette?.editor?.background,
97
+ boxShadow: "0px 4px 10px 0px #00000029",
98
+ borderRadius: "8px",
99
+ maxHeight: "250px",
100
+ overflow: "auto",
101
+ // width: "240px",
102
+ minWidth: "200px",
103
+ border: "1px solid #E0E0E0"
104
+ },
105
+ optionWrapper: {
106
+ position: "relative"
107
+
108
+ // "& .select-option": {
109
+ // position: "absolute",
110
+ // left: 0,
111
+ // top: 0
112
+ // },
113
+ },
114
+
115
+ optionBtn: {
116
+ color: "#373232",
117
+ padding: "8px 12px",
118
+ textTransform: "none",
119
+ justifyContent: "space-between",
120
+ width: "100%",
121
+ fontWeight: "normal !important",
122
+ "& .option-label": {
123
+ display: "flex",
124
+ alignItems: "center",
125
+ gap: "8px"
126
+ }
127
+ },
128
+ optionHeading: {
129
+ color: theme?.palette?.editor?.textColor,
130
+ padding: "14px 0px 8px 4px",
131
+ fontWeight: 600,
132
+ borderBottom: "1px solid #DCE4EC",
133
+ margin: "0px 8px"
134
+ },
135
+ mobileAIInputWrapper: {
136
+ position: "fixed",
137
+ bottom: "env(safe-area-inset-bottom)",
138
+ left: 0,
139
+ width: "100%",
140
+ zIndex: "9000"
141
+ },
142
+ sendIconContainer: {
143
+ alignSelf: "flex-end",
144
+ height: "48px",
145
+ display: "flex",
146
+ alignItems: "center"
147
+ }
148
+ });
149
+ export default Styles;