@embedpdf/viewer-chrome 3.0.0-next.1 → 3.0.0-next.10
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/chrome.cjs +8 -1
- package/dist/chrome.cjs.map +1 -1
- package/dist/chrome.d.cts +1 -0
- package/dist/chrome.d.ts +1 -0
- package/dist/chrome.js +8 -1
- package/dist/chrome.js.map +1 -1
- package/dist/commands.cjs +38 -8
- package/dist/commands.cjs.map +1 -1
- package/dist/commands.js +39 -9
- package/dist/commands.js.map +1 -1
- package/dist/{es-C5084JO6.cjs → es-B7b8KjR3.cjs} +44 -1
- package/dist/es-B7b8KjR3.cjs.map +1 -0
- package/dist/{es-D0PzYE9Z.js → es-aQlZ3hcL.js} +44 -1
- package/dist/es-aQlZ3hcL.js.map +1 -0
- package/dist/index.cjs +723 -136
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +728 -141
- package/dist/index.js.map +1 -1
- package/package.json +5 -4
- package/dist/es-C5084JO6.cjs.map +0 -1
- package/dist/es-D0PzYE9Z.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -171,6 +171,10 @@ const en = {
|
|
|
171
171
|
ungroup: "Ungroup",
|
|
172
172
|
strip: "Annotation actions"
|
|
173
173
|
},
|
|
174
|
+
selection: {
|
|
175
|
+
copy: "Copy",
|
|
176
|
+
strip: "Text selection actions"
|
|
177
|
+
},
|
|
174
178
|
shapes: {
|
|
175
179
|
rectangle: "Rectangle",
|
|
176
180
|
circle: "Circle",
|
|
@@ -217,6 +221,24 @@ const en = {
|
|
|
217
221
|
unknown: "The document could not be opened.",
|
|
218
222
|
close: "Close document"
|
|
219
223
|
},
|
|
224
|
+
annotation: {
|
|
225
|
+
comment: "Comment",
|
|
226
|
+
highlight: "Highlight",
|
|
227
|
+
underline: "Underline",
|
|
228
|
+
squiggly: "Squiggly",
|
|
229
|
+
strikeout: "Strikethrough",
|
|
230
|
+
square: "Square",
|
|
231
|
+
circle: "Circle",
|
|
232
|
+
line: "Line",
|
|
233
|
+
polygon: "Polygon",
|
|
234
|
+
polyline: "Polyline",
|
|
235
|
+
ink: "Ink",
|
|
236
|
+
freeText: "Text",
|
|
237
|
+
stamp: "Stamp",
|
|
238
|
+
caret: "Caret",
|
|
239
|
+
redact: "Redact",
|
|
240
|
+
fileAttachment: "Attachment"
|
|
241
|
+
},
|
|
220
242
|
demo: {
|
|
221
243
|
starting: "Starting viewer…",
|
|
222
244
|
opening: "Opening document…",
|
|
@@ -233,6 +255,27 @@ const en = {
|
|
|
233
255
|
searchNoResults: "No results",
|
|
234
256
|
searchPage: "Page {page}",
|
|
235
257
|
commentsTitle: "Comments",
|
|
258
|
+
commentsForbidden: "You don’t have access to comments on this document.",
|
|
259
|
+
commentsLoading: "Loading comments…",
|
|
260
|
+
commentsError: "Comments failed to load.",
|
|
261
|
+
commentsRetry: "Retry",
|
|
262
|
+
commentsEmpty: "No comments yet. Use the note tool to start a thread.",
|
|
263
|
+
commentsAnonymous: "Unknown",
|
|
264
|
+
commentsPage: "Page {page}",
|
|
265
|
+
commentCount: "{count} comment",
|
|
266
|
+
commentCountPlural: "{count} comments",
|
|
267
|
+
commentsReply: "Reply",
|
|
268
|
+
commentsReplyPlaceholder: "Reply…",
|
|
269
|
+
commentsAddPlaceholder: "Add a comment…",
|
|
270
|
+
commentsDelete: "Delete comment",
|
|
271
|
+
commentsDeleteThread: "Delete thread",
|
|
272
|
+
commentsEdit: "Edit comment",
|
|
273
|
+
commentsStatus: "Status",
|
|
274
|
+
commentsStatusNone: "No status",
|
|
275
|
+
commentsStatusAccepted: "Accepted",
|
|
276
|
+
commentsStatusRejected: "Rejected",
|
|
277
|
+
commentsStatusCancelled: "Cancelled",
|
|
278
|
+
commentsStatusCompleted: "Completed",
|
|
236
279
|
styleTitle: "Style",
|
|
237
280
|
empty: "Nothing here yet in this demo panel.",
|
|
238
281
|
page: "Page {current} of {total}",
|
|
@@ -1323,19 +1366,129 @@ function AppToolbar({ bar, className }) {
|
|
|
1323
1366
|
});
|
|
1324
1367
|
}
|
|
1325
1368
|
//#endregion
|
|
1326
|
-
//#region src/ui/
|
|
1369
|
+
//#region src/ui/link-editor.tsx
|
|
1327
1370
|
/**
|
|
1328
|
-
* The
|
|
1329
|
-
*
|
|
1371
|
+
* The link editor POPOVER — v2's "add a link" popup, reborn. It rides the
|
|
1372
|
+
* SAME selection anchor as the action strip (`<AnnotationMenu>` solves
|
|
1373
|
+
* WHERE), opening from the strip's link icon (`annotation:link` toggles the
|
|
1374
|
+
* `link-editor` shell surface) and rendering IN PLACE of the strip while
|
|
1375
|
+
* open — one anchored card at a time.
|
|
1330
1376
|
*
|
|
1331
|
-
*
|
|
1332
|
-
*
|
|
1333
|
-
*
|
|
1334
|
-
*
|
|
1335
|
-
*
|
|
1336
|
-
*
|
|
1337
|
-
|
|
1338
|
-
|
|
1377
|
+
* Reads the selection's current target from selection props (the `linkOf`
|
|
1378
|
+
* lens — parents derive from their committed child annotations) and writes
|
|
1379
|
+
* through the ONE `updateSelection({ link })` path; the plugin's reconciler
|
|
1380
|
+
* materializes/retargets the attached children. Links are a VERB on the
|
|
1381
|
+
* selection, not a style — which is why this is a popover, not a sidebar
|
|
1382
|
+
* section.
|
|
1383
|
+
*/
|
|
1384
|
+
function ModeToggle({ active, title, onClick, children }) {
|
|
1385
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1386
|
+
type: "button",
|
|
1387
|
+
title,
|
|
1388
|
+
onClick,
|
|
1389
|
+
className: `rounded border px-2.5 py-1 text-xs font-medium transition-colors ${active ? "border-accent bg-accent text-on-accent" : "border-border bg-surface text-fg hover:bg-hover"}`,
|
|
1390
|
+
children
|
|
1391
|
+
});
|
|
1392
|
+
}
|
|
1393
|
+
function LinkEditorCard({ onClose }) {
|
|
1394
|
+
const t = (0, _embedpdf_react_i18n.useT)();
|
|
1395
|
+
const kernel = (0, _embedpdf_react_runtime.useKernel)();
|
|
1396
|
+
const anno = (0, _embedpdf_react_annotation.useAnnotation)();
|
|
1397
|
+
const value = (0, _embedpdf_react_annotation.useSelectionProps)().values.link ?? null;
|
|
1398
|
+
const [mode, setMode] = (0, react.useState)(value?.kind === "goto" ? "page" : "uri");
|
|
1399
|
+
const [uri, setUri] = (0, react.useState)(value?.kind === "uri" ? value.uri : "");
|
|
1400
|
+
const [pageNo, setPageNo] = (0, react.useState)("1");
|
|
1401
|
+
const apply = () => {
|
|
1402
|
+
if (mode === "uri") {
|
|
1403
|
+
const trimmed = uri.trim();
|
|
1404
|
+
if (!trimmed) return;
|
|
1405
|
+
anno.updateSelection({ link: {
|
|
1406
|
+
kind: "uri",
|
|
1407
|
+
uri: trimmed
|
|
1408
|
+
} });
|
|
1409
|
+
onClose();
|
|
1410
|
+
return;
|
|
1411
|
+
}
|
|
1412
|
+
const activeId = kernel.documents.activeId();
|
|
1413
|
+
const layout = (activeId ? kernel.getState().core.documents[activeId] : null)?.pages[Math.max(0, Number(pageNo) - 1)];
|
|
1414
|
+
if (!layout) return;
|
|
1415
|
+
anno.updateSelection({ link: {
|
|
1416
|
+
kind: "goto",
|
|
1417
|
+
destination: {
|
|
1418
|
+
kind: "fit",
|
|
1419
|
+
pageObjectNumber: layout.pageObjectNumber
|
|
1420
|
+
}
|
|
1421
|
+
} });
|
|
1422
|
+
onClose();
|
|
1423
|
+
};
|
|
1424
|
+
const inputCls = "border-border bg-surface text-fg focus:border-accent w-full rounded border px-2.5 py-1.5 text-sm outline-none";
|
|
1425
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1426
|
+
className: "border-border bg-surface w-64 rounded-lg border p-3 shadow-lg",
|
|
1427
|
+
children: [
|
|
1428
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1429
|
+
className: "mb-2 flex items-center justify-between",
|
|
1430
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1431
|
+
className: "text-fg text-sm font-semibold",
|
|
1432
|
+
children: t("commands.annotate.link")
|
|
1433
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1434
|
+
type: "button",
|
|
1435
|
+
onClick: onClose,
|
|
1436
|
+
className: "text-fg-muted hover:bg-hover grid h-6 w-6 place-items-center rounded",
|
|
1437
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
1438
|
+
name: "x",
|
|
1439
|
+
size: 14
|
|
1440
|
+
})
|
|
1441
|
+
})]
|
|
1442
|
+
}),
|
|
1443
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1444
|
+
className: "mb-2 flex gap-1.5",
|
|
1445
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModeToggle, {
|
|
1446
|
+
title: "Link to a URL",
|
|
1447
|
+
active: mode === "uri",
|
|
1448
|
+
onClick: () => setMode("uri"),
|
|
1449
|
+
children: "URL"
|
|
1450
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModeToggle, {
|
|
1451
|
+
title: "Link to a page",
|
|
1452
|
+
active: mode === "page",
|
|
1453
|
+
onClick: () => setMode("page"),
|
|
1454
|
+
children: "Page"
|
|
1455
|
+
})]
|
|
1456
|
+
}),
|
|
1457
|
+
mode === "uri" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1458
|
+
type: "url",
|
|
1459
|
+
className: inputCls,
|
|
1460
|
+
placeholder: "https://…",
|
|
1461
|
+
autoFocus: true,
|
|
1462
|
+
value: uri,
|
|
1463
|
+
onChange: (e) => setUri(e.target.value),
|
|
1464
|
+
onKeyDown: (e) => e.key === "Enter" && apply()
|
|
1465
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1466
|
+
type: "number",
|
|
1467
|
+
min: 1,
|
|
1468
|
+
className: inputCls,
|
|
1469
|
+
placeholder: "Page number",
|
|
1470
|
+
autoFocus: true,
|
|
1471
|
+
value: pageNo,
|
|
1472
|
+
onChange: (e) => setPageNo(e.target.value),
|
|
1473
|
+
onKeyDown: (e) => e.key === "Enter" && apply()
|
|
1474
|
+
}),
|
|
1475
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1476
|
+
type: "button",
|
|
1477
|
+
className: "bg-accent text-on-accent mt-2.5 w-full rounded px-2 py-1.5 text-sm font-medium",
|
|
1478
|
+
onClick: apply,
|
|
1479
|
+
children: value == null ? "Add link" : "Update link"
|
|
1480
|
+
})
|
|
1481
|
+
]
|
|
1482
|
+
});
|
|
1483
|
+
}
|
|
1484
|
+
//#endregion
|
|
1485
|
+
//#region src/ui/strip-bar.tsx
|
|
1486
|
+
/**
|
|
1487
|
+
* The floating strip's PIXELS, shared by every contextual strip (annotation
|
|
1488
|
+
* selection, text selection, …): a rounded toolbar of command buttons with
|
|
1489
|
+
* derived group separators. The WHERE is each strip's menu component
|
|
1490
|
+
* (`<AnnotationMenu>`, `<SelectionMenu>`); the WHICH is the commands'
|
|
1491
|
+
* `visible`/`enabled` derivations via `useStripView`. This file only draws.
|
|
1339
1492
|
*/
|
|
1340
1493
|
function StripButton({ cmd, run }) {
|
|
1341
1494
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
@@ -1353,24 +1506,76 @@ function StripButton({ cmd, run }) {
|
|
|
1353
1506
|
})
|
|
1354
1507
|
});
|
|
1355
1508
|
}
|
|
1509
|
+
function StripBar({ view, label }) {
|
|
1510
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1511
|
+
role: "toolbar",
|
|
1512
|
+
"aria-label": label,
|
|
1513
|
+
className: "border-border-subtle bg-elevated flex items-center gap-1 rounded-lg border p-1 shadow-xl",
|
|
1514
|
+
children: view.groups.map((g, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [i > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1515
|
+
"aria-hidden": true,
|
|
1516
|
+
className: "bg-border h-5 w-px"
|
|
1517
|
+
}), g.commands.map((cmd) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StripButton, {
|
|
1518
|
+
cmd,
|
|
1519
|
+
run: () => view.execute(cmd.id)
|
|
1520
|
+
}, cmd.id))] }, g.id))
|
|
1521
|
+
});
|
|
1522
|
+
}
|
|
1523
|
+
//#endregion
|
|
1524
|
+
//#region src/ui/annotation-strip.tsx
|
|
1525
|
+
/**
|
|
1526
|
+
* The ANNOTATION-selection floating strip:
|
|
1527
|
+
*
|
|
1528
|
+
* config/chrome.ts `strips.annotation` declares WHAT may appear
|
|
1529
|
+
* each command's visible/enabled decides WHICH items show
|
|
1530
|
+
* <AnnotationMenu> solves WHERE (camera transform,
|
|
1531
|
+
* rotate-knob dodging, pointer isolation)
|
|
1532
|
+
*
|
|
1533
|
+
* useStripView is the live intersection: hidden commands drop out, empty
|
|
1534
|
+
* groups vanish, null when nothing applies. Mounted in the Stage `overlay`
|
|
1535
|
+
* slot. The pixels are the shared <StripBar>.
|
|
1536
|
+
*/
|
|
1356
1537
|
function AnnotationStrip() {
|
|
1357
1538
|
const t = (0, _embedpdf_react_i18n.useT)();
|
|
1358
1539
|
const view = (0, _embedpdf_react_toolbar.useStripView)(useStripSchema("annotation"));
|
|
1540
|
+
const editor = (0, _embedpdf_react_shell.useSurface)("link-editor");
|
|
1541
|
+
const selection = (0, _embedpdf_react_annotation.useAnnotationSelection)();
|
|
1542
|
+
(0, react.useEffect)(() => {
|
|
1543
|
+
if (!selection.length && editor.isOpen) editor.close();
|
|
1544
|
+
}, [selection.length, editor.isOpen]);
|
|
1359
1545
|
if (!view) return null;
|
|
1360
1546
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_embedpdf_react_annotation_menu.AnnotationMenu, {
|
|
1361
1547
|
placement: "bottom",
|
|
1362
1548
|
gap: 15,
|
|
1363
|
-
children: ()
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1549
|
+
children: editor.isOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LinkEditorCard, { onClose: editor.close }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StripBar, {
|
|
1550
|
+
view,
|
|
1551
|
+
label: t("commands.annotate.strip")
|
|
1552
|
+
})
|
|
1553
|
+
});
|
|
1554
|
+
}
|
|
1555
|
+
//#endregion
|
|
1556
|
+
//#region src/ui/selection-strip.tsx
|
|
1557
|
+
/**
|
|
1558
|
+
* The TEXT-SELECTION floating strip:
|
|
1559
|
+
*
|
|
1560
|
+
* config/chrome.ts `strips.selection` declares WHAT may appear
|
|
1561
|
+
* each command's visible/enabled decides WHICH items show
|
|
1562
|
+
* <SelectionMenu> solves WHERE (settles on pointer-up,
|
|
1563
|
+
* camera transform, pointer isolation)
|
|
1564
|
+
*
|
|
1565
|
+
* `useStripView` is the live intersection: with copy denied (no
|
|
1566
|
+
* doc.text.copy) every command drops out and this renders nothing — no
|
|
1567
|
+
* empty bubble. Mounted in the Stage `overlay` slot.
|
|
1568
|
+
*/
|
|
1569
|
+
function SelectionStrip() {
|
|
1570
|
+
const t = (0, _embedpdf_react_i18n.useT)();
|
|
1571
|
+
const view = (0, _embedpdf_react_toolbar.useStripView)(useStripSchema("selection"));
|
|
1572
|
+
if (!view) return null;
|
|
1573
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_embedpdf_react_selection.SelectionMenu, {
|
|
1574
|
+
placement: "bottom",
|
|
1575
|
+
gap: 12,
|
|
1576
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StripBar, {
|
|
1577
|
+
view,
|
|
1578
|
+
label: t("commands.selection.strip")
|
|
1374
1579
|
})
|
|
1375
1580
|
});
|
|
1376
1581
|
}
|
|
@@ -2223,99 +2428,6 @@ function Toggle({ active, title, onClick, children }) {
|
|
|
2223
2428
|
children
|
|
2224
2429
|
});
|
|
2225
2430
|
}
|
|
2226
|
-
/**
|
|
2227
|
-
* The link-target editor — v2's "Link / Go to link / Remove link" menu items,
|
|
2228
|
-
* rebuilt on the schema: any kind whose table declares the `link` spec gets
|
|
2229
|
-
* this control, and every write is the ONE `updateSelection({ link })` path
|
|
2230
|
-
* (the plugin materializes/retargets/deletes the attached children).
|
|
2231
|
-
*/
|
|
2232
|
-
function LinkTargetControl({ label, value, mixed, onChange }) {
|
|
2233
|
-
const kernel = (0, _embedpdf_react_runtime.useKernel)();
|
|
2234
|
-
const link = (0, _embedpdf_react_runtime.useOptionalCapability)(_embedpdf_react_link.LinkToken);
|
|
2235
|
-
const [mode, setMode] = (0, react.useState)(value?.kind === "goto" ? "page" : "uri");
|
|
2236
|
-
const [uri, setUri] = (0, react.useState)(value?.kind === "uri" ? value.uri : "");
|
|
2237
|
-
const [pageNo, setPageNo] = (0, react.useState)("1");
|
|
2238
|
-
(0, react.useEffect)(() => {
|
|
2239
|
-
setMode(value?.kind === "goto" ? "page" : "uri");
|
|
2240
|
-
if (value?.kind === "uri") setUri(value.uri);
|
|
2241
|
-
}, [value]);
|
|
2242
|
-
const apply = () => {
|
|
2243
|
-
if (mode === "uri") {
|
|
2244
|
-
const trimmed = uri.trim();
|
|
2245
|
-
if (trimmed) onChange({ link: {
|
|
2246
|
-
kind: "uri",
|
|
2247
|
-
uri: trimmed
|
|
2248
|
-
} });
|
|
2249
|
-
return;
|
|
2250
|
-
}
|
|
2251
|
-
const activeId = kernel.documents.activeId();
|
|
2252
|
-
const layout = (activeId ? kernel.getState().core.documents[activeId] : null)?.pages[Math.max(0, Number(pageNo) - 1)];
|
|
2253
|
-
if (layout) onChange({ link: {
|
|
2254
|
-
kind: "goto",
|
|
2255
|
-
destination: {
|
|
2256
|
-
kind: "fit",
|
|
2257
|
-
pageObjectNumber: layout.pageObjectNumber
|
|
2258
|
-
}
|
|
2259
|
-
} });
|
|
2260
|
-
};
|
|
2261
|
-
const inputCls = "border-border bg-surface text-fg w-full rounded border px-3 py-1.5 text-sm";
|
|
2262
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Field, {
|
|
2263
|
-
label,
|
|
2264
|
-
mixed,
|
|
2265
|
-
children: [
|
|
2266
|
-
value != null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2267
|
-
className: "mb-2 flex items-center gap-2",
|
|
2268
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2269
|
-
type: "button",
|
|
2270
|
-
className: "border-border bg-surface text-fg flex-1 rounded border px-2 py-1.5 text-sm",
|
|
2271
|
-
onClick: () => link?.activate(value),
|
|
2272
|
-
children: "Go to link"
|
|
2273
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2274
|
-
type: "button",
|
|
2275
|
-
className: "border-border bg-surface text-fg flex-1 rounded border px-2 py-1.5 text-sm",
|
|
2276
|
-
onClick: () => onChange({ link: null }),
|
|
2277
|
-
children: "Remove link"
|
|
2278
|
-
})]
|
|
2279
|
-
}),
|
|
2280
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2281
|
-
className: "mb-2 flex gap-2",
|
|
2282
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Toggle, {
|
|
2283
|
-
title: "Link to a URL",
|
|
2284
|
-
active: mode === "uri",
|
|
2285
|
-
onClick: () => setMode("uri"),
|
|
2286
|
-
children: "URL"
|
|
2287
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Toggle, {
|
|
2288
|
-
title: "Link to a page",
|
|
2289
|
-
active: mode === "page",
|
|
2290
|
-
onClick: () => setMode("page"),
|
|
2291
|
-
children: "Page"
|
|
2292
|
-
})]
|
|
2293
|
-
}),
|
|
2294
|
-
mode === "uri" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2295
|
-
type: "url",
|
|
2296
|
-
className: inputCls,
|
|
2297
|
-
placeholder: "https://…",
|
|
2298
|
-
value: uri,
|
|
2299
|
-
onChange: (e) => setUri(e.target.value),
|
|
2300
|
-
onKeyDown: (e) => e.key === "Enter" && apply()
|
|
2301
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2302
|
-
type: "number",
|
|
2303
|
-
min: 1,
|
|
2304
|
-
className: inputCls,
|
|
2305
|
-
placeholder: "Page number",
|
|
2306
|
-
value: pageNo,
|
|
2307
|
-
onChange: (e) => setPageNo(e.target.value),
|
|
2308
|
-
onKeyDown: (e) => e.key === "Enter" && apply()
|
|
2309
|
-
}),
|
|
2310
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2311
|
-
type: "button",
|
|
2312
|
-
className: "border-border bg-surface text-fg mt-2 w-full rounded border px-2 py-1.5 text-sm font-medium",
|
|
2313
|
-
onClick: apply,
|
|
2314
|
-
children: value == null ? "Add link" : "Update link"
|
|
2315
|
-
})
|
|
2316
|
-
]
|
|
2317
|
-
});
|
|
2318
|
-
}
|
|
2319
2431
|
function PropControl({ spec, value, mixed, onChange }) {
|
|
2320
2432
|
switch (spec.key) {
|
|
2321
2433
|
case "color":
|
|
@@ -2458,12 +2570,7 @@ function PropControl({ spec, value, mixed, onChange }) {
|
|
|
2458
2570
|
onChange: (blendMode) => onChange({ blendMode })
|
|
2459
2571
|
})
|
|
2460
2572
|
});
|
|
2461
|
-
case "link": return
|
|
2462
|
-
label: spec.label,
|
|
2463
|
-
value: value ?? null,
|
|
2464
|
-
mixed,
|
|
2465
|
-
onChange
|
|
2466
|
-
});
|
|
2573
|
+
case "link": return null;
|
|
2467
2574
|
default: return null;
|
|
2468
2575
|
}
|
|
2469
2576
|
}
|
|
@@ -2565,6 +2672,493 @@ function refKeyOfRedact(ref) {
|
|
|
2565
2672
|
return JSON.stringify(ref);
|
|
2566
2673
|
}
|
|
2567
2674
|
//#endregion
|
|
2675
|
+
//#region src/ui/comment-config.ts
|
|
2676
|
+
/** `/C`-style Color → CSS. */
|
|
2677
|
+
const cssColor = (c) => c ? `rgb(${c.r}, ${c.g}, ${c.b})` : void 0;
|
|
2678
|
+
const TYPE_CONFIG = {
|
|
2679
|
+
text: {
|
|
2680
|
+
icon: "message",
|
|
2681
|
+
labelKey: "annotation.comment",
|
|
2682
|
+
label: "Comment"
|
|
2683
|
+
},
|
|
2684
|
+
highlight: {
|
|
2685
|
+
icon: "highlight",
|
|
2686
|
+
labelKey: "annotation.highlight",
|
|
2687
|
+
label: "Highlight"
|
|
2688
|
+
},
|
|
2689
|
+
underline: {
|
|
2690
|
+
icon: "underline",
|
|
2691
|
+
labelKey: "annotation.underline",
|
|
2692
|
+
label: "Underline"
|
|
2693
|
+
},
|
|
2694
|
+
squiggly: {
|
|
2695
|
+
icon: "squiggly",
|
|
2696
|
+
labelKey: "annotation.squiggly",
|
|
2697
|
+
label: "Squiggly"
|
|
2698
|
+
},
|
|
2699
|
+
strikeout: {
|
|
2700
|
+
icon: "strikethrough",
|
|
2701
|
+
labelKey: "annotation.strikeout",
|
|
2702
|
+
label: "Strikethrough"
|
|
2703
|
+
},
|
|
2704
|
+
square: {
|
|
2705
|
+
icon: "square",
|
|
2706
|
+
labelKey: "annotation.square",
|
|
2707
|
+
label: "Square"
|
|
2708
|
+
},
|
|
2709
|
+
circle: {
|
|
2710
|
+
icon: "circle",
|
|
2711
|
+
labelKey: "annotation.circle",
|
|
2712
|
+
label: "Circle"
|
|
2713
|
+
},
|
|
2714
|
+
line: {
|
|
2715
|
+
icon: "line",
|
|
2716
|
+
labelKey: "annotation.line",
|
|
2717
|
+
label: "Line"
|
|
2718
|
+
},
|
|
2719
|
+
polygon: {
|
|
2720
|
+
icon: "polygon",
|
|
2721
|
+
labelKey: "annotation.polygon",
|
|
2722
|
+
label: "Polygon"
|
|
2723
|
+
},
|
|
2724
|
+
polyline: {
|
|
2725
|
+
icon: "zigzag",
|
|
2726
|
+
labelKey: "annotation.polyline",
|
|
2727
|
+
label: "Polyline"
|
|
2728
|
+
},
|
|
2729
|
+
ink: {
|
|
2730
|
+
icon: "pencilMarker",
|
|
2731
|
+
labelKey: "annotation.ink",
|
|
2732
|
+
label: "Ink"
|
|
2733
|
+
},
|
|
2734
|
+
"free-text": {
|
|
2735
|
+
icon: "freeText",
|
|
2736
|
+
labelKey: "annotation.freeText",
|
|
2737
|
+
label: "Text"
|
|
2738
|
+
},
|
|
2739
|
+
stamp: {
|
|
2740
|
+
icon: "rubberStamp",
|
|
2741
|
+
labelKey: "annotation.stamp",
|
|
2742
|
+
label: "Stamp"
|
|
2743
|
+
},
|
|
2744
|
+
caret: {
|
|
2745
|
+
icon: "insertText",
|
|
2746
|
+
labelKey: "annotation.caret",
|
|
2747
|
+
label: "Caret"
|
|
2748
|
+
},
|
|
2749
|
+
redact: {
|
|
2750
|
+
icon: "redact",
|
|
2751
|
+
labelKey: "annotation.redact",
|
|
2752
|
+
label: "Redact"
|
|
2753
|
+
},
|
|
2754
|
+
"file-attachment": {
|
|
2755
|
+
icon: "paperclip",
|
|
2756
|
+
labelKey: "annotation.fileAttachment",
|
|
2757
|
+
label: "Attachment"
|
|
2758
|
+
}
|
|
2759
|
+
};
|
|
2760
|
+
const FALLBACK = {
|
|
2761
|
+
icon: "message",
|
|
2762
|
+
labelKey: "annotation.comment",
|
|
2763
|
+
label: "Comment"
|
|
2764
|
+
};
|
|
2765
|
+
const commentTypeConfig = (a) => TYPE_CONFIG[a.subtype] ?? FALLBACK;
|
|
2766
|
+
/**
|
|
2767
|
+
* The glyph's tint slots, read off the annotation itself — this is what makes
|
|
2768
|
+
* a card recognizable as "that yellow highlight on page 3". Free-text prefers
|
|
2769
|
+
* its `fontColor` override, since that is the color a reader actually sees.
|
|
2770
|
+
*/
|
|
2771
|
+
const commentIconAccent = (a) => {
|
|
2772
|
+
const anyA = a;
|
|
2773
|
+
const primary = cssColor(anyA.fontColor ?? anyA.color);
|
|
2774
|
+
const secondary = cssColor(anyA.interiorColor);
|
|
2775
|
+
return {
|
|
2776
|
+
...primary ? { primary } : {},
|
|
2777
|
+
...secondary ? { secondary } : {}
|
|
2778
|
+
};
|
|
2779
|
+
};
|
|
2780
|
+
//#endregion
|
|
2781
|
+
//#region src/ui/comments-panel.tsx
|
|
2782
|
+
/**
|
|
2783
|
+
* The comments sidebar (right panel): a live view over the conversation
|
|
2784
|
+
* plane, in the shape v2's reviewers knew — page-grouped sections, and a
|
|
2785
|
+
* card that identifies its annotation at a glance by the type's glyph tinted
|
|
2786
|
+
* with that annotation's own colors. What v2 lacked, and this adds, is the
|
|
2787
|
+
* ISO 32000 §12.5.6.3 review status per thread.
|
|
2788
|
+
*
|
|
2789
|
+
* There is no per-page loading dance: the plugin hydrates the WHOLE document
|
|
2790
|
+
* at open (`listRawAll`), so the list is complete the moment `hydration()`
|
|
2791
|
+
* says so, whether or not a page was ever scrolled to.
|
|
2792
|
+
*
|
|
2793
|
+
* Selection is two-way, as in v2: clicking a card selects the annotation and
|
|
2794
|
+
* flies the camera to it (`stage.reveal`, the same verb search hits use);
|
|
2795
|
+
* selecting in the document scrolls that card into view here.
|
|
2796
|
+
*
|
|
2797
|
+
* Every action routes through the `comments` verbs — plain annotation writes
|
|
2798
|
+
* on the one optimistic pipeline — and every control is gated by
|
|
2799
|
+
* `permissionsFor`, the engine's own collab-resolver mirrors: what shows here
|
|
2800
|
+
* is what the engine will allow.
|
|
2801
|
+
*/
|
|
2802
|
+
const REVIEW_STATES = [
|
|
2803
|
+
"none",
|
|
2804
|
+
"accepted",
|
|
2805
|
+
"rejected",
|
|
2806
|
+
"cancelled",
|
|
2807
|
+
"completed"
|
|
2808
|
+
];
|
|
2809
|
+
const dateLabel = (iso) => {
|
|
2810
|
+
if (!iso) return "";
|
|
2811
|
+
const d = new Date(iso);
|
|
2812
|
+
if (Number.isNaN(d.getTime())) return "";
|
|
2813
|
+
return d.toLocaleDateString(void 0, {
|
|
2814
|
+
month: "short",
|
|
2815
|
+
day: "numeric"
|
|
2816
|
+
});
|
|
2817
|
+
};
|
|
2818
|
+
function CommentsPanel() {
|
|
2819
|
+
const t = (0, _embedpdf_react_i18n.useT)();
|
|
2820
|
+
const hydration = (0, _embedpdf_react_annotation.useCommentsHydration)();
|
|
2821
|
+
const comments = (0, _embedpdf_react_annotation.useComments)();
|
|
2822
|
+
const threads = (0, _embedpdf_react_annotation.useCommentThreads)();
|
|
2823
|
+
const anno = (0, _embedpdf_react_runtime.useCapability)(_embedpdf_react_annotation.AnnotationToken);
|
|
2824
|
+
const stage = (0, _embedpdf_react_runtime.useCapability)(_embedpdf_react_stage.StageToken);
|
|
2825
|
+
const selection = (0, _embedpdf_react_annotation.useAnnotationSelection)();
|
|
2826
|
+
const scrollRef = (0, react.useRef)(null);
|
|
2827
|
+
const cardRefs = (0, react.useRef)({});
|
|
2828
|
+
/** Which thread the document's selection points at — ANY member counts, so
|
|
2829
|
+
* selecting a reply's parent shape highlights the same card. */
|
|
2830
|
+
const selectedThreadKey = (0, react.useMemo)(() => {
|
|
2831
|
+
for (const id of selection) {
|
|
2832
|
+
const ref = anno.getSelected().find((a) => (0, _embedpdf_react_annotation.refKey)(a.ref) === id || a.nm === id)?.ref ?? null;
|
|
2833
|
+
const thread = ref ? comments.thread(ref) : null;
|
|
2834
|
+
if (thread) return (0, _embedpdf_react_annotation.refKey)(thread.root.ref);
|
|
2835
|
+
}
|
|
2836
|
+
return null;
|
|
2837
|
+
}, [
|
|
2838
|
+
selection,
|
|
2839
|
+
anno,
|
|
2840
|
+
comments
|
|
2841
|
+
]);
|
|
2842
|
+
(0, react.useEffect)(() => {
|
|
2843
|
+
if (!selectedThreadKey || !scrollRef.current) return;
|
|
2844
|
+
const el = cardRefs.current[selectedThreadKey];
|
|
2845
|
+
if (!el) return;
|
|
2846
|
+
const container = scrollRef.current;
|
|
2847
|
+
const top = el.offsetTop - container.clientHeight / 2 + el.clientHeight / 2 - container.offsetTop;
|
|
2848
|
+
container.scrollTo({
|
|
2849
|
+
top,
|
|
2850
|
+
behavior: "smooth"
|
|
2851
|
+
});
|
|
2852
|
+
}, [selectedThreadKey]);
|
|
2853
|
+
/** Click a card: select the annotation AND fly the camera to it. */
|
|
2854
|
+
const goTo = (view) => {
|
|
2855
|
+
anno.select(view.root.ref);
|
|
2856
|
+
if (view.pageIndex < 0) return;
|
|
2857
|
+
stage?.reveal(view.pageIndex, {
|
|
2858
|
+
...view.contentRect ? { rect: view.contentRect } : {},
|
|
2859
|
+
anchor: {
|
|
2860
|
+
x: "center",
|
|
2861
|
+
y: .35
|
|
2862
|
+
},
|
|
2863
|
+
behavior: "smooth"
|
|
2864
|
+
});
|
|
2865
|
+
};
|
|
2866
|
+
const byPage = (0, react.useMemo)(() => {
|
|
2867
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2868
|
+
for (const v of threads) {
|
|
2869
|
+
const arr = groups.get(v.pageIndex);
|
|
2870
|
+
if (arr) arr.push(v);
|
|
2871
|
+
else groups.set(v.pageIndex, [v]);
|
|
2872
|
+
}
|
|
2873
|
+
return [...groups.entries()].sort((a, b) => a[0] - b[0]);
|
|
2874
|
+
}, [threads]);
|
|
2875
|
+
if (hydration.status === "loading") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Empty, {
|
|
2876
|
+
icon: "comment",
|
|
2877
|
+
text: t("demo.commentsLoading")
|
|
2878
|
+
});
|
|
2879
|
+
if (hydration.status === "forbidden") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Empty, {
|
|
2880
|
+
icon: "lock",
|
|
2881
|
+
text: t("demo.commentsForbidden")
|
|
2882
|
+
});
|
|
2883
|
+
if (hydration.status === "error") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Empty, {
|
|
2884
|
+
icon: "alertTriangle",
|
|
2885
|
+
text: t("demo.commentsError"),
|
|
2886
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2887
|
+
type: "button",
|
|
2888
|
+
onClick: () => void comments.rehydrate(),
|
|
2889
|
+
className: "bg-accent text-on-accent rounded-md px-3 py-1.5 text-sm font-medium",
|
|
2890
|
+
children: t("demo.commentsRetry")
|
|
2891
|
+
})
|
|
2892
|
+
});
|
|
2893
|
+
if (threads.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Empty, {
|
|
2894
|
+
icon: "comment",
|
|
2895
|
+
text: t("demo.commentsEmpty")
|
|
2896
|
+
});
|
|
2897
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2898
|
+
ref: scrollRef,
|
|
2899
|
+
className: "min-h-0 flex-1 overflow-y-auto",
|
|
2900
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2901
|
+
className: "flex flex-col gap-5 p-3",
|
|
2902
|
+
children: byPage.map(([pageIndex, group]) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2903
|
+
className: "flex flex-col gap-2",
|
|
2904
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2905
|
+
className: "bg-surface sticky top-0 z-10",
|
|
2906
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2907
|
+
className: "border-border-subtle border-b pb-1.5",
|
|
2908
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
2909
|
+
className: "text-fg text-sm font-semibold",
|
|
2910
|
+
children: t("demo.commentsPage", { params: { page: group[0].pageLabel } })
|
|
2911
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2912
|
+
className: "text-fg-muted text-xs",
|
|
2913
|
+
children: t(group.length === 1 ? "demo.commentCount" : "demo.commentCountPlural", { params: { count: String(group.length) } })
|
|
2914
|
+
})]
|
|
2915
|
+
})
|
|
2916
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
2917
|
+
className: "flex flex-col gap-2",
|
|
2918
|
+
children: group.map((view) => {
|
|
2919
|
+
const key = (0, _embedpdf_react_annotation.refKey)(view.root.ref);
|
|
2920
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ThreadCard, {
|
|
2921
|
+
view,
|
|
2922
|
+
selected: selectedThreadKey === key,
|
|
2923
|
+
onSelect: () => goTo(view),
|
|
2924
|
+
cardRef: (el) => {
|
|
2925
|
+
cardRefs.current[key] = el;
|
|
2926
|
+
}
|
|
2927
|
+
}, key);
|
|
2928
|
+
})
|
|
2929
|
+
})]
|
|
2930
|
+
}, pageIndex))
|
|
2931
|
+
})
|
|
2932
|
+
});
|
|
2933
|
+
}
|
|
2934
|
+
function Empty({ icon, text, children }) {
|
|
2935
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2936
|
+
className: "flex flex-col items-center gap-3 px-4 py-10 text-center",
|
|
2937
|
+
children: [
|
|
2938
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
2939
|
+
name: icon,
|
|
2940
|
+
size: 32,
|
|
2941
|
+
className: "text-fg-muted"
|
|
2942
|
+
}),
|
|
2943
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2944
|
+
className: "text-fg-muted text-sm",
|
|
2945
|
+
children: text
|
|
2946
|
+
}),
|
|
2947
|
+
children
|
|
2948
|
+
]
|
|
2949
|
+
});
|
|
2950
|
+
}
|
|
2951
|
+
function ThreadCard({ view, selected, onSelect, cardRef }) {
|
|
2952
|
+
const t = (0, _embedpdf_react_i18n.useT)();
|
|
2953
|
+
const comments = (0, _embedpdf_react_annotation.useComments)();
|
|
2954
|
+
const [reply, setReply] = (0, react.useState)("");
|
|
2955
|
+
const perms = comments.permissionsFor(view.root.ref);
|
|
2956
|
+
const config = commentTypeConfig(view.root);
|
|
2957
|
+
const status = view.review.mine?.state ?? "none";
|
|
2958
|
+
const latest = view.review.lastChange;
|
|
2959
|
+
const sendReply = () => {
|
|
2960
|
+
const text = reply.trim();
|
|
2961
|
+
if (!text) return;
|
|
2962
|
+
setReply("");
|
|
2963
|
+
comments.reply(view.root.ref, text);
|
|
2964
|
+
};
|
|
2965
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
|
|
2966
|
+
ref: cardRef,
|
|
2967
|
+
onClick: onSelect,
|
|
2968
|
+
className: `bg-surface cursor-pointer rounded-lg border shadow-sm transition-all hover:shadow-md ${selected ? "border-accent ring-accent/40 ring-2" : "border-border-subtle"}`,
|
|
2969
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2970
|
+
className: "flex flex-col gap-2 p-3",
|
|
2971
|
+
children: [
|
|
2972
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2973
|
+
className: "flex items-start gap-2.5",
|
|
2974
|
+
children: [
|
|
2975
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2976
|
+
className: "bg-hover mt-0.5 grid h-8 w-8 shrink-0 place-items-center rounded-full",
|
|
2977
|
+
title: t(config.labelKey, { fallback: config.label }),
|
|
2978
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
2979
|
+
name: config.icon,
|
|
2980
|
+
size: 18,
|
|
2981
|
+
accent: commentIconAccent(view.root)
|
|
2982
|
+
})
|
|
2983
|
+
}),
|
|
2984
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2985
|
+
className: "min-w-0 flex-1",
|
|
2986
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2987
|
+
className: "flex items-baseline gap-2",
|
|
2988
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2989
|
+
className: "text-fg truncate text-sm font-medium",
|
|
2990
|
+
children: view.root.author ?? t("demo.commentsAnonymous")
|
|
2991
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2992
|
+
className: "text-fg-muted ml-auto shrink-0 text-xs",
|
|
2993
|
+
children: dateLabel(view.root.modified ?? view.root.created)
|
|
2994
|
+
})]
|
|
2995
|
+
}), latest && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2996
|
+
className: "bg-accent-light text-accent mt-1 inline-block rounded px-1.5 py-0.5 text-[11px] font-medium",
|
|
2997
|
+
children: [statusLabel(t, latest.state), latest.by ? ` — ${latest.by}` : ""]
|
|
2998
|
+
})]
|
|
2999
|
+
}),
|
|
3000
|
+
perms.canDeleteThread && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3001
|
+
type: "button",
|
|
3002
|
+
onClick: (e) => {
|
|
3003
|
+
e.stopPropagation();
|
|
3004
|
+
comments.removeThread(view.root.ref);
|
|
3005
|
+
},
|
|
3006
|
+
className: "text-fg-muted hover:text-fg grid h-6 w-6 shrink-0 place-items-center rounded",
|
|
3007
|
+
title: t("demo.commentsDeleteThread"),
|
|
3008
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
3009
|
+
name: "trash",
|
|
3010
|
+
size: 14
|
|
3011
|
+
})
|
|
3012
|
+
})
|
|
3013
|
+
]
|
|
3014
|
+
}),
|
|
3015
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CommentBody, {
|
|
3016
|
+
annotationRef: view.root.ref,
|
|
3017
|
+
text: view.root.contents ?? "",
|
|
3018
|
+
deletable: false
|
|
3019
|
+
}),
|
|
3020
|
+
view.replies.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3021
|
+
className: "border-border-subtle flex flex-col gap-2 border-t pt-2",
|
|
3022
|
+
children: view.replies.map((r) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Reply, { dto: r }, (0, _embedpdf_react_annotation.refKey)(r.ref)))
|
|
3023
|
+
}),
|
|
3024
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3025
|
+
className: "border-border-subtle flex flex-col gap-1.5 border-t pt-2",
|
|
3026
|
+
children: [perms.canSetStatus && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
3027
|
+
className: "text-fg-muted flex items-center gap-2 text-xs",
|
|
3028
|
+
onClick: (e) => e.stopPropagation(),
|
|
3029
|
+
children: [t("demo.commentsStatus"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
3030
|
+
value: status,
|
|
3031
|
+
onChange: (e) => void comments.setStatus(view.root.ref, e.target.value),
|
|
3032
|
+
className: "border-border bg-surface text-fg flex-1 rounded-md border px-1.5 py-1 text-xs outline-none",
|
|
3033
|
+
children: REVIEW_STATES.map((s) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
3034
|
+
value: s,
|
|
3035
|
+
children: statusLabel(t, s)
|
|
3036
|
+
}, s))
|
|
3037
|
+
})]
|
|
3038
|
+
}), perms.canReply && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3039
|
+
className: "flex items-center gap-1.5",
|
|
3040
|
+
onClick: (e) => e.stopPropagation(),
|
|
3041
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3042
|
+
type: "text",
|
|
3043
|
+
value: reply,
|
|
3044
|
+
placeholder: view.root.contents || view.replies.length ? t("demo.commentsReplyPlaceholder") : t("demo.commentsAddPlaceholder"),
|
|
3045
|
+
onChange: (e) => setReply(e.target.value),
|
|
3046
|
+
onKeyDown: (e) => e.key === "Enter" && sendReply(),
|
|
3047
|
+
className: "border-border bg-surface text-fg focus:border-accent w-full min-w-0 flex-1 rounded-md border px-2 py-1 text-sm outline-none"
|
|
3048
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3049
|
+
type: "button",
|
|
3050
|
+
onClick: sendReply,
|
|
3051
|
+
disabled: !reply.trim(),
|
|
3052
|
+
className: "text-accent disabled:text-fg-muted grid h-7 w-7 shrink-0 place-items-center rounded-md",
|
|
3053
|
+
title: t("demo.commentsReply"),
|
|
3054
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
3055
|
+
name: "arrowForwardUp",
|
|
3056
|
+
size: 16
|
|
3057
|
+
})
|
|
3058
|
+
})]
|
|
3059
|
+
})]
|
|
3060
|
+
})
|
|
3061
|
+
]
|
|
3062
|
+
})
|
|
3063
|
+
});
|
|
3064
|
+
}
|
|
3065
|
+
function Reply({ dto }) {
|
|
3066
|
+
const t = (0, _embedpdf_react_i18n.useT)();
|
|
3067
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3068
|
+
className: "border-border-subtle ml-1 border-l-2 pl-2",
|
|
3069
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3070
|
+
className: "flex items-baseline gap-2",
|
|
3071
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3072
|
+
className: "text-fg-secondary text-xs font-medium",
|
|
3073
|
+
children: dto.author ?? t("demo.commentsAnonymous")
|
|
3074
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3075
|
+
className: "text-fg-muted text-[11px]",
|
|
3076
|
+
children: dateLabel(dto.modified ?? dto.created)
|
|
3077
|
+
})]
|
|
3078
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CommentBody, {
|
|
3079
|
+
annotationRef: dto.ref,
|
|
3080
|
+
text: dto.contents ?? "",
|
|
3081
|
+
deletable: true
|
|
3082
|
+
})]
|
|
3083
|
+
});
|
|
3084
|
+
}
|
|
3085
|
+
/** One comment's text with inline edit (own, unlocked) and delete (replies). */
|
|
3086
|
+
function CommentBody({ annotationRef, text, deletable }) {
|
|
3087
|
+
const t = (0, _embedpdf_react_i18n.useT)();
|
|
3088
|
+
const comments = (0, _embedpdf_react_annotation.useComments)();
|
|
3089
|
+
const perms = comments.permissionsFor(annotationRef);
|
|
3090
|
+
const [draft, setDraft] = (0, react.useState)(null);
|
|
3091
|
+
if (draft !== null) {
|
|
3092
|
+
const save = () => {
|
|
3093
|
+
const next = draft.trim();
|
|
3094
|
+
setDraft(null);
|
|
3095
|
+
if (next && next !== text) comments.edit(annotationRef, next);
|
|
3096
|
+
};
|
|
3097
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
3098
|
+
value: draft,
|
|
3099
|
+
autoFocus: true,
|
|
3100
|
+
rows: 2,
|
|
3101
|
+
onClick: (e) => e.stopPropagation(),
|
|
3102
|
+
onChange: (e) => setDraft(e.target.value),
|
|
3103
|
+
onBlur: save,
|
|
3104
|
+
onKeyDown: (e) => {
|
|
3105
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
3106
|
+
e.preventDefault();
|
|
3107
|
+
save();
|
|
3108
|
+
}
|
|
3109
|
+
if (e.key === "Escape") setDraft(null);
|
|
3110
|
+
},
|
|
3111
|
+
className: "border-accent bg-surface text-fg w-full resize-none rounded-md border px-2 py-1 text-sm outline-none"
|
|
3112
|
+
});
|
|
3113
|
+
}
|
|
3114
|
+
if (!text && !perms.canEditText && !deletable) return null;
|
|
3115
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3116
|
+
className: "group/body flex items-start gap-1",
|
|
3117
|
+
children: [
|
|
3118
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3119
|
+
className: "text-fg min-w-0 flex-1 whitespace-pre-wrap text-sm",
|
|
3120
|
+
children: text
|
|
3121
|
+
}),
|
|
3122
|
+
perms.canEditText && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3123
|
+
type: "button",
|
|
3124
|
+
onClick: (e) => {
|
|
3125
|
+
e.stopPropagation();
|
|
3126
|
+
setDraft(text);
|
|
3127
|
+
},
|
|
3128
|
+
className: "text-fg-muted hover:text-fg grid h-5 w-5 shrink-0 place-items-center rounded opacity-0 group-hover/body:opacity-100",
|
|
3129
|
+
title: t("demo.commentsEdit"),
|
|
3130
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
3131
|
+
name: "pencilMarker",
|
|
3132
|
+
size: 12
|
|
3133
|
+
})
|
|
3134
|
+
}),
|
|
3135
|
+
deletable && perms.canDelete && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3136
|
+
type: "button",
|
|
3137
|
+
onClick: (e) => {
|
|
3138
|
+
e.stopPropagation();
|
|
3139
|
+
comments.remove(annotationRef);
|
|
3140
|
+
},
|
|
3141
|
+
className: "text-fg-muted hover:text-fg grid h-5 w-5 shrink-0 place-items-center rounded opacity-0 group-hover/body:opacity-100",
|
|
3142
|
+
title: t("demo.commentsDelete"),
|
|
3143
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
3144
|
+
name: "trash",
|
|
3145
|
+
size: 12
|
|
3146
|
+
})
|
|
3147
|
+
})
|
|
3148
|
+
]
|
|
3149
|
+
});
|
|
3150
|
+
}
|
|
3151
|
+
function statusLabel(t, state) {
|
|
3152
|
+
switch (state) {
|
|
3153
|
+
case "none": return t("demo.commentsStatusNone");
|
|
3154
|
+
case "accepted": return t("demo.commentsStatusAccepted");
|
|
3155
|
+
case "rejected": return t("demo.commentsStatusRejected");
|
|
3156
|
+
case "cancelled": return t("demo.commentsStatusCancelled");
|
|
3157
|
+
case "completed": return t("demo.commentsStatusCompleted");
|
|
3158
|
+
default: return state;
|
|
3159
|
+
}
|
|
3160
|
+
}
|
|
3161
|
+
//#endregion
|
|
2568
3162
|
//#region src/ui/redaction-panel.tsx
|
|
2569
3163
|
/**
|
|
2570
3164
|
* The pending-redactions review panel (right sidebar). The list is a LIVE view
|
|
@@ -2999,13 +3593,7 @@ function RightSidebar() {
|
|
|
2999
3593
|
size: 16
|
|
3000
3594
|
})
|
|
3001
3595
|
})]
|
|
3002
|
-
}), active === "style" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AnnotationStylePanel, {}) : active === "search" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SearchPanel, {}, documentId ?? "none") : active === "redaction" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RedactionPanel, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
3003
|
-
className: "p-3",
|
|
3004
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3005
|
-
className: "text-fg-muted text-sm",
|
|
3006
|
-
children: t("demo.empty")
|
|
3007
|
-
})
|
|
3008
|
-
})]
|
|
3596
|
+
}), active === "style" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AnnotationStylePanel, {}) : active === "search" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SearchPanel, {}, documentId ?? "none") : active === "redaction" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RedactionPanel, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CommentsPanel, {}, documentId ?? "none")]
|
|
3009
3597
|
});
|
|
3010
3598
|
}
|
|
3011
3599
|
function PageControls() {
|
|
@@ -3354,10 +3942,12 @@ function Shell() {
|
|
|
3354
3942
|
className: "relative min-w-0 flex-1",
|
|
3355
3943
|
children: [
|
|
3356
3944
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ArmedToolCursor, {}),
|
|
3945
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_embedpdf_react_selection.SelectionClipboard, {}),
|
|
3357
3946
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_embedpdf_react_stage.Stage, {
|
|
3358
|
-
interaction: true,
|
|
3359
3947
|
overlay: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
3360
3948
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(AnnotationStrip, {}),
|
|
3949
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectionStrip, {}),
|
|
3950
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_embedpdf_react_selection.SelectionHandles, {}),
|
|
3361
3951
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_embedpdf_react_scrollbar.Scrollbar, { axis: "y" }),
|
|
3362
3952
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_embedpdf_react_scrollbar.Scrollbar, { axis: "x" })
|
|
3363
3953
|
] }),
|
|
@@ -3365,7 +3955,6 @@ function Shell() {
|
|
|
3365
3955
|
style: { background: "var(--ep-canvas)" },
|
|
3366
3956
|
children: () => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
3367
3957
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_embedpdf_react_render.RenderLayer, { annotations: false }),
|
|
3368
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_embedpdf_react_render.TileLayer, { annotations: false }),
|
|
3369
3958
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_embedpdf_react_selection.SelectionLayer, {}),
|
|
3370
3959
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_embedpdf_react_search.SearchLayer, {}),
|
|
3371
3960
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_embedpdf_react_link.LinkLayer, {}),
|
|
@@ -3404,7 +3993,7 @@ function Shell() {
|
|
|
3404
3993
|
* changing these props on a mounted viewer does not rebuild the workspace.
|
|
3405
3994
|
*/
|
|
3406
3995
|
/** Lazy built-in packs — a strings override for one of these wraps its loader. */
|
|
3407
|
-
const BUILTIN_LOADERS = { es: () => Promise.resolve().then(() => require("./es-
|
|
3996
|
+
const BUILTIN_LOADERS = { es: () => Promise.resolve().then(() => require("./es-B7b8KjR3.cjs")).then((m) => m.es) };
|
|
3408
3997
|
const isDict = (v) => typeof v === "object" && v !== null;
|
|
3409
3998
|
/**
|
|
3410
3999
|
* Deep-merge `over` into `base` (immutably), expanding dotted keys —
|
|
@@ -3493,10 +4082,7 @@ function FullViewer({ engine, initialDocuments, fallback, commands, icons, strin
|
|
|
3493
4082
|
for (const c of resolved.commands) if (c.icon && !(c.icon in ICON_PATHS) && !(c.icon in resolved.icons)) console.warn(`[embedpdf] command "${c.id}": unknown icon "${c.icon}"`);
|
|
3494
4083
|
}, [resolved]);
|
|
3495
4084
|
const [plugins] = (0, react.useState)(() => [
|
|
3496
|
-
(0, _embedpdf_react_stage.stagePlugin)({
|
|
3497
|
-
layout: "vertical",
|
|
3498
|
-
interaction: true
|
|
3499
|
-
}),
|
|
4085
|
+
(0, _embedpdf_react_stage.stagePlugin)({ layout: "vertical" }),
|
|
3500
4086
|
(0, _embedpdf_react_stage.stagePlugin)({
|
|
3501
4087
|
id: "stage-thumbs",
|
|
3502
4088
|
token: ThumbsStageToken,
|
|
@@ -3520,7 +4106,8 @@ function FullViewer({ engine, initialDocuments, fallback, commands, icons, strin
|
|
|
3520
4106
|
}),
|
|
3521
4107
|
(0, _embedpdf_react_render.renderPlugin)(),
|
|
3522
4108
|
(0, _embedpdf_react_page_edit.pageEditPlugin)(),
|
|
3523
|
-
(0, _embedpdf_react_interaction.interactionPlugin)(
|
|
4109
|
+
(0, _embedpdf_react_interaction.interactionPlugin)(),
|
|
4110
|
+
(0, _embedpdf_react_interaction.feedbackPlugin)({ provider: _embedpdf_react_interaction.vibrationFeedback }),
|
|
3524
4111
|
(0, _embedpdf_react_selection.selectionPlugin)(),
|
|
3525
4112
|
(0, _embedpdf_react_annotation.annotationPlugin)({ tools: [{
|
|
3526
4113
|
id: "arrow",
|