dsh-figmark 1.1.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.pdf.js CHANGED
@@ -26166,6 +26166,91 @@ while (prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan] &
26166
26166
  page.node.set(import_cjs.PDFName.of("Annots"), created);
26167
26167
  return created;
26168
26168
  }
26169
+ /**
26170
+ * A page number pulled into the document.
26171
+ * @param page - the page asked for.
26172
+ * @param pageCount - how many pages the document has.
26173
+ * @returns the nearest page that exists.
26174
+ */
26175
+ function clampPage(page, pageCount) {
26176
+ const last = Math.max(pageCount, 1);
26177
+ if (Number.isNaN(page)) return 1;
26178
+ return Math.min(Math.max(Math.round(page), 1), last);
26179
+ }
26180
+ /**
26181
+ * The page one step away, clamped at both ends.
26182
+ * @param page - the page on screen.
26183
+ * @param delta - how many pages to move; negative moves back.
26184
+ * @param pageCount - how many pages the document has.
26185
+ * @returns the page to show.
26186
+ */
26187
+ function stepPage(page, delta, pageCount) {
26188
+ return clampPage(page + delta, pageCount);
26189
+ }
26190
+ /**
26191
+ * A page number typed into the box.
26192
+ *
26193
+ * Digits only: a reader typing "12" means page 12, and anything else is a typo
26194
+ * that must leave them where they are rather than jump them somewhere arbitrary.
26195
+ * A number past the end lands on the last page, which is what a reader typing it
26196
+ * was reaching for anyway.
26197
+ * @param raw - what the box holds.
26198
+ * @param pageCount - how many pages the document has.
26199
+ * @returns the page to show, or null when the text is not a page number.
26200
+ */
26201
+ function parsePageInput(raw, pageCount) {
26202
+ const digits = raw.trim();
26203
+ if (!/^\d+$/u.test(digits)) return null;
26204
+ return clampPage(Number.parseInt(digits, 10), pageCount);
26205
+ }
26206
+ /**
26207
+ * The page turn a keystroke asks for.
26208
+ *
26209
+ * Only the keys a PDF reader answers to, and none of them while a modifier is
26210
+ * held: Cmd+Arrow belongs to the shell, and swallowing it here would turn a text
26211
+ * selection or a tab switch into a page turn.
26212
+ * @param key - the event's key.
26213
+ * @param modifiers - which modifiers were held.
26214
+ * @returns the turn, or null when this key is not one.
26215
+ */
26216
+ function keyedTurn(key, modifiers) {
26217
+ if (modifiers.metaKey || modifiers.ctrlKey || modifiers.altKey) return null;
26218
+ switch (key) {
26219
+ case "ArrowRight":
26220
+ case "ArrowDown":
26221
+ case "PageDown":
26222
+ case " ": return {
26223
+ delta: 1,
26224
+ absolute: false
26225
+ };
26226
+ case "ArrowLeft":
26227
+ case "ArrowUp":
26228
+ case "PageUp": return {
26229
+ delta: -1,
26230
+ absolute: false
26231
+ };
26232
+ case "Home": return {
26233
+ delta: 1,
26234
+ absolute: true
26235
+ };
26236
+ case "End": return {
26237
+ delta: Number.POSITIVE_INFINITY,
26238
+ absolute: true
26239
+ };
26240
+ default: return null;
26241
+ }
26242
+ }
26243
+ /**
26244
+ * The page a key turn lands on.
26245
+ * @param turn - what the key asked for.
26246
+ * @param page - the page on screen.
26247
+ * @param pageCount - how many pages the document has.
26248
+ * @returns the page to show.
26249
+ */
26250
+ function keyedPage(turn, page, pageCount) {
26251
+ if (!turn.absolute) return stepPage(page, turn.delta, pageCount);
26252
+ return clampPage(turn.delta, pageCount);
26253
+ }
26169
26254
  //#endregion
26170
26255
  //#region node_modules/.pnpm/pdfjs-dist@6.3.289/node_modules/pdfjs-dist/build/pdf.mjs
26171
26256
  const isNodeJS = typeof process === "object" && process + "" === "[object process]" && !process.versions.nw && !(process.versions.electron && process.type && process.type !== "browser");
@@ -49919,11 +50004,48 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
49919
50004
  * drawing surface and the marks of that page.
49920
50005
  * @module dsh-figmark/client/pdf/PdfBody
49921
50006
  */
49922
- /** Gap kept around a page inside the pane, matching the annotator's own inset. */
50007
+ /**
50008
+ * Gap kept around a page inside the pane.
50009
+ *
50010
+ * The same number `AnnotatorBody` subtracts from its own stage width to decide how
50011
+ * wide the page is drawn: a raster made for a box of a different width is a raster
50012
+ * resampled to fit, and that is softness the reader sees on every glyph.
50013
+ */
49923
50014
  const PANE_INSET = 24;
49924
- /** Highest device-pixel ratio one page bitmap is rendered at. */
50015
+ /**
50016
+ * Highest device-pixel ratio one page bitmap is rendered at.
50017
+ *
50018
+ * Three rather than two: a Retina display reports 2, but a *scaled* Retina mode (a
50019
+ * "more space" resolution, a zoomed window) reports 3, and capping at 2 there leaves
50020
+ * the page short of the pixels the screen actually has.
50021
+ */
49925
50022
  const MAX_DENSITY = 2;
49926
50023
  /**
50024
+ * Bitmap pixels a page gets per screen pixel.
50025
+ *
50026
+ * A page is laid out in its own units and then scaled to the pane, so the browser
50027
+ * resamples its bitmap whatever it wants. Measured in Chromium at a Retina ratio,
50028
+ * how sharp the result reads depends on how many source pixels each screen pixel
50029
+ * gets, and a bitmap that misses the screen's count by a few percent is the *worst*
50030
+ * case, not the best — the resampler has no whole-pixel neighbours to average, so
50031
+ * every glyph edge is blended with the background beside it.
50032
+ *
50033
+ * Measured, as the 99th percentile of the lateral gradient across a text line:
50034
+ * 1.04× gives 129, 1.25× gives 157, 1.5× gives 168, 2× gives 178 (3× gives 181).
50035
+ * The plateau is reached early: 1.5× already reads as sharp and costs a third of
50036
+ * the memory 2× does, so this is the spare kept.
50037
+ */
50038
+ const SUPERSAMPLE = 1.5;
50039
+ /**
50040
+ * The largest factor "fit width" draws a page at.
50041
+ *
50042
+ * The same ceiling `AnnotatorBody` applies when it decides how big to draw a figure;
50043
+ * it is repeated here because a lazily loaded chunk may not import the client bundle
50044
+ * at run time. Past this factor a wider pane buys no more drawn pixels, so the two
50045
+ * numbers have to agree or this one pays for pixels the page never occupies.
50046
+ */
50047
+ const FIT_CEILING = 1.5;
50048
+ /**
49927
50049
  * Narrowest width a page is rendered for; below it the pane is still settling.
49928
50050
  * Compared against the stepped width, so the guard itself is a whole step wide.
49929
50051
  */
@@ -49939,6 +50061,65 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
49939
50061
  */
49940
50062
  const RENDER_WIDTH_STEP = 64;
49941
50063
  /**
50064
+ * Whether a keystroke landed in a field the reader is typing into.
50065
+ *
50066
+ * An editable region is recognised by its attribute rather than by the
50067
+ * `isContentEditable` property, which a rendered frame's own document does not
50068
+ * answer for. Any element inside one counts, so a caret nested in a paragraph is
50069
+ * as safe as the region itself.
50070
+ * @param target - the event's target.
50071
+ * @returns whether the keystroke belongs to the field.
50072
+ */
50073
+ function typingIn(target) {
50074
+ if (!(target instanceof HTMLElement)) return false;
50075
+ if (target.closest("[contenteditable]:not([contenteditable=\"false\"])") !== null) return true;
50076
+ return target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.tagName === "SELECT";
50077
+ }
50078
+ /**
50079
+ * The document's own icons.
50080
+ *
50081
+ * Drawn here rather than imported from the annotator body: this is the lazy chunk,
50082
+ * and the body reaches it as a prop precisely because the two halves cannot import
50083
+ * each other. The paths follow the body's own icon grid so a strip of these and a
50084
+ * strip of those read as one set.
50085
+ */
50086
+ function ChevronLeftIcon() {
50087
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
50088
+ className: "da-ico",
50089
+ viewBox: "0 0 24 24",
50090
+ "aria-hidden": "true",
50091
+ focusable: "false",
50092
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M 14.5 5.5 L 8 12 L 14.5 18.5" })
50093
+ });
50094
+ }
50095
+ function ChevronRightIcon() {
50096
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
50097
+ className: "da-ico",
50098
+ viewBox: "0 0 24 24",
50099
+ "aria-hidden": "true",
50100
+ focusable: "false",
50101
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M 9.5 5.5 L 16 12 L 9.5 18.5" })
50102
+ });
50103
+ }
50104
+ function SendIcon() {
50105
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
50106
+ className: "da-ico",
50107
+ viewBox: "0 0 24 24",
50108
+ "aria-hidden": "true",
50109
+ focusable: "false",
50110
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M 3 12 L 21 3.9 L 15.9 21 L 12 14.1 Z" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M 12 14.1 L 21 3.9" })]
50111
+ });
50112
+ }
50113
+ function ChatIcon() {
50114
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
50115
+ className: "da-ico",
50116
+ viewBox: "0 0 24 24",
50117
+ "aria-hidden": "true",
50118
+ focusable: "false",
50119
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M 3.75 9.75 a 3 3 0 0 1 3 -3 h 10.5 a 3 3 0 0 1 3 3 v 4.5 a 3 3 0 0 1 -3 3 h -6 l -4.5 3.3 v -3.3 a 3 3 0 0 1 -3 -3 z" })
50120
+ });
50121
+ }
50122
+ /**
49942
50123
  * Group one document's marks by the page they belong to.
49943
50124
  * @param marks - every mark of the document.
49944
50125
  * @returns the marks of each page; a mark without a page is on page 1.
@@ -49980,6 +50161,17 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
49980
50161
  const { createAutosave, annotationSignature, autosaveDelayMs } = props;
49981
50162
  const bytes = props.content?.kind === "bytes" ? props.content.data : void 0;
49982
50163
  const observerRef = (0, react.useRef)(null);
50164
+ /** The panel itself, so the wheel can be followed where it lands. */
50165
+ const hostRef = (0, react.useRef)(null);
50166
+ /**
50167
+ * The node the page bodies draw their floating chrome into.
50168
+ *
50169
+ * The pane, not the page: a bar mounted inside a page scrolls with that page, and
50170
+ * and it must not move with a page: a bar mounted inside one would scroll away.
50171
+ * as state because it has to be a node React has committed before a body can
50172
+ * portal into it.
50173
+ */
50174
+ const [chromeHost, setChromeHost] = (0, react.useState)(null);
49983
50175
  const [pdf, setPdf] = (0, react.useState)(null);
49984
50176
  /** The page the reader asked for: the number in the toolbar follows this one. */
49985
50177
  const [requested, setRequested] = (0, react.useState)(1);
@@ -49994,6 +50186,36 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
49994
50186
  const [createdAt, setCreatedAt] = (0, react.useState)(null);
49995
50187
  const [stale, setStale] = (0, react.useState)(false);
49996
50188
  /**
50189
+ * How many times the document has been cleared.
50190
+ *
50191
+ * A page body seeds its marks once, when it mounts, so clearing the document has
50192
+ * to remount the page it is still showing — otherwise the body keeps listing the
50193
+ * marks that were just delivered and deleted.
50194
+ */
50195
+ const [generation, setGeneration] = (0, react.useState)(0);
50196
+ /**
50197
+ * The card open under the bar, and the mark it was opened about.
50198
+ *
50199
+ * Both live here rather than in a page body: paging away and back mounts a fresh
50200
+ * body, and state that travelled with it would shut the list and put the comment
50201
+ * pill away at every page boundary. One document, one list.
50202
+ */
50203
+ const [openPanel, setOpenPanel] = (0, react.useState)(null);
50204
+ const [selectedId, setSelectedId] = (0, react.useState)(null);
50205
+ /**
50206
+ * How much the whole document is magnified.
50207
+ *
50208
+ * The document is one picture in several sheets, so the factor belongs here and
50209
+ * not in a page: a page body that kept its own would drop the magnification the
50210
+ * moment the reader turned to the next page, and every page would have to be
50211
+ * zoomed again. Held at this level, a pinch on page 3 is a pinch on the document.
50212
+ */
50213
+ const [zoom, setZoom] = (0, react.useState)("fit");
50214
+ /** What the page box holds while the reader is typing in it. */
50215
+ const [pageText, setPageText] = (0, react.useState)("1");
50216
+ /** Whether the page number is a box right now rather than a label. */
50217
+ const [editingPage, setEditingPage] = (0, react.useState)(false);
50218
+ /**
49997
50219
  * How large the numbers are drawn across the whole document.
49998
50220
  *
49999
50221
  * One style for every page, because the numbers have to read as one set: a page
@@ -50003,8 +50225,14 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
50003
50225
  const [busy, setBusy] = (0, react.useState)(null);
50004
50226
  const [status, setStatus] = (0, react.useState)(null);
50005
50227
  const pageCount = pdf === null ? 0 : pdf.pageCount;
50006
- /** The pane width a raster is asked for: one step, whatever the drag did inside it. */
50007
- const renderWidth = Math.ceil(paneWidth / RENDER_WIDTH_STEP) * RENDER_WIDTH_STEP;
50228
+ /**
50229
+ * The width a raster is asked for: the pane's, stepped, times the magnification.
50230
+ *
50231
+ * The factor goes in here rather than into the slot, because a slot widened around
50232
+ * an unzoomed bitmap is a blurred page: the pixels have to be there for the size
50233
+ * the reader asked for.
50234
+ */
50235
+ const renderWidth = Math.ceil(paneWidth * (zoom === "fit" ? 1 : zoom) / RENDER_WIDTH_STEP) * RENDER_WIDTH_STEP;
50008
50236
  /**
50009
50237
  * The seats this document acts through.
50010
50238
  *
@@ -50074,6 +50302,8 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
50074
50302
  * unmount, which is where the observer goes.
50075
50303
  */
50076
50304
  const attachHost = (0, react.useCallback)((element) => {
50305
+ hostRef.current = element;
50306
+ setChromeHost(element);
50077
50307
  observerRef.current?.disconnect();
50078
50308
  observerRef.current = null;
50079
50309
  if (element === null) return;
@@ -50084,6 +50314,67 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
50084
50314
  observer.observe(element);
50085
50315
  observerRef.current = observer;
50086
50316
  }, []);
50317
+ /** The only place a page number is decided: requests are clamped, not trusted. */
50318
+ const goTo = (0, react.useCallback)((next) => {
50319
+ setRequested(() => clampPage(next, pageCount));
50320
+ }, [pageCount]);
50321
+ /** Take the page number the box holds, if it holds one. */
50322
+ const commitPage = (0, react.useCallback)(() => {
50323
+ const next = parsePageInput(pageText, pageCount);
50324
+ if (next === null) {
50325
+ setPageText(String(requested));
50326
+ return;
50327
+ }
50328
+ goTo(next);
50329
+ setPageText(String(next));
50330
+ }, [
50331
+ goTo,
50332
+ pageCount,
50333
+ pageText,
50334
+ requested
50335
+ ]);
50336
+ /** Enter commits the box; Escape puts the box back to the page on screen. */
50337
+ const submitPage = (0, react.useCallback)((event) => {
50338
+ if (event.key === "Enter") {
50339
+ event.preventDefault();
50340
+ commitPage();
50341
+ setEditingPage(false);
50342
+ return;
50343
+ }
50344
+ if (event.key === "Escape") {
50345
+ event.preventDefault();
50346
+ setPageText(String(requested));
50347
+ setEditingPage(false);
50348
+ }
50349
+ }, [commitPage, requested]);
50350
+ /** The page number the toolbar states: the requested one, clamped to the document. */
50351
+ const stated = clampPage(requested, pageCount);
50352
+ /**
50353
+ * Turn to whatever page a keystroke asks for.
50354
+ *
50355
+ * The keys are read here rather than on the page body because paging belongs to
50356
+ * the document: the body annotates one page and has no idea there are others, and
50357
+ * a keystroke it swallowed would never reach the document at all.
50358
+ */
50359
+ (0, react.useEffect)(() => {
50360
+ if (pdf === null) return;
50361
+ const onKeyDown = (event) => {
50362
+ if (typingIn(event.target)) return;
50363
+ const turn = keyedTurn(event.key, event);
50364
+ if (turn === null) return;
50365
+ event.preventDefault();
50366
+ goTo(keyedPage(turn, stated, pageCount));
50367
+ };
50368
+ window.addEventListener("keydown", onKeyDown);
50369
+ return () => {
50370
+ window.removeEventListener("keydown", onKeyDown);
50371
+ };
50372
+ }, [
50373
+ goTo,
50374
+ pageCount,
50375
+ pdf,
50376
+ stated
50377
+ ]);
50087
50378
  (0, react.useEffect)(() => {
50088
50379
  if (pdf === null || renderWidth < MIN_PANE_WIDTH) return;
50089
50380
  let live = true;
@@ -50107,10 +50398,27 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
50107
50398
  requested,
50108
50399
  renderWidth
50109
50400
  ]);
50110
- /** The only place a page number is decided: requests are clamped, not trusted. */
50111
- const goTo = (0, react.useCallback)((next) => {
50112
- setRequested(() => Math.min(Math.max(next, 1), Math.max(pageCount, 1)));
50113
- }, [pageCount]);
50401
+ /**
50402
+ * Drop one mark from wherever in the document it was drawn.
50403
+ *
50404
+ * The list shows the whole file, so its delete button can name a mark on a page
50405
+ * this surface is not holding: the owner is the only one that knows every page.
50406
+ * @param id - the mark to remove.
50407
+ */
50408
+ const removeAnyMark = (0, react.useCallback)((id) => {
50409
+ setMarks((current) => {
50410
+ let changed = false;
50411
+ const next = new Map(current);
50412
+ for (const [page, pageMarks] of current) {
50413
+ const kept = pageMarks.filter((mark) => mark.id !== id);
50414
+ if (kept.length !== pageMarks.length) {
50415
+ changed = true;
50416
+ next.set(page, kept);
50417
+ }
50418
+ }
50419
+ return changed ? next : current;
50420
+ });
50421
+ }, []);
50114
50422
  /** Keep one page's unsaved edits while another page is on screen. */
50115
50423
  const remember = (0, react.useCallback)((edited, draft) => {
50116
50424
  setMarks((current) => current.get(edited) === draft.marks ? current : new Map(current).set(edited, draft.marks));
@@ -50250,6 +50558,7 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
50250
50558
  setSummary("");
50251
50559
  setCreatedAt(null);
50252
50560
  setStale(false);
50561
+ setGeneration((current) => current + 1);
50253
50562
  }, [address]);
50254
50563
  /**
50255
50564
  * Save the document's annotation and hand it to the agent, or into the draft.
@@ -50319,44 +50628,143 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
50319
50628
  pdf,
50320
50629
  signature
50321
50630
  ]);
50322
- const total = allMarks(marks).length;
50631
+ /**
50632
+ * Show a mark the reader pressed in the list, wherever in the document it is.
50633
+ *
50634
+ * The list is the document's, so a row can name a mark on a page that is not on
50635
+ * screen. Turning to it is this component's job because the page number is its
50636
+ * state — and the selection is set at the same moment, so the page that arrives
50637
+ * already has the mark selected and its comment pill beside it. Selecting first
50638
+ * and turning second is deliberate: the turn remounts the page body, and a mark
50639
+ * that became selected after the remount would show the pill only on a second
50640
+ * render.
50641
+ */
50642
+ const revealMark = (0, react.useCallback)((mark) => {
50643
+ setSelectedId(mark.id);
50644
+ if (mark.page !== void 0) goTo(mark.page);
50645
+ }, [goTo]);
50323
50646
  const numbering = new Map(allMarks(marks).map((mark, index) => [mark.id, index + 1]));
50647
+ /**
50648
+ * What every page body is told, assembled once.
50649
+ *
50650
+ * One pane shows one page at a time, so there is a single body to hand these to —
50651
+ * but it is assembled here rather than inline so the props a page needs are one
50652
+ * list, not a list that has to be kept in step with a second mount site.
50653
+ */
50654
+ const pageProps = {
50655
+ resourceAddress: props.resourceAddress,
50656
+ scrollportRef: props.scrollportRef,
50657
+ t,
50658
+ content: void 0,
50659
+ draft: { marks: marks.get(stated) ?? [] },
50660
+ onDraftChange: reportDraft,
50661
+ numbering,
50662
+ summary,
50663
+ onSummaryChange: setSummary,
50664
+ mode,
50665
+ onModeChange: setMode,
50666
+ openPanel,
50667
+ onOpenPanelChange: setOpenPanel,
50668
+ selectedId,
50669
+ onSelectedIdChange: setSelectedId,
50670
+ onRevealMark: revealMark
50671
+ };
50672
+ /**
50673
+ * The paging controls, for the page body's bar to place.
50674
+ *
50675
+ * Drawn here because the page number is this document's state, and placed there
50676
+ * because the bar belongs to the page body: one card holds every control the pane
50677
+ * offers, and none of them move when the page scrolls.
50678
+ */
50679
+ const pagingSlot = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
50680
+ className: "da-docbar-group",
50681
+ children: [
50682
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
50683
+ type: "button",
50684
+ className: "da-dock-btn",
50685
+ "aria-label": t("previousPage"),
50686
+ title: t("previousPage"),
50687
+ disabled: stated <= 1,
50688
+ onClick: () => {
50689
+ goTo(stated - 1);
50690
+ },
50691
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronLeftIcon, {})
50692
+ }),
50693
+ editingPage ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
50694
+ className: "da-page-input",
50695
+ autoFocus: true,
50696
+ inputMode: "numeric",
50697
+ "aria-label": t("pageJump"),
50698
+ value: pageText,
50699
+ onChange: (event) => {
50700
+ setPageText(event.target.value);
50701
+ },
50702
+ onBlur: () => {
50703
+ commitPage();
50704
+ setEditingPage(false);
50705
+ },
50706
+ onKeyDown: submitPage
50707
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
50708
+ type: "button",
50709
+ className: "da-page da-page-btn",
50710
+ title: t("pageJump"),
50711
+ "aria-label": t("pageJump"),
50712
+ onClick: () => {
50713
+ setPageText(String(stated));
50714
+ setEditingPage(true);
50715
+ },
50716
+ children: pdf === null ? `${stated}` : `${stated} / ${pageCount}`
50717
+ }),
50718
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
50719
+ type: "button",
50720
+ className: "da-dock-btn",
50721
+ "aria-label": t("nextPage"),
50722
+ title: t("nextPage"),
50723
+ disabled: pdf !== null && stated >= pageCount,
50724
+ onClick: () => {
50725
+ goTo(stated + 1);
50726
+ },
50727
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronRightIcon, {})
50728
+ })
50729
+ ]
50730
+ }) });
50324
50731
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
50325
50732
  className: "da-pdf",
50326
50733
  ref: attachHost,
50327
50734
  children: [
50328
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
50329
- className: "da-toolbar",
50735
+ shown !== null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
50736
+ className: "da-bar",
50737
+ "data-static": "true",
50738
+ role: "toolbar",
50739
+ "aria-label": t("pagerLabel"),
50330
50740
  children: [
50741
+ pagingSlot,
50742
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "da-spacer" }),
50743
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
50744
+ className: "da-dock-sep",
50745
+ "aria-hidden": true
50746
+ }),
50331
50747
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
50332
50748
  type: "button",
50333
- className: "da-btn",
50334
- disabled: requested <= 1,
50749
+ className: "da-dock-btn",
50750
+ disabled: busy !== null,
50751
+ "aria-label": t("sendToAgent"),
50752
+ title: t("sendToAgent"),
50335
50753
  onClick: () => {
50336
- goTo(requested - 1);
50754
+ run("agent");
50337
50755
  },
50338
- children: t("previousPage")
50339
- }),
50340
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
50341
- className: "da-page",
50342
- children: pdf === null ? `${requested}` : `${requested} / ${pageCount}`
50756
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SendIcon, {})
50343
50757
  }),
50344
50758
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
50345
50759
  type: "button",
50346
- className: "da-btn",
50347
- disabled: requested >= pageCount,
50760
+ className: "da-dock-btn",
50761
+ disabled: busy !== null,
50762
+ "aria-label": t("sendToComposer"),
50763
+ title: t("sendToComposer"),
50348
50764
  onClick: () => {
50349
- goTo(requested + 1);
50765
+ run("composer");
50350
50766
  },
50351
- children: t("nextPage")
50352
- }),
50353
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "da-spacer" }),
50354
- pdf === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
50355
- className: "da-page",
50356
- children: t("documentScope", {
50357
- pages: pageCount,
50358
- marks: total
50359
- })
50767
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChatIcon, {})
50360
50768
  })
50361
50769
  ]
50362
50770
  }),
@@ -50381,39 +50789,17 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
50381
50789
  children: t("pdfRendering")
50382
50790
  })
50383
50791
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AnnotatorBody, {
50384
- resourceAddress: props.resourceAddress,
50385
- scrollportRef: props.scrollportRef,
50386
- t,
50387
- content: void 0,
50792
+ ...pageProps,
50388
50793
  page: shown.bitmap,
50389
50794
  draft: { marks: marks.get(shown.page) ?? [] },
50390
- onDraftChange: reportDraft,
50391
- numbering,
50392
- summary,
50393
- onSummaryChange: setSummary,
50394
- mode,
50395
- onModeChange: setMode
50396
- }, shown.page),
50397
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
50398
- className: "da-send",
50399
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
50400
- type: "button",
50401
- className: "da-btn",
50402
- disabled: busy !== null,
50403
- onClick: () => {
50404
- run("agent");
50405
- },
50406
- children: t("sendToAgent")
50407
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
50408
- type: "button",
50409
- className: "da-btn",
50410
- disabled: busy !== null,
50411
- onClick: () => {
50412
- run("composer");
50413
- },
50414
- children: t("sendToComposer")
50415
- })]
50416
- })
50795
+ chromeHost,
50796
+ pagingSlot,
50797
+ onSend: run,
50798
+ zoom,
50799
+ onZoom: setZoom,
50800
+ documentMarks: allMarks(marks),
50801
+ onDocumentRemove: removeAnyMark
50802
+ }, `${generation}:${shown.page}`)
50417
50803
  ]
50418
50804
  });
50419
50805
  }
@@ -50426,9 +50812,10 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
50426
50812
  */
50427
50813
  async function renderPage(pdf, page, paneWidth) {
50428
50814
  const size = await pdf.size(page);
50429
- const fit = Math.max(.1, (paneWidth - PANE_INSET) / size.width);
50430
- const density = Math.min(MAX_DENSITY, globalThis.devicePixelRatio || 1);
50431
- const rendered = await pdf.render(page, fit * density);
50815
+ const room = Math.max(1, paneWidth - PANE_INSET);
50816
+ const shown = Math.min(room, size.width * FIT_CEILING);
50817
+ const density = Math.min(MAX_DENSITY, Math.max(1, globalThis.devicePixelRatio || 1));
50818
+ const rendered = await pdf.render(page, shown * density * SUPERSAMPLE / size.width);
50432
50819
  return {
50433
50820
  dataUrl: rendered.dataUrl,
50434
50821
  width: rendered.width,
package/lib/index.js CHANGED
@@ -21,9 +21,9 @@ const GUIDANCE = [
21
21
  "",
22
22
  "用户可能在文档预览里直接标注(箭头/框选/椭圆/手绘/单点),发送给你的消息以【附图标注】开头,正文就是**逐条标注清单**,不需要再去读任何附加文件(标注文件是投递用的临时件,发出即删)。收到时按下列纪律处理:",
23
23
  "",
24
- "1. 逐条读消息正文的清单:每条形如 `① 箭头 (10,10) → (40,30),要改:标号指错了`,括号里是图面像素坐标(原点在左上角),`要改:` 之后是用户写的**详细说明**——那是这条标注唯一的内容,按它判断意图。有的条目还带 `锚定:` 后缀,说明该处落在一个具体元素上(SVG 给 `title`/`id`/元素文字,HTML 给 `selector`/元素文字),定位时优先用它。",
24
+ "1. 逐条读消息正文的清单:每条形如 `① 箭头 (10,10) → (40,30),批注:标号指错了`。括号里是图面像素坐标,原点在左上角;框选与圈选给的是**左上角-右下角**,手绘线给的是它画在哪个框里。`批注:` 之后是用户写的**详细说明**——那是这条标注唯一的内容,按它判断意图。有的条目在形状后面紧跟一个圆括号,写着该处落在哪个元素上(SVG 给元素标题/`id`/元素文字,HTML 给 `selector`/元素文字),定位时优先用它。",
25
25
  "2. **PDF 是整份文档一份标注**:每条标注都带 `第 N 页`,坐标是那一页自己的(原点在该页左上角)。看每条标注前先确认它的页码。",
26
- "3. 有形状的标注(箭头/框选/椭圆/手绘)会附一张标注图(`<图名>.annotated.png`,PDF 则是一份**带批注的 PDF**,其批注是原生 PDF 注释对象:`/Ink` 手绘、`/Square` 框选、`/Circle` 圈选、`/Line` 箭头),可直接打开核对图面位置。**若消息说「本条标注只有位置点,没有标注图」,那就是全部内容:只有坐标加一句说明,直接按原图定位处理,不要去找不存在的附件。**",
26
+ "3. 若标注超出原图边界,标注图会把越界的那圈纸也画出来,并用**灰色虚线**框住原图真正的边缘:虚线**里面**是原图,**外面**是你画的标注伸出去的部分——那块不属于图,不要把它当成新增的画布内容。有形状的标注(箭头/框选/椭圆/手绘)会附一张标注图(`<图名>.annotated.png`,PDF 则是一份**带批注的 PDF**,其批注是原生 PDF 注释对象:`/Ink` 手绘、`/Square` 框选、`/Circle` 圈选、`/Line` 箭头),可直接打开核对图面位置。**若消息说「本条标注只有位置点,没有标注图」,那就是全部内容:只有坐标加一句说明,直接按原图定位处理,不要去找不存在的附件。**",
27
27
  "4. 逐条处理标注:每条标注都要落到具体的图面改动,并在答复里逐条回应(编号与标注一致)。无法实现的标注要说明原因,不要静默跳过。",
28
28
  "5. 修改附图以**标注所指的生成源**为准(绘制脚本、SVG 源或 `generate_patent_figure` 的入参),不要直接涂改导出的位图;PDF 同理,改的是生成该 PDF 的源头,不是那份带批注的副本。未标注的部分保持不动。",
29
29
  "6. 改完图后重新渲染并核对:图面尺寸/坐标若变化,旧标注的坐标会失准,此时以 `anchor` 与标注文字为准复核一遍,再回报结果。",
@@ -270,7 +270,7 @@ const WEIGHT_MIN = .5;
270
270
  * @returns the value, pulled to the nearest end of the range.
271
271
  */
272
272
  function clampWeight(value) {
273
- return Math.min(4, Math.max(WEIGHT_MIN, value));
273
+ return Math.min(8, Math.max(WEIGHT_MIN, value));
274
274
  }
275
275
  //#endregion
276
276
  //#region src/host/store.ts
@@ -693,11 +693,11 @@ async function resolveTarget(address, deps) {
693
693
  ok: false,
694
694
  message: `not a file address: ${address}`
695
695
  };
696
+ const sessionId = parsed.sessionId;
697
+ const cwd = sessionId === void 0 ? void 0 : await deps.sessionCwd(sessionId);
696
698
  let absolute;
697
699
  if (isAbsolute(parsed.path)) absolute = parsed.path;
698
700
  else {
699
- const sessionId = parsed.sessionId;
700
- const cwd = sessionId === void 0 ? void 0 : await deps.sessionCwd(sessionId);
701
701
  if (cwd === void 0) return {
702
702
  ok: false,
703
703
  message: `cannot resolve this Session's directory for a relative path: ${address}`
@@ -730,7 +730,8 @@ async function resolveTarget(address, deps) {
730
730
  target: {
731
731
  address,
732
732
  path: absolute,
733
- mediaType
733
+ mediaType,
734
+ workspace: cwd ?? null
734
735
  }
735
736
  };
736
737
  }
package/market/index.json CHANGED
@@ -93,8 +93,8 @@
93
93
  },
94
94
  "versions": [
95
95
  {
96
- "version": "1.1.2",
97
- "publishTimestamp": 1792731605,
96
+ "version": "1.2.0",
97
+ "publishTimestamp": 1793167602,
98
98
  "source": {
99
99
  "type": "npm",
100
100
  "registry": "https://registry.npmjs.org"