papilot 0.1.1 → 0.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/README.md +15 -3
- package/README.zh-CN.md +14 -2
- package/bin/doctor.mjs +239 -0
- package/bin/papilot.mjs +95 -86
- package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/assets/index-CqhlgnOu.css +1 -0
- package/dist/assets/index-Czf1eCno.js +185 -0
- package/dist/assets/math-DEcVZfaU.css +1 -0
- package/dist/assets/math-Ebyrobf9.js +263 -0
- package/dist/assets/{pdf-view-DJo0X38w.js → pdf-view-BhJZr4qw.js} +18 -18
- package/dist/index.html +11 -2
- package/examples/papilot-paper/preamble.tex +17 -11
- package/package.json +12 -2
- package/scripts/service.ts +8 -2
- package/skills/check-math.md +12 -0
- package/skills/consistency.md +11 -0
- package/skills/latex-warnings.md +12 -0
- package/skills/polish.md +13 -0
- package/skills/proofread.md +12 -0
- package/skills/reviewer-response.md +11 -0
- package/skills/shorten.md +12 -0
- package/src/conversation.ts +37 -0
- package/src/notes.ts +5 -0
- package/src/server/agents.ts +4 -3
- package/src/server/main.ts +19 -5
- package/src/server/review.ts +1 -1
- package/src/server/service-environment.ts +22 -0
- package/src/server/skills.ts +57 -0
- package/src/server/workspace.ts +41 -18
- package/src/shared.ts +10 -2
- package/vendor/latex-review/dist/web/assets/index-Db7pq4Cj.css +1 -0
- package/vendor/latex-review/dist/web/assets/index-DqkrTw0t.js +19 -0
- package/vendor/latex-review/dist/web/index.html +2 -2
- package/vendor/latex-review/src/client.ts +10 -3
- package/vendor/latex-review/src/core/build.ts +53 -7
- package/vendor/latex-review/src/core/outline.ts +6 -3
- package/vendor/latex-review/src/core/publish.ts +8 -6
- package/vendor/latex-review/src/runtime.ts +14 -0
- package/vendor/latex-review/src/server.ts +2 -1
- package/vendor/latex-review/src/types.ts +16 -1
- package/vendor/latex-review/src/web/ChangeMap.tsx +21 -9
- package/vendor/latex-review/src/web/PdfStrip.tsx +5 -1
- package/vendor/latex-review/src/web/i18n.ts +48 -0
- package/vendor/latex-review/src/web/model.ts +23 -4
- package/vendor/latex-review/src/web/overview.tsx +25 -20
- package/vendor/latex-review/src/web/review.tsx +387 -143
- package/vendor/latex-review/src/web/style.css +106 -0
- package/vendor/latex-review/src/web/zh-CN.ts +186 -0
- package/dist/assets/index-BhQFCMTx.js +0 -131
- package/dist/assets/index-CAV3-87U.css +0 -1
- package/vendor/latex-review/dist/web/assets/index-DyzYnIGk.css +0 -1
- package/vendor/latex-review/dist/web/assets/index-HxBK-dls.js +0 -19
|
@@ -18,7 +18,9 @@ import type {
|
|
|
18
18
|
import { PdfStrip } from "./PdfStrip.tsx";
|
|
19
19
|
import { ChangeMap } from "./ChangeMap.tsx";
|
|
20
20
|
import { STATUS } from "./overview.tsx";
|
|
21
|
+
import { t, tn, withLanguage } from "./i18n.ts";
|
|
21
22
|
import {
|
|
23
|
+
STATE,
|
|
22
24
|
buildStops,
|
|
23
25
|
copyReference,
|
|
24
26
|
describeLocation,
|
|
@@ -37,6 +39,11 @@ const KIND = {
|
|
|
37
39
|
identical: "No visible change",
|
|
38
40
|
unmapped: "Source only",
|
|
39
41
|
};
|
|
42
|
+
const ACTION: Record<string, string> = {
|
|
43
|
+
accept: "Accept",
|
|
44
|
+
reject: "Reject",
|
|
45
|
+
comment: "Comment",
|
|
46
|
+
};
|
|
40
47
|
const PHASE: Record<string, string> = {
|
|
41
48
|
snapshot: "Taking a snapshot",
|
|
42
49
|
build: "Compiling LaTeX",
|
|
@@ -64,9 +71,13 @@ function store(key: string, value: unknown) {
|
|
|
64
71
|
function jobText(job: PageState["job"]) {
|
|
65
72
|
return job?.status === "queued"
|
|
66
73
|
? job.ahead
|
|
67
|
-
?
|
|
68
|
-
|
|
69
|
-
|
|
74
|
+
? tn(
|
|
75
|
+
job.ahead,
|
|
76
|
+
"Queued behind {n} other round…",
|
|
77
|
+
"Queued behind {n} other rounds…",
|
|
78
|
+
)
|
|
79
|
+
: t("Queued…")
|
|
80
|
+
: `${t(PHASE[job?.phase ?? ""] || "Starting")}…`;
|
|
70
81
|
}
|
|
71
82
|
export function ReviewApp({
|
|
72
83
|
task,
|
|
@@ -85,7 +96,10 @@ export function ReviewApp({
|
|
|
85
96
|
[openSource, setOpenSource] = useState<Set<number>>(new Set()),
|
|
86
97
|
[composer, setComposer] = useState<Composer | null>(null),
|
|
87
98
|
[draft, setDraft] = useState(""),
|
|
88
|
-
[help, setHelp] = useState(false)
|
|
99
|
+
[help, setHelp] = useState(false),
|
|
100
|
+
[history, setHistory] = useState(
|
|
101
|
+
Boolean(pinned) || new URLSearchParams(location.search).has("history"),
|
|
102
|
+
);
|
|
89
103
|
const paper = useRef<HTMLElement>(null),
|
|
90
104
|
epoch = useRef(0),
|
|
91
105
|
scrollUntil = useRef(0),
|
|
@@ -101,6 +115,7 @@ export function ReviewApp({
|
|
|
101
115
|
stops.map((stop) =>
|
|
102
116
|
describeLocation(
|
|
103
117
|
stop.units.map((id) => units.get(id)!).filter(Boolean),
|
|
118
|
+
t,
|
|
104
119
|
),
|
|
105
120
|
),
|
|
106
121
|
[stops, units],
|
|
@@ -113,12 +128,34 @@ export function ReviewApp({
|
|
|
113
128
|
latest = Boolean(
|
|
114
129
|
detail && state && detail.round === state.latest && !state.closed,
|
|
115
130
|
);
|
|
116
|
-
const
|
|
117
|
-
|
|
131
|
+
const navigation = stops
|
|
132
|
+
.map((stop, index) => ({ stop, index }))
|
|
133
|
+
.filter(({ stop }) => history || pendingUnits(stop, decisions).length)
|
|
134
|
+
.map(({ index }) => index);
|
|
135
|
+
const adjacent = (index: number, direction: number) =>
|
|
136
|
+
navigation[navigation.indexOf(index) + direction] ?? -1;
|
|
137
|
+
const undoChanges = state?.undo
|
|
138
|
+
? stops.flatMap((stop, index) =>
|
|
139
|
+
stop.units.some((id) => state.undo!.changes.includes(id))
|
|
140
|
+
? [index + 1]
|
|
141
|
+
: [],
|
|
142
|
+
)
|
|
143
|
+
: [];
|
|
144
|
+
useEffect(() => {
|
|
145
|
+
if (history || composer) return;
|
|
146
|
+
if (!navigation.includes(current)) setCurrent(navigation[0] ?? -1);
|
|
147
|
+
}, [decisions, history, composer, detail]);
|
|
148
|
+
// Lock reasons come from the host in English; known ones are translated.
|
|
149
|
+
const actionLock = (kind: ActionKind) => {
|
|
150
|
+
const reason =
|
|
151
|
+
kind === "comment" ? state?.commentLock : state?.decisionLock;
|
|
152
|
+
return reason ? t(reason) : reason;
|
|
153
|
+
};
|
|
118
154
|
function blocked(kind: ActionKind): boolean {
|
|
119
155
|
const current = snapshot.current.state;
|
|
120
|
-
const reason =
|
|
121
|
-
|
|
156
|
+
const reason =
|
|
157
|
+
kind === "comment" ? current?.commentLock : current?.decisionLock;
|
|
158
|
+
if (reason) setToast(t(reason));
|
|
122
159
|
return Boolean(reason);
|
|
123
160
|
}
|
|
124
161
|
async function api<T>(route: string, body?: unknown): Promise<T> {
|
|
@@ -207,12 +244,19 @@ export function ReviewApp({
|
|
|
207
244
|
!pendingUnits(nextStops[index], loadedState.decisions).length
|
|
208
245
|
)
|
|
209
246
|
index = nextPending(nextStops, loadedState.decisions, index);
|
|
210
|
-
if (index < 0) index = nextStops.length ? 0 : -1;
|
|
247
|
+
if (index < 0) index = pinned && nextStops.length ? 0 : -1;
|
|
248
|
+
if (
|
|
249
|
+
previous.detail &&
|
|
250
|
+
loaded.round !== previous.detail.round &&
|
|
251
|
+
!pinned
|
|
252
|
+
)
|
|
253
|
+
setHistory(false);
|
|
211
254
|
setState(loadedState);
|
|
212
255
|
setDetail(loaded);
|
|
213
256
|
setCurrent(index);
|
|
214
257
|
setOpenSource(new Set());
|
|
215
|
-
if (previous.detail)
|
|
258
|
+
if (previous.detail)
|
|
259
|
+
setToast(t("Round {round} is ready", { round: target }));
|
|
216
260
|
} else setState(next);
|
|
217
261
|
} catch (e) {
|
|
218
262
|
if (!cancelled) {
|
|
@@ -247,7 +291,14 @@ export function ReviewApp({
|
|
|
247
291
|
return () => clearTimeout(timer);
|
|
248
292
|
}, [toast]);
|
|
249
293
|
function beginComposer(changes = pendingUnits(stops[current], decisions)) {
|
|
250
|
-
if (
|
|
294
|
+
if (
|
|
295
|
+
!latest ||
|
|
296
|
+
!changes.length ||
|
|
297
|
+
busy ||
|
|
298
|
+
state?.closed ||
|
|
299
|
+
blocked("comment")
|
|
300
|
+
)
|
|
301
|
+
return;
|
|
251
302
|
const key = `latex-review:${task}:draft:${detail?.round}:${changes.join(",")}`;
|
|
252
303
|
setDraft(readStorage(key, ""));
|
|
253
304
|
setComposer({ changes, stop: current, key });
|
|
@@ -261,7 +312,14 @@ export function ReviewApp({
|
|
|
261
312
|
changes = pendingUnits(stops[current], decisions),
|
|
262
313
|
body = "",
|
|
263
314
|
) {
|
|
264
|
-
if (
|
|
315
|
+
if (
|
|
316
|
+
!changes.length ||
|
|
317
|
+
snapshot.current.busy ||
|
|
318
|
+
!latest ||
|
|
319
|
+
!detail ||
|
|
320
|
+
blocked(kind)
|
|
321
|
+
)
|
|
322
|
+
return;
|
|
265
323
|
epoch.current++;
|
|
266
324
|
setBusy(true);
|
|
267
325
|
snapshot.current.busy = true;
|
|
@@ -279,8 +337,10 @@ export function ReviewApp({
|
|
|
279
337
|
? current
|
|
280
338
|
: nextPending(stops, next.decisions, current);
|
|
281
339
|
if (target >= 0 && target !== current) select(target);
|
|
282
|
-
if (target < 0)
|
|
283
|
-
|
|
340
|
+
if (target < 0) {
|
|
341
|
+
if (!history) setCurrent(-1);
|
|
342
|
+
setToast(t("All changes reviewed. Your decisions are saved."));
|
|
343
|
+
}
|
|
284
344
|
} catch (e) {
|
|
285
345
|
setToast((e as Error).message);
|
|
286
346
|
} finally {
|
|
@@ -290,7 +350,8 @@ export function ReviewApp({
|
|
|
290
350
|
}
|
|
291
351
|
}
|
|
292
352
|
async function undo() {
|
|
293
|
-
if (!state?.undo || snapshot.current.busy || blocked("accept"))
|
|
353
|
+
if (!state?.undo || !latest || snapshot.current.busy || blocked("accept"))
|
|
354
|
+
return;
|
|
294
355
|
epoch.current++;
|
|
295
356
|
setBusy(true);
|
|
296
357
|
snapshot.current.busy = true;
|
|
@@ -302,8 +363,11 @@ export function ReviewApp({
|
|
|
302
363
|
const target = stops.findIndex((s) =>
|
|
303
364
|
s.units.some((id) => next.restored.includes(id)),
|
|
304
365
|
);
|
|
366
|
+
setHistory(false);
|
|
305
367
|
if (target >= 0) select(target);
|
|
306
|
-
setToast(
|
|
368
|
+
setToast(
|
|
369
|
+
t("Last decision undone. The restored changes need review again."),
|
|
370
|
+
);
|
|
307
371
|
} catch (e) {
|
|
308
372
|
setToast((e as Error).message);
|
|
309
373
|
} finally {
|
|
@@ -340,12 +404,14 @@ export function ReviewApp({
|
|
|
340
404
|
);
|
|
341
405
|
setToast(
|
|
342
406
|
selected
|
|
343
|
-
? "Copied selected text with file, lines and PDF page."
|
|
344
|
-
: "Copied the change location.",
|
|
407
|
+
? t("Copied selected text with file, lines and PDF page.")
|
|
408
|
+
: t("Copied the change location."),
|
|
345
409
|
);
|
|
346
410
|
} catch (e) {
|
|
347
411
|
setToast(
|
|
348
|
-
|
|
412
|
+
t(
|
|
413
|
+
"Clipboard unavailable. Select the PDF text and use your browser’s Copy command.",
|
|
414
|
+
),
|
|
349
415
|
);
|
|
350
416
|
}
|
|
351
417
|
}
|
|
@@ -402,8 +468,10 @@ export function ReviewApp({
|
|
|
402
468
|
if (key === "a") void decide("accept");
|
|
403
469
|
else if (key === "r") void decide("reject");
|
|
404
470
|
else if (key === "c") beginComposer();
|
|
405
|
-
else if (key === "j" || key === "arrowright")
|
|
406
|
-
|
|
471
|
+
else if (key === "j" || key === "arrowright")
|
|
472
|
+
select(adjacent(current, 1));
|
|
473
|
+
else if (key === "k" || key === "arrowleft")
|
|
474
|
+
select(adjacent(current, -1));
|
|
407
475
|
else if (key === "n") select(nextPending(stops, decisions, current));
|
|
408
476
|
else if (key === "s") toggleSource(current);
|
|
409
477
|
else if (key === "u") void undo();
|
|
@@ -411,7 +479,7 @@ export function ReviewApp({
|
|
|
411
479
|
};
|
|
412
480
|
window.addEventListener("keydown", listener);
|
|
413
481
|
return () => window.removeEventListener("keydown", listener);
|
|
414
|
-
}, [state, detail, current, busy, composer, draft, help]);
|
|
482
|
+
}, [state, detail, current, busy, composer, draft, help, history]);
|
|
415
483
|
function onScroll() {
|
|
416
484
|
if (
|
|
417
485
|
!paper.current ||
|
|
@@ -421,16 +489,16 @@ export function ReviewApp({
|
|
|
421
489
|
)
|
|
422
490
|
return;
|
|
423
491
|
const box = paper.current.getBoundingClientRect(),
|
|
424
|
-
regions = [
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
492
|
+
regions = [...paper.current.querySelectorAll<HTMLElement>("[data-stop]")]
|
|
493
|
+
.filter((article) => navigation.includes(Number(article.dataset.stop)))
|
|
494
|
+
.map((article) => {
|
|
495
|
+
const b = article.getBoundingClientRect();
|
|
496
|
+
return {
|
|
497
|
+
index: Number(article.dataset.stop),
|
|
498
|
+
top: b.top,
|
|
499
|
+
bottom: b.bottom,
|
|
500
|
+
};
|
|
501
|
+
});
|
|
434
502
|
const index = visibleStop(regions, current, box.top + 60, box.bottom - 100);
|
|
435
503
|
if (index !== current) select(index, false);
|
|
436
504
|
}
|
|
@@ -440,21 +508,21 @@ export function ReviewApp({
|
|
|
440
508
|
remaining = nextPending(stops, decisions);
|
|
441
509
|
return (
|
|
442
510
|
<div className="dock">
|
|
443
|
-
<nav className="actions" aria-label="Review the current change">
|
|
444
|
-
{
|
|
511
|
+
<nav className="actions" aria-label={t("Review the current change")}>
|
|
512
|
+
{navigation.length > 1 && (
|
|
445
513
|
<>
|
|
446
514
|
<button
|
|
447
515
|
className="nav"
|
|
448
|
-
aria-label="Previous change"
|
|
516
|
+
aria-label={t("Previous change")}
|
|
449
517
|
aria-keyshortcuts="k ArrowLeft"
|
|
450
|
-
title="Previous change (K or ←)"
|
|
451
|
-
disabled={index
|
|
452
|
-
onClick={() => select(index -
|
|
518
|
+
title={t("Previous change (K or ←)")}
|
|
519
|
+
disabled={adjacent(index, -1) < 0}
|
|
520
|
+
onClick={() => select(adjacent(index, -1))}
|
|
453
521
|
>
|
|
454
522
|
‹
|
|
455
523
|
</button>
|
|
456
524
|
<span className="position">
|
|
457
|
-
{index + 1} / {
|
|
525
|
+
{navigation.indexOf(index) + 1} / {navigation.length}
|
|
458
526
|
</span>
|
|
459
527
|
</>
|
|
460
528
|
)}
|
|
@@ -465,14 +533,19 @@ export function ReviewApp({
|
|
|
465
533
|
className={kind}
|
|
466
534
|
key={kind}
|
|
467
535
|
aria-keyshortcuts={kind[0]}
|
|
468
|
-
title={
|
|
469
|
-
|
|
536
|
+
title={
|
|
537
|
+
actionLock(kind) ||
|
|
538
|
+
`${t(ACTION[kind])} (${kind[0].toUpperCase()})`
|
|
539
|
+
}
|
|
540
|
+
aria-describedby={
|
|
541
|
+
actionLock(kind) ? "review-lock" : undefined
|
|
542
|
+
}
|
|
470
543
|
disabled={busy || !latest || Boolean(actionLock(kind))}
|
|
471
544
|
onClick={() =>
|
|
472
545
|
kind === "comment" ? beginComposer() : void decide(kind)
|
|
473
546
|
}
|
|
474
547
|
>
|
|
475
|
-
{kind
|
|
548
|
+
{t(ACTION[kind])}
|
|
476
549
|
{pending.length > 1 && kind !== "comment"
|
|
477
550
|
? ` (${pending.length})`
|
|
478
551
|
: ""}
|
|
@@ -485,34 +558,25 @@ export function ReviewApp({
|
|
|
485
558
|
aria-keyshortcuts="n"
|
|
486
559
|
onClick={() => select(nextPending(stops, decisions, current))}
|
|
487
560
|
>
|
|
488
|
-
Next undecided
|
|
561
|
+
{t("Next undecided")}
|
|
489
562
|
</button>
|
|
490
563
|
) : (
|
|
491
564
|
<span className="complete">
|
|
492
|
-
{latest ? "Review complete" : "Earlier round"}
|
|
565
|
+
{latest ? t("Review complete") : t("Earlier round")}
|
|
493
566
|
</span>
|
|
494
567
|
)}
|
|
495
|
-
{
|
|
568
|
+
{navigation.length > 1 && (
|
|
496
569
|
<button
|
|
497
570
|
className="nav"
|
|
498
|
-
aria-label="Next change"
|
|
571
|
+
aria-label={t("Next change")}
|
|
499
572
|
aria-keyshortcuts="j ArrowRight"
|
|
500
|
-
title="Next change (J or →)"
|
|
501
|
-
disabled={index
|
|
502
|
-
onClick={() => select(index
|
|
573
|
+
title={t("Next change (J or →)")}
|
|
574
|
+
disabled={adjacent(index, 1) < 0}
|
|
575
|
+
onClick={() => select(adjacent(index, 1))}
|
|
503
576
|
>
|
|
504
577
|
›
|
|
505
578
|
</button>
|
|
506
579
|
)}
|
|
507
|
-
<button
|
|
508
|
-
className="undo"
|
|
509
|
-
title={state.decisionLock || undefined}
|
|
510
|
-
aria-describedby={state.decisionLock ? "review-lock" : undefined}
|
|
511
|
-
disabled={!state.undo || busy || Boolean(state.decisionLock)}
|
|
512
|
-
onClick={() => void undo()}
|
|
513
|
-
>
|
|
514
|
-
Undo
|
|
515
|
-
</button>
|
|
516
580
|
</nav>
|
|
517
581
|
</div>
|
|
518
582
|
);
|
|
@@ -523,8 +587,12 @@ export function ReviewApp({
|
|
|
523
587
|
inline: boolean,
|
|
524
588
|
): ReactNode {
|
|
525
589
|
const known = block.units.map((id) => units.get(id)!).filter(Boolean),
|
|
526
|
-
resolved = resolvedSide(block.units, decisions),
|
|
527
590
|
status = stopState(block.units, decisions),
|
|
591
|
+
reviewed = !pendingUnits(stops[index], decisions).length,
|
|
592
|
+
resolved = history
|
|
593
|
+
? undefined
|
|
594
|
+
: resolvedSide(block.units, decisions) ||
|
|
595
|
+
(status === "commented" ? "after" : undefined),
|
|
528
596
|
source =
|
|
529
597
|
openSource.has(index) ||
|
|
530
598
|
block.identical ||
|
|
@@ -534,24 +602,24 @@ export function ReviewApp({
|
|
|
534
602
|
block.units.map((id) => state?.comments[id]).filter(Boolean),
|
|
535
603
|
),
|
|
536
604
|
],
|
|
537
|
-
location = describeLocation(known),
|
|
605
|
+
location = describeLocation(known, t),
|
|
538
606
|
kindLabel = block.reflow
|
|
539
|
-
? "Reflow"
|
|
607
|
+
? t("Reflow")
|
|
540
608
|
: [...new Set(known.map((u) => u.visual ?? "unmapped"))]
|
|
541
609
|
.filter((kind) => kind !== "text")
|
|
542
|
-
.map((kind) => KIND[kind])
|
|
610
|
+
.map((kind) => t(KIND[kind]))
|
|
543
611
|
.join(", ");
|
|
544
612
|
const images = (items: Strip[], side: "before" | "after") => (
|
|
545
613
|
<div
|
|
546
614
|
className={`side ${side}${block.identical ? " identical" : ""}`}
|
|
547
|
-
aria-label={side === "before" ? "Before" : "After"}
|
|
615
|
+
aria-label={side === "before" ? t("Before") : t("After")}
|
|
548
616
|
>
|
|
549
617
|
{items.map((image) => (
|
|
550
618
|
<PdfStrip
|
|
551
619
|
key={image.src}
|
|
552
620
|
image={image}
|
|
553
621
|
base={detail!.files}
|
|
554
|
-
label={side === "before" ? "Before" : "After"}
|
|
622
|
+
label={side === "before" ? t("Before") : t("After")}
|
|
555
623
|
clean={Boolean(resolved)}
|
|
556
624
|
/>
|
|
557
625
|
))}
|
|
@@ -560,42 +628,66 @@ export function ReviewApp({
|
|
|
560
628
|
return (
|
|
561
629
|
<article
|
|
562
630
|
key={index}
|
|
563
|
-
className={`change ${inline ? "inline" : "standalone"}${current === index ? " current" : ""}`}
|
|
631
|
+
className={`change ${inline ? "inline" : "standalone"}${current === index ? " current" : ""}${reviewed && !history ? " reviewed-change" : ""}${reviewed && !history && !resolved ? " mixed-reviewed" : ""}`}
|
|
564
632
|
data-stop={index}
|
|
565
633
|
data-state={status}
|
|
566
634
|
data-resolved={resolved}
|
|
567
635
|
tabIndex={-1}
|
|
568
636
|
onClick={() => {
|
|
569
|
-
if (index !== current && !composer
|
|
637
|
+
if (index !== current && !composer && (history || !reviewed))
|
|
638
|
+
select(index, false);
|
|
570
639
|
}}
|
|
571
640
|
>
|
|
641
|
+
{reviewed && !history && !resolved && (
|
|
642
|
+
<p className="reviewed-placeholder">
|
|
643
|
+
<button
|
|
644
|
+
className="link"
|
|
645
|
+
onClick={() => {
|
|
646
|
+
setHistory(true);
|
|
647
|
+
select(index, false);
|
|
648
|
+
}}
|
|
649
|
+
>
|
|
650
|
+
{t("Reviewed changes · View in History")}
|
|
651
|
+
</button>
|
|
652
|
+
</p>
|
|
653
|
+
)}
|
|
572
654
|
<header className="change-head">
|
|
573
|
-
|
|
574
|
-
|
|
655
|
+
{/* The English number and location are kept for tools that quote
|
|
656
|
+
them to an agent, whatever the interface language. */}
|
|
657
|
+
<span className="change-number" data-change={index + 1}>
|
|
658
|
+
{t("Change {n}", { n: index + 1 })}
|
|
659
|
+
</span>
|
|
660
|
+
<span
|
|
661
|
+
className="change-where"
|
|
662
|
+
title={location}
|
|
663
|
+
data-location={describeLocation(known)}
|
|
664
|
+
>
|
|
575
665
|
{location}
|
|
576
666
|
</span>
|
|
577
667
|
{actionBar(index)}
|
|
578
668
|
{kindLabel && <span className="change-kind">{kindLabel}</span>}
|
|
579
669
|
{status !== "pending" && (
|
|
580
670
|
<span className="badge">
|
|
581
|
-
{status
|
|
671
|
+
{t(STATE[status] ?? status)}
|
|
582
672
|
</span>
|
|
583
673
|
)}
|
|
584
674
|
<button
|
|
585
675
|
className="link copy-reference"
|
|
586
|
-
title=
|
|
676
|
+
title={t(
|
|
677
|
+
"Copy selected text with this change’s file, line numbers and PDF page",
|
|
678
|
+
)}
|
|
587
679
|
onPointerDown={(e) => e.preventDefault()}
|
|
588
680
|
onClick={() => void copy(index)}
|
|
589
681
|
>
|
|
590
|
-
Copy reference
|
|
682
|
+
{t("Copy reference")}
|
|
591
683
|
</button>
|
|
592
684
|
<button
|
|
593
685
|
className={`link ${source ? "active" : ""}`}
|
|
594
|
-
title="Show the LaTeX source (S)"
|
|
686
|
+
title={t("Show the LaTeX source (S)")}
|
|
595
687
|
onClick={() => toggleSource(index)}
|
|
596
688
|
>
|
|
597
689
|
{block.units.length > 1
|
|
598
|
-
?
|
|
690
|
+
? t("{n} edits · LaTeX", { n: block.units.length })
|
|
599
691
|
: "LaTeX"}
|
|
600
692
|
</button>
|
|
601
693
|
</header>
|
|
@@ -606,7 +698,9 @@ export function ReviewApp({
|
|
|
606
698
|
.map((h, i) => (
|
|
607
699
|
<p key={i}>
|
|
608
700
|
<strong>
|
|
609
|
-
Revised after your comment in round {
|
|
701
|
+
{t("Revised after your comment in round {round}: ", {
|
|
702
|
+
round: h.round,
|
|
703
|
+
})}
|
|
610
704
|
</strong>
|
|
611
705
|
“{h.comment}”
|
|
612
706
|
</p>
|
|
@@ -620,17 +714,19 @@ export function ReviewApp({
|
|
|
620
714
|
{block.after.length > 0 && images(block.after, "after")}
|
|
621
715
|
{block.identical && (
|
|
622
716
|
<p className="caption">
|
|
623
|
-
No visible change in the PDF; see the LaTeX source.
|
|
717
|
+
{t("No visible change in the PDF; see the LaTeX source.")}
|
|
624
718
|
</p>
|
|
625
719
|
)}
|
|
626
720
|
{block.reflow && (
|
|
627
|
-
<p className="caption">
|
|
721
|
+
<p className="caption">
|
|
722
|
+
{t("Continued: only line breaks moved here.")}
|
|
723
|
+
</p>
|
|
628
724
|
)}
|
|
629
725
|
{remarks.length > 0 && (
|
|
630
726
|
<div className="remark">
|
|
631
727
|
{remarks.map((r, i) => (
|
|
632
728
|
<p key={i}>
|
|
633
|
-
<strong>Your comment: </strong>
|
|
729
|
+
<strong>{t("Your comment: ")}</strong>
|
|
634
730
|
{r}
|
|
635
731
|
</p>
|
|
636
732
|
))}
|
|
@@ -646,13 +742,17 @@ export function ReviewApp({
|
|
|
646
742
|
>
|
|
647
743
|
<div className="source-title">
|
|
648
744
|
<span>
|
|
649
|
-
{unit.file} ·
|
|
745
|
+
{unit.file} ·{" "}
|
|
746
|
+
{t("lines {start}–{end}", {
|
|
747
|
+
start: unit.new_start,
|
|
748
|
+
end: unit.new_end,
|
|
749
|
+
})}
|
|
650
750
|
</span>
|
|
651
751
|
{known.length > 1 && (
|
|
652
752
|
<span className="unit-actions">
|
|
653
753
|
<span className="unit-state">
|
|
654
754
|
{decisions[unit.id] !== "pending"
|
|
655
|
-
? decisions[unit.id]
|
|
755
|
+
? t(STATE[decisions[unit.id]]).toLowerCase()
|
|
656
756
|
: ""}
|
|
657
757
|
</span>
|
|
658
758
|
{decisions[unit.id] === "pending" &&
|
|
@@ -662,15 +762,19 @@ export function ReviewApp({
|
|
|
662
762
|
className={kind}
|
|
663
763
|
key={kind}
|
|
664
764
|
title={actionLock(kind) || undefined}
|
|
665
|
-
aria-describedby={
|
|
666
|
-
|
|
765
|
+
aria-describedby={
|
|
766
|
+
actionLock(kind) ? "review-lock" : undefined
|
|
767
|
+
}
|
|
768
|
+
disabled={
|
|
769
|
+
busy || !latest || Boolean(actionLock(kind))
|
|
770
|
+
}
|
|
667
771
|
onClick={() =>
|
|
668
772
|
kind === "comment"
|
|
669
773
|
? beginComposer([unit.id])
|
|
670
774
|
: void decide(kind, [unit.id])
|
|
671
775
|
}
|
|
672
776
|
>
|
|
673
|
-
{kind
|
|
777
|
+
{t(ACTION[kind])}
|
|
674
778
|
</button>
|
|
675
779
|
),
|
|
676
780
|
)}
|
|
@@ -678,7 +782,7 @@ export function ReviewApp({
|
|
|
678
782
|
)}
|
|
679
783
|
</div>
|
|
680
784
|
{unit.kind === "binary" ? (
|
|
681
|
-
<p>Binary file changed
|
|
785
|
+
<p>{t("Binary file changed.")}</p>
|
|
682
786
|
) : (
|
|
683
787
|
<pre className="source-code">
|
|
684
788
|
{wordDiff(unit.old_text, unit.new_text).map((part, i) =>
|
|
@@ -693,7 +797,9 @@ export function ReviewApp({
|
|
|
693
797
|
</pre>
|
|
694
798
|
)}
|
|
695
799
|
{unit.truncated && (
|
|
696
|
-
<p className="muted">
|
|
800
|
+
<p className="muted">
|
|
801
|
+
{t("Long change: the source is shortened.")}
|
|
802
|
+
</p>
|
|
697
803
|
)}
|
|
698
804
|
</div>
|
|
699
805
|
))}
|
|
@@ -706,21 +812,25 @@ export function ReviewApp({
|
|
|
706
812
|
return (
|
|
707
813
|
<>
|
|
708
814
|
<header className="bar">
|
|
709
|
-
<a className="ghost overview-link" href="/">
|
|
710
|
-
← All reviews
|
|
815
|
+
<a className="ghost overview-link" href={withLanguage("/")}>
|
|
816
|
+
← {t("All reviews")}
|
|
711
817
|
</a>
|
|
712
818
|
<div className="identity">
|
|
713
819
|
<h1>{state?.title ?? "LaTeX Review"}</h1>
|
|
714
820
|
<span className="muted">
|
|
715
821
|
{detail
|
|
716
|
-
?
|
|
822
|
+
? t("Round {round}", { round: detail.round }) +
|
|
823
|
+
(detail.round !== state?.latest ? t(" (earlier round)") : "")
|
|
717
824
|
: ""}
|
|
718
825
|
</span>
|
|
719
826
|
</div>
|
|
720
827
|
{count.total > 0 && (
|
|
721
828
|
<div className="meter">
|
|
722
829
|
<span>
|
|
723
|
-
{
|
|
830
|
+
{t("{decided} of {total} decided", {
|
|
831
|
+
decided: count.decided,
|
|
832
|
+
total: count.total,
|
|
833
|
+
})}
|
|
724
834
|
</span>
|
|
725
835
|
</div>
|
|
726
836
|
)}
|
|
@@ -731,64 +841,169 @@ export function ReviewApp({
|
|
|
731
841
|
{count.pending > 1 && (
|
|
732
842
|
<button
|
|
733
843
|
className="ghost accept accept-all"
|
|
734
|
-
disabled={
|
|
735
|
-
|
|
844
|
+
disabled={
|
|
845
|
+
busy ||
|
|
846
|
+
!latest ||
|
|
847
|
+
Boolean(composer) ||
|
|
848
|
+
Boolean(state?.decisionLock)
|
|
849
|
+
}
|
|
850
|
+
title={
|
|
851
|
+
state?.decisionLock ||
|
|
852
|
+
tn(
|
|
853
|
+
count.pending,
|
|
854
|
+
"Accept the {n} undecided change in this round. Decided and commented changes stay as they are. Undo reverses the whole batch.",
|
|
855
|
+
"Accept the {n} undecided changes in this round. Decided and commented changes stay as they are. Undo reverses the whole batch.",
|
|
856
|
+
)
|
|
857
|
+
}
|
|
736
858
|
aria-describedby={state?.decisionLock ? "review-lock" : undefined}
|
|
737
859
|
onClick={() => void decide("accept", undecided)}
|
|
738
860
|
>
|
|
739
|
-
Accept all ({count.pending})
|
|
861
|
+
{t("Accept all ({n})", { n: count.pending })}
|
|
740
862
|
</button>
|
|
741
863
|
)}
|
|
742
864
|
{count.pending > 1 && (
|
|
743
865
|
<button
|
|
744
866
|
className="ghost reject reject-all"
|
|
745
|
-
disabled={
|
|
746
|
-
|
|
867
|
+
disabled={
|
|
868
|
+
busy ||
|
|
869
|
+
!latest ||
|
|
870
|
+
Boolean(composer) ||
|
|
871
|
+
Boolean(state?.decisionLock)
|
|
872
|
+
}
|
|
873
|
+
title={
|
|
874
|
+
state?.decisionLock ||
|
|
875
|
+
tn(
|
|
876
|
+
count.pending,
|
|
877
|
+
"Reject the {n} undecided change in this round. Decided and commented changes stay as they are. Undo reverses the whole batch.",
|
|
878
|
+
"Reject the {n} undecided changes in this round. Decided and commented changes stay as they are. Undo reverses the whole batch.",
|
|
879
|
+
)
|
|
880
|
+
}
|
|
747
881
|
aria-describedby={state?.decisionLock ? "review-lock" : undefined}
|
|
748
882
|
onClick={() => void decide("reject", undecided)}
|
|
749
883
|
>
|
|
750
|
-
Reject all ({count.pending})
|
|
884
|
+
{t("Reject all ({n})", { n: count.pending })}
|
|
751
885
|
</button>
|
|
752
886
|
)}
|
|
753
|
-
{
|
|
887
|
+
{/* "All decided" would repeat the meter's "N of N decided". */}
|
|
888
|
+
{(!online ||
|
|
889
|
+
state?.closed ||
|
|
890
|
+
!["reviewing", "done"].includes(state?.state ?? "")) && (
|
|
754
891
|
<span
|
|
755
892
|
className="status"
|
|
756
893
|
role="status"
|
|
757
894
|
data-state={!online ? "offline" : state?.state}
|
|
758
895
|
>
|
|
759
896
|
{!online
|
|
760
|
-
? "Reconnecting…"
|
|
897
|
+
? t("Reconnecting…")
|
|
761
898
|
: state?.closed
|
|
762
|
-
? "Finished"
|
|
899
|
+
? t("Finished")
|
|
763
900
|
: state?.state === "building"
|
|
764
901
|
? jobText(state.job)
|
|
765
|
-
: STATUS[state?.state ?? ""]}
|
|
902
|
+
: t(STATUS[state?.state ?? ""] ?? "")}
|
|
766
903
|
</span>
|
|
767
904
|
)}
|
|
905
|
+
{/* History is a toolbar action; its own row appears only while
|
|
906
|
+
browsing earlier decisions. */}
|
|
907
|
+
{detail && !history && (
|
|
908
|
+
<button
|
|
909
|
+
className="ghost history"
|
|
910
|
+
title={t("Earlier decisions and rounds")}
|
|
911
|
+
onClick={() => {
|
|
912
|
+
setHistory(true);
|
|
913
|
+
if (current < 0 && stops.length) setCurrent(0);
|
|
914
|
+
}}
|
|
915
|
+
>
|
|
916
|
+
{t("History")}
|
|
917
|
+
</button>
|
|
918
|
+
)}
|
|
768
919
|
<button
|
|
769
920
|
className="ghost round"
|
|
770
|
-
aria-label="Keyboard shortcuts"
|
|
921
|
+
aria-label={t("Keyboard shortcuts")}
|
|
771
922
|
onClick={() => setHelp(true)}
|
|
772
923
|
>
|
|
773
924
|
?
|
|
774
925
|
</button>
|
|
775
926
|
</div>
|
|
776
927
|
</header>
|
|
928
|
+
{detail && history && (
|
|
929
|
+
<nav className="review-mode" aria-label={t("Review view")}>
|
|
930
|
+
<label>
|
|
931
|
+
{t("History")}{" "}
|
|
932
|
+
<select
|
|
933
|
+
aria-label={t("Review round")}
|
|
934
|
+
value={detail.round}
|
|
935
|
+
onChange={(e) => {
|
|
936
|
+
location.href = withLanguage(
|
|
937
|
+
`?task=${encodeURIComponent(task)}&round=${e.target.value}&history=1`,
|
|
938
|
+
);
|
|
939
|
+
}}
|
|
940
|
+
>
|
|
941
|
+
{Array.from(
|
|
942
|
+
{ length: state?.latest || detail.round },
|
|
943
|
+
(_, i) => i + 1,
|
|
944
|
+
)
|
|
945
|
+
.reverse()
|
|
946
|
+
.map((round) => (
|
|
947
|
+
<option key={round} value={round}>
|
|
948
|
+
{t("Round {round}", { round })}
|
|
949
|
+
{round === state?.latest ? t(" (latest)") : ""}
|
|
950
|
+
</option>
|
|
951
|
+
))}
|
|
952
|
+
</select>
|
|
953
|
+
</label>
|
|
954
|
+
{state?.undo && (
|
|
955
|
+
<button
|
|
956
|
+
className="ghost undo"
|
|
957
|
+
disabled={!latest || busy || Boolean(state.decisionLock)}
|
|
958
|
+
title={
|
|
959
|
+
state.decisionLock ||
|
|
960
|
+
t("Undo {action} · {changes}", {
|
|
961
|
+
action: t(ACTION[state.undo.kind] ?? state.undo.kind).toLowerCase(),
|
|
962
|
+
changes: tn(
|
|
963
|
+
undoChanges.length,
|
|
964
|
+
"Change {list}",
|
|
965
|
+
"Changes {list}",
|
|
966
|
+
{ list: undoChanges.join(", ") },
|
|
967
|
+
),
|
|
968
|
+
})
|
|
969
|
+
}
|
|
970
|
+
onClick={() => void undo()}
|
|
971
|
+
>
|
|
972
|
+
{t("Undo last decision")}
|
|
973
|
+
</button>
|
|
974
|
+
)}
|
|
975
|
+
<button
|
|
976
|
+
className="ghost"
|
|
977
|
+
onClick={() => {
|
|
978
|
+
if (pinned)
|
|
979
|
+
location.href = withLanguage(`?task=${encodeURIComponent(task)}`);
|
|
980
|
+
else {
|
|
981
|
+
setHistory(false);
|
|
982
|
+
setCurrent(nextPending(stops, decisions));
|
|
983
|
+
}
|
|
984
|
+
}}
|
|
985
|
+
>
|
|
986
|
+
{t("Back to review")}
|
|
987
|
+
</button>
|
|
988
|
+
</nav>
|
|
989
|
+
)}
|
|
777
990
|
<div className="review-body">
|
|
778
991
|
<main id="paper" ref={paper} tabIndex={-1} onScroll={onScroll}>
|
|
779
992
|
{(state?.commentLock || state?.decisionLock) && (
|
|
780
993
|
<div className="banner" id="review-lock" role="status">
|
|
781
|
-
{state.commentLock || state.decisionLock}
|
|
994
|
+
{t((state.commentLock || state.decisionLock)!)}
|
|
782
995
|
</div>
|
|
783
996
|
)}
|
|
784
997
|
{state?.closed && !state.commentLock && (
|
|
785
998
|
<div className="banner">
|
|
786
|
-
|
|
999
|
+
{t(
|
|
1000
|
+
"This review is finished. Accepted changes are in your manuscript.",
|
|
1001
|
+
)}
|
|
787
1002
|
</div>
|
|
788
1003
|
)}
|
|
789
1004
|
{state?.state === "build_failed" && (
|
|
790
1005
|
<div className="banner">
|
|
791
|
-
<strong>The latest publish failed: </strong>
|
|
1006
|
+
<strong>{t("The latest publish failed: ")}</strong>
|
|
792
1007
|
{state.job?.error}
|
|
793
1008
|
{state.job?.details && (
|
|
794
1009
|
<pre className="details">{state.job.details.join("\n")}</pre>
|
|
@@ -797,42 +1012,70 @@ export function ReviewApp({
|
|
|
797
1012
|
)}
|
|
798
1013
|
{state?.state === "building" && detail && !state.decisionLock && (
|
|
799
1014
|
<div className="banner">
|
|
800
|
-
|
|
801
|
-
|
|
1015
|
+
{t(
|
|
1016
|
+
"The agent is publishing a new round. You can keep reviewing; the new round will include your decisions.",
|
|
1017
|
+
)}
|
|
802
1018
|
</div>
|
|
803
1019
|
)}
|
|
804
1020
|
{detail && state?.latest && detail.round !== state.latest && (
|
|
805
1021
|
<div className="banner">
|
|
806
|
-
You are viewing round {detail.round}
|
|
807
|
-
<a href={`?task=${encodeURIComponent(task)}`}>
|
|
808
|
-
Go to round {state.latest}
|
|
1022
|
+
{t("You are viewing round {round}.", { round: detail.round })}{" "}
|
|
1023
|
+
<a href={withLanguage(`?task=${encodeURIComponent(task)}`)}>
|
|
1024
|
+
{t("Go to round {round} →", { round: state.latest })}
|
|
809
1025
|
</a>
|
|
810
1026
|
</div>
|
|
811
1027
|
)}
|
|
812
1028
|
{detail?.warnings.length ? (
|
|
813
1029
|
<div className="banner">
|
|
814
1030
|
<details>
|
|
815
|
-
<summary>
|
|
1031
|
+
<summary>
|
|
1032
|
+
{tn(
|
|
1033
|
+
detail.warnings.length,
|
|
1034
|
+
"{n} new LaTeX warning",
|
|
1035
|
+
"{n} new LaTeX warnings",
|
|
1036
|
+
)}
|
|
1037
|
+
</summary>
|
|
816
1038
|
<pre className="details">{detail.warnings.join("\n")}</pre>
|
|
817
1039
|
</details>
|
|
818
1040
|
</div>
|
|
819
1041
|
) : null}
|
|
820
|
-
{detail &&
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
1042
|
+
{detail && !history && !count.pending && (
|
|
1043
|
+
<div className="review-complete" role="status">
|
|
1044
|
+
<strong>
|
|
1045
|
+
{t(
|
|
1046
|
+
state?.workspace?.status === "waiting_input"
|
|
1047
|
+
? "Agent needs your input"
|
|
1048
|
+
: state?.workspace?.status === "queued"
|
|
1049
|
+
? "Feedback queued"
|
|
1050
|
+
: state?.workspace?.status === "running"
|
|
1051
|
+
? "Feedback sent · Agent is working"
|
|
1052
|
+
: state?.workspace?.status === "building" ||
|
|
1053
|
+
state?.state === "building"
|
|
1054
|
+
? "Preparing the next review…"
|
|
1055
|
+
: "All changes reviewed",
|
|
1056
|
+
)}
|
|
1057
|
+
</strong>
|
|
1058
|
+
<span>
|
|
1059
|
+
{t(
|
|
1060
|
+
state?.workspace?.status === "waiting_input"
|
|
1061
|
+
? "Open Chat to answer the agent's question."
|
|
1062
|
+
: ["running", "queued", "building"].includes(
|
|
1063
|
+
state?.workspace?.status || "",
|
|
1064
|
+
) || state?.state === "building"
|
|
1065
|
+
? "New changes will appear here when ready."
|
|
1066
|
+
: state?.workspace?.comments
|
|
1067
|
+
? "Your comments are saved. Send feedback so the agent can revise."
|
|
1068
|
+
: state?.workspace?.accepted
|
|
1069
|
+
? "Accepted changes are in your manuscript."
|
|
1070
|
+
: state?.workspace?.unsent && !count.accepted
|
|
1071
|
+
? "Your decisions are saved. Send them to the agent to continue."
|
|
1072
|
+
: count.commented && !state?.workspace
|
|
1073
|
+
? "Your comments are saved for the agent."
|
|
1074
|
+
: "There are no new changes to review. Previous decisions are available in History.",
|
|
1075
|
+
)}
|
|
1076
|
+
</span>
|
|
1077
|
+
</div>
|
|
1078
|
+
)}
|
|
836
1079
|
<div className="document">
|
|
837
1080
|
{detail ? (
|
|
838
1081
|
<>
|
|
@@ -849,14 +1092,14 @@ export function ReviewApp({
|
|
|
849
1092
|
<PdfStrip
|
|
850
1093
|
image={block.image}
|
|
851
1094
|
base={detail.files}
|
|
852
|
-
label="Unchanged text"
|
|
1095
|
+
label={t("Unchanged text")}
|
|
853
1096
|
/>
|
|
854
1097
|
</div>
|
|
855
1098
|
) : block.kind === "gap" ? (
|
|
856
1099
|
<div
|
|
857
1100
|
className="fold"
|
|
858
1101
|
key={`gap-${b}`}
|
|
859
|
-
title="Unchanged text is folded"
|
|
1102
|
+
title={t("Unchanged text is folded")}
|
|
860
1103
|
>
|
|
861
1104
|
⋯
|
|
862
1105
|
</div>
|
|
@@ -870,13 +1113,13 @@ export function ReviewApp({
|
|
|
870
1113
|
{detail.loose.length > 0 && (
|
|
871
1114
|
<section className="part">
|
|
872
1115
|
<h2 className="part-title">
|
|
873
|
-
{detail.sections.length ? "Other changes" : "Changes"}
|
|
1116
|
+
{detail.sections.length ? t("Other changes") : t("Changes")}
|
|
874
1117
|
</h2>
|
|
875
1118
|
{detail.loose.map((block) => change(block, index++, false))}
|
|
876
1119
|
</section>
|
|
877
1120
|
)}
|
|
878
1121
|
{!stops.length && (
|
|
879
|
-
<p className="empty">This round has no changes
|
|
1122
|
+
<p className="empty">{t("This round has no changes.")}</p>
|
|
880
1123
|
)}
|
|
881
1124
|
</>
|
|
882
1125
|
) : error ? (
|
|
@@ -889,9 +1132,9 @@ export function ReviewApp({
|
|
|
889
1132
|
<p>
|
|
890
1133
|
{state?.state === "building"
|
|
891
1134
|
? jobText(state.job)
|
|
892
|
-
: "Waiting for the agent to publish its changes."}
|
|
1135
|
+
: t("Waiting for the agent to publish its changes.")}
|
|
893
1136
|
</p>
|
|
894
|
-
<p className="muted">This page updates by itself
|
|
1137
|
+
<p className="muted">{t("This page updates by itself.")}</p>
|
|
895
1138
|
</div>
|
|
896
1139
|
)}
|
|
897
1140
|
</div>
|
|
@@ -899,6 +1142,7 @@ export function ReviewApp({
|
|
|
899
1142
|
<ChangeMap
|
|
900
1143
|
scrollRoot={paper}
|
|
901
1144
|
stops={stops}
|
|
1145
|
+
showReviewed={history}
|
|
902
1146
|
locations={locations}
|
|
903
1147
|
decisions={decisions}
|
|
904
1148
|
current={current}
|
|
@@ -915,7 +1159,7 @@ export function ReviewApp({
|
|
|
915
1159
|
}}
|
|
916
1160
|
>
|
|
917
1161
|
<label htmlFor="composer-text">
|
|
918
|
-
Comment on change {composer.stop + 1}
|
|
1162
|
+
{t("Comment on change {n}", { n: composer.stop + 1 })}
|
|
919
1163
|
</label>
|
|
920
1164
|
<textarea
|
|
921
1165
|
id="composer-text"
|
|
@@ -927,20 +1171,20 @@ export function ReviewApp({
|
|
|
927
1171
|
setDraft(e.target.value);
|
|
928
1172
|
store(composer.key, e.target.value);
|
|
929
1173
|
}}
|
|
930
|
-
placeholder="What should the agent change here?"
|
|
1174
|
+
placeholder={t("What should the agent change here?")}
|
|
931
1175
|
/>
|
|
932
1176
|
<div className="composer-row">
|
|
933
|
-
<span className="muted"
|
|
1177
|
+
<span className="muted">{t("⌘↵ send · Esc cancel")}</span>
|
|
934
1178
|
<button type="button" onClick={() => closeComposer()}>
|
|
935
|
-
Cancel
|
|
1179
|
+
{t("Cancel")}
|
|
936
1180
|
</button>
|
|
937
1181
|
<button
|
|
938
1182
|
type="submit"
|
|
939
|
-
title={state?.commentLock
|
|
1183
|
+
title={state?.commentLock ? t(state.commentLock) : undefined}
|
|
940
1184
|
aria-describedby={state?.commentLock ? "review-lock" : undefined}
|
|
941
1185
|
disabled={busy || !draft.trim() || Boolean(state?.commentLock)}
|
|
942
1186
|
>
|
|
943
|
-
Send
|
|
1187
|
+
{t("Send")}
|
|
944
1188
|
</button>
|
|
945
1189
|
</div>
|
|
946
1190
|
</form>
|
|
@@ -957,7 +1201,7 @@ export function ReviewApp({
|
|
|
957
1201
|
aria-labelledby="help-title"
|
|
958
1202
|
onClick={(e) => e.stopPropagation()}
|
|
959
1203
|
>
|
|
960
|
-
<h2 id="help-title">Keyboard shortcuts</h2>
|
|
1204
|
+
<h2 id="help-title">{t("Keyboard shortcuts")}</h2>
|
|
961
1205
|
<dl>
|
|
962
1206
|
{[
|
|
963
1207
|
["A", "Accept"],
|
|
@@ -971,18 +1215,18 @@ export function ReviewApp({
|
|
|
971
1215
|
].map(([key, label]) => (
|
|
972
1216
|
<Fragment key={key}>
|
|
973
1217
|
<dt>
|
|
974
|
-
<kbd>{key}</kbd>
|
|
1218
|
+
<kbd>{t(key)}</kbd>
|
|
975
1219
|
</dt>
|
|
976
|
-
<dd>{label}</dd>
|
|
1220
|
+
<dd>{t(label)}</dd>
|
|
977
1221
|
</Fragment>
|
|
978
1222
|
))}
|
|
979
1223
|
</dl>
|
|
980
1224
|
<p className="muted">
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
1225
|
+
{t(
|
|
1226
|
+
"Select text directly in the PDF preview. Copy reference includes the selection and its file, line numbers and page. Review shortcuts pause while text is selected.",
|
|
1227
|
+
)}
|
|
984
1228
|
</p>
|
|
985
|
-
<button onClick={() => setHelp(false)}>Close</button>
|
|
1229
|
+
<button onClick={() => setHelp(false)}>{t("Close")}</button>
|
|
986
1230
|
</dialog>
|
|
987
1231
|
</div>
|
|
988
1232
|
)}
|