quoin-editor 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -7
- package/dist/headless.cjs +21 -11
- package/dist/headless.cjs.map +1 -1
- package/dist/headless.d.cts +1 -1
- package/dist/headless.d.ts +1 -1
- package/dist/headless.js +21 -11
- package/dist/headless.js.map +1 -1
- package/dist/index.cjs +293 -164
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +35 -18
- package/dist/index.d.ts +35 -18
- package/dist/index.js +295 -166
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +23 -6
package/dist/index.cjs
CHANGED
|
@@ -60,7 +60,7 @@ __export(src_exports, {
|
|
|
60
60
|
module.exports = __toCommonJS(src_exports);
|
|
61
61
|
|
|
62
62
|
// src/canvas/BlockCanvas.tsx
|
|
63
|
-
var
|
|
63
|
+
var React15 = __toESM(require("react"), 1);
|
|
64
64
|
var import_core = require("@dnd-kit/core");
|
|
65
65
|
var import_modifiers = require("@dnd-kit/modifiers");
|
|
66
66
|
var import_sortable2 = require("@dnd-kit/sortable");
|
|
@@ -120,12 +120,13 @@ function paletteBlocks(definitions) {
|
|
|
120
120
|
var import_lucide_react7 = require("lucide-react");
|
|
121
121
|
|
|
122
122
|
// src/blocks/MarkdownBlockEditor.tsx
|
|
123
|
-
var
|
|
123
|
+
var React7 = __toESM(require("react"), 1);
|
|
124
124
|
|
|
125
125
|
// src/upload/FilePickerButton.tsx
|
|
126
126
|
var import_lucide_react = require("lucide-react");
|
|
127
127
|
|
|
128
128
|
// src/ui/button.tsx
|
|
129
|
+
var React2 = __toESM(require("react"), 1);
|
|
129
130
|
var import_react_slot = require("@radix-ui/react-slot");
|
|
130
131
|
var import_class_variance_authority = require("class-variance-authority");
|
|
131
132
|
|
|
@@ -136,6 +137,15 @@ function cn(...inputs) {
|
|
|
136
137
|
return (0, import_tailwind_merge.twMerge)((0, import_clsx.clsx)(inputs));
|
|
137
138
|
}
|
|
138
139
|
|
|
140
|
+
// src/ui/focus.ts
|
|
141
|
+
var FOCUS_RING = [
|
|
142
|
+
"outline-hidden",
|
|
143
|
+
"focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-solid",
|
|
144
|
+
"focus-visible:outline-[var(--quoin-ring)]"
|
|
145
|
+
].join(" ");
|
|
146
|
+
var HIGHLIGHT_BAR = "data-[highlighted]:shadow-[inset_2px_0_0_var(--quoin-foreground)]";
|
|
147
|
+
var SELECTED_BAR = "data-[selected=true]:shadow-[inset_2px_0_0_var(--quoin-foreground)]";
|
|
148
|
+
|
|
139
149
|
// src/ui/touch-target.ts
|
|
140
150
|
var TAP_TARGET_FLOOR = "pointer-coarse:min-h-11";
|
|
141
151
|
var SQUARE_TAP_TARGET_FLOOR = `${TAP_TARGET_FLOOR} pointer-coarse:min-w-11`;
|
|
@@ -148,7 +158,10 @@ var ICON_TAP_TARGET_FLOOR = [
|
|
|
148
158
|
// src/ui/button.tsx
|
|
149
159
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
150
160
|
var buttonVariants = (0, import_class_variance_authority.cva)(
|
|
151
|
-
|
|
161
|
+
[
|
|
162
|
+
"inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-[color,background-color,border-color,box-shadow,opacity] disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 shrink-0",
|
|
163
|
+
FOCUS_RING
|
|
164
|
+
],
|
|
152
165
|
{
|
|
153
166
|
variants: {
|
|
154
167
|
variant: {
|
|
@@ -173,17 +186,12 @@ var buttonVariants = (0, import_class_variance_authority.cva)(
|
|
|
173
186
|
defaultVariants: { variant: "default", size: "default" }
|
|
174
187
|
}
|
|
175
188
|
);
|
|
176
|
-
|
|
177
|
-
className,
|
|
178
|
-
variant = "default",
|
|
179
|
-
size = "default",
|
|
180
|
-
asChild = false,
|
|
181
|
-
...props
|
|
182
|
-
}) {
|
|
189
|
+
var Button = React2.forwardRef(function Button2({ className, variant = "default", size = "default", asChild = false, ...props }, ref) {
|
|
183
190
|
const Comp = asChild ? import_react_slot.Slot : "button";
|
|
184
191
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
185
192
|
Comp,
|
|
186
193
|
{
|
|
194
|
+
ref,
|
|
187
195
|
"data-slot": "button",
|
|
188
196
|
"data-variant": variant,
|
|
189
197
|
"data-size": size,
|
|
@@ -191,12 +199,12 @@ function Button({
|
|
|
191
199
|
...props
|
|
192
200
|
}
|
|
193
201
|
);
|
|
194
|
-
}
|
|
202
|
+
});
|
|
195
203
|
|
|
196
204
|
// src/upload/FilePickerButton.tsx
|
|
197
205
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
198
206
|
function FilePickerButton({ label, accept, disabled, onPick }) {
|
|
199
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "relative inline-flex rounded-md has-[:focus-visible]:
|
|
207
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "relative inline-flex rounded-md has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-offset-2 has-[:focus-visible]:outline-[var(--quoin-ring)]", children: [
|
|
200
208
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Button, { asChild: true, variant: "outline", size: "sm", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { "aria-hidden": "true", className: disabled ? "pointer-events-none opacity-50" : void 0, children: [
|
|
201
209
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react.Upload, { "aria-hidden": "true" }),
|
|
202
210
|
label
|
|
@@ -287,15 +295,18 @@ function insertCommand(command, value, caret) {
|
|
|
287
295
|
};
|
|
288
296
|
}
|
|
289
297
|
|
|
298
|
+
// src/markdown/platform.ts
|
|
299
|
+
function isMac() {
|
|
300
|
+
if (typeof navigator === "undefined") return false;
|
|
301
|
+
const { userAgentData, platform } = navigator;
|
|
302
|
+
return /mac|iphone|ipad/i.test(userAgentData?.platform || platform || "");
|
|
303
|
+
}
|
|
304
|
+
|
|
290
305
|
// src/markdown/shortcut-label.ts
|
|
291
306
|
var MAC_LABEL = { mod: "\u2318", alt: "\u2325", shift: "\u21E7" };
|
|
292
307
|
var OTHER_LABEL = { mod: "Ctrl", alt: "Alt", shift: "Shift" };
|
|
293
|
-
function onMac() {
|
|
294
|
-
if (typeof navigator === "undefined") return false;
|
|
295
|
-
return /Mac|iPhone|iPad/.test(navigator.userAgent);
|
|
296
|
-
}
|
|
297
308
|
function formatShortcut(hint) {
|
|
298
|
-
const mac =
|
|
309
|
+
const mac = isMac();
|
|
299
310
|
const labels = mac ? MAC_LABEL : OTHER_LABEL;
|
|
300
311
|
const parts = hint.split("+").map((part) => labels[part] ?? part.toUpperCase());
|
|
301
312
|
return parts.join(mac ? "" : "+");
|
|
@@ -349,7 +360,8 @@ function DialogContent({
|
|
|
349
360
|
DialogPrimitive.Close,
|
|
350
361
|
{
|
|
351
362
|
className: cn(
|
|
352
|
-
"
|
|
363
|
+
"absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100",
|
|
364
|
+
FOCUS_RING,
|
|
353
365
|
ICON_TAP_TARGET_FLOOR
|
|
354
366
|
),
|
|
355
367
|
children: [
|
|
@@ -638,6 +650,7 @@ function CommandItem({
|
|
|
638
650
|
"data-slot": "command-item",
|
|
639
651
|
className: cn(
|
|
640
652
|
"relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
|
|
653
|
+
SELECTED_BAR,
|
|
641
654
|
className
|
|
642
655
|
),
|
|
643
656
|
...props
|
|
@@ -754,7 +767,7 @@ function SlashPalette({
|
|
|
754
767
|
}
|
|
755
768
|
|
|
756
769
|
// src/markdown/useMarkdownKeymap.ts
|
|
757
|
-
var
|
|
770
|
+
var React3 = __toESM(require("react"), 1);
|
|
758
771
|
|
|
759
772
|
// src/markdown/list-continuation.ts
|
|
760
773
|
var LIST_MARKER = /^(\s*)((?:[-*+] \[[ xX]\] )|(?:[-*+] )|(?:\d+[.)] )|(?:> ))(.*)$/;
|
|
@@ -978,11 +991,12 @@ function useMarkdownKeymap({
|
|
|
978
991
|
commit,
|
|
979
992
|
onOpenPalette
|
|
980
993
|
}) {
|
|
981
|
-
const onKeyDown =
|
|
994
|
+
const onKeyDown = React3.useCallback(
|
|
982
995
|
(event) => {
|
|
996
|
+
if (event.nativeEvent.isComposing || event.keyCode === 229) return;
|
|
983
997
|
const el = event.currentTarget;
|
|
984
998
|
const { value, selectionStart, selectionEnd } = el;
|
|
985
|
-
const mod = event.metaKey
|
|
999
|
+
const mod = isMac() ? event.metaKey : event.ctrlKey;
|
|
986
1000
|
const run = (edit) => {
|
|
987
1001
|
if (!edit) return;
|
|
988
1002
|
event.preventDefault();
|
|
@@ -1023,7 +1037,7 @@ function useMarkdownKeymap({
|
|
|
1023
1037
|
},
|
|
1024
1038
|
[commit, onOpenPalette]
|
|
1025
1039
|
);
|
|
1026
|
-
const onPaste =
|
|
1040
|
+
const onPaste = React3.useCallback(
|
|
1027
1041
|
(event) => {
|
|
1028
1042
|
const el = event.currentTarget;
|
|
1029
1043
|
const edit = linkPaste(
|
|
@@ -1042,7 +1056,7 @@ function useMarkdownKeymap({
|
|
|
1042
1056
|
}
|
|
1043
1057
|
|
|
1044
1058
|
// src/markdown/useSlashPalette.ts
|
|
1045
|
-
var
|
|
1059
|
+
var React4 = __toESM(require("react"), 1);
|
|
1046
1060
|
|
|
1047
1061
|
// src/markdown/caret-coordinates.ts
|
|
1048
1062
|
var COPIED = [
|
|
@@ -1093,10 +1107,10 @@ function caretRect(el) {
|
|
|
1093
1107
|
|
|
1094
1108
|
// src/markdown/useSlashPalette.ts
|
|
1095
1109
|
function useSlashPalette(ref) {
|
|
1096
|
-
const [open, setOpen] =
|
|
1097
|
-
const [caret, setCaret] =
|
|
1098
|
-
const offsetRef =
|
|
1099
|
-
const openAtCaret =
|
|
1110
|
+
const [open, setOpen] = React4.useState(false);
|
|
1111
|
+
const [caret, setCaret] = React4.useState(null);
|
|
1112
|
+
const offsetRef = React4.useRef(0);
|
|
1113
|
+
const openAtCaret = React4.useCallback(() => {
|
|
1100
1114
|
const el = ref.current;
|
|
1101
1115
|
if (!el) return;
|
|
1102
1116
|
offsetRef.current = el.selectionStart;
|
|
@@ -1107,11 +1121,17 @@ function useSlashPalette(ref) {
|
|
|
1107
1121
|
}
|
|
1108
1122
|
|
|
1109
1123
|
// src/hooks/useAutoGrow.ts
|
|
1110
|
-
var
|
|
1124
|
+
var React6 = __toESM(require("react"), 1);
|
|
1125
|
+
|
|
1126
|
+
// src/hooks/use-isomorphic-layout-effect.ts
|
|
1127
|
+
var React5 = __toESM(require("react"), 1);
|
|
1128
|
+
var useIsomorphicLayoutEffect = typeof document === "undefined" ? React5.useEffect : React5.useLayoutEffect;
|
|
1129
|
+
|
|
1130
|
+
// src/hooks/useAutoGrow.ts
|
|
1111
1131
|
function useAutoGrow(value) {
|
|
1112
|
-
const ref =
|
|
1113
|
-
const lastWidth =
|
|
1114
|
-
const fit =
|
|
1132
|
+
const ref = React6.useRef(null);
|
|
1133
|
+
const lastWidth = React6.useRef(null);
|
|
1134
|
+
const fit = React6.useCallback(() => {
|
|
1115
1135
|
const el = ref.current;
|
|
1116
1136
|
if (!el) return;
|
|
1117
1137
|
const scroller = document.scrollingElement ?? document.documentElement;
|
|
@@ -1120,8 +1140,8 @@ function useAutoGrow(value) {
|
|
|
1120
1140
|
if (el.scrollHeight > 0) el.style.height = `${el.scrollHeight}px`;
|
|
1121
1141
|
if (scroller.scrollTop !== scrollTop) scroller.scrollTop = scrollTop;
|
|
1122
1142
|
}, []);
|
|
1123
|
-
|
|
1124
|
-
|
|
1143
|
+
useIsomorphicLayoutEffect(fit, [fit, value]);
|
|
1144
|
+
React6.useEffect(() => {
|
|
1125
1145
|
const el = ref.current;
|
|
1126
1146
|
if (!el || typeof ResizeObserver === "undefined") return;
|
|
1127
1147
|
const observer = new ResizeObserver(([entry]) => {
|
|
@@ -1143,17 +1163,18 @@ var str = (v) => typeof v === "string" ? v : "";
|
|
|
1143
1163
|
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
1144
1164
|
var BOX = [
|
|
1145
1165
|
"-mx-2 min-h-7 w-[calc(100%+1rem)] resize-none rounded-md border-0 bg-transparent px-2 py-0",
|
|
1146
|
-
"text-base leading-7
|
|
1147
|
-
"transition-[color,box-shadow]
|
|
1166
|
+
"text-base leading-7 placeholder:text-muted-foreground",
|
|
1167
|
+
"transition-[color,box-shadow]",
|
|
1168
|
+
FOCUS_RING
|
|
1148
1169
|
].join(" ");
|
|
1149
1170
|
function MarkdownBlockEditor({ data, onChange, onInsertBlock, onDone }) {
|
|
1150
1171
|
const { onError } = useQuoin();
|
|
1151
1172
|
const markdown = str(data.markdown);
|
|
1152
1173
|
const ref = useAutoGrow(markdown);
|
|
1153
1174
|
const palette = useSlashPalette(ref);
|
|
1154
|
-
const [shortcutsOpen, setShortcutsOpen] =
|
|
1175
|
+
const [shortcutsOpen, setShortcutsOpen] = React7.useState(false);
|
|
1155
1176
|
const set = (patch) => onChange({ ...data, ...patch });
|
|
1156
|
-
const commit =
|
|
1177
|
+
const commit = React7.useCallback(
|
|
1157
1178
|
(next) => onChange({ ...data, markdown: next }),
|
|
1158
1179
|
[data, onChange]
|
|
1159
1180
|
);
|
|
@@ -1219,6 +1240,7 @@ function MarkdownBlockEditor({ data, onChange, onInsertBlock, onDone }) {
|
|
|
1219
1240
|
onBlockCommand: (type) => {
|
|
1220
1241
|
palette.setOpen(false);
|
|
1221
1242
|
onInsertBlock?.(type);
|
|
1243
|
+
onDone?.();
|
|
1222
1244
|
},
|
|
1223
1245
|
onShortcuts: () => {
|
|
1224
1246
|
palette.setOpen(false);
|
|
@@ -1255,7 +1277,8 @@ function SelectTrigger({ className, children, ...props }) {
|
|
|
1255
1277
|
{
|
|
1256
1278
|
"data-slot": "select-trigger",
|
|
1257
1279
|
className: cn(
|
|
1258
|
-
"border-input data-[placeholder]:text-muted-foreground flex h-9 w-full cursor-pointer items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm shadow-xs
|
|
1280
|
+
"border-input data-[placeholder]:text-muted-foreground flex h-9 w-full cursor-pointer items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm shadow-xs disabled:cursor-not-allowed disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
|
1281
|
+
FOCUS_RING,
|
|
1259
1282
|
TAP_TARGET_FLOOR,
|
|
1260
1283
|
className
|
|
1261
1284
|
),
|
|
@@ -1294,6 +1317,7 @@ function SelectItem({ className, children, ...props }) {
|
|
|
1294
1317
|
"data-slot": "select-item",
|
|
1295
1318
|
className: cn(
|
|
1296
1319
|
"focus:bg-accent focus:text-accent-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
1320
|
+
HIGHLIGHT_BAR,
|
|
1297
1321
|
TAP_TARGET_FLOOR,
|
|
1298
1322
|
className
|
|
1299
1323
|
),
|
|
@@ -1307,22 +1331,25 @@ function SelectItem({ className, children, ...props }) {
|
|
|
1307
1331
|
}
|
|
1308
1332
|
|
|
1309
1333
|
// src/ui/textarea.tsx
|
|
1334
|
+
var React8 = __toESM(require("react"), 1);
|
|
1310
1335
|
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
1311
|
-
|
|
1336
|
+
var Textarea = React8.forwardRef(function Textarea2({ className, ...props }, ref) {
|
|
1312
1337
|
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
1313
1338
|
"textarea",
|
|
1314
1339
|
{
|
|
1340
|
+
ref,
|
|
1315
1341
|
"data-slot": "textarea",
|
|
1316
1342
|
className: cn(
|
|
1317
|
-
"border-input placeholder:text-muted-foreground flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow]
|
|
1318
|
-
|
|
1319
|
-
"
|
|
1343
|
+
"border-input placeholder:text-muted-foreground flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] disabled:pointer-events-none disabled:opacity-50 md:text-sm",
|
|
1344
|
+
FOCUS_RING,
|
|
1345
|
+
"focus-visible:border-ring",
|
|
1346
|
+
"aria-invalid:border-destructive",
|
|
1320
1347
|
className
|
|
1321
1348
|
),
|
|
1322
1349
|
...props
|
|
1323
1350
|
}
|
|
1324
1351
|
);
|
|
1325
|
-
}
|
|
1352
|
+
});
|
|
1326
1353
|
|
|
1327
1354
|
// src/blocks/CalloutBlockEditor.tsx
|
|
1328
1355
|
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
@@ -1345,24 +1372,27 @@ function CalloutBlockEditor({ data, onChange }) {
|
|
|
1345
1372
|
}
|
|
1346
1373
|
|
|
1347
1374
|
// src/ui/input.tsx
|
|
1375
|
+
var React9 = __toESM(require("react"), 1);
|
|
1348
1376
|
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
1349
|
-
|
|
1377
|
+
var Input = React9.forwardRef(function Input2({ className, type, ...props }, ref) {
|
|
1350
1378
|
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1351
1379
|
"input",
|
|
1352
1380
|
{
|
|
1381
|
+
ref,
|
|
1353
1382
|
type,
|
|
1354
1383
|
"data-slot": "input",
|
|
1355
1384
|
className: cn(
|
|
1356
|
-
"border-input placeholder:text-muted-foreground flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow]
|
|
1357
|
-
|
|
1358
|
-
"
|
|
1385
|
+
"border-input placeholder:text-muted-foreground flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:opacity-50 md:text-sm",
|
|
1386
|
+
FOCUS_RING,
|
|
1387
|
+
"focus-visible:border-ring",
|
|
1388
|
+
"aria-invalid:border-destructive",
|
|
1359
1389
|
TAP_TARGET_FLOOR,
|
|
1360
1390
|
className
|
|
1361
1391
|
),
|
|
1362
1392
|
...props
|
|
1363
1393
|
}
|
|
1364
1394
|
);
|
|
1365
|
-
}
|
|
1395
|
+
});
|
|
1366
1396
|
|
|
1367
1397
|
// src/blocks/CodeBlockEditor.tsx
|
|
1368
1398
|
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
@@ -1400,12 +1430,12 @@ function CodeBlockEditor({ data, onChange }) {
|
|
|
1400
1430
|
var import_react = require("react");
|
|
1401
1431
|
|
|
1402
1432
|
// src/upload/UploadField.tsx
|
|
1403
|
-
var
|
|
1433
|
+
var React10 = __toESM(require("react"), 1);
|
|
1404
1434
|
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
1405
1435
|
function UploadField({ category, label, accept, onUploaded }) {
|
|
1406
1436
|
const { upload, onError } = useQuoin();
|
|
1407
|
-
const [busy, setBusy] =
|
|
1408
|
-
const [error, setError] =
|
|
1437
|
+
const [busy, setBusy] = React10.useState(false);
|
|
1438
|
+
const [error, setError] = React10.useState(null);
|
|
1409
1439
|
async function run(file) {
|
|
1410
1440
|
if (!upload) return;
|
|
1411
1441
|
setBusy(true);
|
|
@@ -1418,11 +1448,12 @@ function UploadField({ category, label, accept, onUploaded }) {
|
|
|
1418
1448
|
setBusy(false);
|
|
1419
1449
|
return;
|
|
1420
1450
|
}
|
|
1421
|
-
setBusy(false);
|
|
1422
1451
|
try {
|
|
1423
|
-
onUploaded(result);
|
|
1452
|
+
await onUploaded(result);
|
|
1424
1453
|
} catch (cause) {
|
|
1425
1454
|
onError(cause instanceof Error ? cause : new Error(String(cause)));
|
|
1455
|
+
} finally {
|
|
1456
|
+
setBusy(false);
|
|
1426
1457
|
}
|
|
1427
1458
|
}
|
|
1428
1459
|
return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex flex-col items-start gap-1.5", children: [
|
|
@@ -1461,10 +1492,14 @@ function ImageBlockEditor({ data, onChange }) {
|
|
|
1461
1492
|
latest.current = data;
|
|
1462
1493
|
});
|
|
1463
1494
|
const set = (patch) => onChange({ ...latest.current, ...patch });
|
|
1464
|
-
const
|
|
1465
|
-
|
|
1495
|
+
const newestKey = (0, import_react.useRef)("");
|
|
1496
|
+
const onUploaded = async ({ storageKey }) => {
|
|
1497
|
+
newestKey.current = storageKey;
|
|
1498
|
+
const { width: _width, height: _height, ...unsized } = latest.current;
|
|
1499
|
+
onChange({ ...unsized, storageKey });
|
|
1500
|
+
const src = resolveAssetUrl(storageKey);
|
|
1466
1501
|
const size = src ? await intrinsicSize(src) : null;
|
|
1467
|
-
set({ storageKey
|
|
1502
|
+
if (size && newestKey.current === storageKey) set({ storageKey, ...size });
|
|
1468
1503
|
};
|
|
1469
1504
|
return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex flex-col gap-2", children: [
|
|
1470
1505
|
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(UploadField, { category: "image", label: "Upload image", onUploaded }),
|
|
@@ -1492,7 +1527,7 @@ function ImageBlockEditor({ data, onChange }) {
|
|
|
1492
1527
|
}
|
|
1493
1528
|
)
|
|
1494
1529
|
] }),
|
|
1495
|
-
str(data.storageKey) && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-xs text-muted-foreground", children: "Image attached
|
|
1530
|
+
str(data.storageKey) && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-xs text-muted-foreground", children: "Image attached" })
|
|
1496
1531
|
] });
|
|
1497
1532
|
}
|
|
1498
1533
|
|
|
@@ -1600,7 +1635,7 @@ var coreBlocks = [
|
|
|
1600
1635
|
];
|
|
1601
1636
|
|
|
1602
1637
|
// src/hooks/useBlockList.ts
|
|
1603
|
-
var
|
|
1638
|
+
var React11 = __toESM(require("react"), 1);
|
|
1604
1639
|
|
|
1605
1640
|
// src/hooks/new-block-id.ts
|
|
1606
1641
|
function newBlockId() {
|
|
@@ -1667,8 +1702,8 @@ function reducer(state, action) {
|
|
|
1667
1702
|
}
|
|
1668
1703
|
}
|
|
1669
1704
|
function useBlockList(initial) {
|
|
1670
|
-
const [state, dispatch] =
|
|
1671
|
-
const insertAt =
|
|
1705
|
+
const [state, dispatch] = React11.useReducer(reducer, { blocks: initial, removals: /* @__PURE__ */ new Map() });
|
|
1706
|
+
const insertAt = React11.useCallback(
|
|
1672
1707
|
(index, type, data = {}) => {
|
|
1673
1708
|
const id = newBlockId();
|
|
1674
1709
|
dispatch({ type: "insertAt", index, block: { id, type, data } });
|
|
@@ -1676,25 +1711,25 @@ function useBlockList(initial) {
|
|
|
1676
1711
|
},
|
|
1677
1712
|
[]
|
|
1678
1713
|
);
|
|
1679
|
-
const update =
|
|
1714
|
+
const update = React11.useCallback((id, data) => {
|
|
1680
1715
|
dispatch({ type: "update", id, data });
|
|
1681
1716
|
}, []);
|
|
1682
|
-
const remove =
|
|
1717
|
+
const remove = React11.useCallback((id) => {
|
|
1683
1718
|
removalSeq += 1;
|
|
1684
1719
|
const token = removalSeq;
|
|
1685
1720
|
dispatch({ type: "remove", id, token });
|
|
1686
1721
|
return token;
|
|
1687
1722
|
}, []);
|
|
1688
|
-
const undo =
|
|
1723
|
+
const undo = React11.useCallback((token) => {
|
|
1689
1724
|
dispatch({ type: "undo", token });
|
|
1690
1725
|
}, []);
|
|
1691
|
-
const move =
|
|
1726
|
+
const move = React11.useCallback((index, dir) => {
|
|
1692
1727
|
dispatch({ type: "move", index, dir });
|
|
1693
1728
|
}, []);
|
|
1694
|
-
const reorder =
|
|
1729
|
+
const reorder = React11.useCallback((from, to) => {
|
|
1695
1730
|
dispatch({ type: "reorder", from, to });
|
|
1696
1731
|
}, []);
|
|
1697
|
-
const reset =
|
|
1732
|
+
const reset = React11.useCallback((blocks) => {
|
|
1698
1733
|
dispatch({ type: "reset", blocks });
|
|
1699
1734
|
}, []);
|
|
1700
1735
|
return { blocks: state.blocks, insertAt, update, remove, undo, move, reorder, reset };
|
|
@@ -1776,7 +1811,8 @@ function DropdownMenuItem({
|
|
|
1776
1811
|
"data-slot": "dropdown-menu-item",
|
|
1777
1812
|
"data-inset": inset,
|
|
1778
1813
|
className: cn(
|
|
1779
|
-
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-
|
|
1814
|
+
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg:not([class*='size-'])]:size-4",
|
|
1815
|
+
HIGHLIGHT_BAR,
|
|
1780
1816
|
TAP_TARGET_FLOOR,
|
|
1781
1817
|
className
|
|
1782
1818
|
),
|
|
@@ -1853,7 +1889,7 @@ function BlockActionsMenu({
|
|
|
1853
1889
|
}
|
|
1854
1890
|
|
|
1855
1891
|
// src/canvas/BlockCard.tsx
|
|
1856
|
-
var
|
|
1892
|
+
var React12 = __toESM(require("react"), 1);
|
|
1857
1893
|
var import_lucide_react10 = require("lucide-react");
|
|
1858
1894
|
|
|
1859
1895
|
// src/canvas/BlockEditor.tsx
|
|
@@ -1947,32 +1983,36 @@ function BlockCardImpl({
|
|
|
1947
1983
|
const definition = blockByType(blockTypes, block.type);
|
|
1948
1984
|
const Preview = definition?.preview;
|
|
1949
1985
|
const editable = Boolean(definition?.editor) && !readOnly;
|
|
1950
|
-
const [editing, setEditing] =
|
|
1951
|
-
const editButtonRef =
|
|
1952
|
-
const editorContainerRef =
|
|
1953
|
-
const returnFocusOnClose =
|
|
1954
|
-
const clickedRef =
|
|
1955
|
-
const scrollTopRef =
|
|
1956
|
-
const
|
|
1986
|
+
const [editing, setEditing] = React12.useState(editingByDefault && editable);
|
|
1987
|
+
const editButtonRef = React12.useRef(null);
|
|
1988
|
+
const editorContainerRef = React12.useRef(null);
|
|
1989
|
+
const returnFocusOnClose = React12.useRef(false);
|
|
1990
|
+
const clickedRef = React12.useRef(null);
|
|
1991
|
+
const scrollTopRef = React12.useRef(null);
|
|
1992
|
+
const seedRef = React12.useRef(initialSelection);
|
|
1993
|
+
React12.useEffect(() => {
|
|
1994
|
+
seedRef.current = initialSelection;
|
|
1995
|
+
});
|
|
1996
|
+
const handleInsertBlock = React12.useCallback(
|
|
1957
1997
|
(type) => onInsertBlock?.(index + 1, type),
|
|
1958
1998
|
[onInsertBlock, index]
|
|
1959
1999
|
);
|
|
1960
|
-
const leaveEditing =
|
|
2000
|
+
const leaveEditing = React12.useCallback(() => {
|
|
1961
2001
|
returnFocusOnClose.current = true;
|
|
1962
2002
|
setEditing(false);
|
|
1963
2003
|
}, []);
|
|
1964
|
-
const settleRef =
|
|
2004
|
+
const settleRef = React12.useRef(null);
|
|
1965
2005
|
const clearSettle = () => {
|
|
1966
2006
|
if (settleRef.current === null) return;
|
|
1967
2007
|
window.clearTimeout(settleRef.current);
|
|
1968
2008
|
settleRef.current = null;
|
|
1969
2009
|
};
|
|
1970
|
-
|
|
2010
|
+
React12.useEffect(() => clearSettle, []);
|
|
1971
2011
|
function stillInside(node) {
|
|
1972
2012
|
if (!node) return false;
|
|
1973
2013
|
return Boolean(editorContainerRef.current?.contains(node)) || Boolean(node.closest(PORTALLED_OVERLAY));
|
|
1974
2014
|
}
|
|
1975
|
-
|
|
2015
|
+
React12.useEffect(() => {
|
|
1976
2016
|
if (!editing) return;
|
|
1977
2017
|
function onPointerDown(e) {
|
|
1978
2018
|
if (stillInside(e.target)) return;
|
|
@@ -1989,7 +2029,7 @@ function BlockCardImpl({
|
|
|
1989
2029
|
setEditing(false);
|
|
1990
2030
|
}, 0);
|
|
1991
2031
|
}
|
|
1992
|
-
|
|
2032
|
+
React12.useEffect(() => {
|
|
1993
2033
|
if (!editing) return;
|
|
1994
2034
|
const field = editorContainerRef.current?.querySelector(
|
|
1995
2035
|
'textarea, input, [role="combobox"]'
|
|
@@ -2000,7 +2040,7 @@ function BlockCardImpl({
|
|
|
2000
2040
|
if (clicked === null || !(field instanceof HTMLTextAreaElement)) {
|
|
2001
2041
|
field.focus();
|
|
2002
2042
|
if (field instanceof HTMLTextAreaElement) {
|
|
2003
|
-
const at =
|
|
2043
|
+
const at = seedRef.current ?? { start: field.value.length, end: field.value.length };
|
|
2004
2044
|
field.setSelectionRange(at.start, at.end);
|
|
2005
2045
|
}
|
|
2006
2046
|
return;
|
|
@@ -2008,26 +2048,26 @@ function BlockCardImpl({
|
|
|
2008
2048
|
const offset = sourceOffsetFor(field.value, clicked);
|
|
2009
2049
|
field.focus({ preventScroll: true });
|
|
2010
2050
|
field.setSelectionRange(offset, offset);
|
|
2011
|
-
}, [editing
|
|
2012
|
-
|
|
2051
|
+
}, [editing]);
|
|
2052
|
+
useIsomorphicLayoutEffect(() => {
|
|
2013
2053
|
const scrollTop = scrollTopRef.current;
|
|
2014
2054
|
scrollTopRef.current = null;
|
|
2015
2055
|
if (!editing || scrollTop === null) return;
|
|
2016
2056
|
const scroller = document.scrollingElement ?? document.documentElement;
|
|
2017
2057
|
if (scroller.scrollTop !== scrollTop) scroller.scrollTop = scrollTop;
|
|
2018
2058
|
}, [editing]);
|
|
2019
|
-
|
|
2059
|
+
React12.useEffect(() => {
|
|
2020
2060
|
if (editing || !returnFocusOnClose.current) return;
|
|
2021
2061
|
returnFocusOnClose.current = false;
|
|
2022
2062
|
editButtonRef.current?.focus();
|
|
2023
2063
|
}, [editing]);
|
|
2024
|
-
const reportedType =
|
|
2025
|
-
|
|
2064
|
+
const reportedType = React12.useRef(null);
|
|
2065
|
+
React12.useEffect(() => {
|
|
2026
2066
|
if (definition || reportedType.current === block.type) return;
|
|
2027
2067
|
reportedType.current = block.type;
|
|
2028
2068
|
onError(new Error(`No block type is registered for "${block.type}".`));
|
|
2029
2069
|
}, [definition, block.type, onError]);
|
|
2030
|
-
|
|
2070
|
+
React12.useEffect(() => {
|
|
2031
2071
|
if (!readOnly || !definition || definition.preview) return;
|
|
2032
2072
|
devWarn(
|
|
2033
2073
|
`the block type "${block.type}" has no \`preview\`, so a read-only canvas can draw only its label. Give its definition a \`preview\` to render the block itself.`
|
|
@@ -2108,20 +2148,21 @@ function BlockCardImpl({
|
|
|
2108
2148
|
)
|
|
2109
2149
|
] });
|
|
2110
2150
|
}
|
|
2111
|
-
var BlockCard =
|
|
2151
|
+
var BlockCard = React12.memo(BlockCardImpl);
|
|
2112
2152
|
|
|
2113
2153
|
// src/canvas/BlockInserter.tsx
|
|
2114
|
-
var
|
|
2154
|
+
var React13 = __toESM(require("react"), 1);
|
|
2115
2155
|
var import_lucide_react11 = require("lucide-react");
|
|
2116
2156
|
var import_jsx_runtime23 = require("react/jsx-runtime");
|
|
2117
2157
|
function BlockInserter({
|
|
2118
2158
|
index,
|
|
2119
2159
|
onInsert,
|
|
2120
|
-
label
|
|
2160
|
+
label,
|
|
2161
|
+
canvasRef
|
|
2121
2162
|
}) {
|
|
2122
2163
|
const { blockTypes } = useQuoin();
|
|
2123
2164
|
const options = paletteBlocks(blockTypes);
|
|
2124
|
-
const insertedAtRef =
|
|
2165
|
+
const insertedAtRef = React13.useRef(null);
|
|
2125
2166
|
function handleSelect(type) {
|
|
2126
2167
|
insertedAtRef.current = index;
|
|
2127
2168
|
onInsert(index, type);
|
|
@@ -2131,7 +2172,7 @@ function BlockInserter({
|
|
|
2131
2172
|
insertedAtRef.current = null;
|
|
2132
2173
|
if (insertedAt === null) return;
|
|
2133
2174
|
event.preventDefault();
|
|
2134
|
-
const card =
|
|
2175
|
+
const card = canvasRef.current?.querySelector(`[data-block-index="${insertedAt}"]`);
|
|
2135
2176
|
if (!card) return;
|
|
2136
2177
|
if (typeof card.scrollIntoView === "function") {
|
|
2137
2178
|
card.scrollIntoView({ block: "center", behavior: "smooth" });
|
|
@@ -2167,21 +2208,22 @@ function BlockInserter({
|
|
|
2167
2208
|
}
|
|
2168
2209
|
|
|
2169
2210
|
// src/canvas/DocumentStarter.tsx
|
|
2170
|
-
var
|
|
2211
|
+
var React14 = __toESM(require("react"), 1);
|
|
2171
2212
|
var import_jsx_runtime24 = require("react/jsx-runtime");
|
|
2172
2213
|
var LINE = [
|
|
2173
|
-
"w-full resize-none rounded-md border-0 bg-transparent px-2 py-1
|
|
2214
|
+
"w-full resize-none rounded-md border-0 bg-transparent px-2 py-1",
|
|
2174
2215
|
"text-base leading-7 placeholder:text-muted-foreground",
|
|
2175
|
-
"transition-[color,box-shadow]
|
|
2216
|
+
"transition-[color,box-shadow]",
|
|
2217
|
+
FOCUS_RING
|
|
2176
2218
|
].join(" ");
|
|
2177
2219
|
function DocumentStarter({
|
|
2178
2220
|
autoFocus = false,
|
|
2179
2221
|
onStart,
|
|
2180
2222
|
onInsertBlock
|
|
2181
2223
|
}) {
|
|
2182
|
-
const ref =
|
|
2224
|
+
const ref = React14.useRef(null);
|
|
2183
2225
|
const palette = useSlashPalette(ref);
|
|
2184
|
-
const [shortcutsOpen, setShortcutsOpen] =
|
|
2226
|
+
const [shortcutsOpen, setShortcutsOpen] = React14.useState(false);
|
|
2185
2227
|
function handleChange(e) {
|
|
2186
2228
|
const value = e.target.value;
|
|
2187
2229
|
e.target.value = "";
|
|
@@ -2203,6 +2245,7 @@ function DocumentStarter({
|
|
|
2203
2245
|
defaultValue: "",
|
|
2204
2246
|
onChange: handleChange,
|
|
2205
2247
|
onKeyDown: (e) => {
|
|
2248
|
+
if (e.nativeEvent.isComposing || e.keyCode === 229) return;
|
|
2206
2249
|
if (e.key !== "/") return;
|
|
2207
2250
|
e.preventDefault();
|
|
2208
2251
|
palette.openAtCaret();
|
|
@@ -2329,14 +2372,36 @@ function SortableBlock({
|
|
|
2329
2372
|
}
|
|
2330
2373
|
|
|
2331
2374
|
// src/canvas/unacknowledged.ts
|
|
2332
|
-
var
|
|
2375
|
+
var STRUCTURAL_ECHO_LIMIT = 64;
|
|
2376
|
+
function createUnacknowledged() {
|
|
2377
|
+
return { sequences: /* @__PURE__ */ new WeakMap(), emitted: 0, acknowledged: 0, newest: [] };
|
|
2378
|
+
}
|
|
2379
|
+
function keepNewest(outstanding) {
|
|
2380
|
+
const kept = Math.min(STRUCTURAL_ECHO_LIMIT, outstanding.emitted - outstanding.acknowledged);
|
|
2381
|
+
outstanding.newest.splice(0, outstanding.newest.length - kept);
|
|
2382
|
+
}
|
|
2333
2383
|
function remember(outstanding, blocks) {
|
|
2334
|
-
outstanding.
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2384
|
+
outstanding.emitted += 1;
|
|
2385
|
+
outstanding.sequences.set(blocks, outstanding.emitted);
|
|
2386
|
+
outstanding.newest.push(blocks);
|
|
2387
|
+
keepNewest(outstanding);
|
|
2388
|
+
}
|
|
2389
|
+
function copiedSequence(outstanding, blocks) {
|
|
2390
|
+
for (const emitted of outstanding.newest) {
|
|
2391
|
+
if (sameBlocks(emitted, blocks)) return outstanding.sequences.get(emitted);
|
|
2339
2392
|
}
|
|
2393
|
+
return void 0;
|
|
2394
|
+
}
|
|
2395
|
+
function acknowledge(outstanding, blocks) {
|
|
2396
|
+
const echoed = outstanding.sequences.get(blocks) ?? copiedSequence(outstanding, blocks);
|
|
2397
|
+
if (echoed === void 0 || echoed <= outstanding.acknowledged) return false;
|
|
2398
|
+
outstanding.acknowledged = echoed;
|
|
2399
|
+
keepNewest(outstanding);
|
|
2400
|
+
return true;
|
|
2401
|
+
}
|
|
2402
|
+
function forgetAll(outstanding) {
|
|
2403
|
+
outstanding.acknowledged = outstanding.emitted;
|
|
2404
|
+
keepNewest(outstanding);
|
|
2340
2405
|
}
|
|
2341
2406
|
|
|
2342
2407
|
// src/canvas/BlockCanvas.tsx
|
|
@@ -2357,20 +2422,20 @@ function Canvas({
|
|
|
2357
2422
|
(0, import_core.useSensor)(import_core.PointerSensor, { activationConstraint: { distance: 4 } }),
|
|
2358
2423
|
(0, import_core.useSensor)(import_core.KeyboardSensor, { coordinateGetter: import_sortable2.sortableKeyboardCoordinates })
|
|
2359
2424
|
);
|
|
2360
|
-
const dndId =
|
|
2361
|
-
const [newBlockId2, setNewBlockId] =
|
|
2362
|
-
const [seedSelection, setSeedSelection] =
|
|
2363
|
-
const listRef =
|
|
2364
|
-
const pendingDeleteIndexRef =
|
|
2365
|
-
const removedRef =
|
|
2366
|
-
const announcements =
|
|
2425
|
+
const dndId = React15.useId();
|
|
2426
|
+
const [newBlockId2, setNewBlockId] = React15.useState(null);
|
|
2427
|
+
const [seedSelection, setSeedSelection] = React15.useState(null);
|
|
2428
|
+
const listRef = React15.useRef(null);
|
|
2429
|
+
const pendingDeleteIndexRef = React15.useRef(null);
|
|
2430
|
+
const removedRef = React15.useRef(null);
|
|
2431
|
+
const announcements = React15.useMemo(
|
|
2367
2432
|
() => buildAnnouncements(list.blocks, blockTypes),
|
|
2368
2433
|
[list.blocks, blockTypes]
|
|
2369
2434
|
);
|
|
2370
|
-
const lastEmitted =
|
|
2371
|
-
const lastSupplied =
|
|
2372
|
-
const unacknowledged =
|
|
2373
|
-
|
|
2435
|
+
const lastEmitted = React15.useRef(blocks);
|
|
2436
|
+
const lastSupplied = React15.useRef(blocks);
|
|
2437
|
+
const unacknowledged = React15.useRef(createUnacknowledged());
|
|
2438
|
+
React15.useEffect(() => {
|
|
2374
2439
|
if (list.blocks === lastEmitted.current) return;
|
|
2375
2440
|
lastEmitted.current = list.blocks;
|
|
2376
2441
|
remember(unacknowledged.current, list.blocks);
|
|
@@ -2379,21 +2444,22 @@ function Canvas({
|
|
|
2379
2444
|
removedRef.current = null;
|
|
2380
2445
|
if (removed) onDelete?.(removed.block, removed.index);
|
|
2381
2446
|
}, [list.blocks, onChange, onDelete]);
|
|
2382
|
-
|
|
2447
|
+
React15.useEffect(() => {
|
|
2383
2448
|
const previous = lastSupplied.current;
|
|
2384
2449
|
lastSupplied.current = blocks;
|
|
2385
|
-
if (unacknowledged.current
|
|
2386
|
-
if (blocks === lastEmitted.current) unacknowledged.current.clear();
|
|
2387
|
-
return;
|
|
2388
|
-
}
|
|
2450
|
+
if (acknowledge(unacknowledged.current, blocks)) return;
|
|
2389
2451
|
if (sameBlocks(blocks, previous) || sameBlocks(blocks, list.blocks)) return;
|
|
2390
|
-
unacknowledged.current
|
|
2452
|
+
forgetAll(unacknowledged.current);
|
|
2391
2453
|
lastEmitted.current = blocks;
|
|
2392
2454
|
reset(blocks);
|
|
2393
2455
|
}, [blocks, reset]);
|
|
2394
|
-
|
|
2456
|
+
React15.useEffect(() => {
|
|
2457
|
+
if (newBlockId2 === null) return;
|
|
2458
|
+
setNewBlockId(null);
|
|
2459
|
+
setSeedSelection(null);
|
|
2460
|
+
}, [newBlockId2]);
|
|
2461
|
+
const insertPlain = React15.useCallback(
|
|
2395
2462
|
(index, type, data) => {
|
|
2396
|
-
setSeedSelection(null);
|
|
2397
2463
|
const seed = data ?? blockByType(blockTypes, type)?.initialData?.() ?? {};
|
|
2398
2464
|
setNewBlockId(insertAt(index, type, seed));
|
|
2399
2465
|
},
|
|
@@ -2403,10 +2469,9 @@ function Canvas({
|
|
|
2403
2469
|
const block = list.blocks[index];
|
|
2404
2470
|
pendingDeleteIndexRef.current = index;
|
|
2405
2471
|
if (block) removedRef.current = { block, index };
|
|
2406
|
-
setNewBlockId((current) => current === id2 ? null : current);
|
|
2407
2472
|
remove(id2);
|
|
2408
2473
|
}
|
|
2409
|
-
|
|
2474
|
+
React15.useEffect(() => {
|
|
2410
2475
|
const index = pendingDeleteIndexRef.current;
|
|
2411
2476
|
pendingDeleteIndexRef.current = null;
|
|
2412
2477
|
if (index === null || !listRef.current) return;
|
|
@@ -2432,7 +2497,7 @@ function Canvas({
|
|
|
2432
2497
|
};
|
|
2433
2498
|
if (readOnly) return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(ReadOnlyCanvas, { blocks: list.blocks, onUpdate: update, root: rootProps });
|
|
2434
2499
|
return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { ...rootProps, children: [
|
|
2435
|
-
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)(BlockInserter, { index: 0, onInsert: insertPlain, label: "Insert a block at the start" }),
|
|
2500
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)(BlockInserter, { index: 0, onInsert: insertPlain, label: "Insert a block at the start", canvasRef: listRef }),
|
|
2436
2501
|
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
2437
2502
|
import_core.DndContext,
|
|
2438
2503
|
{
|
|
@@ -2476,7 +2541,8 @@ function Canvas({
|
|
|
2476
2541
|
{
|
|
2477
2542
|
index: i + 1,
|
|
2478
2543
|
onInsert: insertPlain,
|
|
2479
|
-
label: `Insert a block after block ${i + 1}
|
|
2544
|
+
label: `Insert a block after block ${i + 1}`,
|
|
2545
|
+
canvasRef: listRef
|
|
2480
2546
|
}
|
|
2481
2547
|
)
|
|
2482
2548
|
] }, b.id)) }) })
|
|
@@ -2508,7 +2574,7 @@ function BlockCanvas({
|
|
|
2508
2574
|
id,
|
|
2509
2575
|
"aria-label": ariaLabel
|
|
2510
2576
|
}) {
|
|
2511
|
-
const config =
|
|
2577
|
+
const config = React15.useMemo(
|
|
2512
2578
|
() => ({ blockTypes, upload, resolveAssetUrl, onError }),
|
|
2513
2579
|
[blockTypes, upload, resolveAssetUrl, onError]
|
|
2514
2580
|
);
|
|
@@ -2528,71 +2594,134 @@ function BlockCanvas({
|
|
|
2528
2594
|
}
|
|
2529
2595
|
|
|
2530
2596
|
// src/hooks/useAutosave.ts
|
|
2531
|
-
var
|
|
2597
|
+
var React16 = __toESM(require("react"), 1);
|
|
2598
|
+
|
|
2599
|
+
// src/hooks/autosave-backoff.ts
|
|
2600
|
+
function clearTimer(ref) {
|
|
2601
|
+
if (ref.current !== null) clearTimeout(ref.current);
|
|
2602
|
+
ref.current = null;
|
|
2603
|
+
}
|
|
2604
|
+
var alwaysRetry = () => true;
|
|
2532
2605
|
function reportedFailure(result) {
|
|
2533
2606
|
return typeof result === "object" && result !== null && "ok" in result && result.ok === false;
|
|
2534
2607
|
}
|
|
2608
|
+
async function attemptSave(run) {
|
|
2609
|
+
try {
|
|
2610
|
+
const result = await run();
|
|
2611
|
+
return reportedFailure(result) ? { error: result } : null;
|
|
2612
|
+
} catch (error) {
|
|
2613
|
+
return { error };
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2616
|
+
function nextRetryDelay(error, retriesSoFar, { debounceMs, maxBackoffMs, maxRetries, isRetryable }) {
|
|
2617
|
+
if (retriesSoFar >= maxRetries || !isRetryable(error)) return null;
|
|
2618
|
+
return Math.min(debounceMs * 2 ** (retriesSoFar + 1), maxBackoffMs);
|
|
2619
|
+
}
|
|
2620
|
+
|
|
2621
|
+
// src/hooks/useAutosave.ts
|
|
2535
2622
|
function useAutosave({
|
|
2536
2623
|
deps,
|
|
2537
2624
|
build,
|
|
2538
2625
|
save,
|
|
2539
2626
|
debounceMs = 2e3,
|
|
2540
|
-
maxBackoffMs = 3e4
|
|
2627
|
+
maxBackoffMs = 3e4,
|
|
2628
|
+
maxRetries = 8,
|
|
2629
|
+
isRetryable = alwaysRetry,
|
|
2630
|
+
flushOnHide = true
|
|
2541
2631
|
}) {
|
|
2542
|
-
const [status, setStatus] =
|
|
2543
|
-
const
|
|
2544
|
-
const
|
|
2545
|
-
const
|
|
2546
|
-
const
|
|
2547
|
-
const
|
|
2548
|
-
const
|
|
2632
|
+
const [status, setStatus] = React16.useState("idle");
|
|
2633
|
+
const [error, setError] = React16.useState(null);
|
|
2634
|
+
const dirtyRef = React16.useRef(false);
|
|
2635
|
+
const inFlightRef = React16.useRef(false);
|
|
2636
|
+
const disposedRef = React16.useRef(false);
|
|
2637
|
+
const retriesRef = React16.useRef(0);
|
|
2638
|
+
const debounceRef = React16.useRef(null);
|
|
2639
|
+
const backoffRef = React16.useRef(null);
|
|
2640
|
+
const buildRef = React16.useRef(build);
|
|
2641
|
+
const saveRef = React16.useRef(save);
|
|
2642
|
+
const runRef = React16.useRef(() => {
|
|
2549
2643
|
});
|
|
2550
|
-
|
|
2644
|
+
React16.useEffect(() => {
|
|
2551
2645
|
buildRef.current = build;
|
|
2552
2646
|
saveRef.current = save;
|
|
2647
|
+
const policy = { debounceMs, maxBackoffMs, maxRetries, isRetryable };
|
|
2553
2648
|
runRef.current = () => {
|
|
2649
|
+
clearTimer(debounceRef);
|
|
2650
|
+
clearTimer(backoffRef);
|
|
2651
|
+
if (inFlightRef.current) return;
|
|
2652
|
+
inFlightRef.current = true;
|
|
2653
|
+
dirtyRef.current = false;
|
|
2654
|
+
if (!disposedRef.current) setStatus("saving");
|
|
2554
2655
|
void (async () => {
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
if (
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2656
|
+
const failure = await attemptSave(() => saveRef.current(buildRef.current()));
|
|
2657
|
+
inFlightRef.current = false;
|
|
2658
|
+
if (disposedRef.current) {
|
|
2659
|
+
if (dirtyRef.current) runRef.current();
|
|
2660
|
+
return;
|
|
2661
|
+
}
|
|
2662
|
+
if (!failure) {
|
|
2663
|
+
retriesRef.current = 0;
|
|
2664
|
+
setError(null);
|
|
2665
|
+
if (dirtyRef.current) runRef.current();
|
|
2666
|
+
else setStatus("saved");
|
|
2667
|
+
return;
|
|
2668
|
+
}
|
|
2669
|
+
dirtyRef.current = true;
|
|
2670
|
+
setError(failure.error);
|
|
2671
|
+
const delay = nextRetryDelay(failure.error, retriesRef.current, policy);
|
|
2672
|
+
if (delay === null) {
|
|
2673
|
+
setStatus("failed");
|
|
2674
|
+
return;
|
|
2568
2675
|
}
|
|
2676
|
+
retriesRef.current += 1;
|
|
2677
|
+
setStatus("retrying");
|
|
2678
|
+
backoffRef.current = setTimeout(() => runRef.current(), delay);
|
|
2569
2679
|
})();
|
|
2570
2680
|
};
|
|
2571
2681
|
});
|
|
2572
|
-
const seenRef =
|
|
2573
|
-
|
|
2682
|
+
const seenRef = React16.useRef(null);
|
|
2683
|
+
React16.useEffect(() => {
|
|
2574
2684
|
const seen2 = seenRef.current;
|
|
2575
2685
|
seenRef.current = deps;
|
|
2576
2686
|
if (seen2 === null) return;
|
|
2577
2687
|
const unchanged = seen2.length === deps.length && seen2.every((v, i) => v === deps[i]);
|
|
2578
2688
|
if (!unchanged) dirtyRef.current = true;
|
|
2579
2689
|
}, deps);
|
|
2580
|
-
|
|
2690
|
+
React16.useEffect(() => {
|
|
2581
2691
|
if (!dirtyRef.current) return;
|
|
2582
|
-
|
|
2583
|
-
|
|
2692
|
+
clearTimer(debounceRef);
|
|
2693
|
+
debounceRef.current = setTimeout(() => runRef.current(), debounceMs);
|
|
2694
|
+
return () => clearTimer(debounceRef);
|
|
2584
2695
|
}, deps);
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2696
|
+
React16.useEffect(() => {
|
|
2697
|
+
disposedRef.current = false;
|
|
2698
|
+
return () => {
|
|
2699
|
+
disposedRef.current = true;
|
|
2700
|
+
clearTimer(debounceRef);
|
|
2701
|
+
clearTimer(backoffRef);
|
|
2702
|
+
if (dirtyRef.current) runRef.current();
|
|
2703
|
+
};
|
|
2704
|
+
}, []);
|
|
2705
|
+
const retry = React16.useCallback(() => {
|
|
2706
|
+
retriesRef.current = 0;
|
|
2593
2707
|
runRef.current();
|
|
2594
2708
|
}, []);
|
|
2595
|
-
|
|
2709
|
+
const flush = React16.useCallback(() => {
|
|
2710
|
+
if (dirtyRef.current) retry();
|
|
2711
|
+
}, [retry]);
|
|
2712
|
+
React16.useEffect(() => {
|
|
2713
|
+
if (!flushOnHide) return;
|
|
2714
|
+
const onVisibility = () => {
|
|
2715
|
+
if (document.visibilityState === "hidden") flush();
|
|
2716
|
+
};
|
|
2717
|
+
window.addEventListener("pagehide", flush);
|
|
2718
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
2719
|
+
return () => {
|
|
2720
|
+
window.removeEventListener("pagehide", flush);
|
|
2721
|
+
document.removeEventListener("visibilitychange", onVisibility);
|
|
2722
|
+
};
|
|
2723
|
+
}, [flushOnHide, flush]);
|
|
2724
|
+
return { status, error, retry, flush };
|
|
2596
2725
|
}
|
|
2597
2726
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2598
2727
|
0 && (module.exports = {
|