@valbuild/ui 0.18.0 → 0.20.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/dist/valbuild-ui.cjs.d.ts +3 -3
- package/dist/valbuild-ui.cjs.js +7217 -2477
- package/dist/valbuild-ui.esm.js +7083 -2343
- package/jest.config.js +4 -0
- package/package.json +6 -3
- package/src/assets/icons/Bold.tsx +2 -2
- package/src/assets/icons/Chevron.tsx +2 -2
- package/src/assets/icons/ImageIcon.tsx +2 -2
- package/src/assets/icons/Italic.tsx +2 -2
- package/src/assets/icons/Strikethrough.tsx +2 -2
- package/src/assets/icons/Underline.tsx +2 -2
- package/src/components/Button.tsx +2 -2
- package/src/components/Dropdown.tsx +2 -2
- package/src/components/RichTextEditor/Nodes/ImageNode.tsx +50 -59
- package/src/components/RichTextEditor/Plugins/ImagePlugin.tsx +1 -2
- package/src/components/RichTextEditor/Plugins/Toolbar.tsx +32 -54
- package/src/components/RichTextEditor/RichTextEditor.tsx +22 -118
- package/src/components/RichTextEditor/conversion.test.ts +132 -0
- package/src/components/RichTextEditor/conversion.ts +389 -0
- package/src/components/ValOverlay.tsx +333 -88
- package/src/components/ValWindow.stories.tsx +55 -49
- package/src/components/ValWindow.tsx +80 -78
- package/src/components/forms/Form.tsx +6 -2
- package/src/components/forms/TextArea.tsx +1 -1
- package/src/stories/RichTextEditor.stories.tsx +30 -278
- package/src/utils/readImage.ts +78 -0
- package/tailwind.config.js +4 -4
- package/tsconfig.json +2 -1
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
import React, { useEffect, useRef, useState } from "react";
|
|
2
2
|
import { AlignJustify, X } from "react-feather";
|
|
3
3
|
import classNames from "classnames";
|
|
4
|
+
import { Resizable } from "react-resizable";
|
|
4
5
|
|
|
5
6
|
export type ValWindowProps = {
|
|
6
|
-
children:
|
|
7
|
+
children:
|
|
8
|
+
| [React.ReactNode, React.ReactNode, React.ReactNode]
|
|
9
|
+
| React.ReactNode;
|
|
10
|
+
|
|
7
11
|
onClose: () => void;
|
|
8
12
|
position?: { left: number; top: number };
|
|
9
13
|
isInitialized?: true;
|
|
10
14
|
};
|
|
11
15
|
|
|
16
|
+
const MIN_WIDTH = 320;
|
|
17
|
+
const MIN_HEIGHT = 320;
|
|
18
|
+
|
|
12
19
|
export function ValWindow({
|
|
13
20
|
position,
|
|
14
21
|
onClose,
|
|
@@ -30,99 +37,94 @@ export function ValWindow({
|
|
|
30
37
|
}, []);
|
|
31
38
|
|
|
32
39
|
//
|
|
33
|
-
const [size,
|
|
40
|
+
const [size, setSize] = useState<{ height: number; width: number }>();
|
|
41
|
+
//
|
|
42
|
+
const bottomRef = useRef<HTMLDivElement>(null);
|
|
34
43
|
|
|
35
44
|
return (
|
|
36
|
-
<
|
|
45
|
+
<Resizable
|
|
46
|
+
width={size?.width || MIN_WIDTH}
|
|
47
|
+
height={size?.height || MIN_HEIGHT}
|
|
48
|
+
onResize={(_, { size }) => setSize(size)}
|
|
49
|
+
handle={
|
|
50
|
+
<div className="fixed bottom-0 right-0 cursor-se-resize">
|
|
51
|
+
<svg
|
|
52
|
+
height="18"
|
|
53
|
+
viewBox="0 0 18 18"
|
|
54
|
+
width="18"
|
|
55
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
56
|
+
>
|
|
57
|
+
<path
|
|
58
|
+
d="m14.228 16.227a1 1 0 0 1 -.707-1.707l1-1a1 1 0 0 1 1.416 1.414l-1 1a1 1 0 0 1 -.707.293zm-5.638 0a1 1 0 0 1 -.707-1.707l6.638-6.638a1 1 0 0 1 1.416 1.414l-6.638 6.638a1 1 0 0 1 -.707.293zm-5.84 0a1 1 0 0 1 -.707-1.707l12.477-12.477a1 1 0 1 1 1.415 1.414l-12.478 12.477a1 1 0 0 1 -.707.293z"
|
|
59
|
+
fill="currentColor"
|
|
60
|
+
/>
|
|
61
|
+
</svg>
|
|
62
|
+
</div>
|
|
63
|
+
}
|
|
64
|
+
draggableOpts={{}}
|
|
37
65
|
className={classNames(
|
|
38
|
-
"absolute inset-0
|
|
66
|
+
"absolute inset-0 w-full tablet:w-auto tablet:h-auto tablet:min-h-fit tablet:rounded bg-base text-primary drop-shadow-2xl min-w-[320px] transition-opacity duration-300 delay-75 max-w-full",
|
|
39
67
|
{
|
|
40
68
|
"opacity-0": !isInitialized,
|
|
41
69
|
"opacity-100": isInitialized,
|
|
42
70
|
}
|
|
43
71
|
)}
|
|
44
|
-
ref={resizeRef}
|
|
45
|
-
style={{
|
|
46
|
-
left: draggedPosition.left,
|
|
47
|
-
top: draggedPosition.top,
|
|
48
|
-
width: size?.width || 320,
|
|
49
|
-
height: size?.height || 320,
|
|
50
|
-
}}
|
|
51
72
|
>
|
|
52
73
|
<div
|
|
53
|
-
ref={dragRef}
|
|
54
|
-
className="relative flex justify-center px-2 pt-2 text-primary"
|
|
55
|
-
>
|
|
56
|
-
<AlignJustify
|
|
57
|
-
size={16}
|
|
58
|
-
className="hidden w-full cursor-grab tablet:block"
|
|
59
|
-
onMouseDown={(e) => {
|
|
60
|
-
e.preventDefault();
|
|
61
|
-
e.stopPropagation();
|
|
62
|
-
onMouseDownDrag();
|
|
63
|
-
}}
|
|
64
|
-
/>
|
|
65
|
-
<button
|
|
66
|
-
className="absolute top-0 right-0 px-4 py-2 focus:outline-none focus-visible:outline-highlight"
|
|
67
|
-
onClick={onClose}
|
|
68
|
-
>
|
|
69
|
-
<X size={16} />
|
|
70
|
-
</button>
|
|
71
|
-
</div>
|
|
72
|
-
<div style={{ height: (size?.height || 320) - 64 }}>{children}</div>
|
|
73
|
-
<div
|
|
74
|
-
className="absolute bottom-0 right-0 hidden ml-auto select-none tablet:block text-border cursor-nwse-resize"
|
|
75
74
|
style={{
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
width: size?.width || MIN_WIDTH,
|
|
76
|
+
height: size?.height || MIN_HEIGHT,
|
|
77
|
+
left: draggedPosition.left,
|
|
78
|
+
top: draggedPosition.top,
|
|
78
79
|
}}
|
|
79
|
-
onMouseDown={onMouseDownResize}
|
|
80
80
|
>
|
|
81
|
-
<
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
width="18"
|
|
85
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
81
|
+
<div
|
|
82
|
+
ref={dragRef}
|
|
83
|
+
className="relative flex items-center justify-center px-2 pt-2 text-primary"
|
|
86
84
|
>
|
|
87
|
-
<
|
|
88
|
-
|
|
89
|
-
|
|
85
|
+
<AlignJustify
|
|
86
|
+
size={16}
|
|
87
|
+
className="hidden w-full cursor-grab tablet:block"
|
|
88
|
+
onMouseDown={(e) => {
|
|
89
|
+
e.preventDefault();
|
|
90
|
+
e.stopPropagation();
|
|
91
|
+
onMouseDownDrag();
|
|
92
|
+
}}
|
|
90
93
|
/>
|
|
91
|
-
|
|
94
|
+
<button
|
|
95
|
+
className="absolute top-0 right-0 px-4 py-2 focus:outline-none focus-visible:outline-highlight"
|
|
96
|
+
onClick={onClose}
|
|
97
|
+
>
|
|
98
|
+
<X size={16} />
|
|
99
|
+
</button>
|
|
100
|
+
</div>
|
|
101
|
+
<form
|
|
102
|
+
className="h-full"
|
|
103
|
+
onSubmit={(ev) => {
|
|
104
|
+
ev.preventDefault();
|
|
105
|
+
}}
|
|
106
|
+
>
|
|
107
|
+
{Array.isArray(children) && children.slice(0, 1)}
|
|
108
|
+
<div
|
|
109
|
+
className="relative overflow-scroll"
|
|
110
|
+
style={{
|
|
111
|
+
height:
|
|
112
|
+
(size?.height || MIN_HEIGHT) -
|
|
113
|
+
(64 +
|
|
114
|
+
(bottomRef.current?.getBoundingClientRect()?.height || 0)),
|
|
115
|
+
}}
|
|
116
|
+
>
|
|
117
|
+
{Array.isArray(children) ? children.slice(1, -1) : children}
|
|
118
|
+
</div>
|
|
119
|
+
<div ref={bottomRef} className="w-full px-4 pb-0">
|
|
120
|
+
{Array.isArray(children) && children.slice(-1)}
|
|
121
|
+
</div>
|
|
122
|
+
</form>
|
|
92
123
|
</div>
|
|
93
|
-
</
|
|
124
|
+
</Resizable>
|
|
94
125
|
);
|
|
95
126
|
}
|
|
96
127
|
|
|
97
|
-
function useResize() {
|
|
98
|
-
const ref = useRef<HTMLDivElement>(null);
|
|
99
|
-
const [size, setSize] = useState<{ height: number; width: number }>();
|
|
100
|
-
|
|
101
|
-
const handler = (mouseDownEvent: React.MouseEvent) => {
|
|
102
|
-
const startSize = ref.current?.getBoundingClientRect();
|
|
103
|
-
|
|
104
|
-
const startPosition = { x: mouseDownEvent.pageX, y: mouseDownEvent.pageY };
|
|
105
|
-
function onMouseMove(mouseMoveEvent: MouseEvent) {
|
|
106
|
-
if (startSize) {
|
|
107
|
-
const nextWidth =
|
|
108
|
-
startSize.width - startPosition.x + mouseMoveEvent.pageX;
|
|
109
|
-
const nextHeight =
|
|
110
|
-
startSize.height - startPosition.y + mouseMoveEvent.pageY;
|
|
111
|
-
setSize({
|
|
112
|
-
width: nextWidth > 320 ? nextWidth : 320,
|
|
113
|
-
height: nextHeight > 320 ? nextHeight : 320,
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
function onMouseUp() {
|
|
118
|
-
document.body.removeEventListener("mousemove", onMouseMove);
|
|
119
|
-
}
|
|
120
|
-
document.body.addEventListener("mousemove", onMouseMove);
|
|
121
|
-
document.body.addEventListener("mouseup", onMouseUp, { once: true });
|
|
122
|
-
};
|
|
123
|
-
return [size, ref, handler] as const;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
128
|
function useDrag({
|
|
127
129
|
position: initPosition,
|
|
128
130
|
}: {
|
|
@@ -140,8 +142,8 @@ function useDrag({
|
|
|
140
142
|
top: top < 0 ? 0 : top,
|
|
141
143
|
});
|
|
142
144
|
} else {
|
|
143
|
-
const left = window.innerWidth / 2 -
|
|
144
|
-
const top = window.innerHeight / 2 -
|
|
145
|
+
const left = window.innerWidth / 2 - MIN_WIDTH / 2 - window.scrollX;
|
|
146
|
+
const top = window.innerHeight / 2 - MIN_HEIGHT / 2 + window.scrollY;
|
|
145
147
|
setPosition({
|
|
146
148
|
left,
|
|
147
149
|
top,
|
|
@@ -182,7 +184,7 @@ function useDrag({
|
|
|
182
184
|
// TODO: rename hook from useDrag to usePosition or something since we also check for screen width here?
|
|
183
185
|
useEffect(() => {
|
|
184
186
|
const onResize = () => {
|
|
185
|
-
if (window.screen.width <
|
|
187
|
+
if (window.screen.width < MIN_WIDTH * 2) {
|
|
186
188
|
setPosition({
|
|
187
189
|
left: 0,
|
|
188
190
|
top: 0,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RichText } from "@valbuild/core";
|
|
1
|
+
import { AnyRichTextOptions, RichText } from "@valbuild/core";
|
|
2
2
|
import { LexicalEditor } from "lexical";
|
|
3
3
|
import { useEffect, useState } from "react";
|
|
4
4
|
import { RichTextEditor } from "../RichTextEditor/RichTextEditor";
|
|
@@ -15,7 +15,11 @@ export type Inputs = {
|
|
|
15
15
|
data: TextData;
|
|
16
16
|
}
|
|
17
17
|
| { status: "completed"; type: "image"; data: ImageData }
|
|
18
|
-
| {
|
|
18
|
+
| {
|
|
19
|
+
status: "completed";
|
|
20
|
+
type: "richtext";
|
|
21
|
+
data: RichText<AnyRichTextOptions>;
|
|
22
|
+
};
|
|
19
23
|
};
|
|
20
24
|
|
|
21
25
|
export type FormProps = {
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
RichTextEditorProps,
|
|
4
4
|
} from "../components/RichTextEditor/RichTextEditor";
|
|
5
5
|
import { Meta, Story } from "@storybook/react";
|
|
6
|
-
import {
|
|
6
|
+
import { AnyRichTextOptions, RichText } from "@valbuild/core";
|
|
7
7
|
|
|
8
8
|
export default {
|
|
9
9
|
title: "RichTextEditor",
|
|
@@ -18,299 +18,51 @@ export const DropdownStory = Template.bind({});
|
|
|
18
18
|
|
|
19
19
|
DropdownStory.args = {
|
|
20
20
|
richtext: {
|
|
21
|
-
|
|
21
|
+
_type: "richtext",
|
|
22
22
|
children: [
|
|
23
|
+
{ tag: "h1", children: ["Title 1"] },
|
|
24
|
+
{ tag: "h2", children: ["Title 2"] },
|
|
25
|
+
{ tag: "h3", children: ["Title 3"] },
|
|
26
|
+
{ tag: "h4", children: ["Title 4"] },
|
|
27
|
+
{ tag: "h5", children: ["Title 5"] },
|
|
28
|
+
{ tag: "h6", children: ["Title 6"] },
|
|
23
29
|
{
|
|
30
|
+
tag: "p",
|
|
24
31
|
children: [
|
|
25
32
|
{
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
style: "",
|
|
30
|
-
text: "Heading 1",
|
|
31
|
-
type: "text",
|
|
32
|
-
version: 1,
|
|
33
|
+
tag: "span",
|
|
34
|
+
classes: ["bold", "italic", "line-through"],
|
|
35
|
+
children: ["Formatted span"],
|
|
33
36
|
},
|
|
34
37
|
],
|
|
35
|
-
direction: "ltr",
|
|
36
|
-
format: "",
|
|
37
|
-
indent: 0,
|
|
38
|
-
type: "heading",
|
|
39
|
-
version: 1,
|
|
40
|
-
tag: "h1",
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
children: [
|
|
44
|
-
{
|
|
45
|
-
detail: 0,
|
|
46
|
-
format: 0,
|
|
47
|
-
mode: "normal",
|
|
48
|
-
style: "",
|
|
49
|
-
text: "Heading 2",
|
|
50
|
-
type: "text",
|
|
51
|
-
version: 1,
|
|
52
|
-
},
|
|
53
|
-
],
|
|
54
|
-
direction: "ltr",
|
|
55
|
-
format: "",
|
|
56
|
-
indent: 0,
|
|
57
|
-
type: "heading",
|
|
58
|
-
version: 1,
|
|
59
|
-
tag: "h2",
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
children: [],
|
|
63
|
-
direction: "ltr",
|
|
64
|
-
format: "",
|
|
65
|
-
indent: 0,
|
|
66
|
-
type: "paragraph",
|
|
67
|
-
version: 1,
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
children: [
|
|
71
|
-
{
|
|
72
|
-
detail: 0,
|
|
73
|
-
format: 0,
|
|
74
|
-
mode: "normal",
|
|
75
|
-
style: "",
|
|
76
|
-
text: "Normal",
|
|
77
|
-
type: "text",
|
|
78
|
-
version: 1,
|
|
79
|
-
},
|
|
80
|
-
],
|
|
81
|
-
direction: "ltr",
|
|
82
|
-
format: "",
|
|
83
|
-
indent: 0,
|
|
84
|
-
type: "paragraph",
|
|
85
|
-
version: 1,
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
children: [
|
|
89
|
-
{
|
|
90
|
-
detail: 0,
|
|
91
|
-
format: 0,
|
|
92
|
-
mode: "normal",
|
|
93
|
-
style: "font-size: 20px;",
|
|
94
|
-
text: "Normal Font size 20",
|
|
95
|
-
type: "text",
|
|
96
|
-
version: 1,
|
|
97
|
-
},
|
|
98
|
-
],
|
|
99
|
-
direction: "ltr",
|
|
100
|
-
format: "",
|
|
101
|
-
indent: 0,
|
|
102
|
-
type: "paragraph",
|
|
103
|
-
version: 1,
|
|
104
|
-
},
|
|
105
|
-
{
|
|
106
|
-
children: [
|
|
107
|
-
{
|
|
108
|
-
detail: 0,
|
|
109
|
-
format: 0,
|
|
110
|
-
mode: "normal",
|
|
111
|
-
style: "font-family: serif;",
|
|
112
|
-
text: "Normal font type serif",
|
|
113
|
-
type: "text",
|
|
114
|
-
version: 1,
|
|
115
|
-
},
|
|
116
|
-
],
|
|
117
|
-
direction: "ltr",
|
|
118
|
-
format: "",
|
|
119
|
-
indent: 0,
|
|
120
|
-
type: "paragraph",
|
|
121
|
-
version: 1,
|
|
122
|
-
},
|
|
123
|
-
{
|
|
124
|
-
children: [
|
|
125
|
-
{
|
|
126
|
-
detail: 0,
|
|
127
|
-
format: 1,
|
|
128
|
-
mode: "normal",
|
|
129
|
-
style: "font-family: serif;",
|
|
130
|
-
text: "Serif and bold",
|
|
131
|
-
type: "text",
|
|
132
|
-
version: 1,
|
|
133
|
-
},
|
|
134
|
-
],
|
|
135
|
-
direction: "ltr",
|
|
136
|
-
format: "",
|
|
137
|
-
indent: 0,
|
|
138
|
-
type: "paragraph",
|
|
139
|
-
version: 1,
|
|
140
|
-
},
|
|
141
|
-
{
|
|
142
|
-
children: [
|
|
143
|
-
{
|
|
144
|
-
detail: 0,
|
|
145
|
-
format: 2,
|
|
146
|
-
mode: "normal",
|
|
147
|
-
style: "",
|
|
148
|
-
text: "Arial and italic",
|
|
149
|
-
type: "text",
|
|
150
|
-
version: 1,
|
|
151
|
-
},
|
|
152
|
-
],
|
|
153
|
-
direction: "ltr",
|
|
154
|
-
format: "",
|
|
155
|
-
indent: 0,
|
|
156
|
-
type: "paragraph",
|
|
157
|
-
version: 1,
|
|
158
|
-
},
|
|
159
|
-
{
|
|
160
|
-
children: [],
|
|
161
|
-
direction: null,
|
|
162
|
-
format: "",
|
|
163
|
-
indent: 0,
|
|
164
|
-
type: "paragraph",
|
|
165
|
-
version: 1,
|
|
166
|
-
},
|
|
167
|
-
{
|
|
168
|
-
children: [
|
|
169
|
-
{
|
|
170
|
-
children: [
|
|
171
|
-
{
|
|
172
|
-
detail: 0,
|
|
173
|
-
format: 0,
|
|
174
|
-
mode: "normal",
|
|
175
|
-
style: "",
|
|
176
|
-
text: "Num list 1",
|
|
177
|
-
type: "text",
|
|
178
|
-
version: 1,
|
|
179
|
-
},
|
|
180
|
-
],
|
|
181
|
-
direction: "ltr",
|
|
182
|
-
format: "",
|
|
183
|
-
indent: 0,
|
|
184
|
-
type: "listitem",
|
|
185
|
-
version: 1,
|
|
186
|
-
value: 1,
|
|
187
|
-
},
|
|
188
|
-
{
|
|
189
|
-
children: [
|
|
190
|
-
{
|
|
191
|
-
detail: 0,
|
|
192
|
-
format: 0,
|
|
193
|
-
mode: "normal",
|
|
194
|
-
style: "",
|
|
195
|
-
text: "Num list 2",
|
|
196
|
-
type: "text",
|
|
197
|
-
version: 1,
|
|
198
|
-
},
|
|
199
|
-
// TODO: image
|
|
200
|
-
// {
|
|
201
|
-
// altText: "",
|
|
202
|
-
// height: 0,
|
|
203
|
-
// maxWidth: 0,
|
|
204
|
-
// src: "https://picsum.photos/id/237/200/300",
|
|
205
|
-
// type: "image",
|
|
206
|
-
// version: 1,
|
|
207
|
-
// width: 0,
|
|
208
|
-
// },
|
|
209
|
-
],
|
|
210
|
-
direction: "ltr",
|
|
211
|
-
format: "",
|
|
212
|
-
indent: 0,
|
|
213
|
-
type: "listitem",
|
|
214
|
-
version: 1,
|
|
215
|
-
value: 2,
|
|
216
|
-
},
|
|
217
|
-
],
|
|
218
|
-
direction: "ltr",
|
|
219
|
-
format: "",
|
|
220
|
-
indent: 0,
|
|
221
|
-
type: "list",
|
|
222
|
-
version: 1,
|
|
223
|
-
listType: "number",
|
|
224
|
-
start: 1,
|
|
225
|
-
tag: "ol",
|
|
226
|
-
},
|
|
227
|
-
{
|
|
228
|
-
children: [],
|
|
229
|
-
direction: null,
|
|
230
|
-
format: "",
|
|
231
|
-
indent: 0,
|
|
232
|
-
type: "paragraph",
|
|
233
|
-
version: 1,
|
|
234
38
|
},
|
|
235
39
|
{
|
|
40
|
+
tag: "ul",
|
|
236
41
|
children: [
|
|
237
42
|
{
|
|
43
|
+
tag: "li",
|
|
238
44
|
children: [
|
|
239
45
|
{
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
},
|
|
256
|
-
{
|
|
257
|
-
children: [
|
|
258
|
-
{
|
|
259
|
-
detail: 0,
|
|
260
|
-
format: 0,
|
|
261
|
-
mode: "normal",
|
|
262
|
-
style: "",
|
|
263
|
-
text: "Bullet list 2",
|
|
264
|
-
type: "text",
|
|
265
|
-
version: 1,
|
|
46
|
+
tag: "ol",
|
|
47
|
+
dir: "rtl",
|
|
48
|
+
children: [
|
|
49
|
+
{
|
|
50
|
+
tag: "li",
|
|
51
|
+
children: [
|
|
52
|
+
{
|
|
53
|
+
tag: "span",
|
|
54
|
+
classes: ["italic"],
|
|
55
|
+
children: ["number 1.1"],
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
{ tag: "li", children: ["number 1.2"] },
|
|
60
|
+
],
|
|
266
61
|
},
|
|
267
62
|
],
|
|
268
|
-
direction: "ltr",
|
|
269
|
-
format: "",
|
|
270
|
-
indent: 0,
|
|
271
|
-
type: "listitem",
|
|
272
|
-
version: 1,
|
|
273
|
-
value: 2,
|
|
274
63
|
},
|
|
275
64
|
],
|
|
276
|
-
direction: "ltr",
|
|
277
|
-
format: "",
|
|
278
|
-
indent: 0,
|
|
279
|
-
type: "list",
|
|
280
|
-
version: 1,
|
|
281
|
-
listType: "bullet",
|
|
282
|
-
start: 1,
|
|
283
|
-
tag: "ul",
|
|
284
|
-
},
|
|
285
|
-
{
|
|
286
|
-
children: [],
|
|
287
|
-
direction: null,
|
|
288
|
-
format: "",
|
|
289
|
-
indent: 0,
|
|
290
|
-
type: "paragraph",
|
|
291
|
-
version: 1,
|
|
292
|
-
},
|
|
293
|
-
{
|
|
294
|
-
children: [],
|
|
295
|
-
direction: "ltr",
|
|
296
|
-
format: "",
|
|
297
|
-
indent: 0,
|
|
298
|
-
type: "paragraph",
|
|
299
|
-
version: 1,
|
|
300
|
-
},
|
|
301
|
-
{
|
|
302
|
-
children: [],
|
|
303
|
-
direction: null,
|
|
304
|
-
format: "",
|
|
305
|
-
indent: 0,
|
|
306
|
-
type: "paragraph",
|
|
307
|
-
version: 1,
|
|
308
65
|
},
|
|
309
66
|
],
|
|
310
|
-
|
|
311
|
-
format: "",
|
|
312
|
-
indent: 0,
|
|
313
|
-
type: "root",
|
|
314
|
-
version: 1,
|
|
315
|
-
} as RichText,
|
|
67
|
+
} as RichText<AnyRichTextOptions>,
|
|
316
68
|
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Internal } from "@valbuild/core";
|
|
2
|
+
import { ChangeEvent } from "react";
|
|
3
|
+
|
|
4
|
+
const textEncoder = new TextEncoder();
|
|
5
|
+
|
|
6
|
+
export function readImage(ev: ChangeEvent<HTMLInputElement>) {
|
|
7
|
+
return new Promise<{
|
|
8
|
+
src: string;
|
|
9
|
+
sha256: string;
|
|
10
|
+
width?: number;
|
|
11
|
+
height?: number;
|
|
12
|
+
mimeType?: string;
|
|
13
|
+
fileExt?: string;
|
|
14
|
+
}>((resolve, reject) => {
|
|
15
|
+
const imageFile = ev.currentTarget.files?.[0];
|
|
16
|
+
const reader = new FileReader();
|
|
17
|
+
reader.addEventListener("load", () => {
|
|
18
|
+
const result = reader.result;
|
|
19
|
+
if (typeof result === "string") {
|
|
20
|
+
const image = new Image();
|
|
21
|
+
image.addEventListener("load", async () => {
|
|
22
|
+
const sha256 = await Internal.getSHA256Hash(
|
|
23
|
+
textEncoder.encode(result)
|
|
24
|
+
);
|
|
25
|
+
if (image.naturalWidth && image.naturalHeight) {
|
|
26
|
+
const mimeType = getMimeType(result);
|
|
27
|
+
console.log(result.slice(0, 30), mimeType);
|
|
28
|
+
resolve({
|
|
29
|
+
src: result,
|
|
30
|
+
width: image.naturalWidth,
|
|
31
|
+
height: image.naturalHeight,
|
|
32
|
+
sha256,
|
|
33
|
+
mimeType,
|
|
34
|
+
fileExt: mimeType && mimeTypeToFileExt(mimeType),
|
|
35
|
+
});
|
|
36
|
+
} else {
|
|
37
|
+
resolve({
|
|
38
|
+
src: result,
|
|
39
|
+
sha256,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
image.src = result;
|
|
44
|
+
} else if (!result) {
|
|
45
|
+
reject({ message: "Empty result" });
|
|
46
|
+
} else {
|
|
47
|
+
reject({ message: "Unexpected image result type", result });
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
if (imageFile) {
|
|
51
|
+
reader.readAsDataURL(imageFile);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const MIME_TYPE_REGEX =
|
|
57
|
+
/^data:(image\/(png|jpeg|jpg|gif|webp|bmp|tiff|ico|svg\+xml));base64,/;
|
|
58
|
+
|
|
59
|
+
function getMimeType(base64Url: string): string | undefined {
|
|
60
|
+
const match = MIME_TYPE_REGEX.exec(base64Url);
|
|
61
|
+
if (match && match[1]) {
|
|
62
|
+
return match[1];
|
|
63
|
+
}
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function mimeTypeToFileExt(mimeType: string) {
|
|
68
|
+
if (mimeType === "image/svg+xml") {
|
|
69
|
+
return "svg";
|
|
70
|
+
}
|
|
71
|
+
if (mimeType === "image/vnd.microsoft.icon") {
|
|
72
|
+
return "ico";
|
|
73
|
+
}
|
|
74
|
+
if (mimeType.startsWith("image/")) {
|
|
75
|
+
return mimeType.slice("image/".length);
|
|
76
|
+
}
|
|
77
|
+
return mimeType;
|
|
78
|
+
}
|
package/tailwind.config.js
CHANGED
|
@@ -4,10 +4,10 @@ module.exports = {
|
|
|
4
4
|
darkMode: ["class", '[data-mode="dark"]'],
|
|
5
5
|
theme: {
|
|
6
6
|
zIndex: {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
hover: 1,
|
|
8
|
+
window: 2,
|
|
9
|
+
full: 3,
|
|
10
|
+
overlay: 4,
|
|
11
11
|
},
|
|
12
12
|
colors: {
|
|
13
13
|
base: "var(--val-theme-base)",
|