@valbuild/ui 0.26.0 → 0.27.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/package.json +7 -3
- package/server/.tmp/assets/index-082e6676.css +1 -0
- package/server/.tmp/assets/index-3108ab2a.js +197 -0
- package/{index.html → server/.tmp/index.html} +3 -1
- package/.babelrc.json +0 -10
- package/.storybook/main.js +0 -25
- package/.storybook/preview-head.html +0 -6
- package/.storybook/preview.js +0 -33
- package/.storybook/theme.css +0 -34
- package/CHANGELOG.md +0 -0
- package/components.json +0 -16
- package/fix-server-hack.js +0 -54
- package/fullscreen.vite.config.ts +0 -9
- package/jest.config.js +0 -4
- package/postcss.config.js +0 -6
- package/rollup.config.js +0 -23
- package/server.vite.config.ts +0 -31
- package/src/App.tsx +0 -73
- package/src/assets/icons/Bold.tsx +0 -23
- package/src/assets/icons/Chevron.tsx +0 -28
- package/src/assets/icons/FontColor.tsx +0 -30
- package/src/assets/icons/ImageIcon.tsx +0 -29
- package/src/assets/icons/Italic.tsx +0 -24
- package/src/assets/icons/Logo.tsx +0 -103
- package/src/assets/icons/Section.tsx +0 -41
- package/src/assets/icons/Strikethrough.tsx +0 -22
- package/src/assets/icons/TextIcon.tsx +0 -20
- package/src/assets/icons/Underline.tsx +0 -22
- package/src/assets/icons/Undo.tsx +0 -20
- package/src/components/Button.tsx +0 -68
- package/src/components/Checkbox.tsx +0 -51
- package/src/components/DraggableList.stories.tsx +0 -20
- package/src/components/DraggableList.tsx +0 -95
- package/src/components/Dropdown.tsx +0 -101
- package/src/components/EditButton.tsx +0 -10
- package/src/components/ErrorText.tsx +0 -3
- package/src/components/ExpandLogo.tsx +0 -72
- package/src/components/Grid.stories.tsx +0 -43
- package/src/components/Grid.tsx +0 -139
- package/src/components/RichTextEditor/ContentEditable.tsx +0 -117
- package/src/components/RichTextEditor/Nodes/ImageNode.tsx +0 -100
- package/src/components/RichTextEditor/Plugins/AutoFocus.tsx +0 -12
- package/src/components/RichTextEditor/Plugins/ImagePlugin.tsx +0 -45
- package/src/components/RichTextEditor/Plugins/LinkEditorPlugin.tsx +0 -58
- package/src/components/RichTextEditor/Plugins/Toolbar.tsx +0 -412
- package/src/components/RichTextEditor/RichTextEditor.tsx +0 -105
- package/src/components/UploadModal.tsx +0 -109
- package/src/components/User.tsx +0 -17
- package/src/components/ValFormField.tsx +0 -574
- package/src/components/ValFullscreen.tsx +0 -1278
- package/src/components/ValMenu.tsx +0 -92
- package/src/components/ValOverlay.tsx +0 -488
- package/src/components/ValOverlayContext.tsx +0 -80
- package/src/components/ValWindow.stories.tsx +0 -146
- package/src/components/ValWindow.tsx +0 -220
- package/src/components/dashboard/DashboardButton.tsx +0 -25
- package/src/components/dashboard/DashboardDropdown.tsx +0 -59
- package/src/components/dashboard/Dropdown.stories.tsx +0 -11
- package/src/components/dashboard/Dropdown.tsx +0 -70
- package/src/components/dashboard/FormGroup.stories.tsx +0 -37
- package/src/components/dashboard/FormGroup.tsx +0 -42
- package/src/components/dashboard/Grid2.stories.tsx +0 -56
- package/src/components/dashboard/Grid2.tsx +0 -72
- package/src/components/dashboard/Tree.stories.tsx +0 -91
- package/src/components/dashboard/Tree.tsx +0 -72
- package/src/components/dashboard/ValDashboardEditor.tsx +0 -269
- package/src/components/dashboard/ValDashboardGrid.tsx +0 -142
- package/src/components/dashboard/ValTreeNavigator.tsx +0 -253
- package/src/components/forms/Form.tsx +0 -126
- package/src/components/forms/FormContainer.tsx +0 -24
- package/src/components/forms/ImageForm.tsx +0 -195
- package/src/components/forms/TextArea.tsx +0 -24
- package/src/components/ui/accordion.tsx +0 -58
- package/src/components/ui/alert-dialog.tsx +0 -139
- package/src/components/ui/avatar.tsx +0 -48
- package/src/components/ui/button.tsx +0 -56
- package/src/components/ui/calendar.tsx +0 -62
- package/src/components/ui/card.tsx +0 -86
- package/src/components/ui/checkbox.tsx +0 -28
- package/src/components/ui/command.tsx +0 -153
- package/src/components/ui/dialog.tsx +0 -120
- package/src/components/ui/dropdown-menu.tsx +0 -198
- package/src/components/ui/form.tsx +0 -177
- package/src/components/ui/input.tsx +0 -24
- package/src/components/ui/label.tsx +0 -24
- package/src/components/ui/popover.tsx +0 -29
- package/src/components/ui/progress.tsx +0 -26
- package/src/components/ui/radio-group.tsx +0 -42
- package/src/components/ui/scroll-area.tsx +0 -51
- package/src/components/ui/select.tsx +0 -119
- package/src/components/ui/switch.tsx +0 -27
- package/src/components/ui/tabs.tsx +0 -53
- package/src/components/ui/toggle.tsx +0 -43
- package/src/components/ui/tooltip.tsx +0 -28
- package/src/components/usePatch.ts +0 -86
- package/src/components/useTheme.ts +0 -45
- package/src/dto/SerializedSchema.ts +0 -69
- package/src/dto/Session.ts +0 -12
- package/src/dto/SessionMode.ts +0 -5
- package/src/dto/Tree.ts +0 -18
- package/src/exports.ts +0 -6
- package/src/index.css +0 -115
- package/src/index.tsx +0 -14
- package/src/lib/IValStore.ts +0 -6
- package/src/lib/utils.ts +0 -6
- package/src/main.jsx +0 -10
- package/src/richtext/conversion/conversion.test.ts +0 -146
- package/src/richtext/conversion/lexicalToRichTextSource.test.ts +0 -89
- package/src/richtext/conversion/lexicalToRichTextSource.ts +0 -285
- package/src/richtext/conversion/parseRichTextSource.test.ts +0 -469
- package/src/richtext/conversion/parseRichTextSource.ts +0 -233
- package/src/richtext/conversion/richTextSourceToLexical.test.ts +0 -381
- package/src/richtext/conversion/richTextSourceToLexical.ts +0 -293
- package/src/richtext/shadowRootPolyFill.js +0 -115
- package/src/server.ts +0 -70
- package/src/stories/Button.stories.tsx +0 -20
- package/src/stories/Checkbox.stories.tsx +0 -14
- package/src/stories/Dropdown.stories.tsx +0 -23
- package/src/stories/Introduction.mdx +0 -221
- package/src/stories/RichTextEditor.stories.tsx +0 -24
- package/src/stories/assets/code-brackets.svg +0 -1
- package/src/stories/assets/colors.svg +0 -1
- package/src/stories/assets/comments.svg +0 -1
- package/src/stories/assets/direction.svg +0 -1
- package/src/stories/assets/flow.svg +0 -1
- package/src/stories/assets/plugin.svg +0 -1
- package/src/stories/assets/repo.svg +0 -1
- package/src/stories/assets/stackalt.svg +0 -1
- package/src/utils/Remote.ts +0 -15
- package/src/utils/imageMimeType.ts +0 -23
- package/src/utils/readImage.ts +0 -54
- package/src/utils/resolvePath.ts +0 -32
- package/src/vite-env.d.ts +0 -1
- package/src/vite-index.tsx +0 -7
- package/src/vite-server.ts +0 -42
- package/tailwind.config.js +0 -83
- package/tsconfig.json +0 -19
- package/vite.config.ts +0 -43
|
@@ -1,412 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2
|
-
import {
|
|
3
|
-
INSERT_CHECK_LIST_COMMAND,
|
|
4
|
-
INSERT_ORDERED_LIST_COMMAND,
|
|
5
|
-
INSERT_UNORDERED_LIST_COMMAND,
|
|
6
|
-
REMOVE_LIST_COMMAND,
|
|
7
|
-
$isListNode,
|
|
8
|
-
ListNode,
|
|
9
|
-
} from "@lexical/list";
|
|
10
|
-
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
|
|
11
|
-
import {
|
|
12
|
-
$createHeadingNode,
|
|
13
|
-
$isHeadingNode,
|
|
14
|
-
HeadingTagType,
|
|
15
|
-
} from "@lexical/rich-text";
|
|
16
|
-
import {
|
|
17
|
-
$getSelectionStyleValueForProperty,
|
|
18
|
-
$patchStyleText,
|
|
19
|
-
$setBlocksType,
|
|
20
|
-
} from "@lexical/selection";
|
|
21
|
-
import {
|
|
22
|
-
$findMatchingParent,
|
|
23
|
-
$getNearestBlockElementAncestorOrThrow,
|
|
24
|
-
mergeRegister,
|
|
25
|
-
$getNearestNodeOfType,
|
|
26
|
-
} from "@lexical/utils";
|
|
27
|
-
import {
|
|
28
|
-
$createParagraphNode,
|
|
29
|
-
$getSelection,
|
|
30
|
-
$isRangeSelection,
|
|
31
|
-
$isRootOrShadowRoot,
|
|
32
|
-
$isTextNode,
|
|
33
|
-
COMMAND_PRIORITY_CRITICAL,
|
|
34
|
-
DEPRECATED_$isGridSelection,
|
|
35
|
-
FORMAT_TEXT_COMMAND,
|
|
36
|
-
LexicalEditor,
|
|
37
|
-
NodeKey,
|
|
38
|
-
REDO_COMMAND,
|
|
39
|
-
SELECTION_CHANGE_COMMAND,
|
|
40
|
-
SerializedLexicalNode,
|
|
41
|
-
UNDO_COMMAND,
|
|
42
|
-
} from "lexical";
|
|
43
|
-
import { SerializedEditorState } from "lexical/LexicalEditorState";
|
|
44
|
-
import { FC, useCallback, useEffect, useState } from "react";
|
|
45
|
-
import Bold from "../../../assets/icons/Bold";
|
|
46
|
-
import ImageIcon from "../../../assets/icons/ImageIcon";
|
|
47
|
-
import Italic from "../../../assets/icons/Italic";
|
|
48
|
-
import Strikethrough from "../../../assets/icons/Strikethrough";
|
|
49
|
-
import Underline from "../../../assets/icons/Underline";
|
|
50
|
-
import Undo from "../../../assets/icons/Undo";
|
|
51
|
-
import Button from "../../Button";
|
|
52
|
-
import Dropdown from "../../Dropdown";
|
|
53
|
-
import UploadModal from "../../UploadModal";
|
|
54
|
-
import { INSERT_IMAGE_COMMAND } from "./ImagePlugin";
|
|
55
|
-
import { readImage } from "../../../utils/readImage";
|
|
56
|
-
import { $isLinkNode, LinkNode, TOGGLE_LINK_COMMAND } from "@lexical/link";
|
|
57
|
-
import { getSelectedNode } from "./LinkEditorPlugin";
|
|
58
|
-
import { Check, Link, X } from "react-feather";
|
|
59
|
-
|
|
60
|
-
export interface ToolbarSettingsProps {
|
|
61
|
-
fontsFamilies?: string[];
|
|
62
|
-
fontSizes?: string[];
|
|
63
|
-
colors?: string[];
|
|
64
|
-
onEditor?: (editor: LexicalEditor) => void;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const Toolbar: FC<ToolbarSettingsProps> = ({
|
|
68
|
-
fontSizes,
|
|
69
|
-
fontsFamilies,
|
|
70
|
-
onEditor,
|
|
71
|
-
colors,
|
|
72
|
-
}) => {
|
|
73
|
-
const [editor] = useLexicalComposerContext();
|
|
74
|
-
useEffect(() => {
|
|
75
|
-
if (onEditor) {
|
|
76
|
-
onEditor(editor);
|
|
77
|
-
}
|
|
78
|
-
}, [editor]);
|
|
79
|
-
const [activeEditor, setActiveEditor] = useState(editor);
|
|
80
|
-
const [selectedElementKey, setSelectedElementKey] = useState<NodeKey | null>(
|
|
81
|
-
null
|
|
82
|
-
);
|
|
83
|
-
const [isBold, setIsBold] = useState(false);
|
|
84
|
-
const [isItalic, setIsItalic] = useState(false);
|
|
85
|
-
const [isStrikethrough, setIsStrikethrough] = useState(false);
|
|
86
|
-
const [isUnderline, setIsUnderline] = useState(false);
|
|
87
|
-
const [fontSize, setFontSize] = useState<string>("15px");
|
|
88
|
-
const [fontColor, setFontColor] = useState<string>("#000");
|
|
89
|
-
const [fontFamily, setFontFamily] = useState<string>("Sans");
|
|
90
|
-
const [blockType, setBlockType] =
|
|
91
|
-
useState<keyof typeof blockTypes>("paragraph");
|
|
92
|
-
|
|
93
|
-
const [showModal, setShowModal] = useState<boolean>(false);
|
|
94
|
-
const [inputUrl, setInputUrl] = useState<boolean>(false);
|
|
95
|
-
const [uploadMode, setUploadMode] = useState<"url" | "file">("url");
|
|
96
|
-
const [file, setFile] = useState<File | null>(null);
|
|
97
|
-
|
|
98
|
-
const [url, setUrl] = useState<string | null>(null);
|
|
99
|
-
|
|
100
|
-
const dispatchLinkChange = (url: string | null) => {
|
|
101
|
-
editor.dispatchCommand(TOGGLE_LINK_COMMAND, url);
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
const blockTypes: { [key: string]: string } = {
|
|
105
|
-
paragraph: "Normal",
|
|
106
|
-
h1: "Heading 1",
|
|
107
|
-
h2: "Heading 2",
|
|
108
|
-
h3: "Heading 3",
|
|
109
|
-
h4: "Heading 4",
|
|
110
|
-
h5: "Heading 5",
|
|
111
|
-
h6: "Heading 6",
|
|
112
|
-
number: "Numbered List",
|
|
113
|
-
bullet: "Bulleted List",
|
|
114
|
-
};
|
|
115
|
-
const blockTypesLookup: { [key: string]: string } = {};
|
|
116
|
-
|
|
117
|
-
for (const key in blockTypes) {
|
|
118
|
-
blockTypesLookup[blockTypes[key]] = key;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const updateToolbar = useCallback(() => {
|
|
122
|
-
const selection = $getSelection();
|
|
123
|
-
if ($isRangeSelection(selection)) {
|
|
124
|
-
const anchorNode = selection.anchor.getNode();
|
|
125
|
-
let element =
|
|
126
|
-
anchorNode.getKey() === "root"
|
|
127
|
-
? anchorNode
|
|
128
|
-
: $findMatchingParent(anchorNode, (e) => {
|
|
129
|
-
const parent = e.getParent();
|
|
130
|
-
return parent !== null && $isRootOrShadowRoot(parent);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
if (element === null) {
|
|
134
|
-
element = anchorNode.getTopLevelElementOrThrow();
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// LINK STUFF
|
|
138
|
-
const node = getSelectedNode(selection);
|
|
139
|
-
const linkParent = $findMatchingParent(
|
|
140
|
-
node,
|
|
141
|
-
$isLinkNode
|
|
142
|
-
) as LinkNode | null;
|
|
143
|
-
if (linkParent !== null) {
|
|
144
|
-
const href = linkParent.getURL();
|
|
145
|
-
setUrl(href);
|
|
146
|
-
} else {
|
|
147
|
-
setUrl(null);
|
|
148
|
-
}
|
|
149
|
-
// ====================
|
|
150
|
-
|
|
151
|
-
const elementKey = element.getKey();
|
|
152
|
-
const elementDOM = activeEditor.getElementByKey(elementKey);
|
|
153
|
-
setIsBold(selection.hasFormat("bold"));
|
|
154
|
-
setIsItalic(selection.hasFormat("italic"));
|
|
155
|
-
setIsStrikethrough(selection.hasFormat("strikethrough"));
|
|
156
|
-
setIsUnderline(selection.hasFormat("underline"));
|
|
157
|
-
if (elementDOM !== null) {
|
|
158
|
-
setSelectedElementKey(elementKey);
|
|
159
|
-
if ($isListNode(element)) {
|
|
160
|
-
const parentList = $getNearestNodeOfType<ListNode>(
|
|
161
|
-
anchorNode,
|
|
162
|
-
ListNode
|
|
163
|
-
);
|
|
164
|
-
const type = parentList
|
|
165
|
-
? parentList.getListType()
|
|
166
|
-
: element.getListType();
|
|
167
|
-
setBlockType(type);
|
|
168
|
-
} else {
|
|
169
|
-
const type = $isHeadingNode(element)
|
|
170
|
-
? element.getTag()
|
|
171
|
-
: element.getType();
|
|
172
|
-
if (type in blockTypes) {
|
|
173
|
-
setBlockType(type as keyof typeof blockTypes);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// setFontSize(
|
|
179
|
-
// $getSelectionStyleValueForProperty(selection, "font-size", "15px")
|
|
180
|
-
// );
|
|
181
|
-
// setFontColor(
|
|
182
|
-
// $getSelectionStyleValueForProperty(selection, "color", "#000")
|
|
183
|
-
// );
|
|
184
|
-
// setFontFamily(
|
|
185
|
-
// $getSelectionStyleValueForProperty(selection, "font-family", "Arial")
|
|
186
|
-
// );
|
|
187
|
-
}
|
|
188
|
-
}, [activeEditor]);
|
|
189
|
-
|
|
190
|
-
useEffect(() => {
|
|
191
|
-
return mergeRegister(
|
|
192
|
-
editor.registerUpdateListener(({ editorState }) => {
|
|
193
|
-
editorState.read(() => {
|
|
194
|
-
updateToolbar();
|
|
195
|
-
});
|
|
196
|
-
})
|
|
197
|
-
);
|
|
198
|
-
}, [updateToolbar, activeEditor, editor]);
|
|
199
|
-
|
|
200
|
-
useEffect(() => {
|
|
201
|
-
return editor.registerCommand(
|
|
202
|
-
SELECTION_CHANGE_COMMAND,
|
|
203
|
-
(_payload, newEditor) => {
|
|
204
|
-
updateToolbar();
|
|
205
|
-
setActiveEditor(newEditor);
|
|
206
|
-
return false;
|
|
207
|
-
},
|
|
208
|
-
COMMAND_PRIORITY_CRITICAL
|
|
209
|
-
);
|
|
210
|
-
}, [editor, updateToolbar]);
|
|
211
|
-
|
|
212
|
-
const formatText = (format: keyof typeof blockTypes) => {
|
|
213
|
-
if (["h1", "h2", "h3", "h4", "h5", "h6"].includes(format as string)) {
|
|
214
|
-
if (blockType !== format) {
|
|
215
|
-
editor.update(() => {
|
|
216
|
-
const selection = $getSelection();
|
|
217
|
-
if ($isRangeSelection(selection)) {
|
|
218
|
-
$setBlocksType(selection, () => {
|
|
219
|
-
return $createHeadingNode(format as HeadingTagType);
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
} else if (format === "paragraph" && blockType !== "paragraph") {
|
|
225
|
-
editor.update(() => {
|
|
226
|
-
const selection = $getSelection();
|
|
227
|
-
if (
|
|
228
|
-
$isRangeSelection(selection) ||
|
|
229
|
-
DEPRECATED_$isGridSelection(selection)
|
|
230
|
-
) {
|
|
231
|
-
$setBlocksType(selection, () => $createParagraphNode());
|
|
232
|
-
}
|
|
233
|
-
});
|
|
234
|
-
} else {
|
|
235
|
-
if (format === "number" && blockType !== "number") {
|
|
236
|
-
editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, undefined);
|
|
237
|
-
} else if (format === "bullet" && blockType !== "bullet") {
|
|
238
|
-
editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined);
|
|
239
|
-
} else {
|
|
240
|
-
editor.dispatchCommand(REMOVE_LIST_COMMAND, undefined);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
};
|
|
244
|
-
|
|
245
|
-
const clearFormatting = useCallback(() => {
|
|
246
|
-
editor.update(() => {
|
|
247
|
-
const selection = $getSelection();
|
|
248
|
-
if ($isRangeSelection(selection)) {
|
|
249
|
-
const anchor = selection.anchor;
|
|
250
|
-
const focus = selection.focus;
|
|
251
|
-
const nodes = selection.getNodes();
|
|
252
|
-
|
|
253
|
-
if (anchor.key === focus.key && anchor.offset === focus.offset) {
|
|
254
|
-
return;
|
|
255
|
-
}
|
|
256
|
-
nodes.forEach((node, idx) => {
|
|
257
|
-
if ($isTextNode(node)) {
|
|
258
|
-
if (idx === 0 && anchor.offset !== 0) {
|
|
259
|
-
node = node.splitText(anchor.offset)[1] || node;
|
|
260
|
-
}
|
|
261
|
-
if (idx === nodes.length - 1) {
|
|
262
|
-
node = node.splitText(focus.offset)[0] || node;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
if (node.__style !== "") {
|
|
266
|
-
node.setStyle("");
|
|
267
|
-
}
|
|
268
|
-
if (node.__format !== 0) {
|
|
269
|
-
node.setFormat(0);
|
|
270
|
-
$getNearestBlockElementAncestorOrThrow(node).setFormat("");
|
|
271
|
-
}
|
|
272
|
-
} else if ($isHeadingNode(node)) {
|
|
273
|
-
node.replace($createParagraphNode(), true);
|
|
274
|
-
}
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
});
|
|
278
|
-
}, [activeEditor]);
|
|
279
|
-
|
|
280
|
-
const changeFontFamily = (fontFamily: string) => {
|
|
281
|
-
editor.update(() => {
|
|
282
|
-
const selection = $getSelection();
|
|
283
|
-
if ($isRangeSelection(selection)) {
|
|
284
|
-
$patchStyleText(selection, {
|
|
285
|
-
["font-family"]: fontFamily,
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
});
|
|
289
|
-
};
|
|
290
|
-
|
|
291
|
-
const changeFontSize = (fontSize: string) => {
|
|
292
|
-
editor.update(() => {
|
|
293
|
-
const selection = $getSelection();
|
|
294
|
-
if ($isRangeSelection(selection)) {
|
|
295
|
-
$patchStyleText(selection, {
|
|
296
|
-
["font-size"]: fontSize,
|
|
297
|
-
});
|
|
298
|
-
}
|
|
299
|
-
});
|
|
300
|
-
};
|
|
301
|
-
|
|
302
|
-
return (
|
|
303
|
-
<div className="sticky top-0 border-b bg-background border-highlight flex flex-col">
|
|
304
|
-
<div className="flex flex-row gap-1">
|
|
305
|
-
<Dropdown
|
|
306
|
-
options={Object.values(blockTypes)}
|
|
307
|
-
label={
|
|
308
|
-
blockTypes[blockType as string] ?? blockType + " (not supported)"
|
|
309
|
-
}
|
|
310
|
-
onChange={(selectedOption) => {
|
|
311
|
-
formatText(blockTypesLookup[selectedOption]);
|
|
312
|
-
}}
|
|
313
|
-
/>
|
|
314
|
-
<Button
|
|
315
|
-
variant="primary"
|
|
316
|
-
onClick={(ev) => {
|
|
317
|
-
ev.preventDefault();
|
|
318
|
-
editor.dispatchCommand(FORMAT_TEXT_COMMAND, "bold");
|
|
319
|
-
}}
|
|
320
|
-
active={isBold}
|
|
321
|
-
icon={<Bold className={`${isBold && "stroke-[3px]"}`} />}
|
|
322
|
-
/>
|
|
323
|
-
<Button
|
|
324
|
-
active={isStrikethrough}
|
|
325
|
-
onClick={(ev) => {
|
|
326
|
-
ev.preventDefault();
|
|
327
|
-
editor.dispatchCommand(FORMAT_TEXT_COMMAND, "strikethrough");
|
|
328
|
-
}}
|
|
329
|
-
icon={
|
|
330
|
-
<Strikethrough className={`${isStrikethrough && "stroke-[2px]"}`} />
|
|
331
|
-
}
|
|
332
|
-
/>
|
|
333
|
-
<Button
|
|
334
|
-
active={isItalic}
|
|
335
|
-
onClick={(ev) => {
|
|
336
|
-
ev.preventDefault();
|
|
337
|
-
editor.dispatchCommand(FORMAT_TEXT_COMMAND, "italic");
|
|
338
|
-
}}
|
|
339
|
-
icon={<Italic className={`${isItalic && "stroke-[3px]"}`} />}
|
|
340
|
-
/>
|
|
341
|
-
<Button
|
|
342
|
-
active={url !== null}
|
|
343
|
-
onClick={(ev) => {
|
|
344
|
-
ev.preventDefault();
|
|
345
|
-
setUrl("");
|
|
346
|
-
dispatchLinkChange("");
|
|
347
|
-
}}
|
|
348
|
-
icon={
|
|
349
|
-
<Link
|
|
350
|
-
width={12}
|
|
351
|
-
height={12}
|
|
352
|
-
className={`${url !== null && "stroke-[3px]"}`}
|
|
353
|
-
/>
|
|
354
|
-
}
|
|
355
|
-
/>
|
|
356
|
-
<label className="flex items-center justify-center">
|
|
357
|
-
<ImageIcon />
|
|
358
|
-
<input
|
|
359
|
-
type="file"
|
|
360
|
-
hidden={true}
|
|
361
|
-
onChange={(ev) => {
|
|
362
|
-
ev.preventDefault();
|
|
363
|
-
|
|
364
|
-
readImage(ev)
|
|
365
|
-
.then((res) => {
|
|
366
|
-
editor.dispatchCommand(INSERT_IMAGE_COMMAND, {
|
|
367
|
-
...res,
|
|
368
|
-
});
|
|
369
|
-
})
|
|
370
|
-
.catch((err) => {
|
|
371
|
-
console.error("Error reading image", err);
|
|
372
|
-
});
|
|
373
|
-
}}
|
|
374
|
-
/>
|
|
375
|
-
</label>
|
|
376
|
-
</div>
|
|
377
|
-
{url !== null && (
|
|
378
|
-
<div className="flex flex-row p-2">
|
|
379
|
-
<input
|
|
380
|
-
type="text"
|
|
381
|
-
placeholder="Enter URL"
|
|
382
|
-
className="w-1/3 text-primary bg-background px-2"
|
|
383
|
-
value={url}
|
|
384
|
-
onChange={(ev) => {
|
|
385
|
-
ev.preventDefault();
|
|
386
|
-
setUrl(ev.target.value);
|
|
387
|
-
}}
|
|
388
|
-
></input>
|
|
389
|
-
<Button
|
|
390
|
-
variant="primary"
|
|
391
|
-
onClick={(ev) => {
|
|
392
|
-
ev.preventDefault();
|
|
393
|
-
// empty url will remove link
|
|
394
|
-
dispatchLinkChange(url || null);
|
|
395
|
-
}}
|
|
396
|
-
icon={<Check size={14} />}
|
|
397
|
-
/>
|
|
398
|
-
<Button
|
|
399
|
-
variant="primary"
|
|
400
|
-
onClick={(ev) => {
|
|
401
|
-
ev.preventDefault();
|
|
402
|
-
dispatchLinkChange(null);
|
|
403
|
-
}}
|
|
404
|
-
icon={<X size={14} />}
|
|
405
|
-
/>
|
|
406
|
-
</div>
|
|
407
|
-
)}
|
|
408
|
-
</div>
|
|
409
|
-
);
|
|
410
|
-
};
|
|
411
|
-
|
|
412
|
-
export default Toolbar;
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
4
|
-
import { LexicalEditor } from "lexical";
|
|
5
|
-
import { ListItemNode, ListNode } from "@lexical/list";
|
|
6
|
-
import { LexicalComposer } from "@lexical/react/LexicalComposer";
|
|
7
|
-
import LexicalErrorBoundary from "@lexical/react/LexicalErrorBoundary";
|
|
8
|
-
import { HistoryPlugin } from "@lexical/react/LexicalHistoryPlugin";
|
|
9
|
-
import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin";
|
|
10
|
-
import { ListPlugin } from "@lexical/react/LexicalListPlugin";
|
|
11
|
-
import { LinkPlugin } from "@lexical/react/LexicalLinkPlugin";
|
|
12
|
-
import { FC } from "react";
|
|
13
|
-
import LexicalContentEditable from "./ContentEditable";
|
|
14
|
-
import { ImageNode } from "./Nodes/ImageNode";
|
|
15
|
-
import { AutoFocus } from "./Plugins/AutoFocus";
|
|
16
|
-
import ImagesPlugin from "./Plugins/ImagePlugin";
|
|
17
|
-
import Toolbar from "./Plugins/Toolbar";
|
|
18
|
-
import { AnyRichTextOptions, RichTextSource } from "@valbuild/core";
|
|
19
|
-
import { HeadingNode } from "@lexical/rich-text";
|
|
20
|
-
import { richTextSourceToLexical } from "../../richtext/conversion/richTextSourceToLexical";
|
|
21
|
-
import { useValOverlayContext } from "../ValOverlayContext";
|
|
22
|
-
import { parseRichTextSource } from "../../exports";
|
|
23
|
-
import { LinkNode } from "@lexical/link";
|
|
24
|
-
import LinkEditorPlugin from "./Plugins/LinkEditorPlugin";
|
|
25
|
-
|
|
26
|
-
export interface RichTextEditorProps {
|
|
27
|
-
richtext: RichTextSource<AnyRichTextOptions>;
|
|
28
|
-
onEditor?: (editor: LexicalEditor) => void; // Not the ideal way of passing the editor to the upper context, we need it to be able to save
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function onError(error: any) {
|
|
32
|
-
console.error(error);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const TOOLBAR_HEIGHT = 28;
|
|
36
|
-
|
|
37
|
-
export const RichTextEditor: FC<RichTextEditorProps> = ({
|
|
38
|
-
richtext,
|
|
39
|
-
onEditor,
|
|
40
|
-
}) => {
|
|
41
|
-
const { windowSize } = useValOverlayContext();
|
|
42
|
-
const prePopulatedState = (editor: LexicalEditor) => {
|
|
43
|
-
editor.setEditorState(
|
|
44
|
-
editor.parseEditorState({
|
|
45
|
-
root: richTextSourceToLexical(parseRichTextSource(richtext)),
|
|
46
|
-
})
|
|
47
|
-
);
|
|
48
|
-
};
|
|
49
|
-
const initialConfig = {
|
|
50
|
-
namespace: "val",
|
|
51
|
-
editorState: prePopulatedState,
|
|
52
|
-
nodes: [HeadingNode, ImageNode, ListNode, ListItemNode, LinkNode],
|
|
53
|
-
theme: {
|
|
54
|
-
text: {
|
|
55
|
-
bold: "font-semibold",
|
|
56
|
-
underline: "underline",
|
|
57
|
-
italic: "italic",
|
|
58
|
-
strikethrough: "line-through",
|
|
59
|
-
underlineStrikethrough: "underline line-through",
|
|
60
|
-
},
|
|
61
|
-
list: {
|
|
62
|
-
listitem: "ml-[20px]",
|
|
63
|
-
ol: "list-decimal",
|
|
64
|
-
ul: "list-disc",
|
|
65
|
-
},
|
|
66
|
-
heading: {
|
|
67
|
-
h1: "text-4xl font-bold",
|
|
68
|
-
h2: "text-3xl font-bold",
|
|
69
|
-
h3: "text-2xl font-bold",
|
|
70
|
-
h4: "text-xl font-bold",
|
|
71
|
-
h5: "text-lg font-bold",
|
|
72
|
-
h6: "text-md font-bold",
|
|
73
|
-
},
|
|
74
|
-
link: "text-highlight underline",
|
|
75
|
-
},
|
|
76
|
-
onError,
|
|
77
|
-
};
|
|
78
|
-
return (
|
|
79
|
-
<LexicalComposer initialConfig={initialConfig}>
|
|
80
|
-
<AutoFocus />
|
|
81
|
-
<Toolbar onEditor={onEditor} />
|
|
82
|
-
<RichTextPlugin
|
|
83
|
-
contentEditable={
|
|
84
|
-
<div
|
|
85
|
-
className="font-sans border-b text-primary border-highlight"
|
|
86
|
-
style={{
|
|
87
|
-
minHeight: windowSize?.innerHeight
|
|
88
|
-
? windowSize?.innerHeight - TOOLBAR_HEIGHT
|
|
89
|
-
: undefined,
|
|
90
|
-
}}
|
|
91
|
-
>
|
|
92
|
-
<LexicalContentEditable className="p-4 outline-none bg-fill" />
|
|
93
|
-
</div>
|
|
94
|
-
}
|
|
95
|
-
placeholder={<div className="">Enter some text...</div>}
|
|
96
|
-
ErrorBoundary={LexicalErrorBoundary}
|
|
97
|
-
/>
|
|
98
|
-
<LinkPlugin />
|
|
99
|
-
<LinkEditorPlugin />
|
|
100
|
-
<ListPlugin />
|
|
101
|
-
<ImagesPlugin />
|
|
102
|
-
<HistoryPlugin />
|
|
103
|
-
</LexicalComposer>
|
|
104
|
-
);
|
|
105
|
-
};
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import { FC, useEffect, useState } from "react";
|
|
2
|
-
import Button from "./Button";
|
|
3
|
-
|
|
4
|
-
interface UploadModalProps {
|
|
5
|
-
showModal: boolean;
|
|
6
|
-
setShowModal: React.Dispatch<React.SetStateAction<boolean>>;
|
|
7
|
-
uploadImage: (url: string, alt?: string) => void;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const UploadModal: FC<UploadModalProps> = ({
|
|
11
|
-
showModal,
|
|
12
|
-
setShowModal,
|
|
13
|
-
uploadImage,
|
|
14
|
-
}) => {
|
|
15
|
-
const [uploadUrl, setUploadUrl] = useState<boolean>(true);
|
|
16
|
-
const [url, setUrl] = useState<string>("");
|
|
17
|
-
|
|
18
|
-
useEffect(() => {
|
|
19
|
-
setUrl("");
|
|
20
|
-
}, [uploadUrl]);
|
|
21
|
-
|
|
22
|
-
const loadImage = (files: FileList | null) => {
|
|
23
|
-
const reader = new FileReader();
|
|
24
|
-
reader.onload = function () {
|
|
25
|
-
if (typeof reader.result === "string") {
|
|
26
|
-
setUrl(reader.result);
|
|
27
|
-
}
|
|
28
|
-
return "";
|
|
29
|
-
};
|
|
30
|
-
if (files !== null) {
|
|
31
|
-
reader.readAsDataURL(files[0]);
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
const onSubmit = () => {
|
|
36
|
-
if (url) {
|
|
37
|
-
uploadImage(url);
|
|
38
|
-
setUrl("");
|
|
39
|
-
setShowModal(false);
|
|
40
|
-
setUploadUrl(true);
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
return (
|
|
45
|
-
<div className="absolute z-10 flex flex-col justify-center items-center top-[50%] left-[50%] font-mono">
|
|
46
|
-
{showModal && (
|
|
47
|
-
<div className="flex flex-col items-center justify-center">
|
|
48
|
-
<div className="flex flex-col items-start justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:block sm:p-0">
|
|
49
|
-
<div className="fixed inset-0 transition-opacity">
|
|
50
|
-
<div className="absolute inset-0 bg-gray-500 opacity-75" />
|
|
51
|
-
</div>
|
|
52
|
-
|
|
53
|
-
<div className="flex flex-col items-center justify-between bg-fill rounded-lg transform transition-all min-h-[300px] min-w-[500px] h-full px-5 py-7">
|
|
54
|
-
<div className="flex flex-col items-center w-full gap-5">
|
|
55
|
-
<div className="mb-4">
|
|
56
|
-
<Button
|
|
57
|
-
variant={uploadUrl ? "primary" : "secondary"}
|
|
58
|
-
onClick={() => setUploadUrl(true)}
|
|
59
|
-
>
|
|
60
|
-
Paste URL
|
|
61
|
-
</Button>
|
|
62
|
-
<Button
|
|
63
|
-
variant={uploadUrl ? "secondary" : "primary"}
|
|
64
|
-
onClick={() => setUploadUrl(false)}
|
|
65
|
-
>
|
|
66
|
-
Upload file
|
|
67
|
-
</Button>
|
|
68
|
-
</div>
|
|
69
|
-
{uploadUrl ? (
|
|
70
|
-
<div className="flex flex-col items-center justify-center w-full gap-5">
|
|
71
|
-
<label className="text-primary">Upload URL</label>
|
|
72
|
-
<input
|
|
73
|
-
className="w-full h-10 rounded-lg bg-border"
|
|
74
|
-
value={url}
|
|
75
|
-
onChange={(event) => setUrl(event.target.value)}
|
|
76
|
-
/>
|
|
77
|
-
</div>
|
|
78
|
-
) : (
|
|
79
|
-
<div className="flex flex-col items-center justify-center w-full gap-5">
|
|
80
|
-
<label className="text-primary">Choose File</label>
|
|
81
|
-
<input
|
|
82
|
-
className="h-10 rounded-lg w-fit"
|
|
83
|
-
type="file"
|
|
84
|
-
onChange={(e) => loadImage(e.target.files)}
|
|
85
|
-
/>
|
|
86
|
-
</div>
|
|
87
|
-
)}
|
|
88
|
-
</div>
|
|
89
|
-
<div className="flex flex-row items-center justify-center gap-5 ">
|
|
90
|
-
<Button variant="secondary" onClick={() => setShowModal(false)}>
|
|
91
|
-
Cancel
|
|
92
|
-
</Button>
|
|
93
|
-
<Button
|
|
94
|
-
variant="primary"
|
|
95
|
-
disabled={url === ""}
|
|
96
|
-
onClick={() => onSubmit()}
|
|
97
|
-
>
|
|
98
|
-
Upload
|
|
99
|
-
</Button>
|
|
100
|
-
</div>
|
|
101
|
-
</div>
|
|
102
|
-
</div>
|
|
103
|
-
</div>
|
|
104
|
-
)}
|
|
105
|
-
</div>
|
|
106
|
-
);
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
export default UploadModal;
|
package/src/components/User.tsx
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { FC } from "react";
|
|
2
|
-
|
|
3
|
-
const User: FC<{ name: string }> = ({ name }) => {
|
|
4
|
-
return (
|
|
5
|
-
<div className="flex flex-row items-center gap-2">
|
|
6
|
-
<div className="w-[32px] h-[32px] rounded-full bg-light-gray flex justify-center items-center">
|
|
7
|
-
{name
|
|
8
|
-
.split(" ")
|
|
9
|
-
.map((name) => name.charAt(0))
|
|
10
|
-
.join("")}
|
|
11
|
-
</div>
|
|
12
|
-
<div className="text-white">{name}</div>
|
|
13
|
-
</div>
|
|
14
|
-
);
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export default User;
|