@pipelex/mthds-form 0.13.0 → 0.14.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 +11 -0
- package/dist/{chunk-IR3OFSGD.js → chunk-2EGRONBV.js} +3 -3
- package/dist/{chunk-IR3OFSGD.js.map → chunk-2EGRONBV.js.map} +1 -1
- package/dist/{chunk-D2EQVWGN.js → chunk-7JRT6ZII.js} +1631 -171
- package/dist/chunk-7JRT6ZII.js.map +1 -0
- package/dist/{chunk-7KQXGG6J.js → chunk-ICUEHNEH.js} +19 -6
- package/dist/chunk-ICUEHNEH.js.map +1 -0
- package/dist/{chunk-DARLDVJM.js → chunk-JIGRWJTV.js} +2 -2
- package/dist/chunk-JIGRWJTV.js.map +1 -0
- package/dist/chunk-VH5CDQDR.js +41 -0
- package/dist/chunk-VH5CDQDR.js.map +1 -0
- package/dist/{chunk-G6SFMTL4.js → chunk-VT5E5W4E.js} +4 -3
- package/dist/chunk-VT5E5W4E.js.map +1 -0
- package/dist/{chunk-MJQ7CXUB.js → chunk-XO5OENNF.js} +25 -4
- package/dist/chunk-XO5OENNF.js.map +1 -0
- package/dist/core/gate.js +2 -2
- package/dist/core/index.d.ts +2 -1
- package/dist/core/index.js +6 -5
- package/dist/core/output-form.d.ts +5 -4
- package/dist/core/output-form.js +1 -1
- package/dist/core/readiness.d.ts +47 -2
- package/dist/core/readiness.js +1 -1
- package/dist/core/save-plan.d.ts +31 -1
- package/dist/core/save-plan.js +1 -1
- package/dist/core/seed.d.ts +56 -0
- package/dist/core/seed.js +5 -0
- package/dist/core/seed.js.map +1 -0
- package/dist/core/values.js +2 -2
- package/dist/generative/index.d.ts +2 -11
- package/dist/generative/index.js +11 -17
- package/dist/generative/index.js.map +1 -1
- package/dist/react/index.d.ts +699 -192
- package/dist/react/index.js +9 -3
- package/dist/styles.css +1 -1
- package/package.json +3 -1
- package/dist/chunk-7KQXGG6J.js.map +0 -1
- package/dist/chunk-D2EQVWGN.js.map +0 -1
- package/dist/chunk-DARLDVJM.js.map +0 -1
- package/dist/chunk-G6SFMTL4.js.map +0 -1
- package/dist/chunk-MJQ7CXUB.js.map +0 -1
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { seedInputs, closeOptionalStructures, openOptionalStructures } from './chunk-VH5CDQDR.js';
|
|
2
|
+
import { setValueAtPath, apiInputsFromRunValues } from './chunk-VT5E5W4E.js';
|
|
3
|
+
import { planFileSave, planStuffSave, splitFileName, typedFileStem } from './chunk-ICUEHNEH.js';
|
|
2
4
|
import { readStuffFile } from './chunk-RRTI7AIR.js';
|
|
3
5
|
import { viewableUrl, isNativeDateNode, isNativeHtmlNode, readHtmlContent, isNativeCompositeNode, readCompositeContent, isNativeValueNode, readDateContent, formatDateContent, readImageContent, readDataUrl, readDocumentContent } from './chunk-CM4KVG53.js';
|
|
6
|
+
import { buildRunFields } from './chunk-ZB4RYUKV.js';
|
|
4
7
|
import { conceptCategory } from './chunk-FABY3DEY.js';
|
|
5
8
|
import { acceptLabel, acceptMap, isAcceptedFile, formatOfFilename, DOCUMENT_FORMATS, IMAGE_FORMATS } from './chunk-XCO6GTEC.js';
|
|
6
|
-
import { shownAsOptional, isFilled } from './chunk-
|
|
9
|
+
import { shownAsOptional, isFoldedOptional, anyOptionalStructureOpen, isOptionalStructure, computeReadiness, optionalStructureOpen, mustBeFilled, fieldFilled, isFilled } from './chunk-XO5OENNF.js';
|
|
7
10
|
import { toStoredDateValue, toDateInputValue } from './chunk-GKV77RAS.js';
|
|
8
11
|
import { ownProp } from './chunk-V3OV2XQT.js';
|
|
9
12
|
import { clsx } from 'clsx';
|
|
10
13
|
import { twMerge } from 'tailwind-merge';
|
|
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
|
|
14
|
+
import { ChevronDown, ChevronUp, Check, List, Braces, ListChecks, ToggleLeft, Hash, Image as Image$1, FileText, CalendarDays, Type, Loader2, ImageOff, Upload, Link2, EyeOff, Eye, X, Minus, Plus, Trash2, TriangleAlert, FileDown, Copy, Download, ChevronRight } from 'lucide-react';
|
|
12
15
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
13
16
|
import * as React2 from 'react';
|
|
14
17
|
import { createContext, use, useId, useMemo, useContext, useState, useRef, useCallback, useEffect, Fragment as Fragment$1, useLayoutEffect } from 'react';
|
|
@@ -28,7 +31,7 @@ var CATEGORY_ICON = {
|
|
|
28
31
|
text: Type,
|
|
29
32
|
date: CalendarDays,
|
|
30
33
|
document: FileText,
|
|
31
|
-
image: Image,
|
|
34
|
+
image: Image$1,
|
|
32
35
|
number: Hash,
|
|
33
36
|
boolean: ToggleLeft,
|
|
34
37
|
choice: ListChecks,
|
|
@@ -200,7 +203,7 @@ var EN_FIELD_STRINGS = {
|
|
|
200
203
|
addItem: "Add item",
|
|
201
204
|
removeItemAria: (index) => `Remove item ${index}`,
|
|
202
205
|
itemsCount: (count) => count === 1 ? "1 item" : `${count} items`,
|
|
203
|
-
itemsCountOf: (count,
|
|
206
|
+
itemsCountOf: (count, total2) => `${count} of ${total2} ${total2 === 1 ? "item" : "items"}`,
|
|
204
207
|
fieldsCount: (count) => count === 1 ? "1 field" : `${count} fields`,
|
|
205
208
|
uploading: "Uploading\u2026",
|
|
206
209
|
dropToUpload: "Drop to upload",
|
|
@@ -227,6 +230,14 @@ var EN_FIELD_STRINGS = {
|
|
|
227
230
|
downloadIncomplete: (names) => `${names.length === 1 ? "One file" : `${names.length} files`} could not be saved: ${names.join(", ")}`,
|
|
228
231
|
downloadFile: (name) => `Download ${name}`,
|
|
229
232
|
downloadFileFailed: "The file could not be saved",
|
|
233
|
+
downloadPdf: "Download PDF",
|
|
234
|
+
downloadPdfBusy: "Preparing the PDF\u2026",
|
|
235
|
+
downloadPdfFailed: "The PDF could not be made",
|
|
236
|
+
pageLoading: "Loading the page\u2026",
|
|
237
|
+
downloadHtml: "Download HTML",
|
|
238
|
+
fileNameTitle: "File name",
|
|
239
|
+
fileNameCancel: "Cancel",
|
|
240
|
+
fileNameConfirm: "Download",
|
|
230
241
|
viewRendered: "Result",
|
|
231
242
|
viewJson: "JSON",
|
|
232
243
|
copyJson: "Copy the JSON",
|
|
@@ -237,7 +248,10 @@ var EN_FIELD_STRINGS = {
|
|
|
237
248
|
hideOptionalFields: "Hide optional fields",
|
|
238
249
|
hideOptionalInputs: "Hide optional inputs",
|
|
239
250
|
optionalFieldsCount: (count) => count === 1 ? "1 optional field" : `${count} optional fields`,
|
|
240
|
-
optionalInputsCount: (count) => count === 1 ? "1 optional input" : `${count} optional inputs
|
|
251
|
+
optionalInputsCount: (count) => count === 1 ? "1 optional input" : `${count} optional inputs`,
|
|
252
|
+
requiredField: "Required",
|
|
253
|
+
incompleteField: "Incomplete: fill in its required fields",
|
|
254
|
+
uploadFailed: "This file could not be uploaded. Try again."
|
|
241
255
|
};
|
|
242
256
|
|
|
243
257
|
// src/react/locales/fr.ts
|
|
@@ -275,7 +289,7 @@ var FR_FIELD_STRINGS = {
|
|
|
275
289
|
addItem: "Ajouter un \xE9l\xE9ment",
|
|
276
290
|
removeItemAria: (index) => `Supprimer l\u2019\xE9l\xE9ment ${index}`,
|
|
277
291
|
itemsCount: (count) => `${count} ${plural(count, "\xE9l\xE9ment", "\xE9l\xE9ments")}`,
|
|
278
|
-
itemsCountOf: (count,
|
|
292
|
+
itemsCountOf: (count, total2) => `${count} sur ${total2} ${plural(total2, "\xE9l\xE9ment", "\xE9l\xE9ments")}`,
|
|
279
293
|
fieldsCount: (count) => `${count} ${plural(count, "champ", "champs")}`,
|
|
280
294
|
uploading: "Envoi en cours\u2026",
|
|
281
295
|
dropToUpload: "D\xE9posez pour envoyer",
|
|
@@ -302,6 +316,14 @@ var FR_FIELD_STRINGS = {
|
|
|
302
316
|
downloadIncomplete: (names) => `${names.length <= 1 ? "Un fichier n\u2019a pas pu \xEAtre enregistr\xE9" : `${names.length} fichiers n\u2019ont pas pu \xEAtre enregistr\xE9s`}\xA0: ${names.join(", ")}`,
|
|
303
317
|
downloadFile: (name) => `T\xE9l\xE9charger ${name}`,
|
|
304
318
|
downloadFileFailed: "Le fichier n\u2019a pas pu \xEAtre enregistr\xE9",
|
|
319
|
+
downloadPdf: "T\xE9l\xE9charger le PDF",
|
|
320
|
+
downloadPdfBusy: "Pr\xE9paration du PDF\u2026",
|
|
321
|
+
downloadPdfFailed: "Le PDF n\u2019a pas pu \xEAtre cr\xE9\xE9",
|
|
322
|
+
pageLoading: "Chargement de la page\u2026",
|
|
323
|
+
downloadHtml: "T\xE9l\xE9charger le HTML",
|
|
324
|
+
fileNameTitle: "Nom du fichier",
|
|
325
|
+
fileNameCancel: "Annuler",
|
|
326
|
+
fileNameConfirm: "T\xE9l\xE9charger",
|
|
305
327
|
viewRendered: "R\xE9sultat",
|
|
306
328
|
viewJson: "JSON",
|
|
307
329
|
copyJson: "Copier le JSON",
|
|
@@ -312,7 +334,10 @@ var FR_FIELD_STRINGS = {
|
|
|
312
334
|
hideOptionalFields: "Masquer les champs facultatifs",
|
|
313
335
|
hideOptionalInputs: "Masquer les entr\xE9es facultatives",
|
|
314
336
|
optionalFieldsCount: (count) => `${count} ${plural(count, "champ facultatif", "champs facultatifs")}`,
|
|
315
|
-
optionalInputsCount: (count) => `${count} ${plural(count, "entr\xE9e facultative", "entr\xE9es facultatives")}
|
|
337
|
+
optionalInputsCount: (count) => `${count} ${plural(count, "entr\xE9e facultative", "entr\xE9es facultatives")}`,
|
|
338
|
+
requiredField: "Obligatoire",
|
|
339
|
+
incompleteField: "Incomplet\xA0: remplissez ses champs obligatoires",
|
|
340
|
+
uploadFailed: "Ce fichier n\u2019a pas pu \xEAtre envoy\xE9. R\xE9essayez."
|
|
316
341
|
};
|
|
317
342
|
|
|
318
343
|
// src/react/locales/index.ts
|
|
@@ -1183,6 +1208,7 @@ function OptionalToggle({ count, expanded, onToggle, noun = "field" }) {
|
|
|
1183
1208
|
{
|
|
1184
1209
|
type: "button",
|
|
1185
1210
|
onClick: onToggle,
|
|
1211
|
+
"aria-expanded": expanded,
|
|
1186
1212
|
className: "flex w-fit items-center gap-1.5 text-[11px] text-muted-foreground transition-colors hover:text-foreground",
|
|
1187
1213
|
children: [
|
|
1188
1214
|
expanded ? /* @__PURE__ */ jsx(Minus, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx(Plus, { className: "h-3 w-3" }),
|
|
@@ -1192,16 +1218,24 @@ function OptionalToggle({ count, expanded, onToggle, noun = "field" }) {
|
|
|
1192
1218
|
);
|
|
1193
1219
|
}
|
|
1194
1220
|
function ObjectField({ field, value, onChange, id, error, env }) {
|
|
1195
|
-
const s = useFieldStrings();
|
|
1196
1221
|
const [showOptional, setShowOptional] = useState(false);
|
|
1197
1222
|
const presentation = useFieldPresentation();
|
|
1198
1223
|
const isApp = presentation === "app";
|
|
1199
1224
|
const data = value ?? {};
|
|
1200
1225
|
const setChild = (name, childValue) => onChange({ ...data, [name]: childValue });
|
|
1201
|
-
const
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
const visible =
|
|
1226
|
+
const folded = field.fields.filter((f) => isFoldedOptional(f, ownProp(data, f.name)));
|
|
1227
|
+
const structureOpen = anyOptionalStructureOpen(field.fields, data);
|
|
1228
|
+
const expanded = showOptional || structureOpen;
|
|
1229
|
+
const visible = expanded ? field.fields : field.fields.filter((f) => !isFoldedOptional(f, ownProp(data, f.name)));
|
|
1230
|
+
const hasStructure = field.fields.some(isOptionalStructure);
|
|
1231
|
+
const toggle = () => {
|
|
1232
|
+
setShowOptional(!expanded);
|
|
1233
|
+
if (hasStructure) {
|
|
1234
|
+
onChange(
|
|
1235
|
+
expanded ? closeOptionalStructures(field.fields, data) : openOptionalStructures(field.fields, data)
|
|
1236
|
+
);
|
|
1237
|
+
}
|
|
1238
|
+
};
|
|
1205
1239
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
1206
1240
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-baseline gap-x-2 gap-y-1", children: [
|
|
1207
1241
|
/* @__PURE__ */ jsx(
|
|
@@ -1214,8 +1248,7 @@ function ObjectField({ field, value, onChange, id, error, env }) {
|
|
|
1214
1248
|
children: fieldLabel(field.title, field.name, presentation)
|
|
1215
1249
|
}
|
|
1216
1250
|
),
|
|
1217
|
-
!isApp && /* @__PURE__ */ jsx(ConceptPill, { conceptRef: field.conceptRef, category: "structured" })
|
|
1218
|
-
shownAsOptional(field) && /* @__PURE__ */ jsx("span", { className: "ml-auto font-mono text-[10px] uppercase tracking-wider text-muted-foreground", children: s.optionalBadge })
|
|
1251
|
+
!isApp && /* @__PURE__ */ jsx(ConceptPill, { conceptRef: field.conceptRef, category: "structured" })
|
|
1219
1252
|
] }),
|
|
1220
1253
|
field.description && /* @__PURE__ */ jsx("p", { className: "text-[12px] leading-relaxed text-muted-foreground", children: field.description }),
|
|
1221
1254
|
/* @__PURE__ */ jsxs(
|
|
@@ -1237,12 +1270,12 @@ function ObjectField({ field, value, onChange, id, error, env }) {
|
|
|
1237
1270
|
},
|
|
1238
1271
|
child.name
|
|
1239
1272
|
)),
|
|
1240
|
-
|
|
1273
|
+
(folded.length > 0 || structureOpen) && /* @__PURE__ */ jsx(
|
|
1241
1274
|
OptionalToggle,
|
|
1242
1275
|
{
|
|
1243
|
-
count:
|
|
1244
|
-
expanded
|
|
1245
|
-
onToggle:
|
|
1276
|
+
count: folded.length,
|
|
1277
|
+
expanded,
|
|
1278
|
+
onToggle: toggle,
|
|
1246
1279
|
noun: "field"
|
|
1247
1280
|
}
|
|
1248
1281
|
)
|
|
@@ -1329,7 +1362,12 @@ function ListField({ field, value, onChange, id, error, env }) {
|
|
|
1329
1362
|
/* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx(
|
|
1330
1363
|
FieldRenderer,
|
|
1331
1364
|
{
|
|
1332
|
-
field: {
|
|
1365
|
+
field: {
|
|
1366
|
+
...field.item,
|
|
1367
|
+
title: "",
|
|
1368
|
+
name: `${field.name}[${index}]`,
|
|
1369
|
+
required: true
|
|
1370
|
+
},
|
|
1333
1371
|
value: item,
|
|
1334
1372
|
onChange: (v) => setItem(index, v),
|
|
1335
1373
|
id: `${id}.${index}`,
|
|
@@ -1600,6 +1638,192 @@ function FieldRenderer({ field, value, onChange, id, error, env }) {
|
|
|
1600
1638
|
);
|
|
1601
1639
|
}
|
|
1602
1640
|
}
|
|
1641
|
+
var EMPTY_IDS = /* @__PURE__ */ new Set();
|
|
1642
|
+
function withId(ids, id) {
|
|
1643
|
+
if (ids.has(id)) return ids;
|
|
1644
|
+
const next = new Set(ids);
|
|
1645
|
+
next.add(id);
|
|
1646
|
+
return next;
|
|
1647
|
+
}
|
|
1648
|
+
function withoutId(ids, id) {
|
|
1649
|
+
if (!ids.has(id)) return ids;
|
|
1650
|
+
const next = new Set(ids);
|
|
1651
|
+
next.delete(id);
|
|
1652
|
+
return next;
|
|
1653
|
+
}
|
|
1654
|
+
function orderFields(fields, order) {
|
|
1655
|
+
if (!order || order.length === 0) return fields;
|
|
1656
|
+
const rank = (field) => {
|
|
1657
|
+
const index = order.indexOf(field.name);
|
|
1658
|
+
return index === -1 ? order.length : index;
|
|
1659
|
+
};
|
|
1660
|
+
return fields.map((field, index) => ({ field, index })).sort((a, b) => rank(a.field) - rank(b.field) || a.index - b.index).map(({ field }) => field);
|
|
1661
|
+
}
|
|
1662
|
+
function useMethodForm({
|
|
1663
|
+
descriptor,
|
|
1664
|
+
contract,
|
|
1665
|
+
prepareFields,
|
|
1666
|
+
order,
|
|
1667
|
+
initialValues,
|
|
1668
|
+
uploadFile
|
|
1669
|
+
}) {
|
|
1670
|
+
const orderKey = order ? order.join("\0") : "";
|
|
1671
|
+
const fields = useMemo(() => {
|
|
1672
|
+
const derived = buildRunFields(descriptor, contract.inputs);
|
|
1673
|
+
const prepared = prepareFields ? prepareFields(derived) : derived;
|
|
1674
|
+
return orderFields(prepared, orderKey ? orderKey.split("\0") : void 0);
|
|
1675
|
+
}, [descriptor, contract.inputs, prepareFields, orderKey]);
|
|
1676
|
+
const initial = useMemo(() => {
|
|
1677
|
+
if (typeof initialValues === "function") return initialValues(seedInputs(fields));
|
|
1678
|
+
return initialValues ?? seedInputs(fields);
|
|
1679
|
+
}, [initialValues, fields]);
|
|
1680
|
+
const [edited, setEdited] = useState(null);
|
|
1681
|
+
const [uploadingIds, setUploadingIds] = useState(EMPTY_IDS);
|
|
1682
|
+
const [failedUploadIds, setFailedUploadIds] = useState(EMPTY_IDS);
|
|
1683
|
+
const [attempted, setAttempted] = useState(false);
|
|
1684
|
+
const pendingUploads = useRef(/* @__PURE__ */ new Map());
|
|
1685
|
+
const uploadCounter = useRef(0);
|
|
1686
|
+
const values = edited ?? initial;
|
|
1687
|
+
const setValues = useCallback(
|
|
1688
|
+
(next) => setEdited((previous) => typeof next === "function" ? next(previous ?? initial) : next),
|
|
1689
|
+
[initial]
|
|
1690
|
+
);
|
|
1691
|
+
const reset = useCallback((next) => {
|
|
1692
|
+
pendingUploads.current.clear();
|
|
1693
|
+
setEdited(next ?? null);
|
|
1694
|
+
setUploadingIds(EMPTY_IDS);
|
|
1695
|
+
setFailedUploadIds(EMPTY_IDS);
|
|
1696
|
+
setAttempted(false);
|
|
1697
|
+
}, []);
|
|
1698
|
+
const dropFile = useMemo(() => {
|
|
1699
|
+
if (!uploadFile) return void 0;
|
|
1700
|
+
return (id, file) => {
|
|
1701
|
+
const token = ++uploadCounter.current;
|
|
1702
|
+
pendingUploads.current.set(id, token);
|
|
1703
|
+
const current = () => pendingUploads.current.get(id) === token;
|
|
1704
|
+
setUploadingIds((previous) => withId(previous, id));
|
|
1705
|
+
setFailedUploadIds((previous) => withoutId(previous, id));
|
|
1706
|
+
const path = id.split(".");
|
|
1707
|
+
let upload;
|
|
1708
|
+
try {
|
|
1709
|
+
upload = Promise.resolve(uploadFile(file, { id, path }));
|
|
1710
|
+
} catch (error) {
|
|
1711
|
+
upload = Promise.reject(error);
|
|
1712
|
+
}
|
|
1713
|
+
upload.then(
|
|
1714
|
+
(stored) => {
|
|
1715
|
+
if (!current()) return;
|
|
1716
|
+
const value = typeof stored === "string" ? { url: stored, filename: file.name } : { url: stored.url, filename: stored.filename ?? file.name };
|
|
1717
|
+
setEdited((previous) => setValueAtPath(previous ?? initial, path, value));
|
|
1718
|
+
},
|
|
1719
|
+
() => {
|
|
1720
|
+
if (current()) setFailedUploadIds((previous) => withId(previous, id));
|
|
1721
|
+
}
|
|
1722
|
+
).finally(() => {
|
|
1723
|
+
if (!current()) return;
|
|
1724
|
+
pendingUploads.current.delete(id);
|
|
1725
|
+
setUploadingIds((previous) => withoutId(previous, id));
|
|
1726
|
+
});
|
|
1727
|
+
};
|
|
1728
|
+
}, [uploadFile, initial]);
|
|
1729
|
+
const readiness = useMemo(() => computeReadiness(fields, values), [fields, values]);
|
|
1730
|
+
const inputs = useMemo(
|
|
1731
|
+
() => apiInputsFromRunValues(values, fields, contract.inputs),
|
|
1732
|
+
[values, fields, contract.inputs]
|
|
1733
|
+
);
|
|
1734
|
+
const uploading = uploadingIds.size > 0;
|
|
1735
|
+
const ready = readiness.ready === readiness.total && !uploading;
|
|
1736
|
+
const attempt = useCallback(() => {
|
|
1737
|
+
setAttempted(true);
|
|
1738
|
+
return ready;
|
|
1739
|
+
}, [ready]);
|
|
1740
|
+
return {
|
|
1741
|
+
fields,
|
|
1742
|
+
values,
|
|
1743
|
+
inputs,
|
|
1744
|
+
readiness,
|
|
1745
|
+
uploading,
|
|
1746
|
+
ready,
|
|
1747
|
+
attempted,
|
|
1748
|
+
setValues,
|
|
1749
|
+
reset,
|
|
1750
|
+
attempt,
|
|
1751
|
+
uploadingIds,
|
|
1752
|
+
failedUploadIds,
|
|
1753
|
+
dropFile
|
|
1754
|
+
};
|
|
1755
|
+
}
|
|
1756
|
+
function MethodForm({ locale, strings, presentation, ...props }) {
|
|
1757
|
+
let body = /* @__PURE__ */ jsx(MethodFormFields, { ...props });
|
|
1758
|
+
if (presentation) {
|
|
1759
|
+
body = /* @__PURE__ */ jsx(FieldPresentationProvider, { presentation, children: body });
|
|
1760
|
+
}
|
|
1761
|
+
if (locale !== void 0 || strings !== void 0) {
|
|
1762
|
+
body = /* @__PURE__ */ jsx(FieldStringsProvider, { locale, strings, children: body });
|
|
1763
|
+
}
|
|
1764
|
+
return body;
|
|
1765
|
+
}
|
|
1766
|
+
function MethodFormFields({
|
|
1767
|
+
form,
|
|
1768
|
+
disabled,
|
|
1769
|
+
allowUrl,
|
|
1770
|
+
resolveUrl,
|
|
1771
|
+
foldOptional = true,
|
|
1772
|
+
errors,
|
|
1773
|
+
className
|
|
1774
|
+
}) {
|
|
1775
|
+
const s = useFieldStrings();
|
|
1776
|
+
const [showOptional, setShowOptional] = useState(false);
|
|
1777
|
+
const { fields, values, setValues, attempted, uploadingIds, failedUploadIds, dropFile } = form;
|
|
1778
|
+
const uploadErrors = useMemo(
|
|
1779
|
+
() => new Map([...failedUploadIds].map((id) => [id, s.uploadFailed])),
|
|
1780
|
+
[failedUploadIds, s.uploadFailed]
|
|
1781
|
+
);
|
|
1782
|
+
const env = useMemo(
|
|
1783
|
+
() => ({ disabled, onDropFile: dropFile, uploadingIds, uploadErrors, allowUrl, resolveUrl }),
|
|
1784
|
+
[disabled, dropFile, uploadingIds, uploadErrors, allowUrl, resolveUrl]
|
|
1785
|
+
);
|
|
1786
|
+
const folds = (field) => {
|
|
1787
|
+
const value = ownProp(values, field.name);
|
|
1788
|
+
return foldOptional ? isFoldedOptional(field, value) : isOptionalStructure(field) && !optionalStructureOpen(value);
|
|
1789
|
+
};
|
|
1790
|
+
const folded = fields.filter(folds);
|
|
1791
|
+
const structureOpen = anyOptionalStructureOpen(fields, values);
|
|
1792
|
+
const expanded = showOptional || structureOpen;
|
|
1793
|
+
const visible = expanded ? fields : fields.filter((field) => !folds(field));
|
|
1794
|
+
const hasStructure = fields.some(isOptionalStructure);
|
|
1795
|
+
const toggle = () => {
|
|
1796
|
+
setShowOptional(!expanded);
|
|
1797
|
+
if (hasStructure) {
|
|
1798
|
+
setValues(
|
|
1799
|
+
(previous) => expanded ? closeOptionalStructures(fields, previous) : openOptionalStructures(fields, previous)
|
|
1800
|
+
);
|
|
1801
|
+
}
|
|
1802
|
+
};
|
|
1803
|
+
const errorFor = (field) => {
|
|
1804
|
+
const hostError = errors ? ownProp(errors, field.name) : void 0;
|
|
1805
|
+
if (hostError) return hostError;
|
|
1806
|
+
if (!attempted) return void 0;
|
|
1807
|
+
const value = ownProp(values, field.name);
|
|
1808
|
+
if (mustBeFilled(field)) return fieldFilled(field, value) ? void 0 : s.requiredField;
|
|
1809
|
+
return isFilled(value) && !fieldFilled(field, value) ? s.incompleteField : void 0;
|
|
1810
|
+
};
|
|
1811
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("space-y-6", className), children: [
|
|
1812
|
+
visible.map((field) => /* @__PURE__ */ jsx(
|
|
1813
|
+
FieldRenderer,
|
|
1814
|
+
{
|
|
1815
|
+
field,
|
|
1816
|
+
id: field.name,
|
|
1817
|
+
value: ownProp(values, field.name),
|
|
1818
|
+
error: errorFor(field),
|
|
1819
|
+
env,
|
|
1820
|
+
onChange: (next) => setValues((previous) => ({ ...previous, [field.name]: next }))
|
|
1821
|
+
},
|
|
1822
|
+
field.name
|
|
1823
|
+
)),
|
|
1824
|
+
(folded.length > 0 || structureOpen) && /* @__PURE__ */ jsx(OptionalToggle, { count: folded.length, expanded, onToggle: toggle, noun: "input" })
|
|
1825
|
+
] });
|
|
1826
|
+
}
|
|
1603
1827
|
|
|
1604
1828
|
// src/react/absolute-url.ts
|
|
1605
1829
|
function absoluteUrl(url) {
|
|
@@ -1612,6 +1836,287 @@ function absoluteUrl(url) {
|
|
|
1612
1836
|
}
|
|
1613
1837
|
}
|
|
1614
1838
|
|
|
1839
|
+
// src/react/html-frame.ts
|
|
1840
|
+
var DEFAULT_IMG_SRC = "'self' data: https:";
|
|
1841
|
+
function pageTypography(font) {
|
|
1842
|
+
return { color: "#000", mutedColor: "#555", borderColor: "#bbb", font };
|
|
1843
|
+
}
|
|
1844
|
+
function frameStyles({ color, mutedColor, borderColor, font }) {
|
|
1845
|
+
return `
|
|
1846
|
+
:root { color-scheme: inherit; }
|
|
1847
|
+
body {
|
|
1848
|
+
margin: 0;
|
|
1849
|
+
color: ${color};
|
|
1850
|
+
font-family: ${font};
|
|
1851
|
+
font-size: 13px;
|
|
1852
|
+
line-height: 1.55;
|
|
1853
|
+
background: transparent;
|
|
1854
|
+
overflow-wrap: anywhere;
|
|
1855
|
+
}
|
|
1856
|
+
:where(h1, h2, h3, h4, h5, h6) { margin: 0 0 0.4em; line-height: 1.25; }
|
|
1857
|
+
:where(h1) { font-size: 1.4em; }
|
|
1858
|
+
:where(h2) { font-size: 1.2em; }
|
|
1859
|
+
:where(h3) { font-size: 1.05em; }
|
|
1860
|
+
:where(p, ul, ol, table, pre, blockquote) { margin: 0 0 0.75em; }
|
|
1861
|
+
:where(ul, ol) { padding-inline-start: 1.25em; }
|
|
1862
|
+
:where(table) { border-collapse: collapse; width: 100%; }
|
|
1863
|
+
:where(th, td) {
|
|
1864
|
+
border: 1px solid ${borderColor};
|
|
1865
|
+
padding: 4px 8px;
|
|
1866
|
+
text-align: left;
|
|
1867
|
+
vertical-align: top;
|
|
1868
|
+
}
|
|
1869
|
+
:where(th) { font-weight: 600; }
|
|
1870
|
+
:where(caption) { caption-side: top; color: ${mutedColor}; padding-bottom: 4px; text-align: left; }
|
|
1871
|
+
:where(a) { color: inherit; }
|
|
1872
|
+
:where(code, pre) { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.92em; }
|
|
1873
|
+
:where(pre) { white-space: pre-wrap; }
|
|
1874
|
+
:where(blockquote) { border-inline-start: 2px solid ${borderColor}; margin-inline-start: 0; padding-inline-start: 0.75em; color: ${mutedColor}; }
|
|
1875
|
+
:where(img) { max-width: 100%; height: auto; }
|
|
1876
|
+
:where(hr) { border: 0; border-top: 1px solid ${borderColor}; }
|
|
1877
|
+
:where(*:last-child) { margin-bottom: 0; }
|
|
1878
|
+
`;
|
|
1879
|
+
}
|
|
1880
|
+
function isWholeDocument(markup) {
|
|
1881
|
+
return /^\s*(?:<!--[\s\S]*?-->\s*)*<(?:!doctype\b|html[\s>]|head[\s>])/i.test(markup);
|
|
1882
|
+
}
|
|
1883
|
+
function frameDocument(content, { styles, imgSrc, fontSrc }) {
|
|
1884
|
+
const head = [
|
|
1885
|
+
'<!doctype html><html><head><meta charset="utf-8">',
|
|
1886
|
+
`<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src ${imgSrc}; font-src ${fontSrc}; base-uri 'none'; form-action 'none'">`,
|
|
1887
|
+
`<style>${styles}</style></head>`
|
|
1888
|
+
].join("");
|
|
1889
|
+
if (isWholeDocument(content.innerHtml)) return head + content.innerHtml;
|
|
1890
|
+
const body = content.cssClass ? (
|
|
1891
|
+
// The class the value states, honoured the way the runtime's own HTML
|
|
1892
|
+
// rendering honours it: as a wrapper, not as something merged into ours.
|
|
1893
|
+
`<div class="${escapeAttribute(content.cssClass)}">${content.innerHtml}</div>`
|
|
1894
|
+
) : content.innerHtml;
|
|
1895
|
+
return `${head}<body>${body}</body></html>`;
|
|
1896
|
+
}
|
|
1897
|
+
function escapeAttribute(value) {
|
|
1898
|
+
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<");
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
// src/react/markup-storage.ts
|
|
1902
|
+
var MARKUP_RESOLVE_TIMEOUT_MS = 1e4;
|
|
1903
|
+
var STORAGE_SCHEME = "pipelex-storage:";
|
|
1904
|
+
function mayHoldStorageRefs(markup) {
|
|
1905
|
+
return markup.toLowerCase().includes("pipelex-storage");
|
|
1906
|
+
}
|
|
1907
|
+
function isStorageRef(value) {
|
|
1908
|
+
return value.trim().toLowerCase().startsWith(STORAGE_SCHEME);
|
|
1909
|
+
}
|
|
1910
|
+
function parseMarkup(markup) {
|
|
1911
|
+
if (typeof DOMParser === "undefined") return void 0;
|
|
1912
|
+
const parser = new DOMParser();
|
|
1913
|
+
if (isWholeDocument(markup)) {
|
|
1914
|
+
const doc2 = parser.parseFromString(markup, "text/html");
|
|
1915
|
+
return {
|
|
1916
|
+
root: doc2,
|
|
1917
|
+
serialize: () => (doc2.doctype ? `<!DOCTYPE ${doc2.doctype.name}>` : "") + doc2.documentElement.outerHTML
|
|
1918
|
+
};
|
|
1919
|
+
}
|
|
1920
|
+
const doc = parser.parseFromString("<!doctype html><body></body>", "text/html");
|
|
1921
|
+
const template = doc.createElement("template");
|
|
1922
|
+
template.innerHTML = markup;
|
|
1923
|
+
return { root: template.content, serialize: () => template.innerHTML };
|
|
1924
|
+
}
|
|
1925
|
+
var CSS_URL = /url\(\s*(['"]?)(.*?)\1\s*\)/gi;
|
|
1926
|
+
function parseSrcset(value) {
|
|
1927
|
+
const candidates = [];
|
|
1928
|
+
let at = 0;
|
|
1929
|
+
while (at < value.length) {
|
|
1930
|
+
while (at < value.length && /[\s,]/.test(value[at] ?? "")) at += 1;
|
|
1931
|
+
if (at >= value.length) break;
|
|
1932
|
+
const start = at;
|
|
1933
|
+
while (at < value.length && !/\s/.test(value[at] ?? "")) at += 1;
|
|
1934
|
+
let url = value.slice(start, at);
|
|
1935
|
+
if (url.endsWith(",")) {
|
|
1936
|
+
url = url.replace(/,+$/, "");
|
|
1937
|
+
candidates.push({ url, descriptor: "" });
|
|
1938
|
+
continue;
|
|
1939
|
+
}
|
|
1940
|
+
let depth = 0;
|
|
1941
|
+
const descriptorStart = at;
|
|
1942
|
+
while (at < value.length) {
|
|
1943
|
+
const char = value[at];
|
|
1944
|
+
if (char === "(") depth += 1;
|
|
1945
|
+
else if (char === ")") depth = Math.max(0, depth - 1);
|
|
1946
|
+
else if (char === "," && depth === 0) break;
|
|
1947
|
+
at += 1;
|
|
1948
|
+
}
|
|
1949
|
+
candidates.push({ url, descriptor: value.slice(descriptorStart, at).trim() });
|
|
1950
|
+
}
|
|
1951
|
+
return candidates;
|
|
1952
|
+
}
|
|
1953
|
+
function cssUrl(url) {
|
|
1954
|
+
return `url("${url.replace(/[\\"]/g, (char) => `\\${char}`).replace(/[\n\r\f]/g, " ")}")`;
|
|
1955
|
+
}
|
|
1956
|
+
function attributeSite(element, name) {
|
|
1957
|
+
return {
|
|
1958
|
+
refs: () => {
|
|
1959
|
+
const value = element.getAttribute(name) ?? "";
|
|
1960
|
+
return isStorageRef(value) ? [value.trim()] : [];
|
|
1961
|
+
},
|
|
1962
|
+
rewrite: (answer) => {
|
|
1963
|
+
const value = element.getAttribute(name) ?? "";
|
|
1964
|
+
if (!isStorageRef(value)) return false;
|
|
1965
|
+
const url = answer(value.trim());
|
|
1966
|
+
if (url === void 0) return false;
|
|
1967
|
+
element.setAttribute(name, url);
|
|
1968
|
+
return true;
|
|
1969
|
+
}
|
|
1970
|
+
};
|
|
1971
|
+
}
|
|
1972
|
+
function srcsetSite(element, name) {
|
|
1973
|
+
const read = () => parseSrcset(element.getAttribute(name) ?? "");
|
|
1974
|
+
return {
|
|
1975
|
+
refs: () => read().flatMap(({ url }) => isStorageRef(url) ? [url] : []),
|
|
1976
|
+
rewrite: (answer) => {
|
|
1977
|
+
let changed = false;
|
|
1978
|
+
const candidates = read().map((candidate) => {
|
|
1979
|
+
if (!isStorageRef(candidate.url)) return candidate;
|
|
1980
|
+
const url = answer(candidate.url);
|
|
1981
|
+
if (url === void 0) return candidate;
|
|
1982
|
+
changed = true;
|
|
1983
|
+
return { ...candidate, url: url.replace(/,/g, "%2C").replace(/\s/g, "%20") };
|
|
1984
|
+
});
|
|
1985
|
+
if (changed) {
|
|
1986
|
+
element.setAttribute(
|
|
1987
|
+
name,
|
|
1988
|
+
candidates.map(({ url, descriptor }) => descriptor ? `${url} ${descriptor}` : url).join(", ")
|
|
1989
|
+
);
|
|
1990
|
+
}
|
|
1991
|
+
return changed;
|
|
1992
|
+
}
|
|
1993
|
+
};
|
|
1994
|
+
}
|
|
1995
|
+
function cssSite(read, write) {
|
|
1996
|
+
return {
|
|
1997
|
+
refs: () => Array.from(read().matchAll(CSS_URL), (match) => (match[2] ?? "").trim()).filter(isStorageRef),
|
|
1998
|
+
rewrite: (answer) => {
|
|
1999
|
+
let changed = false;
|
|
2000
|
+
const css = read().replace(CSS_URL, (token, _quote, inner) => {
|
|
2001
|
+
const ref = inner.trim();
|
|
2002
|
+
if (!isStorageRef(ref)) return token;
|
|
2003
|
+
const url = answer(ref);
|
|
2004
|
+
if (url === void 0) return token;
|
|
2005
|
+
changed = true;
|
|
2006
|
+
return cssUrl(url);
|
|
2007
|
+
});
|
|
2008
|
+
if (changed) write(css);
|
|
2009
|
+
return changed;
|
|
2010
|
+
}
|
|
2011
|
+
};
|
|
2012
|
+
}
|
|
2013
|
+
function urlSites(root) {
|
|
2014
|
+
const sites = [];
|
|
2015
|
+
for (const element of Array.from(root.querySelectorAll("*"))) {
|
|
2016
|
+
const tag = element.localName;
|
|
2017
|
+
if (tag === "img" || tag === "source") {
|
|
2018
|
+
sites.push(attributeSite(element, "src"), srcsetSite(element, "srcset"));
|
|
2019
|
+
}
|
|
2020
|
+
if (tag === "video") sites.push(attributeSite(element, "poster"));
|
|
2021
|
+
if (tag === "image") {
|
|
2022
|
+
sites.push(attributeSite(element, "href"), attributeSite(element, "xlink:href"));
|
|
2023
|
+
}
|
|
2024
|
+
if (element.hasAttribute("background")) sites.push(attributeSite(element, "background"));
|
|
2025
|
+
if (element.hasAttribute("style")) {
|
|
2026
|
+
sites.push(
|
|
2027
|
+
cssSite(
|
|
2028
|
+
() => element.getAttribute("style") ?? "",
|
|
2029
|
+
(css) => element.setAttribute("style", css)
|
|
2030
|
+
)
|
|
2031
|
+
);
|
|
2032
|
+
}
|
|
2033
|
+
if (tag === "style") {
|
|
2034
|
+
sites.push(
|
|
2035
|
+
cssSite(
|
|
2036
|
+
() => element.textContent ?? "",
|
|
2037
|
+
(css) => {
|
|
2038
|
+
element.textContent = css;
|
|
2039
|
+
}
|
|
2040
|
+
)
|
|
2041
|
+
);
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
return sites;
|
|
2045
|
+
}
|
|
2046
|
+
function storageRefsIn(markup) {
|
|
2047
|
+
if (!mayHoldStorageRefs(markup)) return [];
|
|
2048
|
+
const parsed = parseMarkup(markup);
|
|
2049
|
+
if (!parsed) return [];
|
|
2050
|
+
return Array.from(new Set(urlSites(parsed.root).flatMap((site) => site.refs())));
|
|
2051
|
+
}
|
|
2052
|
+
function rewriteStorageRefs(markup, answer) {
|
|
2053
|
+
if (!mayHoldStorageRefs(markup)) return markup;
|
|
2054
|
+
const parsed = parseMarkup(markup);
|
|
2055
|
+
if (!parsed) return markup;
|
|
2056
|
+
let changed = false;
|
|
2057
|
+
for (const site of urlSites(parsed.root)) {
|
|
2058
|
+
if (site.rewrite(answer)) changed = true;
|
|
2059
|
+
}
|
|
2060
|
+
return changed ? parsed.serialize() : markup;
|
|
2061
|
+
}
|
|
2062
|
+
function readBulk(result) {
|
|
2063
|
+
if (result instanceof Map) {
|
|
2064
|
+
return (ref) => {
|
|
2065
|
+
const url = result.get(ref);
|
|
2066
|
+
return typeof url === "string" ? url : void 0;
|
|
2067
|
+
};
|
|
2068
|
+
}
|
|
2069
|
+
if (result !== null && typeof result === "object") {
|
|
2070
|
+
const record = result;
|
|
2071
|
+
return (ref) => {
|
|
2072
|
+
const url = ownProp(record, ref);
|
|
2073
|
+
return typeof url === "string" ? url : void 0;
|
|
2074
|
+
};
|
|
2075
|
+
}
|
|
2076
|
+
return () => void 0;
|
|
2077
|
+
}
|
|
2078
|
+
async function askBulkUntimed(resolveUrls, refs) {
|
|
2079
|
+
try {
|
|
2080
|
+
return readBulk(await resolveUrls(refs));
|
|
2081
|
+
} catch {
|
|
2082
|
+
return () => void 0;
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
async function askBulk(resolveUrls, refs, timeoutMs = MARKUP_RESOLVE_TIMEOUT_MS) {
|
|
2086
|
+
let timer;
|
|
2087
|
+
const timeout = new Promise((resolve) => {
|
|
2088
|
+
timer = setTimeout(() => resolve(() => void 0), timeoutMs);
|
|
2089
|
+
});
|
|
2090
|
+
const asked = askBulkUntimed(resolveUrls, refs);
|
|
2091
|
+
try {
|
|
2092
|
+
return await Promise.race([asked, timeout]);
|
|
2093
|
+
} finally {
|
|
2094
|
+
clearTimeout(timer);
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
function answerFor(bulk, resolveUrl) {
|
|
2098
|
+
return (ref) => {
|
|
2099
|
+
const fromBulk = viewableUrl(bulk?.(ref));
|
|
2100
|
+
if (fromBulk !== void 0) return fromBulk;
|
|
2101
|
+
try {
|
|
2102
|
+
return viewableUrl(resolveUrl?.(ref));
|
|
2103
|
+
} catch {
|
|
2104
|
+
return void 0;
|
|
2105
|
+
}
|
|
2106
|
+
};
|
|
2107
|
+
}
|
|
2108
|
+
async function resolveHtmlStorageUrls(markup, { resolveUrl, resolveUrls }, { timeoutMs = MARKUP_RESOLVE_TIMEOUT_MS } = {}) {
|
|
2109
|
+
if (!resolveUrl && !resolveUrls) return markup;
|
|
2110
|
+
try {
|
|
2111
|
+
const refs = storageRefsIn(markup);
|
|
2112
|
+
if (refs.length === 0) return markup;
|
|
2113
|
+
const bulk = resolveUrls ? await askBulk(resolveUrls, refs, timeoutMs) : void 0;
|
|
2114
|
+
return rewriteStorageRefs(markup, answerFor(bulk, resolveUrl));
|
|
2115
|
+
} catch {
|
|
2116
|
+
return markup;
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
|
|
1615
2120
|
// src/react/save-in-browser.ts
|
|
1616
2121
|
function saveBlob(blob, filename) {
|
|
1617
2122
|
const href = URL.createObjectURL(blob);
|
|
@@ -1646,6 +2151,7 @@ async function saveInBrowser(files) {
|
|
|
1646
2151
|
var ResultEnvContext = createContext({});
|
|
1647
2152
|
function ResultEnvProvider({
|
|
1648
2153
|
resolveUrl,
|
|
2154
|
+
resolveUrls,
|
|
1649
2155
|
resolveShareUrl,
|
|
1650
2156
|
proseImages,
|
|
1651
2157
|
tableColumns,
|
|
@@ -1654,8 +2160,16 @@ function ResultEnvProvider({
|
|
|
1654
2160
|
children
|
|
1655
2161
|
}) {
|
|
1656
2162
|
const env = useMemo(
|
|
1657
|
-
() => ({
|
|
1658
|
-
|
|
2163
|
+
() => ({
|
|
2164
|
+
resolveUrl,
|
|
2165
|
+
resolveUrls,
|
|
2166
|
+
resolveShareUrl,
|
|
2167
|
+
proseImages,
|
|
2168
|
+
tableColumns,
|
|
2169
|
+
saveFiles,
|
|
2170
|
+
downloads
|
|
2171
|
+
}),
|
|
2172
|
+
[resolveUrl, resolveUrls, resolveShareUrl, proseImages, tableColumns, saveFiles, downloads]
|
|
1659
2173
|
);
|
|
1660
2174
|
return /* @__PURE__ */ jsx(ResultEnvContext, { value: env, children });
|
|
1661
2175
|
}
|
|
@@ -1666,15 +2180,19 @@ function DownloadDisplayOverride({
|
|
|
1666
2180
|
const env = use(ResultEnvContext);
|
|
1667
2181
|
const result = downloads?.result;
|
|
1668
2182
|
const files = downloads?.files;
|
|
2183
|
+
const pdf = downloads?.pdf;
|
|
2184
|
+
const askFileName = downloads?.askFileName;
|
|
1669
2185
|
const merged = useMemo(
|
|
1670
|
-
() => result === void 0 && files === void 0 ? env : {
|
|
2186
|
+
() => result === void 0 && files === void 0 && pdf === void 0 && askFileName === void 0 ? env : {
|
|
1671
2187
|
...env,
|
|
1672
2188
|
downloads: {
|
|
1673
2189
|
result: result ?? env.downloads?.result,
|
|
1674
|
-
files: files ?? env.downloads?.files
|
|
2190
|
+
files: files ?? env.downloads?.files,
|
|
2191
|
+
pdf: pdf ?? env.downloads?.pdf,
|
|
2192
|
+
askFileName: askFileName ?? env.downloads?.askFileName
|
|
1675
2193
|
}
|
|
1676
2194
|
},
|
|
1677
|
-
[env, result, files]
|
|
2195
|
+
[env, result, files, pdf, askFileName]
|
|
1678
2196
|
);
|
|
1679
2197
|
return /* @__PURE__ */ jsx(ResultEnvContext, { value: merged, children });
|
|
1680
2198
|
}
|
|
@@ -1684,16 +2202,25 @@ function useResolvedUrl(url) {
|
|
|
1684
2202
|
function useResolveUrl() {
|
|
1685
2203
|
return use(ResultEnvContext).resolveUrl;
|
|
1686
2204
|
}
|
|
2205
|
+
function useMarkupResolvers() {
|
|
2206
|
+
const { resolveUrl, resolveUrls } = use(ResultEnvContext);
|
|
2207
|
+
return useMemo(() => ({ resolveUrl, resolveUrls }), [resolveUrl, resolveUrls]);
|
|
2208
|
+
}
|
|
1687
2209
|
function useResolveShareUrl() {
|
|
1688
2210
|
return use(ResultEnvContext).resolveShareUrl;
|
|
1689
2211
|
}
|
|
1690
2212
|
function useSaveFiles() {
|
|
1691
2213
|
const save = use(ResultEnvContext).saveFiles ?? saveInBrowser;
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
2214
|
+
const resolvers = useMarkupResolvers();
|
|
2215
|
+
return useCallback((files) => deliver(save, files, resolvers), [save, resolvers]);
|
|
2216
|
+
}
|
|
2217
|
+
async function deliver(save, files, resolvers) {
|
|
2218
|
+
const handed = await Promise.all(
|
|
2219
|
+
files.map(async (file) => {
|
|
2220
|
+
if (file.url !== void 0) return { ...file, url: absoluteUrl(file.url) };
|
|
2221
|
+
if (file.mimeType !== "text/html") return file;
|
|
2222
|
+
return { ...file, text: await resolveHtmlStorageUrls(file.text, resolvers) };
|
|
2223
|
+
})
|
|
1697
2224
|
);
|
|
1698
2225
|
try {
|
|
1699
2226
|
const result = await save(handed);
|
|
@@ -1713,6 +2240,12 @@ function useFileDownloadShown(kind) {
|
|
|
1713
2240
|
const files = use(ResultEnvContext).downloads?.files ?? true;
|
|
1714
2241
|
return typeof files === "boolean" ? files : files.includes(kind);
|
|
1715
2242
|
}
|
|
2243
|
+
function usePdfSaveShown() {
|
|
2244
|
+
return use(ResultEnvContext).downloads?.pdf ?? true;
|
|
2245
|
+
}
|
|
2246
|
+
function useAskFileName() {
|
|
2247
|
+
return use(ResultEnvContext).downloads?.askFileName ?? false;
|
|
2248
|
+
}
|
|
1716
2249
|
function useProseImages() {
|
|
1717
2250
|
return use(ResultEnvContext).proseImages ?? "link";
|
|
1718
2251
|
}
|
|
@@ -1761,88 +2294,857 @@ var TooltipContent = React2.forwardRef(({ className, sideOffset = 6, ...props },
|
|
|
1761
2294
|
}
|
|
1762
2295
|
) }));
|
|
1763
2296
|
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
2297
|
+
function useFileNamePrompt() {
|
|
2298
|
+
const asking = useAskFileName();
|
|
2299
|
+
const [pending, setPending] = useState(null);
|
|
2300
|
+
const ask = useCallback(
|
|
2301
|
+
(stem2, extension) => {
|
|
2302
|
+
if (!asking) return Promise.resolve(`${stem2}${extension}`);
|
|
2303
|
+
return new Promise((resolve) => {
|
|
2304
|
+
setPending({
|
|
2305
|
+
stem: stem2,
|
|
2306
|
+
extension,
|
|
2307
|
+
settle: (name) => {
|
|
2308
|
+
setPending(null);
|
|
2309
|
+
resolve(name);
|
|
2310
|
+
}
|
|
2311
|
+
});
|
|
2312
|
+
});
|
|
2313
|
+
},
|
|
2314
|
+
[asking]
|
|
2315
|
+
);
|
|
2316
|
+
const dialog = pending ? /* @__PURE__ */ jsx(
|
|
2317
|
+
FileNameDialog,
|
|
2318
|
+
{
|
|
2319
|
+
stem: pending.stem,
|
|
2320
|
+
extension: pending.extension,
|
|
2321
|
+
onConfirm: (typed) => pending.settle(
|
|
2322
|
+
`${typedFileStem(typed, pending.extension) || pending.stem}${pending.extension}`
|
|
2323
|
+
),
|
|
2324
|
+
onCancel: () => pending.settle(null)
|
|
1776
2325
|
}
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
2326
|
+
) : null;
|
|
2327
|
+
return { ask, dialog };
|
|
2328
|
+
}
|
|
2329
|
+
function FileNameDialog({ stem: stem2, extension, onConfirm, onCancel }) {
|
|
2330
|
+
const s = useFieldStrings();
|
|
2331
|
+
const titleId = useId();
|
|
2332
|
+
const extensionId = useId();
|
|
2333
|
+
const [value, setValue] = useState(stem2);
|
|
2334
|
+
const panel = useRef(null);
|
|
2335
|
+
const field = useRef(null);
|
|
2336
|
+
const pressedBackdrop = useRef(false);
|
|
2337
|
+
useEffect(() => {
|
|
2338
|
+
const opener = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
2339
|
+
field.current?.focus();
|
|
2340
|
+
field.current?.select();
|
|
2341
|
+
return () => {
|
|
2342
|
+
if (opener?.isConnected) opener.focus();
|
|
2343
|
+
};
|
|
2344
|
+
}, []);
|
|
2345
|
+
const onKeyDown = (event) => {
|
|
2346
|
+
event.stopPropagation();
|
|
2347
|
+
if (event.key === "Escape") {
|
|
2348
|
+
event.preventDefault();
|
|
2349
|
+
onCancel();
|
|
2350
|
+
return;
|
|
1789
2351
|
}
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
2352
|
+
if (event.key !== "Tab") return;
|
|
2353
|
+
const controls = Array.from(
|
|
2354
|
+
panel.current?.querySelectorAll("input, button") ?? []
|
|
2355
|
+
);
|
|
2356
|
+
const first = controls[0];
|
|
2357
|
+
const last = controls[controls.length - 1];
|
|
2358
|
+
if (!first || !last) return;
|
|
2359
|
+
if (event.shiftKey && document.activeElement === first) {
|
|
2360
|
+
event.preventDefault();
|
|
2361
|
+
last.focus();
|
|
2362
|
+
} else if (!event.shiftKey && document.activeElement === last) {
|
|
2363
|
+
event.preventDefault();
|
|
2364
|
+
first.focus();
|
|
2365
|
+
}
|
|
2366
|
+
};
|
|
2367
|
+
return /* @__PURE__ */ jsx(
|
|
2368
|
+
"div",
|
|
2369
|
+
{
|
|
2370
|
+
"data-file-name-backdrop": true,
|
|
2371
|
+
className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4",
|
|
2372
|
+
onMouseDown: (event) => {
|
|
2373
|
+
event.stopPropagation();
|
|
2374
|
+
pressedBackdrop.current = event.target === event.currentTarget;
|
|
2375
|
+
},
|
|
2376
|
+
onClick: (event) => {
|
|
2377
|
+
event.stopPropagation();
|
|
2378
|
+
if (pressedBackdrop.current && event.target === event.currentTarget) onCancel();
|
|
2379
|
+
pressedBackdrop.current = false;
|
|
2380
|
+
},
|
|
2381
|
+
children: /* @__PURE__ */ jsxs(
|
|
2382
|
+
"div",
|
|
2383
|
+
{
|
|
2384
|
+
ref: panel,
|
|
2385
|
+
role: "dialog",
|
|
2386
|
+
"aria-modal": "true",
|
|
2387
|
+
"aria-labelledby": titleId,
|
|
2388
|
+
onKeyDown,
|
|
2389
|
+
className: "w-full max-w-sm space-y-3 rounded-lg border border-border bg-popover p-4 text-popover-foreground shadow-lg",
|
|
2390
|
+
children: [
|
|
2391
|
+
/* @__PURE__ */ jsx("h2", { id: titleId, className: "text-[14px] font-medium", children: s.fileNameTitle }),
|
|
2392
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center rounded-md border border-border bg-input focus-within:outline-solid focus-within:outline-2 focus-within:outline-offset-1 focus-within:outline-ring", children: [
|
|
2393
|
+
/* @__PURE__ */ jsx(
|
|
2394
|
+
"input",
|
|
2395
|
+
{
|
|
2396
|
+
ref: field,
|
|
2397
|
+
type: "text",
|
|
2398
|
+
value,
|
|
2399
|
+
onChange: (event) => setValue(event.target.value),
|
|
2400
|
+
onKeyDown: (event) => {
|
|
2401
|
+
if (event.key !== "Enter") return;
|
|
2402
|
+
event.preventDefault();
|
|
2403
|
+
onConfirm(value);
|
|
2404
|
+
},
|
|
2405
|
+
"aria-labelledby": titleId,
|
|
2406
|
+
...extension ? { "aria-describedby": extensionId } : {},
|
|
2407
|
+
autoComplete: "off",
|
|
2408
|
+
spellCheck: false,
|
|
2409
|
+
className: "min-w-0 flex-1 bg-transparent px-2 py-1.5 text-[13px] text-foreground outline-none"
|
|
2410
|
+
}
|
|
2411
|
+
),
|
|
2412
|
+
extension ? /* @__PURE__ */ jsx("span", { id: extensionId, className: "shrink-0 pr-2 text-[13px] text-muted-foreground", children: extension }) : null
|
|
2413
|
+
] }),
|
|
2414
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
|
|
2415
|
+
/* @__PURE__ */ jsx(
|
|
2416
|
+
"button",
|
|
2417
|
+
{
|
|
2418
|
+
type: "button",
|
|
2419
|
+
onClick: onCancel,
|
|
2420
|
+
className: "rounded-md border border-border px-3 py-1 text-[12px] text-muted-foreground hover:text-foreground focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-1",
|
|
2421
|
+
children: s.fileNameCancel
|
|
2422
|
+
}
|
|
2423
|
+
),
|
|
2424
|
+
/* @__PURE__ */ jsx(
|
|
2425
|
+
"button",
|
|
2426
|
+
{
|
|
2427
|
+
type: "button",
|
|
2428
|
+
onClick: () => onConfirm(value),
|
|
2429
|
+
className: "rounded-md bg-primary px-3 py-1 text-[12px] font-medium text-primary-foreground hover:bg-primary/90 focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-1",
|
|
2430
|
+
children: s.fileNameConfirm
|
|
2431
|
+
}
|
|
2432
|
+
)
|
|
2433
|
+
] })
|
|
2434
|
+
]
|
|
2435
|
+
}
|
|
2436
|
+
)
|
|
2437
|
+
}
|
|
2438
|
+
);
|
|
1800
2439
|
}
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
2440
|
+
|
|
2441
|
+
// src/react/page-layout.ts
|
|
2442
|
+
var FIXED_ATTRIBUTE = "data-mthds-pdf-fixed";
|
|
2443
|
+
var ATOMIC_TAGS = /* @__PURE__ */ new Set([
|
|
2444
|
+
"IMG",
|
|
2445
|
+
"SVG",
|
|
2446
|
+
"CANVAS",
|
|
2447
|
+
"VIDEO",
|
|
2448
|
+
"IFRAME",
|
|
2449
|
+
"OBJECT",
|
|
2450
|
+
"EMBED",
|
|
2451
|
+
"INPUT",
|
|
2452
|
+
"TEXTAREA",
|
|
2453
|
+
"SELECT",
|
|
2454
|
+
"BUTTON",
|
|
2455
|
+
"HR",
|
|
2456
|
+
"PICTURE",
|
|
2457
|
+
"svg"
|
|
2458
|
+
]);
|
|
2459
|
+
var HEADING_TAGS = /* @__PURE__ */ new Set(["H1", "H2", "H3", "H4", "H5", "H6"]);
|
|
2460
|
+
var FORCED = /* @__PURE__ */ new Set(["page", "always", "left", "right", "recto", "verso"]);
|
|
2461
|
+
var AVOIDED = /* @__PURE__ */ new Set(["avoid", "avoid-page"]);
|
|
2462
|
+
function measureFlow(doc) {
|
|
2463
|
+
const win = doc.defaultView;
|
|
2464
|
+
const root = doc.documentElement;
|
|
2465
|
+
const offset = root.getBoundingClientRect().top;
|
|
2466
|
+
const y = (value) => value - offset;
|
|
2467
|
+
const spanOf = (el) => {
|
|
2468
|
+
const rect = el.getBoundingClientRect();
|
|
2469
|
+
return { top: y(rect.top), bottom: y(rect.bottom) };
|
|
2470
|
+
};
|
|
2471
|
+
const hard = [];
|
|
2472
|
+
const avoid = [];
|
|
2473
|
+
const forced = [];
|
|
2474
|
+
const tables = [];
|
|
2475
|
+
const fixed = [];
|
|
2476
|
+
const keepAfter = [];
|
|
2477
|
+
const keepBefore = [];
|
|
2478
|
+
const walk = (el) => {
|
|
2479
|
+
const style = win.getComputedStyle(el);
|
|
2480
|
+
if (style.display === "none") return;
|
|
2481
|
+
if (style.position === "fixed") {
|
|
2482
|
+
fixed.push(el);
|
|
2483
|
+
return;
|
|
2484
|
+
}
|
|
2485
|
+
const span = spanOf(el);
|
|
2486
|
+
const display = style.display;
|
|
2487
|
+
if (FORCED.has(style.breakBefore) || FORCED.has(style.getPropertyValue("page-break-before"))) {
|
|
2488
|
+
forced.push(span.top);
|
|
2489
|
+
}
|
|
2490
|
+
if (FORCED.has(style.breakAfter) || FORCED.has(style.getPropertyValue("page-break-after"))) {
|
|
2491
|
+
forced.push(span.bottom);
|
|
2492
|
+
}
|
|
2493
|
+
if (AVOIDED.has(style.breakAfter) || HEADING_TAGS.has(el.tagName)) keepAfter.push(span);
|
|
2494
|
+
if (AVOIDED.has(style.breakBefore)) keepBefore.push(span);
|
|
2495
|
+
const atomic = ATOMIC_TAGS.has(el.tagName) || display.startsWith("inline-");
|
|
2496
|
+
if (atomic) {
|
|
2497
|
+
if (span.bottom > span.top) hard.push(span);
|
|
2498
|
+
return;
|
|
2499
|
+
}
|
|
2500
|
+
if (AVOIDED.has(style.breakInside) || AVOIDED.has(style.getPropertyValue("page-break-inside")) || display === "table-row") {
|
|
2501
|
+
avoid.push(span);
|
|
2502
|
+
}
|
|
2503
|
+
if (display === "table" || display === "inline-table") {
|
|
2504
|
+
tables.push(tableBox(el, span));
|
|
2505
|
+
}
|
|
2506
|
+
for (const child of Array.from(el.childNodes)) {
|
|
2507
|
+
if (child.nodeType === 1) walk(child);
|
|
2508
|
+
else if (child.nodeType === 3 && /\S/.test(child.textContent ?? "")) {
|
|
2509
|
+
hard.push(...lineSpans(doc, child, y));
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
};
|
|
2513
|
+
const tableBox = (table, span) => {
|
|
2514
|
+
const box = { ...span };
|
|
2515
|
+
for (const part of Array.from(table.children)) {
|
|
2516
|
+
const display = win.getComputedStyle(part).display;
|
|
2517
|
+
if (display === "table-header-group" && !box.head) box.head = spanOf(part);
|
|
2518
|
+
if (display === "table-footer-group" && !box.foot) box.foot = spanOf(part);
|
|
2519
|
+
}
|
|
2520
|
+
return box;
|
|
2521
|
+
};
|
|
2522
|
+
walk(doc.body ?? root);
|
|
2523
|
+
fixed.forEach((el) => el.setAttribute(FIXED_ATTRIBUTE, ""));
|
|
2524
|
+
const lines = [...hard].sort((a, b) => a.top - b.top);
|
|
2525
|
+
const firstLineFrom = (at) => lines.find((line) => line.top >= at - 0.5);
|
|
2526
|
+
const lastLineBefore = (at) => [...lines].reverse().find((line) => line.bottom <= at + 0.5);
|
|
2527
|
+
for (const span of keepAfter) {
|
|
2528
|
+
const next = firstLineFrom(span.bottom);
|
|
2529
|
+
if (next) avoid.push({ top: span.top, bottom: next.bottom });
|
|
2530
|
+
}
|
|
2531
|
+
for (const span of keepBefore) {
|
|
2532
|
+
const previous = lastLineBefore(span.top);
|
|
2533
|
+
const first = firstLineFrom(span.top);
|
|
2534
|
+
if (previous && first) avoid.push({ top: previous.top, bottom: first.bottom });
|
|
2535
|
+
}
|
|
2536
|
+
for (const table of tables) {
|
|
2537
|
+
if (!table.head) continue;
|
|
2538
|
+
const next = firstLineFrom(table.head.bottom);
|
|
2539
|
+
if (next && next.bottom <= (table.foot?.top ?? table.bottom) + 0.5) {
|
|
2540
|
+
avoid.push({ top: table.top, bottom: next.bottom });
|
|
2541
|
+
}
|
|
2542
|
+
}
|
|
2543
|
+
return {
|
|
2544
|
+
layout: { height: root.scrollHeight, hard, avoid, forced, tables },
|
|
2545
|
+
fixed
|
|
2546
|
+
};
|
|
1812
2547
|
}
|
|
1813
|
-
function
|
|
1814
|
-
|
|
2548
|
+
function lineSpans(doc, text, y) {
|
|
2549
|
+
const range = doc.createRange();
|
|
2550
|
+
range.selectNodeContents(text);
|
|
2551
|
+
const spans = [];
|
|
2552
|
+
for (const rect of Array.from(range.getClientRects())) {
|
|
2553
|
+
if (rect.height === 0) continue;
|
|
2554
|
+
const span = { top: y(rect.top), bottom: y(rect.bottom) };
|
|
2555
|
+
const same = spans.find((line) => span.top < line.bottom && span.bottom > line.top);
|
|
2556
|
+
if (same) {
|
|
2557
|
+
same.top = Math.min(same.top, span.top);
|
|
2558
|
+
same.bottom = Math.max(same.bottom, span.bottom);
|
|
2559
|
+
} else spans.push(span);
|
|
2560
|
+
}
|
|
2561
|
+
return spans;
|
|
2562
|
+
}
|
|
2563
|
+
|
|
2564
|
+
// src/react/paginate.ts
|
|
2565
|
+
var EPSILON = 0.5;
|
|
2566
|
+
function paginate(layout, pageHeight) {
|
|
2567
|
+
const pages = [];
|
|
2568
|
+
const end = layout.height;
|
|
2569
|
+
const inkBottom = Math.max(0, ...layout.hard.map((span) => span.bottom));
|
|
2570
|
+
const forced = [...layout.forced].sort((a, b) => a - b);
|
|
2571
|
+
let start = 0;
|
|
2572
|
+
for (let guard = 0; start < end - EPSILON && guard < 1e4; guard++) {
|
|
2573
|
+
const heads = openAt(layout.tables, start).filter((table) => table.head && start >= table.head.bottom - EPSILON).map((table) => table.head);
|
|
2574
|
+
const room = pageHeight - total(heads);
|
|
2575
|
+
const nextForced = forced.find((at) => at > start + EPSILON && at < end - EPSILON);
|
|
2576
|
+
if (end - start <= room + EPSILON && nextForced === void 0) {
|
|
2577
|
+
pages.push({ heads, body: { top: start, bottom: end }, feet: [] });
|
|
2578
|
+
break;
|
|
2579
|
+
}
|
|
2580
|
+
let feet = [];
|
|
2581
|
+
let cut = start;
|
|
2582
|
+
for (let pass = 0; pass < 4; pass++) {
|
|
2583
|
+
const limit = start + room - total(feet);
|
|
2584
|
+
cut = nextForced !== void 0 && nextForced <= limit + EPSILON ? nextForced : bestCut(layout, start, limit);
|
|
2585
|
+
const needed = openAt(layout.tables, cut).filter((table) => table.foot).map((table) => table.foot).reverse();
|
|
2586
|
+
if (total(needed) <= total(feet) + EPSILON) {
|
|
2587
|
+
feet = needed;
|
|
2588
|
+
break;
|
|
2589
|
+
}
|
|
2590
|
+
feet = needed;
|
|
2591
|
+
}
|
|
2592
|
+
pages.push({ heads, body: { top: start, bottom: cut }, feet });
|
|
2593
|
+
start = cut;
|
|
2594
|
+
if (inkBottom <= cut + EPSILON) break;
|
|
2595
|
+
}
|
|
2596
|
+
return pages;
|
|
2597
|
+
}
|
|
2598
|
+
function bestCut(layout, start, limit) {
|
|
2599
|
+
const room = limit - start;
|
|
2600
|
+
const hard = layout.hard.filter((span) => span.bottom - span.top <= room);
|
|
2601
|
+
const avoid = layout.avoid.filter((span) => span.bottom - span.top <= room);
|
|
2602
|
+
const candidates = /* @__PURE__ */ new Set([limit]);
|
|
2603
|
+
for (const span of [...hard, ...avoid]) {
|
|
2604
|
+
candidates.add(span.top);
|
|
2605
|
+
candidates.add(span.bottom);
|
|
2606
|
+
}
|
|
2607
|
+
for (const table of layout.tables) {
|
|
2608
|
+
candidates.add(table.top);
|
|
2609
|
+
candidates.add(table.bottom);
|
|
2610
|
+
}
|
|
2611
|
+
const sorted = [...candidates].filter((at) => at > start + EPSILON && at <= limit + EPSILON).sort((a, b) => b - a);
|
|
2612
|
+
for (const constraints of [[...hard, ...avoid], hard]) {
|
|
2613
|
+
const cut = sorted.find((at) => !constraints.some((span) => inside(span, at)));
|
|
2614
|
+
if (cut !== void 0) return Math.min(cut, limit);
|
|
2615
|
+
}
|
|
2616
|
+
return limit;
|
|
2617
|
+
}
|
|
2618
|
+
function inside(span, at) {
|
|
2619
|
+
return at > span.top + EPSILON && at < span.bottom - EPSILON;
|
|
2620
|
+
}
|
|
2621
|
+
function openAt(tables, at) {
|
|
2622
|
+
return tables.filter((table) => at > table.top + EPSILON && at < (table.foot?.top ?? table.bottom) - EPSILON).sort((a, b) => a.top - b.top);
|
|
2623
|
+
}
|
|
2624
|
+
function total(spans) {
|
|
2625
|
+
return spans.reduce((sum, span) => sum + (span.bottom - span.top), 0);
|
|
2626
|
+
}
|
|
2627
|
+
|
|
2628
|
+
// src/react/print-media.ts
|
|
2629
|
+
var PX_PER_MM = 96 / 25.4;
|
|
2630
|
+
var PAPER = {
|
|
2631
|
+
a3: [297, 420],
|
|
2632
|
+
a4: [210, 297],
|
|
2633
|
+
a5: [148, 210],
|
|
2634
|
+
b4: [250, 353],
|
|
2635
|
+
b5: [176, 250],
|
|
2636
|
+
"jis-b4": [257, 364],
|
|
2637
|
+
"jis-b5": [182, 257],
|
|
2638
|
+
letter: [215.9, 279.4],
|
|
2639
|
+
legal: [215.9, 355.6],
|
|
2640
|
+
ledger: [279.4, 431.8]
|
|
2641
|
+
};
|
|
2642
|
+
var DEFAULT_PAGE_BOX = {
|
|
2643
|
+
width: 210 * PX_PER_MM,
|
|
2644
|
+
height: 297 * PX_PER_MM,
|
|
2645
|
+
margin: {
|
|
2646
|
+
top: 10 * PX_PER_MM,
|
|
2647
|
+
right: 10 * PX_PER_MM,
|
|
2648
|
+
bottom: 10 * PX_PER_MM,
|
|
2649
|
+
left: 10 * PX_PER_MM
|
|
2650
|
+
}
|
|
2651
|
+
};
|
|
2652
|
+
function promotePrintRules(doc) {
|
|
2653
|
+
for (const sheet of Array.from(doc.styleSheets)) {
|
|
2654
|
+
if (sheet.media.length > 0) rewriteMedia(sheet.media);
|
|
2655
|
+
let rules;
|
|
2656
|
+
try {
|
|
2657
|
+
rules = sheet.cssRules;
|
|
2658
|
+
} catch {
|
|
2659
|
+
continue;
|
|
2660
|
+
}
|
|
2661
|
+
rewriteRules(rules);
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
function rewriteRules(rules) {
|
|
2665
|
+
for (const rule of Array.from(rules)) {
|
|
2666
|
+
if (isMediaRule(rule)) rewriteMedia(rule.media);
|
|
2667
|
+
if ("cssRules" in rule) rewriteRules(rule.cssRules);
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
function isMediaRule(rule) {
|
|
2671
|
+
return rule.constructor.name === "CSSMediaRule" || rule.type === 4;
|
|
2672
|
+
}
|
|
2673
|
+
function rewriteMedia(media) {
|
|
2674
|
+
const queries = Array.from({ length: media.length }, (_, i) => media.item(i) ?? "");
|
|
2675
|
+
const printed = queries.map(onPaper);
|
|
2676
|
+
if (printed.every((query, i) => query === queries[i])) return;
|
|
2677
|
+
media.mediaText = printed.join(", ");
|
|
2678
|
+
}
|
|
2679
|
+
var MEDIA_QUERY = /^\s*(not\s+|only\s+)?([a-z][a-z-]*)(\s+and\s+[\s\S]*)?\s*$/i;
|
|
2680
|
+
function onPaper(query) {
|
|
2681
|
+
const match = MEDIA_QUERY.exec(query);
|
|
2682
|
+
if (!match) return query;
|
|
2683
|
+
const [, prefix = "", type = "", rest = ""] = match;
|
|
2684
|
+
const negated = /^not/i.test(prefix);
|
|
2685
|
+
const typeHolds = ["print", "all"].includes(type.toLowerCase());
|
|
2686
|
+
if (negated) return typeHolds ? `not all${rest}` : "all";
|
|
2687
|
+
return typeHolds ? `all${rest}` : "not all";
|
|
2688
|
+
}
|
|
2689
|
+
function readPageBox(doc) {
|
|
2690
|
+
let size = "";
|
|
2691
|
+
const margin = {};
|
|
2692
|
+
const visit = (rules) => {
|
|
2693
|
+
for (const rule of Array.from(rules)) {
|
|
2694
|
+
if (rule.constructor.name === "CSSPageRule" || rule.type === 6) {
|
|
2695
|
+
const page = rule;
|
|
2696
|
+
if (page.selectorText.trim() !== "") continue;
|
|
2697
|
+
size = page.style.getPropertyValue("size") || size;
|
|
2698
|
+
for (const side2 of ["top", "right", "bottom", "left"]) {
|
|
2699
|
+
const value = page.style.getPropertyValue(`margin-${side2}`);
|
|
2700
|
+
if (value) margin[side2] = value;
|
|
2701
|
+
}
|
|
2702
|
+
} else if (isMediaRule(rule)) {
|
|
2703
|
+
if (doc.defaultView?.matchMedia(rule.media.mediaText).matches !== false)
|
|
2704
|
+
visit(rule.cssRules);
|
|
2705
|
+
} else if ("cssRules" in rule) {
|
|
2706
|
+
visit(rule.cssRules);
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
};
|
|
2710
|
+
for (const sheet of Array.from(doc.styleSheets)) {
|
|
2711
|
+
try {
|
|
2712
|
+
visit(sheet.cssRules);
|
|
2713
|
+
} catch {
|
|
2714
|
+
}
|
|
2715
|
+
}
|
|
2716
|
+
const [width, height] = pageSize(size);
|
|
2717
|
+
const side = (name) => {
|
|
2718
|
+
const declared = margin[name];
|
|
2719
|
+
const px = declared === void 0 ? void 0 : toPx(declared);
|
|
2720
|
+
return px ?? DEFAULT_PAGE_BOX.margin[name];
|
|
2721
|
+
};
|
|
2722
|
+
return {
|
|
2723
|
+
width,
|
|
2724
|
+
height,
|
|
2725
|
+
margin: { top: side("top"), right: side("right"), bottom: side("bottom"), left: side("left") }
|
|
2726
|
+
};
|
|
2727
|
+
}
|
|
2728
|
+
function pageSize(value) {
|
|
2729
|
+
const words = value.trim().toLowerCase().split(/\s+/).filter(Boolean);
|
|
2730
|
+
const landscape = words.includes("landscape");
|
|
2731
|
+
const named = words.find((word) => word in PAPER);
|
|
2732
|
+
const lengths = words.map(toPx).filter((px) => px !== void 0);
|
|
2733
|
+
let width = DEFAULT_PAGE_BOX.width;
|
|
2734
|
+
let height = DEFAULT_PAGE_BOX.height;
|
|
2735
|
+
if (named) {
|
|
2736
|
+
[width, height] = PAPER[named].map((mm) => mm * PX_PER_MM);
|
|
2737
|
+
} else if (lengths.length === 2) {
|
|
2738
|
+
[width, height] = lengths;
|
|
2739
|
+
} else if (lengths.length === 1) {
|
|
2740
|
+
width = height = lengths[0];
|
|
2741
|
+
}
|
|
2742
|
+
if (landscape && width < height) [width, height] = [height, width];
|
|
2743
|
+
return [width, height];
|
|
2744
|
+
}
|
|
2745
|
+
var UNIT_PX = {
|
|
2746
|
+
px: 1,
|
|
2747
|
+
in: 96,
|
|
2748
|
+
cm: 10 * PX_PER_MM,
|
|
2749
|
+
mm: PX_PER_MM,
|
|
2750
|
+
q: PX_PER_MM / 4,
|
|
2751
|
+
pt: 96 / 72,
|
|
2752
|
+
pc: 16
|
|
2753
|
+
};
|
|
2754
|
+
function toPx(value) {
|
|
2755
|
+
const match = /^(-?\d*\.?\d+)([a-z]*)$/i.exec(value.trim());
|
|
2756
|
+
if (!match) return void 0;
|
|
2757
|
+
const amount = Number(match[1]);
|
|
2758
|
+
const unit = (match[2] || "px").toLowerCase();
|
|
2759
|
+
if (amount === 0) return 0;
|
|
2760
|
+
const factor = UNIT_PX[unit];
|
|
2761
|
+
return factor === void 0 ? void 0 : amount * factor;
|
|
2762
|
+
}
|
|
2763
|
+
|
|
2764
|
+
// src/react/html-pdf.ts
|
|
2765
|
+
var SETTLE_TIMEOUT_MS = 1e4;
|
|
2766
|
+
var PT_PER_PX = 72 / 96;
|
|
2767
|
+
async function renderHtmlPdf(content, {
|
|
2768
|
+
imgSrc = DEFAULT_IMG_SRC,
|
|
2769
|
+
fontSrc,
|
|
2770
|
+
fontFamily = "system-ui, sans-serif",
|
|
2771
|
+
title,
|
|
2772
|
+
scale = 3,
|
|
2773
|
+
quality = 0.92
|
|
2774
|
+
} = {}) {
|
|
2775
|
+
const libraries = Promise.all([import('modern-screenshot'), import('jspdf')]);
|
|
2776
|
+
const copy = await openCopy(content, {
|
|
2777
|
+
imgSrc,
|
|
2778
|
+
fontSrc: fontSrc ?? imgSrc,
|
|
2779
|
+
fontFamily
|
|
2780
|
+
});
|
|
2781
|
+
try {
|
|
2782
|
+
const { doc } = copy;
|
|
2783
|
+
promotePrintRules(doc);
|
|
2784
|
+
const page = readPageBox(doc);
|
|
2785
|
+
const area = {
|
|
2786
|
+
width: page.width - page.margin.left - page.margin.right,
|
|
2787
|
+
height: page.height - page.margin.top - page.margin.bottom
|
|
2788
|
+
};
|
|
2789
|
+
copy.frame.style.width = `${area.width}px`;
|
|
2790
|
+
copy.frame.style.height = `${area.height}px`;
|
|
2791
|
+
await nextFrame();
|
|
2792
|
+
await settle(doc);
|
|
2793
|
+
const { layout, fixed } = measureFlow(doc);
|
|
2794
|
+
const slices = paginate(layout, area.height);
|
|
2795
|
+
const [{ domToForeignObjectSvg }, { jsPDF }] = await libraries;
|
|
2796
|
+
const root = doc.documentElement;
|
|
2797
|
+
const notOurs = (node) => node.nodeType !== 1 || !node.id.startsWith("__SANDBOX__");
|
|
2798
|
+
const flow = await domToForeignObjectSvg(root, {
|
|
2799
|
+
width: area.width,
|
|
2800
|
+
height: layout.height,
|
|
2801
|
+
filter: (node) => notOurs(node) && !isFixed(node)
|
|
2802
|
+
});
|
|
2803
|
+
const overlay = fixed.length > 0 ? await paintFixed(doc, fixed, area, domToForeignObjectSvg, notOurs) : void 0;
|
|
2804
|
+
const landscape = page.width > page.height;
|
|
2805
|
+
const pdf = new jsPDF({
|
|
2806
|
+
unit: "pt",
|
|
2807
|
+
format: [page.width * PT_PER_PX, page.height * PT_PER_PX],
|
|
2808
|
+
orientation: landscape ? "landscape" : "portrait",
|
|
2809
|
+
compress: true
|
|
2810
|
+
});
|
|
2811
|
+
if (title) pdf.setProperties({ title });
|
|
2812
|
+
for (const [index, slice] of slices.entries()) {
|
|
2813
|
+
const jpeg = await paintPage(slice, page, area, scale, quality, flow, overlay);
|
|
2814
|
+
if (index > 0)
|
|
2815
|
+
pdf.addPage(
|
|
2816
|
+
[page.width * PT_PER_PX, page.height * PT_PER_PX],
|
|
2817
|
+
landscape ? "landscape" : "portrait"
|
|
2818
|
+
);
|
|
2819
|
+
pdf.addImage(
|
|
2820
|
+
jpeg,
|
|
2821
|
+
"JPEG",
|
|
2822
|
+
0,
|
|
2823
|
+
0,
|
|
2824
|
+
page.width * PT_PER_PX,
|
|
2825
|
+
page.height * PT_PER_PX,
|
|
2826
|
+
void 0,
|
|
2827
|
+
"NONE"
|
|
2828
|
+
);
|
|
2829
|
+
}
|
|
2830
|
+
return { blob: pdf.output("blob"), pageCount: slices.length, page };
|
|
2831
|
+
} finally {
|
|
2832
|
+
copy.frame.remove();
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
2835
|
+
function isFixed(node) {
|
|
2836
|
+
return node.nodeType === 1 && node.hasAttribute(FIXED_ATTRIBUTE);
|
|
2837
|
+
}
|
|
2838
|
+
async function paintFixed(doc, fixed, area, toSvg, notOurs) {
|
|
2839
|
+
const root = doc.documentElement;
|
|
2840
|
+
const keep = /* @__PURE__ */ new Set();
|
|
2841
|
+
for (const el of fixed) {
|
|
2842
|
+
for (let node = el; node; node = node.parentNode) keep.add(node);
|
|
2843
|
+
}
|
|
2844
|
+
const pins = new Map(fixed.map((el, i) => [String(i), el.getBoundingClientRect()]));
|
|
2845
|
+
fixed.forEach((el, i) => el.setAttribute(FIXED_ATTRIBUTE, String(i)));
|
|
2846
|
+
const previous = root.style.getPropertyValue("visibility");
|
|
2847
|
+
root.style.setProperty("visibility", "hidden", "important");
|
|
2848
|
+
fixed.forEach((el) => el.style.setProperty("visibility", "visible", "important"));
|
|
2849
|
+
try {
|
|
2850
|
+
return await toSvg(root, {
|
|
2851
|
+
width: area.width,
|
|
2852
|
+
height: area.height,
|
|
2853
|
+
filter: (node) => notOurs(node) && (keep.has(node) || insideFixed(node, fixed)),
|
|
2854
|
+
onCloneNode: (clone) => {
|
|
2855
|
+
const cloned = clone;
|
|
2856
|
+
cloned.style.setProperty("position", "relative");
|
|
2857
|
+
for (const el of Array.from(cloned.querySelectorAll(`[${FIXED_ATTRIBUTE}]`))) {
|
|
2858
|
+
const pin = pins.get(el.getAttribute(FIXED_ATTRIBUTE) ?? "");
|
|
2859
|
+
if (!pin) continue;
|
|
2860
|
+
Object.assign(el.style, {
|
|
2861
|
+
position: "absolute",
|
|
2862
|
+
top: `${pin.top}px`,
|
|
2863
|
+
left: `${pin.left}px`,
|
|
2864
|
+
width: `${pin.width}px`,
|
|
2865
|
+
height: `${pin.height}px`,
|
|
2866
|
+
right: "auto",
|
|
2867
|
+
bottom: "auto",
|
|
2868
|
+
margin: "0"
|
|
2869
|
+
});
|
|
2870
|
+
}
|
|
2871
|
+
}
|
|
2872
|
+
});
|
|
2873
|
+
} finally {
|
|
2874
|
+
if (previous) root.style.setProperty("visibility", previous);
|
|
2875
|
+
else root.style.removeProperty("visibility");
|
|
2876
|
+
fixed.forEach((el) => el.style.removeProperty("visibility"));
|
|
2877
|
+
}
|
|
2878
|
+
}
|
|
2879
|
+
function insideFixed(node, fixed) {
|
|
2880
|
+
return fixed.some((el) => el.contains(node));
|
|
2881
|
+
}
|
|
2882
|
+
async function paintPage(slice, page, area, scale, quality, flow, overlay) {
|
|
2883
|
+
const canvas = document.createElement("canvas");
|
|
2884
|
+
canvas.width = Math.round(page.width * scale);
|
|
2885
|
+
canvas.height = Math.round(page.height * scale);
|
|
2886
|
+
const context = canvas.getContext("2d");
|
|
2887
|
+
if (!context) throw new Error("No 2D canvas to paint the PDF page on");
|
|
2888
|
+
context.fillStyle = "#fff";
|
|
2889
|
+
context.fillRect(0, 0, canvas.width, canvas.height);
|
|
2890
|
+
let y = page.margin.top;
|
|
2891
|
+
for (const span of [...slice.heads, slice.body, ...slice.feet]) {
|
|
2892
|
+
const height = span.bottom - span.top;
|
|
2893
|
+
if (height <= 0) continue;
|
|
2894
|
+
const image = await svgImage(flow, { top: span.top, width: area.width, height }, scale);
|
|
2895
|
+
context.drawImage(
|
|
2896
|
+
image,
|
|
2897
|
+
page.margin.left * scale,
|
|
2898
|
+
y * scale,
|
|
2899
|
+
area.width * scale,
|
|
2900
|
+
height * scale
|
|
2901
|
+
);
|
|
2902
|
+
y += height;
|
|
2903
|
+
}
|
|
2904
|
+
if (overlay) {
|
|
2905
|
+
const image = await svgImage(overlay, { top: 0, ...area }, scale);
|
|
2906
|
+
context.drawImage(
|
|
2907
|
+
image,
|
|
2908
|
+
page.margin.left * scale,
|
|
2909
|
+
page.margin.top * scale,
|
|
2910
|
+
area.width * scale,
|
|
2911
|
+
area.height * scale
|
|
2912
|
+
);
|
|
2913
|
+
}
|
|
2914
|
+
const blob = await new Promise(
|
|
2915
|
+
(resolve) => canvas.toBlob(resolve, "image/jpeg", quality)
|
|
2916
|
+
);
|
|
2917
|
+
if (!blob) throw new Error("The PDF page could not be encoded");
|
|
2918
|
+
return new Uint8Array(await blob.arrayBuffer());
|
|
2919
|
+
}
|
|
2920
|
+
async function svgImage(svg, { top, width, height }, scale) {
|
|
2921
|
+
const copy = svg.cloneNode(false);
|
|
2922
|
+
copy.setAttribute("viewBox", `0 ${top} ${width} ${height}`);
|
|
2923
|
+
copy.setAttribute("width", String(Math.max(1, Math.round(width * scale))));
|
|
2924
|
+
copy.setAttribute("height", String(Math.max(1, Math.round(height * scale))));
|
|
2925
|
+
copy.setAttribute("preserveAspectRatio", "none");
|
|
2926
|
+
for (const child of Array.from(svg.childNodes)) copy.appendChild(child.cloneNode(true));
|
|
2927
|
+
const object = Array.from(copy.children).find((child) => child.localName === "foreignObject");
|
|
2928
|
+
object?.setAttribute("width", svg.getAttribute("width") ?? String(width));
|
|
2929
|
+
object?.setAttribute("height", svg.getAttribute("height") ?? String(top + height));
|
|
2930
|
+
const markup = new XMLSerializer().serializeToString(copy).replace(/[\u0000-\u0008\v\f\u000E-\u001F]/g, "");
|
|
2931
|
+
const image = new Image();
|
|
2932
|
+
image.decoding = "sync";
|
|
2933
|
+
image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(markup)}`;
|
|
2934
|
+
await image.decode();
|
|
2935
|
+
return image;
|
|
2936
|
+
}
|
|
2937
|
+
async function openCopy(content, { imgSrc, fontSrc, fontFamily }) {
|
|
2938
|
+
const frame = document.createElement("iframe");
|
|
2939
|
+
frame.setAttribute("sandbox", "allow-same-origin");
|
|
2940
|
+
frame.setAttribute("aria-hidden", "true");
|
|
2941
|
+
frame.setAttribute("data-mthds-pdf-copy", "");
|
|
2942
|
+
frame.tabIndex = -1;
|
|
2943
|
+
frame.title = "PDF";
|
|
2944
|
+
Object.assign(frame.style, {
|
|
2945
|
+
position: "fixed",
|
|
2946
|
+
left: "-10000px",
|
|
2947
|
+
top: "0",
|
|
2948
|
+
width: "794px",
|
|
2949
|
+
height: "1123px",
|
|
2950
|
+
border: "0",
|
|
2951
|
+
opacity: "0",
|
|
2952
|
+
pointerEvents: "none"
|
|
2953
|
+
});
|
|
2954
|
+
frame.srcdoc = frameDocument(content, {
|
|
2955
|
+
styles: frameStyles(pageTypography(fontFamily)),
|
|
2956
|
+
imgSrc,
|
|
2957
|
+
fontSrc
|
|
2958
|
+
});
|
|
2959
|
+
const loaded = new Promise(
|
|
2960
|
+
(resolve) => frame.addEventListener("load", () => resolve(), { once: true })
|
|
2961
|
+
);
|
|
2962
|
+
document.body.append(frame);
|
|
2963
|
+
try {
|
|
2964
|
+
await loaded;
|
|
2965
|
+
const doc = frame.contentDocument;
|
|
2966
|
+
if (!doc?.defaultView) throw new Error("The PDF copy could not be opened");
|
|
2967
|
+
await settle(doc);
|
|
2968
|
+
return { frame, doc };
|
|
2969
|
+
} catch (error) {
|
|
2970
|
+
frame.remove();
|
|
2971
|
+
throw error;
|
|
2972
|
+
}
|
|
2973
|
+
}
|
|
2974
|
+
async function settle(doc) {
|
|
2975
|
+
const images = Array.from(doc.images, (image) => {
|
|
2976
|
+
image.loading = "eager";
|
|
2977
|
+
if (image.complete) return Promise.resolve();
|
|
2978
|
+
return new Promise((resolve) => {
|
|
2979
|
+
image.addEventListener("load", () => resolve(), { once: true });
|
|
2980
|
+
image.addEventListener("error", () => resolve(), { once: true });
|
|
2981
|
+
});
|
|
2982
|
+
});
|
|
2983
|
+
const fonts = doc.fonts ? [
|
|
2984
|
+
...Array.from(doc.fonts, (face) => face.load().then(noop, noop)),
|
|
2985
|
+
doc.fonts.ready.then(noop, noop)
|
|
2986
|
+
] : [];
|
|
2987
|
+
let timer;
|
|
2988
|
+
await Promise.race([
|
|
2989
|
+
Promise.all([...images, ...fonts]),
|
|
2990
|
+
new Promise((resolve) => {
|
|
2991
|
+
timer = setTimeout(resolve, SETTLE_TIMEOUT_MS);
|
|
2992
|
+
})
|
|
2993
|
+
]);
|
|
2994
|
+
clearTimeout(timer);
|
|
2995
|
+
}
|
|
2996
|
+
function nextFrame() {
|
|
2997
|
+
return new Promise((resolve) => {
|
|
2998
|
+
const timer = setTimeout(resolve, 100);
|
|
2999
|
+
requestAnimationFrame(() => {
|
|
3000
|
+
clearTimeout(timer);
|
|
3001
|
+
resolve();
|
|
3002
|
+
});
|
|
3003
|
+
});
|
|
3004
|
+
}
|
|
3005
|
+
function noop() {
|
|
3006
|
+
}
|
|
3007
|
+
function PdfDownloadButton({
|
|
3008
|
+
content,
|
|
3009
|
+
fileName,
|
|
3010
|
+
path = "page",
|
|
3011
|
+
imgSrc,
|
|
3012
|
+
fontSrc,
|
|
3013
|
+
primary = false,
|
|
3014
|
+
className
|
|
3015
|
+
}) {
|
|
3016
|
+
const s = useFieldStrings();
|
|
3017
|
+
const save = useSaveFiles();
|
|
3018
|
+
const resolvers = useMarkupResolvers();
|
|
3019
|
+
const ref = useRef(null);
|
|
3020
|
+
const statusId = useId();
|
|
3021
|
+
const [state, setState] = useState("idle");
|
|
3022
|
+
const failed = state === "failed";
|
|
3023
|
+
const { ask, dialog } = useFileNamePrompt();
|
|
3024
|
+
const name = `${pdfStem(fileName) || "page"}.pdf`;
|
|
3025
|
+
const download = async () => {
|
|
3026
|
+
const chosen = await ask(pdfStem(fileName) || "page", ".pdf");
|
|
3027
|
+
if (chosen === null) return;
|
|
3028
|
+
const title = chosen === name ? pdfStem(fileName) : pdfStem(chosen);
|
|
3029
|
+
setState("busy");
|
|
3030
|
+
try {
|
|
3031
|
+
const fontFamily = ref.current ? getComputedStyle(ref.current).fontFamily : void 0;
|
|
3032
|
+
const innerHtml = await resolveHtmlStorageUrls(content.innerHtml, resolvers);
|
|
3033
|
+
const page = innerHtml === content.innerHtml ? content : { ...content, innerHtml };
|
|
3034
|
+
const { blob } = await renderHtmlPdf(page, {
|
|
3035
|
+
...title ? { title } : {},
|
|
3036
|
+
...imgSrc === void 0 ? {} : { imgSrc },
|
|
3037
|
+
...fontSrc === void 0 ? {} : { fontSrc },
|
|
3038
|
+
...fontFamily ? { fontFamily } : {}
|
|
3039
|
+
});
|
|
3040
|
+
const { failed: missed } = await save([
|
|
3041
|
+
{
|
|
3042
|
+
name: chosen,
|
|
3043
|
+
mimeType: "application/pdf",
|
|
3044
|
+
kind: "markup",
|
|
3045
|
+
path,
|
|
3046
|
+
url: await dataUrl(blob)
|
|
3047
|
+
}
|
|
3048
|
+
]);
|
|
3049
|
+
setState(missed.length > 0 ? "failed" : "idle");
|
|
3050
|
+
} catch {
|
|
3051
|
+
setState("failed");
|
|
3052
|
+
}
|
|
3053
|
+
};
|
|
3054
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3055
|
+
/* @__PURE__ */ jsxs(
|
|
3056
|
+
"button",
|
|
3057
|
+
{
|
|
3058
|
+
ref,
|
|
3059
|
+
type: "button",
|
|
3060
|
+
"data-pdf-download": true,
|
|
3061
|
+
onClick: () => void download(),
|
|
3062
|
+
disabled: state === "busy",
|
|
3063
|
+
"aria-busy": state === "busy",
|
|
3064
|
+
title: failed ? s.downloadPdfFailed : name,
|
|
3065
|
+
...failed ? { "aria-describedby": statusId } : {},
|
|
3066
|
+
className: cn(
|
|
3067
|
+
"inline-flex shrink-0 items-center gap-1.5 rounded-md px-2 py-1 text-[12px] font-medium focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-1 disabled:opacity-70",
|
|
3068
|
+
primary ? "bg-primary text-primary-foreground hover:bg-primary/90" : "border border-border text-muted-foreground hover:text-foreground",
|
|
3069
|
+
failed && (primary ? "bg-destructive text-white hover:bg-destructive/90" : "text-destructive"),
|
|
3070
|
+
className
|
|
3071
|
+
),
|
|
3072
|
+
children: [
|
|
3073
|
+
state === "busy" ? /* @__PURE__ */ jsx(Loader2, { "aria-hidden": true, className: "size-3.5 animate-spin" }) : failed ? /* @__PURE__ */ jsx(TriangleAlert, { "aria-hidden": true, className: "size-3.5" }) : /* @__PURE__ */ jsx(FileDown, { "aria-hidden": true, className: "size-3.5" }),
|
|
3074
|
+
state === "busy" ? s.downloadPdfBusy : s.downloadPdf
|
|
3075
|
+
]
|
|
3076
|
+
}
|
|
3077
|
+
),
|
|
3078
|
+
/* @__PURE__ */ jsx("span", { id: statusId, role: "status", className: "sr-only", children: failed ? s.downloadPdfFailed : "" }),
|
|
3079
|
+
dialog
|
|
3080
|
+
] });
|
|
3081
|
+
}
|
|
3082
|
+
function pdfStem(name) {
|
|
3083
|
+
return (name ?? "").trim().replace(/\.pdf$/i, "").trim();
|
|
3084
|
+
}
|
|
3085
|
+
function dataUrl(blob) {
|
|
3086
|
+
return new Promise((resolve, reject) => {
|
|
3087
|
+
const reader = new FileReader();
|
|
3088
|
+
reader.onload = () => resolve(String(reader.result));
|
|
3089
|
+
reader.onerror = () => reject(reader.error ?? new Error("The PDF could not be read"));
|
|
3090
|
+
reader.readAsDataURL(blob);
|
|
3091
|
+
});
|
|
1815
3092
|
}
|
|
1816
3093
|
var useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
3094
|
+
var FRAGMENT_MAX_HEIGHT = 1400;
|
|
3095
|
+
var FILL_MIN_HEIGHT = 320;
|
|
1817
3096
|
function HtmlPreview({
|
|
1818
3097
|
content,
|
|
1819
|
-
maxHeight
|
|
1820
|
-
imgSrc = DEFAULT_IMG_SRC
|
|
3098
|
+
maxHeight,
|
|
3099
|
+
imgSrc = DEFAULT_IMG_SRC,
|
|
3100
|
+
fontSrc,
|
|
3101
|
+
downloadPdf,
|
|
3102
|
+
pdfFileName: pdfFileName2,
|
|
3103
|
+
bare = false,
|
|
3104
|
+
fill = false
|
|
1821
3105
|
}) {
|
|
1822
3106
|
const hostRef = useRef(null);
|
|
1823
3107
|
const frameRef = useRef(null);
|
|
3108
|
+
const s = useFieldStrings();
|
|
1824
3109
|
const [doc, setDoc] = useState(null);
|
|
1825
3110
|
const [height, setHeight] = useState(0);
|
|
3111
|
+
const resolvedHtml = useResolvedMarkup(content.innerHtml);
|
|
3112
|
+
const resolved = useMemo(
|
|
3113
|
+
() => resolvedHtml === void 0 ? void 0 : resolvedHtml === content.innerHtml ? content : { ...content, innerHtml: resolvedHtml },
|
|
3114
|
+
[content, resolvedHtml]
|
|
3115
|
+
);
|
|
3116
|
+
const page = isWholeDocument(content.innerHtml);
|
|
3117
|
+
const pdfSetting = usePdfSaveShown();
|
|
3118
|
+
const pdfShown = downloadPdf ?? pdfSetting;
|
|
3119
|
+
const limit = maxHeight ?? (page ? void 0 : FRAGMENT_MAX_HEIGHT);
|
|
3120
|
+
const filling = page && fill;
|
|
1826
3121
|
useIsomorphicLayoutEffect(() => {
|
|
1827
3122
|
const host = hostRef.current;
|
|
1828
|
-
if (!host) return;
|
|
3123
|
+
if (!host || !resolved) return;
|
|
1829
3124
|
const computed = getComputedStyle(host);
|
|
3125
|
+
const typography = page ? pageTypography(computed.fontFamily) : {
|
|
3126
|
+
color: computed.color,
|
|
3127
|
+
mutedColor: computed.getPropertyValue("--muted-foreground").trim() || computed.color,
|
|
3128
|
+
borderColor: computed.borderColor || computed.color,
|
|
3129
|
+
font: computed.fontFamily
|
|
3130
|
+
};
|
|
1830
3131
|
setDoc(
|
|
1831
|
-
frameDocument(
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
computed.borderColor || computed.color,
|
|
1837
|
-
computed.fontFamily
|
|
1838
|
-
),
|
|
1839
|
-
imgSrc
|
|
1840
|
-
)
|
|
3132
|
+
frameDocument(resolved, {
|
|
3133
|
+
styles: frameStyles(typography),
|
|
3134
|
+
imgSrc,
|
|
3135
|
+
fontSrc: fontSrc ?? imgSrc
|
|
3136
|
+
})
|
|
1841
3137
|
);
|
|
1842
|
-
}, [
|
|
3138
|
+
}, [resolved, imgSrc, fontSrc, page]);
|
|
1843
3139
|
const measure = () => {
|
|
1844
|
-
const
|
|
1845
|
-
|
|
3140
|
+
const frameDoc = frameRef.current?.contentDocument;
|
|
3141
|
+
const body = frameDoc?.body;
|
|
3142
|
+
if (!frameDoc || !body) return;
|
|
3143
|
+
setHeight(Math.max(body.scrollHeight, frameDoc.documentElement.scrollHeight));
|
|
3144
|
+
};
|
|
3145
|
+
const onLoad = () => {
|
|
3146
|
+
measure();
|
|
3147
|
+
void frameRef.current?.contentDocument?.fonts?.ready.then(measure, () => void 0);
|
|
1846
3148
|
};
|
|
1847
3149
|
useEffect(() => {
|
|
1848
3150
|
if (!doc || !hostRef.current || typeof ResizeObserver === "undefined") return;
|
|
@@ -1850,30 +3152,110 @@ function HtmlPreview({
|
|
|
1850
3152
|
observer.observe(hostRef.current);
|
|
1851
3153
|
return () => observer.disconnect();
|
|
1852
3154
|
}, [doc]);
|
|
1853
|
-
|
|
3155
|
+
const frameBox = /* @__PURE__ */ jsx(
|
|
1854
3156
|
"div",
|
|
1855
3157
|
{
|
|
1856
3158
|
ref: hostRef,
|
|
1857
|
-
|
|
1858
|
-
|
|
3159
|
+
...page ? { "data-html-page": "" } : {},
|
|
3160
|
+
className: cn(
|
|
3161
|
+
page ? bare ? "overflow-hidden bg-white text-[13px] text-black" : "overflow-hidden rounded-lg border border-border bg-white text-[13px] text-black" : "overflow-hidden rounded-lg border border-border bg-card/40 px-3.5 py-3 text-[13px] text-foreground",
|
|
3162
|
+
// A flex column that may shrink, so the frame inside can.
|
|
3163
|
+
filling && "flex min-h-0 flex-col"
|
|
3164
|
+
),
|
|
3165
|
+
children: resolved === void 0 ? (
|
|
3166
|
+
// The page's stored pictures are being resolved: a short wait, said
|
|
3167
|
+
// as such, rather than a page painted with broken images first.
|
|
3168
|
+
/* @__PURE__ */ jsxs(
|
|
3169
|
+
"div",
|
|
3170
|
+
{
|
|
3171
|
+
role: "status",
|
|
3172
|
+
"data-html-resolving": true,
|
|
3173
|
+
className: "flex min-h-24 items-center justify-center gap-2 text-[12px] text-muted-foreground",
|
|
3174
|
+
children: [
|
|
3175
|
+
/* @__PURE__ */ jsx(Loader2, { "aria-hidden": true, className: "size-4 animate-spin" }),
|
|
3176
|
+
/* @__PURE__ */ jsx("span", { children: s.pageLoading })
|
|
3177
|
+
]
|
|
3178
|
+
}
|
|
3179
|
+
)
|
|
3180
|
+
) : doc === null ? null : /* @__PURE__ */ jsx(
|
|
1859
3181
|
"iframe",
|
|
1860
3182
|
{
|
|
1861
3183
|
ref: frameRef,
|
|
1862
3184
|
title: content.cssClass ? `HTML result (${content.cssClass})` : "HTML result",
|
|
1863
3185
|
srcDoc: doc,
|
|
1864
|
-
onLoad
|
|
3186
|
+
onLoad,
|
|
1865
3187
|
sandbox: "allow-same-origin",
|
|
1866
3188
|
style: {
|
|
1867
3189
|
display: "block",
|
|
1868
3190
|
width: "100%",
|
|
1869
3191
|
border: 0,
|
|
1870
|
-
height: height === 0 ? void 0 : Math.min(height,
|
|
1871
|
-
maxHeight
|
|
3192
|
+
height: height === 0 ? void 0 : limit === void 0 ? height : Math.min(height, limit),
|
|
3193
|
+
...limit === void 0 ? {} : { maxHeight: limit },
|
|
3194
|
+
// Its height is the page's, as a flex basis: in a bounded flex
|
|
3195
|
+
// column it shrinks to what is left (never below the floor) and the
|
|
3196
|
+
// page scrolls inside the frame; anywhere else it is the page's.
|
|
3197
|
+
// `minHeight` is explicit because a replaced element's automatic
|
|
3198
|
+
// minimum is its own height, which would forbid the shrink.
|
|
3199
|
+
...filling && height > 0 ? { flex: "0 1 auto", minHeight: Math.min(height, FILL_MIN_HEIGHT) } : {}
|
|
1872
3200
|
}
|
|
1873
3201
|
}
|
|
1874
3202
|
)
|
|
1875
3203
|
}
|
|
1876
3204
|
);
|
|
3205
|
+
if (!pdfShown) return frameBox;
|
|
3206
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
3207
|
+
/* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx(
|
|
3208
|
+
PdfDownloadButton,
|
|
3209
|
+
{
|
|
3210
|
+
content,
|
|
3211
|
+
imgSrc,
|
|
3212
|
+
...fontSrc === void 0 ? {} : { fontSrc },
|
|
3213
|
+
...pdfFileName2 === void 0 ? {} : { fileName: pdfFileName2 }
|
|
3214
|
+
}
|
|
3215
|
+
) }),
|
|
3216
|
+
frameBox
|
|
3217
|
+
] });
|
|
3218
|
+
}
|
|
3219
|
+
function useResolvedMarkup(markup) {
|
|
3220
|
+
const resolvers = useMarkupResolvers();
|
|
3221
|
+
const needed = (resolvers.resolveUrl !== void 0 || resolvers.resolveUrls !== void 0) && mayHoldStorageRefs(markup);
|
|
3222
|
+
const [shown, setShown] = useState(null);
|
|
3223
|
+
useEffect(() => {
|
|
3224
|
+
if (!needed) return;
|
|
3225
|
+
let live = true;
|
|
3226
|
+
const show = (bulk) => {
|
|
3227
|
+
if (!live) return;
|
|
3228
|
+
let html = markup;
|
|
3229
|
+
try {
|
|
3230
|
+
html = rewriteStorageRefs(markup, answerFor(bulk, resolvers.resolveUrl));
|
|
3231
|
+
} catch {
|
|
3232
|
+
}
|
|
3233
|
+
setShown({ source: markup, html });
|
|
3234
|
+
};
|
|
3235
|
+
let refs = [];
|
|
3236
|
+
try {
|
|
3237
|
+
refs = storageRefsIn(markup);
|
|
3238
|
+
} catch {
|
|
3239
|
+
refs = [];
|
|
3240
|
+
}
|
|
3241
|
+
if (refs.length === 0 || !resolvers.resolveUrls) {
|
|
3242
|
+
show();
|
|
3243
|
+
return () => {
|
|
3244
|
+
live = false;
|
|
3245
|
+
};
|
|
3246
|
+
}
|
|
3247
|
+
const timer = setTimeout(() => show(), MARKUP_RESOLVE_TIMEOUT_MS);
|
|
3248
|
+
void askBulkUntimed(resolvers.resolveUrls, refs).then((bulk) => {
|
|
3249
|
+
clearTimeout(timer);
|
|
3250
|
+
show(bulk);
|
|
3251
|
+
});
|
|
3252
|
+
return () => {
|
|
3253
|
+
live = false;
|
|
3254
|
+
clearTimeout(timer);
|
|
3255
|
+
};
|
|
3256
|
+
}, [needed, markup, resolvers]);
|
|
3257
|
+
if (!needed) return markup;
|
|
3258
|
+
return shown?.source === markup ? shown.html : void 0;
|
|
1877
3259
|
}
|
|
1878
3260
|
var LEX_OPTIONS = { gfm: true, breaks: true };
|
|
1879
3261
|
function Markdown({ text, className, proseImages }) {
|
|
@@ -2325,6 +3707,7 @@ function FileDownloadButton({ file }) {
|
|
|
2325
3707
|
const resolve = useResolveUrl();
|
|
2326
3708
|
const save = useSaveFiles();
|
|
2327
3709
|
const shown = useFileDownloadShown(file.kind);
|
|
3710
|
+
const { ask, dialog } = useFileNamePrompt();
|
|
2328
3711
|
const statusId = useId();
|
|
2329
3712
|
const [attempt, setAttempt] = useState(null);
|
|
2330
3713
|
useEffect(() => {
|
|
@@ -2351,13 +3734,15 @@ ${planned.url ?? planned.text}`;
|
|
|
2351
3734
|
"button",
|
|
2352
3735
|
{
|
|
2353
3736
|
type: "button",
|
|
2354
|
-
onClick: () => {
|
|
3737
|
+
onClick: async () => {
|
|
3738
|
+
const { stem: stem2, extension } = splitFileName(planned.name);
|
|
3739
|
+
const name = await ask(stem2, extension);
|
|
3740
|
+
if (name === null) return;
|
|
2355
3741
|
const started = { identity, state: "saving" };
|
|
2356
3742
|
setAttempt(started);
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
});
|
|
3743
|
+
const result = await save([name === planned.name ? planned : { ...planned, name }]);
|
|
3744
|
+
const settled = result.failed.length > 0 ? "failed" : "saved";
|
|
3745
|
+
setAttempt((current) => current === started ? { identity, state: settled } : current);
|
|
2361
3746
|
},
|
|
2362
3747
|
disabled: state === "saving",
|
|
2363
3748
|
"aria-label": label,
|
|
@@ -2370,7 +3755,8 @@ ${planned.url ?? planned.text}`;
|
|
|
2370
3755
|
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" })
|
|
2371
3756
|
}
|
|
2372
3757
|
),
|
|
2373
|
-
/* @__PURE__ */ jsx("span", { id: statusId, role: "status", className: "sr-only", children: failed ? s.downloadFileFailed : "" })
|
|
3758
|
+
/* @__PURE__ */ jsx("span", { id: statusId, role: "status", className: "sr-only", children: failed ? s.downloadFileFailed : "" }),
|
|
3759
|
+
dialog
|
|
2374
3760
|
] });
|
|
2375
3761
|
}
|
|
2376
3762
|
function paintable(content, resolve) {
|
|
@@ -2807,7 +4193,7 @@ function CompositeValue({ members }) {
|
|
|
2807
4193
|
function NativeValue({ value }) {
|
|
2808
4194
|
if (value === null || value === void 0 || value === "") return /* @__PURE__ */ jsx(Absent, {});
|
|
2809
4195
|
const html = readHtmlContent(value);
|
|
2810
|
-
if (html) return /* @__PURE__ */ jsx(HtmlPreview, { content: html });
|
|
4196
|
+
if (html) return /* @__PURE__ */ jsx(HtmlPreview, { content: html, downloadPdf: false });
|
|
2811
4197
|
const date = readDateContent(value);
|
|
2812
4198
|
if (date) return /* @__PURE__ */ jsx("span", { className: "text-[13px] text-foreground", children: formatDateContent(date) });
|
|
2813
4199
|
const document2 = readDocumentContent(value);
|
|
@@ -2887,6 +4273,7 @@ function ResultNode({ field, value, depth = 0, hideLabel = false }) {
|
|
|
2887
4273
|
const s = useFieldStrings();
|
|
2888
4274
|
const unwrapped = unwrap(field, value);
|
|
2889
4275
|
const pageDownloadShown = useFileDownloadShown("markup");
|
|
4276
|
+
const pdfShown = usePdfSaveShown();
|
|
2890
4277
|
const location = useResultLocation();
|
|
2891
4278
|
const header = hideLabel ? null : /* @__PURE__ */ jsx(Label2, { field, describe: true });
|
|
2892
4279
|
const copyText = (field.kind === "text" || field.kind === "prose") && typeof unwrapped === "string" && unwrapped !== "" ? unwrapped : void 0;
|
|
@@ -2898,13 +4285,24 @@ function ResultNode({ field, value, depth = 0, hideLabel = false }) {
|
|
|
2898
4285
|
}
|
|
2899
4286
|
if (isNativeHtmlNode(field)) {
|
|
2900
4287
|
const content = readHtmlContent(unwrapped);
|
|
2901
|
-
const
|
|
4288
|
+
const pagePath = location?.path.join(".");
|
|
4289
|
+
const pageFile = pagePath === void 0 ? void 0 : readStuffFile("markup", unwrapped, pagePath);
|
|
4290
|
+
const page = pageDownloadShown ? pageFile : void 0;
|
|
4291
|
+
const pdfName = pdfShown && content && pageFile && location ? stem(planFileSave(pageFile, { baseName: location.baseName })?.name) : void 0;
|
|
2902
4292
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
2903
|
-
page ? /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
|
|
4293
|
+
page || pdfName !== void 0 ? /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
|
|
2904
4294
|
/* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: header }),
|
|
4295
|
+
content && pdfName !== void 0 ? /* @__PURE__ */ jsx(
|
|
4296
|
+
PdfDownloadButton,
|
|
4297
|
+
{
|
|
4298
|
+
content,
|
|
4299
|
+
...pagePath === void 0 ? {} : { path: pagePath },
|
|
4300
|
+
...pdfName ? { fileName: pdfName } : {}
|
|
4301
|
+
}
|
|
4302
|
+
) : null,
|
|
2905
4303
|
/* @__PURE__ */ jsx(FileActions, { file: page })
|
|
2906
4304
|
] }) : header,
|
|
2907
|
-
content ? /* @__PURE__ */ jsx(HtmlPreview, { content }) : /* @__PURE__ */ jsx(Absent, {})
|
|
4305
|
+
content ? /* @__PURE__ */ jsx(HtmlPreview, { content, downloadPdf: false }) : /* @__PURE__ */ jsx(Absent, {})
|
|
2908
4306
|
] });
|
|
2909
4307
|
}
|
|
2910
4308
|
if (isNativeCompositeNode(field)) {
|
|
@@ -3035,6 +4433,11 @@ function ResultNode({ field, value, depth = 0, hideLabel = false }) {
|
|
|
3035
4433
|
}
|
|
3036
4434
|
return null;
|
|
3037
4435
|
}
|
|
4436
|
+
function stem(name) {
|
|
4437
|
+
return name === void 0 ? "" : name.replace(/\.[^.]+$/, "");
|
|
4438
|
+
}
|
|
4439
|
+
var ALL_VIEWS = ["rendered", "json"];
|
|
4440
|
+
var RESULT_ONLY = ["rendered"];
|
|
3038
4441
|
var JSON_TOKEN_RE = /("(?:\\.|[^"\\])*"\s*:)|("(?:\\.|[^"\\])*")|(\b(?:true|false|null)\b|-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)/g;
|
|
3039
4442
|
function highlight(json) {
|
|
3040
4443
|
const out = [];
|
|
@@ -3117,14 +4520,18 @@ function StuffPanel({
|
|
|
3117
4520
|
field,
|
|
3118
4521
|
value,
|
|
3119
4522
|
name,
|
|
4523
|
+
views: viewsProp,
|
|
3120
4524
|
defaultView = "rendered",
|
|
3121
4525
|
downloadBaseName,
|
|
3122
4526
|
className
|
|
3123
4527
|
}) {
|
|
3124
4528
|
const s = useFieldStrings();
|
|
4529
|
+
const presentation = useFieldPresentation();
|
|
4530
|
+
const offered = viewsProp && viewsProp.length > 0 ? viewsProp : viewsProp || presentation === "app" ? RESULT_ONLY : ALL_VIEWS;
|
|
3125
4531
|
const named = name ? { ...field, name } : field;
|
|
3126
4532
|
const baseName = downloadBaseName ?? named.name ?? "result";
|
|
3127
|
-
const [
|
|
4533
|
+
const [chosen, setView] = useState(defaultView);
|
|
4534
|
+
const view = offered.includes(chosen) ? chosen : offered[0] ?? "rendered";
|
|
3128
4535
|
const [saving, setSaving] = useState(null);
|
|
3129
4536
|
const [missed, setMissed] = useState(null);
|
|
3130
4537
|
const latest = useRef(null);
|
|
@@ -3132,8 +4539,28 @@ function StuffPanel({
|
|
|
3132
4539
|
const missedShown = missed && isShownResult(missed.attempt, field, baseName, value) ? missed.names : [];
|
|
3133
4540
|
const resolveUrl = useResolveUrl();
|
|
3134
4541
|
const save = useSaveFiles();
|
|
3135
|
-
const
|
|
4542
|
+
const resultDownloadShown = useResultDownloadShown();
|
|
4543
|
+
const pageDownloadShown = useFileDownloadShown("markup");
|
|
4544
|
+
const pdfShown = usePdfSaveShown();
|
|
4545
|
+
const { ask, dialog } = useFileNamePrompt();
|
|
4546
|
+
const page = isNativeHtmlNode(field) ? readHtmlContent(value) : void 0;
|
|
4547
|
+
const downloadShown = resultDownloadShown || page !== void 0 && pageDownloadShown;
|
|
3136
4548
|
const handleDownload = useCallback(async () => {
|
|
4549
|
+
const options = resolveUrl ? { resolveUrl } : {};
|
|
4550
|
+
let plan = planStuffSave(field, value, { baseName, ...options });
|
|
4551
|
+
const only = plan.files.length === 1 ? plan.files[0] : void 0;
|
|
4552
|
+
const json = plan.files.length > 1 ? plan.files[plan.files.length - 1] : void 0;
|
|
4553
|
+
if (only) {
|
|
4554
|
+
const { stem: stem2, extension } = splitFileName(only.name);
|
|
4555
|
+
const name2 = await ask(stem2, extension);
|
|
4556
|
+
if (name2 === null) return;
|
|
4557
|
+
if (name2 !== only.name) plan = { ...plan, files: [{ ...only, name: name2 }] };
|
|
4558
|
+
} else if (json) {
|
|
4559
|
+
const { stem: stem2 } = splitFileName(json.name);
|
|
4560
|
+
const name2 = await ask(stem2, "");
|
|
4561
|
+
if (name2 === null) return;
|
|
4562
|
+
if (name2 !== stem2) plan = planStuffSave(field, value, { baseName: name2, ...options });
|
|
4563
|
+
}
|
|
3137
4564
|
const attempt = {
|
|
3138
4565
|
field,
|
|
3139
4566
|
baseName,
|
|
@@ -3144,10 +4571,6 @@ function StuffPanel({
|
|
|
3144
4571
|
setSaving(attempt);
|
|
3145
4572
|
setMissed(null);
|
|
3146
4573
|
try {
|
|
3147
|
-
const plan = planStuffSave(field, value, {
|
|
3148
|
-
baseName,
|
|
3149
|
-
...resolveUrl ? { resolveUrl } : {}
|
|
3150
|
-
});
|
|
3151
4574
|
const { failed } = await save(plan.files);
|
|
3152
4575
|
if (latest.current !== attempt) return;
|
|
3153
4576
|
setMissed({
|
|
@@ -3160,65 +4583,102 @@ function StuffPanel({
|
|
|
3160
4583
|
} finally {
|
|
3161
4584
|
setSaving((current) => current === attempt ? null : current);
|
|
3162
4585
|
}
|
|
3163
|
-
}, [field, value, baseName, resolveUrl, save]);
|
|
3164
|
-
const
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
4586
|
+
}, [field, value, baseName, resolveUrl, save, ask]);
|
|
4587
|
+
const labels = { rendered: s.viewRendered, json: s.viewJson };
|
|
4588
|
+
const pdfName = page && pdfShown ? pdfFileName(field, value, baseName) : void 0;
|
|
4589
|
+
const pageView = page !== void 0 && view !== "json";
|
|
4590
|
+
return /* @__PURE__ */ jsxs(
|
|
4591
|
+
"div",
|
|
4592
|
+
{
|
|
4593
|
+
className: cn(
|
|
4594
|
+
// A page shown as the view is a flex column that may shrink: in a host
|
|
4595
|
+
// whose scrolling panel is a flex column (`flex min-h-0 flex-col
|
|
4596
|
+
// overflow-y-auto`), the page's frame takes the panel's remaining
|
|
4597
|
+
// height and scrolls inside itself, so there is one scroll container
|
|
4598
|
+
// under the pointer and the first wheel scrolls the page. Anywhere
|
|
4599
|
+
// else the column is as tall as its content and nothing changes.
|
|
4600
|
+
pageView ? "flex min-h-0 flex-col gap-2" : "space-y-2",
|
|
4601
|
+
className
|
|
4602
|
+
),
|
|
4603
|
+
children: [
|
|
4604
|
+
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 flex-wrap items-center justify-between gap-x-4 gap-y-1", children: [
|
|
4605
|
+
page && presentation === "app" ? /* @__PURE__ */ jsx("div", {}) : /* @__PURE__ */ jsx("div", { className: "min-w-0 space-y-1", children: /* @__PURE__ */ jsx(ResultHeader, { field: named }) }),
|
|
4606
|
+
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [
|
|
4607
|
+
page && pdfName !== void 0 && /* @__PURE__ */ jsx(
|
|
4608
|
+
PdfDownloadButton,
|
|
4609
|
+
{
|
|
4610
|
+
content: page,
|
|
4611
|
+
path: field.name,
|
|
4612
|
+
primary: true,
|
|
4613
|
+
...pdfName ? { fileName: pdfName } : {}
|
|
4614
|
+
}
|
|
4615
|
+
),
|
|
4616
|
+
downloadShown && /* @__PURE__ */ jsxs(
|
|
3193
4617
|
"button",
|
|
3194
4618
|
{
|
|
3195
4619
|
type: "button",
|
|
3196
|
-
onClick: () =>
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
)
|
|
4620
|
+
onClick: () => void handleDownload(),
|
|
4621
|
+
disabled: savingShown,
|
|
4622
|
+
"aria-label": page ? s.downloadHtml : s.download,
|
|
4623
|
+
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",
|
|
4624
|
+
children: [
|
|
4625
|
+
savingShown ? /* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ jsx(Download, { className: "h-3.5 w-3.5" }),
|
|
4626
|
+
savingShown ? s.downloading : page ? s.downloadHtml : s.download
|
|
4627
|
+
]
|
|
4628
|
+
}
|
|
4629
|
+
),
|
|
4630
|
+
dialog,
|
|
4631
|
+
offered.length > 1 && /* @__PURE__ */ jsx(
|
|
4632
|
+
"div",
|
|
4633
|
+
{
|
|
4634
|
+
role: "group",
|
|
4635
|
+
"aria-label": s.resultViewGroup,
|
|
4636
|
+
className: "flex rounded-md border border-border p-0.5",
|
|
4637
|
+
children: offered.map((id) => /* @__PURE__ */ jsx(
|
|
4638
|
+
"button",
|
|
4639
|
+
{
|
|
4640
|
+
type: "button",
|
|
4641
|
+
onClick: () => setView(id),
|
|
4642
|
+
"aria-pressed": view === id,
|
|
4643
|
+
className: cn(
|
|
4644
|
+
"rounded px-2 py-0.5 text-[12px] focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-1",
|
|
4645
|
+
view === id ? "bg-card font-medium text-foreground" : "text-muted-foreground hover:text-foreground"
|
|
4646
|
+
),
|
|
4647
|
+
children: labels[id]
|
|
4648
|
+
},
|
|
4649
|
+
id
|
|
4650
|
+
))
|
|
4651
|
+
}
|
|
4652
|
+
)
|
|
4653
|
+
] })
|
|
4654
|
+
] }),
|
|
4655
|
+
downloadShown && /* @__PURE__ */ jsx(
|
|
4656
|
+
"p",
|
|
4657
|
+
{
|
|
4658
|
+
role: "status",
|
|
4659
|
+
className: cn(
|
|
4660
|
+
"shrink-0 text-[12px] text-destructive",
|
|
4661
|
+
missedShown.length === 0 && "sr-only"
|
|
4662
|
+
),
|
|
4663
|
+
children: missedShown.length > 0 ? s.downloadIncomplete(missedShown) : ""
|
|
3206
4664
|
}
|
|
3207
|
-
)
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
4665
|
+
),
|
|
4666
|
+
view === "json" ? /* @__PURE__ */ jsx(JsonView, { value }) : page ? (
|
|
4667
|
+
// The page is the panel's main view: drawn here rather than through
|
|
4668
|
+
// `ResultField`, whose page arm would add a second control row saving
|
|
4669
|
+
// the same file, and with no box around it.
|
|
4670
|
+
/* @__PURE__ */ jsx(HtmlPreview, { content: page, downloadPdf: false, bare: true, fill: true })
|
|
4671
|
+
) : /* @__PURE__ */ jsx(ResultRoot, { baseName, path: [field.name], children: /* @__PURE__ */ jsx(ResultField, { field, value, hideLabel: true }) })
|
|
4672
|
+
]
|
|
4673
|
+
}
|
|
4674
|
+
);
|
|
4675
|
+
}
|
|
4676
|
+
function pdfFileName(field, value, baseName) {
|
|
4677
|
+
const file = readStuffFile("markup", value, field.name);
|
|
4678
|
+
const name = file ? planFileSave(file, { baseName })?.name : void 0;
|
|
4679
|
+
return name === void 0 ? "" : name.replace(/\.[^.]+$/, "");
|
|
3220
4680
|
}
|
|
3221
4681
|
|
|
3222
|
-
export { BooleanField, ConceptPill, DEFAULT_FIELD_STRINGS, DateField, DocumentField, EN_FIELD_STRINGS, EnumField, FIELD_STRINGS_BY_LOCALE, FR_FIELD_STRINGS, FieldDomIdProvider, FieldPresentationProvider, FieldRenderer, FieldShell, FieldStringsProvider, HtmlPreview, ImageField, JsonView, ListField, Markdown, NumberField, ObjectField, OptionalToggle, ProseField, ResultEnvProvider, ResultField, ResultRoot, SUPPORTED_LOCALES, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, StuffViewer, Switch, TextField, ToggleGroup, ToggleGroupItem, UnknownField, cn, fieldControlClass, fieldStringsFor, humanizeFieldName, saveInBrowser, useFieldDomId, useFieldPresentation, useFieldStrings, useProseImages, useResolveShareUrl, useResolveUrl, useResolvedUrl };
|
|
3223
|
-
//# sourceMappingURL=chunk-
|
|
3224
|
-
//# sourceMappingURL=chunk-
|
|
4682
|
+
export { BooleanField, ConceptPill, DEFAULT_FIELD_STRINGS, DateField, DocumentField, EN_FIELD_STRINGS, EnumField, FIELD_STRINGS_BY_LOCALE, FR_FIELD_STRINGS, FieldDomIdProvider, FieldPresentationProvider, FieldRenderer, FieldShell, FieldStringsProvider, HtmlPreview, ImageField, JsonView, ListField, Markdown, MethodForm, NumberField, ObjectField, OptionalToggle, PdfDownloadButton, ProseField, ResultEnvProvider, ResultField, ResultRoot, SUPPORTED_LOCALES, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, StuffViewer, Switch, TextField, ToggleGroup, ToggleGroupItem, UnknownField, cn, fieldControlClass, fieldStringsFor, humanizeFieldName, renderHtmlPdf, resolveHtmlStorageUrls, saveInBrowser, useFieldDomId, useFieldPresentation, useFieldStrings, useMethodForm, useProseImages, useResolveShareUrl, useResolveUrl, useResolvedUrl };
|
|
4683
|
+
//# sourceMappingURL=chunk-7JRT6ZII.js.map
|
|
4684
|
+
//# sourceMappingURL=chunk-7JRT6ZII.js.map
|