papilot 0.8.2 → 0.8.3

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.
Files changed (115) hide show
  1. package/README.md +2 -2
  2. package/bin/doctor.mjs +1 -1
  3. package/dist/assets/{_virtual_papilot-licenses-CMfCtEYQ.js → _virtual_papilot-licenses-YJbh9c1G.js} +1 -1
  4. package/dist/assets/index-0siHHtfz.js +267 -0
  5. package/dist/assets/index-CxdYm81a.css +1 -0
  6. package/dist/assets/{math-CEU4m4-H.js → math-ibBpTslk.js} +1 -1
  7. package/dist/assets/{pdf-view-HWRGBLsZ.js → pdf-view-CD1kNKYH.js} +1 -1
  8. package/dist/index.html +2 -2
  9. package/engine/dist/web/assets/FullPaper-DUgLb01O.js +54 -0
  10. package/engine/dist/web/assets/index-D3_ZFdT8.css +1 -0
  11. package/engine/dist/web/assets/index-DJywe0wU.js +19 -0
  12. package/engine/dist/web/assets/pdf.worker.min-CjEcRF4W.mjs +28 -0
  13. package/{vendor/latex-review → engine}/dist/web/index.html +2 -2
  14. package/engine/package-lock.json +3390 -0
  15. package/engine/package.json +50 -0
  16. package/{vendor/latex-review → engine}/src/core/build.ts +22 -6
  17. package/engine/src/core/progress.ts +67 -0
  18. package/{vendor/latex-review → engine}/src/core/review.ts +32 -0
  19. package/{vendor/latex-review → engine}/src/render/inline.ts +1 -0
  20. package/{vendor/latex-review → engine}/src/render/locate.ts +26 -16
  21. package/engine/src/render/positions.ts +34 -0
  22. package/engine/src/render/synctex.ts +188 -0
  23. package/engine/src/render/synctex.wasm +0 -0
  24. package/engine/src/render/synctex.wasm.LICENSE +50 -0
  25. package/{vendor/latex-review → engine}/src/server.ts +5 -3
  26. package/{vendor/latex-review → engine}/src/types.ts +13 -0
  27. package/engine/src/web/FullPaper.tsx +318 -0
  28. package/{vendor/latex-review → engine}/src/web/locales/de.json +3 -0
  29. package/{vendor/latex-review → engine}/src/web/locales/es.json +3 -0
  30. package/{vendor/latex-review → engine}/src/web/locales/fr.json +3 -0
  31. package/{vendor/latex-review → engine}/src/web/locales/it.json +3 -0
  32. package/{vendor/latex-review → engine}/src/web/locales/ja.json +3 -0
  33. package/{vendor/latex-review → engine}/src/web/locales/ko.json +3 -0
  34. package/{vendor/latex-review → engine}/src/web/locales/pt.json +3 -0
  35. package/{vendor/latex-review → engine}/src/web/locales/sv.json +3 -0
  36. package/{vendor/latex-review → engine}/src/web/locales/zh-Hant.json +3 -0
  37. package/engine/src/web/paper.ts +13 -0
  38. package/{vendor/latex-review → engine}/src/web/review.tsx +98 -1
  39. package/{vendor/latex-review → engine}/src/web/style.css +70 -0
  40. package/engine/src/web/view-preferences.ts +13 -0
  41. package/{vendor/latex-review → engine}/src/web/zh-CN.ts +3 -0
  42. package/package.json +10 -8
  43. package/src/conversation.ts +1 -1
  44. package/src/server/app.ts +16 -0
  45. package/src/server/changes.ts +12 -3
  46. package/src/server/imports.ts +9 -4
  47. package/src/server/main.ts +3 -2
  48. package/src/server/manuscript.ts +5 -4
  49. package/src/server/paths.ts +1 -1
  50. package/src/server/start.ts +3 -10
  51. package/src/server/update-install.ts +8 -25
  52. package/src/server/updates.ts +43 -25
  53. package/src/shared.ts +6 -3
  54. package/vendor/latex-review/package-lock.json +4 -3106
  55. package/vendor/latex-review/package.json +3 -46
  56. package/README.de.md +0 -110
  57. package/README.es.md +0 -110
  58. package/README.fr.md +0 -110
  59. package/README.it.md +0 -110
  60. package/README.ja.md +0 -110
  61. package/README.ko.md +0 -110
  62. package/README.pt.md +0 -110
  63. package/README.sv.md +0 -110
  64. package/README.zh-CN.md +0 -110
  65. package/README.zh-Hant.md +0 -110
  66. package/dist/assets/index-oAIkxhfp.js +0 -265
  67. package/dist/assets/index-t_qCOF_B.css +0 -1
  68. package/vendor/latex-review/dist/web/assets/index-DVs7cTBl.js +0 -19
  69. package/vendor/latex-review/dist/web/assets/index-ysOpu05D.css +0 -1
  70. /package/{vendor/latex-review → engine}/LICENSE +0 -0
  71. /package/{vendor/latex-review → engine}/dist/web/assets/TeXGyrePagellaX-Bold-CEtYdOXj.otf +0 -0
  72. /package/{vendor/latex-review → engine}/dist/web/assets/TeXGyrePagellaX-Regular-DQJjZE_L.otf +0 -0
  73. /package/{vendor/latex-review → engine}/src/cli.ts +0 -0
  74. /package/{vendor/latex-review → engine}/src/client.ts +0 -0
  75. /package/{vendor/latex-review → engine}/src/core/bib.ts +0 -0
  76. /package/{vendor/latex-review → engine}/src/core/changes.ts +0 -0
  77. /package/{vendor/latex-review → engine}/src/core/config.ts +0 -0
  78. /package/{vendor/latex-review → engine}/src/core/current-commit.ts +0 -0
  79. /package/{vendor/latex-review → engine}/src/core/delivery.ts +0 -0
  80. /package/{vendor/latex-review → engine}/src/core/diff.ts +0 -0
  81. /package/{vendor/latex-review → engine}/src/core/files.ts +0 -0
  82. /package/{vendor/latex-review → engine}/src/core/git.ts +0 -0
  83. /package/{vendor/latex-review → engine}/src/core/outline.ts +0 -0
  84. /package/{vendor/latex-review → engine}/src/core/process.ts +0 -0
  85. /package/{vendor/latex-review → engine}/src/core/publish.ts +0 -0
  86. /package/{vendor/latex-review → engine}/src/core/reconcile.ts +0 -0
  87. /package/{vendor/latex-review → engine}/src/core/store.ts +0 -0
  88. /package/{vendor/latex-review → engine}/src/core/tasks.ts +0 -0
  89. /package/{vendor/latex-review → engine}/src/core/verify.ts +0 -0
  90. /package/{vendor/latex-review → engine}/src/core/view.ts +0 -0
  91. /package/{vendor/latex-review → engine}/src/locales.ts +0 -0
  92. /package/{vendor/latex-review → engine}/src/render/effects.ts +0 -0
  93. /package/{vendor/latex-review → engine}/src/render/pdf.ts +0 -0
  94. /package/{vendor/latex-review → engine}/src/render/pdfdiff.ts +0 -0
  95. /package/{vendor/latex-review → engine}/src/render/round.ts +0 -0
  96. /package/{vendor/latex-review → engine}/src/render/worker.ts +0 -0
  97. /package/{vendor/latex-review → engine}/src/runtime.ts +0 -0
  98. /package/{vendor/latex-review → engine}/src/web/ChangeMap.tsx +0 -0
  99. /package/{vendor/latex-review → engine}/src/web/PdfStrip.tsx +0 -0
  100. /package/{vendor/latex-review → engine}/src/web/effect-preferences.ts +0 -0
  101. /package/{vendor/latex-review → engine}/src/web/fonts/GUST-FONT-LICENSE.txt +0 -0
  102. /package/{vendor/latex-review → engine}/src/web/fonts/LPPL.txt +0 -0
  103. /package/{vendor/latex-review → engine}/src/web/fonts/MANIFEST-newpx.txt +0 -0
  104. /package/{vendor/latex-review → engine}/src/web/fonts/README-newpx.txt +0 -0
  105. /package/{vendor/latex-review → engine}/src/web/fonts/README.md +0 -0
  106. /package/{vendor/latex-review → engine}/src/web/fonts/TeXGyrePagellaX-Bold.otf +0 -0
  107. /package/{vendor/latex-review → engine}/src/web/fonts/TeXGyrePagellaX-Regular.otf +0 -0
  108. /package/{vendor/latex-review → engine}/src/web/i18n.ts +0 -0
  109. /package/{vendor/latex-review → engine}/src/web/index.html +0 -0
  110. /package/{vendor/latex-review → engine}/src/web/locales/index.ts +0 -0
  111. /package/{vendor/latex-review → engine}/src/web/main.tsx +0 -0
  112. /package/{vendor/latex-review → engine}/src/web/model.ts +0 -0
  113. /package/{vendor/latex-review → engine}/src/web/overview.tsx +0 -0
  114. /package/{vendor/latex-review → engine}/src/web/references.tsx +0 -0
  115. /package/{vendor/latex-review → engine}/src/web/shortcuts.ts +0 -0
@@ -0,0 +1,318 @@
1
+ import {
2
+ Fragment,
3
+ useEffect,
4
+ useMemo,
5
+ useRef,
6
+ useState,
7
+ type ReactNode,
8
+ type RefObject,
9
+ } from "react";
10
+ import * as pdfjs from "pdfjs-dist";
11
+ import type { PDFDocumentProxy, RenderTask } from "pdfjs-dist";
12
+ import worker from "pdfjs-dist/build/pdf.worker.min.mjs?url";
13
+ import type { ChangeBlock, Strip } from "../types.ts";
14
+ import { t } from "./i18n.ts";
15
+ import type { PlacedChange } from "./paper.ts";
16
+
17
+ // Built assets share one folder, and a host such as Papilot serves the viewer
18
+ // under its own prefix: find the worker next to this chunk, not at /assets.
19
+ pdfjs.GlobalWorkerOptions.workerSrc = import.meta.env.DEV
20
+ ? worker
21
+ : new URL(worker.split("/").pop()!, import.meta.url).href;
22
+
23
+ // The whole paper with its changes in place: the new PDF's pages, opened up
24
+ // where a change's new lines are, and that change shown there as in the
25
+ // changes view, old lines above new. Pages are painted only near the
26
+ // viewport, so a long thesis scrolls like a short paper.
27
+ interface Cut {
28
+ top: number;
29
+ bottom: number;
30
+ // Changes shown at this cut: the one whose new lines start here, and
31
+ // changes without new lines of their own that follow it.
32
+ shown: PlacedChange[];
33
+ }
34
+ interface Paper {
35
+ pdf: PDFDocumentProxy;
36
+ width: number;
37
+ height: number;
38
+ pages: Cut[][];
39
+ before: PlacedChange[];
40
+ }
41
+
42
+ // The page of this PDF that shows a strip of the round's PDF. A round may
43
+ // compile only the changed chapters, so pages are matched by their labels,
44
+ // moving forward through the paper.
45
+ function locator(count: number, labels: string[] | null) {
46
+ let cursor = 0;
47
+ return (strip: Strip) => {
48
+ if (!labels) return strip.page - 1 < count ? strip.page - 1 : -1;
49
+ for (const from of [cursor, 0]) {
50
+ const found = labels.indexOf(strip.label, from);
51
+ if (found >= 0) return (cursor = found);
52
+ }
53
+ return -1;
54
+ };
55
+ }
56
+
57
+ function plan(count: number, labels: string[] | null, changes: PlacedChange[]) {
58
+ const pages: Cut[][] = Array.from({ length: count }, () => []),
59
+ before: PlacedChange[] = [],
60
+ find = locator(count, labels);
61
+ let last: Cut | null = null;
62
+ for (const change of changes) {
63
+ const spots = change.block.after.map((strip) => ({ strip, page: strip.top === undefined ? -1 : find(strip) }));
64
+ if (!spots.length || spots.some((s) => s.page < 0)) {
65
+ // No new lines here (a deletion), or none this PDF has: after the
66
+ // previous change, or before the paper when it is the first.
67
+ if (last) last.shown.push(change);
68
+ else before.push(change);
69
+ continue;
70
+ }
71
+ for (const [k, { strip, page }] of spots.entries()) {
72
+ const cut: Cut = { top: strip.top!, bottom: strip.top! + strip.height, shown: k ? [] : [change] };
73
+ pages[page].push(cut);
74
+ if (!k) last = cut;
75
+ }
76
+ }
77
+ for (const cuts of pages) {
78
+ cuts.sort((a, b) => a.top - b.top);
79
+ for (let i = 1; i < cuts.length; i++) {
80
+ cuts[i].top = Math.max(cuts[i].top, cuts[i - 1].bottom);
81
+ cuts[i].bottom = Math.max(cuts[i].bottom, cuts[i].top);
82
+ }
83
+ }
84
+ return { pages, before };
85
+ }
86
+
87
+ const clock = (ms: number) => {
88
+ const seconds = Math.max(0, Math.floor(ms / 1000));
89
+ return `${Math.floor(seconds / 60)}:${String(seconds % 60).padStart(2, "0")}`;
90
+ };
91
+
92
+ export function FullPaper({
93
+ url,
94
+ changes,
95
+ render,
96
+ scrollRoot,
97
+ onReady,
98
+ onUnavailable,
99
+ }: {
100
+ url: string;
101
+ changes: PlacedChange[];
102
+ render: (block: ChangeBlock, index: number) => ReactNode;
103
+ scrollRoot: RefObject<HTMLElement | null>;
104
+ onReady: () => void;
105
+ onUnavailable: () => void;
106
+ }) {
107
+ const [loaded, setLoaded] = useState<{ pdf: PDFDocumentProxy; width: number; height: number; labels: string[] | null } | null>(null),
108
+ [error, setError] = useState(""),
109
+ [started] = useState(() => Date.now()),
110
+ [now, setNow] = useState(() => Date.now());
111
+ useEffect(() => {
112
+ let live = true;
113
+ setLoaded(null);
114
+ setError("");
115
+ const task = pdfjs.getDocument({ url, enableXfa: false });
116
+ task.promise.then(
117
+ async (pdf) => {
118
+ const first = (await pdf.getPage(1)).getViewport({ scale: 1 }),
119
+ labels = await pdf.getPageLabels().catch(() => null);
120
+ if (live) setLoaded({ pdf, width: first.width, height: first.height, labels });
121
+ },
122
+ (failure: Error) => {
123
+ if (live) setError(failure.message);
124
+ },
125
+ );
126
+ return () => {
127
+ live = false;
128
+ void task.destroy();
129
+ };
130
+ }, [url]);
131
+ useEffect(() => {
132
+ if (loaded || error) return;
133
+ const timer = setInterval(() => setNow(Date.now()), 1000);
134
+ return () => clearInterval(timer);
135
+ }, [loaded, error]);
136
+ const paper = useMemo<Paper | null>(
137
+ () =>
138
+ loaded && {
139
+ pdf: loaded.pdf,
140
+ width: loaded.width,
141
+ height: loaded.height,
142
+ ...plan(loaded.pdf.numPages, loaded.labels, changes),
143
+ },
144
+ [loaded, changes],
145
+ );
146
+ if (error)
147
+ return (
148
+ <div className="paper-status" role="alert">
149
+ <p>{t("The full PDF could not be loaded.")}</p>
150
+ <button className="ghost" onClick={onUnavailable}>
151
+ {t("Changes")}
152
+ </button>
153
+ </div>
154
+ );
155
+ if (!paper)
156
+ return (
157
+ <div className="paper-status" role="status">
158
+ <div className="pulse" />
159
+ <p>
160
+ {t("Preparing the full PDF…")} <span className="paper-clock">{clock(now - started)}</span>
161
+ </p>
162
+ </div>
163
+ );
164
+ return (
165
+ <Pages paper={paper} render={render} scrollRoot={scrollRoot} onReady={onReady} />
166
+ );
167
+ }
168
+
169
+ function Pages({
170
+ paper,
171
+ render,
172
+ scrollRoot,
173
+ onReady,
174
+ }: {
175
+ paper: Paper;
176
+ render: (block: ChangeBlock, index: number) => ReactNode;
177
+ scrollRoot: RefObject<HTMLElement | null>;
178
+ onReady: () => void;
179
+ }) {
180
+ const box = useRef<HTMLDivElement>(null),
181
+ ready = useRef(onReady);
182
+ ready.current = onReady;
183
+ useEffect(() => paint(box.current!, paper, scrollRoot.current), [paper, scrollRoot]);
184
+ // The pages exist now: the selected change can be scrolled to.
185
+ useEffect(() => ready.current(), [paper]);
186
+ const slice = (page: number, top: number, bottom: number) =>
187
+ bottom - top > 0.5 && (
188
+ <div className="paper-context" key={`${page}:${top}`}>
189
+ <div
190
+ className="paper-slice"
191
+ data-top={top}
192
+ data-bottom={bottom}
193
+ style={{ aspectRatio: `${paper.width} / ${bottom - top}` }}
194
+ />
195
+ </div>
196
+ );
197
+ return (
198
+ <div className="paper" ref={box}>
199
+ {paper.before.map((c) => (
200
+ <Fragment key={c.index}>{render(c.block, c.index)}</Fragment>
201
+ ))}
202
+ {paper.pages.map((cuts, page) => {
203
+ let cursor = 0;
204
+ const parts: ReactNode[] = [];
205
+ for (const cut of cuts) {
206
+ parts.push(slice(page, cursor, cut.top));
207
+ for (const c of cut.shown) parts.push(<Fragment key={c.index}>{render(c.block, c.index)}</Fragment>);
208
+ cursor = cut.bottom;
209
+ }
210
+ parts.push(slice(page, cursor, paper.height));
211
+ return (
212
+ <section className="paper-page" data-page={page} key={page}>
213
+ {parts}
214
+ </section>
215
+ );
216
+ })}
217
+ </div>
218
+ );
219
+ }
220
+
221
+ // Paints each page into its slices while it is within about a screen and a
222
+ // half of the viewport, and frees the canvases again further away.
223
+ function paint(container: HTMLElement, paper: Paper, root: HTMLElement | null) {
224
+ const alive = new Map<number, { canvases: HTMLCanvasElement[]; task?: RenderTask }>(),
225
+ wanted = new Set<number>();
226
+ let running = 0,
227
+ width = container.clientWidth,
228
+ stopped = false;
229
+ const pages = () => Array.from(container.querySelectorAll<HTMLElement>(".paper-page"));
230
+ const release = (index: number) => {
231
+ const entry = alive.get(index);
232
+ if (!entry) return;
233
+ alive.delete(index);
234
+ entry.task?.cancel();
235
+ for (const canvas of entry.canvases) {
236
+ canvas.remove();
237
+ canvas.width = canvas.height = 0;
238
+ }
239
+ };
240
+ const pump = () => {
241
+ while (!stopped && running < 2) {
242
+ const middle = (root?.scrollTop ?? 0) + (root?.clientHeight ?? 0) / 2,
243
+ list = pages(),
244
+ next = [...wanted]
245
+ .filter((i) => !alive.has(i))
246
+ .sort((a, b) => Math.abs(list[a].offsetTop - middle) - Math.abs(list[b].offsetTop - middle))[0];
247
+ if (next === undefined) return;
248
+ void draw(next, list[next]);
249
+ }
250
+ };
251
+ const draw = async (index: number, element: HTMLElement) => {
252
+ running++;
253
+ const entry: { canvases: HTMLCanvasElement[]; task?: RenderTask } = { canvases: [] };
254
+ alive.set(index, entry);
255
+ try {
256
+ const page = await paper.pdf.getPage(index + 1);
257
+ if (alive.get(index) !== entry) return;
258
+ const slices = Array.from(element.querySelectorAll<HTMLElement>(".paper-slice")),
259
+ cssWidth = slices[0]?.clientWidth || element.clientWidth,
260
+ size = page.getViewport({ scale: 1 }),
261
+ scale = (cssWidth * Math.min(window.devicePixelRatio || 1, 3)) / size.width,
262
+ viewport = page.getViewport({ scale }),
263
+ source = document.createElement("canvas");
264
+ source.width = Math.floor(viewport.width);
265
+ source.height = Math.floor(viewport.height);
266
+ entry.task = page.render({ canvas: source, viewport });
267
+ await entry.task.promise;
268
+ if (alive.get(index) !== entry) return;
269
+ for (const slice of slices) {
270
+ const top = Math.round(Number(slice.dataset.top) * scale),
271
+ bottom = Math.min(source.height, Math.round(Number(slice.dataset.bottom) * scale));
272
+ if (bottom - top < 1) continue;
273
+ const canvas = document.createElement("canvas");
274
+ canvas.width = source.width;
275
+ canvas.height = bottom - top;
276
+ canvas.getContext("2d")!.drawImage(source, 0, top, source.width, bottom - top, 0, 0, source.width, bottom - top);
277
+ slice.append(canvas);
278
+ entry.canvases.push(canvas);
279
+ }
280
+ source.width = source.height = 0;
281
+ } catch (failure) {
282
+ if ((failure as Error)?.name !== "RenderingCancelledException") console.error(failure);
283
+ if (alive.get(index) === entry) alive.delete(index);
284
+ } finally {
285
+ running--;
286
+ pump();
287
+ }
288
+ };
289
+ const observer = new IntersectionObserver(
290
+ (entries) => {
291
+ for (const entry of entries) {
292
+ const index = Number((entry.target as HTMLElement).dataset.page);
293
+ if (entry.isIntersecting) wanted.add(index);
294
+ else {
295
+ wanted.delete(index);
296
+ release(index);
297
+ }
298
+ }
299
+ pump();
300
+ },
301
+ { root, rootMargin: "1200px 0px" },
302
+ );
303
+ pages().forEach((page) => observer.observe(page));
304
+ // A wider column needs sharper pages.
305
+ const resized = new ResizeObserver(() => {
306
+ if (Math.abs(container.clientWidth - width) < 1) return;
307
+ width = container.clientWidth;
308
+ for (const index of [...alive.keys()]) release(index);
309
+ pump();
310
+ });
311
+ resized.observe(container);
312
+ return () => {
313
+ stopped = true;
314
+ observer.disconnect();
315
+ resized.disconnect();
316
+ for (const index of [...alive.keys()]) release(index);
317
+ };
318
+ }
@@ -37,6 +37,7 @@
37
37
  "Change {list}": "Änderung {list}",
38
38
  "Change {n}": "Änderung {n}",
39
39
  "Changed entry": "Geänderter Eintrag",
40
+ "Changes": "Änderungen",
40
41
  "Changes are ready": "Änderungen sind bereit",
41
42
  "Changes {list}": "Änderungen {list}",
42
43
  "Check again": "Erneut prüfen",
@@ -120,6 +121,7 @@
120
121
  "Partly staged": "Teilweise vorgemerkt",
121
122
  "Pause this task in Papilot before accepting, rejecting, or undoing changes. You can still add comments.": "Pausieren Sie diese Aufgabe in Papilot, bevor Sie Änderungen annehmen, ablehnen oder rückgängig machen. Sie können weiterhin Kommentare hinzufügen.",
122
123
  "Pending": "Ausstehend",
124
+ "Preparing the full PDF…": "Vollständiges PDF wird vorbereitet…",
123
125
  "Preparing the next review…": "Nächste Überprüfung wird vorbereitet…",
124
126
  "Previous change": "Vorherige Änderung",
125
127
  "Previous change (K or ←)": "Vorherige Änderung (K oder ←)",
@@ -176,6 +178,7 @@
176
178
  "The PDF matches the last commit.": "Das PDF stimmt mit dem letzten Commit überein.",
177
179
  "The comparison could not be built: ": "Der Vergleich konnte nicht erstellt werden: ",
178
180
  "The entry has no title, DOI or arXiv identifier to look up.": "Der Eintrag enthält weder Titel noch DOI oder arXiv-Kennung für die Suche.",
181
+ "The full PDF could not be loaded.": "Das vollständige PDF konnte nicht geladen werden.",
179
182
  "The last commit changed. The comparison is being updated.": "Der letzte Commit hat sich geändert. Der Vergleich wird aktualisiert.",
180
183
  "The latest publish failed: ": "Die letzte Veröffentlichung ist fehlgeschlagen: ",
181
184
  "The paper does not use this .bib file, so the entry will not appear.": "Der Artikel verwendet diese .bib-Datei nicht, daher wird der Eintrag nicht angezeigt.",
@@ -37,6 +37,7 @@
37
37
  "Change {list}": "Cambio {list}",
38
38
  "Change {n}": "Cambio {n}",
39
39
  "Changed entry": "Entrada modificada",
40
+ "Changes": "Cambios",
40
41
  "Changes are ready": "Los cambios están listos",
41
42
  "Changes {list}": "Cambios {list}",
42
43
  "Check again": "Comprobar de nuevo",
@@ -120,6 +121,7 @@
120
121
  "Partly staged": "Parcialmente preparado",
121
122
  "Pause this task in Papilot before accepting, rejecting, or undoing changes. You can still add comments.": "Pausa esta tarea en Papilot antes de aceptar, rechazar o deshacer cambios. Aún puedes añadir comentarios.",
122
123
  "Pending": "Pendiente",
124
+ "Preparing the full PDF…": "Preparando el PDF completo…",
123
125
  "Preparing the next review…": "Preparando la siguiente revisión…",
124
126
  "Previous change": "Cambio anterior",
125
127
  "Previous change (K or ←)": "Cambio anterior (K o ←)",
@@ -176,6 +178,7 @@
176
178
  "The PDF matches the last commit.": "El PDF coincide con la última confirmación.",
177
179
  "The comparison could not be built: ": "No se pudo generar la comparación: ",
178
180
  "The entry has no title, DOI or arXiv identifier to look up.": "La entrada no tiene título, DOI ni identificador de arXiv para buscar.",
181
+ "The full PDF could not be loaded.": "No se pudo cargar el PDF completo.",
179
182
  "The last commit changed. The comparison is being updated.": "La última confirmación cambió. La comparación se está actualizando.",
180
183
  "The latest publish failed: ": "Error en la última publicación: ",
181
184
  "The paper does not use this .bib file, so the entry will not appear.": "El artículo no usa este archivo .bib, por lo que la entrada no aparecerá.",
@@ -37,6 +37,7 @@
37
37
  "Change {list}": "Modification {list}",
38
38
  "Change {n}": "Modification {n}",
39
39
  "Changed entry": "Entrée modifiée",
40
+ "Changes": "Modifications",
40
41
  "Changes are ready": "Les modifications sont prêtes",
41
42
  "Changes {list}": "Modifications {list}",
42
43
  "Check again": "Vérifier à nouveau",
@@ -120,6 +121,7 @@
120
121
  "Partly staged": "Partiellement indexé",
121
122
  "Pause this task in Papilot before accepting, rejecting, or undoing changes. You can still add comments.": "Mettez cette tâche en pause dans Papilot avant d’accepter, de refuser ou d’annuler des modifications. Vous pouvez toujours ajouter des commentaires.",
122
123
  "Pending": "En attente",
124
+ "Preparing the full PDF…": "Préparation du PDF complet…",
123
125
  "Preparing the next review…": "Préparation de la prochaine révision…",
124
126
  "Previous change": "Modification précédente",
125
127
  "Previous change (K or ←)": "Modification précédente (K ou ←)",
@@ -176,6 +178,7 @@
176
178
  "The PDF matches the last commit.": "Le PDF correspond au dernier commit.",
177
179
  "The comparison could not be built: ": "La comparaison n’a pas pu être générée : ",
178
180
  "The entry has no title, DOI or arXiv identifier to look up.": "L’entrée n’a ni titre, ni DOI, ni identifiant arXiv à rechercher.",
181
+ "The full PDF could not be loaded.": "Impossible de charger le PDF complet.",
179
182
  "The last commit changed. The comparison is being updated.": "Le dernier commit a changé. La comparaison est en cours de mise à jour.",
180
183
  "The latest publish failed: ": "La dernière publication a échoué : ",
181
184
  "The paper does not use this .bib file, so the entry will not appear.": "L’article n’utilise pas ce fichier .bib ; l’entrée n’apparaîtra donc pas.",
@@ -37,6 +37,7 @@
37
37
  "Change {list}": "Modifica {list}",
38
38
  "Change {n}": "Modifica {n}",
39
39
  "Changed entry": "Voce modificata",
40
+ "Changes": "Modifiche",
40
41
  "Changes are ready": "Le modifiche sono pronte",
41
42
  "Changes {list}": "Modifiche {list}",
42
43
  "Check again": "Controlla di nuovo",
@@ -120,6 +121,7 @@
120
121
  "Partly staged": "Parzialmente preparato",
121
122
  "Pause this task in Papilot before accepting, rejecting, or undoing changes. You can still add comments.": "Metti in pausa questa attività in Papilot prima di accettare, rifiutare o annullare le modifiche. Puoi comunque aggiungere commenti.",
122
123
  "Pending": "In sospeso",
124
+ "Preparing the full PDF…": "Preparazione del PDF completo…",
123
125
  "Preparing the next review…": "Preparazione della prossima revisione…",
124
126
  "Previous change": "Modifica precedente",
125
127
  "Previous change (K or ←)": "Modifica precedente (K o ←)",
@@ -176,6 +178,7 @@
176
178
  "The PDF matches the last commit.": "Il PDF corrisponde all'ultimo commit.",
177
179
  "The comparison could not be built: ": "Impossibile generare il confronto: ",
178
180
  "The entry has no title, DOI or arXiv identifier to look up.": "La voce non ha titolo, DOI o identificatore arXiv da cercare.",
181
+ "The full PDF could not be loaded.": "Impossibile caricare il PDF completo.",
179
182
  "The last commit changed. The comparison is being updated.": "L'ultimo commit è cambiato. Il confronto è in fase di aggiornamento.",
180
183
  "The latest publish failed: ": "L'ultima pubblicazione non è riuscita: ",
181
184
  "The paper does not use this .bib file, so the entry will not appear.": "L'articolo non usa questo file .bib, quindi la voce non apparirà.",
@@ -37,6 +37,7 @@
37
37
  "Change {list}": "変更 {list}",
38
38
  "Change {n}": "変更 {n}",
39
39
  "Changed entry": "変更されたエントリ",
40
+ "Changes": "変更",
40
41
  "Changes are ready": "変更の準備が完了しました",
41
42
  "Changes {list}": "変更 {list}",
42
43
  "Check again": "再確認",
@@ -120,6 +121,7 @@
120
121
  "Partly staged": "一部ステージ済み",
121
122
  "Pause this task in Papilot before accepting, rejecting, or undoing changes. You can still add comments.": "変更の承認、却下、または取り消しを行う前に、Papilotでこのタスクを一時停止してください。コメントの追加は引き続き可能です。",
122
123
  "Pending": "保留中",
124
+ "Preparing the full PDF…": "PDF全体を準備中…",
123
125
  "Preparing the next review…": "次のレビューを準備中…",
124
126
  "Previous change": "前の変更",
125
127
  "Previous change (K or ←)": "前の変更 (K または ←)",
@@ -176,6 +178,7 @@
176
178
  "The PDF matches the last commit.": "PDF は最新のコミットと一致しています。",
177
179
  "The comparison could not be built: ": "比較を作成できませんでした: ",
178
180
  "The entry has no title, DOI or arXiv identifier to look up.": "このエントリには検索用のタイトル、DOI、または arXiv 識別子がありません。",
181
+ "The full PDF could not be loaded.": "PDF全体を読み込めませんでした。",
179
182
  "The last commit changed. The comparison is being updated.": "最新のコミットが変更されました。比較を更新しています。",
180
183
  "The latest publish failed: ": "最新の公開に失敗しました: ",
181
184
  "The paper does not use this .bib file, so the entry will not appear.": "論文でこの .bib ファイルが使用されていないため、このエントリは表示されません。",
@@ -37,6 +37,7 @@
37
37
  "Change {list}": "변경사항 {list}",
38
38
  "Change {n}": "변경사항 {n}",
39
39
  "Changed entry": "변경된 항목",
40
+ "Changes": "변경 사항",
40
41
  "Changes are ready": "변경사항 준비 완료",
41
42
  "Changes {list}": "변경사항 {list}",
42
43
  "Check again": "다시 확인",
@@ -120,6 +121,7 @@
120
121
  "Partly staged": "일부 스테이징됨",
121
122
  "Pause this task in Papilot before accepting, rejecting, or undoing changes. You can still add comments.": "변경 사항을 수락, 거부 또는 실행 취소하기 전에 Papilot에서 이 작업을 일시 중지하세요. 의견은 계속 추가할 수 있습니다.",
122
123
  "Pending": "보류 중",
124
+ "Preparing the full PDF…": "전체 PDF를 준비하는 중…",
123
125
  "Preparing the next review…": "다음 검토 준비 중…",
124
126
  "Previous change": "이전 변경사항",
125
127
  "Previous change (K or ←)": "이전 변경사항 (K 또는 ←)",
@@ -176,6 +178,7 @@
176
178
  "The PDF matches the last commit.": "PDF가 마지막 커밋과 일치합니다.",
177
179
  "The comparison could not be built: ": "비교를 생성할 수 없습니다: ",
178
180
  "The entry has no title, DOI or arXiv identifier to look up.": "항목에 조회할 제목, DOI 또는 arXiv 식별자가 없습니다.",
181
+ "The full PDF could not be loaded.": "전체 PDF를 불러올 수 없습니다.",
179
182
  "The last commit changed. The comparison is being updated.": "마지막 커밋이 변경되었습니다. 비교를 업데이트하는 중입니다.",
180
183
  "The latest publish failed: ": "최근 게시 실패: ",
181
184
  "The paper does not use this .bib file, so the entry will not appear.": "논문에서 이 .bib 파일을 사용하지 않으므로 항목이 표시되지 않습니다.",
@@ -37,6 +37,7 @@
37
37
  "Change {list}": "Alteração {list}",
38
38
  "Change {n}": "Alteração {n}",
39
39
  "Changed entry": "Entrada alterada",
40
+ "Changes": "Alterações",
40
41
  "Changes are ready": "As alterações estão prontas",
41
42
  "Changes {list}": "Alterações {list}",
42
43
  "Check again": "Verificar novamente",
@@ -120,6 +121,7 @@
120
121
  "Partly staged": "Parcialmente preparado",
121
122
  "Pause this task in Papilot before accepting, rejecting, or undoing changes. You can still add comments.": "Pause esta tarefa no Papilot antes de aceitar, rejeitar ou desfazer alterações. Você ainda pode adicionar comentários.",
122
123
  "Pending": "Pendente",
124
+ "Preparing the full PDF…": "Preparando o PDF completo…",
123
125
  "Preparing the next review…": "Preparando a próxima revisão…",
124
126
  "Previous change": "Alteração anterior",
125
127
  "Previous change (K or ←)": "Alteração anterior (K ou ←)",
@@ -176,6 +178,7 @@
176
178
  "The PDF matches the last commit.": "O PDF corresponde ao último commit.",
177
179
  "The comparison could not be built: ": "Não foi possível gerar a comparação: ",
178
180
  "The entry has no title, DOI or arXiv identifier to look up.": "A entrada não tem título, DOI nem identificador do arXiv para consulta.",
181
+ "The full PDF could not be loaded.": "Não foi possível carregar o PDF completo.",
179
182
  "The last commit changed. The comparison is being updated.": "O último commit mudou. A comparação está sendo atualizada.",
180
183
  "The latest publish failed: ": "A última publicação falhou: ",
181
184
  "The paper does not use this .bib file, so the entry will not appear.": "O artigo não usa este arquivo .bib, portanto a entrada não aparecerá.",
@@ -37,6 +37,7 @@
37
37
  "Change {list}": "Ändring {list}",
38
38
  "Change {n}": "Ändring {n}",
39
39
  "Changed entry": "Ändrad post",
40
+ "Changes": "Ändringar",
40
41
  "Changes are ready": "Ändringarna är klara",
41
42
  "Changes {list}": "Ändringar {list}",
42
43
  "Check again": "Kontrollera igen",
@@ -120,6 +121,7 @@
120
121
  "Partly staged": "Delvis förberedd",
121
122
  "Pause this task in Papilot before accepting, rejecting, or undoing changes. You can still add comments.": "Pausa denna uppgift i Papilot innan du godkänner, avvisar eller ångrar ändringar. Du kan fortfarande lägga till kommentarer.",
122
123
  "Pending": "Väntande",
124
+ "Preparing the full PDF…": "Förbereder hela PDF-filen…",
123
125
  "Preparing the next review…": "Förbereder nästa granskning…",
124
126
  "Previous change": "Föregående ändring",
125
127
  "Previous change (K or ←)": "Föregående ändring (K eller ←)",
@@ -176,6 +178,7 @@
176
178
  "The PDF matches the last commit.": "PDF-filen stämmer överens med senaste commit.",
177
179
  "The comparison could not be built: ": "Jämförelsen kunde inte skapas: ",
178
180
  "The entry has no title, DOI or arXiv identifier to look up.": "Posten saknar titel, DOI eller arXiv-identifierare att slå upp.",
181
+ "The full PDF could not be loaded.": "Det gick inte att läsa in hela PDF-filen.",
179
182
  "The last commit changed. The comparison is being updated.": "Senaste commit har ändrats. Jämförelsen uppdateras.",
180
183
  "The latest publish failed: ": "Den senaste publiceringen misslyckades: ",
181
184
  "The paper does not use this .bib file, so the entry will not appear.": "Artikeln använder inte denna .bib-fil, så posten kommer inte att visas.",
@@ -37,6 +37,7 @@
37
37
  "Change {list}": "變更 {list}",
38
38
  "Change {n}": "變更 {n}",
39
39
  "Changed entry": "已變更書目項目",
40
+ "Changes": "變更",
40
41
  "Changes are ready": "變更已就緒",
41
42
  "Changes {list}": "變更 {list}",
42
43
  "Check again": "再次檢查",
@@ -120,6 +121,7 @@
120
121
  "Partly staged": "部分已暫存",
121
122
  "Pause this task in Papilot before accepting, rejecting, or undoing changes. You can still add comments.": "在接受、拒絕或復原變更前,請先在 Papilot 中暫停此任務。您仍可新增評論。",
122
123
  "Pending": "待處理",
124
+ "Preparing the full PDF…": "正在準備完整 PDF…",
123
125
  "Preparing the next review…": "正在準備下一次審閱…",
124
126
  "Previous change": "上一個變更",
125
127
  "Previous change (K or ←)": "上一個變更 (K 或 ←)",
@@ -176,6 +178,7 @@
176
178
  "The PDF matches the last commit.": "PDF 與上次提交一致。",
177
179
  "The comparison could not be built: ": "無法建立比對:",
178
180
  "The entry has no title, DOI or arXiv identifier to look up.": "此書目項目沒有可供查詢的標題、DOI 或 arXiv 識別碼。",
181
+ "The full PDF could not be loaded.": "無法載入完整 PDF。",
179
182
  "The last commit changed. The comparison is being updated.": "上次提交已變更。正在更新比對。",
180
183
  "The latest publish failed: ": "最近一次發布失敗:",
181
184
  "The paper does not use this .bib file, so the entry will not appear.": "論文未使用此 .bib 檔案,因此該書目項目不會顯示。",
@@ -0,0 +1,13 @@
1
+ import type { ChangeBlock } from "../types.ts";
2
+
3
+ // A change block in the numbering of the changes view, for the full paper.
4
+ export interface PlacedChange {
5
+ block: ChangeBlock;
6
+ index: number;
7
+ }
8
+
9
+ // The full paper can show the changes when at least one strip knows where
10
+ // it starts on its page; a change whose strips do not follows the change
11
+ // before it.
12
+ export const placeable = (changes: PlacedChange[]) =>
13
+ changes.some((c) => c.block.after.some((s) => s.top !== undefined));