document-cli 1.0.1 → 1.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.
@@ -1,6 +1,6 @@
1
- import { a as inferFormatFromExtension, i as formatToExtension, t as readInput } from "./io-Cl3MaB0L.js";
1
+ import { d as inferFormatFromExtension, i as formatOdbReportLines, l as loadProvidedFonts, n as describeOdbReport, o as readInput, r as formatOdbFormLines, t as describeOdbForm, u as formatToExtension } from "./odb-structure-CX_0zL8_.js";
2
2
  import { readFile, writeFile } from "node:fs/promises";
3
- import { createDocx, createOdg, createOdp, createOds, createOdt, createPptx, docxToPdf, hsqldbCellDisplayText, odgToPdf, odpToPdf, odsToPdf, odtToPdf, openDocx, openOdg, openOdp, openOds, openOdt, openPptx, pptxToPdf, readOdbTables, readOdgContent, readOdsContent, readPdf, rgbHexToColor } from "documents.js";
3
+ import { createDocx, createOdg, createOdp, createOds, createOdt, createPptx, decodeMarkdownText, docxToPdf, encodeMarkdownText, hsqldbCellDisplayText, markdownToPdf, odgToPdf, odpToPdf, odsToPdf, odtToPdf, openDocx, openOdg, openOdp, openOds, openOdt, openPptx, pptxToPdf, readOdbForms, readOdbReports, readOdbTables, readOdgContent, readOdsContent, readPdf, rgbHexToColor, xlsxToPdf } from "documents.js";
4
4
  import { basename, dirname, extname, join } from "node:path";
5
5
  import { cellReference, columnIndexToLetters, decodePackage } from "odf.js";
6
6
  import { readdirSync } from "node:fs";
@@ -15,9 +15,17 @@ function describeError(error) {
15
15
  }
16
16
  //#endregion
17
17
  //#region src/tui/format/export-pdf.ts
18
- function toPdfOptions(options) {
18
+ function toPdfOptions(options, fonts) {
19
19
  return {
20
20
  signal: options.signal,
21
+ fonts,
22
+ onFontSubstitution: (substitution) => {
23
+ const requested = `${substitution.requestedFamily}${substitution.requestedBold ? " bold" : ""}${substitution.requestedItalic ? " italic" : ""}`;
24
+ options.onDiagnostic({
25
+ severity: "info",
26
+ message: `No "${requested}" face available; drew it in "${substitution.resolvedFamily}"`
27
+ });
28
+ },
21
29
  onSubstitution: (substitution, context) => {
22
30
  options.onDiagnostic({
23
31
  severity: "info",
@@ -29,8 +37,18 @@ function toPdfOptions(options) {
29
37
  }
30
38
  async function exportToPdf(openDocument, destinationPath, options) {
31
39
  if (openDocument.format === "odb" || openDocument.format === "pdf") throw new Error(`A ${openDocument.format} document is already a read-only source; there is no export-to-PDF path for it`);
40
+ const pdfOptions = toPdfOptions(options, await loadProvidedFonts(options.fontFiles ?? [], { signal: options.signal }));
41
+ if (openDocument.format === "markdown") {
42
+ const pdfBytes = markdownToPdf(encodeMarkdownText(openDocument.source), pdfOptions);
43
+ await writeFile(destinationPath, pdfBytes);
44
+ return;
45
+ }
46
+ if (openDocument.format === "xlsx") {
47
+ const pdfBytes = xlsxToPdf(openDocument.bytes, pdfOptions);
48
+ await writeFile(destinationPath, pdfBytes);
49
+ return;
50
+ }
32
51
  const bytes = openDocument.editor.toBytes();
33
- const pdfOptions = toPdfOptions(options);
34
52
  const pdfBytes = convert(openDocument.format, bytes, pdfOptions);
35
53
  await writeFile(destinationPath, pdfBytes);
36
54
  }
@@ -59,11 +77,16 @@ function detectFormat(path) {
59
77
  const ODB_EXTENSION = ".odb";
60
78
  async function openDocumentAtPath(path) {
61
79
  const bytes = new Uint8Array(await readFile(path));
62
- if (path.toLowerCase().endsWith(ODB_EXTENSION)) return {
63
- format: "odb",
64
- tables: readOdbTables(decodePackage(bytes)),
65
- path
66
- };
80
+ if (path.toLowerCase().endsWith(ODB_EXTENSION)) {
81
+ const pkg = decodePackage(bytes);
82
+ return {
83
+ format: "odb",
84
+ tables: readOdbTables(pkg),
85
+ forms: readOdbForms(pkg),
86
+ reports: readOdbReports(pkg),
87
+ path
88
+ };
89
+ }
67
90
  const format = detectFormat(path);
68
91
  if (format === void 0) throw new Error(`Cannot tell what kind of document ${path} is from its extension`);
69
92
  switch (format) {
@@ -102,7 +125,17 @@ async function openDocumentAtPath(path) {
102
125
  layout: readPdf(bytes),
103
126
  path
104
127
  };
105
- case "xlsx": throw new Error("documents.js has no xlsx editor; convert the workbook to ods first (xlsxToOds) and open that");
128
+ case "markdown": return {
129
+ format,
130
+ source: decodeMarkdownText(bytes),
131
+ path
132
+ };
133
+ case "xlsx": return {
134
+ format,
135
+ layout: readPdf(xlsxToPdf(bytes)),
136
+ bytes,
137
+ path
138
+ };
106
139
  case "odf": throw new Error("A standalone .odf formula document has no editor; convert it to PDF (odfToPdf) instead");
107
140
  }
108
141
  }
@@ -141,7 +174,11 @@ function createNewDocument(format) {
141
174
  }
142
175
  }
143
176
  async function saveDocumentTo(openDocument, path) {
144
- if (openDocument.format === "odb" || openDocument.format === "pdf") throw new Error(`A ${openDocument.format} document is opened read-only and cannot be written back`);
177
+ if (openDocument.format === "odb" || openDocument.format === "pdf" || openDocument.format === "xlsx") throw new Error(`A ${openDocument.format} document is opened read-only and cannot be written back`);
178
+ if (openDocument.format === "markdown") {
179
+ await writeFile(path, encodeMarkdownText(openDocument.source));
180
+ return;
181
+ }
145
182
  await writeFile(path, openDocument.editor.toBytes());
146
183
  }
147
184
  //#endregion
@@ -154,11 +191,18 @@ const EDITABLE_FORMATS = {
154
191
  ods: true,
155
192
  odg: true
156
193
  };
194
+ const WRITABLE_FORMATS = {
195
+ ...EDITABLE_FORMATS,
196
+ markdown: true
197
+ };
157
198
  function isEditableFormat(value) {
158
199
  return value in EDITABLE_FORMATS;
159
200
  }
160
- function isEditableDocument(document) {
161
- return isEditableFormat(document.format);
201
+ function isWritableFormat(value) {
202
+ return value in WRITABLE_FORMATS;
203
+ }
204
+ function isWritableDocument(document) {
205
+ return isWritableFormat(document.format);
162
206
  }
163
207
  function selectionKeyFor(screen) {
164
208
  switch (screen.kind) {
@@ -169,9 +213,12 @@ function selectionKeyFor(screen) {
169
213
  case "sheetList":
170
214
  case "pageList":
171
215
  case "odbTableList":
216
+ case "odbFormList":
217
+ case "odbReportList":
172
218
  case "pdfPageList":
173
219
  case "exportOptions":
174
- case "saveAsPrompt": return screen.kind;
220
+ case "saveAsPrompt":
221
+ case "markdownLineList": return screen.kind;
175
222
  case "filePicker": return `filePicker:${screen.purpose}:${screen.cwd}`;
176
223
  case "paragraphDetail":
177
224
  case "tableView":
@@ -189,6 +236,9 @@ function selectionKeyFor(screen) {
189
236
  case "shapeOrVectorDetail":
190
237
  case "pdfItemDetail": return `${screen.kind}:${screen.pageIndex}:${screen.itemIndex}`;
191
238
  case "odbTableRows": return `odbTableRows:${screen.tableName}`;
239
+ case "odbFormDetail": return `odbFormDetail:${screen.formName}`;
240
+ case "odbReportDetail": return `odbReportDetail:${screen.reportName}`;
241
+ case "markdownLineEditor": return `markdownLineEditor:${screen.lineIndex}`;
192
242
  }
193
243
  }
194
244
  function rootScreenForFormat(format) {
@@ -200,7 +250,9 @@ function rootScreenForFormat(format) {
200
250
  case "ods": return { kind: "sheetList" };
201
251
  case "odg": return { kind: "pageList" };
202
252
  case "odb": return { kind: "odbTableList" };
203
- case "pdf": return { kind: "pdfPageList" };
253
+ case "markdown": return { kind: "markdownLineList" };
254
+ case "pdf":
255
+ case "xlsx": return { kind: "pdfPageList" };
204
256
  }
205
257
  }
206
258
  function currentScreen(state) {
@@ -315,6 +367,13 @@ function documentWithPath(doc, path) {
315
367
  case "odb": return {
316
368
  format: "odb",
317
369
  tables: doc.tables,
370
+ forms: doc.forms,
371
+ reports: doc.reports,
372
+ path
373
+ };
374
+ case "markdown": return {
375
+ format: "markdown",
376
+ source: doc.source,
318
377
  path
319
378
  };
320
379
  case "pdf": return {
@@ -322,6 +381,12 @@ function documentWithPath(doc, path) {
322
381
  layout: doc.layout,
323
382
  path
324
383
  };
384
+ case "xlsx": return {
385
+ format: "xlsx",
386
+ layout: doc.layout,
387
+ bytes: doc.bytes,
388
+ path
389
+ };
325
390
  }
326
391
  }
327
392
  function reopenEditable(doc, bytes) {
@@ -367,6 +432,18 @@ function mutate(state, doc, apply) {
367
432
  undoStack: pushSnapshot(state.undoStack, snapshot)
368
433
  };
369
434
  }
435
+ function mutateMarkdown(state, doc, source) {
436
+ const snapshot = encodeMarkdownText(doc.source);
437
+ return {
438
+ ...state,
439
+ openDocument: {
440
+ ...doc,
441
+ source
442
+ },
443
+ hasUnsavedChanges: true,
444
+ undoStack: pushSnapshot(state.undoStack, snapshot)
445
+ };
446
+ }
370
447
  function wrongDocument(state, expected) {
371
448
  return withStatus(state, "warning", `That action needs ${expected}; the open document is ${state.openDocument === void 0 ? "no document" : state.openDocument.format}`);
372
449
  }
@@ -395,6 +472,11 @@ function drawingDocument(state) {
395
472
  if (doc === void 0) return;
396
473
  return doc.format === "odg" ? doc : void 0;
397
474
  }
475
+ function markdownDocument(state) {
476
+ const doc = state.openDocument;
477
+ if (doc === void 0) return;
478
+ return doc.format === "markdown" ? doc : void 0;
479
+ }
398
480
  function paragraphAt(doc, blockIndex) {
399
481
  return doc.editor.paragraphs()[blockIndex];
400
482
  }
@@ -513,7 +595,7 @@ function appReducer(state, action) {
513
595
  selection: {},
514
596
  errorDetail: void 0,
515
597
  stack: [rootScreenForFormat(action.doc.format)]
516
- }, "info", `Opened ${action.path}`);
598
+ }, "info", action.doc.format === "xlsx" ? `Opened ${action.path} as a read-only PDF preview -- press ':' then 'export pdf' to save it as a real PDF` : `Opened ${action.path}`);
517
599
  case "OPEN_FILE_ERROR": return withStatus({
518
600
  ...state,
519
601
  errorDetail: {
@@ -643,6 +725,21 @@ function appReducer(state, action) {
643
725
  doc.editor.addSlide();
644
726
  });
645
727
  }
728
+ case "ADD_SLIDE_TABLE": {
729
+ const doc = presentationDocument(state);
730
+ if (doc === void 0) return wrongDocument(state, "a pptx or odp document");
731
+ const slide = doc.editor.slides()[action.slideIndex];
732
+ if (slide === void 0) return withStatus(state, "warning", `There is no slide at index ${action.slideIndex}`);
733
+ return mutate(state, doc, () => {
734
+ slide.addTable({
735
+ frame: action.frame,
736
+ table: {
737
+ rows: action.rows,
738
+ columns: action.columns
739
+ }
740
+ });
741
+ });
742
+ }
646
743
  case "ADD_TEXTBOX": {
647
744
  const doc = shapeHostDocument(state);
648
745
  if (doc === void 0) return wrongDocument(state, "a pptx, odp or odg document");
@@ -771,6 +868,11 @@ function appReducer(state, action) {
771
868
  action.vector.stroke = action.stroke;
772
869
  });
773
870
  }
871
+ case "SET_MARKDOWN_SOURCE": {
872
+ const doc = markdownDocument(state);
873
+ if (doc === void 0) return wrongDocument(state, "a markdown document");
874
+ return mutateMarkdown(state, doc, action.source);
875
+ }
774
876
  case "APPEND_DIAGNOSTIC": return {
775
877
  ...state,
776
878
  diagnostics: [...state.diagnostics, action.diagnostic]
@@ -799,12 +901,16 @@ function appReducer(state, action) {
799
901
  case "UNDO": {
800
902
  const doc = state.openDocument;
801
903
  if (doc === void 0) return withStatus(state, "info", "There is nothing to undo");
802
- if (doc.format === "odb" || doc.format === "pdf") return withStatus(state, "warning", `A ${doc.format} document is read-only, so it has no history to undo`);
904
+ if (doc.format === "odb" || doc.format === "pdf" || doc.format === "xlsx") return withStatus(state, "warning", `A ${doc.format} document is read-only, so it has no history to undo`);
803
905
  const snapshot = state.undoStack.at(-1);
804
906
  if (snapshot === void 0) return withStatus(state, "info", "There is nothing to undo");
907
+ const restored = doc.format === "markdown" ? {
908
+ ...doc,
909
+ source: decodeMarkdownText(snapshot)
910
+ } : reopenEditable(doc, snapshot);
805
911
  return withStatus({
806
912
  ...state,
807
- openDocument: reopenEditable(doc, snapshot),
913
+ openDocument: restored,
808
914
  undoStack: state.undoStack.slice(0, -1),
809
915
  hasUnsavedChanges: true
810
916
  }, "info", "Undone");
@@ -2171,29 +2277,33 @@ function DocxBodyListScreen() {
2171
2277
  return /* @__PURE__ */ jsx(ParagraphFamilyBodyList, { adapter });
2172
2278
  }
2173
2279
  //#endregion
2174
- //#region src/tui/screens/editors/odb/shared.ts
2175
- function requireOdbDocument(openDocument) {
2176
- if (openDocument?.format !== "odb") throw new Error("An .odb browsing screen rendered without an open .odb document; the app router only reaches this screen group from odbTableList, which is only ever the root screen of an open .odb document.");
2280
+ //#region src/tui/screens/editors/markdown/shared.ts
2281
+ function requireMarkdownDocument(openDocument) {
2282
+ if (openDocument?.format !== "markdown") throw new Error("A markdown editing screen rendered without an open markdown document; the app router only reaches this screen group from markdownLineList, which is only ever the root screen of an open markdown document.");
2177
2283
  return openDocument;
2178
2284
  }
2179
2285
  //#endregion
2180
- //#region src/tui/screens/editors/odb/table-list.tsx
2181
- function OdbTableListScreen() {
2286
+ //#region src/tui/screens/editors/markdown/line-list.tsx
2287
+ function MarkdownLineListScreen() {
2182
2288
  const state = useAppState();
2183
2289
  const dispatch = useAppDispatch();
2184
- const doc = requireOdbDocument(state.openDocument);
2290
+ const doc = requireMarkdownDocument(state.openDocument);
2185
2291
  const query = state.searchQuery.trim().toLowerCase();
2186
- const tables = query === "" ? doc.tables : doc.tables.filter((table) => table.tableName.toLowerCase().includes(query));
2292
+ const allLines = doc.source.split("\n").map((line, lineIndex) => ({
2293
+ line,
2294
+ lineIndex
2295
+ }));
2296
+ const lines = query === "" ? allLines : allLines.filter((entry) => entry.line.toLowerCase().includes(query));
2187
2297
  const { selectedIndex } = useNavigationInput({
2188
- itemCount: tables.length,
2298
+ itemCount: lines.length,
2189
2299
  onSelect: (index) => {
2190
- const table = tables[index];
2191
- if (table === void 0) return;
2300
+ const entry = lines[index];
2301
+ if (entry === void 0) return;
2192
2302
  dispatch({
2193
2303
  type: "PUSH_SCREEN",
2194
2304
  screen: {
2195
- kind: "odbTableRows",
2196
- tableName: table.tableName
2305
+ kind: "markdownLineEditor",
2306
+ lineIndex: entry.lineIndex
2197
2307
  }
2198
2308
  });
2199
2309
  },
@@ -2207,32 +2317,410 @@ function OdbTableListScreen() {
2207
2317
  children: [/* @__PURE__ */ jsxs(Text, {
2208
2318
  bold: true,
2209
2319
  children: [
2210
- "Tables (",
2211
- tables.length,
2320
+ "Lines (",
2321
+ lines.length,
2212
2322
  " of ",
2213
- doc.tables.length,
2323
+ allLines.length,
2214
2324
  ")"
2215
2325
  ]
2216
2326
  }), /* @__PURE__ */ jsx(ListView, {
2217
- items: tables,
2327
+ items: lines,
2218
2328
  selectedIndex,
2219
- emptyMessage: query === "" ? "This database has no tables." : `No tables match "${state.searchQuery}".`,
2220
- renderItem: (table, isSelected) => /* @__PURE__ */ jsxs(Text, {
2329
+ emptyMessage: query === "" ? "This document has no lines." : `No lines match "${state.searchQuery}".`,
2330
+ renderItem: ({ line, lineIndex }, isSelected) => /* @__PURE__ */ jsxs(Text, {
2221
2331
  color: isSelected ? "cyan" : void 0,
2222
2332
  inverse: isSelected,
2223
2333
  children: [
2224
- table.tableName,
2225
- " (",
2226
- table.columns.length,
2227
- " columns, ",
2228
- table.rows.length,
2229
- " rows)"
2334
+ lineIndex + 1,
2335
+ ": ",
2336
+ line === "" ? "(blank)" : line
2230
2337
  ]
2231
2338
  })
2232
2339
  })]
2233
2340
  });
2234
2341
  }
2235
2342
  //#endregion
2343
+ //#region src/tui/screens/editors/markdown/line-editor.tsx
2344
+ function MarkdownLineEditorScreen() {
2345
+ const state = useAppState();
2346
+ const dispatch = useAppDispatch();
2347
+ const screen = currentScreen(state);
2348
+ const doc = state.openDocument;
2349
+ if (screen.kind !== "markdownLineEditor") return /* @__PURE__ */ jsx(Text, {
2350
+ color: "red",
2351
+ children: "MarkdownLineEditorScreen rendered outside a markdownLineEditor screen."
2352
+ });
2353
+ if (doc?.format !== "markdown") return /* @__PURE__ */ jsx(Text, {
2354
+ color: "red",
2355
+ children: "MarkdownLineEditorScreen requires an open markdown document."
2356
+ });
2357
+ const lines = doc.source.split("\n");
2358
+ const line = lines[screen.lineIndex];
2359
+ if (line === void 0) return /* @__PURE__ */ jsxs(Text, {
2360
+ color: "red",
2361
+ children: [
2362
+ "There is no line at index ",
2363
+ screen.lineIndex,
2364
+ "."
2365
+ ]
2366
+ });
2367
+ return /* @__PURE__ */ jsxs(Box, {
2368
+ flexDirection: "column",
2369
+ children: [
2370
+ /* @__PURE__ */ jsxs(Text, {
2371
+ bold: true,
2372
+ children: ["Edit line ", screen.lineIndex + 1]
2373
+ }),
2374
+ /* @__PURE__ */ jsx(RunTextEditor, {
2375
+ initialText: line,
2376
+ onCommit: (text) => {
2377
+ const nextLines = [...lines];
2378
+ nextLines[screen.lineIndex] = text;
2379
+ dispatch({
2380
+ type: "SET_MARKDOWN_SOURCE",
2381
+ source: nextLines.join("\n")
2382
+ });
2383
+ dispatch({ type: "POP_SCREEN" });
2384
+ },
2385
+ onCancel: () => {
2386
+ dispatch({ type: "POP_SCREEN" });
2387
+ }
2388
+ }),
2389
+ /* @__PURE__ */ jsx(Text, {
2390
+ dimColor: true,
2391
+ children: "Enter to commit, Esc to discard"
2392
+ })
2393
+ ]
2394
+ });
2395
+ }
2396
+ //#endregion
2397
+ //#region src/tui/screens/editors/odb/shared.ts
2398
+ function requireOdbDocument(openDocument) {
2399
+ if (openDocument?.format !== "odb") throw new Error("An .odb browsing screen rendered without an open .odb document; the app router only reaches this screen group from odbTableList, which is only ever the root screen of an open .odb document.");
2400
+ return openDocument;
2401
+ }
2402
+ //#endregion
2403
+ //#region src/tui/screens/editors/odb/form-detail.tsx
2404
+ const FORM_DETAIL_RESERVED_ROWS = 5;
2405
+ function requireForm(forms, formName) {
2406
+ const form = forms.find((candidate) => candidate.name === formName);
2407
+ if (form === void 0) throw new Error(`odbFormDetail was pushed for form "${formName}", but the open .odb document has no form by that name.`);
2408
+ return form;
2409
+ }
2410
+ function OdbFormDetailScreen() {
2411
+ const state = useAppState();
2412
+ const dispatch = useAppDispatch();
2413
+ const doc = requireOdbDocument(state.openDocument);
2414
+ const screen = currentScreen(state);
2415
+ if (screen.kind !== "odbFormDetail") throw new Error(`OdbFormDetailScreen rendered while the current screen is "${screen.kind}", not "odbFormDetail".`);
2416
+ const form = requireForm(doc.forms, screen.formName);
2417
+ const allLines = formatOdbFormLines(form);
2418
+ const query = state.searchQuery.trim().toLowerCase();
2419
+ const lines = query === "" ? allLines : allLines.filter((line) => line.toLowerCase().includes(query));
2420
+ const { selectedIndex } = useNavigationInput({
2421
+ itemCount: lines.length,
2422
+ onSelect: () => {},
2423
+ onBack: () => {
2424
+ dispatch({ type: "POP_SCREEN" });
2425
+ },
2426
+ isActive: !anyOverlayOpen(state)
2427
+ });
2428
+ return /* @__PURE__ */ jsxs(Box, {
2429
+ flexDirection: "column",
2430
+ children: [
2431
+ /* @__PURE__ */ jsxs(Text, {
2432
+ bold: true,
2433
+ children: [
2434
+ form.name,
2435
+ " (",
2436
+ lines.length,
2437
+ " of ",
2438
+ allLines.length,
2439
+ " lines)"
2440
+ ]
2441
+ }),
2442
+ /* @__PURE__ */ jsx(Text, {
2443
+ dimColor: true,
2444
+ children: form.href
2445
+ }),
2446
+ /* @__PURE__ */ jsx(ListView, {
2447
+ items: lines,
2448
+ selectedIndex,
2449
+ reservedRows: FORM_DETAIL_RESERVED_ROWS,
2450
+ emptyMessage: query === "" ? "This form declares no structure." : `No lines match "${state.searchQuery}".`,
2451
+ renderItem: (line, isSelected) => /* @__PURE__ */ jsx(Text, {
2452
+ color: isSelected ? "cyan" : void 0,
2453
+ inverse: isSelected,
2454
+ children: line
2455
+ })
2456
+ }),
2457
+ /* @__PURE__ */ jsx(Text, {
2458
+ dimColor: true,
2459
+ children: "Esc to go back to the form list"
2460
+ })
2461
+ ]
2462
+ });
2463
+ }
2464
+ //#endregion
2465
+ //#region src/tui/screens/editors/odb/form-list.tsx
2466
+ function OdbFormListScreen() {
2467
+ const state = useAppState();
2468
+ const dispatch = useAppDispatch();
2469
+ const doc = requireOdbDocument(state.openDocument);
2470
+ const query = state.searchQuery.trim().toLowerCase();
2471
+ const forms = query === "" ? doc.forms : doc.forms.filter((form) => form.name.toLowerCase().includes(query));
2472
+ const { selectedIndex } = useNavigationInput({
2473
+ itemCount: forms.length,
2474
+ onSelect: (index) => {
2475
+ const form = forms[index];
2476
+ if (form === void 0) return;
2477
+ dispatch({
2478
+ type: "PUSH_SCREEN",
2479
+ screen: {
2480
+ kind: "odbFormDetail",
2481
+ formName: form.name
2482
+ }
2483
+ });
2484
+ },
2485
+ onBack: () => {
2486
+ dispatch({ type: "POP_SCREEN" });
2487
+ },
2488
+ isActive: !anyOverlayOpen(state)
2489
+ });
2490
+ return /* @__PURE__ */ jsxs(Box, {
2491
+ flexDirection: "column",
2492
+ children: [
2493
+ /* @__PURE__ */ jsxs(Text, {
2494
+ bold: true,
2495
+ children: [
2496
+ "Forms (",
2497
+ forms.length,
2498
+ " of ",
2499
+ doc.forms.length,
2500
+ ")"
2501
+ ]
2502
+ }),
2503
+ /* @__PURE__ */ jsx(ListView, {
2504
+ items: forms,
2505
+ selectedIndex,
2506
+ emptyMessage: query === "" ? "This database declares no forms." : `No forms match "${state.searchQuery}".`,
2507
+ renderItem: (form, isSelected) => /* @__PURE__ */ jsx(Text, {
2508
+ color: isSelected ? "cyan" : void 0,
2509
+ inverse: isSelected,
2510
+ children: describeOdbForm(form)
2511
+ })
2512
+ }),
2513
+ /* @__PURE__ */ jsx(Text, {
2514
+ dimColor: true,
2515
+ children: "Enter to open a form, Esc to go back to the tables"
2516
+ })
2517
+ ]
2518
+ });
2519
+ }
2520
+ //#endregion
2521
+ //#region src/tui/screens/editors/odb/report-detail.tsx
2522
+ const REPORT_DETAIL_RESERVED_ROWS = 5;
2523
+ function requireReport(reports, reportName) {
2524
+ const report = reports.find((candidate) => candidate.name === reportName);
2525
+ if (report === void 0) throw new Error(`odbReportDetail was pushed for report "${reportName}", but the open .odb document has no report by that name.`);
2526
+ return report;
2527
+ }
2528
+ function OdbReportDetailScreen() {
2529
+ const state = useAppState();
2530
+ const dispatch = useAppDispatch();
2531
+ const doc = requireOdbDocument(state.openDocument);
2532
+ const screen = currentScreen(state);
2533
+ if (screen.kind !== "odbReportDetail") throw new Error(`OdbReportDetailScreen rendered while the current screen is "${screen.kind}", not "odbReportDetail".`);
2534
+ const report = requireReport(doc.reports, screen.reportName);
2535
+ const allLines = formatOdbReportLines(report);
2536
+ const query = state.searchQuery.trim().toLowerCase();
2537
+ const lines = query === "" ? allLines : allLines.filter((line) => line.toLowerCase().includes(query));
2538
+ const { selectedIndex } = useNavigationInput({
2539
+ itemCount: lines.length,
2540
+ onSelect: () => {},
2541
+ onBack: () => {
2542
+ dispatch({ type: "POP_SCREEN" });
2543
+ },
2544
+ isActive: !anyOverlayOpen(state)
2545
+ });
2546
+ return /* @__PURE__ */ jsxs(Box, {
2547
+ flexDirection: "column",
2548
+ children: [
2549
+ /* @__PURE__ */ jsxs(Text, {
2550
+ bold: true,
2551
+ children: [
2552
+ report.name,
2553
+ " (",
2554
+ lines.length,
2555
+ " of ",
2556
+ allLines.length,
2557
+ " lines)"
2558
+ ]
2559
+ }),
2560
+ /* @__PURE__ */ jsx(Text, {
2561
+ dimColor: true,
2562
+ children: report.href
2563
+ }),
2564
+ /* @__PURE__ */ jsx(ListView, {
2565
+ items: lines,
2566
+ selectedIndex,
2567
+ reservedRows: REPORT_DETAIL_RESERVED_ROWS,
2568
+ emptyMessage: query === "" ? "This report declares no structure." : `No lines match "${state.searchQuery}".`,
2569
+ renderItem: (line, isSelected) => /* @__PURE__ */ jsx(Text, {
2570
+ color: isSelected ? "cyan" : void 0,
2571
+ inverse: isSelected,
2572
+ children: line
2573
+ })
2574
+ }),
2575
+ /* @__PURE__ */ jsx(Text, {
2576
+ dimColor: true,
2577
+ children: "Esc to go back to the report list"
2578
+ })
2579
+ ]
2580
+ });
2581
+ }
2582
+ //#endregion
2583
+ //#region src/tui/screens/editors/odb/report-list.tsx
2584
+ function OdbReportListScreen() {
2585
+ const state = useAppState();
2586
+ const dispatch = useAppDispatch();
2587
+ const doc = requireOdbDocument(state.openDocument);
2588
+ const query = state.searchQuery.trim().toLowerCase();
2589
+ const reports = query === "" ? doc.reports : doc.reports.filter((report) => report.name.toLowerCase().includes(query));
2590
+ const { selectedIndex } = useNavigationInput({
2591
+ itemCount: reports.length,
2592
+ onSelect: (index) => {
2593
+ const report = reports[index];
2594
+ if (report === void 0) return;
2595
+ dispatch({
2596
+ type: "PUSH_SCREEN",
2597
+ screen: {
2598
+ kind: "odbReportDetail",
2599
+ reportName: report.name
2600
+ }
2601
+ });
2602
+ },
2603
+ onBack: () => {
2604
+ dispatch({ type: "POP_SCREEN" });
2605
+ },
2606
+ isActive: !anyOverlayOpen(state)
2607
+ });
2608
+ return /* @__PURE__ */ jsxs(Box, {
2609
+ flexDirection: "column",
2610
+ children: [
2611
+ /* @__PURE__ */ jsxs(Text, {
2612
+ bold: true,
2613
+ children: [
2614
+ "Reports (",
2615
+ reports.length,
2616
+ " of ",
2617
+ doc.reports.length,
2618
+ ")"
2619
+ ]
2620
+ }),
2621
+ /* @__PURE__ */ jsx(ListView, {
2622
+ items: reports,
2623
+ selectedIndex,
2624
+ emptyMessage: query === "" ? "This database declares no reports." : `No reports match "${state.searchQuery}".`,
2625
+ renderItem: (report, isSelected) => /* @__PURE__ */ jsx(Text, {
2626
+ color: isSelected ? "cyan" : void 0,
2627
+ inverse: isSelected,
2628
+ children: describeOdbReport(report)
2629
+ })
2630
+ }),
2631
+ /* @__PURE__ */ jsx(Text, {
2632
+ dimColor: true,
2633
+ children: "Enter to open a report, Esc to go back to the tables"
2634
+ })
2635
+ ]
2636
+ });
2637
+ }
2638
+ //#endregion
2639
+ //#region src/tui/screens/editors/odb/table-list.tsx
2640
+ const TABLE_LIST_RESERVED_ROWS = 5;
2641
+ function OdbTableListScreen() {
2642
+ const state = useAppState();
2643
+ const dispatch = useAppDispatch();
2644
+ const doc = requireOdbDocument(state.openDocument);
2645
+ const overlayOpen = anyOverlayOpen(state);
2646
+ const query = state.searchQuery.trim().toLowerCase();
2647
+ const tables = query === "" ? doc.tables : doc.tables.filter((table) => table.tableName.toLowerCase().includes(query));
2648
+ const { selectedIndex } = useNavigationInput({
2649
+ itemCount: tables.length,
2650
+ onSelect: (index) => {
2651
+ const table = tables[index];
2652
+ if (table === void 0) return;
2653
+ dispatch({
2654
+ type: "PUSH_SCREEN",
2655
+ screen: {
2656
+ kind: "odbTableRows",
2657
+ tableName: table.tableName
2658
+ }
2659
+ });
2660
+ },
2661
+ onBack: () => {
2662
+ dispatch({ type: "POP_SCREEN" });
2663
+ },
2664
+ isActive: !overlayOpen
2665
+ });
2666
+ useInput((input) => {
2667
+ if (input === "f") {
2668
+ dispatch({
2669
+ type: "PUSH_SCREEN",
2670
+ screen: { kind: "odbFormList" }
2671
+ });
2672
+ return;
2673
+ }
2674
+ if (input === "r") dispatch({
2675
+ type: "PUSH_SCREEN",
2676
+ screen: { kind: "odbReportList" }
2677
+ });
2678
+ }, { isActive: !overlayOpen });
2679
+ return /* @__PURE__ */ jsxs(Box, {
2680
+ flexDirection: "column",
2681
+ children: [
2682
+ /* @__PURE__ */ jsxs(Text, {
2683
+ bold: true,
2684
+ children: [
2685
+ "Tables (",
2686
+ tables.length,
2687
+ " of ",
2688
+ doc.tables.length,
2689
+ ")"
2690
+ ]
2691
+ }),
2692
+ /* @__PURE__ */ jsx(ListView, {
2693
+ items: tables,
2694
+ selectedIndex,
2695
+ reservedRows: TABLE_LIST_RESERVED_ROWS,
2696
+ emptyMessage: query === "" ? "This database has no tables." : `No tables match "${state.searchQuery}".`,
2697
+ renderItem: (table, isSelected) => /* @__PURE__ */ jsxs(Text, {
2698
+ color: isSelected ? "cyan" : void 0,
2699
+ inverse: isSelected,
2700
+ children: [
2701
+ table.tableName,
2702
+ " (",
2703
+ table.columns.length,
2704
+ " columns, ",
2705
+ table.rows.length,
2706
+ " rows)"
2707
+ ]
2708
+ })
2709
+ }),
2710
+ /* @__PURE__ */ jsxs(Text, {
2711
+ dimColor: true,
2712
+ children: [
2713
+ "Enter to open a table, f for forms (",
2714
+ doc.forms.length,
2715
+ "), r for reports (",
2716
+ doc.reports.length,
2717
+ ")"
2718
+ ]
2719
+ })
2720
+ ]
2721
+ });
2722
+ }
2723
+ //#endregion
2236
2724
  //#region src/tui/screens/editors/odb/table-rows.tsx
2237
2725
  const TABLE_ROWS_RESERVED_ROWS = 5;
2238
2726
  const CELL_SEPARATOR = " │ ";
@@ -3548,6 +4036,12 @@ const IMAGE_EXTENSION_TO_FORMAT = {
3548
4036
  jpg: "jpeg",
3549
4037
  jpeg: "jpeg"
3550
4038
  };
4039
+ const DEFAULT_TABLE_ROWS = 2;
4040
+ const DEFAULT_TABLE_COLUMNS = 2;
4041
+ function parsePositiveIntField(raw, fallback) {
4042
+ const parsed = Number.parseInt(raw, 10);
4043
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
4044
+ }
3551
4045
  function imageFormatFromPath(path) {
3552
4046
  const dotIndex = path.lastIndexOf(".");
3553
4047
  if (dotIndex < 0) return;
@@ -3576,6 +4070,7 @@ function SlideDetailScreen(props) {
3576
4070
  const [addMode, setAddMode] = useState("closed");
3577
4071
  const [draft, setDraft] = useState("");
3578
4072
  const [imageError, setImageError] = useState(void 0);
4073
+ const [tableRows, setTableRows] = useState(DEFAULT_TABLE_ROWS);
3579
4074
  const formIsOpen = addMode !== "closed";
3580
4075
  const { selectedIndex } = useNavigationInput({
3581
4076
  itemCount: rows.length,
@@ -3616,10 +4111,15 @@ function SlideDetailScreen(props) {
3616
4111
  setDraft("");
3617
4112
  setImageError(void 0);
3618
4113
  setAddMode("image");
4114
+ return;
4115
+ }
4116
+ if (input === "b") {
4117
+ setDraft(String(DEFAULT_TABLE_ROWS));
4118
+ setAddMode("tableRows");
3619
4119
  }
3620
4120
  }, { isActive: !overlayOpen && addMode === "chooseKind" });
3621
4121
  useInput((input) => {
3622
- if (input === "n" && doc.format === "odp") dispatch({
4122
+ if (input === "n") dispatch({
3623
4123
  type: "PUSH_SCREEN",
3624
4124
  screen: {
3625
4125
  kind: "notesEditor",
@@ -3654,6 +4154,22 @@ function SlideDetailScreen(props) {
3654
4154
  }
3655
4155
  })();
3656
4156
  };
4157
+ const commitTableRows = (raw) => {
4158
+ setTableRows(parsePositiveIntField(raw, DEFAULT_TABLE_ROWS));
4159
+ setDraft(String(DEFAULT_TABLE_COLUMNS));
4160
+ setAddMode("tableColumns");
4161
+ };
4162
+ const commitTableColumns = (raw) => {
4163
+ const columns = parsePositiveIntField(raw, DEFAULT_TABLE_COLUMNS);
4164
+ dispatch({
4165
+ type: "ADD_SLIDE_TABLE",
4166
+ slideIndex,
4167
+ frame: defaultShapeFrame(doc.editor.slideSize),
4168
+ rows: tableRows,
4169
+ columns
4170
+ });
4171
+ setAddMode("closed");
4172
+ };
3657
4173
  return /* @__PURE__ */ jsxs(Box, {
3658
4174
  flexDirection: "column",
3659
4175
  children: [
@@ -3690,7 +4206,7 @@ function SlideDetailScreen(props) {
3690
4206
  }),
3691
4207
  addMode === "chooseKind" ? /* @__PURE__ */ jsx(Text, {
3692
4208
  color: "cyan",
3693
- children: "Add shape: t textbox, i image, Esc cancel"
4209
+ children: "Add shape: t textbox, i image, b table, Esc cancel"
3694
4210
  }) : void 0,
3695
4211
  addMode === "textbox" ? /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, {
3696
4212
  color: "cyan",
@@ -3722,13 +4238,33 @@ function SlideDetailScreen(props) {
3722
4238
  children: imageError
3723
4239
  })]
3724
4240
  }) : void 0,
3725
- /* @__PURE__ */ jsxs(Text, {
4241
+ addMode === "tableRows" ? /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, {
4242
+ color: "cyan",
4243
+ children: "Rows: "
4244
+ }), /* @__PURE__ */ jsx(TextField, {
4245
+ value: draft,
4246
+ isFocused: true,
4247
+ onChange: setDraft,
4248
+ onSubmit: commitTableRows,
4249
+ onCancel: () => {
4250
+ setAddMode("closed");
4251
+ }
4252
+ })] }) : void 0,
4253
+ addMode === "tableColumns" ? /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, {
4254
+ color: "cyan",
4255
+ children: "Columns: "
4256
+ }), /* @__PURE__ */ jsx(TextField, {
4257
+ value: draft,
4258
+ isFocused: true,
4259
+ onChange: setDraft,
4260
+ onSubmit: commitTableColumns,
4261
+ onCancel: () => {
4262
+ setAddMode("closed");
4263
+ }
4264
+ })] }) : void 0,
4265
+ /* @__PURE__ */ jsx(Text, {
3726
4266
  dimColor: true,
3727
- children: [
3728
- "Enter: edit shape a: add shape",
3729
- doc.format === "odp" ? " n: notes" : "",
3730
- " Esc: back"
3731
- ]
4267
+ children: "Enter: edit shape a: add shape n: notes Esc: back"
3732
4268
  })
3733
4269
  ]
3734
4270
  });
@@ -3742,15 +4278,11 @@ function PptxSlideListScreen() {
3742
4278
  }
3743
4279
  //#endregion
3744
4280
  //#region src/tui/screens/editors/odp/notes-editor.tsx
3745
- function assertOdpDocument(doc) {
3746
- if (doc?.format !== "odp") throw new Error("NotesEditorScreen rendered without an open odp document; check the screen router in app.tsx.");
3747
- return doc;
3748
- }
3749
4281
  function NotesEditorScreen(props) {
3750
4282
  const state = useAppState();
3751
4283
  const dispatch = useAppDispatch();
3752
4284
  const overlayOpen = anyOverlayOpen(state);
3753
- const doc = assertOdpDocument(state.openDocument);
4285
+ const doc = assertPresentationDocument(state.openDocument);
3754
4286
  const { slideIndex } = props.screen;
3755
4287
  const slide = doc.editor.slides()[slideIndex];
3756
4288
  const [draft, setDraft] = useState(() => slide?.notes ?? "");
@@ -3855,6 +4387,7 @@ const KIND_BADGE = {
3855
4387
  boolean: "B",
3856
4388
  date: "D",
3857
4389
  time: "T",
4390
+ dateTime: "@",
3858
4391
  error: "E",
3859
4392
  empty: "."
3860
4393
  };
@@ -3866,6 +4399,7 @@ const CELL_VALUE_KINDS = [
3866
4399
  "boolean",
3867
4400
  "date",
3868
4401
  "time",
4402
+ "dateTime",
3869
4403
  "error"
3870
4404
  ];
3871
4405
  function rawEditableText(value) {
@@ -3874,6 +4408,7 @@ function rawEditableText(value) {
3874
4408
  case "string":
3875
4409
  case "date":
3876
4410
  case "time":
4411
+ case "dateTime":
3877
4412
  case "error": return value.value;
3878
4413
  case "boolean": return value.value ? "TRUE" : "FALSE";
3879
4414
  case "number":
@@ -3944,6 +4479,13 @@ function buildCellValue(kind, text) {
3944
4479
  value: trimmed
3945
4480
  };
3946
4481
  }
4482
+ case "dateTime": {
4483
+ const trimmed = text.trim();
4484
+ return trimmed.length === 0 ? void 0 : {
4485
+ kind: "dateTime",
4486
+ value: trimmed
4487
+ };
4488
+ }
3947
4489
  case "error": {
3948
4490
  const trimmed = text.trim();
3949
4491
  return trimmed.length === 0 ? void 0 : {
@@ -4691,7 +5233,7 @@ function OdtBodyListScreen() {
4691
5233
  //#endregion
4692
5234
  //#region src/tui/screens/editors/pdf/shared.ts
4693
5235
  function requirePdfDocument(openDocument) {
4694
- if (openDocument?.format !== "pdf") throw new Error("A PDF inspection screen rendered without an open PDF document; the app router only reaches this screen group from pdfPageList, which is only ever the root screen of an open PDF document.");
5236
+ if (openDocument?.format !== "pdf" && openDocument?.format !== "xlsx") throw new Error("A PDF inspection screen rendered without an open PDF or xlsx document; the app router only reaches this screen group from pdfPageList, which is only ever the root screen of one of those two formats.");
4695
5237
  return openDocument;
4696
5238
  }
4697
5239
  function formatSize(widthPt, heightPt) {
@@ -4990,12 +5532,17 @@ function PdfPageItemsScreen() {
4990
5532
  function defaultExportDestinationFor(document, cwd) {
4991
5533
  return document.path === void 0 ? join(cwd, "untitled.pdf") : defaultPdfPathFor(document.path);
4992
5534
  }
5535
+ function parseFontFileField(value) {
5536
+ return value.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
5537
+ }
4993
5538
  function ExportOptionsScreen() {
4994
5539
  const state = useAppState();
4995
5540
  const dispatch = useAppDispatch();
4996
5541
  const isActive = !anyOverlayOpen(state);
4997
5542
  const document = state.openDocument;
4998
5543
  const [destination, setDestination] = useState(() => document === void 0 ? "" : defaultExportDestinationFor(document, state.cwd));
5544
+ const [fontFiles, setFontFiles] = useState("");
5545
+ const [field, setField] = useState("destination");
4999
5546
  if (document === void 0) return /* @__PURE__ */ jsx(Box, {
5000
5547
  flexDirection: "column",
5001
5548
  children: /* @__PURE__ */ jsx(Text, {
@@ -5003,21 +5550,27 @@ function ExportOptionsScreen() {
5003
5550
  children: "There is no open document to export."
5004
5551
  })
5005
5552
  });
5006
- const submit = (value) => {
5553
+ const cancel = () => {
5554
+ dispatch({ type: "POP_SCREEN" });
5555
+ };
5556
+ const submit = () => {
5007
5557
  (async () => {
5008
5558
  let diagnosticCount = 0;
5009
5559
  try {
5010
- await exportToPdf(document, value, { onDiagnostic: (diagnostic) => {
5011
- diagnosticCount += 1;
5012
- dispatch({
5013
- type: "APPEND_DIAGNOSTIC",
5014
- diagnostic
5015
- });
5016
- } });
5560
+ await exportToPdf(document, destination, {
5561
+ fontFiles: parseFontFileField(fontFiles),
5562
+ onDiagnostic: (diagnostic) => {
5563
+ diagnosticCount += 1;
5564
+ dispatch({
5565
+ type: "APPEND_DIAGNOSTIC",
5566
+ diagnostic
5567
+ });
5568
+ }
5569
+ });
5017
5570
  dispatch({
5018
5571
  type: "SET_STATUS",
5019
5572
  severity: "info",
5020
- text: `Exported ${value}`
5573
+ text: `Exported ${destination}`
5021
5574
  });
5022
5575
  if (diagnosticCount > 0) dispatch({
5023
5576
  type: "OPEN_OVERLAY",
@@ -5027,7 +5580,7 @@ function ExportOptionsScreen() {
5027
5580
  } catch (error) {
5028
5581
  dispatch({
5029
5582
  type: "OPEN_FILE_ERROR",
5030
- message: `Could not export to ${value}`,
5583
+ message: `Could not export to ${destination}`,
5031
5584
  detail: describeError(error)
5032
5585
  });
5033
5586
  }
@@ -5045,17 +5598,28 @@ function ExportOptionsScreen() {
5045
5598
  children: "Path: "
5046
5599
  }), /* @__PURE__ */ jsx(TextField, {
5047
5600
  value: destination,
5048
- isFocused: isActive,
5601
+ isFocused: isActive && field === "destination",
5049
5602
  placeholder: "destination path",
5050
5603
  onChange: setDestination,
5604
+ onSubmit: () => {
5605
+ setField("fonts");
5606
+ },
5607
+ onCancel: cancel
5608
+ })] }),
5609
+ /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, {
5610
+ color: "cyan",
5611
+ children: "Fonts: "
5612
+ }), /* @__PURE__ */ jsx(TextField, {
5613
+ value: fontFiles,
5614
+ isFocused: isActive && field === "fonts",
5615
+ placeholder: "optional .ttf/.otf paths, comma-separated",
5616
+ onChange: setFontFiles,
5051
5617
  onSubmit: submit,
5052
- onCancel: () => {
5053
- dispatch({ type: "POP_SCREEN" });
5054
- }
5618
+ onCancel: cancel
5055
5619
  })] }),
5056
5620
  /* @__PURE__ */ jsx(Text, {
5057
5621
  dimColor: true,
5058
- children: "Enter to export, Esc to cancel"
5622
+ children: field === "destination" ? "Enter for fonts, Esc to cancel" : "Enter to export, Esc to cancel. Each font's family is read from the file itself"
5059
5623
  })
5060
5624
  ]
5061
5625
  });
@@ -5093,7 +5657,7 @@ function defaultBasenameFor(purpose, document) {
5093
5657
  const sourcePath = document?.path;
5094
5658
  const stem = sourcePath === void 0 ? "untitled" : basename(sourcePath, extname(sourcePath));
5095
5659
  if (purpose === "exportTarget") return `${stem}.pdf`;
5096
- return document !== void 0 && isEditableDocument(document) ? `${stem}.${formatToExtension(document.format)}` : stem;
5660
+ return document !== void 0 && isWritableDocument(document) ? `${stem}.${formatToExtension(document.format)}` : stem;
5097
5661
  }
5098
5662
  function titleFor(purpose) {
5099
5663
  switch (purpose) {
@@ -5307,7 +5871,7 @@ function LauncherScreen() {
5307
5871
  }),
5308
5872
  /* @__PURE__ */ jsx(Text, {
5309
5873
  dimColor: true,
5310
- children: "A terminal editor for docx, pptx, odt, odp, ods, odg, odb and pdf."
5874
+ children: "A terminal editor for docx, pptx, odt, odp, ods, odg, markdown, odb and pdf -- xlsx opens as a read-only PDF preview."
5311
5875
  }),
5312
5876
  /* @__PURE__ */ jsx(Text, { children: " " }),
5313
5877
  /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
@@ -5410,7 +5974,7 @@ function NewDocumentPickerScreen() {
5410
5974
  //#endregion
5411
5975
  //#region src/tui/screens/save-as-prompt.tsx
5412
5976
  function defaultDestinationFor(document, cwd) {
5413
- const extension = isEditableDocument(document) ? formatToExtension(document.format) : "bin";
5977
+ const extension = isWritableDocument(document) ? formatToExtension(document.format) : "bin";
5414
5978
  const suggestedName = document.path === void 0 ? `untitled.${extension}` : basename(document.path);
5415
5979
  return join(cwd, suggestedName);
5416
5980
  }
@@ -5493,6 +6057,12 @@ function ScreenBody({ screen }) {
5493
6057
  case "shapeOrVectorDetail": return /* @__PURE__ */ jsx(OdgShapeOrVectorDetailScreen, {});
5494
6058
  case "odbTableList": return /* @__PURE__ */ jsx(OdbTableListScreen, {});
5495
6059
  case "odbTableRows": return /* @__PURE__ */ jsx(OdbTableRowsScreen, {});
6060
+ case "odbFormList": return /* @__PURE__ */ jsx(OdbFormListScreen, {});
6061
+ case "odbFormDetail": return /* @__PURE__ */ jsx(OdbFormDetailScreen, {});
6062
+ case "odbReportList": return /* @__PURE__ */ jsx(OdbReportListScreen, {});
6063
+ case "odbReportDetail": return /* @__PURE__ */ jsx(OdbReportDetailScreen, {});
6064
+ case "markdownLineList": return /* @__PURE__ */ jsx(MarkdownLineListScreen, {});
6065
+ case "markdownLineEditor": return /* @__PURE__ */ jsx(MarkdownLineEditorScreen, {});
5496
6066
  case "pdfPageList": return /* @__PURE__ */ jsx(PdfPageListScreen, {});
5497
6067
  case "pdfPageItems": return /* @__PURE__ */ jsx(PdfPageItemsScreen, {});
5498
6068
  case "pdfItemDetail": return /* @__PURE__ */ jsx(PdfItemDetailScreen, {});