document-cli 1.10.0 → 1.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,8 @@
1
- import { c as formatDocxExtrasLines, f as loadProvidedFonts, i as formatOdbReportLines, l as readInput, m as inferFormatFromExtension, n as describeOdbReport, o as formatMetadataLines, p as formatToExtension, r as formatOdbFormLines, t as describeOdbForm } from "./odb-structure-Du1w_hOU.js";
1
+ import { c as formatDocxExtrasLines, f as loadProvidedFonts, i as formatOdbReportLines, l as readInput, m as inferFormatFromExtension, n as describeOdbReport, o as formatMetadataLines, p as formatToExtension, r as formatOdbFormLines, t as describeOdbForm } from "./odb-structure-CS2Eybob.js";
2
2
  import { readFile, writeFile } from "node:fs/promises";
3
- import { buildDocxPackage, buildOdtPackage, bytesToBase64, convertWordprocessingToLayout, createDocx, createFontMeasurer, createFontRegistry, createOdg, createOdp, createOds, createOdt, createPdf, createPptx, decodeMarkdownText, docxToPdf, encodeMarkdownText, encodePackage, hsqldbCellDisplayText, markdownToPdf, odgToPdf, odpToPdf, odsToPdf, odtToPdf, openDocx, openOdg, openOdp, openOds, openOdt, openPdf, openPptx, parseXml, pptxToPdf, readDocxContent, readDocxExtras, readMarkdownContent, readOdbForms, readOdbReportContent, readOdbReports, readOdbTables, readOdgContent, readOdpContent, readOdsContent, readOdtContent, readPdf, readPptxContent, rgbHexToColor, writePdf, xlsxToPdf } from "documents.js";
3
+ import { bytesToBase64, createDocx, createOdg, createOdp, createOds, createOdt, createPdf, createPptx, decodeMarkdownText, docxToPdf, encodeMarkdownText, hsqldbCellDisplayText, markdownToPdf, odbReportToDocx, odbReportToOdt, odbReportToPdf, odgToPdf, odpToPdf, odsToPdf, odtToPdf, openDocx, openMarkdown, openOdg, openOdp, openOds, openOdt, openPdf, openPptx, parseXml, pptxToPdf, readDocxContent, readDocxExtras, readMarkdownContent, readOdbForms, readOdbReportContent, readOdbReports, readOdbTables, readOdgContent, readOdpContent, readOdsContent, readOdtContent, readPdf, readPptxContent, rgbHexToColor, xlsxToPdf } from "documents.js";
4
4
  import { basename, dirname, extname, join } from "node:path";
5
- import { cellReference, columnIndexToLetters, decodePackage as decodePackage$1, encodePackage as encodePackage$1 } from "odf.js";
5
+ import { cellReference, columnIndexToLetters, decodePackage as decodePackage$1 } from "odf.js";
6
6
  import { readdirSync } from "node:fs";
7
7
  import { Box, Text, render, useApp, useInput, useWindowSize } from "ink";
8
8
  import { createContext, useContext, useEffect, useReducer, useState } from "react";
@@ -39,7 +39,7 @@ async function exportToPdf(openDocument, destinationPath, options) {
39
39
  if (openDocument.format === "odb" || openDocument.format === "pdf") throw new Error(`A ${openDocument.format} document has no export-to-PDF path -- ${openDocument.format === "pdf" ? "save it directly instead" : "it is a read-only source with nothing to convert"}`);
40
40
  const pdfOptions = toPdfOptions(options, await loadProvidedFonts(options.fontFiles ?? [], { signal: options.signal }));
41
41
  if (openDocument.format === "markdown") {
42
- const pdfBytes = markdownToPdf(encodeMarkdownText(openDocument.source), pdfOptions);
42
+ const pdfBytes = markdownToPdf(encodeMarkdownText(openDocument.editor.toMarkdownText()), pdfOptions);
43
43
  await writeFile(destinationPath, pdfBytes);
44
44
  return;
45
45
  }
@@ -75,7 +75,7 @@ function detectFormat(path) {
75
75
  //#endregion
76
76
  //#region src/tui/format/open-document.ts
77
77
  const ODB_EXTENSION = ".odb";
78
- async function openDocumentAtPath(path) {
78
+ async function openDocumentAtPath(path, options = {}) {
79
79
  const bytes = new Uint8Array(await readFile(path));
80
80
  if (path.toLowerCase().endsWith(ODB_EXTENSION)) {
81
81
  const pkg = decodePackage$1(bytes);
@@ -129,11 +129,20 @@ async function openDocumentAtPath(path) {
129
129
  path
130
130
  };
131
131
  }
132
- case "markdown": return {
133
- format,
134
- source: decodeMarkdownText(bytes),
135
- path
136
- };
132
+ case "markdown": {
133
+ const text = decodeMarkdownText(bytes);
134
+ return {
135
+ format,
136
+ editor: openMarkdown(text, { sink: (diagnostic) => {
137
+ options.onDiagnostic?.({
138
+ severity: diagnostic.severity,
139
+ message: diagnostic.message
140
+ });
141
+ } }),
142
+ originalText: text,
143
+ path
144
+ };
145
+ }
137
146
  case "xlsx": return {
138
147
  format,
139
148
  layout: readPdf(xlsxToPdf(bytes)),
@@ -189,7 +198,7 @@ function createNewDocument(format) {
189
198
  async function saveDocumentTo(openDocument, path) {
190
199
  if (openDocument.format === "odb" || openDocument.format === "xlsx") throw new Error(`A ${openDocument.format} document is opened read-only and cannot be written back`);
191
200
  if (openDocument.format === "markdown") {
192
- await writeFile(path, encodeMarkdownText(openDocument.source));
201
+ await writeFile(path, encodeMarkdownText(openDocument.editor.toMarkdownText()));
193
202
  return;
194
203
  }
195
204
  await writeFile(path, openDocument.editor.toBytes());
@@ -233,7 +242,7 @@ function selectionKeyFor(screen) {
233
242
  case "pdfPageList":
234
243
  case "exportOptions":
235
244
  case "saveAsPrompt":
236
- case "markdownLineList":
245
+ case "viewSource":
237
246
  case "metadata": return screen.kind;
238
247
  case "filePicker": return `filePicker:${screen.purpose}:${screen.cwd}`;
239
248
  case "paragraphDetail":
@@ -256,19 +265,18 @@ function selectionKeyFor(screen) {
256
265
  case "odbFormDetail": return `odbFormDetail:${screen.formName}`;
257
266
  case "odbReportDetail": return `odbReportDetail:${screen.reportName}`;
258
267
  case "odbReportRender": return `odbReportRender:${screen.reportName}`;
259
- case "markdownLineEditor": return `markdownLineEditor:${screen.lineIndex}`;
260
268
  }
261
269
  }
262
270
  function rootScreenForFormat(format) {
263
271
  switch (format) {
264
272
  case "docx":
265
- case "odt": return { kind: "bodyList" };
273
+ case "odt":
274
+ case "markdown": return { kind: "bodyList" };
266
275
  case "pptx":
267
276
  case "odp": return { kind: "slideList" };
268
277
  case "ods": return { kind: "sheetList" };
269
278
  case "odg": return { kind: "pageList" };
270
279
  case "odb": return { kind: "odbTableList" };
271
- case "markdown": return { kind: "markdownLineList" };
272
280
  case "pdf":
273
281
  case "xlsx": return { kind: "pdfPageList" };
274
282
  }
@@ -397,7 +405,8 @@ function documentWithPath(doc, path) {
397
405
  };
398
406
  case "markdown": return {
399
407
  format: "markdown",
400
- source: doc.source,
408
+ editor: doc.editor,
409
+ originalText: doc.originalText,
401
410
  path
402
411
  };
403
412
  case "xlsx": return {
@@ -451,8 +460,11 @@ function reopenEditable(doc, bytes) {
451
460
  }
452
461
  }
453
462
  }
463
+ function toUndoSnapshot(doc) {
464
+ return doc.format === "markdown" ? encodeMarkdownText(doc.editor.toMarkdownText()) : doc.editor.toBytes();
465
+ }
454
466
  function mutate(state, doc, apply) {
455
- const snapshot = doc.editor.toBytes();
467
+ const snapshot = toUndoSnapshot(doc);
456
468
  apply();
457
469
  return {
458
470
  ...state,
@@ -492,22 +504,15 @@ function mergePptxTableCells(table, startRow, startColumn, rowSpan, colSpan) {
492
504
  }
493
505
  }
494
506
  }
495
- function mutateMarkdown(state, doc, source) {
496
- const snapshot = encodeMarkdownText(doc.source);
497
- return {
498
- ...state,
499
- openDocument: {
500
- ...doc,
501
- source
502
- },
503
- hasUnsavedChanges: true,
504
- undoStack: pushSnapshot(state.undoStack, snapshot)
505
- };
506
- }
507
507
  function wrongDocument(state, expected) {
508
508
  return withStatus(state, "warning", `That action needs ${expected}; the open document is ${state.openDocument === void 0 ? "no document" : state.openDocument.format}`);
509
509
  }
510
510
  function wordprocessingDocument(state) {
511
+ const doc = state.openDocument;
512
+ if (doc === void 0) return;
513
+ return doc.format === "docx" || doc.format === "odt" || doc.format === "markdown" ? doc : void 0;
514
+ }
515
+ function styledWordprocessingDocument(state) {
511
516
  const doc = state.openDocument;
512
517
  if (doc === void 0) return;
513
518
  return doc.format === "docx" || doc.format === "odt" ? doc : void 0;
@@ -571,17 +576,15 @@ function vectorHostDocument(state) {
571
576
  if (doc === void 0) return;
572
577
  return doc.format === "odg" || doc.format === "odp" ? doc : void 0;
573
578
  }
574
- function markdownDocument(state) {
575
- const doc = state.openDocument;
576
- if (doc === void 0) return;
577
- return doc.format === "markdown" ? doc : void 0;
578
- }
579
579
  function paragraphAt(doc, blockIndex) {
580
580
  return doc.editor.paragraphs()[blockIndex];
581
581
  }
582
582
  function tableAt(doc, tableIndex) {
583
583
  return doc.editor.tables()[tableIndex];
584
584
  }
585
+ function tableCellAt(table, row, column) {
586
+ return table.rows()[row]?.cells()[column];
587
+ }
585
588
  function shapeAt(doc, containerIndex, shapeIndex) {
586
589
  if (doc.format === "odg") return doc.editor.pages()[containerIndex]?.shapes()[shapeIndex];
587
590
  return doc.editor.slides()[containerIndex]?.shapes()[shapeIndex];
@@ -591,7 +594,7 @@ function sheetAt(doc, sheetIndex) {
591
594
  }
592
595
  function withRun(state, blockIndex, runIndex, apply) {
593
596
  const doc = wordprocessingDocument(state);
594
- if (doc === void 0) return wrongDocument(state, "a docx or odt document");
597
+ if (doc === void 0) return wrongDocument(state, "a docx, odt or markdown document");
595
598
  const paragraph = paragraphAt(doc, blockIndex);
596
599
  if (paragraph === void 0) return withStatus(state, "warning", `There is no paragraph at index ${blockIndex}`);
597
600
  const run = paragraph.runs()[runIndex];
@@ -600,6 +603,17 @@ function withRun(state, blockIndex, runIndex, apply) {
600
603
  apply(run);
601
604
  });
602
605
  }
606
+ function withStyledRun(state, blockIndex, runIndex, apply) {
607
+ const doc = styledWordprocessingDocument(state);
608
+ if (doc === void 0) return wrongDocument(state, "a docx or odt document");
609
+ const paragraph = doc.editor.paragraphs()[blockIndex];
610
+ if (paragraph === void 0) return withStatus(state, "warning", `There is no paragraph at index ${blockIndex}`);
611
+ const run = paragraph.runs()[runIndex];
612
+ if (run === void 0) return withStatus(state, "warning", `Paragraph ${blockIndex} has no run at index ${runIndex}`);
613
+ return mutate(state, doc, () => {
614
+ apply(run);
615
+ });
616
+ }
603
617
  function withShape(state, containerIndex, shapeIndex, apply) {
604
618
  const doc = shapeHostDocument(state);
605
619
  if (doc === void 0) return wrongDocument(state, "a pptx, odp or odg document");
@@ -761,8 +775,15 @@ function appReducer(state, action) {
761
775
  };
762
776
  case "APPEND_PARAGRAPH": {
763
777
  const doc = wordprocessingDocument(state);
764
- if (doc === void 0) return wrongDocument(state, "a docx or odt document");
778
+ if (doc === void 0) return wrongDocument(state, "a docx, odt or markdown document");
765
779
  return mutate(state, doc, () => {
780
+ if (doc.format === "markdown") {
781
+ doc.editor.body.appendParagraph({
782
+ text: action.text,
783
+ styleId: action.styleId
784
+ });
785
+ return;
786
+ }
766
787
  doc.editor.body.appendParagraph({
767
788
  text: action.text,
768
789
  styleId: action.styleId,
@@ -771,9 +792,9 @@ function appReducer(state, action) {
771
792
  });
772
793
  }
773
794
  case "SET_PARAGRAPH_ALIGNMENT": {
774
- const doc = wordprocessingDocument(state);
795
+ const doc = styledWordprocessingDocument(state);
775
796
  if (doc === void 0) return wrongDocument(state, "a docx or odt document");
776
- const paragraph = paragraphAt(doc, action.blockIndex);
797
+ const paragraph = doc.editor.paragraphs()[action.blockIndex];
777
798
  if (paragraph === void 0) return withStatus(state, "warning", `There is no paragraph at index ${action.blockIndex}`);
778
799
  return mutate(state, doc, () => {
779
800
  paragraph.alignment = action.alignment;
@@ -781,7 +802,7 @@ function appReducer(state, action) {
781
802
  }
782
803
  case "APPEND_RUN": {
783
804
  const doc = wordprocessingDocument(state);
784
- if (doc === void 0) return wrongDocument(state, "a docx or odt document");
805
+ if (doc === void 0) return wrongDocument(state, "a docx, odt or markdown document");
785
806
  const paragraph = paragraphAt(doc, action.blockIndex);
786
807
  if (paragraph === void 0) return withStatus(state, "warning", `There is no paragraph at index ${action.blockIndex}`);
787
808
  return mutate(state, doc, () => {
@@ -797,50 +818,60 @@ function appReducer(state, action) {
797
818
  case "TOGGLE_RUN_ITALIC": return withRun(state, action.blockIndex, action.runIndex, (run) => {
798
819
  run.italic = !run.italic;
799
820
  });
800
- case "TOGGLE_RUN_UNDERLINE": return withRun(state, action.blockIndex, action.runIndex, (run) => {
821
+ case "TOGGLE_RUN_UNDERLINE": return withStyledRun(state, action.blockIndex, action.runIndex, (run) => {
801
822
  run.underline = !run.underline;
802
823
  });
803
- case "SET_RUN_COLOR": return withRun(state, action.blockIndex, action.runIndex, (run) => {
824
+ case "SET_RUN_COLOR": return withStyledRun(state, action.blockIndex, action.runIndex, (run) => {
804
825
  run.color = action.color;
805
826
  });
806
- case "SET_RUN_FONT_FAMILY": return withRun(state, action.blockIndex, action.runIndex, (run) => {
827
+ case "SET_RUN_FONT_FAMILY": return withStyledRun(state, action.blockIndex, action.runIndex, (run) => {
807
828
  run.fontFamily = action.fontFamily;
808
829
  });
809
- case "SET_RUN_FONT_SIZE": return withRun(state, action.blockIndex, action.runIndex, (run) => {
830
+ case "SET_RUN_FONT_SIZE": return withStyledRun(state, action.blockIndex, action.runIndex, (run) => {
810
831
  run.sizePt = action.sizePt;
811
832
  });
812
833
  case "APPEND_TABLE": {
813
834
  const doc = wordprocessingDocument(state);
814
- if (doc === void 0) return wrongDocument(state, "a docx or odt document");
815
- return mutateGuarded(state, doc, () => {
835
+ if (doc === void 0) return wrongDocument(state, "a docx, odt or markdown document");
836
+ let mergeUnsupported = false;
837
+ const nextState = mutateGuarded(state, doc, () => {
816
838
  const table = doc.editor.body.appendTable({
817
839
  rows: action.rows,
818
840
  columns: action.columns
819
841
  });
820
- if (action.merge !== void 0) table.mergeCells(action.merge.startRow, action.merge.startColumn, action.merge.rowSpan, action.merge.colSpan);
842
+ if (action.merge === void 0) return;
843
+ if (!("mergeCells" in table)) {
844
+ mergeUnsupported = true;
845
+ return;
846
+ }
847
+ table.mergeCells(action.merge.startRow, action.merge.startColumn, action.merge.rowSpan, action.merge.colSpan);
821
848
  });
849
+ return mergeUnsupported ? withStatus(nextState, "warning", "Markdown tables do not support merged cells -- the table was created without merging") : nextState;
822
850
  }
823
851
  case "MERGE_TABLE_CELLS": {
824
852
  const doc = wordprocessingDocument(state);
825
- if (doc === void 0) return wrongDocument(state, "a docx or odt document");
853
+ if (doc === void 0) return wrongDocument(state, "a docx, odt or markdown document");
826
854
  const table = tableAt(doc, action.tableIndex);
827
855
  if (table === void 0) return withStatus(state, "warning", `There is no table at index ${action.tableIndex}`);
856
+ if (!("mergeCells" in table)) return withStatus(state, "warning", "Markdown tables do not support merged cells");
828
857
  return mutateGuarded(state, doc, () => {
829
858
  table.mergeCells(action.startRow, action.startColumn, action.rowSpan, action.colSpan);
830
859
  });
831
860
  }
832
861
  case "SET_TABLE_CELL_TEXT": {
833
862
  const doc = wordprocessingDocument(state);
834
- if (doc === void 0) return wrongDocument(state, "a docx or odt document");
863
+ if (doc === void 0) return wrongDocument(state, "a docx, odt or markdown document");
835
864
  const table = tableAt(doc, action.tableIndex);
836
865
  if (table === void 0) return withStatus(state, "warning", `There is no table at index ${action.tableIndex}`);
866
+ const cell = tableCellAt(table, action.row, action.column);
867
+ if (cell === void 0) return withStatus(state, "warning", `There is no cell at row ${action.row}, column ${action.column} of table ${action.tableIndex}`);
837
868
  return mutate(state, doc, () => {
838
- setCellText(table.cell(action.row, action.column), action.text);
869
+ setCellText(cell, action.text);
839
870
  });
840
871
  }
841
872
  case "ADD_LIST_ITEM": {
842
873
  const doc = wordprocessingDocument(state);
843
- if (doc === void 0) return wrongDocument(state, "a docx or odt document");
874
+ if (doc === void 0) return wrongDocument(state, "a docx, odt or markdown document");
844
875
  if (doc.format === "odt") {
845
876
  const list = doc.editor.lists()[action.blockIndex];
846
877
  if (list === void 0) return withStatus(state, "warning", `There is no list at index ${action.blockIndex}`);
@@ -859,7 +890,7 @@ function appReducer(state, action) {
859
890
  }
860
891
  case "SET_LIST_ITEM_TEXT": {
861
892
  const doc = wordprocessingDocument(state);
862
- if (doc === void 0) return wrongDocument(state, "a docx or odt document");
893
+ if (doc === void 0) return wrongDocument(state, "a docx, odt or markdown document");
863
894
  if (doc.format !== "odt") return wrongDocument(state, "an odt document (lists are an odt-only concept)");
864
895
  const list = doc.editor.lists()[action.blockIndex];
865
896
  if (list === void 0) return withStatus(state, "warning", `There is no list at index ${action.blockIndex}`);
@@ -871,16 +902,16 @@ function appReducer(state, action) {
871
902
  }
872
903
  case "ADD_LIST": {
873
904
  const doc = wordprocessingDocument(state);
874
- if (doc === void 0) return wrongDocument(state, "a docx or odt document");
905
+ if (doc === void 0) return wrongDocument(state, "a docx, odt or markdown document");
875
906
  if (doc.format !== "odt") return wrongDocument(state, "an odt document (lists are an odt-only concept)");
876
907
  return mutate(state, doc, () => {
877
908
  doc.editor.body.appendList();
878
909
  });
879
910
  }
880
911
  case "INSERT_PARAGRAPH_IMAGE": {
881
- const doc = wordprocessingDocument(state);
912
+ const doc = styledWordprocessingDocument(state);
882
913
  if (doc === void 0) return wrongDocument(state, "a docx or odt document");
883
- const paragraph = paragraphAt(doc, action.blockIndex);
914
+ const paragraph = doc.editor.paragraphs()[action.blockIndex];
884
915
  if (paragraph === void 0) return withStatus(state, "warning", `There is no paragraph at index ${action.blockIndex}`);
885
916
  return mutate(state, doc, () => {
886
917
  paragraph.insertImageAfter({
@@ -1259,11 +1290,6 @@ function appReducer(state, action) {
1259
1290
  item.widthPt = action.widthPt;
1260
1291
  item.heightPt = action.heightPt;
1261
1292
  });
1262
- case "SET_MARKDOWN_SOURCE": {
1263
- const doc = markdownDocument(state);
1264
- if (doc === void 0) return wrongDocument(state, "a markdown document");
1265
- return mutateMarkdown(state, doc, action.source);
1266
- }
1267
1293
  case "APPEND_DIAGNOSTIC": return {
1268
1294
  ...state,
1269
1295
  diagnostics: [...state.diagnostics, action.diagnostic]
@@ -1297,7 +1323,7 @@ function appReducer(state, action) {
1297
1323
  if (snapshot === void 0) return withStatus(state, "info", "There is nothing to undo");
1298
1324
  const restored = doc.format === "markdown" ? {
1299
1325
  ...doc,
1300
- source: decodeMarkdownText(snapshot)
1326
+ editor: openMarkdown(decodeMarkdownText(snapshot))
1301
1327
  } : reopenEditable(doc, snapshot);
1302
1328
  return withStatus({
1303
1329
  ...state,
@@ -1413,6 +1439,11 @@ const COMMANDS = [
1413
1439
  usage: ":undo",
1414
1440
  description: "Undo the last change"
1415
1441
  },
1442
+ {
1443
+ name: "view-source",
1444
+ usage: ":view-source",
1445
+ description: "Compare a markdown document as opened vs. as it will save now"
1446
+ },
1416
1447
  {
1417
1448
  name: "help",
1418
1449
  usage: ":help",
@@ -1531,7 +1562,12 @@ async function runCommand(line, state, dispatch) {
1531
1562
  dispatch({
1532
1563
  type: "OPEN_FILE_SUCCESS",
1533
1564
  path,
1534
- doc: await openDocumentAtPath(path)
1565
+ doc: await openDocumentAtPath(path, { onDiagnostic: (diagnostic) => {
1566
+ dispatch({
1567
+ type: "APPEND_DIAGNOSTIC",
1568
+ diagnostic
1569
+ });
1570
+ } })
1535
1571
  });
1536
1572
  } catch (error) {
1537
1573
  dispatch({
@@ -1548,6 +1584,16 @@ async function runCommand(line, state, dispatch) {
1548
1584
  case "undo":
1549
1585
  dispatch({ type: "UNDO" });
1550
1586
  return;
1587
+ case "view-source":
1588
+ if (doc?.format !== "markdown") {
1589
+ warn(dispatch, ":view-source only applies to an open markdown document");
1590
+ return;
1591
+ }
1592
+ dispatch({
1593
+ type: "PUSH_SCREEN",
1594
+ screen: { kind: "viewSource" }
1595
+ });
1596
+ return;
1551
1597
  case "help":
1552
1598
  dispatch({
1553
1599
  type: "OPEN_OVERLAY",
@@ -2218,9 +2264,12 @@ function parseNumberField(raw, fallback) {
2218
2264
  }
2219
2265
  //#endregion
2220
2266
  //#region src/tui/screens/shared/paragraph-family.tsx
2267
+ function supportsRunStyleExtras(run) {
2268
+ return "underline" in run;
2269
+ }
2221
2270
  function paragraphFamilyDocument(openDocument) {
2222
2271
  if (openDocument === void 0) return;
2223
- return openDocument.format === "docx" || openDocument.format === "odt" ? openDocument : void 0;
2272
+ return openDocument.format === "docx" || openDocument.format === "odt" || openDocument.format === "markdown" ? openDocument : void 0;
2224
2273
  }
2225
2274
  function liveParagraphAt(doc, blockIndex) {
2226
2275
  return doc.editor.paragraphs()[blockIndex];
@@ -2746,14 +2795,17 @@ function ParagraphRunsView(props) {
2746
2795
  dimColor: true,
2747
2796
  children: "(no runs -- press 'a' to append one)"
2748
2797
  });
2749
- return /* @__PURE__ */ jsx(Box, { children: props.runs.map((run, index) => /* @__PURE__ */ jsx(Text, {
2750
- bold: run.bold,
2751
- italic: run.italic,
2752
- underline: run.underline,
2753
- color: run.color === void 0 ? void 0 : layoutColorToHex(run.color),
2754
- inverse: index === props.selectedRunIndex,
2755
- children: run.text.length === 0 ? "<empty run>" : run.text
2756
- }, index)) });
2798
+ return /* @__PURE__ */ jsx(Box, { children: props.runs.map((run, index) => {
2799
+ const styled = supportsRunStyleExtras(run);
2800
+ return /* @__PURE__ */ jsx(Text, {
2801
+ bold: run.bold,
2802
+ italic: run.italic,
2803
+ underline: styled && run.underline,
2804
+ color: styled && run.color !== void 0 ? layoutColorToHex(run.color) : void 0,
2805
+ inverse: index === props.selectedRunIndex,
2806
+ children: run.text.length === 0 ? "<empty run>" : run.text
2807
+ }, index);
2808
+ }) });
2757
2809
  }
2758
2810
  function ParagraphDetailScreen() {
2759
2811
  const state = useAppState();
@@ -2826,7 +2878,7 @@ function ParagraphDetailScreen() {
2826
2878
  });
2827
2879
  return;
2828
2880
  }
2829
- if (input === "I") {
2881
+ if (input === "I" && doc?.format !== "markdown") {
2830
2882
  setImageWizardOpen(true);
2831
2883
  return;
2832
2884
  }
@@ -2851,6 +2903,7 @@ function ParagraphDetailScreen() {
2851
2903
  });
2852
2904
  return;
2853
2905
  }
2906
+ if (!supportsRunStyleExtras(selectedRun)) return;
2854
2907
  if (input === "u") {
2855
2908
  dispatch({
2856
2909
  type: "TOGGLE_RUN_UNDERLINE",
@@ -2875,7 +2928,7 @@ function ParagraphDetailScreen() {
2875
2928
  });
2876
2929
  if (doc === void 0) return /* @__PURE__ */ jsx(Text, {
2877
2930
  color: "red",
2878
- children: "ParagraphDetailScreen requires an open docx or odt document."
2931
+ children: "ParagraphDetailScreen requires an open docx, odt or markdown document."
2879
2932
  });
2880
2933
  if (paragraph === void 0) return /* @__PURE__ */ jsxs(Text, {
2881
2934
  color: "red",
@@ -2962,7 +3015,7 @@ function ParagraphDetailScreen() {
2962
3015
  placeholder: "e.g. 12",
2963
3016
  onChange: setFontSizeInput,
2964
3017
  onSubmit: (value) => {
2965
- const sizePt = parseNumberField(value, selectedRun?.sizePt ?? DEFAULT_RUN_SIZE_PT);
3018
+ const sizePt = parseNumberField(value, selectedRun !== void 0 && supportsRunStyleExtras(selectedRun) ? selectedRun.sizePt ?? DEFAULT_RUN_SIZE_PT : DEFAULT_RUN_SIZE_PT);
2966
3019
  if (sizePt <= 0) dispatch({
2967
3020
  type: "SET_STATUS",
2968
3021
  severity: "warning",
@@ -3015,7 +3068,12 @@ function ParagraphDetailScreen() {
3015
3068
  /* @__PURE__ */ jsxs(Text, {
3016
3069
  dimColor: true,
3017
3070
  children: [
3018
- "<- / -> move, Enter edit text, b/i/u toggle, c colour, f font, s size, a append run, I image",
3071
+ "<- / -> move, Enter edit text, b/i",
3072
+ doc.format === "markdown" ? "" : "/u",
3073
+ " toggle",
3074
+ doc.format === "markdown" ? "" : ", c colour, f font, s size",
3075
+ ", a append run",
3076
+ doc.format === "markdown" ? "" : ", I image",
3019
3077
  doc.format === "docx" ? ", m formula" : "",
3020
3078
  ", Esc back"
3021
3079
  ]
@@ -3050,7 +3108,7 @@ function RunEditorScreen() {
3050
3108
  });
3051
3109
  if (doc === void 0) return /* @__PURE__ */ jsx(Text, {
3052
3110
  color: "red",
3053
- children: "RunEditorScreen requires an open docx or odt document."
3111
+ children: "RunEditorScreen requires an open docx, odt or markdown document."
3054
3112
  });
3055
3113
  const paragraph = liveParagraphAt(doc, screen.blockIndex);
3056
3114
  if (paragraph === void 0) return /* @__PURE__ */ jsxs(Text, {
@@ -3209,7 +3267,7 @@ function TableViewScreen() {
3209
3267
  });
3210
3268
  if (doc === void 0) return /* @__PURE__ */ jsx(Text, {
3211
3269
  color: "red",
3212
- children: "TableViewScreen requires an open docx or odt document."
3270
+ children: "TableViewScreen requires an open docx, odt or markdown document."
3213
3271
  });
3214
3272
  if (table === void 0) return /* @__PURE__ */ jsxs(Text, {
3215
3273
  color: "red",
@@ -3283,7 +3341,7 @@ function TableCellDetailScreen() {
3283
3341
  });
3284
3342
  if (doc === void 0) return /* @__PURE__ */ jsx(Text, {
3285
3343
  color: "red",
3286
- children: "TableCellDetailScreen requires an open docx or odt document."
3344
+ children: "TableCellDetailScreen requires an open docx, odt or markdown document."
3287
3345
  });
3288
3346
  if (cell === void 0) return /* @__PURE__ */ jsxs(Text, {
3289
3347
  color: "red",
@@ -3430,123 +3488,67 @@ function DocxExtrasScreen() {
3430
3488
  });
3431
3489
  }
3432
3490
  //#endregion
3433
- //#region src/tui/screens/editors/markdown/shared.ts
3434
- function requireMarkdownDocument(openDocument) {
3435
- if (openDocument?.format !== "markdown") throw new Error("A markdown editing screen rendered without an open markdown document; the app router only reaches this screen group from markdownLineList, which is only ever the root screen of an open markdown document.");
3436
- return openDocument;
3437
- }
3438
- //#endregion
3439
- //#region src/tui/screens/editors/markdown/line-list.tsx
3440
- function MarkdownLineListScreen() {
3441
- const state = useAppState();
3442
- const dispatch = useAppDispatch();
3443
- const doc = requireMarkdownDocument(state.openDocument);
3444
- const query = state.searchQuery.trim().toLowerCase();
3445
- const allLines = doc.source.split("\n").map((line, lineIndex) => ({
3446
- line,
3447
- lineIndex
3448
- }));
3449
- const lines = query === "" ? allLines : allLines.filter((entry) => entry.line.toLowerCase().includes(query));
3450
- const { selectedIndex } = useNavigationInput({
3451
- itemCount: lines.length,
3452
- onSelect: (index) => {
3453
- const entry = lines[index];
3454
- if (entry === void 0) return;
3455
- dispatch({
3456
- type: "PUSH_SCREEN",
3457
- screen: {
3458
- kind: "markdownLineEditor",
3459
- lineIndex: entry.lineIndex
3460
- }
3461
- });
3462
- },
3463
- onBack: () => {
3464
- dispatch({ type: "POP_SCREEN" });
3465
- },
3466
- isActive: !anyOverlayOpen(state)
3467
- });
3468
- return /* @__PURE__ */ jsxs(Box, {
3469
- flexDirection: "column",
3470
- children: [/* @__PURE__ */ jsxs(Text, {
3471
- bold: true,
3472
- children: [
3473
- "Lines (",
3474
- lines.length,
3475
- " of ",
3476
- allLines.length,
3477
- ")"
3478
- ]
3479
- }), /* @__PURE__ */ jsx(ListView, {
3480
- items: lines,
3481
- selectedIndex,
3482
- emptyMessage: query === "" ? "This document has no lines." : `No lines match "${state.searchQuery}".`,
3483
- renderItem: ({ line, lineIndex }, isSelected) => /* @__PURE__ */ jsxs(Text, {
3484
- color: isSelected ? "cyan" : void 0,
3485
- inverse: isSelected,
3486
- children: [
3487
- lineIndex + 1,
3488
- ": ",
3489
- line === "" ? "(blank)" : line
3490
- ]
3491
- })
3492
- })]
3493
- });
3494
- }
3495
- //#endregion
3496
- //#region src/tui/screens/editors/markdown/line-editor.tsx
3497
- function MarkdownLineEditorScreen() {
3491
+ //#region src/tui/screens/editors/markdown/view-source.tsx
3492
+ function MarkdownViewSourceScreen() {
3498
3493
  const state = useAppState();
3499
3494
  const dispatch = useAppDispatch();
3500
3495
  const screen = currentScreen(state);
3501
3496
  const doc = state.openDocument;
3502
- if (screen.kind !== "markdownLineEditor") return /* @__PURE__ */ jsx(Text, {
3497
+ useInput((_input, key) => {
3498
+ if (key.escape) dispatch({ type: "POP_SCREEN" });
3499
+ }, { isActive: !anyOverlayOpen(state) && screen.kind === "viewSource" });
3500
+ if (screen.kind !== "viewSource") return /* @__PURE__ */ jsx(Text, {
3503
3501
  color: "red",
3504
- children: "MarkdownLineEditorScreen rendered outside a markdownLineEditor screen."
3502
+ children: "MarkdownViewSourceScreen rendered outside a viewSource screen."
3505
3503
  });
3506
3504
  if (doc?.format !== "markdown") return /* @__PURE__ */ jsx(Text, {
3507
3505
  color: "red",
3508
- children: "MarkdownLineEditorScreen requires an open markdown document."
3509
- });
3510
- const lines = doc.source.split("\n");
3511
- const line = lines[screen.lineIndex];
3512
- if (line === void 0) return /* @__PURE__ */ jsxs(Text, {
3513
- color: "red",
3514
- children: [
3515
- "There is no line at index ",
3516
- screen.lineIndex,
3517
- "."
3518
- ]
3506
+ children: "view-source requires an open markdown document."
3519
3507
  });
3508
+ const asItWillSaveNow = doc.editor.toMarkdownText();
3520
3509
  return /* @__PURE__ */ jsxs(Box, {
3521
3510
  flexDirection: "column",
3522
3511
  children: [
3523
- /* @__PURE__ */ jsxs(Text, {
3512
+ /* @__PURE__ */ jsx(Text, {
3524
3513
  bold: true,
3525
- children: ["Edit line ", screen.lineIndex + 1]
3514
+ children: "As opened"
3526
3515
  }),
3527
- /* @__PURE__ */ jsx(RunTextEditor, {
3528
- initialText: line,
3529
- onCommit: (text) => {
3530
- const nextLines = [...lines];
3531
- nextLines[screen.lineIndex] = text;
3532
- dispatch({
3533
- type: "SET_MARKDOWN_SOURCE",
3534
- source: nextLines.join("\n")
3535
- });
3536
- dispatch({ type: "POP_SCREEN" });
3537
- },
3538
- onCancel: () => {
3539
- dispatch({ type: "POP_SCREEN" });
3540
- }
3516
+ /* @__PURE__ */ jsx(Text, { children: doc.originalText ?? "(this document was created fresh, with no original text to compare against)" }),
3517
+ /* @__PURE__ */ jsx(Text, {
3518
+ bold: true,
3519
+ children: "As it will save right now"
3541
3520
  }),
3521
+ /* @__PURE__ */ jsx(Text, { children: asItWillSaveNow }),
3542
3522
  /* @__PURE__ */ jsx(Text, {
3543
3523
  dimColor: true,
3544
- children: "Enter to commit, Esc to discard"
3524
+ children: "Esc back"
3545
3525
  })
3546
3526
  ]
3547
3527
  });
3548
3528
  }
3549
3529
  //#endregion
3530
+ //#region src/tui/screens/editors/markdown/index.tsx
3531
+ function MarkdownBodyListScreen() {
3532
+ const state = useAppState();
3533
+ const dispatch = useAppDispatch();
3534
+ const doc = state.openDocument;
3535
+ if (doc?.format !== "markdown") return /* @__PURE__ */ jsxs(Text, {
3536
+ color: "red",
3537
+ children: [
3538
+ "MarkdownBodyListScreen requires an open markdown document, found ",
3539
+ doc === void 0 ? "no open document" : doc.format,
3540
+ "."
3541
+ ]
3542
+ });
3543
+ const adapter = createParagraphFamilyAdapter({
3544
+ formatLabel: "markdown",
3545
+ paragraphs: () => doc.editor.paragraphs(),
3546
+ tables: () => doc.editor.tables(),
3547
+ dispatch
3548
+ });
3549
+ return /* @__PURE__ */ jsx(ParagraphFamilyBodyList, { adapter });
3550
+ }
3551
+ //#endregion
3550
3552
  //#region src/tui/screens/editors/odb/shared.ts
3551
3553
  function requireOdbDocument(openDocument) {
3552
3554
  if (openDocument?.format !== "odb") throw new Error("An .odb browsing screen rendered without an open .odb document; the app router only reaches this screen group from odbTableList, which is only ever the root screen of an open .odb document.");
@@ -3806,40 +3808,13 @@ const REPORT_RENDER_TARGET_FORMATS = {
3806
3808
  function isReportRenderTargetFormat(format) {
3807
3809
  return format !== void 0 && format in REPORT_RENDER_TARGET_FORMATS;
3808
3810
  }
3809
- function renderReportBytes(content, format, fonts, options) {
3810
- if (format === "docx") return encodePackage(buildDocxPackage(content));
3811
- if (format === "odt") return encodePackage$1(buildOdtPackage(content));
3812
- if (content.kind !== "wordprocessing") throw new Error("readOdbReportContent returned a non-wordprocessing ContentDocument");
3813
- const registry = createFontRegistry({
3814
- fonts,
3815
- onSubstitution: (substitution) => {
3816
- const requested = `${substitution.requestedFamily}${substitution.requestedBold ? " bold" : ""}${substitution.requestedItalic ? " italic" : ""}`;
3817
- options.onDiagnostic({
3818
- severity: "info",
3819
- message: `No "${requested}" face available; drew it in "${substitution.resolvedFamily}"`
3820
- });
3821
- }
3822
- });
3823
- const { document: layout, formulas } = convertWordprocessingToLayout(content, { measurer: createFontMeasurer(registry) });
3824
- return writePdf(layout, {
3825
- signal: options.signal,
3826
- onSubstitution: (substitution, context) => {
3827
- options.onDiagnostic({
3828
- severity: "info",
3829
- message: `Substituted "${substitution.to}" for "${substitution.from}"`,
3830
- pageIndex: context.pageIndex
3831
- });
3832
- },
3833
- formulas,
3834
- fonts: registry
3835
- });
3836
- }
3837
3811
  async function renderOdbReportTo(doc, destinationPath, options) {
3838
3812
  const format = detectFormat(destinationPath);
3839
3813
  if (!isReportRenderTargetFormat(format)) throw new Error(`Cannot tell whether to render "${options.reportName}" as docx, odt, or pdf from '${destinationPath}' -- give the destination one of those three extensions`);
3840
3814
  const fonts = await loadProvidedFonts(options.fontFiles ?? [], { signal: options.signal });
3841
3815
  const pkg = decodePackage$1(new Uint8Array(await readFile(doc.path)));
3842
- const bytes = renderReportBytes(readOdbReportContent(pkg, { report: options.reportName }), format, fonts, options);
3816
+ const content = readOdbReportContent(pkg, { report: options.reportName });
3817
+ const bytes = format === "docx" ? odbReportToDocx(content) : format === "odt" ? odbReportToOdt(content) : odbReportToPdf(content, toPdfOptions(options, fonts));
3843
3818
  await writeFile(destinationPath, bytes);
3844
3819
  }
3845
3820
  //#endregion
@@ -8545,7 +8520,12 @@ function FilePickerScreen() {
8545
8520
  function openAtPath(path) {
8546
8521
  (async () => {
8547
8522
  try {
8548
- const doc = await openDocumentAtPath(path);
8523
+ const doc = await openDocumentAtPath(path, { onDiagnostic: (diagnostic) => {
8524
+ dispatch({
8525
+ type: "APPEND_DIAGNOSTIC",
8526
+ diagnostic
8527
+ });
8528
+ } });
8549
8529
  dispatch({
8550
8530
  type: "OPEN_FILE_SUCCESS",
8551
8531
  path,
@@ -8896,7 +8876,7 @@ function metadataFor(doc) {
8896
8876
  case "odp": return readOdpContent(doc.editor.toPackage()).metadata;
8897
8877
  case "ods": return readOdsContent(doc.editor.toPackage()).metadata;
8898
8878
  case "odg": return readOdgContent(doc.editor.toPackage()).metadata;
8899
- case "markdown": return readMarkdownContent(doc.source).metadata;
8879
+ case "markdown": return readMarkdownContent(doc.editor.toMarkdownText()).metadata;
8900
8880
  case "pdf":
8901
8881
  case "xlsx": return doc.layout.metadata;
8902
8882
  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.");
@@ -8961,7 +8941,9 @@ function ScreenBody({ screen }) {
8961
8941
  case "launcher": return /* @__PURE__ */ jsx(LauncherScreen, {});
8962
8942
  case "filePicker": return /* @__PURE__ */ jsx(FilePickerScreen, {});
8963
8943
  case "newDocumentPicker": return /* @__PURE__ */ jsx(NewDocumentPickerScreen, {});
8964
- case "bodyList": return format === "odt" ? /* @__PURE__ */ jsx(OdtBodyListScreen, {}) : /* @__PURE__ */ jsx(DocxBodyListScreen, {});
8944
+ case "bodyList":
8945
+ if (format === "odt") return /* @__PURE__ */ jsx(OdtBodyListScreen, {});
8946
+ return format === "markdown" ? /* @__PURE__ */ jsx(MarkdownBodyListScreen, {}) : /* @__PURE__ */ jsx(DocxBodyListScreen, {});
8965
8947
  case "docxExtras": return /* @__PURE__ */ jsx(DocxExtrasScreen, {});
8966
8948
  case "paragraphDetail": return /* @__PURE__ */ jsx(ParagraphDetailScreen, {});
8967
8949
  case "runEditor": return /* @__PURE__ */ jsx(RunEditorScreen, {});
@@ -8987,8 +8969,7 @@ function ScreenBody({ screen }) {
8987
8969
  case "odbReportList": return /* @__PURE__ */ jsx(OdbReportListScreen, {});
8988
8970
  case "odbReportDetail": return /* @__PURE__ */ jsx(OdbReportDetailScreen, {});
8989
8971
  case "odbReportRender": return /* @__PURE__ */ jsx(OdbReportRenderScreen, {});
8990
- case "markdownLineList": return /* @__PURE__ */ jsx(MarkdownLineListScreen, {});
8991
- case "markdownLineEditor": return /* @__PURE__ */ jsx(MarkdownLineEditorScreen, {});
8972
+ case "viewSource": return /* @__PURE__ */ jsx(MarkdownViewSourceScreen, {});
8992
8973
  case "pdfPageList": return /* @__PURE__ */ jsx(PdfPageListScreen, {});
8993
8974
  case "pdfPageItems": return /* @__PURE__ */ jsx(PdfPageItemsScreen, {});
8994
8975
  case "pdfItemDetail": return /* @__PURE__ */ jsx(PdfItemDetailScreen, {});
@@ -9034,7 +9015,12 @@ function AppShell({ startPath }) {
9034
9015
  let cancelled = false;
9035
9016
  (async () => {
9036
9017
  try {
9037
- const doc = await openDocumentAtPath(startPath);
9018
+ const doc = await openDocumentAtPath(startPath, { onDiagnostic: (diagnostic) => {
9019
+ dispatch({
9020
+ type: "APPEND_DIAGNOSTIC",
9021
+ diagnostic
9022
+ });
9023
+ } });
9038
9024
  if (!cancelled) dispatch({
9039
9025
  type: "OPEN_FILE_SUCCESS",
9040
9026
  path: startPath,