@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.
Files changed (136) hide show
  1. package/CHANGELOG.md +29 -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 -50
  16. package/dist/index.js +1 -50
  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 -61
  22. package/dist/state/tauriBridge.js +8 -297
  23. package/dist/styles.css +2 -2
  24. package/package.json +25 -98
  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 -303
  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
  127. package/dist/state/browserQa.d.ts +0 -21
  128. package/dist/state/browserQa.js +0 -303
  129. package/dist/state/browserQaDocx.d.ts +0 -3
  130. package/dist/state/browserQaDocx.js +0 -35
  131. package/dist/state/browserQaLicense.d.ts +0 -8
  132. package/dist/state/browserQaLicense.js +0 -7
  133. package/dist/state/browserQaPdf.d.ts +0 -60
  134. package/dist/state/browserQaPdf.js +0 -302
  135. package/dist/state/browserQaText.d.ts +0 -6
  136. package/dist/state/browserQaText.js +0 -48
@@ -1,11 +0,0 @@
1
- import type { EditorView } from "@codemirror/view";
2
- /**
3
- * Scroll a document position to vertical center, reliably.
4
- *
5
- * CodeMirror's own scroll (`EditorView.scrollIntoView` effect AND the `scrollIntoView` transaction
6
- * flag) does not move the editor in this app — find/inspect set the selection but the view stays put.
7
- * This drives `scrollDOM.scrollTop` directly, which always works. The wrinkle is that line heights for
8
- * not-yet-rendered lines are ESTIMATED, so the first scroll only lands approximately; once the target
9
- * region renders with real heights we recompute and correct, converging within a few measure passes.
10
- */
11
- export declare function scrollPosIntoView(view: EditorView, pos: number): void;
@@ -1,52 +0,0 @@
1
- const scrollRevision = new WeakMap();
2
- const MAX_SCROLL_MEASURES = 8;
3
- /**
4
- * Scroll a document position to vertical center, reliably.
5
- *
6
- * CodeMirror's own scroll (`EditorView.scrollIntoView` effect AND the `scrollIntoView` transaction
7
- * flag) does not move the editor in this app — find/inspect set the selection but the view stays put.
8
- * This drives `scrollDOM.scrollTop` directly, which always works. The wrinkle is that line heights for
9
- * not-yet-rendered lines are ESTIMATED, so the first scroll only lands approximately; once the target
10
- * region renders with real heights we recompute and correct, converging within a few measure passes.
11
- */
12
- export function scrollPosIntoView(view, pos) {
13
- const target = Math.max(0, Math.min(pos, view.state.doc.length));
14
- const revision = (scrollRevision.get(view) ?? 0) + 1;
15
- scrollRevision.set(view, revision);
16
- let measures = 0;
17
- let stablePaints = 0;
18
- const isCurrent = () => view.dom.isConnected && scrollRevision.get(view) === revision;
19
- const schedule = () => {
20
- if (!isCurrent() || measures >= MAX_SCROLL_MEASURES)
21
- return;
22
- measures += 1;
23
- view.requestMeasure({
24
- read: () => {
25
- if (!isCurrent())
26
- return null;
27
- const sc = view.scrollDOM;
28
- const block = view.lineBlockAt(target);
29
- const margin = Math.max(0, (sc.clientHeight - block.height) / 2);
30
- return Math.max(0, Math.min(block.top - margin, sc.scrollHeight - sc.clientHeight));
31
- },
32
- write: (want) => {
33
- if (want === null || !isCurrent())
34
- return;
35
- const sc = view.scrollDOM;
36
- if (Math.abs(sc.scrollTop - want) > 2) {
37
- sc.scrollTop = want;
38
- stablePaints = 0;
39
- }
40
- else {
41
- stablePaints += 1;
42
- }
43
- // CodeMirror replaces offscreen line-height estimates only after the newly reached viewport
44
- // paints. Immediate requestMeasure recursion sees the same estimate and stops too early,
45
- // leaving long wrapped-source matches outside the drawer. Wait for paint, then converge.
46
- if (stablePaints < 2 && measures < MAX_SCROLL_MEASURES)
47
- requestAnimationFrame(schedule);
48
- },
49
- });
50
- };
51
- schedule();
52
- }
@@ -1,15 +0,0 @@
1
- import { type Dispatch, type SetStateAction } from "react";
2
- /**
3
- * Mirror an externally-owned text value into local editable state.
4
- *
5
- * `liveText` is the document's live unsaved edit (useEditText); `text` is the
6
- * base prop text. The mirror follows `liveText` whenever a live edit exists,
7
- * and follows `text` only when the prop changes while no live edit exists —
8
- * so a live local edit is never clobbered by a stale prop.
9
- *
10
- * When `sink` is supplied the mirrored value is routed to it instead of local
11
- * state (MarkdownSplitPane uses this to push external text into the document
12
- * core's `replaceFromExternal` while the core owns the rendered source).
13
- * Returns the mirrored text plus its setter for local edits.
14
- */
15
- export declare function useMirroredEditText(liveText: string | undefined, text: string, sink?: (next: string) => void): [string, Dispatch<SetStateAction<string>>];
@@ -1,29 +0,0 @@
1
- import { useEffect, useRef, useState } from "react";
2
- /**
3
- * Mirror an externally-owned text value into local editable state.
4
- *
5
- * `liveText` is the document's live unsaved edit (useEditText); `text` is the
6
- * base prop text. The mirror follows `liveText` whenever a live edit exists,
7
- * and follows `text` only when the prop changes while no live edit exists —
8
- * so a live local edit is never clobbered by a stale prop.
9
- *
10
- * When `sink` is supplied the mirrored value is routed to it instead of local
11
- * state (MarkdownSplitPane uses this to push external text into the document
12
- * core's `replaceFromExternal` while the core owns the rendered source).
13
- * Returns the mirrored text plus its setter for local edits.
14
- */
15
- export function useMirroredEditText(liveText, text, sink) {
16
- const [localText, setLocalText] = useState(() => liveText ?? text);
17
- const propTextRef = useRef(text);
18
- useEffect(() => {
19
- const next = liveText ?? text;
20
- if (sink) {
21
- sink(next);
22
- return;
23
- }
24
- if (liveText !== undefined || propTextRef.current !== text)
25
- setLocalText(next);
26
- propTextRef.current = text;
27
- }, [liveText, text, sink]);
28
- return [localText, setLocalText];
29
- }
@@ -1,7 +0,0 @@
1
- import { type PointerEvent as ReactPointerEvent, type RefObject } from "react";
2
- /**
3
- * Pointer-drag handler for the source/preview split divider: clamps the source pane between
4
- * minRight..(width - minLeft) and reports it as a width percentage. Shared by the markdown + mdx
5
- * split panes (the drag math is identical; only the container ref + width setter differ).
6
- */
7
- export declare function useSplitResize(containerRef: RefObject<HTMLDivElement | null>, setSourceWidth: (pct: number) => void): (event: ReactPointerEvent<HTMLDivElement>) => void;
@@ -1,37 +0,0 @@
1
- import { useCallback } from "react";
2
- import { sourceWidthPercent } from "./splitResizeMath.js";
3
- /**
4
- * Pointer-drag handler for the source/preview split divider: clamps the source pane between
5
- * minRight..(width - minLeft) and reports it as a width percentage. Shared by the markdown + mdx
6
- * split panes (the drag math is identical; only the container ref + width setter differ).
7
- */
8
- export function useSplitResize(containerRef, setSourceWidth) {
9
- return useCallback((event) => {
10
- event.preventDefault();
11
- const container = containerRef.current;
12
- if (!container)
13
- return;
14
- const bounds = container.getBoundingClientRect();
15
- let frame = 0;
16
- let pointerX = event.clientX;
17
- const onMove = (ev) => {
18
- pointerX = ev.clientX;
19
- if (frame)
20
- return;
21
- frame = requestAnimationFrame(() => {
22
- frame = 0;
23
- setSourceWidth(sourceWidthPercent(bounds, pointerX));
24
- });
25
- };
26
- const onUp = () => {
27
- window.removeEventListener("pointermove", onMove);
28
- window.removeEventListener("pointerup", onUp);
29
- window.removeEventListener("pointercancel", onUp);
30
- if (frame)
31
- cancelAnimationFrame(frame);
32
- };
33
- window.addEventListener("pointermove", onMove);
34
- window.addEventListener("pointerup", onUp);
35
- window.addEventListener("pointercancel", onUp);
36
- }, [containerRef, setSourceWidth]);
37
- }
@@ -1,38 +0,0 @@
1
- /* Published @rightkit/settings-ui sections inside the host app own settings dialog.
2
- Layer order matches src/styles/index.css, so Tailwind utilities still win over these rules. */
3
- @layer theme, base, rk, components, utilities;
4
- @import "@rightkit/react-shell/shell.visual.css" layer(rk);
5
- @import "@rightkit/settings-ui/settings.css" layer(rk);
6
-
7
- /* The one token map: --rk-* (read by the published CSS and react-shell controls) -> the host app tokens. */
8
- .vr-rk {
9
- --rk-plane: var(--ink);
10
- --rk-chrome: var(--panel);
11
- --rk-panel: rgb(var(--panel-rgb) / 0.5);
12
- --rk-elevated: var(--panel-2);
13
- --rk-sunk: var(--panel-2);
14
- --rk-ink: var(--paper);
15
- --rk-ink-2: var(--muted);
16
- --rk-ink-3: var(--muted-2);
17
- --rk-line: var(--line);
18
- --rk-hairline: var(--line);
19
- --rk-line-strong: rgb(var(--muted-2-rgb) / 0.4);
20
- --rk-fill: rgb(var(--paper-rgb) / 0.05);
21
- --rk-fill-2: rgb(var(--paper-rgb) / 0.085);
22
- --rk-fill-3: rgb(var(--paper-rgb) / 0.13);
23
- --rk-sel: rgb(var(--paper-rgb) / 0.06);
24
- --rk-accent: var(--copper);
25
- --rk-accent-ink: var(--ink);
26
- --rk-accent-soft: rgb(var(--copper-rgb) / 0.14);
27
- --rk-focus: rgb(var(--copper-rgb) / 0.4);
28
- --rk-bad: var(--danger-hi);
29
- --rk-danger: var(--danger-hi);
30
- --rk-bad-soft: rgb(var(--danger-rgb) / 0.12);
31
- --rk-radius: 8px;
32
- --rk-card: 0 0 0 1px var(--line);
33
- --rk-font-mono: var(--font-mono);
34
- --rk-sg-margin: 12px;
35
- color: var(--paper);
36
- font-family: var(--font-sans);
37
- font-size: var(--vr-text-md);
38
- }
@@ -1 +0,0 @@
1
- export declare function EditorSection(): import("react").JSX.Element;
@@ -1,18 +0,0 @@
1
- import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { SegmentedControl } from "@rightkit/react-shell/react";
3
- import { SettingsGroup } from "@rightkit/settings-ui";
4
- import { useSettings } from "../settings.js";
5
- import { onOff } from "./onOff.js";
6
- export function EditorSection() {
7
- const { settings, update, reset } = useSettings();
8
- return (_jsxs(_Fragment, { children: [_jsx(SettingsGroup, { title: "Source size", children: _jsx(SegmentedControl, { label: "Source size", options: [
9
- { value: "11", label: "small", title: "Small source editor size (11px)" },
10
- { value: "12", label: "normal", title: "Normal source editor size (12px)" },
11
- { value: "14", label: "large", title: "Large source editor size (14px)" },
12
- { value: "16", label: "x-large", title: "Extra large source editor size (16px)" },
13
- ], value: String(settings.sourceFontSizePx), onChange: (v) => update({ sourceFontSizePx: Number(v) }) }) }), _jsx(SettingsGroup, { title: "Motion", description: "System follows your OS reduced-motion setting.", children: _jsx(SegmentedControl, { label: "Motion", options: [
14
- { value: "system", label: "system", title: "Motion follows system" },
15
- { value: "reduce", label: "reduced", title: "Reduce motion" },
16
- { value: "full", label: "full", title: "Full motion" },
17
- ], value: settings.motion, onChange: (v) => update({ motion: v }) }) }), _jsx(SettingsGroup, { title: "Spell check", children: _jsx(SegmentedControl, { label: "Spell check", options: onOff("Spell check"), value: settings.spellcheck ? "on" : "off", onChange: (v) => update({ spellcheck: v === "on" }) }) }), _jsx(SettingsGroup, { title: "Word wrap", children: _jsx(SegmentedControl, { label: "Word wrap", options: onOff("Word wrap"), value: settings.wordWrap ? "on" : "off", onChange: (v) => update({ wordWrap: v === "on" }) }) }), _jsx(SettingsGroup, { title: "Format on save", description: "Reindent Markdown & HTML source when you save.", children: _jsx(SegmentedControl, { label: "Format on save", options: onOff("Format on save"), value: settings.formatOnSave ? "on" : "off", onChange: (v) => update({ formatOnSave: v === "on" }) }) }), _jsx(SettingsGroup, { title: "Safe Markdown HTML", description: "Render only sanitized local HTML. Source remains unchanged.", children: _jsx(SegmentedControl, { label: "Safe Markdown HTML", options: onOff("Safe Markdown HTML"), value: settings.markdownSafeHtml ? "on" : "off", onChange: (v) => update({ markdownSafeHtml: v === "on" }) }) }), _jsx(SettingsGroup, { title: "Auto save", description: "Save file-backed edits after you pause. Untitled drafts wait for their first Save.", children: _jsx(SegmentedControl, { label: "Auto save", options: onOff("Auto save"), value: settings.autoSave ? "on" : "off", onChange: (v) => update({ autoSave: v === "on" }) }) }), _jsx("button", { type: "button", onClick: reset, className: "vr-press vr-focus rounded px-2 py-1.5 font-sans text-xs text-muted-2 hover:bg-panel-2 hover:text-paper", children: "Reset to defaults" })] }));
18
- }
@@ -1 +0,0 @@
1
- export declare function FileAssociationsSection(): import("react").JSX.Element;
@@ -1,5 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { FileAssociationsBlock } from "./MacFileAssociationsBlock.js";
3
- export function FileAssociationsSection() {
4
- return _jsx(FileAssociationsBlock, {});
5
- }
@@ -1,33 +0,0 @@
1
- import type { FileAssociationFormatStatus, FileAssociationStatus, MarkdownUtiHandlerStatus } from "../../state/tauriBridge.js";
2
- export declare const SUPPORTED_ASSOCIATION_FORMATS: readonly [{
3
- readonly id: "md";
4
- readonly label: "Markdown";
5
- }, {
6
- readonly id: "mdx";
7
- readonly label: "MDX";
8
- }, {
9
- readonly id: "txt";
10
- readonly label: "Plain text & code";
11
- }, {
12
- readonly id: "pdf";
13
- readonly label: "PDF";
14
- }, {
15
- readonly id: "html";
16
- readonly label: "HTML";
17
- }, {
18
- readonly id: "docx";
19
- readonly label: "DOCX";
20
- }, {
21
- readonly id: "image";
22
- readonly label: "Images";
23
- }];
24
- /** Extensions come exclusively from shared/document_formats.tsv via canonical entries. */
25
- export declare function extensionsForFormat(formatId: string): string[];
26
- export declare function formatExtensionPreview(formatId: string): string;
27
- export declare function toggleFormatSelection(selected: string[], formatId: string, checked: boolean): string[];
28
- export declare function allFormatsSelected(selected: string[]): boolean;
29
- export declare function selectedFormatsVerified(status: FileAssociationStatus | null, selected: string[]): boolean;
30
- /** One UTI row: identifier → current preferred handler (verified from LaunchServices). */
31
- export declare function describeHandler(handler: MarkdownUtiHandlerStatus): string;
32
- export declare function formatOwnership(status: FileAssociationFormatStatus): string;
33
- export declare function FileAssociationsBlock(): import("react").JSX.Element | null;
@@ -1,151 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useMemo, useState } from "react";
3
- import { documentFormatEntries } from "../../documents/documentFormats.js";
4
- import { fileAssociationStatus, inTauri, isBrowserQaMode, openFileAssociationSettings, setFileAssociationDefaults, } from "../../state/tauriBridge.js";
5
- import { isMac, isWindows } from "../../platform.js";
6
- import { hostAppName } from "../../appName.js";
7
- export const SUPPORTED_ASSOCIATION_FORMATS = [
8
- { id: "md", label: "Markdown" },
9
- { id: "mdx", label: "MDX" },
10
- { id: "txt", label: "Plain text & code" },
11
- { id: "pdf", label: "PDF" },
12
- { id: "html", label: "HTML" },
13
- { id: "docx", label: "DOCX" },
14
- { id: "image", label: "Images" },
15
- ];
16
- const supportedFormatIds = SUPPORTED_ASSOCIATION_FORMATS.map(({ id }) => id);
17
- /** Extensions come exclusively from shared/document_formats.tsv via canonical entries. */
18
- export function extensionsForFormat(formatId) {
19
- return documentFormatEntries
20
- .filter((entry) => entry.format === formatId)
21
- .map((entry) => `.${entry.extension}`);
22
- }
23
- export function formatExtensionPreview(formatId) {
24
- return extensionsForFormat(formatId).join(", ");
25
- }
26
- export function toggleFormatSelection(selected, formatId, checked) {
27
- const next = new Set(selected);
28
- if (checked)
29
- next.add(formatId);
30
- else
31
- next.delete(formatId);
32
- return supportedFormatIds.filter((id) => next.has(id));
33
- }
34
- export function allFormatsSelected(selected) {
35
- return supportedFormatIds.every((id) => selected.includes(id));
36
- }
37
- export function selectedFormatsVerified(status, selected) {
38
- if (!status || selected.length === 0)
39
- return false;
40
- const byId = new Map(status.formats.map((format) => [format.formatId, format]));
41
- return selected.every((id) => byId.get(id)?.allSet === true);
42
- }
43
- /** One UTI row: identifier → current preferred handler (verified from LaunchServices). */
44
- export function describeHandler(handler) {
45
- const target = handler.hostIsPreferred
46
- ? hostAppName()
47
- : (handler.preferredHandler ?? "no handler");
48
- return `${handler.uti} → ${target}`;
49
- }
50
- function formatStatus(status, formatId) {
51
- return status?.formats.find((format) => format.formatId === formatId);
52
- }
53
- export function formatOwnership(status) {
54
- if (status.allSet)
55
- return hostAppName();
56
- const preferred = status.handlers.filter((handler) => handler.hostIsPreferred).length;
57
- if (preferred > 0)
58
- return `${preferred}/${status.handlers.length} ${hostAppName()}`;
59
- return "Other app";
60
- }
61
- export function FileAssociationsBlock() {
62
- const [status, setStatus] = useState(null);
63
- const [selected, setSelected] = useState(() => [...supportedFormatIds]);
64
- const [busy, setBusy] = useState(false);
65
- const [error, setError] = useState("");
66
- const [notice, setNotice] = useState("");
67
- const mac = isMac();
68
- const windows = isWindows();
69
- const available = (mac || windows) && (inTauri() || isBrowserQaMode());
70
- const allSelected = allFormatsSelected(selected);
71
- const extensionCounts = useMemo(() => new Map(SUPPORTED_ASSOCIATION_FORMATS.map((format) => [
72
- format.id,
73
- extensionsForFormat(format.id).length,
74
- ])), []);
75
- const extensionCount = useMemo(() => Array.from(extensionCounts.values()).reduce((sum, count) => sum + count, 0), [extensionCounts]);
76
- useEffect(() => {
77
- if (!available)
78
- return;
79
- let cancelled = false;
80
- const refresh = () => {
81
- void fileAssociationStatus()
82
- .then((next) => {
83
- if (!cancelled)
84
- setStatus(next);
85
- })
86
- .catch(() => {
87
- /* surfaced by explicit action */
88
- });
89
- };
90
- refresh();
91
- window.addEventListener("focus", refresh);
92
- return () => {
93
- cancelled = true;
94
- window.removeEventListener("focus", refresh);
95
- };
96
- }, [available]);
97
- if (!available)
98
- return null;
99
- const apply = async () => {
100
- if (selected.length === 0)
101
- return;
102
- setBusy(true);
103
- setError("");
104
- setNotice("");
105
- try {
106
- await setFileAssociationDefaults(selected);
107
- // Re-query source of truth rather than trusting write result.
108
- const next = await fileAssociationStatus();
109
- setStatus(next);
110
- if (!selectedFormatsVerified(next, selected)) {
111
- setError("macOS did not approve every selected content type. Confirm each system prompt, then try remaining formats.");
112
- }
113
- }
114
- catch (e) {
115
- setError(e instanceof Error ? e.message : String(e));
116
- }
117
- finally {
118
- setBusy(false);
119
- }
120
- };
121
- const openWindowsSettings = async () => {
122
- setBusy(true);
123
- setError("");
124
- setNotice("");
125
- try {
126
- await openFileAssociationSettings(supportedFormatIds);
127
- setNotice(`Windows Settings opened. Choose ${hostAppName()} for the file types you want it to open.`);
128
- }
129
- catch (e) {
130
- setError(e instanceof Error ? e.message : String(e));
131
- }
132
- finally {
133
- setBusy(false);
134
- }
135
- };
136
- const verified = selectedFormatsVerified(status, selected);
137
- return (_jsxs("section", { className: "space-y-4", children: [_jsx("h3", { className: "font-sans text-sm font-semibold text-paper", children: "Open by default" }), _jsx("p", { className: "text-xs text-muted-2", children: mac
138
- ? `Choose which supported formats ${hostAppName()} should open by default. Nothing changes until you apply your selection. macOS may ask once for each changed content type.`
139
- : `${hostAppName()} is registered for every supported format. Windows protects default-app choices, so review current ownership here, then confirm changes in Windows Settings.` }), mac ? (_jsxs("label", { className: "flex items-center gap-2 text-sm text-paper", children: [_jsx("input", { type: "checkbox", checked: allSelected, onChange: (event) => setSelected(event.target.checked ? [...supportedFormatIds] : []), "data-testid": "file-association-select-all" }), _jsx("span", { children: "Select all formats" }), _jsxs("span", { className: "ml-auto text-xs text-muted-2", children: [extensionCount, " file types"] })] })) : (_jsxs("div", { className: "flex items-center justify-between rounded-md bg-panel/50 px-3 py-2 text-xs text-muted-2", children: [_jsxs("span", { children: ["Supported by ", hostAppName()] }), _jsxs("span", { children: [extensionCount, " file types"] })] })), _jsxs("div", { className: "flex flex-wrap items-center gap-2 font-mono text-xs", children: [mac ? (_jsx("button", { type: "button", onClick: () => void apply(), disabled: busy || selected.length === 0, className: "vr-press vr-focus rounded bg-copper/20 px-3 py-1.5 text-copper-hi hover:bg-copper/30 disabled:opacity-50", "data-testid": "file-association-apply", children: busy
140
- ? "Applying..."
141
- : allSelected
142
- ? `Make ${hostAppName()} default`
143
- : "Apply selected defaults" })) : (_jsx("button", { type: "button", onClick: () => void openWindowsSettings(), disabled: busy, className: "vr-press vr-focus rounded bg-copper/20 px-3 py-1.5 text-copper-hi hover:bg-copper/30 disabled:opacity-50", "data-testid": "file-association-open-windows", children: busy ? "Opening..." : "Open Windows Default Apps" })), mac && status && (_jsx("span", { className: verified ? "text-copper-hi" : "text-red-300", "data-testid": "file-association-result", children: verified ? "Selected formats verified" : "Selected formats are not all verified" }))] }), notice && _jsx("p", { className: "text-xs text-muted-2", children: notice }), error && _jsx("p", { className: "font-mono text-xs text-red-300", children: error }), _jsx("div", { className: "divide-y divide-line overflow-hidden rounded-lg border border-line bg-ink/30", "data-testid": "file-association-formats", children: SUPPORTED_ASSOCIATION_FORMATS.map((format) => {
144
- const current = formatStatus(status, format.id);
145
- const checked = selected.includes(format.id);
146
- return (_jsxs("div", { className: `flex items-center gap-3 px-3 py-2.5 ${mac ? "hover:bg-line/15" : ""}`, children: [mac ? (_jsx("input", { type: "checkbox", checked: checked, onChange: (event) => setSelected(toggleFormatSelection(selected, format.id, event.target.checked)), "aria-label": `Set ${format.label} defaults`, "data-testid": `file-association-format-${format.id}` })) : (_jsx("span", { "aria-hidden": "true", className: `h-2 w-2 rounded-full ${current?.allSet ? "bg-copper-hi" : "bg-muted-2/50"}` })), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "block text-sm font-medium text-paper", children: format.label }), _jsxs("span", { className: "block text-chrome-xs text-muted-2", children: [extensionCounts.get(format.id), " file", " ", extensionCounts.get(format.id) === 1 ? "type" : "types"] })] }), current && (_jsx("span", { className: `text-xs ${current.allSet ? "text-copper-hi" : "text-muted-2"}`, children: formatOwnership(current) }))] }, format.id));
147
- }) }), _jsxs("details", { className: "rounded border border-line px-3 py-2 text-xs text-muted-2", children: [_jsx("summary", { className: "cursor-pointer select-none text-paper", children: "Technical details" }), _jsx("div", { className: "mt-3 space-y-3 font-mono text-chrome-xs leading-relaxed", children: SUPPORTED_ASSOCIATION_FORMATS.map((format) => {
148
- const current = formatStatus(status, format.id);
149
- return (_jsxs("div", { children: [_jsx("div", { className: "font-semibold text-paper", children: format.label }), _jsx("div", { className: "break-words", children: formatExtensionPreview(format.id) }), current?.handlers.map((handler) => (_jsx("div", { children: describeHandler(handler) }, handler.uti)))] }, format.id));
150
- }) })] })] }));
151
- }
@@ -1 +0,0 @@
1
- export declare function ThemeSection(): import("react").JSX.Element;
@@ -1,31 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { resolveAccentColors, resolveTheme, useSettings } from "../settings.js";
3
- import { ACCENT_PRESETS, DEFAULT_DARK, DEFAULT_LIGHT, THEMES, themeById, } from "../themes.js";
4
- export function ThemeSection() {
5
- const { settings, update, reset } = useSettings();
6
- const theme = resolveTheme(settings);
7
- const accents = resolveAccentColors(settings);
8
- const primaryId = activeAccentId(settings.primaryAccentId, accents.primary, theme.mode);
9
- const secondaryId = activeAccentId(settings.secondaryAccentId, accents.secondary, theme.mode);
10
- return (_jsxs("div", { className: "space-y-3", children: [_jsx(ThemeGroup, { title: "Theme", hint: "Applies to shell and documents; Auto follows the OS", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("div", { className: "grid min-w-0 flex-1 grid-cols-[repeat(auto-fit,minmax(100px,1fr))] gap-1.5", children: [_jsx(ThemeSwatch, { name: "Auto", colors: [themeById(DEFAULT_LIGHT).tokens.ink, themeById(DEFAULT_DARK).tokens.ink], selected: settings.themeId === "auto", onSelect: () => update({ themeId: "auto" }) }), THEMES.map((preset) => (_jsx(ThemeSwatch, { name: preset.name, colors: [preset.tokens.chrome, preset.tokens.ink, preset.tokens.paper], selected: settings.themeId === preset.id, resolved: settings.themeId === "auto" && theme.id === preset.id, onSelect: () => update({ themeId: preset.id }) }, preset.id)))] }), _jsx("button", { type: "button", onClick: reset, className: "vr-press vr-focus rounded-md border border-line px-2.5 py-1.5 font-sans text-xs text-muted hover:bg-panel hover:text-paper", children: "Reset" })] }) }), _jsx(AccentChoices, { title: "Interface accent", hint: "Tools, links, focus, and selection", selectedId: primaryId, mode: theme.mode, onSelect: (primaryAccentId) => update({ primaryAccentId, customPrimary: null }) }), _jsx(AccentChoices, { title: "Code accent", hint: "Syntax and secondary status", selectedId: secondaryId, mode: theme.mode, onSelect: (secondaryAccentId) => update({ secondaryAccentId, customSecondary: null }) })] }));
11
- }
12
- function ThemeSwatch({ name, colors, selected, resolved = false, onSelect, }) {
13
- return (_jsxs("button", { type: "button", "aria-label": `Theme: ${name}`, "aria-pressed": selected, title: resolved ? `${name} (Auto)` : name, onClick: onSelect, className: `vr-press vr-focus relative min-w-0 rounded-md border p-1.5 text-left transition-colors ${selected ? "border-copper ring-1 ring-copper/35" : "border-line hover:border-muted-2"}`, children: [_jsxs("span", { "aria-hidden": true, className: "mb-1 block h-10 overflow-hidden rounded-sm border border-line", style: { background: colors[0] }, children: [_jsx("span", { className: "block h-2.5 border-b border-white/10" }), _jsxs("span", { className: "ml-3 block h-7 rounded-tl-sm px-2 py-1.5", style: { background: colors[1] }, children: [_jsx("span", { className: "mb-1 block h-0.5 w-3/4 opacity-70", style: { background: colors[2] ?? colors[0] } }), _jsx("span", { className: "block h-0.5 w-1/2 opacity-40", style: { background: colors[2] ?? colors[0] } })] })] }), _jsx("span", { className: `block whitespace-normal font-sans text-chrome-xs ${selected ? "text-paper" : "text-muted"}`, children: name }), resolved && (_jsx("span", { "aria-hidden": true, "data-vr-theme-resolved": true, className: "absolute -top-1 right-1 h-2 w-2 rounded-full bg-paper ring-1 ring-line" }))] }));
14
- }
15
- function AccentChoices({ title, hint, selectedId, mode, onSelect, }) {
16
- return (_jsx(ThemeGroup, { title: title, hint: hint, children: _jsx("div", { className: "flex flex-wrap gap-1.5", children: ACCENT_PRESETS.map((accent) => {
17
- const selected = selectedId === accent.id;
18
- return (_jsxs("button", { type: "button", title: accent.name, "aria-label": `${title}: ${accent.name}`, "aria-pressed": selected, onClick: () => onSelect(accent.id), className: `vr-press vr-focus flex w-16 flex-col items-center gap-1 rounded-md border p-1 ${selected ? "border-paper ring-2 ring-paper/20" : "border-line hover:border-muted-2"}`, children: [_jsx("span", { className: "block h-6 w-full rounded-sm", style: { background: accent[mode] } }), _jsx("span", { "aria-hidden": true, className: `w-full whitespace-normal text-center font-sans text-chrome-xs leading-tight ${selected ? "text-paper" : "text-muted"}`, children: accent.name })] }, accent.id));
19
- }) }) }));
20
- }
21
- function ThemeLabel({ children }) {
22
- return (_jsx("div", { className: "mb-1.5 font-sans text-chrome-xs font-semibold uppercase tracking-[0.18em] text-muted-2", children: children }));
23
- }
24
- function ThemeGroup({ title, hint, children, }) {
25
- return (_jsxs("section", { "data-theme-row": title, className: "grid grid-cols-[8.75rem_minmax(0,1fr)] items-center gap-3 rounded-lg border border-line bg-panel/35 p-2.5", children: [_jsxs("div", { className: "min-w-0", children: [_jsx(ThemeLabel, { children: title }), _jsx("span", { className: "block text-chrome-xs leading-tight text-muted-2", children: hint })] }), children] }));
26
- }
27
- function activeAccentId(selectedId, color, mode) {
28
- if (selectedId)
29
- return selectedId;
30
- return ACCENT_PRESETS.find((accent) => accent[mode] === color)?.id ?? null;
31
- }
@@ -1 +0,0 @@
1
- export declare function TypographySection(): import("react").JSX.Element;
@@ -1,19 +0,0 @@
1
- import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { SegmentedControl } from "@rightkit/react-shell/react";
3
- import { SettingsGroup } from "@rightkit/settings-ui";
4
- import { FONT_SIZES } from "../fonts.js";
5
- import { useSettings } from "../settings.js";
6
- export function TypographySection() {
7
- const { settings, update, reset } = useSettings();
8
- return (_jsxs(_Fragment, { children: [_jsx(SettingsGroup, { title: "Size", children: _jsx(SegmentedControl, { label: "Size", options: FONT_SIZES.map((s) => ({
9
- value: String(s.px),
10
- label: s.label,
11
- title: `${s.label} reading size (${s.px}px)`,
12
- })), value: String(settings.fontSizePx), onChange: (v) => update({ fontSizePx: Number(v) }) }) }), _jsxs(SettingsGroup, { title: "Line height", children: [_jsx("input", { type: "range", "aria-label": "Reading line height", min: 1.3, max: 2.1, step: 0.05, value: settings.lineHeight,
13
- // A drag fires onChange on every pixel of movement; the settings store applies each change
14
- // optimistically, serialises the native commits, and applyTokens skips unchanged variables.
15
- onChange: (e) => update({ lineHeight: Number(e.target.value) }), className: "w-full accent-[var(--copper)]" }), _jsx("div", { className: "mt-1 font-mono text-xs text-muted-2", children: settings.lineHeight.toFixed(2) })] }), _jsx(SettingsGroup, { title: "Contrast", children: _jsx(SegmentedControl, { label: "Contrast", options: [
16
- { value: "normal", label: "normal", title: "Normal reading contrast" },
17
- { value: "high", label: "high", title: "High reading contrast" },
18
- ], value: settings.contrast, onChange: (v) => update({ contrast: v }) }) }), _jsx("button", { type: "button", onClick: reset, className: "vr-press vr-focus rounded px-2 py-1.5 font-sans text-xs text-muted-2 hover:bg-panel-2 hover:text-paper", children: "Reset to defaults" })] }));
19
- }
@@ -1,11 +0,0 @@
1
- /** The two options of an on/off `SegmentedControl`. `title` carries the full phrase ("Spell check off")
2
- * as the tooltip; the group `label` plus the visible "on" / "off" is the accessible name. */
3
- export declare function onOff(name: string): ({
4
- value: "on";
5
- label: string;
6
- title: string;
7
- } | {
8
- value: "off";
9
- label: string;
10
- title: string;
11
- })[];
@@ -1,8 +0,0 @@
1
- /** The two options of an on/off `SegmentedControl`. `title` carries the full phrase ("Spell check off")
2
- * as the tooltip; the group `label` plus the visible "on" / "off" is the accessible name. */
3
- export function onOff(name) {
4
- return [
5
- { value: "on", label: "on", title: `${name} on` },
6
- { value: "off", label: "off", title: `${name} off` },
7
- ];
8
- }
@@ -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>;