document-cli 5.0.0 → 5.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Joseph Mearman
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -70,7 +70,7 @@ npm i -g document-cli
70
70
  npm i -g doculi
71
71
  ```
72
72
 
73
- Both names install the exact same package and the exact same binary — `package.json`'s `bin` field declares both `document-cli` and `doculi` pointing at the one built entry point unconditionally, so there is no "real" name and an alias; pick whichever you find easier to type. Unlike a sibling's second *npm package name* (`documents.js`'s own `js.documents` — see that package's README, and note the older per-repo pipeline's GitHub Packages republish this pattern used to mirror is no longer running, per [ExaDev/documents.js#732](https://github.com/ExaDev/documents.js/issues/732)), this is one package with two `bin` entries: a second name for the same build, not a second build, and unaffected by that gap.
73
+ Both names install the exact same package and the exact same binary — `package.json`'s `bin` field declares both `document-cli` and `doculi` pointing at the one built entry point unconditionally, so there is no "real" name and an alias; pick whichever you find easier to type. Unlike a sibling's second _npm package name_ (`documents.js`'s own `js.documents` — see that package's README, and note the older per-repo pipeline's GitHub Packages republish this pattern used to mirror is no longer running, per [ExaDev/documents.js#732](https://github.com/ExaDev/documents.js/issues/732)), this is one package with two `bin` entries: a second name for the same build, not a second build, and unaffected by that gap.
74
74
 
75
75
  ## Usage
76
76
 
@@ -116,7 +116,7 @@ document-cli odb-tables customers.odb
116
116
  document-cli odb-to-csv customers.odb --table CUSTOMERS customers.csv
117
117
  ```
118
118
 
119
- **`odb-forms <input>`**, **`odb-reports <input>`** — read a `.odb`'s form and report *structure* rather than its table data. A form or report is a static ODF sub-document inside the package, so neither command consults the embedded database at all: they work on an `.odb` whose connection points at an external server just as well as on an embedded one. `odb-forms` prints each form's own data source (table or saved query) and its field-bound controls, sub-forms nested under their parent with their own separate command; `odb-reports` prints each report's data-source command, its band structure (report/page headers and footers, groups, detail), every `rpt:` formula expression (`field:[AMOUNT]`, `rpt:SUM([AMOUNT])`), and any user-defined report functions. `--json` emits the same structure machine-readably — for a form that is the structure only, with the form's own parsed sub-document dropped:
119
+ **`odb-forms <input>`**, **`odb-reports <input>`** — read a `.odb`'s form and report _structure_ rather than its table data. A form or report is a static ODF sub-document inside the package, so neither command consults the embedded database at all: they work on an `.odb` whose connection points at an external server just as well as on an embedded one. `odb-forms` prints each form's own data source (table or saved query) and its field-bound controls, sub-forms nested under their parent with their own separate command; `odb-reports` prints each report's data-source command, its band structure (report/page headers and footers, groups, detail), every `rpt:` formula expression (`field:[AMOUNT]`, `rpt:SUM([AMOUNT])`), and any user-defined report functions. `--json` emits the same structure machine-readably — for a form that is the structure only, with the form's own parsed sub-document dropped:
120
120
 
121
121
  ```sh
122
122
  document-cli odb-forms sales.odb
@@ -181,17 +181,17 @@ document-cli outline slides.pptx --json
181
181
 
182
182
  The explicit conversions, `convert`, `odm-to-pdf`, `odb-to-xlsx`, `odb-to-csv`, `set-metadata`, and `odb-render-report` — every command that reads one file and writes one — share:
183
183
 
184
- | Flag | Meaning |
185
- |---|---|
186
- | `-o, --out <file>` | Output path; defaults to the input path with the target format's own extension. Conflicts with a positional output argument that names a different path. |
187
- | `--timeout <ms>` | Abort the run after this many milliseconds. |
188
- | `--json` | Emit diagnostics and the result summary as newline-delimited JSON on stderr, instead of human-readable lines. |
189
- | `-q, --quiet` | Suppress diagnostic and summary output (the JSON result-summary line still prints in `--json` mode, so a script consuming NDJSON always gets a terminating record). |
190
- | `--verbose` | Include a full stack trace in the error line when the run fails. |
184
+ | Flag | Meaning |
185
+ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
186
+ | `-o, --out <file>` | Output path; defaults to the input path with the target format's own extension. Conflicts with a positional output argument that names a different path. |
187
+ | `--timeout <ms>` | Abort the run after this many milliseconds. |
188
+ | `--json` | Emit diagnostics and the result summary as newline-delimited JSON on stderr, instead of human-readable lines. |
189
+ | `-q, --quiet` | Suppress diagnostic and summary output (the JSON result-summary line still prints in `--json` mode, so a script consuming NDJSON always gets a terminating record). |
190
+ | `--verbose` | Include a full stack trace in the error line when the run fails. |
191
191
 
192
192
  Three further flags select what a csv or svg edge of a conversion works on, threaded straight into documents.js's own `ConversionOptions`: `--delimiter <char>` (the field delimiter a csv source reads with, or a csv target writes with — default `,`), `--sheet <name>` (the sheet a csv target writes, required when the source document has more than one), and `--page <index>` (the 0-based page an svg target draws, required when the source document has more than one). On the explicit commands they are registered only where the pair can reach the edge in question — `--delimiter` on any pair with a csv edge, `--sheet` on a csv target, `--page` on an svg target — and unconditionally on `convert` and `from-package`, whose target is only known once the output path or `--to` resolves at run time (the same registration reasoning the font flags below document). Leaving `--sheet` or `--page` unanswered on an ambiguous document fails with exit `3`, naming the sheets or page count to pick from:
193
193
 
194
- `--dump-package <file>` is one flag further, registered only on the explicit conversions and `convert` — it writes the intermediate `DocumentTree` (the tree form: content grouped one group per container, the content nodes carrying their own rendered `frames`, plus the `pages` geometry those frames index into and the minted `styles` table at the root) that conversion built to a JSON file, tagged with its own version-pinned `$schema` — the URI *is* the package's version — so `from-package` (above) can read it back in. Every conversion populates one, `odf-to-pdf` included — its dump carries a `formula`-kind content and the one A4 page it renders (the formula's glyphs travel through the PDF writer's own positioned-formula channel rather than as frame-stamped page content); every PDF-bypassing bridge (`ods-to-xlsx`, `docx-to-pptx`, `odg-to-svg`, and every other content-format pair above) populates one too, just with `pages` always absent, since a bridge never runs a layout engine. `odm-to-pdf`/`odb-*`/`set-metadata` don't expose the flag at all, since none of them goes through `DocumentConverter.convert` in the first place. `odb-tables`, `odb-forms`, `odb-reports`, `fonts`, `docx-extras`, `metadata`, `formats`, and `pdf-inspect` each take only their own `--json` (plus `pdf-inspect`'s own `--full`); `odb-query` takes `--sql <text>`/`--query <savedName>` (mutually exclusive) alongside its own `--json`, with none of the shared flags above since it only reads and writes nothing; `from-package` and `set-metadata` each take `--to <format>` alongside the shared flags in this table; `outline` takes the shared flags except `--out` (it prints to stdout and writes no file), plus its own `--from <format>` for a source format extension inference can't resolve — it runs a conversion internally to obtain the package it projects, so its diagnostics and `--timeout` behave like any conversion's; `odb-render-report` takes `--report <name>` and `--to <format>` alongside the shared flags and the font flags below; `tui` takes no flags at all, only an optional positional file.
194
+ `--dump-package <file>` is one flag further, registered only on the explicit conversions and `convert` — it writes the intermediate `DocumentTree` (the tree form: content grouped one group per container, the content nodes carrying their own rendered `frames`, plus the `pages` geometry those frames index into and the minted `styles` table at the root) that conversion built to a JSON file, tagged with its own version-pinned `$schema` — the URI _is_ the package's version — so `from-package` (above) can read it back in. Every conversion populates one, `odf-to-pdf` included — its dump carries a `formula`-kind content and the one A4 page it renders (the formula's glyphs travel through the PDF writer's own positioned-formula channel rather than as frame-stamped page content); every PDF-bypassing bridge (`ods-to-xlsx`, `docx-to-pptx`, `odg-to-svg`, and every other content-format pair above) populates one too, just with `pages` always absent, since a bridge never runs a layout engine. `odm-to-pdf`/`odb-*`/`set-metadata` don't expose the flag at all, since none of them goes through `DocumentConverter.convert` in the first place. `odb-tables`, `odb-forms`, `odb-reports`, `fonts`, `docx-extras`, `metadata`, `formats`, and `pdf-inspect` each take only their own `--json` (plus `pdf-inspect`'s own `--full`); `odb-query` takes `--sql <text>`/`--query <savedName>` (mutually exclusive) alongside its own `--json`, with none of the shared flags above since it only reads and writes nothing; `from-package` and `set-metadata` each take `--to <format>` alongside the shared flags in this table; `outline` takes the shared flags except `--out` (it prints to stdout and writes no file), plus its own `--from <format>` for a source format extension inference can't resolve — it runs a conversion internally to obtain the package it projects, so its diagnostics and `--timeout` behave like any conversion's; `odb-render-report` takes `--report <name>` and `--to <format>` alongside the shared flags and the font flags below; `tui` takes no flags at all, only an optional positional file.
195
195
 
196
196
  ### Real fonts
197
197
 
@@ -204,7 +204,7 @@ document-cli docx-to-pdf report.docx report.pdf \
204
204
  --report-font-substitutions
205
205
  ```
206
206
 
207
- The flag is repeatable, takes a `.ttf`/`.otf` path, and needs **no accompanying family flag**: the family, weight, and slope are read from the font file's own `name` and `OS/2` tables. That is a deliberate choice over the alternative of a parallel `--font-family`/`--font-bold`/`--font-italic` set — three repeatable flags whose values must stay index-aligned with a fourth is a silent-misalignment hazard (pass two font files and one `--font-family` and the second face is mis-declared, with nothing reporting it), and every real font already states all three facts itself. The consequence to know about: a font file can only be supplied *as the family it says it is*. There is no way to say "draw Calibri using this file instead" — for that, the family in the document has to match the family in the font. A file that is not a readable font (a `.woff`, a `.ttc` collection, a mistyped path pointing at something else) fails the run outright, naming the file, rather than being quietly skipped.
207
+ The flag is repeatable, takes a `.ttf`/`.otf` path, and needs **no accompanying family flag**: the family, weight, and slope are read from the font file's own `name` and `OS/2` tables. That is a deliberate choice over the alternative of a parallel `--font-family`/`--font-bold`/`--font-italic` set — three repeatable flags whose values must stay index-aligned with a fourth is a silent-misalignment hazard (pass two font files and one `--font-family` and the second face is mis-declared, with nothing reporting it), and every real font already states all three facts itself. The consequence to know about: a font file can only be supplied _as the family it says it is_. There is no way to say "draw Calibri using this file instead" — for that, the family in the document has to match the family in the font. A file that is not a readable font (a `.woff`, a `.ttc` collection, a mistyped path pointing at something else) fails the run outright, naming the file, rather than being quietly skipped.
208
208
 
209
209
  `--report-font-substitutions` prints each face that resolved to something other than what the document asked for, as it happens, with its structured fields intact (`--json` makes it one more NDJSON record: `{"type":"font-substitution","requestedFamily":"Calibri",…}`). Without it, the same fallbacks are still reported — the `font/substituted` diagnostic lines every conversion already emits — just as rendered messages after the fact rather than structured events as they occur.
210
210
 
@@ -214,14 +214,14 @@ Diagnostics and the summary line always go to stderr; stdout is reserved for the
214
214
 
215
215
  ### Exit codes
216
216
 
217
- | Code | Meaning |
218
- |---|---|
219
- | `0` | Success. |
220
- | `1` | The input was unusable — a malformed or encrypted PDF, or any other conversion failure not covered by the codes below. |
221
- | `2` | A usage error — bad flags, conflicting output destinations, an unrecognised format, or (for a bare/`--help`/`--version` invocation) commander's own exit path. |
222
- | `3` | documents.js needs more information to finish, not a different file — an unresolved `.odm` chapter, a `.odb` table that wasn't specified (or wasn't found, or has no embedded engine at all, or uses an unsupported HSQLDB script serialisation), a `.odb` report that wasn't specified when the database declares more than one, a csv target whose source carries more than one sheet (or a `--sheet` that names one it doesn't), or an svg target whose source carries more than one page (or a `--page` that indexes past the last). |
223
- | `124` | The run's own `--timeout` elapsed before it finished. |
224
- | `130` | Interrupted by `SIGINT` (Ctrl+C). |
217
+ | Code | Meaning |
218
+ | ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
219
+ | `0` | Success. |
220
+ | `1` | The input was unusable — a malformed or encrypted PDF, or any other conversion failure not covered by the codes below. |
221
+ | `2` | A usage error — bad flags, conflicting output destinations, an unrecognised format, or (for a bare/`--help`/`--version` invocation) commander's own exit path. |
222
+ | `3` | documents.js needs more information to finish, not a different file — an unresolved `.odm` chapter, a `.odb` table that wasn't specified (or wasn't found, or has no embedded engine at all, or uses an unsupported HSQLDB script serialisation), a `.odb` report that wasn't specified when the database declares more than one, a csv target whose source carries more than one sheet (or a `--sheet` that names one it doesn't), or an svg target whose source carries more than one page (or a `--page` that indexes past the last). |
223
+ | `124` | The run's own `--timeout` elapsed before it finished. |
224
+ | `130` | Interrupted by `SIGINT` (Ctrl+C). |
225
225
 
226
226
  ## The TUI
227
227
 
@@ -231,7 +231,7 @@ It supports the same seven formats documents.js's live-view editors cover — do
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`, or `.svg` opens as a converted PDF preview — documents.js has no spreadsheet or svg editor to hold a live view into, so opening one runs `xlsxToPdf`, `csvToPdf`, or `svgToPdf` 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`, or `.svg` opens as a converted PDF preview — documents.js has no spreadsheet or svg editor to hold a live view into, so opening one runs `xlsxToPdf`, `csvToPdf`, or `svgToPdf` 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.
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
 
@@ -239,24 +239,24 @@ The export-to-PDF screen (`e` from any editor screen) is a two-field form: a des
239
239
 
240
240
  The global bindings below apply everywhere; individual screens (a docx run's own bold/italic toggles, an ods cell's own value-kind picker) add their own on top:
241
241
 
242
- | Keys | Action |
243
- |---|---|
244
- | `↑` / `k` | Move the selection up |
245
- | `↓` / `j` | Move the selection down |
246
- | `Enter` / `→` / `l` | Open or edit the selected item |
247
- | `Esc` / `←` / `h` | Go back to the previous screen |
248
- | `PageUp` / `PageDown` | Scroll a page at a time |
249
- | `Home` / `End` | Jump to the first or last item |
250
- | `a` | Append a new item to the current list |
251
- | `m` | Show the open document's metadata (read-only) |
252
- | `Ctrl+S` | Save the open document |
253
- | `Ctrl+W` | Close the open document |
254
- | `Ctrl+Z` | Undo the last change |
255
- | `q` / `Ctrl+C` | Quit |
256
- | `:` | Open the command palette |
257
- | `/` | Search within the current screen |
258
- | `?` | Show this help |
259
- | `Ctrl+D` | Show the diagnostics panel |
242
+ | Keys | Action |
243
+ | --------------------- | --------------------------------------------- |
244
+ | `↑` / `k` | Move the selection up |
245
+ | `↓` / `j` | Move the selection down |
246
+ | `Enter` / `→` / `l` | Open or edit the selected item |
247
+ | `Esc` / `←` / `h` | Go back to the previous screen |
248
+ | `PageUp` / `PageDown` | Scroll a page at a time |
249
+ | `Home` / `End` | Jump to the first or last item |
250
+ | `a` | Append a new item to the current list |
251
+ | `m` | Show the open document's metadata (read-only) |
252
+ | `Ctrl+S` | Save the open document |
253
+ | `Ctrl+W` | Close the open document |
254
+ | `Ctrl+Z` | Undo the last change |
255
+ | `q` / `Ctrl+C` | Quit |
256
+ | `:` | Open the command palette |
257
+ | `/` | Search within the current screen |
258
+ | `?` | Show this help |
259
+ | `Ctrl+D` | Show the diagnostics panel |
260
260
 
261
261
  ## Architecture
262
262
 
package/dist/cli.js CHANGED
@@ -14,9 +14,13 @@ function combineSignals(a, b) {
14
14
  controller.abort(signal.reason);
15
15
  };
16
16
  if (a.aborted) forward(a);
17
- else a.addEventListener("abort", () => forward(a), { once: true });
17
+ else a.addEventListener("abort", () => {
18
+ forward(a);
19
+ }, { once: true });
18
20
  if (b.aborted) forward(b);
19
- else b.addEventListener("abort", () => forward(b), { once: true });
21
+ else b.addEventListener("abort", () => {
22
+ forward(b);
23
+ }, { once: true });
20
24
  return controller.signal;
21
25
  }
22
26
  function createRuntimeSignal(options) {
@@ -1211,7 +1215,7 @@ function registerSetMetadataCommand(program) {
1211
1215
  }
1212
1216
  //#endregion
1213
1217
  //#region package.json
1214
- var version = "5.0.0";
1218
+ var version = "5.1.0";
1215
1219
  //#endregion
1216
1220
  //#region src/program.ts
1217
1221
  function createProgram() {
@@ -1239,7 +1243,7 @@ function createProgram() {
1239
1243
  //#region src/cli.ts
1240
1244
  async function launchTui(startPath, signal) {
1241
1245
  try {
1242
- const { runTui } = await import("./tui-B85Sb2t2.js");
1246
+ const { runTui } = await import("./tui-D62kBV1i.js");
1243
1247
  await runTui({
1244
1248
  startPath,
1245
1249
  signal
@@ -1258,7 +1262,7 @@ async function main() {
1258
1262
  const args = process.argv.slice(2);
1259
1263
  const [dispatchToken] = args;
1260
1264
  if (dispatchToken === void 0 || dispatchToken === "tui") {
1261
- if (process.stdout.isTTY !== true) {
1265
+ if (!process.stdout.isTTY) {
1262
1266
  if (dispatchToken === "tui") {
1263
1267
  process.stderr.write("the TUI requires an interactive terminal (TTY); stdout is currently redirected\n");
1264
1268
  process.exitCode = 2;
package/dist/index.cjs CHANGED
@@ -69,9 +69,13 @@ function combineSignals(a, b) {
69
69
  controller.abort(signal.reason);
70
70
  };
71
71
  if (a.aborted) forward(a);
72
- else a.addEventListener("abort", () => forward(a), { once: true });
72
+ else a.addEventListener("abort", () => {
73
+ forward(a);
74
+ }, { once: true });
73
75
  if (b.aborted) forward(b);
74
- else b.addEventListener("abort", () => forward(b), { once: true });
76
+ else b.addEventListener("abort", () => {
77
+ forward(b);
78
+ }, { once: true });
75
79
  return controller.signal;
76
80
  }
77
81
  function createRuntimeSignal(options) {
@@ -1549,7 +1553,7 @@ function registerSetMetadataCommand(program) {
1549
1553
  }
1550
1554
  //#endregion
1551
1555
  //#region package.json
1552
- var version = "5.0.0";
1556
+ var version = "5.1.0";
1553
1557
  //#endregion
1554
1558
  //#region src/program.ts
1555
1559
  function createProgram() {
package/dist/index.d.cts CHANGED
@@ -32,6 +32,6 @@ declare const EXIT_USAGE_ERROR = 2;
32
32
  declare const EXIT_NEEDS_INFO = 3;
33
33
  declare const EXIT_TIMEOUT = 124;
34
34
  declare const EXIT_INTERRUPTED = 130;
35
- declare function mapErrorToExit(error: unknown, abortReason: 'interrupt' | 'timeout' | undefined): number;
35
+ declare function mapErrorToExit(error: unknown, abortReason: "interrupt" | "timeout" | undefined): number;
36
36
  //#endregion
37
37
  export { type ConversionCommandOptions, EXIT_INPUT_ERROR, EXIT_INTERRUPTED, EXIT_NEEDS_INFO, EXIT_SUCCESS, EXIT_TIMEOUT, EXIT_USAGE_ERROR, buildConversionAction, createProgram, formatError, formatToExtension, inferFormatFromExtension, isDocumentFormat, mapErrorToExit };
package/dist/index.d.ts CHANGED
@@ -32,6 +32,6 @@ declare const EXIT_USAGE_ERROR = 2;
32
32
  declare const EXIT_NEEDS_INFO = 3;
33
33
  declare const EXIT_TIMEOUT = 124;
34
34
  declare const EXIT_INTERRUPTED = 130;
35
- declare function mapErrorToExit(error: unknown, abortReason: 'interrupt' | 'timeout' | undefined): number;
35
+ declare function mapErrorToExit(error: unknown, abortReason: "interrupt" | "timeout" | undefined): number;
36
36
  //#endregion
37
37
  export { type ConversionCommandOptions, EXIT_INPUT_ERROR, EXIT_INTERRUPTED, EXIT_NEEDS_INFO, EXIT_SUCCESS, EXIT_TIMEOUT, EXIT_USAGE_ERROR, buildConversionAction, createProgram, formatError, formatToExtension, inferFormatFromExtension, isDocumentFormat, mapErrorToExit };
package/dist/index.js CHANGED
@@ -68,9 +68,13 @@ function combineSignals(a, b) {
68
68
  controller.abort(signal.reason);
69
69
  };
70
70
  if (a.aborted) forward(a);
71
- else a.addEventListener("abort", () => forward(a), { once: true });
71
+ else a.addEventListener("abort", () => {
72
+ forward(a);
73
+ }, { once: true });
72
74
  if (b.aborted) forward(b);
73
- else b.addEventListener("abort", () => forward(b), { once: true });
75
+ else b.addEventListener("abort", () => {
76
+ forward(b);
77
+ }, { once: true });
74
78
  return controller.signal;
75
79
  }
76
80
  function createRuntimeSignal(options) {
@@ -1548,7 +1552,7 @@ function registerSetMetadataCommand(program) {
1548
1552
  }
1549
1553
  //#endregion
1550
1554
  //#region package.json
1551
- var version = "5.0.0";
1555
+ var version = "5.1.0";
1552
1556
  //#endregion
1553
1557
  //#region src/program.ts
1554
1558
  function createProgram() {
@@ -869,7 +869,7 @@ function appReducer(state, action) {
869
869
  case "APPEND_TABLE": {
870
870
  const doc = wordprocessingDocument(state);
871
871
  if (doc === void 0) return wrongDocument(state, "a docx, odt or markdown document");
872
- let mergeUnsupported = false;
872
+ const merge = { unsupported: false };
873
873
  const nextState = mutateGuarded(state, doc, () => {
874
874
  const table = doc.editor.body.appendTable({
875
875
  rows: action.rows,
@@ -877,12 +877,12 @@ function appReducer(state, action) {
877
877
  });
878
878
  if (action.merge === void 0) return;
879
879
  if (!("mergeCells" in table)) {
880
- mergeUnsupported = true;
880
+ merge.unsupported = true;
881
881
  return;
882
882
  }
883
883
  table.mergeCells(action.merge.startRow, action.merge.startColumn, action.merge.rowSpan, action.merge.colSpan);
884
884
  });
885
- return mergeUnsupported ? withStatus(nextState, "warning", "Markdown tables do not support merged cells -- the table was created without merging") : nextState;
885
+ return merge.unsupported ? withStatus(nextState, "warning", "Markdown tables do not support merged cells -- the table was created without merging") : nextState;
886
886
  }
887
887
  case "MERGE_TABLE_CELLS": {
888
888
  const doc = wordprocessingDocument(state);
@@ -964,11 +964,11 @@ function appReducer(state, action) {
964
964
  if (doc?.format !== "docx") return wrongDocument(state, "a docx document");
965
965
  const paragraph = doc.editor.paragraphs()[action.blockIndex];
966
966
  if (paragraph === void 0) return withStatus(state, "warning", `There is no paragraph at index ${action.blockIndex}`);
967
- let written = true;
967
+ const omml = { written: true };
968
968
  const nextState = mutate(state, doc, () => {
969
- written = paragraph.appendOfficeMath(action.mathml).written;
969
+ omml.written = paragraph.appendOfficeMath(action.mathml).written;
970
970
  });
971
- return written ? nextState : withStatus(nextState, "warning", "The formula produced no OMML content and was not written");
971
+ return omml.written ? nextState : withStatus(nextState, "warning", "The formula produced no OMML content and was not written");
972
972
  }
973
973
  case "INSERT_ODT_FORMULA": {
974
974
  const doc = state.openDocument;
@@ -2067,7 +2067,8 @@ function StatusLine() {
2067
2067
  showDiagnosticsBadge ? /* @__PURE__ */ jsxs(Text, {
2068
2068
  color: "yellow",
2069
2069
  children: [
2070
- " ⚠ ",
2070
+ " ",
2071
+ "⚠ ",
2071
2072
  state.diagnostics.length,
2072
2073
  " diagnostics -- Ctrl+D"
2073
2074
  ]
@@ -2663,7 +2664,8 @@ function ParagraphFamilyBodyList(props) {
2663
2664
  children: [
2664
2665
  "Merge start row (0-",
2665
2666
  Math.max(0, wizardRows - 1),
2666
- "): "
2667
+ "):",
2668
+ " "
2667
2669
  ]
2668
2670
  }), /* @__PURE__ */ jsx(TextField, {
2669
2671
  value: wizardDraft,
@@ -2677,7 +2679,8 @@ function ParagraphFamilyBodyList(props) {
2677
2679
  children: [
2678
2680
  "Merge start column (0-",
2679
2681
  Math.max(0, wizardColumns - 1),
2680
- "): "
2682
+ "):",
2683
+ " "
2681
2684
  ]
2682
2685
  }), /* @__PURE__ */ jsx(TextField, {
2683
2686
  value: wizardDraft,
@@ -2691,7 +2694,8 @@ function ParagraphFamilyBodyList(props) {
2691
2694
  children: [
2692
2695
  "Merge row span (1-",
2693
2696
  Math.max(1, wizardRows - wizardStartRow),
2694
- "): "
2697
+ "):",
2698
+ " "
2695
2699
  ]
2696
2700
  }), /* @__PURE__ */ jsx(TextField, {
2697
2701
  value: wizardDraft,
@@ -2705,7 +2709,8 @@ function ParagraphFamilyBodyList(props) {
2705
2709
  children: [
2706
2710
  "Merge column span (1-",
2707
2711
  Math.max(1, wizardColumns - wizardStartColumn),
2708
- "): "
2712
+ "):",
2713
+ " "
2709
2714
  ]
2710
2715
  }), /* @__PURE__ */ jsx(TextField, {
2711
2716
  value: wizardDraft,
@@ -3342,7 +3347,7 @@ function TableViewScreen() {
3342
3347
  children: "This table has no rows."
3343
3348
  }) : rows.map((row, rowIndex) => /* @__PURE__ */ jsx(Box, { children: row.cells().map((cell, columnIndex) => {
3344
3349
  const isSelected = rowIndex === clampedRow && columnIndex === clampedColumn;
3345
- const isAnchor = rowIndex === mergeAnchor?.row && columnIndex === mergeAnchor?.column;
3350
+ const isAnchor = rowIndex === mergeAnchor?.row && columnIndex === mergeAnchor.column;
3346
3351
  return /* @__PURE__ */ jsx(Box, {
3347
3352
  width: CELL_WIDTH$2,
3348
3353
  borderStyle: "single",
@@ -3395,7 +3400,8 @@ function TableCellDetailScreen() {
3395
3400
  screen.row,
3396
3401
  ", column ",
3397
3402
  screen.col,
3398
- " of table ",
3403
+ " of table",
3404
+ " ",
3399
3405
  screen.blockIndex,
3400
3406
  "."
3401
3407
  ]
@@ -3460,7 +3466,8 @@ function DocxBodyListScreen() {
3460
3466
  if (doc?.format !== "docx") return /* @__PURE__ */ jsxs(Text, {
3461
3467
  color: "red",
3462
3468
  children: [
3463
- "DocxBodyListScreen requires an open docx document, found ",
3469
+ "DocxBodyListScreen requires an open docx document, found",
3470
+ " ",
3464
3471
  doc === void 0 ? "no open document" : doc.format,
3465
3472
  "."
3466
3473
  ]
@@ -3496,7 +3503,8 @@ function DocxExtrasScreen() {
3496
3503
  if (!isDocx) return /* @__PURE__ */ jsxs(Text, {
3497
3504
  color: "red",
3498
3505
  children: [
3499
- "DocxExtrasScreen requires an open docx document, found ",
3506
+ "DocxExtrasScreen requires an open docx document, found",
3507
+ " ",
3500
3508
  doc === void 0 ? "no open document" : doc.format,
3501
3509
  "."
3502
3510
  ]
@@ -3509,7 +3517,8 @@ function DocxExtrasScreen() {
3509
3517
  children: [
3510
3518
  "Comments, footnotes, headers, footers, numbering (",
3511
3519
  lines.length,
3512
- " of ",
3520
+ " of",
3521
+ " ",
3513
3522
  allLines.length,
3514
3523
  " lines)"
3515
3524
  ]
@@ -3580,7 +3589,8 @@ function MarkdownBodyListScreen() {
3580
3589
  if (doc?.format !== "markdown") return /* @__PURE__ */ jsxs(Text, {
3581
3590
  color: "red",
3582
3591
  children: [
3583
- "MarkdownBodyListScreen requires an open markdown document, found ",
3592
+ "MarkdownBodyListScreen requires an open markdown document, found",
3593
+ " ",
3584
3594
  doc === void 0 ? "no open document" : doc.format,
3585
3595
  "."
3586
3596
  ]
@@ -4022,7 +4032,8 @@ function OdbTableListScreen() {
4022
4032
  table.tableName,
4023
4033
  " (",
4024
4034
  table.columns.length,
4025
- " columns, ",
4035
+ " columns,",
4036
+ " ",
4026
4037
  table.rows.length,
4027
4038
  " rows)"
4028
4039
  ]
@@ -4665,20 +4676,17 @@ function OdgPageDetailScreen() {
4665
4676
  //#region src/tui/screens/editors/odg/shape-or-vector-detail.tsx
4666
4677
  function buildVectorRows(vector, liveVector, dispatch) {
4667
4678
  const rows = [];
4668
- if (vector.kind !== "line" && liveVector.kind !== "line") {
4669
- const fillTarget = liveVector;
4670
- rows.push({
4671
- label: `Fill: ${vector.fill === void 0 ? "none" : formatColor$1(vector.fill)}`,
4672
- currentValue: vector.fill === void 0 ? "" : `${vector.fill.r} ${vector.fill.g} ${vector.fill.b}`,
4673
- commit: (raw) => {
4674
- dispatch({
4675
- type: "SET_VECTOR_FILL",
4676
- vector: fillTarget,
4677
- fill: parseColorField(raw)
4678
- });
4679
- }
4680
- });
4681
- }
4679
+ if (vector.kind !== "line" && liveVector.kind !== "line") rows.push({
4680
+ label: `Fill: ${vector.fill === void 0 ? "none" : formatColor$1(vector.fill)}`,
4681
+ currentValue: vector.fill === void 0 ? "" : `${vector.fill.r} ${vector.fill.g} ${vector.fill.b}`,
4682
+ commit: (raw) => {
4683
+ dispatch({
4684
+ type: "SET_VECTOR_FILL",
4685
+ vector: liveVector,
4686
+ fill: parseColorField(raw)
4687
+ });
4688
+ }
4689
+ });
4682
4690
  rows.push({
4683
4691
  label: `Stroke: ${vector.stroke === void 0 ? "none" : `${formatColor$1(vector.stroke.color)} ${formatPt$1(vector.stroke.widthPt)}pt`}`,
4684
4692
  currentValue: vector.stroke === void 0 ? "" : `${vector.stroke.color.r} ${vector.stroke.color.g} ${vector.stroke.color.b} ${vector.stroke.widthPt}`,
@@ -5789,7 +5797,8 @@ function SlideDetailScreen(props) {
5789
5797
  inverse: isSelected,
5790
5798
  children: [
5791
5799
  row.index + 1,
5792
- ". ",
5800
+ ".",
5801
+ " ",
5793
5802
  describeSlideFamilyShape({
5794
5803
  text: row.text,
5795
5804
  frame: row.frame
@@ -6000,7 +6009,7 @@ function SlideTableDetailScreen(props) {
6000
6009
  children: "This table has no rows."
6001
6010
  }) : rows.map((row, rowIndex) => /* @__PURE__ */ jsx(Box, { children: row.cells.map((cell, columnIndex) => {
6002
6011
  const isCursor = rowIndex === clampedRow && columnIndex === clampedColumn;
6003
- const isAnchor = rowIndex === mergeAnchor?.row && columnIndex === mergeAnchor?.column;
6012
+ const isAnchor = rowIndex === mergeAnchor?.row && columnIndex === mergeAnchor.column;
6004
6013
  return /* @__PURE__ */ jsx(Box, {
6005
6014
  width: CELL_WIDTH$1,
6006
6015
  borderStyle: "single",
@@ -6924,7 +6933,8 @@ function OdsSpreadsheetGridScreen() {
6924
6933
  padCell(row.address, COMPACT_ADDRESS_WIDTH),
6925
6934
  "[",
6926
6935
  row.badge,
6927
- "] ",
6936
+ "]",
6937
+ " ",
6928
6938
  row.displayText
6929
6939
  ]
6930
6940
  })
@@ -6941,7 +6951,7 @@ function OdsSpreadsheetGridScreen() {
6941
6951
  children: [`${row + 1}`.padStart(4), " "]
6942
6952
  }), visibleColumns.map((column) => {
6943
6953
  const isCursor = row === clampedRow && column === clampedColumn;
6944
- const isAnchor = row === mergeAnchor?.row && column === mergeAnchor?.column;
6954
+ const isAnchor = row === mergeAnchor?.row && column === mergeAnchor.column;
6945
6955
  const cell = cells.get(cellKey(row, column));
6946
6956
  return /* @__PURE__ */ jsx(Text, {
6947
6957
  inverse: isCursor,
@@ -7135,7 +7145,8 @@ function ListEditorScreen() {
7135
7145
  inverse: isSelected,
7136
7146
  children: [
7137
7147
  row.index + 1,
7138
- ". ",
7148
+ ".",
7149
+ " ",
7139
7150
  trimmed.length === 0 ? "(empty)" : row.item.text
7140
7151
  ]
7141
7152
  });
@@ -7178,7 +7189,8 @@ function OdtBodyListScreen() {
7178
7189
  if (doc?.format !== "odt") return /* @__PURE__ */ jsxs(Text, {
7179
7190
  color: "red",
7180
7191
  children: [
7181
- "OdtBodyListScreen requires an open odt document, found ",
7192
+ "OdtBodyListScreen requires an open odt document, found",
7193
+ " ",
7182
7194
  doc === void 0 ? "no open document" : doc.format,
7183
7195
  "."
7184
7196
  ]
@@ -7428,34 +7440,42 @@ function buildFrameRows(frame, onFrameChange) {
7428
7440
  {
7429
7441
  label: `X: ${formatPt(frame.xPt)}pt`,
7430
7442
  currentValue: String(frame.xPt),
7431
- commit: (raw) => onFrameChange({
7432
- ...frame,
7433
- xPt: parseNumberField(raw, frame.xPt)
7434
- })
7443
+ commit: (raw) => {
7444
+ onFrameChange({
7445
+ ...frame,
7446
+ xPt: parseNumberField(raw, frame.xPt)
7447
+ });
7448
+ }
7435
7449
  },
7436
7450
  {
7437
7451
  label: `Y: ${formatPt(frame.yPt)}pt`,
7438
7452
  currentValue: String(frame.yPt),
7439
- commit: (raw) => onFrameChange({
7440
- ...frame,
7441
- yPt: parseNumberField(raw, frame.yPt)
7442
- })
7453
+ commit: (raw) => {
7454
+ onFrameChange({
7455
+ ...frame,
7456
+ yPt: parseNumberField(raw, frame.yPt)
7457
+ });
7458
+ }
7443
7459
  },
7444
7460
  {
7445
7461
  label: `Width: ${formatPt(frame.widthPt)}pt`,
7446
7462
  currentValue: String(frame.widthPt),
7447
- commit: (raw) => onFrameChange({
7448
- ...frame,
7449
- widthPt: parseNumberField(raw, frame.widthPt)
7450
- })
7463
+ commit: (raw) => {
7464
+ onFrameChange({
7465
+ ...frame,
7466
+ widthPt: parseNumberField(raw, frame.widthPt)
7467
+ });
7468
+ }
7451
7469
  },
7452
7470
  {
7453
7471
  label: `Height: ${formatPt(frame.heightPt)}pt`,
7454
7472
  currentValue: String(frame.heightPt),
7455
- commit: (raw) => onFrameChange({
7456
- ...frame,
7457
- heightPt: parseNumberField(raw, frame.heightPt)
7458
- })
7473
+ commit: (raw) => {
7474
+ onFrameChange({
7475
+ ...frame,
7476
+ heightPt: parseNumberField(raw, frame.heightPt)
7477
+ });
7478
+ }
7459
7479
  }
7460
7480
  ];
7461
7481
  }
@@ -7463,11 +7483,15 @@ function buildFillStrokeRows(fill, stroke, onFillChange, onStrokeChange) {
7463
7483
  return [{
7464
7484
  label: `Fill: ${fill === void 0 ? "none" : formatColor(fill)}`,
7465
7485
  currentValue: fill === void 0 ? "" : `${fill.r} ${fill.g} ${fill.b}`,
7466
- commit: (raw) => onFillChange(parseColorField(raw))
7486
+ commit: (raw) => {
7487
+ onFillChange(parseColorField(raw));
7488
+ }
7467
7489
  }, {
7468
7490
  label: `Stroke: ${stroke === void 0 ? "none" : formatStroke(stroke)}`,
7469
7491
  currentValue: stroke === void 0 ? "" : `${stroke.color.r} ${stroke.color.g} ${stroke.color.b} ${stroke.widthPt}`,
7470
- commit: (raw) => onStrokeChange(parseStrokeField(raw))
7492
+ commit: (raw) => {
7493
+ onStrokeChange(parseStrokeField(raw));
7494
+ }
7471
7495
  }];
7472
7496
  }
7473
7497
  function buildTextRows(item, pageIndex, itemIndex, dispatch) {
@@ -7475,34 +7499,40 @@ function buildTextRows(item, pageIndex, itemIndex, dispatch) {
7475
7499
  {
7476
7500
  label: `Text: ${item.text}`,
7477
7501
  currentValue: item.text,
7478
- commit: (raw) => dispatch({
7479
- type: "SET_PDF_TEXT_TEXT",
7480
- pageIndex,
7481
- itemIndex,
7482
- text: raw
7483
- })
7502
+ commit: (raw) => {
7503
+ dispatch({
7504
+ type: "SET_PDF_TEXT_TEXT",
7505
+ pageIndex,
7506
+ itemIndex,
7507
+ text: raw
7508
+ });
7509
+ }
7484
7510
  },
7485
7511
  {
7486
7512
  label: `X: ${formatPt(item.xPt)}pt`,
7487
7513
  currentValue: String(item.xPt),
7488
- commit: (raw) => dispatch({
7489
- type: "SET_PDF_TEXT_POSITION",
7490
- pageIndex,
7491
- itemIndex,
7492
- xPt: parseNumberField(raw, item.xPt),
7493
- yPt: item.yPt
7494
- })
7514
+ commit: (raw) => {
7515
+ dispatch({
7516
+ type: "SET_PDF_TEXT_POSITION",
7517
+ pageIndex,
7518
+ itemIndex,
7519
+ xPt: parseNumberField(raw, item.xPt),
7520
+ yPt: item.yPt
7521
+ });
7522
+ }
7495
7523
  },
7496
7524
  {
7497
7525
  label: `Y: ${formatPt(item.yPt)}pt`,
7498
7526
  currentValue: String(item.yPt),
7499
- commit: (raw) => dispatch({
7500
- type: "SET_PDF_TEXT_POSITION",
7501
- pageIndex,
7502
- itemIndex,
7503
- xPt: item.xPt,
7504
- yPt: parseNumberField(raw, item.yPt)
7505
- })
7527
+ commit: (raw) => {
7528
+ dispatch({
7529
+ type: "SET_PDF_TEXT_POSITION",
7530
+ pageIndex,
7531
+ itemIndex,
7532
+ xPt: item.xPt,
7533
+ yPt: parseNumberField(raw, item.yPt)
7534
+ });
7535
+ }
7506
7536
  },
7507
7537
  {
7508
7538
  label: `Font family: ${item.font.family}`,
@@ -7523,181 +7553,219 @@ function buildTextRows(item, pageIndex, itemIndex, dispatch) {
7523
7553
  {
7524
7554
  label: `Font weight: ${item.font.weight} (Enter to toggle)`,
7525
7555
  currentValue: "",
7526
- activate: () => dispatch({
7527
- type: "SET_PDF_TEXT_FONT",
7528
- pageIndex,
7529
- itemIndex,
7530
- font: {
7531
- ...item.font,
7532
- weight: item.font.weight === "bold" ? "normal" : "bold"
7533
- }
7534
- })
7556
+ activate: () => {
7557
+ dispatch({
7558
+ type: "SET_PDF_TEXT_FONT",
7559
+ pageIndex,
7560
+ itemIndex,
7561
+ font: {
7562
+ ...item.font,
7563
+ weight: item.font.weight === "bold" ? "normal" : "bold"
7564
+ }
7565
+ });
7566
+ }
7535
7567
  },
7536
7568
  {
7537
7569
  label: `Font style: ${item.font.style} (Enter to toggle)`,
7538
7570
  currentValue: "",
7539
- activate: () => dispatch({
7540
- type: "SET_PDF_TEXT_FONT",
7541
- pageIndex,
7542
- itemIndex,
7543
- font: {
7544
- ...item.font,
7545
- style: item.font.style === "italic" ? "normal" : "italic"
7546
- }
7547
- })
7571
+ activate: () => {
7572
+ dispatch({
7573
+ type: "SET_PDF_TEXT_FONT",
7574
+ pageIndex,
7575
+ itemIndex,
7576
+ font: {
7577
+ ...item.font,
7578
+ style: item.font.style === "italic" ? "normal" : "italic"
7579
+ }
7580
+ });
7581
+ }
7548
7582
  },
7549
7583
  {
7550
7584
  label: `Size: ${item.sizePt}pt`,
7551
7585
  currentValue: String(item.sizePt),
7552
- commit: (raw) => dispatch({
7553
- type: "SET_PDF_TEXT_SIZE",
7554
- pageIndex,
7555
- itemIndex,
7556
- sizePt: parseNumberField(raw, item.sizePt)
7557
- })
7586
+ commit: (raw) => {
7587
+ dispatch({
7588
+ type: "SET_PDF_TEXT_SIZE",
7589
+ pageIndex,
7590
+ itemIndex,
7591
+ sizePt: parseNumberField(raw, item.sizePt)
7592
+ });
7593
+ }
7558
7594
  },
7559
7595
  {
7560
7596
  label: `Colour: ${formatColor(item.color)}`,
7561
7597
  currentValue: `${item.color.r} ${item.color.g} ${item.color.b}`,
7562
- commit: (raw) => dispatch({
7563
- type: "SET_PDF_TEXT_COLOR",
7564
- pageIndex,
7565
- itemIndex,
7566
- color: parseRequiredColorField(raw, item.color)
7567
- })
7598
+ commit: (raw) => {
7599
+ dispatch({
7600
+ type: "SET_PDF_TEXT_COLOR",
7601
+ pageIndex,
7602
+ itemIndex,
7603
+ color: parseRequiredColorField(raw, item.color)
7604
+ });
7605
+ }
7568
7606
  },
7569
7607
  {
7570
7608
  label: `Width: ${item.widthPt === void 0 ? "unset" : `${item.widthPt}pt`}`,
7571
7609
  currentValue: item.widthPt === void 0 ? "" : String(item.widthPt),
7572
- commit: (raw) => dispatch({
7573
- type: "SET_PDF_TEXT_WIDTH",
7574
- pageIndex,
7575
- itemIndex,
7576
- widthPt: parseOptionalNumberField(raw)
7577
- })
7610
+ commit: (raw) => {
7611
+ dispatch({
7612
+ type: "SET_PDF_TEXT_WIDTH",
7613
+ pageIndex,
7614
+ itemIndex,
7615
+ widthPt: parseOptionalNumberField(raw)
7616
+ });
7617
+ }
7578
7618
  },
7579
7619
  {
7580
7620
  label: `Rotation: ${item.rotationDeg === void 0 ? "unset" : `${item.rotationDeg}°`}`,
7581
7621
  currentValue: item.rotationDeg === void 0 ? "" : String(item.rotationDeg),
7582
- commit: (raw) => dispatch({
7583
- type: "SET_PDF_TEXT_ROTATION",
7584
- pageIndex,
7585
- itemIndex,
7586
- rotationDeg: parseOptionalNumberField(raw)
7587
- })
7622
+ commit: (raw) => {
7623
+ dispatch({
7624
+ type: "SET_PDF_TEXT_ROTATION",
7625
+ pageIndex,
7626
+ itemIndex,
7627
+ rotationDeg: parseOptionalNumberField(raw)
7628
+ });
7629
+ }
7588
7630
  },
7589
7631
  {
7590
7632
  label: `Underline: ${item.underline === true ? "yes" : "no"} (Enter to toggle)`,
7591
7633
  currentValue: "",
7592
- activate: () => dispatch({
7593
- type: "TOGGLE_PDF_TEXT_UNDERLINE",
7594
- pageIndex,
7595
- itemIndex
7596
- })
7634
+ activate: () => {
7635
+ dispatch({
7636
+ type: "TOGGLE_PDF_TEXT_UNDERLINE",
7637
+ pageIndex,
7638
+ itemIndex
7639
+ });
7640
+ }
7597
7641
  }
7598
7642
  ];
7599
7643
  }
7600
7644
  function buildRectRows(item, pageIndex, itemIndex, dispatch) {
7601
- return [...buildFrameRows(item, (frame) => dispatch({
7602
- type: "SET_PDF_RECT_FRAME",
7603
- pageIndex,
7604
- itemIndex,
7605
- ...frame
7606
- })), ...buildFillStrokeRows(item.fill, item.stroke, (fill) => dispatch({
7607
- type: "SET_PDF_RECT_FILL",
7608
- pageIndex,
7609
- itemIndex,
7610
- fill
7611
- }), (stroke) => dispatch({
7612
- type: "SET_PDF_RECT_STROKE",
7613
- pageIndex,
7614
- itemIndex,
7615
- stroke
7616
- }))];
7645
+ return [...buildFrameRows(item, (frame) => {
7646
+ dispatch({
7647
+ type: "SET_PDF_RECT_FRAME",
7648
+ pageIndex,
7649
+ itemIndex,
7650
+ ...frame
7651
+ });
7652
+ }), ...buildFillStrokeRows(item.fill, item.stroke, (fill) => {
7653
+ dispatch({
7654
+ type: "SET_PDF_RECT_FILL",
7655
+ pageIndex,
7656
+ itemIndex,
7657
+ fill
7658
+ });
7659
+ }, (stroke) => {
7660
+ dispatch({
7661
+ type: "SET_PDF_RECT_STROKE",
7662
+ pageIndex,
7663
+ itemIndex,
7664
+ stroke
7665
+ });
7666
+ })];
7617
7667
  }
7618
7668
  function buildEllipseRows(item, pageIndex, itemIndex, dispatch) {
7619
- return [...buildFrameRows(item, (frame) => dispatch({
7620
- type: "SET_PDF_ELLIPSE_FRAME",
7621
- pageIndex,
7622
- itemIndex,
7623
- ...frame
7624
- })), ...buildFillStrokeRows(item.fill, item.stroke, (fill) => dispatch({
7625
- type: "SET_PDF_ELLIPSE_FILL",
7626
- pageIndex,
7627
- itemIndex,
7628
- fill
7629
- }), (stroke) => dispatch({
7630
- type: "SET_PDF_ELLIPSE_STROKE",
7631
- pageIndex,
7632
- itemIndex,
7633
- stroke
7634
- }))];
7669
+ return [...buildFrameRows(item, (frame) => {
7670
+ dispatch({
7671
+ type: "SET_PDF_ELLIPSE_FRAME",
7672
+ pageIndex,
7673
+ itemIndex,
7674
+ ...frame
7675
+ });
7676
+ }), ...buildFillStrokeRows(item.fill, item.stroke, (fill) => {
7677
+ dispatch({
7678
+ type: "SET_PDF_ELLIPSE_FILL",
7679
+ pageIndex,
7680
+ itemIndex,
7681
+ fill
7682
+ });
7683
+ }, (stroke) => {
7684
+ dispatch({
7685
+ type: "SET_PDF_ELLIPSE_STROKE",
7686
+ pageIndex,
7687
+ itemIndex,
7688
+ stroke
7689
+ });
7690
+ })];
7635
7691
  }
7636
7692
  function buildLineRows(item, pageIndex, itemIndex, dispatch) {
7637
7693
  return [
7638
7694
  {
7639
7695
  label: `From X: ${formatPt(item.x1Pt)}pt`,
7640
7696
  currentValue: String(item.x1Pt),
7641
- commit: (raw) => dispatch({
7642
- type: "SET_PDF_LINE_FROM",
7643
- pageIndex,
7644
- itemIndex,
7645
- x1Pt: parseNumberField(raw, item.x1Pt),
7646
- y1Pt: item.y1Pt
7647
- })
7697
+ commit: (raw) => {
7698
+ dispatch({
7699
+ type: "SET_PDF_LINE_FROM",
7700
+ pageIndex,
7701
+ itemIndex,
7702
+ x1Pt: parseNumberField(raw, item.x1Pt),
7703
+ y1Pt: item.y1Pt
7704
+ });
7705
+ }
7648
7706
  },
7649
7707
  {
7650
7708
  label: `From Y: ${formatPt(item.y1Pt)}pt`,
7651
7709
  currentValue: String(item.y1Pt),
7652
- commit: (raw) => dispatch({
7653
- type: "SET_PDF_LINE_FROM",
7654
- pageIndex,
7655
- itemIndex,
7656
- x1Pt: item.x1Pt,
7657
- y1Pt: parseNumberField(raw, item.y1Pt)
7658
- })
7710
+ commit: (raw) => {
7711
+ dispatch({
7712
+ type: "SET_PDF_LINE_FROM",
7713
+ pageIndex,
7714
+ itemIndex,
7715
+ x1Pt: item.x1Pt,
7716
+ y1Pt: parseNumberField(raw, item.y1Pt)
7717
+ });
7718
+ }
7659
7719
  },
7660
7720
  {
7661
7721
  label: `To X: ${formatPt(item.x2Pt)}pt`,
7662
7722
  currentValue: String(item.x2Pt),
7663
- commit: (raw) => dispatch({
7664
- type: "SET_PDF_LINE_TO",
7665
- pageIndex,
7666
- itemIndex,
7667
- x2Pt: parseNumberField(raw, item.x2Pt),
7668
- y2Pt: item.y2Pt
7669
- })
7723
+ commit: (raw) => {
7724
+ dispatch({
7725
+ type: "SET_PDF_LINE_TO",
7726
+ pageIndex,
7727
+ itemIndex,
7728
+ x2Pt: parseNumberField(raw, item.x2Pt),
7729
+ y2Pt: item.y2Pt
7730
+ });
7731
+ }
7670
7732
  },
7671
7733
  {
7672
7734
  label: `To Y: ${formatPt(item.y2Pt)}pt`,
7673
7735
  currentValue: String(item.y2Pt),
7674
- commit: (raw) => dispatch({
7675
- type: "SET_PDF_LINE_TO",
7676
- pageIndex,
7677
- itemIndex,
7678
- x2Pt: item.x2Pt,
7679
- y2Pt: parseNumberField(raw, item.y2Pt)
7680
- })
7736
+ commit: (raw) => {
7737
+ dispatch({
7738
+ type: "SET_PDF_LINE_TO",
7739
+ pageIndex,
7740
+ itemIndex,
7741
+ x2Pt: item.x2Pt,
7742
+ y2Pt: parseNumberField(raw, item.y2Pt)
7743
+ });
7744
+ }
7681
7745
  },
7682
7746
  {
7683
7747
  label: `Colour: ${formatColor(item.color)}`,
7684
7748
  currentValue: `${item.color.r} ${item.color.g} ${item.color.b}`,
7685
- commit: (raw) => dispatch({
7686
- type: "SET_PDF_LINE_COLOR",
7687
- pageIndex,
7688
- itemIndex,
7689
- color: parseRequiredColorField(raw, item.color)
7690
- })
7749
+ commit: (raw) => {
7750
+ dispatch({
7751
+ type: "SET_PDF_LINE_COLOR",
7752
+ pageIndex,
7753
+ itemIndex,
7754
+ color: parseRequiredColorField(raw, item.color)
7755
+ });
7756
+ }
7691
7757
  },
7692
7758
  {
7693
7759
  label: `Width: ${item.widthPt}pt`,
7694
7760
  currentValue: String(item.widthPt),
7695
- commit: (raw) => dispatch({
7696
- type: "SET_PDF_LINE_WIDTH",
7697
- pageIndex,
7698
- itemIndex,
7699
- widthPt: Math.max(parseNumberField(raw, item.widthPt), Number.EPSILON)
7700
- })
7761
+ commit: (raw) => {
7762
+ dispatch({
7763
+ type: "SET_PDF_LINE_WIDTH",
7764
+ pageIndex,
7765
+ itemIndex,
7766
+ widthPt: Math.max(parseNumberField(raw, item.widthPt), Number.EPSILON)
7767
+ });
7768
+ }
7701
7769
  }
7702
7770
  ];
7703
7771
  }
@@ -7705,41 +7773,51 @@ function buildPathRows(item, pageIndex, itemIndex, dispatch) {
7705
7773
  return [{
7706
7774
  label: `Fill rule: ${item.fillRule ?? "nonzero (default)"} (Enter to cycle)`,
7707
7775
  currentValue: "",
7708
- activate: () => dispatch({
7709
- type: "SET_PDF_PATH_FILL_RULE",
7776
+ activate: () => {
7777
+ dispatch({
7778
+ type: "SET_PDF_PATH_FILL_RULE",
7779
+ pageIndex,
7780
+ itemIndex,
7781
+ fillRule: item.fillRule === "evenodd" ? void 0 : item.fillRule === "nonzero" ? "evenodd" : "nonzero"
7782
+ });
7783
+ }
7784
+ }, ...buildFillStrokeRows(item.fill, item.stroke, (fill) => {
7785
+ dispatch({
7786
+ type: "SET_PDF_PATH_FILL",
7710
7787
  pageIndex,
7711
7788
  itemIndex,
7712
- fillRule: item.fillRule === "evenodd" ? void 0 : item.fillRule === "nonzero" ? "evenodd" : "nonzero"
7713
- })
7714
- }, ...buildFillStrokeRows(item.fill, item.stroke, (fill) => dispatch({
7715
- type: "SET_PDF_PATH_FILL",
7716
- pageIndex,
7717
- itemIndex,
7718
- fill
7719
- }), (stroke) => dispatch({
7720
- type: "SET_PDF_PATH_STROKE",
7721
- pageIndex,
7722
- itemIndex,
7723
- stroke
7724
- }))];
7789
+ fill
7790
+ });
7791
+ }, (stroke) => {
7792
+ dispatch({
7793
+ type: "SET_PDF_PATH_STROKE",
7794
+ pageIndex,
7795
+ itemIndex,
7796
+ stroke
7797
+ });
7798
+ })];
7725
7799
  }
7726
7800
  function buildImageRows(item, pageIndex, itemIndex, dispatch, onReplaceImage) {
7727
7801
  return [
7728
- ...buildFrameRows(item, (frame) => dispatch({
7729
- type: "SET_PDF_IMAGE_FRAME",
7730
- pageIndex,
7731
- itemIndex,
7732
- ...frame
7733
- })),
7802
+ ...buildFrameRows(item, (frame) => {
7803
+ dispatch({
7804
+ type: "SET_PDF_IMAGE_FRAME",
7805
+ pageIndex,
7806
+ itemIndex,
7807
+ ...frame
7808
+ });
7809
+ }),
7734
7810
  {
7735
7811
  label: `Rotation: ${item.rotationDeg === void 0 ? "unset" : `${item.rotationDeg}°`}`,
7736
7812
  currentValue: item.rotationDeg === void 0 ? "" : String(item.rotationDeg),
7737
- commit: (raw) => dispatch({
7738
- type: "SET_PDF_IMAGE_ROTATION",
7739
- pageIndex,
7740
- itemIndex,
7741
- rotationDeg: parseOptionalNumberField(raw)
7742
- })
7813
+ commit: (raw) => {
7814
+ dispatch({
7815
+ type: "SET_PDF_IMAGE_ROTATION",
7816
+ pageIndex,
7817
+ itemIndex,
7818
+ rotationDeg: parseOptionalNumberField(raw)
7819
+ });
7820
+ }
7743
7821
  },
7744
7822
  {
7745
7823
  label: "Replace image...",
@@ -7752,35 +7830,43 @@ function buildLinkRows(item, pageIndex, itemIndex, dispatch) {
7752
7830
  return [{
7753
7831
  label: `URI: ${item.uri}`,
7754
7832
  currentValue: item.uri,
7755
- commit: (raw) => dispatch({
7756
- type: "SET_PDF_LINK_URI",
7833
+ commit: (raw) => {
7834
+ dispatch({
7835
+ type: "SET_PDF_LINK_URI",
7836
+ pageIndex,
7837
+ itemIndex,
7838
+ uri: raw
7839
+ });
7840
+ }
7841
+ }, ...buildFrameRows(item, (frame) => {
7842
+ dispatch({
7843
+ type: "SET_PDF_LINK_FRAME",
7757
7844
  pageIndex,
7758
7845
  itemIndex,
7759
- uri: raw
7760
- })
7761
- }, ...buildFrameRows(item, (frame) => dispatch({
7762
- type: "SET_PDF_LINK_FRAME",
7763
- pageIndex,
7764
- itemIndex,
7765
- ...frame
7766
- }))];
7846
+ ...frame
7847
+ });
7848
+ })];
7767
7849
  }
7768
7850
  function buildInternalLinkRows(item, pageIndex, itemIndex, dispatch) {
7769
7851
  return [{
7770
7852
  label: `Destination: ${item.destination}`,
7771
7853
  currentValue: item.destination,
7772
- commit: (raw) => dispatch({
7773
- type: "SET_PDF_INTERNAL_LINK_DESTINATION",
7854
+ commit: (raw) => {
7855
+ dispatch({
7856
+ type: "SET_PDF_INTERNAL_LINK_DESTINATION",
7857
+ pageIndex,
7858
+ itemIndex,
7859
+ destination: raw
7860
+ });
7861
+ }
7862
+ }, ...buildFrameRows(item, (frame) => {
7863
+ dispatch({
7864
+ type: "SET_PDF_INTERNAL_LINK_FRAME",
7774
7865
  pageIndex,
7775
7866
  itemIndex,
7776
- destination: raw
7777
- })
7778
- }, ...buildFrameRows(item, (frame) => dispatch({
7779
- type: "SET_PDF_INTERNAL_LINK_FRAME",
7780
- pageIndex,
7781
- itemIndex,
7782
- ...frame
7783
- }))];
7867
+ ...frame
7868
+ });
7869
+ })];
7784
7870
  }
7785
7871
  function buildRowsFor(item, pageIndex, itemIndex, dispatch, onReplaceImage) {
7786
7872
  switch (item.kind) {
@@ -7831,7 +7917,9 @@ function EditableItemDetail(props) {
7831
7917
  const [draft, setDraft] = useState("");
7832
7918
  const [replacingImage, setReplacingImage] = useState(false);
7833
7919
  const item = doc.editor.page(pageIndex)?.items()[itemIndex];
7834
- const rows = item === void 0 ? [] : buildRowsFor(item, pageIndex, itemIndex, dispatch, () => setReplacingImage(true));
7920
+ const rows = item === void 0 ? [] : buildRowsFor(item, pageIndex, itemIndex, dispatch, () => {
7921
+ setReplacingImage(true);
7922
+ });
7835
7923
  const { selectedIndex } = useNavigationInput({
7836
7924
  itemCount: rows.length,
7837
7925
  isActive: props.isActive && editingField === void 0 && !replacingImage,
@@ -9084,7 +9172,7 @@ function AppShell({ startPath }) {
9084
9172
  const overlayOpen = anyOverlayOpen(state);
9085
9173
  useEffect(() => {
9086
9174
  if (startPath === void 0) return;
9087
- let cancelled = false;
9175
+ const lifecycle = { cancelled: false };
9088
9176
  (async () => {
9089
9177
  try {
9090
9178
  const doc = await openDocumentAtPath(startPath, { onDiagnostic: (diagnostic) => {
@@ -9093,13 +9181,13 @@ function AppShell({ startPath }) {
9093
9181
  diagnostic
9094
9182
  });
9095
9183
  } });
9096
- if (!cancelled) dispatch({
9184
+ if (!lifecycle.cancelled) dispatch({
9097
9185
  type: "OPEN_FILE_SUCCESS",
9098
9186
  path: startPath,
9099
9187
  doc
9100
9188
  });
9101
9189
  } catch (error) {
9102
- if (!cancelled) dispatch({
9190
+ if (!lifecycle.cancelled) dispatch({
9103
9191
  type: "OPEN_FILE_ERROR",
9104
9192
  message: `Could not open ${startPath}`,
9105
9193
  detail: describeError(error)
@@ -9107,7 +9195,7 @@ function AppShell({ startPath }) {
9107
9195
  }
9108
9196
  })();
9109
9197
  return () => {
9110
- cancelled = true;
9198
+ lifecycle.cancelled = true;
9111
9199
  };
9112
9200
  }, [startPath, dispatch]);
9113
9201
  useEffect(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "document-cli",
3
- "version": "5.0.0",
3
+ "version": "5.1.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 conversion, bridge, and editor as a scriptable command or a terminal app.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -90,9 +90,9 @@
90
90
  "packageManager": "pnpm@11.6.0",
91
91
  "dependencies": {
92
92
  "commander": "^15.0.0",
93
- "document-outline.js": "^3.0.0",
94
- "document-schema.js": "^5.0.0",
95
- "documents.js": "^6.0.0",
93
+ "document-outline.js": "^3.1.0",
94
+ "document-schema.js": "^5.1.0",
95
+ "documents.js": "^6.1.0",
96
96
  "ink": "^7.1.1",
97
97
  "ink-text-input": "^6.0.0",
98
98
  "react": "^19.2.8"
@@ -100,22 +100,13 @@
100
100
  "devDependencies": {
101
101
  "@arethetypeswrong/cli": "^0.18.5",
102
102
  "@cloudflare/vitest-pool-workers": "^0.20.2",
103
- "@commitlint/cli": "^21.2.1",
104
- "@commitlint/config-conventional": "^21.2.0",
105
- "@eslint/js": "^10.0.1",
106
- "@semantic-release/changelog": "^7.0.0",
107
- "@semantic-release/git": "^11.0.1",
108
103
  "@types/node": "^26.1.2",
109
104
  "@types/react": "^19.2.18",
110
105
  "@vitest/coverage-v8": "^4.1.10",
111
106
  "eslint": "^10.8.0",
112
- "eslint-plugin-react-hooks": "^7.1.1",
113
- "globals": "^17.8.0",
114
107
  "husky": "^9.1.7",
115
108
  "ink-testing-library": "^4.0.0",
116
- "lint-staged": "^17.3.0",
117
- "odf.js": "^6.0.0",
118
- "pdf-codec": "^3.4.4",
109
+ "odf.js": "^6.1.0",
119
110
  "publint": "^0.3.22",
120
111
  "semantic-release": "^25.0.8",
121
112
  "tsdown": "^0.22.14",