@bizapps/design-system-testing 1.8.0 → 1.9.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/README.md +5 -3
- package/dist/main.cjs +131 -385
- package/dist/main.cjs.map +1 -1
- package/dist/main.css +1 -1
- package/dist/main.d.cts +95 -122
- package/dist/main.d.ts +95 -122
- package/dist/main.js +133 -385
- package/dist/main.js.map +1 -1
- package/package.json +175 -225
- package/dist/editor.cjs +0 -1478
- package/dist/editor.cjs.map +0 -1
- package/dist/editor.d.cts +0 -428
- package/dist/editor.d.ts +0 -428
- package/dist/editor.js +0 -1461
- package/dist/editor.js.map +0 -1
- package/dist/policy-DPpMNLiO.d.cts +0 -75
- package/dist/policy-DPpMNLiO.d.ts +0 -75
- package/dist/policy.cjs +0 -33
- package/dist/policy.cjs.map +0 -1
- package/dist/policy.d.cts +0 -1
- package/dist/policy.d.ts +0 -1
- package/dist/policy.js +0 -29
- package/dist/policy.js.map +0 -1
package/dist/editor.cjs
DELETED
|
@@ -1,1478 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
var react = require('react');
|
|
5
|
-
var react$1 = require('@tiptap/react');
|
|
6
|
-
var clsx = require('clsx');
|
|
7
|
-
var tailwindMerge = require('tailwind-merge');
|
|
8
|
-
var radixUi = require('radix-ui');
|
|
9
|
-
var jsxRuntime = require('react/jsx-runtime');
|
|
10
|
-
var classVarianceAuthority = require('class-variance-authority');
|
|
11
|
-
var lucideReact = require('lucide-react');
|
|
12
|
-
var react$2 = require('motion/react');
|
|
13
|
-
var reactDropzone = require('react-dropzone');
|
|
14
|
-
var Highlight = require('@tiptap/extension-highlight');
|
|
15
|
-
var extensionTable = require('@tiptap/extension-table');
|
|
16
|
-
var StarterKit = require('@tiptap/starter-kit');
|
|
17
|
-
var extensionImage = require('@tiptap/extension-image');
|
|
18
|
-
var menus = require('@tiptap/react/menus');
|
|
19
|
-
|
|
20
|
-
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
21
|
-
|
|
22
|
-
var Highlight__default = /*#__PURE__*/_interopDefault(Highlight);
|
|
23
|
-
var StarterKit__default = /*#__PURE__*/_interopDefault(StarterKit);
|
|
24
|
-
|
|
25
|
-
// src/components/compounds/RichTextEditor/rich-text-editor.tsx
|
|
26
|
-
function cn(...inputs) {
|
|
27
|
-
return tailwindMerge.twMerge(clsx.clsx(inputs));
|
|
28
|
-
}
|
|
29
|
-
function Label({ className, ...props }) {
|
|
30
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
31
|
-
radixUi.Label.Root,
|
|
32
|
-
{
|
|
33
|
-
"data-slot": "label",
|
|
34
|
-
className: cn(
|
|
35
|
-
"text-foreground text-sm leading-6 font-medium transition-colors select-none",
|
|
36
|
-
"group-has-disabled/field:cursor-not-allowed group-has-disabled/field:opacity-50",
|
|
37
|
-
"peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
|
|
38
|
-
"aria-invalid:text-destructive dark:aria-invalid:text-destructive/90",
|
|
39
|
-
className
|
|
40
|
-
),
|
|
41
|
-
...props
|
|
42
|
-
}
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
var buttonVariants = classVarianceAuthority.cva(
|
|
46
|
-
"group/button inline-flex shrink-0 items-center justify-center gap-2 rounded-lg border border-transparent bg-clip-padding text-sm font-semibold whitespace-nowrap transition-all duration-150 outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/30 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 data-[loading]:cursor-not-allowed data-[loading]:opacity-80 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
47
|
-
{
|
|
48
|
-
variants: {
|
|
49
|
-
variant: {
|
|
50
|
-
primary: "cursor-pointer bg-primary text-primary-foreground hover:bg-primary/80 focus-visible:ring-primary/30",
|
|
51
|
-
secondary: "cursor-pointer bg-secondary text-secondary-foreground hover:bg-[var(--color-orange-700)] focus-visible:ring-secondary/30",
|
|
52
|
-
cancel: "cursor-pointer bg-destructive text-secondary-foreground hover:bg-[var(--color-red-600)] focus-visible:ring-destructive/30",
|
|
53
|
-
outline: "border border-foreground/50 bg-transparent text-muted-foreground hover:bg-foreground/50 hover:text-white",
|
|
54
|
-
ghost: "cursor-pointer bg-transparent text-muted-foreground hover:bg-foreground/10 hover:text-primary focus-visible:ring-ring/30 border-none",
|
|
55
|
-
cancelGhost: "cursor-pointer bg-destructive/10 text-red-600 hover:bg-destructive/15 hover:text-red-600 focus-visible:ring-ring/30"
|
|
56
|
-
},
|
|
57
|
-
size: {
|
|
58
|
-
sm: "h-8 px-3 text-xs",
|
|
59
|
-
md: "h-9 px-4 text-sm",
|
|
60
|
-
lg: "h-10 px-6 text-sm",
|
|
61
|
-
"icon-sm": "size-8 rounded-full p-0"
|
|
62
|
-
}
|
|
63
|
-
},
|
|
64
|
-
defaultVariants: {
|
|
65
|
-
variant: "primary",
|
|
66
|
-
size: "md"
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
);
|
|
70
|
-
function Button({
|
|
71
|
-
className,
|
|
72
|
-
variant = "primary",
|
|
73
|
-
size = "md",
|
|
74
|
-
asChild = false,
|
|
75
|
-
isLoading = false,
|
|
76
|
-
children,
|
|
77
|
-
onClick,
|
|
78
|
-
...props
|
|
79
|
-
}) {
|
|
80
|
-
const Comp = asChild ? radixUi.Slot.Root : "button";
|
|
81
|
-
function handleClick(e) {
|
|
82
|
-
if (isLoading) {
|
|
83
|
-
e.preventDefault();
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
onClick?.(e);
|
|
87
|
-
}
|
|
88
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
89
|
-
Comp,
|
|
90
|
-
{
|
|
91
|
-
"data-slot": "button",
|
|
92
|
-
"data-variant": variant,
|
|
93
|
-
"data-size": size,
|
|
94
|
-
"data-loading": isLoading || void 0,
|
|
95
|
-
"aria-busy": isLoading || void 0,
|
|
96
|
-
className: cn(buttonVariants({ variant, size, className })),
|
|
97
|
-
onClick: handleClick,
|
|
98
|
-
...props,
|
|
99
|
-
children: [
|
|
100
|
-
isLoading && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.LoaderCircle, { size: 16, className: "animate-spin", "aria-hidden": "true" }),
|
|
101
|
-
children
|
|
102
|
-
]
|
|
103
|
-
}
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
function RichTextToolbar({ editor, items }) {
|
|
107
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
108
|
-
"div",
|
|
109
|
-
{
|
|
110
|
-
role: "toolbar",
|
|
111
|
-
"data-slot": "rich-text-toolbar",
|
|
112
|
-
className: "flex items-center gap-1 rounded-t-md border border-b-0 p-1",
|
|
113
|
-
children: items.map((item, index) => {
|
|
114
|
-
if (item.kind === "separator") {
|
|
115
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
116
|
-
"div",
|
|
117
|
-
{
|
|
118
|
-
"data-slot": "rich-text-toolbar-separator",
|
|
119
|
-
"aria-hidden": "true",
|
|
120
|
-
className: "bg-border mx-1 h-5 w-px"
|
|
121
|
-
},
|
|
122
|
-
`separator-${index}`
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
if (item.kind === "custom") {
|
|
126
|
-
return /* @__PURE__ */ jsxRuntime.jsx("span", { children: item.render(editor) }, item.id);
|
|
127
|
-
}
|
|
128
|
-
const active = item.active ? editor?.isActive(item.active.name, item.active.attrs) ?? false : false;
|
|
129
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
130
|
-
Button,
|
|
131
|
-
{
|
|
132
|
-
type: "button",
|
|
133
|
-
size: "sm",
|
|
134
|
-
variant: active ? "secondary" : "ghost",
|
|
135
|
-
disabled: !editor,
|
|
136
|
-
title: item.label,
|
|
137
|
-
"aria-label": item.label,
|
|
138
|
-
"aria-pressed": item.active ? active : void 0,
|
|
139
|
-
"data-slot": "rich-text-toolbar-button",
|
|
140
|
-
onClick: () => editor && item.run(editor),
|
|
141
|
-
children: item.icon
|
|
142
|
-
},
|
|
143
|
-
item.id
|
|
144
|
-
);
|
|
145
|
-
})
|
|
146
|
-
}
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
|
-
var EDITOR_CONTENT_STYLE = cn(
|
|
150
|
-
"min-h-[220px] px-3 py-2 text-sm outline-none",
|
|
151
|
-
"[&_h2]:text-lg [&_h2]:font-semibold",
|
|
152
|
-
"[&_ol]:my-4 [&_ol]:list-decimal [&_ol]:pl-5 [&_ul]:my-4 [&_ul]:list-disc [&_ul]:pl-5",
|
|
153
|
-
"[&_blockquote]:border-secondary [&_blockquote]:my-4 [&_blockquote]:ml-4 [&_blockquote]:border-l-4 [&_blockquote]:pl-2",
|
|
154
|
-
"[&_a]:text-primary [&_a]:underline",
|
|
155
|
-
// last-of-type, not last-child: prosemirror-tables appends its
|
|
156
|
-
// .column-resize-handle widget inside the hovered cell, which makes the
|
|
157
|
-
// cell's <p> stop matching :last-child and springs mb-4 back on — the row
|
|
158
|
-
// grows 16px on hover. The handle is absolutely positioned but still counts
|
|
159
|
-
// as a child for structural selectors.
|
|
160
|
-
"[&_p]:mb-4 [&_p]:min-h-[1.25em] [&_p:last-of-type]:mb-0",
|
|
161
|
-
// Paragraphs inside a quote sit closer together than body copy. Every Enter
|
|
162
|
-
// inside a blockquote opens a new <p>, and at the body's mb-4 the lines of a
|
|
163
|
-
// single quote read as separate paragraphs. Declared after the rule above so
|
|
164
|
-
// it wins on equal specificity.
|
|
165
|
-
"[&_blockquote_p]:mb-1 [&_blockquote_p:last-of-type]:mb-0",
|
|
166
|
-
"[&_img]:my-2 [&_img]:max-w-full [&_img]:rounded-md",
|
|
167
|
-
"[&_table]:my-3 [&_table]:w-full [&_table]:table-fixed [&_table]:border-collapse",
|
|
168
|
-
"[&_td]:relative [&_td]:border [&_td]:px-2 [&_td]:py-1 [&_td]:align-top",
|
|
169
|
-
"[&_th]:bg-muted [&_th]:relative [&_th]:border [&_th]:px-2 [&_th]:py-1 [&_th]:text-left [&_th]:font-semibold",
|
|
170
|
-
"[&_.selectedCell]:bg-primary/10",
|
|
171
|
-
// prosemirror-tables renders one handle PER ROW, so the column reads as a
|
|
172
|
-
// single bar down the table. At full primary it looked like the table had
|
|
173
|
-
// broken rather than like something to grab — hence the muted tint and the
|
|
174
|
-
// wider, border-straddling hit area (matches upstream's 4px).
|
|
175
|
-
"[&_.column-resize-handle]:bg-primary/40 [&_.column-resize-handle]:pointer-events-none [&_.column-resize-handle]:absolute [&_.column-resize-handle]:top-0 [&_.column-resize-handle]:-right-0.5 [&_.column-resize-handle]:bottom-0 [&_.column-resize-handle]:z-20 [&_.column-resize-handle]:w-1",
|
|
176
|
-
"[&.resize-cursor]:cursor-col-resize",
|
|
177
|
-
// Selected-node outline, scoped away from images.
|
|
178
|
-
//
|
|
179
|
-
// ProseMirror marks whichever node is selected, and this ring is the only
|
|
180
|
-
// cue for most of them — but an image already announces its selection with a
|
|
181
|
-
// visible resize handle, so the extra box only framed the picture.
|
|
182
|
-
//
|
|
183
|
-
// Excluded via `.node-image`, the class Tiptap puts on the wrapper it
|
|
184
|
-
// generates for a node view (`div.react-renderer.node-<name>`). That wrapper,
|
|
185
|
-
// not the NodeViewWrapper inside it, is what receives
|
|
186
|
-
// ProseMirror-selectednode — so a data-slot on our own element sits one level
|
|
187
|
-
// too deep to match.
|
|
188
|
-
"[&_.ProseMirror-selectednode:not(.node-image)]:outline-primary [&_.ProseMirror-selectednode:not(.node-image)]:outline-2 [&_.ProseMirror-selectednode:not(.node-image)]:outline-offset-2 [&_.ProseMirror-selectednode:not(.node-image)]:outline"
|
|
189
|
-
);
|
|
190
|
-
function RichTextEditor({ editor, items, label, error, footer, className }) {
|
|
191
|
-
const errorId = react.useId();
|
|
192
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-slot": "rich-text-editor", className: cn("flex flex-col gap-1", className), children: [
|
|
193
|
-
label && /* @__PURE__ */ jsxRuntime.jsx(Label, { children: label }),
|
|
194
|
-
/* @__PURE__ */ jsxRuntime.jsx(RichTextToolbar, { editor, items }),
|
|
195
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
196
|
-
react$1.EditorContent,
|
|
197
|
-
{
|
|
198
|
-
editor,
|
|
199
|
-
"aria-invalid": !!error,
|
|
200
|
-
"aria-errormessage": error ? errorId : void 0,
|
|
201
|
-
onClick: () => editor?.commands.focus(),
|
|
202
|
-
className: cn(
|
|
203
|
-
"rounded-t-none rounded-b-md border shadow-xs",
|
|
204
|
-
"focus-within:border-ring focus-within:ring-ring/30 focus-within:ring-3",
|
|
205
|
-
error && "border-destructive"
|
|
206
|
-
)
|
|
207
|
-
}
|
|
208
|
-
),
|
|
209
|
-
error && /* @__PURE__ */ jsxRuntime.jsx("span", { id: errorId, role: "alert", className: "text-destructive text-xs", children: error }),
|
|
210
|
-
footer
|
|
211
|
-
] });
|
|
212
|
-
}
|
|
213
|
-
function DropdownMenu({ ...props }) {
|
|
214
|
-
return /* @__PURE__ */ jsxRuntime.jsx(radixUi.DropdownMenu.Root, { ...props });
|
|
215
|
-
}
|
|
216
|
-
function DropdownMenuTrigger({
|
|
217
|
-
...props
|
|
218
|
-
}) {
|
|
219
|
-
return /* @__PURE__ */ jsxRuntime.jsx(radixUi.DropdownMenu.Trigger, { "data-slot": "dropdown-menu-trigger", ...props });
|
|
220
|
-
}
|
|
221
|
-
function DropdownMenuContent({
|
|
222
|
-
className,
|
|
223
|
-
align = "start",
|
|
224
|
-
sideOffset = 4,
|
|
225
|
-
...props
|
|
226
|
-
}) {
|
|
227
|
-
return /* @__PURE__ */ jsxRuntime.jsx(radixUi.DropdownMenu.Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
228
|
-
radixUi.DropdownMenu.Content,
|
|
229
|
-
{
|
|
230
|
-
"data-slot": "dropdown-menu-content",
|
|
231
|
-
sideOffset,
|
|
232
|
-
align,
|
|
233
|
-
className: cn(
|
|
234
|
-
"bg-popover text-popover-foreground ring-foreground/5 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:ring-foreground/10 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 z-50 max-h-(--radix-dropdown-menu-content-available-height) w-(--radix-dropdown-menu-trigger-width) min-w-48 origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-3xl p-1.5 shadow-lg ring-1 duration-100 data-[state=closed]:overflow-hidden",
|
|
235
|
-
className
|
|
236
|
-
),
|
|
237
|
-
...props
|
|
238
|
-
}
|
|
239
|
-
) });
|
|
240
|
-
}
|
|
241
|
-
function DropdownMenuItem({
|
|
242
|
-
className,
|
|
243
|
-
inset,
|
|
244
|
-
variant = "default",
|
|
245
|
-
...props
|
|
246
|
-
}) {
|
|
247
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
248
|
-
radixUi.DropdownMenu.Item,
|
|
249
|
-
{
|
|
250
|
-
"data-slot": "dropdown-menu-item",
|
|
251
|
-
"data-inset": inset,
|
|
252
|
-
"data-variant": variant,
|
|
253
|
-
className: cn(
|
|
254
|
-
"group/dropdown-menu-item focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:*:[svg]:text-destructive relative flex cursor-default items-center gap-2.5 rounded-2xl px-3 py-2 text-sm font-medium outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-9.5 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
255
|
-
className
|
|
256
|
-
),
|
|
257
|
-
...props
|
|
258
|
-
}
|
|
259
|
-
);
|
|
260
|
-
}
|
|
261
|
-
function DropdownMenuSeparator({
|
|
262
|
-
className,
|
|
263
|
-
...props
|
|
264
|
-
}) {
|
|
265
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
266
|
-
radixUi.DropdownMenu.Separator,
|
|
267
|
-
{
|
|
268
|
-
"data-slot": "dropdown-menu-separator",
|
|
269
|
-
className: cn("bg-border/50 -mx-1.5 my-1.5 h-px", className),
|
|
270
|
-
...props
|
|
271
|
-
}
|
|
272
|
-
);
|
|
273
|
-
}
|
|
274
|
-
var DEFAULT_ROWS = 8;
|
|
275
|
-
var DEFAULT_COLUMNS = 10;
|
|
276
|
-
function TableMenu({
|
|
277
|
-
editor,
|
|
278
|
-
labels,
|
|
279
|
-
icon,
|
|
280
|
-
maxRows = DEFAULT_ROWS,
|
|
281
|
-
maxColumns = DEFAULT_COLUMNS
|
|
282
|
-
}) {
|
|
283
|
-
const [hovered, setHovered] = react.useState({ rows: 0, columns: 0 });
|
|
284
|
-
const insideTable = editor?.can().deleteTable() ?? false;
|
|
285
|
-
function insert(rows, columns) {
|
|
286
|
-
editor?.chain().focus().insertTable({ rows, cols: columns, withHeaderRow: true }).run();
|
|
287
|
-
}
|
|
288
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(DropdownMenu, { children: [
|
|
289
|
-
/* @__PURE__ */ jsxRuntime.jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
290
|
-
Button,
|
|
291
|
-
{
|
|
292
|
-
type: "button",
|
|
293
|
-
size: "sm",
|
|
294
|
-
variant: editor?.isActive("table") ? "secondary" : "ghost",
|
|
295
|
-
disabled: !editor,
|
|
296
|
-
title: labels.trigger,
|
|
297
|
-
"aria-label": labels.trigger,
|
|
298
|
-
"data-slot": "table-menu-trigger",
|
|
299
|
-
children: icon
|
|
300
|
-
}
|
|
301
|
-
) }),
|
|
302
|
-
/* @__PURE__ */ jsxRuntime.jsxs(DropdownMenuContent, { align: "start", className: "w-56", children: [
|
|
303
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { role: "none", className: "px-2 py-1.5", children: [
|
|
304
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground mb-1.5 text-xs", children: labels.insert }),
|
|
305
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
306
|
-
"div",
|
|
307
|
-
{
|
|
308
|
-
className: "grid gap-0.5",
|
|
309
|
-
style: { gridTemplateColumns: `repeat(${maxColumns}, 1fr)` },
|
|
310
|
-
onMouseLeave: () => setHovered({ rows: 0, columns: 0 }),
|
|
311
|
-
children: Array.from({ length: maxRows * maxColumns }, (_, index) => {
|
|
312
|
-
const rows = Math.floor(index / maxColumns) + 1;
|
|
313
|
-
const columns = index % maxColumns + 1;
|
|
314
|
-
const active = rows <= hovered.rows && columns <= hovered.columns;
|
|
315
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
316
|
-
"button",
|
|
317
|
-
{
|
|
318
|
-
type: "button",
|
|
319
|
-
"aria-label": labels.dimensions(rows, columns),
|
|
320
|
-
className: cn(
|
|
321
|
-
"h-3.5 w-3.5 cursor-pointer rounded-[2px] border",
|
|
322
|
-
active ? "bg-primary border-primary" : "bg-background"
|
|
323
|
-
),
|
|
324
|
-
onMouseEnter: () => setHovered({ rows, columns }),
|
|
325
|
-
onFocus: () => setHovered({ rows, columns }),
|
|
326
|
-
onClick: () => insert(rows, columns)
|
|
327
|
-
},
|
|
328
|
-
index
|
|
329
|
-
);
|
|
330
|
-
})
|
|
331
|
-
}
|
|
332
|
-
),
|
|
333
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
334
|
-
"p",
|
|
335
|
-
{
|
|
336
|
-
"aria-live": "polite",
|
|
337
|
-
"data-slot": "table-menu-dimensions",
|
|
338
|
-
className: "text-muted-foreground mt-1.5 text-xs tabular-nums",
|
|
339
|
-
children: labels.dimensions(hovered.rows, hovered.columns)
|
|
340
|
-
}
|
|
341
|
-
)
|
|
342
|
-
] }),
|
|
343
|
-
/* @__PURE__ */ jsxRuntime.jsx(DropdownMenuSeparator, {}),
|
|
344
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
345
|
-
DropdownMenuItem,
|
|
346
|
-
{
|
|
347
|
-
className: "cursor-pointer",
|
|
348
|
-
disabled: !insideTable,
|
|
349
|
-
onSelect: () => editor?.chain().focus().addRowBefore().run(),
|
|
350
|
-
children: labels.rowBefore
|
|
351
|
-
}
|
|
352
|
-
),
|
|
353
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
354
|
-
DropdownMenuItem,
|
|
355
|
-
{
|
|
356
|
-
className: "cursor-pointer",
|
|
357
|
-
disabled: !insideTable,
|
|
358
|
-
onSelect: () => editor?.chain().focus().addRowAfter().run(),
|
|
359
|
-
children: labels.rowAfter
|
|
360
|
-
}
|
|
361
|
-
),
|
|
362
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
363
|
-
DropdownMenuItem,
|
|
364
|
-
{
|
|
365
|
-
className: "cursor-pointer",
|
|
366
|
-
variant: "destructive",
|
|
367
|
-
disabled: !insideTable,
|
|
368
|
-
onSelect: () => editor?.chain().focus().deleteRow().run(),
|
|
369
|
-
children: labels.deleteRow
|
|
370
|
-
}
|
|
371
|
-
),
|
|
372
|
-
/* @__PURE__ */ jsxRuntime.jsx(DropdownMenuSeparator, {}),
|
|
373
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
374
|
-
DropdownMenuItem,
|
|
375
|
-
{
|
|
376
|
-
className: "cursor-pointer",
|
|
377
|
-
disabled: !insideTable,
|
|
378
|
-
onSelect: () => editor?.chain().focus().addColumnBefore().run(),
|
|
379
|
-
children: labels.columnBefore
|
|
380
|
-
}
|
|
381
|
-
),
|
|
382
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
383
|
-
DropdownMenuItem,
|
|
384
|
-
{
|
|
385
|
-
className: "cursor-pointer",
|
|
386
|
-
disabled: !insideTable,
|
|
387
|
-
onSelect: () => editor?.chain().focus().addColumnAfter().run(),
|
|
388
|
-
children: labels.columnAfter
|
|
389
|
-
}
|
|
390
|
-
),
|
|
391
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
392
|
-
DropdownMenuItem,
|
|
393
|
-
{
|
|
394
|
-
className: "cursor-pointer",
|
|
395
|
-
variant: "destructive",
|
|
396
|
-
disabled: !insideTable,
|
|
397
|
-
onSelect: () => editor?.chain().focus().deleteColumn().run(),
|
|
398
|
-
children: labels.deleteColumn
|
|
399
|
-
}
|
|
400
|
-
),
|
|
401
|
-
/* @__PURE__ */ jsxRuntime.jsx(DropdownMenuSeparator, {}),
|
|
402
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
403
|
-
DropdownMenuItem,
|
|
404
|
-
{
|
|
405
|
-
className: "cursor-pointer",
|
|
406
|
-
disabled: !insideTable,
|
|
407
|
-
onSelect: () => editor?.chain().focus().toggleHeaderRow().run(),
|
|
408
|
-
children: labels.toggleHeader
|
|
409
|
-
}
|
|
410
|
-
),
|
|
411
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
412
|
-
DropdownMenuItem,
|
|
413
|
-
{
|
|
414
|
-
className: "cursor-pointer",
|
|
415
|
-
variant: "destructive",
|
|
416
|
-
disabled: !insideTable,
|
|
417
|
-
onSelect: () => editor?.chain().focus().deleteTable().run(),
|
|
418
|
-
children: labels.deleteTable
|
|
419
|
-
}
|
|
420
|
-
)
|
|
421
|
-
] })
|
|
422
|
-
] });
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
// src/tokens/animation.ts
|
|
426
|
-
var duration = {
|
|
427
|
-
normal: 0.25};
|
|
428
|
-
var ease = {
|
|
429
|
-
smooth: [0.4, 0, 0.2, 1]};
|
|
430
|
-
var spring = {
|
|
431
|
-
gentle: { type: "spring", stiffness: 260, damping: 28 }};
|
|
432
|
-
var transition = {
|
|
433
|
-
// Modal / dialog — más lento para dar peso al overlay
|
|
434
|
-
modal: {
|
|
435
|
-
...spring.gentle
|
|
436
|
-
}};
|
|
437
|
-
var DialogContext = react.createContext(false);
|
|
438
|
-
function Dialog({ children, ...props }) {
|
|
439
|
-
const [internalOpen, setInternalOpen] = react.useState(props.defaultOpen ?? false);
|
|
440
|
-
const isOpen = props.open !== void 0 ? props.open : internalOpen;
|
|
441
|
-
return /* @__PURE__ */ jsxRuntime.jsx(DialogContext.Provider, { value: isOpen, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
442
|
-
radixUi.Dialog.Root,
|
|
443
|
-
{
|
|
444
|
-
"data-slot": "dialog",
|
|
445
|
-
open: isOpen,
|
|
446
|
-
onOpenChange: (v) => {
|
|
447
|
-
setInternalOpen(v);
|
|
448
|
-
props.onOpenChange?.(v);
|
|
449
|
-
},
|
|
450
|
-
...props,
|
|
451
|
-
children
|
|
452
|
-
}
|
|
453
|
-
) });
|
|
454
|
-
}
|
|
455
|
-
function DialogPortal({ children, ...props }) {
|
|
456
|
-
const isOpen = react.useContext(DialogContext);
|
|
457
|
-
return /* @__PURE__ */ jsxRuntime.jsx(radixUi.Dialog.Portal, { "data-slot": "dialog-portal", forceMount: true, ...props, children: /* @__PURE__ */ jsxRuntime.jsx(react$2.AnimatePresence, { mode: "sync", children: isOpen && children }) });
|
|
458
|
-
}
|
|
459
|
-
function DialogOverlay({ className, ...props }) {
|
|
460
|
-
return /* @__PURE__ */ jsxRuntime.jsx(radixUi.Dialog.Overlay, { "data-slot": "dialog-overlay", asChild: true, forceMount: true, ...props, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
461
|
-
react$2.motion.div,
|
|
462
|
-
{
|
|
463
|
-
className: cn(
|
|
464
|
-
"fixed inset-0 isolate z-50 bg-black/30 supports-backdrop-filter:backdrop-blur-sm",
|
|
465
|
-
className
|
|
466
|
-
),
|
|
467
|
-
initial: { opacity: 0 },
|
|
468
|
-
animate: { opacity: 1 },
|
|
469
|
-
exit: { opacity: 0 },
|
|
470
|
-
transition: { duration: duration.normal, ease: ease.smooth }
|
|
471
|
-
}
|
|
472
|
-
) });
|
|
473
|
-
}
|
|
474
|
-
function DialogContent({
|
|
475
|
-
className,
|
|
476
|
-
children,
|
|
477
|
-
showCloseButton = false,
|
|
478
|
-
...props
|
|
479
|
-
}) {
|
|
480
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(DialogPortal, { children: [
|
|
481
|
-
/* @__PURE__ */ jsxRuntime.jsx(DialogOverlay, {}, "dialog-overlay"),
|
|
482
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
483
|
-
radixUi.Dialog.Content,
|
|
484
|
-
{
|
|
485
|
-
"data-slot": "dialog-content",
|
|
486
|
-
asChild: true,
|
|
487
|
-
forceMount: true,
|
|
488
|
-
className: cn(
|
|
489
|
-
"bg-popover text-popover-foreground ring-foreground/5 dark:ring-foreground/10 fixed top-1/2 left-1/2 z-50 flex max-h-[85vh] w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 flex-col rounded-lg text-sm shadow-xl ring-1 outline-none sm:max-w-sm",
|
|
490
|
-
className
|
|
491
|
-
),
|
|
492
|
-
...props,
|
|
493
|
-
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
494
|
-
react$2.motion.div,
|
|
495
|
-
{
|
|
496
|
-
className: "flex max-h-[85vh] flex-col",
|
|
497
|
-
initial: { opacity: 0, scale: 0.96, y: 8 },
|
|
498
|
-
animate: { opacity: 1, scale: 1, y: 0 },
|
|
499
|
-
exit: { opacity: 0, scale: 0.96, y: 4 },
|
|
500
|
-
transition: transition.modal,
|
|
501
|
-
children: [
|
|
502
|
-
children,
|
|
503
|
-
showCloseButton && /* @__PURE__ */ jsxRuntime.jsx(radixUi.Dialog.Close, { "data-slot": "dialog-close", asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
504
|
-
Button,
|
|
505
|
-
{
|
|
506
|
-
variant: "ghost",
|
|
507
|
-
className: "absolute top-4 right-4",
|
|
508
|
-
size: "icon-sm",
|
|
509
|
-
children: [
|
|
510
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.XIcon, {}),
|
|
511
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "sr-only", children: "Close" })
|
|
512
|
-
]
|
|
513
|
-
}
|
|
514
|
-
) })
|
|
515
|
-
]
|
|
516
|
-
}
|
|
517
|
-
)
|
|
518
|
-
},
|
|
519
|
-
"dialog-content"
|
|
520
|
-
)
|
|
521
|
-
] });
|
|
522
|
-
}
|
|
523
|
-
function DialogHeader({ className, ...props }) {
|
|
524
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
525
|
-
"div",
|
|
526
|
-
{
|
|
527
|
-
"data-slot": "dialog-header",
|
|
528
|
-
className: cn("flex shrink-0 flex-col gap-3 px-6 pt-6", className),
|
|
529
|
-
...props
|
|
530
|
-
}
|
|
531
|
-
);
|
|
532
|
-
}
|
|
533
|
-
function DialogFooter({
|
|
534
|
-
className,
|
|
535
|
-
showCloseButton = false,
|
|
536
|
-
children,
|
|
537
|
-
...props
|
|
538
|
-
}) {
|
|
539
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
540
|
-
"div",
|
|
541
|
-
{
|
|
542
|
-
"data-slot": "dialog-footer",
|
|
543
|
-
className: cn(
|
|
544
|
-
"border-border bg-muted/40 flex shrink-0 flex-wrap items-center justify-end gap-2 rounded-b-lg border-t px-6 pt-4 pb-4",
|
|
545
|
-
className
|
|
546
|
-
),
|
|
547
|
-
...props,
|
|
548
|
-
children: [
|
|
549
|
-
children,
|
|
550
|
-
showCloseButton && /* @__PURE__ */ jsxRuntime.jsx(radixUi.Dialog.Close, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "secondary", children: "Close" }) })
|
|
551
|
-
]
|
|
552
|
-
}
|
|
553
|
-
);
|
|
554
|
-
}
|
|
555
|
-
function DialogTitle({ className, ...props }) {
|
|
556
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
557
|
-
radixUi.Dialog.Title,
|
|
558
|
-
{
|
|
559
|
-
"data-slot": "dialog-title",
|
|
560
|
-
className: cn("font-heading text-lg leading-none font-bold", className),
|
|
561
|
-
...props
|
|
562
|
-
}
|
|
563
|
-
);
|
|
564
|
-
}
|
|
565
|
-
var renderBytes = (bytes) => {
|
|
566
|
-
const units = ["B", "KB", "MB", "GB", "TB", "PB"];
|
|
567
|
-
let size = bytes;
|
|
568
|
-
let unitIndex = 0;
|
|
569
|
-
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
570
|
-
size /= 1024;
|
|
571
|
-
unitIndex++;
|
|
572
|
-
}
|
|
573
|
-
return `${size.toFixed(2)}${units[unitIndex]}`;
|
|
574
|
-
};
|
|
575
|
-
var DropzoneContext = react.createContext(void 0);
|
|
576
|
-
var dropzoneVariants = classVarianceAuthority.cva("", {
|
|
577
|
-
variants: {
|
|
578
|
-
border: {
|
|
579
|
-
default: "border border-solid border-foreground/15",
|
|
580
|
-
dashed: "border-dashed border-foreground/50"
|
|
581
|
-
}
|
|
582
|
-
},
|
|
583
|
-
defaultVariants: {
|
|
584
|
-
border: "default"
|
|
585
|
-
}
|
|
586
|
-
});
|
|
587
|
-
var Dropzone = ({
|
|
588
|
-
accept,
|
|
589
|
-
maxFiles = 1,
|
|
590
|
-
maxSize,
|
|
591
|
-
minSize,
|
|
592
|
-
onDrop,
|
|
593
|
-
onError,
|
|
594
|
-
disabled,
|
|
595
|
-
src,
|
|
596
|
-
className,
|
|
597
|
-
children,
|
|
598
|
-
border,
|
|
599
|
-
...props
|
|
600
|
-
}) => {
|
|
601
|
-
const { getRootProps, getInputProps, isDragActive } = reactDropzone.useDropzone({
|
|
602
|
-
accept,
|
|
603
|
-
maxFiles,
|
|
604
|
-
maxSize,
|
|
605
|
-
minSize,
|
|
606
|
-
onError,
|
|
607
|
-
disabled,
|
|
608
|
-
onDrop: (acceptedFiles, fileRejections, event) => {
|
|
609
|
-
if (fileRejections.length > 0) {
|
|
610
|
-
const message = fileRejections.at(0)?.errors.at(0)?.message;
|
|
611
|
-
onError?.(new Error(message));
|
|
612
|
-
}
|
|
613
|
-
if (acceptedFiles.length > 0) {
|
|
614
|
-
onDrop?.(acceptedFiles, fileRejections, event);
|
|
615
|
-
}
|
|
616
|
-
},
|
|
617
|
-
...props
|
|
618
|
-
});
|
|
619
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
620
|
-
DropzoneContext.Provider,
|
|
621
|
-
{
|
|
622
|
-
value: { src, accept, maxSize, minSize, maxFiles },
|
|
623
|
-
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
624
|
-
Button,
|
|
625
|
-
{
|
|
626
|
-
"data-slot": "dropzone",
|
|
627
|
-
"data-border": border ?? "default",
|
|
628
|
-
className: cn(
|
|
629
|
-
"cursor-auto relative h-auto w-full flex-col overflow-hidden p-8",
|
|
630
|
-
dropzoneVariants({ border }),
|
|
631
|
-
isDragActive && "bg-foreground/10 outline-none",
|
|
632
|
-
className
|
|
633
|
-
),
|
|
634
|
-
disabled,
|
|
635
|
-
type: "button",
|
|
636
|
-
variant: "ghost",
|
|
637
|
-
...getRootProps(),
|
|
638
|
-
children: [
|
|
639
|
-
/* @__PURE__ */ jsxRuntime.jsx("input", { ...getInputProps(), disabled }),
|
|
640
|
-
children
|
|
641
|
-
]
|
|
642
|
-
}
|
|
643
|
-
)
|
|
644
|
-
},
|
|
645
|
-
JSON.stringify(src)
|
|
646
|
-
);
|
|
647
|
-
};
|
|
648
|
-
var useDropzoneContext = () => {
|
|
649
|
-
const context = react.useContext(DropzoneContext);
|
|
650
|
-
if (!context) {
|
|
651
|
-
throw new Error("useDropzoneContext must be used within a Dropzone");
|
|
652
|
-
}
|
|
653
|
-
return context;
|
|
654
|
-
};
|
|
655
|
-
var maxLabelItems = 3;
|
|
656
|
-
var DropzoneContent = ({ children, className }) => {
|
|
657
|
-
const { src } = useDropzoneContext();
|
|
658
|
-
if (!src) {
|
|
659
|
-
return null;
|
|
660
|
-
}
|
|
661
|
-
if (children) {
|
|
662
|
-
return children;
|
|
663
|
-
}
|
|
664
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex flex-col items-center justify-center", className), children: [
|
|
665
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-muted text-muted-foreground flex size-8 items-center justify-center rounded-md", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.UploadIcon, { size: 16 }) }),
|
|
666
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-foreground my-2 w-full truncate text-sm font-medium", children: src.length > maxLabelItems ? `${new Intl.ListFormat("en").format(
|
|
667
|
-
src.slice(0, maxLabelItems).map((file) => file.name)
|
|
668
|
-
)} and ${src.length - maxLabelItems} more` : new Intl.ListFormat("en").format(src.map((file) => file.name)) }),
|
|
669
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground w-full text-xs font-normal text-wrap", children: "Drag and drop or click to replace" })
|
|
670
|
-
] });
|
|
671
|
-
};
|
|
672
|
-
var DropzoneEmptyState = ({ children, className }) => {
|
|
673
|
-
const { src, accept, maxSize, minSize, maxFiles } = useDropzoneContext();
|
|
674
|
-
if (src) {
|
|
675
|
-
return null;
|
|
676
|
-
}
|
|
677
|
-
if (children) {
|
|
678
|
-
return children;
|
|
679
|
-
}
|
|
680
|
-
let caption = "";
|
|
681
|
-
if (accept) {
|
|
682
|
-
caption += "Accepts ";
|
|
683
|
-
caption += new Intl.ListFormat("en").format(Object.keys(accept));
|
|
684
|
-
}
|
|
685
|
-
if (minSize && maxSize) {
|
|
686
|
-
caption += ` between ${renderBytes(minSize)} and ${renderBytes(maxSize)}`;
|
|
687
|
-
} else if (minSize) {
|
|
688
|
-
caption += ` at least ${renderBytes(minSize)}`;
|
|
689
|
-
} else if (maxSize) {
|
|
690
|
-
caption += ` less than ${renderBytes(maxSize)}`;
|
|
691
|
-
}
|
|
692
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex flex-col items-center justify-center", className), children: [
|
|
693
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-muted text-muted-foreground flex size-8 items-center justify-center rounded-md", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.UploadIcon, { size: 16 }) }),
|
|
694
|
-
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-foreground my-2 w-full text-center text-sm font-medium text-wrap", children: [
|
|
695
|
-
"Upload ",
|
|
696
|
-
maxFiles === 1 ? "a file" : "files"
|
|
697
|
-
] }),
|
|
698
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground text-center text-xs font-normal text-wrap", children: "Drag and drop or click to upload" }),
|
|
699
|
-
caption && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-muted-foreground text-xs font-normal text-wrap", children: [
|
|
700
|
-
caption,
|
|
701
|
-
"."
|
|
702
|
-
] })
|
|
703
|
-
] });
|
|
704
|
-
};
|
|
705
|
-
function Input({ className, type, ...props }) {
|
|
706
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
707
|
-
"input",
|
|
708
|
-
{
|
|
709
|
-
type,
|
|
710
|
-
"data-slot": "input",
|
|
711
|
-
className: cn(
|
|
712
|
-
"border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-sm shadow-xs transition-[color,box-shadow] outline-none",
|
|
713
|
-
"placeholder:text-muted-foreground",
|
|
714
|
-
"focus-visible:border-ring focus-visible:ring-ring/30 focus-visible:ring-3",
|
|
715
|
-
"disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
|
|
716
|
-
"aria-invalid:border-destructive aria-invalid:ring-destructive/20 aria-invalid:ring-3",
|
|
717
|
-
"dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
|
|
718
|
-
className
|
|
719
|
-
),
|
|
720
|
-
...props
|
|
721
|
-
}
|
|
722
|
-
);
|
|
723
|
-
}
|
|
724
|
-
var DEFAULT_MAX_SIZE = 5 * 1024 * 1024;
|
|
725
|
-
var DEFAULT_ACCEPT = { "image/*": [".png", ".jpg", ".jpeg", ".webp"] };
|
|
726
|
-
function InsertImageDialog({
|
|
727
|
-
open,
|
|
728
|
-
onOpenChange,
|
|
729
|
-
labels,
|
|
730
|
-
url,
|
|
731
|
-
onUrlChange,
|
|
732
|
-
alt,
|
|
733
|
-
onAltChange,
|
|
734
|
-
file,
|
|
735
|
-
onFileChange,
|
|
736
|
-
uploading,
|
|
737
|
-
onApply,
|
|
738
|
-
onCancel,
|
|
739
|
-
removable = false,
|
|
740
|
-
onRemove,
|
|
741
|
-
maxSizeBytes = DEFAULT_MAX_SIZE,
|
|
742
|
-
accept = DEFAULT_ACCEPT
|
|
743
|
-
}) {
|
|
744
|
-
const urlId = react.useId();
|
|
745
|
-
const altId = react.useId();
|
|
746
|
-
const canApply = Boolean(file || url.trim()) && Boolean(alt.trim()) && !uploading;
|
|
747
|
-
return /* @__PURE__ */ jsxRuntime.jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxRuntime.jsxs(DialogContent, { className: "px-4 py-2", "data-slot": "insert-image-dialog", children: [
|
|
748
|
-
/* @__PURE__ */ jsxRuntime.jsx(DialogHeader, { children: /* @__PURE__ */ jsxRuntime.jsx(DialogTitle, { children: labels.title }) }),
|
|
749
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-4 flex flex-col gap-3", children: [
|
|
750
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
751
|
-
Dropzone,
|
|
752
|
-
{
|
|
753
|
-
accept,
|
|
754
|
-
maxFiles: 1,
|
|
755
|
-
maxSize: maxSizeBytes,
|
|
756
|
-
disabled: uploading,
|
|
757
|
-
src: file ? [file] : [],
|
|
758
|
-
onDrop: (files) => onFileChange(files[0] ?? null),
|
|
759
|
-
children: [
|
|
760
|
-
/* @__PURE__ */ jsxRuntime.jsx(DropzoneEmptyState, {}),
|
|
761
|
-
/* @__PURE__ */ jsxRuntime.jsx(DropzoneContent, {})
|
|
762
|
-
]
|
|
763
|
-
}
|
|
764
|
-
),
|
|
765
|
-
file && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground text-xs", children: file.name }),
|
|
766
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
767
|
-
/* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: urlId, children: labels.urlLabel }),
|
|
768
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
769
|
-
Input,
|
|
770
|
-
{
|
|
771
|
-
id: urlId,
|
|
772
|
-
placeholder: labels.urlPlaceholder,
|
|
773
|
-
value: url,
|
|
774
|
-
onChange: (event) => onUrlChange(event.target.value),
|
|
775
|
-
onKeyDown: (event) => {
|
|
776
|
-
if (event.key === "Enter" && canApply) {
|
|
777
|
-
event.preventDefault();
|
|
778
|
-
onApply();
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
)
|
|
783
|
-
] }),
|
|
784
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
785
|
-
/* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: altId, children: labels.altLabel }),
|
|
786
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
787
|
-
Input,
|
|
788
|
-
{
|
|
789
|
-
id: altId,
|
|
790
|
-
value: alt,
|
|
791
|
-
onChange: (event) => onAltChange(event.target.value)
|
|
792
|
-
}
|
|
793
|
-
)
|
|
794
|
-
] }),
|
|
795
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground text-xs", children: labels.hint })
|
|
796
|
-
] }),
|
|
797
|
-
/* @__PURE__ */ jsxRuntime.jsxs(DialogFooter, { children: [
|
|
798
|
-
removable && onRemove && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
799
|
-
Button,
|
|
800
|
-
{
|
|
801
|
-
type: "button",
|
|
802
|
-
variant: "ghost",
|
|
803
|
-
className: "text-destructive",
|
|
804
|
-
onClick: onRemove,
|
|
805
|
-
children: [
|
|
806
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-4 w-4" }),
|
|
807
|
-
labels.remove
|
|
808
|
-
]
|
|
809
|
-
}
|
|
810
|
-
),
|
|
811
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
812
|
-
Button,
|
|
813
|
-
{
|
|
814
|
-
type: "button",
|
|
815
|
-
variant: "outline",
|
|
816
|
-
onClick: () => {
|
|
817
|
-
if (onCancel) onCancel();
|
|
818
|
-
else onOpenChange(false);
|
|
819
|
-
},
|
|
820
|
-
children: labels.cancel
|
|
821
|
-
}
|
|
822
|
-
),
|
|
823
|
-
/* @__PURE__ */ jsxRuntime.jsx(Button, { type: "button", onClick: onApply, disabled: !canApply, children: labels.apply })
|
|
824
|
-
] })
|
|
825
|
-
] }) });
|
|
826
|
-
}
|
|
827
|
-
var DEFAULT_MIN_WIDTH = 120;
|
|
828
|
-
var DEFAULT_START_WIDTH = 400;
|
|
829
|
-
var DEFAULT_RATIO = 4 / 3;
|
|
830
|
-
var KEYBOARD_STEP = 20;
|
|
831
|
-
function ResizableImageView({ node, updateAttributes, selected, extension }) {
|
|
832
|
-
const { src, alt, width, height } = node.attrs;
|
|
833
|
-
const sizeRef = react.useRef({ width, height });
|
|
834
|
-
sizeRef.current = { width, height };
|
|
835
|
-
const { resizeLabel, minWidth = DEFAULT_MIN_WIDTH } = extension.options;
|
|
836
|
-
const dragRef = react.useRef(null);
|
|
837
|
-
const [dragging, setDragging] = react.useState(false);
|
|
838
|
-
const resizeTo = react.useCallback(
|
|
839
|
-
(nextWidth, ratio, maxWidth) => {
|
|
840
|
-
const clamped = Math.max(minWidth, Math.min(Math.round(nextWidth), maxWidth));
|
|
841
|
-
updateAttributes({ width: clamped, height: Math.round(clamped / ratio) });
|
|
842
|
-
},
|
|
843
|
-
[minWidth, updateAttributes]
|
|
844
|
-
);
|
|
845
|
-
const measure = react.useCallback((handle) => {
|
|
846
|
-
const image = handle.parentElement?.querySelector("img") ?? null;
|
|
847
|
-
const current = sizeRef.current;
|
|
848
|
-
const editable = handle.closest(".ProseMirror");
|
|
849
|
-
const available = editable?.clientWidth ?? 0;
|
|
850
|
-
const naturalWidth = image?.naturalWidth ?? 0;
|
|
851
|
-
const naturalHeight = image?.naturalHeight ?? 0;
|
|
852
|
-
const ratio = current.width > 0 && current.height > 0 ? current.width / current.height : naturalWidth > 0 && naturalHeight > 0 ? naturalWidth / naturalHeight : DEFAULT_RATIO;
|
|
853
|
-
const unbounded = current.width > 0 ? current.width : naturalWidth || DEFAULT_START_WIDTH;
|
|
854
|
-
const startWidth = available > 0 ? Math.min(unbounded, available) : unbounded;
|
|
855
|
-
return {
|
|
856
|
-
width: startWidth,
|
|
857
|
-
ratio,
|
|
858
|
-
// jsdom reports clientWidth as 0, so a zero measure means "no
|
|
859
|
-
// constraint" — the real clamp only applies in a browser.
|
|
860
|
-
maxWidth: available > 0 ? available : Infinity
|
|
861
|
-
};
|
|
862
|
-
}, []);
|
|
863
|
-
const imageRef = react.useRef(null);
|
|
864
|
-
const fittedSrcRef = react.useRef(null);
|
|
865
|
-
react.useEffect(() => {
|
|
866
|
-
const img = imageRef.current;
|
|
867
|
-
if (!img) return;
|
|
868
|
-
if (sizeRef.current.width > 0 || fittedSrcRef.current === src) return;
|
|
869
|
-
const fit = () => {
|
|
870
|
-
const available = img.closest(".ProseMirror")?.clientWidth ?? 0;
|
|
871
|
-
if (!img.naturalWidth || !img.naturalHeight || available <= 0) return;
|
|
872
|
-
fittedSrcRef.current = src;
|
|
873
|
-
const nextWidth = Math.min(img.naturalWidth, available);
|
|
874
|
-
updateAttributes({
|
|
875
|
-
width: nextWidth,
|
|
876
|
-
height: Math.round(nextWidth / (img.naturalWidth / img.naturalHeight))
|
|
877
|
-
});
|
|
878
|
-
};
|
|
879
|
-
if (img.complete) {
|
|
880
|
-
fit();
|
|
881
|
-
return;
|
|
882
|
-
}
|
|
883
|
-
img.addEventListener("load", fit, { once: true });
|
|
884
|
-
return () => img.removeEventListener("load", fit);
|
|
885
|
-
}, [src, updateAttributes]);
|
|
886
|
-
const onPointerDown = react.useCallback(
|
|
887
|
-
(event) => {
|
|
888
|
-
event.preventDefault();
|
|
889
|
-
event.stopPropagation();
|
|
890
|
-
const start = measure(event.currentTarget);
|
|
891
|
-
dragRef.current = { startX: event.clientX, ...start };
|
|
892
|
-
setDragging(true);
|
|
893
|
-
const onMove = (moveEvent) => {
|
|
894
|
-
const drag = dragRef.current;
|
|
895
|
-
if (!drag) return;
|
|
896
|
-
resizeTo(drag.width + (moveEvent.clientX - drag.startX), drag.ratio, drag.maxWidth);
|
|
897
|
-
};
|
|
898
|
-
const onUp = () => {
|
|
899
|
-
dragRef.current = null;
|
|
900
|
-
setDragging(false);
|
|
901
|
-
window.removeEventListener("pointermove", onMove);
|
|
902
|
-
window.removeEventListener("pointerup", onUp);
|
|
903
|
-
};
|
|
904
|
-
window.addEventListener("pointermove", onMove);
|
|
905
|
-
window.addEventListener("pointerup", onUp);
|
|
906
|
-
},
|
|
907
|
-
[measure, resizeTo]
|
|
908
|
-
);
|
|
909
|
-
const onKeyDown = react.useCallback(
|
|
910
|
-
(event) => {
|
|
911
|
-
const step = event.key === "ArrowRight" ? KEYBOARD_STEP : event.key === "ArrowLeft" ? -KEYBOARD_STEP : 0;
|
|
912
|
-
if (step === 0) return;
|
|
913
|
-
event.preventDefault();
|
|
914
|
-
const start = measure(event.currentTarget);
|
|
915
|
-
resizeTo(start.width + step, start.ratio, start.maxWidth);
|
|
916
|
-
},
|
|
917
|
-
[measure, resizeTo]
|
|
918
|
-
);
|
|
919
|
-
const sizeStyle = width > 0 ? { width, height: height > 0 ? height : void 0, maxWidth: "100%" } : void 0;
|
|
920
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
921
|
-
react$1.NodeViewWrapper,
|
|
922
|
-
{
|
|
923
|
-
"data-slot": "resizable-image",
|
|
924
|
-
className: "group relative inline-block max-w-full",
|
|
925
|
-
children: [
|
|
926
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
927
|
-
"img",
|
|
928
|
-
{
|
|
929
|
-
ref: imageRef,
|
|
930
|
-
src,
|
|
931
|
-
alt,
|
|
932
|
-
className: "max-w-full rounded-md",
|
|
933
|
-
style: sizeStyle,
|
|
934
|
-
draggable: false
|
|
935
|
-
}
|
|
936
|
-
),
|
|
937
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
938
|
-
"div",
|
|
939
|
-
{
|
|
940
|
-
role: "button",
|
|
941
|
-
tabIndex: 0,
|
|
942
|
-
"aria-label": resizeLabel,
|
|
943
|
-
title: resizeLabel,
|
|
944
|
-
"data-slot": "resizable-image-handle",
|
|
945
|
-
onPointerDown,
|
|
946
|
-
onKeyDown,
|
|
947
|
-
className: cn(
|
|
948
|
-
"border-primary absolute -right-1 -bottom-1 h-4 w-4 cursor-se-resize touch-none rounded-full border-2 bg-white transition-opacity",
|
|
949
|
-
"focus-visible:ring-ring/50 focus-visible:opacity-100 focus-visible:ring-2 focus-visible:outline-none",
|
|
950
|
-
dragging || selected ? "opacity-100" : "opacity-0 group-hover:opacity-100"
|
|
951
|
-
)
|
|
952
|
-
}
|
|
953
|
-
)
|
|
954
|
-
]
|
|
955
|
-
}
|
|
956
|
-
);
|
|
957
|
-
}
|
|
958
|
-
function createResizableImage(options) {
|
|
959
|
-
return extensionImage.Image.extend({
|
|
960
|
-
addOptions() {
|
|
961
|
-
return { ...this.parent(), ...options };
|
|
962
|
-
},
|
|
963
|
-
// The size attributes belong to the extension, not to the consumer: the
|
|
964
|
-
// stock Image extension keeps width/height too, but they default to null
|
|
965
|
-
// and render as HTML attributes, which pins the image to a fixed size.
|
|
966
|
-
//
|
|
967
|
-
// A numeric default with `rendered: false` is what makes resizing work
|
|
968
|
-
// at all — Tiptap v3 gates DOM emission on `rendered`, so the size stays
|
|
969
|
-
// out of the markup and lives only in the document JSON, where a
|
|
970
|
-
// serializer can read it. Without these declarations the schema drops
|
|
971
|
-
// every updateAttributes({ width }) call and the handle does nothing.
|
|
972
|
-
addAttributes() {
|
|
973
|
-
return {
|
|
974
|
-
...this.parent?.(),
|
|
975
|
-
width: { default: 0, rendered: false },
|
|
976
|
-
height: { default: 0, rendered: false }
|
|
977
|
-
};
|
|
978
|
-
},
|
|
979
|
-
addNodeView() {
|
|
980
|
-
return react$1.ReactNodeViewRenderer(ResizableImageView);
|
|
981
|
-
}
|
|
982
|
-
});
|
|
983
|
-
}
|
|
984
|
-
function LinkActions({ editor, labels, onEdit, onOpen }) {
|
|
985
|
-
const href = react$1.useEditorState({
|
|
986
|
-
editor,
|
|
987
|
-
selector: ({ editor: current }) => current.getAttributes("link").href ?? ""
|
|
988
|
-
});
|
|
989
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
990
|
-
"div",
|
|
991
|
-
{
|
|
992
|
-
"data-slot": "link-bubble-menu",
|
|
993
|
-
className: "bg-popover text-popover-foreground flex items-center gap-1 rounded-md border p-1 shadow-md",
|
|
994
|
-
children: [
|
|
995
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground max-w-[16rem] truncate px-2 text-xs", children: href }),
|
|
996
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
997
|
-
Button,
|
|
998
|
-
{
|
|
999
|
-
type: "button",
|
|
1000
|
-
size: "sm",
|
|
1001
|
-
variant: "ghost",
|
|
1002
|
-
title: labels.open,
|
|
1003
|
-
"aria-label": labels.open,
|
|
1004
|
-
disabled: !href,
|
|
1005
|
-
onClick: () => {
|
|
1006
|
-
if (!href) return;
|
|
1007
|
-
if (onOpen) {
|
|
1008
|
-
onOpen(href);
|
|
1009
|
-
return;
|
|
1010
|
-
}
|
|
1011
|
-
window.open(href, "_blank", "noopener,noreferrer");
|
|
1012
|
-
},
|
|
1013
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ExternalLink, { className: "h-4 w-4" })
|
|
1014
|
-
}
|
|
1015
|
-
),
|
|
1016
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1017
|
-
Button,
|
|
1018
|
-
{
|
|
1019
|
-
type: "button",
|
|
1020
|
-
size: "sm",
|
|
1021
|
-
variant: "ghost",
|
|
1022
|
-
title: labels.edit,
|
|
1023
|
-
"aria-label": labels.edit,
|
|
1024
|
-
onClick: onEdit,
|
|
1025
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Pencil, { className: "h-4 w-4" })
|
|
1026
|
-
}
|
|
1027
|
-
),
|
|
1028
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1029
|
-
Button,
|
|
1030
|
-
{
|
|
1031
|
-
type: "button",
|
|
1032
|
-
size: "sm",
|
|
1033
|
-
variant: "ghost",
|
|
1034
|
-
className: "text-destructive",
|
|
1035
|
-
title: labels.remove,
|
|
1036
|
-
"aria-label": labels.remove,
|
|
1037
|
-
onClick: () => editor.chain().focus().extendMarkRange("link").unsetLink().run(),
|
|
1038
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Unlink, { className: "h-4 w-4" })
|
|
1039
|
-
}
|
|
1040
|
-
)
|
|
1041
|
-
]
|
|
1042
|
-
}
|
|
1043
|
-
);
|
|
1044
|
-
}
|
|
1045
|
-
function LinkBubbleMenu({ editor, labels, onEdit, onOpen }) {
|
|
1046
|
-
const shouldShow = react.useCallback(
|
|
1047
|
-
// Only while the caret is actually in a link — otherwise this bar would
|
|
1048
|
-
// compete with any other bubble menu the consumer adds.
|
|
1049
|
-
({ editor: current }) => current.isActive("link"),
|
|
1050
|
-
[]
|
|
1051
|
-
);
|
|
1052
|
-
const options = react.useMemo(() => ({ placement: "bottom", offset: 8 }), []);
|
|
1053
|
-
if (!editor) return null;
|
|
1054
|
-
return /* @__PURE__ */ jsxRuntime.jsx(menus.BubbleMenu, { editor, shouldShow, options, children: /* @__PURE__ */ jsxRuntime.jsx(LinkActions, { editor, labels, onEdit, onOpen }) });
|
|
1055
|
-
}
|
|
1056
|
-
function ListMenu({ editor, triggerLabel, icon, options }) {
|
|
1057
|
-
const active = options.find((option) => editor?.isActive(option.node));
|
|
1058
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(DropdownMenu, { children: [
|
|
1059
|
-
/* @__PURE__ */ jsxRuntime.jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1060
|
-
Button,
|
|
1061
|
-
{
|
|
1062
|
-
type: "button",
|
|
1063
|
-
size: "sm",
|
|
1064
|
-
variant: active ? "secondary" : "ghost",
|
|
1065
|
-
disabled: !editor,
|
|
1066
|
-
title: active ? active.label : triggerLabel,
|
|
1067
|
-
"aria-label": active ? active.label : triggerLabel,
|
|
1068
|
-
"data-slot": "list-menu-trigger",
|
|
1069
|
-
children: [
|
|
1070
|
-
active ? active.icon : icon,
|
|
1071
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "h-3 w-3 opacity-60" })
|
|
1072
|
-
]
|
|
1073
|
-
}
|
|
1074
|
-
) }),
|
|
1075
|
-
/* @__PURE__ */ jsxRuntime.jsx(DropdownMenuContent, { align: "start", children: options.map((option) => {
|
|
1076
|
-
const isActive = editor?.isActive(option.node) ?? false;
|
|
1077
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1078
|
-
DropdownMenuItem,
|
|
1079
|
-
{
|
|
1080
|
-
disabled: !editor,
|
|
1081
|
-
className: "cursor-pointer",
|
|
1082
|
-
onSelect: () => editor && option.run(editor),
|
|
1083
|
-
children: [
|
|
1084
|
-
option.icon,
|
|
1085
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1", children: option.label }),
|
|
1086
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1087
|
-
lucideReact.Check,
|
|
1088
|
-
{
|
|
1089
|
-
"aria-hidden": "true",
|
|
1090
|
-
className: cn("h-4 w-4", isActive ? "opacity-100" : "opacity-0")
|
|
1091
|
-
}
|
|
1092
|
-
)
|
|
1093
|
-
]
|
|
1094
|
-
},
|
|
1095
|
-
option.node
|
|
1096
|
-
);
|
|
1097
|
-
}) })
|
|
1098
|
-
] });
|
|
1099
|
-
}
|
|
1100
|
-
function useRichTextEditor({
|
|
1101
|
-
labels,
|
|
1102
|
-
onUpdate,
|
|
1103
|
-
content,
|
|
1104
|
-
onInsertLink,
|
|
1105
|
-
onInsertImage,
|
|
1106
|
-
extraExtensions = [],
|
|
1107
|
-
extraToolbar = [],
|
|
1108
|
-
replaceToolbar,
|
|
1109
|
-
contentClassName,
|
|
1110
|
-
allowBase64Images = false,
|
|
1111
|
-
editorProps
|
|
1112
|
-
}) {
|
|
1113
|
-
const [linkNeedsText, setLinkNeedsText] = react.useState(false);
|
|
1114
|
-
const editor = react$1.useEditor({
|
|
1115
|
-
extensions: [
|
|
1116
|
-
// openOnClick false: a plain click has to place the caret so the
|
|
1117
|
-
// author can edit link text. Left at its default, Tiptap navigates
|
|
1118
|
-
// the current tab and throws the author out of the editor.
|
|
1119
|
-
StarterKit__default.default.configure({ link: { openOnClick: false } }),
|
|
1120
|
-
Highlight__default.default,
|
|
1121
|
-
extensionTable.TableKit.configure({ table: { resizable: true } }),
|
|
1122
|
-
createResizableImage({ resizeLabel: labels.resizeImage }).configure({
|
|
1123
|
-
inline: false,
|
|
1124
|
-
allowBase64: allowBase64Images
|
|
1125
|
-
}),
|
|
1126
|
-
...extraExtensions
|
|
1127
|
-
],
|
|
1128
|
-
content,
|
|
1129
|
-
immediatelyRender: false,
|
|
1130
|
-
// Required for the toolbar's pressed state and the image node view to
|
|
1131
|
-
// repaint as the selection and attributes change.
|
|
1132
|
-
shouldRerenderOnTransaction: true,
|
|
1133
|
-
editorProps: {
|
|
1134
|
-
...editorProps,
|
|
1135
|
-
attributes: {
|
|
1136
|
-
// ProseMirror's editable div needs its own accessible name: a
|
|
1137
|
-
// visible <Label> beside it is decoration, and a screen reader
|
|
1138
|
-
// would announce an unnamed box.
|
|
1139
|
-
role: "textbox",
|
|
1140
|
-
"aria-label": labels.editor,
|
|
1141
|
-
class: contentClassName ? `${EDITOR_CONTENT_STYLE} ${contentClassName}` : EDITOR_CONTENT_STYLE
|
|
1142
|
-
},
|
|
1143
|
-
// Ctrl/Cmd-click opens the link in a new tab and leaves the author
|
|
1144
|
-
// where they were: the shortcut is a deliberate gesture, and keeping
|
|
1145
|
-
// the editor open means no unsaved work is at risk. A plain click
|
|
1146
|
-
// falls through to ProseMirror and just moves the caret.
|
|
1147
|
-
handleClick(_view, _pos, event) {
|
|
1148
|
-
if (!event.ctrlKey && !event.metaKey) return false;
|
|
1149
|
-
const href = event.target?.closest("a")?.getAttribute("href");
|
|
1150
|
-
if (!href) return false;
|
|
1151
|
-
event.preventDefault();
|
|
1152
|
-
window.open(href, "_blank", "noopener,noreferrer");
|
|
1153
|
-
return true;
|
|
1154
|
-
}
|
|
1155
|
-
},
|
|
1156
|
-
onUpdate: onUpdate ? ({ editor: current }) => onUpdate(current.getJSON()) : void 0
|
|
1157
|
-
});
|
|
1158
|
-
const currentLinkHref = react.useCallback(
|
|
1159
|
-
() => editor?.getAttributes("link").href ?? "",
|
|
1160
|
-
[editor]
|
|
1161
|
-
);
|
|
1162
|
-
const openLink = react.useCallback(() => {
|
|
1163
|
-
if (editor) {
|
|
1164
|
-
const { from, to } = editor.state.selection;
|
|
1165
|
-
setLinkNeedsText(from === to && !editor.isActive("link"));
|
|
1166
|
-
}
|
|
1167
|
-
onInsertLink?.();
|
|
1168
|
-
}, [editor, onInsertLink]);
|
|
1169
|
-
const applyLink = react.useCallback(
|
|
1170
|
-
(href, text) => {
|
|
1171
|
-
if (!editor) return;
|
|
1172
|
-
const url = href.trim();
|
|
1173
|
-
if (linkNeedsText) {
|
|
1174
|
-
if (url && text?.trim()) {
|
|
1175
|
-
editor.chain().focus().insertContent({
|
|
1176
|
-
type: "text",
|
|
1177
|
-
text: text.trim(),
|
|
1178
|
-
marks: [{ type: "link", attrs: { href: url } }]
|
|
1179
|
-
}).run();
|
|
1180
|
-
}
|
|
1181
|
-
return;
|
|
1182
|
-
}
|
|
1183
|
-
if (url) editor.chain().focus().extendMarkRange("link").setLink({ href: url }).run();
|
|
1184
|
-
else editor.chain().focus().extendMarkRange("link").unsetLink().run();
|
|
1185
|
-
},
|
|
1186
|
-
[editor, linkNeedsText]
|
|
1187
|
-
);
|
|
1188
|
-
const toolbar = react.useMemo(() => {
|
|
1189
|
-
const builtIn = [
|
|
1190
|
-
{
|
|
1191
|
-
kind: "button",
|
|
1192
|
-
id: "bold",
|
|
1193
|
-
icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Bold, { className: "h-4 w-4" }),
|
|
1194
|
-
label: labels.bold,
|
|
1195
|
-
active: { name: "bold" },
|
|
1196
|
-
run: (e) => e.chain().focus().toggleBold().run()
|
|
1197
|
-
},
|
|
1198
|
-
{
|
|
1199
|
-
kind: "button",
|
|
1200
|
-
id: "italic",
|
|
1201
|
-
icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Italic, { className: "h-4 w-4" }),
|
|
1202
|
-
label: labels.italic,
|
|
1203
|
-
active: { name: "italic" },
|
|
1204
|
-
run: (e) => e.chain().focus().toggleItalic().run()
|
|
1205
|
-
},
|
|
1206
|
-
{
|
|
1207
|
-
kind: "button",
|
|
1208
|
-
id: "heading",
|
|
1209
|
-
icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Heading2, { className: "h-4 w-4" }),
|
|
1210
|
-
label: labels.heading,
|
|
1211
|
-
active: { name: "heading", attrs: { level: 2 } },
|
|
1212
|
-
run: (e) => e.chain().focus().toggleHeading({ level: 2 }).run()
|
|
1213
|
-
},
|
|
1214
|
-
{ kind: "separator" },
|
|
1215
|
-
{
|
|
1216
|
-
kind: "button",
|
|
1217
|
-
id: "highlight",
|
|
1218
|
-
icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Highlighter, { className: "h-4 w-4" }),
|
|
1219
|
-
label: labels.highlight,
|
|
1220
|
-
active: { name: "highlight" },
|
|
1221
|
-
run: (e) => e.chain().focus().toggleHighlight().run()
|
|
1222
|
-
},
|
|
1223
|
-
{
|
|
1224
|
-
kind: "button",
|
|
1225
|
-
id: "underline",
|
|
1226
|
-
icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Underline, { className: "h-4 w-4" }),
|
|
1227
|
-
label: labels.underline,
|
|
1228
|
-
active: { name: "underline" },
|
|
1229
|
-
run: (e) => e.chain().focus().toggleMark("underline").run()
|
|
1230
|
-
},
|
|
1231
|
-
{
|
|
1232
|
-
kind: "button",
|
|
1233
|
-
id: "strike",
|
|
1234
|
-
icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Strikethrough, { className: "h-4 w-4" }),
|
|
1235
|
-
label: labels.strikethrough,
|
|
1236
|
-
active: { name: "strike" },
|
|
1237
|
-
run: (e) => e.chain().focus().toggleMark("strike").run()
|
|
1238
|
-
},
|
|
1239
|
-
{ kind: "separator" },
|
|
1240
|
-
{
|
|
1241
|
-
kind: "button",
|
|
1242
|
-
id: "quote",
|
|
1243
|
-
icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Quote, { className: "h-4 w-4" }),
|
|
1244
|
-
label: labels.quote,
|
|
1245
|
-
active: { name: "blockquote" },
|
|
1246
|
-
run: (e) => e.chain().focus().toggleBlockquote().run()
|
|
1247
|
-
},
|
|
1248
|
-
{
|
|
1249
|
-
kind: "custom",
|
|
1250
|
-
id: "lists",
|
|
1251
|
-
render: (current) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1252
|
-
ListMenu,
|
|
1253
|
-
{
|
|
1254
|
-
editor: current,
|
|
1255
|
-
triggerLabel: labels.lists.trigger,
|
|
1256
|
-
icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.List, { className: "h-4 w-4" }),
|
|
1257
|
-
options: [
|
|
1258
|
-
{
|
|
1259
|
-
node: "bulletList",
|
|
1260
|
-
label: labels.lists.bullet,
|
|
1261
|
-
icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.List, { className: "h-4 w-4" }),
|
|
1262
|
-
run: (e) => e.chain().focus().toggleBulletList().run()
|
|
1263
|
-
},
|
|
1264
|
-
{
|
|
1265
|
-
node: "orderedList",
|
|
1266
|
-
label: labels.lists.ordered,
|
|
1267
|
-
icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ListOrdered, { className: "h-4 w-4" }),
|
|
1268
|
-
run: (e) => e.chain().focus().toggleOrderedList().run()
|
|
1269
|
-
}
|
|
1270
|
-
]
|
|
1271
|
-
}
|
|
1272
|
-
)
|
|
1273
|
-
},
|
|
1274
|
-
{ kind: "separator" },
|
|
1275
|
-
{
|
|
1276
|
-
kind: "button",
|
|
1277
|
-
id: "link",
|
|
1278
|
-
icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Link, { className: "h-4 w-4" }),
|
|
1279
|
-
label: labels.insertLink,
|
|
1280
|
-
active: { name: "link" },
|
|
1281
|
-
run: () => openLink()
|
|
1282
|
-
},
|
|
1283
|
-
{
|
|
1284
|
-
kind: "button",
|
|
1285
|
-
id: "image",
|
|
1286
|
-
icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ImagePlus, { className: "h-4 w-4" }),
|
|
1287
|
-
label: labels.insertImage,
|
|
1288
|
-
// No `active`: this opens a dialog, it is never a pressed state.
|
|
1289
|
-
run: () => onInsertImage?.()
|
|
1290
|
-
},
|
|
1291
|
-
{
|
|
1292
|
-
kind: "custom",
|
|
1293
|
-
id: "table",
|
|
1294
|
-
render: (current) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1295
|
-
TableMenu,
|
|
1296
|
-
{
|
|
1297
|
-
editor: current,
|
|
1298
|
-
icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Table, { className: "h-4 w-4" }),
|
|
1299
|
-
labels: labels.table
|
|
1300
|
-
}
|
|
1301
|
-
)
|
|
1302
|
-
}
|
|
1303
|
-
];
|
|
1304
|
-
let items = replaceToolbar ? builtIn.map(
|
|
1305
|
-
(item) => item.kind !== "separator" && replaceToolbar[item.id] ? replaceToolbar[item.id] : item
|
|
1306
|
-
) : builtIn;
|
|
1307
|
-
const appended = [];
|
|
1308
|
-
for (const group of extraToolbar) {
|
|
1309
|
-
if (!group.after) {
|
|
1310
|
-
appended.push(...group.items);
|
|
1311
|
-
continue;
|
|
1312
|
-
}
|
|
1313
|
-
const at = items.findIndex(
|
|
1314
|
-
(item) => item.kind !== "separator" && item.id === group.after
|
|
1315
|
-
);
|
|
1316
|
-
if (at === -1) {
|
|
1317
|
-
appended.push(...group.items);
|
|
1318
|
-
continue;
|
|
1319
|
-
}
|
|
1320
|
-
items = [...items.slice(0, at + 1), ...group.items, ...items.slice(at + 1)];
|
|
1321
|
-
}
|
|
1322
|
-
return [...items, ...appended];
|
|
1323
|
-
}, [labels, extraToolbar, replaceToolbar, onInsertImage, openLink]);
|
|
1324
|
-
const linkMenu = /* @__PURE__ */ jsxRuntime.jsx(LinkBubbleMenu, { editor, labels: labels.link, onEdit: openLink });
|
|
1325
|
-
return { editor, toolbar, linkMenu, linkNeedsText, currentLinkHref, applyLink };
|
|
1326
|
-
}
|
|
1327
|
-
function ColorMenu({ editor, labels, icon, swatches }) {
|
|
1328
|
-
const active = swatches.find((swatch) => editor?.isActive(swatch.mark));
|
|
1329
|
-
function apply(mark) {
|
|
1330
|
-
if (!editor) return;
|
|
1331
|
-
const chain = editor.chain().focus();
|
|
1332
|
-
for (const swatch of swatches) chain.unsetMark(swatch.mark);
|
|
1333
|
-
if (mark) chain.setMark(mark);
|
|
1334
|
-
chain.run();
|
|
1335
|
-
}
|
|
1336
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(DropdownMenu, { children: [
|
|
1337
|
-
/* @__PURE__ */ jsxRuntime.jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1338
|
-
Button,
|
|
1339
|
-
{
|
|
1340
|
-
type: "button",
|
|
1341
|
-
size: "sm",
|
|
1342
|
-
variant: active ? "secondary" : "ghost",
|
|
1343
|
-
disabled: !editor,
|
|
1344
|
-
title: labels.trigger,
|
|
1345
|
-
"aria-label": labels.trigger,
|
|
1346
|
-
"data-slot": "color-menu-trigger",
|
|
1347
|
-
children: icon
|
|
1348
|
-
}
|
|
1349
|
-
) }),
|
|
1350
|
-
/* @__PURE__ */ jsxRuntime.jsx(DropdownMenuContent, { align: "start", className: "w-auto", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1351
|
-
"div",
|
|
1352
|
-
{
|
|
1353
|
-
role: "group",
|
|
1354
|
-
"aria-label": labels.heading,
|
|
1355
|
-
className: "flex items-center gap-1.5 px-1.5 py-1",
|
|
1356
|
-
children: [
|
|
1357
|
-
swatches.map((swatch) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1358
|
-
"button",
|
|
1359
|
-
{
|
|
1360
|
-
type: "button",
|
|
1361
|
-
"aria-label": swatch.label,
|
|
1362
|
-
"aria-pressed": editor?.isActive(swatch.mark) ?? false,
|
|
1363
|
-
title: swatch.label,
|
|
1364
|
-
style: { backgroundColor: swatch.preview },
|
|
1365
|
-
className: cn(
|
|
1366
|
-
"h-7 w-7 cursor-pointer rounded-full border transition-transform",
|
|
1367
|
-
"focus-visible:ring-ring/50 focus-visible:ring-2 focus-visible:outline-none",
|
|
1368
|
-
editor?.isActive(swatch.mark) ? "border-primary scale-110 border-2" : "border-border/60 hover:scale-110"
|
|
1369
|
-
),
|
|
1370
|
-
onClick: () => apply(swatch.mark)
|
|
1371
|
-
},
|
|
1372
|
-
swatch.mark
|
|
1373
|
-
)),
|
|
1374
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", className: "bg-border mx-0.5 h-5 w-px" }),
|
|
1375
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1376
|
-
"button",
|
|
1377
|
-
{
|
|
1378
|
-
type: "button",
|
|
1379
|
-
"aria-label": labels.clear,
|
|
1380
|
-
title: labels.clear,
|
|
1381
|
-
disabled: !active,
|
|
1382
|
-
className: cn(
|
|
1383
|
-
"text-muted-foreground flex h-7 w-7 cursor-pointer items-center justify-center rounded-full",
|
|
1384
|
-
"focus-visible:ring-ring/50 focus-visible:ring-2 focus-visible:outline-none",
|
|
1385
|
-
"hover:text-foreground disabled:cursor-default disabled:opacity-40"
|
|
1386
|
-
),
|
|
1387
|
-
onClick: () => apply(""),
|
|
1388
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Ban, { className: "h-4 w-4" })
|
|
1389
|
-
}
|
|
1390
|
-
)
|
|
1391
|
-
]
|
|
1392
|
-
}
|
|
1393
|
-
) })
|
|
1394
|
-
] });
|
|
1395
|
-
}
|
|
1396
|
-
function InsertLinkDialog({
|
|
1397
|
-
open,
|
|
1398
|
-
onOpenChange,
|
|
1399
|
-
labels,
|
|
1400
|
-
url,
|
|
1401
|
-
onUrlChange,
|
|
1402
|
-
text = "",
|
|
1403
|
-
onTextChange,
|
|
1404
|
-
needsText = false,
|
|
1405
|
-
onApply,
|
|
1406
|
-
onCancel
|
|
1407
|
-
}) {
|
|
1408
|
-
const urlId = react.useId();
|
|
1409
|
-
const textId = react.useId();
|
|
1410
|
-
const canApply = needsText ? Boolean(url.trim()) && Boolean(text.trim()) : true;
|
|
1411
|
-
function submitOnEnter(event) {
|
|
1412
|
-
if (event.key === "Enter" && canApply) {
|
|
1413
|
-
event.preventDefault();
|
|
1414
|
-
onApply();
|
|
1415
|
-
}
|
|
1416
|
-
}
|
|
1417
|
-
return /* @__PURE__ */ jsxRuntime.jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxRuntime.jsxs(DialogContent, { className: "px-4 py-2", "data-slot": "insert-link-dialog", children: [
|
|
1418
|
-
/* @__PURE__ */ jsxRuntime.jsx(DialogHeader, { children: /* @__PURE__ */ jsxRuntime.jsx(DialogTitle, { children: labels.title }) }),
|
|
1419
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-4 flex flex-col gap-3", children: [
|
|
1420
|
-
needsText && onTextChange && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
1421
|
-
/* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: textId, children: labels.textLabel }),
|
|
1422
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1423
|
-
Input,
|
|
1424
|
-
{
|
|
1425
|
-
id: textId,
|
|
1426
|
-
placeholder: labels.textPlaceholder,
|
|
1427
|
-
value: text,
|
|
1428
|
-
onChange: (event) => onTextChange(event.target.value),
|
|
1429
|
-
onKeyDown: submitOnEnter
|
|
1430
|
-
}
|
|
1431
|
-
)
|
|
1432
|
-
] }),
|
|
1433
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
1434
|
-
/* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: urlId, children: labels.urlLabel }),
|
|
1435
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1436
|
-
Input,
|
|
1437
|
-
{
|
|
1438
|
-
id: urlId,
|
|
1439
|
-
placeholder: labels.urlPlaceholder,
|
|
1440
|
-
value: url,
|
|
1441
|
-
onChange: (event) => onUrlChange(event.target.value),
|
|
1442
|
-
onKeyDown: submitOnEnter
|
|
1443
|
-
}
|
|
1444
|
-
),
|
|
1445
|
-
!needsText && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground text-xs", children: labels.hint })
|
|
1446
|
-
] })
|
|
1447
|
-
] }),
|
|
1448
|
-
/* @__PURE__ */ jsxRuntime.jsxs(DialogFooter, { children: [
|
|
1449
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1450
|
-
Button,
|
|
1451
|
-
{
|
|
1452
|
-
type: "button",
|
|
1453
|
-
variant: "outline",
|
|
1454
|
-
onClick: () => {
|
|
1455
|
-
if (onCancel) onCancel();
|
|
1456
|
-
else onOpenChange(false);
|
|
1457
|
-
},
|
|
1458
|
-
children: labels.cancel
|
|
1459
|
-
}
|
|
1460
|
-
),
|
|
1461
|
-
/* @__PURE__ */ jsxRuntime.jsx(Button, { type: "button", onClick: onApply, disabled: !canApply, children: labels.apply })
|
|
1462
|
-
] })
|
|
1463
|
-
] }) });
|
|
1464
|
-
}
|
|
1465
|
-
|
|
1466
|
-
exports.ColorMenu = ColorMenu;
|
|
1467
|
-
exports.EDITOR_CONTENT_STYLE = EDITOR_CONTENT_STYLE;
|
|
1468
|
-
exports.InsertImageDialog = InsertImageDialog;
|
|
1469
|
-
exports.InsertLinkDialog = InsertLinkDialog;
|
|
1470
|
-
exports.LinkBubbleMenu = LinkBubbleMenu;
|
|
1471
|
-
exports.ListMenu = ListMenu;
|
|
1472
|
-
exports.RichTextEditor = RichTextEditor;
|
|
1473
|
-
exports.RichTextToolbar = RichTextToolbar;
|
|
1474
|
-
exports.TableMenu = TableMenu;
|
|
1475
|
-
exports.createResizableImage = createResizableImage;
|
|
1476
|
-
exports.useRichTextEditor = useRichTextEditor;
|
|
1477
|
-
//# sourceMappingURL=editor.cjs.map
|
|
1478
|
-
//# sourceMappingURL=editor.cjs.map
|