@rightkit/document-core 0.1.2 → 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.
Files changed (126) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +31 -7
  3. package/dist/app/chrome.js +1 -1
  4. package/dist/app/documentPrinting.d.ts +0 -6
  5. package/dist/app/documentPrinting.js +0 -18
  6. package/dist/app/shell/nativeMenu.d.ts +3 -2
  7. package/dist/app/shell/nativeMenu.js +8 -5
  8. package/dist/documents/perfEventCounters.d.ts +0 -8
  9. package/dist/documents/perfEventCounters.js +2 -1
  10. package/dist/documents/preview/previewFrame.d.ts +2 -1
  11. package/dist/documents/preview/previewFrame.js +9 -7
  12. package/dist/documents/searchUtils.js +1 -1
  13. package/dist/host.d.ts +36 -0
  14. package/dist/host.js +54 -0
  15. package/dist/index.d.ts +1 -46
  16. package/dist/index.js +1 -46
  17. package/dist/settings/themes.js +1 -1
  18. package/dist/state/bridgeEvents.d.ts +1 -1
  19. package/dist/state/bridgeEvents.js +7 -4
  20. package/dist/state/recentUrls.js +4 -3
  21. package/dist/state/tauriBridge.d.ts +3 -47
  22. package/dist/state/tauriBridge.js +6 -162
  23. package/dist/styles.css +2 -2
  24. package/package.json +16 -87
  25. package/dist/app/shortcutChords.d.ts +0 -6
  26. package/dist/app/shortcutChords.js +0 -14
  27. package/dist/app/useCloseFindOnSurfaceChange.d.ts +0 -14
  28. package/dist/app/useCloseFindOnSurfaceChange.js +0 -23
  29. package/dist/app/useEditText.d.ts +0 -6
  30. package/dist/app/useEditText.js +0 -10
  31. package/dist/appName.d.ts +0 -2
  32. package/dist/appName.js +0 -8
  33. package/dist/documents/FullscreenIcon.d.ts +0 -4
  34. package/dist/documents/FullscreenIcon.js +0 -7
  35. package/dist/documents/mdx/mdxRuntimeScript.d.ts +0 -2
  36. package/dist/documents/mdx/mdxRuntimeScript.js +0 -3
  37. package/dist/documents/mdx/mdxRuntimeScript.txt +0 -584
  38. package/dist/documents/pdf/MacPdfReadPane.d.ts +0 -23
  39. package/dist/documents/pdf/MacPdfReadPane.js +0 -708
  40. package/dist/documents/pdf/NativePdfDocument.d.ts +0 -9
  41. package/dist/documents/pdf/NativePdfDocument.js +0 -41
  42. package/dist/documents/pdf/PdfInlineTextEditor.d.ts +0 -53
  43. package/dist/documents/pdf/PdfInlineTextEditor.js +0 -225
  44. package/dist/documents/pdf/PdfOcrLayer.d.ts +0 -4
  45. package/dist/documents/pdf/PdfOcrLayer.js +0 -30
  46. package/dist/documents/pdf/PdfOutline.d.ts +0 -6
  47. package/dist/documents/pdf/PdfOutline.js +0 -12
  48. package/dist/documents/pdf/PdfPasswordPrompt.d.ts +0 -5
  49. package/dist/documents/pdf/PdfPasswordPrompt.js +0 -24
  50. package/dist/documents/pdf/PdfThumbnails.d.ts +0 -8
  51. package/dist/documents/pdf/PdfThumbnails.js +0 -201
  52. package/dist/documents/pdf/PdfToolbar.d.ts +0 -47
  53. package/dist/documents/pdf/PdfToolbar.js +0 -76
  54. package/dist/documents/pdf/PdfViewer.d.ts +0 -36
  55. package/dist/documents/pdf/PdfViewer.js +0 -1522
  56. package/dist/documents/pdf/PdfViewerBody.d.ts +0 -22
  57. package/dist/documents/pdf/PdfViewerBody.js +0 -42
  58. package/dist/documents/pdf/macPdfNativeBridge.d.ts +0 -118
  59. package/dist/documents/pdf/macPdfNativeBridge.js +0 -134
  60. package/dist/documents/pdf/nearestVisiblePdfPage.d.ts +0 -2
  61. package/dist/documents/pdf/nearestVisiblePdfPage.js +0 -22
  62. package/dist/documents/pdf/pdfBench.d.ts +0 -88
  63. package/dist/documents/pdf/pdfBench.js +0 -535
  64. package/dist/documents/pdf/pdfBenchAutopilot.d.ts +0 -48
  65. package/dist/documents/pdf/pdfBenchAutopilot.js +0 -154
  66. package/dist/documents/pdf/pdfBenchTransport.d.ts +0 -21
  67. package/dist/documents/pdf/pdfBenchTransport.js +0 -233
  68. package/dist/documents/pdf/pdfEditModel.d.ts +0 -294
  69. package/dist/documents/pdf/pdfEditModel.js +0 -105
  70. package/dist/documents/pdf/pdfEditor.d.ts +0 -100
  71. package/dist/documents/pdf/pdfEditor.js +0 -68
  72. package/dist/documents/pdf/pdfOcr.d.ts +0 -36
  73. package/dist/documents/pdf/pdfOcr.js +0 -88
  74. package/dist/documents/pdf/pdfOcrEdit.d.ts +0 -32
  75. package/dist/documents/pdf/pdfOcrEdit.js +0 -138
  76. package/dist/documents/pdf/pdfPrinting.d.ts +0 -28
  77. package/dist/documents/pdf/pdfPrinting.js +0 -110
  78. package/dist/documents/pdf/pdfSave.d.ts +0 -87
  79. package/dist/documents/pdf/pdfSave.js +0 -250
  80. package/dist/documents/pdf/pdfShared.d.ts +0 -16
  81. package/dist/documents/pdf/pdfShared.js +0 -20
  82. package/dist/documents/pdf/pdfToMarkdown.d.ts +0 -11
  83. package/dist/documents/pdf/pdfToMarkdown.js +0 -67
  84. package/dist/documents/pdf/pdfjsLibGlobal.d.ts +0 -1
  85. package/dist/documents/pdf/pdfjsLibGlobal.js +0 -12
  86. package/dist/documents/pdf/toolbarIcons.d.ts +0 -18
  87. package/dist/documents/pdf/toolbarIcons.js +0 -31
  88. package/dist/documents/pdf/usePdfDocumentLoader.d.ts +0 -40
  89. package/dist/documents/pdf/usePdfDocumentLoader.js +0 -243
  90. package/dist/documents/pdf/usePdfEditSession.d.ts +0 -88
  91. package/dist/documents/pdf/usePdfEditSession.js +0 -341
  92. package/dist/documents/pdf/usePdfEditor.d.ts +0 -59
  93. package/dist/documents/pdf/usePdfEditor.js +0 -317
  94. package/dist/documents/pdf/usePdfFit.d.ts +0 -19
  95. package/dist/documents/pdf/usePdfFit.js +0 -55
  96. package/dist/documents/pdf/usePdfOcr.d.ts +0 -38
  97. package/dist/documents/pdf/usePdfOcr.js +0 -337
  98. package/dist/documents/pdf/usePdfPreviewRecompose.d.ts +0 -28
  99. package/dist/documents/pdf/usePdfPreviewRecompose.js +0 -256
  100. package/dist/documents/pdf/usePdfSavePlan.d.ts +0 -61
  101. package/dist/documents/pdf/usePdfSavePlan.js +0 -287
  102. package/dist/documents/pdf/usePdfSearch.d.ts +0 -31
  103. package/dist/documents/pdf/usePdfSearch.js +0 -367
  104. package/dist/documents/pdf/usePdfViewControls.d.ts +0 -72
  105. package/dist/documents/pdf/usePdfViewControls.js +0 -152
  106. package/dist/documents/scopedSelectAll.d.ts +0 -5
  107. package/dist/documents/scopedSelectAll.js +0 -77
  108. package/dist/documents/txt/cmScroll.d.ts +0 -11
  109. package/dist/documents/txt/cmScroll.js +0 -52
  110. package/dist/documents/useMirroredEditText.d.ts +0 -15
  111. package/dist/documents/useMirroredEditText.js +0 -29
  112. package/dist/documents/useSplitResize.d.ts +0 -7
  113. package/dist/documents/useSplitResize.js +0 -37
  114. package/dist/settings/rk-settings.css +0 -38
  115. package/dist/settings/sections/EditorSection.d.ts +0 -1
  116. package/dist/settings/sections/EditorSection.js +0 -18
  117. package/dist/settings/sections/FileAssociationsSection.d.ts +0 -1
  118. package/dist/settings/sections/FileAssociationsSection.js +0 -5
  119. package/dist/settings/sections/MacFileAssociationsBlock.d.ts +0 -33
  120. package/dist/settings/sections/MacFileAssociationsBlock.js +0 -151
  121. package/dist/settings/sections/ThemeSection.d.ts +0 -1
  122. package/dist/settings/sections/ThemeSection.js +0 -31
  123. package/dist/settings/sections/TypographySection.d.ts +0 -1
  124. package/dist/settings/sections/TypographySection.js +0 -19
  125. package/dist/settings/sections/onOff.d.ts +0 -11
  126. package/dist/settings/sections/onOff.js +0 -8
@@ -1,61 +0,0 @@
1
- import { type MutableRefObject } from "react";
2
- import * as pdfjs from "pdfjs-dist";
3
- import type { PDFDocumentProxy } from "pdfjs-dist";
4
- import type { PdfEditCommand, PdfPageGraph, PdfSavePlan } from "./pdfEditModel.js";
5
- import type { usePdfEditSession } from "./usePdfEditSession.js";
6
- import type { usePdfEditor } from "./usePdfEditor.js";
7
- /**
8
- * Forces a fresh native stream fetch after a successful same-path commit. Tauri's stream URL can
9
- * remain byte-cacheable inside WKWebView even when its handler reads current disk bytes; binding
10
- * URL identity to committed revision prevents a second edit from inspecting stale pre-commit PDF
11
- * bytes. Browser QA reloads from its byte store instead and must keep its static URL unchanged.
12
- */
13
- export declare function withPdfRevisionCacheBust(url: string, revision: string): string;
14
- /** Invalidates published candidate preview state after disk commit, allowing any in-flight
15
- * recompose to retire itself by sequence before it can publish against the old source revision. */
16
- export declare function invalidatePdfPreview(previewSeqRef: MutableRefObject<number>, previewLoadingTaskRef: MutableRefObject<ReturnType<typeof pdfjs.getDocument> | null>, previewDocRef: MutableRefObject<PDFDocumentProxy | null>, previewPagesRef: MutableRefObject<Set<number>>): void;
17
- interface PdfSavePlanDeps {
18
- documentPath: string;
19
- url?: string;
20
- editSession: ReturnType<typeof usePdfEditSession>;
21
- pdfEditor: ReturnType<typeof usePdfEditor>;
22
- /** Every explicit "commit the active draft" call site routes through this single function. */
23
- handleCommitDraft: () => PdfEditCommand | null;
24
- /** Rust source graph cache (per page) — cleared on document switch and after commit. */
25
- pageGraphsRef: MutableRefObject<Map<number, PdfPageGraph>>;
26
- /** Bumped whenever pageGraphsRef is cleared; lets in-flight inspections detect the clear. */
27
- pageGraphsGenRef: MutableRefObject<number>;
28
- /** Best-known document_sha256 (§5.1), refreshed to the committed revision's sha after Save. */
29
- sourceShaRef: MutableRefObject<string | null>;
30
- visibleRef: MutableRefObject<Set<number>>;
31
- renderedRef: MutableRefObject<Map<number, () => void>>;
32
- /** Indirection to the per-page renderer so this path never grows renderPage's dep list. */
33
- renderPageRef: MutableRefObject<(n: number) => Promise<void>>;
34
- /** Shared preview sequence used by fallback invalidation before caller wires hook callback. */
35
- previewSeqRef?: MutableRefObject<number>;
36
- /** Invalidation callback from usePdfPreviewRecompose; handles queued and pending work. */
37
- invalidatePreview?: () => void;
38
- previewLoadingTaskRef: MutableRefObject<ReturnType<typeof pdfjs.getDocument> | null>;
39
- previewDocRef: MutableRefObject<PDFDocumentProxy | null>;
40
- previewPagesRef: MutableRefObject<Set<number>>;
41
- /** Post-commit affected pages, drained by the `[pdfEditor.ready, pdfDoc]` catch-up effect. */
42
- pendingReloadRerenderPagesRef: MutableRefObject<Set<number> | null>;
43
- /** Deferred document swap that disposes the stale task only after React commits the new pdfDoc. */
44
- replaceDocument: (doc: PDFDocumentProxy, task: ReturnType<typeof pdfjs.getDocument>) => void;
45
- /** Register this pane's full two-phase source/OCR edit save plan (§10.1-§10.3). */
46
- registerPdfSavePlan?: (fn: (() => Promise<PdfSavePlan>) | null) => void;
47
- /** Optional outer lock: blocks edit/history mutations during prepare/commit/reload. */
48
- setSaveLocked?: (locked: boolean) => void;
49
- }
50
- /**
51
- * §10 save orchestration — additive PdfSavePlan registration: commit the active draft, gather
52
- * source commands + annotation bytes, run the two-phase prepare/verify/commit, then reload the
53
- * document from disk truth and re-render affected pages. Extracted from PdfViewer verbatim — no
54
- * behavior change.
55
- */
56
- export declare function usePdfSavePlan({ documentPath, url, editSession, pdfEditor, handleCommitDraft, pageGraphsRef, pageGraphsGenRef, sourceShaRef, visibleRef, renderedRef, renderPageRef, previewSeqRef, invalidatePreview, previewLoadingTaskRef, previewDocRef, previewPagesRef, pendingReloadRerenderPagesRef, replaceDocument, registerPdfSavePlan, setSaveLocked, }: PdfSavePlanDeps): {
57
- retryReload: () => Promise<boolean>;
58
- isReloadRequired: () => boolean;
59
- hasPreservedEdits: () => boolean;
60
- };
61
- export {};
@@ -1,287 +0,0 @@
1
- import { useCallback, useEffect, useRef } from "react";
2
- import * as pdfjs from "pdfjs-dist";
3
- import { pdfDocumentInit, pdfWasmUrl } from "@rightkit/pdf-ui";
4
- import { prepareAndVerifyPdfSave } from "./pdfSave.js";
5
- // New PDF-edit backend commands (docs/PDF-EDITOR-NOW.md §10.1-§10.3), imported by name from
6
- // tauriBridge.ts against the frozen type contract in pdfEditModel.ts.
7
- import { preparePdfSave, commitPdfSave } from "../../state/tauriBridge.js";
8
- import { logError, logEvent } from "../../state/diagnostics.js";
9
- /**
10
- * Forces a fresh native stream fetch after a successful same-path commit. Tauri's stream URL can
11
- * remain byte-cacheable inside WKWebView even when its handler reads current disk bytes; binding
12
- * URL identity to committed revision prevents a second edit from inspecting stale pre-commit PDF
13
- * bytes. Browser QA reloads from its byte store instead and must keep its static URL unchanged.
14
- */
15
- export function withPdfRevisionCacheBust(url, revision) {
16
- const hashIndex = url.indexOf("#");
17
- const base = hashIndex >= 0 ? url.slice(0, hashIndex) : url;
18
- const hash = hashIndex >= 0 ? url.slice(hashIndex) : "";
19
- const separator = base.includes("?") ? "&" : "?";
20
- return `${base}${separator}vr_revision=${encodeURIComponent(revision)}${hash}`;
21
- }
22
- /** Invalidates published candidate preview state after disk commit, allowing any in-flight
23
- * recompose to retire itself by sequence before it can publish against the old source revision. */
24
- export function invalidatePdfPreview(previewSeqRef, previewLoadingTaskRef, previewDocRef, previewPagesRef) {
25
- previewSeqRef.current += 1;
26
- const stalePreview = previewLoadingTaskRef.current;
27
- previewLoadingTaskRef.current = null;
28
- previewDocRef.current = null;
29
- previewPagesRef.current = new Set();
30
- if (stalePreview && !stalePreview.destroyed)
31
- void stalePreview.destroy();
32
- }
33
- function sourceCommandSignature(commands) {
34
- return JSON.stringify(commands);
35
- }
36
- function latestSourceCommands(editSession) {
37
- return editSession.sessionRef?.current.sourceCommands ?? editSession.session.sourceCommands;
38
- }
39
- function latestEditSignature(editSession) {
40
- const session = editSession.sessionRef?.current ?? editSession.session;
41
- return `${sourceCommandSignature(session.sourceCommands)}|draft:${JSON.stringify(session.draft)}`;
42
- }
43
- /**
44
- * §10 save orchestration — additive PdfSavePlan registration: commit the active draft, gather
45
- * source commands + annotation bytes, run the two-phase prepare/verify/commit, then reload the
46
- * document from disk truth and re-render affected pages. Extracted from PdfViewer verbatim — no
47
- * behavior change.
48
- */
49
- export function usePdfSavePlan({ documentPath, url, editSession, pdfEditor, handleCommitDraft, pageGraphsRef, pageGraphsGenRef, sourceShaRef, visibleRef, renderedRef, renderPageRef, previewSeqRef, invalidatePreview, previewLoadingTaskRef, previewDocRef, previewPagesRef, pendingReloadRerenderPagesRef, replaceDocument, registerPdfSavePlan, setSaveLocked, }) {
50
- const reloadRequiredRef = useRef(false);
51
- const reloadPendingNewerEditsRef = useRef(false);
52
- const preservedUnsavedEditsRef = useRef(false);
53
- // biome-ignore lint/correctness/useExhaustiveDependencies: refs arrive as stable hook params
54
- const buildSavePlan = useCallback(async () => {
55
- if (reloadRequiredRef.current) {
56
- throw new Error(reloadPendingNewerEditsRef.current
57
- ? "PDF was saved while newer edits were active; reopen document before another save"
58
- : "PDF was saved, but document reload is required before another save");
59
- }
60
- // Lock synchronously before any inspect/serialize await so initial save snapshot is stable.
61
- setSaveLocked?.(true);
62
- const initialAnnotationState = pdfEditor.annotationStateToken();
63
- const committed = handleCommitDraft(); // §10.1 step 1: commit active valid draft first
64
- const latestCommands = latestSourceCommands(editSession);
65
- const sourceCommands = committed && !latestCommands.some((command) => command.id === committed.id)
66
- ? [...latestCommands, committed]
67
- : latestCommands;
68
- const savedSourceSignature = sourceCommandSignature(sourceCommands);
69
- const savedEditSignature = latestEditSignature(editSession);
70
- // An annotation-only save (or a save right after Save As, before any page was ever clicked for
71
- // source editing) never populated sourceShaRef via handlePageClick's inspectPage call — inspect
72
- // page 1 now so `expectedSourceSha256` isn't sent empty, which the backend rejects as StaleSource.
73
- if (!sourceShaRef.current) {
74
- try {
75
- const graph = await editSession.inspectPage(1);
76
- pageGraphsRef.current.set(1, graph);
77
- sourceShaRef.current = graph.documentSha256;
78
- }
79
- catch {
80
- logError("pdf-editor:inspect");
81
- }
82
- }
83
- // pdf.js warns when saveDocument() is called with empty annotation storage. Native
84
- // source-command composition does not consume this byte channel, but Tauri still requires a
85
- // non-empty raw body on WKWebView's successful response path, so source-only saves send one
86
- // sentinel byte while real annotation saves serialize their actual PDF bytes.
87
- const hasAnnotationEdits = pdfEditor.dirty;
88
- const annotationPdfBytes = hasAnnotationEdits
89
- ? ((await pdfEditor.saveBytes()) ?? new Uint8Array())
90
- : new Uint8Array([0]);
91
- // pdf.js may normalize an editor while synchronously building save bytes (FreeText updates
92
- // its serialized hash without representing a user edit). Use that exact post-serialization
93
- // token as the save baseline when available. A null token means saveBytes observed a mutation
94
- // during/after serialization, so initialAnnotationState remains the fail-closed baseline.
95
- const annotationSaveState = (hasAnnotationEdits ? pdfEditor.serializedAnnotationStateToken() : null) ||
96
- initialAnnotationState;
97
- logEvent("pdf-save:plan-input");
98
- // `commit_pdf_save` never returns affected pages (tauriBridge.ts's `commitPdfSave` always
99
- // returns `[]` — the backend result type carries no such field). Derive them locally instead,
100
- // same rule tauriBridge's own preview path uses, plus every currently visible page when real
101
- // annotation edits were serialized (those can touch any visible page, not just source-edited
102
- // ones).
103
- const affectedPages = new Set();
104
- for (const command of sourceCommands) {
105
- affectedPages.add(command.kind === "replaceSourceText" ? command.locator.page : command.page);
106
- }
107
- if (hasAnnotationEdits) {
108
- for (const page of visibleRef.current)
109
- affectedPages.add(page);
110
- }
111
- let plan;
112
- try {
113
- plan = await prepareAndVerifyPdfSave({ preparePdfSave, commitPdfSave }, {
114
- sourcePath: documentPath,
115
- expectedSourceSha256: sourceShaRef.current ?? "",
116
- annotationPdfBytes,
117
- sourceCommands,
118
- });
119
- }
120
- catch (error) {
121
- setSaveLocked?.(false);
122
- throw error;
123
- }
124
- return {
125
- commit: async (targetPath) => {
126
- setSaveLocked?.(true);
127
- // Prepare may have awaited inspection/serialization while callers still held a live
128
- // draft. Refuse to commit a stale plan before it reaches disk; preserving newer edits is
129
- // safer than committing an older snapshot then forcing a re-open.
130
- const editsChangedBeforeCommit = pdfEditor.annotationStateToken() !== annotationSaveState ||
131
- latestEditSignature(editSession) !== savedEditSignature;
132
- if (editsChangedBeforeCommit) {
133
- setSaveLocked?.(false);
134
- throw new Error("PDF edits changed while save was preparing; save again");
135
- }
136
- let result;
137
- try {
138
- result = await plan.commit(targetPath);
139
- }
140
- catch (error) {
141
- setSaveLocked?.(false);
142
- throw error;
143
- }
144
- const annotationChangedDuringSave = pdfEditor.annotationStateToken() !== annotationSaveState;
145
- // A successful commit is the annotation checkpoint even when PDF reload fails. The hook
146
- // keeps an epoch/hash captured at serialization, so edits made while prepare/commit was in
147
- // flight remain dirty instead of being destroyed by reload.
148
- const annotationsCheckpointed = !annotationChangedDuringSave &&
149
- (hasAnnotationEdits ? pdfEditor.checkpointAnnotationSave() : true);
150
- const newerSourceEdits = sourceCommandSignature(latestSourceCommands(editSession)) !== savedSourceSignature;
151
- // Invalidate any old in-memory candidate before loading disk truth. In-flight recomposes
152
- // observe the sequence change and retire their own tasks when they settle.
153
- if (invalidatePreview) {
154
- invalidatePreview();
155
- }
156
- else if (previewSeqRef) {
157
- invalidatePdfPreview(previewSeqRef, previewLoadingTaskRef, previewDocRef, previewPagesRef);
158
- }
159
- await plan.didCommit(result);
160
- // §10.3: successful commit refreshes graph/revision. If the user changed source or
161
- // annotations while the async save was running, keep those edits in memory and fail closed
162
- // until a fresh document base is available; replacing pdfDoc here would silently destroy
163
- // them and leave the next annotation save serializing an obsolete base.
164
- pageGraphsRef.current = new Map();
165
- pageGraphsGenRef.current++;
166
- sourceShaRef.current = result.revision.documentSha256;
167
- if (newerSourceEdits || !annotationsCheckpointed) {
168
- reloadRequiredRef.current = true;
169
- reloadPendingNewerEditsRef.current = true;
170
- preservedUnsavedEditsRef.current = true;
171
- logError("pdf-editor:reload-required:memory-edits");
172
- setSaveLocked?.(false);
173
- return { ...result, preservedUnsavedEdits: true };
174
- }
175
- editSession.resetForDocument();
176
- editSession.checkpoint();
177
- let reloaded = false;
178
- if (url) {
179
- try {
180
- // The backend stream `url` is a revocable token that re-resolves to the same
181
- // (now-committed) file (see tauriBridge's stream-token comment) — re-fetching it gives
182
- // a fresh PDFDocumentProxy reflecting disk truth without needing useDocumentSaver.ts
183
- // (a sibling lane) to push bytes back into this component. `replaceDocument` defers
184
- // disposing the stale task until React has committed this fresh `pdfDoc`, so pages
185
- // still rendering off the old document mid-swap can't crash against a destroyed worker.
186
- //
187
- // Native reloads bind URL identity to committed revision below so WKWebView cannot
188
- // hand the next edit session cached pre-commit bytes.
189
- const freshTask = pdfjs.getDocument(pdfDocumentInit(withPdfRevisionCacheBust(url, result.revision.documentSha256), {
190
- wasmUrl: pdfWasmUrl(),
191
- }));
192
- const fresh = await freshTask.promise;
193
- // A user can mutate annotation storage while fresh bytes are loading. Check again
194
- // immediately before swapping pdfDoc so reload cannot erase that newer state.
195
- if (pdfEditor.annotationStateToken() !== annotationSaveState ||
196
- (hasAnnotationEdits && !pdfEditor.checkpointAnnotationSave())) {
197
- if (!freshTask.destroyed)
198
- void freshTask.destroy();
199
- reloadRequiredRef.current = true;
200
- reloadPendingNewerEditsRef.current = true;
201
- preservedUnsavedEditsRef.current = true;
202
- logError("pdf-editor:reload-required:annotation-edits");
203
- setSaveLocked?.(false);
204
- return { ...result, preservedUnsavedEdits: true };
205
- }
206
- replaceDocument(fresh, freshTask);
207
- reloaded = true;
208
- }
209
- catch {
210
- logError("pdf-editor:reload");
211
- }
212
- }
213
- if (!reloaded) {
214
- reloadRequiredRef.current = true;
215
- reloadPendingNewerEditsRef.current = false;
216
- logError("pdf-editor:reload-required:reload-failed");
217
- }
218
- else {
219
- reloadRequiredRef.current = false;
220
- reloadPendingNewerEditsRef.current = false;
221
- preservedUnsavedEditsRef.current = false;
222
- }
223
- // Only drop the in-memory candidate preview once the fresh on-disk document is actually
224
- // showing — if the reload above failed, keep it so previously-edited pages keep rendering
225
- // from the (still-valid) candidate instead of silently reverting to stale pre-edit content.
226
- if (reloaded) {
227
- void previewLoadingTaskRef.current?.destroy();
228
- previewLoadingTaskRef.current = null;
229
- previewDocRef.current = null;
230
- previewPagesRef.current = new Set();
231
- }
232
- if (reloaded) {
233
- // Defer to the `[pdfEditor.ready, pdfDoc]` catch-up effect (below) instead of rendering
234
- // here: rendering synchronously, right after `replaceDocument`, races React's own
235
- // processing of the `pdfDoc` swap — see that effect's doc comment.
236
- pendingReloadRerenderPagesRef.current = new Set(affectedPages);
237
- }
238
- else {
239
- for (const n of affectedPages) {
240
- renderedRef.current.get(n)?.();
241
- void renderPageRef.current(n);
242
- }
243
- }
244
- setSaveLocked?.(false);
245
- return result;
246
- },
247
- didCommit: async () => {
248
- /* no-op seam — see pdfSave.ts doc comment; app-level bookkeeping owns this. */
249
- },
250
- };
251
- }, [documentPath, editSession, handleCommitDraft, pdfEditor, url, replaceDocument]);
252
- // biome-ignore lint/correctness/useExhaustiveDependencies: refs arrive as stable hook params
253
- const retryReload = useCallback(async () => {
254
- if (!reloadRequiredRef.current || reloadPendingNewerEditsRef.current || !url)
255
- return false;
256
- setSaveLocked?.(true);
257
- try {
258
- const freshTask = pdfjs.getDocument(pdfDocumentInit(withPdfRevisionCacheBust(url, sourceShaRef.current ?? "retry"), {
259
- wasmUrl: pdfWasmUrl(),
260
- }));
261
- const fresh = await freshTask.promise;
262
- replaceDocument(fresh, freshTask);
263
- reloadRequiredRef.current = false;
264
- reloadPendingNewerEditsRef.current = false;
265
- preservedUnsavedEditsRef.current = false;
266
- return true;
267
- }
268
- catch {
269
- logError("pdf-editor:reload-retry");
270
- return false;
271
- }
272
- finally {
273
- setSaveLocked?.(false);
274
- }
275
- }, [documentPath, replaceDocument, setSaveLocked, url]);
276
- useEffect(() => {
277
- if (!registerPdfSavePlan)
278
- return;
279
- registerPdfSavePlan(buildSavePlan);
280
- return () => registerPdfSavePlan(null);
281
- }, [registerPdfSavePlan, buildSavePlan]);
282
- return {
283
- retryReload,
284
- isReloadRequired: () => reloadRequiredRef.current,
285
- hasPreservedEdits: () => preservedUnsavedEditsRef.current,
286
- };
287
- }
@@ -1,31 +0,0 @@
1
- import type { PDFDocumentProxy } from "pdfjs-dist";
2
- import type { SearchOptions, SearchProvider } from "../types.js";
3
- export declare const PDF_SEARCH_CACHE_MAX_BYTES: number;
4
- /**
5
- * Read page text through same streaming API used by rendered text layers. WKWebView's PDF.js worker
6
- * can finish a stream-backed text layer while a parallel getTextContent() request remains empty;
7
- * search must consume stream chunks so native search sees text already visible on page.
8
- */
9
- export declare function readPdfPageText(page: Awaited<ReturnType<PDFDocumentProxy["getPage"]>>, signal?: AbortSignal): Promise<string>;
10
- /**
11
- * PDF find, exposed as the app's shared `SearchProvider`. Owns the match model + committed query +
12
- * options (read by the viewer's render path via the returned refs to draw <mark>s, and as effect
13
- * deps to re-highlight already-rendered pages). Page text is extracted once per page and cached
14
- * (original case — case folding happens at match time); rapid typing supersedes in-flight runs via a
15
- * sequence guard. Supports match-case + whole-word; read-only (no replace).
16
- */
17
- export declare function usePdfSearch({ url, pdfRef, slotRefs, scrollRef, goToPage, registerSearchProvider, }: {
18
- url?: string;
19
- pdfRef: React.MutableRefObject<PDFDocumentProxy | null>;
20
- slotRefs: React.MutableRefObject<Map<number, HTMLDivElement>>;
21
- scrollRef: React.RefObject<HTMLDivElement | null>;
22
- goToPage: (n: number) => void;
23
- registerSearchProvider?: (provider: SearchProvider | null) => void;
24
- }): {
25
- committedQuery: string;
26
- queryRef: import("react").RefObject<string>;
27
- optionsRef: import("react").RefObject<SearchOptions>;
28
- setPageText: (page: number, text: string) => void;
29
- invalidatePageText: (page: number) => void;
30
- rehighlight: () => void;
31
- };