@drawnagency/primitives 0.1.100 → 0.1.102
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-GOCFTXR3.js → chunk-6SLGKRLW.js} +1 -1
- package/dist/{chunk-72FMQQIA.js → chunk-P3NNI3QI.js} +99 -18
- package/dist/components/docfile/DocFilePane.d.ts.map +1 -1
- package/dist/components/docfile/doc-file-state.d.ts +5 -0
- package/dist/components/docfile/doc-file-state.d.ts.map +1 -1
- package/dist/components/primitives/DocumentDropZone.d.ts +18 -0
- package/dist/components/primitives/DocumentDropZone.d.ts.map +1 -0
- package/dist/components/sections/Document/index.d.ts +7 -1
- package/dist/components/sections/Document/index.d.ts.map +1 -1
- package/dist/components/sections/brand-guide.js +1 -1
- package/dist/components/sections/register-schemas.js +2 -2
- package/dist/components/sections/register.js +2 -2
- package/dist/components/shell/EditorShell.d.ts.map +1 -1
- package/dist/components/shell/MediaLibraryContext.d.ts +6 -0
- package/dist/components/shell/MediaLibraryContext.d.ts.map +1 -1
- package/dist/hooks/useDocumentUploads.d.ts.map +1 -1
- package/dist/hooks/useMediaPipeline.d.ts.map +1 -1
- package/dist/lib/dexie.d.ts +3 -1
- package/dist/lib/dexie.d.ts.map +1 -1
- package/dist/lib/dexie.js +7 -2
- package/dist/storage/types.d.ts +14 -5
- package/dist/storage/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/docfile/DocFilePane.tsx +8 -1
- package/src/components/docfile/doc-file-state.ts +31 -3
- package/src/components/primitives/DocumentDropZone.tsx +79 -0
- package/src/components/sections/Document/index.tsx +32 -1
- package/src/components/shell/EditorShell.tsx +21 -1
- package/src/components/shell/MediaLibraryContext.tsx +6 -0
- package/src/hooks/useDocumentUploads.ts +7 -1
- package/src/hooks/useMediaPipeline.ts +6 -1
- package/src/lib/dexie.ts +13 -2
- package/src/storage/types.ts +14 -6
|
@@ -14,12 +14,16 @@ import {
|
|
|
14
14
|
AddIcon,
|
|
15
15
|
DeleteIcon,
|
|
16
16
|
DragHandle,
|
|
17
|
-
IconButton
|
|
17
|
+
IconButton,
|
|
18
|
+
useMediaLibrary
|
|
18
19
|
} from "./chunk-26ALJO4K.js";
|
|
19
20
|
import {
|
|
20
21
|
DEFAULT_LINK,
|
|
21
22
|
LinkValueSchema
|
|
22
23
|
} from "./chunk-PZJL2S3Z.js";
|
|
24
|
+
import {
|
|
25
|
+
DOCUMENT_MIME_TO_EXT
|
|
26
|
+
} from "./chunk-BS5AQHZG.js";
|
|
23
27
|
import {
|
|
24
28
|
curatedIcons,
|
|
25
29
|
getIcon
|
|
@@ -1299,10 +1303,61 @@ var DoDontMedia_default = defineSection({
|
|
|
1299
1303
|
});
|
|
1300
1304
|
|
|
1301
1305
|
// src/components/sections/Document/index.tsx
|
|
1302
|
-
import { useEffect as useEffect5, useState as
|
|
1306
|
+
import { useEffect as useEffect5, useState as useState7 } from "react";
|
|
1303
1307
|
import { z as z5 } from "zod";
|
|
1304
1308
|
import { ExternalLink, FileText } from "lucide-react";
|
|
1305
1309
|
|
|
1310
|
+
// src/components/primitives/DocumentDropZone.tsx
|
|
1311
|
+
import { useState as useState6 } from "react";
|
|
1312
|
+
import { FileUp } from "lucide-react";
|
|
1313
|
+
import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1314
|
+
var isDocumentFile = (file) => file.type in DOCUMENT_MIME_TO_EXT;
|
|
1315
|
+
function DocumentDropZone({ onDocumentSelected, className }) {
|
|
1316
|
+
const mediaLibrary = useMediaLibrary();
|
|
1317
|
+
const [dragging, setDragging] = useState6(false);
|
|
1318
|
+
const hasFiles = (e) => e.dataTransfer.types.includes("Files");
|
|
1319
|
+
const handleDragOver = (e) => {
|
|
1320
|
+
if (!hasFiles(e)) return;
|
|
1321
|
+
e.preventDefault();
|
|
1322
|
+
e.dataTransfer.dropEffect = "copy";
|
|
1323
|
+
setDragging(true);
|
|
1324
|
+
};
|
|
1325
|
+
const handleDragLeave = (e) => {
|
|
1326
|
+
if (!hasFiles(e)) return;
|
|
1327
|
+
const related = e.relatedTarget;
|
|
1328
|
+
if (related && e.currentTarget.contains(related)) return;
|
|
1329
|
+
setDragging(false);
|
|
1330
|
+
};
|
|
1331
|
+
const handleDrop = (e) => {
|
|
1332
|
+
if (!hasFiles(e)) return;
|
|
1333
|
+
e.preventDefault();
|
|
1334
|
+
e.stopPropagation();
|
|
1335
|
+
setDragging(false);
|
|
1336
|
+
const files = Array.from(e.dataTransfer.files);
|
|
1337
|
+
if (files.length === 0) return;
|
|
1338
|
+
void mediaLibrary?.uploadDocument?.(files.find(isDocumentFile) ?? files[0], onDocumentSelected);
|
|
1339
|
+
};
|
|
1340
|
+
return /* @__PURE__ */ jsxs9(
|
|
1341
|
+
"button",
|
|
1342
|
+
{
|
|
1343
|
+
type: "button",
|
|
1344
|
+
onClick: () => mediaLibrary?.openSelectModal(onDocumentSelected, { kinds: ["document"] }),
|
|
1345
|
+
onDragOver: handleDragOver,
|
|
1346
|
+
onDragLeave: handleDragLeave,
|
|
1347
|
+
onDrop: handleDrop,
|
|
1348
|
+
className: cn(
|
|
1349
|
+
"flex w-full cursor-pointer items-center justify-center gap-2 rounded-outer border-2 border-dashed border-base-200 bg-base px-4 py-6 text-sm text-base-contrast-light transition-colors hover:border-base-300 hover:bg-base-accent hover:text-base-contrast",
|
|
1350
|
+
dragging && "border-primary bg-primary/5 text-base-contrast",
|
|
1351
|
+
className
|
|
1352
|
+
),
|
|
1353
|
+
children: [
|
|
1354
|
+
/* @__PURE__ */ jsx13(FileUp, { size: 18, "aria-hidden": "true", className: "shrink-0" }),
|
|
1355
|
+
/* @__PURE__ */ jsx13("span", { children: "Drop a PDF or HTML file, or choose from the library" })
|
|
1356
|
+
]
|
|
1357
|
+
}
|
|
1358
|
+
);
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1306
1361
|
// src/lib/container-ops.ts
|
|
1307
1362
|
function getContainerChildren(block) {
|
|
1308
1363
|
const content = block.content;
|
|
@@ -1377,6 +1432,8 @@ function formatDocSize(bytes) {
|
|
|
1377
1432
|
|
|
1378
1433
|
// src/components/docfile/doc-file-state.ts
|
|
1379
1434
|
var DOC_OPEN_CLASS = "bp-doc-open";
|
|
1435
|
+
var DOC_CLOSING_CLASS = "bp-doc-closing";
|
|
1436
|
+
var DOC_CLOSING_MS = 350;
|
|
1380
1437
|
var DOC_DESKTOP_QUERY = "(min-width: 1024px)";
|
|
1381
1438
|
var docFileOpenEvent = createEvent("bp-doc-file-open");
|
|
1382
1439
|
var STORE_KEY = /* @__PURE__ */ Symbol.for("@drawnagency/primitives/doc-file-state");
|
|
@@ -1384,7 +1441,7 @@ var g = globalThis;
|
|
|
1384
1441
|
function store() {
|
|
1385
1442
|
let s = g[STORE_KEY];
|
|
1386
1443
|
if (!s) {
|
|
1387
|
-
s = { open: null, docs: null, openListeners: /* @__PURE__ */ new Set(), docsListeners: /* @__PURE__ */ new Set(), teardown: null };
|
|
1444
|
+
s = { open: null, docs: null, openListeners: /* @__PURE__ */ new Set(), docsListeners: /* @__PURE__ */ new Set(), teardown: null, closingTimer: null };
|
|
1388
1445
|
g[STORE_KEY] = s;
|
|
1389
1446
|
}
|
|
1390
1447
|
return s;
|
|
@@ -1396,9 +1453,23 @@ function isDocDesktop() {
|
|
|
1396
1453
|
function apply(sectionId) {
|
|
1397
1454
|
const s = store();
|
|
1398
1455
|
if (s.open === sectionId) return;
|
|
1456
|
+
const wasOpen = s.open !== null;
|
|
1399
1457
|
s.open = sectionId;
|
|
1400
1458
|
if (typeof document !== "undefined") {
|
|
1401
|
-
document.documentElement
|
|
1459
|
+
const root = document.documentElement;
|
|
1460
|
+
root.classList.toggle(DOC_OPEN_CLASS, sectionId !== null);
|
|
1461
|
+
if (s.closingTimer !== null) {
|
|
1462
|
+
clearTimeout(s.closingTimer);
|
|
1463
|
+
s.closingTimer = null;
|
|
1464
|
+
root.classList.remove(DOC_CLOSING_CLASS);
|
|
1465
|
+
}
|
|
1466
|
+
if (sectionId === null && wasOpen) {
|
|
1467
|
+
root.classList.add(DOC_CLOSING_CLASS);
|
|
1468
|
+
s.closingTimer = setTimeout(() => {
|
|
1469
|
+
root.classList.remove(DOC_CLOSING_CLASS);
|
|
1470
|
+
s.closingTimer = null;
|
|
1471
|
+
}, DOC_CLOSING_MS);
|
|
1472
|
+
}
|
|
1402
1473
|
}
|
|
1403
1474
|
for (const listener of [...s.openListeners]) listener(sectionId);
|
|
1404
1475
|
}
|
|
@@ -1424,7 +1495,7 @@ function subscribeSiteDocuments(cb) {
|
|
|
1424
1495
|
}
|
|
1425
1496
|
|
|
1426
1497
|
// src/components/sections/Document/index.tsx
|
|
1427
|
-
import { jsx as
|
|
1498
|
+
import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1428
1499
|
var schema4 = z5.object({
|
|
1429
1500
|
type: z5.literal("document"),
|
|
1430
1501
|
content: z5.object({
|
|
@@ -1441,7 +1512,7 @@ var schema4 = z5.object({
|
|
|
1441
1512
|
options: z5.object({}).default({})
|
|
1442
1513
|
});
|
|
1443
1514
|
function useSiteDocument(sectionId) {
|
|
1444
|
-
const [docs, setDocs] =
|
|
1515
|
+
const [docs, setDocs] = useState7(null);
|
|
1445
1516
|
useEffect5(() => {
|
|
1446
1517
|
setDocs(getSiteDocuments());
|
|
1447
1518
|
return subscribeSiteDocuments(setDocs);
|
|
@@ -1454,10 +1525,15 @@ function useSiteDocument(sectionId) {
|
|
|
1454
1525
|
function DocumentRow({
|
|
1455
1526
|
sectionId,
|
|
1456
1527
|
title,
|
|
1457
|
-
|
|
1528
|
+
documentId = "",
|
|
1529
|
+
isEditMode,
|
|
1530
|
+
onDocumentSelected
|
|
1458
1531
|
}) {
|
|
1459
1532
|
const { doc, resolved } = useSiteDocument(sectionId);
|
|
1460
1533
|
if (resolved && !doc && !isEditMode) return null;
|
|
1534
|
+
if (isEditMode && !documentId && onDocumentSelected) {
|
|
1535
|
+
return /* @__PURE__ */ jsx14(DocumentDropZone, { onDocumentSelected });
|
|
1536
|
+
}
|
|
1461
1537
|
const label = doc?.title ?? title;
|
|
1462
1538
|
function handleClick() {
|
|
1463
1539
|
if (!doc) return;
|
|
@@ -1467,7 +1543,7 @@ function DocumentRow({
|
|
|
1467
1543
|
}
|
|
1468
1544
|
openDocFile(sectionId);
|
|
1469
1545
|
}
|
|
1470
|
-
return /* @__PURE__ */
|
|
1546
|
+
return /* @__PURE__ */ jsxs10(
|
|
1471
1547
|
"button",
|
|
1472
1548
|
{
|
|
1473
1549
|
type: "button",
|
|
@@ -1479,15 +1555,15 @@ function DocumentRow({
|
|
|
1479
1555
|
doc ? "cursor-pointer hover:bg-base-accent" : "cursor-default opacity-60"
|
|
1480
1556
|
),
|
|
1481
1557
|
children: [
|
|
1482
|
-
/* @__PURE__ */
|
|
1483
|
-
/* @__PURE__ */
|
|
1484
|
-
/* @__PURE__ */
|
|
1485
|
-
/* @__PURE__ */
|
|
1486
|
-
doc && /* @__PURE__ */
|
|
1558
|
+
/* @__PURE__ */ jsx14(FileText, { size: 18, "aria-hidden": "true", className: "shrink-0 text-base-contrast/60" }),
|
|
1559
|
+
/* @__PURE__ */ jsxs10("div", { className: "min-w-0 flex-1", children: [
|
|
1560
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2", children: [
|
|
1561
|
+
/* @__PURE__ */ jsx14("span", { className: "truncate text-sm font-medium text-base-contrast", children: label }),
|
|
1562
|
+
doc && /* @__PURE__ */ jsx14("span", { className: "shrink-0 rounded bg-base-100 px-1.5 py-0.5 text-xs font-medium text-base-contrast", children: doc.kind === "pdf" ? "PDF" : "HTML" })
|
|
1487
1563
|
] }),
|
|
1488
|
-
/* @__PURE__ */
|
|
1564
|
+
/* @__PURE__ */ jsx14("div", { className: "truncate text-xs text-base-contrast/70", children: doc ? `${doc.filename} \xB7 ${formatDocSize(doc.size)}` : resolved ? "File missing" : "\xA0" })
|
|
1489
1565
|
] }),
|
|
1490
|
-
doc && /* @__PURE__ */
|
|
1566
|
+
doc && /* @__PURE__ */ jsx14(ExternalLink, { size: 16, "aria-hidden": "true", className: "shrink-0 text-base-contrast/50" })
|
|
1491
1567
|
]
|
|
1492
1568
|
}
|
|
1493
1569
|
);
|
|
@@ -1496,18 +1572,23 @@ var Document_default = defineSection({
|
|
|
1496
1572
|
type: "document",
|
|
1497
1573
|
category: "brand-guide",
|
|
1498
1574
|
label: "Document",
|
|
1499
|
-
icon: /* @__PURE__ */
|
|
1575
|
+
icon: /* @__PURE__ */ jsx14(FileText, { size: 18 }),
|
|
1500
1576
|
schema: schema4,
|
|
1501
1577
|
// The row reads the client-side document registry and opens the overlay, so
|
|
1502
1578
|
// the viewer hydrates it as an island rather than emitting static HTML.
|
|
1503
1579
|
interactive: true,
|
|
1504
1580
|
authoringNote: "One downloadable/viewable file \u2014 a PDF or a single self-contained HTML file \u2014 presented as a clickable row. Clicking opens it in a full-viewport preview overlay without leaving the page; on phones a PDF opens in the OS viewer instead. Pick the file from the media library (documents tab); leave the title blank to show the file name without its extension, or type one to override it. Leave 'Show in Documents menu' on for anything a viewer should be able to reach from any page \u2014 those entries collect into a derived Documents group at the bottom of the left nav. Use one section per file; for a set of downloads, place several document sections in a container.",
|
|
1505
|
-
component: ({ content, isEditMode }) => /* @__PURE__ */
|
|
1581
|
+
component: ({ content, isEditMode, onChange }) => /* @__PURE__ */ jsx14(
|
|
1506
1582
|
DocumentRow,
|
|
1507
1583
|
{
|
|
1508
1584
|
sectionId: content.id ?? "",
|
|
1509
1585
|
title: content.content.title,
|
|
1510
|
-
|
|
1586
|
+
documentId: content.content.doc?.documentId ?? "",
|
|
1587
|
+
isEditMode,
|
|
1588
|
+
onDocumentSelected: onChange ? (documentId) => onChange({
|
|
1589
|
+
...content,
|
|
1590
|
+
content: { ...content.content, doc: { ...content.content.doc, documentId } }
|
|
1591
|
+
}) : void 0
|
|
1511
1592
|
}
|
|
1512
1593
|
),
|
|
1513
1594
|
defaults: () => ({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DocFilePane.d.ts","sourceRoot":"","sources":["../../../src/components/docfile/DocFilePane.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAOxD;;;;;;;;;GASG;AACH,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,EAClC,GAAG,EACH,OAAO,EACP,MAAc,GACf,EAAE;IAAE,GAAG,EAAE,YAAY,CAAC;IAAC,OAAO,EAAE,MAAM,IAAI,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,
|
|
1
|
+
{"version":3,"file":"DocFilePane.d.ts","sourceRoot":"","sources":["../../../src/components/docfile/DocFilePane.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAOxD;;;;;;;;;GASG;AACH,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,EAClC,GAAG,EACH,OAAO,EACP,MAAc,GACf,EAAE;IAAE,GAAG,EAAE,YAAY,CAAC;IAAC,OAAO,EAAE,MAAM,IAAI,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,2CAgH9D"}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { SiteDocument } from "../../lib/documents";
|
|
2
2
|
/** Toggled on <html> while a document is open (the chat's class mechanism). */
|
|
3
3
|
export declare const DOC_OPEN_CLASS = "bp-doc-open";
|
|
4
|
+
/** Held on <html> for a beat after close. bp-doc-open's `transition: left`
|
|
5
|
+
* rule leaves WITH the class, so without this the nav sidebar snaps home
|
|
6
|
+
* instead of gliding back the way it glided out — this class carries the
|
|
7
|
+
* same transition while the sidebar makes the return trip. */
|
|
8
|
+
export declare const DOC_CLOSING_CLASS = "bp-doc-closing";
|
|
4
9
|
/** The one breakpoint the doc feature keys off: at or above it the overlay
|
|
5
10
|
* gets the desktop stage; below it a PDF goes to the OS viewer instead (iOS
|
|
6
11
|
* Safari cannot scroll an iframed PDF, Android Chrome forces a download).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doc-file-state.d.ts","sourceRoot":"","sources":["../../../src/components/docfile/doc-file-state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGxD,+EAA+E;AAC/E,eAAO,MAAM,cAAc,gBAAgB,CAAC;AAE5C;;;;;;;mBAOmB;AACnB,eAAO,MAAM,iBAAiB,wBAAwB,CAAC;AAEvD;;4CAE4C;AAC5C,eAAO,MAAM,gBAAgB;eAA4B,MAAM,GAAG,IAAI;EAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"doc-file-state.d.ts","sourceRoot":"","sources":["../../../src/components/docfile/doc-file-state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGxD,+EAA+E;AAC/E,eAAO,MAAM,cAAc,gBAAgB,CAAC;AAE5C;;;+DAG+D;AAC/D,eAAO,MAAM,iBAAiB,mBAAmB,CAAC;AAKlD;;;;;;;mBAOmB;AACnB,eAAO,MAAM,iBAAiB,wBAAwB,CAAC;AAEvD;;4CAE4C;AAC5C,eAAO,MAAM,gBAAgB;eAA4B,MAAM,GAAG,IAAI;EAAuB,CAAC;AA4B9F,wBAAgB,YAAY,IAAI,OAAO,CAGtC;AAkDD,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAWnD;AAED,wBAAgB,YAAY,IAAI,IAAI,CAUnC;AAED,wBAAgB,cAAc,IAAI,MAAM,GAAG,IAAI,CAE9C;AAED,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,GAAG,MAAM,IAAI,CAkCnF;AAED,4DAA4D;AAC5D,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,YAAY,EAAE,GAAG,IAAI,CAI3D;AAED;2EAC2E;AAC3E,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,YAAY,EAAE,GAAG,IAAI,CAG5D;AAED,wBAAgB,gBAAgB,IAAI,YAAY,EAAE,GAAG,IAAI,CAExD;AAED,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,YAAY,EAAE,GAAG,IAAI,KAAK,IAAI,GAAG,MAAM,IAAI,CAI5F;AAED,+EAA+E;AAC/E,wBAAgB,iBAAiB,IAAI,IAAI,CAQxC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
interface DocumentDropZoneProps {
|
|
2
|
+
/** Fired with the staged/committed document id once the upload resolves. */
|
|
3
|
+
onDocumentSelected: (documentId: string) => void;
|
|
4
|
+
className?: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* The empty state of a `document` section in edit mode: drop a PDF/HTML file to
|
|
8
|
+
* upload it, or click to pick one from the library. Editor-only — the viewer
|
|
9
|
+
* never renders an unconfigured document section at all.
|
|
10
|
+
*
|
|
11
|
+
* Deliberately thin on validation: every rejection (wrong type, too large, no
|
|
12
|
+
* documents store) belongs to `uploadDocument`, which already routes typed copy
|
|
13
|
+
* through the pipeline's `onUploadError` → `UploadRejectionAlert` channel. The
|
|
14
|
+
* one judgement made here is WHICH file of a multi-file drop to hand over.
|
|
15
|
+
*/
|
|
16
|
+
export declare function DocumentDropZone({ onDocumentSelected, className }: DocumentDropZoneProps): import("react/jsx-runtime").JSX.Element;
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=DocumentDropZone.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DocumentDropZone.d.ts","sourceRoot":"","sources":["../../../src/components/primitives/DocumentDropZone.tsx"],"names":[],"mappings":"AAMA,UAAU,qBAAqB;IAC7B,4EAA4E;IAC5E,kBAAkB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACjD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAID;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,kBAAkB,EAAE,SAAS,EAAE,EAAE,qBAAqB,2CAsDxF"}
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
export declare function DocumentRow({ sectionId, title, isEditMode, }: {
|
|
1
|
+
export declare function DocumentRow({ sectionId, title, documentId, isEditMode, onDocumentSelected, }: {
|
|
2
2
|
sectionId: string;
|
|
3
3
|
title: string;
|
|
4
|
+
/** Raw `content.doc.documentId`. Empty means UNCONFIGURED, which is a
|
|
5
|
+
* different state from an id that no longer resolves (see below). */
|
|
6
|
+
documentId?: string;
|
|
4
7
|
isEditMode: boolean;
|
|
8
|
+
/** Present only when the section is editable — wires a picked/uploaded id
|
|
9
|
+
* back into content. Absent for viewers and for a locked/read-only render. */
|
|
10
|
+
onDocumentSelected?: (documentId: string) => void;
|
|
5
11
|
}): import("react/jsx-runtime").JSX.Element | null;
|
|
6
12
|
declare const _default: import("../../..").SectionDefinition<{
|
|
7
13
|
type: "document";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/sections/Document/index.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/sections/Document/index.tsx"],"names":[],"mappings":"AAsDA,wBAAgB,WAAW,CAAC,EAC1B,SAAS,EACT,KAAK,EACL,UAAe,EACf,UAAU,EACV,kBAAkB,GACnB,EAAE;IACD,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd;0EACsE;IACtE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;IACpB;mFAC+E;IAC/E,kBAAkB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;CACnD,kDA8DA;;;;;;;;;;;;AAED,wBA2DG"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
allSectionDefs
|
|
3
|
-
} from "../../chunk-
|
|
4
|
-
import "../../chunk-
|
|
3
|
+
} from "../../chunk-6SLGKRLW.js";
|
|
4
|
+
import "../../chunk-P3NNI3QI.js";
|
|
5
5
|
import "../../chunk-LBHQWIVL.js";
|
|
6
6
|
import "../../chunk-5THS4K5D.js";
|
|
7
7
|
import "../../chunk-EWNDBKYK.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
allSectionDefs
|
|
3
|
-
} from "../../chunk-
|
|
4
|
-
import "../../chunk-
|
|
3
|
+
} from "../../chunk-6SLGKRLW.js";
|
|
4
|
+
import "../../chunk-P3NNI3QI.js";
|
|
5
5
|
import "../../chunk-LBHQWIVL.js";
|
|
6
6
|
import "../../chunk-5THS4K5D.js";
|
|
7
7
|
import "../../chunk-EWNDBKYK.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EditorShell.d.ts","sourceRoot":"","sources":["../../../src/components/shell/EditorShell.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAejD,OAAO,EAAoC,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAyDtF,OAAO,KAAK,EAAE,eAAe,EAA2D,MAAM,oBAAoB,CAAC;AA2CnH,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AA2BxD,UAAU,KAAK;IACb,OAAO,EAAE,MAAM,CAAC;IAIhB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,YAAY,EAAE;QACZ,KAAK,EAAE,OAAO,CAAC;QACf,aAAa,EAAE,OAAO,CAAC;QACvB,YAAY,EAAE,OAAO,CAAC;QACtB,cAAc,EAAE,OAAO,CAAC;QACxB,kBAAkB,EAAE,OAAO,CAAC;QAC5B,cAAc,EAAE,OAAO,CAAC;KACzB,CAAC;IACF,WAAW,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,CAAA;KAAE,GAAG,IAAI,CAAC;IAIhE,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAKjC,eAAe,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;CAC1C;AAED,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,EAClC,OAAO,EACP,YAAY,EACZ,MAAM,EACN,SAAS,EAAE,gBAAgB,EAC3B,YAAY,EACZ,WAAW,EACX,sBAA8B,EAC9B,eAAe,GAChB,EAAE,KAAK,
|
|
1
|
+
{"version":3,"file":"EditorShell.d.ts","sourceRoot":"","sources":["../../../src/components/shell/EditorShell.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAejD,OAAO,EAAoC,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAyDtF,OAAO,KAAK,EAAE,eAAe,EAA2D,MAAM,oBAAoB,CAAC;AA2CnH,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AA2BxD,UAAU,KAAK;IACb,OAAO,EAAE,MAAM,CAAC;IAIhB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,YAAY,EAAE;QACZ,KAAK,EAAE,OAAO,CAAC;QACf,aAAa,EAAE,OAAO,CAAC;QACvB,YAAY,EAAE,OAAO,CAAC;QACtB,cAAc,EAAE,OAAO,CAAC;QACxB,kBAAkB,EAAE,OAAO,CAAC;QAC5B,cAAc,EAAE,OAAO,CAAC;KACzB,CAAC;IACF,WAAW,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,CAAA;KAAE,GAAG,IAAI,CAAC;IAIhE,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAKjC,eAAe,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;CAC1C;AAED,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,EAClC,OAAO,EACP,YAAY,EACZ,MAAM,EACN,SAAS,EAAE,gBAAgB,EAC3B,YAAY,EACZ,WAAW,EACX,sBAA8B,EAC9B,eAAe,GAChB,EAAE,KAAK,2CA82DP;AAwED;6EAC6E;AAC7E,wBAAgB,kBAAkB,CAChC,YAAY,EAAE,WAAW,CAAC,YAAY,CAAC,EACvC,aAAa,EAAE,MAAM,GAAG,IAAI,EAC5B,eAAe,EAAE,MAAM,GAAG,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAK/B"}
|
|
@@ -31,6 +31,12 @@ export interface MediaLibraryContextValue {
|
|
|
31
31
|
* predate documents and must keep compiling. */
|
|
32
32
|
documentManifest?: DocumentManifest;
|
|
33
33
|
pendingDocuments?: PendingDocument[];
|
|
34
|
+
/** Upload a PDF/HTML file through the document pipeline, calling back with the
|
|
35
|
+
* document id once it is staged. Optional for the same reason as the two
|
|
36
|
+
* fields above. Owns every rejection itself (unsupported type, too large, no
|
|
37
|
+
* documents store) — they surface on the pipeline's `onUploadError` channel,
|
|
38
|
+
* so a caller never needs to pre-validate or report. */
|
|
39
|
+
uploadDocument?: (file: File, onComplete?: (documentId: string) => void) => void | Promise<void>;
|
|
34
40
|
}
|
|
35
41
|
export declare const MediaLibraryContext: import("react").Context<MediaLibraryContextValue | null>;
|
|
36
42
|
export declare function useMediaLibrary(): MediaLibraryContextValue | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MediaLibraryContext.d.ts","sourceRoot":"","sources":["../../../src/components/shell/MediaLibraryContext.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAEvF,0EAA0E;AAC1E,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,UAAU,GAAG,OAAO,GAAG,UAAU,CAAC;AAEtE;wEACwE;AACxE,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,qBAAqB,CAAC;CAC9B;AAED,MAAM,WAAW,wBAAwB;IACvC;0EACsE;IACtE,eAAe,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,EAAE,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,aAAa,CAAC,WAAW,CAAC,CAAA;KAAE,KAAK,IAAI,CAAC;IAC9G,UAAU,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IACjC,sBAAsB,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,EAAE,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,MAAM,CAAC;IACnH,qBAAqB,EAAE,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,EAAE,KAAK,IAAI,CAAC;IACpF,QAAQ,EAAE,aAAa,CAAC;IACxB,YAAY,EAAE,SAAS,EAAE,CAAC;IAC1B,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B;qDACiD;IACjD,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,gBAAgB,CAAC,EAAE,eAAe,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"MediaLibraryContext.d.ts","sourceRoot":"","sources":["../../../src/components/shell/MediaLibraryContext.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAEvF,0EAA0E;AAC1E,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,UAAU,GAAG,OAAO,GAAG,UAAU,CAAC;AAEtE;wEACwE;AACxE,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,qBAAqB,CAAC;CAC9B;AAED,MAAM,WAAW,wBAAwB;IACvC;0EACsE;IACtE,eAAe,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,EAAE,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,aAAa,CAAC,WAAW,CAAC,CAAA;KAAE,KAAK,IAAI,CAAC;IAC9G,UAAU,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IACjC,sBAAsB,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,EAAE,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,MAAM,CAAC;IACnH,qBAAqB,EAAE,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,EAAE,KAAK,IAAI,CAAC;IACpF,QAAQ,EAAE,aAAa,CAAC;IACxB,YAAY,EAAE,SAAS,EAAE,CAAC;IAC1B,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B;qDACiD;IACjD,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,gBAAgB,CAAC,EAAE,eAAe,EAAE,CAAC;IACrC;;;;6DAIyD;IACzD,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,UAAU,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAClG;AAED,eAAO,MAAM,mBAAmB,0DAAuD,CAAC;AAExF,wBAAgB,eAAe,IAAI,wBAAwB,GAAG,IAAI,CAEjE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useDocumentUploads.d.ts","sourceRoot":"","sources":["../../src/hooks/useDocumentUploads.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useDocumentUploads.d.ts","sourceRoot":"","sources":["../../src/hooks/useDocumentUploads.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAyB,MAAM,sBAAsB,CAAC;AAapF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAS/E,UAAU,uBAAuB;IAC/B,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,mBAAmB,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,gBAAgB,CAAC,CAAC,CAAC;IAC5E,oBAAoB,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IACpE;;;yEAGqE;IACrE,gBAAgB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,+EAA+E;IAC/E,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAC3C;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,gBAAgB,EAChB,mBAAmB,EACnB,oBAAoB,EACpB,gBAAgB,EAChB,aAAa,GACd,EAAE,uBAAuB;;;;2BAiET,IAAI,eAAe,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,KAAG,OAAO,CAAC,IAAI,CAAC;4BAiJhE,MAAM,EAAE;4BAqC2B,MAAM,EAAE;;EAqC1D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useMediaPipeline.d.ts","sourceRoot":"","sources":["../../src/hooks/useMediaPipeline.ts"],"names":[],"mappings":"AAEA,OAAO,EAAmB,KAAK,SAAS,EAAmB,MAAM,gBAAgB,CAAC;AAClF,OAAO,KAAK,EAAE,aAAa,EAAiC,MAAM,gBAAgB,CAAC;AACnF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,yCAAyC,CAAC;AAiBxF,OAAO,EAAwB,KAAK,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAEnF,UAAU,qBAAqB;IAC7B,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,aAAa,EAAE,aAAa,CAAC;IAC7B,gBAAgB,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC,CAAC;IACtE,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,mBAAmB,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,gBAAgB,CAAC,CAAC,CAAC;IAC5E,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,WAAW,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC;IACnE,oBAAoB,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IACpE,kBAAkB,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACtE,gBAAgB,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC;IAIvE,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAG1C,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAI3C,kBAAkB,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,UAAU,KAAK,IAAI,CAAC;CAC1E;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,UAAU,EACV,aAAa,EACb,gBAAgB,EAChB,gBAAgB,EAChB,mBAAmB,EACnB,QAAQ,EACR,WAAW,EACX,oBAAoB,EACpB,kBAAkB,EAClB,gBAAgB,EAChB,aAAa,EACb,cAAc,EACd,kBAAkB,GACnB,EAAE,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+BAoV8B,IAAI,EAAE,mBAAkB,MAAM,GAAG,IAAI;6BAiEvC,MAAM,EAAE;gCA0DL,MAAM,EAAE;;;+BA+Bf,MAAM,OAAO,MAAM;+BAiBnB,MAAM,YAAY,MAAM,GAAG,IAAI,KAAG,MAAM;6BAW1C,MAAM,QAAQ,MAAM;2BAWtB,MAAM,YAAY,MAAM,GAAG,IAAI;6BA4BvB,MAAM,YAAY,MAAM,GAAG,QAAQ;2BAd3C,MAAM,EAAE,YAAY,MAAM,GAAG,IAAI;kCA4DhE;QAAE,IAAI,EAAE,IAAI,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,EAAE,mBAC9B,MAAM,GAAG,IAAI;;;;wBA4CO,IAAI,eAAe,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,SAAS;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE;;;;;;;
|
|
1
|
+
{"version":3,"file":"useMediaPipeline.d.ts","sourceRoot":"","sources":["../../src/hooks/useMediaPipeline.ts"],"names":[],"mappings":"AAEA,OAAO,EAAmB,KAAK,SAAS,EAAmB,MAAM,gBAAgB,CAAC;AAClF,OAAO,KAAK,EAAE,aAAa,EAAiC,MAAM,gBAAgB,CAAC;AACnF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,yCAAyC,CAAC;AAiBxF,OAAO,EAAwB,KAAK,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAEnF,UAAU,qBAAqB;IAC7B,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,aAAa,EAAE,aAAa,CAAC;IAC7B,gBAAgB,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC,CAAC;IACtE,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,mBAAmB,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,gBAAgB,CAAC,CAAC,CAAC;IAC5E,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,WAAW,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC;IACnE,oBAAoB,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IACpE,kBAAkB,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACtE,gBAAgB,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC;IAIvE,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAG1C,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAI3C,kBAAkB,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,UAAU,KAAK,IAAI,CAAC;CAC1E;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,UAAU,EACV,aAAa,EACb,gBAAgB,EAChB,gBAAgB,EAChB,mBAAmB,EACnB,QAAQ,EACR,WAAW,EACX,oBAAoB,EACpB,kBAAkB,EAClB,gBAAgB,EAChB,aAAa,EACb,cAAc,EACd,kBAAkB,GACnB,EAAE,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+BAoV8B,IAAI,EAAE,mBAAkB,MAAM,GAAG,IAAI;6BAiEvC,MAAM,EAAE;gCA0DL,MAAM,EAAE;;;+BA+Bf,MAAM,OAAO,MAAM;+BAiBnB,MAAM,YAAY,MAAM,GAAG,IAAI,KAAG,MAAM;6BAW1C,MAAM,QAAQ,MAAM;2BAWtB,MAAM,YAAY,MAAM,GAAG,IAAI;6BA4BvB,MAAM,YAAY,MAAM,GAAG,QAAQ;2BAd3C,MAAM,EAAE,YAAY,MAAM,GAAG,IAAI;kCA4DhE;QAAE,IAAI,EAAE,IAAI,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,EAAE,mBAC9B,MAAM,GAAG,IAAI;;;;wBA4CO,IAAI,eAAe,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,SAAS;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE;;;;;;;EAqHjH"}
|
package/dist/lib/dexie.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { SectionContent } from "../schemas/sections";
|
|
|
2
2
|
import type { SiteIndex, SiteConfig } from "../schemas/site-config";
|
|
3
3
|
import type { LoadedSection } from "./loader";
|
|
4
4
|
import type { MediaManifest, MediaItem } from "../media/types";
|
|
5
|
-
import type { DocumentManifestEntry } from "../schemas/documents";
|
|
5
|
+
import type { DocumentManifest, DocumentManifestEntry } from "../schemas/documents";
|
|
6
6
|
export interface PreservedEditRow {
|
|
7
7
|
id: string;
|
|
8
8
|
sectionId: string;
|
|
@@ -69,6 +69,7 @@ export declare function cacheContent(sha: string, sections: LoadedSection[], ind
|
|
|
69
69
|
savedBranchSha: string | null;
|
|
70
70
|
changedSectionIds: string[];
|
|
71
71
|
mainIndex: SiteIndex | null;
|
|
72
|
+
documentManifest?: DocumentManifest;
|
|
72
73
|
}): Promise<void>;
|
|
73
74
|
export declare function getCachedContent(): Promise<{
|
|
74
75
|
sha: string;
|
|
@@ -78,6 +79,7 @@ export declare function getCachedContent(): Promise<{
|
|
|
78
79
|
savedBranchSha: string | null;
|
|
79
80
|
changedSectionIds: string[];
|
|
80
81
|
mainIndex: SiteIndex | null;
|
|
82
|
+
documentManifest: DocumentManifest;
|
|
81
83
|
} | null>;
|
|
82
84
|
/** Invalidate only the cached content snapshot (e.g. after a publish removes the
|
|
83
85
|
* draft), forcing the next editor load to refetch rather than serve stale diff
|
package/dist/lib/dexie.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dexie.d.ts","sourceRoot":"","sources":["../../src/lib/dexie.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,SAAS,EAAe,UAAU,EAAQ,MAAM,wBAAwB,CAAC;AAEvF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC/D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"dexie.d.ts","sourceRoot":"","sources":["../../src/lib/dexie.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/dexie.js
CHANGED
|
@@ -321,7 +321,8 @@ async function cacheContent(sha, sections, index, siteConfig, diff) {
|
|
|
321
321
|
updatedAt: now,
|
|
322
322
|
savedBranchSha: diff?.savedBranchSha ?? null,
|
|
323
323
|
changedSectionIds: diff?.changedSectionIds ?? [],
|
|
324
|
-
mainIndex: diff?.mainIndex ?? null
|
|
324
|
+
mainIndex: diff?.mainIndex ?? null,
|
|
325
|
+
documentManifest: diff?.documentManifest ?? null
|
|
325
326
|
});
|
|
326
327
|
}
|
|
327
328
|
async function getCachedContent() {
|
|
@@ -336,7 +337,11 @@ async function getCachedContent() {
|
|
|
336
337
|
changedSectionIds: row.changedSectionIds ?? [],
|
|
337
338
|
// No stored mainIndex (e.g. legacy or post-publish cache) means "no draft",
|
|
338
339
|
// so the published index equals the current index.
|
|
339
|
-
mainIndex: row.mainIndex ?? row.index
|
|
340
|
+
mainIndex: row.mainIndex ?? row.index,
|
|
341
|
+
// Legacy row (pre-documents package): {} matches the fetch path's posture.
|
|
342
|
+
// The narrow stale edge — an old row at a sha that DOES hold a manifest —
|
|
343
|
+
// heals on the next fresh load or save.
|
|
344
|
+
documentManifest: row.documentManifest ?? {}
|
|
340
345
|
};
|
|
341
346
|
}
|
|
342
347
|
async function clearContentCache() {
|
package/dist/storage/types.d.ts
CHANGED
|
@@ -111,9 +111,18 @@ export declare const ASSET_CACHE_CONTROL = "max-age=31536000, immutable";
|
|
|
111
111
|
* can slot in. All paths are SITE-RELATIVE ("media/{hash}/original.mp4") —
|
|
112
112
|
* implementations prepend the tenant prefix ("sites/{siteId}/") themselves so
|
|
113
113
|
* a caller can never address another tenant's objects.
|
|
114
|
+
*
|
|
115
|
+
* This bucket is PUBLIC: an object's URL is statically derivable from the
|
|
116
|
+
* bucket's public base, so `createUploadUrl` returns it once at upload time
|
|
117
|
+
* (built locally in standalone mode, brokered from the admin app's env in
|
|
118
|
+
* platform mode) and the media manifest stores that absolute URL. Rendering
|
|
119
|
+
* reads the manifest — nothing on the read path re-derives a URL.
|
|
114
120
|
*/
|
|
115
121
|
export interface AssetStore {
|
|
116
|
-
/**
|
|
122
|
+
/**
|
|
123
|
+
* Mint a signed direct-upload URL for the browser (PUT), along with the
|
|
124
|
+
* object's permanent public URL for the caller to persist.
|
|
125
|
+
*/
|
|
117
126
|
createUploadUrl(path: string, opts: {
|
|
118
127
|
contentType: string;
|
|
119
128
|
}): Promise<{
|
|
@@ -121,8 +130,6 @@ export interface AssetStore {
|
|
|
121
130
|
path: string;
|
|
122
131
|
publicUrl: string;
|
|
123
132
|
}>;
|
|
124
|
-
/** Deterministic public CDN URL for an object (no network call). */
|
|
125
|
-
getPublicUrl(path: string): string;
|
|
126
133
|
delete(paths: string[]): Promise<void>;
|
|
127
134
|
/** Recursive listing under a site-relative prefix. */
|
|
128
135
|
list(prefix: string): Promise<{
|
|
@@ -145,8 +152,10 @@ export declare const DOCUMENT_CACHE_CONTROL = "private, max-age=31536000, immuta
|
|
|
145
152
|
* Deliberately not an AssetStore: the assets bucket is public by configuration
|
|
146
153
|
* at R2_PUBLIC_BASE (videos depend on it), so a signed GET there would protect
|
|
147
154
|
* nothing and hosted HTML would render unsandboxed on a sibling origin. Hence
|
|
148
|
-
*
|
|
149
|
-
*
|
|
155
|
+
* the asymmetry with AssetStore — an asset has a permanent public URL handed
|
|
156
|
+
* back by `createUploadUrl`, while a document has no durable URL at all: every
|
|
157
|
+
* read is a short-lived signed GET from `createDownloadUrl`, minted only after
|
|
158
|
+
* the gated route has authorized the requester.
|
|
150
159
|
*
|
|
151
160
|
* Paths are SITE-RELATIVE ("documents/{hash}/original.pdf"); implementations
|
|
152
161
|
* prepend the tenant prefix themselves so a caller can never address another
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/storage/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;aACjB,cAAc,EAAE,MAAM,GAAG,IAAI;gBAA7B,cAAc,EAAE,MAAM,GAAG,IAAI;CAI1D;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,CACR,KAAK,EAAE,SAAS,EAAE,EAClB,OAAO,EAAE,MAAM,EAIf,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GACxC,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAEhC,QAAQ,CACN,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAC5B,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IAE9B,YAAY,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAE7C,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAE9B,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAE9E;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,0BAA0B,CAAC,CACzB,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAC3B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC3B;AAED;;;;;GAKG;AACH,qBAAa,iCAAkC,SAAQ,KAAK;aAC5B,KAAK,CAAC,EAAE,OAAO;gBAAf,KAAK,CAAC,EAAE,OAAO,YAAA;CAI9C;AAED;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,oBAAoB,CAAC;AAErD;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,QAA6B,CAAC;AAE7D;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAM5E;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;CAC/B,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,mBAAmB,gCAAgC,CAAC;AAEjE
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/storage/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;aACjB,cAAc,EAAE,MAAM,GAAG,IAAI;gBAA7B,cAAc,EAAE,MAAM,GAAG,IAAI;CAI1D;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,CACR,KAAK,EAAE,SAAS,EAAE,EAClB,OAAO,EAAE,MAAM,EAIf,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GACxC,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAEhC,QAAQ,CACN,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAC5B,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IAE9B,YAAY,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAE7C,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAE9B,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAE9E;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,0BAA0B,CAAC,CACzB,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAC3B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC3B;AAED;;;;;GAKG;AACH,qBAAa,iCAAkC,SAAQ,KAAK;aAC5B,KAAK,CAAC,EAAE,OAAO;gBAAf,KAAK,CAAC,EAAE,OAAO,YAAA;CAI9C;AAED;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,oBAAoB,CAAC;AAErD;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,QAA6B,CAAC;AAE7D;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAM5E;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;CAC/B,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,mBAAmB,gCAAgC,CAAC;AAEjE;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,UAAU;IACzB;;;OAGG;IACH,eAAe,CACb,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,GAC5B,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAEnE,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvC,sDAAsD;IACtD,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC,CAAC;CACpF;AAED;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,yCAAyC,CAAC;AAE7E;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,aAAa;IAC5B,6DAA6D;IAC7D,eAAe,CACb,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,GAC5B,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAEhD;;;;;OAKG;IACH,iBAAiB,CACf,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE;QAAE,gBAAgB,EAAE,MAAM,CAAC;QAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAAE,GAC5D,OAAO,CAAC,MAAM,CAAC,CAAC;IAEnB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvC,sDAAsD;IACtD,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;CACzF"}
|
package/package.json
CHANGED
|
@@ -112,7 +112,14 @@ export default function DocFilePane({
|
|
|
112
112
|
<iframe
|
|
113
113
|
key={attempt}
|
|
114
114
|
ref={frameRef}
|
|
115
|
-
|
|
115
|
+
// PDF open-parameters hide the native viewer's toolbar/side panel in
|
|
116
|
+
// Chromium so the pane's own top bar is the only chrome; Firefox and
|
|
117
|
+
// Safari ignore them harmlessly. view=FitH fits the page to the pane
|
|
118
|
+
// width in Chromium (default is fit-page, which wastes ~half the
|
|
119
|
+
// pane); zoom=page-width is the Firefox/pdf.js spelling of the same
|
|
120
|
+
// thing, verified harmless in Chromium. A client-side fragment: it
|
|
121
|
+
// survives the gated route's 302 and works on pre-save blob: URLs too.
|
|
122
|
+
src={doc.kind === "pdf" ? `${doc.url}#toolbar=0&navpanes=0&view=FitH&zoom=page-width` : doc.url}
|
|
116
123
|
title={doc.title}
|
|
117
124
|
// Opaque origin for authored HTML: no portal cookies, storage or
|
|
118
125
|
// parent DOM. The ATTRIBUTE applies regardless of origin, so
|
|
@@ -4,6 +4,15 @@ import { createEvent } from "../../lib/events";
|
|
|
4
4
|
/** Toggled on <html> while a document is open (the chat's class mechanism). */
|
|
5
5
|
export const DOC_OPEN_CLASS = "bp-doc-open";
|
|
6
6
|
|
|
7
|
+
/** Held on <html> for a beat after close. bp-doc-open's `transition: left`
|
|
8
|
+
* rule leaves WITH the class, so without this the nav sidebar snaps home
|
|
9
|
+
* instead of gliding back the way it glided out — this class carries the
|
|
10
|
+
* same transition while the sidebar makes the return trip. */
|
|
11
|
+
export const DOC_CLOSING_CLASS = "bp-doc-closing";
|
|
12
|
+
|
|
13
|
+
/** Slightly longer than the 300ms CSS ease so the class outlives the glide. */
|
|
14
|
+
const DOC_CLOSING_MS = 350;
|
|
15
|
+
|
|
7
16
|
/** The one breakpoint the doc feature keys off: at or above it the overlay
|
|
8
17
|
* gets the desktop stage; below it a PDF goes to the OS viewer instead (iOS
|
|
9
18
|
* Safari cannot scroll an iframed PDF, Android Chrome forces a download).
|
|
@@ -27,6 +36,7 @@ interface DocFileStore {
|
|
|
27
36
|
openListeners: Set<(id: string | null) => void>;
|
|
28
37
|
docsListeners: Set<(docs: SiteDocument[] | null) => void>;
|
|
29
38
|
teardown: (() => void) | null;
|
|
39
|
+
closingTimer: ReturnType<typeof setTimeout> | null;
|
|
30
40
|
}
|
|
31
41
|
|
|
32
42
|
// Symbol-keyed on globalThis for the same reason the section registry is: the
|
|
@@ -38,7 +48,7 @@ const g = globalThis as unknown as Record<symbol, DocFileStore | undefined>;
|
|
|
38
48
|
function store(): DocFileStore {
|
|
39
49
|
let s = g[STORE_KEY];
|
|
40
50
|
if (!s) {
|
|
41
|
-
s = { open: null, docs: null, openListeners: new Set(), docsListeners: new Set(), teardown: null };
|
|
51
|
+
s = { open: null, docs: null, openListeners: new Set(), docsListeners: new Set(), teardown: null, closingTimer: null };
|
|
42
52
|
g[STORE_KEY] = s;
|
|
43
53
|
}
|
|
44
54
|
return s;
|
|
@@ -63,9 +73,24 @@ function readHash(): string | null {
|
|
|
63
73
|
function apply(sectionId: string | null): void {
|
|
64
74
|
const s = store();
|
|
65
75
|
if (s.open === sectionId) return;
|
|
76
|
+
const wasOpen = s.open !== null;
|
|
66
77
|
s.open = sectionId;
|
|
67
78
|
if (typeof document !== "undefined") {
|
|
68
|
-
document.documentElement
|
|
79
|
+
const root = document.documentElement;
|
|
80
|
+
root.classList.toggle(DOC_OPEN_CLASS, sectionId !== null);
|
|
81
|
+
// A reopen during the return glide cancels the closing state outright.
|
|
82
|
+
if (s.closingTimer !== null) {
|
|
83
|
+
clearTimeout(s.closingTimer);
|
|
84
|
+
s.closingTimer = null;
|
|
85
|
+
root.classList.remove(DOC_CLOSING_CLASS);
|
|
86
|
+
}
|
|
87
|
+
if (sectionId === null && wasOpen) {
|
|
88
|
+
root.classList.add(DOC_CLOSING_CLASS);
|
|
89
|
+
s.closingTimer = setTimeout(() => {
|
|
90
|
+
root.classList.remove(DOC_CLOSING_CLASS);
|
|
91
|
+
s.closingTimer = null;
|
|
92
|
+
}, DOC_CLOSING_MS);
|
|
93
|
+
}
|
|
69
94
|
}
|
|
70
95
|
for (const listener of [...s.openListeners]) listener(sectionId);
|
|
71
96
|
}
|
|
@@ -175,6 +200,9 @@ export function subscribeSiteDocuments(cb: (docs: SiteDocument[] | null) => void
|
|
|
175
200
|
export function resetDocFileState(): void {
|
|
176
201
|
const s = g[STORE_KEY];
|
|
177
202
|
if (s?.teardown) s.teardown();
|
|
178
|
-
if (
|
|
203
|
+
if (s?.closingTimer) clearTimeout(s.closingTimer);
|
|
204
|
+
if (typeof document !== "undefined") {
|
|
205
|
+
document.documentElement.classList.remove(DOC_OPEN_CLASS, DOC_CLOSING_CLASS);
|
|
206
|
+
}
|
|
179
207
|
g[STORE_KEY] = undefined;
|
|
180
208
|
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { useState, type DragEvent } from "react";
|
|
2
|
+
import { FileUp } from "lucide-react";
|
|
3
|
+
import { cn } from "../../lib/cn";
|
|
4
|
+
import { DOCUMENT_MIME_TO_EXT } from "../../schemas/documents";
|
|
5
|
+
import { useMediaLibrary } from "../shell/MediaLibraryContext";
|
|
6
|
+
|
|
7
|
+
interface DocumentDropZoneProps {
|
|
8
|
+
/** Fired with the staged/committed document id once the upload resolves. */
|
|
9
|
+
onDocumentSelected: (documentId: string) => void;
|
|
10
|
+
className?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const isDocumentFile = (file: File) => file.type in DOCUMENT_MIME_TO_EXT;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The empty state of a `document` section in edit mode: drop a PDF/HTML file to
|
|
17
|
+
* upload it, or click to pick one from the library. Editor-only — the viewer
|
|
18
|
+
* never renders an unconfigured document section at all.
|
|
19
|
+
*
|
|
20
|
+
* Deliberately thin on validation: every rejection (wrong type, too large, no
|
|
21
|
+
* documents store) belongs to `uploadDocument`, which already routes typed copy
|
|
22
|
+
* through the pipeline's `onUploadError` → `UploadRejectionAlert` channel. The
|
|
23
|
+
* one judgement made here is WHICH file of a multi-file drop to hand over.
|
|
24
|
+
*/
|
|
25
|
+
export function DocumentDropZone({ onDocumentSelected, className }: DocumentDropZoneProps) {
|
|
26
|
+
const mediaLibrary = useMediaLibrary();
|
|
27
|
+
const [dragging, setDragging] = useState(false);
|
|
28
|
+
|
|
29
|
+
// Only react to drags carrying files, so the editor's block reorder (which
|
|
30
|
+
// carries no `Files`) passes straight through. Mirrors ImageDropZone.
|
|
31
|
+
const hasFiles = (e: DragEvent) => e.dataTransfer.types.includes("Files");
|
|
32
|
+
|
|
33
|
+
const handleDragOver = (e: DragEvent) => {
|
|
34
|
+
if (!hasFiles(e)) return;
|
|
35
|
+
e.preventDefault();
|
|
36
|
+
e.dataTransfer.dropEffect = "copy";
|
|
37
|
+
setDragging(true);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const handleDragLeave = (e: DragEvent) => {
|
|
41
|
+
if (!hasFiles(e)) return;
|
|
42
|
+
const related = e.relatedTarget as Node | null;
|
|
43
|
+
if (related && e.currentTarget.contains(related)) return;
|
|
44
|
+
setDragging(false);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const handleDrop = (e: DragEvent) => {
|
|
48
|
+
if (!hasFiles(e)) return;
|
|
49
|
+
e.preventDefault();
|
|
50
|
+
e.stopPropagation();
|
|
51
|
+
setDragging(false);
|
|
52
|
+
const files = Array.from(e.dataTransfer.files);
|
|
53
|
+
if (files.length === 0) return;
|
|
54
|
+
// First document-typed file wins. With none, the first file still goes to
|
|
55
|
+
// the pipeline so its rejection names the real constraint ("only PDF and
|
|
56
|
+
// HTML documents can be uploaded") instead of a hand-rolled message here.
|
|
57
|
+
void mediaLibrary?.uploadDocument?.(files.find(isDocumentFile) ?? files[0], onDocumentSelected);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<button
|
|
62
|
+
type="button"
|
|
63
|
+
onClick={() =>
|
|
64
|
+
mediaLibrary?.openSelectModal(onDocumentSelected, { kinds: ["document"] })
|
|
65
|
+
}
|
|
66
|
+
onDragOver={handleDragOver}
|
|
67
|
+
onDragLeave={handleDragLeave}
|
|
68
|
+
onDrop={handleDrop}
|
|
69
|
+
className={cn(
|
|
70
|
+
"flex w-full cursor-pointer items-center justify-center gap-2 rounded-outer border-2 border-dashed border-base-200 bg-base px-4 py-6 text-sm text-base-contrast-light transition-colors hover:border-base-300 hover:bg-base-accent hover:text-base-contrast",
|
|
71
|
+
dragging && "border-primary bg-primary/5 text-base-contrast",
|
|
72
|
+
className,
|
|
73
|
+
)}
|
|
74
|
+
>
|
|
75
|
+
<FileUp size={18} aria-hidden="true" className="shrink-0" />
|
|
76
|
+
<span>Drop a PDF or HTML file, or choose from the library</span>
|
|
77
|
+
</button>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
@@ -3,6 +3,7 @@ import { z } from "zod";
|
|
|
3
3
|
import { ExternalLink, FileText } from "lucide-react";
|
|
4
4
|
import { defineSection } from "../../../lib/registry";
|
|
5
5
|
import { cn } from "../../../lib/cn";
|
|
6
|
+
import { DocumentDropZone } from "../../primitives/DocumentDropZone";
|
|
6
7
|
import { formatDocSize, type SiteDocument } from "../../../lib/documents";
|
|
7
8
|
import {
|
|
8
9
|
getSiteDocuments,
|
|
@@ -54,11 +55,19 @@ function useSiteDocument(sectionId: string): { doc: SiteDocument | null; resolve
|
|
|
54
55
|
export function DocumentRow({
|
|
55
56
|
sectionId,
|
|
56
57
|
title,
|
|
58
|
+
documentId = "",
|
|
57
59
|
isEditMode,
|
|
60
|
+
onDocumentSelected,
|
|
58
61
|
}: {
|
|
59
62
|
sectionId: string;
|
|
60
63
|
title: string;
|
|
64
|
+
/** Raw `content.doc.documentId`. Empty means UNCONFIGURED, which is a
|
|
65
|
+
* different state from an id that no longer resolves (see below). */
|
|
66
|
+
documentId?: string;
|
|
61
67
|
isEditMode: boolean;
|
|
68
|
+
/** Present only when the section is editable — wires a picked/uploaded id
|
|
69
|
+
* back into content. Absent for viewers and for a locked/read-only render. */
|
|
70
|
+
onDocumentSelected?: (documentId: string) => void;
|
|
62
71
|
}) {
|
|
63
72
|
const { doc, resolved } = useSiteDocument(sectionId);
|
|
64
73
|
|
|
@@ -67,6 +76,14 @@ export function DocumentRow({
|
|
|
67
76
|
// the section stays selectable and repairable.
|
|
68
77
|
if (resolved && !doc && !isEditMode) return null;
|
|
69
78
|
|
|
79
|
+
// An empty id is an unconfigured section: give editors somewhere to drop a
|
|
80
|
+
// file instead of a dead, permanently-disabled row. A NON-empty id that does
|
|
81
|
+
// not resolve is a different problem — it keeps the "File missing" row so the
|
|
82
|
+
// broken reference stays visible rather than looking like a fresh section.
|
|
83
|
+
if (isEditMode && !documentId && onDocumentSelected) {
|
|
84
|
+
return <DocumentDropZone onDocumentSelected={onDocumentSelected} />;
|
|
85
|
+
}
|
|
86
|
+
|
|
70
87
|
// `doc.title` is already `content.title || filenameStem(entry.filename)` —
|
|
71
88
|
// deriveSiteDocuments applies the fallback once so the row, the Documents nav
|
|
72
89
|
// entry and the overlay top bar can never disagree. Picking a file NEVER
|
|
@@ -130,14 +147,28 @@ export default defineSection({
|
|
|
130
147
|
"Pick the file from the media library (documents tab); leave the title blank to show the file name without its extension, or type one to override it. " +
|
|
131
148
|
"Leave 'Show in Documents menu' on for anything a viewer should be able to reach from any page — those entries collect into a derived Documents group at the bottom of the left nav. " +
|
|
132
149
|
"Use one section per file; for a set of downloads, place several document sections in a container.",
|
|
133
|
-
component: ({ content, isEditMode }) => (
|
|
150
|
+
component: ({ content, isEditMode, onChange }) => (
|
|
134
151
|
<DocumentRow
|
|
135
152
|
// The section id is added by the outer Section schema (id + layout
|
|
136
153
|
// intersected with the per-type schema), so it is present at render time
|
|
137
154
|
// but absent from this type's inferred content shape.
|
|
138
155
|
sectionId={(content as unknown as { id?: string }).id ?? ""}
|
|
139
156
|
title={content.content.title}
|
|
157
|
+
documentId={content.content.doc?.documentId ?? ""}
|
|
140
158
|
isEditMode={isEditMode}
|
|
159
|
+
// Writes `doc` only: `title` stays the editor's (deriveSiteDocuments
|
|
160
|
+
// applies the filename-stem fallback for display) and showInDocumentsNav
|
|
161
|
+
// is untouched. Spreading `content` preserves the id and layout envelope,
|
|
162
|
+
// which the container path (Container.tsx updateChild) also re-attaches.
|
|
163
|
+
onDocumentSelected={
|
|
164
|
+
onChange
|
|
165
|
+
? (documentId) =>
|
|
166
|
+
onChange({
|
|
167
|
+
...content,
|
|
168
|
+
content: { ...content.content, doc: { ...content.content.doc, documentId } },
|
|
169
|
+
})
|
|
170
|
+
: undefined
|
|
171
|
+
}
|
|
141
172
|
/>
|
|
142
173
|
),
|
|
143
174
|
defaults: () => ({
|
|
@@ -247,7 +247,8 @@ export default function EditorShell({
|
|
|
247
247
|
const [rejectedUploads, setRejectedUploads] = useState<{ name: string; reason: "size" | "type" }[]>([]);
|
|
248
248
|
// Surfaces useMediaPipeline's onUploadError (oversized/disallowed files,
|
|
249
249
|
// upload-intent failures, direct-to-bucket PUT failures) in the media
|
|
250
|
-
// library modal, alongside its existing client-side rejection banner
|
|
250
|
+
// library modal, alongside its existing client-side rejection banner — and at
|
|
251
|
+
// shell level while that modal is closed, for uploads started from a section.
|
|
251
252
|
const [uploadErrorMessage, setUploadErrorMessage] = useState<string | null>(null);
|
|
252
253
|
// Surfaces useMediaPipeline's onUploadNotice (e.g. "N files already exist
|
|
253
254
|
// and were left where they are" on dedup skip) in the media library modal.
|
|
@@ -1089,6 +1090,10 @@ export default function EditorShell({
|
|
|
1089
1090
|
setMainSha(headSha);
|
|
1090
1091
|
setChangedSectionIds(new Set(cached.changedSectionIds ?? []));
|
|
1091
1092
|
setMainIndex(cached.mainIndex ?? loadedIndex);
|
|
1093
|
+
// Without this, a cache-hit load left documentManifest null forever —
|
|
1094
|
+
// the siteDocuments memo's null-guard then never lifted and every
|
|
1095
|
+
// document row (committed or staged) read "File missing".
|
|
1096
|
+
setDocumentManifest(cached.documentManifest ?? {});
|
|
1092
1097
|
} else {
|
|
1093
1098
|
const data = await fetchSavedContent();
|
|
1094
1099
|
loadedSections = data.sections;
|
|
@@ -1098,6 +1103,7 @@ export default function EditorShell({
|
|
|
1098
1103
|
savedBranchSha: data.savedBranchSha,
|
|
1099
1104
|
changedSectionIds: data.changedSectionIds,
|
|
1100
1105
|
mainIndex: data.mainIndex,
|
|
1106
|
+
documentManifest: data.documentManifest ?? {},
|
|
1101
1107
|
});
|
|
1102
1108
|
if (data.mediaManifest) {
|
|
1103
1109
|
loadedManifest = data.mediaManifest;
|
|
@@ -1824,6 +1830,20 @@ export default function EditorShell({
|
|
|
1824
1830
|
</div>
|
|
1825
1831
|
)}
|
|
1826
1832
|
|
|
1833
|
+
{/* Same onUploadError message the media modal shows in its own banner,
|
|
1834
|
+
surfaced at shell level when the modal is closed — an upload can now
|
|
1835
|
+
start from a document section's drop zone, where nothing else would
|
|
1836
|
+
ever render the rejection. Gated on the modal being shut so the two
|
|
1837
|
+
never show the same message twice. */}
|
|
1838
|
+
{uploadErrorMessage && !showMediaLibrary && (
|
|
1839
|
+
<div className="sticky top-2 z-30 mx-auto w-full max-w-3xl px-4">
|
|
1840
|
+
<UploadRejectionAlert
|
|
1841
|
+
message={uploadErrorMessage}
|
|
1842
|
+
onDismiss={() => setUploadErrorMessage(null)}
|
|
1843
|
+
/>
|
|
1844
|
+
</div>
|
|
1845
|
+
)}
|
|
1846
|
+
|
|
1827
1847
|
{unparseableSectionIds.length > 0 && (
|
|
1828
1848
|
<div className="sticky top-2 z-30 mx-auto w-full max-w-3xl px-4">
|
|
1829
1849
|
<UploadRejectionAlert
|
|
@@ -28,6 +28,12 @@ export interface MediaLibraryContextValue {
|
|
|
28
28
|
* predate documents and must keep compiling. */
|
|
29
29
|
documentManifest?: DocumentManifest;
|
|
30
30
|
pendingDocuments?: PendingDocument[];
|
|
31
|
+
/** Upload a PDF/HTML file through the document pipeline, calling back with the
|
|
32
|
+
* document id once it is staged. Optional for the same reason as the two
|
|
33
|
+
* fields above. Owns every rejection itself (unsupported type, too large, no
|
|
34
|
+
* documents store) — they surface on the pipeline's `onUploadError` channel,
|
|
35
|
+
* so a caller never needs to pre-validate or report. */
|
|
36
|
+
uploadDocument?: (file: File, onComplete?: (documentId: string) => void) => void | Promise<void>;
|
|
31
37
|
}
|
|
32
38
|
|
|
33
39
|
export const MediaLibraryContext = createContext<MediaLibraryContextValue | null>(null);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
import { DOCUMENT_CACHE_CONTROL } from "../storage/types";
|
|
2
3
|
import { hashFileBuffer } from "../media/utils";
|
|
3
4
|
import type { DocumentManifest, DocumentManifestEntry } from "../schemas/documents";
|
|
4
5
|
import {
|
|
@@ -209,6 +210,11 @@ export function useDocumentUploads({
|
|
|
209
210
|
const xhr = new XMLHttpRequest();
|
|
210
211
|
xhr.open("PUT", uploadUrl);
|
|
211
212
|
xhr.setRequestHeader("Content-Type", file.type);
|
|
213
|
+
// The presign signs cache-control;content-type;host — BOTH headers
|
|
214
|
+
// must be sent byte-exact or R2 403s the PUT, and R2 omits CORS
|
|
215
|
+
// headers on errors, so the browser misreports it as a CORS block.
|
|
216
|
+
// (The video path sets ASSET_CACHE_CONTROL the same way.)
|
|
217
|
+
xhr.setRequestHeader("Cache-Control", DOCUMENT_CACHE_CONTROL);
|
|
212
218
|
xhr.upload.onprogress = (e) => {
|
|
213
219
|
if (e.lengthComputable) {
|
|
214
220
|
setDocumentUploadProgress((prev) => ({ ...prev, [documentId]: e.loaded / e.total }));
|
|
@@ -216,7 +222,7 @@ export function useDocumentUploads({
|
|
|
216
222
|
};
|
|
217
223
|
xhr.onload = () =>
|
|
218
224
|
xhr.status >= 200 && xhr.status < 300 ? resolvePut() : rejectPut(new Error(`upload ${xhr.status}`));
|
|
219
|
-
xhr.onerror = () => rejectPut(new Error("
|
|
225
|
+
xhr.onerror = () => rejectPut(new Error("the storage request was blocked or interrupted"));
|
|
220
226
|
xhr.send(file);
|
|
221
227
|
});
|
|
222
228
|
} catch (err) {
|
|
@@ -790,7 +790,12 @@ export function useMediaPipeline({
|
|
|
790
790
|
// pipeline's pendingDocumentUrls — a context copy would be dead surface.
|
|
791
791
|
documentManifest,
|
|
792
792
|
pendingDocuments: documents.pendingDocuments,
|
|
793
|
-
|
|
793
|
+
// Direct document upload, for the `document` section's empty-state drop
|
|
794
|
+
// zone. Deliberately not routed through enqueueFile: that gate keys off the
|
|
795
|
+
// MIME type, so a file the drop zone means as a document but the browser
|
|
796
|
+
// typed as something else would silently become an image.
|
|
797
|
+
uploadDocument: documents.uploadDocument,
|
|
798
|
+
}), [handleMediaUpload, enqueueFile, mediaManifest, pendingMediaItems, pendingLocalUrls, siteConfig, documentManifest, documents.pendingDocuments, documents.uploadDocument]);
|
|
794
799
|
|
|
795
800
|
return {
|
|
796
801
|
processingItems,
|
package/src/lib/dexie.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { SiteIndex, SectionMeta, SiteConfig, Page } from "../schemas/site-c
|
|
|
4
4
|
import type { NavEntry } from "../schemas/nav";
|
|
5
5
|
import type { LoadedSection } from "./loader";
|
|
6
6
|
import type { MediaManifest, MediaItem } from "../media/types";
|
|
7
|
-
import type { DocumentManifestEntry } from "../schemas/documents";
|
|
7
|
+
import type { DocumentManifest, DocumentManifestEntry } from "../schemas/documents";
|
|
8
8
|
|
|
9
9
|
interface SectionRow {
|
|
10
10
|
sectionId: string;
|
|
@@ -50,6 +50,11 @@ interface ContentCacheRow {
|
|
|
50
50
|
savedBranchSha?: string | null;
|
|
51
51
|
changedSectionIds?: string[];
|
|
52
52
|
mainIndex?: SiteIndex | null;
|
|
53
|
+
// The document sidecar at this sha. Optional for rows written by older
|
|
54
|
+
// packages; readers fall back to {} (same posture as the fetch path's
|
|
55
|
+
// `data.documentManifest ?? {}`). Without this, a cache-hit editor load left
|
|
56
|
+
// documentManifest null and the whole documents feature dead for the session.
|
|
57
|
+
documentManifest?: DocumentManifest | null;
|
|
53
58
|
}
|
|
54
59
|
|
|
55
60
|
interface MediaManifestRow {
|
|
@@ -478,7 +483,7 @@ export async function cacheContent(
|
|
|
478
483
|
sections: LoadedSection[],
|
|
479
484
|
index: SiteIndex,
|
|
480
485
|
siteConfig: Record<string, unknown>,
|
|
481
|
-
diff?: { savedBranchSha: string | null; changedSectionIds: string[]; mainIndex: SiteIndex | null },
|
|
486
|
+
diff?: { savedBranchSha: string | null; changedSectionIds: string[]; mainIndex: SiteIndex | null; documentManifest?: DocumentManifest },
|
|
482
487
|
): Promise<void> {
|
|
483
488
|
const now = new Date().toISOString();
|
|
484
489
|
await getDb().contentCache.put({
|
|
@@ -491,6 +496,7 @@ export async function cacheContent(
|
|
|
491
496
|
savedBranchSha: diff?.savedBranchSha ?? null,
|
|
492
497
|
changedSectionIds: diff?.changedSectionIds ?? [],
|
|
493
498
|
mainIndex: diff?.mainIndex ?? null,
|
|
499
|
+
documentManifest: diff?.documentManifest ?? null,
|
|
494
500
|
});
|
|
495
501
|
}
|
|
496
502
|
|
|
@@ -502,6 +508,7 @@ export async function getCachedContent(): Promise<{
|
|
|
502
508
|
savedBranchSha: string | null;
|
|
503
509
|
changedSectionIds: string[];
|
|
504
510
|
mainIndex: SiteIndex | null;
|
|
511
|
+
documentManifest: DocumentManifest;
|
|
505
512
|
} | null> {
|
|
506
513
|
const row = await getDb().contentCache.get("current");
|
|
507
514
|
if (!row) return null;
|
|
@@ -515,6 +522,10 @@ export async function getCachedContent(): Promise<{
|
|
|
515
522
|
// No stored mainIndex (e.g. legacy or post-publish cache) means "no draft",
|
|
516
523
|
// so the published index equals the current index.
|
|
517
524
|
mainIndex: row.mainIndex ?? row.index,
|
|
525
|
+
// Legacy row (pre-documents package): {} matches the fetch path's posture.
|
|
526
|
+
// The narrow stale edge — an old row at a sha that DOES hold a manifest —
|
|
527
|
+
// heals on the next fresh load or save.
|
|
528
|
+
documentManifest: row.documentManifest ?? {},
|
|
518
529
|
};
|
|
519
530
|
}
|
|
520
531
|
|
package/src/storage/types.ts
CHANGED
|
@@ -135,17 +135,23 @@ export const ASSET_CACHE_CONTROL = "max-age=31536000, immutable";
|
|
|
135
135
|
* can slot in. All paths are SITE-RELATIVE ("media/{hash}/original.mp4") —
|
|
136
136
|
* implementations prepend the tenant prefix ("sites/{siteId}/") themselves so
|
|
137
137
|
* a caller can never address another tenant's objects.
|
|
138
|
+
*
|
|
139
|
+
* This bucket is PUBLIC: an object's URL is statically derivable from the
|
|
140
|
+
* bucket's public base, so `createUploadUrl` returns it once at upload time
|
|
141
|
+
* (built locally in standalone mode, brokered from the admin app's env in
|
|
142
|
+
* platform mode) and the media manifest stores that absolute URL. Rendering
|
|
143
|
+
* reads the manifest — nothing on the read path re-derives a URL.
|
|
138
144
|
*/
|
|
139
145
|
export interface AssetStore {
|
|
140
|
-
/**
|
|
146
|
+
/**
|
|
147
|
+
* Mint a signed direct-upload URL for the browser (PUT), along with the
|
|
148
|
+
* object's permanent public URL for the caller to persist.
|
|
149
|
+
*/
|
|
141
150
|
createUploadUrl(
|
|
142
151
|
path: string,
|
|
143
152
|
opts: { contentType: string },
|
|
144
153
|
): Promise<{ uploadUrl: string; path: string; publicUrl: string }>;
|
|
145
154
|
|
|
146
|
-
/** Deterministic public CDN URL for an object (no network call). */
|
|
147
|
-
getPublicUrl(path: string): string;
|
|
148
|
-
|
|
149
155
|
delete(paths: string[]): Promise<void>;
|
|
150
156
|
|
|
151
157
|
/** Recursive listing under a site-relative prefix. */
|
|
@@ -167,8 +173,10 @@ export const DOCUMENT_CACHE_CONTROL = "private, max-age=31536000, immutable";
|
|
|
167
173
|
* Deliberately not an AssetStore: the assets bucket is public by configuration
|
|
168
174
|
* at R2_PUBLIC_BASE (videos depend on it), so a signed GET there would protect
|
|
169
175
|
* nothing and hosted HTML would render unsandboxed on a sibling origin. Hence
|
|
170
|
-
*
|
|
171
|
-
*
|
|
176
|
+
* the asymmetry with AssetStore — an asset has a permanent public URL handed
|
|
177
|
+
* back by `createUploadUrl`, while a document has no durable URL at all: every
|
|
178
|
+
* read is a short-lived signed GET from `createDownloadUrl`, minted only after
|
|
179
|
+
* the gated route has authorized the requester.
|
|
172
180
|
*
|
|
173
181
|
* Paths are SITE-RELATIVE ("documents/{hash}/original.pdf"); implementations
|
|
174
182
|
* prepend the tenant prefix themselves so a caller can never address another
|