@pipelex/mthds-form 0.10.1 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-W56WIIVC.js → chunk-4KX6YTLW.js} +415 -127
- package/dist/chunk-4KX6YTLW.js.map +1 -0
- package/dist/{chunk-TID7ZN63.js → chunk-CM4KVG53.js} +13 -10
- package/dist/chunk-CM4KVG53.js.map +1 -0
- package/dist/chunk-V7T4DM63.js +125 -0
- package/dist/chunk-V7T4DM63.js.map +1 -0
- package/dist/{chunk-4JQAJT7C.js → chunk-VS5JMFUV.js} +25 -38
- package/dist/chunk-VS5JMFUV.js.map +1 -0
- package/dist/core/index.d.ts +2 -1
- package/dist/core/index.js +3 -2
- package/dist/core/native-content.d.ts +6 -1
- package/dist/core/native-content.js +1 -1
- package/dist/core/save-plan.d.ts +136 -0
- package/dist/core/save-plan.js +6 -0
- package/dist/core/save-plan.js.map +1 -0
- package/dist/core/stuff-files.d.ts +19 -2
- package/dist/core/stuff-files.js +2 -2
- package/dist/field-renderer-C5Onx6qB.d.ts +65 -0
- package/dist/generative/index.d.ts +1 -1
- package/dist/generative/index.js +7 -5
- package/dist/generative/index.js.map +1 -1
- package/dist/react/index.d.ts +202 -112
- package/dist/react/index.js +4 -3
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/dist/chunk-4JQAJT7C.js.map +0 -1
- package/dist/chunk-TID7ZN63.js.map +0 -1
- package/dist/chunk-W56WIIVC.js.map +0 -1
- package/dist/field-renderer-BSU2hMf6.d.ts +0 -34
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { planStuffSave, planFileSave } from './chunk-V7T4DM63.js';
|
|
2
|
+
import { readStuffFile } from './chunk-VS5JMFUV.js';
|
|
3
|
+
import { viewableUrl, isNativeDateNode, isNativeHtmlNode, readHtmlContent, isNativeCompositeNode, readCompositeContent, isNativeValueNode, readDateContent, formatDateContent, readImageContent, readDataUrl, readDocumentContent } from './chunk-CM4KVG53.js';
|
|
3
4
|
import { conceptCategory } from './chunk-FABY3DEY.js';
|
|
4
5
|
import { acceptLabel, acceptMap, isAcceptedFile, formatOfFilename, DOCUMENT_FORMATS, IMAGE_FORMATS } from './chunk-XCO6GTEC.js';
|
|
5
6
|
import { isFilled } from './chunk-PM55AHRO.js';
|
|
@@ -7,7 +8,7 @@ import { toStoredDateValue, toDateInputValue } from './chunk-GKV77RAS.js';
|
|
|
7
8
|
import { ownProp } from './chunk-V3OV2XQT.js';
|
|
8
9
|
import { clsx } from 'clsx';
|
|
9
10
|
import { twMerge } from 'tailwind-merge';
|
|
10
|
-
import { ChevronDown, ChevronUp, Check, List, Braces, ListChecks, ToggleLeft, Hash, Image, FileText, CalendarDays, Type, Loader2, ImageOff, Upload, Link2, EyeOff, Eye, X, Minus, Plus, Trash2, Copy, Download, ChevronRight } from 'lucide-react';
|
|
11
|
+
import { ChevronDown, ChevronUp, Check, List, Braces, ListChecks, ToggleLeft, Hash, Image, FileText, CalendarDays, Type, Loader2, ImageOff, Upload, Link2, EyeOff, Eye, X, Minus, Plus, Trash2, Copy, Download, ChevronRight, TriangleAlert } from 'lucide-react';
|
|
11
12
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
12
13
|
import * as React2 from 'react';
|
|
13
14
|
import { createContext, use, useId, useMemo, useContext, useState, useRef, useCallback, useEffect, Fragment as Fragment$1, useLayoutEffect } from 'react';
|
|
@@ -203,6 +204,7 @@ var DEFAULT_FIELD_STRINGS = {
|
|
|
203
204
|
dropToUpload: "Drop to upload",
|
|
204
205
|
dropOrBrowse: "Drop a file or click to browse",
|
|
205
206
|
pasteUrlInstead: "paste a URL instead",
|
|
207
|
+
uploadUnavailable: "Files cannot be uploaded here. Paste a link to the file instead.",
|
|
206
208
|
urlPlaceholder: "https://\u2026",
|
|
207
209
|
fileUrlAria: (label) => label ? `Link to the file for ${label}` : "Link to the file",
|
|
208
210
|
uploadedFile: "Attached file",
|
|
@@ -220,6 +222,9 @@ var DEFAULT_FIELD_STRINGS = {
|
|
|
220
222
|
copyText: "Copy the text",
|
|
221
223
|
download: "Download",
|
|
222
224
|
downloading: "Downloading\u2026",
|
|
225
|
+
downloadIncomplete: (names) => `${names.length === 1 ? "One file" : `${names.length} files`} could not be saved: ${names.join(", ")}`,
|
|
226
|
+
downloadFile: (name) => `Download ${name}`,
|
|
227
|
+
downloadFileFailed: "The file could not be saved",
|
|
223
228
|
viewRendered: "Result",
|
|
224
229
|
viewJson: "JSON",
|
|
225
230
|
copyJson: "Copy the JSON",
|
|
@@ -278,7 +283,16 @@ function FieldShell({
|
|
|
278
283
|
!required && /* @__PURE__ */ jsx("span", { className: "ml-auto font-mono text-[10px] uppercase tracking-wider text-muted-foreground", children: s.optionalBadge })
|
|
279
284
|
] }),
|
|
280
285
|
description && /* @__PURE__ */ jsx("p", { className: "text-[12px] leading-relaxed text-muted-foreground", children: description }),
|
|
281
|
-
|
|
286
|
+
/* @__PURE__ */ jsx(
|
|
287
|
+
"div",
|
|
288
|
+
{
|
|
289
|
+
className: cn(
|
|
290
|
+
"space-y-2",
|
|
291
|
+
isApp && error && "rounded-lg ring-1 ring-destructive/70 ring-offset-4 ring-offset-background"
|
|
292
|
+
),
|
|
293
|
+
children
|
|
294
|
+
}
|
|
295
|
+
),
|
|
282
296
|
error && /* @__PURE__ */ jsx("p", { className: "flex items-center gap-1 text-[11px] text-destructive", role: "alert", children: error })
|
|
283
297
|
] });
|
|
284
298
|
}
|
|
@@ -348,9 +362,11 @@ function FileField({
|
|
|
348
362
|
field,
|
|
349
363
|
value,
|
|
350
364
|
onDropFile,
|
|
365
|
+
allowUrl = true,
|
|
351
366
|
onChange,
|
|
352
367
|
id,
|
|
353
368
|
uploading,
|
|
369
|
+
uploadError,
|
|
354
370
|
resolveUrl,
|
|
355
371
|
error,
|
|
356
372
|
disabled,
|
|
@@ -372,16 +388,28 @@ function FileField({
|
|
|
372
388
|
}, []);
|
|
373
389
|
useEffect(() => () => setLocal(null), [setLocal]);
|
|
374
390
|
const [rejected, setRejected] = useState(null);
|
|
391
|
+
const [dismissedUploadError, setDismissedUploadError] = useState(null);
|
|
392
|
+
useEffect(() => {
|
|
393
|
+
if (uploadError === void 0) setDismissedUploadError(null);
|
|
394
|
+
else setRejected(null);
|
|
395
|
+
}, [uploadError]);
|
|
396
|
+
const dismissUploadError = useCallback(() => {
|
|
397
|
+
setDismissedUploadError(uploadError ?? null);
|
|
398
|
+
}, [uploadError]);
|
|
399
|
+
const failure = uploadError !== void 0 && uploadError !== dismissedUploadError ? uploadError : null;
|
|
375
400
|
const formats = field.formats;
|
|
376
401
|
const hint = acceptLabel(formats);
|
|
377
402
|
const accept = useMemo(() => acceptMap(formats), [formats]);
|
|
403
|
+
const canUpload = onDropFile !== void 0;
|
|
378
404
|
const handleFile = useCallback(
|
|
379
405
|
(file) => {
|
|
406
|
+
if (!onDropFile) return;
|
|
380
407
|
if (!isAcceptedFile(formats, file)) {
|
|
381
408
|
setRejected(file.name);
|
|
382
409
|
return;
|
|
383
410
|
}
|
|
384
411
|
setRejected(null);
|
|
412
|
+
setDismissedUploadError(null);
|
|
385
413
|
setLocal({ objectUrl: URL.createObjectURL(file), type: file.type });
|
|
386
414
|
onDropFile(file);
|
|
387
415
|
},
|
|
@@ -391,7 +419,9 @@ function FileField({
|
|
|
391
419
|
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
|
392
420
|
maxFiles: 1,
|
|
393
421
|
multiple: false,
|
|
394
|
-
|
|
422
|
+
// Disabled, not merely unrendered, when there is no upload path: the hook
|
|
423
|
+
// cannot be skipped, and nothing may open a picker through it.
|
|
424
|
+
disabled: busy || !canUpload,
|
|
395
425
|
// Filters the OS picker so a wrong file is hard to choose in the first
|
|
396
426
|
// place. It is NOT the enforcement: a drag-and-drop bypasses the picker
|
|
397
427
|
// entirely, and a browser reporting an empty `File.type` slips through
|
|
@@ -421,8 +451,9 @@ function FileField({
|
|
|
421
451
|
setResolved(null);
|
|
422
452
|
setPreviewOpen(false);
|
|
423
453
|
setRejected(null);
|
|
454
|
+
dismissUploadError();
|
|
424
455
|
onChange(void 0);
|
|
425
|
-
}, [setLocal, onChange]);
|
|
456
|
+
}, [setLocal, onChange, dismissUploadError]);
|
|
426
457
|
const localIsCurrent = !!localPreview && (localPreview.boundUrl === void 0 || localPreview.boundUrl === value?.url);
|
|
427
458
|
useEffect(() => {
|
|
428
459
|
if (!localPreview) return;
|
|
@@ -464,6 +495,14 @@ function FileField({
|
|
|
464
495
|
};
|
|
465
496
|
const urlValue = value?.url ?? "";
|
|
466
497
|
const urlText = presentation === "studio" || isReadableLink(urlValue) || urlValue === typedUrl ? urlValue : "";
|
|
498
|
+
if (!canUpload && !allowUrl) {
|
|
499
|
+
throw new Error(
|
|
500
|
+
`The file field at "${id}" has no way in: the form's environment supplies no onDropFile and sets allowUrl to false. Supply onDropFile so the file can be uploaded, or leave allowUrl unset so a link can be pasted.`
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
const linkOpen = allowUrl && (!canUpload || showUrl);
|
|
504
|
+
const noUploadLineShown = !canUpload && !hasFile && !uploading;
|
|
505
|
+
const noUploadLineId = `${domId}-no-upload`;
|
|
467
506
|
return /* @__PURE__ */ jsxs(
|
|
468
507
|
FieldShell,
|
|
469
508
|
{
|
|
@@ -497,7 +536,19 @@ function FileField({
|
|
|
497
536
|
// load, which is the one thing a refusal must not look like.
|
|
498
537
|
/* @__PURE__ */ jsx("div", { className: "flex h-24 items-center justify-center rounded-md border border-border bg-muted", children: /* @__PURE__ */ jsx(ImageOff, { className: "h-5 w-5 text-muted-foreground" }) })
|
|
499
538
|
))
|
|
500
|
-
] }) : (
|
|
539
|
+
] }) : !canUpload ? (
|
|
540
|
+
// No upload path, so no dropzone and no picker: a control that takes a
|
|
541
|
+
// file with nowhere to send it drops the file without a word. The line
|
|
542
|
+
// says why the link input below is the way in, and the hint says what
|
|
543
|
+
// that link has to point at.
|
|
544
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-0.5", children: uploading ? /* @__PURE__ */ jsxs("p", { className: "flex items-center gap-1.5 text-[12px] text-muted-foreground", children: [
|
|
545
|
+
/* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }),
|
|
546
|
+
s.uploading
|
|
547
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
548
|
+
/* @__PURE__ */ jsx("p", { id: noUploadLineId, className: "text-[12px] text-muted-foreground", children: s.uploadUnavailable }),
|
|
549
|
+
hint && /* @__PURE__ */ jsx("p", { className: "font-mono text-[10.5px] text-muted-foreground", children: hint })
|
|
550
|
+
] }) })
|
|
551
|
+
) : (
|
|
501
552
|
// `role="presentation"` and no tab stop: this div is the drop TARGET and
|
|
502
553
|
// the visual affordance, and the control inside it is a real
|
|
503
554
|
// `<input type="file">`, which already has the right role and now has a
|
|
@@ -539,39 +590,43 @@ function FileField({
|
|
|
539
590
|
}
|
|
540
591
|
)
|
|
541
592
|
),
|
|
542
|
-
|
|
593
|
+
!busy && (rejected ? /* @__PURE__ */ jsxs("p", { role: "alert", className: "text-[12px] text-destructive", children: [
|
|
543
594
|
/* @__PURE__ */ jsx("span", { className: "font-mono", children: rejected }),
|
|
544
595
|
" \u2014 ",
|
|
545
596
|
s.unsupportedFileType(hint)
|
|
546
|
-
] }),
|
|
547
|
-
|
|
548
|
-
"button",
|
|
549
|
-
{
|
|
550
|
-
type: "button",
|
|
551
|
-
disabled: busy,
|
|
552
|
-
onClick: () => setShowUrl(true),
|
|
553
|
-
className: "flex w-fit items-center gap-1 font-mono text-[11px] text-muted-foreground transition-colors hover:text-foreground disabled:opacity-50",
|
|
554
|
-
children: [
|
|
555
|
-
/* @__PURE__ */ jsx(Link2, { className: "h-3 w-3" }),
|
|
556
|
-
s.pasteUrlInstead
|
|
557
|
-
]
|
|
558
|
-
}
|
|
559
|
-
) : /* @__PURE__ */ jsx(
|
|
597
|
+
] }) : failure && /* @__PURE__ */ jsx("p", { role: "alert", className: "text-[12px] text-destructive", children: failure })),
|
|
598
|
+
linkOpen ? /* @__PURE__ */ jsx(
|
|
560
599
|
"input",
|
|
561
600
|
{
|
|
562
601
|
type: "text",
|
|
563
|
-
|
|
602
|
+
id: canUpload ? void 0 : domId,
|
|
603
|
+
autoFocus: showUrl,
|
|
564
604
|
value: urlText,
|
|
565
605
|
disabled: busy,
|
|
566
606
|
"aria-label": s.fileUrlAria(fieldLabel(field.title, field.name, presentation)),
|
|
607
|
+
"aria-describedby": noUploadLineShown ? noUploadLineId : void 0,
|
|
608
|
+
"aria-invalid": canUpload ? void 0 : !!error,
|
|
567
609
|
placeholder: s.urlPlaceholder,
|
|
568
610
|
onChange: (e) => {
|
|
569
611
|
setLocal(null);
|
|
612
|
+
dismissUploadError();
|
|
570
613
|
setTypedUrl(e.target.value);
|
|
571
614
|
onChange(e.target.value ? { url: e.target.value } : void 0);
|
|
572
615
|
},
|
|
573
616
|
className: cn(fieldControlClass, "h-9 px-3 font-mono text-[12px]")
|
|
574
617
|
}
|
|
618
|
+
) : allowUrl && /* @__PURE__ */ jsxs(
|
|
619
|
+
"button",
|
|
620
|
+
{
|
|
621
|
+
type: "button",
|
|
622
|
+
disabled: busy,
|
|
623
|
+
onClick: () => setShowUrl(true),
|
|
624
|
+
className: "flex w-fit items-center gap-1 font-mono text-[11px] text-muted-foreground transition-colors hover:text-foreground disabled:opacity-50",
|
|
625
|
+
children: [
|
|
626
|
+
/* @__PURE__ */ jsx(Link2, { className: "h-3 w-3" }),
|
|
627
|
+
s.pasteUrlInstead
|
|
628
|
+
]
|
|
629
|
+
}
|
|
575
630
|
)
|
|
576
631
|
]
|
|
577
632
|
}
|
|
@@ -1111,6 +1166,7 @@ function ListField({ field, value, onChange, id, error, env }) {
|
|
|
1111
1166
|
const isApp = presentation === "app";
|
|
1112
1167
|
const items = value ?? [];
|
|
1113
1168
|
const [rowKeys, setRowKeys] = useRowKeys(items.length);
|
|
1169
|
+
const [staleErrors, setStaleErrors] = useState(NO_ERRORS);
|
|
1114
1170
|
const setItem = (index, itemValue) => {
|
|
1115
1171
|
const next = items.slice();
|
|
1116
1172
|
next[index] = itemValue;
|
|
@@ -1118,10 +1174,40 @@ function ListField({ field, value, onChange, id, error, env }) {
|
|
|
1118
1174
|
};
|
|
1119
1175
|
const removeItem = (index) => {
|
|
1120
1176
|
setRowKeys((prev) => prev.filter((_, i) => i !== index));
|
|
1177
|
+
setStaleErrors((prev) => withRenumberedErrors(prev, id, index, env?.uploadErrors));
|
|
1121
1178
|
onChange(items.filter((_, i) => i !== index));
|
|
1122
1179
|
};
|
|
1123
1180
|
const addItem = () => onChange([...items, emptyValue(field.item)]);
|
|
1124
1181
|
const busy = listIsBusy(id, env?.uploadingIds);
|
|
1182
|
+
const hostErrors = env?.uploadErrors;
|
|
1183
|
+
useEffect(() => {
|
|
1184
|
+
setStaleErrors((prev) => {
|
|
1185
|
+
if (prev.size === 0) return prev;
|
|
1186
|
+
const next = new Map([...prev].filter(([key, message]) => hostErrors?.get(key) === message));
|
|
1187
|
+
return next.size === prev.size ? prev : next;
|
|
1188
|
+
});
|
|
1189
|
+
}, [hostErrors]);
|
|
1190
|
+
const rowEnv = useMemo(() => {
|
|
1191
|
+
if (!env || staleErrors.size === 0) return env;
|
|
1192
|
+
const drop = env.onDropFile;
|
|
1193
|
+
return {
|
|
1194
|
+
...env,
|
|
1195
|
+
uploadErrors: new Map(
|
|
1196
|
+
[...env.uploadErrors ?? []].filter(([key, message]) => staleErrors.get(key) !== message)
|
|
1197
|
+
),
|
|
1198
|
+
// A new file at a position is a new attempt there, so what the host says
|
|
1199
|
+
// about that position from now on is about the file that is there.
|
|
1200
|
+
onDropFile: drop ? (dropId, file) => {
|
|
1201
|
+
setStaleErrors((prev) => {
|
|
1202
|
+
if (!prev.has(dropId)) return prev;
|
|
1203
|
+
const next = new Map(prev);
|
|
1204
|
+
next.delete(dropId);
|
|
1205
|
+
return next;
|
|
1206
|
+
});
|
|
1207
|
+
drop(dropId, file);
|
|
1208
|
+
} : void 0
|
|
1209
|
+
};
|
|
1210
|
+
}, [env, staleErrors]);
|
|
1125
1211
|
const isFull = field.maxItemCount !== void 0 && items.length >= field.maxItemCount;
|
|
1126
1212
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
1127
1213
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-baseline gap-x-2 gap-y-1", children: [
|
|
@@ -1155,7 +1241,7 @@ function ListField({ field, value, onChange, id, error, env }) {
|
|
|
1155
1241
|
value: item,
|
|
1156
1242
|
onChange: (v) => setItem(index, v),
|
|
1157
1243
|
id: `${id}.${index}`,
|
|
1158
|
-
env
|
|
1244
|
+
env: rowEnv
|
|
1159
1245
|
}
|
|
1160
1246
|
) }),
|
|
1161
1247
|
/* @__PURE__ */ jsx(
|
|
@@ -1199,6 +1285,18 @@ function listIsBusy(id, uploadingIds) {
|
|
|
1199
1285
|
for (const uploading of uploadingIds) if (uploading.startsWith(prefix)) return true;
|
|
1200
1286
|
return false;
|
|
1201
1287
|
}
|
|
1288
|
+
var NO_ERRORS = /* @__PURE__ */ new Map();
|
|
1289
|
+
function withRenumberedErrors(stale, listId, removed, hostErrors) {
|
|
1290
|
+
if (!hostErrors?.size) return stale;
|
|
1291
|
+
const prefix = `${listId}.`;
|
|
1292
|
+
const next = new Map(stale);
|
|
1293
|
+
for (const [key, message] of hostErrors) {
|
|
1294
|
+
if (!key.startsWith(prefix)) continue;
|
|
1295
|
+
const row = key.slice(prefix.length).split(".")[0] ?? "";
|
|
1296
|
+
if (/^\d+$/.test(row) && Number(row) >= removed) next.set(key, message);
|
|
1297
|
+
}
|
|
1298
|
+
return next.size === stale.size && [...next].every(([k, m]) => stale.get(k) === m) ? stale : next;
|
|
1299
|
+
}
|
|
1202
1300
|
function useRowKeys(length) {
|
|
1203
1301
|
const [state, setState] = useState(() => ({
|
|
1204
1302
|
keys: Array.from({ length }, (_, i) => i),
|
|
@@ -1265,6 +1363,7 @@ function UnknownField({ field, value, onChange, id, error, disabled }) {
|
|
|
1265
1363
|
}
|
|
1266
1364
|
function FieldRenderer({ field, value, onChange, id, error, env }) {
|
|
1267
1365
|
const disabled = env?.disabled;
|
|
1366
|
+
const drop = env?.onDropFile;
|
|
1268
1367
|
switch (field.kind) {
|
|
1269
1368
|
case "text":
|
|
1270
1369
|
return /* @__PURE__ */ jsx(
|
|
@@ -1344,8 +1443,10 @@ function FieldRenderer({ field, value, onChange, id, error, env }) {
|
|
|
1344
1443
|
field,
|
|
1345
1444
|
value,
|
|
1346
1445
|
onChange,
|
|
1347
|
-
onDropFile: (file) =>
|
|
1446
|
+
onDropFile: drop ? (file) => drop(id, file) : void 0,
|
|
1447
|
+
allowUrl: env?.allowUrl,
|
|
1348
1448
|
uploading: env?.uploadingIds?.has(id),
|
|
1449
|
+
uploadError: env?.uploadErrors?.get(id),
|
|
1349
1450
|
resolveUrl: env?.resolveUrl,
|
|
1350
1451
|
id,
|
|
1351
1452
|
error,
|
|
@@ -1359,8 +1460,10 @@ function FieldRenderer({ field, value, onChange, id, error, env }) {
|
|
|
1359
1460
|
field,
|
|
1360
1461
|
value,
|
|
1361
1462
|
onChange,
|
|
1362
|
-
onDropFile: (file) =>
|
|
1463
|
+
onDropFile: drop ? (file) => drop(id, file) : void 0,
|
|
1464
|
+
allowUrl: env?.allowUrl,
|
|
1363
1465
|
uploading: env?.uploadingIds?.has(id),
|
|
1466
|
+
uploadError: env?.uploadErrors?.get(id),
|
|
1364
1467
|
resolveUrl: env?.resolveUrl,
|
|
1365
1468
|
id,
|
|
1366
1469
|
error,
|
|
@@ -1405,20 +1508,84 @@ function FieldRenderer({ field, value, onChange, id, error, env }) {
|
|
|
1405
1508
|
);
|
|
1406
1509
|
}
|
|
1407
1510
|
}
|
|
1511
|
+
|
|
1512
|
+
// src/react/absolute-url.ts
|
|
1513
|
+
function absoluteUrl(url) {
|
|
1514
|
+
const gated = viewableUrl(url);
|
|
1515
|
+
if (!gated?.startsWith("/") || typeof document === "undefined") return url;
|
|
1516
|
+
try {
|
|
1517
|
+
return new URL(gated, document.baseURI).href;
|
|
1518
|
+
} catch {
|
|
1519
|
+
return url;
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
// src/react/save-in-browser.ts
|
|
1524
|
+
function saveBlob(blob, filename) {
|
|
1525
|
+
const href = URL.createObjectURL(blob);
|
|
1526
|
+
const anchor = document.createElement("a");
|
|
1527
|
+
anchor.href = href;
|
|
1528
|
+
anchor.download = filename;
|
|
1529
|
+
document.body.appendChild(anchor);
|
|
1530
|
+
anchor.click();
|
|
1531
|
+
anchor.remove();
|
|
1532
|
+
setTimeout(() => URL.revokeObjectURL(href), 0);
|
|
1533
|
+
}
|
|
1534
|
+
function blobType(mimeType) {
|
|
1535
|
+
return /^(text\/|application\/json)/.test(mimeType) && !/charset=/i.test(mimeType) ? `${mimeType};charset=utf-8` : mimeType;
|
|
1536
|
+
}
|
|
1537
|
+
async function saveInBrowser(files) {
|
|
1538
|
+
const failed = [];
|
|
1539
|
+
for (const file of files) {
|
|
1540
|
+
try {
|
|
1541
|
+
if (file.text !== void 0) {
|
|
1542
|
+
saveBlob(new Blob([file.text], { type: blobType(file.mimeType) }), file.name);
|
|
1543
|
+
continue;
|
|
1544
|
+
}
|
|
1545
|
+
const response = await fetch(file.url);
|
|
1546
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
1547
|
+
saveBlob(await response.blob(), file.name);
|
|
1548
|
+
} catch (error) {
|
|
1549
|
+
failed.push({ file, reason: error instanceof Error ? error.message : String(error) });
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
return { failed };
|
|
1553
|
+
}
|
|
1408
1554
|
var ResultEnvContext = createContext({});
|
|
1409
1555
|
function ResultEnvProvider({
|
|
1410
1556
|
resolveUrl,
|
|
1411
1557
|
resolveShareUrl,
|
|
1412
1558
|
proseImages,
|
|
1413
1559
|
tableColumns,
|
|
1560
|
+
saveFiles,
|
|
1561
|
+
downloads,
|
|
1414
1562
|
children
|
|
1415
1563
|
}) {
|
|
1416
1564
|
const env = useMemo(
|
|
1417
|
-
() => ({ resolveUrl, resolveShareUrl, proseImages, tableColumns }),
|
|
1418
|
-
[resolveUrl, resolveShareUrl, proseImages, tableColumns]
|
|
1565
|
+
() => ({ resolveUrl, resolveShareUrl, proseImages, tableColumns, saveFiles, downloads }),
|
|
1566
|
+
[resolveUrl, resolveShareUrl, proseImages, tableColumns, saveFiles, downloads]
|
|
1419
1567
|
);
|
|
1420
1568
|
return /* @__PURE__ */ jsx(ResultEnvContext, { value: env, children });
|
|
1421
1569
|
}
|
|
1570
|
+
function DownloadDisplayOverride({
|
|
1571
|
+
downloads,
|
|
1572
|
+
children
|
|
1573
|
+
}) {
|
|
1574
|
+
const env = use(ResultEnvContext);
|
|
1575
|
+
const result = downloads?.result;
|
|
1576
|
+
const files = downloads?.files;
|
|
1577
|
+
const merged = useMemo(
|
|
1578
|
+
() => result === void 0 && files === void 0 ? env : {
|
|
1579
|
+
...env,
|
|
1580
|
+
downloads: {
|
|
1581
|
+
result: result ?? env.downloads?.result,
|
|
1582
|
+
files: files ?? env.downloads?.files
|
|
1583
|
+
}
|
|
1584
|
+
},
|
|
1585
|
+
[env, result, files]
|
|
1586
|
+
);
|
|
1587
|
+
return /* @__PURE__ */ jsx(ResultEnvContext, { value: merged, children });
|
|
1588
|
+
}
|
|
1422
1589
|
function useResolvedUrl(url) {
|
|
1423
1590
|
return use(ResultEnvContext).resolveUrl?.(url) ?? url;
|
|
1424
1591
|
}
|
|
@@ -1428,6 +1595,32 @@ function useResolveUrl() {
|
|
|
1428
1595
|
function useResolveShareUrl() {
|
|
1429
1596
|
return use(ResultEnvContext).resolveShareUrl;
|
|
1430
1597
|
}
|
|
1598
|
+
function useSaveFiles() {
|
|
1599
|
+
const save = use(ResultEnvContext).saveFiles ?? saveInBrowser;
|
|
1600
|
+
return useCallback((files) => deliver(save, files), [save]);
|
|
1601
|
+
}
|
|
1602
|
+
async function deliver(save, files) {
|
|
1603
|
+
const handed = files.map(
|
|
1604
|
+
(file) => file.url === void 0 ? file : { ...file, url: absoluteUrl(file.url) }
|
|
1605
|
+
);
|
|
1606
|
+
try {
|
|
1607
|
+
const result = await save(handed);
|
|
1608
|
+
if (Array.isArray(result?.failed)) return { failed: result.failed };
|
|
1609
|
+
return {
|
|
1610
|
+
failed: handed.map((file) => ({ file, reason: "The save function reported no result" }))
|
|
1611
|
+
};
|
|
1612
|
+
} catch (error) {
|
|
1613
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
1614
|
+
return { failed: handed.map((file) => ({ file, reason })) };
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
function useResultDownloadShown() {
|
|
1618
|
+
return use(ResultEnvContext).downloads?.result ?? true;
|
|
1619
|
+
}
|
|
1620
|
+
function useFileDownloadShown(kind) {
|
|
1621
|
+
const files = use(ResultEnvContext).downloads?.files ?? true;
|
|
1622
|
+
return typeof files === "boolean" ? files : files.includes(kind);
|
|
1623
|
+
}
|
|
1431
1624
|
function useProseImages() {
|
|
1432
1625
|
return use(ResultEnvContext).proseImages ?? "link";
|
|
1433
1626
|
}
|
|
@@ -1437,6 +1630,27 @@ function useTableColumns() {
|
|
|
1437
1630
|
if (budget === void 0 || Number.isNaN(budget)) return DEFAULT_TABLE_COLUMNS;
|
|
1438
1631
|
return Math.max(1, Math.floor(budget));
|
|
1439
1632
|
}
|
|
1633
|
+
var ResultLocationContext = createContext(void 0);
|
|
1634
|
+
function useResultLocation() {
|
|
1635
|
+
return use(ResultLocationContext);
|
|
1636
|
+
}
|
|
1637
|
+
function ResultRoot({
|
|
1638
|
+
baseName,
|
|
1639
|
+
path,
|
|
1640
|
+
children
|
|
1641
|
+
}) {
|
|
1642
|
+
const key = path.join("\0");
|
|
1643
|
+
const location = useMemo(() => ({ baseName, path: key.split("\0") }), [baseName, key]);
|
|
1644
|
+
return /* @__PURE__ */ jsx(ResultLocationContext, { value: location, children });
|
|
1645
|
+
}
|
|
1646
|
+
function ResultAt({ segment, children }) {
|
|
1647
|
+
const parent = use(ResultLocationContext);
|
|
1648
|
+
const location = useMemo(
|
|
1649
|
+
() => parent ? { baseName: parent.baseName, path: [...parent.path, segment] } : void 0,
|
|
1650
|
+
[parent, segment]
|
|
1651
|
+
);
|
|
1652
|
+
return /* @__PURE__ */ jsx(ResultLocationContext, { value: location, children });
|
|
1653
|
+
}
|
|
1440
1654
|
var TooltipProvider = TooltipPrimitive.Provider;
|
|
1441
1655
|
var TooltipRoot = TooltipPrimitive.Root;
|
|
1442
1656
|
var TooltipTrigger = TooltipPrimitive.Trigger;
|
|
@@ -1959,15 +2173,12 @@ function CopyUrlButton({ url, storageUrl }) {
|
|
|
1959
2173
|
};
|
|
1960
2174
|
return /* @__PURE__ */ jsx(CopyButton, { provide, label: s.copyUrl, title: url });
|
|
1961
2175
|
}
|
|
1962
|
-
function absoluteUrl(url) {
|
|
1963
|
-
if (!/^\/(?!\/)/.test(url) || typeof window === "undefined") return url;
|
|
1964
|
-
return `${window.location.origin}${url}`;
|
|
1965
|
-
}
|
|
1966
2176
|
function FileRef({
|
|
1967
2177
|
url,
|
|
1968
2178
|
storageUrl,
|
|
1969
2179
|
mimeType,
|
|
1970
|
-
filename
|
|
2180
|
+
filename,
|
|
2181
|
+
file
|
|
1971
2182
|
}) {
|
|
1972
2183
|
const s = useFieldStrings();
|
|
1973
2184
|
const encoded = encodedFileSummary(url, s);
|
|
@@ -1994,7 +2205,80 @@ function FileRef({
|
|
|
1994
2205
|
children: label
|
|
1995
2206
|
}
|
|
1996
2207
|
),
|
|
1997
|
-
/* @__PURE__ */ jsx(
|
|
2208
|
+
/* @__PURE__ */ jsx(FileActions, { copy: { url, ...storageUrl ? { storageUrl } : {} }, file })
|
|
2209
|
+
] });
|
|
2210
|
+
}
|
|
2211
|
+
function FileActions({
|
|
2212
|
+
copy,
|
|
2213
|
+
file
|
|
2214
|
+
}) {
|
|
2215
|
+
return /* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-0.5", children: [
|
|
2216
|
+
copy && /* @__PURE__ */ jsx(
|
|
2217
|
+
CopyUrlButton,
|
|
2218
|
+
{
|
|
2219
|
+
url: copy.url,
|
|
2220
|
+
...copy.storageUrl ? { storageUrl: copy.storageUrl } : {}
|
|
2221
|
+
}
|
|
2222
|
+
),
|
|
2223
|
+
file && /* @__PURE__ */ jsx(FileDownloadButton, { file })
|
|
2224
|
+
] });
|
|
2225
|
+
}
|
|
2226
|
+
function useStuffFile(kind, value) {
|
|
2227
|
+
const location = useResultLocation();
|
|
2228
|
+
return location ? readStuffFile(kind, value, location.path.join(".")) : void 0;
|
|
2229
|
+
}
|
|
2230
|
+
function FileDownloadButton({ file }) {
|
|
2231
|
+
const s = useFieldStrings();
|
|
2232
|
+
const location = useResultLocation();
|
|
2233
|
+
const resolve = useResolveUrl();
|
|
2234
|
+
const save = useSaveFiles();
|
|
2235
|
+
const shown = useFileDownloadShown(file.kind);
|
|
2236
|
+
const statusId = useId();
|
|
2237
|
+
const [attempt, setAttempt] = useState(null);
|
|
2238
|
+
useEffect(() => {
|
|
2239
|
+
if (attempt?.state !== "saved") return;
|
|
2240
|
+
const timer = window.setTimeout(
|
|
2241
|
+
() => setAttempt((current) => current === attempt ? null : current),
|
|
2242
|
+
1500
|
|
2243
|
+
);
|
|
2244
|
+
return () => window.clearTimeout(timer);
|
|
2245
|
+
}, [attempt]);
|
|
2246
|
+
if (!shown) return null;
|
|
2247
|
+
const planned = planFileSave(file, {
|
|
2248
|
+
baseName: location?.baseName ?? file.path,
|
|
2249
|
+
...resolve ? { resolveUrl: resolve } : {}
|
|
2250
|
+
});
|
|
2251
|
+
if (!planned) return null;
|
|
2252
|
+
const identity = `${planned.name}
|
|
2253
|
+
${planned.url ?? planned.text}`;
|
|
2254
|
+
const state = attempt?.identity === identity ? attempt.state : "idle";
|
|
2255
|
+
const failed = state === "failed";
|
|
2256
|
+
const label = s.downloadFile(planned.name);
|
|
2257
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2258
|
+
/* @__PURE__ */ jsx(
|
|
2259
|
+
"button",
|
|
2260
|
+
{
|
|
2261
|
+
type: "button",
|
|
2262
|
+
onClick: () => {
|
|
2263
|
+
const started = { identity, state: "saving" };
|
|
2264
|
+
setAttempt(started);
|
|
2265
|
+
void save([planned]).then((result) => {
|
|
2266
|
+
const settled = result.failed.length > 0 ? "failed" : "saved";
|
|
2267
|
+
setAttempt((current) => current === started ? { identity, state: settled } : current);
|
|
2268
|
+
});
|
|
2269
|
+
},
|
|
2270
|
+
disabled: state === "saving",
|
|
2271
|
+
"aria-label": label,
|
|
2272
|
+
title: failed ? s.downloadFileFailed : label,
|
|
2273
|
+
...failed ? { "aria-describedby": statusId } : {},
|
|
2274
|
+
className: cn(
|
|
2275
|
+
"shrink-0 rounded p-0.5 hover:bg-card focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-1 disabled:opacity-60",
|
|
2276
|
+
failed ? "text-destructive" : "text-muted-foreground hover:text-foreground"
|
|
2277
|
+
),
|
|
2278
|
+
children: state === "saving" ? /* @__PURE__ */ jsx(Loader2, { "aria-hidden": true, className: "size-3.5 animate-spin" }) : state === "saved" ? /* @__PURE__ */ jsx(Check, { "aria-hidden": true, className: "size-3.5" }) : failed ? /* @__PURE__ */ jsx(TriangleAlert, { "aria-hidden": true, className: "size-3.5" }) : /* @__PURE__ */ jsx(Download, { "aria-hidden": true, className: "size-3.5" })
|
|
2279
|
+
}
|
|
2280
|
+
),
|
|
2281
|
+
/* @__PURE__ */ jsx("span", { id: statusId, role: "status", className: "sr-only", children: failed ? s.downloadFileFailed : "" })
|
|
1998
2282
|
] });
|
|
1999
2283
|
}
|
|
2000
2284
|
function paintable(content, resolve) {
|
|
@@ -2036,6 +2320,7 @@ function DocumentValue({ value }) {
|
|
|
2036
2320
|
const s = useFieldStrings();
|
|
2037
2321
|
const resolve = useResolveUrl();
|
|
2038
2322
|
const [open, setOpen] = useState(false);
|
|
2323
|
+
const file = useStuffFile("document", value);
|
|
2039
2324
|
const content = readDocumentContent(value);
|
|
2040
2325
|
if (!content) return /* @__PURE__ */ jsx(Absent, {});
|
|
2041
2326
|
const name = content.title ?? content.filename ?? fileLabel(content.url, content.filename, s);
|
|
@@ -2051,7 +2336,8 @@ function DocumentValue({ value }) {
|
|
|
2051
2336
|
url: paintableUrl(content, resolve) ?? content.url,
|
|
2052
2337
|
storageUrl: content.url,
|
|
2053
2338
|
mimeType: content.mimeType,
|
|
2054
|
-
...content.filename ? { filename: content.filename } : {}
|
|
2339
|
+
...content.filename ? { filename: content.filename } : {},
|
|
2340
|
+
file
|
|
2055
2341
|
}
|
|
2056
2342
|
),
|
|
2057
2343
|
content.snippet && /* @__PURE__ */ jsx("p", { className: "line-clamp-2 text-[12px] leading-relaxed text-muted-foreground", children: content.snippet })
|
|
@@ -2081,6 +2367,7 @@ function ImageValue({
|
|
|
2081
2367
|
}) {
|
|
2082
2368
|
const s = useFieldStrings();
|
|
2083
2369
|
const resolve = useResolveUrl();
|
|
2370
|
+
const file = useStuffFile("image", value);
|
|
2084
2371
|
const content = readImageContent(value);
|
|
2085
2372
|
if (!content) return /* @__PURE__ */ jsx(Absent, {});
|
|
2086
2373
|
const src = paintableUrl(content, resolve);
|
|
@@ -2131,7 +2418,8 @@ function ImageValue({
|
|
|
2131
2418
|
{
|
|
2132
2419
|
url: content.url,
|
|
2133
2420
|
mimeType: content.mimeType,
|
|
2134
|
-
...content.filename ? { filename: content.filename } : {}
|
|
2421
|
+
...content.filename ? { filename: content.filename } : {},
|
|
2422
|
+
file
|
|
2135
2423
|
}
|
|
2136
2424
|
) })
|
|
2137
2425
|
] })
|
|
@@ -2156,7 +2444,8 @@ function ImageValue({
|
|
|
2156
2444
|
{
|
|
2157
2445
|
url: content.url,
|
|
2158
2446
|
mimeType: content.mimeType,
|
|
2159
|
-
...content.filename ? { filename: content.filename } : {}
|
|
2447
|
+
...content.filename ? { filename: content.filename } : {},
|
|
2448
|
+
file
|
|
2160
2449
|
}
|
|
2161
2450
|
),
|
|
2162
2451
|
src && !compact && /* @__PURE__ */ jsx("div", { className: inGallery ? "px-2.5" : void 0, children: /* @__PURE__ */ jsx(
|
|
@@ -2165,7 +2454,8 @@ function ImageValue({
|
|
|
2165
2454
|
url: src,
|
|
2166
2455
|
storageUrl: content.url,
|
|
2167
2456
|
mimeType: content.mimeType,
|
|
2168
|
-
...content.filename ? { filename: content.filename } : {}
|
|
2457
|
+
...content.filename ? { filename: content.filename } : {},
|
|
2458
|
+
file
|
|
2169
2459
|
}
|
|
2170
2460
|
) }),
|
|
2171
2461
|
content.caption && /* @__PURE__ */ jsx(
|
|
@@ -2368,7 +2658,7 @@ function ObjectTable({
|
|
|
2368
2658
|
] }) }),
|
|
2369
2659
|
/* @__PURE__ */ jsx("tbody", { children: items.map((item, index) => {
|
|
2370
2660
|
const isOpen = canExpand && expanded.has(index);
|
|
2371
|
-
return /* @__PURE__ */ jsx(
|
|
2661
|
+
return /* @__PURE__ */ jsx(ResultAt, { segment: String(index), children: /* @__PURE__ */ jsxs("tr", { className: "border-b border-border/60 last:border-b-0", children: [
|
|
2372
2662
|
canExpand && /* @__PURE__ */ jsx("td", { className: "px-1 pt-px align-top", children: /* @__PURE__ */ jsx(
|
|
2373
2663
|
"button",
|
|
2374
2664
|
{
|
|
@@ -2405,7 +2695,7 @@ function ObjectTable({
|
|
|
2405
2695
|
column === name ? "max-w-none min-w-[16ch] wrap-anywhere [&>span]:whitespace-normal" : column.kind === "list" && isInlineColumn(column) ? "min-w-[16ch]" : "truncate"
|
|
2406
2696
|
),
|
|
2407
2697
|
...typeof cell === "string" || typeof cell === "number" ? { title: cellTitle(column, cell, presentation) } : {},
|
|
2408
|
-
children: /* @__PURE__ */ jsx(LeafValue, { field: column, value: cell, compact: true })
|
|
2698
|
+
children: /* @__PURE__ */ jsx(ResultAt, { segment: column.name, children: /* @__PURE__ */ jsx(LeafValue, { field: column, value: cell, compact: true }) })
|
|
2409
2699
|
}
|
|
2410
2700
|
) }, column.name);
|
|
2411
2701
|
})
|
|
@@ -2466,10 +2756,10 @@ function ImageGallery({ items }) {
|
|
|
2466
2756
|
return content ? !!(viewableUrl(content.publicUrl) ?? viewableUrl(content.url)) : false;
|
|
2467
2757
|
});
|
|
2468
2758
|
if (!anyViewable) return /* @__PURE__ */ jsx(FileRows, { items, kind: "image" });
|
|
2469
|
-
return /* @__PURE__ */ jsx("div", { className: "grid grid-cols-[repeat(auto-fill,minmax(140px,1fr))] gap-2", children: items.map((item, index) => /* @__PURE__ */ jsx("div", { className: "overflow-hidden rounded-lg border border-border bg-card/40", children: /* @__PURE__ */ jsx(ImageValue, { value: item, inGallery: true }) }, index)) });
|
|
2759
|
+
return /* @__PURE__ */ jsx("div", { className: "grid grid-cols-[repeat(auto-fill,minmax(140px,1fr))] gap-2", children: items.map((item, index) => /* @__PURE__ */ jsx("div", { className: "overflow-hidden rounded-lg border border-border bg-card/40", children: /* @__PURE__ */ jsx(ResultAt, { segment: String(index), children: /* @__PURE__ */ jsx(ImageValue, { value: item, inGallery: true }) }) }, index)) });
|
|
2470
2760
|
}
|
|
2471
2761
|
function FileRows({ items, kind }) {
|
|
2472
|
-
return /* @__PURE__ */ jsx("div", { className: "divide-y divide-border overflow-hidden rounded-lg border border-border", children: items.map((item, index) => /* @__PURE__ */ jsx("div", { className: "bg-card/40 px-3.5 py-2", children: kind === "document" ? /* @__PURE__ */ jsx(DocumentValue, { value: item }) : /* @__PURE__ */ jsx(ImageValue, { value: item, inRow: true }) }, index)) });
|
|
2762
|
+
return /* @__PURE__ */ jsx("div", { className: "divide-y divide-border overflow-hidden rounded-lg border border-border", children: items.map((item, index) => /* @__PURE__ */ jsx("div", { className: "bg-card/40 px-3.5 py-2", children: /* @__PURE__ */ jsx(ResultAt, { segment: String(index), children: kind === "document" ? /* @__PURE__ */ jsx(DocumentValue, { value: item }) : /* @__PURE__ */ jsx(ImageValue, { value: item, inRow: true }) }) }, index)) });
|
|
2473
2763
|
}
|
|
2474
2764
|
function recordColumns(item) {
|
|
2475
2765
|
if (item.kind !== "object" || item.fields.length === 0) return void 0;
|
|
@@ -2496,9 +2786,16 @@ function chooseColumns(fields, budget) {
|
|
|
2496
2786
|
...name ? { name } : {}
|
|
2497
2787
|
};
|
|
2498
2788
|
}
|
|
2499
|
-
function ResultField(
|
|
2789
|
+
function ResultField(props) {
|
|
2790
|
+
const location = useResultLocation();
|
|
2791
|
+
if (location) return /* @__PURE__ */ jsx(ResultNode, { ...props });
|
|
2792
|
+
return /* @__PURE__ */ jsx(ResultRoot, { baseName: props.field.name, path: [props.field.name], children: /* @__PURE__ */ jsx(ResultNode, { ...props }) });
|
|
2793
|
+
}
|
|
2794
|
+
function ResultNode({ field, value, depth = 0, hideLabel = false }) {
|
|
2500
2795
|
const s = useFieldStrings();
|
|
2501
2796
|
const unwrapped = unwrap(field, value);
|
|
2797
|
+
const pageDownloadShown = useFileDownloadShown("markup");
|
|
2798
|
+
const location = useResultLocation();
|
|
2502
2799
|
const header = hideLabel ? null : /* @__PURE__ */ jsx(Label2, { field, describe: true });
|
|
2503
2800
|
const copyText = (field.kind === "text" || field.kind === "prose") && typeof unwrapped === "string" && unwrapped !== "" ? unwrapped : void 0;
|
|
2504
2801
|
if (isNativeDateNode(field)) {
|
|
@@ -2509,8 +2806,12 @@ function ResultField({ field, value, depth = 0, hideLabel = false }) {
|
|
|
2509
2806
|
}
|
|
2510
2807
|
if (isNativeHtmlNode(field)) {
|
|
2511
2808
|
const content = readHtmlContent(unwrapped);
|
|
2809
|
+
const page = pageDownloadShown && location ? readStuffFile("markup", unwrapped, location.path.join(".")) : void 0;
|
|
2512
2810
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
2513
|
-
|
|
2811
|
+
page ? /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
|
|
2812
|
+
/* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: header }),
|
|
2813
|
+
/* @__PURE__ */ jsx(FileActions, { file: page })
|
|
2814
|
+
] }) : header,
|
|
2514
2815
|
content ? /* @__PURE__ */ jsx(HtmlPreview, { content }) : /* @__PURE__ */ jsx(Absent, {})
|
|
2515
2816
|
] });
|
|
2516
2817
|
}
|
|
@@ -2558,7 +2859,7 @@ function ResultField({ field, value, depth = 0, hideLabel = false }) {
|
|
|
2558
2859
|
isInlineColumn(child) ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
2559
2860
|
/* @__PURE__ */ jsx("div", { className: "min-w-0", children: /* @__PURE__ */ jsx(Label2, { field: child, describe: true }) }),
|
|
2560
2861
|
/* @__PURE__ */ jsx("div", { className: "flex min-w-0 justify-end **:data-chips:justify-end", children: /* @__PURE__ */ jsx("div", { className: "min-w-0 text-left wrap-break-word", children: /* @__PURE__ */ jsx(LeafValue, { field: child, value: memberValue(child) }) }) })
|
|
2561
|
-
] }, child.name) : /* @__PURE__ */ jsx("div", { className: "col-span-2 min-w-0", children: /* @__PURE__ */ jsx(ResultField, { field: child, value: memberValue(child), depth: depth + 1 }) }, child.name)
|
|
2862
|
+
] }, child.name) : /* @__PURE__ */ jsx("div", { className: "col-span-2 min-w-0", children: /* @__PURE__ */ jsx(ResultAt, { segment: child.name, children: /* @__PURE__ */ jsx(ResultField, { field: child, value: memberValue(child), depth: depth + 1 }) }) }, child.name)
|
|
2562
2863
|
)
|
|
2563
2864
|
)
|
|
2564
2865
|
}
|
|
@@ -2580,7 +2881,7 @@ function ResultField({ field, value, depth = 0, hideLabel = false }) {
|
|
|
2580
2881
|
// date's `date` and `time`, print a page's markup source down a
|
|
2581
2882
|
// column named by it, and show a composite as JSON. The arm keyed by
|
|
2582
2883
|
// concept is the one that knows what each entry is.
|
|
2583
|
-
/* @__PURE__ */ jsx("div", { className: "divide-y divide-border/60 overflow-hidden rounded-lg border border-border", children: items.map((item, index) => /* @__PURE__ */ jsx("div", { className: "bg-card/40 px-3 py-1.5", children: /* @__PURE__ */ jsx(ResultField, { field: field.item, value: item, depth: depth + 1, hideLabel: true }) }, index)) })
|
|
2884
|
+
/* @__PURE__ */ jsx("div", { className: "divide-y divide-border/60 overflow-hidden rounded-lg border border-border", children: items.map((item, index) => /* @__PURE__ */ jsx("div", { className: "bg-card/40 px-3 py-1.5", children: /* @__PURE__ */ jsx(ResultAt, { segment: String(index), children: /* @__PURE__ */ jsx(ResultField, { field: field.item, value: item, depth: depth + 1, hideLabel: true }) }) }, index)) })
|
|
2584
2885
|
) : columns ? (
|
|
2585
2886
|
// Same shape every row: the labels are column headers, not per-row
|
|
2586
2887
|
// labels. See `ObjectTable`.
|
|
@@ -2615,7 +2916,7 @@ function ResultField({ field, value, depth = 0, hideLabel = false }) {
|
|
|
2615
2916
|
className: "space-y-2 rounded-lg border border-border bg-card/40 px-3.5 py-3",
|
|
2616
2917
|
children: [
|
|
2617
2918
|
/* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] text-muted-foreground", children: index + 1 }),
|
|
2618
|
-
/* @__PURE__ */ jsx(ResultField, { field: field.item, value: item, depth: depth + 1, hideLabel: true })
|
|
2919
|
+
/* @__PURE__ */ jsx(ResultAt, { segment: String(index), children: /* @__PURE__ */ jsx(ResultField, { field: field.item, value: item, depth: depth + 1, hideLabel: true }) })
|
|
2619
2920
|
]
|
|
2620
2921
|
},
|
|
2621
2922
|
index
|
|
@@ -2642,67 +2943,6 @@ function ResultField({ field, value, depth = 0, hideLabel = false }) {
|
|
|
2642
2943
|
}
|
|
2643
2944
|
return null;
|
|
2644
2945
|
}
|
|
2645
|
-
|
|
2646
|
-
// src/react/download-stuff.ts
|
|
2647
|
-
function saveBlob(blob, filename) {
|
|
2648
|
-
const href = URL.createObjectURL(blob);
|
|
2649
|
-
const anchor = document.createElement("a");
|
|
2650
|
-
anchor.href = href;
|
|
2651
|
-
anchor.download = filename;
|
|
2652
|
-
document.body.appendChild(anchor);
|
|
2653
|
-
anchor.click();
|
|
2654
|
-
anchor.remove();
|
|
2655
|
-
setTimeout(() => URL.revokeObjectURL(href), 0);
|
|
2656
|
-
}
|
|
2657
|
-
function mimeForExtension(extension) {
|
|
2658
|
-
return extension === "html" ? "text/html;charset=utf-8" : "text/plain;charset=utf-8";
|
|
2659
|
-
}
|
|
2660
|
-
function extensionOf(url, filename) {
|
|
2661
|
-
const named = filename?.match(/\.([A-Za-z0-9]{1,8})$/)?.[1];
|
|
2662
|
-
if (named) return named.toLowerCase();
|
|
2663
|
-
const path = url.split(/[?#]/)[0] ?? url;
|
|
2664
|
-
const match = path.match(/\.([A-Za-z0-9]{1,8})$/)?.[1];
|
|
2665
|
-
return match ? match.toLowerCase() : "";
|
|
2666
|
-
}
|
|
2667
|
-
function fileNameFor(file, base) {
|
|
2668
|
-
if (file.filename) return file.filename;
|
|
2669
|
-
const extension = file.extension ?? extensionOf(file.url ?? "", file.filename);
|
|
2670
|
-
const stem = `${base}-${file.path.replace(/\./g, "-")}`;
|
|
2671
|
-
return extension ? `${stem}.${extension}` : stem;
|
|
2672
|
-
}
|
|
2673
|
-
async function downloadStuff({
|
|
2674
|
-
field,
|
|
2675
|
-
value,
|
|
2676
|
-
baseName,
|
|
2677
|
-
resolveUrl
|
|
2678
|
-
}) {
|
|
2679
|
-
const files = collectStuffFiles(field, value);
|
|
2680
|
-
const isBareFile = files.length === 1 && files[0]?.path === field.name;
|
|
2681
|
-
let deliveredAFile = false;
|
|
2682
|
-
for (const file of files) {
|
|
2683
|
-
const name = fileNameFor(file, baseName);
|
|
2684
|
-
if (file.text !== void 0) {
|
|
2685
|
-
saveBlob(new Blob([file.text], { type: mimeForExtension(file.extension) }), name);
|
|
2686
|
-
deliveredAFile = true;
|
|
2687
|
-
continue;
|
|
2688
|
-
}
|
|
2689
|
-
const href = viewableUrl(resolveUrl?.(file.url ?? "")) ?? viewableUrl(file.publicUrl) ?? viewableUrl(file.url);
|
|
2690
|
-
if (!href) continue;
|
|
2691
|
-
try {
|
|
2692
|
-
const response = await fetch(href);
|
|
2693
|
-
if (!response.ok) throw new Error(String(response.status));
|
|
2694
|
-
saveBlob(await response.blob(), name);
|
|
2695
|
-
} catch {
|
|
2696
|
-
window.open(href, "_blank", "noopener");
|
|
2697
|
-
}
|
|
2698
|
-
deliveredAFile = true;
|
|
2699
|
-
}
|
|
2700
|
-
if (isBareFile && deliveredAFile) return;
|
|
2701
|
-
saveBlob(
|
|
2702
|
-
new Blob([JSON.stringify(value, null, 2)], { type: "application/json;charset=utf-8" }),
|
|
2703
|
-
`${baseName}.json`
|
|
2704
|
-
);
|
|
2705
|
-
}
|
|
2706
2946
|
var JSON_TOKEN_RE = /("(?:\\.|[^"\\])*"\s*:)|("(?:\\.|[^"\\])*")|(\b(?:true|false|null)\b|-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)/g;
|
|
2707
2947
|
function highlight(json) {
|
|
2708
2948
|
const out = [];
|
|
@@ -2762,33 +3002,73 @@ function JsonView({ value }) {
|
|
|
2762
3002
|
/* @__PURE__ */ jsx("pre", { className: "overflow-x-auto rounded-lg border border-border bg-card/40 px-3.5 py-3 pr-12 font-mono text-[12px] leading-relaxed", children: highlight(text) })
|
|
2763
3003
|
] });
|
|
2764
3004
|
}
|
|
2765
|
-
function
|
|
3005
|
+
function isShownResult(attempt, field, baseName, value) {
|
|
3006
|
+
if (attempt.field === field && attempt.baseName === baseName && attempt.value === value) {
|
|
3007
|
+
return true;
|
|
3008
|
+
}
|
|
3009
|
+
return attempt.key !== void 0 && attempt.key === resultKey(field, baseName, value);
|
|
3010
|
+
}
|
|
3011
|
+
function resultKey(field, baseName, value) {
|
|
3012
|
+
try {
|
|
3013
|
+
return JSON.stringify(
|
|
3014
|
+
[baseName, field, value],
|
|
3015
|
+
(_key, member) => typeof member === "bigint" ? `${member}n` : member
|
|
3016
|
+
);
|
|
3017
|
+
} catch {
|
|
3018
|
+
return void 0;
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
function StuffViewer({ downloads, ...props }) {
|
|
3022
|
+
return /* @__PURE__ */ jsx(DownloadDisplayOverride, { downloads, children: /* @__PURE__ */ jsx(StuffPanel, { ...props }) });
|
|
3023
|
+
}
|
|
3024
|
+
function StuffPanel({
|
|
2766
3025
|
field,
|
|
2767
3026
|
value,
|
|
2768
3027
|
name,
|
|
2769
3028
|
defaultView = "rendered",
|
|
2770
3029
|
downloadBaseName,
|
|
2771
|
-
hideDownload = false,
|
|
2772
3030
|
className
|
|
2773
3031
|
}) {
|
|
2774
3032
|
const s = useFieldStrings();
|
|
2775
3033
|
const named = name ? { ...field, name } : field;
|
|
3034
|
+
const baseName = downloadBaseName ?? named.name ?? "result";
|
|
2776
3035
|
const [view, setView] = useState(defaultView);
|
|
2777
|
-
const [saving, setSaving] = useState(
|
|
3036
|
+
const [saving, setSaving] = useState(null);
|
|
3037
|
+
const [missed, setMissed] = useState(null);
|
|
3038
|
+
const latest = useRef(null);
|
|
3039
|
+
const savingShown = saving !== null && isShownResult(saving, field, baseName, value);
|
|
3040
|
+
const missedShown = missed && isShownResult(missed.attempt, field, baseName, value) ? missed.names : [];
|
|
2778
3041
|
const resolveUrl = useResolveUrl();
|
|
3042
|
+
const save = useSaveFiles();
|
|
3043
|
+
const downloadShown = useResultDownloadShown();
|
|
2779
3044
|
const handleDownload = useCallback(async () => {
|
|
2780
|
-
|
|
3045
|
+
const attempt = {
|
|
3046
|
+
field,
|
|
3047
|
+
baseName,
|
|
3048
|
+
value,
|
|
3049
|
+
key: resultKey(field, baseName, value)
|
|
3050
|
+
};
|
|
3051
|
+
latest.current = attempt;
|
|
3052
|
+
setSaving(attempt);
|
|
3053
|
+
setMissed(null);
|
|
2781
3054
|
try {
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
3055
|
+
const plan = planStuffSave(field, value, {
|
|
3056
|
+
baseName,
|
|
3057
|
+
...resolveUrl ? { resolveUrl } : {}
|
|
3058
|
+
});
|
|
3059
|
+
const { failed } = await save(plan.files);
|
|
3060
|
+
if (latest.current !== attempt) return;
|
|
3061
|
+
setMissed({
|
|
3062
|
+
attempt,
|
|
3063
|
+
names: [
|
|
3064
|
+
...plan.unavailable.map((file) => file.name),
|
|
3065
|
+
...failed.map((failure) => failure.file.name)
|
|
3066
|
+
]
|
|
2787
3067
|
});
|
|
2788
3068
|
} finally {
|
|
2789
|
-
setSaving(
|
|
3069
|
+
setSaving((current) => current === attempt ? null : current);
|
|
2790
3070
|
}
|
|
2791
|
-
}, [field,
|
|
3071
|
+
}, [field, value, baseName, resolveUrl, save]);
|
|
2792
3072
|
const views = [
|
|
2793
3073
|
{ id: "rendered", label: s.viewRendered },
|
|
2794
3074
|
{ id: "json", label: s.viewJson }
|
|
@@ -2797,17 +3077,17 @@ function StuffViewer({
|
|
|
2797
3077
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-start justify-between gap-x-4 gap-y-1", children: [
|
|
2798
3078
|
/* @__PURE__ */ jsx("div", { className: "min-w-0 space-y-1", children: /* @__PURE__ */ jsx(ResultHeader, { field: named }) }),
|
|
2799
3079
|
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [
|
|
2800
|
-
|
|
3080
|
+
downloadShown && /* @__PURE__ */ jsxs(
|
|
2801
3081
|
"button",
|
|
2802
3082
|
{
|
|
2803
3083
|
type: "button",
|
|
2804
3084
|
onClick: () => void handleDownload(),
|
|
2805
|
-
disabled:
|
|
3085
|
+
disabled: savingShown,
|
|
2806
3086
|
"aria-label": s.download,
|
|
2807
3087
|
className: "inline-flex items-center gap-1.5 rounded-md border border-border px-2 py-1 text-[12px] text-muted-foreground hover:text-foreground focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-1 disabled:opacity-60",
|
|
2808
3088
|
children: [
|
|
2809
|
-
|
|
2810
|
-
|
|
3089
|
+
savingShown ? /* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ jsx(Download, { className: "h-3.5 w-3.5" }),
|
|
3090
|
+
savingShown ? s.downloading : s.download
|
|
2811
3091
|
]
|
|
2812
3092
|
}
|
|
2813
3093
|
),
|
|
@@ -2835,10 +3115,18 @@ function StuffViewer({
|
|
|
2835
3115
|
)
|
|
2836
3116
|
] })
|
|
2837
3117
|
] }),
|
|
2838
|
-
|
|
3118
|
+
downloadShown && /* @__PURE__ */ jsx(
|
|
3119
|
+
"p",
|
|
3120
|
+
{
|
|
3121
|
+
role: "status",
|
|
3122
|
+
className: cn("text-[12px] text-destructive", missedShown.length === 0 && "sr-only"),
|
|
3123
|
+
children: missedShown.length > 0 ? s.downloadIncomplete(missedShown) : ""
|
|
3124
|
+
}
|
|
3125
|
+
),
|
|
3126
|
+
view === "json" ? /* @__PURE__ */ jsx(JsonView, { value }) : /* @__PURE__ */ jsx(ResultRoot, { baseName, path: [field.name], children: /* @__PURE__ */ jsx(ResultField, { field, value, hideLabel: true }) })
|
|
2839
3127
|
] });
|
|
2840
3128
|
}
|
|
2841
3129
|
|
|
2842
|
-
export { BooleanField, ConceptPill, DEFAULT_FIELD_STRINGS, DateField, DocumentField, EnumField, FieldDomIdProvider, FieldPresentationProvider, FieldRenderer, FieldShell, FieldStringsProvider, HtmlPreview, ImageField, JsonView, ListField, Markdown, NumberField, ObjectField, OptionalToggle, ProseField, ResultEnvProvider, ResultField, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, StuffViewer, Switch, TextField, ToggleGroup, ToggleGroupItem, UnknownField, cn,
|
|
2843
|
-
//# sourceMappingURL=chunk-
|
|
2844
|
-
//# sourceMappingURL=chunk-
|
|
3130
|
+
export { BooleanField, ConceptPill, DEFAULT_FIELD_STRINGS, DateField, DocumentField, EnumField, FieldDomIdProvider, FieldPresentationProvider, FieldRenderer, FieldShell, FieldStringsProvider, HtmlPreview, ImageField, JsonView, ListField, Markdown, NumberField, ObjectField, OptionalToggle, ProseField, ResultEnvProvider, ResultField, ResultRoot, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, StuffViewer, Switch, TextField, ToggleGroup, ToggleGroupItem, UnknownField, cn, fieldControlClass, humanizeFieldName, saveInBrowser, useFieldDomId, useFieldPresentation, useFieldStrings, useProseImages, useResolveShareUrl, useResolveUrl, useResolvedUrl };
|
|
3131
|
+
//# sourceMappingURL=chunk-4KX6YTLW.js.map
|
|
3132
|
+
//# sourceMappingURL=chunk-4KX6YTLW.js.map
|