@rightkit/document-core 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/README.md +31 -7
- package/dist/app/chrome.js +1 -1
- package/dist/app/documentPrinting.d.ts +0 -6
- package/dist/app/documentPrinting.js +0 -18
- package/dist/app/shell/nativeMenu.d.ts +3 -2
- package/dist/app/shell/nativeMenu.js +8 -5
- package/dist/documents/perfEventCounters.d.ts +0 -8
- package/dist/documents/perfEventCounters.js +2 -1
- package/dist/documents/preview/previewFrame.d.ts +2 -1
- package/dist/documents/preview/previewFrame.js +9 -7
- package/dist/documents/searchUtils.js +1 -1
- package/dist/host.d.ts +36 -0
- package/dist/host.js +54 -0
- package/dist/index.d.ts +1 -50
- package/dist/index.js +1 -50
- package/dist/settings/themes.js +1 -1
- package/dist/state/bridgeEvents.d.ts +1 -1
- package/dist/state/bridgeEvents.js +7 -4
- package/dist/state/recentUrls.js +4 -3
- package/dist/state/tauriBridge.d.ts +3 -61
- package/dist/state/tauriBridge.js +8 -297
- package/dist/styles.css +2 -2
- package/package.json +25 -98
- package/dist/app/shortcutChords.d.ts +0 -6
- package/dist/app/shortcutChords.js +0 -14
- package/dist/app/useCloseFindOnSurfaceChange.d.ts +0 -14
- package/dist/app/useCloseFindOnSurfaceChange.js +0 -23
- package/dist/app/useEditText.d.ts +0 -6
- package/dist/app/useEditText.js +0 -10
- package/dist/appName.d.ts +0 -2
- package/dist/appName.js +0 -8
- package/dist/documents/FullscreenIcon.d.ts +0 -4
- package/dist/documents/FullscreenIcon.js +0 -7
- package/dist/documents/mdx/mdxRuntimeScript.d.ts +0 -2
- package/dist/documents/mdx/mdxRuntimeScript.js +0 -3
- package/dist/documents/mdx/mdxRuntimeScript.txt +0 -584
- package/dist/documents/pdf/MacPdfReadPane.d.ts +0 -23
- package/dist/documents/pdf/MacPdfReadPane.js +0 -708
- package/dist/documents/pdf/NativePdfDocument.d.ts +0 -9
- package/dist/documents/pdf/NativePdfDocument.js +0 -41
- package/dist/documents/pdf/PdfInlineTextEditor.d.ts +0 -53
- package/dist/documents/pdf/PdfInlineTextEditor.js +0 -225
- package/dist/documents/pdf/PdfOcrLayer.d.ts +0 -4
- package/dist/documents/pdf/PdfOcrLayer.js +0 -30
- package/dist/documents/pdf/PdfOutline.d.ts +0 -6
- package/dist/documents/pdf/PdfOutline.js +0 -12
- package/dist/documents/pdf/PdfPasswordPrompt.d.ts +0 -5
- package/dist/documents/pdf/PdfPasswordPrompt.js +0 -24
- package/dist/documents/pdf/PdfThumbnails.d.ts +0 -8
- package/dist/documents/pdf/PdfThumbnails.js +0 -201
- package/dist/documents/pdf/PdfToolbar.d.ts +0 -47
- package/dist/documents/pdf/PdfToolbar.js +0 -76
- package/dist/documents/pdf/PdfViewer.d.ts +0 -36
- package/dist/documents/pdf/PdfViewer.js +0 -1522
- package/dist/documents/pdf/PdfViewerBody.d.ts +0 -22
- package/dist/documents/pdf/PdfViewerBody.js +0 -42
- package/dist/documents/pdf/macPdfNativeBridge.d.ts +0 -118
- package/dist/documents/pdf/macPdfNativeBridge.js +0 -134
- package/dist/documents/pdf/nearestVisiblePdfPage.d.ts +0 -2
- package/dist/documents/pdf/nearestVisiblePdfPage.js +0 -22
- package/dist/documents/pdf/pdfBench.d.ts +0 -88
- package/dist/documents/pdf/pdfBench.js +0 -535
- package/dist/documents/pdf/pdfBenchAutopilot.d.ts +0 -48
- package/dist/documents/pdf/pdfBenchAutopilot.js +0 -154
- package/dist/documents/pdf/pdfBenchTransport.d.ts +0 -21
- package/dist/documents/pdf/pdfBenchTransport.js +0 -233
- package/dist/documents/pdf/pdfEditModel.d.ts +0 -294
- package/dist/documents/pdf/pdfEditModel.js +0 -105
- package/dist/documents/pdf/pdfEditor.d.ts +0 -100
- package/dist/documents/pdf/pdfEditor.js +0 -68
- package/dist/documents/pdf/pdfOcr.d.ts +0 -36
- package/dist/documents/pdf/pdfOcr.js +0 -88
- package/dist/documents/pdf/pdfOcrEdit.d.ts +0 -32
- package/dist/documents/pdf/pdfOcrEdit.js +0 -138
- package/dist/documents/pdf/pdfPrinting.d.ts +0 -28
- package/dist/documents/pdf/pdfPrinting.js +0 -110
- package/dist/documents/pdf/pdfSave.d.ts +0 -87
- package/dist/documents/pdf/pdfSave.js +0 -250
- package/dist/documents/pdf/pdfShared.d.ts +0 -16
- package/dist/documents/pdf/pdfShared.js +0 -20
- package/dist/documents/pdf/pdfToMarkdown.d.ts +0 -11
- package/dist/documents/pdf/pdfToMarkdown.js +0 -67
- package/dist/documents/pdf/pdfjsLibGlobal.d.ts +0 -1
- package/dist/documents/pdf/pdfjsLibGlobal.js +0 -12
- package/dist/documents/pdf/toolbarIcons.d.ts +0 -18
- package/dist/documents/pdf/toolbarIcons.js +0 -31
- package/dist/documents/pdf/usePdfDocumentLoader.d.ts +0 -40
- package/dist/documents/pdf/usePdfDocumentLoader.js +0 -243
- package/dist/documents/pdf/usePdfEditSession.d.ts +0 -88
- package/dist/documents/pdf/usePdfEditSession.js +0 -341
- package/dist/documents/pdf/usePdfEditor.d.ts +0 -59
- package/dist/documents/pdf/usePdfEditor.js +0 -317
- package/dist/documents/pdf/usePdfFit.d.ts +0 -19
- package/dist/documents/pdf/usePdfFit.js +0 -55
- package/dist/documents/pdf/usePdfOcr.d.ts +0 -38
- package/dist/documents/pdf/usePdfOcr.js +0 -337
- package/dist/documents/pdf/usePdfPreviewRecompose.d.ts +0 -28
- package/dist/documents/pdf/usePdfPreviewRecompose.js +0 -256
- package/dist/documents/pdf/usePdfSavePlan.d.ts +0 -61
- package/dist/documents/pdf/usePdfSavePlan.js +0 -303
- package/dist/documents/pdf/usePdfSearch.d.ts +0 -31
- package/dist/documents/pdf/usePdfSearch.js +0 -367
- package/dist/documents/pdf/usePdfViewControls.d.ts +0 -72
- package/dist/documents/pdf/usePdfViewControls.js +0 -152
- package/dist/documents/scopedSelectAll.d.ts +0 -5
- package/dist/documents/scopedSelectAll.js +0 -77
- package/dist/documents/txt/cmScroll.d.ts +0 -11
- package/dist/documents/txt/cmScroll.js +0 -52
- package/dist/documents/useMirroredEditText.d.ts +0 -15
- package/dist/documents/useMirroredEditText.js +0 -29
- package/dist/documents/useSplitResize.d.ts +0 -7
- package/dist/documents/useSplitResize.js +0 -37
- package/dist/settings/rk-settings.css +0 -38
- package/dist/settings/sections/EditorSection.d.ts +0 -1
- package/dist/settings/sections/EditorSection.js +0 -18
- package/dist/settings/sections/FileAssociationsSection.d.ts +0 -1
- package/dist/settings/sections/FileAssociationsSection.js +0 -5
- package/dist/settings/sections/MacFileAssociationsBlock.d.ts +0 -33
- package/dist/settings/sections/MacFileAssociationsBlock.js +0 -151
- package/dist/settings/sections/ThemeSection.d.ts +0 -1
- package/dist/settings/sections/ThemeSection.js +0 -31
- package/dist/settings/sections/TypographySection.d.ts +0 -1
- package/dist/settings/sections/TypographySection.js +0 -19
- package/dist/settings/sections/onOff.d.ts +0 -11
- package/dist/settings/sections/onOff.js +0 -8
- package/dist/state/browserQa.d.ts +0 -21
- package/dist/state/browserQa.js +0 -303
- package/dist/state/browserQaDocx.d.ts +0 -3
- package/dist/state/browserQaDocx.js +0 -35
- package/dist/state/browserQaLicense.d.ts +0 -8
- package/dist/state/browserQaLicense.js +0 -7
- package/dist/state/browserQaPdf.d.ts +0 -60
- package/dist/state/browserQaPdf.js +0 -302
- package/dist/state/browserQaText.d.ts +0 -6
- package/dist/state/browserQaText.js +0 -48
|
@@ -1,243 +0,0 @@
|
|
|
1
|
-
import { useCallback, useEffect, useRef, useState, } from "react";
|
|
2
|
-
import * as pdfjs from "pdfjs-dist";
|
|
3
|
-
import { acquireResource } from "../../app/resourceCensus.js";
|
|
4
|
-
import { beginPdfBench, getPdfBenchLoadMode, markPdfBenchDocumentReady, markPdfBenchError, } from "./pdfBench.js";
|
|
5
|
-
import { preparePdfLoading } from "./pdfBenchTransport.js";
|
|
6
|
-
import { pdfWasmUrl } from "@rightkit/pdf-ui";
|
|
7
|
-
/** Benchmark-only password injection; production & manual unlocks always use the prompt. */
|
|
8
|
-
export function getPdfBenchPassword() {
|
|
9
|
-
if (!import.meta.env?.DEV || import.meta.env?.VITE_PDF_BENCH_AUTOPILOT !== "1")
|
|
10
|
-
return null;
|
|
11
|
-
const password = import.meta.env?.VITE_PDF_BENCH_PASSWORD;
|
|
12
|
-
return password ? password : null;
|
|
13
|
-
}
|
|
14
|
-
export function usePdfDocumentLoader({ documentPath, url, initialView, pdfRef, renderedRef, slotRefs, visibleRef, restoredRef, reportReadyRef, setCurrent, setScale, setRotation, setFitMode, }) {
|
|
15
|
-
const [pdfDoc, setPdfDoc] = useState(null);
|
|
16
|
-
const [numPages, setNumPages] = useState(0);
|
|
17
|
-
const [base, setBase] = useState(null);
|
|
18
|
-
const [pageSizes, setPageSizes] = useState(new Map());
|
|
19
|
-
const [error, setError] = useState(null);
|
|
20
|
-
const [renderErrors, setRenderErrors] = useState(new Map());
|
|
21
|
-
const [pwPrompt, setPwPrompt] = useState(null);
|
|
22
|
-
const [outline, setOutline] = useState(null);
|
|
23
|
-
// The pdf.js loading task currently backing `pdfDoc`/`pdfRef.current` — either this effect's own
|
|
24
|
-
// initial-load task, or a post-commit `replaceDocument` task (§10.3 reload after Save). Whichever
|
|
25
|
-
// task is here must only be destroyed once React has committed the DIFFERENT `pdfDoc` that
|
|
26
|
-
// superseded it (the promote effect below) — destroying a task while page layers still hold the
|
|
27
|
-
// document it produced is what previously crashed the viewer to its ErrorBoundary.
|
|
28
|
-
const activeTaskRef = useRef(null);
|
|
29
|
-
// Set by `replaceDocument`, consumed by the promote effect once `pdfDoc` actually changes.
|
|
30
|
-
const pendingReplacementTaskRef = useRef(null);
|
|
31
|
-
const replaceDocument = useCallback((doc, task) => {
|
|
32
|
-
pdfRef.current = doc;
|
|
33
|
-
pendingReplacementTaskRef.current = task;
|
|
34
|
-
setPdfDoc(doc);
|
|
35
|
-
}, [pdfRef]);
|
|
36
|
-
// Promotes a pending replacement to "active" — and disposes whatever it superseded — only after
|
|
37
|
-
// React has committed `pdfDoc` to the new document. Also runs (as a no-op) after the ordinary
|
|
38
|
-
// initial load's `setPdfDoc`, since `pendingReplacementTaskRef` is still null at that point.
|
|
39
|
-
// `pdfDoc` is the intentional trigger (the effect must run after React commits the new document),
|
|
40
|
-
// not a value read inside the effect body.
|
|
41
|
-
// biome-ignore lint/correctness/useExhaustiveDependencies: see comment above.
|
|
42
|
-
useEffect(() => {
|
|
43
|
-
const pending = pendingReplacementTaskRef.current;
|
|
44
|
-
if (!pending || pending === activeTaskRef.current)
|
|
45
|
-
return;
|
|
46
|
-
const stale = activeTaskRef.current;
|
|
47
|
-
activeTaskRef.current = pending;
|
|
48
|
-
pendingReplacementTaskRef.current = null;
|
|
49
|
-
if (stale)
|
|
50
|
-
void stale.destroy();
|
|
51
|
-
}, [pdfDoc]);
|
|
52
|
-
useEffect(() => {
|
|
53
|
-
if (!url)
|
|
54
|
-
return;
|
|
55
|
-
let cancelled = false;
|
|
56
|
-
let passwordCancelled = false;
|
|
57
|
-
renderedRef.current.forEach((cleanup) => cleanup());
|
|
58
|
-
renderedRef.current.clear();
|
|
59
|
-
slotRefs.current.clear();
|
|
60
|
-
visibleRef.current.clear();
|
|
61
|
-
pdfRef.current = null;
|
|
62
|
-
restoredRef.current = false;
|
|
63
|
-
reportReadyRef.current = false;
|
|
64
|
-
setError(null);
|
|
65
|
-
setRenderErrors(new Map());
|
|
66
|
-
setNumPages(0);
|
|
67
|
-
setBase(null);
|
|
68
|
-
setPageSizes(new Map());
|
|
69
|
-
setCurrent(1);
|
|
70
|
-
setPwPrompt(null);
|
|
71
|
-
setOutline(null);
|
|
72
|
-
setPdfDoc(null);
|
|
73
|
-
const controller = new AbortController();
|
|
74
|
-
let task = null;
|
|
75
|
-
let preparedLoad = null;
|
|
76
|
-
let loadedDoc = null;
|
|
77
|
-
// One document proxy + its worker per open PDF. Released in the effect cleanup below so a
|
|
78
|
-
// cold-unmounted PDF pane provably stops counting against resident cost.
|
|
79
|
-
let releaseDocumentCensus = null;
|
|
80
|
-
let releaseWorkerCensus = null;
|
|
81
|
-
let finalized = false;
|
|
82
|
-
const finalize = () => {
|
|
83
|
-
if (finalized)
|
|
84
|
-
return;
|
|
85
|
-
finalized = true;
|
|
86
|
-
controller.abort();
|
|
87
|
-
if (preparedLoad)
|
|
88
|
-
preparedLoad.dispose();
|
|
89
|
-
else if (task)
|
|
90
|
-
void task.destroy();
|
|
91
|
-
if (activeTaskRef.current === task)
|
|
92
|
-
activeTaskRef.current = null;
|
|
93
|
-
if (pendingReplacementTaskRef.current === task)
|
|
94
|
-
pendingReplacementTaskRef.current = null;
|
|
95
|
-
releaseDocumentCensus?.();
|
|
96
|
-
releaseDocumentCensus = null;
|
|
97
|
-
releaseWorkerCensus?.();
|
|
98
|
-
releaseWorkerCensus = null;
|
|
99
|
-
if (pdfRef.current && pdfRef.current === loadedDoc)
|
|
100
|
-
pdfRef.current = null;
|
|
101
|
-
};
|
|
102
|
-
const load = async () => {
|
|
103
|
-
const mode = getPdfBenchLoadMode();
|
|
104
|
-
await beginPdfBench(documentPath, pdfjs.version, mode);
|
|
105
|
-
if (cancelled) {
|
|
106
|
-
const error = new Error("PDF load cancelled");
|
|
107
|
-
error.name = "AbortError";
|
|
108
|
-
throw error;
|
|
109
|
-
}
|
|
110
|
-
preparedLoad = await preparePdfLoading({
|
|
111
|
-
url,
|
|
112
|
-
mode,
|
|
113
|
-
signal: controller.signal,
|
|
114
|
-
wasmUrl: pdfWasmUrl(),
|
|
115
|
-
});
|
|
116
|
-
task = preparedLoad.task;
|
|
117
|
-
releaseWorkerCensus = acquireResource({
|
|
118
|
-
kind: "pdf-worker",
|
|
119
|
-
documentPath,
|
|
120
|
-
format: "pdf",
|
|
121
|
-
});
|
|
122
|
-
task.onPassword = (updatePassword, reason) => {
|
|
123
|
-
const benchPassword = getPdfBenchPassword();
|
|
124
|
-
if (benchPassword) {
|
|
125
|
-
updatePassword(benchPassword);
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
setPwPrompt({
|
|
129
|
-
retry: reason === 2,
|
|
130
|
-
onSubmit: (pw) => {
|
|
131
|
-
setPwPrompt(null);
|
|
132
|
-
updatePassword(pw);
|
|
133
|
-
},
|
|
134
|
-
onCancel: () => {
|
|
135
|
-
passwordCancelled = true;
|
|
136
|
-
setPwPrompt(null);
|
|
137
|
-
setError("Password required to open this PDF.");
|
|
138
|
-
finalize();
|
|
139
|
-
},
|
|
140
|
-
});
|
|
141
|
-
};
|
|
142
|
-
return preparedLoad.promise;
|
|
143
|
-
};
|
|
144
|
-
load()
|
|
145
|
-
.then(async (doc) => {
|
|
146
|
-
if (cancelled || finalized)
|
|
147
|
-
return;
|
|
148
|
-
markPdfBenchDocumentReady(documentPath);
|
|
149
|
-
setPwPrompt(null);
|
|
150
|
-
loadedDoc = doc;
|
|
151
|
-
pdfRef.current = doc;
|
|
152
|
-
activeTaskRef.current = task;
|
|
153
|
-
releaseDocumentCensus = acquireResource({
|
|
154
|
-
kind: "pdf-document",
|
|
155
|
-
documentPath,
|
|
156
|
-
format: "pdf",
|
|
157
|
-
});
|
|
158
|
-
setPdfDoc(doc);
|
|
159
|
-
setNumPages(doc.numPages);
|
|
160
|
-
doc
|
|
161
|
-
.getOutline()
|
|
162
|
-
.then((o) => {
|
|
163
|
-
if (!cancelled && !finalized)
|
|
164
|
-
setOutline(o ?? []);
|
|
165
|
-
})
|
|
166
|
-
.catch(() => {
|
|
167
|
-
if (!cancelled && !finalized)
|
|
168
|
-
setOutline([]);
|
|
169
|
-
});
|
|
170
|
-
const first = await doc.getPage(1);
|
|
171
|
-
const v = first.getViewport({ scale: 1 });
|
|
172
|
-
if (!cancelled && !finalized) {
|
|
173
|
-
setBase({ w: v.width, h: v.height });
|
|
174
|
-
setRotation((((initialView?.rotation ?? 0) % 360) + 360) % 360);
|
|
175
|
-
if (initialView?.fit)
|
|
176
|
-
setFitMode(initialView.fit);
|
|
177
|
-
else if (initialView?.scale) {
|
|
178
|
-
setScale(initialView.scale);
|
|
179
|
-
setFitMode(null);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
})
|
|
183
|
-
.catch((e) => {
|
|
184
|
-
if (!cancelled && !passwordCancelled && e?.name !== "AbortError") {
|
|
185
|
-
finalize();
|
|
186
|
-
if (loadedDoc) {
|
|
187
|
-
pdfRef.current = null;
|
|
188
|
-
setPdfDoc(null);
|
|
189
|
-
setNumPages(0);
|
|
190
|
-
}
|
|
191
|
-
markPdfBenchError(documentPath, e);
|
|
192
|
-
setError(String(e?.message ?? e));
|
|
193
|
-
}
|
|
194
|
-
});
|
|
195
|
-
return () => {
|
|
196
|
-
cancelled = true;
|
|
197
|
-
renderedRef.current.forEach((cleanup) => cleanup());
|
|
198
|
-
renderedRef.current.clear();
|
|
199
|
-
finalize();
|
|
200
|
-
// A post-commit `replaceDocument` call may have swapped in a newer task after the initial
|
|
201
|
-
// load above finished; dispose it too (and any not-yet-promoted pending one) so a document
|
|
202
|
-
// switch/unmount can't leak its pdf.js worker.
|
|
203
|
-
if (activeTaskRef.current && activeTaskRef.current !== task) {
|
|
204
|
-
void activeTaskRef.current.destroy();
|
|
205
|
-
}
|
|
206
|
-
if (pendingReplacementTaskRef.current &&
|
|
207
|
-
pendingReplacementTaskRef.current !== activeTaskRef.current) {
|
|
208
|
-
void pendingReplacementTaskRef.current.destroy();
|
|
209
|
-
}
|
|
210
|
-
pdfRef.current = null;
|
|
211
|
-
};
|
|
212
|
-
}, [
|
|
213
|
-
documentPath,
|
|
214
|
-
url,
|
|
215
|
-
slotRefs.current.clear,
|
|
216
|
-
setScale,
|
|
217
|
-
visibleRef.current.clear,
|
|
218
|
-
setRotation,
|
|
219
|
-
setFitMode,
|
|
220
|
-
setCurrent,
|
|
221
|
-
restoredRef,
|
|
222
|
-
reportReadyRef,
|
|
223
|
-
pdfRef,
|
|
224
|
-
renderedRef.current.forEach,
|
|
225
|
-
initialView?.scale,
|
|
226
|
-
renderedRef.current.clear,
|
|
227
|
-
initialView?.rotation,
|
|
228
|
-
initialView?.fit,
|
|
229
|
-
]);
|
|
230
|
-
return {
|
|
231
|
-
pdfDoc,
|
|
232
|
-
numPages,
|
|
233
|
-
base,
|
|
234
|
-
pageSizes,
|
|
235
|
-
setPageSizes,
|
|
236
|
-
error,
|
|
237
|
-
renderErrors,
|
|
238
|
-
setRenderErrors,
|
|
239
|
-
pwPrompt,
|
|
240
|
-
outline,
|
|
241
|
-
replaceDocument,
|
|
242
|
-
};
|
|
243
|
-
}
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import { type InspectPdfPage, type ListPdfFonts, type PdfEditCommand, type PdfEditSession, type PdfFontCatalogEntry, type PdfPageGraph, type PdfPoint, type PdfColor, type PdfRect, type PdfSelection, type PdfTextAppearance } from "./pdfEditModel.js";
|
|
2
|
-
export type PdfEditSessionTool = PdfEditSession["tool"];
|
|
3
|
-
/** Bridge functions this hook needs, injected so it can be unit-tested without the real
|
|
4
|
-
* `tauriBridge.ts` (§12: real bridge lands at Dispatch D2). */
|
|
5
|
-
export interface PdfEditSessionBridge {
|
|
6
|
-
inspectPdfPage: InspectPdfPage;
|
|
7
|
-
listPdfFonts: ListPdfFonts;
|
|
8
|
-
}
|
|
9
|
-
export interface UsePdfEditSessionOptions {
|
|
10
|
-
bridge: PdfEditSessionBridge;
|
|
11
|
-
/** Authorized path passed to `inspectPdfPage` (§5.2). */
|
|
12
|
-
sourcePath: string;
|
|
13
|
-
}
|
|
14
|
-
export interface PdfEditSessionController {
|
|
15
|
-
session: PdfEditSession;
|
|
16
|
-
/** Mutable latest session snapshot for async save callbacks; avoids stale render closures. */
|
|
17
|
-
sessionRef: {
|
|
18
|
-
current: PdfEditSession;
|
|
19
|
-
};
|
|
20
|
-
dirty: boolean;
|
|
21
|
-
/** Enters Edit PDF mode (§3.2). Idempotent. */
|
|
22
|
-
enterEditMode: () => void;
|
|
23
|
-
/** §3.2 "Done": commits an active valid draft, then returns to View mode. Never saves. */
|
|
24
|
-
exitEditMode: () => void;
|
|
25
|
-
setTool: (tool: PdfEditSessionTool) => void;
|
|
26
|
-
/** Selecting a new target commits any active valid draft first (§3.2 "Clicking another page
|
|
27
|
-
* target commits active valid draft, then selects target"). */
|
|
28
|
-
selectIsland: (selection: PdfSelection) => void;
|
|
29
|
-
clearSelection: () => void;
|
|
30
|
-
/** Seeds a `sourceEdit` draft from the current selection's island text/appearance. */
|
|
31
|
-
startSourceEditDraft: (params: {
|
|
32
|
-
id: string;
|
|
33
|
-
rect: PdfRect;
|
|
34
|
-
text: string;
|
|
35
|
-
appearance: PdfTextAppearance;
|
|
36
|
-
availableBounds: PdfRect;
|
|
37
|
-
}) => void;
|
|
38
|
-
/** Seeds an `addText` draft at a clicked PDF-space anchor (§6, `PdfAddText.anchor`). */
|
|
39
|
-
startAddTextDraft: (params: {
|
|
40
|
-
id: string;
|
|
41
|
-
page: number;
|
|
42
|
-
anchor: PdfPoint;
|
|
43
|
-
rect: PdfRect;
|
|
44
|
-
appearance: PdfTextAppearance;
|
|
45
|
-
}) => void;
|
|
46
|
-
/** Seeds a persisted OCR correction overlay from recognized line geometry. */
|
|
47
|
-
startOcrEditDraft: (params: {
|
|
48
|
-
id: string;
|
|
49
|
-
page: number;
|
|
50
|
-
lineId: string;
|
|
51
|
-
rect: PdfRect;
|
|
52
|
-
text: string;
|
|
53
|
-
appearance: PdfTextAppearance;
|
|
54
|
-
coverColor: PdfColor;
|
|
55
|
-
}) => void;
|
|
56
|
-
updateDraftText: (text: string) => void;
|
|
57
|
-
updateDraftAppearance: (appearance: PdfTextAppearance) => void;
|
|
58
|
-
/** External layout measurement sets this (§3.3); this hook does not measure text itself. */
|
|
59
|
-
setDraftOverflow: (overflow: boolean) => void;
|
|
60
|
-
/** §3.2 "Clicking outside active text commits valid draft" / Done / selecting elsewhere.
|
|
61
|
-
* No-ops when there is no draft. A no-op (unchanged) `sourceEdit` draft commits nothing —
|
|
62
|
-
* matches §6's dirty semantics: an identical before/after would not need a history command.
|
|
63
|
-
* Returns the command that was applied, or null if nothing was committed. */
|
|
64
|
-
commitDraft: () => PdfEditCommand | null;
|
|
65
|
-
/** §3.2 Escape: "discards active draft without a history command." */
|
|
66
|
-
cancelDraft: () => void;
|
|
67
|
-
/** Applies `command` to `sourceCommands` and recomputes `sourceDigest`. Exposed directly (not
|
|
68
|
-
* only via `commitDraft`) so an external pdf.js command-history wrapper
|
|
69
|
-
* (`pushSourceCommand`, pdfEditor.ts) can drive apply/undo/redo through this same session. */
|
|
70
|
-
apply: (command: PdfEditCommand) => void;
|
|
71
|
-
/** Reverts the effect of `apply(command)` — removes the command by id. */
|
|
72
|
-
revert: (command: PdfEditCommand) => void;
|
|
73
|
-
/** Called after apply/revert reached through undo/redo (§9.4 in-memory preview recompose hook).
|
|
74
|
-
* This hook does not call `compose_pdf_preview` itself — it only tracks a subscriber list so a
|
|
75
|
-
* caller (PdfViewer) can react to every source-history mutation. */
|
|
76
|
-
onPublish: (listener: () => void) => () => void;
|
|
77
|
-
publish: () => void;
|
|
78
|
-
/** Marks the current `sourceDigest` as saved and clears `annotationDirty` — call only after
|
|
79
|
-
* `commit_pdf_save` succeeds (§10.3: "checkpoints sourceDigest"). */
|
|
80
|
-
checkpoint: () => void;
|
|
81
|
-
setAnnotationDirty: (dirty: boolean) => void;
|
|
82
|
-
inspectPage: (page: number) => Promise<PdfPageGraph>;
|
|
83
|
-
listFonts: () => Promise<PdfFontCatalogEntry[]>;
|
|
84
|
-
/** Resets session state to a fresh document (e.g. after `commit_pdf_save` reloads the active
|
|
85
|
-
* document, §10.3). Clears commands, selection, draft, and checkpoints to empty history. */
|
|
86
|
-
resetForDocument: () => void;
|
|
87
|
-
}
|
|
88
|
-
export declare function usePdfEditSession(options: UsePdfEditSessionOptions): PdfEditSessionController;
|
|
@@ -1,341 +0,0 @@
|
|
|
1
|
-
// Edit PDF mode session state (docs/PDF-EDITOR-NOW.md §3.2, §6, §12 "usePdfEditSession.ts").
|
|
2
|
-
//
|
|
3
|
-
// Owns mode/tool/selection/draft lifecycle, the ordered `sourceCommands` list, and digest/dirty
|
|
4
|
-
// checkpoint tracking. Does not own pdf.js's `AnnotationEditorUIManager` command history (§6.2,
|
|
5
|
-
// `pushSourceCommand` in pdfEditor.ts) or the real Tauri bridge (`tauriBridge.ts`, Dispatch D2) —
|
|
6
|
-
// this hook accepts bridge functions as injected parameters (§12's "injected-bridge-function
|
|
7
|
-
// pattern", typed in pdfEditModel.ts) so it is testable today against mocks. A caller that wires
|
|
8
|
-
// pdf.js's shared history can drive it through `apply`/`revert`/`publish`, which together satisfy
|
|
9
|
-
// pdfEditor.ts's `PdfSourceCommandSession<PdfEditCommand>` shape.
|
|
10
|
-
//
|
|
11
|
-
// No disk writes happen anywhere in this file (§3.2: "It never saves"). Save/checkpointing is
|
|
12
|
-
// triggered externally via `checkpoint()` once `commit_pdf_save` (§10.3) succeeds.
|
|
13
|
-
import { useCallback, useMemo, useRef, useState } from "react";
|
|
14
|
-
import { computeSourceDigest, isSessionDirty, } from "./pdfEditModel.js";
|
|
15
|
-
const INITIAL_TOOL = "selectText";
|
|
16
|
-
function initialSession() {
|
|
17
|
-
const digest = computeSourceDigest([]);
|
|
18
|
-
return {
|
|
19
|
-
mode: "view",
|
|
20
|
-
tool: INITIAL_TOOL,
|
|
21
|
-
selection: null,
|
|
22
|
-
draft: null,
|
|
23
|
-
sourceCommands: [],
|
|
24
|
-
sourceDigest: digest,
|
|
25
|
-
savedSourceDigest: digest,
|
|
26
|
-
annotationDirty: false,
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
function draftIsNoOp(draft) {
|
|
30
|
-
if (draft.kind === "addText")
|
|
31
|
-
return draft.text.trim().length === 0;
|
|
32
|
-
const sameText = draft.text === draft.originalText;
|
|
33
|
-
const sameAppearance = !!draft.originalAppearance &&
|
|
34
|
-
draft.appearance.fontId === draft.originalAppearance.fontId &&
|
|
35
|
-
draft.appearance.sizePt === draft.originalAppearance.sizePt &&
|
|
36
|
-
draft.appearance.color.r === draft.originalAppearance.color.r &&
|
|
37
|
-
draft.appearance.color.g === draft.originalAppearance.color.g &&
|
|
38
|
-
draft.appearance.color.b === draft.originalAppearance.color.b &&
|
|
39
|
-
draft.appearance.color.alpha === draft.originalAppearance.color.alpha;
|
|
40
|
-
return sameText && sameAppearance;
|
|
41
|
-
}
|
|
42
|
-
function draftToCommand(draft) {
|
|
43
|
-
if (draft.kind === "sourceEdit") {
|
|
44
|
-
if (!draft.locator ||
|
|
45
|
-
!draft.originalText ||
|
|
46
|
-
!draft.originalAppearance ||
|
|
47
|
-
!draft.availableBounds) {
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
const command = {
|
|
51
|
-
kind: "replaceSourceText",
|
|
52
|
-
id: draft.id,
|
|
53
|
-
locator: draft.locator,
|
|
54
|
-
before: { text: draft.originalText, appearance: draft.originalAppearance },
|
|
55
|
-
after: { text: draft.text, appearance: draft.appearance },
|
|
56
|
-
availableBounds: draft.availableBounds,
|
|
57
|
-
};
|
|
58
|
-
return command;
|
|
59
|
-
}
|
|
60
|
-
if (draft.kind === "ocrEdit") {
|
|
61
|
-
if (!draft.lineId || !draft.coverColor)
|
|
62
|
-
return null;
|
|
63
|
-
return {
|
|
64
|
-
kind: "correctOcrText",
|
|
65
|
-
id: draft.id,
|
|
66
|
-
page: draft.page,
|
|
67
|
-
lineId: draft.lineId,
|
|
68
|
-
rect: draft.rect,
|
|
69
|
-
text: draft.text,
|
|
70
|
-
appearance: draft.appearance,
|
|
71
|
-
coverColor: draft.coverColor,
|
|
72
|
-
backgroundProof: "nearUniform",
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
if (!draft.anchor)
|
|
76
|
-
return null;
|
|
77
|
-
const command = {
|
|
78
|
-
kind: "addText",
|
|
79
|
-
id: draft.id,
|
|
80
|
-
page: draft.page,
|
|
81
|
-
anchor: draft.anchor,
|
|
82
|
-
text: draft.text,
|
|
83
|
-
appearance: draft.appearance,
|
|
84
|
-
};
|
|
85
|
-
return command;
|
|
86
|
-
}
|
|
87
|
-
export function usePdfEditSession(options) {
|
|
88
|
-
const { bridge, sourcePath } = options;
|
|
89
|
-
const [session, setSession] = useState(initialSession);
|
|
90
|
-
const sessionRef = useRef(session);
|
|
91
|
-
sessionRef.current = session;
|
|
92
|
-
const listenersRef = useRef(new Set());
|
|
93
|
-
const fontsCacheRef = useRef(null);
|
|
94
|
-
// Mirrors `session.draft`, refreshed every render, but ALSO nulled synchronously inside
|
|
95
|
-
// `commitDraftInternal`/`cancelDraft` themselves (not only via their `setSession` updaters). A
|
|
96
|
-
// caller (PdfViewer.tsx) can call `handleCommitDraft()` and then `selectIsland`/`exitEditMode`/an
|
|
97
|
-
// annotation-mode switch in the same synchronous event handler — each of which also commits
|
|
98
|
-
// internally — and React does not guarantee `session` (the render-closure value) reflects the
|
|
99
|
-
// first commit's `draft: null` before the second one reads it. Reading/nulling this ref instead
|
|
100
|
-
// means the second call sees the draft already cleared, instead of re-deriving and re-appending
|
|
101
|
-
// the same command a second time before React ever re-renders.
|
|
102
|
-
const draftRef = useRef(session.draft);
|
|
103
|
-
draftRef.current = session.draft;
|
|
104
|
-
// Mirrors `session.selection`, but is ALSO updated synchronously inside `selectIsland`/
|
|
105
|
-
// `clearSelection` (not only via their `setSession` updaters). `startSourceEditDraft` reads it
|
|
106
|
-
// instead of `session.selection`: PdfViewer calls `selectIsland(...)` and then
|
|
107
|
-
// `startSourceEditDraft(...)` in the same synchronous click handler, and React has not
|
|
108
|
-
// re-rendered between them, so the render-closure `session.selection` is still the pre-click
|
|
109
|
-
// value (null on a first click) — which made `startSourceEditDraft` bail and no editor open on
|
|
110
|
-
// the first click of an existing text run.
|
|
111
|
-
const selectionRef = useRef(session.selection);
|
|
112
|
-
selectionRef.current = session.selection;
|
|
113
|
-
const dirty = useMemo(() => isSessionDirty(session), [session]);
|
|
114
|
-
const publish = useCallback(() => {
|
|
115
|
-
for (const listener of listenersRef.current)
|
|
116
|
-
listener();
|
|
117
|
-
}, []);
|
|
118
|
-
const onPublish = useCallback((listener) => {
|
|
119
|
-
listenersRef.current.add(listener);
|
|
120
|
-
return () => {
|
|
121
|
-
listenersRef.current.delete(listener);
|
|
122
|
-
};
|
|
123
|
-
}, []);
|
|
124
|
-
const apply = useCallback((command) => {
|
|
125
|
-
setSession((prev) => {
|
|
126
|
-
const sourceCommands = [...prev.sourceCommands, command];
|
|
127
|
-
return { ...prev, sourceCommands, sourceDigest: computeSourceDigest(sourceCommands) };
|
|
128
|
-
});
|
|
129
|
-
}, []);
|
|
130
|
-
const revert = useCallback((command) => {
|
|
131
|
-
setSession((prev) => {
|
|
132
|
-
const index = prev.sourceCommands.findIndex((existing) => existing.id === command.id);
|
|
133
|
-
if (index === -1)
|
|
134
|
-
return prev;
|
|
135
|
-
const sourceCommands = prev.sourceCommands.slice();
|
|
136
|
-
sourceCommands.splice(index, 1);
|
|
137
|
-
return { ...prev, sourceCommands, sourceDigest: computeSourceDigest(sourceCommands) };
|
|
138
|
-
});
|
|
139
|
-
}, []);
|
|
140
|
-
const enterEditMode = useCallback(() => {
|
|
141
|
-
setSession((prev) => (prev.mode === "edit" ? prev : { ...prev, mode: "edit" }));
|
|
142
|
-
}, []);
|
|
143
|
-
// Reads `draftRef.current` (not `session.draft` from the render-time closure, and not from
|
|
144
|
-
// inside the `setSession` updater) so the command this returns is computed synchronously: React
|
|
145
|
-
// does not guarantee a functional `setSession` updater runs before this function returns, so a
|
|
146
|
-
// side effect set inside one cannot be relied on as this function's return value — and reading
|
|
147
|
-
// `session.draft` directly would go stale across two synchronous calls in the same handler (see
|
|
148
|
-
// `draftRef`'s doc comment above).
|
|
149
|
-
const commitDraftInternal = useCallback(() => {
|
|
150
|
-
const draft = draftRef.current;
|
|
151
|
-
if (!draft)
|
|
152
|
-
return null;
|
|
153
|
-
draftRef.current = null;
|
|
154
|
-
if (draftIsNoOp(draft)) {
|
|
155
|
-
const current = sessionRef.current;
|
|
156
|
-
if (current.draft)
|
|
157
|
-
sessionRef.current = { ...current, draft: null };
|
|
158
|
-
setSession((prev) => (prev.draft ? { ...prev, draft: null } : prev));
|
|
159
|
-
return null;
|
|
160
|
-
}
|
|
161
|
-
const command = draftToCommand(draft);
|
|
162
|
-
if (!command) {
|
|
163
|
-
const current = sessionRef.current;
|
|
164
|
-
if (current.draft)
|
|
165
|
-
sessionRef.current = { ...current, draft: null };
|
|
166
|
-
setSession((prev) => (prev.draft ? { ...prev, draft: null } : prev));
|
|
167
|
-
return null;
|
|
168
|
-
}
|
|
169
|
-
const current = sessionRef.current;
|
|
170
|
-
const sourceCommands = [...current.sourceCommands, command];
|
|
171
|
-
sessionRef.current = {
|
|
172
|
-
...current,
|
|
173
|
-
draft: null,
|
|
174
|
-
sourceCommands,
|
|
175
|
-
sourceDigest: computeSourceDigest(sourceCommands),
|
|
176
|
-
};
|
|
177
|
-
setSession((prev) => {
|
|
178
|
-
const sourceCommands = [...prev.sourceCommands, command];
|
|
179
|
-
return {
|
|
180
|
-
...prev,
|
|
181
|
-
draft: null,
|
|
182
|
-
sourceCommands,
|
|
183
|
-
sourceDigest: computeSourceDigest(sourceCommands),
|
|
184
|
-
};
|
|
185
|
-
});
|
|
186
|
-
return command;
|
|
187
|
-
}, []);
|
|
188
|
-
const commitDraft = useCallback(() => {
|
|
189
|
-
const command = commitDraftInternal();
|
|
190
|
-
if (command)
|
|
191
|
-
publish();
|
|
192
|
-
return command;
|
|
193
|
-
}, [commitDraftInternal, publish]);
|
|
194
|
-
const cancelDraft = useCallback(() => {
|
|
195
|
-
draftRef.current = null;
|
|
196
|
-
const current = sessionRef.current;
|
|
197
|
-
if (current.draft)
|
|
198
|
-
sessionRef.current = { ...current, draft: null };
|
|
199
|
-
setSession((prev) => (prev.draft ? { ...prev, draft: null } : prev));
|
|
200
|
-
}, []);
|
|
201
|
-
const exitEditMode = useCallback(() => {
|
|
202
|
-
commitDraftInternal();
|
|
203
|
-
setSession((prev) => ({ ...prev, mode: "view", draft: null }));
|
|
204
|
-
}, [commitDraftInternal]);
|
|
205
|
-
const setTool = useCallback((tool) => {
|
|
206
|
-
setSession((prev) => (prev.tool === tool ? prev : { ...prev, tool }));
|
|
207
|
-
}, []);
|
|
208
|
-
const selectIsland = useCallback((selection) => {
|
|
209
|
-
commitDraftInternal();
|
|
210
|
-
selectionRef.current = selection;
|
|
211
|
-
setSession((prev) => ({ ...prev, selection }));
|
|
212
|
-
}, [commitDraftInternal]);
|
|
213
|
-
const clearSelection = useCallback(() => {
|
|
214
|
-
selectionRef.current = null;
|
|
215
|
-
setSession((prev) => (prev.selection ? { ...prev, selection: null } : prev));
|
|
216
|
-
}, []);
|
|
217
|
-
const startSourceEditDraft = useCallback((params) => {
|
|
218
|
-
const selection = selectionRef.current;
|
|
219
|
-
if (!selection)
|
|
220
|
-
return;
|
|
221
|
-
const draft = {
|
|
222
|
-
kind: "sourceEdit",
|
|
223
|
-
id: params.id,
|
|
224
|
-
page: selection.page,
|
|
225
|
-
rect: params.rect,
|
|
226
|
-
text: params.text,
|
|
227
|
-
appearance: params.appearance,
|
|
228
|
-
locator: selection.locator,
|
|
229
|
-
originalText: params.text,
|
|
230
|
-
originalAppearance: params.appearance,
|
|
231
|
-
availableBounds: params.availableBounds,
|
|
232
|
-
anchor: null,
|
|
233
|
-
overflow: false,
|
|
234
|
-
};
|
|
235
|
-
setSession((prev) => ({ ...prev, draft }));
|
|
236
|
-
},
|
|
237
|
-
// Reads `selectionRef.current` (see its doc comment), not `session.selection`, so this callback
|
|
238
|
-
// needs no reactive dependency and stays stable across selection changes.
|
|
239
|
-
[]);
|
|
240
|
-
const startAddTextDraft = useCallback((params) => {
|
|
241
|
-
const draft = {
|
|
242
|
-
kind: "addText",
|
|
243
|
-
id: params.id,
|
|
244
|
-
page: params.page,
|
|
245
|
-
rect: params.rect,
|
|
246
|
-
text: "",
|
|
247
|
-
appearance: params.appearance,
|
|
248
|
-
locator: null,
|
|
249
|
-
originalText: null,
|
|
250
|
-
originalAppearance: null,
|
|
251
|
-
availableBounds: null,
|
|
252
|
-
anchor: params.anchor,
|
|
253
|
-
overflow: false,
|
|
254
|
-
};
|
|
255
|
-
setSession((prev) => ({ ...prev, draft }));
|
|
256
|
-
}, []);
|
|
257
|
-
const startOcrEditDraft = useCallback((params) => {
|
|
258
|
-
const draft = {
|
|
259
|
-
kind: "ocrEdit",
|
|
260
|
-
id: params.id,
|
|
261
|
-
page: params.page,
|
|
262
|
-
rect: params.rect,
|
|
263
|
-
text: params.text,
|
|
264
|
-
appearance: params.appearance,
|
|
265
|
-
locator: null,
|
|
266
|
-
originalText: params.text,
|
|
267
|
-
originalAppearance: params.appearance,
|
|
268
|
-
availableBounds: params.rect,
|
|
269
|
-
anchor: null,
|
|
270
|
-
lineId: params.lineId,
|
|
271
|
-
coverColor: params.coverColor,
|
|
272
|
-
backgroundProof: "nearUniform",
|
|
273
|
-
overflow: false,
|
|
274
|
-
};
|
|
275
|
-
setSession((prev) => ({ ...prev, draft }));
|
|
276
|
-
}, []);
|
|
277
|
-
const updateDraftText = useCallback((text) => {
|
|
278
|
-
setSession((prev) => (prev.draft ? { ...prev, draft: { ...prev.draft, text } } : prev));
|
|
279
|
-
}, []);
|
|
280
|
-
const updateDraftAppearance = useCallback((appearance) => {
|
|
281
|
-
setSession((prev) => (prev.draft ? { ...prev, draft: { ...prev.draft, appearance } } : prev));
|
|
282
|
-
}, []);
|
|
283
|
-
const setDraftOverflow = useCallback((overflow) => {
|
|
284
|
-
setSession((prev) => prev.draft && prev.draft.overflow !== overflow
|
|
285
|
-
? { ...prev, draft: { ...prev.draft, overflow } }
|
|
286
|
-
: prev);
|
|
287
|
-
}, []);
|
|
288
|
-
const checkpoint = useCallback(() => {
|
|
289
|
-
setSession((prev) => ({
|
|
290
|
-
...prev,
|
|
291
|
-
savedSourceDigest: prev.sourceDigest,
|
|
292
|
-
annotationDirty: false,
|
|
293
|
-
}));
|
|
294
|
-
}, []);
|
|
295
|
-
const setAnnotationDirty = useCallback((next) => {
|
|
296
|
-
setSession((prev) => prev.annotationDirty === next ? prev : { ...prev, annotationDirty: next });
|
|
297
|
-
}, []);
|
|
298
|
-
const inspectPage = useCallback((page) => bridge.inspectPdfPage(sourcePath, page), [bridge, sourcePath]);
|
|
299
|
-
const listFonts = useCallback(() => {
|
|
300
|
-
if (!fontsCacheRef.current) {
|
|
301
|
-
fontsCacheRef.current = bridge.listPdfFonts().catch((error) => {
|
|
302
|
-
fontsCacheRef.current = null;
|
|
303
|
-
throw error;
|
|
304
|
-
});
|
|
305
|
-
}
|
|
306
|
-
return fontsCacheRef.current;
|
|
307
|
-
}, [bridge]);
|
|
308
|
-
const resetForDocument = useCallback(() => {
|
|
309
|
-
fontsCacheRef.current = null;
|
|
310
|
-
draftRef.current = null;
|
|
311
|
-
selectionRef.current = null;
|
|
312
|
-
setSession(initialSession());
|
|
313
|
-
}, []);
|
|
314
|
-
return {
|
|
315
|
-
session,
|
|
316
|
-
sessionRef,
|
|
317
|
-
dirty,
|
|
318
|
-
enterEditMode,
|
|
319
|
-
exitEditMode,
|
|
320
|
-
setTool,
|
|
321
|
-
selectIsland,
|
|
322
|
-
clearSelection,
|
|
323
|
-
startSourceEditDraft,
|
|
324
|
-
startAddTextDraft,
|
|
325
|
-
startOcrEditDraft,
|
|
326
|
-
updateDraftText,
|
|
327
|
-
updateDraftAppearance,
|
|
328
|
-
setDraftOverflow,
|
|
329
|
-
commitDraft,
|
|
330
|
-
cancelDraft,
|
|
331
|
-
apply,
|
|
332
|
-
revert,
|
|
333
|
-
onPublish,
|
|
334
|
-
publish,
|
|
335
|
-
checkpoint,
|
|
336
|
-
setAnnotationDirty,
|
|
337
|
-
inspectPage,
|
|
338
|
-
listFonts,
|
|
339
|
-
resetForDocument,
|
|
340
|
-
};
|
|
341
|
-
}
|