papilot 0.7.0 → 0.8.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 +52 -30
- package/README.zh-CN.md +54 -30
- package/bin/doctor.mjs +53 -6
- package/bin/installation.d.mts +2 -0
- package/bin/installation.mjs +7 -1
- package/dist/assets/{_virtual_papilot-licenses-BWIsfrQh.js → _virtual_papilot-licenses-D7ro6ecN.js} +54 -3
- package/dist/assets/index-Cc-awdNn.css +1 -0
- package/dist/assets/index-D_VX51no.js +265 -0
- package/dist/assets/latex-BarqJFtQ.svg +1 -0
- package/dist/assets/{math-BsEl3u6P.js → math-V9uUaUzA.js} +1 -1
- package/dist/assets/pdf-view-Bnb2uIBE.js +598 -0
- package/dist/index.html +2 -2
- package/examples/papilot-paper/main.tex +1 -1
- package/examples/papilot-paper/sections/02-using.tex +3 -1
- package/examples/papilot-paper/sections/09-agents.tex +8 -5
- package/package.json +10 -6
- package/skills/ai-review.md +18 -0
- package/skills/latex-warnings.md +1 -1
- package/src/conversation.ts +6 -0
- package/src/server/acp.ts +354 -0
- package/src/server/agents.ts +220 -43
- package/src/server/ai-review-routes.ts +72 -0
- package/src/server/ai-review.ts +38 -0
- package/src/server/app.ts +53 -11
- package/src/server/build-problems.ts +329 -0
- package/src/server/changes.ts +25 -2
- package/src/server/codex.ts +2 -1
- package/src/server/cursor.ts +153 -0
- package/src/server/latex-installation.ts +43 -0
- package/src/server/manuscript.ts +165 -23
- package/src/server/paths.ts +1 -1
- package/src/server/references.ts +70 -0
- package/src/server/review.ts +3 -3
- package/src/server/skills.ts +5 -8
- package/src/server/store.ts +20 -2
- package/src/server/update-install.ts +53 -2
- package/src/server/update-worker.ts +6 -6
- package/src/server/updates.ts +43 -14
- package/src/server/workspace.ts +183 -29
- package/src/shared.ts +78 -7
- package/src/skill-icons.ts +18 -0
- package/src/skill-metadata.ts +45 -0
- package/vendor/latex-review/dist/web/assets/index-7VKAte5x.js +19 -0
- package/vendor/latex-review/dist/web/assets/index-DEz-LsGl.css +1 -0
- package/vendor/latex-review/dist/web/index.html +2 -2
- package/vendor/latex-review/src/core/bib.ts +692 -0
- package/vendor/latex-review/src/core/changes.ts +39 -0
- package/vendor/latex-review/src/core/review.ts +1 -0
- package/vendor/latex-review/src/core/verify.ts +784 -0
- package/vendor/latex-review/src/core/view.ts +204 -0
- package/vendor/latex-review/src/render/round.ts +11 -0
- package/vendor/latex-review/src/server.ts +65 -0
- package/vendor/latex-review/src/types.ts +125 -0
- package/vendor/latex-review/src/web/effect-preferences.ts +10 -0
- package/vendor/latex-review/src/web/references.tsx +499 -0
- package/vendor/latex-review/src/web/review.tsx +259 -131
- package/vendor/latex-review/src/web/style.css +285 -0
- package/vendor/latex-review/src/web/zh-CN.ts +75 -0
- package/dist/assets/index-CIJEYlP7.js +0 -252
- package/dist/assets/index-hOJEruFc.css +0 -1
- package/dist/assets/pdf-view-Cz9_vK5U.js +0 -598
- package/vendor/latex-review/dist/web/assets/index-Be9CtgcZ.css +0 -1
- package/vendor/latex-review/dist/web/assets/index-TvMWVSaw.js +0 -19
|
@@ -18,8 +18,16 @@ import type {
|
|
|
18
18
|
} from "../types.ts";
|
|
19
19
|
import { PdfStrip } from "./PdfStrip.tsx";
|
|
20
20
|
import { ChangeMap } from "./ChangeMap.tsx";
|
|
21
|
+
import { EFFECTS_EVENT, EFFECTS_STORAGE, readExpandedEffects } from "./effect-preferences.ts";
|
|
21
22
|
import { STATUS } from "./overview.tsx";
|
|
22
|
-
import { locale, t, tn, withLanguage } from "./i18n.ts";
|
|
23
|
+
import { language, locale, t, tn, withLanguage } from "./i18n.ts";
|
|
24
|
+
import {
|
|
25
|
+
ConfirmReferences,
|
|
26
|
+
ReferenceBanner,
|
|
27
|
+
ReferenceEntries,
|
|
28
|
+
risks,
|
|
29
|
+
useReferenceChecks,
|
|
30
|
+
} from "./references.tsx";
|
|
23
31
|
import { bindingLabel, readShortcuts, resolveShortcuts, shortcutAction, SHORTCUT_EVENT, SHORTCUT_STORAGE } from "./shortcuts.ts";
|
|
24
32
|
import {
|
|
25
33
|
STATE,
|
|
@@ -137,6 +145,13 @@ export function ReviewApp({
|
|
|
137
145
|
Record<string, PlaceView[] | "loading" | { error: string }>
|
|
138
146
|
>({}),
|
|
139
147
|
[composer, setComposer] = useState<Composer | null>(null),
|
|
148
|
+
// Accepting bibliography entries that are not verified waits for this.
|
|
149
|
+
[confirm, setConfirm] = useState<{
|
|
150
|
+
kind: Kind;
|
|
151
|
+
chosen: string[];
|
|
152
|
+
body: string;
|
|
153
|
+
items: { label: string; reasons: string[] }[];
|
|
154
|
+
} | null>(null),
|
|
140
155
|
[draft, setDraft] = useState(""),
|
|
141
156
|
[help, setHelp] = useState(false),
|
|
142
157
|
// A note can reveal a decided change without opening the history controls.
|
|
@@ -152,6 +167,18 @@ export function ReviewApp({
|
|
|
152
167
|
scrollUntil = useRef(0),
|
|
153
168
|
snapshot = useRef({ state, detail, busy, composer, current });
|
|
154
169
|
const [shortcuts, setShortcuts] = useState(readShortcuts);
|
|
170
|
+
const [expandedEffects, setExpandedEffects] = useState(readExpandedEffects);
|
|
171
|
+
// Per-edit disclosure is temporary and scoped to its round. Changing the
|
|
172
|
+
// global preference resets those overrides without reloading the review.
|
|
173
|
+
const [effectOverrides, setEffectOverrides] = useState<Record<string, boolean>>({});
|
|
174
|
+
useEffect(() => {
|
|
175
|
+
const changed = (event: Event) => setExpandedEffects((event as CustomEvent).detail === true);
|
|
176
|
+
const stored = (event: StorageEvent) => { if (event.key === EFFECTS_STORAGE || event.key === null) setExpandedEffects(readExpandedEffects()); };
|
|
177
|
+
window.addEventListener(EFFECTS_EVENT, changed);
|
|
178
|
+
window.addEventListener('storage', stored);
|
|
179
|
+
return () => { window.removeEventListener(EFFECTS_EVENT, changed); window.removeEventListener('storage', stored); };
|
|
180
|
+
}, []);
|
|
181
|
+
useEffect(() => { setEffectOverrides({}); }, [expandedEffects]);
|
|
155
182
|
useEffect(() => {
|
|
156
183
|
const changed = (event: Event) => setShortcuts(resolveShortcuts((event as CustomEvent).detail));
|
|
157
184
|
const stored = (event: StorageEvent) => { if (event.key === SHORTCUT_STORAGE || event.key === null) setShortcuts(readShortcuts()); };
|
|
@@ -191,6 +218,13 @@ export function ReviewApp({
|
|
|
191
218
|
undecided = Object.keys(decisions).filter(
|
|
192
219
|
(id) => decisions[id] === "pending",
|
|
193
220
|
),
|
|
221
|
+
references = useReferenceChecks(detail, api),
|
|
222
|
+
hazards = useMemo(
|
|
223
|
+
() => risks(detail?.references, references.checks, references.pending, references.confirmed),
|
|
224
|
+
[detail, references.checks, references.pending, references.confirmed],
|
|
225
|
+
),
|
|
226
|
+
// Batch acceptance leaves the changes whose entries need a look.
|
|
227
|
+
batch = undecided.filter((id) => !hazards.has(id)),
|
|
194
228
|
latest = Boolean(
|
|
195
229
|
detail && state && detail.round === state.latest && !state.closed,
|
|
196
230
|
),
|
|
@@ -198,6 +232,9 @@ export function ReviewApp({
|
|
|
198
232
|
label = (kind: string) => t((changes ? CHANGE_ACTION : ACTION)[kind] ?? kind),
|
|
199
233
|
stateLabel = (value: string) =>
|
|
200
234
|
t((changes ? CHANGE_STATE : STATE)[value] ?? value);
|
|
235
|
+
// Keyboard handlers read the newest checks, not those of their render.
|
|
236
|
+
const hazardsNow = useRef(hazards);
|
|
237
|
+
hazardsNow.current = hazards;
|
|
201
238
|
// What each button acts on: undecided changes, or in changes mode the
|
|
202
239
|
// staged ones for Unstage and every change still on disk for Revert.
|
|
203
240
|
const targets = (units: string[], kind: Kind) =>
|
|
@@ -281,11 +318,20 @@ export function ReviewApp({
|
|
|
281
318
|
const article = paper.current?.querySelector<HTMLElement>(
|
|
282
319
|
`[data-stop="${index}"]`,
|
|
283
320
|
);
|
|
284
|
-
article
|
|
321
|
+
if (article) reveal(article, "smooth");
|
|
285
322
|
article?.focus({ preventScroll: true });
|
|
286
323
|
});
|
|
287
324
|
}
|
|
288
325
|
}
|
|
326
|
+
function reveal(article: HTMLElement, behavior: ScrollBehavior = "instant") {
|
|
327
|
+
const root = paper.current;
|
|
328
|
+
if (!root) return;
|
|
329
|
+
// scrollIntoView also scrolls ancestors across the embedding iframe.
|
|
330
|
+
// Navigation belongs to the paper, independently of the host's Chat.
|
|
331
|
+
const padding = parseFloat(getComputedStyle(root).scrollPaddingTop) || 0;
|
|
332
|
+
const margin = parseFloat(getComputedStyle(article).scrollMarginTop) || 0;
|
|
333
|
+
root.scrollTo({ top: root.scrollTop + article.getBoundingClientRect().top - root.getBoundingClientRect().top - padding - margin, behavior });
|
|
334
|
+
}
|
|
289
335
|
useEffect(() => {
|
|
290
336
|
let cancelled = false,
|
|
291
337
|
timer: ReturnType<typeof setTimeout>;
|
|
@@ -369,11 +415,10 @@ export function ReviewApp({
|
|
|
369
415
|
}, [task, pinned]);
|
|
370
416
|
useEffect(() => {
|
|
371
417
|
if (detail && current >= 0)
|
|
372
|
-
requestAnimationFrame(() =>
|
|
373
|
-
paper.current
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
);
|
|
418
|
+
requestAnimationFrame(() => {
|
|
419
|
+
const article = paper.current?.querySelector<HTMLElement>(`[data-stop="${current}"]`);
|
|
420
|
+
if (article) reveal(article);
|
|
421
|
+
});
|
|
377
422
|
}, [detail?.round]);
|
|
378
423
|
useEffect(() => {
|
|
379
424
|
if (state) document.title = `${t(state.title)} · LaTeX Review`;
|
|
@@ -408,6 +453,7 @@ export function ReviewApp({
|
|
|
408
453
|
kind: Kind,
|
|
409
454
|
chosen = targets(stops[current]?.units ?? [], kind),
|
|
410
455
|
body = "",
|
|
456
|
+
confirmed = false,
|
|
411
457
|
) {
|
|
412
458
|
if (
|
|
413
459
|
!chosen.length ||
|
|
@@ -417,6 +463,13 @@ export function ReviewApp({
|
|
|
417
463
|
blocked(kind)
|
|
418
464
|
)
|
|
419
465
|
return;
|
|
466
|
+
if (kind === "accept" && !confirmed) {
|
|
467
|
+
const items = chosen.flatMap((id) => hazardsNow.current.get(id) ?? []);
|
|
468
|
+
if (items.length) {
|
|
469
|
+
setConfirm({ kind, chosen, body, items });
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
420
473
|
epoch.current++;
|
|
421
474
|
setBusy(true);
|
|
422
475
|
snapshot.current.busy = true;
|
|
@@ -530,7 +583,8 @@ export function ReviewApp({
|
|
|
530
583
|
typing =
|
|
531
584
|
target.matches("input,textarea,select") || target.isContentEditable;
|
|
532
585
|
if (event.key === "Escape") {
|
|
533
|
-
if (
|
|
586
|
+
if (confirm) setConfirm(null);
|
|
587
|
+
else if (help) setHelp(false);
|
|
534
588
|
else if (composer) closeComposer();
|
|
535
589
|
return;
|
|
536
590
|
}
|
|
@@ -543,7 +597,7 @@ export function ReviewApp({
|
|
|
543
597
|
void submitComposer();
|
|
544
598
|
return;
|
|
545
599
|
}
|
|
546
|
-
if (typing || help || window.getSelection()?.toString()) return;
|
|
600
|
+
if (typing || help || confirm || window.getSelection()?.toString()) return;
|
|
547
601
|
if ((event.metaKey || event.ctrlKey) && !event.altKey && !event.shiftKey && event.key.toLowerCase() === "z") {
|
|
548
602
|
event.preventDefault();
|
|
549
603
|
void undo();
|
|
@@ -568,7 +622,7 @@ export function ReviewApp({
|
|
|
568
622
|
};
|
|
569
623
|
window.addEventListener("keydown", listener);
|
|
570
624
|
return () => window.removeEventListener("keydown", listener);
|
|
571
|
-
}, [state, detail, current, busy, composer, draft, help, history, shortcuts]);
|
|
625
|
+
}, [state, detail, current, busy, composer, draft, help, confirm, history, shortcuts]);
|
|
572
626
|
function onScroll() {
|
|
573
627
|
if (
|
|
574
628
|
!paper.current ||
|
|
@@ -698,7 +752,10 @@ export function ReviewApp({
|
|
|
698
752
|
clean: boolean,
|
|
699
753
|
): ReactNode {
|
|
700
754
|
const { total, shown } = unit.effects!,
|
|
701
|
-
|
|
755
|
+
key = `${detail!.round}:${unit.id}`,
|
|
756
|
+
open = effectOverrides[key] ?? (expandedEffects || unit.visual === "unmapped" || unit.visual === "identical"),
|
|
757
|
+
bodyId = `effects-${detail!.round}-${unit.id}`,
|
|
758
|
+
loaded = more[key],
|
|
702
759
|
places = [...shown, ...(Array.isArray(loaded) ? loaded : [])],
|
|
703
760
|
strips = (items: Strip[], side: "before" | "after") =>
|
|
704
761
|
items.length > 0 && (
|
|
@@ -720,47 +777,57 @@ export function ReviewApp({
|
|
|
720
777
|
return (
|
|
721
778
|
<section className="effects" key={unit.id}>
|
|
722
779
|
<p className="effects-head">
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
<p className="place-where">
|
|
728
|
-
{t("p. {pages}", { pages: place.page })}
|
|
729
|
-
{place.section ? ` · ${place.section}` : ""}
|
|
730
|
-
{place.with
|
|
731
|
-
? ` · ${t("with {changes}", {
|
|
732
|
-
changes: place.with
|
|
733
|
-
.map((id) =>
|
|
734
|
-
firstStop.has(id)
|
|
735
|
-
? t("Change {n}", { n: firstStop.get(id)! + 1 })
|
|
736
|
-
: (units.get(id)?.file ?? id),
|
|
737
|
-
)
|
|
738
|
-
.join(", "),
|
|
739
|
-
})}`
|
|
740
|
-
: ""}
|
|
741
|
-
</p>
|
|
742
|
-
{strips(place.before, "before")}
|
|
743
|
-
{strips(place.after, "after")}
|
|
744
|
-
</div>
|
|
745
|
-
))}
|
|
746
|
-
{total > places.length && (
|
|
747
|
-
<button
|
|
748
|
-
className="link more-places"
|
|
749
|
-
disabled={loaded === "loading"}
|
|
750
|
-
onClick={() => void loadPlaces(unit.id)}
|
|
751
|
-
>
|
|
752
|
-
{loaded === "loading"
|
|
753
|
-
? t("Loading…")
|
|
754
|
-
: tn(
|
|
755
|
-
total - places.length,
|
|
756
|
-
"Show {n} more",
|
|
757
|
-
"Show {n} more",
|
|
758
|
-
)}
|
|
780
|
+
<button className="link effects-toggle" aria-expanded={open} aria-controls={bodyId}
|
|
781
|
+
onClick={() => setEffectOverrides((values) => ({ ...values, [key]: !open }))}>
|
|
782
|
+
<span aria-hidden="true">{open ? "▾" : "▸"}</span>{" "}
|
|
783
|
+
{tn(total, "Affects {n} place", "Affects {n} places")}
|
|
759
784
|
</button>
|
|
760
|
-
|
|
761
|
-
{
|
|
762
|
-
|
|
763
|
-
|
|
785
|
+
</p>
|
|
786
|
+
<div id={bodyId} hidden={!open}>
|
|
787
|
+
{open && (
|
|
788
|
+
<>
|
|
789
|
+
{places.map((place, i) => (
|
|
790
|
+
<div className="place" key={i}>
|
|
791
|
+
<p className="place-where">
|
|
792
|
+
{t("p. {pages}", { pages: place.page })}
|
|
793
|
+
{place.section ? ` · ${place.section}` : ""}
|
|
794
|
+
{place.with
|
|
795
|
+
? ` · ${t("with {changes}", {
|
|
796
|
+
changes: place.with
|
|
797
|
+
.map((id) =>
|
|
798
|
+
firstStop.has(id)
|
|
799
|
+
? t("Change {n}", { n: firstStop.get(id)! + 1 })
|
|
800
|
+
: (units.get(id)?.file ?? id),
|
|
801
|
+
)
|
|
802
|
+
.join(", "),
|
|
803
|
+
})}`
|
|
804
|
+
: ""}
|
|
805
|
+
</p>
|
|
806
|
+
{strips(place.before, "before")}
|
|
807
|
+
{strips(place.after, "after")}
|
|
808
|
+
</div>
|
|
809
|
+
))}
|
|
810
|
+
{total > places.length && (
|
|
811
|
+
<button
|
|
812
|
+
className="link more-places"
|
|
813
|
+
disabled={loaded === "loading"}
|
|
814
|
+
onClick={() => void loadPlaces(unit.id)}
|
|
815
|
+
>
|
|
816
|
+
{loaded === "loading"
|
|
817
|
+
? t("Loading…")
|
|
818
|
+
: tn(
|
|
819
|
+
total - places.length,
|
|
820
|
+
"Show {n} more",
|
|
821
|
+
"Show {n} more",
|
|
822
|
+
)}
|
|
823
|
+
</button>
|
|
824
|
+
)}
|
|
825
|
+
{loaded && typeof loaded === "object" && "error" in loaded && (
|
|
826
|
+
<p className="caption">{loaded.error}</p>
|
|
827
|
+
)}
|
|
828
|
+
</>
|
|
829
|
+
)}
|
|
830
|
+
</div>
|
|
764
831
|
</section>
|
|
765
832
|
);
|
|
766
833
|
}
|
|
@@ -770,6 +837,10 @@ export function ReviewApp({
|
|
|
770
837
|
inline: boolean,
|
|
771
838
|
): ReactNode {
|
|
772
839
|
const known = block.units.map((id) => units.get(id)!).filter(Boolean),
|
|
840
|
+
// A .bib edit is shown entry by entry, every field, rather than as
|
|
841
|
+
// the bibliography's typeset lines alone.
|
|
842
|
+
bib = detail!.references?.entries.filter((e) => (e.units ?? [e.unit]).some((id) => block.units.includes(id))) ?? [],
|
|
843
|
+
uncited = detail!.references?.missing.filter((m) => m.unit && block.units.includes(m.unit)) ?? [],
|
|
773
844
|
status = stopState(block.units, decisions),
|
|
774
845
|
reviewed = !pendingUnits(stops[index], decisions).length,
|
|
775
846
|
expanded = browsing || index === linkedChange,
|
|
@@ -785,15 +856,17 @@ export function ReviewApp({
|
|
|
785
856
|
(status === "commented" ? "after" : undefined),
|
|
786
857
|
source =
|
|
787
858
|
openSource.has(index) ||
|
|
788
|
-
|
|
789
|
-
|
|
859
|
+
(!bib.length &&
|
|
860
|
+
(block.identical || (!block.before.length && !block.after.length))),
|
|
790
861
|
remarks = [
|
|
791
862
|
...new Set(
|
|
792
863
|
block.units.map((id) => state?.comments[id]).filter(Boolean),
|
|
793
864
|
),
|
|
794
865
|
],
|
|
795
866
|
location = describeLocation(known, t),
|
|
796
|
-
kindLabel =
|
|
867
|
+
kindLabel = bib.length
|
|
868
|
+
? t("Bibliography")
|
|
869
|
+
: block.reflow
|
|
797
870
|
? t("Reflow")
|
|
798
871
|
: [
|
|
799
872
|
...new Set(
|
|
@@ -905,7 +978,7 @@ export function ReviewApp({
|
|
|
905
978
|
block.before.length > 0 &&
|
|
906
979
|
images(block.before, "before")}
|
|
907
980
|
{block.after.length > 0 && images(block.after, "after")}
|
|
908
|
-
{block.identical && (
|
|
981
|
+
{block.identical && !bib.length && (
|
|
909
982
|
<p className="caption">
|
|
910
983
|
{t("No visible change in the PDF; see the LaTeX source.")}
|
|
911
984
|
</p>
|
|
@@ -925,6 +998,25 @@ export function ReviewApp({
|
|
|
925
998
|
))}
|
|
926
999
|
</div>
|
|
927
1000
|
)}
|
|
1001
|
+
{uncited.length > 0 && (
|
|
1002
|
+
<p className="reference-problem reference-uncited">
|
|
1003
|
+
{uncited
|
|
1004
|
+
.map((m) => t("Cites {key}, which no bibliography entry defines.", { key: m.key }))
|
|
1005
|
+
.join(language() === "zh-CN" ? "" : " ")}
|
|
1006
|
+
</p>
|
|
1007
|
+
)}
|
|
1008
|
+
{bib.length > 0 && (
|
|
1009
|
+
<ReferenceEntries
|
|
1010
|
+
entries={bib}
|
|
1011
|
+
checks={references.checks}
|
|
1012
|
+
pending={references.pending > 0}
|
|
1013
|
+
onRetry={references.retry}
|
|
1014
|
+
confirmed={references.confirmed}
|
|
1015
|
+
onConfirm={(id, on) =>
|
|
1016
|
+
void references.confirm(id, on).catch((e) => setToast((e as Error).message))
|
|
1017
|
+
}
|
|
1018
|
+
/>
|
|
1019
|
+
)}
|
|
928
1020
|
{source && (
|
|
929
1021
|
<div className="source">
|
|
930
1022
|
{known.map((unit) => (
|
|
@@ -1036,7 +1128,7 @@ export function ReviewApp({
|
|
|
1036
1128
|
{/* Batch decisions cover only undecided changes; accepted,
|
|
1037
1129
|
rejected and commented changes keep their state. A single
|
|
1038
1130
|
undecided change is decided from its own bar. */}
|
|
1039
|
-
{count.pending > 1 && (
|
|
1131
|
+
{count.pending > 1 && batch.length > 0 && (
|
|
1040
1132
|
<button
|
|
1041
1133
|
className="ghost accept accept-all"
|
|
1042
1134
|
disabled={
|
|
@@ -1047,30 +1139,41 @@ export function ReviewApp({
|
|
|
1047
1139
|
}
|
|
1048
1140
|
title={
|
|
1049
1141
|
state?.decisionLock ||
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1142
|
+
[
|
|
1143
|
+
changes
|
|
1144
|
+
? tn(
|
|
1145
|
+
batch.length,
|
|
1146
|
+
"Stage the {n} unstaged change.",
|
|
1147
|
+
"Stage the {n} unstaged changes.",
|
|
1148
|
+
)
|
|
1149
|
+
: tn(
|
|
1150
|
+
batch.length,
|
|
1151
|
+
"Accept {n} undecided change",
|
|
1152
|
+
"Accept {n} undecided changes",
|
|
1153
|
+
),
|
|
1154
|
+
undecided.length > batch.length
|
|
1155
|
+
? tn(
|
|
1156
|
+
undecided.length - batch.length,
|
|
1157
|
+
"Leaves {n} change with bibliography entries to check.",
|
|
1158
|
+
"Leaves {n} changes with bibliography entries to check.",
|
|
1159
|
+
)
|
|
1160
|
+
: "",
|
|
1161
|
+
]
|
|
1162
|
+
.filter(Boolean)
|
|
1163
|
+
.join(" ")
|
|
1061
1164
|
}
|
|
1062
1165
|
aria-label={
|
|
1063
1166
|
changes
|
|
1064
|
-
? t("Stage all ({n})", { n:
|
|
1065
|
-
: t("Accept all ({n})", { n:
|
|
1167
|
+
? t("Stage all ({n})", { n: batch.length })
|
|
1168
|
+
: t("Accept all ({n})", { n: batch.length })
|
|
1066
1169
|
}
|
|
1067
1170
|
aria-describedby={state?.decisionLock ? "review-lock" : undefined}
|
|
1068
|
-
onClick={() => void decide("accept",
|
|
1171
|
+
onClick={() => void decide("accept", batch)}
|
|
1069
1172
|
>
|
|
1070
1173
|
<span className="batch-label">
|
|
1071
1174
|
{changes ? t("Stage all") : t("Accept all")}
|
|
1072
1175
|
</span>
|
|
1073
|
-
<span className="batch-count">{
|
|
1176
|
+
<span className="batch-count">{batch.length}</span>
|
|
1074
1177
|
</button>
|
|
1075
1178
|
)}
|
|
1076
1179
|
{count.pending > 1 && !changes && (
|
|
@@ -1223,66 +1326,8 @@ export function ReviewApp({
|
|
|
1223
1326
|
)}
|
|
1224
1327
|
<div className="review-body">
|
|
1225
1328
|
<main id="paper" ref={paper} tabIndex={-1} onScroll={onScroll}>
|
|
1226
|
-
{(state?.commentLock || state?.decisionLock) && (
|
|
1227
|
-
<div className="banner" id="review-lock" role="status">
|
|
1228
|
-
{t((state.commentLock || state.decisionLock)!)}
|
|
1229
|
-
</div>
|
|
1230
|
-
)}
|
|
1231
|
-
{state?.closed && !state.commentLock && (
|
|
1232
|
-
<div className="banner">
|
|
1233
|
-
{t(
|
|
1234
|
-
"This review is finished. Accepted changes are in your manuscript.",
|
|
1235
|
-
)}
|
|
1236
|
-
</div>
|
|
1237
|
-
)}
|
|
1238
|
-
{state?.state === "build_failed" && (
|
|
1239
|
-
<div className="banner">
|
|
1240
|
-
<strong>
|
|
1241
|
-
{changes
|
|
1242
|
-
? t("The comparison could not be built: ")
|
|
1243
|
-
: t("The latest publish failed: ")}
|
|
1244
|
-
</strong>
|
|
1245
|
-
{state.job?.error}
|
|
1246
|
-
{state.job?.details && (
|
|
1247
|
-
<pre className="details">{state.job.details.join("\n")}</pre>
|
|
1248
|
-
)}
|
|
1249
|
-
</div>
|
|
1250
|
-
)}
|
|
1251
|
-
{state?.state === "building" && detail && !state.decisionLock && (
|
|
1252
|
-
<div className="banner">
|
|
1253
|
-
{changes
|
|
1254
|
-
? t(
|
|
1255
|
-
"Updating the comparison with your latest edits. You can keep staging and reverting meanwhile.",
|
|
1256
|
-
)
|
|
1257
|
-
: t(
|
|
1258
|
-
"Updating changes…",
|
|
1259
|
-
)}
|
|
1260
|
-
</div>
|
|
1261
|
-
)}
|
|
1262
|
-
{!changes && detail && state?.latest && detail.round !== state.latest && (
|
|
1263
|
-
<div className="banner">
|
|
1264
|
-
{t("Viewing earlier changes.")}{" "}
|
|
1265
|
-
<a href={withLanguage(`?task=${encodeURIComponent(task)}`)}>
|
|
1266
|
-
{t("Back to current changes →")}
|
|
1267
|
-
</a>
|
|
1268
|
-
</div>
|
|
1269
|
-
)}
|
|
1270
|
-
{detail?.warnings.length ? (
|
|
1271
|
-
<div className="banner">
|
|
1272
|
-
<details>
|
|
1273
|
-
<summary>
|
|
1274
|
-
{tn(
|
|
1275
|
-
detail.warnings.length,
|
|
1276
|
-
"{n} new LaTeX warning",
|
|
1277
|
-
"{n} new LaTeX warnings",
|
|
1278
|
-
)}
|
|
1279
|
-
</summary>
|
|
1280
|
-
<pre className="details">{detail.warnings.join("\n")}</pre>
|
|
1281
|
-
</details>
|
|
1282
|
-
</div>
|
|
1283
|
-
) : null}
|
|
1284
1329
|
{changes && detail && !count.pending && (count.accepted > 0 || !stops.length) && (
|
|
1285
|
-
<div className="review-complete" role="status">
|
|
1330
|
+
<div className="review-complete" role="status" tabIndex={-1}>
|
|
1286
1331
|
<strong>
|
|
1287
1332
|
{count.accepted
|
|
1288
1333
|
? t("All changes are staged")
|
|
@@ -1297,7 +1342,7 @@ export function ReviewApp({
|
|
|
1297
1342
|
</div>
|
|
1298
1343
|
)}
|
|
1299
1344
|
{!changes && detail && !history && !count.pending && (
|
|
1300
|
-
<div className="review-complete" role="status">
|
|
1345
|
+
<div className="review-complete" role="status" tabIndex={-1}>
|
|
1301
1346
|
<strong>
|
|
1302
1347
|
{t(
|
|
1303
1348
|
state?.workspace?.status === "waiting_input"
|
|
@@ -1343,6 +1388,77 @@ export function ReviewApp({
|
|
|
1343
1388
|
<div className="review-complete-actions" />
|
|
1344
1389
|
</div>
|
|
1345
1390
|
)}
|
|
1391
|
+
{(state?.commentLock || state?.decisionLock) && (
|
|
1392
|
+
<div className="banner" id="review-lock" role="status">
|
|
1393
|
+
{t((state.commentLock || state.decisionLock)!)}
|
|
1394
|
+
</div>
|
|
1395
|
+
)}
|
|
1396
|
+
{state?.closed && !state.commentLock && (
|
|
1397
|
+
<div className="banner">
|
|
1398
|
+
{t(
|
|
1399
|
+
"This review is finished. Accepted changes are in your manuscript.",
|
|
1400
|
+
)}
|
|
1401
|
+
</div>
|
|
1402
|
+
)}
|
|
1403
|
+
{state?.state === "build_failed" && (
|
|
1404
|
+
<div className="banner">
|
|
1405
|
+
<strong>
|
|
1406
|
+
{changes
|
|
1407
|
+
? t("The comparison could not be built: ")
|
|
1408
|
+
: t("The latest publish failed: ")}
|
|
1409
|
+
</strong>
|
|
1410
|
+
{state.job?.error}
|
|
1411
|
+
{state.job?.details && (
|
|
1412
|
+
<pre className="details">{state.job.details.join("\n")}</pre>
|
|
1413
|
+
)}
|
|
1414
|
+
</div>
|
|
1415
|
+
)}
|
|
1416
|
+
{state?.state === "building" && detail && !state.decisionLock && (
|
|
1417
|
+
<div className="banner">
|
|
1418
|
+
{changes
|
|
1419
|
+
? t(
|
|
1420
|
+
"Updating the comparison with your latest edits. You can keep staging and reverting meanwhile.",
|
|
1421
|
+
)
|
|
1422
|
+
: t(
|
|
1423
|
+
"Updating changes…",
|
|
1424
|
+
)}
|
|
1425
|
+
</div>
|
|
1426
|
+
)}
|
|
1427
|
+
{!changes && detail && state?.latest && detail.round !== state.latest && (
|
|
1428
|
+
<div className="banner">
|
|
1429
|
+
{t("Viewing earlier changes.")}{" "}
|
|
1430
|
+
<a href={withLanguage(`?task=${encodeURIComponent(task)}`)}>
|
|
1431
|
+
{t("Back to current changes →")}
|
|
1432
|
+
</a>
|
|
1433
|
+
</div>
|
|
1434
|
+
)}
|
|
1435
|
+
{detail?.warnings.length ? (
|
|
1436
|
+
<div className="banner">
|
|
1437
|
+
<details>
|
|
1438
|
+
<summary>
|
|
1439
|
+
{tn(
|
|
1440
|
+
detail.warnings.length,
|
|
1441
|
+
"{n} new LaTeX warning",
|
|
1442
|
+
"{n} new LaTeX warnings",
|
|
1443
|
+
)}
|
|
1444
|
+
</summary>
|
|
1445
|
+
<pre className="details">{detail.warnings.join("\n")}</pre>
|
|
1446
|
+
</details>
|
|
1447
|
+
</div>
|
|
1448
|
+
) : null}
|
|
1449
|
+
{detail?.references &&
|
|
1450
|
+
(detail.references.entries.length > 0 ||
|
|
1451
|
+
detail.references.missing.length > 0) && (
|
|
1452
|
+
<ReferenceBanner
|
|
1453
|
+
references={detail.references}
|
|
1454
|
+
checks={references.checks}
|
|
1455
|
+
pending={references.pending}
|
|
1456
|
+
confirmed={references.confirmed}
|
|
1457
|
+
changeOf={(unit) => (unit ? (firstStop.get(unit) ?? -1) : -1)}
|
|
1458
|
+
onSelect={(index) => select(index)}
|
|
1459
|
+
onRetry={references.retry}
|
|
1460
|
+
/>
|
|
1461
|
+
)}
|
|
1346
1462
|
<div className="document">
|
|
1347
1463
|
{detail ? (
|
|
1348
1464
|
<>
|
|
@@ -1463,6 +1579,18 @@ export function ReviewApp({
|
|
|
1463
1579
|
{toast}
|
|
1464
1580
|
</div>
|
|
1465
1581
|
)}
|
|
1582
|
+
{confirm && (
|
|
1583
|
+
<ConfirmReferences
|
|
1584
|
+
items={confirm.items}
|
|
1585
|
+
staging={changes}
|
|
1586
|
+
onCancel={() => setConfirm(null)}
|
|
1587
|
+
onConfirm={() => {
|
|
1588
|
+
const chosen = confirm;
|
|
1589
|
+
setConfirm(null);
|
|
1590
|
+
void decide(chosen.kind, chosen.chosen, chosen.body, true);
|
|
1591
|
+
}}
|
|
1592
|
+
/>
|
|
1593
|
+
)}
|
|
1466
1594
|
{help && (
|
|
1467
1595
|
<div className="help-backdrop" onClick={() => setHelp(false)}>
|
|
1468
1596
|
<dialog
|