@bendyline/docblocks-react 2.4.0 → 2.6.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.
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  ExportDialog
3
- } from "./chunk-SIIEGOHY.js";
3
+ } from "./chunk-FMDLZ4ZG.js";
4
4
  import "./chunk-YBEYTVU2.js";
5
- import "./chunk-LG6HAWCK.js";
6
5
  import "./chunk-EBWYGTN7.js";
6
+ import "./chunk-LG6HAWCK.js";
7
7
  export {
8
8
  ExportDialog
9
9
  };
@@ -5,21 +5,21 @@ import {
5
5
  import {
6
6
  useMenuKeyboard
7
7
  } from "./chunk-MRUK56JS.js";
8
- import {
9
- loadTransformStyleSummaries
10
- } from "./chunk-YBEYTVU2.js";
11
- import {
12
- Dialog
13
- } from "./chunk-LG6HAWCK.js";
14
8
  import {
15
9
  buildExportFilename
16
10
  } from "./chunk-M5Y5WO7Z.js";
11
+ import {
12
+ loadTransformStyleSummaries
13
+ } from "./chunk-YBEYTVU2.js";
17
14
  import {
18
15
  DEFAULT_OPTIONS,
19
16
  FORMAT_EXTENSIONS,
20
17
  loadLastExportOptions,
21
18
  saveExportOptions
22
19
  } from "./chunk-EBWYGTN7.js";
20
+ import {
21
+ Dialog
22
+ } from "./chunk-LG6HAWCK.js";
23
23
 
24
24
  // src/Export/ExportToolbarControls.tsx
25
25
  import {
@@ -36,10 +36,10 @@ import { getThemeSummaries } from "@bendyline/squisq/schemas";
36
36
  import { parseMarkdown } from "@bendyline/squisq/markdown";
37
37
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
38
38
  var ExportDialog = lazy(
39
- () => import("./ExportDialog-VBWHFJ2R.js").then((module) => ({ default: module.ExportDialog }))
39
+ () => import("./ExportDialog-Q4VWI43H.js").then((module) => ({ default: module.ExportDialog }))
40
40
  );
41
41
  var ShareDialog = lazy(
42
- () => import("./ShareDialog-PAMOQQFB.js").then((module) => ({ default: module.ShareDialog }))
42
+ () => import("./ShareDialog-4OX3UPJ4.js").then((module) => ({ default: module.ShareDialog }))
43
43
  );
44
44
  var runExportModulePromise = null;
45
45
  function loadRunExportModule() {
@@ -0,0 +1,29 @@
1
+ import {
2
+ useGitContext
3
+ } from "./chunk-6E635KCJ.js";
4
+
5
+ // src/Git/GitGrantNotice.tsx
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ function GitGrantNotice() {
8
+ const git = useGitContext();
9
+ if (!git || git.repo || !git.pendingGrant) return null;
10
+ const { repositoryRoot } = git.pendingGrant;
11
+ const detail = repositoryRoot ? `This folder is part of the repository ${repositoryRoot}.` : "This folder is part of a larger repository.";
12
+ return /* @__PURE__ */ jsxs("div", { className: "db-git-grant-notice", children: [
13
+ /* @__PURE__ */ jsx("span", { className: "db-git-grant-notice-text", children: "Git is off for this folder" }),
14
+ /* @__PURE__ */ jsx(
15
+ "button",
16
+ {
17
+ type: "button",
18
+ className: "db-git-grant-notice-action",
19
+ onClick: () => git.openDialog({ kind: "expanded-grant" }),
20
+ "aria-label": `Enable Git for this folder. ${detail}`,
21
+ title: detail,
22
+ children: "Enable"
23
+ }
24
+ )
25
+ ] });
26
+ }
27
+ export {
28
+ GitGrantNotice
29
+ };
@@ -1,11 +1,13 @@
1
1
  import "./chunk-APC4KI4B.js";
2
2
  import {
3
- isFileDirty,
4
- useGitContext
5
- } from "./chunk-HFYPTTCH.js";
3
+ isFileDirty
4
+ } from "./chunk-V2ENBGUA.js";
6
5
  import {
7
6
  useMenuKeyboard
8
7
  } from "./chunk-MRUK56JS.js";
8
+ import {
9
+ useGitContext
10
+ } from "./chunk-6E635KCJ.js";
9
11
 
10
12
  // src/Git/GitToolbarControl.tsx
11
13
  import { useEffect, useRef, useState } from "react";
@@ -4,12 +4,14 @@ import {
4
4
  BADGE_LABELS,
5
5
  badgeKindFor,
6
6
  formatAheadBehind,
7
- summarizeDirty,
8
- useGitContext
9
- } from "./chunk-HFYPTTCH.js";
7
+ summarizeDirty
8
+ } from "./chunk-V2ENBGUA.js";
10
9
  import {
11
10
  useMenuKeyboard
12
11
  } from "./chunk-MRUK56JS.js";
12
+ import {
13
+ useGitContext
14
+ } from "./chunk-6E635KCJ.js";
13
15
  import {
14
16
  Dialog
15
17
  } from "./chunk-LG6HAWCK.js";
@@ -1094,20 +1096,71 @@ function GitConflictsDialog({
1094
1096
  );
1095
1097
  }
1096
1098
 
1097
- // src/Git/GitUI.tsx
1099
+ // src/Git/GitExpandedGrantDialog.tsx
1100
+ import { useState as useState7 } from "react";
1098
1101
  import { Fragment as Fragment7, jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
1102
+ function GitExpandedGrantDialog({ onClose }) {
1103
+ const git = useGitContext();
1104
+ const [always, setAlways] = useState7(false);
1105
+ if (!git) return null;
1106
+ const repositoryRoot = git.pendingGrant?.repositoryRoot ?? null;
1107
+ return /* @__PURE__ */ jsxs10(
1108
+ Dialog,
1109
+ {
1110
+ title: "Enable Git for this folder?",
1111
+ onClose,
1112
+ footer: /* @__PURE__ */ jsxs10(Fragment7, { children: [
1113
+ /* @__PURE__ */ jsx10("button", { type: "button", className: "db-git-secondary-btn", onClick: onClose, children: "Not now" }),
1114
+ /* @__PURE__ */ jsx10(
1115
+ "button",
1116
+ {
1117
+ type: "button",
1118
+ className: "db-git-primary-btn",
1119
+ disabled: git.grantBusy,
1120
+ onClick: () => {
1121
+ void git.enableExpandedRepo({ always }).then(() => onClose());
1122
+ },
1123
+ children: git.grantBusy ? "Enabling\u2026" : "Enable Git"
1124
+ }
1125
+ )
1126
+ ] }),
1127
+ children: [
1128
+ /* @__PURE__ */ jsx10("p", { className: "db-settings-hint", children: "This folder is part of a larger Git repository. Git operations such as pull, branch switching, and commit can change files outside the folder you opened." }),
1129
+ repositoryRoot && /* @__PURE__ */ jsxs10("p", { className: "db-git-hint", children: [
1130
+ "Repository: ",
1131
+ /* @__PURE__ */ jsx10("code", { children: repositoryRoot })
1132
+ ] }),
1133
+ /* @__PURE__ */ jsxs10("label", { className: "db-git-grant-remember", children: [
1134
+ /* @__PURE__ */ jsx10(
1135
+ "input",
1136
+ {
1137
+ type: "checkbox",
1138
+ checked: always,
1139
+ onChange: (event) => setAlways(event.target.checked)
1140
+ }
1141
+ ),
1142
+ /* @__PURE__ */ jsx10("span", { children: "Enable Git for every folder inside a repository, without asking" })
1143
+ ] }),
1144
+ /* @__PURE__ */ jsx10("p", { className: "db-settings-hint", children: "DocBlocks remembers this repository either way. You can turn the choice back off by removing it from the desktop settings file." })
1145
+ ]
1146
+ }
1147
+ );
1148
+ }
1149
+
1150
+ // src/Git/GitUI.tsx
1151
+ import { Fragment as Fragment8, jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
1099
1152
  function GitUI({ onOpenFile, onWorkspaceCloned }) {
1100
1153
  const git = useGitContext();
1101
1154
  if (!git || !git.available) return null;
1102
1155
  const { dialog, closeDialog } = git;
1103
- return /* @__PURE__ */ jsxs10(Fragment7, { children: [
1104
- /* @__PURE__ */ jsx10(GitStatusBar, {}),
1105
- git.repo && /* @__PURE__ */ jsx10("span", { className: "db-git-inline-status", role: "status", "aria-live": "polite", children: git.lastResult?.message ?? "" }),
1106
- dialog.kind === "commit" && /* @__PURE__ */ jsx10(GitCommitDialog, { onClose: closeDialog }),
1107
- dialog.kind === "branches" && /* @__PURE__ */ jsx10(GitBranchesDialog, { createFocus: dialog.createFocus, onClose: closeDialog }),
1108
- dialog.kind === "history" && /* @__PURE__ */ jsx10(GitHistoryDialog, { path: dialog.path, onClose: closeDialog }),
1109
- dialog.kind === "diff" && /* @__PURE__ */ jsx10(GitDiffDialog, { path: dialog.path, revision: dialog.revision, onClose: closeDialog }),
1110
- dialog.kind === "clone" && /* @__PURE__ */ jsx10(
1156
+ return /* @__PURE__ */ jsxs11(Fragment8, { children: [
1157
+ /* @__PURE__ */ jsx11(GitStatusBar, {}),
1158
+ git.repo && /* @__PURE__ */ jsx11("span", { className: "db-git-inline-status", role: "status", "aria-live": "polite", children: git.lastResult?.message ?? "" }),
1159
+ dialog.kind === "commit" && /* @__PURE__ */ jsx11(GitCommitDialog, { onClose: closeDialog }),
1160
+ dialog.kind === "branches" && /* @__PURE__ */ jsx11(GitBranchesDialog, { createFocus: dialog.createFocus, onClose: closeDialog }),
1161
+ dialog.kind === "history" && /* @__PURE__ */ jsx11(GitHistoryDialog, { path: dialog.path, onClose: closeDialog }),
1162
+ dialog.kind === "diff" && /* @__PURE__ */ jsx11(GitDiffDialog, { path: dialog.path, revision: dialog.revision, onClose: closeDialog }),
1163
+ dialog.kind === "clone" && /* @__PURE__ */ jsx11(
1111
1164
  GitCloneDialog,
1112
1165
  {
1113
1166
  onClose: closeDialog,
@@ -1117,7 +1170,7 @@ function GitUI({ onOpenFile, onWorkspaceCloned }) {
1117
1170
  }
1118
1171
  }
1119
1172
  ),
1120
- dialog.kind === "auth-guidance" && /* @__PURE__ */ jsx10(
1173
+ dialog.kind === "auth-guidance" && /* @__PURE__ */ jsx11(
1121
1174
  GitAuthGuidanceDialog,
1122
1175
  {
1123
1176
  operation: dialog.operation,
@@ -1125,7 +1178,8 @@ function GitUI({ onOpenFile, onWorkspaceCloned }) {
1125
1178
  onClose: closeDialog
1126
1179
  }
1127
1180
  ),
1128
- dialog.kind === "conflicts" && /* @__PURE__ */ jsx10(GitConflictsDialog, { paths: dialog.paths, onOpenFile, onClose: closeDialog })
1181
+ dialog.kind === "expanded-grant" && /* @__PURE__ */ jsx11(GitExpandedGrantDialog, { onClose: closeDialog }),
1182
+ dialog.kind === "conflicts" && /* @__PURE__ */ jsx11(GitConflictsDialog, { paths: dialog.paths, onOpenFile, onClose: closeDialog })
1129
1183
  ] });
1130
1184
  }
1131
1185
  export {
@@ -7,7 +7,7 @@ import {
7
7
  SHARED_DOCUMENT_LIMITS,
8
8
  createSharedDocumentUrl
9
9
  } from "@bendyline/docblocks/share";
10
- var PUBLIC_DOCBLOCKS_SHARE_URL = "https://bendyline.github.io/docblocks/";
10
+ var PUBLIC_DOCBLOCKS_SHARE_URL = "https://docblocks.com/";
11
11
  function resolveSharedDocumentBaseUrl(currentUrl) {
12
12
  try {
13
13
  const current = new URL(currentUrl);
@@ -45,6 +45,45 @@ async function createSharedDocumentArchive(markdown, selectedFile) {
45
45
  function buildSharedDocumentUrl(baseUrl, archive, mode) {
46
46
  return createSharedDocumentUrl(resolveSharedDocumentBaseUrl(baseUrl), archive, mode);
47
47
  }
48
+ function buildSharedDocumentQrUrl(archive, mode) {
49
+ const url = createSharedDocumentUrl(PUBLIC_DOCBLOCKS_SHARE_URL, archive, mode);
50
+ if (url.length > SHARED_DOCUMENT_LIMITS.qrUrlCharacters) {
51
+ throw new Error(
52
+ "This document is too large for a reliable QR code (maximum " + SHARED_DOCUMENT_LIMITS.qrUrlCharacters.toLocaleString() + " URL characters). Copy the share link instead."
53
+ );
54
+ }
55
+ return url;
56
+ }
57
+ function sharedDocumentQrFilename(selectedFile) {
58
+ return sharedDocumentFilename(selectedFile).replace(/\.md$/iu, "-qr.png");
59
+ }
60
+
61
+ // src/Export/share-qr.ts
62
+ import QRCode from "qrcode";
63
+ async function renderSharedDocumentQrPng(url) {
64
+ return QRCode.toDataURL(url, {
65
+ type: "image/png",
66
+ errorCorrectionLevel: "M",
67
+ margin: 4,
68
+ width: 1024,
69
+ color: {
70
+ dark: "#111111ff",
71
+ light: "#ffffffff"
72
+ }
73
+ });
74
+ }
75
+ function qrPngDataUrlToBlob(dataUrl) {
76
+ const prefix = "data:image/png;base64,";
77
+ if (!dataUrl.startsWith(prefix)) {
78
+ throw new Error("The QR code renderer returned an unsupported image format.");
79
+ }
80
+ const binary = atob(dataUrl.slice(prefix.length));
81
+ const bytes = new Uint8Array(binary.length);
82
+ for (let index = 0; index < binary.length; index += 1) {
83
+ bytes[index] = binary.charCodeAt(index);
84
+ }
85
+ return new Blob([bytes], { type: "image/png" });
86
+ }
48
87
 
49
88
  // src/Export/ShareDialog.tsx
50
89
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -56,11 +95,19 @@ var MODES = [
56
95
  { value: "document", label: "Document" },
57
96
  { value: "narrate", label: "Narrate" }
58
97
  ];
59
- function ShareDialog({ markdown, selectedFile, baseUrl, onClose }) {
98
+ function ShareDialog({
99
+ markdown,
100
+ selectedFile,
101
+ baseUrl,
102
+ onClose
103
+ }) {
60
104
  const [mode, setMode] = useState("");
61
105
  const [archive, setArchive] = useState(null);
62
106
  const [archiveError, setArchiveError] = useState(null);
63
107
  const [copyStatus, setCopyStatus] = useState("idle");
108
+ const [qrDataUrl, setQrDataUrl] = useState(null);
109
+ const [qrRenderError, setQrRenderError] = useState(null);
110
+ const [qrCopyStatus, setQrCopyStatus] = useState("idle");
64
111
  const linkRef = useRef(null);
65
112
  useEffect(() => {
66
113
  let cancelled = false;
@@ -90,6 +137,36 @@ function ShareDialog({ markdown, selectedFile, baseUrl, onClose }) {
90
137
  };
91
138
  }
92
139
  }, [archive, archiveError, baseUrl, mode]);
140
+ const qrLinkResult = useMemo(() => {
141
+ if (!archive) return { url: "", error: archiveError };
142
+ try {
143
+ return { url: buildSharedDocumentQrUrl(archive, mode || null), error: null };
144
+ } catch (error) {
145
+ return {
146
+ url: "",
147
+ error: error instanceof Error ? error.message : "DocBlocks could not create the QR code."
148
+ };
149
+ }
150
+ }, [archive, archiveError, mode]);
151
+ useEffect(() => {
152
+ let cancelled = false;
153
+ setQrDataUrl(null);
154
+ setQrRenderError(null);
155
+ setQrCopyStatus("idle");
156
+ if (!qrLinkResult.url) return;
157
+ void renderSharedDocumentQrPng(qrLinkResult.url).then((dataUrl) => {
158
+ if (!cancelled) setQrDataUrl(dataUrl);
159
+ }).catch((error) => {
160
+ if (!cancelled) {
161
+ setQrRenderError(
162
+ error instanceof Error ? error.message : "DocBlocks could not render the QR code."
163
+ );
164
+ }
165
+ });
166
+ return () => {
167
+ cancelled = true;
168
+ };
169
+ }, [qrLinkResult.url]);
93
170
  useEffect(() => setCopyStatus("idle"), [linkResult.url]);
94
171
  useEffect(() => {
95
172
  const handleKeyDown = (event) => {
@@ -117,7 +194,31 @@ function ShareDialog({ markdown, selectedFile, baseUrl, onClose }) {
117
194
  setCopyStatus("failed");
118
195
  }
119
196
  }, [linkResult.url]);
197
+ const copyQrImage = useCallback(async () => {
198
+ if (!qrDataUrl) return;
199
+ try {
200
+ if (!navigator.clipboard?.write || typeof ClipboardItem === "undefined") {
201
+ throw new Error("Image clipboard access is unavailable.");
202
+ }
203
+ const png = qrPngDataUrlToBlob(qrDataUrl);
204
+ await navigator.clipboard.write([new ClipboardItem({ "image/png": png })]);
205
+ setQrCopyStatus("copied");
206
+ } catch {
207
+ setQrCopyStatus("failed");
208
+ }
209
+ }, [qrDataUrl]);
210
+ const saveQrImage = useCallback(() => {
211
+ if (!qrDataUrl) return;
212
+ const download = document.createElement("a");
213
+ download.href = qrDataUrl;
214
+ download.download = sharedDocumentQrFilename(selectedFile);
215
+ download.style.display = "none";
216
+ document.body.append(download);
217
+ download.click();
218
+ download.remove();
219
+ }, [qrDataUrl, selectedFile]);
120
220
  const isLong = linkResult.url.length > SHARED_DOCUMENT_LIMITS2.portableUrlCharacters;
221
+ const qrError = qrLinkResult.error ?? qrRenderError;
121
222
  return /* @__PURE__ */ jsx(
122
223
  "div",
123
224
  {
@@ -134,7 +235,7 @@ function ShareDialog({ markdown, selectedFile, baseUrl, onClose }) {
134
235
  "aria-labelledby": "db-share-dialog-title",
135
236
  children: [
136
237
  /* @__PURE__ */ jsxs("div", { className: "db-dialog-header", children: [
137
- /* @__PURE__ */ jsx("h2", { className: "db-dialog-title", id: "db-share-dialog-title", children: "Share document via URL" }),
238
+ /* @__PURE__ */ jsx("h2", { className: "db-dialog-title", id: "db-share-dialog-title", children: "Share document" }),
138
239
  /* @__PURE__ */ jsx("button", { className: "db-dialog-close", type: "button", onClick: onClose, "aria-label": "Close", children: "\xD7" })
139
240
  ] }),
140
241
  /* @__PURE__ */ jsxs("div", { className: "db-dialog-body", children: [
@@ -178,6 +279,52 @@ function ShareDialog({ markdown, selectedFile, baseUrl, onClose }) {
178
279
  }
179
280
  )
180
281
  ] }),
282
+ /* @__PURE__ */ jsxs("section", { className: "db-share-qr", "aria-labelledby": "db-share-qr-title", children: [
283
+ /* @__PURE__ */ jsxs("div", { className: "db-share-qr-heading", children: [
284
+ /* @__PURE__ */ jsxs("div", { children: [
285
+ /* @__PURE__ */ jsx("h3", { id: "db-share-qr-title", children: "QR code" }),
286
+ /* @__PURE__ */ jsx("span", { className: "db-export-hint", children: "A self-contained docblocks.com link. No document content is uploaded." })
287
+ ] }),
288
+ /* @__PURE__ */ jsxs("div", { className: "db-share-qr-actions", children: [
289
+ /* @__PURE__ */ jsx(
290
+ "button",
291
+ {
292
+ className: "db-export-btn db-export-btn--secondary",
293
+ type: "button",
294
+ onClick: () => void copyQrImage(),
295
+ disabled: !qrDataUrl,
296
+ children: qrCopyStatus === "copied" ? "Copied QR" : qrCopyStatus === "failed" ? "Copy unavailable" : "Copy QR image"
297
+ }
298
+ ),
299
+ /* @__PURE__ */ jsx(
300
+ "button",
301
+ {
302
+ className: "db-export-btn db-export-btn--secondary",
303
+ type: "button",
304
+ onClick: saveQrImage,
305
+ disabled: !qrDataUrl,
306
+ children: "Save QR PNG"
307
+ }
308
+ )
309
+ ] })
310
+ ] }),
311
+ qrDataUrl ? /* @__PURE__ */ jsx(
312
+ "img",
313
+ {
314
+ className: "db-share-qr-image",
315
+ src: qrDataUrl,
316
+ alt: "QR code for this shared DocBlocks document"
317
+ }
318
+ ) : /* @__PURE__ */ jsx(
319
+ "div",
320
+ {
321
+ className: `db-share-qr-placeholder${qrError ? " db-share-qr-placeholder--error" : ""}`,
322
+ role: qrError ? "note" : "status",
323
+ children: qrError ?? "Rendering QR code..."
324
+ }
325
+ ),
326
+ /* @__PURE__ */ jsx("span", { className: "db-export-hint db-share-qr-status", "aria-live": "polite", children: qrCopyStatus === "failed" ? "This browser could not copy the PNG. Save it instead." : qrDataUrl ? `${qrLinkResult.url.length.toLocaleString()} of ${SHARED_DOCUMENT_LIMITS2.qrUrlCharacters.toLocaleString()} supported QR characters.` : "" })
327
+ ] }),
181
328
  /* @__PURE__ */ jsx("p", { className: "db-share-temporary-note", children: "Opening the link creates a temporary workspace. Changes there do not alter this document or the link." })
182
329
  ] }),
183
330
  /* @__PURE__ */ jsxs("div", { className: "db-export-footer", children: [
@@ -0,0 +1,27 @@
1
+ import { CalcEngineFactory } from '@bendyline/squisq-editor-react';
2
+
3
+ /**
4
+ * Host wiring for Squisq's optional IronCalc spreadsheet engine.
5
+ *
6
+ * Creating the factory imports no engine code and downloads no bytes. Squisq
7
+ * calls it only when a formula session opens, at which point the IronCalc
8
+ * adapter and the surface's same-origin WASM asset load asynchronously. If the
9
+ * factory rejects, Squisq falls back to its in-house calculation tier.
10
+ */
11
+
12
+ interface DocBlocksCalculationOptions {
13
+ /**
14
+ * `@ironcalc/wasm` binary source. Browser hosts normally pass the stable
15
+ * same-origin URL they publish; tests and non-browser hosts may pass bytes
16
+ * or a compiled module instead.
17
+ */
18
+ wasmSource: string | URL | Response | BufferSource | WebAssembly.Module;
19
+ /** IronCalc workbook locale. Defaults to `en`. */
20
+ locale?: string;
21
+ /** IronCalc workbook timezone. Defaults to `UTC`. */
22
+ timezone?: string;
23
+ }
24
+ /** Create the lazy IronCalc factory a DocBlocks surface passes to its shell. */
25
+ declare function createDocBlocksCalcEngineFactory(options: DocBlocksCalculationOptions): CalcEngineFactory;
26
+
27
+ export { type DocBlocksCalculationOptions, createDocBlocksCalcEngineFactory };
@@ -0,0 +1,16 @@
1
+ // src/Calculation/public-api.ts
2
+ function createDocBlocksCalcEngineFactory(options) {
3
+ const { wasmSource, locale, timezone } = options;
4
+ return async (config) => {
5
+ const { createIronCalcEngine } = await import("@bendyline/squisq-calc/ironcalc");
6
+ return createIronCalcEngine({
7
+ ...config,
8
+ wasmSource,
9
+ locale,
10
+ timezone
11
+ });
12
+ };
13
+ }
14
+ export {
15
+ createDocBlocksCalcEngineFactory
16
+ };
@@ -0,0 +1,11 @@
1
+ // src/Git/GitContext.tsx
2
+ import { createContext, useContext } from "react";
3
+ var GitContext = createContext(null);
4
+ function useGitContext() {
5
+ return useContext(GitContext);
6
+ }
7
+
8
+ export {
9
+ GitContext,
10
+ useGitContext
11
+ };
@@ -0,0 +1,83 @@
1
+ // src/Proofing/proofing-ignores.ts
2
+ var STORAGE_KEY = "docblocks:proofingIgnores";
3
+ var MAX_ENTRY_CHARACTERS = 64 * 1024;
4
+ var MAX_DOCUMENTS = 200;
5
+ function resolveStorage() {
6
+ try {
7
+ const candidate = Reflect.get(globalThis, "localStorage");
8
+ if (typeof candidate !== "object" || candidate === null || typeof Reflect.get(candidate, "getItem") !== "function" || typeof Reflect.get(candidate, "setItem") !== "function") {
9
+ return null;
10
+ }
11
+ return candidate;
12
+ } catch {
13
+ return null;
14
+ }
15
+ }
16
+ function proofingIgnoreKey(workspaceId, documentPath) {
17
+ return `${workspaceId}\0${documentPath}`;
18
+ }
19
+ function readAll(storage) {
20
+ try {
21
+ const raw = storage.getItem(STORAGE_KEY);
22
+ if (!raw) return {};
23
+ const parsed = JSON.parse(raw);
24
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {};
25
+ const entries = {};
26
+ for (const [key, value] of Object.entries(parsed)) {
27
+ if (typeof value !== "object" || value === null) continue;
28
+ const json = Reflect.get(value, "json");
29
+ const savedAt = Reflect.get(value, "savedAt");
30
+ if (typeof json !== "string" || json.length > MAX_ENTRY_CHARACTERS) continue;
31
+ entries[key] = { json, savedAt: typeof savedAt === "number" ? savedAt : 0 };
32
+ }
33
+ return entries;
34
+ } catch {
35
+ return {};
36
+ }
37
+ }
38
+ function writeAll(storage, entries) {
39
+ const keys = Object.keys(entries);
40
+ if (keys.length > MAX_DOCUMENTS) {
41
+ const ordered = keys.sort((a, b) => (entries[a]?.savedAt ?? 0) - (entries[b]?.savedAt ?? 0));
42
+ for (const key of ordered.slice(0, keys.length - MAX_DOCUMENTS)) delete entries[key];
43
+ }
44
+ try {
45
+ storage.setItem(STORAGE_KEY, JSON.stringify(entries));
46
+ } catch {
47
+ }
48
+ }
49
+ function createLocalProofingIgnoreStore(resolveWorkspaceId, storageOverride) {
50
+ const storage = storageOverride ?? resolveStorage();
51
+ const keyFor = (document) => {
52
+ const workspaceId = resolveWorkspaceId();
53
+ if (!workspaceId) return null;
54
+ const documentPath = document.fileName ?? document.articleId;
55
+ if (!documentPath) return null;
56
+ return proofingIgnoreKey(workspaceId, documentPath);
57
+ };
58
+ return {
59
+ load(document) {
60
+ if (!storage) return void 0;
61
+ const key = keyFor(document);
62
+ if (!key) return void 0;
63
+ return readAll(storage)[key]?.json;
64
+ },
65
+ save(document, ignoredJson) {
66
+ if (!storage) return;
67
+ const key = keyFor(document);
68
+ if (!key) return;
69
+ const entries = readAll(storage);
70
+ if (ignoredJson.length > MAX_ENTRY_CHARACTERS) {
71
+ delete entries[key];
72
+ } else {
73
+ entries[key] = { json: ignoredJson, savedAt: Date.now() };
74
+ }
75
+ writeAll(storage, entries);
76
+ }
77
+ };
78
+ }
79
+
80
+ export {
81
+ proofingIgnoreKey,
82
+ createLocalProofingIgnoreStore
83
+ };
@@ -283,6 +283,35 @@ function AccentColorSettings({
283
283
  )) })
284
284
  ] });
285
285
  }
286
+ function ProofingSettingsControls({ value, onChange }) {
287
+ return /* @__PURE__ */ jsxs("fieldset", { className: "db-settings-fieldset", children: [
288
+ /* @__PURE__ */ jsx("legend", { className: "db-settings-legend", children: "Spelling and grammar" }),
289
+ /* @__PURE__ */ jsx("p", { className: "db-settings-hint", children: "Checking runs on this device \u2014 nothing you write is sent anywhere." }),
290
+ /* @__PURE__ */ jsxs("label", { className: "db-settings-checkbox", children: [
291
+ /* @__PURE__ */ jsx(
292
+ "input",
293
+ {
294
+ type: "checkbox",
295
+ checked: value.spelling,
296
+ onChange: (event) => onChange({ ...value, spelling: event.currentTarget.checked })
297
+ }
298
+ ),
299
+ "Show inline spell checking"
300
+ ] }),
301
+ /* @__PURE__ */ jsxs("label", { className: "db-settings-checkbox", children: [
302
+ /* @__PURE__ */ jsx(
303
+ "input",
304
+ {
305
+ type: "checkbox",
306
+ checked: value.grammar,
307
+ onChange: (event) => onChange({ ...value, grammar: event.currentTarget.checked })
308
+ }
309
+ ),
310
+ "Show inline grammar checking"
311
+ ] }),
312
+ /* @__PURE__ */ jsx("p", { className: "db-settings-hint", children: "(Grammar checking is currently only available for English)" })
313
+ ] });
314
+ }
286
315
  function WriteCanvasSettingsControls({ value, onChange }) {
287
316
  return /* @__PURE__ */ jsxs("fieldset", { className: "db-settings-fieldset", children: [
288
317
  /* @__PURE__ */ jsx("legend", { className: "db-settings-legend", children: "Write canvas" }),
@@ -587,6 +616,36 @@ function formatLineSpacing(value) {
587
616
  return `${value.toFixed(1).replace(/\.0$/, "")}\xD7`;
588
617
  }
589
618
 
619
+ // src/preferences/proofing.ts
620
+ var DEFAULT_PROOFING_PREFERENCES = {
621
+ spelling: true,
622
+ grammar: true
623
+ };
624
+ var STORAGE_KEY = "docblocks:proofingPreferences";
625
+ function loadProofingPreferences() {
626
+ try {
627
+ const raw = localStorage.getItem(STORAGE_KEY);
628
+ if (!raw) return { ...DEFAULT_PROOFING_PREFERENCES };
629
+ const stored = JSON.parse(raw);
630
+ if (typeof stored !== "object" || stored === null) {
631
+ return { ...DEFAULT_PROOFING_PREFERENCES };
632
+ }
633
+ const record = stored;
634
+ return {
635
+ spelling: typeof record.spelling === "boolean" ? record.spelling : DEFAULT_PROOFING_PREFERENCES.spelling,
636
+ grammar: typeof record.grammar === "boolean" ? record.grammar : DEFAULT_PROOFING_PREFERENCES.grammar
637
+ };
638
+ } catch {
639
+ }
640
+ return { ...DEFAULT_PROOFING_PREFERENCES };
641
+ }
642
+ function saveProofingPreferences(value) {
643
+ try {
644
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(value));
645
+ } catch {
646
+ }
647
+ }
648
+
590
649
  export {
591
650
  WRITE_CANVAS_FONT_SCHEMES,
592
651
  DEFAULT_WRITE_CANVAS_FONT_SCHEME,
@@ -594,6 +653,9 @@ export {
594
653
  DEFAULT_WRITE_CANVAS_PREFERENCES,
595
654
  loadWriteCanvasPreferences,
596
655
  saveWriteCanvasPreferences,
656
+ DEFAULT_PROOFING_PREFERENCES,
657
+ loadProofingPreferences,
658
+ saveProofingPreferences,
597
659
  DB_CHROME_COLORS,
598
660
  loadThemePreference,
599
661
  saveThemePreference,
@@ -602,5 +664,6 @@ export {
602
664
  SettingsDialog,
603
665
  ThemeSettings,
604
666
  AccentColorSettings,
667
+ ProofingSettingsControls,
605
668
  WriteCanvasSettingsControls
606
669
  };
@@ -1,10 +1,3 @@
1
- // src/Git/GitContext.tsx
2
- import { createContext, useContext } from "react";
3
- var GitContext = createContext(null);
4
- function useGitContext() {
5
- return useContext(GitContext);
6
- }
7
-
8
1
  // src/Git/git-status.ts
9
2
  var BADGE_GLYPHS = {
10
3
  modified: "M",
@@ -83,8 +76,6 @@ function summarizeDirty(status) {
83
76
  }
84
77
 
85
78
  export {
86
- GitContext,
87
- useGitContext,
88
79
  BADGE_GLYPHS,
89
80
  BADGE_LABELS,
90
81
  badgeKindFor,