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.js +1382 -516
- package/lib/client.pdf.js +452 -65
- package/lib/index.js +7 -6
- package/market/index.json +2 -2
- package/package.json +1 -1
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
50007
|
-
|
|
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
|
-
/**
|
|
50111
|
-
|
|
50112
|
-
|
|
50113
|
-
|
|
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
|
-
|
|
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-
|
|
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:
|
|
50749
|
+
className: "da-dock-btn",
|
|
50750
|
+
disabled: busy !== null,
|
|
50751
|
+
"aria-label": t("sendToAgent"),
|
|
50752
|
+
title: t("sendToAgent"),
|
|
50335
50753
|
onClick: () => {
|
|
50336
|
-
|
|
50754
|
+
run("agent");
|
|
50337
50755
|
},
|
|
50338
|
-
children:
|
|
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:
|
|
50760
|
+
className: "da-dock-btn",
|
|
50761
|
+
disabled: busy !== null,
|
|
50762
|
+
"aria-label": t("sendToComposer"),
|
|
50763
|
+
title: t("sendToComposer"),
|
|
50348
50764
|
onClick: () => {
|
|
50349
|
-
|
|
50765
|
+
run("composer");
|
|
50350
50766
|
},
|
|
50351
|
-
children:
|
|
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
|
-
|
|
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
|
-
|
|
50391
|
-
|
|
50392
|
-
|
|
50393
|
-
|
|
50394
|
-
|
|
50395
|
-
|
|
50396
|
-
|
|
50397
|
-
|
|
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
|
|
50430
|
-
const
|
|
50431
|
-
const
|
|
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)
|
|
24
|
+
"1. 逐条读消息正文的清单:每条形如 `① 箭头 (10,10) → (40,30),批注:标号指错了`。括号里是图面像素坐标,原点在左上角;框选与圈选给的是**左上角-右下角**,手绘线给的是它画在哪个框里。`批注:` 之后是用户写的**详细说明**——那是这条标注唯一的内容,按它判断意图。有的条目在形状后面紧跟一个圆括号,写着该处落在哪个元素上(SVG 给元素标题/`id`/元素文字,HTML 给 `selector`/元素文字),定位时优先用它。",
|
|
25
25
|
"2. **PDF 是整份文档一份标注**:每条标注都带 `第 N 页`,坐标是那一页自己的(原点在该页左上角)。看每条标注前先确认它的页码。",
|
|
26
|
-
"3.
|
|
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(
|
|
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