papilot 0.4.0 → 0.5.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/dist/assets/index-BqX4tIMz.js +217 -0
- package/dist/assets/index-C0hHDQdZ.css +1 -0
- package/dist/assets/{math-C3k2egGJ.js → math-CUuXX16h.js} +1 -1
- package/dist/assets/{pdf-view-Df0q8hSk.js → pdf-view-CNYCI_LH.js} +2 -2
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/server/app.ts +53 -6
- package/src/server/changes.ts +295 -0
- package/src/server/main.ts +2 -1
- package/src/server/manuscript.ts +36 -14
- package/src/server/workspace.ts +2 -2
- package/src/shared.ts +2 -1
- package/vendor/latex-review/dist/web/assets/index-Bq8CnbPr.js +19 -0
- package/vendor/latex-review/dist/web/assets/index-vftKrRf_.css +1 -0
- package/vendor/latex-review/dist/web/index.html +2 -2
- package/vendor/latex-review/src/web/main.tsx +8 -1
- package/vendor/latex-review/src/web/review.tsx +249 -109
- package/vendor/latex-review/src/web/style.css +6 -0
- package/vendor/latex-review/src/web/zh-CN.ts +35 -0
- package/dist/assets/index-BB0zC0wj.css +0 -1
- package/dist/assets/index-DTScOfwx.js +0 -206
- package/vendor/latex-review/dist/web/assets/index-Bg-VqnPa.css +0 -1
- package/vendor/latex-review/dist/web/assets/index-xLeqYBJt.js +0 -19
|
@@ -44,6 +44,23 @@ const ACTION: Record<string, string> = {
|
|
|
44
44
|
reject: "Reject",
|
|
45
45
|
comment: "Comment",
|
|
46
46
|
};
|
|
47
|
+
// A host can show a repository's uncommitted changes instead of a task's
|
|
48
|
+
// round (Papilot does, with ?changes). Accepting stages a change in the Git
|
|
49
|
+
// index, rejecting reverts it on disk, and a staged change can be unstaged.
|
|
50
|
+
// Every change stays in view: staged ones remain uncommitted.
|
|
51
|
+
const CHANGE_ACTION: Record<string, string> = {
|
|
52
|
+
accept: "Stage",
|
|
53
|
+
reject: "Revert",
|
|
54
|
+
unstage: "Unstage",
|
|
55
|
+
};
|
|
56
|
+
const CHANGE_STATE: Record<string, string> = {
|
|
57
|
+
pending: "Unstaged",
|
|
58
|
+
accepted: "Staged",
|
|
59
|
+
rejected: "Reverted",
|
|
60
|
+
commented: "Edited",
|
|
61
|
+
mixed: "Partly staged",
|
|
62
|
+
};
|
|
63
|
+
type Kind = ActionKind | "unstage";
|
|
47
64
|
const PHASE: Record<string, string> = {
|
|
48
65
|
snapshot: "Taking a snapshot",
|
|
49
66
|
build: "Compiling LaTeX",
|
|
@@ -82,9 +99,11 @@ function jobText(job: PageState["job"]) {
|
|
|
82
99
|
export function ReviewApp({
|
|
83
100
|
task,
|
|
84
101
|
pinned,
|
|
102
|
+
changes = false,
|
|
85
103
|
}: {
|
|
86
104
|
task: string;
|
|
87
105
|
pinned: number | null;
|
|
106
|
+
changes?: boolean;
|
|
88
107
|
}) {
|
|
89
108
|
const [state, setState] = useState<PageState | null>(null),
|
|
90
109
|
[detail, setDetail] = useState<RoundDetail | null>(null),
|
|
@@ -127,10 +146,32 @@ export function ReviewApp({
|
|
|
127
146
|
),
|
|
128
147
|
latest = Boolean(
|
|
129
148
|
detail && state && detail.round === state.latest && !state.closed,
|
|
149
|
+
),
|
|
150
|
+
browsing = history || changes,
|
|
151
|
+
label = (kind: string) => t((changes ? CHANGE_ACTION : ACTION)[kind] ?? kind),
|
|
152
|
+
stateLabel = (value: string) =>
|
|
153
|
+
t((changes ? CHANGE_STATE : STATE)[value] ?? value);
|
|
154
|
+
// What each button acts on: undecided changes, or in changes mode the
|
|
155
|
+
// staged ones for Unstage and every change still on disk for Revert.
|
|
156
|
+
const targets = (units: string[], kind: Kind) =>
|
|
157
|
+
units.filter((id) =>
|
|
158
|
+
kind === "unstage"
|
|
159
|
+
? decisions[id] === "accepted"
|
|
160
|
+
: changes && kind === "reject"
|
|
161
|
+
? ["pending", "accepted"].includes(decisions[id])
|
|
162
|
+
: decisions[id] === "pending",
|
|
130
163
|
);
|
|
164
|
+
const kinds = (units: string[]): Kind[] =>
|
|
165
|
+
!changes
|
|
166
|
+
? ["accept", "reject", "comment"]
|
|
167
|
+
: targets(units, "accept").length
|
|
168
|
+
? ["accept", "reject"]
|
|
169
|
+
: targets(units, "unstage").length
|
|
170
|
+
? ["unstage", "reject"]
|
|
171
|
+
: [];
|
|
131
172
|
const navigation = stops
|
|
132
173
|
.map((stop, index) => ({ stop, index }))
|
|
133
|
-
.filter(({ stop }) =>
|
|
174
|
+
.filter(({ stop }) => browsing || pendingUnits(stop, decisions).length)
|
|
134
175
|
.map(({ index }) => index);
|
|
135
176
|
const adjacent = (index: number, direction: number) =>
|
|
136
177
|
navigation[navigation.indexOf(index) + direction] ?? -1;
|
|
@@ -142,16 +183,20 @@ export function ReviewApp({
|
|
|
142
183
|
)
|
|
143
184
|
: [];
|
|
144
185
|
useEffect(() => {
|
|
145
|
-
if (
|
|
186
|
+
if (browsing || composer) return;
|
|
146
187
|
if (!navigation.includes(current)) setCurrent(navigation[0] ?? -1);
|
|
147
188
|
}, [decisions, history, composer, detail]);
|
|
189
|
+
useEffect(() => {
|
|
190
|
+
if (changes && current < 0 && stops.length)
|
|
191
|
+
setCurrent(Math.max(nextPending(stops, decisions), 0));
|
|
192
|
+
}, [detail]);
|
|
148
193
|
// Lock reasons come from the host in English; known ones are translated.
|
|
149
|
-
const actionLock = (kind:
|
|
194
|
+
const actionLock = (kind: Kind) => {
|
|
150
195
|
const reason =
|
|
151
196
|
kind === "comment" ? state?.commentLock : state?.decisionLock;
|
|
152
197
|
return reason ? t(reason) : reason;
|
|
153
198
|
};
|
|
154
|
-
function blocked(kind:
|
|
199
|
+
function blocked(kind: Kind): boolean {
|
|
155
200
|
const current = snapshot.current.state;
|
|
156
201
|
const reason =
|
|
157
202
|
kind === "comment" ? current?.commentLock : current?.decisionLock;
|
|
@@ -160,7 +205,7 @@ export function ReviewApp({
|
|
|
160
205
|
}
|
|
161
206
|
async function api<T>(route: string, body?: unknown): Promise<T> {
|
|
162
207
|
const response = await fetch(
|
|
163
|
-
`/api/tasks/${encodeURIComponent(task)}${route}`,
|
|
208
|
+
`${changes ? "/api/changes" : `/api/tasks/${encodeURIComponent(task)}`}${route}`,
|
|
164
209
|
body === undefined
|
|
165
210
|
? {}
|
|
166
211
|
: {
|
|
@@ -283,8 +328,12 @@ export function ReviewApp({
|
|
|
283
328
|
);
|
|
284
329
|
}, [detail?.round]);
|
|
285
330
|
useEffect(() => {
|
|
286
|
-
if (state) document.title = `${state.title} · LaTeX Review`;
|
|
331
|
+
if (state) document.title = `${t(state.title)} · LaTeX Review`;
|
|
287
332
|
}, [state?.title]);
|
|
333
|
+
// Host styles can tell the two modes apart.
|
|
334
|
+
useEffect(() => {
|
|
335
|
+
if (changes) document.documentElement.dataset.mode = "changes";
|
|
336
|
+
}, [changes]);
|
|
288
337
|
useEffect(() => {
|
|
289
338
|
if (!toast) return;
|
|
290
339
|
const timer = setTimeout(() => setToast(""), 6000);
|
|
@@ -308,12 +357,12 @@ export function ReviewApp({
|
|
|
308
357
|
setComposer(null);
|
|
309
358
|
}
|
|
310
359
|
async function decide(
|
|
311
|
-
kind:
|
|
312
|
-
|
|
360
|
+
kind: Kind,
|
|
361
|
+
chosen = targets(stops[current]?.units ?? [], kind),
|
|
313
362
|
body = "",
|
|
314
363
|
) {
|
|
315
364
|
if (
|
|
316
|
-
!
|
|
365
|
+
!chosen.length ||
|
|
317
366
|
snapshot.current.busy ||
|
|
318
367
|
!latest ||
|
|
319
368
|
!detail ||
|
|
@@ -328,18 +377,26 @@ export function ReviewApp({
|
|
|
328
377
|
id: crypto.randomUUID(),
|
|
329
378
|
action: kind,
|
|
330
379
|
round: detail.round,
|
|
331
|
-
changes,
|
|
380
|
+
changes: chosen,
|
|
332
381
|
body,
|
|
333
382
|
});
|
|
334
383
|
setState(next);
|
|
384
|
+
// The host refreshes what depends on the decision, such as Git status.
|
|
385
|
+
window.dispatchEvent(new CustomEvent("latex-review:decided"));
|
|
335
386
|
if (kind === "comment") closeComposer(true);
|
|
336
387
|
const target = pendingUnits(stops[current], next.decisions).length
|
|
337
388
|
? current
|
|
338
389
|
: nextPending(stops, next.decisions, current);
|
|
339
390
|
if (target >= 0 && target !== current) select(target);
|
|
340
391
|
if (target < 0) {
|
|
341
|
-
if (!
|
|
342
|
-
setToast(
|
|
392
|
+
if (!browsing) setCurrent(-1);
|
|
393
|
+
setToast(
|
|
394
|
+
changes
|
|
395
|
+
? kind === "accept"
|
|
396
|
+
? t("All changes are staged.")
|
|
397
|
+
: ""
|
|
398
|
+
: t("All changes reviewed. Your decisions are saved."),
|
|
399
|
+
);
|
|
343
400
|
}
|
|
344
401
|
} catch (e) {
|
|
345
402
|
setToast((e as Error).message);
|
|
@@ -360,13 +417,16 @@ export function ReviewApp({
|
|
|
360
417
|
id: state.undo.id,
|
|
361
418
|
});
|
|
362
419
|
setState(next);
|
|
420
|
+
window.dispatchEvent(new CustomEvent("latex-review:decided"));
|
|
363
421
|
const target = stops.findIndex((s) =>
|
|
364
422
|
s.units.some((id) => next.restored.includes(id)),
|
|
365
423
|
);
|
|
366
424
|
setHistory(false);
|
|
367
425
|
if (target >= 0) select(target);
|
|
368
426
|
setToast(
|
|
369
|
-
|
|
427
|
+
changes
|
|
428
|
+
? t("Revert undone. The changes are back in your files.")
|
|
429
|
+
: t("Last decision undone. The restored changes need review again."),
|
|
370
430
|
);
|
|
371
431
|
} catch (e) {
|
|
372
432
|
setToast((e as Error).message);
|
|
@@ -465,9 +525,10 @@ export function ReviewApp({
|
|
|
465
525
|
].includes(key)
|
|
466
526
|
)
|
|
467
527
|
event.preventDefault();
|
|
468
|
-
if (key === "a")
|
|
528
|
+
if (key === "a")
|
|
529
|
+
void decide(kinds(stops[current]?.units ?? [])[0] ?? "accept");
|
|
469
530
|
else if (key === "r") void decide("reject");
|
|
470
|
-
else if (key === "c") beginComposer();
|
|
531
|
+
else if (key === "c" && !changes) beginComposer();
|
|
471
532
|
else if (key === "j" || key === "arrowright")
|
|
472
533
|
select(adjacent(current, 1));
|
|
473
534
|
else if (key === "k" || key === "arrowleft")
|
|
@@ -504,7 +565,8 @@ export function ReviewApp({
|
|
|
504
565
|
}
|
|
505
566
|
const actionBar = (index: number) => {
|
|
506
567
|
if (index !== current || !state) return null;
|
|
507
|
-
const
|
|
568
|
+
const units = stops[index].units,
|
|
569
|
+
available = kinds(units),
|
|
508
570
|
remaining = nextPending(stops, decisions);
|
|
509
571
|
return (
|
|
510
572
|
<div className="dock">
|
|
@@ -526,31 +588,33 @@ export function ReviewApp({
|
|
|
526
588
|
</span>
|
|
527
589
|
</>
|
|
528
590
|
)}
|
|
529
|
-
{
|
|
591
|
+
{available.length ? (
|
|
530
592
|
<>
|
|
531
|
-
{
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
593
|
+
{available.map((kind) => {
|
|
594
|
+
const key = kind === "unstage" ? "a" : kind[0],
|
|
595
|
+
count = targets(units, kind).length;
|
|
596
|
+
return (
|
|
597
|
+
<button
|
|
598
|
+
className={kind}
|
|
599
|
+
key={kind}
|
|
600
|
+
aria-keyshortcuts={key}
|
|
601
|
+
title={
|
|
602
|
+
actionLock(kind) ||
|
|
603
|
+
`${label(kind)} (${key.toUpperCase()})`
|
|
604
|
+
}
|
|
605
|
+
aria-describedby={
|
|
606
|
+
actionLock(kind) ? "review-lock" : undefined
|
|
607
|
+
}
|
|
608
|
+
disabled={busy || !latest || Boolean(actionLock(kind))}
|
|
609
|
+
onClick={() =>
|
|
610
|
+
kind === "comment" ? beginComposer() : void decide(kind)
|
|
611
|
+
}
|
|
612
|
+
>
|
|
613
|
+
{label(kind)}
|
|
614
|
+
{count > 1 && kind !== "comment" ? ` (${count})` : ""}
|
|
615
|
+
</button>
|
|
616
|
+
);
|
|
617
|
+
})}
|
|
554
618
|
</>
|
|
555
619
|
) : remaining >= 0 ? (
|
|
556
620
|
<button
|
|
@@ -558,11 +622,15 @@ export function ReviewApp({
|
|
|
558
622
|
aria-keyshortcuts="n"
|
|
559
623
|
onClick={() => select(nextPending(stops, decisions, current))}
|
|
560
624
|
>
|
|
561
|
-
{t("Next undecided")}
|
|
625
|
+
{changes ? t("Next unstaged") : t("Next undecided")}
|
|
562
626
|
</button>
|
|
563
627
|
) : (
|
|
564
628
|
<span className="complete">
|
|
565
|
-
{
|
|
629
|
+
{changes
|
|
630
|
+
? stateLabel(stopState(units, decisions))
|
|
631
|
+
: latest
|
|
632
|
+
? t("Review complete")
|
|
633
|
+
: t("Earlier round")}
|
|
566
634
|
</span>
|
|
567
635
|
)}
|
|
568
636
|
{navigation.length > 1 && (
|
|
@@ -589,10 +657,16 @@ export function ReviewApp({
|
|
|
589
657
|
const known = block.units.map((id) => units.get(id)!).filter(Boolean),
|
|
590
658
|
status = stopState(block.units, decisions),
|
|
591
659
|
reviewed = !pendingUnits(stops[index], decisions).length,
|
|
660
|
+
// A reverted change shows the text that is back on disk; staged and
|
|
661
|
+
// unstaged ones keep their before and after.
|
|
592
662
|
resolved = history
|
|
593
663
|
? undefined
|
|
594
|
-
:
|
|
595
|
-
|
|
664
|
+
: changes
|
|
665
|
+
? status === "rejected"
|
|
666
|
+
? "before"
|
|
667
|
+
: undefined
|
|
668
|
+
: resolvedSide(block.units, decisions) ||
|
|
669
|
+
(status === "commented" ? "after" : undefined),
|
|
596
670
|
source =
|
|
597
671
|
openSource.has(index) ||
|
|
598
672
|
block.identical ||
|
|
@@ -628,17 +702,17 @@ export function ReviewApp({
|
|
|
628
702
|
return (
|
|
629
703
|
<article
|
|
630
704
|
key={index}
|
|
631
|
-
className={`change ${inline ? "inline" : "standalone"}${current === index ? " current" : ""}${reviewed && !
|
|
705
|
+
className={`change ${inline ? "inline" : "standalone"}${current === index ? " current" : ""}${reviewed && !browsing ? " reviewed-change" : ""}${reviewed && !browsing && !resolved ? " mixed-reviewed" : ""}`}
|
|
632
706
|
data-stop={index}
|
|
633
707
|
data-state={status}
|
|
634
708
|
data-resolved={resolved}
|
|
635
709
|
tabIndex={-1}
|
|
636
710
|
onClick={() => {
|
|
637
|
-
if (index !== current && !composer && (
|
|
711
|
+
if (index !== current && !composer && (browsing || !reviewed))
|
|
638
712
|
select(index, false);
|
|
639
713
|
}}
|
|
640
714
|
>
|
|
641
|
-
{reviewed && !
|
|
715
|
+
{reviewed && !browsing && !resolved && (
|
|
642
716
|
<p className="reviewed-placeholder">
|
|
643
717
|
<button
|
|
644
718
|
className="link"
|
|
@@ -667,7 +741,7 @@ export function ReviewApp({
|
|
|
667
741
|
{actionBar(index)}
|
|
668
742
|
{kindLabel && <span className="change-kind">{kindLabel}</span>}
|
|
669
743
|
{status !== "pending" && (
|
|
670
|
-
<span className="badge">{
|
|
744
|
+
<span className="badge">{stateLabel(status)}</span>
|
|
671
745
|
)}
|
|
672
746
|
<button
|
|
673
747
|
className="link copy-reference"
|
|
@@ -750,32 +824,30 @@ export function ReviewApp({
|
|
|
750
824
|
<span className="unit-actions">
|
|
751
825
|
<span className="unit-state">
|
|
752
826
|
{decisions[unit.id] !== "pending"
|
|
753
|
-
?
|
|
827
|
+
? stateLabel(decisions[unit.id]).toLowerCase()
|
|
754
828
|
: ""}
|
|
755
829
|
</span>
|
|
756
|
-
{decisions[unit.id] === "pending" &&
|
|
757
|
-
([
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
),
|
|
778
|
-
)}
|
|
830
|
+
{(changes || decisions[unit.id] === "pending") &&
|
|
831
|
+
kinds([unit.id]).map((kind) => (
|
|
832
|
+
<button
|
|
833
|
+
className={kind}
|
|
834
|
+
key={kind}
|
|
835
|
+
title={actionLock(kind) || undefined}
|
|
836
|
+
aria-describedby={
|
|
837
|
+
actionLock(kind) ? "review-lock" : undefined
|
|
838
|
+
}
|
|
839
|
+
disabled={
|
|
840
|
+
busy || !latest || Boolean(actionLock(kind))
|
|
841
|
+
}
|
|
842
|
+
onClick={() =>
|
|
843
|
+
kind === "comment"
|
|
844
|
+
? beginComposer([unit.id])
|
|
845
|
+
: void decide(kind, [unit.id])
|
|
846
|
+
}
|
|
847
|
+
>
|
|
848
|
+
{label(kind)}
|
|
849
|
+
</button>
|
|
850
|
+
))}
|
|
779
851
|
</span>
|
|
780
852
|
)}
|
|
781
853
|
</div>
|
|
@@ -816,19 +888,26 @@ export function ReviewApp({
|
|
|
816
888
|
<div className="identity">
|
|
817
889
|
<h1>{state?.title ?? "LaTeX Review"}</h1>
|
|
818
890
|
<span className="muted">
|
|
819
|
-
{
|
|
820
|
-
? t("
|
|
821
|
-
|
|
822
|
-
|
|
891
|
+
{changes
|
|
892
|
+
? t("Since the last commit")
|
|
893
|
+
: detail
|
|
894
|
+
? t("Round {round}", { round: detail.round }) +
|
|
895
|
+
(detail.round !== state?.latest ? t(" (earlier round)") : "")
|
|
896
|
+
: ""}
|
|
823
897
|
</span>
|
|
824
898
|
</div>
|
|
825
899
|
{count.total > 0 && (
|
|
826
900
|
<div className="meter">
|
|
827
901
|
<span>
|
|
828
|
-
{
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
902
|
+
{changes
|
|
903
|
+
? t("{staged} of {total} staged", {
|
|
904
|
+
staged: count.accepted,
|
|
905
|
+
total: count.accepted + count.pending,
|
|
906
|
+
})
|
|
907
|
+
: t("{decided} of {total} decided", {
|
|
908
|
+
decided: count.decided,
|
|
909
|
+
total: count.total,
|
|
910
|
+
})}
|
|
832
911
|
</span>
|
|
833
912
|
</div>
|
|
834
913
|
)}
|
|
@@ -847,21 +926,33 @@ export function ReviewApp({
|
|
|
847
926
|
}
|
|
848
927
|
title={
|
|
849
928
|
state?.decisionLock ||
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
929
|
+
(changes
|
|
930
|
+
? tn(
|
|
931
|
+
count.pending,
|
|
932
|
+
"Stage the {n} unstaged change.",
|
|
933
|
+
"Stage the {n} unstaged changes.",
|
|
934
|
+
)
|
|
935
|
+
: tn(
|
|
936
|
+
count.pending,
|
|
937
|
+
"Accept the {n} undecided change in this round. Decided and commented changes stay as they are. Undo reverses the whole batch.",
|
|
938
|
+
"Accept the {n} undecided changes in this round. Decided and commented changes stay as they are. Undo reverses the whole batch.",
|
|
939
|
+
))
|
|
940
|
+
}
|
|
941
|
+
aria-label={
|
|
942
|
+
changes
|
|
943
|
+
? t("Stage all ({n})", { n: count.pending })
|
|
944
|
+
: t("Accept all ({n})", { n: count.pending })
|
|
855
945
|
}
|
|
856
|
-
aria-label={t("Accept all ({n})", { n: count.pending })}
|
|
857
946
|
aria-describedby={state?.decisionLock ? "review-lock" : undefined}
|
|
858
947
|
onClick={() => void decide("accept", undecided)}
|
|
859
948
|
>
|
|
860
|
-
<span className="batch-label">
|
|
949
|
+
<span className="batch-label">
|
|
950
|
+
{changes ? t("Stage all") : t("Accept all")}
|
|
951
|
+
</span>
|
|
861
952
|
<span className="batch-count">{count.pending}</span>
|
|
862
953
|
</button>
|
|
863
954
|
)}
|
|
864
|
-
{count.pending > 1 && (
|
|
955
|
+
{count.pending > 1 && !changes && (
|
|
865
956
|
<button
|
|
866
957
|
className="ghost reject reject-all"
|
|
867
958
|
disabled={
|
|
@@ -901,12 +992,25 @@ export function ReviewApp({
|
|
|
901
992
|
? t("Finished")
|
|
902
993
|
: state?.state === "building"
|
|
903
994
|
? jobText(state.job)
|
|
904
|
-
:
|
|
995
|
+
: changes && state?.state === "build_failed"
|
|
996
|
+
? t("Comparison failed")
|
|
997
|
+
: t(STATUS[state?.state ?? ""] ?? "")}
|
|
905
998
|
</span>
|
|
906
999
|
)}
|
|
1000
|
+
{/* Changes keep no history; the last revert can be undone. */}
|
|
1001
|
+
{changes && state?.undo && (
|
|
1002
|
+
<button
|
|
1003
|
+
className="ghost undo"
|
|
1004
|
+
disabled={!latest || busy || Boolean(state.decisionLock)}
|
|
1005
|
+
title={state.decisionLock || t("Put back what the last revert removed (U)")}
|
|
1006
|
+
onClick={() => void undo()}
|
|
1007
|
+
>
|
|
1008
|
+
{t("Undo revert")}
|
|
1009
|
+
</button>
|
|
1010
|
+
)}
|
|
907
1011
|
{/* History is a toolbar action; its own row appears only while
|
|
908
1012
|
browsing earlier decisions. */}
|
|
909
|
-
{detail && !history && (
|
|
1013
|
+
{detail && !history && !changes && (
|
|
910
1014
|
<button
|
|
911
1015
|
className="ghost history"
|
|
912
1016
|
title={t("Earlier decisions and rounds")}
|
|
@@ -1009,7 +1113,11 @@ export function ReviewApp({
|
|
|
1009
1113
|
)}
|
|
1010
1114
|
{state?.state === "build_failed" && (
|
|
1011
1115
|
<div className="banner">
|
|
1012
|
-
<strong>
|
|
1116
|
+
<strong>
|
|
1117
|
+
{changes
|
|
1118
|
+
? t("The comparison could not be built: ")
|
|
1119
|
+
: t("The latest publish failed: ")}
|
|
1120
|
+
</strong>
|
|
1013
1121
|
{state.job?.error}
|
|
1014
1122
|
{state.job?.details && (
|
|
1015
1123
|
<pre className="details">{state.job.details.join("\n")}</pre>
|
|
@@ -1018,12 +1126,16 @@ export function ReviewApp({
|
|
|
1018
1126
|
)}
|
|
1019
1127
|
{state?.state === "building" && detail && !state.decisionLock && (
|
|
1020
1128
|
<div className="banner">
|
|
1021
|
-
{
|
|
1022
|
-
|
|
1023
|
-
|
|
1129
|
+
{changes
|
|
1130
|
+
? t(
|
|
1131
|
+
"Updating the comparison with your latest edits. You can keep staging and reverting meanwhile.",
|
|
1132
|
+
)
|
|
1133
|
+
: t(
|
|
1134
|
+
"The agent is publishing a new round. You can keep reviewing; the new round will include your decisions.",
|
|
1135
|
+
)}
|
|
1024
1136
|
</div>
|
|
1025
1137
|
)}
|
|
1026
|
-
{detail && state?.latest && detail.round !== state.latest && (
|
|
1138
|
+
{!changes && detail && state?.latest && detail.round !== state.latest && (
|
|
1027
1139
|
<div className="banner">
|
|
1028
1140
|
{t("You are viewing round {round}.", { round: detail.round })}{" "}
|
|
1029
1141
|
<a href={withLanguage(`?task=${encodeURIComponent(task)}`)}>
|
|
@@ -1045,7 +1157,22 @@ export function ReviewApp({
|
|
|
1045
1157
|
</details>
|
|
1046
1158
|
</div>
|
|
1047
1159
|
) : null}
|
|
1048
|
-
{
|
|
1160
|
+
{changes && detail && !count.pending && (count.accepted > 0 || !stops.length) && (
|
|
1161
|
+
<div className="review-complete" role="status">
|
|
1162
|
+
<strong>
|
|
1163
|
+
{count.accepted
|
|
1164
|
+
? t("All changes are staged")
|
|
1165
|
+
: t("No uncommitted changes")}
|
|
1166
|
+
</strong>
|
|
1167
|
+
<span>
|
|
1168
|
+
{count.accepted
|
|
1169
|
+
? t("Commit them when you are ready.")
|
|
1170
|
+
: t("The PDF matches the last commit.")}
|
|
1171
|
+
</span>
|
|
1172
|
+
<div className="review-complete-actions" />
|
|
1173
|
+
</div>
|
|
1174
|
+
)}
|
|
1175
|
+
{!changes && detail && !history && !count.pending && (
|
|
1049
1176
|
<div className="review-complete" role="status">
|
|
1050
1177
|
<strong>
|
|
1051
1178
|
{t(
|
|
@@ -1128,7 +1255,7 @@ export function ReviewApp({
|
|
|
1128
1255
|
{detail.loose.map((block) => change(block, index++, false))}
|
|
1129
1256
|
</section>
|
|
1130
1257
|
)}
|
|
1131
|
-
{!stops.length && (
|
|
1258
|
+
{!stops.length && !changes && (
|
|
1132
1259
|
<p className="empty">{t("This round has no changes.")}</p>
|
|
1133
1260
|
)}
|
|
1134
1261
|
</>
|
|
@@ -1142,7 +1269,9 @@ export function ReviewApp({
|
|
|
1142
1269
|
<p>
|
|
1143
1270
|
{state?.state === "building"
|
|
1144
1271
|
? jobText(state.job)
|
|
1145
|
-
:
|
|
1272
|
+
: changes
|
|
1273
|
+
? t("Comparing with the last commit…")
|
|
1274
|
+
: t("Waiting for the agent to publish its changes.")}
|
|
1146
1275
|
</p>
|
|
1147
1276
|
<p className="muted">{t("This page updates by itself.")}</p>
|
|
1148
1277
|
</div>
|
|
@@ -1152,7 +1281,7 @@ export function ReviewApp({
|
|
|
1152
1281
|
<ChangeMap
|
|
1153
1282
|
scrollRoot={paper}
|
|
1154
1283
|
stops={stops}
|
|
1155
|
-
showReviewed={
|
|
1284
|
+
showReviewed={browsing}
|
|
1156
1285
|
locations={locations}
|
|
1157
1286
|
decisions={decisions}
|
|
1158
1287
|
current={current}
|
|
@@ -1213,16 +1342,27 @@ export function ReviewApp({
|
|
|
1213
1342
|
>
|
|
1214
1343
|
<h2 id="help-title">{t("Keyboard shortcuts")}</h2>
|
|
1215
1344
|
<dl>
|
|
1216
|
-
{
|
|
1217
|
-
[
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1345
|
+
{(changes
|
|
1346
|
+
? [
|
|
1347
|
+
["A", "Stage or unstage"],
|
|
1348
|
+
["R", "Revert"],
|
|
1349
|
+
["J / K or → / ←", "Next / previous change"],
|
|
1350
|
+
["N", "Next unstaged"],
|
|
1351
|
+
["S", "Show LaTeX source"],
|
|
1352
|
+
["⌘Z / U", "Undo revert"],
|
|
1353
|
+
["⌘C / Ctrl+C", "Copy selected PDF text"],
|
|
1354
|
+
]
|
|
1355
|
+
: [
|
|
1356
|
+
["A", "Accept"],
|
|
1357
|
+
["R", "Reject"],
|
|
1358
|
+
["C", "Comment"],
|
|
1359
|
+
["J / K or → / ←", "Next / previous change"],
|
|
1360
|
+
["N", "Next undecided"],
|
|
1361
|
+
["S", "Show LaTeX source"],
|
|
1362
|
+
["⌘Z / U", "Undo"],
|
|
1363
|
+
["⌘C / Ctrl+C", "Copy selected PDF text"],
|
|
1364
|
+
]
|
|
1365
|
+
).map(([key, label]) => (
|
|
1226
1366
|
<Fragment key={key}>
|
|
1227
1367
|
<dt>
|
|
1228
1368
|
<kbd>{t(key)}</kbd>
|
|
@@ -517,6 +517,9 @@ kbd {
|
|
|
517
517
|
.unit-actions .comment {
|
|
518
518
|
color: var(--comment);
|
|
519
519
|
}
|
|
520
|
+
.unit-actions .unstage {
|
|
521
|
+
color: var(--muted);
|
|
522
|
+
}
|
|
520
523
|
.unit-state {
|
|
521
524
|
font-weight: 600;
|
|
522
525
|
}
|
|
@@ -787,6 +790,9 @@ kbd {
|
|
|
787
790
|
.actions .comment {
|
|
788
791
|
color: var(--comment);
|
|
789
792
|
}
|
|
793
|
+
.actions .unstage {
|
|
794
|
+
color: var(--muted);
|
|
795
|
+
}
|
|
790
796
|
.actions .skip {
|
|
791
797
|
font-weight: 600;
|
|
792
798
|
}
|
|
@@ -185,4 +185,39 @@ export const zhCN: Record<string, string> = {
|
|
|
185
185
|
"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.":
|
|
186
186
|
"可直接在 PDF 预览中选择文本。复制引用会包含选中的文本及其文件、行号和页码。选中文本时,审阅快捷键会暂停。",
|
|
187
187
|
Close: "关闭",
|
|
188
|
+
|
|
189
|
+
// Uncommitted changes, when a host such as Papilot shows them
|
|
190
|
+
"Uncommitted changes": "未提交的修改",
|
|
191
|
+
Stage: "暂存",
|
|
192
|
+
Revert: "还原",
|
|
193
|
+
Unstage: "取消暂存",
|
|
194
|
+
Unstaged: "未暂存",
|
|
195
|
+
Staged: "已暂存",
|
|
196
|
+
Reverted: "已还原",
|
|
197
|
+
Edited: "已改动",
|
|
198
|
+
"Partly staged": "部分暂存",
|
|
199
|
+
"All changes are staged.": "所有修改都已暂存。",
|
|
200
|
+
"Revert undone. The changes are back in your files.": "已撤销还原,这些修改已回到你的文件中。",
|
|
201
|
+
"Next unstaged": "下一处未暂存",
|
|
202
|
+
"Since the last commit": "自上次提交以来",
|
|
203
|
+
"{staged} of {total} staged": "已暂存 {staged} / {total}",
|
|
204
|
+
"Stage the {n} unstaged change.": "暂存这 {n} 处未暂存的修改。",
|
|
205
|
+
"Stage the {n} unstaged changes.": "暂存这 {n} 处未暂存的修改。",
|
|
206
|
+
"Stage all ({n})": "全部暂存({n})",
|
|
207
|
+
"Stage all": "全部暂存",
|
|
208
|
+
"Comparison failed": "比较失败",
|
|
209
|
+
"Put back what the last revert removed (U)": "恢复上一次还原掉的内容(U)",
|
|
210
|
+
"Undo revert": "撤销还原",
|
|
211
|
+
"The comparison could not be built: ": "无法生成比较:",
|
|
212
|
+
"Updating the comparison with your latest edits. You can keep staging and reverting meanwhile.":
|
|
213
|
+
"正在根据最新修改更新比较。在此期间你可以继续暂存或还原。",
|
|
214
|
+
"All changes are staged": "所有修改都已暂存",
|
|
215
|
+
"No uncommitted changes": "没有未提交的修改",
|
|
216
|
+
"Commit them when you are ready.": "准备好后就可以提交。",
|
|
217
|
+
"The PDF matches the last commit.": "PDF 与上次提交一致。",
|
|
218
|
+
"Comparing with the last commit…": "正在与上次提交比较…",
|
|
219
|
+
"Stage or unstage": "暂存或取消暂存",
|
|
220
|
+
"Finish or abort the merge in the Git panel before staging or reverting changes.":
|
|
221
|
+
"请先在 Git 面板中完成或中止合并,再暂存或还原修改。",
|
|
222
|
+
"The last commit changed. The comparison is being updated.": "上次提交已变化,正在更新比较。",
|
|
188
223
|
};
|