document-cli 5.6.45 → 5.7.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.
package/README.md CHANGED
@@ -227,11 +227,11 @@ Diagnostics and the summary line always go to stderr; stdout is reserved for the
227
227
 
228
228
  Launch it either bare (`document-cli`, with no arguments) or explicitly with `document-cli tui [file]` — both open the same app; the explicit form additionally opens `file` immediately, skipping the launcher screen. The TUI needs an interactive terminal: a bare invocation with redirected stdout prints help text instead, and an explicit `tui` invocation with redirected stdout fails outright, since there's no terminal for Ink to draw into.
229
229
 
230
- It supports the same seven formats documents.js's live-view editors cover — docx, pptx, odt, odp, ods, odg, markdown — each with a full navigate/edit/save experience built on that format's editor (paragraphs and runs for docx/odt/markdown, slides and shapes for pptx/odp, sheets and cells for ods, pages and vectors/shapes for odg), plus undo (whole-document snapshots taken before each committed mutation), search, a command palette, and PDF export straight from the open document. On a pptx or odp slide, `a` from the shape list also adds a real table (rows then columns, a two-step prompt) alongside the existing textbox/image choices, and `n` opens the slide's own speaker notes for either format — `PptxSlide` and `OdpSlide` both carry a real `.notes` getter/setter, so notes editing was never odp-specific, only gated that way until this phase removed the gate. On a docx document, `x` from the paragraph/table list opens a read-only view of the document's own comments, footnotes, headers/footers, and numbering definitions — a docx-only concept with no odt equivalent, and the TUI counterpart to the `docx-extras` command, rendered through the identical `src/docx-extras-format.ts` line formatter so the two can't drift apart. `m`, global to every screen with a document open, shows that document's own title/author/subject/keywords/etc metadata — every format the `metadata` command covers, including `.odb`/`.pdf`/`.xlsx`. For the seven live-view-editor formats, title/author/subject/keywords are editable in place through documents.js's own `editor.metadata` setter (ExaDev/documents.js#933): select a field and Enter opens it for editing, committing a partial merge that leaves every other field untouched. Every other format (and creator/producer/created/modified on the editable formats too, since those are derived/producer-stamped rather than user-authored) stays read-only here — use the `set-metadata` command for a format with no live-view editor.
230
+ It supports the same formats documents.js's live-view editors cover — docx, pptx, odt, odp, ods, odg, markdown, and since their codecs gained writers the three legacy binary formats doc, xls, and ppt — each with a full navigate/edit/save experience built on that format's editor (paragraphs and runs for docx/odt/markdown/doc, slides and shapes for pptx/odp/ppt, sheets and cells for ods/xls, pages and vectors/shapes for odg), plus undo (whole-document snapshots taken before each committed mutation), search, a command palette, and PDF export straight from the open document. On a pptx or odp slide, `a` from the shape list also adds a real table (rows then columns, a two-step prompt) alongside the existing textbox/image choices, and `n` opens the slide's own speaker notes for either format — `PptxSlide` and `OdpSlide` both carry a real `.notes` getter/setter, so notes editing was never odp-specific, only gated that way until this phase removed the gate. On a docx document, `x` from the paragraph/table list opens a read-only view of the document's own comments, footnotes, headers/footers, and numbering definitions — a docx-only concept with no odt equivalent, and the TUI counterpart to the `docx-extras` command, rendered through the identical `src/docx-extras-format.ts` line formatter so the two can't drift apart. `m`, global to every screen with a document open, shows that document's own title/author/subject/keywords/etc metadata — every format the `metadata` command covers, including `.odb`/`.pdf`/`.xlsx`. For the seven live-view-editor formats, title/author/subject/keywords are editable in place through documents.js's own `editor.metadata` setter (ExaDev/documents.js#933): select a field and Enter opens it for editing, committing a partial merge that leaves every other field untouched. Every other format (and creator/producer/created/modified on the editable formats too, since those are derived/producer-stamped rather than user-authored) stays read-only here — use the `set-metadata` command for a format with no live-view editor.
231
231
 
232
232
  Markdown (`.md`/`.markdown`) shares the same paragraph/run/table body-list screens docx and odt already use, through documents.js's own `MarkdownEditor` (`openMarkdown`/`createMarkdownEditor`) — a genuine live view over a mutable `ContentDocument`, the same live-view contract every other editor here follows, even though there is no `XmlElement` tree underneath it the way there is for docx/odt (`MarkdownEditor.toMarkdownText()` re-serialises the whole document fresh on every call, rather than exposing a `toBytes()`). Appending a paragraph, appending a run, and toggling bold/italic all go through the identical reducer actions docx/odt use; a markdown run has no underline, colour, font family, or font size at all (CommonMark/GFM has no construct for any of the four), so those keys — along with image insertion, which `MarkdownParagraph` has no counterpart for — are simply absent from a markdown paragraph's own key hints rather than opening a prompt that could only end in a warning. A markdown table can be created and its cells edited through the same 'T' wizard and table-view screens docx/odt use, but GFM tables have no cell-merge concept, so a merge requested alongside table creation still creates the table (unmerged) and reports why the merge itself didn't happen. `:view-source` (markdown documents only) shows the literal text the document was opened with side by side with what a save would write right now — these can genuinely differ even with no edits made this session, from a heading-style, bullet-marker, or line-ending choice the writer normalises. Every save re-serialises the whole document fresh through `buildMarkdownText`, a deliberate, permanent consequence of structured editing rather than something to work around. Diagnostics from the read side (a clamped heading level, a dropped front-matter key, a fenced code block's own info string with nowhere to go, …) now surface into the same diagnostics panel a PDF export's own substitutions already populate, the moment a `.md` file is opened, not only on export. documents.js's own `createMarkdownEditor()` exists now, but this TUI does not yet wire a "new markdown document" flow into `:new`/the new-document picker, so a markdown document can still only be opened from an existing file.
233
233
 
234
- Three further kinds of format open read-only: a `.odb` browses its tables and rows with no write path at all (documents.js's own `.odb` support has no write direction to offer), a `.pdf` browses its pages and positioned items rather than being edited in place, and a `.xlsx`, `.csv`, `.svg`, `.rtf`, `.wpd`, `.doc`, `.xls`, or `.ppt` opens as a converted PDF preview — documents.js has no spreadsheet, svg, rtf, wpd, doc, xls, or ppt editor to hold a live view into (wpd-codec ships no writer at all, so a wpd document could never gain one; doc-codec/xls-codec/ppt-codec ship real writers now, but no live TUI editor targets them yet), so opening one runs `xlsxToPdf`, `csvToPdf`, `svgToPdf`, `rtfToPdf`, `convertDocument("wpd", "pdf", ...)`, `docToPdf`, `xlsToPdf`, or `pptToPdf` once at open time and browses the result through the identical page-list/page-items/item-detail screens a real `.pdf` uses, with the original bytes kept alongside so a later export re-runs the same conversion with the caller's own fonts and diagnostics rather than reusing the fixed preview conversion. A `.odb` additionally browses its _structure_ alongside its data: `f` from the table list opens the form browser and `r` the report browser, each listing what the database declares and opening one to show it in full — a form's own data source and field-bound controls (sub-forms nested under their parent), a report's data-source command, band and group structure, and every `rpt:` formula. Both are rendered through the same `src/odb-structure.ts` the `odb-forms`/`odb-reports` commands print, so the two views cannot drift apart, and search filters by line (`/SUM` narrows a long report to its aggregate expressions). `Enter` on a report's own detail screen renders it — its data-source command resolved, its `rpt:` formulas evaluated, its bands laid out — to a real `docx`/`odt`/`pdf` file, through the same two-field destination-path-then-font-list form the PDF-export screen below uses; the TUI counterpart to the `odb-render-report` command. A standalone `.odf` formula document has no TUI editor either — nothing to edit interactively, only a PDF conversion.
234
+ Three further kinds of format open read-only: a `.odb` browses its tables and rows with no write path at all (documents.js's own `.odb` support has no write direction to offer), a `.pdf` browses its pages and positioned items rather than being edited in place, and a `.xlsx`, `.csv`, `.svg`, `.rtf`, `.wpd`, or `.epub` opens as a converted PDF preview — documents.js has no spreadsheet, svg, rtf, wpd, or epub editor to hold a live view into (wpd-codec ships no writer at all, so a wpd document could never gain one) so opening one runs `xlsxToPdf`, `csvToPdf`, `svgToPdf`, `rtfToPdf`, `convertDocument("wpd", "pdf", ...)`, or `epubToPdf` once at open time and browses the result through the identical page-list/page-items/item-detail screens a real `.pdf` uses, with the original bytes kept alongside so a later export re-runs the same conversion with the caller's own fonts and diagnostics rather than reusing the fixed preview conversion. A `.doc`, `.xls`, or `.ppt` is NOT in that read-only group any more: each opens through its own live-view editor (`DocEditor`/`XlsEditor`/`PptEditor`) exactly the way docx/odt do — doc through the shared paragraph/run/table screens, xls through the sheet-list/grid pair (cell values of every kind, with the value editor the ods grid already uses), ppt through the slide list plus a detail screen scoped to what a .ppt can state (shape text, speaker notes, adding text boxes). A later export runs `docToPdf`/`xlsToPdf`/`pptToPdf` from the editor's current bytes, so it reflects edits made this session. Two honest limits carried over from the codecs themselves: an xls cell's formula is readable but never written back (xls-codec's writer has no formula path), and an xls merge is set as colSpan/rowSpan on the anchor cell rather than through a rectangle prompt. A `.odb` additionally browses its _structure_ alongside its data: `f` from the table list opens the form browser and `r` the report browser, each listing what the database declares and opening one to show it in full — a form's own data source and field-bound controls (sub-forms nested under their parent), a report's data-source command, band and group structure, and every `rpt:` formula. Both are rendered through the same `src/odb-structure.ts` the `odb-forms`/`odb-reports` commands print, so the two views cannot drift apart, and search filters by line (`/SUM` narrows a long report to its aggregate expressions). `Enter` on a report's own detail screen renders it — its data-source command resolved, its `rpt:` formulas evaluated, its bands laid out — to a real `docx`/`odt`/`pdf` file, through the same two-field destination-path-then-font-list form the PDF-export screen below uses; the TUI counterpart to the `odb-render-report` command. A standalone `.odf` formula document has no TUI editor either — nothing to edit interactively, only a PDF conversion.
235
235
 
236
236
  The export-to-PDF screen (`e` from any editor screen) is a two-field form: a destination path, then an optional comma-separated list of local `.ttf`/`.otf` paths, which are the same `--font-file` faces the CLI takes and are derived the same way — each font's family, weight, and slope come from the file itself. `Enter` on the path field moves to the fonts field and `Enter` there exports, so leaving fonts empty is still "type a path, press Enter twice". Comma-separated rather than space-separated because a font path routinely contains spaces and almost never a comma. A face falling back to a substitute is reported into the same diagnostics panel a character substitution already is, and a bad font path fails the export with the file named, before anything is written to the destination.
237
237
 
package/dist/cli.js CHANGED
@@ -1199,7 +1199,7 @@ function registerSetMetadataCommand(program) {
1199
1199
  }
1200
1200
  //#endregion
1201
1201
  //#region package.json
1202
- var version = "5.6.45";
1202
+ var version = "5.7.0";
1203
1203
  //#endregion
1204
1204
  //#region src/program.ts
1205
1205
  function createProgram() {
@@ -1227,7 +1227,7 @@ function createProgram() {
1227
1227
  //#region src/cli.ts
1228
1228
  async function launchTui(startPath, signal) {
1229
1229
  try {
1230
- const { runTui } = await import("./tui-FXazDCHg.js");
1230
+ const { runTui } = await import("./tui-CGo4rAhe.js");
1231
1231
  await runTui({
1232
1232
  startPath,
1233
1233
  signal
package/dist/index.cjs CHANGED
@@ -1549,7 +1549,7 @@ function registerSetMetadataCommand(program) {
1549
1549
  }
1550
1550
  //#endregion
1551
1551
  //#region package.json
1552
- var version = "5.6.45";
1552
+ var version = "5.7.0";
1553
1553
  //#endregion
1554
1554
  //#region src/program.ts
1555
1555
  function createProgram() {
package/dist/index.js CHANGED
@@ -1548,7 +1548,7 @@ function registerSetMetadataCommand(program) {
1548
1548
  }
1549
1549
  //#endregion
1550
1550
  //#region package.json
1551
- var version = "5.6.45";
1551
+ var version = "5.7.0";
1552
1552
  //#endregion
1553
1553
  //#region src/program.ts
1554
1554
  function createProgram() {
@@ -1,7 +1,7 @@
1
1
  import { h as inferFormatFromExtension, i as formatOdbReportLines, l as formatDocxExtrasLines, m as formatToExtension, n as describeOdbReport, o as formatMetadataLines, p as loadProvidedFonts, r as formatOdbFormLines, s as formatMetadataValue, t as describeOdbForm, u as readInput } from "./odb-structure-CItnH50p.js";
2
2
  import { readFile, writeFile } from "node:fs/promises";
3
3
  import { basename, dirname, extname, join } from "node:path";
4
- import { bytesToBase64, cellReference, columnIndexToLetters, convertDocument, createDocx, createMarkdownEditor, createOdg, createOdp, createOds, createOdt, createPdf, createPptx, csvToPdf, decodeMarkdownText, decodeOdbPackage, docToPdf, docxToPdf, encodeMarkdownText, epubToPdf, hsqldbCellDisplayText, markdownToPdf, odbReportToDocx, odbReportToOdt, odbReportToPdf, odgToPdf, odpToPdf, odsToPdf, odtToPdf, openDocx, openMarkdown, openOdg, openOdp, openOds, openOdt, openPdf, openPptx, parseXml, pptToPdf, pptxToPdf, readDocxContent, readDocxExtras, readMarkdownContent, readOdbForms, readOdbReportContent, readOdbReports, readOdbTables, readOdgContent, readOdpContent, readOdsContent, readOdtContent, readPdf, readPptxContent, rgbHexToColor, rtfToPdf, svgToPdf, xlsToPdf, xlsxToPdf } from "documents.js";
4
+ import { bytesToBase64, cellReference, columnIndexToLetters, convertDocument, createDoc, createDocx, createMarkdownEditor, createOdg, createOdp, createOds, createOdt, createPdf, createPpt, createPptx, createXls, csvToPdf, decodeMarkdownText, decodeOdbPackage, docToPdf, docxToPdf, encodeMarkdownText, epubToPdf, hsqldbCellDisplayText, markdownToPdf, odbReportToDocx, odbReportToOdt, odbReportToPdf, odgToPdf, odpToPdf, odsToPdf, odtToPdf, openDoc, openDocx, openMarkdown, openOdg, openOdp, openOds, openOdt, openPdf, openPpt, openPptx, openXls, parseXml, pptToPdf, pptxToPdf, readDocxContent, readDocxExtras, readMarkdownContent, readOdbForms, readOdbReportContent, readOdbReports, readOdbTables, readOdgContent, readOdpContent, readOdsContent, readOdtContent, readPdf, readPptxContent, rgbHexToColor, rtfToPdf, svgToPdf, xlsToPdf, xlsxToPdf } from "documents.js";
5
5
  import { readdirSync } from "node:fs";
6
6
  import { Box, Text, render, useApp, useInput, useWindowSize } from "ink";
7
7
  import { createContext, useContext, useEffect, useReducer, useState } from "react";
@@ -68,17 +68,17 @@ async function exportToPdf(openDocument, destinationPath, options) {
68
68
  return;
69
69
  }
70
70
  if (openDocument.format === "doc") {
71
- const pdfBytes = docToPdf(openDocument.bytes, pdfOptions);
71
+ const pdfBytes = docToPdf(openDocument.editor.toBytes(), pdfOptions);
72
72
  await writeFile(destinationPath, pdfBytes);
73
73
  return;
74
74
  }
75
75
  if (openDocument.format === "xls") {
76
- const pdfBytes = xlsToPdf(openDocument.bytes, pdfOptions);
76
+ const pdfBytes = xlsToPdf(openDocument.editor.toBytes(), pdfOptions);
77
77
  await writeFile(destinationPath, pdfBytes);
78
78
  return;
79
79
  }
80
80
  if (openDocument.format === "ppt") {
81
- const pdfBytes = pptToPdf(openDocument.bytes, pdfOptions);
81
+ const pdfBytes = pptToPdf(openDocument.editor.toBytes(), pdfOptions);
82
82
  await writeFile(destinationPath, pdfBytes);
83
83
  return;
84
84
  }
@@ -214,20 +214,17 @@ async function openDocumentAtPath(path, options = {}) {
214
214
  };
215
215
  case "doc": return {
216
216
  format,
217
- layout: readPdf(docToPdf(bytes)),
218
- bytes,
217
+ editor: openDoc(bytes),
219
218
  path
220
219
  };
221
220
  case "xls": return {
222
221
  format,
223
- layout: readPdf(xlsToPdf(bytes)),
224
- bytes,
222
+ editor: openXls(bytes),
225
223
  path
226
224
  };
227
225
  case "ppt": return {
228
226
  format,
229
- layout: readPdf(pptToPdf(bytes)),
230
- bytes,
227
+ editor: openPpt(bytes),
231
228
  path
232
229
  };
233
230
  case "epub": return {
@@ -277,6 +274,21 @@ function createNewDocument(format) {
277
274
  editor: createOdg(),
278
275
  path: void 0
279
276
  };
277
+ case "doc": return {
278
+ format,
279
+ editor: createDoc(),
280
+ path: void 0
281
+ };
282
+ case "xls": return {
283
+ format,
284
+ editor: createXls(),
285
+ path: void 0
286
+ };
287
+ case "ppt": return {
288
+ format,
289
+ editor: createPpt(),
290
+ path: void 0
291
+ };
280
292
  case "pdf": {
281
293
  const editor = createPdf();
282
294
  return {
@@ -289,7 +301,7 @@ function createNewDocument(format) {
289
301
  }
290
302
  }
291
303
  async function saveDocumentTo(openDocument, path) {
292
- if (openDocument.format === "odb" || openDocument.format === "xlsx" || openDocument.format === "csv" || openDocument.format === "svg" || openDocument.format === "rtf" || openDocument.format === "wpd" || openDocument.format === "doc" || openDocument.format === "xls" || openDocument.format === "ppt" || openDocument.format === "epub") throw new Error(`A ${openDocument.format} document is opened read-only and cannot be written back`);
304
+ if (openDocument.format === "odb" || openDocument.format === "xlsx" || openDocument.format === "csv" || openDocument.format === "svg" || openDocument.format === "rtf" || openDocument.format === "wpd" || openDocument.format === "epub") throw new Error(`A ${openDocument.format} document is opened read-only and cannot be written back`);
293
305
  if (openDocument.format === "markdown") {
294
306
  await writeFile(path, encodeMarkdownText(openDocument.editor.toMarkdownText()));
295
307
  return;
@@ -305,7 +317,10 @@ const EDITABLE_FORMATS = {
305
317
  odp: true,
306
318
  ods: true,
307
319
  odg: true,
308
- pdf: true
320
+ pdf: true,
321
+ doc: true,
322
+ xls: true,
323
+ ppt: true
309
324
  };
310
325
  const WRITABLE_FORMATS = {
311
326
  ...EDITABLE_FORMATS,
@@ -367,10 +382,13 @@ function rootScreenForFormat(format) {
367
382
  switch (format) {
368
383
  case "docx":
369
384
  case "odt":
370
- case "markdown": return { kind: "bodyList" };
385
+ case "markdown":
386
+ case "doc": return { kind: "bodyList" };
371
387
  case "pptx":
372
- case "odp": return { kind: "slideList" };
373
- case "ods": return { kind: "sheetList" };
388
+ case "odp":
389
+ case "ppt": return { kind: "slideList" };
390
+ case "ods":
391
+ case "xls": return { kind: "sheetList" };
374
392
  case "odg": return { kind: "pageList" };
375
393
  case "odb": return { kind: "odbTableList" };
376
394
  case "pdf":
@@ -379,9 +397,6 @@ function rootScreenForFormat(format) {
379
397
  case "svg":
380
398
  case "rtf":
381
399
  case "wpd":
382
- case "doc":
383
- case "xls":
384
- case "ppt":
385
400
  case "epub": return { kind: "pdfPageList" };
386
401
  }
387
402
  }
@@ -545,20 +560,17 @@ function documentWithPath(doc, path) {
545
560
  };
546
561
  case "doc": return {
547
562
  format: "doc",
548
- layout: doc.layout,
549
- bytes: doc.bytes,
563
+ editor: doc.editor,
550
564
  path
551
565
  };
552
566
  case "xls": return {
553
567
  format: "xls",
554
- layout: doc.layout,
555
- bytes: doc.bytes,
568
+ editor: doc.editor,
556
569
  path
557
570
  };
558
571
  case "ppt": return {
559
572
  format: "ppt",
560
- layout: doc.layout,
561
- bytes: doc.bytes,
573
+ editor: doc.editor,
562
574
  path
563
575
  };
564
576
  case "epub": return {
@@ -601,6 +613,21 @@ function reopenEditable(doc, bytes) {
601
613
  editor: openOdg(bytes),
602
614
  path: doc.path
603
615
  };
616
+ case "doc": return {
617
+ format: "doc",
618
+ editor: openDoc(bytes),
619
+ path: doc.path
620
+ };
621
+ case "xls": return {
622
+ format: "xls",
623
+ editor: openXls(bytes),
624
+ path: doc.path
625
+ };
626
+ case "ppt": return {
627
+ format: "ppt",
628
+ editor: openPpt(bytes),
629
+ path: doc.path
630
+ };
604
631
  case "pdf": {
605
632
  const editor = openPdf(bytes);
606
633
  return {
@@ -662,24 +689,39 @@ function wrongDocument(state, expected) {
662
689
  function wordprocessingDocument(state) {
663
690
  const doc = state.openDocument;
664
691
  if (doc === void 0) return;
665
- return doc.format === "docx" || doc.format === "odt" || doc.format === "markdown" ? doc : void 0;
692
+ return doc.format === "docx" || doc.format === "odt" || doc.format === "markdown" || doc.format === "doc" ? doc : void 0;
666
693
  }
667
694
  function styledWordprocessingDocument(state) {
695
+ const doc = state.openDocument;
696
+ if (doc === void 0) return;
697
+ return doc.format === "docx" || doc.format === "odt" || doc.format === "doc" ? doc : void 0;
698
+ }
699
+ function docxOdtDocument(state) {
668
700
  const doc = state.openDocument;
669
701
  if (doc === void 0) return;
670
702
  return doc.format === "docx" || doc.format === "odt" ? doc : void 0;
671
703
  }
672
- function presentationDocument(state) {
704
+ function richPresentationDocument(state) {
673
705
  const doc = state.openDocument;
674
706
  if (doc === void 0) return;
675
707
  return doc.format === "pptx" || doc.format === "odp" ? doc : void 0;
676
708
  }
709
+ function presentationDocument(state) {
710
+ const doc = state.openDocument;
711
+ if (doc === void 0) return;
712
+ return doc.format === "pptx" || doc.format === "odp" || doc.format === "ppt" ? doc : void 0;
713
+ }
677
714
  function shapeHostDocument(state) {
678
715
  const doc = state.openDocument;
679
716
  if (doc === void 0) return;
680
717
  return doc.format === "pptx" || doc.format === "odp" || doc.format === "odg" ? doc : void 0;
681
718
  }
682
719
  function spreadsheetDocument(state) {
720
+ const doc = state.openDocument;
721
+ if (doc === void 0) return;
722
+ return doc.format === "ods" || doc.format === "xls" ? doc : void 0;
723
+ }
724
+ function odsDocument$1(state) {
683
725
  const doc = state.openDocument;
684
726
  if (doc === void 0) return;
685
727
  return doc.format === "ods" ? doc : void 0;
@@ -745,6 +787,9 @@ function shapeAt(doc, containerIndex, shapeIndex) {
745
787
  function sheetAt(doc, sheetIndex) {
746
788
  return doc.editor.sheets()[sheetIndex];
747
789
  }
790
+ function odsSheetAt(doc, sheetIndex) {
791
+ return doc.editor.sheets()[sheetIndex];
792
+ }
748
793
  function withRun(state, blockIndex, runIndex, apply) {
749
794
  const doc = wordprocessingDocument(state);
750
795
  if (doc === void 0) return wrongDocument(state, "a docx, odt or markdown document");
@@ -758,7 +803,7 @@ function withRun(state, blockIndex, runIndex, apply) {
758
803
  }
759
804
  function withStyledRun(state, blockIndex, runIndex, apply) {
760
805
  const doc = styledWordprocessingDocument(state);
761
- if (doc === void 0) return wrongDocument(state, "a docx or odt document");
806
+ if (doc === void 0) return wrongDocument(state, "a docx, odt or doc document");
762
807
  const paragraph = doc.editor.paragraphs()[blockIndex];
763
808
  if (paragraph === void 0) return withStatus(state, "warning", `There is no paragraph at index ${blockIndex}`);
764
809
  const run = paragraph.runs()[runIndex];
@@ -776,15 +821,35 @@ function withShape(state, containerIndex, shapeIndex, apply) {
776
821
  apply(shape);
777
822
  });
778
823
  }
824
+ function shapeWideAt(doc, containerIndex, shapeIndex) {
825
+ if (doc.format === "odg") return doc.editor.pages()[containerIndex]?.shapes()[shapeIndex];
826
+ return doc.editor.slides()[containerIndex]?.shapes()[shapeIndex];
827
+ }
828
+ function withWideShape(state, containerIndex, shapeIndex, apply) {
829
+ const doc = drawingDocument(state) ?? presentationDocument(state);
830
+ if (doc === void 0) return wrongDocument(state, "a pptx, odp, ppt or odg document");
831
+ const shape = shapeWideAt(doc, containerIndex, shapeIndex);
832
+ if (shape === void 0) return withStatus(state, "warning", `There is no shape ${shapeIndex} on ${doc.format === "odg" ? "page" : "slide"} ${containerIndex}`);
833
+ return mutate(state, doc, () => {
834
+ apply(shape);
835
+ });
836
+ }
779
837
  function withSheet(state, sheetIndex, apply) {
780
838
  const doc = spreadsheetDocument(state);
781
- if (doc === void 0) return wrongDocument(state, "an ods document");
839
+ if (doc === void 0) return wrongDocument(state, "an ods or xls document");
782
840
  const sheet = sheetAt(doc, sheetIndex);
783
841
  if (sheet === void 0) return withStatus(state, "warning", `There is no sheet at index ${sheetIndex}`);
784
842
  return mutate(state, doc, () => {
785
843
  apply(sheet);
786
844
  });
787
845
  }
846
+ function withOdsSheet(state, doc, sheetIndex, apply) {
847
+ const sheet = odsSheetAt(doc, sheetIndex);
848
+ if (sheet === void 0) return withStatus(state, "warning", `There is no sheet at index ${sheetIndex}`);
849
+ return mutate(state, doc, () => {
850
+ apply(sheet);
851
+ });
852
+ }
788
853
  function setTextContainerText(container, text) {
789
854
  const paragraph = container.paragraphs()[0] ?? container.appendParagraph();
790
855
  const runs = paragraph.runs();
@@ -1072,7 +1137,7 @@ function appReducer(state, action) {
1072
1137
  });
1073
1138
  }
1074
1139
  case "INSERT_PARAGRAPH_IMAGE": {
1075
- const doc = styledWordprocessingDocument(state);
1140
+ const doc = docxOdtDocument(state);
1076
1141
  if (doc === void 0) return wrongDocument(state, "a docx or odt document");
1077
1142
  const paragraph = doc.editor.paragraphs()[action.blockIndex];
1078
1143
  if (paragraph === void 0) return withStatus(state, "warning", `There is no paragraph at index ${action.blockIndex}`);
@@ -1112,7 +1177,7 @@ function appReducer(state, action) {
1112
1177
  });
1113
1178
  }
1114
1179
  case "ADD_SLIDE_TABLE": {
1115
- const doc = presentationDocument(state);
1180
+ const doc = richPresentationDocument(state);
1116
1181
  if (doc === void 0) return wrongDocument(state, "a pptx or odp document");
1117
1182
  const slide = doc.editor.slides()[action.slideIndex];
1118
1183
  if (slide === void 0) return withStatus(state, "warning", `There is no slide at index ${action.slideIndex}`);
@@ -1127,7 +1192,7 @@ function appReducer(state, action) {
1127
1192
  });
1128
1193
  }
1129
1194
  case "MERGE_SLIDE_TABLE_CELLS": {
1130
- const doc = presentationDocument(state);
1195
+ const doc = richPresentationDocument(state);
1131
1196
  if (doc === void 0) return wrongDocument(state, "a pptx or odp document");
1132
1197
  if (doc.format === "odp") {
1133
1198
  const entry = doc.editor.slides()[action.slideIndex]?.tables()[action.tableIndex];
@@ -1143,18 +1208,19 @@ function appReducer(state, action) {
1143
1208
  });
1144
1209
  }
1145
1210
  case "ADD_TEXTBOX": {
1146
- const doc = shapeHostDocument(state);
1147
- if (doc === void 0) return wrongDocument(state, "a pptx, odp or odg document");
1148
- if (doc.format === "odg") {
1149
- const page = doc.editor.pages()[action.containerIndex];
1211
+ const drawing = drawingDocument(state);
1212
+ if (drawing !== void 0) {
1213
+ const page = drawing.editor.pages()[action.containerIndex];
1150
1214
  if (page === void 0) return withStatus(state, "warning", `There is no page at index ${action.containerIndex}`);
1151
- return mutate(state, doc, () => {
1215
+ return mutate(state, drawing, () => {
1152
1216
  page.addTextBox({
1153
1217
  frame: action.frame,
1154
1218
  text: action.text
1155
1219
  });
1156
1220
  });
1157
1221
  }
1222
+ const doc = presentationDocument(state);
1223
+ if (doc === void 0) return wrongDocument(state, "a pptx, odp, ppt or odg document");
1158
1224
  const slide = doc.editor.slides()[action.containerIndex];
1159
1225
  if (slide === void 0) return withStatus(state, "warning", `There is no slide at index ${action.containerIndex}`);
1160
1226
  return mutate(state, doc, () => {
@@ -1186,10 +1252,10 @@ function appReducer(state, action) {
1186
1252
  slide.addImage(image);
1187
1253
  });
1188
1254
  }
1189
- case "SET_SHAPE_TEXT": return withShape(state, action.containerIndex, action.shapeIndex, (shape) => {
1255
+ case "SET_SHAPE_TEXT": return withWideShape(state, action.containerIndex, action.shapeIndex, (shape) => {
1190
1256
  shape.text = action.text;
1191
1257
  });
1192
- case "SET_SHAPE_FRAME": return withShape(state, action.containerIndex, action.shapeIndex, (shape) => {
1258
+ case "SET_SHAPE_FRAME": return withWideShape(state, action.containerIndex, action.shapeIndex, (shape) => {
1193
1259
  shape.frame = action.frame;
1194
1260
  });
1195
1261
  case "SET_SHAPE_ROTATION": return withShape(state, action.containerIndex, action.shapeIndex, (shape) => {
@@ -1206,7 +1272,7 @@ function appReducer(state, action) {
1206
1272
  }
1207
1273
  case "ADD_SHEET": {
1208
1274
  const doc = spreadsheetDocument(state);
1209
- if (doc === void 0) return wrongDocument(state, "an ods document");
1275
+ if (doc === void 0) return wrongDocument(state, "an ods or xls document");
1210
1276
  return mutate(state, doc, () => {
1211
1277
  doc.editor.addSheet(action.name);
1212
1278
  });
@@ -1214,27 +1280,37 @@ function appReducer(state, action) {
1214
1280
  case "SET_CELL_VALUE": return withSheet(state, action.sheetIndex, (sheet) => {
1215
1281
  sheet.cell(action.row, action.column).value = action.value;
1216
1282
  });
1217
- case "SET_CELL_FORMULA": return withSheet(state, action.sheetIndex, (sheet) => {
1218
- sheet.cell(action.row, action.column).formula = action.formula;
1219
- });
1220
- case "ADD_SHEET_IMAGE": return withSheet(state, action.sheetIndex, (sheet) => {
1221
- sheet.addImage({
1222
- kind: "image",
1223
- format: action.format,
1224
- base64: bytesToBase64(action.bytes),
1225
- widthPt: action.widthPt,
1226
- heightPt: action.heightPt,
1227
- altText: action.altText,
1228
- anchorRow: action.anchorRow,
1229
- anchorColumn: action.anchorColumn,
1230
- offsetXPt: action.offsetXPt,
1231
- offsetYPt: action.offsetYPt
1283
+ case "SET_CELL_FORMULA": {
1284
+ const doc = odsDocument$1(state);
1285
+ if (doc === void 0) return wrongDocument(state, "an ods document");
1286
+ const sheet = odsSheetAt(doc, action.sheetIndex);
1287
+ if (sheet === void 0) return withStatus(state, "warning", `There is no sheet at index ${action.sheetIndex}`);
1288
+ return mutate(state, doc, () => {
1289
+ sheet.cell(action.row, action.column).formula = action.formula;
1232
1290
  });
1233
- });
1291
+ }
1292
+ case "ADD_SHEET_IMAGE": {
1293
+ const ods = odsDocument$1(state);
1294
+ if (ods === void 0) return wrongDocument(state, "an ods document");
1295
+ return withOdsSheet(state, ods, action.sheetIndex, (sheet) => {
1296
+ sheet.addImage({
1297
+ kind: "image",
1298
+ format: action.format,
1299
+ base64: bytesToBase64(action.bytes),
1300
+ widthPt: action.widthPt,
1301
+ heightPt: action.heightPt,
1302
+ altText: action.altText,
1303
+ anchorRow: action.anchorRow,
1304
+ anchorColumn: action.anchorColumn,
1305
+ offsetXPt: action.offsetXPt,
1306
+ offsetYPt: action.offsetYPt
1307
+ });
1308
+ });
1309
+ }
1234
1310
  case "MERGE_CELLS": {
1235
- const doc = spreadsheetDocument(state);
1311
+ const doc = odsDocument$1(state);
1236
1312
  if (doc === void 0) return wrongDocument(state, "an ods document");
1237
- const sheet = sheetAt(doc, action.sheetIndex);
1313
+ const sheet = odsSheetAt(doc, action.sheetIndex);
1238
1314
  if (sheet === void 0) return withStatus(state, "warning", `There is no sheet at index ${action.sheetIndex}`);
1239
1315
  return mutateGuarded(state, doc, () => {
1240
1316
  sheet.mergeCells(action.startRow, action.startColumn, action.rowSpan, action.colSpan);
@@ -2449,7 +2525,7 @@ function supportsRunStyleExtras(run) {
2449
2525
  }
2450
2526
  function paragraphFamilyDocument(openDocument) {
2451
2527
  if (openDocument === void 0) return;
2452
- return openDocument.format === "docx" || openDocument.format === "odt" || openDocument.format === "markdown" ? openDocument : void 0;
2528
+ return openDocument.format === "docx" || openDocument.format === "odt" || openDocument.format === "markdown" || openDocument.format === "doc" ? openDocument : void 0;
2453
2529
  }
2454
2530
  function liveParagraphAt(doc, blockIndex) {
2455
2531
  return doc.editor.paragraphs()[blockIndex];
@@ -5147,8 +5223,15 @@ function buildPptxSlideFamilyAdapter(editor) {
5147
5223
  function buildOdpSlideFamilyAdapter(editor) {
5148
5224
  return buildSlideFamilyAdapter("odp", editor);
5149
5225
  }
5226
+ function buildPptSlideFamilyAdapter(editor) {
5227
+ return buildSlideFamilyAdapter("ppt", editor);
5228
+ }
5150
5229
  function assertPresentationDocument(doc) {
5151
- if (doc === void 0 || doc.format !== "pptx" && doc.format !== "odp") throw new Error("Expected an open pptx or odp document here; the screen router in app.tsx should only reach a slide-family screen for one of those formats.");
5230
+ if (doc === void 0 || doc.format !== "pptx" && doc.format !== "odp" && doc.format !== "ppt") throw new Error("Expected an open pptx, odp or ppt document here; the screen router in app.tsx should only reach a slide-family screen for one of those formats.");
5231
+ return doc;
5232
+ }
5233
+ function assertRichPresentationDocument(doc) {
5234
+ if (doc === void 0 || doc.format !== "pptx" && doc.format !== "odp") throw new Error("Expected an open pptx or odp document here; the screen router in app.tsx should only reach this rich slide-family screen for one of those formats -- a ppt document routes to its own narrower detail screen.");
5152
5235
  return doc;
5153
5236
  }
5154
5237
  const DEFAULT_FRAME_MARGIN_FRACTION = .1;
@@ -5231,8 +5314,9 @@ function SlideFamilySlideList(props) {
5231
5314
  /* @__PURE__ */ jsxs(Text, {
5232
5315
  bold: true,
5233
5316
  children: [
5234
- props.adapter.formatLabel === "pptx" ? "PowerPoint" : "Impress",
5235
- " slides (",
5317
+ props.adapter.formatLabel === "odp" ? "Impress" : props.adapter.formatLabel === "ppt" ? "PowerPoint 97-2003" : "PowerPoint",
5318
+ " ",
5319
+ "slides (",
5236
5320
  rows.length,
5237
5321
  ")"
5238
5322
  ]
@@ -5376,7 +5460,7 @@ function ShapeEditorScreen(props) {
5376
5460
  const state = useAppState();
5377
5461
  const dispatch = useAppDispatch();
5378
5462
  const overlayOpen = anyOverlayOpen(state);
5379
- const doc = assertPresentationDocument(state.openDocument);
5463
+ const doc = assertRichPresentationDocument(state.openDocument);
5380
5464
  const { slideIndex, shapeIndex } = props.screen;
5381
5465
  const shape = doc.editor.slides()[slideIndex]?.shapes()[shapeIndex];
5382
5466
  const [editingField, setEditingField] = useState(void 0);
@@ -5718,7 +5802,7 @@ function SlideDetailScreen(props) {
5718
5802
  const state = useAppState();
5719
5803
  const dispatch = useAppDispatch();
5720
5804
  const overlayOpen = anyOverlayOpen(state);
5721
- const doc = assertPresentationDocument(state.openDocument);
5805
+ const doc = assertRichPresentationDocument(state.openDocument);
5722
5806
  const { slideIndex } = props.screen;
5723
5807
  const slide = doc.editor.slides()[slideIndex];
5724
5808
  const shapes = slide === void 0 ? [] : slide.shapes();
@@ -6028,7 +6112,7 @@ const CELL_WIDTH$1 = 16;
6028
6112
  function SlideTableDetailScreen(props) {
6029
6113
  const state = useAppState();
6030
6114
  const dispatch = useAppDispatch();
6031
- const doc = assertPresentationDocument(state.openDocument);
6115
+ const doc = assertRichPresentationDocument(state.openDocument);
6032
6116
  const { slideIndex, tableIndex } = props.screen;
6033
6117
  const [cursor, setCursor] = useState({
6034
6118
  row: 0,
@@ -6240,6 +6324,29 @@ function OdpSlideListScreen() {
6240
6324
  return /* @__PURE__ */ jsx(SlideFamilySlideList, { adapter: buildOdpSlideFamilyAdapter(doc.editor) });
6241
6325
  }
6242
6326
  //#endregion
6327
+ //#region src/tui/screens/editors/doc/index.tsx
6328
+ function DocBodyListScreen() {
6329
+ const state = useAppState();
6330
+ const dispatch = useAppDispatch();
6331
+ const doc = state.openDocument;
6332
+ if (doc?.format !== "doc") return /* @__PURE__ */ jsxs(Text, {
6333
+ color: "red",
6334
+ children: [
6335
+ "DocBodyListScreen requires an open doc document, found",
6336
+ " ",
6337
+ doc === void 0 ? "no open document" : doc.format,
6338
+ "."
6339
+ ]
6340
+ });
6341
+ const adapter = createParagraphFamilyAdapter({
6342
+ formatLabel: "doc",
6343
+ paragraphs: () => doc.editor.paragraphs(),
6344
+ tables: () => doc.editor.tables(),
6345
+ dispatch
6346
+ });
6347
+ return /* @__PURE__ */ jsx(ParagraphFamilyBodyList, { adapter });
6348
+ }
6349
+ //#endregion
6243
6350
  //#region src/tui/screens/editors/ods/shared.ts
6244
6351
  function odsDocument(state) {
6245
6352
  const doc = state.openDocument;
@@ -7169,6 +7276,438 @@ function OdsSpreadsheetGridScreen() {
7169
7276
  });
7170
7277
  }
7171
7278
  //#endregion
7279
+ //#region src/tui/screens/editors/ppt/index.tsx
7280
+ function pptDocument(state) {
7281
+ const doc = state.openDocument;
7282
+ if (doc?.format !== "ppt") throw new Error("A ppt screen rendered without an open ppt document; check the screen router in app.tsx.");
7283
+ return doc;
7284
+ }
7285
+ function PptSlideListScreen() {
7286
+ const doc = pptDocument(useAppState());
7287
+ return /* @__PURE__ */ jsx(SlideFamilySlideList, { adapter: buildPptSlideFamilyAdapter(doc.editor) });
7288
+ }
7289
+ const TEXTBOX_FIELDS = [
7290
+ {
7291
+ key: "xPt",
7292
+ label: "x (pt)",
7293
+ defaultValue: "40"
7294
+ },
7295
+ {
7296
+ key: "yPt",
7297
+ label: "y (pt)",
7298
+ defaultValue: "30"
7299
+ },
7300
+ {
7301
+ key: "widthPt",
7302
+ label: "width (pt)",
7303
+ defaultValue: "640"
7304
+ },
7305
+ {
7306
+ key: "heightPt",
7307
+ label: "height (pt)",
7308
+ defaultValue: "80"
7309
+ },
7310
+ {
7311
+ key: "text",
7312
+ label: "text",
7313
+ defaultValue: ""
7314
+ }
7315
+ ];
7316
+ function PptSlideDetailScreen(props) {
7317
+ const state = useAppState();
7318
+ const dispatch = useAppDispatch();
7319
+ const doc = pptDocument(state);
7320
+ const overlayOpen = anyOverlayOpen(state);
7321
+ const slide = doc.editor.slides()[props.slideIndex];
7322
+ const [draft, setDraft] = useState(void 0);
7323
+ const [draftShape, setDraftShape] = useState(void 0);
7324
+ const [addingTextbox, setAddingTextbox] = useState(false);
7325
+ const shapes = slide?.shapes() ?? [];
7326
+ const rows = [...shapes.map((shape, index) => {
7327
+ const frame = shape.frame;
7328
+ return {
7329
+ kind: "shape",
7330
+ index,
7331
+ label: `${`${frame.xPt.toFixed(0)},${frame.yPt.toFixed(0)} ${frame.widthPt.toFixed(0)}x${frame.heightPt.toFixed(0)}pt`} ${shape.text === "" ? "(empty shape)" : shape.text}`
7332
+ };
7333
+ }), {
7334
+ kind: "notes",
7335
+ index: shapes.length,
7336
+ label: slide === void 0 || slide.notes === "" ? "Notes: (none)" : `Notes: ${slide.notes}`
7337
+ }];
7338
+ const { selectedIndex } = useNavigationInput({
7339
+ itemCount: rows.length,
7340
+ isActive: !overlayOpen && draft === void 0 && !addingTextbox,
7341
+ onBack: () => {
7342
+ dispatch({ type: "POP_SCREEN" });
7343
+ },
7344
+ onSelect: (index) => {
7345
+ const row = rows[index];
7346
+ if (row === void 0) return;
7347
+ if (row.kind === "notes") {
7348
+ dispatch({
7349
+ type: "PUSH_SCREEN",
7350
+ screen: {
7351
+ kind: "notesEditor",
7352
+ slideIndex: props.slideIndex
7353
+ }
7354
+ });
7355
+ return;
7356
+ }
7357
+ setDraft(shapes[row.index]?.text ?? "");
7358
+ setDraftShape(row.index);
7359
+ },
7360
+ onAppend: () => {
7361
+ setAddingTextbox(true);
7362
+ }
7363
+ });
7364
+ useInput((input) => {
7365
+ if (input === "n" && slide !== void 0) dispatch({
7366
+ type: "PUSH_SCREEN",
7367
+ screen: {
7368
+ kind: "notesEditor",
7369
+ slideIndex: props.slideIndex
7370
+ }
7371
+ });
7372
+ }, { isActive: !overlayOpen && draft === void 0 && !addingTextbox });
7373
+ if (slide === void 0) return /* @__PURE__ */ jsxs(Text, {
7374
+ color: "red",
7375
+ children: [
7376
+ "There is no slide at index ",
7377
+ props.slideIndex,
7378
+ " in this presentation."
7379
+ ]
7380
+ });
7381
+ if (draft !== void 0 && draftShape !== void 0) return /* @__PURE__ */ jsxs(Box, {
7382
+ flexDirection: "column",
7383
+ children: [
7384
+ /* @__PURE__ */ jsxs(Text, {
7385
+ bold: true,
7386
+ children: [
7387
+ "Shape ",
7388
+ draftShape,
7389
+ " text"
7390
+ ]
7391
+ }),
7392
+ /* @__PURE__ */ jsx(TextField, {
7393
+ value: draft,
7394
+ isFocused: !overlayOpen,
7395
+ placeholder: "Shape text",
7396
+ onChange: setDraft,
7397
+ onSubmit: (value) => {
7398
+ dispatch({
7399
+ type: "SET_SHAPE_TEXT",
7400
+ containerIndex: props.slideIndex,
7401
+ shapeIndex: draftShape,
7402
+ text: value
7403
+ });
7404
+ setDraft(void 0);
7405
+ setDraftShape(void 0);
7406
+ },
7407
+ onCancel: () => {
7408
+ setDraft(void 0);
7409
+ setDraftShape(void 0);
7410
+ }
7411
+ }),
7412
+ /* @__PURE__ */ jsx(Text, {
7413
+ dimColor: true,
7414
+ children: "Enter to save, Esc to cancel"
7415
+ })
7416
+ ]
7417
+ });
7418
+ if (addingTextbox) return /* @__PURE__ */ jsx(FieldWizard, {
7419
+ fields: TEXTBOX_FIELDS,
7420
+ onCancel: () => {
7421
+ setAddingTextbox(false);
7422
+ },
7423
+ onComplete: (values) => {
7424
+ dispatch({
7425
+ type: "ADD_TEXTBOX",
7426
+ containerIndex: props.slideIndex,
7427
+ frame: {
7428
+ xPt: parseNumberField(requireFieldValue(values, "xPt"), 40),
7429
+ yPt: parseNumberField(requireFieldValue(values, "yPt"), 30),
7430
+ widthPt: parseNumberField(requireFieldValue(values, "widthPt"), 640),
7431
+ heightPt: parseNumberField(requireFieldValue(values, "heightPt"), 80)
7432
+ },
7433
+ text: requireFieldValue(values, "text")
7434
+ });
7435
+ setAddingTextbox(false);
7436
+ }
7437
+ });
7438
+ return /* @__PURE__ */ jsxs(Box, {
7439
+ flexDirection: "column",
7440
+ children: [
7441
+ /* @__PURE__ */ jsxs(Text, {
7442
+ bold: true,
7443
+ children: ["Slide ", props.slideIndex + 1]
7444
+ }),
7445
+ rows.map((row, index) => /* @__PURE__ */ jsx(Text, {
7446
+ color: index === selectedIndex ? "cyan" : void 0,
7447
+ inverse: index === selectedIndex,
7448
+ children: row.kind === "notes" ? row.label : ` ${row.label}`
7449
+ }, `${row.kind}:${row.index}`)),
7450
+ /* @__PURE__ */ jsx(Text, {
7451
+ dimColor: true,
7452
+ children: "Enter: edit text / notes n: notes a: add text box Esc: back"
7453
+ })
7454
+ ]
7455
+ });
7456
+ }
7457
+ //#endregion
7458
+ //#region src/tui/screens/editors/xls/index.tsx
7459
+ function xlsDocument(state) {
7460
+ const doc = state.openDocument;
7461
+ if (doc?.format !== "xls") throw new Error("An xls screen rendered without an open xls document; check the screen router in app.tsx.");
7462
+ return doc;
7463
+ }
7464
+ function XlsSheetListScreen() {
7465
+ const state = useAppState();
7466
+ const dispatch = useAppDispatch();
7467
+ const doc = xlsDocument(state);
7468
+ const [draftName, setDraftName] = useState(void 0);
7469
+ const isAdding = draftName !== void 0;
7470
+ const isActive = !anyOverlayOpen(state) && !isAdding;
7471
+ const query = state.searchQuery.trim().toLowerCase();
7472
+ const rows = doc.editor.sheets().map((sheet, index) => ({
7473
+ index,
7474
+ name: sheet.name
7475
+ })).filter((row) => query.length === 0 || row.name.toLowerCase().includes(query));
7476
+ const commitAdd = (name) => {
7477
+ const trimmed = name.trim();
7478
+ if (trimmed.length === 0) {
7479
+ dispatch({
7480
+ type: "SET_STATUS",
7481
+ severity: "warning",
7482
+ text: "A sheet needs a name"
7483
+ });
7484
+ return;
7485
+ }
7486
+ dispatch({
7487
+ type: "ADD_SHEET",
7488
+ name: trimmed
7489
+ });
7490
+ setDraftName(void 0);
7491
+ };
7492
+ const { selectedIndex } = useNavigationInput({
7493
+ itemCount: rows.length,
7494
+ onSelect: (index) => {
7495
+ const row = rows[index];
7496
+ if (row === void 0) return;
7497
+ dispatch({
7498
+ type: "PUSH_SCREEN",
7499
+ screen: {
7500
+ kind: "spreadsheetGrid",
7501
+ sheetIndex: row.index
7502
+ }
7503
+ });
7504
+ },
7505
+ onBack: () => {
7506
+ dispatch({ type: "POP_SCREEN" });
7507
+ },
7508
+ onAppend: () => {
7509
+ setDraftName("");
7510
+ },
7511
+ isActive
7512
+ });
7513
+ return /* @__PURE__ */ jsxs(Box, {
7514
+ flexDirection: "column",
7515
+ children: [
7516
+ /* @__PURE__ */ jsxs(Text, {
7517
+ bold: true,
7518
+ children: [
7519
+ "Sheets (",
7520
+ rows.length,
7521
+ ")"
7522
+ ]
7523
+ }),
7524
+ /* @__PURE__ */ jsx(ListView, {
7525
+ items: rows,
7526
+ selectedIndex,
7527
+ emptyMessage: "This workbook has no sheets yet -- press 'a' to add one.",
7528
+ renderItem: (row, isSelected) => /* @__PURE__ */ jsx(Text, {
7529
+ color: isSelected ? "cyan" : void 0,
7530
+ inverse: isSelected,
7531
+ children: row.name
7532
+ })
7533
+ }),
7534
+ draftName === void 0 ? /* @__PURE__ */ jsx(Text, {
7535
+ dimColor: true,
7536
+ children: "Enter to open, a to add a sheet, Esc to go back"
7537
+ }) : /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, {
7538
+ color: "cyan",
7539
+ children: "New sheet name: "
7540
+ }), /* @__PURE__ */ jsx(TextField, {
7541
+ value: draftName,
7542
+ isFocused: !anyOverlayOpen(state),
7543
+ placeholder: "Sheet name",
7544
+ onChange: setDraftName,
7545
+ onSubmit: commitAdd,
7546
+ onCancel: () => {
7547
+ setDraftName(void 0);
7548
+ }
7549
+ })] })
7550
+ ]
7551
+ });
7552
+ }
7553
+ function columnLetters(column) {
7554
+ let value = column;
7555
+ let letters = "";
7556
+ do {
7557
+ letters = String.fromCharCode(value % 26 + 65) + letters;
7558
+ value = Math.floor(value / 26) - 1;
7559
+ } while (value >= 0);
7560
+ return letters;
7561
+ }
7562
+ const GRID_ROWS = 10;
7563
+ const GRID_COLUMNS = 6;
7564
+ function XlsSpreadsheetGridScreen(props) {
7565
+ const state = useAppState();
7566
+ const dispatch = useAppDispatch();
7567
+ const doc = xlsDocument(state);
7568
+ const overlayOpen = anyOverlayOpen(state);
7569
+ const [compact, setCompact] = useState(false);
7570
+ const [cursor, setCursor] = useState({
7571
+ row: 0,
7572
+ column: 0
7573
+ });
7574
+ const [editSession, setEditSession] = useState(void 0);
7575
+ const editing = editSession !== void 0;
7576
+ const sheet = doc.editor.sheets()[props.sheetIndex];
7577
+ useInput((input, key) => {
7578
+ if (sheet === void 0) return;
7579
+ const move = (dRow, dColumn) => {
7580
+ setCursor((current) => ({
7581
+ row: Math.max(0, current.row + dRow),
7582
+ column: Math.max(0, current.column + dColumn)
7583
+ }));
7584
+ };
7585
+ if (key.escape) {
7586
+ if (editing) {
7587
+ setEditSession(void 0);
7588
+ return;
7589
+ }
7590
+ dispatch({ type: "POP_SCREEN" });
7591
+ return;
7592
+ }
7593
+ if (key.upArrow || input === "k") {
7594
+ move(-1, 0);
7595
+ return;
7596
+ }
7597
+ if (key.downArrow || input === "j") {
7598
+ move(1, 0);
7599
+ return;
7600
+ }
7601
+ if (key.leftArrow || input === "h") {
7602
+ move(0, -1);
7603
+ return;
7604
+ }
7605
+ if (key.rightArrow || input === "l") {
7606
+ move(0, 1);
7607
+ return;
7608
+ }
7609
+ if (key.return) {
7610
+ const existing = sheet.cells().find((cell) => cell.row === cursor.row && cell.column === cursor.column);
7611
+ setEditSession({
7612
+ seedText: existing?.displayText ?? "",
7613
+ seedKind: existing?.value.kind ?? "empty"
7614
+ });
7615
+ return;
7616
+ }
7617
+ if (input === "t") setCompact((value) => !value);
7618
+ }, { isActive: !overlayOpen });
7619
+ if (sheet === void 0) return /* @__PURE__ */ jsxs(Text, {
7620
+ color: "red",
7621
+ children: [
7622
+ "There is no sheet at index ",
7623
+ props.sheetIndex,
7624
+ " in this workbook."
7625
+ ]
7626
+ });
7627
+ const cells = new Map(sheet.cells().map((cell) => [`${cell.row}:${cell.column}`, cell]));
7628
+ const cursorAddress = `${columnLetters(cursor.column)}${cursor.row + 1}`;
7629
+ const cursorCell = cells.get(`${cursor.row}:${cursor.column}`);
7630
+ if (compact) {
7631
+ const entries = sheet.cells();
7632
+ return /* @__PURE__ */ jsxs(Box, {
7633
+ flexDirection: "column",
7634
+ children: [
7635
+ /* @__PURE__ */ jsxs(Text, {
7636
+ bold: true,
7637
+ children: [
7638
+ sheet.name,
7639
+ " -- non-empty cells (",
7640
+ entries.length,
7641
+ ") -- 't' back to grid"
7642
+ ]
7643
+ }),
7644
+ entries.length === 0 ? /* @__PURE__ */ jsx(Text, {
7645
+ dimColor: true,
7646
+ children: "No cells carry a value yet."
7647
+ }) : entries.slice(0, 200).map((cell) => /* @__PURE__ */ jsxs(Text, { children: [
7648
+ columnLetters(cell.column),
7649
+ cell.row + 1,
7650
+ ":",
7651
+ " ",
7652
+ cell.displayText === "" ? "(empty)" : cell.displayText
7653
+ ] }, `${cell.row}:${cell.column}`)),
7654
+ /* @__PURE__ */ jsx(Text, {
7655
+ dimColor: true,
7656
+ children: "t: full grid Esc: back"
7657
+ })
7658
+ ]
7659
+ });
7660
+ }
7661
+ const windowStartRow = Math.min(Math.max(0, cursor.row - 9), Math.max(0, cursor.row));
7662
+ const windowStartColumn = Math.min(Math.max(0, cursor.column - 5), Math.max(0, cursor.column));
7663
+ return /* @__PURE__ */ jsxs(Box, {
7664
+ flexDirection: "column",
7665
+ children: [
7666
+ /* @__PURE__ */ jsxs(Text, {
7667
+ bold: true,
7668
+ children: [
7669
+ sheet.name,
7670
+ " (",
7671
+ cells.size,
7672
+ " cells)"
7673
+ ]
7674
+ }),
7675
+ Array.from({ length: GRID_ROWS }, (_, r) => windowStartRow + r).map((row) => /* @__PURE__ */ jsx(Text, { children: Array.from({ length: GRID_COLUMNS }, (_, c) => windowStartColumn + c).map((column) => {
7676
+ const cell = cells.get(`${row}:${column}`);
7677
+ const isCursor = row === cursor.row && column === cursor.column;
7678
+ const padded = (cell?.displayText ?? "").slice(0, 12).padEnd(12, " ");
7679
+ return isCursor ? `[${padded.slice(0, 12)}]` : ` ${padded} `;
7680
+ }).join("") }, row)),
7681
+ editSession === void 0 ? /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsxs(Text, {
7682
+ color: "cyan",
7683
+ children: [cursorAddress, " "]
7684
+ }), /* @__PURE__ */ jsx(Text, { children: cursorCell === void 0 ? "(empty)" : cursorCell.displayText })] }) : /* @__PURE__ */ jsx(OdsCellEditor, {
7685
+ address: cursorAddress,
7686
+ initialText: editSession.seedText,
7687
+ initialKind: editSession.seedKind,
7688
+ isActive: !overlayOpen,
7689
+ onCommit: (value) => {
7690
+ dispatch({
7691
+ type: "SET_CELL_VALUE",
7692
+ sheetIndex: props.sheetIndex,
7693
+ row: cursor.row,
7694
+ column: cursor.column,
7695
+ value
7696
+ });
7697
+ setEditSession(void 0);
7698
+ },
7699
+ onCancel: () => {
7700
+ setEditSession(void 0);
7701
+ }
7702
+ }),
7703
+ /* @__PURE__ */ jsx(Text, {
7704
+ dimColor: true,
7705
+ children: "hjkl/arrows: move Enter: edit t: non-empty list Esc: back"
7706
+ })
7707
+ ]
7708
+ });
7709
+ }
7710
+ //#endregion
7172
7711
  //#region src/tui/screens/editors/odt/list-editor.tsx
7173
7712
  function ListEditorScreen() {
7174
7713
  const state = useAppState();
@@ -7349,7 +7888,7 @@ function OdtBodyListScreen() {
7349
7888
  //#endregion
7350
7889
  //#region src/tui/screens/editors/pdf/shared.ts
7351
7890
  function requirePdfDocument(openDocument) {
7352
- if (openDocument?.format !== "pdf" && openDocument?.format !== "xlsx" && openDocument?.format !== "csv" && openDocument?.format !== "svg" && openDocument?.format !== "rtf" && openDocument?.format !== "wpd" && openDocument?.format !== "doc" && openDocument?.format !== "xls" && openDocument?.format !== "ppt") throw new Error("A PDF inspection screen rendered without an open PDF, xlsx, csv, svg, rtf, wpd, doc, xls, or ppt document; the app router only reaches this screen group from pdfPageList, which is only ever the root screen of one of those nine formats.");
7891
+ if (openDocument?.format !== "pdf" && openDocument?.format !== "xlsx" && openDocument?.format !== "csv" && openDocument?.format !== "svg" && openDocument?.format !== "rtf" && openDocument?.format !== "wpd" && openDocument?.format !== "epub") throw new Error("A PDF inspection screen rendered without an open PDF, xlsx, csv, svg, rtf, wpd, or epub document; the app router only reaches this screen group from pdfPageList, which is only ever the root screen of one of those formats.");
7353
7892
  return openDocument;
7354
7893
  }
7355
7894
  function isEditablePdfDocument(doc) {
@@ -9013,7 +9552,7 @@ function LauncherScreen() {
9013
9552
  }),
9014
9553
  /* @__PURE__ */ jsx(Text, {
9015
9554
  dimColor: true,
9016
- children: "A terminal editor for docx, pptx, odt, odp, ods, odg, markdown, odb and pdf -- xlsx, csv, svg and rtf open as read-only PDF previews."
9555
+ children: "A terminal editor for docx, pptx, odt, odp, ods, odg, doc, xls, ppt, markdown, odb and pdf -- xlsx, csv, svg, rtf, wpd and epub open as read-only PDF previews."
9017
9556
  }),
9018
9557
  /* @__PURE__ */ jsx(Text, { children: " " }),
9019
9558
  /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
@@ -9062,6 +9601,18 @@ const CREATABLE_FORMATS = [
9062
9601
  format: "odg",
9063
9602
  description: "Vector drawing (OpenDocument)"
9064
9603
  },
9604
+ {
9605
+ format: "doc",
9606
+ description: "Word-processing document (Word 97-2003)"
9607
+ },
9608
+ {
9609
+ format: "xls",
9610
+ description: "Spreadsheet workbook (Excel 97-2003)"
9611
+ },
9612
+ {
9613
+ format: "ppt",
9614
+ description: "Presentation slide deck (PowerPoint 97-2003)"
9615
+ },
9065
9616
  {
9066
9617
  format: "markdown",
9067
9618
  description: "Markdown document"
@@ -9181,15 +9732,15 @@ function metadataFor(doc) {
9181
9732
  case "ods": return readOdsContent(doc.editor.toPackage()).metadata;
9182
9733
  case "odg": return readOdgContent(doc.editor.toPackage()).metadata;
9183
9734
  case "markdown": return readMarkdownContent(doc.editor.toMarkdownText()).metadata;
9735
+ case "doc":
9736
+ case "xls":
9737
+ case "ppt": return doc.editor.metadata;
9184
9738
  case "pdf":
9185
9739
  case "xlsx":
9186
9740
  case "csv":
9187
9741
  case "svg":
9188
9742
  case "rtf":
9189
9743
  case "wpd":
9190
- case "doc":
9191
- case "xls":
9192
- case "ppt":
9193
9744
  case "epub": return doc.layout.metadata;
9194
9745
  case "odb": throw new Error("A .odb database has no document-level metadata -- it is a table/form/report container, not a single document with its own title/author/etc.");
9195
9746
  }
@@ -9349,6 +9900,7 @@ function ScreenBody({ screen }) {
9349
9900
  case "newDocumentPicker": return /* @__PURE__ */ jsx(NewDocumentPickerScreen, {});
9350
9901
  case "bodyList":
9351
9902
  if (format === "odt") return /* @__PURE__ */ jsx(OdtBodyListScreen, {});
9903
+ if (format === "doc") return /* @__PURE__ */ jsx(DocBodyListScreen, {});
9352
9904
  return format === "markdown" ? /* @__PURE__ */ jsx(MarkdownBodyListScreen, {}) : /* @__PURE__ */ jsx(DocxBodyListScreen, {});
9353
9905
  case "docxExtras": return /* @__PURE__ */ jsx(DocxExtrasScreen, {});
9354
9906
  case "paragraphDetail": return /* @__PURE__ */ jsx(ParagraphDetailScreen, {});
@@ -9356,13 +9908,15 @@ function ScreenBody({ screen }) {
9356
9908
  case "tableView": return /* @__PURE__ */ jsx(TableViewScreen, {});
9357
9909
  case "tableCellDetail": return /* @__PURE__ */ jsx(TableCellDetailScreen, {});
9358
9910
  case "listEditor": return /* @__PURE__ */ jsx(ListEditorScreen, {});
9359
- case "slideList": return format === "odp" ? /* @__PURE__ */ jsx(OdpSlideListScreen, {}) : /* @__PURE__ */ jsx(PptxSlideListScreen, {});
9360
- case "slideDetail": return /* @__PURE__ */ jsx(SlideDetailScreen, { screen });
9911
+ case "slideList":
9912
+ if (format === "ppt") return /* @__PURE__ */ jsx(PptSlideListScreen, {});
9913
+ return format === "odp" ? /* @__PURE__ */ jsx(OdpSlideListScreen, {}) : /* @__PURE__ */ jsx(PptxSlideListScreen, {});
9914
+ case "slideDetail": return format === "ppt" ? /* @__PURE__ */ jsx(PptSlideDetailScreen, { slideIndex: screen.slideIndex }) : /* @__PURE__ */ jsx(SlideDetailScreen, { screen });
9361
9915
  case "shapeEditor": return /* @__PURE__ */ jsx(ShapeEditorScreen, { screen });
9362
9916
  case "slideTableDetail": return /* @__PURE__ */ jsx(SlideTableDetailScreen, { screen });
9363
9917
  case "notesEditor": return /* @__PURE__ */ jsx(NotesEditorScreen, { screen });
9364
- case "sheetList": return /* @__PURE__ */ jsx(OdsSheetListScreen, {});
9365
- case "spreadsheetGrid": return /* @__PURE__ */ jsx(OdsSpreadsheetGridScreen, {});
9918
+ case "sheetList": return format === "xls" ? /* @__PURE__ */ jsx(XlsSheetListScreen, {}) : /* @__PURE__ */ jsx(OdsSheetListScreen, {});
9919
+ case "spreadsheetGrid": return format === "xls" ? /* @__PURE__ */ jsx(XlsSpreadsheetGridScreen, { sheetIndex: screen.sheetIndex }) : /* @__PURE__ */ jsx(OdsSpreadsheetGridScreen, {});
9366
9920
  case "cellDetail": return /* @__PURE__ */ jsx(Text, { children: "cellDetail is rendered inline by spreadsheetGrid and is never pushed as its own screen." });
9367
9921
  case "printSettingsEditor": return /* @__PURE__ */ jsx(OdsPrintSettingsEditorScreen, {});
9368
9922
  case "pageList": return /* @__PURE__ */ jsx(OdgPageListScreen, {});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "document-cli",
3
- "version": "5.6.45",
3
+ "version": "5.7.0",
4
4
  "description": "CLI and interactive Ink TUI for documents.js: every docx/pptx/odt/odp/ods/odg/odf/pdf/odm/odb/xlsx/csv/svg/markdown/rtf conversion, bridge, and editor as a scriptable command or a terminal app.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -94,7 +94,7 @@
94
94
  "commander": "15.0.0",
95
95
  "document-outline.js": "3.6.5",
96
96
  "document-schema.js": "7.5.1",
97
- "documents.js": "7.13.0",
97
+ "documents.js": "7.15.0",
98
98
  "ink": "7.1.1",
99
99
  "ink-text-input": "6.0.0",
100
100
  "react": "19.2.8"