papilot 0.1.1 → 0.3.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 +16 -4
- package/README.zh-CN.md +15 -3
- 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-BkowXmKr.js +206 -0
- package/dist/assets/index-Dq3s-vhH.css +1 -0
- package/dist/assets/math-BTm2hvOz.js +263 -0
- package/dist/assets/math-DEcVZfaU.css +1 -0
- package/dist/assets/{pdf-view-DJo0X38w.js → pdf-view-BEn1EztO.js} +18 -18
- package/dist/index.html +11 -2
- package/examples/papilot-paper/preamble.tex +17 -11
- package/package.json +13 -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/commit.ts +23 -0
- package/src/conversation.ts +48 -0
- package/src/notes.ts +5 -0
- package/src/server/agents.ts +4 -3
- package/src/server/main.ts +51 -8
- package/src/server/manuscript.ts +91 -10
- package/src/server/review.ts +2 -1
- package/src/server/service-environment.ts +22 -0
- package/src/server/skills.ts +57 -0
- package/src/server/workspace.ts +186 -26
- package/src/shared.ts +17 -3
- package/vendor/latex-review/dist/web/assets/index-Bg-VqnPa.css +1 -0
- package/vendor/latex-review/dist/web/assets/index-xLeqYBJt.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 +399 -145
- package/vendor/latex-review/src/web/style.css +130 -0
- package/vendor/latex-review/src/web/zh-CN.ts +188 -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,64 @@ 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
|
-
<span className="badge">
|
|
581
|
-
{status[0].toUpperCase() + status.slice(1)}
|
|
582
|
-
</span>
|
|
670
|
+
<span className="badge">{t(STATE[status] ?? status)}</span>
|
|
583
671
|
)}
|
|
584
672
|
<button
|
|
585
673
|
className="link copy-reference"
|
|
586
|
-
title=
|
|
674
|
+
title={t(
|
|
675
|
+
"Copy selected text with this change’s file, line numbers and PDF page",
|
|
676
|
+
)}
|
|
587
677
|
onPointerDown={(e) => e.preventDefault()}
|
|
588
678
|
onClick={() => void copy(index)}
|
|
589
679
|
>
|
|
590
|
-
Copy reference
|
|
680
|
+
{t("Copy reference")}
|
|
591
681
|
</button>
|
|
592
682
|
<button
|
|
593
683
|
className={`link ${source ? "active" : ""}`}
|
|
594
|
-
title="Show the LaTeX source (S)"
|
|
684
|
+
title={t("Show the LaTeX source (S)")}
|
|
595
685
|
onClick={() => toggleSource(index)}
|
|
596
686
|
>
|
|
597
687
|
{block.units.length > 1
|
|
598
|
-
?
|
|
688
|
+
? t("{n} edits · LaTeX", { n: block.units.length })
|
|
599
689
|
: "LaTeX"}
|
|
600
690
|
</button>
|
|
601
691
|
</header>
|
|
@@ -606,7 +696,9 @@ export function ReviewApp({
|
|
|
606
696
|
.map((h, i) => (
|
|
607
697
|
<p key={i}>
|
|
608
698
|
<strong>
|
|
609
|
-
Revised after your comment in round {
|
|
699
|
+
{t("Revised after your comment in round {round}: ", {
|
|
700
|
+
round: h.round,
|
|
701
|
+
})}
|
|
610
702
|
</strong>
|
|
611
703
|
“{h.comment}”
|
|
612
704
|
</p>
|
|
@@ -620,17 +712,19 @@ export function ReviewApp({
|
|
|
620
712
|
{block.after.length > 0 && images(block.after, "after")}
|
|
621
713
|
{block.identical && (
|
|
622
714
|
<p className="caption">
|
|
623
|
-
No visible change in the PDF; see the LaTeX source.
|
|
715
|
+
{t("No visible change in the PDF; see the LaTeX source.")}
|
|
624
716
|
</p>
|
|
625
717
|
)}
|
|
626
718
|
{block.reflow && (
|
|
627
|
-
<p className="caption">
|
|
719
|
+
<p className="caption">
|
|
720
|
+
{t("Continued: only line breaks moved here.")}
|
|
721
|
+
</p>
|
|
628
722
|
)}
|
|
629
723
|
{remarks.length > 0 && (
|
|
630
724
|
<div className="remark">
|
|
631
725
|
{remarks.map((r, i) => (
|
|
632
726
|
<p key={i}>
|
|
633
|
-
<strong>Your comment: </strong>
|
|
727
|
+
<strong>{t("Your comment: ")}</strong>
|
|
634
728
|
{r}
|
|
635
729
|
</p>
|
|
636
730
|
))}
|
|
@@ -646,13 +740,17 @@ export function ReviewApp({
|
|
|
646
740
|
>
|
|
647
741
|
<div className="source-title">
|
|
648
742
|
<span>
|
|
649
|
-
{unit.file} ·
|
|
743
|
+
{unit.file} ·{" "}
|
|
744
|
+
{t("lines {start}–{end}", {
|
|
745
|
+
start: unit.new_start,
|
|
746
|
+
end: unit.new_end,
|
|
747
|
+
})}
|
|
650
748
|
</span>
|
|
651
749
|
{known.length > 1 && (
|
|
652
750
|
<span className="unit-actions">
|
|
653
751
|
<span className="unit-state">
|
|
654
752
|
{decisions[unit.id] !== "pending"
|
|
655
|
-
? decisions[unit.id]
|
|
753
|
+
? t(STATE[decisions[unit.id]]).toLowerCase()
|
|
656
754
|
: ""}
|
|
657
755
|
</span>
|
|
658
756
|
{decisions[unit.id] === "pending" &&
|
|
@@ -662,15 +760,19 @@ export function ReviewApp({
|
|
|
662
760
|
className={kind}
|
|
663
761
|
key={kind}
|
|
664
762
|
title={actionLock(kind) || undefined}
|
|
665
|
-
aria-describedby={
|
|
666
|
-
|
|
763
|
+
aria-describedby={
|
|
764
|
+
actionLock(kind) ? "review-lock" : undefined
|
|
765
|
+
}
|
|
766
|
+
disabled={
|
|
767
|
+
busy || !latest || Boolean(actionLock(kind))
|
|
768
|
+
}
|
|
667
769
|
onClick={() =>
|
|
668
770
|
kind === "comment"
|
|
669
771
|
? beginComposer([unit.id])
|
|
670
772
|
: void decide(kind, [unit.id])
|
|
671
773
|
}
|
|
672
774
|
>
|
|
673
|
-
{kind
|
|
775
|
+
{t(ACTION[kind])}
|
|
674
776
|
</button>
|
|
675
777
|
),
|
|
676
778
|
)}
|
|
@@ -678,7 +780,7 @@ export function ReviewApp({
|
|
|
678
780
|
)}
|
|
679
781
|
</div>
|
|
680
782
|
{unit.kind === "binary" ? (
|
|
681
|
-
<p>Binary file changed
|
|
783
|
+
<p>{t("Binary file changed.")}</p>
|
|
682
784
|
) : (
|
|
683
785
|
<pre className="source-code">
|
|
684
786
|
{wordDiff(unit.old_text, unit.new_text).map((part, i) =>
|
|
@@ -693,7 +795,9 @@ export function ReviewApp({
|
|
|
693
795
|
</pre>
|
|
694
796
|
)}
|
|
695
797
|
{unit.truncated && (
|
|
696
|
-
<p className="muted">
|
|
798
|
+
<p className="muted">
|
|
799
|
+
{t("Long change: the source is shortened.")}
|
|
800
|
+
</p>
|
|
697
801
|
)}
|
|
698
802
|
</div>
|
|
699
803
|
))}
|
|
@@ -706,21 +810,25 @@ export function ReviewApp({
|
|
|
706
810
|
return (
|
|
707
811
|
<>
|
|
708
812
|
<header className="bar">
|
|
709
|
-
<a className="ghost overview-link" href="/">
|
|
710
|
-
← All reviews
|
|
813
|
+
<a className="ghost overview-link" href={withLanguage("/")}>
|
|
814
|
+
← {t("All reviews")}
|
|
711
815
|
</a>
|
|
712
816
|
<div className="identity">
|
|
713
817
|
<h1>{state?.title ?? "LaTeX Review"}</h1>
|
|
714
818
|
<span className="muted">
|
|
715
819
|
{detail
|
|
716
|
-
?
|
|
820
|
+
? t("Round {round}", { round: detail.round }) +
|
|
821
|
+
(detail.round !== state?.latest ? t(" (earlier round)") : "")
|
|
717
822
|
: ""}
|
|
718
823
|
</span>
|
|
719
824
|
</div>
|
|
720
825
|
{count.total > 0 && (
|
|
721
826
|
<div className="meter">
|
|
722
827
|
<span>
|
|
723
|
-
{
|
|
828
|
+
{t("{decided} of {total} decided", {
|
|
829
|
+
decided: count.decided,
|
|
830
|
+
total: count.total,
|
|
831
|
+
})}
|
|
724
832
|
</span>
|
|
725
833
|
</div>
|
|
726
834
|
)}
|
|
@@ -731,64 +839,177 @@ export function ReviewApp({
|
|
|
731
839
|
{count.pending > 1 && (
|
|
732
840
|
<button
|
|
733
841
|
className="ghost accept accept-all"
|
|
734
|
-
disabled={
|
|
735
|
-
|
|
842
|
+
disabled={
|
|
843
|
+
busy ||
|
|
844
|
+
!latest ||
|
|
845
|
+
Boolean(composer) ||
|
|
846
|
+
Boolean(state?.decisionLock)
|
|
847
|
+
}
|
|
848
|
+
title={
|
|
849
|
+
state?.decisionLock ||
|
|
850
|
+
tn(
|
|
851
|
+
count.pending,
|
|
852
|
+
"Accept the {n} undecided change in this round. Decided and commented changes stay as they are. Undo reverses the whole batch.",
|
|
853
|
+
"Accept the {n} undecided changes in this round. Decided and commented changes stay as they are. Undo reverses the whole batch.",
|
|
854
|
+
)
|
|
855
|
+
}
|
|
856
|
+
aria-label={t("Accept all ({n})", { n: count.pending })}
|
|
736
857
|
aria-describedby={state?.decisionLock ? "review-lock" : undefined}
|
|
737
858
|
onClick={() => void decide("accept", undecided)}
|
|
738
859
|
>
|
|
739
|
-
Accept all
|
|
860
|
+
<span className="batch-label">{t("Accept all")}</span>
|
|
861
|
+
<span className="batch-count">{count.pending}</span>
|
|
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
|
+
}
|
|
881
|
+
aria-label={t("Reject all ({n})", { n: count.pending })}
|
|
747
882
|
aria-describedby={state?.decisionLock ? "review-lock" : undefined}
|
|
748
883
|
onClick={() => void decide("reject", undecided)}
|
|
749
884
|
>
|
|
750
|
-
Reject all
|
|
885
|
+
<span className="batch-label">{t("Reject all")}</span>
|
|
886
|
+
<span className="batch-count">{count.pending}</span>
|
|
751
887
|
</button>
|
|
752
888
|
)}
|
|
753
|
-
{
|
|
889
|
+
{/* "All decided" would repeat the meter's "N of N decided". */}
|
|
890
|
+
{(!online ||
|
|
891
|
+
state?.closed ||
|
|
892
|
+
!["reviewing", "done"].includes(state?.state ?? "")) && (
|
|
754
893
|
<span
|
|
755
894
|
className="status"
|
|
756
895
|
role="status"
|
|
757
896
|
data-state={!online ? "offline" : state?.state}
|
|
758
897
|
>
|
|
759
898
|
{!online
|
|
760
|
-
? "Reconnecting…"
|
|
899
|
+
? t("Reconnecting…")
|
|
761
900
|
: state?.closed
|
|
762
|
-
? "Finished"
|
|
901
|
+
? t("Finished")
|
|
763
902
|
: state?.state === "building"
|
|
764
903
|
? jobText(state.job)
|
|
765
|
-
: STATUS[state?.state ?? ""]}
|
|
904
|
+
: t(STATUS[state?.state ?? ""] ?? "")}
|
|
766
905
|
</span>
|
|
767
906
|
)}
|
|
907
|
+
{/* History is a toolbar action; its own row appears only while
|
|
908
|
+
browsing earlier decisions. */}
|
|
909
|
+
{detail && !history && (
|
|
910
|
+
<button
|
|
911
|
+
className="ghost history"
|
|
912
|
+
title={t("Earlier decisions and rounds")}
|
|
913
|
+
onClick={() => {
|
|
914
|
+
setHistory(true);
|
|
915
|
+
if (current < 0 && stops.length) setCurrent(0);
|
|
916
|
+
}}
|
|
917
|
+
>
|
|
918
|
+
{t("History")}
|
|
919
|
+
</button>
|
|
920
|
+
)}
|
|
768
921
|
<button
|
|
769
922
|
className="ghost round"
|
|
770
|
-
aria-label="Keyboard shortcuts"
|
|
923
|
+
aria-label={t("Keyboard shortcuts")}
|
|
771
924
|
onClick={() => setHelp(true)}
|
|
772
925
|
>
|
|
773
926
|
?
|
|
774
927
|
</button>
|
|
775
928
|
</div>
|
|
776
929
|
</header>
|
|
930
|
+
{detail && history && (
|
|
931
|
+
<nav className="review-mode" aria-label={t("Review view")}>
|
|
932
|
+
<label>
|
|
933
|
+
{t("History")}{" "}
|
|
934
|
+
<select
|
|
935
|
+
aria-label={t("Review round")}
|
|
936
|
+
value={detail.round}
|
|
937
|
+
onChange={(e) => {
|
|
938
|
+
location.href = withLanguage(
|
|
939
|
+
`?task=${encodeURIComponent(task)}&round=${e.target.value}&history=1`,
|
|
940
|
+
);
|
|
941
|
+
}}
|
|
942
|
+
>
|
|
943
|
+
{Array.from(
|
|
944
|
+
{ length: state?.latest || detail.round },
|
|
945
|
+
(_, i) => i + 1,
|
|
946
|
+
)
|
|
947
|
+
.reverse()
|
|
948
|
+
.map((round) => (
|
|
949
|
+
<option key={round} value={round}>
|
|
950
|
+
{t("Round {round}", { round })}
|
|
951
|
+
{round === state?.latest ? t(" (latest)") : ""}
|
|
952
|
+
</option>
|
|
953
|
+
))}
|
|
954
|
+
</select>
|
|
955
|
+
</label>
|
|
956
|
+
{state?.undo && (
|
|
957
|
+
<button
|
|
958
|
+
className="ghost undo"
|
|
959
|
+
disabled={!latest || busy || Boolean(state.decisionLock)}
|
|
960
|
+
title={
|
|
961
|
+
state.decisionLock ||
|
|
962
|
+
t("Undo {action} · {changes}", {
|
|
963
|
+
action: t(
|
|
964
|
+
ACTION[state.undo.kind] ?? state.undo.kind,
|
|
965
|
+
).toLowerCase(),
|
|
966
|
+
changes: tn(
|
|
967
|
+
undoChanges.length,
|
|
968
|
+
"Change {list}",
|
|
969
|
+
"Changes {list}",
|
|
970
|
+
{ list: undoChanges.join(", ") },
|
|
971
|
+
),
|
|
972
|
+
})
|
|
973
|
+
}
|
|
974
|
+
onClick={() => void undo()}
|
|
975
|
+
>
|
|
976
|
+
{t("Undo last decision")}
|
|
977
|
+
</button>
|
|
978
|
+
)}
|
|
979
|
+
<button
|
|
980
|
+
className="ghost"
|
|
981
|
+
onClick={() => {
|
|
982
|
+
if (pinned)
|
|
983
|
+
location.href = withLanguage(
|
|
984
|
+
`?task=${encodeURIComponent(task)}`,
|
|
985
|
+
);
|
|
986
|
+
else {
|
|
987
|
+
setHistory(false);
|
|
988
|
+
setCurrent(nextPending(stops, decisions));
|
|
989
|
+
}
|
|
990
|
+
}}
|
|
991
|
+
>
|
|
992
|
+
{t("Back to review")}
|
|
993
|
+
</button>
|
|
994
|
+
</nav>
|
|
995
|
+
)}
|
|
777
996
|
<div className="review-body">
|
|
778
997
|
<main id="paper" ref={paper} tabIndex={-1} onScroll={onScroll}>
|
|
779
998
|
{(state?.commentLock || state?.decisionLock) && (
|
|
780
999
|
<div className="banner" id="review-lock" role="status">
|
|
781
|
-
{state.commentLock || state.decisionLock}
|
|
1000
|
+
{t((state.commentLock || state.decisionLock)!)}
|
|
782
1001
|
</div>
|
|
783
1002
|
)}
|
|
784
1003
|
{state?.closed && !state.commentLock && (
|
|
785
1004
|
<div className="banner">
|
|
786
|
-
|
|
1005
|
+
{t(
|
|
1006
|
+
"This review is finished. Accepted changes are in your manuscript.",
|
|
1007
|
+
)}
|
|
787
1008
|
</div>
|
|
788
1009
|
)}
|
|
789
1010
|
{state?.state === "build_failed" && (
|
|
790
1011
|
<div className="banner">
|
|
791
|
-
<strong>The latest publish failed: </strong>
|
|
1012
|
+
<strong>{t("The latest publish failed: ")}</strong>
|
|
792
1013
|
{state.job?.error}
|
|
793
1014
|
{state.job?.details && (
|
|
794
1015
|
<pre className="details">{state.job.details.join("\n")}</pre>
|
|
@@ -797,42 +1018,72 @@ export function ReviewApp({
|
|
|
797
1018
|
)}
|
|
798
1019
|
{state?.state === "building" && detail && !state.decisionLock && (
|
|
799
1020
|
<div className="banner">
|
|
800
|
-
|
|
801
|
-
|
|
1021
|
+
{t(
|
|
1022
|
+
"The agent is publishing a new round. You can keep reviewing; the new round will include your decisions.",
|
|
1023
|
+
)}
|
|
802
1024
|
</div>
|
|
803
1025
|
)}
|
|
804
1026
|
{detail && state?.latest && detail.round !== state.latest && (
|
|
805
1027
|
<div className="banner">
|
|
806
|
-
You are viewing round {detail.round}
|
|
807
|
-
<a href={`?task=${encodeURIComponent(task)}`}>
|
|
808
|
-
Go to round {state.latest}
|
|
1028
|
+
{t("You are viewing round {round}.", { round: detail.round })}{" "}
|
|
1029
|
+
<a href={withLanguage(`?task=${encodeURIComponent(task)}`)}>
|
|
1030
|
+
{t("Go to round {round} →", { round: state.latest })}
|
|
809
1031
|
</a>
|
|
810
1032
|
</div>
|
|
811
1033
|
)}
|
|
812
1034
|
{detail?.warnings.length ? (
|
|
813
1035
|
<div className="banner">
|
|
814
1036
|
<details>
|
|
815
|
-
<summary>
|
|
1037
|
+
<summary>
|
|
1038
|
+
{tn(
|
|
1039
|
+
detail.warnings.length,
|
|
1040
|
+
"{n} new LaTeX warning",
|
|
1041
|
+
"{n} new LaTeX warnings",
|
|
1042
|
+
)}
|
|
1043
|
+
</summary>
|
|
816
1044
|
<pre className="details">{detail.warnings.join("\n")}</pre>
|
|
817
1045
|
</details>
|
|
818
1046
|
</div>
|
|
819
1047
|
) : null}
|
|
820
|
-
{detail &&
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
1048
|
+
{detail && !history && !count.pending && (
|
|
1049
|
+
<div className="review-complete" role="status">
|
|
1050
|
+
<strong>
|
|
1051
|
+
{t(
|
|
1052
|
+
state?.workspace?.status === "waiting_input"
|
|
1053
|
+
? "Agent needs your input"
|
|
1054
|
+
: state?.workspace?.status === "queued"
|
|
1055
|
+
? "Feedback queued"
|
|
1056
|
+
: state?.workspace?.status === "running"
|
|
1057
|
+
? "Feedback sent · Agent is working"
|
|
1058
|
+
: state?.workspace?.status === "building" ||
|
|
1059
|
+
state?.state === "building"
|
|
1060
|
+
? "Preparing the next review…"
|
|
1061
|
+
: "All changes reviewed",
|
|
1062
|
+
)}
|
|
1063
|
+
</strong>
|
|
1064
|
+
<span>
|
|
1065
|
+
{t(
|
|
1066
|
+
state?.workspace?.status === "waiting_input"
|
|
1067
|
+
? "Open Chat to answer the agent's question."
|
|
1068
|
+
: ["running", "queued", "building"].includes(
|
|
1069
|
+
state?.workspace?.status || "",
|
|
1070
|
+
) || state?.state === "building"
|
|
1071
|
+
? "New changes will appear here when ready."
|
|
1072
|
+
: state?.workspace?.comments
|
|
1073
|
+
? "Your comments are saved. Send feedback so the agent can revise."
|
|
1074
|
+
: state?.workspace?.accepted
|
|
1075
|
+
? "Accepted changes are in your manuscript."
|
|
1076
|
+
: state?.workspace?.unsent && !count.accepted
|
|
1077
|
+
? "Your decisions are saved. Send them to the agent to continue."
|
|
1078
|
+
: count.commented && !state?.workspace
|
|
1079
|
+
? "Your comments are saved for the agent."
|
|
1080
|
+
: "There are no new changes to review. Previous decisions are available in History.",
|
|
1081
|
+
)}
|
|
1082
|
+
</span>
|
|
1083
|
+
{/* A host such as Papilot puts its next step here, for example a commit. */}
|
|
1084
|
+
<div className="review-complete-actions" />
|
|
1085
|
+
</div>
|
|
1086
|
+
)}
|
|
836
1087
|
<div className="document">
|
|
837
1088
|
{detail ? (
|
|
838
1089
|
<>
|
|
@@ -849,14 +1100,14 @@ export function ReviewApp({
|
|
|
849
1100
|
<PdfStrip
|
|
850
1101
|
image={block.image}
|
|
851
1102
|
base={detail.files}
|
|
852
|
-
label="Unchanged text"
|
|
1103
|
+
label={t("Unchanged text")}
|
|
853
1104
|
/>
|
|
854
1105
|
</div>
|
|
855
1106
|
) : block.kind === "gap" ? (
|
|
856
1107
|
<div
|
|
857
1108
|
className="fold"
|
|
858
1109
|
key={`gap-${b}`}
|
|
859
|
-
title="Unchanged text is folded"
|
|
1110
|
+
title={t("Unchanged text is folded")}
|
|
860
1111
|
>
|
|
861
1112
|
⋯
|
|
862
1113
|
</div>
|
|
@@ -870,13 +1121,15 @@ export function ReviewApp({
|
|
|
870
1121
|
{detail.loose.length > 0 && (
|
|
871
1122
|
<section className="part">
|
|
872
1123
|
<h2 className="part-title">
|
|
873
|
-
{detail.sections.length
|
|
1124
|
+
{detail.sections.length
|
|
1125
|
+
? t("Other changes")
|
|
1126
|
+
: t("Changes")}
|
|
874
1127
|
</h2>
|
|
875
1128
|
{detail.loose.map((block) => change(block, index++, false))}
|
|
876
1129
|
</section>
|
|
877
1130
|
)}
|
|
878
1131
|
{!stops.length && (
|
|
879
|
-
<p className="empty">This round has no changes
|
|
1132
|
+
<p className="empty">{t("This round has no changes.")}</p>
|
|
880
1133
|
)}
|
|
881
1134
|
</>
|
|
882
1135
|
) : error ? (
|
|
@@ -889,9 +1142,9 @@ export function ReviewApp({
|
|
|
889
1142
|
<p>
|
|
890
1143
|
{state?.state === "building"
|
|
891
1144
|
? jobText(state.job)
|
|
892
|
-
: "Waiting for the agent to publish its changes."}
|
|
1145
|
+
: t("Waiting for the agent to publish its changes.")}
|
|
893
1146
|
</p>
|
|
894
|
-
<p className="muted">This page updates by itself
|
|
1147
|
+
<p className="muted">{t("This page updates by itself.")}</p>
|
|
895
1148
|
</div>
|
|
896
1149
|
)}
|
|
897
1150
|
</div>
|
|
@@ -899,6 +1152,7 @@ export function ReviewApp({
|
|
|
899
1152
|
<ChangeMap
|
|
900
1153
|
scrollRoot={paper}
|
|
901
1154
|
stops={stops}
|
|
1155
|
+
showReviewed={history}
|
|
902
1156
|
locations={locations}
|
|
903
1157
|
decisions={decisions}
|
|
904
1158
|
current={current}
|
|
@@ -915,7 +1169,7 @@ export function ReviewApp({
|
|
|
915
1169
|
}}
|
|
916
1170
|
>
|
|
917
1171
|
<label htmlFor="composer-text">
|
|
918
|
-
Comment on change {composer.stop + 1}
|
|
1172
|
+
{t("Comment on change {n}", { n: composer.stop + 1 })}
|
|
919
1173
|
</label>
|
|
920
1174
|
<textarea
|
|
921
1175
|
id="composer-text"
|
|
@@ -927,20 +1181,20 @@ export function ReviewApp({
|
|
|
927
1181
|
setDraft(e.target.value);
|
|
928
1182
|
store(composer.key, e.target.value);
|
|
929
1183
|
}}
|
|
930
|
-
placeholder="What should the agent change here?"
|
|
1184
|
+
placeholder={t("What should the agent change here?")}
|
|
931
1185
|
/>
|
|
932
1186
|
<div className="composer-row">
|
|
933
|
-
<span className="muted"
|
|
1187
|
+
<span className="muted">{t("⌘↵ send · Esc cancel")}</span>
|
|
934
1188
|
<button type="button" onClick={() => closeComposer()}>
|
|
935
|
-
Cancel
|
|
1189
|
+
{t("Cancel")}
|
|
936
1190
|
</button>
|
|
937
1191
|
<button
|
|
938
1192
|
type="submit"
|
|
939
|
-
title={state?.commentLock
|
|
1193
|
+
title={state?.commentLock ? t(state.commentLock) : undefined}
|
|
940
1194
|
aria-describedby={state?.commentLock ? "review-lock" : undefined}
|
|
941
1195
|
disabled={busy || !draft.trim() || Boolean(state?.commentLock)}
|
|
942
1196
|
>
|
|
943
|
-
Send
|
|
1197
|
+
{t("Send")}
|
|
944
1198
|
</button>
|
|
945
1199
|
</div>
|
|
946
1200
|
</form>
|
|
@@ -957,7 +1211,7 @@ export function ReviewApp({
|
|
|
957
1211
|
aria-labelledby="help-title"
|
|
958
1212
|
onClick={(e) => e.stopPropagation()}
|
|
959
1213
|
>
|
|
960
|
-
<h2 id="help-title">Keyboard shortcuts</h2>
|
|
1214
|
+
<h2 id="help-title">{t("Keyboard shortcuts")}</h2>
|
|
961
1215
|
<dl>
|
|
962
1216
|
{[
|
|
963
1217
|
["A", "Accept"],
|
|
@@ -971,18 +1225,18 @@ export function ReviewApp({
|
|
|
971
1225
|
].map(([key, label]) => (
|
|
972
1226
|
<Fragment key={key}>
|
|
973
1227
|
<dt>
|
|
974
|
-
<kbd>{key}</kbd>
|
|
1228
|
+
<kbd>{t(key)}</kbd>
|
|
975
1229
|
</dt>
|
|
976
|
-
<dd>{label}</dd>
|
|
1230
|
+
<dd>{t(label)}</dd>
|
|
977
1231
|
</Fragment>
|
|
978
1232
|
))}
|
|
979
1233
|
</dl>
|
|
980
1234
|
<p className="muted">
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
1235
|
+
{t(
|
|
1236
|
+
"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.",
|
|
1237
|
+
)}
|
|
984
1238
|
</p>
|
|
985
|
-
<button onClick={() => setHelp(false)}>Close</button>
|
|
1239
|
+
<button onClick={() => setHelp(false)}>{t("Close")}</button>
|
|
986
1240
|
</dialog>
|
|
987
1241
|
</div>
|
|
988
1242
|
)}
|