@rightkit/document-core 0.1.0 → 0.1.2
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 +17 -0
- package/dist/documents/pdf/usePdfSavePlan.js +7 -23
- package/dist/documents/toolbarPrimitives.js +1 -1
- package/dist/index.d.ts +0 -4
- package/dist/index.js +0 -4
- package/dist/settings/sections/MacFileAssociationsBlock.js +2 -2
- package/dist/settings/themes.js +9 -1
- package/dist/state/tauriBridge.d.ts +0 -14
- package/dist/state/tauriBridge.js +4 -137
- package/package.json +10 -12
- 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
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.2 (unpublished)
|
|
4
|
+
|
|
5
|
+
- Removed (breaking, Adrian, 2026-11-03: "no dummy data, no fucking made up shit. real data."): the browser-QA fixture layer. `BROWSER_QA_BUILD`, `isBrowserQaMode()`, `qaReloadPdfBytes()`, every fixture branch of `state/tauriBridge` and the `state/browserQa`, `browserQaText`, `browserQaDocx` and `browserQaPdf` modules (and their `index` exports) are gone. Outside the Tauri webview the bridge keeps its plain no-op returns.
|
|
6
|
+
- Removed (breaking, Adrian, 2026-11-03: no dummy data): the in-memory browser-QA licence client. `browserQaLicenseClient()` is gone from `state/tauriBridge`, `state/browserQaLicense` is deleted, and the `@rightkit/license` dependency with it (`@rightkit/license/testing` no longer exists).
|
|
7
|
+
- `@rightkit/license` is a dependency again: the settings sections `document-core` exports come from `@rightkit/settings-ui`, whose `sections` entry imports its peer `@rightkit/license`; without it an installed `document-core` failed to import.
|
|
8
|
+
|
|
9
|
+
## 0.1.1
|
|
10
|
+
|
|
11
|
+
- Ask 1: the host bridge (`state/tauriBridge`), diagnostics (`state/diagnostics`) and platform modules are the single copy the document editors import. Hosts delete their own copies. Host-facing changes a host must adopt:
|
|
12
|
+
- Browser-QA build switch is `VITE_RIGHTKIT_BROWSER_QA=1` (was the host-named variable). A host QA build sets this variable; without it the QA fixtures are dropped.
|
|
13
|
+
- `parseViewRightError` is now `parseHostError`.
|
|
14
|
+
- The file-association field `viewrightIsPreferred` is now `hostIsPreferred`.
|
|
15
|
+
- Diagnostics allowlist adds `pdf-editor:viewer-load`.
|
|
16
|
+
- Ask 2 and 3 are in `@rightkit/markdown-editor` 0.1.1; ask 4 pins are in `@rightkit/docx-editor` 0.1.1 and `@rightkit/html-editor` 0.1.1.
|
|
17
|
+
- The accent preset `blue` reads its name from `hostAppName()` when displayed, so `setHostAppName()` at startup takes effect.
|
|
18
|
+
- Wording fix in the toolbar primitives comment; no behaviour change.
|
|
19
|
+
|
|
3
20
|
## 0.1.0
|
|
4
21
|
|
|
5
22
|
- First release. Shared base for the document editors: platform bridge, settings, state, PDF helpers and the format registry.
|
|
@@ -4,7 +4,7 @@ import { pdfDocumentInit, pdfWasmUrl } from "@rightkit/pdf-ui";
|
|
|
4
4
|
import { prepareAndVerifyPdfSave } from "./pdfSave.js";
|
|
5
5
|
// New PDF-edit backend commands (docs/PDF-EDITOR-NOW.md §10.1-§10.3), imported by name from
|
|
6
6
|
// tauriBridge.ts against the frozen type contract in pdfEditModel.ts.
|
|
7
|
-
import { preparePdfSave, commitPdfSave
|
|
7
|
+
import { preparePdfSave, commitPdfSave } from "../../state/tauriBridge.js";
|
|
8
8
|
import { logError, logEvent } from "../../state/diagnostics.js";
|
|
9
9
|
/**
|
|
10
10
|
* Forces a fresh native stream fetch after a successful same-path commit. Tauri's stream URL can
|
|
@@ -184,24 +184,11 @@ export function usePdfSavePlan({ documentPath, url, editSession, pdfEditor, hand
|
|
|
184
184
|
// disposing the stale task until React has committed this fresh `pdfDoc`, so pages
|
|
185
185
|
// still rendering off the old document mid-swap can't crash against a destroyed worker.
|
|
186
186
|
//
|
|
187
|
-
// Browser QA has no such revocable token: `url` there is a static `data:` URL captured
|
|
188
|
-
// once at document-open time, so re-fetching it would always reload the ORIGINAL
|
|
189
|
-
// pre-edit bytes — silently discarding every annotation just committed, and (worse)
|
|
190
|
-
// breaking a second save's incremental-update prefix check against the QA disk's
|
|
191
|
-
// now-updated bytes. `qaReloadPdfBytes` resolves to `null` outside QA mode, so this is a
|
|
192
187
|
// Native reloads bind URL identity to committed revision below so WKWebView cannot
|
|
193
188
|
// hand the next edit session cached pre-commit bytes.
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
// copied on read, so later saves keep seeing the same array), so handing it over
|
|
198
|
-
// directly would corrupt that entry the moment this load transfers it. Same convention
|
|
199
|
-
// `handleRecomposePreview` above already uses for its own `data:` load.
|
|
200
|
-
const freshTask = pdfjs.getDocument(qaBytes
|
|
201
|
-
? pdfDocumentInit(qaBytes.slice(), { wasmUrl: pdfWasmUrl() })
|
|
202
|
-
: pdfDocumentInit(withPdfRevisionCacheBust(url, result.revision.documentSha256), {
|
|
203
|
-
wasmUrl: pdfWasmUrl(),
|
|
204
|
-
}));
|
|
189
|
+
const freshTask = pdfjs.getDocument(pdfDocumentInit(withPdfRevisionCacheBust(url, result.revision.documentSha256), {
|
|
190
|
+
wasmUrl: pdfWasmUrl(),
|
|
191
|
+
}));
|
|
205
192
|
const fresh = await freshTask.promise;
|
|
206
193
|
// A user can mutate annotation storage while fresh bytes are loading. Check again
|
|
207
194
|
// immediately before swapping pdfDoc so reload cannot erase that newer state.
|
|
@@ -268,12 +255,9 @@ export function usePdfSavePlan({ documentPath, url, editSession, pdfEditor, hand
|
|
|
268
255
|
return false;
|
|
269
256
|
setSaveLocked?.(true);
|
|
270
257
|
try {
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
: pdfDocumentInit(withPdfRevisionCacheBust(url, sourceShaRef.current ?? "retry"), {
|
|
275
|
-
wasmUrl: pdfWasmUrl(),
|
|
276
|
-
}));
|
|
258
|
+
const freshTask = pdfjs.getDocument(pdfDocumentInit(withPdfRevisionCacheBust(url, sourceShaRef.current ?? "retry"), {
|
|
259
|
+
wasmUrl: pdfWasmUrl(),
|
|
260
|
+
}));
|
|
277
261
|
const fresh = await freshTask.promise;
|
|
278
262
|
replaceDocument(fresh, freshTask);
|
|
279
263
|
reloadRequiredRef.current = false;
|
|
@@ -4,7 +4,7 @@ import { createPortal } from "react-dom";
|
|
|
4
4
|
import { createShortcutRegistry } from "@rightkit/react-shell";
|
|
5
5
|
import { isMac } from "../platform.js";
|
|
6
6
|
import { floatingPanelStyle, moveIndex, typeaheadMatch, useDismiss, } from "@rightkit/platform-ui/headless";
|
|
7
|
-
// Native
|
|
7
|
+
// Native host app toolbar/menu primitives — square, copper-on-theme, compact app UI.
|
|
8
8
|
// Zero eigenpal UI. Every interactive element uses onMouseDown + preventDefault so clicking
|
|
9
9
|
// a control never steals the document selection before the command bridge formats it.
|
|
10
10
|
const focusSafe = (fn) => (e) => {
|
package/dist/index.d.ts
CHANGED
|
@@ -78,10 +78,6 @@ export * from "./settings/settings.js";
|
|
|
78
78
|
export * from "./settings/settingsBackend.js";
|
|
79
79
|
export * from "./settings/themes.js";
|
|
80
80
|
export * from "./state/bridgeEvents.js";
|
|
81
|
-
export * from "./state/browserQa.js";
|
|
82
|
-
export * from "./state/browserQaDocx.js";
|
|
83
|
-
export * from "./state/browserQaPdf.js";
|
|
84
|
-
export * from "./state/browserQaText.js";
|
|
85
81
|
export * from "./state/diagnostics.js";
|
|
86
82
|
export * from "./state/errors.js";
|
|
87
83
|
export * from "./state/motion.js";
|
package/dist/index.js
CHANGED
|
@@ -80,10 +80,6 @@ export * from "./settings/settings.js";
|
|
|
80
80
|
export * from "./settings/settingsBackend.js";
|
|
81
81
|
export * from "./settings/themes.js";
|
|
82
82
|
export * from "./state/bridgeEvents.js";
|
|
83
|
-
export * from "./state/browserQa.js";
|
|
84
|
-
export * from "./state/browserQaDocx.js";
|
|
85
|
-
export * from "./state/browserQaPdf.js";
|
|
86
|
-
export * from "./state/browserQaText.js";
|
|
87
83
|
export * from "./state/diagnostics.js";
|
|
88
84
|
export * from "./state/errors.js";
|
|
89
85
|
export * from "./state/motion.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useMemo, useState } from "react";
|
|
3
3
|
import { documentFormatEntries } from "../../documents/documentFormats.js";
|
|
4
|
-
import { fileAssociationStatus, inTauri,
|
|
4
|
+
import { fileAssociationStatus, inTauri, openFileAssociationSettings, setFileAssociationDefaults, } from "../../state/tauriBridge.js";
|
|
5
5
|
import { isMac, isWindows } from "../../platform.js";
|
|
6
6
|
import { hostAppName } from "../../appName.js";
|
|
7
7
|
export const SUPPORTED_ASSOCIATION_FORMATS = [
|
|
@@ -66,7 +66,7 @@ export function FileAssociationsBlock() {
|
|
|
66
66
|
const [notice, setNotice] = useState("");
|
|
67
67
|
const mac = isMac();
|
|
68
68
|
const windows = isWindows();
|
|
69
|
-
const available = (mac || windows) &&
|
|
69
|
+
const available = (mac || windows) && inTauri();
|
|
70
70
|
const allSelected = allFormatsSelected(selected);
|
|
71
71
|
const extensionCounts = useMemo(() => new Map(SUPPORTED_ASSOCIATION_FORMATS.map((format) => [
|
|
72
72
|
format.id,
|
package/dist/settings/themes.js
CHANGED
|
@@ -114,7 +114,15 @@ export const BASE_THEME_IDS = [
|
|
|
114
114
|
];
|
|
115
115
|
/** Each choice owns a dark- and light-body value so the same intent remains readable. */
|
|
116
116
|
export const ACCENT_PRESETS = [
|
|
117
|
-
|
|
117
|
+
// A getter: the name reads hostAppName() when displayed, not at import, so setHostAppName() at host startup takes effect.
|
|
118
|
+
{
|
|
119
|
+
id: "blue",
|
|
120
|
+
get name() {
|
|
121
|
+
return `${hostAppName()} blue`;
|
|
122
|
+
},
|
|
123
|
+
dark: "#3FA3DE",
|
|
124
|
+
light: "#0C6AA5",
|
|
125
|
+
},
|
|
118
126
|
{ id: "copper", name: "Copper", dark: "#C46A3D", light: "#9D5530" },
|
|
119
127
|
{ id: "iris", name: "Iris", dark: "#A78BFA", light: "#6D4BC3" },
|
|
120
128
|
{ id: "amber", name: "Amber", dark: "#E0A458", light: "#87570E" },
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { type FileFingerprint, type LoadedDocument } from "../documents/types.js";
|
|
2
|
-
import type { LicenseClient } from "@rightkit/license";
|
|
3
2
|
import { isTauri } from "@rightkit/platform-ui";
|
|
4
3
|
import { type Shell } from "@rightkit/tauri-shell";
|
|
5
4
|
/** True when running inside the Tauri webview (vs. plain `vite dev` in a browser). */
|
|
@@ -12,7 +11,6 @@ export declare function getShell(): Shell;
|
|
|
12
11
|
* reports a copy that did not happen (an absent browser clipboard used to read as success).
|
|
13
12
|
*/
|
|
14
13
|
export declare function writeClipboardText(text: string): Promise<void>;
|
|
15
|
-
export declare function isBrowserQaMode(): boolean;
|
|
16
14
|
declare function invoke<T>(cmd: string, args?: Record<string, unknown>): Promise<T>;
|
|
17
15
|
declare function listen<T>(event: string, callback: (event: {
|
|
18
16
|
payload: T;
|
|
@@ -26,12 +24,6 @@ export declare const nativeIpc: {
|
|
|
26
24
|
invoke: typeof invoke;
|
|
27
25
|
listen: typeof listen;
|
|
28
26
|
};
|
|
29
|
-
/**
|
|
30
|
-
* Browser-QA licence client (`@rightkit/license/testing`, in-memory, no network). Returns null
|
|
31
|
-
* outside browser QA, so the caller uses the native client. The `testing` export resolves only
|
|
32
|
-
* under the `development` condition and the call site folds to `false` in a release build.
|
|
33
|
-
*/
|
|
34
|
-
export declare function browserQaLicenseClient(): Promise<LicenseClient | null>;
|
|
35
27
|
/** Classified shape every bridge caller can rely on. `code` matches the Rust `HostError`
|
|
36
28
|
* variant in snake_case (e.g. "broker_denied", "file_not_found"), or "unknown" when the rejection
|
|
37
29
|
* didn't carry the envelope shape at all (a raw JS Error, a plain string, or something else). */
|
|
@@ -164,12 +156,6 @@ export declare const preparePdfSave: PreparePdfSave;
|
|
|
164
156
|
* `targetPath`/`documentSha256`; `affectedPages` is returned empty and `revision` as an
|
|
165
157
|
* empty-pages placeholder so the return type still satisfies `PdfCommitResult` structurally. */
|
|
166
158
|
export declare const commitPdfSave: CommitPdfSave;
|
|
167
|
-
/** Browser-QA-only: current bytes of the in-memory QA PDF "disk" at `path`, or `null` outside QA
|
|
168
|
-
* mode / for an untouched path. PdfViewer.tsx's post-commit reload uses this instead of
|
|
169
|
-
* re-fetching its `url` prop — a real Tauri stream token is a revocable token that re-resolves to
|
|
170
|
-
* the same (now-committed) file on every fetch, but browser QA's `url` is a static `data:` URL
|
|
171
|
-
* captured once at document-open time, which can never reflect a later commit on its own. */
|
|
172
|
-
export declare function qaReloadPdfBytes(path: string): Promise<Uint8Array | null>;
|
|
173
159
|
export declare function setInspectableHtmlPreview(token: string | null): void;
|
|
174
160
|
/** Clear the DevTools owner, but only if `token` still owns it (a newer frame may have replaced it). */
|
|
175
161
|
export declare function releaseInspectableHtmlPreview(token: string): void;
|
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
import { baseName, formatFromPath, normalizeViewMode, } from "../documents/types.js";
|
|
2
2
|
import { isTauri } from "@rightkit/platform-ui";
|
|
3
3
|
import { createShell } from "@rightkit/tauri-shell";
|
|
4
|
-
// Browser-QA fixture modules load on demand. Every call site sits behind `BROWSER_QA_BUILD` (a
|
|
5
|
-
// build-time constant), so production builds fold the guard to `false`, drop these dynamic imports,
|
|
6
|
-
// and never emit the fixture chunks. Keep static imports of these modules out of this file.
|
|
7
|
-
const loadQaFixtures = () => import("./browserQa.js");
|
|
8
|
-
const loadQaText = () => import("./browserQaText.js");
|
|
9
|
-
const loadQaDocx = () => import("./browserQaDocx.js");
|
|
10
|
-
const loadQaPdf = () => import("./browserQaPdf.js");
|
|
11
|
-
const loadQaLicense = () => import("./browserQaLicense.js");
|
|
12
4
|
/** True when running inside the Tauri webview (vs. plain `vite dev` in a browser). */
|
|
13
5
|
const inTauri = isTauri;
|
|
14
6
|
let shell = null;
|
|
@@ -17,13 +9,6 @@ export function getShell() {
|
|
|
17
9
|
shell ??= createShell();
|
|
18
10
|
return shell;
|
|
19
11
|
}
|
|
20
|
-
/**
|
|
21
|
-
* Build-time switch for the browser-QA fixtures: `true` in `vite dev` and in the
|
|
22
|
-
* `VITE_RIGHTKIT_BROWSER_QA=1` build, a literal `false` in production builds. Every QA branch in
|
|
23
|
-
* this module is guarded with it, so the minifier drops those branches and nothing references the
|
|
24
|
-
* `browserQa*` fixture modules in a production bundle.
|
|
25
|
-
*/
|
|
26
|
-
const BROWSER_QA_BUILD = import.meta.env?.DEV || import.meta.env?.VITE_RIGHTKIT_BROWSER_QA === "1";
|
|
27
12
|
/**
|
|
28
13
|
* Write text to the system clipboard: through the shell's native clipboard in the app, through the
|
|
29
14
|
* browser clipboard in browser QA. It rejects when no clipboard is available, so a caller never
|
|
@@ -39,15 +24,6 @@ export async function writeClipboardText(text) {
|
|
|
39
24
|
}
|
|
40
25
|
await navigator.clipboard.writeText(text);
|
|
41
26
|
}
|
|
42
|
-
export function isBrowserQaMode() {
|
|
43
|
-
const qaBuild = import.meta.env?.VITE_RIGHTKIT_BROWSER_QA === "1";
|
|
44
|
-
if (!import.meta.env?.DEV && !qaBuild)
|
|
45
|
-
return false;
|
|
46
|
-
if (typeof window === "undefined" || inTauri())
|
|
47
|
-
return false;
|
|
48
|
-
const params = new URLSearchParams(window.location.search);
|
|
49
|
-
return params.has("qa") || qaBuild;
|
|
50
|
-
}
|
|
51
27
|
async function invoke(cmd, args) {
|
|
52
28
|
const { invoke } = await import("@tauri-apps/api/core");
|
|
53
29
|
return invoke(cmd, args);
|
|
@@ -62,16 +38,6 @@ async function listen(event, callback) {
|
|
|
62
38
|
* update Bearer; the webview only sends commands and receives validated display views.
|
|
63
39
|
*/
|
|
64
40
|
export const nativeIpc = { invoke, listen };
|
|
65
|
-
/**
|
|
66
|
-
* Browser-QA licence client (`@rightkit/license/testing`, in-memory, no network). Returns null
|
|
67
|
-
* outside browser QA, so the caller uses the native client. The `testing` export resolves only
|
|
68
|
-
* under the `development` condition and the call site folds to `false` in a release build.
|
|
69
|
-
*/
|
|
70
|
-
export async function browserQaLicenseClient() {
|
|
71
|
-
if (BROWSER_QA_BUILD && isBrowserQaMode())
|
|
72
|
-
return (await loadQaLicense()).qaLicenseClient();
|
|
73
|
-
return null;
|
|
74
|
-
}
|
|
75
41
|
/** Classify an IPC rejection from `invoke()` into `{code, message}`. Tauri serializes a command's
|
|
76
42
|
* `Err(HostError)` using the envelope's own `#[serde(tag = "code", content = "message")]`
|
|
77
43
|
* representation, so a migrated command's rejection value IS already `{code, message}` — this
|
|
@@ -97,9 +63,6 @@ export function parseHostError(e) {
|
|
|
97
63
|
return { code: "unknown", message: String(e) };
|
|
98
64
|
}
|
|
99
65
|
export async function openFileDialog() {
|
|
100
|
-
if (BROWSER_QA_BUILD && isBrowserQaMode()) {
|
|
101
|
-
return (await loadQaFixtures()).qaDialogPath();
|
|
102
|
-
}
|
|
103
66
|
if (!inTauri()) {
|
|
104
67
|
console.warn("openFileDialog: not in Tauri — open a real document via `pnpm tauri dev`.");
|
|
105
68
|
return null;
|
|
@@ -108,27 +71,6 @@ export async function openFileDialog() {
|
|
|
108
71
|
}
|
|
109
72
|
export async function readDocument(path) {
|
|
110
73
|
const format = formatFromPath(path);
|
|
111
|
-
if (BROWSER_QA_BUILD && isBrowserQaMode()) {
|
|
112
|
-
const qaDocx = await loadQaDocx();
|
|
113
|
-
const qaText = await loadQaText();
|
|
114
|
-
const savedDocx = qaDocx.readQaDocxDocument({
|
|
115
|
-
path,
|
|
116
|
-
displayName: baseName(path),
|
|
117
|
-
format,
|
|
118
|
-
content: "",
|
|
119
|
-
});
|
|
120
|
-
if (savedDocx)
|
|
121
|
-
return savedDocx;
|
|
122
|
-
const doc = await (await loadQaFixtures()).qaDocumentForPath(path);
|
|
123
|
-
if (doc) {
|
|
124
|
-
if (format === "pdf") {
|
|
125
|
-
const assetUrl = await (await loadQaPdf()).qaCurrentPdfAssetUrl(path);
|
|
126
|
-
return { ...doc, assetUrl: assetUrl ?? doc.assetUrl };
|
|
127
|
-
}
|
|
128
|
-
return qaText.readQaTextDocument(doc);
|
|
129
|
-
}
|
|
130
|
-
return qaText.readQaTextDocument({ path, displayName: baseName(path), format, content: "" });
|
|
131
|
-
}
|
|
132
74
|
if (!inTauri()) {
|
|
133
75
|
return { path, displayName: baseName(path), format, content: "" };
|
|
134
76
|
}
|
|
@@ -191,10 +133,6 @@ export async function getPersistenceStatus() {
|
|
|
191
133
|
return { degraded: status.degraded === true, quarantinedTo: status.quarantined_to ?? null };
|
|
192
134
|
}
|
|
193
135
|
export async function getInitialOpenPaths() {
|
|
194
|
-
if (BROWSER_QA_BUILD && isBrowserQaMode()) {
|
|
195
|
-
const path = (await loadQaFixtures()).qaInitialOpenPath();
|
|
196
|
-
return path ? [path] : [];
|
|
197
|
-
}
|
|
198
136
|
if (!inTauri())
|
|
199
137
|
return [];
|
|
200
138
|
// The shell's open-paths gate: it grants and drains what argv / a second launch / macOS
|
|
@@ -325,8 +263,6 @@ export async function revokePath(path) {
|
|
|
325
263
|
/** Write text, preserving the file's `encoding` (default UTF-8). Returns actual encoding plus the
|
|
326
264
|
* committed source SHA-256; in-place callers send the prior SHA for stale-source protection. */
|
|
327
265
|
export async function writeDocument(path, content, encoding, expectedSourceSha256) {
|
|
328
|
-
if (BROWSER_QA_BUILD && isBrowserQaMode())
|
|
329
|
-
return (await loadQaText()).writeQaTextDocument(path, content, encoding, expectedSourceSha256);
|
|
330
266
|
if (!inTauri())
|
|
331
267
|
throw new Error("Saving requires the desktop app.");
|
|
332
268
|
const result = await invoke("write_document", {
|
|
@@ -344,9 +280,6 @@ export async function writeDocument(path, content, encoding, expectedSourceSha25
|
|
|
344
280
|
// changing writeDocumentBytes's contract (out of scope here) — and a 32k-arg spread/apply call is
|
|
345
281
|
// exactly the call-stack-pressure risk the loop below was already written to avoid. Not a clear
|
|
346
282
|
// improvement, so kept unchanged per the audit's own guidance.
|
|
347
|
-
function arrayBufferToBase64(ab) {
|
|
348
|
-
return byteArrayToBase64(new Uint8Array(ab));
|
|
349
|
-
}
|
|
350
283
|
function byteArrayToBase64(bytes) {
|
|
351
284
|
let bin = "";
|
|
352
285
|
const chunk = 0x8000;
|
|
@@ -362,11 +295,6 @@ function byteArrayToBase64(bytes) {
|
|
|
362
295
|
return btoa(bin);
|
|
363
296
|
}
|
|
364
297
|
export async function writeDocumentBytes(path, data) {
|
|
365
|
-
if (BROWSER_QA_BUILD && isBrowserQaMode()) {
|
|
366
|
-
if (formatFromPath(path) === "docx")
|
|
367
|
-
await (await loadQaDocx()).writeQaDocxDocument(path, data, arrayBufferToBase64(data));
|
|
368
|
-
return;
|
|
369
|
-
}
|
|
370
298
|
if (!inTauri())
|
|
371
299
|
throw new Error("Saving requires the desktop app.");
|
|
372
300
|
// Raw request body (like ocr_page): the DOCX bytes cross IPC as-is, with no JS base64 encode on
|
|
@@ -386,8 +314,6 @@ function pathToHex(path) {
|
|
|
386
314
|
* reserializing its bytes. The desktop command owns both broker checks and the filesystem copy so
|
|
387
315
|
* PDF Save As preserves signatures, encryption, widgets, and any bytes pdf.js cannot round-trip. */
|
|
388
316
|
export async function copyDocument(sourcePath, targetPath) {
|
|
389
|
-
if (BROWSER_QA_BUILD && isBrowserQaMode())
|
|
390
|
-
return;
|
|
391
317
|
if (!inTauri())
|
|
392
318
|
throw new Error("Saving requires the desktop app.");
|
|
393
319
|
await invoke("copy_document", { sourcePath, targetPath });
|
|
@@ -412,9 +338,6 @@ export async function editImageExternal(path) {
|
|
|
412
338
|
return;
|
|
413
339
|
await invoke("edit_image_external", { path });
|
|
414
340
|
}
|
|
415
|
-
import { hostAppName } from "../appName.js";
|
|
416
|
-
if (BROWSER_QA_BUILD && isBrowserQaMode())
|
|
417
|
-
void loadQaPdf().then((qaPdf) => qaPdf.registerViewrightQaGlobal());
|
|
418
341
|
/** Normalizes an `invoke()` raw-body result (an `ArrayBuffer`, or occasionally already a
|
|
419
342
|
* `Uint8Array`/plain byte array depending on Tauri's IPC transport) into a `Uint8Array`. */
|
|
420
343
|
function toUint8Array(body) {
|
|
@@ -431,9 +354,8 @@ function toUint8Array(body) {
|
|
|
431
354
|
* dynamic `@tauri-apps/api/core` import reach into `window.__TAURI_INTERNALS__`, which is
|
|
432
355
|
* `undefined` there and throws a raw, unclassified "Cannot read properties of undefined (reading
|
|
433
356
|
* 'invoke')" instead of a `ClassifiedError` a caller's existing catch/`errorMessage` handling can
|
|
434
|
-
* show meaningfully. These six PDF-editor commands
|
|
435
|
-
*
|
|
436
|
-
* inspection/composition/save — so the correct graceful behavior is a clean, classified rejection
|
|
357
|
+
* show meaningfully. These six PDF-editor commands need the real Rust backend for source
|
|
358
|
+
* inspection/composition/save, so the correct graceful behavior is a clean, classified rejection
|
|
437
359
|
* rather than either a raw crash or a fabricated result. */
|
|
438
360
|
function requireTauriForPdfEdit(command) {
|
|
439
361
|
if (!inTauri()) {
|
|
@@ -444,8 +366,6 @@ function requireTauriForPdfEdit(command) {
|
|
|
444
366
|
export const ocrPage = async (png) => {
|
|
445
367
|
// Browser QA has no Rust OCR backend, so the IPC-boundary fixture keeps the scanned-page UI
|
|
446
368
|
// exercisable without moving a mock into the document components.
|
|
447
|
-
if (BROWSER_QA_BUILD && isBrowserQaMode())
|
|
448
|
-
return (await loadQaFixtures()).qaOcrLines();
|
|
449
369
|
requireTauriForPdfEdit("ocr_page");
|
|
450
370
|
const { invoke: tauriInvoke } = await import("@tauri-apps/api/core");
|
|
451
371
|
return tauriInvoke("ocr_page", png);
|
|
@@ -460,8 +380,6 @@ let ocrAvailableProbe = null;
|
|
|
460
380
|
export const ocrAvailable = () => {
|
|
461
381
|
// Browser QA has no Rust OCR backend, so the IPC-boundary fixture keeps the scanned-page UI
|
|
462
382
|
// exercisable without moving a mock into the document components.
|
|
463
|
-
if (BROWSER_QA_BUILD && isBrowserQaMode())
|
|
464
|
-
return Promise.resolve(true);
|
|
465
383
|
if (!inTauri())
|
|
466
384
|
return Promise.resolve(false);
|
|
467
385
|
ocrAvailableProbe ??= invoke("ocr_available").catch(() => {
|
|
@@ -488,15 +406,11 @@ export const ocrBackendMissing = () => {
|
|
|
488
406
|
export const inspectPdfPage = async (path, page) => {
|
|
489
407
|
// Browser QA has no Rust content-stream walker, so the IPC-boundary fixture keeps Edit-text mode
|
|
490
408
|
// reachable/non-crashing (islands always empty — source-text editing stays native-only).
|
|
491
|
-
if (BROWSER_QA_BUILD && isBrowserQaMode())
|
|
492
|
-
return (await loadQaPdf()).qaInspectPdfPage(path, page);
|
|
493
409
|
requireTauriForPdfEdit("inspect_pdf_page");
|
|
494
410
|
return invoke("inspect_pdf_page", { path, page });
|
|
495
411
|
};
|
|
496
412
|
/** §7.2 `list_pdf_fonts`. Returns the bundled + system font catalog available for embedding. */
|
|
497
413
|
export const listPdfFonts = async () => {
|
|
498
|
-
if (BROWSER_QA_BUILD && isBrowserQaMode())
|
|
499
|
-
return (await loadQaPdf()).qaListPdfFonts();
|
|
500
414
|
requireTauriForPdfEdit("list_pdf_fonts");
|
|
501
415
|
return invoke("list_pdf_fonts");
|
|
502
416
|
};
|
|
@@ -504,8 +418,6 @@ export const listPdfFonts = async () => {
|
|
|
504
418
|
* disk bytes, for committed-unsaved-edit preview re-render — no token, no retained candidate, no
|
|
505
419
|
* writes. Candidate bytes use JSON; affected pages derive from request commands. */
|
|
506
420
|
export const composePdfPreview = async (request) => {
|
|
507
|
-
if (BROWSER_QA_BUILD && isBrowserQaMode())
|
|
508
|
-
return (await loadQaPdf()).qaComposePdfPreview(request);
|
|
509
421
|
requireTauriForPdfEdit("compose_pdf_preview");
|
|
510
422
|
const body = await invoke("compose_pdf_preview", {
|
|
511
423
|
request: {
|
|
@@ -537,8 +449,6 @@ export const composePdfPreview = async (request) => {
|
|
|
537
449
|
* populated as an empty string (the backend recheck matches by page + text substring only, not by
|
|
538
450
|
* island id; see `pdf_save.rs`'s `structurally_verify_candidate`). */
|
|
539
451
|
export const preparePdfSave = async (request) => {
|
|
540
|
-
if (BROWSER_QA_BUILD && isBrowserQaMode())
|
|
541
|
-
return (await loadQaPdf()).qaPreparePdfSave(request);
|
|
542
452
|
requireTauriForPdfEdit("prepare_pdf_save");
|
|
543
453
|
const meta = await invoke("prepare_pdf_save", {
|
|
544
454
|
request: {
|
|
@@ -573,8 +483,6 @@ export const preparePdfSave = async (request) => {
|
|
|
573
483
|
* `targetPath`/`documentSha256`; `affectedPages` is returned empty and `revision` as an
|
|
574
484
|
* empty-pages placeholder so the return type still satisfies `PdfCommitResult` structurally. */
|
|
575
485
|
export const commitPdfSave = async (token, targetPath) => {
|
|
576
|
-
if (BROWSER_QA_BUILD && isBrowserQaMode())
|
|
577
|
-
return (await loadQaPdf()).qaCommitPdfSave(token, targetPath);
|
|
578
486
|
requireTauriForPdfEdit("commit_pdf_save");
|
|
579
487
|
const result = await invoke("commit_pdf_save", {
|
|
580
488
|
token,
|
|
@@ -587,16 +495,6 @@ export const commitPdfSave = async (token, targetPath) => {
|
|
|
587
495
|
revision: { documentSha256: result.documentSha256, pageCount: 0, pages: [] },
|
|
588
496
|
};
|
|
589
497
|
};
|
|
590
|
-
/** Browser-QA-only: current bytes of the in-memory QA PDF "disk" at `path`, or `null` outside QA
|
|
591
|
-
* mode / for an untouched path. PdfViewer.tsx's post-commit reload uses this instead of
|
|
592
|
-
* re-fetching its `url` prop — a real Tauri stream token is a revocable token that re-resolves to
|
|
593
|
-
* the same (now-committed) file on every fetch, but browser QA's `url` is a static `data:` URL
|
|
594
|
-
* captured once at document-open time, which can never reflect a later commit on its own. */
|
|
595
|
-
export async function qaReloadPdfBytes(path) {
|
|
596
|
-
if (!BROWSER_QA_BUILD || !isBrowserQaMode())
|
|
597
|
-
return null;
|
|
598
|
-
return (await loadQaPdf()).qaCurrentPdfBytes(path);
|
|
599
|
-
}
|
|
600
498
|
/** Token of the interactive HTML preview that currently owns DevTools (the active tab's frame), or
|
|
601
499
|
* null. Set by the HTML preview frame; DevTools opens exactly that registered document. */
|
|
602
500
|
let inspectableHtmlToken = null;
|
|
@@ -613,14 +511,14 @@ export function releaseInspectableHtmlPreview(token) {
|
|
|
613
511
|
* main webview is never inspectable. No-op without an active HTML preview or outside Tauri. */
|
|
614
512
|
export async function openDevtools() {
|
|
615
513
|
const token = inspectableHtmlToken;
|
|
616
|
-
if (!token ||
|
|
514
|
+
if (!token || !inTauri())
|
|
617
515
|
return;
|
|
618
516
|
await invoke("open_html_devtools", { token });
|
|
619
517
|
}
|
|
620
518
|
/** No-op: the macOS inspect-accelerator scope command was removed with the shell port (viewright#375). */
|
|
621
519
|
export async function setDevtoolsShortcutScope(_enabled) { }
|
|
622
520
|
export async function revealInFileExplorer(path) {
|
|
623
|
-
if (
|
|
521
|
+
if (!inTauri())
|
|
624
522
|
return;
|
|
625
523
|
await invoke("reveal_in_file_explorer", { path });
|
|
626
524
|
}
|
|
@@ -651,24 +549,10 @@ export async function printCurrentWebview() {
|
|
|
651
549
|
}
|
|
652
550
|
}
|
|
653
551
|
export async function listSystemFonts() {
|
|
654
|
-
if (BROWSER_QA_BUILD && isBrowserQaMode())
|
|
655
|
-
return ["Inter", "Source Serif 4", "JetBrains Mono", "Geist", "Newsreader"];
|
|
656
552
|
if (!inTauri())
|
|
657
553
|
return [];
|
|
658
554
|
return invoke("list_system_fonts");
|
|
659
555
|
}
|
|
660
|
-
const QA_FILE_ASSOCIATION_FORMATS = ["md", "mdx", "txt", "pdf", "html", "docx", "image"];
|
|
661
|
-
let qaPreferredAssociationFormats = new Set(["md", "mdx"]);
|
|
662
|
-
function qaFileAssociationStatus() {
|
|
663
|
-
return {
|
|
664
|
-
formats: QA_FILE_ASSOCIATION_FORMATS.map((formatId) => ({
|
|
665
|
-
formatId,
|
|
666
|
-
extensions: [],
|
|
667
|
-
handlers: [],
|
|
668
|
-
allSet: qaPreferredAssociationFormats.has(formatId),
|
|
669
|
-
})),
|
|
670
|
-
};
|
|
671
|
-
}
|
|
672
556
|
function toFormatStatus(group) {
|
|
673
557
|
return {
|
|
674
558
|
formatId: group.id,
|
|
@@ -682,17 +566,10 @@ function toFormatStatus(group) {
|
|
|
682
566
|
};
|
|
683
567
|
}
|
|
684
568
|
export async function fileAssociationStatus() {
|
|
685
|
-
if (BROWSER_QA_BUILD && isBrowserQaMode())
|
|
686
|
-
return qaFileAssociationStatus();
|
|
687
569
|
const status = await getShell().associations.status();
|
|
688
570
|
return { formats: status.groups.map(toFormatStatus) };
|
|
689
571
|
}
|
|
690
572
|
export async function setFileAssociationDefaults(formatIds) {
|
|
691
|
-
if (BROWSER_QA_BUILD && isBrowserQaMode()) {
|
|
692
|
-
qaPreferredAssociationFormats = new Set(formatIds);
|
|
693
|
-
const status = qaFileAssociationStatus();
|
|
694
|
-
return { formats: status.formats, allSet: formatIds.length > 0 };
|
|
695
|
-
}
|
|
696
573
|
// macOS: one NSWorkspace request per content type (system consent sheet), one shared 60 s
|
|
697
574
|
// deadline (the shell default; the host clamps a given value to 1 to 120 s).
|
|
698
575
|
const result = await getShell().associations.set(formatIds);
|
|
@@ -701,8 +578,6 @@ export async function setFileAssociationDefaults(formatIds) {
|
|
|
701
578
|
/** Windows owns protected default-app choices. This opens Windows Default apps; the shell never
|
|
702
579
|
* writes UserChoice registry hashes or claims a default changed. */
|
|
703
580
|
export async function openFileAssociationSettings(_formatIds) {
|
|
704
|
-
if (BROWSER_QA_BUILD && isBrowserQaMode())
|
|
705
|
-
return;
|
|
706
581
|
await getShell().associations.openDefaultApps();
|
|
707
582
|
}
|
|
708
583
|
export async function saveSession(tabs) {
|
|
@@ -818,15 +693,11 @@ export function installExternalLinkGuard() {
|
|
|
818
693
|
});
|
|
819
694
|
}
|
|
820
695
|
export async function saveFileDialog(defaultName, format) {
|
|
821
|
-
if (BROWSER_QA_BUILD && isBrowserQaMode())
|
|
822
|
-
return `D:\\${hostAppName()} QA\\${defaultName ?? `untitled.${format ?? "txt"}`}`;
|
|
823
696
|
if (!inTauri())
|
|
824
697
|
return null;
|
|
825
698
|
return invoke("save_file_dialog", { defaultName, format });
|
|
826
699
|
}
|
|
827
700
|
export async function grantKnownFileToken(token) {
|
|
828
|
-
if (BROWSER_QA_BUILD && isBrowserQaMode())
|
|
829
|
-
return token;
|
|
830
701
|
if (!inTauri())
|
|
831
702
|
return null;
|
|
832
703
|
return invoke("grant_known_file_token", { token });
|
|
@@ -840,15 +711,11 @@ export async function grantKnownFileToken(token) {
|
|
|
840
711
|
// - regrantSavedTarget: re-grant a path the host app itself just wrote (save-as, duplicate, DOCX
|
|
841
712
|
// save-as) — refused unless the exact path was written in the last few writes.
|
|
842
713
|
export async function openLinkedDocument(target, from) {
|
|
843
|
-
if (BROWSER_QA_BUILD && isBrowserQaMode())
|
|
844
|
-
return target;
|
|
845
714
|
if (!inTauri())
|
|
846
715
|
return null;
|
|
847
716
|
return invoke("open_linked_document", { target, from: from ?? null });
|
|
848
717
|
}
|
|
849
718
|
export async function regrantSavedTarget(path) {
|
|
850
|
-
if (BROWSER_QA_BUILD && isBrowserQaMode())
|
|
851
|
-
return path;
|
|
852
719
|
if (!inTauri())
|
|
853
720
|
return null;
|
|
854
721
|
return invoke("regrant_saved_target", { path });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rightkit/document-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Shared base for the document editors: platform bridge, settings, state, PDF helpers and the format registry.",
|
|
5
5
|
"license": "MIT OR Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -248,14 +248,14 @@
|
|
|
248
248
|
"dependencies": {
|
|
249
249
|
"@codemirror/view": "^6.43.14",
|
|
250
250
|
"@phosphor-icons/react": "^2.1.10",
|
|
251
|
-
"@rightkit/license": "^0.3.
|
|
251
|
+
"@rightkit/license": "^0.3.1",
|
|
252
252
|
"@rightkit/logs": "^0.1.6",
|
|
253
|
-
"@rightkit/pdf-ui": "^0.
|
|
254
|
-
"@rightkit/platform-ui": "^0.4.
|
|
255
|
-
"@rightkit/react-shell": "^0.7.
|
|
256
|
-
"@rightkit/settings-ui": "^0.7.
|
|
257
|
-
"@rightkit/tauri-shell": "^0.1.
|
|
258
|
-
"@rightkit/theme": "^0.3.
|
|
253
|
+
"@rightkit/pdf-ui": "^0.3.0",
|
|
254
|
+
"@rightkit/platform-ui": "^0.4.1",
|
|
255
|
+
"@rightkit/react-shell": "^0.7.3",
|
|
256
|
+
"@rightkit/settings-ui": "^0.7.1",
|
|
257
|
+
"@rightkit/tauri-shell": "^0.1.4",
|
|
258
|
+
"@rightkit/theme": "^0.3.2",
|
|
259
259
|
"@tauri-apps/api": "^2.12.2",
|
|
260
260
|
"pdfjs-dist": "6.4.299"
|
|
261
261
|
},
|
|
@@ -270,8 +270,7 @@
|
|
|
270
270
|
"react": "^19.3.1",
|
|
271
271
|
"react-dom": "^19.3.1",
|
|
272
272
|
"typescript": "^7.0.2",
|
|
273
|
-
"vite": "^8.3.4"
|
|
274
|
-
"vitest": "^5.0.5"
|
|
273
|
+
"vite": "^8.3.4"
|
|
275
274
|
},
|
|
276
275
|
"publishConfig": {
|
|
277
276
|
"registry": "https://registry.npmjs.org",
|
|
@@ -284,7 +283,6 @@
|
|
|
284
283
|
},
|
|
285
284
|
"scripts": {
|
|
286
285
|
"build": "tsc -p tsconfig.json && node scripts/copy-assets.mjs",
|
|
287
|
-
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
288
|
-
"test": "pnpm --filter \"@rightkit/document-core^...\" run build && vitest run"
|
|
286
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
289
287
|
}
|
|
290
288
|
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Fixture documents + IPC-boundary fallbacks for browser QA mode (`VITE_RIGHTKIT_BROWSER_QA=1`,
|
|
3
|
-
* `?qa=1&doc=<key>`) — the headless-Chrome harness `scripts/qa-browser-functional.mjs` drives, and
|
|
4
|
-
* the manual escape hatch for poking the app in a plain browser tab outside Tauri. `qaOcrLines`
|
|
5
|
-
* lives here; the PDF-edit-command mocks (`inspect_pdf_page`/`compose_pdf_preview`/
|
|
6
|
-
* `prepare_pdf_save`/`commit_pdf_save`/`list_pdf_fonts`, plus the in-memory PDF "disk" and the
|
|
7
|
-
* `window.__viewrightQa` assertion surface) live in the sibling `browserQaPdf.ts` instead, since
|
|
8
|
-
* they need their own save-token/sha bookkeeping this file has no reason to carry.
|
|
9
|
-
*
|
|
10
|
-
* PDF coverage split: annotation editing (FreeText/Ink/Highlight/Stamp, via pdf.js's own
|
|
11
|
-
* `AnnotationEditorLayer`/`saveDocument()`) + two-phase Save/Save As is fully exercised in browser
|
|
12
|
-
* QA against `browserQaPdf.ts`'s mocks. Existing-text/add-text editing (`inspect_pdf_page`'s
|
|
13
|
-
* `islands`) stays native-only — browser QA has no Rust content-stream walker, so that path is only
|
|
14
|
-
* reachable/non-crashing here, never a real edit round trip.
|
|
15
|
-
*/
|
|
16
|
-
import { type LoadedDocument } from "../documents/types.js";
|
|
17
|
-
import type { OcrLine } from "../documents/pdf/pdfOcr.js";
|
|
18
|
-
export declare function qaOcrLines(): OcrLine[];
|
|
19
|
-
export declare function qaDialogPath(): string;
|
|
20
|
-
export declare function qaInitialOpenPath(): string | null;
|
|
21
|
-
export declare function qaDocumentForPath(path: string): Promise<LoadedDocument | null>;
|