document-cli 1.4.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
- import { d as formatToExtension, f as inferFormatFromExtension, i as formatOdbReportLines, n as describeOdbReport, o as formatDocxExtrasLines, r as formatOdbFormLines, s as readInput, t as describeOdbForm, u as loadProvidedFonts } from "./odb-structure-DkfNyHGR.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-Du1w_hOU.js";
2
2
  import { readFile, writeFile } from "node:fs/promises";
3
- import { buildDocxPackage, buildOdtPackage, convertWordprocessingToLayout, createDocx, createFontMeasurer, createFontRegistry, createOdg, createOdp, createOds, createOdt, createPptx, decodeMarkdownText, docxToPdf, encodeMarkdownText, encodePackage, hsqldbCellDisplayText, markdownToPdf, odgToPdf, odpToPdf, odsToPdf, odtToPdf, openDocx, openOdg, openOdp, openOds, openOdt, openPptx, pptxToPdf, readDocxExtras, readOdbForms, readOdbReportContent, readOdbReports, readOdbTables, readOdgContent, readOdsContent, readPdf, rgbHexToColor, writePdf, xlsxToPdf } from "documents.js";
3
+ import { buildDocxPackage, buildOdtPackage, convertWordprocessingToLayout, createDocx, createFontMeasurer, createFontRegistry, createOdg, createOdp, createOds, createOdt, createPptx, decodeMarkdownText, docxToPdf, encodeMarkdownText, encodePackage, hsqldbCellDisplayText, markdownToPdf, odgToPdf, odpToPdf, odsToPdf, odtToPdf, openDocx, openOdg, openOdp, openOds, openOdt, openPptx, pptxToPdf, readDocxContent, readDocxExtras, readMarkdownContent, readOdbForms, readOdbReportContent, readOdbReports, readOdbTables, readOdgContent, readOdpContent, readOdsContent, readOdtContent, readPdf, readPptxContent, rgbHexToColor, writePdf, xlsxToPdf } from "documents.js";
4
4
  import { basename, dirname, extname, join } from "node:path";
5
5
  import { cellReference, columnIndexToLetters, decodePackage as decodePackage$1, encodePackage as encodePackage$1 } from "odf.js";
6
6
  import { readdirSync } from "node:fs";
@@ -219,7 +219,8 @@ function selectionKeyFor(screen) {
219
219
  case "pdfPageList":
220
220
  case "exportOptions":
221
221
  case "saveAsPrompt":
222
- case "markdownLineList": return screen.kind;
222
+ case "markdownLineList":
223
+ case "metadata": return screen.kind;
223
224
  case "filePicker": return `filePicker:${screen.purpose}:${screen.cwd}`;
224
225
  case "paragraphDetail":
225
226
  case "tableView":
@@ -489,10 +490,6 @@ function shapeAt(doc, containerIndex, shapeIndex) {
489
490
  if (doc.format === "odg") return doc.editor.pages()[containerIndex]?.shapes()[shapeIndex];
490
491
  return doc.editor.slides()[containerIndex]?.shapes()[shapeIndex];
491
492
  }
492
- function rotatableShapeAt(doc, containerIndex, shapeIndex) {
493
- if (doc.format === "odg") return doc.editor.pages()[containerIndex]?.shapes()[shapeIndex];
494
- return doc.editor.slides()[containerIndex]?.shapes()[shapeIndex];
495
- }
496
493
  function sheetAt(doc, sheetIndex) {
497
494
  return doc.editor.sheets()[sheetIndex];
498
495
  }
@@ -525,8 +522,8 @@ function withSheet(state, sheetIndex, apply) {
525
522
  apply(sheet);
526
523
  });
527
524
  }
528
- function setCellText(cell, text) {
529
- const paragraph = cell.paragraphs()[0] ?? cell.appendParagraph();
525
+ function setTextContainerText(container, text) {
526
+ const paragraph = container.paragraphs()[0] ?? container.appendParagraph();
530
527
  const runs = paragraph.runs();
531
528
  const firstRun = runs[0];
532
529
  if (firstRun === void 0) {
@@ -536,6 +533,9 @@ function setCellText(cell, text) {
536
533
  firstRun.text = text;
537
534
  for (const extra of runs.slice(1)) extra.remove();
538
535
  }
536
+ function setCellText(cell, text) {
537
+ setTextContainerText(cell, text);
538
+ }
539
539
  function appReducer(state, action) {
540
540
  switch (action.type) {
541
541
  case "PUSH_SCREEN": return {
@@ -720,6 +720,18 @@ function appReducer(state, action) {
720
720
  appended.list = membership;
721
721
  });
722
722
  }
723
+ case "SET_LIST_ITEM_TEXT": {
724
+ const doc = wordprocessingDocument(state);
725
+ if (doc === void 0) return wrongDocument(state, "a docx or odt document");
726
+ if (doc.format !== "odt") return wrongDocument(state, "an odt document (lists are an odt-only concept)");
727
+ const list = doc.editor.lists()[action.blockIndex];
728
+ if (list === void 0) return withStatus(state, "warning", `There is no list at index ${action.blockIndex}`);
729
+ const item = list.items()[action.itemIndex];
730
+ if (item === void 0) return withStatus(state, "warning", `List ${action.blockIndex} has no item at index ${action.itemIndex}`);
731
+ return mutate(state, doc, () => {
732
+ setTextContainerText(item, action.text);
733
+ });
734
+ }
723
735
  case "ADD_SLIDE": {
724
736
  const doc = presentationDocument(state);
725
737
  if (doc === void 0) return wrongDocument(state, "a pptx or odp document");
@@ -792,16 +804,9 @@ function appReducer(state, action) {
792
804
  case "SET_SHAPE_FRAME": return withShape(state, action.containerIndex, action.shapeIndex, (shape) => {
793
805
  shape.frame = action.frame;
794
806
  });
795
- case "SET_SHAPE_ROTATION": {
796
- const doc = shapeHostDocument(state);
797
- if (doc === void 0) return wrongDocument(state, "a pptx, odp or odg document");
798
- if (doc.format === "pptx") return withStatus(state, "warning", "documents.js has no rotation setter for a pptx shape; rotate the shape in odp instead");
799
- const shape = rotatableShapeAt(doc, action.containerIndex, action.shapeIndex);
800
- if (shape === void 0) return withStatus(state, "warning", `There is no shape ${action.shapeIndex} at index ${action.containerIndex}`);
801
- return mutate(state, doc, () => {
802
- shape.rotationDeg = action.rotationDeg;
803
- });
804
- }
807
+ case "SET_SHAPE_ROTATION": return withShape(state, action.containerIndex, action.shapeIndex, (shape) => {
808
+ shape.rotationDeg = action.rotationDeg;
809
+ });
805
810
  case "SET_SLIDE_NOTES": {
806
811
  const doc = presentationDocument(state);
807
812
  if (doc === void 0) return wrongDocument(state, "a pptx or odp document");
@@ -1019,6 +1024,11 @@ const COMMANDS = [
1019
1024
  usage: ":close",
1020
1025
  description: "Close the open document"
1021
1026
  },
1027
+ {
1028
+ name: "undo",
1029
+ usage: ":undo",
1030
+ description: "Undo the last change"
1031
+ },
1022
1032
  {
1023
1033
  name: "help",
1024
1034
  usage: ":help",
@@ -1151,6 +1161,9 @@ async function runCommand(line, state, dispatch) {
1151
1161
  case "close":
1152
1162
  dispatch({ type: "REQUEST_CLOSE" });
1153
1163
  return;
1164
+ case "undo":
1165
+ dispatch({ type: "UNDO" });
1166
+ return;
1154
1167
  case "help":
1155
1168
  dispatch({
1156
1169
  type: "OPEN_OVERLAY",
@@ -1417,6 +1430,10 @@ const GLOBAL_KEYS = [
1417
1430
  keys: "a",
1418
1431
  description: "Append a new item to the current list"
1419
1432
  },
1433
+ {
1434
+ keys: "m",
1435
+ description: "Show the open document's metadata (read-only)"
1436
+ },
1420
1437
  {
1421
1438
  keys: "Ctrl+S",
1422
1439
  description: "Save the open document"
@@ -1425,6 +1442,10 @@ const GLOBAL_KEYS = [
1425
1442
  keys: "Ctrl+W",
1426
1443
  description: "Close the open document"
1427
1444
  },
1445
+ {
1446
+ keys: "Ctrl+Z",
1447
+ description: "Undo the last change"
1448
+ },
1428
1449
  {
1429
1450
  keys: "q / Ctrl+C",
1430
1451
  description: "Quit"
@@ -3014,6 +3035,10 @@ function requireShapeOrVectorDetailScreen(state) {
3014
3035
  if (screen.kind !== "shapeOrVectorDetail") throw new Error(`OdgShapeOrVectorDetailScreen rendered while the top of the stack is '${screen.kind}', not 'shapeOrVectorDetail' -- app.tsx's ScreenBody switch only mounts this component for that screen kind, so this cannot happen without a routing bug.`);
3015
3036
  return screen;
3016
3037
  }
3038
+ function vectorsParityMatch(liveVectors, contentVectors) {
3039
+ if (liveVectors.length !== contentVectors.length) return false;
3040
+ return liveVectors.every((live, index) => live.kind === contentVectors[index]?.kind);
3041
+ }
3017
3042
  function buildPageItems(doc, pageIndex) {
3018
3043
  const page = doc.editor.pages()[pageIndex];
3019
3044
  if (page === void 0) return [];
@@ -3021,9 +3046,12 @@ function buildPageItems(doc, pageIndex) {
3021
3046
  if (content.kind !== "drawing") throw new Error(`readOdgContent(doc.editor.toPackage()) returned a '${content.kind}' ContentDocument for an odg-format open document -- an odg package should always read back as the 'drawing' variant, so this indicates a real inconsistency in documents.js's own reader, not a state this screen should paper over.`);
3022
3047
  const contentPage = content.pages[pageIndex];
3023
3048
  if (contentPage === void 0) throw new Error(`readOdgContent found no page at index ${pageIndex}, but doc.editor.pages() has a page there -- the two read the same live package, so they should always agree on page count.`);
3024
- const vectorItems = contentPage.vectors.map((vector) => ({
3049
+ const liveVectors = page.vectors();
3050
+ const parityOk = vectorsParityMatch(liveVectors, contentPage.vectors);
3051
+ const vectorItems = contentPage.vectors.map((vector, index) => ({
3025
3052
  kind: "vector",
3026
- vector
3053
+ vector,
3054
+ liveVector: parityOk ? liveVectors[index] : void 0
3027
3055
  }));
3028
3056
  const shapeItems = page.shapes().map((shape, index) => {
3029
3057
  return {
@@ -3315,15 +3343,19 @@ function warnVectorIsViewOnly(dispatch, label) {
3315
3343
  dispatch({
3316
3344
  type: "SET_STATUS",
3317
3345
  severity: "info",
3318
- text: `${label} added -- documents.js exposes no way to enumerate an existing odg vector, so it will show in this list read-only and cannot be edited or removed from the TUI.`
3346
+ text: `${label} added -- but this page also has a vector element documents.js's OdgPage.vectors() cannot wrap, so every vector on it (including this one) shows read-only in this list rather than risk pairing the wrong live handle to the wrong row.`
3319
3347
  });
3320
3348
  }
3349
+ function warnIfVectorAddedReadOnly(doc, pageIndex, dispatch, label) {
3350
+ const added = buildPageItems(doc, pageIndex).filter((item) => item.kind === "vector").at(-1);
3351
+ if (added !== void 0 && added.liveVector === void 0) warnVectorIsViewOnly(dispatch, label);
3352
+ }
3321
3353
  function inferImageFormat(path) {
3322
3354
  const extension = path.slice(path.lastIndexOf(".") + 1).toLowerCase();
3323
3355
  if (extension === "png") return "png";
3324
3356
  if (extension === "jpg" || extension === "jpeg") return "jpeg";
3325
3357
  }
3326
- async function applyAddKind(kind, pageIndex, values, dispatch) {
3358
+ async function applyAddKind(kind, pageIndex, doc, values, dispatch) {
3327
3359
  switch (kind) {
3328
3360
  case "rect":
3329
3361
  dispatch({
@@ -3335,7 +3367,7 @@ async function applyAddKind(kind, pageIndex, values, dispatch) {
3335
3367
  stroke: parseStrokeField(requireFieldValue(values, "stroke"))
3336
3368
  }
3337
3369
  });
3338
- warnVectorIsViewOnly(dispatch, "Rectangle");
3370
+ warnIfVectorAddedReadOnly(doc, pageIndex, dispatch, "Rectangle");
3339
3371
  return;
3340
3372
  case "ellipse":
3341
3373
  dispatch({
@@ -3347,7 +3379,7 @@ async function applyAddKind(kind, pageIndex, values, dispatch) {
3347
3379
  stroke: parseStrokeField(requireFieldValue(values, "stroke"))
3348
3380
  }
3349
3381
  });
3350
- warnVectorIsViewOnly(dispatch, "Ellipse");
3382
+ warnIfVectorAddedReadOnly(doc, pageIndex, dispatch, "Ellipse");
3351
3383
  return;
3352
3384
  case "line":
3353
3385
  dispatch({
@@ -3372,7 +3404,7 @@ async function applyAddKind(kind, pageIndex, values, dispatch) {
3372
3404
  }
3373
3405
  }
3374
3406
  });
3375
- warnVectorIsViewOnly(dispatch, "Line");
3407
+ warnIfVectorAddedReadOnly(doc, pageIndex, dispatch, "Line");
3376
3408
  return;
3377
3409
  case "path": {
3378
3410
  const frame = readFrame(values);
@@ -3386,7 +3418,7 @@ async function applyAddKind(kind, pageIndex, values, dispatch) {
3386
3418
  stroke: parseStrokeField(requireFieldValue(values, "stroke"))
3387
3419
  }
3388
3420
  });
3389
- warnVectorIsViewOnly(dispatch, "Path");
3421
+ warnIfVectorAddedReadOnly(doc, pageIndex, dispatch, "Path");
3390
3422
  return;
3391
3423
  }
3392
3424
  case "textbox":
@@ -3515,7 +3547,7 @@ function AddItemFlow(props) {
3515
3547
  fields: fieldsForAddKind(kind),
3516
3548
  onCancel: props.onCancel,
3517
3549
  onComplete: (values) => {
3518
- applyAddKind(kind, props.pageIndex, values, dispatch).then(props.onCreated);
3550
+ applyAddKind(kind, props.pageIndex, props.doc, values, dispatch).then(props.onCreated);
3519
3551
  }
3520
3552
  });
3521
3553
  }
@@ -3569,6 +3601,7 @@ function OdgPageDetailScreen() {
3569
3601
  });
3570
3602
  if (isAdding) return /* @__PURE__ */ jsx(AddItemFlow, {
3571
3603
  pageIndex,
3604
+ doc,
3572
3605
  isActive: !overlayOpen,
3573
3606
  onCancel: () => {
3574
3607
  setIsAdding(false);
@@ -3602,19 +3635,75 @@ function OdgPageDetailScreen() {
3602
3635
  }
3603
3636
  //#endregion
3604
3637
  //#region src/tui/screens/editors/odg/shape-or-vector-detail.tsx
3638
+ function buildVectorRows(vector, liveVector, dispatch) {
3639
+ const rows = [];
3640
+ if (vector.kind !== "line" && liveVector.kind !== "line") {
3641
+ const fillTarget = liveVector;
3642
+ rows.push({
3643
+ label: `Fill: ${vector.fill === void 0 ? "none" : formatColor$1(vector.fill)}`,
3644
+ currentValue: vector.fill === void 0 ? "" : `${vector.fill.r} ${vector.fill.g} ${vector.fill.b}`,
3645
+ commit: (raw) => {
3646
+ dispatch({
3647
+ type: "SET_VECTOR_FILL",
3648
+ vector: fillTarget,
3649
+ fill: parseColorField(raw)
3650
+ });
3651
+ }
3652
+ });
3653
+ }
3654
+ rows.push({
3655
+ label: `Stroke: ${vector.stroke === void 0 ? "none" : `${formatColor$1(vector.stroke.color)} ${formatPt(vector.stroke.widthPt)}pt`}`,
3656
+ currentValue: vector.stroke === void 0 ? "" : `${vector.stroke.color.r} ${vector.stroke.color.g} ${vector.stroke.color.b} ${vector.stroke.widthPt}`,
3657
+ commit: (raw) => {
3658
+ const stroke = parseStrokeField(raw);
3659
+ if (stroke === void 0) {
3660
+ dispatch({
3661
+ type: "SET_STATUS",
3662
+ severity: "warning",
3663
+ text: "A vector stroke cannot be cleared to none through this editor -- enter \"r g b widthPt\" (0-1 colour, pt width) instead"
3664
+ });
3665
+ return;
3666
+ }
3667
+ dispatch({
3668
+ type: "SET_VECTOR_STROKE",
3669
+ vector: liveVector,
3670
+ stroke
3671
+ });
3672
+ }
3673
+ });
3674
+ return rows;
3675
+ }
3605
3676
  function VectorDetail(props) {
3606
- return /* @__PURE__ */ jsxs(Box, {
3677
+ const dispatch = useAppDispatch();
3678
+ const [editingField, setEditingField] = useState(void 0);
3679
+ const [draft, setDraft] = useState("");
3680
+ const { vector, liveVector, isActive } = props;
3681
+ const rows = liveVector === void 0 ? [] : buildVectorRows(vector, liveVector, dispatch);
3682
+ const { selectedIndex } = useNavigationInput({
3683
+ itemCount: rows.length,
3684
+ isActive: isActive && editingField === void 0,
3685
+ onBack: () => {
3686
+ dispatch({ type: "POP_SCREEN" });
3687
+ },
3688
+ onSelect: (index) => {
3689
+ const row = rows[index];
3690
+ if (row === void 0) return;
3691
+ setDraft(row.currentValue);
3692
+ setEditingField(index);
3693
+ }
3694
+ });
3695
+ if (liveVector === void 0) return /* @__PURE__ */ jsxs(Box, {
3607
3696
  flexDirection: "column",
3608
3697
  children: [
3609
3698
  /* @__PURE__ */ jsxs(Text, {
3610
3699
  bold: true,
3611
- children: [vectorKindLabel(props.vector.kind), " (view-only)"]
3700
+ children: [vectorKindLabel(vector.kind), " (view-only)"]
3612
3701
  }),
3613
- /* @__PURE__ */ jsxs(Text, { children: ["Geometry: ", describeVectorGeometry(props.vector)] }),
3614
- /* @__PURE__ */ jsx(Text, { children: describeFillStroke(props.vector) }),
3702
+ /* @__PURE__ */ jsxs(Text, { children: ["Geometry: ", describeVectorGeometry(vector)] }),
3703
+ /* @__PURE__ */ jsx(Text, { children: describeFillStroke(vector) }),
3615
3704
  /* @__PURE__ */ jsx(Text, {
3616
3705
  color: "yellow",
3617
- children: "documents.js's OdgPage has no accessor for an existing rect/ellipse/line/path vector, so there is no live handle to edit or remove this one from the TUI -- open the file in a real ODF editor to change it."
3706
+ children: "This page's live vectors (documents.js's `OdgPage.vectors()`) don't line up one-to-one with what odf.js's own reader found here -- likely a `draw:circle`/`polygon`/`polyline`/`custom-shape` element the live accessor has no wrapper for, sitting alongside a plain rect/ellipse/line/path it does. Rather than risk pairing the wrong live handle to this row, every vector on this page is shown read-only until that mismatch is resolved outside this TUI. Open the file in a real ODF editor to change it."
3618
3707
  }),
3619
3708
  /* @__PURE__ */ jsx(Text, {
3620
3709
  dimColor: true,
@@ -3622,6 +3711,53 @@ function VectorDetail(props) {
3622
3711
  })
3623
3712
  ]
3624
3713
  });
3714
+ if (editingField !== void 0) {
3715
+ const row = rows[editingField];
3716
+ if (row === void 0) throw new Error(`VectorDetail is editing field index ${editingField}, but there are only ${rows.length} rows -- selecting a row always sets editingField to a valid index from that same rows array, so this indicates a bug in that selection.`);
3717
+ return /* @__PURE__ */ jsxs(Box, {
3718
+ flexDirection: "column",
3719
+ borderStyle: "round",
3720
+ paddingX: 1,
3721
+ children: [/* @__PURE__ */ jsx(Text, {
3722
+ bold: true,
3723
+ children: row.label
3724
+ }), /* @__PURE__ */ jsx(TextField, {
3725
+ value: draft,
3726
+ isFocused: true,
3727
+ onChange: setDraft,
3728
+ onCancel: () => {
3729
+ setEditingField(void 0);
3730
+ },
3731
+ onSubmit: (value) => {
3732
+ row.commit(value);
3733
+ setEditingField(void 0);
3734
+ }
3735
+ })]
3736
+ });
3737
+ }
3738
+ return /* @__PURE__ */ jsxs(Box, {
3739
+ flexDirection: "column",
3740
+ children: [
3741
+ /* @__PURE__ */ jsx(Text, {
3742
+ bold: true,
3743
+ children: vectorKindLabel(vector.kind)
3744
+ }),
3745
+ /* @__PURE__ */ jsxs(Text, { children: ["Geometry: ", describeVectorGeometry(vector)] }),
3746
+ /* @__PURE__ */ jsx(ListView, {
3747
+ items: rows,
3748
+ selectedIndex,
3749
+ reservedRows: 5,
3750
+ renderItem: (row, isSelected) => /* @__PURE__ */ jsxs(Text, {
3751
+ color: isSelected ? "cyan" : void 0,
3752
+ children: [isSelected ? "> " : " ", row.label]
3753
+ })
3754
+ }),
3755
+ /* @__PURE__ */ jsx(Text, {
3756
+ dimColor: true,
3757
+ children: "Enter to edit a field, Esc to go back"
3758
+ })
3759
+ ]
3760
+ });
3625
3761
  }
3626
3762
  function ShapeDetail(props) {
3627
3763
  const dispatch = useAppDispatch();
@@ -3791,7 +3927,7 @@ function OdgShapeOrVectorDetailScreen() {
3791
3927
  const item = items[itemIndex];
3792
3928
  useInput((input, key) => {
3793
3929
  if (key.escape || key.leftArrow || input === "h") dispatch({ type: "POP_SCREEN" });
3794
- }, { isActive: !overlayOpen && item?.kind !== "shape" });
3930
+ }, { isActive: !overlayOpen && item === void 0 });
3795
3931
  if (item === void 0) return /* @__PURE__ */ jsxs(Box, {
3796
3932
  flexDirection: "column",
3797
3933
  children: [/* @__PURE__ */ jsxs(Text, {
@@ -3808,7 +3944,11 @@ function OdgShapeOrVectorDetailScreen() {
3808
3944
  children: "Esc to go back"
3809
3945
  })]
3810
3946
  });
3811
- if (item.kind === "vector") return /* @__PURE__ */ jsx(VectorDetail, { vector: item.vector });
3947
+ if (item.kind === "vector") return /* @__PURE__ */ jsx(VectorDetail, {
3948
+ vector: item.vector,
3949
+ liveVector: item.liveVector,
3950
+ isActive: !overlayOpen
3951
+ });
3812
3952
  const vectorCount = items.filter((entry) => entry.kind === "vector").length;
3813
3953
  return /* @__PURE__ */ jsx(ShapeDetail, {
3814
3954
  pageIndex,
@@ -3950,10 +4090,6 @@ function formatRotationDeg(value) {
3950
4090
  return value === void 0 ? "(unset)" : `${Math.round(value * ROTATION_DISPLAY_PRECISION) / ROTATION_DISPLAY_PRECISION}°`;
3951
4091
  }
3952
4092
  function RotationField(props) {
3953
- if (!props.isEditable) return /* @__PURE__ */ jsx(Text, {
3954
- dimColor: true,
3955
- children: "[R] Rotation: not available for pptx shapes"
3956
- });
3957
4093
  if (props.isEditing) return /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, {
3958
4094
  color: "cyan",
3959
4095
  children: "[R] Rotation (deg, blank to unset): "
@@ -3987,7 +4123,6 @@ const FIELD_LABELS = {
3987
4123
  width: "Width (pt)",
3988
4124
  height: "Height (pt)"
3989
4125
  };
3990
- const ROTATION_UNAVAILABLE_MESSAGE = "documents.js has no rotation setter for a pptx shape; rotate the shape in odp instead";
3991
4126
  const POINT_DISPLAY_PRECISION = 100;
3992
4127
  function formatPoints(value) {
3993
4128
  return value === void 0 ? "(unset)" : `${Math.round(value * POINT_DISPLAY_PRECISION) / POINT_DISPLAY_PRECISION}pt`;
@@ -4007,9 +4142,9 @@ function describeFieldValue(key, shape) {
4007
4142
  function roundForDisplay(value) {
4008
4143
  return Math.round(value * POINT_DISPLAY_PRECISION) / POINT_DISPLAY_PRECISION;
4009
4144
  }
4010
- function initialDraftFor(key, shape, rotatableShape) {
4145
+ function initialDraftFor(key, shape) {
4011
4146
  if (key === "rotation") {
4012
- const value = rotatableShape?.rotationDeg;
4147
+ const value = shape.rotationDeg;
4013
4148
  return value === void 0 ? "" : String(roundForDisplay(value));
4014
4149
  }
4015
4150
  if (key === "text") return shape.text;
@@ -4022,10 +4157,9 @@ function initialDraftFor(key, shape, rotatableShape) {
4022
4157
  }
4023
4158
  }
4024
4159
  function FieldRow(props) {
4025
- const { fieldKey, shape, rotatableShape, isOdp, isSelected, isEditing, draft, onDraftChange, onSubmit, onCancel } = props;
4160
+ const { fieldKey, shape, isSelected, isEditing, draft, onDraftChange, onSubmit, onCancel } = props;
4026
4161
  if (fieldKey === "rotation") return /* @__PURE__ */ jsx(RotationField, {
4027
- rotationDeg: rotatableShape?.rotationDeg,
4028
- isEditable: isOdp,
4162
+ rotationDeg: shape.rotationDeg,
4029
4163
  isSelected,
4030
4164
  isEditing,
4031
4165
  draftValue: draft,
@@ -4074,9 +4208,7 @@ function ShapeEditorScreen(props) {
4074
4208
  const overlayOpen = anyOverlayOpen(state);
4075
4209
  const doc = assertPresentationDocument(state.openDocument);
4076
4210
  const { slideIndex, shapeIndex } = props.screen;
4077
- const isOdp = doc.format === "odp";
4078
4211
  const shape = doc.editor.slides()[slideIndex]?.shapes()[shapeIndex];
4079
- const rotatableShape = doc.format === "odp" ? doc.editor.slides()[slideIndex]?.shapes()[shapeIndex] : void 0;
4080
4212
  const [editingField, setEditingField] = useState(void 0);
4081
4213
  const [draft, setDraft] = useState("");
4082
4214
  const { selectedIndex } = useNavigationInput({
@@ -4089,20 +4221,12 @@ function ShapeEditorScreen(props) {
4089
4221
  if (shape === void 0) return;
4090
4222
  const key = FIELD_KEYS[index];
4091
4223
  if (key === void 0) return;
4092
- if (key === "rotation" && !isOdp) {
4093
- dispatch({
4094
- type: "SET_STATUS",
4095
- severity: "warning",
4096
- text: ROTATION_UNAVAILABLE_MESSAGE
4097
- });
4098
- return;
4099
- }
4100
4224
  dispatch({
4101
4225
  type: "SET_SELECTION",
4102
4226
  key: selectionKeyFor(props.screen),
4103
4227
  index
4104
4228
  });
4105
- setDraft(initialDraftFor(key, shape, rotatableShape));
4229
+ setDraft(initialDraftFor(key, shape));
4106
4230
  setEditingField(key);
4107
4231
  }
4108
4232
  });
@@ -4227,8 +4351,6 @@ function ShapeEditorScreen(props) {
4227
4351
  renderItem: (key, isSelected) => /* @__PURE__ */ jsx(FieldRow, {
4228
4352
  fieldKey: key,
4229
4353
  shape,
4230
- rotatableShape,
4231
- isOdp,
4232
4354
  isSelected,
4233
4355
  isEditing: editingField === key,
4234
4356
  draft,
@@ -5334,28 +5456,40 @@ function OdsSpreadsheetGridScreen() {
5334
5456
  function ListEditorScreen() {
5335
5457
  const state = useAppState();
5336
5458
  const dispatch = useAppDispatch();
5459
+ const overlayOpen = anyOverlayOpen(state);
5337
5460
  const [isAdding, setIsAdding] = useState(false);
5338
5461
  const [newItemText, setNewItemText] = useState("");
5462
+ const [editingIndex, setEditingIndex] = useState(void 0);
5339
5463
  const screen = currentScreen(state);
5340
5464
  const doc = paragraphFamilyDocument(state.openDocument);
5341
5465
  const list = screen.kind === "listEditor" && doc?.format === "odt" ? doc.editor.lists()[screen.blockIndex] : void 0;
5342
- const itemCount = list === void 0 ? 0 : list.items().length;
5343
- useInput((input, key) => {
5344
- if (list === void 0 || screen.kind !== "listEditor") return;
5345
- if (key.escape) {
5466
+ const items = list === void 0 ? [] : list.items();
5467
+ const rows = items.map((item, index) => ({
5468
+ item,
5469
+ index
5470
+ }));
5471
+ const itemCount = items.length;
5472
+ const isNavigationActive = !overlayOpen && !isAdding && editingIndex === void 0;
5473
+ const { selectedIndex } = useNavigationInput({
5474
+ itemCount,
5475
+ isActive: isNavigationActive,
5476
+ onBack: () => {
5346
5477
  dispatch({ type: "POP_SCREEN" });
5347
- return;
5348
- }
5349
- if (input === "a") {
5478
+ },
5479
+ onSelect: (index) => {
5480
+ setEditingIndex(index);
5481
+ },
5482
+ onAppend: () => {
5350
5483
  setIsAdding(true);
5351
- return;
5352
5484
  }
5485
+ });
5486
+ useInput((input, key) => {
5353
5487
  if (key.tab || input === ">" || input === "<") dispatch({
5354
5488
  type: "SET_STATUS",
5355
5489
  severity: "warning",
5356
5490
  text: "Indenting a list item needs a new reducer action this pass didn't add -- OdtListItem.addNestedList() has no wiring yet"
5357
5491
  });
5358
- }, { isActive: !anyOverlayOpen(state) && !isAdding });
5492
+ }, { isActive: isNavigationActive });
5359
5493
  if (screen.kind !== "listEditor") return /* @__PURE__ */ jsx(Text, {
5360
5494
  color: "red",
5361
5495
  children: "ListEditorScreen rendered outside a listEditor screen."
@@ -5372,6 +5506,36 @@ function ListEditorScreen() {
5372
5506
  "."
5373
5507
  ]
5374
5508
  });
5509
+ if (editingIndex !== void 0) {
5510
+ const item = items[editingIndex];
5511
+ if (item === void 0) throw new Error(`ListEditorScreen is editing item index ${editingIndex}, but list ${screen.blockIndex} only has ${items.length} items -- selecting a row always sets editingIndex to a valid index from that same items array, so this indicates a bug in that selection.`);
5512
+ return /* @__PURE__ */ jsxs(Box, {
5513
+ flexDirection: "column",
5514
+ children: [/* @__PURE__ */ jsxs(Text, {
5515
+ bold: true,
5516
+ children: [
5517
+ "List ",
5518
+ screen.blockIndex,
5519
+ ", item ",
5520
+ editingIndex + 1
5521
+ ]
5522
+ }), /* @__PURE__ */ jsx(RunTextEditor, {
5523
+ initialText: item.text,
5524
+ onCommit: (text) => {
5525
+ dispatch({
5526
+ type: "SET_LIST_ITEM_TEXT",
5527
+ blockIndex: screen.blockIndex,
5528
+ itemIndex: editingIndex,
5529
+ text
5530
+ });
5531
+ setEditingIndex(void 0);
5532
+ },
5533
+ onCancel: () => {
5534
+ setEditingIndex(void 0);
5535
+ }
5536
+ })]
5537
+ });
5538
+ }
5375
5539
  return /* @__PURE__ */ jsxs(Box, {
5376
5540
  flexDirection: "column",
5377
5541
  children: [
@@ -5387,13 +5551,23 @@ function ListEditorScreen() {
5387
5551
  ")"
5388
5552
  ]
5389
5553
  }),
5390
- itemCount === 0 ? /* @__PURE__ */ jsx(Text, {
5391
- dimColor: true,
5392
- children: "This list has no items yet."
5393
- }) : Array.from({ length: itemCount }, (_, index) => /* @__PURE__ */ jsxs(Text, {
5394
- dimColor: true,
5395
- children: [index + 1, ". (item content is not readable through documents.js's OdtListItem API)"]
5396
- }, index)),
5554
+ /* @__PURE__ */ jsx(ListView, {
5555
+ items: rows,
5556
+ selectedIndex,
5557
+ emptyMessage: "This list has no items yet -- press 'a' to add one.",
5558
+ renderItem: (row, isSelected) => {
5559
+ const trimmed = row.item.text.trim();
5560
+ return /* @__PURE__ */ jsxs(Text, {
5561
+ color: isSelected ? "cyan" : void 0,
5562
+ inverse: isSelected,
5563
+ children: [
5564
+ row.index + 1,
5565
+ ". ",
5566
+ trimmed.length === 0 ? "(empty)" : row.item.text
5567
+ ]
5568
+ });
5569
+ }
5570
+ }),
5397
5571
  isAdding ? /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, {
5398
5572
  color: "cyan",
5399
5573
  children: "+ "
@@ -5417,7 +5591,7 @@ function ListEditorScreen() {
5417
5591
  }
5418
5592
  })] }) : /* @__PURE__ */ jsx(Text, {
5419
5593
  dimColor: true,
5420
- children: "a to add an item, Esc back"
5594
+ children: "Enter to edit an item, a to add, Esc back"
5421
5595
  })
5422
5596
  ]
5423
5597
  });
@@ -6240,6 +6414,67 @@ function SaveAsPromptScreen() {
6240
6414
  });
6241
6415
  }
6242
6416
  //#endregion
6417
+ //#region src/tui/format/read-metadata.ts
6418
+ function metadataFor(doc) {
6419
+ switch (doc.format) {
6420
+ case "docx": return readDocxContent(doc.editor.toPackage()).metadata;
6421
+ case "pptx": return readPptxContent(doc.editor.toPackage()).metadata;
6422
+ case "odt": return readOdtContent(doc.editor.toPackage()).metadata;
6423
+ case "odp": return readOdpContent(doc.editor.toPackage()).metadata;
6424
+ case "ods": return readOdsContent(doc.editor.toPackage()).metadata;
6425
+ case "odg": return readOdgContent(doc.editor.toPackage()).metadata;
6426
+ case "markdown": return readMarkdownContent(doc.source).metadata;
6427
+ case "pdf":
6428
+ case "xlsx": return doc.layout.metadata;
6429
+ 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.");
6430
+ }
6431
+ }
6432
+ //#endregion
6433
+ //#region src/tui/screens/shared/metadata.tsx
6434
+ function MetadataScreen() {
6435
+ const state = useAppState();
6436
+ const dispatch = useAppDispatch();
6437
+ const doc = state.openDocument;
6438
+ useInput((input, key) => {
6439
+ if (key.escape || key.leftArrow || input === "h") dispatch({ type: "POP_SCREEN" });
6440
+ }, { isActive: doc !== void 0 && !anyOverlayOpen(state) });
6441
+ if (doc === void 0) return /* @__PURE__ */ jsx(Text, {
6442
+ color: "red",
6443
+ children: "MetadataScreen requires an open document."
6444
+ });
6445
+ let lines;
6446
+ let errorMessage;
6447
+ try {
6448
+ lines = formatMetadataLines(metadataFor(doc));
6449
+ } catch (error) {
6450
+ errorMessage = error instanceof Error ? error.message : String(error);
6451
+ }
6452
+ return /* @__PURE__ */ jsxs(Box, {
6453
+ flexDirection: "column",
6454
+ children: [
6455
+ /* @__PURE__ */ jsxs(Text, {
6456
+ bold: true,
6457
+ children: [
6458
+ "Document metadata (",
6459
+ doc.format,
6460
+ ")"
6461
+ ]
6462
+ }),
6463
+ errorMessage !== void 0 ? /* @__PURE__ */ jsx(Text, {
6464
+ color: "yellow",
6465
+ children: errorMessage
6466
+ }) : lines !== void 0 && lines.length > 0 ? lines.map((line) => /* @__PURE__ */ jsx(Text, { children: line }, line)) : /* @__PURE__ */ jsx(Text, {
6467
+ dimColor: true,
6468
+ children: "This document carries no metadata."
6469
+ }),
6470
+ /* @__PURE__ */ jsx(Text, {
6471
+ dimColor: true,
6472
+ children: "Esc / ← / h to go back"
6473
+ })
6474
+ ]
6475
+ });
6476
+ }
6477
+ //#endregion
6243
6478
  //#region src/tui/app.tsx
6244
6479
  function App(props) {
6245
6480
  return /* @__PURE__ */ jsx(AppStateProvider, {
@@ -6285,6 +6520,7 @@ function ScreenBody({ screen }) {
6285
6520
  case "pdfItemDetail": return /* @__PURE__ */ jsx(PdfItemDetailScreen, {});
6286
6521
  case "exportOptions": return /* @__PURE__ */ jsx(ExportOptionsScreen, {});
6287
6522
  case "saveAsPrompt": return /* @__PURE__ */ jsx(SaveAsPromptScreen, {});
6523
+ case "metadata": return /* @__PURE__ */ jsx(MetadataScreen, {});
6288
6524
  }
6289
6525
  }
6290
6526
  function Overlay({ state }) {
@@ -6374,6 +6610,10 @@ function AppShell({ startPath }) {
6374
6610
  dispatch({ type: "REQUEST_CLOSE" });
6375
6611
  return;
6376
6612
  }
6613
+ if (key.ctrl && input === "z") {
6614
+ dispatch({ type: "UNDO" });
6615
+ return;
6616
+ }
6377
6617
  if (input === ":") {
6378
6618
  dispatch({
6379
6619
  type: "OPEN_OVERLAY",
@@ -6395,10 +6635,27 @@ function AppShell({ startPath }) {
6395
6635
  });
6396
6636
  return;
6397
6637
  }
6398
- if (key.ctrl && input === "d") dispatch({
6399
- type: "OPEN_OVERLAY",
6400
- overlay: "diagnosticsPanel"
6401
- });
6638
+ if (key.ctrl && input === "d") {
6639
+ dispatch({
6640
+ type: "OPEN_OVERLAY",
6641
+ overlay: "diagnosticsPanel"
6642
+ });
6643
+ return;
6644
+ }
6645
+ if (input === "m") {
6646
+ if (state.openDocument === void 0) {
6647
+ dispatch({
6648
+ type: "SET_STATUS",
6649
+ severity: "warning",
6650
+ text: "There is no open document to show metadata for"
6651
+ });
6652
+ return;
6653
+ }
6654
+ dispatch({
6655
+ type: "PUSH_SCREEN",
6656
+ screen: { kind: "metadata" }
6657
+ });
6658
+ }
6402
6659
  }, { isActive: !overlayOpen });
6403
6660
  return /* @__PURE__ */ jsxs(Box, {
6404
6661
  flexDirection: "column",