@pipelex/mthds-form 0.12.1 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -0
- package/dist/{chunk-IUWLOSAM.js → chunk-2EGRONBV.js} +3 -3
- package/dist/{chunk-IUWLOSAM.js.map → chunk-2EGRONBV.js.map} +1 -1
- package/dist/{chunk-2D4NEFUH.js → chunk-7JRT6ZII.js} +1729 -177
- 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-4KGQ65TQ.js → chunk-VT5E5W4E.js} +4 -3
- package/dist/chunk-VT5E5W4E.js.map +1 -0
- package/dist/{chunk-PM55AHRO.js → chunk-XO5OENNF.js} +28 -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 +55 -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 +739 -185
- package/dist/react/index.js +9 -3
- package/dist/styles.css +1 -1
- package/package.json +3 -1
- package/dist/chunk-2D4NEFUH.js.map +0 -1
- package/dist/chunk-4KGQ65TQ.js.map +0 -1
- package/dist/chunk-7KQXGG6J.js.map +0 -1
- package/dist/chunk-DARLDVJM.js.map +0 -1
- package/dist/chunk-PM55AHRO.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 { 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,
|
|
@@ -181,12 +184,14 @@ function BooleanField({ field, value, onChange, id, disabled }) {
|
|
|
181
184
|
}
|
|
182
185
|
);
|
|
183
186
|
}
|
|
187
|
+
|
|
188
|
+
// src/react/locales/en.ts
|
|
184
189
|
function formatBytes(bytes) {
|
|
185
190
|
if (bytes < 1024) return bytes === 1 ? "1 byte" : `${bytes} bytes`;
|
|
186
191
|
if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;
|
|
187
192
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
188
193
|
}
|
|
189
|
-
var
|
|
194
|
+
var EN_FIELD_STRINGS = {
|
|
190
195
|
optionalBadge: "optional",
|
|
191
196
|
selectPlaceholder: "Select\u2026",
|
|
192
197
|
typeValuePlaceholder: "Type a value\u2026",
|
|
@@ -198,7 +203,7 @@ var DEFAULT_FIELD_STRINGS = {
|
|
|
198
203
|
addItem: "Add item",
|
|
199
204
|
removeItemAria: (index) => `Remove item ${index}`,
|
|
200
205
|
itemsCount: (count) => count === 1 ? "1 item" : `${count} items`,
|
|
201
|
-
itemsCountOf: (count,
|
|
206
|
+
itemsCountOf: (count, total2) => `${count} of ${total2} ${total2 === 1 ? "item" : "items"}`,
|
|
202
207
|
fieldsCount: (count) => count === 1 ? "1 field" : `${count} fields`,
|
|
203
208
|
uploading: "Uploading\u2026",
|
|
204
209
|
dropToUpload: "Drop to upload",
|
|
@@ -225,6 +230,14 @@ var DEFAULT_FIELD_STRINGS = {
|
|
|
225
230
|
downloadIncomplete: (names) => `${names.length === 1 ? "One file" : `${names.length} files`} could not be saved: ${names.join(", ")}`,
|
|
226
231
|
downloadFile: (name) => `Download ${name}`,
|
|
227
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",
|
|
228
241
|
viewRendered: "Result",
|
|
229
242
|
viewJson: "JSON",
|
|
230
243
|
copyJson: "Copy the JSON",
|
|
@@ -235,14 +248,116 @@ var DEFAULT_FIELD_STRINGS = {
|
|
|
235
248
|
hideOptionalFields: "Hide optional fields",
|
|
236
249
|
hideOptionalInputs: "Hide optional inputs",
|
|
237
250
|
optionalFieldsCount: (count) => count === 1 ? "1 optional field" : `${count} optional fields`,
|
|
238
|
-
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."
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
// src/react/locales/fr.ts
|
|
258
|
+
var PLURALS = new Intl.PluralRules("fr");
|
|
259
|
+
var BYTES = new Intl.NumberFormat("fr-FR", { style: "unit", unit: "byte", unitDisplay: "long" });
|
|
260
|
+
var KILOBYTES = new Intl.NumberFormat("fr-FR", {
|
|
261
|
+
style: "unit",
|
|
262
|
+
unit: "kilobyte",
|
|
263
|
+
unitDisplay: "short",
|
|
264
|
+
maximumFractionDigits: 0
|
|
265
|
+
});
|
|
266
|
+
var MEGABYTES = new Intl.NumberFormat("fr-FR", {
|
|
267
|
+
style: "unit",
|
|
268
|
+
unit: "megabyte",
|
|
269
|
+
unitDisplay: "short",
|
|
270
|
+
maximumFractionDigits: 1
|
|
271
|
+
});
|
|
272
|
+
function plural(count, one, other) {
|
|
273
|
+
return PLURALS.select(count) === "one" ? one : other;
|
|
274
|
+
}
|
|
275
|
+
function formatBytes2(bytes) {
|
|
276
|
+
if (bytes < 1024) return BYTES.format(bytes);
|
|
277
|
+
if (bytes < 1024 * 1024) return KILOBYTES.format(bytes / 1024);
|
|
278
|
+
return MEGABYTES.format(bytes / (1024 * 1024));
|
|
279
|
+
}
|
|
280
|
+
var FR_FIELD_STRINGS = {
|
|
281
|
+
optionalBadge: "facultatif",
|
|
282
|
+
selectPlaceholder: "Choisir\u2026",
|
|
283
|
+
typeValuePlaceholder: "Saisissez une valeur\u2026",
|
|
284
|
+
writeHerePlaceholder: "\xC9crivez ici\u2026",
|
|
285
|
+
jsonHint: "Saisissez cette valeur en JSON brut.",
|
|
286
|
+
decrease: "Diminuer",
|
|
287
|
+
increase: "Augmenter",
|
|
288
|
+
noItemsYet: "Aucun \xE9l\xE9ment pour le moment",
|
|
289
|
+
addItem: "Ajouter un \xE9l\xE9ment",
|
|
290
|
+
removeItemAria: (index) => `Supprimer l\u2019\xE9l\xE9ment ${index}`,
|
|
291
|
+
itemsCount: (count) => `${count} ${plural(count, "\xE9l\xE9ment", "\xE9l\xE9ments")}`,
|
|
292
|
+
itemsCountOf: (count, total2) => `${count} sur ${total2} ${plural(total2, "\xE9l\xE9ment", "\xE9l\xE9ments")}`,
|
|
293
|
+
fieldsCount: (count) => `${count} ${plural(count, "champ", "champs")}`,
|
|
294
|
+
uploading: "Envoi en cours\u2026",
|
|
295
|
+
dropToUpload: "D\xE9posez pour envoyer",
|
|
296
|
+
dropOrBrowse: "D\xE9posez un fichier ou cliquez pour parcourir",
|
|
297
|
+
pasteUrlInstead: "coller un lien \xE0 la place",
|
|
298
|
+
uploadUnavailable: "Impossible d\u2019envoyer un fichier ici. Collez plut\xF4t un lien vers le fichier.",
|
|
299
|
+
urlPlaceholder: "https://\u2026",
|
|
300
|
+
fileUrlAria: (label) => label ? `Lien vers le fichier pour ${label}` : "Lien vers le fichier",
|
|
301
|
+
uploadedFile: "Fichier joint",
|
|
302
|
+
encodedFileSummary: (format, bytes) => `${format} \xB7 ${formatBytes2(bytes)}`,
|
|
303
|
+
preview: "Aper\xE7u",
|
|
304
|
+
removeFileAria: "Retirer le fichier",
|
|
305
|
+
previewUnavailablePdf: "Aper\xE7u indisponible - ouvrez le fichier pour le consulter.",
|
|
306
|
+
// U+002D HYPHEN-MINUS, as in the English defaults.
|
|
307
|
+
resultAbsent: "-",
|
|
308
|
+
resultAbsentDescription: "Non renseign\xE9",
|
|
309
|
+
resultUndescribed: "Aucun descripteur de sortie pour ce pipe \u2014 affichage de la valeur brute.",
|
|
310
|
+
toggleRowDetails: (index) => `Afficher ou masquer le d\xE9tail de la ligne ${index}`,
|
|
311
|
+
rowDetailsColumn: "D\xE9tail",
|
|
312
|
+
copyUrl: "Copier le lien",
|
|
313
|
+
copyText: "Copier le texte",
|
|
314
|
+
download: "T\xE9l\xE9charger",
|
|
315
|
+
downloading: "T\xE9l\xE9chargement\u2026",
|
|
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(", ")}`,
|
|
317
|
+
downloadFile: (name) => `T\xE9l\xE9charger ${name}`,
|
|
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",
|
|
327
|
+
viewRendered: "R\xE9sultat",
|
|
328
|
+
viewJson: "JSON",
|
|
329
|
+
copyJson: "Copier le JSON",
|
|
330
|
+
resultViewGroup: "Affichage du r\xE9sultat",
|
|
331
|
+
yes: "Oui",
|
|
332
|
+
no: "Non",
|
|
333
|
+
unsupportedFileType: (formats) => `Ce type de fichier n\u2019est pas pris en charge. Formats accept\xE9s\xA0: ${formats}.`,
|
|
334
|
+
hideOptionalFields: "Masquer les champs facultatifs",
|
|
335
|
+
hideOptionalInputs: "Masquer les entr\xE9es facultatives",
|
|
336
|
+
optionalFieldsCount: (count) => `${count} ${plural(count, "champ facultatif", "champs facultatifs")}`,
|
|
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."
|
|
239
341
|
};
|
|
342
|
+
|
|
343
|
+
// src/react/locales/index.ts
|
|
344
|
+
var FIELD_STRINGS_BY_LOCALE = {
|
|
345
|
+
en: EN_FIELD_STRINGS,
|
|
346
|
+
fr: FR_FIELD_STRINGS
|
|
347
|
+
};
|
|
348
|
+
var SUPPORTED_LOCALES = Object.keys(FIELD_STRINGS_BY_LOCALE);
|
|
349
|
+
function fieldStringsFor(locale) {
|
|
350
|
+
const language = (locale ?? "").trim().toLowerCase().split(/[-_]/)[0] ?? "";
|
|
351
|
+
return FIELD_STRINGS_BY_LOCALE[language] ?? EN_FIELD_STRINGS;
|
|
352
|
+
}
|
|
353
|
+
var DEFAULT_FIELD_STRINGS = EN_FIELD_STRINGS;
|
|
240
354
|
var FieldStringsContext = createContext(DEFAULT_FIELD_STRINGS);
|
|
241
355
|
function FieldStringsProvider({
|
|
356
|
+
locale,
|
|
242
357
|
strings,
|
|
243
358
|
children
|
|
244
359
|
}) {
|
|
245
|
-
const merged = useMemo(() => ({ ...
|
|
360
|
+
const merged = useMemo(() => ({ ...fieldStringsFor(locale), ...strings }), [locale, strings]);
|
|
246
361
|
return /* @__PURE__ */ jsx(FieldStringsContext.Provider, { value: merged, children });
|
|
247
362
|
}
|
|
248
363
|
function useFieldStrings() {
|
|
@@ -315,7 +430,7 @@ function DateField({ field, value, onChange, id, error, disabled }) {
|
|
|
315
430
|
conceptRef: field.conceptRef,
|
|
316
431
|
category: "date",
|
|
317
432
|
description: field.description,
|
|
318
|
-
required: field
|
|
433
|
+
required: !shownAsOptional(field),
|
|
319
434
|
error,
|
|
320
435
|
htmlFor: domId,
|
|
321
436
|
children: /* @__PURE__ */ jsx(
|
|
@@ -511,7 +626,7 @@ function FileField({
|
|
|
511
626
|
conceptRef: field.conceptRef,
|
|
512
627
|
category,
|
|
513
628
|
description: field.description,
|
|
514
|
-
required: field
|
|
629
|
+
required: !shownAsOptional(field),
|
|
515
630
|
error,
|
|
516
631
|
htmlFor: domId,
|
|
517
632
|
children: [
|
|
@@ -903,7 +1018,7 @@ function EnumField({ field, value, onChange, id, error, disabled }) {
|
|
|
903
1018
|
conceptRef: field.conceptRef,
|
|
904
1019
|
category: "choice",
|
|
905
1020
|
description: field.description,
|
|
906
|
-
required: field
|
|
1021
|
+
required: !shownAsOptional(field),
|
|
907
1022
|
error,
|
|
908
1023
|
htmlFor: isSegmented ? void 0 : domId,
|
|
909
1024
|
children: isSegmented ? (
|
|
@@ -972,7 +1087,7 @@ function NumberField({ field, value, onChange, id, error, disabled }) {
|
|
|
972
1087
|
conceptRef: field.conceptRef,
|
|
973
1088
|
category: "number",
|
|
974
1089
|
description: field.description ?? rangeHint,
|
|
975
|
-
required: field
|
|
1090
|
+
required: !shownAsOptional(field),
|
|
976
1091
|
error,
|
|
977
1092
|
htmlFor: domId,
|
|
978
1093
|
children: /* @__PURE__ */ jsxs("div", { className: "flex items-stretch gap-1.5", children: [
|
|
@@ -1034,7 +1149,7 @@ function TextField({ field, value, onChange, id, error, disabled }) {
|
|
|
1034
1149
|
conceptRef: field.conceptRef,
|
|
1035
1150
|
category: "text",
|
|
1036
1151
|
description: field.description,
|
|
1037
|
-
required: field
|
|
1152
|
+
required: !shownAsOptional(field),
|
|
1038
1153
|
error,
|
|
1039
1154
|
htmlFor: domId,
|
|
1040
1155
|
children: /* @__PURE__ */ jsx(
|
|
@@ -1064,7 +1179,7 @@ function ProseField({ field, value, onChange, id, error, disabled }) {
|
|
|
1064
1179
|
conceptRef: field.conceptRef,
|
|
1065
1180
|
category: "text",
|
|
1066
1181
|
description: field.description,
|
|
1067
|
-
required: field
|
|
1182
|
+
required: !shownAsOptional(field),
|
|
1068
1183
|
error,
|
|
1069
1184
|
htmlFor: domId,
|
|
1070
1185
|
children: /* @__PURE__ */ jsx(
|
|
@@ -1093,6 +1208,7 @@ function OptionalToggle({ count, expanded, onToggle, noun = "field" }) {
|
|
|
1093
1208
|
{
|
|
1094
1209
|
type: "button",
|
|
1095
1210
|
onClick: onToggle,
|
|
1211
|
+
"aria-expanded": expanded,
|
|
1096
1212
|
className: "flex w-fit items-center gap-1.5 text-[11px] text-muted-foreground transition-colors hover:text-foreground",
|
|
1097
1213
|
children: [
|
|
1098
1214
|
expanded ? /* @__PURE__ */ jsx(Minus, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx(Plus, { className: "h-3 w-3" }),
|
|
@@ -1102,14 +1218,24 @@ function OptionalToggle({ count, expanded, onToggle, noun = "field" }) {
|
|
|
1102
1218
|
);
|
|
1103
1219
|
}
|
|
1104
1220
|
function ObjectField({ field, value, onChange, id, error, env }) {
|
|
1105
|
-
const s = useFieldStrings();
|
|
1106
1221
|
const [showOptional, setShowOptional] = useState(false);
|
|
1107
1222
|
const presentation = useFieldPresentation();
|
|
1108
1223
|
const isApp = presentation === "app";
|
|
1109
1224
|
const data = value ?? {};
|
|
1110
1225
|
const setChild = (name, childValue) => onChange({ ...data, [name]: childValue });
|
|
1111
|
-
const
|
|
1112
|
-
const
|
|
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
|
+
};
|
|
1113
1239
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
1114
1240
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-baseline gap-x-2 gap-y-1", children: [
|
|
1115
1241
|
/* @__PURE__ */ jsx(
|
|
@@ -1122,8 +1248,7 @@ function ObjectField({ field, value, onChange, id, error, env }) {
|
|
|
1122
1248
|
children: fieldLabel(field.title, field.name, presentation)
|
|
1123
1249
|
}
|
|
1124
1250
|
),
|
|
1125
|
-
!isApp && /* @__PURE__ */ jsx(ConceptPill, { conceptRef: field.conceptRef, category: "structured" })
|
|
1126
|
-
!field.required && /* @__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" })
|
|
1127
1252
|
] }),
|
|
1128
1253
|
field.description && /* @__PURE__ */ jsx("p", { className: "text-[12px] leading-relaxed text-muted-foreground", children: field.description }),
|
|
1129
1254
|
/* @__PURE__ */ jsxs(
|
|
@@ -1145,12 +1270,12 @@ function ObjectField({ field, value, onChange, id, error, env }) {
|
|
|
1145
1270
|
},
|
|
1146
1271
|
child.name
|
|
1147
1272
|
)),
|
|
1148
|
-
|
|
1273
|
+
(folded.length > 0 || structureOpen) && /* @__PURE__ */ jsx(
|
|
1149
1274
|
OptionalToggle,
|
|
1150
1275
|
{
|
|
1151
|
-
count:
|
|
1152
|
-
expanded
|
|
1153
|
-
onToggle:
|
|
1276
|
+
count: folded.length,
|
|
1277
|
+
expanded,
|
|
1278
|
+
onToggle: toggle,
|
|
1154
1279
|
noun: "field"
|
|
1155
1280
|
}
|
|
1156
1281
|
)
|
|
@@ -1223,7 +1348,7 @@ function ListField({ field, value, onChange, id, error, env }) {
|
|
|
1223
1348
|
),
|
|
1224
1349
|
!isApp && /* @__PURE__ */ jsx(ConceptPill, { conceptRef: field.conceptRef, category: "list" }),
|
|
1225
1350
|
/* @__PURE__ */ jsx("span", { className: "font-mono text-[10.5px] text-muted-foreground", children: field.itemCount !== void 0 && field.maxItemCount === field.itemCount ? s.itemsCountOf(items.length, field.itemCount) : s.itemsCount(items.length) }),
|
|
1226
|
-
|
|
1351
|
+
shownAsOptional(field) && /* @__PURE__ */ jsx("span", { className: "ml-auto font-mono text-[10px] uppercase tracking-wider text-muted-foreground", children: s.optionalBadge })
|
|
1227
1352
|
] }),
|
|
1228
1353
|
field.description && /* @__PURE__ */ jsx("p", { className: "text-[12px] leading-relaxed text-muted-foreground", children: field.description }),
|
|
1229
1354
|
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
@@ -1237,7 +1362,12 @@ function ListField({ field, value, onChange, id, error, env }) {
|
|
|
1237
1362
|
/* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx(
|
|
1238
1363
|
FieldRenderer,
|
|
1239
1364
|
{
|
|
1240
|
-
field: {
|
|
1365
|
+
field: {
|
|
1366
|
+
...field.item,
|
|
1367
|
+
title: "",
|
|
1368
|
+
name: `${field.name}[${index}]`,
|
|
1369
|
+
required: true
|
|
1370
|
+
},
|
|
1241
1371
|
value: item,
|
|
1242
1372
|
onChange: (v) => setItem(index, v),
|
|
1243
1373
|
id: `${id}.${index}`,
|
|
@@ -1341,7 +1471,7 @@ function UnknownField({ field, value, onChange, id, error, disabled }) {
|
|
|
1341
1471
|
conceptRef: field.conceptRef,
|
|
1342
1472
|
category: "structured",
|
|
1343
1473
|
description: field.description ?? s.jsonHint,
|
|
1344
|
-
required: field
|
|
1474
|
+
required: !shownAsOptional(field),
|
|
1345
1475
|
error,
|
|
1346
1476
|
htmlFor: domId,
|
|
1347
1477
|
children: /* @__PURE__ */ jsx(
|
|
@@ -1508,6 +1638,192 @@ function FieldRenderer({ field, value, onChange, id, error, env }) {
|
|
|
1508
1638
|
);
|
|
1509
1639
|
}
|
|
1510
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
|
+
}
|
|
1511
1827
|
|
|
1512
1828
|
// src/react/absolute-url.ts
|
|
1513
1829
|
function absoluteUrl(url) {
|
|
@@ -1520,6 +1836,287 @@ function absoluteUrl(url) {
|
|
|
1520
1836
|
}
|
|
1521
1837
|
}
|
|
1522
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
|
+
|
|
1523
2120
|
// src/react/save-in-browser.ts
|
|
1524
2121
|
function saveBlob(blob, filename) {
|
|
1525
2122
|
const href = URL.createObjectURL(blob);
|
|
@@ -1554,6 +2151,7 @@ async function saveInBrowser(files) {
|
|
|
1554
2151
|
var ResultEnvContext = createContext({});
|
|
1555
2152
|
function ResultEnvProvider({
|
|
1556
2153
|
resolveUrl,
|
|
2154
|
+
resolveUrls,
|
|
1557
2155
|
resolveShareUrl,
|
|
1558
2156
|
proseImages,
|
|
1559
2157
|
tableColumns,
|
|
@@ -1562,8 +2160,16 @@ function ResultEnvProvider({
|
|
|
1562
2160
|
children
|
|
1563
2161
|
}) {
|
|
1564
2162
|
const env = useMemo(
|
|
1565
|
-
() => ({
|
|
1566
|
-
|
|
2163
|
+
() => ({
|
|
2164
|
+
resolveUrl,
|
|
2165
|
+
resolveUrls,
|
|
2166
|
+
resolveShareUrl,
|
|
2167
|
+
proseImages,
|
|
2168
|
+
tableColumns,
|
|
2169
|
+
saveFiles,
|
|
2170
|
+
downloads
|
|
2171
|
+
}),
|
|
2172
|
+
[resolveUrl, resolveUrls, resolveShareUrl, proseImages, tableColumns, saveFiles, downloads]
|
|
1567
2173
|
);
|
|
1568
2174
|
return /* @__PURE__ */ jsx(ResultEnvContext, { value: env, children });
|
|
1569
2175
|
}
|
|
@@ -1574,15 +2180,19 @@ function DownloadDisplayOverride({
|
|
|
1574
2180
|
const env = use(ResultEnvContext);
|
|
1575
2181
|
const result = downloads?.result;
|
|
1576
2182
|
const files = downloads?.files;
|
|
2183
|
+
const pdf = downloads?.pdf;
|
|
2184
|
+
const askFileName = downloads?.askFileName;
|
|
1577
2185
|
const merged = useMemo(
|
|
1578
|
-
() => result === void 0 && files === void 0 ? env : {
|
|
2186
|
+
() => result === void 0 && files === void 0 && pdf === void 0 && askFileName === void 0 ? env : {
|
|
1579
2187
|
...env,
|
|
1580
2188
|
downloads: {
|
|
1581
2189
|
result: result ?? env.downloads?.result,
|
|
1582
|
-
files: files ?? env.downloads?.files
|
|
2190
|
+
files: files ?? env.downloads?.files,
|
|
2191
|
+
pdf: pdf ?? env.downloads?.pdf,
|
|
2192
|
+
askFileName: askFileName ?? env.downloads?.askFileName
|
|
1583
2193
|
}
|
|
1584
2194
|
},
|
|
1585
|
-
[env, result, files]
|
|
2195
|
+
[env, result, files, pdf, askFileName]
|
|
1586
2196
|
);
|
|
1587
2197
|
return /* @__PURE__ */ jsx(ResultEnvContext, { value: merged, children });
|
|
1588
2198
|
}
|
|
@@ -1592,16 +2202,25 @@ function useResolvedUrl(url) {
|
|
|
1592
2202
|
function useResolveUrl() {
|
|
1593
2203
|
return use(ResultEnvContext).resolveUrl;
|
|
1594
2204
|
}
|
|
2205
|
+
function useMarkupResolvers() {
|
|
2206
|
+
const { resolveUrl, resolveUrls } = use(ResultEnvContext);
|
|
2207
|
+
return useMemo(() => ({ resolveUrl, resolveUrls }), [resolveUrl, resolveUrls]);
|
|
2208
|
+
}
|
|
1595
2209
|
function useResolveShareUrl() {
|
|
1596
2210
|
return use(ResultEnvContext).resolveShareUrl;
|
|
1597
2211
|
}
|
|
1598
2212
|
function useSaveFiles() {
|
|
1599
2213
|
const save = use(ResultEnvContext).saveFiles ?? saveInBrowser;
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
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
|
+
})
|
|
1605
2224
|
);
|
|
1606
2225
|
try {
|
|
1607
2226
|
const result = await save(handed);
|
|
@@ -1621,6 +2240,12 @@ function useFileDownloadShown(kind) {
|
|
|
1621
2240
|
const files = use(ResultEnvContext).downloads?.files ?? true;
|
|
1622
2241
|
return typeof files === "boolean" ? files : files.includes(kind);
|
|
1623
2242
|
}
|
|
2243
|
+
function usePdfSaveShown() {
|
|
2244
|
+
return use(ResultEnvContext).downloads?.pdf ?? true;
|
|
2245
|
+
}
|
|
2246
|
+
function useAskFileName() {
|
|
2247
|
+
return use(ResultEnvContext).downloads?.askFileName ?? false;
|
|
2248
|
+
}
|
|
1624
2249
|
function useProseImages() {
|
|
1625
2250
|
return use(ResultEnvContext).proseImages ?? "link";
|
|
1626
2251
|
}
|
|
@@ -1669,88 +2294,857 @@ var TooltipContent = React2.forwardRef(({ className, sideOffset = 6, ...props },
|
|
|
1669
2294
|
}
|
|
1670
2295
|
) }));
|
|
1671
2296
|
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
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)
|
|
1684
2325
|
}
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
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;
|
|
1697
2351
|
}
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
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
|
+
);
|
|
1708
2439
|
}
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
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
|
+
};
|
|
1720
2547
|
}
|
|
1721
|
-
function
|
|
1722
|
-
|
|
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
|
+
});
|
|
1723
3092
|
}
|
|
1724
3093
|
var useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
3094
|
+
var FRAGMENT_MAX_HEIGHT = 1400;
|
|
3095
|
+
var FILL_MIN_HEIGHT = 320;
|
|
1725
3096
|
function HtmlPreview({
|
|
1726
3097
|
content,
|
|
1727
|
-
maxHeight
|
|
1728
|
-
imgSrc = DEFAULT_IMG_SRC
|
|
3098
|
+
maxHeight,
|
|
3099
|
+
imgSrc = DEFAULT_IMG_SRC,
|
|
3100
|
+
fontSrc,
|
|
3101
|
+
downloadPdf,
|
|
3102
|
+
pdfFileName: pdfFileName2,
|
|
3103
|
+
bare = false,
|
|
3104
|
+
fill = false
|
|
1729
3105
|
}) {
|
|
1730
3106
|
const hostRef = useRef(null);
|
|
1731
3107
|
const frameRef = useRef(null);
|
|
3108
|
+
const s = useFieldStrings();
|
|
1732
3109
|
const [doc, setDoc] = useState(null);
|
|
1733
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;
|
|
1734
3121
|
useIsomorphicLayoutEffect(() => {
|
|
1735
3122
|
const host = hostRef.current;
|
|
1736
|
-
if (!host) return;
|
|
3123
|
+
if (!host || !resolved) return;
|
|
1737
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
|
+
};
|
|
1738
3131
|
setDoc(
|
|
1739
|
-
frameDocument(
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
computed.borderColor || computed.color,
|
|
1745
|
-
computed.fontFamily
|
|
1746
|
-
),
|
|
1747
|
-
imgSrc
|
|
1748
|
-
)
|
|
3132
|
+
frameDocument(resolved, {
|
|
3133
|
+
styles: frameStyles(typography),
|
|
3134
|
+
imgSrc,
|
|
3135
|
+
fontSrc: fontSrc ?? imgSrc
|
|
3136
|
+
})
|
|
1749
3137
|
);
|
|
1750
|
-
}, [
|
|
3138
|
+
}, [resolved, imgSrc, fontSrc, page]);
|
|
1751
3139
|
const measure = () => {
|
|
1752
|
-
const
|
|
1753
|
-
|
|
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);
|
|
1754
3148
|
};
|
|
1755
3149
|
useEffect(() => {
|
|
1756
3150
|
if (!doc || !hostRef.current || typeof ResizeObserver === "undefined") return;
|
|
@@ -1758,30 +3152,110 @@ function HtmlPreview({
|
|
|
1758
3152
|
observer.observe(hostRef.current);
|
|
1759
3153
|
return () => observer.disconnect();
|
|
1760
3154
|
}, [doc]);
|
|
1761
|
-
|
|
3155
|
+
const frameBox = /* @__PURE__ */ jsx(
|
|
1762
3156
|
"div",
|
|
1763
3157
|
{
|
|
1764
3158
|
ref: hostRef,
|
|
1765
|
-
|
|
1766
|
-
|
|
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(
|
|
1767
3181
|
"iframe",
|
|
1768
3182
|
{
|
|
1769
3183
|
ref: frameRef,
|
|
1770
3184
|
title: content.cssClass ? `HTML result (${content.cssClass})` : "HTML result",
|
|
1771
3185
|
srcDoc: doc,
|
|
1772
|
-
onLoad
|
|
3186
|
+
onLoad,
|
|
1773
3187
|
sandbox: "allow-same-origin",
|
|
1774
3188
|
style: {
|
|
1775
3189
|
display: "block",
|
|
1776
3190
|
width: "100%",
|
|
1777
3191
|
border: 0,
|
|
1778
|
-
height: height === 0 ? void 0 : Math.min(height,
|
|
1779
|
-
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) } : {}
|
|
1780
3200
|
}
|
|
1781
3201
|
}
|
|
1782
3202
|
)
|
|
1783
3203
|
}
|
|
1784
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;
|
|
1785
3259
|
}
|
|
1786
3260
|
var LEX_OPTIONS = { gfm: true, breaks: true };
|
|
1787
3261
|
function Markdown({ text, className, proseImages }) {
|
|
@@ -2233,6 +3707,7 @@ function FileDownloadButton({ file }) {
|
|
|
2233
3707
|
const resolve = useResolveUrl();
|
|
2234
3708
|
const save = useSaveFiles();
|
|
2235
3709
|
const shown = useFileDownloadShown(file.kind);
|
|
3710
|
+
const { ask, dialog } = useFileNamePrompt();
|
|
2236
3711
|
const statusId = useId();
|
|
2237
3712
|
const [attempt, setAttempt] = useState(null);
|
|
2238
3713
|
useEffect(() => {
|
|
@@ -2259,13 +3734,15 @@ ${planned.url ?? planned.text}`;
|
|
|
2259
3734
|
"button",
|
|
2260
3735
|
{
|
|
2261
3736
|
type: "button",
|
|
2262
|
-
onClick: () => {
|
|
3737
|
+
onClick: async () => {
|
|
3738
|
+
const { stem: stem2, extension } = splitFileName(planned.name);
|
|
3739
|
+
const name = await ask(stem2, extension);
|
|
3740
|
+
if (name === null) return;
|
|
2263
3741
|
const started = { identity, state: "saving" };
|
|
2264
3742
|
setAttempt(started);
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
});
|
|
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);
|
|
2269
3746
|
},
|
|
2270
3747
|
disabled: state === "saving",
|
|
2271
3748
|
"aria-label": label,
|
|
@@ -2278,7 +3755,8 @@ ${planned.url ?? planned.text}`;
|
|
|
2278
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" })
|
|
2279
3756
|
}
|
|
2280
3757
|
),
|
|
2281
|
-
/* @__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
|
|
2282
3760
|
] });
|
|
2283
3761
|
}
|
|
2284
3762
|
function paintable(content, resolve) {
|
|
@@ -2715,7 +4193,7 @@ function CompositeValue({ members }) {
|
|
|
2715
4193
|
function NativeValue({ value }) {
|
|
2716
4194
|
if (value === null || value === void 0 || value === "") return /* @__PURE__ */ jsx(Absent, {});
|
|
2717
4195
|
const html = readHtmlContent(value);
|
|
2718
|
-
if (html) return /* @__PURE__ */ jsx(HtmlPreview, { content: html });
|
|
4196
|
+
if (html) return /* @__PURE__ */ jsx(HtmlPreview, { content: html, downloadPdf: false });
|
|
2719
4197
|
const date = readDateContent(value);
|
|
2720
4198
|
if (date) return /* @__PURE__ */ jsx("span", { className: "text-[13px] text-foreground", children: formatDateContent(date) });
|
|
2721
4199
|
const document2 = readDocumentContent(value);
|
|
@@ -2795,6 +4273,7 @@ function ResultNode({ field, value, depth = 0, hideLabel = false }) {
|
|
|
2795
4273
|
const s = useFieldStrings();
|
|
2796
4274
|
const unwrapped = unwrap(field, value);
|
|
2797
4275
|
const pageDownloadShown = useFileDownloadShown("markup");
|
|
4276
|
+
const pdfShown = usePdfSaveShown();
|
|
2798
4277
|
const location = useResultLocation();
|
|
2799
4278
|
const header = hideLabel ? null : /* @__PURE__ */ jsx(Label2, { field, describe: true });
|
|
2800
4279
|
const copyText = (field.kind === "text" || field.kind === "prose") && typeof unwrapped === "string" && unwrapped !== "" ? unwrapped : void 0;
|
|
@@ -2806,13 +4285,24 @@ function ResultNode({ field, value, depth = 0, hideLabel = false }) {
|
|
|
2806
4285
|
}
|
|
2807
4286
|
if (isNativeHtmlNode(field)) {
|
|
2808
4287
|
const content = readHtmlContent(unwrapped);
|
|
2809
|
-
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;
|
|
2810
4292
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
2811
|
-
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: [
|
|
2812
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,
|
|
2813
4303
|
/* @__PURE__ */ jsx(FileActions, { file: page })
|
|
2814
4304
|
] }) : header,
|
|
2815
|
-
content ? /* @__PURE__ */ jsx(HtmlPreview, { content }) : /* @__PURE__ */ jsx(Absent, {})
|
|
4305
|
+
content ? /* @__PURE__ */ jsx(HtmlPreview, { content, downloadPdf: false }) : /* @__PURE__ */ jsx(Absent, {})
|
|
2816
4306
|
] });
|
|
2817
4307
|
}
|
|
2818
4308
|
if (isNativeCompositeNode(field)) {
|
|
@@ -2943,6 +4433,11 @@ function ResultNode({ field, value, depth = 0, hideLabel = false }) {
|
|
|
2943
4433
|
}
|
|
2944
4434
|
return null;
|
|
2945
4435
|
}
|
|
4436
|
+
function stem(name) {
|
|
4437
|
+
return name === void 0 ? "" : name.replace(/\.[^.]+$/, "");
|
|
4438
|
+
}
|
|
4439
|
+
var ALL_VIEWS = ["rendered", "json"];
|
|
4440
|
+
var RESULT_ONLY = ["rendered"];
|
|
2946
4441
|
var JSON_TOKEN_RE = /("(?:\\.|[^"\\])*"\s*:)|("(?:\\.|[^"\\])*")|(\b(?:true|false|null)\b|-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)/g;
|
|
2947
4442
|
function highlight(json) {
|
|
2948
4443
|
const out = [];
|
|
@@ -3025,14 +4520,18 @@ function StuffPanel({
|
|
|
3025
4520
|
field,
|
|
3026
4521
|
value,
|
|
3027
4522
|
name,
|
|
4523
|
+
views: viewsProp,
|
|
3028
4524
|
defaultView = "rendered",
|
|
3029
4525
|
downloadBaseName,
|
|
3030
4526
|
className
|
|
3031
4527
|
}) {
|
|
3032
4528
|
const s = useFieldStrings();
|
|
4529
|
+
const presentation = useFieldPresentation();
|
|
4530
|
+
const offered = viewsProp && viewsProp.length > 0 ? viewsProp : viewsProp || presentation === "app" ? RESULT_ONLY : ALL_VIEWS;
|
|
3033
4531
|
const named = name ? { ...field, name } : field;
|
|
3034
4532
|
const baseName = downloadBaseName ?? named.name ?? "result";
|
|
3035
|
-
const [
|
|
4533
|
+
const [chosen, setView] = useState(defaultView);
|
|
4534
|
+
const view = offered.includes(chosen) ? chosen : offered[0] ?? "rendered";
|
|
3036
4535
|
const [saving, setSaving] = useState(null);
|
|
3037
4536
|
const [missed, setMissed] = useState(null);
|
|
3038
4537
|
const latest = useRef(null);
|
|
@@ -3040,8 +4539,28 @@ function StuffPanel({
|
|
|
3040
4539
|
const missedShown = missed && isShownResult(missed.attempt, field, baseName, value) ? missed.names : [];
|
|
3041
4540
|
const resolveUrl = useResolveUrl();
|
|
3042
4541
|
const save = useSaveFiles();
|
|
3043
|
-
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;
|
|
3044
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
|
+
}
|
|
3045
4564
|
const attempt = {
|
|
3046
4565
|
field,
|
|
3047
4566
|
baseName,
|
|
@@ -3052,10 +4571,6 @@ function StuffPanel({
|
|
|
3052
4571
|
setSaving(attempt);
|
|
3053
4572
|
setMissed(null);
|
|
3054
4573
|
try {
|
|
3055
|
-
const plan = planStuffSave(field, value, {
|
|
3056
|
-
baseName,
|
|
3057
|
-
...resolveUrl ? { resolveUrl } : {}
|
|
3058
|
-
});
|
|
3059
4574
|
const { failed } = await save(plan.files);
|
|
3060
4575
|
if (latest.current !== attempt) return;
|
|
3061
4576
|
setMissed({
|
|
@@ -3068,65 +4583,102 @@ function StuffPanel({
|
|
|
3068
4583
|
} finally {
|
|
3069
4584
|
setSaving((current) => current === attempt ? null : current);
|
|
3070
4585
|
}
|
|
3071
|
-
}, [field, value, baseName, resolveUrl, save]);
|
|
3072
|
-
const
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
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(
|
|
3101
4617
|
"button",
|
|
3102
4618
|
{
|
|
3103
4619
|
type: "button",
|
|
3104
|
-
onClick: () =>
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
)
|
|
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) : ""
|
|
3114
4664
|
}
|
|
3115
|
-
)
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
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(/\.[^.]+$/, "");
|
|
3128
4680
|
}
|
|
3129
4681
|
|
|
3130
|
-
export { BooleanField, ConceptPill, DEFAULT_FIELD_STRINGS, DateField, DocumentField, EnumField, FieldDomIdProvider, FieldPresentationProvider, FieldRenderer, FieldShell, FieldStringsProvider, HtmlPreview, ImageField, JsonView, ListField, Markdown, NumberField, ObjectField, OptionalToggle, ProseField, ResultEnvProvider, ResultField, ResultRoot, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, StuffViewer, Switch, TextField, ToggleGroup, ToggleGroupItem, UnknownField, cn, fieldControlClass, humanizeFieldName, saveInBrowser, useFieldDomId, useFieldPresentation, useFieldStrings, useProseImages, useResolveShareUrl, useResolveUrl, useResolvedUrl };
|
|
3131
|
-
//# sourceMappingURL=chunk-
|
|
3132
|
-
//# 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
|