document-cli 1.8.0 → 1.9.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/dist/cli.js CHANGED
@@ -1194,7 +1194,7 @@ function registerSetMetadataCommand(program) {
1194
1194
  }
1195
1195
  //#endregion
1196
1196
  //#region package.json
1197
- var version = "1.8.0";
1197
+ var version = "1.9.0";
1198
1198
  //#endregion
1199
1199
  //#region src/program.ts
1200
1200
  function createProgram() {
@@ -1221,7 +1221,7 @@ function createProgram() {
1221
1221
  //#region src/cli.ts
1222
1222
  async function launchTui(startPath, signal) {
1223
1223
  try {
1224
- const { runTui } = await import("./tui-BCl5_L4H.js");
1224
+ const { runTui } = await import("./tui-D9SLI7oT.js");
1225
1225
  await runTui({
1226
1226
  startPath,
1227
1227
  signal
package/dist/index.cjs CHANGED
@@ -1661,7 +1661,7 @@ function registerSetMetadataCommand(program) {
1661
1661
  }
1662
1662
  //#endregion
1663
1663
  //#region package.json
1664
- var version = "1.8.0";
1664
+ var version = "1.9.0";
1665
1665
  //#endregion
1666
1666
  //#region src/program.ts
1667
1667
  function createProgram() {
package/dist/index.js CHANGED
@@ -1660,7 +1660,7 @@ function registerSetMetadataCommand(program) {
1660
1660
  }
1661
1661
  //#endregion
1662
1662
  //#region package.json
1663
- var version = "1.8.0";
1663
+ var version = "1.9.0";
1664
1664
  //#endregion
1665
1665
  //#region src/program.ts
1666
1666
  function createProgram() {
@@ -1,6 +1,6 @@
1
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, parseXml, pptxToPdf, readDocxContent, readDocxExtras, readMarkdownContent, readOdbForms, readOdbReportContent, readOdbReports, readOdbTables, readOdgContent, readOdpContent, readOdsContent, readOdtContent, readPdf, readPptxContent, rgbHexToColor, writePdf, xlsxToPdf } from "documents.js";
3
+ import { buildDocxPackage, buildOdtPackage, bytesToBase64, 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, parseXml, 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";
@@ -508,6 +508,11 @@ function drawingDocument(state) {
508
508
  if (doc === void 0) return;
509
509
  return doc.format === "odg" ? doc : void 0;
510
510
  }
511
+ function vectorHostDocument(state) {
512
+ const doc = state.openDocument;
513
+ if (doc === void 0) return;
514
+ return doc.format === "odg" || doc.format === "odp" ? doc : void 0;
515
+ }
511
516
  function markdownDocument(state) {
512
517
  const doc = state.openDocument;
513
518
  if (doc === void 0) return;
@@ -740,6 +745,12 @@ function appReducer(state, action) {
740
745
  case "SET_RUN_COLOR": return withRun(state, action.blockIndex, action.runIndex, (run) => {
741
746
  run.color = action.color;
742
747
  });
748
+ case "SET_RUN_FONT_FAMILY": return withRun(state, action.blockIndex, action.runIndex, (run) => {
749
+ run.fontFamily = action.fontFamily;
750
+ });
751
+ case "SET_RUN_FONT_SIZE": return withRun(state, action.blockIndex, action.runIndex, (run) => {
752
+ run.sizePt = action.sizePt;
753
+ });
743
754
  case "APPEND_TABLE": {
744
755
  const doc = wordprocessingDocument(state);
745
756
  if (doc === void 0) return wrongDocument(state, "a docx or odt document");
@@ -800,6 +811,14 @@ function appReducer(state, action) {
800
811
  setTextContainerText(item, action.text);
801
812
  });
802
813
  }
814
+ case "ADD_LIST": {
815
+ const doc = wordprocessingDocument(state);
816
+ if (doc === void 0) return wrongDocument(state, "a docx or odt document");
817
+ if (doc.format !== "odt") return wrongDocument(state, "an odt document (lists are an odt-only concept)");
818
+ return mutate(state, doc, () => {
819
+ doc.editor.body.appendList();
820
+ });
821
+ }
803
822
  case "INSERT_PARAGRAPH_IMAGE": {
804
823
  const doc = wordprocessingDocument(state);
805
824
  if (doc === void 0) return wrongDocument(state, "a docx or odt document");
@@ -943,6 +962,23 @@ function appReducer(state, action) {
943
962
  case "SET_CELL_VALUE": return withSheet(state, action.sheetIndex, (sheet) => {
944
963
  sheet.cell(action.row, action.column).value = action.value;
945
964
  });
965
+ case "SET_CELL_FORMULA": return withSheet(state, action.sheetIndex, (sheet) => {
966
+ sheet.cell(action.row, action.column).formula = action.formula;
967
+ });
968
+ case "ADD_SHEET_IMAGE": return withSheet(state, action.sheetIndex, (sheet) => {
969
+ sheet.addImage({
970
+ kind: "image",
971
+ format: action.format,
972
+ base64: bytesToBase64(action.bytes),
973
+ widthPt: action.widthPt,
974
+ heightPt: action.heightPt,
975
+ altText: action.altText,
976
+ anchorRow: action.anchorRow,
977
+ anchorColumn: action.anchorColumn,
978
+ offsetXPt: action.offsetXPt,
979
+ offsetYPt: action.offsetYPt
980
+ });
981
+ });
946
982
  case "MERGE_CELLS": {
947
983
  const doc = spreadsheetDocument(state);
948
984
  if (doc === void 0) return wrongDocument(state, "an ods document");
@@ -966,23 +1002,64 @@ function appReducer(state, action) {
966
1002
  case "ADD_ELLIPSE":
967
1003
  case "ADD_LINE":
968
1004
  case "ADD_PATH": {
969
- const doc = drawingDocument(state);
970
- if (doc === void 0) return wrongDocument(state, "an odg document");
971
- const page = doc.editor.pages()[action.pageIndex];
972
- if (page === void 0) return withStatus(state, "warning", `There is no page at index ${action.pageIndex}`);
1005
+ const doc = vectorHostDocument(state);
1006
+ if (doc === void 0) return wrongDocument(state, "an odg or odp document");
1007
+ if (doc.format === "odg") {
1008
+ const page = doc.editor.pages()[action.containerIndex];
1009
+ if (page === void 0) return withStatus(state, "warning", `There is no page at index ${action.containerIndex}`);
1010
+ return mutate(state, doc, () => {
1011
+ switch (action.type) {
1012
+ case "ADD_RECT":
1013
+ page.addRect(action.init);
1014
+ return;
1015
+ case "ADD_ELLIPSE":
1016
+ page.addEllipse(action.init);
1017
+ return;
1018
+ case "ADD_LINE":
1019
+ page.addLine(action.init);
1020
+ return;
1021
+ case "ADD_PATH":
1022
+ page.addPath(action.init);
1023
+ return;
1024
+ }
1025
+ });
1026
+ }
1027
+ const slide = doc.editor.slides()[action.containerIndex];
1028
+ if (slide === void 0) return withStatus(state, "warning", `There is no slide at index ${action.containerIndex}`);
973
1029
  return mutate(state, doc, () => {
974
1030
  switch (action.type) {
975
1031
  case "ADD_RECT":
976
- page.addRect(action.init);
1032
+ slide.addVector({
1033
+ kind: "rect",
1034
+ frame: action.init.frame,
1035
+ fill: action.init.fill,
1036
+ stroke: action.init.stroke
1037
+ });
977
1038
  return;
978
1039
  case "ADD_ELLIPSE":
979
- page.addEllipse(action.init);
1040
+ slide.addVector({
1041
+ kind: "ellipse",
1042
+ frame: action.init.frame,
1043
+ fill: action.init.fill,
1044
+ stroke: action.init.stroke
1045
+ });
980
1046
  return;
981
1047
  case "ADD_LINE":
982
- page.addLine(action.init);
1048
+ slide.addVector({
1049
+ kind: "line",
1050
+ from: action.init.from,
1051
+ to: action.init.to,
1052
+ stroke: action.init.stroke
1053
+ });
983
1054
  return;
984
1055
  case "ADD_PATH":
985
- page.addPath(action.init);
1056
+ slide.addVector({
1057
+ kind: "path",
1058
+ frame: action.init.frame,
1059
+ subpaths: [...action.init.subpaths],
1060
+ fill: action.init.fill,
1061
+ stroke: action.init.stroke
1062
+ });
986
1063
  return;
987
1064
  }
988
1065
  });
@@ -2132,6 +2209,19 @@ function ParagraphFamilyBodyList(props) {
2132
2209
  setTableWizard("rows");
2133
2210
  }
2134
2211
  }, { isActive: !anyOverlayOpen(state) && !wizardOpen && !formulaFlowOpen });
2212
+ useInput((input) => {
2213
+ if (input === "L" && adapter.lists !== void 0) {
2214
+ const newIndex = adapter.lists().length;
2215
+ dispatch({ type: "ADD_LIST" });
2216
+ dispatch({
2217
+ type: "PUSH_SCREEN",
2218
+ screen: {
2219
+ kind: "listEditor",
2220
+ blockIndex: newIndex
2221
+ }
2222
+ });
2223
+ }
2224
+ }, { isActive: !anyOverlayOpen(state) && !wizardOpen && !formulaFlowOpen });
2135
2225
  useInput((input) => {
2136
2226
  if (input === "m" && adapter.formatLabel === "odt") setFormulaFlow("picking");
2137
2227
  }, { isActive: !anyOverlayOpen(state) && !wizardOpen && !formulaFlowOpen });
@@ -2386,6 +2476,7 @@ function ParagraphFamilyBodyList(props) {
2386
2476
  dimColor: true,
2387
2477
  children: [
2388
2478
  "Enter to open, a to append a paragraph, T to append a table",
2479
+ adapter.lists !== void 0 ? ", L to add a list" : "",
2389
2480
  adapter.formatLabel === "odt" ? ", m to insert a formula" : "",
2390
2481
  ", Esc back"
2391
2482
  ]
@@ -2408,6 +2499,7 @@ function isValidHexColorInput(input) {
2408
2499
  }
2409
2500
  //#endregion
2410
2501
  //#region src/tui/screens/editors/docx/paragraph-detail.tsx
2502
+ const DEFAULT_RUN_SIZE_PT = 12;
2411
2503
  const IMAGE_FIELDS = [
2412
2504
  {
2413
2505
  key: "path",
@@ -2487,6 +2579,8 @@ function ParagraphDetailScreen() {
2487
2579
  const dispatch = useAppDispatch();
2488
2580
  const [runIndex, setRunIndex] = useState(0);
2489
2581
  const [colorInput, setColorInput] = useState(void 0);
2582
+ const [fontFamilyInput, setFontFamilyInput] = useState(void 0);
2583
+ const [fontSizeInput, setFontSizeInput] = useState(void 0);
2490
2584
  const [imageWizardOpen, setImageWizardOpen] = useState(false);
2491
2585
  const [formulaPickerOpen, setFormulaPickerOpen] = useState(false);
2492
2586
  const screen = currentScreen(state);
@@ -2584,8 +2678,16 @@ function ParagraphDetailScreen() {
2584
2678
  });
2585
2679
  return;
2586
2680
  }
2587
- if (input === "c") setColorInput(selectedRun.color === void 0 ? "" : layoutColorToHex(selectedRun.color).slice(1));
2588
- }, { isActive: !anyOverlayOpen(state) && colorInput === void 0 && !imageWizardOpen && !formulaPickerOpen });
2681
+ if (input === "c") {
2682
+ setColorInput(selectedRun.color === void 0 ? "" : layoutColorToHex(selectedRun.color).slice(1));
2683
+ return;
2684
+ }
2685
+ if (input === "f") {
2686
+ setFontFamilyInput(selectedRun.fontFamily ?? "");
2687
+ return;
2688
+ }
2689
+ if (input === "s") setFontSizeInput(selectedRun.sizePt === void 0 ? "" : String(selectedRun.sizePt));
2690
+ }, { isActive: !anyOverlayOpen(state) && colorInput === void 0 && fontFamilyInput === void 0 && fontSizeInput === void 0 && !imageWizardOpen && !formulaPickerOpen });
2589
2691
  if (screen.kind !== "paragraphDetail") return /* @__PURE__ */ jsx(Text, {
2590
2692
  color: "red",
2591
2693
  children: "ParagraphDetailScreen rendered outside a paragraphDetail screen."
@@ -2643,6 +2745,60 @@ function ParagraphDetailScreen() {
2643
2745
  setColorInput(void 0);
2644
2746
  }
2645
2747
  })] }),
2748
+ fontFamilyInput === void 0 ? void 0 : /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, {
2749
+ color: "cyan",
2750
+ children: "Font family: "
2751
+ }), /* @__PURE__ */ jsx(TextField, {
2752
+ value: fontFamilyInput,
2753
+ isFocused: true,
2754
+ placeholder: "e.g. Calibri",
2755
+ onChange: setFontFamilyInput,
2756
+ onSubmit: (value) => {
2757
+ const trimmed = value.trim();
2758
+ if (trimmed.length === 0) dispatch({
2759
+ type: "SET_STATUS",
2760
+ severity: "warning",
2761
+ text: "A font family name cannot be blank"
2762
+ });
2763
+ else dispatch({
2764
+ type: "SET_RUN_FONT_FAMILY",
2765
+ blockIndex,
2766
+ runIndex: clampedRunIndex,
2767
+ fontFamily: trimmed
2768
+ });
2769
+ setFontFamilyInput(void 0);
2770
+ },
2771
+ onCancel: () => {
2772
+ setFontFamilyInput(void 0);
2773
+ }
2774
+ })] }),
2775
+ fontSizeInput === void 0 ? void 0 : /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, {
2776
+ color: "cyan",
2777
+ children: "Font size (pt): "
2778
+ }), /* @__PURE__ */ jsx(TextField, {
2779
+ value: fontSizeInput,
2780
+ isFocused: true,
2781
+ placeholder: "e.g. 12",
2782
+ onChange: setFontSizeInput,
2783
+ onSubmit: (value) => {
2784
+ const sizePt = parseNumberField(value, selectedRun?.sizePt ?? DEFAULT_RUN_SIZE_PT);
2785
+ if (sizePt <= 0) dispatch({
2786
+ type: "SET_STATUS",
2787
+ severity: "warning",
2788
+ text: `"${value}" is not a positive font size`
2789
+ });
2790
+ else dispatch({
2791
+ type: "SET_RUN_FONT_SIZE",
2792
+ blockIndex,
2793
+ runIndex: clampedRunIndex,
2794
+ sizePt
2795
+ });
2796
+ setFontSizeInput(void 0);
2797
+ },
2798
+ onCancel: () => {
2799
+ setFontSizeInput(void 0);
2800
+ }
2801
+ })] }),
2646
2802
  imageWizardOpen ? /* @__PURE__ */ jsx(FieldWizard, {
2647
2803
  fields: IMAGE_FIELDS,
2648
2804
  onCancel: () => {
@@ -2678,7 +2834,7 @@ function ParagraphDetailScreen() {
2678
2834
  /* @__PURE__ */ jsxs(Text, {
2679
2835
  dimColor: true,
2680
2836
  children: [
2681
- "<- / -> move, Enter edit text, b/i/u toggle, c colour, a append run, I image",
2837
+ "<- / -> move, Enter edit text, b/i/u toggle, c colour, f font, s size, a append run, I image",
2682
2838
  doc.format === "docx" ? ", m formula" : "",
2683
2839
  ", Esc back"
2684
2840
  ]
@@ -3747,6 +3903,64 @@ function OdbTableRowsScreen() {
3747
3903
  });
3748
3904
  }
3749
3905
  //#endregion
3906
+ //#region src/tui/screens/shared/vector-fields.ts
3907
+ function parseColorField(raw) {
3908
+ const trimmed = raw.trim();
3909
+ if (trimmed.length === 0) return;
3910
+ const [r, g, b] = trimmed.split(/\s+/).map((part) => Number.parseFloat(part));
3911
+ if (r === void 0 || g === void 0 || b === void 0 || ![
3912
+ r,
3913
+ g,
3914
+ b
3915
+ ].every((value) => Number.isFinite(value))) return;
3916
+ return {
3917
+ r,
3918
+ g,
3919
+ b
3920
+ };
3921
+ }
3922
+ function parseStrokeField(raw) {
3923
+ const trimmed = raw.trim();
3924
+ if (trimmed.length === 0) return;
3925
+ const [r, g, b, widthPt] = trimmed.split(/\s+/).map((part) => Number.parseFloat(part));
3926
+ if (r === void 0 || g === void 0 || b === void 0 || widthPt === void 0 || ![
3927
+ r,
3928
+ g,
3929
+ b,
3930
+ widthPt
3931
+ ].every((value) => Number.isFinite(value))) return;
3932
+ return {
3933
+ color: {
3934
+ r,
3935
+ g,
3936
+ b
3937
+ },
3938
+ widthPt
3939
+ };
3940
+ }
3941
+ function defaultTriangleSubpaths(widthPt, heightPt) {
3942
+ return [{
3943
+ start: {
3944
+ xPt: 0,
3945
+ yPt: heightPt
3946
+ },
3947
+ segments: [{
3948
+ kind: "line",
3949
+ to: {
3950
+ xPt: widthPt / 2,
3951
+ yPt: 0
3952
+ }
3953
+ }, {
3954
+ kind: "line",
3955
+ to: {
3956
+ xPt: widthPt,
3957
+ yPt: heightPt
3958
+ }
3959
+ }],
3960
+ closed: true
3961
+ }];
3962
+ }
3963
+ //#endregion
3750
3964
  //#region src/tui/screens/editors/odg/shared.ts
3751
3965
  function requireOdgDocument(state) {
3752
3966
  const doc = state.openDocument;
@@ -3820,62 +4034,6 @@ function describeFillStroke(vector) {
3820
4034
  if (vector.stroke !== void 0) parts.push(`stroke ${formatColor$1(vector.stroke.color)} ${formatPt(vector.stroke.widthPt)}pt`);
3821
4035
  return parts.length === 0 ? "no fill or stroke" : parts.join(", ");
3822
4036
  }
3823
- function parseColorField(raw) {
3824
- const trimmed = raw.trim();
3825
- if (trimmed.length === 0) return;
3826
- const [r, g, b] = trimmed.split(/\s+/).map((part) => Number.parseFloat(part));
3827
- if (r === void 0 || g === void 0 || b === void 0 || ![
3828
- r,
3829
- g,
3830
- b
3831
- ].every((value) => Number.isFinite(value))) return;
3832
- return {
3833
- r,
3834
- g,
3835
- b
3836
- };
3837
- }
3838
- function parseStrokeField(raw) {
3839
- const trimmed = raw.trim();
3840
- if (trimmed.length === 0) return;
3841
- const [r, g, b, widthPt] = trimmed.split(/\s+/).map((part) => Number.parseFloat(part));
3842
- if (r === void 0 || g === void 0 || b === void 0 || widthPt === void 0 || ![
3843
- r,
3844
- g,
3845
- b,
3846
- widthPt
3847
- ].every((value) => Number.isFinite(value))) return;
3848
- return {
3849
- color: {
3850
- r,
3851
- g,
3852
- b
3853
- },
3854
- widthPt
3855
- };
3856
- }
3857
- function defaultTriangleSubpaths(widthPt, heightPt) {
3858
- return [{
3859
- start: {
3860
- xPt: 0,
3861
- yPt: heightPt
3862
- },
3863
- segments: [{
3864
- kind: "line",
3865
- to: {
3866
- xPt: widthPt / 2,
3867
- yPt: 0
3868
- }
3869
- }, {
3870
- kind: "line",
3871
- to: {
3872
- xPt: widthPt,
3873
- yPt: heightPt
3874
- }
3875
- }],
3876
- closed: true
3877
- }];
3878
- }
3879
4037
  //#endregion
3880
4038
  //#region src/tui/screens/editors/odg/page-list.tsx
3881
4039
  function OdgPageListScreen() {
@@ -4079,7 +4237,7 @@ async function applyAddKind(kind, pageIndex, doc, values, dispatch) {
4079
4237
  case "rect":
4080
4238
  dispatch({
4081
4239
  type: "ADD_RECT",
4082
- pageIndex,
4240
+ containerIndex: pageIndex,
4083
4241
  init: {
4084
4242
  frame: readFrame(values),
4085
4243
  fill: parseColorField(requireFieldValue(values, "fill")),
@@ -4091,7 +4249,7 @@ async function applyAddKind(kind, pageIndex, doc, values, dispatch) {
4091
4249
  case "ellipse":
4092
4250
  dispatch({
4093
4251
  type: "ADD_ELLIPSE",
4094
- pageIndex,
4252
+ containerIndex: pageIndex,
4095
4253
  init: {
4096
4254
  frame: readFrame(values),
4097
4255
  fill: parseColorField(requireFieldValue(values, "fill")),
@@ -4103,7 +4261,7 @@ async function applyAddKind(kind, pageIndex, doc, values, dispatch) {
4103
4261
  case "line":
4104
4262
  dispatch({
4105
4263
  type: "ADD_LINE",
4106
- pageIndex,
4264
+ containerIndex: pageIndex,
4107
4265
  init: {
4108
4266
  from: {
4109
4267
  xPt: parseNumberField(requireFieldValue(values, "fromXPt"), 0),
@@ -4129,7 +4287,7 @@ async function applyAddKind(kind, pageIndex, doc, values, dispatch) {
4129
4287
  const frame = readFrame(values);
4130
4288
  dispatch({
4131
4289
  type: "ADD_PATH",
4132
- pageIndex,
4290
+ containerIndex: pageIndex,
4133
4291
  init: {
4134
4292
  frame,
4135
4293
  subpaths: defaultTriangleSubpaths(frame.widthPt, frame.heightPt),
@@ -5061,17 +5219,149 @@ function summarizeSlideTables(doc, slideIndex) {
5061
5219
  }
5062
5220
  //#endregion
5063
5221
  //#region src/tui/screens/editors/pptx/slide-detail.tsx
5064
- const IMAGE_EXTENSION_TO_FORMAT = {
5222
+ const IMAGE_EXTENSION_TO_FORMAT$1 = {
5065
5223
  png: "png",
5066
5224
  jpg: "jpeg",
5067
5225
  jpeg: "jpeg"
5068
5226
  };
5069
5227
  const DEFAULT_TABLE_ROWS = 2;
5070
5228
  const DEFAULT_TABLE_COLUMNS = 2;
5229
+ const VECTOR_GEOMETRY_FIELDS = [
5230
+ {
5231
+ key: "xPt",
5232
+ label: "X (pt)",
5233
+ defaultValue: "40"
5234
+ },
5235
+ {
5236
+ key: "yPt",
5237
+ label: "Y (pt)",
5238
+ defaultValue: "40"
5239
+ },
5240
+ {
5241
+ key: "widthPt",
5242
+ label: "Width (pt)",
5243
+ defaultValue: "160"
5244
+ },
5245
+ {
5246
+ key: "heightPt",
5247
+ label: "Height (pt)",
5248
+ defaultValue: "100"
5249
+ }
5250
+ ];
5251
+ const VECTOR_FILL_FIELD = {
5252
+ key: "fill",
5253
+ label: "Fill \"r g b\" (0-1 each), blank for none",
5254
+ defaultValue: "0.8 0.8 0.8"
5255
+ };
5256
+ const VECTOR_STROKE_FIELD = {
5257
+ key: "stroke",
5258
+ label: "Stroke \"r g b widthPt\" (0-1 colour, pt width), blank for none",
5259
+ defaultValue: "0 0 0 1"
5260
+ };
5261
+ const VECTOR_LINE_FIELDS = [
5262
+ {
5263
+ key: "fromXPt",
5264
+ label: "From X (pt)",
5265
+ defaultValue: "40"
5266
+ },
5267
+ {
5268
+ key: "fromYPt",
5269
+ label: "From Y (pt)",
5270
+ defaultValue: "40"
5271
+ },
5272
+ {
5273
+ key: "toXPt",
5274
+ label: "To X (pt)",
5275
+ defaultValue: "200"
5276
+ },
5277
+ {
5278
+ key: "toYPt",
5279
+ label: "To Y (pt)",
5280
+ defaultValue: "40"
5281
+ },
5282
+ VECTOR_STROKE_FIELD
5283
+ ];
5284
+ function fieldsForVectorKind(kind) {
5285
+ switch (kind) {
5286
+ case "rect":
5287
+ case "ellipse":
5288
+ case "path": return [
5289
+ ...VECTOR_GEOMETRY_FIELDS,
5290
+ VECTOR_FILL_FIELD,
5291
+ VECTOR_STROKE_FIELD
5292
+ ];
5293
+ case "line": return VECTOR_LINE_FIELDS;
5294
+ }
5295
+ }
5296
+ function readVectorFrame(values) {
5297
+ return {
5298
+ xPt: parseNumberField(requireFieldValue(values, "xPt"), 0),
5299
+ yPt: parseNumberField(requireFieldValue(values, "yPt"), 0),
5300
+ widthPt: parseNumberField(requireFieldValue(values, "widthPt"), 160),
5301
+ heightPt: parseNumberField(requireFieldValue(values, "heightPt"), 100)
5302
+ };
5303
+ }
5304
+ function buildVectorAction(kind, slideIndex, values) {
5305
+ switch (kind) {
5306
+ case "rect": return {
5307
+ type: "ADD_RECT",
5308
+ containerIndex: slideIndex,
5309
+ init: {
5310
+ frame: readVectorFrame(values),
5311
+ fill: parseColorField(requireFieldValue(values, "fill")),
5312
+ stroke: parseStrokeField(requireFieldValue(values, "stroke"))
5313
+ }
5314
+ };
5315
+ case "ellipse": return {
5316
+ type: "ADD_ELLIPSE",
5317
+ containerIndex: slideIndex,
5318
+ init: {
5319
+ frame: readVectorFrame(values),
5320
+ fill: parseColorField(requireFieldValue(values, "fill")),
5321
+ stroke: parseStrokeField(requireFieldValue(values, "stroke"))
5322
+ }
5323
+ };
5324
+ case "line": return {
5325
+ type: "ADD_LINE",
5326
+ containerIndex: slideIndex,
5327
+ init: {
5328
+ from: {
5329
+ xPt: parseNumberField(requireFieldValue(values, "fromXPt"), 0),
5330
+ yPt: parseNumberField(requireFieldValue(values, "fromYPt"), 0)
5331
+ },
5332
+ to: {
5333
+ xPt: parseNumberField(requireFieldValue(values, "toXPt"), 100),
5334
+ yPt: parseNumberField(requireFieldValue(values, "toYPt"), 0)
5335
+ },
5336
+ stroke: parseStrokeField(requireFieldValue(values, "stroke")) ?? {
5337
+ color: {
5338
+ r: 0,
5339
+ g: 0,
5340
+ b: 0
5341
+ },
5342
+ widthPt: 1
5343
+ }
5344
+ }
5345
+ };
5346
+ case "path": {
5347
+ const frame = readVectorFrame(values);
5348
+ return {
5349
+ type: "ADD_PATH",
5350
+ containerIndex: slideIndex,
5351
+ init: {
5352
+ frame,
5353
+ subpaths: defaultTriangleSubpaths(frame.widthPt, frame.heightPt),
5354
+ fill: parseColorField(requireFieldValue(values, "fill")),
5355
+ stroke: parseStrokeField(requireFieldValue(values, "stroke"))
5356
+ }
5357
+ };
5358
+ }
5359
+ }
5360
+ }
5071
5361
  function imageFormatFromPath(path) {
5072
5362
  const dotIndex = path.lastIndexOf(".");
5073
5363
  if (dotIndex < 0) return;
5074
- return IMAGE_EXTENSION_TO_FORMAT[path.slice(dotIndex + 1).toLowerCase()];
5364
+ return IMAGE_EXTENSION_TO_FORMAT$1[path.slice(dotIndex + 1).toLowerCase()];
5075
5365
  }
5076
5366
  async function readImageForShape(path) {
5077
5367
  const format = imageFormatFromPath(path);
@@ -5112,6 +5402,7 @@ function SlideDetailScreen(props) {
5112
5402
  const [draft, setDraft] = useState("");
5113
5403
  const [imageError, setImageError] = useState(void 0);
5114
5404
  const [tableRows, setTableRows] = useState(DEFAULT_TABLE_ROWS);
5405
+ const [vectorKind, setVectorKind] = useState(void 0);
5115
5406
  const formIsOpen = addMode !== "closed";
5116
5407
  const { selectedIndex } = useNavigationInput({
5117
5408
  itemCount: selectableRowIndices.length,
@@ -5172,6 +5463,27 @@ function SlideDetailScreen(props) {
5172
5463
  if (input === "b") {
5173
5464
  setDraft(String(DEFAULT_TABLE_ROWS));
5174
5465
  setAddMode("tableRows");
5466
+ return;
5467
+ }
5468
+ if (doc.format !== "odp") return;
5469
+ if (input === "r") {
5470
+ setVectorKind("rect");
5471
+ setAddMode("vector");
5472
+ return;
5473
+ }
5474
+ if (input === "e") {
5475
+ setVectorKind("ellipse");
5476
+ setAddMode("vector");
5477
+ return;
5478
+ }
5479
+ if (input === "n") {
5480
+ setVectorKind("line");
5481
+ setAddMode("vector");
5482
+ return;
5483
+ }
5484
+ if (input === "p") {
5485
+ setVectorKind("path");
5486
+ setAddMode("vector");
5175
5487
  }
5176
5488
  }, { isActive: !overlayOpen && addMode === "chooseKind" });
5177
5489
  useInput((input) => {
@@ -5285,9 +5597,25 @@ function SlideDetailScreen(props) {
5285
5597
  });
5286
5598
  }
5287
5599
  }),
5288
- addMode === "chooseKind" ? /* @__PURE__ */ jsx(Text, {
5600
+ addMode === "chooseKind" ? /* @__PURE__ */ jsxs(Text, {
5289
5601
  color: "cyan",
5290
- children: "Add shape: t textbox, i image, b table, Esc cancel"
5602
+ children: [
5603
+ "Add shape: t textbox, i image, b table",
5604
+ doc.format === "odp" ? ", r rect, e ellipse, n line, p path" : "",
5605
+ ", Esc cancel"
5606
+ ]
5607
+ }) : void 0,
5608
+ addMode === "vector" && vectorKind !== void 0 ? /* @__PURE__ */ jsx(FieldWizard, {
5609
+ fields: fieldsForVectorKind(vectorKind),
5610
+ onCancel: () => {
5611
+ setAddMode("closed");
5612
+ setVectorKind(void 0);
5613
+ },
5614
+ onComplete: (values) => {
5615
+ dispatch(buildVectorAction(vectorKind, slideIndex, values));
5616
+ setAddMode("closed");
5617
+ setVectorKind(void 0);
5618
+ }
5291
5619
  }) : void 0,
5292
5620
  addMode === "textbox" ? /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, {
5293
5621
  color: "cyan",
@@ -6110,8 +6438,90 @@ const RESERVED_LETTERS = /* @__PURE__ */ new Set([
6110
6438
  "l",
6111
6439
  "p",
6112
6440
  "t",
6113
- "m"
6441
+ "m",
6442
+ "f",
6443
+ "i"
6114
6444
  ]);
6445
+ const IMAGE_EXTENSION_TO_FORMAT = {
6446
+ png: "png",
6447
+ jpg: "jpeg",
6448
+ jpeg: "jpeg"
6449
+ };
6450
+ function inferSheetImageFormat(path) {
6451
+ const extension = path.slice(path.lastIndexOf(".") + 1).toLowerCase();
6452
+ return IMAGE_EXTENSION_TO_FORMAT[extension];
6453
+ }
6454
+ const SHEET_IMAGE_FIELDS = [
6455
+ {
6456
+ key: "path",
6457
+ label: "Image file path (.png/.jpg/.jpeg)",
6458
+ defaultValue: ""
6459
+ },
6460
+ {
6461
+ key: "widthPt",
6462
+ label: "Width (pt)",
6463
+ defaultValue: "100"
6464
+ },
6465
+ {
6466
+ key: "heightPt",
6467
+ label: "Height (pt)",
6468
+ defaultValue: "60"
6469
+ },
6470
+ {
6471
+ key: "offsetXPt",
6472
+ label: "Offset X from anchor cell (pt)",
6473
+ defaultValue: "0"
6474
+ },
6475
+ {
6476
+ key: "offsetYPt",
6477
+ label: "Offset Y from anchor cell (pt)",
6478
+ defaultValue: "0"
6479
+ },
6480
+ {
6481
+ key: "altText",
6482
+ label: "Alt text, blank for none",
6483
+ defaultValue: ""
6484
+ }
6485
+ ];
6486
+ async function applyAddSheetImage(sheetIndex, anchorRow, anchorColumn, values, dispatch) {
6487
+ const path = requireFieldValue(values, "path");
6488
+ const format = inferSheetImageFormat(path);
6489
+ if (format === void 0) {
6490
+ dispatch({
6491
+ type: "SET_STATUS",
6492
+ severity: "warning",
6493
+ text: `${path} is not a .png or .jpg/.jpeg file -- image not added`
6494
+ });
6495
+ return;
6496
+ }
6497
+ try {
6498
+ const bytes = new Uint8Array(await readInput(path));
6499
+ const widthPt = parseNumberField(requireFieldValue(values, "widthPt"), 100);
6500
+ const heightPt = parseNumberField(requireFieldValue(values, "heightPt"), 60);
6501
+ const offsetXPt = parseNumberField(requireFieldValue(values, "offsetXPt"), 0);
6502
+ const offsetYPt = parseNumberField(requireFieldValue(values, "offsetYPt"), 0);
6503
+ const altTextRaw = requireFieldValue(values, "altText").trim();
6504
+ dispatch({
6505
+ type: "ADD_SHEET_IMAGE",
6506
+ sheetIndex,
6507
+ anchorRow,
6508
+ anchorColumn,
6509
+ offsetXPt,
6510
+ offsetYPt,
6511
+ format,
6512
+ bytes,
6513
+ widthPt,
6514
+ heightPt,
6515
+ altText: altTextRaw.length === 0 ? void 0 : altTextRaw
6516
+ });
6517
+ } catch (error) {
6518
+ dispatch({
6519
+ type: "SET_STATUS",
6520
+ severity: "error",
6521
+ text: `Could not read ${path}: ${describeError(error)}`
6522
+ });
6523
+ }
6524
+ }
6115
6525
  function windowStart(cursor, total, viewport) {
6116
6526
  const maxStart = Math.max(0, total - viewport);
6117
6527
  return Math.min(Math.max(cursor - Math.floor(viewport / 2), 0), maxStart);
@@ -6134,6 +6544,9 @@ function OdsSpreadsheetGridScreen() {
6134
6544
  const [viewMode, setViewMode] = useState("grid");
6135
6545
  const [editSession, setEditSession] = useState(void 0);
6136
6546
  const [mergeAnchor, setMergeAnchor] = useState(void 0);
6547
+ const [formulaEditing, setFormulaEditing] = useState(false);
6548
+ const [formulaDraft, setFormulaDraft] = useState("");
6549
+ const [imageWizardOpen, setImageWizardOpen] = useState(false);
6137
6550
  const { columns: terminalColumns, rows: terminalRows } = useWindowSize();
6138
6551
  const sheet = resolveSheet(doc.editor, sheetIndex);
6139
6552
  const { rowCount, columnCount } = sheetExtent(sheet);
@@ -6142,6 +6555,7 @@ function OdsSpreadsheetGridScreen() {
6142
6555
  const clampedColumn = Math.min(cursorColumn, columnCount - 1);
6143
6556
  const overlayOpen = anyOverlayOpen(state);
6144
6557
  const editing = editSession !== void 0;
6558
+ const editingAnything = editing || formulaEditing || imageWizardOpen;
6145
6559
  function moveCursor(deltaRow, deltaColumn) {
6146
6560
  setCursorRow((row) => Math.min(Math.max(row + deltaRow, 0), rowCount - 1));
6147
6561
  setCursorColumn((column) => Math.min(Math.max(column + deltaColumn, 0), columnCount - 1));
@@ -6179,7 +6593,7 @@ function OdsSpreadsheetGridScreen() {
6179
6593
  sheetIndex
6180
6594
  }
6181
6595
  });
6182
- }, { isActive: !overlayOpen && !editing });
6596
+ }, { isActive: !overlayOpen && !editingAnything });
6183
6597
  useInput((input, key) => {
6184
6598
  if (key.upArrow || input === "k") {
6185
6599
  moveCursor(-1, 0);
@@ -6229,6 +6643,16 @@ function OdsSpreadsheetGridScreen() {
6229
6643
  else commitMerge(mergeAnchor);
6230
6644
  return;
6231
6645
  }
6646
+ if (input === "f") {
6647
+ const cell = cells.get(cellKey(clampedRow, clampedColumn));
6648
+ setFormulaDraft(cell?.formula ?? "");
6649
+ setFormulaEditing(true);
6650
+ return;
6651
+ }
6652
+ if (input === "i") {
6653
+ setImageWizardOpen(true);
6654
+ return;
6655
+ }
6232
6656
  if (key.return) {
6233
6657
  if (mergeAnchor !== void 0) {
6234
6658
  commitMerge(mergeAnchor);
@@ -6240,7 +6664,7 @@ function OdsSpreadsheetGridScreen() {
6240
6664
  return;
6241
6665
  }
6242
6666
  if (input.length === 1 && !key.ctrl && !key.meta && !RESERVED_LETTERS.has(input)) beginEdit(input, inferKind(input));
6243
- }, { isActive: !overlayOpen && !editing && viewMode === "grid" });
6667
+ }, { isActive: !overlayOpen && !editingAnything && viewMode === "grid" });
6244
6668
  const compactRows = sheet === void 0 ? [] : sheet.cells.filter((cell) => cell.value.kind !== "empty").map((cell) => ({
6245
6669
  row: cell.row,
6246
6670
  column: cell.column,
@@ -6358,9 +6782,43 @@ function OdsSpreadsheetGridScreen() {
6358
6782
  setEditSession(void 0);
6359
6783
  }
6360
6784
  }),
6785
+ formulaEditing ? /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsxs(Text, {
6786
+ color: "cyan",
6787
+ children: [cursorAddress, " formula: "]
6788
+ }), /* @__PURE__ */ jsx(TextField, {
6789
+ value: formulaDraft,
6790
+ isFocused: !overlayOpen,
6791
+ placeholder: "e.g. of:=[.A1]+[.A2]",
6792
+ onChange: setFormulaDraft,
6793
+ onSubmit: (value) => {
6794
+ const trimmed = value.trim();
6795
+ dispatch({
6796
+ type: "SET_CELL_FORMULA",
6797
+ sheetIndex,
6798
+ row: clampedRow,
6799
+ column: clampedColumn,
6800
+ formula: trimmed.length === 0 ? void 0 : trimmed
6801
+ });
6802
+ setFormulaEditing(false);
6803
+ },
6804
+ onCancel: () => {
6805
+ setFormulaEditing(false);
6806
+ }
6807
+ })] }) : void 0,
6808
+ imageWizardOpen ? /* @__PURE__ */ jsx(FieldWizard, {
6809
+ fields: SHEET_IMAGE_FIELDS,
6810
+ onCancel: () => {
6811
+ setImageWizardOpen(false);
6812
+ },
6813
+ onComplete: (values) => {
6814
+ applyAddSheetImage(sheetIndex, clampedRow, clampedColumn, values, dispatch).then(() => {
6815
+ setImageWizardOpen(false);
6816
+ });
6817
+ }
6818
+ }) : void 0,
6361
6819
  /* @__PURE__ */ jsx(Text, {
6362
6820
  dimColor: true,
6363
- children: mergeAnchor === void 0 ? `hjkl/arrows move, Enter/type to edit, m to anchor a merge, p print settings, t ${viewMode === "grid" ? "compact list" : "grid"} view, Esc back` : "hjkl/arrows to the opposite corner, m/Enter to merge, Esc to cancel"
6821
+ children: mergeAnchor === void 0 ? `hjkl/arrows move, Enter/type to edit, m to anchor a merge, p print settings, t ${viewMode === "grid" ? "compact list" : "grid"} view, f formula, i image, Esc back` : "hjkl/arrows to the opposite corner, m/Enter to merge, Esc to cancel"
6364
6822
  })
6365
6823
  ]
6366
6824
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "document-cli",
3
- "version": "1.8.0",
3
+ "version": "1.9.0",
4
4
  "description": "CLI and interactive Ink TUI for documents.js: every docx/pptx/odt/odp/ods/odg/odf/pdf/odm/odb/xlsx/markdown conversion, bridge, and editor as a scriptable command or a terminal app.",
5
5
  "type": "module",
6
6
  "repository": {