doculi 3.1.7 → 5.7.10

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,11 +1,11 @@
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-B1ygPCEa.js";
1
+ import { h as inferFormatFromExtension, i as formatOdbReportLines, l as formatDocxExtrasLines, m as formatToExtension, n as describeOdbReport, o as formatMetadataLines, p as loadProvidedFonts, r as formatOdbFormLines, s as formatMetadataValue, t as describeOdbForm, u as readInput } from "./odb-structure-CItnH50p.js";
2
2
  import { readFile, writeFile } from "node:fs/promises";
3
3
  import { basename, dirname, extname, join } from "node:path";
4
- import { bytesToBase64, cellReference, columnIndexToLetters, createDocx, createOdg, createOdp, createOds, createOdt, createPdf, createPptx, csvToPdf, decodeMarkdownText, decodeOdbPackage, 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, svgToPdf, xlsxToPdf } from "documents.js";
4
+ import { bytesToBase64, cellReference, columnIndexToLetters, convertDocument, createDoc, createDocx, createMarkdownEditor, createOdg, createOdp, createOds, createOdt, createPdf, createPpt, createPptx, createXls, csvToPdf, decodeMarkdownText, decodeOdbPackage, docToPdf, docxToPdf, encodeMarkdownText, epubToPdf, hsqldbCellDisplayText, markdownToPdf, odbReportToDocx, odbReportToOdt, odbReportToPdf, odgToPdf, odpToPdf, odsToPdf, odtToPdf, openDoc, openDocx, openMarkdown, openOdg, openOdp, openOds, openOdt, openPdf, openPpt, openPptx, openXls, parseXml, pptToPdf, pptxToPdf, readDocxContent, readDocxExtras, readMarkdownContent, readOdbForms, readOdbReportContent, readOdbReports, readOdbTables, readOdgContent, readOdpContent, readOdsContent, readOdtContent, readPdf, readPptxContent, rgbHexToColor, rtfToPdf, svgToPdf, xlsToPdf, xlsxToPdf } from "documents.js";
5
5
  import { readdirSync } from "node:fs";
6
6
  import { Box, Text, render, useApp, useInput, useWindowSize } from "ink";
7
7
  import { createContext, useContext, useEffect, useReducer, useState } from "react";
8
- import { jsx, jsxs } from "react/jsx-runtime";
8
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
9
  import TextInput from "ink-text-input";
10
10
  //#region src/tui/errors.ts
11
11
  function describeError(error) {
@@ -57,6 +57,36 @@ async function exportToPdf(openDocument, destinationPath, options) {
57
57
  await writeFile(destinationPath, pdfBytes);
58
58
  return;
59
59
  }
60
+ if (openDocument.format === "rtf") {
61
+ const pdfBytes = rtfToPdf(openDocument.bytes, pdfOptions);
62
+ await writeFile(destinationPath, pdfBytes);
63
+ return;
64
+ }
65
+ if (openDocument.format === "wpd") {
66
+ const pdfBytes = convertDocument("wpd", "pdf", openDocument.bytes, pdfOptions);
67
+ await writeFile(destinationPath, pdfBytes);
68
+ return;
69
+ }
70
+ if (openDocument.format === "doc") {
71
+ const pdfBytes = docToPdf(openDocument.editor.toBytes(), pdfOptions);
72
+ await writeFile(destinationPath, pdfBytes);
73
+ return;
74
+ }
75
+ if (openDocument.format === "xls") {
76
+ const pdfBytes = xlsToPdf(openDocument.editor.toBytes(), pdfOptions);
77
+ await writeFile(destinationPath, pdfBytes);
78
+ return;
79
+ }
80
+ if (openDocument.format === "ppt") {
81
+ const pdfBytes = pptToPdf(openDocument.editor.toBytes(), pdfOptions);
82
+ await writeFile(destinationPath, pdfBytes);
83
+ return;
84
+ }
85
+ if (openDocument.format === "epub") {
86
+ const pdfBytes = epubToPdf(openDocument.bytes, pdfOptions);
87
+ await writeFile(destinationPath, pdfBytes);
88
+ return;
89
+ }
60
90
  const bytes = openDocument.editor.toBytes();
61
91
  const pdfBytes = convert(openDocument.format, bytes, pdfOptions);
62
92
  await writeFile(destinationPath, pdfBytes);
@@ -170,6 +200,39 @@ async function openDocumentAtPath(path, options = {}) {
170
200
  bytes,
171
201
  path
172
202
  };
203
+ case "rtf": return {
204
+ format,
205
+ layout: readPdf(rtfToPdf(bytes)),
206
+ bytes,
207
+ path
208
+ };
209
+ case "wpd": return {
210
+ format,
211
+ layout: readPdf(convertDocument("wpd", "pdf", bytes)),
212
+ bytes,
213
+ path
214
+ };
215
+ case "doc": return {
216
+ format,
217
+ editor: openDoc(bytes),
218
+ path
219
+ };
220
+ case "xls": return {
221
+ format,
222
+ editor: openXls(bytes),
223
+ path
224
+ };
225
+ case "ppt": return {
226
+ format,
227
+ editor: openPpt(bytes),
228
+ path
229
+ };
230
+ case "epub": return {
231
+ format,
232
+ layout: readPdf(epubToPdf(bytes)),
233
+ bytes,
234
+ path
235
+ };
173
236
  case "odf": throw new Error("A standalone .odf formula document has no editor; convert it to PDF (odfToPdf) instead");
174
237
  }
175
238
  }
@@ -180,6 +243,12 @@ function createNewDocument(format) {
180
243
  editor: createDocx(),
181
244
  path: void 0
182
245
  };
246
+ case "markdown": return {
247
+ format,
248
+ editor: createMarkdownEditor(),
249
+ originalText: void 0,
250
+ path: void 0
251
+ };
183
252
  case "pptx": return {
184
253
  format,
185
254
  editor: createPptx(),
@@ -205,6 +274,21 @@ function createNewDocument(format) {
205
274
  editor: createOdg(),
206
275
  path: void 0
207
276
  };
277
+ case "doc": return {
278
+ format,
279
+ editor: createDoc(),
280
+ path: void 0
281
+ };
282
+ case "xls": return {
283
+ format,
284
+ editor: createXls(),
285
+ path: void 0
286
+ };
287
+ case "ppt": return {
288
+ format,
289
+ editor: createPpt(),
290
+ path: void 0
291
+ };
208
292
  case "pdf": {
209
293
  const editor = createPdf();
210
294
  return {
@@ -217,7 +301,7 @@ function createNewDocument(format) {
217
301
  }
218
302
  }
219
303
  async function saveDocumentTo(openDocument, path) {
220
- if (openDocument.format === "odb" || openDocument.format === "xlsx" || openDocument.format === "csv" || openDocument.format === "svg") throw new Error(`A ${openDocument.format} document is opened read-only and cannot be written back`);
304
+ if (openDocument.format === "odb" || openDocument.format === "xlsx" || openDocument.format === "csv" || openDocument.format === "svg" || openDocument.format === "rtf" || openDocument.format === "wpd" || openDocument.format === "epub") throw new Error(`A ${openDocument.format} document is opened read-only and cannot be written back`);
221
305
  if (openDocument.format === "markdown") {
222
306
  await writeFile(path, encodeMarkdownText(openDocument.editor.toMarkdownText()));
223
307
  return;
@@ -233,7 +317,10 @@ const EDITABLE_FORMATS = {
233
317
  odp: true,
234
318
  ods: true,
235
319
  odg: true,
236
- pdf: true
320
+ pdf: true,
321
+ doc: true,
322
+ xls: true,
323
+ ppt: true
237
324
  };
238
325
  const WRITABLE_FORMATS = {
239
326
  ...EDITABLE_FORMATS,
@@ -242,6 +329,9 @@ const WRITABLE_FORMATS = {
242
329
  function isEditableFormat(value) {
243
330
  return value in EDITABLE_FORMATS;
244
331
  }
332
+ function isEditableDocument(document) {
333
+ return isEditableFormat(document.format);
334
+ }
245
335
  function isWritableFormat(value) {
246
336
  return value in WRITABLE_FORMATS;
247
337
  }
@@ -292,16 +382,22 @@ function rootScreenForFormat(format) {
292
382
  switch (format) {
293
383
  case "docx":
294
384
  case "odt":
295
- case "markdown": return { kind: "bodyList" };
385
+ case "markdown":
386
+ case "doc": return { kind: "bodyList" };
296
387
  case "pptx":
297
- case "odp": return { kind: "slideList" };
298
- case "ods": return { kind: "sheetList" };
388
+ case "odp":
389
+ case "ppt": return { kind: "slideList" };
390
+ case "ods":
391
+ case "xls": return { kind: "sheetList" };
299
392
  case "odg": return { kind: "pageList" };
300
393
  case "odb": return { kind: "odbTableList" };
301
394
  case "pdf":
302
395
  case "xlsx":
303
396
  case "csv":
304
- case "svg": return { kind: "pdfPageList" };
397
+ case "svg":
398
+ case "rtf":
399
+ case "wpd":
400
+ case "epub": return { kind: "pdfPageList" };
305
401
  }
306
402
  }
307
403
  function currentScreen(state) {
@@ -450,6 +546,39 @@ function documentWithPath(doc, path) {
450
546
  bytes: doc.bytes,
451
547
  path
452
548
  };
549
+ case "rtf": return {
550
+ format: "rtf",
551
+ layout: doc.layout,
552
+ bytes: doc.bytes,
553
+ path
554
+ };
555
+ case "wpd": return {
556
+ format: "wpd",
557
+ layout: doc.layout,
558
+ bytes: doc.bytes,
559
+ path
560
+ };
561
+ case "doc": return {
562
+ format: "doc",
563
+ editor: doc.editor,
564
+ path
565
+ };
566
+ case "xls": return {
567
+ format: "xls",
568
+ editor: doc.editor,
569
+ path
570
+ };
571
+ case "ppt": return {
572
+ format: "ppt",
573
+ editor: doc.editor,
574
+ path
575
+ };
576
+ case "epub": return {
577
+ format: "epub",
578
+ layout: doc.layout,
579
+ bytes: doc.bytes,
580
+ path
581
+ };
453
582
  }
454
583
  }
455
584
  function reopenEditable(doc, bytes) {
@@ -484,6 +613,21 @@ function reopenEditable(doc, bytes) {
484
613
  editor: openOdg(bytes),
485
614
  path: doc.path
486
615
  };
616
+ case "doc": return {
617
+ format: "doc",
618
+ editor: openDoc(bytes),
619
+ path: doc.path
620
+ };
621
+ case "xls": return {
622
+ format: "xls",
623
+ editor: openXls(bytes),
624
+ path: doc.path
625
+ };
626
+ case "ppt": return {
627
+ format: "ppt",
628
+ editor: openPpt(bytes),
629
+ path: doc.path
630
+ };
487
631
  case "pdf": {
488
632
  const editor = openPdf(bytes);
489
633
  return {
@@ -545,24 +689,39 @@ function wrongDocument(state, expected) {
545
689
  function wordprocessingDocument(state) {
546
690
  const doc = state.openDocument;
547
691
  if (doc === void 0) return;
548
- return doc.format === "docx" || doc.format === "odt" || doc.format === "markdown" ? doc : void 0;
692
+ return doc.format === "docx" || doc.format === "odt" || doc.format === "markdown" || doc.format === "doc" ? doc : void 0;
549
693
  }
550
694
  function styledWordprocessingDocument(state) {
695
+ const doc = state.openDocument;
696
+ if (doc === void 0) return;
697
+ return doc.format === "docx" || doc.format === "odt" || doc.format === "doc" ? doc : void 0;
698
+ }
699
+ function docxOdtDocument(state) {
551
700
  const doc = state.openDocument;
552
701
  if (doc === void 0) return;
553
702
  return doc.format === "docx" || doc.format === "odt" ? doc : void 0;
554
703
  }
555
- function presentationDocument(state) {
704
+ function richPresentationDocument(state) {
556
705
  const doc = state.openDocument;
557
706
  if (doc === void 0) return;
558
707
  return doc.format === "pptx" || doc.format === "odp" ? doc : void 0;
559
708
  }
709
+ function presentationDocument(state) {
710
+ const doc = state.openDocument;
711
+ if (doc === void 0) return;
712
+ return doc.format === "pptx" || doc.format === "odp" || doc.format === "ppt" ? doc : void 0;
713
+ }
560
714
  function shapeHostDocument(state) {
561
715
  const doc = state.openDocument;
562
716
  if (doc === void 0) return;
563
717
  return doc.format === "pptx" || doc.format === "odp" || doc.format === "odg" ? doc : void 0;
564
718
  }
565
719
  function spreadsheetDocument(state) {
720
+ const doc = state.openDocument;
721
+ if (doc === void 0) return;
722
+ return doc.format === "ods" || doc.format === "xls" ? doc : void 0;
723
+ }
724
+ function odsDocument$1(state) {
566
725
  const doc = state.openDocument;
567
726
  if (doc === void 0) return;
568
727
  return doc.format === "ods" ? doc : void 0;
@@ -606,6 +765,7 @@ const isPdfLineItem = (item) => item.kind === "line";
606
765
  const isPdfPathItem = (item) => item.kind === "path";
607
766
  const isPdfImageItem = (item) => item.kind === "image";
608
767
  const isPdfLinkItem = (item) => item.kind === "link";
768
+ const isPdfInternalLinkItem = (item) => item.kind === "internalLink";
609
769
  function vectorHostDocument(state) {
610
770
  const doc = state.openDocument;
611
771
  if (doc === void 0) return;
@@ -627,6 +787,9 @@ function shapeAt(doc, containerIndex, shapeIndex) {
627
787
  function sheetAt(doc, sheetIndex) {
628
788
  return doc.editor.sheets()[sheetIndex];
629
789
  }
790
+ function odsSheetAt(doc, sheetIndex) {
791
+ return doc.editor.sheets()[sheetIndex];
792
+ }
630
793
  function withRun(state, blockIndex, runIndex, apply) {
631
794
  const doc = wordprocessingDocument(state);
632
795
  if (doc === void 0) return wrongDocument(state, "a docx, odt or markdown document");
@@ -640,7 +803,7 @@ function withRun(state, blockIndex, runIndex, apply) {
640
803
  }
641
804
  function withStyledRun(state, blockIndex, runIndex, apply) {
642
805
  const doc = styledWordprocessingDocument(state);
643
- if (doc === void 0) return wrongDocument(state, "a docx or odt document");
806
+ if (doc === void 0) return wrongDocument(state, "a docx, odt or doc document");
644
807
  const paragraph = doc.editor.paragraphs()[blockIndex];
645
808
  if (paragraph === void 0) return withStatus(state, "warning", `There is no paragraph at index ${blockIndex}`);
646
809
  const run = paragraph.runs()[runIndex];
@@ -658,15 +821,35 @@ function withShape(state, containerIndex, shapeIndex, apply) {
658
821
  apply(shape);
659
822
  });
660
823
  }
824
+ function shapeWideAt(doc, containerIndex, shapeIndex) {
825
+ if (doc.format === "odg") return doc.editor.pages()[containerIndex]?.shapes()[shapeIndex];
826
+ return doc.editor.slides()[containerIndex]?.shapes()[shapeIndex];
827
+ }
828
+ function withWideShape(state, containerIndex, shapeIndex, apply) {
829
+ const doc = drawingDocument(state) ?? presentationDocument(state);
830
+ if (doc === void 0) return wrongDocument(state, "a pptx, odp, ppt or odg document");
831
+ const shape = shapeWideAt(doc, containerIndex, shapeIndex);
832
+ if (shape === void 0) return withStatus(state, "warning", `There is no shape ${shapeIndex} on ${doc.format === "odg" ? "page" : "slide"} ${containerIndex}`);
833
+ return mutate(state, doc, () => {
834
+ apply(shape);
835
+ });
836
+ }
661
837
  function withSheet(state, sheetIndex, apply) {
662
838
  const doc = spreadsheetDocument(state);
663
- if (doc === void 0) return wrongDocument(state, "an ods document");
839
+ if (doc === void 0) return wrongDocument(state, "an ods or xls document");
664
840
  const sheet = sheetAt(doc, sheetIndex);
665
841
  if (sheet === void 0) return withStatus(state, "warning", `There is no sheet at index ${sheetIndex}`);
666
842
  return mutate(state, doc, () => {
667
843
  apply(sheet);
668
844
  });
669
845
  }
846
+ function withOdsSheet(state, doc, sheetIndex, apply) {
847
+ const sheet = odsSheetAt(doc, sheetIndex);
848
+ if (sheet === void 0) return withStatus(state, "warning", `There is no sheet at index ${sheetIndex}`);
849
+ return mutate(state, doc, () => {
850
+ apply(sheet);
851
+ });
852
+ }
670
853
  function setTextContainerText(container, text) {
671
854
  const paragraph = container.paragraphs()[0] ?? container.appendParagraph();
672
855
  const runs = paragraph.runs();
@@ -767,7 +950,7 @@ function appReducer(state, action) {
767
950
  selection: {},
768
951
  errorDetail: void 0,
769
952
  stack: [rootScreenForFormat(action.doc.format)]
770
- }, "info", action.doc.format === "xlsx" || action.doc.format === "csv" || action.doc.format === "svg" ? `Opened ${action.path} as a read-only PDF preview -- press ':' then 'export pdf' to save it as a real PDF` : `Opened ${action.path}`);
953
+ }, "info", action.doc.format === "xlsx" || action.doc.format === "csv" || action.doc.format === "svg" || action.doc.format === "rtf" || action.doc.format === "wpd" || action.doc.format === "doc" || action.doc.format === "xls" || action.doc.format === "ppt" || action.doc.format === "epub" ? `Opened ${action.path} as a read-only PDF preview -- press ':' then 'export pdf' to save it as a real PDF` : `Opened ${action.path}`);
771
954
  case "OPEN_FILE_ERROR": return withStatus({
772
955
  ...state,
773
956
  errorDetail: {
@@ -868,7 +1051,7 @@ function appReducer(state, action) {
868
1051
  case "APPEND_TABLE": {
869
1052
  const doc = wordprocessingDocument(state);
870
1053
  if (doc === void 0) return wrongDocument(state, "a docx, odt or markdown document");
871
- let mergeUnsupported = false;
1054
+ const merge = { unsupported: false };
872
1055
  const nextState = mutateGuarded(state, doc, () => {
873
1056
  const table = doc.editor.body.appendTable({
874
1057
  rows: action.rows,
@@ -876,12 +1059,12 @@ function appReducer(state, action) {
876
1059
  });
877
1060
  if (action.merge === void 0) return;
878
1061
  if (!("mergeCells" in table)) {
879
- mergeUnsupported = true;
1062
+ merge.unsupported = true;
880
1063
  return;
881
1064
  }
882
1065
  table.mergeCells(action.merge.startRow, action.merge.startColumn, action.merge.rowSpan, action.merge.colSpan);
883
1066
  });
884
- return mergeUnsupported ? withStatus(nextState, "warning", "Markdown tables do not support merged cells -- the table was created without merging") : nextState;
1067
+ return merge.unsupported ? withStatus(nextState, "warning", "Markdown tables do not support merged cells -- the table was created without merging") : nextState;
885
1068
  }
886
1069
  case "MERGE_TABLE_CELLS": {
887
1070
  const doc = wordprocessingDocument(state);
@@ -935,6 +1118,16 @@ function appReducer(state, action) {
935
1118
  setTextContainerText(item, action.text);
936
1119
  });
937
1120
  }
1121
+ case "INDENT_LIST_ITEM": {
1122
+ const doc = wordprocessingDocument(state);
1123
+ if (doc === void 0) return wrongDocument(state, "a docx, odt or markdown document");
1124
+ if (doc.format !== "odt") return wrongDocument(state, "an odt document (lists are an odt-only concept)");
1125
+ const list = doc.editor.lists()[action.blockIndex];
1126
+ if (list === void 0) return withStatus(state, "warning", `There is no list at index ${action.blockIndex}`);
1127
+ return mutateGuarded(state, doc, () => {
1128
+ list.indentItem(action.itemIndex);
1129
+ });
1130
+ }
938
1131
  case "ADD_LIST": {
939
1132
  const doc = wordprocessingDocument(state);
940
1133
  if (doc === void 0) return wrongDocument(state, "a docx, odt or markdown document");
@@ -944,7 +1137,7 @@ function appReducer(state, action) {
944
1137
  });
945
1138
  }
946
1139
  case "INSERT_PARAGRAPH_IMAGE": {
947
- const doc = styledWordprocessingDocument(state);
1140
+ const doc = docxOdtDocument(state);
948
1141
  if (doc === void 0) return wrongDocument(state, "a docx or odt document");
949
1142
  const paragraph = doc.editor.paragraphs()[action.blockIndex];
950
1143
  if (paragraph === void 0) return withStatus(state, "warning", `There is no paragraph at index ${action.blockIndex}`);
@@ -963,11 +1156,11 @@ function appReducer(state, action) {
963
1156
  if (doc?.format !== "docx") return wrongDocument(state, "a docx document");
964
1157
  const paragraph = doc.editor.paragraphs()[action.blockIndex];
965
1158
  if (paragraph === void 0) return withStatus(state, "warning", `There is no paragraph at index ${action.blockIndex}`);
966
- let written = true;
1159
+ const omml = { written: true };
967
1160
  const nextState = mutate(state, doc, () => {
968
- written = paragraph.appendOfficeMath(action.mathml).written;
1161
+ omml.written = paragraph.appendOfficeMath(action.mathml).written;
969
1162
  });
970
- return written ? nextState : withStatus(nextState, "warning", "The formula produced no OMML content and was not written");
1163
+ return omml.written ? nextState : withStatus(nextState, "warning", "The formula produced no OMML content and was not written");
971
1164
  }
972
1165
  case "INSERT_ODT_FORMULA": {
973
1166
  const doc = state.openDocument;
@@ -984,7 +1177,7 @@ function appReducer(state, action) {
984
1177
  });
985
1178
  }
986
1179
  case "ADD_SLIDE_TABLE": {
987
- const doc = presentationDocument(state);
1180
+ const doc = richPresentationDocument(state);
988
1181
  if (doc === void 0) return wrongDocument(state, "a pptx or odp document");
989
1182
  const slide = doc.editor.slides()[action.slideIndex];
990
1183
  if (slide === void 0) return withStatus(state, "warning", `There is no slide at index ${action.slideIndex}`);
@@ -999,7 +1192,7 @@ function appReducer(state, action) {
999
1192
  });
1000
1193
  }
1001
1194
  case "MERGE_SLIDE_TABLE_CELLS": {
1002
- const doc = presentationDocument(state);
1195
+ const doc = richPresentationDocument(state);
1003
1196
  if (doc === void 0) return wrongDocument(state, "a pptx or odp document");
1004
1197
  if (doc.format === "odp") {
1005
1198
  const entry = doc.editor.slides()[action.slideIndex]?.tables()[action.tableIndex];
@@ -1015,18 +1208,19 @@ function appReducer(state, action) {
1015
1208
  });
1016
1209
  }
1017
1210
  case "ADD_TEXTBOX": {
1018
- const doc = shapeHostDocument(state);
1019
- if (doc === void 0) return wrongDocument(state, "a pptx, odp or odg document");
1020
- if (doc.format === "odg") {
1021
- const page = doc.editor.pages()[action.containerIndex];
1211
+ const drawing = drawingDocument(state);
1212
+ if (drawing !== void 0) {
1213
+ const page = drawing.editor.pages()[action.containerIndex];
1022
1214
  if (page === void 0) return withStatus(state, "warning", `There is no page at index ${action.containerIndex}`);
1023
- return mutate(state, doc, () => {
1215
+ return mutate(state, drawing, () => {
1024
1216
  page.addTextBox({
1025
1217
  frame: action.frame,
1026
1218
  text: action.text
1027
1219
  });
1028
1220
  });
1029
1221
  }
1222
+ const doc = presentationDocument(state);
1223
+ if (doc === void 0) return wrongDocument(state, "a pptx, odp, ppt or odg document");
1030
1224
  const slide = doc.editor.slides()[action.containerIndex];
1031
1225
  if (slide === void 0) return withStatus(state, "warning", `There is no slide at index ${action.containerIndex}`);
1032
1226
  return mutate(state, doc, () => {
@@ -1058,10 +1252,10 @@ function appReducer(state, action) {
1058
1252
  slide.addImage(image);
1059
1253
  });
1060
1254
  }
1061
- case "SET_SHAPE_TEXT": return withShape(state, action.containerIndex, action.shapeIndex, (shape) => {
1255
+ case "SET_SHAPE_TEXT": return withWideShape(state, action.containerIndex, action.shapeIndex, (shape) => {
1062
1256
  shape.text = action.text;
1063
1257
  });
1064
- case "SET_SHAPE_FRAME": return withShape(state, action.containerIndex, action.shapeIndex, (shape) => {
1258
+ case "SET_SHAPE_FRAME": return withWideShape(state, action.containerIndex, action.shapeIndex, (shape) => {
1065
1259
  shape.frame = action.frame;
1066
1260
  });
1067
1261
  case "SET_SHAPE_ROTATION": return withShape(state, action.containerIndex, action.shapeIndex, (shape) => {
@@ -1078,7 +1272,7 @@ function appReducer(state, action) {
1078
1272
  }
1079
1273
  case "ADD_SHEET": {
1080
1274
  const doc = spreadsheetDocument(state);
1081
- if (doc === void 0) return wrongDocument(state, "an ods document");
1275
+ if (doc === void 0) return wrongDocument(state, "an ods or xls document");
1082
1276
  return mutate(state, doc, () => {
1083
1277
  doc.editor.addSheet(action.name);
1084
1278
  });
@@ -1086,27 +1280,37 @@ function appReducer(state, action) {
1086
1280
  case "SET_CELL_VALUE": return withSheet(state, action.sheetIndex, (sheet) => {
1087
1281
  sheet.cell(action.row, action.column).value = action.value;
1088
1282
  });
1089
- case "SET_CELL_FORMULA": return withSheet(state, action.sheetIndex, (sheet) => {
1090
- sheet.cell(action.row, action.column).formula = action.formula;
1091
- });
1092
- case "ADD_SHEET_IMAGE": return withSheet(state, action.sheetIndex, (sheet) => {
1093
- sheet.addImage({
1094
- kind: "image",
1095
- format: action.format,
1096
- base64: bytesToBase64(action.bytes),
1097
- widthPt: action.widthPt,
1098
- heightPt: action.heightPt,
1099
- altText: action.altText,
1100
- anchorRow: action.anchorRow,
1101
- anchorColumn: action.anchorColumn,
1102
- offsetXPt: action.offsetXPt,
1103
- offsetYPt: action.offsetYPt
1283
+ case "SET_CELL_FORMULA": {
1284
+ const doc = odsDocument$1(state);
1285
+ if (doc === void 0) return wrongDocument(state, "an ods document");
1286
+ const sheet = odsSheetAt(doc, action.sheetIndex);
1287
+ if (sheet === void 0) return withStatus(state, "warning", `There is no sheet at index ${action.sheetIndex}`);
1288
+ return mutate(state, doc, () => {
1289
+ sheet.cell(action.row, action.column).formula = action.formula;
1104
1290
  });
1105
- });
1291
+ }
1292
+ case "ADD_SHEET_IMAGE": {
1293
+ const ods = odsDocument$1(state);
1294
+ if (ods === void 0) return wrongDocument(state, "an ods document");
1295
+ return withOdsSheet(state, ods, action.sheetIndex, (sheet) => {
1296
+ sheet.addImage({
1297
+ kind: "image",
1298
+ format: action.format,
1299
+ base64: bytesToBase64(action.bytes),
1300
+ widthPt: action.widthPt,
1301
+ heightPt: action.heightPt,
1302
+ altText: action.altText,
1303
+ anchorRow: action.anchorRow,
1304
+ anchorColumn: action.anchorColumn,
1305
+ offsetXPt: action.offsetXPt,
1306
+ offsetYPt: action.offsetYPt
1307
+ });
1308
+ });
1309
+ }
1106
1310
  case "MERGE_CELLS": {
1107
- const doc = spreadsheetDocument(state);
1311
+ const doc = odsDocument$1(state);
1108
1312
  if (doc === void 0) return wrongDocument(state, "an ods document");
1109
- const sheet = sheetAt(doc, action.sheetIndex);
1313
+ const sheet = odsSheetAt(doc, action.sheetIndex);
1110
1314
  if (sheet === void 0) return withStatus(state, "warning", `There is no sheet at index ${action.sheetIndex}`);
1111
1315
  return mutateGuarded(state, doc, () => {
1112
1316
  sheet.mergeCells(action.startRow, action.startColumn, action.rowSpan, action.colSpan);
@@ -1325,6 +1529,22 @@ function appReducer(state, action) {
1325
1529
  item.widthPt = action.widthPt;
1326
1530
  item.heightPt = action.heightPt;
1327
1531
  });
1532
+ case "SET_PDF_INTERNAL_LINK_DESTINATION": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfInternalLinkItem, "internalLink", (item) => {
1533
+ item.destination = action.destination;
1534
+ });
1535
+ case "SET_PDF_INTERNAL_LINK_FRAME": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfInternalLinkItem, "internalLink", (item) => {
1536
+ item.xPt = action.xPt;
1537
+ item.yPt = action.yPt;
1538
+ item.widthPt = action.widthPt;
1539
+ item.heightPt = action.heightPt;
1540
+ });
1541
+ case "SET_METADATA": {
1542
+ const doc = state.openDocument;
1543
+ if (doc === void 0 || !isEditableDocument(doc)) return wrongDocument(state, "an editable document");
1544
+ return mutate(state, doc, () => {
1545
+ doc.editor.metadata = action.overrides;
1546
+ });
1547
+ }
1328
1548
  case "APPEND_DIAGNOSTIC": return {
1329
1549
  ...state,
1330
1550
  diagnostics: [...state.diagnostics, action.diagnostic]
@@ -1353,7 +1573,7 @@ function appReducer(state, action) {
1353
1573
  case "UNDO": {
1354
1574
  const doc = state.openDocument;
1355
1575
  if (doc === void 0) return withStatus(state, "info", "There is nothing to undo");
1356
- if (doc.format === "odb" || doc.format === "xlsx" || doc.format === "csv" || doc.format === "svg") return withStatus(state, "warning", `A ${doc.format} document is read-only, so it has no history to undo`);
1576
+ if (doc.format === "odb" || doc.format === "xlsx" || doc.format === "csv" || doc.format === "svg" || doc.format === "rtf" || doc.format === "wpd" || doc.format === "doc" || doc.format === "xls" || doc.format === "ppt" || doc.format === "epub") return withStatus(state, "warning", `A ${doc.format} document is read-only, so it has no history to undo`);
1357
1577
  const snapshot = state.undoStack.at(-1);
1358
1578
  if (snapshot === void 0) return withStatus(state, "info", "There is nothing to undo");
1359
1579
  const restored = doc.format === "markdown" ? {
@@ -2057,7 +2277,8 @@ function StatusLine() {
2057
2277
  showDiagnosticsBadge ? /* @__PURE__ */ jsxs(Text, {
2058
2278
  color: "yellow",
2059
2279
  children: [
2060
- " ",
2280
+ " ",
2281
+ "⚠ ",
2061
2282
  state.diagnostics.length,
2062
2283
  " diagnostics -- Ctrl+D"
2063
2284
  ]
@@ -2304,7 +2525,7 @@ function supportsRunStyleExtras(run) {
2304
2525
  }
2305
2526
  function paragraphFamilyDocument(openDocument) {
2306
2527
  if (openDocument === void 0) return;
2307
- return openDocument.format === "docx" || openDocument.format === "odt" || openDocument.format === "markdown" ? openDocument : void 0;
2528
+ return openDocument.format === "docx" || openDocument.format === "odt" || openDocument.format === "markdown" || openDocument.format === "doc" ? openDocument : void 0;
2308
2529
  }
2309
2530
  function liveParagraphAt(doc, blockIndex) {
2310
2531
  return doc.editor.paragraphs()[blockIndex];
@@ -2653,7 +2874,8 @@ function ParagraphFamilyBodyList(props) {
2653
2874
  children: [
2654
2875
  "Merge start row (0-",
2655
2876
  Math.max(0, wizardRows - 1),
2656
- "): "
2877
+ "):",
2878
+ " "
2657
2879
  ]
2658
2880
  }), /* @__PURE__ */ jsx(TextField, {
2659
2881
  value: wizardDraft,
@@ -2667,7 +2889,8 @@ function ParagraphFamilyBodyList(props) {
2667
2889
  children: [
2668
2890
  "Merge start column (0-",
2669
2891
  Math.max(0, wizardColumns - 1),
2670
- "): "
2892
+ "):",
2893
+ " "
2671
2894
  ]
2672
2895
  }), /* @__PURE__ */ jsx(TextField, {
2673
2896
  value: wizardDraft,
@@ -2681,7 +2904,8 @@ function ParagraphFamilyBodyList(props) {
2681
2904
  children: [
2682
2905
  "Merge row span (1-",
2683
2906
  Math.max(1, wizardRows - wizardStartRow),
2684
- "): "
2907
+ "):",
2908
+ " "
2685
2909
  ]
2686
2910
  }), /* @__PURE__ */ jsx(TextField, {
2687
2911
  value: wizardDraft,
@@ -2695,7 +2919,8 @@ function ParagraphFamilyBodyList(props) {
2695
2919
  children: [
2696
2920
  "Merge column span (1-",
2697
2921
  Math.max(1, wizardColumns - wizardStartColumn),
2698
- "): "
2922
+ "):",
2923
+ " "
2699
2924
  ]
2700
2925
  }), /* @__PURE__ */ jsx(TextField, {
2701
2926
  value: wizardDraft,
@@ -3332,7 +3557,7 @@ function TableViewScreen() {
3332
3557
  children: "This table has no rows."
3333
3558
  }) : rows.map((row, rowIndex) => /* @__PURE__ */ jsx(Box, { children: row.cells().map((cell, columnIndex) => {
3334
3559
  const isSelected = rowIndex === clampedRow && columnIndex === clampedColumn;
3335
- const isAnchor = rowIndex === mergeAnchor?.row && columnIndex === mergeAnchor?.column;
3560
+ const isAnchor = rowIndex === mergeAnchor?.row && columnIndex === mergeAnchor.column;
3336
3561
  return /* @__PURE__ */ jsx(Box, {
3337
3562
  width: CELL_WIDTH$2,
3338
3563
  borderStyle: "single",
@@ -3385,7 +3610,8 @@ function TableCellDetailScreen() {
3385
3610
  screen.row,
3386
3611
  ", column ",
3387
3612
  screen.col,
3388
- " of table ",
3613
+ " of table",
3614
+ " ",
3389
3615
  screen.blockIndex,
3390
3616
  "."
3391
3617
  ]
@@ -3450,7 +3676,8 @@ function DocxBodyListScreen() {
3450
3676
  if (doc?.format !== "docx") return /* @__PURE__ */ jsxs(Text, {
3451
3677
  color: "red",
3452
3678
  children: [
3453
- "DocxBodyListScreen requires an open docx document, found ",
3679
+ "DocxBodyListScreen requires an open docx document, found",
3680
+ " ",
3454
3681
  doc === void 0 ? "no open document" : doc.format,
3455
3682
  "."
3456
3683
  ]
@@ -3486,7 +3713,8 @@ function DocxExtrasScreen() {
3486
3713
  if (!isDocx) return /* @__PURE__ */ jsxs(Text, {
3487
3714
  color: "red",
3488
3715
  children: [
3489
- "DocxExtrasScreen requires an open docx document, found ",
3716
+ "DocxExtrasScreen requires an open docx document, found",
3717
+ " ",
3490
3718
  doc === void 0 ? "no open document" : doc.format,
3491
3719
  "."
3492
3720
  ]
@@ -3499,7 +3727,8 @@ function DocxExtrasScreen() {
3499
3727
  children: [
3500
3728
  "Comments, footnotes, headers, footers, numbering (",
3501
3729
  lines.length,
3502
- " of ",
3730
+ " of",
3731
+ " ",
3503
3732
  allLines.length,
3504
3733
  " lines)"
3505
3734
  ]
@@ -3570,7 +3799,8 @@ function MarkdownBodyListScreen() {
3570
3799
  if (doc?.format !== "markdown") return /* @__PURE__ */ jsxs(Text, {
3571
3800
  color: "red",
3572
3801
  children: [
3573
- "MarkdownBodyListScreen requires an open markdown document, found ",
3802
+ "MarkdownBodyListScreen requires an open markdown document, found",
3803
+ " ",
3574
3804
  doc === void 0 ? "no open document" : doc.format,
3575
3805
  "."
3576
3806
  ]
@@ -4012,7 +4242,8 @@ function OdbTableListScreen() {
4012
4242
  table.tableName,
4013
4243
  " (",
4014
4244
  table.columns.length,
4015
- " columns, ",
4245
+ " columns,",
4246
+ " ",
4016
4247
  table.rows.length,
4017
4248
  " rows)"
4018
4249
  ]
@@ -4655,20 +4886,17 @@ function OdgPageDetailScreen() {
4655
4886
  //#region src/tui/screens/editors/odg/shape-or-vector-detail.tsx
4656
4887
  function buildVectorRows(vector, liveVector, dispatch) {
4657
4888
  const rows = [];
4658
- if (vector.kind !== "line" && liveVector.kind !== "line") {
4659
- const fillTarget = liveVector;
4660
- rows.push({
4661
- label: `Fill: ${vector.fill === void 0 ? "none" : formatColor$1(vector.fill)}`,
4662
- currentValue: vector.fill === void 0 ? "" : `${vector.fill.r} ${vector.fill.g} ${vector.fill.b}`,
4663
- commit: (raw) => {
4664
- dispatch({
4665
- type: "SET_VECTOR_FILL",
4666
- vector: fillTarget,
4667
- fill: parseColorField(raw)
4668
- });
4669
- }
4670
- });
4671
- }
4889
+ if (vector.kind !== "line" && liveVector.kind !== "line") rows.push({
4890
+ label: `Fill: ${vector.fill === void 0 ? "none" : formatColor$1(vector.fill)}`,
4891
+ currentValue: vector.fill === void 0 ? "" : `${vector.fill.r} ${vector.fill.g} ${vector.fill.b}`,
4892
+ commit: (raw) => {
4893
+ dispatch({
4894
+ type: "SET_VECTOR_FILL",
4895
+ vector: liveVector,
4896
+ fill: parseColorField(raw)
4897
+ });
4898
+ }
4899
+ });
4672
4900
  rows.push({
4673
4901
  label: `Stroke: ${vector.stroke === void 0 ? "none" : `${formatColor$1(vector.stroke.color)} ${formatPt$1(vector.stroke.widthPt)}pt`}`,
4674
4902
  currentValue: vector.stroke === void 0 ? "" : `${vector.stroke.color.r} ${vector.stroke.color.g} ${vector.stroke.color.b} ${vector.stroke.widthPt}`,
@@ -4995,8 +5223,15 @@ function buildPptxSlideFamilyAdapter(editor) {
4995
5223
  function buildOdpSlideFamilyAdapter(editor) {
4996
5224
  return buildSlideFamilyAdapter("odp", editor);
4997
5225
  }
5226
+ function buildPptSlideFamilyAdapter(editor) {
5227
+ return buildSlideFamilyAdapter("ppt", editor);
5228
+ }
4998
5229
  function assertPresentationDocument(doc) {
4999
- if (doc === void 0 || doc.format !== "pptx" && doc.format !== "odp") throw new Error("Expected an open pptx or odp document here; the screen router in app.tsx should only reach a slide-family screen for one of those formats.");
5230
+ if (doc === void 0 || doc.format !== "pptx" && doc.format !== "odp" && doc.format !== "ppt") throw new Error("Expected an open pptx, odp or ppt document here; the screen router in app.tsx should only reach a slide-family screen for one of those formats.");
5231
+ return doc;
5232
+ }
5233
+ function assertRichPresentationDocument(doc) {
5234
+ if (doc === void 0 || doc.format !== "pptx" && doc.format !== "odp") throw new Error("Expected an open pptx or odp document here; the screen router in app.tsx should only reach this rich slide-family screen for one of those formats -- a ppt document routes to its own narrower detail screen.");
5000
5235
  return doc;
5001
5236
  }
5002
5237
  const DEFAULT_FRAME_MARGIN_FRACTION = .1;
@@ -5079,8 +5314,9 @@ function SlideFamilySlideList(props) {
5079
5314
  /* @__PURE__ */ jsxs(Text, {
5080
5315
  bold: true,
5081
5316
  children: [
5082
- props.adapter.formatLabel === "pptx" ? "PowerPoint" : "Impress",
5083
- " slides (",
5317
+ props.adapter.formatLabel === "odp" ? "Impress" : props.adapter.formatLabel === "ppt" ? "PowerPoint 97-2003" : "PowerPoint",
5318
+ " ",
5319
+ "slides (",
5084
5320
  rows.length,
5085
5321
  ")"
5086
5322
  ]
@@ -5224,7 +5460,7 @@ function ShapeEditorScreen(props) {
5224
5460
  const state = useAppState();
5225
5461
  const dispatch = useAppDispatch();
5226
5462
  const overlayOpen = anyOverlayOpen(state);
5227
- const doc = assertPresentationDocument(state.openDocument);
5463
+ const doc = assertRichPresentationDocument(state.openDocument);
5228
5464
  const { slideIndex, shapeIndex } = props.screen;
5229
5465
  const shape = doc.editor.slides()[slideIndex]?.shapes()[shapeIndex];
5230
5466
  const [editingField, setEditingField] = useState(void 0);
@@ -5566,7 +5802,7 @@ function SlideDetailScreen(props) {
5566
5802
  const state = useAppState();
5567
5803
  const dispatch = useAppDispatch();
5568
5804
  const overlayOpen = anyOverlayOpen(state);
5569
- const doc = assertPresentationDocument(state.openDocument);
5805
+ const doc = assertRichPresentationDocument(state.openDocument);
5570
5806
  const { slideIndex } = props.screen;
5571
5807
  const slide = doc.editor.slides()[slideIndex];
5572
5808
  const shapes = slide === void 0 ? [] : slide.shapes();
@@ -5779,7 +6015,8 @@ function SlideDetailScreen(props) {
5779
6015
  inverse: isSelected,
5780
6016
  children: [
5781
6017
  row.index + 1,
5782
- ". ",
6018
+ ".",
6019
+ " ",
5783
6020
  describeSlideFamilyShape({
5784
6021
  text: row.text,
5785
6022
  frame: row.frame
@@ -5875,7 +6112,7 @@ const CELL_WIDTH$1 = 16;
5875
6112
  function SlideTableDetailScreen(props) {
5876
6113
  const state = useAppState();
5877
6114
  const dispatch = useAppDispatch();
5878
- const doc = assertPresentationDocument(state.openDocument);
6115
+ const doc = assertRichPresentationDocument(state.openDocument);
5879
6116
  const { slideIndex, tableIndex } = props.screen;
5880
6117
  const [cursor, setCursor] = useState({
5881
6118
  row: 0,
@@ -5990,7 +6227,7 @@ function SlideTableDetailScreen(props) {
5990
6227
  children: "This table has no rows."
5991
6228
  }) : rows.map((row, rowIndex) => /* @__PURE__ */ jsx(Box, { children: row.cells.map((cell, columnIndex) => {
5992
6229
  const isCursor = rowIndex === clampedRow && columnIndex === clampedColumn;
5993
- const isAnchor = rowIndex === mergeAnchor?.row && columnIndex === mergeAnchor?.column;
6230
+ const isAnchor = rowIndex === mergeAnchor?.row && columnIndex === mergeAnchor.column;
5994
6231
  return /* @__PURE__ */ jsx(Box, {
5995
6232
  width: CELL_WIDTH$1,
5996
6233
  borderStyle: "single",
@@ -6087,6 +6324,29 @@ function OdpSlideListScreen() {
6087
6324
  return /* @__PURE__ */ jsx(SlideFamilySlideList, { adapter: buildOdpSlideFamilyAdapter(doc.editor) });
6088
6325
  }
6089
6326
  //#endregion
6327
+ //#region src/tui/screens/editors/doc/index.tsx
6328
+ function DocBodyListScreen() {
6329
+ const state = useAppState();
6330
+ const dispatch = useAppDispatch();
6331
+ const doc = state.openDocument;
6332
+ if (doc?.format !== "doc") return /* @__PURE__ */ jsxs(Text, {
6333
+ color: "red",
6334
+ children: [
6335
+ "DocBodyListScreen requires an open doc document, found",
6336
+ " ",
6337
+ doc === void 0 ? "no open document" : doc.format,
6338
+ "."
6339
+ ]
6340
+ });
6341
+ const adapter = createParagraphFamilyAdapter({
6342
+ formatLabel: "doc",
6343
+ paragraphs: () => doc.editor.paragraphs(),
6344
+ tables: () => doc.editor.tables(),
6345
+ dispatch
6346
+ });
6347
+ return /* @__PURE__ */ jsx(ParagraphFamilyBodyList, { adapter });
6348
+ }
6349
+ //#endregion
6090
6350
  //#region src/tui/screens/editors/ods/shared.ts
6091
6351
  function odsDocument(state) {
6092
6352
  const doc = state.openDocument;
@@ -6914,7 +7174,8 @@ function OdsSpreadsheetGridScreen() {
6914
7174
  padCell(row.address, COMPACT_ADDRESS_WIDTH),
6915
7175
  "[",
6916
7176
  row.badge,
6917
- "] ",
7177
+ "]",
7178
+ " ",
6918
7179
  row.displayText
6919
7180
  ]
6920
7181
  })
@@ -6931,7 +7192,7 @@ function OdsSpreadsheetGridScreen() {
6931
7192
  children: [`${row + 1}`.padStart(4), " "]
6932
7193
  }), visibleColumns.map((column) => {
6933
7194
  const isCursor = row === clampedRow && column === clampedColumn;
6934
- const isAnchor = row === mergeAnchor?.row && column === mergeAnchor?.column;
7195
+ const isAnchor = row === mergeAnchor?.row && column === mergeAnchor.column;
6935
7196
  const cell = cells.get(cellKey(row, column));
6936
7197
  return /* @__PURE__ */ jsx(Text, {
6937
7198
  inverse: isCursor,
@@ -7015,51 +7276,491 @@ function OdsSpreadsheetGridScreen() {
7015
7276
  });
7016
7277
  }
7017
7278
  //#endregion
7018
- //#region src/tui/screens/editors/odt/list-editor.tsx
7019
- function ListEditorScreen() {
7279
+ //#region src/tui/screens/editors/ppt/index.tsx
7280
+ function pptDocument(state) {
7281
+ const doc = state.openDocument;
7282
+ if (doc?.format !== "ppt") throw new Error("A ppt screen rendered without an open ppt document; check the screen router in app.tsx.");
7283
+ return doc;
7284
+ }
7285
+ function PptSlideListScreen() {
7286
+ const doc = pptDocument(useAppState());
7287
+ return /* @__PURE__ */ jsx(SlideFamilySlideList, { adapter: buildPptSlideFamilyAdapter(doc.editor) });
7288
+ }
7289
+ const TEXTBOX_FIELDS = [
7290
+ {
7291
+ key: "xPt",
7292
+ label: "x (pt)",
7293
+ defaultValue: "40"
7294
+ },
7295
+ {
7296
+ key: "yPt",
7297
+ label: "y (pt)",
7298
+ defaultValue: "30"
7299
+ },
7300
+ {
7301
+ key: "widthPt",
7302
+ label: "width (pt)",
7303
+ defaultValue: "640"
7304
+ },
7305
+ {
7306
+ key: "heightPt",
7307
+ label: "height (pt)",
7308
+ defaultValue: "80"
7309
+ },
7310
+ {
7311
+ key: "text",
7312
+ label: "text",
7313
+ defaultValue: ""
7314
+ }
7315
+ ];
7316
+ function PptSlideDetailScreen(props) {
7020
7317
  const state = useAppState();
7021
7318
  const dispatch = useAppDispatch();
7319
+ const doc = pptDocument(state);
7022
7320
  const overlayOpen = anyOverlayOpen(state);
7023
- const [isAdding, setIsAdding] = useState(false);
7024
- const [newItemText, setNewItemText] = useState("");
7025
- const [editingIndex, setEditingIndex] = useState(void 0);
7026
- const screen = currentScreen(state);
7027
- const doc = paragraphFamilyDocument(state.openDocument);
7028
- const list = screen.kind === "listEditor" && doc?.format === "odt" ? doc.editor.lists()[screen.blockIndex] : void 0;
7029
- const items = list === void 0 ? [] : list.items();
7030
- const rows = items.map((item, index) => ({
7031
- item,
7032
- index
7033
- }));
7034
- const itemCount = items.length;
7035
- const isNavigationActive = !overlayOpen && !isAdding && editingIndex === void 0;
7321
+ const slide = doc.editor.slides()[props.slideIndex];
7322
+ const [draft, setDraft] = useState(void 0);
7323
+ const [draftShape, setDraftShape] = useState(void 0);
7324
+ const [addingTextbox, setAddingTextbox] = useState(false);
7325
+ const shapes = slide?.shapes() ?? [];
7326
+ const rows = [...shapes.map((shape, index) => {
7327
+ const frame = shape.frame;
7328
+ return {
7329
+ kind: "shape",
7330
+ index,
7331
+ label: `${`${frame.xPt.toFixed(0)},${frame.yPt.toFixed(0)} ${frame.widthPt.toFixed(0)}x${frame.heightPt.toFixed(0)}pt`} ${shape.text === "" ? "(empty shape)" : shape.text}`
7332
+ };
7333
+ }), {
7334
+ kind: "notes",
7335
+ index: shapes.length,
7336
+ label: slide === void 0 || slide.notes === "" ? "Notes: (none)" : `Notes: ${slide.notes}`
7337
+ }];
7036
7338
  const { selectedIndex } = useNavigationInput({
7037
- itemCount,
7038
- isActive: isNavigationActive,
7339
+ itemCount: rows.length,
7340
+ isActive: !overlayOpen && draft === void 0 && !addingTextbox,
7039
7341
  onBack: () => {
7040
7342
  dispatch({ type: "POP_SCREEN" });
7041
7343
  },
7042
7344
  onSelect: (index) => {
7043
- setEditingIndex(index);
7345
+ const row = rows[index];
7346
+ if (row === void 0) return;
7347
+ if (row.kind === "notes") {
7348
+ dispatch({
7349
+ type: "PUSH_SCREEN",
7350
+ screen: {
7351
+ kind: "notesEditor",
7352
+ slideIndex: props.slideIndex
7353
+ }
7354
+ });
7355
+ return;
7356
+ }
7357
+ setDraft(shapes[row.index]?.text ?? "");
7358
+ setDraftShape(row.index);
7044
7359
  },
7045
7360
  onAppend: () => {
7046
- setIsAdding(true);
7361
+ setAddingTextbox(true);
7047
7362
  }
7048
7363
  });
7049
- useInput((input, key) => {
7050
- if (key.tab || input === ">" || input === "<") dispatch({
7051
- type: "SET_STATUS",
7052
- severity: "warning",
7053
- text: "Indenting a list item needs a new reducer action this pass didn't add -- OdtListItem.addNestedList() has no wiring yet"
7364
+ useInput((input) => {
7365
+ if (input === "n" && slide !== void 0) dispatch({
7366
+ type: "PUSH_SCREEN",
7367
+ screen: {
7368
+ kind: "notesEditor",
7369
+ slideIndex: props.slideIndex
7370
+ }
7054
7371
  });
7055
- }, { isActive: isNavigationActive });
7056
- if (screen.kind !== "listEditor") return /* @__PURE__ */ jsx(Text, {
7057
- color: "red",
7058
- children: "ListEditorScreen rendered outside a listEditor screen."
7059
- });
7060
- if (doc?.format !== "odt") return /* @__PURE__ */ jsx(Text, {
7372
+ }, { isActive: !overlayOpen && draft === void 0 && !addingTextbox });
7373
+ if (slide === void 0) return /* @__PURE__ */ jsxs(Text, {
7061
7374
  color: "red",
7062
- children: "ListEditorScreen requires an open odt document."
7375
+ children: [
7376
+ "There is no slide at index ",
7377
+ props.slideIndex,
7378
+ " in this presentation."
7379
+ ]
7380
+ });
7381
+ if (draft !== void 0 && draftShape !== void 0) return /* @__PURE__ */ jsxs(Box, {
7382
+ flexDirection: "column",
7383
+ children: [
7384
+ /* @__PURE__ */ jsxs(Text, {
7385
+ bold: true,
7386
+ children: [
7387
+ "Shape ",
7388
+ draftShape,
7389
+ " text"
7390
+ ]
7391
+ }),
7392
+ /* @__PURE__ */ jsx(TextField, {
7393
+ value: draft,
7394
+ isFocused: !overlayOpen,
7395
+ placeholder: "Shape text",
7396
+ onChange: setDraft,
7397
+ onSubmit: (value) => {
7398
+ dispatch({
7399
+ type: "SET_SHAPE_TEXT",
7400
+ containerIndex: props.slideIndex,
7401
+ shapeIndex: draftShape,
7402
+ text: value
7403
+ });
7404
+ setDraft(void 0);
7405
+ setDraftShape(void 0);
7406
+ },
7407
+ onCancel: () => {
7408
+ setDraft(void 0);
7409
+ setDraftShape(void 0);
7410
+ }
7411
+ }),
7412
+ /* @__PURE__ */ jsx(Text, {
7413
+ dimColor: true,
7414
+ children: "Enter to save, Esc to cancel"
7415
+ })
7416
+ ]
7417
+ });
7418
+ if (addingTextbox) return /* @__PURE__ */ jsx(FieldWizard, {
7419
+ fields: TEXTBOX_FIELDS,
7420
+ onCancel: () => {
7421
+ setAddingTextbox(false);
7422
+ },
7423
+ onComplete: (values) => {
7424
+ dispatch({
7425
+ type: "ADD_TEXTBOX",
7426
+ containerIndex: props.slideIndex,
7427
+ frame: {
7428
+ xPt: parseNumberField(requireFieldValue(values, "xPt"), 40),
7429
+ yPt: parseNumberField(requireFieldValue(values, "yPt"), 30),
7430
+ widthPt: parseNumberField(requireFieldValue(values, "widthPt"), 640),
7431
+ heightPt: parseNumberField(requireFieldValue(values, "heightPt"), 80)
7432
+ },
7433
+ text: requireFieldValue(values, "text")
7434
+ });
7435
+ setAddingTextbox(false);
7436
+ }
7437
+ });
7438
+ return /* @__PURE__ */ jsxs(Box, {
7439
+ flexDirection: "column",
7440
+ children: [
7441
+ /* @__PURE__ */ jsxs(Text, {
7442
+ bold: true,
7443
+ children: ["Slide ", props.slideIndex + 1]
7444
+ }),
7445
+ rows.map((row, index) => /* @__PURE__ */ jsx(Text, {
7446
+ color: index === selectedIndex ? "cyan" : void 0,
7447
+ inverse: index === selectedIndex,
7448
+ children: row.kind === "notes" ? row.label : ` ${row.label}`
7449
+ }, `${row.kind}:${row.index}`)),
7450
+ /* @__PURE__ */ jsx(Text, {
7451
+ dimColor: true,
7452
+ children: "Enter: edit text / notes n: notes a: add text box Esc: back"
7453
+ })
7454
+ ]
7455
+ });
7456
+ }
7457
+ //#endregion
7458
+ //#region src/tui/screens/editors/xls/index.tsx
7459
+ function xlsDocument(state) {
7460
+ const doc = state.openDocument;
7461
+ if (doc?.format !== "xls") throw new Error("An xls screen rendered without an open xls document; check the screen router in app.tsx.");
7462
+ return doc;
7463
+ }
7464
+ function XlsSheetListScreen() {
7465
+ const state = useAppState();
7466
+ const dispatch = useAppDispatch();
7467
+ const doc = xlsDocument(state);
7468
+ const [draftName, setDraftName] = useState(void 0);
7469
+ const isAdding = draftName !== void 0;
7470
+ const isActive = !anyOverlayOpen(state) && !isAdding;
7471
+ const query = state.searchQuery.trim().toLowerCase();
7472
+ const rows = doc.editor.sheets().map((sheet, index) => ({
7473
+ index,
7474
+ name: sheet.name
7475
+ })).filter((row) => query.length === 0 || row.name.toLowerCase().includes(query));
7476
+ const commitAdd = (name) => {
7477
+ const trimmed = name.trim();
7478
+ if (trimmed.length === 0) {
7479
+ dispatch({
7480
+ type: "SET_STATUS",
7481
+ severity: "warning",
7482
+ text: "A sheet needs a name"
7483
+ });
7484
+ return;
7485
+ }
7486
+ dispatch({
7487
+ type: "ADD_SHEET",
7488
+ name: trimmed
7489
+ });
7490
+ setDraftName(void 0);
7491
+ };
7492
+ const { selectedIndex } = useNavigationInput({
7493
+ itemCount: rows.length,
7494
+ onSelect: (index) => {
7495
+ const row = rows[index];
7496
+ if (row === void 0) return;
7497
+ dispatch({
7498
+ type: "PUSH_SCREEN",
7499
+ screen: {
7500
+ kind: "spreadsheetGrid",
7501
+ sheetIndex: row.index
7502
+ }
7503
+ });
7504
+ },
7505
+ onBack: () => {
7506
+ dispatch({ type: "POP_SCREEN" });
7507
+ },
7508
+ onAppend: () => {
7509
+ setDraftName("");
7510
+ },
7511
+ isActive
7512
+ });
7513
+ return /* @__PURE__ */ jsxs(Box, {
7514
+ flexDirection: "column",
7515
+ children: [
7516
+ /* @__PURE__ */ jsxs(Text, {
7517
+ bold: true,
7518
+ children: [
7519
+ "Sheets (",
7520
+ rows.length,
7521
+ ")"
7522
+ ]
7523
+ }),
7524
+ /* @__PURE__ */ jsx(ListView, {
7525
+ items: rows,
7526
+ selectedIndex,
7527
+ emptyMessage: "This workbook has no sheets yet -- press 'a' to add one.",
7528
+ renderItem: (row, isSelected) => /* @__PURE__ */ jsx(Text, {
7529
+ color: isSelected ? "cyan" : void 0,
7530
+ inverse: isSelected,
7531
+ children: row.name
7532
+ })
7533
+ }),
7534
+ draftName === void 0 ? /* @__PURE__ */ jsx(Text, {
7535
+ dimColor: true,
7536
+ children: "Enter to open, a to add a sheet, Esc to go back"
7537
+ }) : /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, {
7538
+ color: "cyan",
7539
+ children: "New sheet name: "
7540
+ }), /* @__PURE__ */ jsx(TextField, {
7541
+ value: draftName,
7542
+ isFocused: !anyOverlayOpen(state),
7543
+ placeholder: "Sheet name",
7544
+ onChange: setDraftName,
7545
+ onSubmit: commitAdd,
7546
+ onCancel: () => {
7547
+ setDraftName(void 0);
7548
+ }
7549
+ })] })
7550
+ ]
7551
+ });
7552
+ }
7553
+ function columnLetters(column) {
7554
+ let value = column;
7555
+ let letters = "";
7556
+ do {
7557
+ letters = String.fromCharCode(value % 26 + 65) + letters;
7558
+ value = Math.floor(value / 26) - 1;
7559
+ } while (value >= 0);
7560
+ return letters;
7561
+ }
7562
+ const GRID_ROWS = 10;
7563
+ const GRID_COLUMNS = 6;
7564
+ function XlsSpreadsheetGridScreen(props) {
7565
+ const state = useAppState();
7566
+ const dispatch = useAppDispatch();
7567
+ const doc = xlsDocument(state);
7568
+ const overlayOpen = anyOverlayOpen(state);
7569
+ const [compact, setCompact] = useState(false);
7570
+ const [cursor, setCursor] = useState({
7571
+ row: 0,
7572
+ column: 0
7573
+ });
7574
+ const [editSession, setEditSession] = useState(void 0);
7575
+ const editing = editSession !== void 0;
7576
+ const sheet = doc.editor.sheets()[props.sheetIndex];
7577
+ useInput((input, key) => {
7578
+ if (sheet === void 0) return;
7579
+ const move = (dRow, dColumn) => {
7580
+ setCursor((current) => ({
7581
+ row: Math.max(0, current.row + dRow),
7582
+ column: Math.max(0, current.column + dColumn)
7583
+ }));
7584
+ };
7585
+ if (key.escape) {
7586
+ if (editing) {
7587
+ setEditSession(void 0);
7588
+ return;
7589
+ }
7590
+ dispatch({ type: "POP_SCREEN" });
7591
+ return;
7592
+ }
7593
+ if (key.upArrow || input === "k") {
7594
+ move(-1, 0);
7595
+ return;
7596
+ }
7597
+ if (key.downArrow || input === "j") {
7598
+ move(1, 0);
7599
+ return;
7600
+ }
7601
+ if (key.leftArrow || input === "h") {
7602
+ move(0, -1);
7603
+ return;
7604
+ }
7605
+ if (key.rightArrow || input === "l") {
7606
+ move(0, 1);
7607
+ return;
7608
+ }
7609
+ if (key.return) {
7610
+ const existing = sheet.cells().find((cell) => cell.row === cursor.row && cell.column === cursor.column);
7611
+ setEditSession({
7612
+ seedText: existing?.displayText ?? "",
7613
+ seedKind: existing?.value.kind ?? "empty"
7614
+ });
7615
+ return;
7616
+ }
7617
+ if (input === "t") setCompact((value) => !value);
7618
+ }, { isActive: !overlayOpen });
7619
+ if (sheet === void 0) return /* @__PURE__ */ jsxs(Text, {
7620
+ color: "red",
7621
+ children: [
7622
+ "There is no sheet at index ",
7623
+ props.sheetIndex,
7624
+ " in this workbook."
7625
+ ]
7626
+ });
7627
+ const cells = new Map(sheet.cells().map((cell) => [`${cell.row}:${cell.column}`, cell]));
7628
+ const cursorAddress = `${columnLetters(cursor.column)}${cursor.row + 1}`;
7629
+ const cursorCell = cells.get(`${cursor.row}:${cursor.column}`);
7630
+ if (compact) {
7631
+ const entries = sheet.cells();
7632
+ return /* @__PURE__ */ jsxs(Box, {
7633
+ flexDirection: "column",
7634
+ children: [
7635
+ /* @__PURE__ */ jsxs(Text, {
7636
+ bold: true,
7637
+ children: [
7638
+ sheet.name,
7639
+ " -- non-empty cells (",
7640
+ entries.length,
7641
+ ") -- 't' back to grid"
7642
+ ]
7643
+ }),
7644
+ entries.length === 0 ? /* @__PURE__ */ jsx(Text, {
7645
+ dimColor: true,
7646
+ children: "No cells carry a value yet."
7647
+ }) : entries.slice(0, 200).map((cell) => /* @__PURE__ */ jsxs(Text, { children: [
7648
+ columnLetters(cell.column),
7649
+ cell.row + 1,
7650
+ ":",
7651
+ " ",
7652
+ cell.displayText === "" ? "(empty)" : cell.displayText
7653
+ ] }, `${cell.row}:${cell.column}`)),
7654
+ /* @__PURE__ */ jsx(Text, {
7655
+ dimColor: true,
7656
+ children: "t: full grid Esc: back"
7657
+ })
7658
+ ]
7659
+ });
7660
+ }
7661
+ const windowStartRow = Math.min(Math.max(0, cursor.row - 9), Math.max(0, cursor.row));
7662
+ const windowStartColumn = Math.min(Math.max(0, cursor.column - 5), Math.max(0, cursor.column));
7663
+ return /* @__PURE__ */ jsxs(Box, {
7664
+ flexDirection: "column",
7665
+ children: [
7666
+ /* @__PURE__ */ jsxs(Text, {
7667
+ bold: true,
7668
+ children: [
7669
+ sheet.name,
7670
+ " (",
7671
+ cells.size,
7672
+ " cells)"
7673
+ ]
7674
+ }),
7675
+ Array.from({ length: GRID_ROWS }, (_, r) => windowStartRow + r).map((row) => /* @__PURE__ */ jsx(Text, { children: Array.from({ length: GRID_COLUMNS }, (_, c) => windowStartColumn + c).map((column) => {
7676
+ const cell = cells.get(`${row}:${column}`);
7677
+ const isCursor = row === cursor.row && column === cursor.column;
7678
+ const padded = (cell?.displayText ?? "").slice(0, 12).padEnd(12, " ");
7679
+ return isCursor ? `[${padded.slice(0, 12)}]` : ` ${padded} `;
7680
+ }).join("") }, row)),
7681
+ editSession === void 0 ? /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsxs(Text, {
7682
+ color: "cyan",
7683
+ children: [cursorAddress, " "]
7684
+ }), /* @__PURE__ */ jsx(Text, { children: cursorCell === void 0 ? "(empty)" : cursorCell.displayText })] }) : /* @__PURE__ */ jsx(OdsCellEditor, {
7685
+ address: cursorAddress,
7686
+ initialText: editSession.seedText,
7687
+ initialKind: editSession.seedKind,
7688
+ isActive: !overlayOpen,
7689
+ onCommit: (value) => {
7690
+ dispatch({
7691
+ type: "SET_CELL_VALUE",
7692
+ sheetIndex: props.sheetIndex,
7693
+ row: cursor.row,
7694
+ column: cursor.column,
7695
+ value
7696
+ });
7697
+ setEditSession(void 0);
7698
+ },
7699
+ onCancel: () => {
7700
+ setEditSession(void 0);
7701
+ }
7702
+ }),
7703
+ /* @__PURE__ */ jsx(Text, {
7704
+ dimColor: true,
7705
+ children: "hjkl/arrows: move Enter: edit t: non-empty list Esc: back"
7706
+ })
7707
+ ]
7708
+ });
7709
+ }
7710
+ //#endregion
7711
+ //#region src/tui/screens/editors/odt/list-editor.tsx
7712
+ function ListEditorScreen() {
7713
+ const state = useAppState();
7714
+ const dispatch = useAppDispatch();
7715
+ const overlayOpen = anyOverlayOpen(state);
7716
+ const [isAdding, setIsAdding] = useState(false);
7717
+ const [newItemText, setNewItemText] = useState("");
7718
+ const [editingIndex, setEditingIndex] = useState(void 0);
7719
+ const screen = currentScreen(state);
7720
+ const doc = paragraphFamilyDocument(state.openDocument);
7721
+ const blockIndex = screen.kind === "listEditor" ? screen.blockIndex : void 0;
7722
+ const list = blockIndex !== void 0 && doc?.format === "odt" ? doc.editor.lists()[blockIndex] : void 0;
7723
+ const items = list === void 0 ? [] : list.items();
7724
+ const rows = items.map((item, index) => ({
7725
+ item,
7726
+ index
7727
+ }));
7728
+ const itemCount = items.length;
7729
+ const isNavigationActive = !overlayOpen && !isAdding && editingIndex === void 0;
7730
+ const { selectedIndex } = useNavigationInput({
7731
+ itemCount,
7732
+ isActive: isNavigationActive,
7733
+ onBack: () => {
7734
+ dispatch({ type: "POP_SCREEN" });
7735
+ },
7736
+ onSelect: (index) => {
7737
+ setEditingIndex(index);
7738
+ },
7739
+ onAppend: () => {
7740
+ setIsAdding(true);
7741
+ }
7742
+ });
7743
+ useInput((input, key) => {
7744
+ if (key.tab || input === ">") {
7745
+ if (blockIndex === void 0) return;
7746
+ dispatch({
7747
+ type: "INDENT_LIST_ITEM",
7748
+ blockIndex,
7749
+ itemIndex: selectedIndex
7750
+ });
7751
+ } else if (input === "<") dispatch({
7752
+ type: "SET_STATUS",
7753
+ severity: "warning",
7754
+ text: "Outdenting isn't reachable from this screen yet -- it only browses top-level lists, with no route into an item's own nested list"
7755
+ });
7756
+ }, { isActive: isNavigationActive });
7757
+ if (screen.kind !== "listEditor") return /* @__PURE__ */ jsx(Text, {
7758
+ color: "red",
7759
+ children: "ListEditorScreen rendered outside a listEditor screen."
7760
+ });
7761
+ if (doc?.format !== "odt") return /* @__PURE__ */ jsx(Text, {
7762
+ color: "red",
7763
+ children: "ListEditorScreen requires an open odt document."
7063
7764
  });
7064
7765
  if (list === void 0) return /* @__PURE__ */ jsxs(Text, {
7065
7766
  color: "red",
@@ -7125,7 +7826,8 @@ function ListEditorScreen() {
7125
7826
  inverse: isSelected,
7126
7827
  children: [
7127
7828
  row.index + 1,
7128
- ". ",
7829
+ ".",
7830
+ " ",
7129
7831
  trimmed.length === 0 ? "(empty)" : row.item.text
7130
7832
  ]
7131
7833
  });
@@ -7154,7 +7856,7 @@ function ListEditorScreen() {
7154
7856
  }
7155
7857
  })] }) : /* @__PURE__ */ jsx(Text, {
7156
7858
  dimColor: true,
7157
- children: "Enter to edit an item, a to add, Esc back"
7859
+ children: "Enter to edit an item, a to add, Tab/> to indent, Esc back"
7158
7860
  })
7159
7861
  ]
7160
7862
  });
@@ -7168,7 +7870,8 @@ function OdtBodyListScreen() {
7168
7870
  if (doc?.format !== "odt") return /* @__PURE__ */ jsxs(Text, {
7169
7871
  color: "red",
7170
7872
  children: [
7171
- "OdtBodyListScreen requires an open odt document, found ",
7873
+ "OdtBodyListScreen requires an open odt document, found",
7874
+ " ",
7172
7875
  doc === void 0 ? "no open document" : doc.format,
7173
7876
  "."
7174
7877
  ]
@@ -7185,7 +7888,7 @@ function OdtBodyListScreen() {
7185
7888
  //#endregion
7186
7889
  //#region src/tui/screens/editors/pdf/shared.ts
7187
7890
  function requirePdfDocument(openDocument) {
7188
- if (openDocument?.format !== "pdf" && openDocument?.format !== "xlsx" && openDocument?.format !== "csv" && openDocument?.format !== "svg") throw new Error("A PDF inspection screen rendered without an open PDF, xlsx, csv, or svg document; the app router only reaches this screen group from pdfPageList, which is only ever the root screen of one of those four formats.");
7891
+ if (openDocument?.format !== "pdf" && openDocument?.format !== "xlsx" && openDocument?.format !== "csv" && openDocument?.format !== "svg" && openDocument?.format !== "rtf" && openDocument?.format !== "wpd" && openDocument?.format !== "epub") throw new Error("A PDF inspection screen rendered without an open PDF, xlsx, csv, svg, rtf, wpd, or epub document; the app router only reaches this screen group from pdfPageList, which is only ever the root screen of one of those formats.");
7189
7892
  return openDocument;
7190
7893
  }
7191
7894
  function isEditablePdfDocument(doc) {
@@ -7374,9 +8077,15 @@ function fieldsFor(item) {
7374
8077
  fields.push(["Position", formatPoint(item.xPt, item.yPt)]);
7375
8078
  fields.push(["Size", formatSize(item.widthPt, item.heightPt)]);
7376
8079
  break;
8080
+ case "internalLink":
8081
+ fields.push(["Destination", item.destination]);
8082
+ if (item.title !== void 0) fields.push(["Title", item.title]);
8083
+ fields.push(["Position", formatPoint(item.xPt, item.yPt)]);
8084
+ fields.push(["Size", formatSize(item.widthPt, item.heightPt)]);
8085
+ break;
7377
8086
  default: return item;
7378
8087
  }
7379
- if (item.sourcePath !== void 0) fields.push(["Source path", item.sourcePath]);
8088
+ if (item.kind !== "internalLink" && item.sourcePath !== void 0) fields.push(["Source path", item.sourcePath]);
7380
8089
  return fields;
7381
8090
  }
7382
8091
  function ReadOnlyItemDetail(props) {
@@ -7412,34 +8121,42 @@ function buildFrameRows(frame, onFrameChange) {
7412
8121
  {
7413
8122
  label: `X: ${formatPt(frame.xPt)}pt`,
7414
8123
  currentValue: String(frame.xPt),
7415
- commit: (raw) => onFrameChange({
7416
- ...frame,
7417
- xPt: parseNumberField(raw, frame.xPt)
7418
- })
8124
+ commit: (raw) => {
8125
+ onFrameChange({
8126
+ ...frame,
8127
+ xPt: parseNumberField(raw, frame.xPt)
8128
+ });
8129
+ }
7419
8130
  },
7420
8131
  {
7421
8132
  label: `Y: ${formatPt(frame.yPt)}pt`,
7422
8133
  currentValue: String(frame.yPt),
7423
- commit: (raw) => onFrameChange({
7424
- ...frame,
7425
- yPt: parseNumberField(raw, frame.yPt)
7426
- })
8134
+ commit: (raw) => {
8135
+ onFrameChange({
8136
+ ...frame,
8137
+ yPt: parseNumberField(raw, frame.yPt)
8138
+ });
8139
+ }
7427
8140
  },
7428
8141
  {
7429
8142
  label: `Width: ${formatPt(frame.widthPt)}pt`,
7430
8143
  currentValue: String(frame.widthPt),
7431
- commit: (raw) => onFrameChange({
7432
- ...frame,
7433
- widthPt: parseNumberField(raw, frame.widthPt)
7434
- })
8144
+ commit: (raw) => {
8145
+ onFrameChange({
8146
+ ...frame,
8147
+ widthPt: parseNumberField(raw, frame.widthPt)
8148
+ });
8149
+ }
7435
8150
  },
7436
8151
  {
7437
8152
  label: `Height: ${formatPt(frame.heightPt)}pt`,
7438
8153
  currentValue: String(frame.heightPt),
7439
- commit: (raw) => onFrameChange({
7440
- ...frame,
7441
- heightPt: parseNumberField(raw, frame.heightPt)
7442
- })
8154
+ commit: (raw) => {
8155
+ onFrameChange({
8156
+ ...frame,
8157
+ heightPt: parseNumberField(raw, frame.heightPt)
8158
+ });
8159
+ }
7443
8160
  }
7444
8161
  ];
7445
8162
  }
@@ -7447,11 +8164,15 @@ function buildFillStrokeRows(fill, stroke, onFillChange, onStrokeChange) {
7447
8164
  return [{
7448
8165
  label: `Fill: ${fill === void 0 ? "none" : formatColor(fill)}`,
7449
8166
  currentValue: fill === void 0 ? "" : `${fill.r} ${fill.g} ${fill.b}`,
7450
- commit: (raw) => onFillChange(parseColorField(raw))
8167
+ commit: (raw) => {
8168
+ onFillChange(parseColorField(raw));
8169
+ }
7451
8170
  }, {
7452
8171
  label: `Stroke: ${stroke === void 0 ? "none" : formatStroke(stroke)}`,
7453
8172
  currentValue: stroke === void 0 ? "" : `${stroke.color.r} ${stroke.color.g} ${stroke.color.b} ${stroke.widthPt}`,
7454
- commit: (raw) => onStrokeChange(parseStrokeField(raw))
8173
+ commit: (raw) => {
8174
+ onStrokeChange(parseStrokeField(raw));
8175
+ }
7455
8176
  }];
7456
8177
  }
7457
8178
  function buildTextRows(item, pageIndex, itemIndex, dispatch) {
@@ -7459,34 +8180,40 @@ function buildTextRows(item, pageIndex, itemIndex, dispatch) {
7459
8180
  {
7460
8181
  label: `Text: ${item.text}`,
7461
8182
  currentValue: item.text,
7462
- commit: (raw) => dispatch({
7463
- type: "SET_PDF_TEXT_TEXT",
7464
- pageIndex,
7465
- itemIndex,
7466
- text: raw
7467
- })
8183
+ commit: (raw) => {
8184
+ dispatch({
8185
+ type: "SET_PDF_TEXT_TEXT",
8186
+ pageIndex,
8187
+ itemIndex,
8188
+ text: raw
8189
+ });
8190
+ }
7468
8191
  },
7469
8192
  {
7470
8193
  label: `X: ${formatPt(item.xPt)}pt`,
7471
8194
  currentValue: String(item.xPt),
7472
- commit: (raw) => dispatch({
7473
- type: "SET_PDF_TEXT_POSITION",
7474
- pageIndex,
7475
- itemIndex,
7476
- xPt: parseNumberField(raw, item.xPt),
7477
- yPt: item.yPt
7478
- })
8195
+ commit: (raw) => {
8196
+ dispatch({
8197
+ type: "SET_PDF_TEXT_POSITION",
8198
+ pageIndex,
8199
+ itemIndex,
8200
+ xPt: parseNumberField(raw, item.xPt),
8201
+ yPt: item.yPt
8202
+ });
8203
+ }
7479
8204
  },
7480
8205
  {
7481
8206
  label: `Y: ${formatPt(item.yPt)}pt`,
7482
8207
  currentValue: String(item.yPt),
7483
- commit: (raw) => dispatch({
7484
- type: "SET_PDF_TEXT_POSITION",
7485
- pageIndex,
7486
- itemIndex,
7487
- xPt: item.xPt,
7488
- yPt: parseNumberField(raw, item.yPt)
7489
- })
8208
+ commit: (raw) => {
8209
+ dispatch({
8210
+ type: "SET_PDF_TEXT_POSITION",
8211
+ pageIndex,
8212
+ itemIndex,
8213
+ xPt: item.xPt,
8214
+ yPt: parseNumberField(raw, item.yPt)
8215
+ });
8216
+ }
7490
8217
  },
7491
8218
  {
7492
8219
  label: `Font family: ${item.font.family}`,
@@ -7507,181 +8234,219 @@ function buildTextRows(item, pageIndex, itemIndex, dispatch) {
7507
8234
  {
7508
8235
  label: `Font weight: ${item.font.weight} (Enter to toggle)`,
7509
8236
  currentValue: "",
7510
- activate: () => dispatch({
7511
- type: "SET_PDF_TEXT_FONT",
7512
- pageIndex,
7513
- itemIndex,
7514
- font: {
7515
- ...item.font,
7516
- weight: item.font.weight === "bold" ? "normal" : "bold"
7517
- }
7518
- })
8237
+ activate: () => {
8238
+ dispatch({
8239
+ type: "SET_PDF_TEXT_FONT",
8240
+ pageIndex,
8241
+ itemIndex,
8242
+ font: {
8243
+ ...item.font,
8244
+ weight: item.font.weight === "bold" ? "normal" : "bold"
8245
+ }
8246
+ });
8247
+ }
7519
8248
  },
7520
8249
  {
7521
8250
  label: `Font style: ${item.font.style} (Enter to toggle)`,
7522
8251
  currentValue: "",
7523
- activate: () => dispatch({
7524
- type: "SET_PDF_TEXT_FONT",
7525
- pageIndex,
7526
- itemIndex,
7527
- font: {
7528
- ...item.font,
7529
- style: item.font.style === "italic" ? "normal" : "italic"
7530
- }
7531
- })
8252
+ activate: () => {
8253
+ dispatch({
8254
+ type: "SET_PDF_TEXT_FONT",
8255
+ pageIndex,
8256
+ itemIndex,
8257
+ font: {
8258
+ ...item.font,
8259
+ style: item.font.style === "italic" ? "normal" : "italic"
8260
+ }
8261
+ });
8262
+ }
7532
8263
  },
7533
8264
  {
7534
8265
  label: `Size: ${item.sizePt}pt`,
7535
8266
  currentValue: String(item.sizePt),
7536
- commit: (raw) => dispatch({
7537
- type: "SET_PDF_TEXT_SIZE",
7538
- pageIndex,
7539
- itemIndex,
7540
- sizePt: parseNumberField(raw, item.sizePt)
7541
- })
8267
+ commit: (raw) => {
8268
+ dispatch({
8269
+ type: "SET_PDF_TEXT_SIZE",
8270
+ pageIndex,
8271
+ itemIndex,
8272
+ sizePt: parseNumberField(raw, item.sizePt)
8273
+ });
8274
+ }
7542
8275
  },
7543
8276
  {
7544
8277
  label: `Colour: ${formatColor(item.color)}`,
7545
8278
  currentValue: `${item.color.r} ${item.color.g} ${item.color.b}`,
7546
- commit: (raw) => dispatch({
7547
- type: "SET_PDF_TEXT_COLOR",
7548
- pageIndex,
7549
- itemIndex,
7550
- color: parseRequiredColorField(raw, item.color)
7551
- })
8279
+ commit: (raw) => {
8280
+ dispatch({
8281
+ type: "SET_PDF_TEXT_COLOR",
8282
+ pageIndex,
8283
+ itemIndex,
8284
+ color: parseRequiredColorField(raw, item.color)
8285
+ });
8286
+ }
7552
8287
  },
7553
8288
  {
7554
8289
  label: `Width: ${item.widthPt === void 0 ? "unset" : `${item.widthPt}pt`}`,
7555
8290
  currentValue: item.widthPt === void 0 ? "" : String(item.widthPt),
7556
- commit: (raw) => dispatch({
7557
- type: "SET_PDF_TEXT_WIDTH",
7558
- pageIndex,
7559
- itemIndex,
7560
- widthPt: parseOptionalNumberField(raw)
7561
- })
8291
+ commit: (raw) => {
8292
+ dispatch({
8293
+ type: "SET_PDF_TEXT_WIDTH",
8294
+ pageIndex,
8295
+ itemIndex,
8296
+ widthPt: parseOptionalNumberField(raw)
8297
+ });
8298
+ }
7562
8299
  },
7563
8300
  {
7564
8301
  label: `Rotation: ${item.rotationDeg === void 0 ? "unset" : `${item.rotationDeg}°`}`,
7565
8302
  currentValue: item.rotationDeg === void 0 ? "" : String(item.rotationDeg),
7566
- commit: (raw) => dispatch({
7567
- type: "SET_PDF_TEXT_ROTATION",
7568
- pageIndex,
7569
- itemIndex,
7570
- rotationDeg: parseOptionalNumberField(raw)
7571
- })
8303
+ commit: (raw) => {
8304
+ dispatch({
8305
+ type: "SET_PDF_TEXT_ROTATION",
8306
+ pageIndex,
8307
+ itemIndex,
8308
+ rotationDeg: parseOptionalNumberField(raw)
8309
+ });
8310
+ }
7572
8311
  },
7573
8312
  {
7574
8313
  label: `Underline: ${item.underline === true ? "yes" : "no"} (Enter to toggle)`,
7575
8314
  currentValue: "",
7576
- activate: () => dispatch({
7577
- type: "TOGGLE_PDF_TEXT_UNDERLINE",
7578
- pageIndex,
7579
- itemIndex
7580
- })
8315
+ activate: () => {
8316
+ dispatch({
8317
+ type: "TOGGLE_PDF_TEXT_UNDERLINE",
8318
+ pageIndex,
8319
+ itemIndex
8320
+ });
8321
+ }
7581
8322
  }
7582
8323
  ];
7583
8324
  }
7584
8325
  function buildRectRows(item, pageIndex, itemIndex, dispatch) {
7585
- return [...buildFrameRows(item, (frame) => dispatch({
7586
- type: "SET_PDF_RECT_FRAME",
7587
- pageIndex,
7588
- itemIndex,
7589
- ...frame
7590
- })), ...buildFillStrokeRows(item.fill, item.stroke, (fill) => dispatch({
7591
- type: "SET_PDF_RECT_FILL",
7592
- pageIndex,
7593
- itemIndex,
7594
- fill
7595
- }), (stroke) => dispatch({
7596
- type: "SET_PDF_RECT_STROKE",
7597
- pageIndex,
7598
- itemIndex,
7599
- stroke
7600
- }))];
8326
+ return [...buildFrameRows(item, (frame) => {
8327
+ dispatch({
8328
+ type: "SET_PDF_RECT_FRAME",
8329
+ pageIndex,
8330
+ itemIndex,
8331
+ ...frame
8332
+ });
8333
+ }), ...buildFillStrokeRows(item.fill, item.stroke, (fill) => {
8334
+ dispatch({
8335
+ type: "SET_PDF_RECT_FILL",
8336
+ pageIndex,
8337
+ itemIndex,
8338
+ fill
8339
+ });
8340
+ }, (stroke) => {
8341
+ dispatch({
8342
+ type: "SET_PDF_RECT_STROKE",
8343
+ pageIndex,
8344
+ itemIndex,
8345
+ stroke
8346
+ });
8347
+ })];
7601
8348
  }
7602
8349
  function buildEllipseRows(item, pageIndex, itemIndex, dispatch) {
7603
- return [...buildFrameRows(item, (frame) => dispatch({
7604
- type: "SET_PDF_ELLIPSE_FRAME",
7605
- pageIndex,
7606
- itemIndex,
7607
- ...frame
7608
- })), ...buildFillStrokeRows(item.fill, item.stroke, (fill) => dispatch({
7609
- type: "SET_PDF_ELLIPSE_FILL",
7610
- pageIndex,
7611
- itemIndex,
7612
- fill
7613
- }), (stroke) => dispatch({
7614
- type: "SET_PDF_ELLIPSE_STROKE",
7615
- pageIndex,
7616
- itemIndex,
7617
- stroke
7618
- }))];
8350
+ return [...buildFrameRows(item, (frame) => {
8351
+ dispatch({
8352
+ type: "SET_PDF_ELLIPSE_FRAME",
8353
+ pageIndex,
8354
+ itemIndex,
8355
+ ...frame
8356
+ });
8357
+ }), ...buildFillStrokeRows(item.fill, item.stroke, (fill) => {
8358
+ dispatch({
8359
+ type: "SET_PDF_ELLIPSE_FILL",
8360
+ pageIndex,
8361
+ itemIndex,
8362
+ fill
8363
+ });
8364
+ }, (stroke) => {
8365
+ dispatch({
8366
+ type: "SET_PDF_ELLIPSE_STROKE",
8367
+ pageIndex,
8368
+ itemIndex,
8369
+ stroke
8370
+ });
8371
+ })];
7619
8372
  }
7620
8373
  function buildLineRows(item, pageIndex, itemIndex, dispatch) {
7621
8374
  return [
7622
8375
  {
7623
8376
  label: `From X: ${formatPt(item.x1Pt)}pt`,
7624
8377
  currentValue: String(item.x1Pt),
7625
- commit: (raw) => dispatch({
7626
- type: "SET_PDF_LINE_FROM",
7627
- pageIndex,
7628
- itemIndex,
7629
- x1Pt: parseNumberField(raw, item.x1Pt),
7630
- y1Pt: item.y1Pt
7631
- })
8378
+ commit: (raw) => {
8379
+ dispatch({
8380
+ type: "SET_PDF_LINE_FROM",
8381
+ pageIndex,
8382
+ itemIndex,
8383
+ x1Pt: parseNumberField(raw, item.x1Pt),
8384
+ y1Pt: item.y1Pt
8385
+ });
8386
+ }
7632
8387
  },
7633
8388
  {
7634
8389
  label: `From Y: ${formatPt(item.y1Pt)}pt`,
7635
8390
  currentValue: String(item.y1Pt),
7636
- commit: (raw) => dispatch({
7637
- type: "SET_PDF_LINE_FROM",
7638
- pageIndex,
7639
- itemIndex,
7640
- x1Pt: item.x1Pt,
7641
- y1Pt: parseNumberField(raw, item.y1Pt)
7642
- })
8391
+ commit: (raw) => {
8392
+ dispatch({
8393
+ type: "SET_PDF_LINE_FROM",
8394
+ pageIndex,
8395
+ itemIndex,
8396
+ x1Pt: item.x1Pt,
8397
+ y1Pt: parseNumberField(raw, item.y1Pt)
8398
+ });
8399
+ }
7643
8400
  },
7644
8401
  {
7645
8402
  label: `To X: ${formatPt(item.x2Pt)}pt`,
7646
8403
  currentValue: String(item.x2Pt),
7647
- commit: (raw) => dispatch({
7648
- type: "SET_PDF_LINE_TO",
7649
- pageIndex,
7650
- itemIndex,
7651
- x2Pt: parseNumberField(raw, item.x2Pt),
7652
- y2Pt: item.y2Pt
7653
- })
8404
+ commit: (raw) => {
8405
+ dispatch({
8406
+ type: "SET_PDF_LINE_TO",
8407
+ pageIndex,
8408
+ itemIndex,
8409
+ x2Pt: parseNumberField(raw, item.x2Pt),
8410
+ y2Pt: item.y2Pt
8411
+ });
8412
+ }
7654
8413
  },
7655
8414
  {
7656
8415
  label: `To Y: ${formatPt(item.y2Pt)}pt`,
7657
8416
  currentValue: String(item.y2Pt),
7658
- commit: (raw) => dispatch({
7659
- type: "SET_PDF_LINE_TO",
7660
- pageIndex,
7661
- itemIndex,
7662
- x2Pt: item.x2Pt,
7663
- y2Pt: parseNumberField(raw, item.y2Pt)
7664
- })
8417
+ commit: (raw) => {
8418
+ dispatch({
8419
+ type: "SET_PDF_LINE_TO",
8420
+ pageIndex,
8421
+ itemIndex,
8422
+ x2Pt: item.x2Pt,
8423
+ y2Pt: parseNumberField(raw, item.y2Pt)
8424
+ });
8425
+ }
7665
8426
  },
7666
8427
  {
7667
8428
  label: `Colour: ${formatColor(item.color)}`,
7668
8429
  currentValue: `${item.color.r} ${item.color.g} ${item.color.b}`,
7669
- commit: (raw) => dispatch({
7670
- type: "SET_PDF_LINE_COLOR",
7671
- pageIndex,
7672
- itemIndex,
7673
- color: parseRequiredColorField(raw, item.color)
7674
- })
8430
+ commit: (raw) => {
8431
+ dispatch({
8432
+ type: "SET_PDF_LINE_COLOR",
8433
+ pageIndex,
8434
+ itemIndex,
8435
+ color: parseRequiredColorField(raw, item.color)
8436
+ });
8437
+ }
7675
8438
  },
7676
8439
  {
7677
8440
  label: `Width: ${item.widthPt}pt`,
7678
8441
  currentValue: String(item.widthPt),
7679
- commit: (raw) => dispatch({
7680
- type: "SET_PDF_LINE_WIDTH",
7681
- pageIndex,
7682
- itemIndex,
7683
- widthPt: Math.max(parseNumberField(raw, item.widthPt), Number.EPSILON)
7684
- })
8442
+ commit: (raw) => {
8443
+ dispatch({
8444
+ type: "SET_PDF_LINE_WIDTH",
8445
+ pageIndex,
8446
+ itemIndex,
8447
+ widthPt: Math.max(parseNumberField(raw, item.widthPt), Number.EPSILON)
8448
+ });
8449
+ }
7685
8450
  }
7686
8451
  ];
7687
8452
  }
@@ -7689,41 +8454,51 @@ function buildPathRows(item, pageIndex, itemIndex, dispatch) {
7689
8454
  return [{
7690
8455
  label: `Fill rule: ${item.fillRule ?? "nonzero (default)"} (Enter to cycle)`,
7691
8456
  currentValue: "",
7692
- activate: () => dispatch({
7693
- type: "SET_PDF_PATH_FILL_RULE",
8457
+ activate: () => {
8458
+ dispatch({
8459
+ type: "SET_PDF_PATH_FILL_RULE",
8460
+ pageIndex,
8461
+ itemIndex,
8462
+ fillRule: item.fillRule === "evenodd" ? void 0 : item.fillRule === "nonzero" ? "evenodd" : "nonzero"
8463
+ });
8464
+ }
8465
+ }, ...buildFillStrokeRows(item.fill, item.stroke, (fill) => {
8466
+ dispatch({
8467
+ type: "SET_PDF_PATH_FILL",
7694
8468
  pageIndex,
7695
8469
  itemIndex,
7696
- fillRule: item.fillRule === "evenodd" ? void 0 : item.fillRule === "nonzero" ? "evenodd" : "nonzero"
7697
- })
7698
- }, ...buildFillStrokeRows(item.fill, item.stroke, (fill) => dispatch({
7699
- type: "SET_PDF_PATH_FILL",
7700
- pageIndex,
7701
- itemIndex,
7702
- fill
7703
- }), (stroke) => dispatch({
7704
- type: "SET_PDF_PATH_STROKE",
7705
- pageIndex,
7706
- itemIndex,
7707
- stroke
7708
- }))];
8470
+ fill
8471
+ });
8472
+ }, (stroke) => {
8473
+ dispatch({
8474
+ type: "SET_PDF_PATH_STROKE",
8475
+ pageIndex,
8476
+ itemIndex,
8477
+ stroke
8478
+ });
8479
+ })];
7709
8480
  }
7710
8481
  function buildImageRows(item, pageIndex, itemIndex, dispatch, onReplaceImage) {
7711
8482
  return [
7712
- ...buildFrameRows(item, (frame) => dispatch({
7713
- type: "SET_PDF_IMAGE_FRAME",
7714
- pageIndex,
7715
- itemIndex,
7716
- ...frame
7717
- })),
8483
+ ...buildFrameRows(item, (frame) => {
8484
+ dispatch({
8485
+ type: "SET_PDF_IMAGE_FRAME",
8486
+ pageIndex,
8487
+ itemIndex,
8488
+ ...frame
8489
+ });
8490
+ }),
7718
8491
  {
7719
8492
  label: `Rotation: ${item.rotationDeg === void 0 ? "unset" : `${item.rotationDeg}°`}`,
7720
8493
  currentValue: item.rotationDeg === void 0 ? "" : String(item.rotationDeg),
7721
- commit: (raw) => dispatch({
7722
- type: "SET_PDF_IMAGE_ROTATION",
7723
- pageIndex,
7724
- itemIndex,
7725
- rotationDeg: parseOptionalNumberField(raw)
7726
- })
8494
+ commit: (raw) => {
8495
+ dispatch({
8496
+ type: "SET_PDF_IMAGE_ROTATION",
8497
+ pageIndex,
8498
+ itemIndex,
8499
+ rotationDeg: parseOptionalNumberField(raw)
8500
+ });
8501
+ }
7727
8502
  },
7728
8503
  {
7729
8504
  label: "Replace image...",
@@ -7736,18 +8511,43 @@ function buildLinkRows(item, pageIndex, itemIndex, dispatch) {
7736
8511
  return [{
7737
8512
  label: `URI: ${item.uri}`,
7738
8513
  currentValue: item.uri,
7739
- commit: (raw) => dispatch({
7740
- type: "SET_PDF_LINK_URI",
8514
+ commit: (raw) => {
8515
+ dispatch({
8516
+ type: "SET_PDF_LINK_URI",
8517
+ pageIndex,
8518
+ itemIndex,
8519
+ uri: raw
8520
+ });
8521
+ }
8522
+ }, ...buildFrameRows(item, (frame) => {
8523
+ dispatch({
8524
+ type: "SET_PDF_LINK_FRAME",
7741
8525
  pageIndex,
7742
8526
  itemIndex,
7743
- uri: raw
7744
- })
7745
- }, ...buildFrameRows(item, (frame) => dispatch({
7746
- type: "SET_PDF_LINK_FRAME",
7747
- pageIndex,
7748
- itemIndex,
7749
- ...frame
7750
- }))];
8527
+ ...frame
8528
+ });
8529
+ })];
8530
+ }
8531
+ function buildInternalLinkRows(item, pageIndex, itemIndex, dispatch) {
8532
+ return [{
8533
+ label: `Destination: ${item.destination}`,
8534
+ currentValue: item.destination,
8535
+ commit: (raw) => {
8536
+ dispatch({
8537
+ type: "SET_PDF_INTERNAL_LINK_DESTINATION",
8538
+ pageIndex,
8539
+ itemIndex,
8540
+ destination: raw
8541
+ });
8542
+ }
8543
+ }, ...buildFrameRows(item, (frame) => {
8544
+ dispatch({
8545
+ type: "SET_PDF_INTERNAL_LINK_FRAME",
8546
+ pageIndex,
8547
+ itemIndex,
8548
+ ...frame
8549
+ });
8550
+ })];
7751
8551
  }
7752
8552
  function buildRowsFor(item, pageIndex, itemIndex, dispatch, onReplaceImage) {
7753
8553
  switch (item.kind) {
@@ -7758,6 +8558,7 @@ function buildRowsFor(item, pageIndex, itemIndex, dispatch, onReplaceImage) {
7758
8558
  case "path": return buildPathRows(item, pageIndex, itemIndex, dispatch);
7759
8559
  case "image": return buildImageRows(item, pageIndex, itemIndex, dispatch, onReplaceImage);
7760
8560
  case "link": return buildLinkRows(item, pageIndex, itemIndex, dispatch);
8561
+ case "internalLink": return buildInternalLinkRows(item, pageIndex, itemIndex, dispatch);
7761
8562
  }
7762
8563
  }
7763
8564
  function pathSummary(item) {
@@ -7797,7 +8598,9 @@ function EditableItemDetail(props) {
7797
8598
  const [draft, setDraft] = useState("");
7798
8599
  const [replacingImage, setReplacingImage] = useState(false);
7799
8600
  const item = doc.editor.page(pageIndex)?.items()[itemIndex];
7800
- const rows = item === void 0 ? [] : buildRowsFor(item, pageIndex, itemIndex, dispatch, () => setReplacingImage(true));
8601
+ const rows = item === void 0 ? [] : buildRowsFor(item, pageIndex, itemIndex, dispatch, () => {
8602
+ setReplacingImage(true);
8603
+ });
7801
8604
  const { selectedIndex } = useNavigationInput({
7802
8605
  itemCount: rows.length,
7803
8606
  isActive: props.isActive && editingField === void 0 && !replacingImage,
@@ -7972,6 +8775,7 @@ function previewFor(item) {
7972
8775
  switch (item.kind) {
7973
8776
  case "text": return truncate(item.text, TEXT_PREVIEW_MAX_CHARS);
7974
8777
  case "link": return item.uri;
8778
+ case "internalLink": return `→ ${item.destination}`;
7975
8779
  case "image":
7976
8780
  case "rect":
7977
8781
  case "ellipse": return formatSize(item.widthPt, item.heightPt);
@@ -8748,7 +9552,7 @@ function LauncherScreen() {
8748
9552
  }),
8749
9553
  /* @__PURE__ */ jsx(Text, {
8750
9554
  dimColor: true,
8751
- children: "A terminal editor for docx, pptx, odt, odp, ods, odg, markdown, odb and pdf -- xlsx, csv and svg open as read-only PDF previews."
9555
+ children: "A terminal editor for docx, pptx, odt, odp, ods, odg, doc, xls, ppt, markdown, odb and pdf -- xlsx, csv, svg, rtf, wpd and epub open as read-only PDF previews."
8752
9556
  }),
8753
9557
  /* @__PURE__ */ jsx(Text, { children: " " }),
8754
9558
  /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
@@ -8796,6 +9600,22 @@ const CREATABLE_FORMATS = [
8796
9600
  {
8797
9601
  format: "odg",
8798
9602
  description: "Vector drawing (OpenDocument)"
9603
+ },
9604
+ {
9605
+ format: "doc",
9606
+ description: "Word-processing document (Word 97-2003)"
9607
+ },
9608
+ {
9609
+ format: "xls",
9610
+ description: "Spreadsheet workbook (Excel 97-2003)"
9611
+ },
9612
+ {
9613
+ format: "ppt",
9614
+ description: "Presentation slide deck (PowerPoint 97-2003)"
9615
+ },
9616
+ {
9617
+ format: "markdown",
9618
+ description: "Markdown document"
8799
9619
  }
8800
9620
  ];
8801
9621
  const EXTENSION_COLUMN_WIDTH = 8;
@@ -8912,22 +9732,73 @@ function metadataFor(doc) {
8912
9732
  case "ods": return readOdsContent(doc.editor.toPackage()).metadata;
8913
9733
  case "odg": return readOdgContent(doc.editor.toPackage()).metadata;
8914
9734
  case "markdown": return readMarkdownContent(doc.editor.toMarkdownText()).metadata;
9735
+ case "doc":
9736
+ case "xls":
9737
+ case "ppt": return doc.editor.metadata;
8915
9738
  case "pdf":
8916
9739
  case "xlsx":
8917
9740
  case "csv":
8918
- case "svg": return doc.layout.metadata;
9741
+ case "svg":
9742
+ case "rtf":
9743
+ case "wpd":
9744
+ case "epub": return doc.layout.metadata;
8919
9745
  case "odb": throw new Error("A .odb database has no document-level metadata -- it is a table/form/report container, not a single document with its own title/author/etc.");
8920
9746
  }
8921
9747
  }
8922
9748
  //#endregion
8923
9749
  //#region src/tui/screens/shared/metadata.tsx
9750
+ const EDITABLE_FIELDS = [
9751
+ {
9752
+ key: "title",
9753
+ label: "title"
9754
+ },
9755
+ {
9756
+ key: "author",
9757
+ label: "author"
9758
+ },
9759
+ {
9760
+ key: "subject",
9761
+ label: "subject"
9762
+ },
9763
+ {
9764
+ key: "keywords",
9765
+ label: "keywords"
9766
+ }
9767
+ ];
9768
+ function parseKeywords(text) {
9769
+ return text.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
9770
+ }
9771
+ function overridesFor(field, text) {
9772
+ switch (field) {
9773
+ case "title": return { title: text };
9774
+ case "author": return { author: text };
9775
+ case "subject": return { subject: text };
9776
+ case "keywords": return { keywords: parseKeywords(text) };
9777
+ }
9778
+ }
8924
9779
  function MetadataScreen() {
8925
9780
  const state = useAppState();
8926
9781
  const dispatch = useAppDispatch();
8927
9782
  const doc = state.openDocument;
8928
- useInput((input, key) => {
8929
- if (key.escape || key.leftArrow || input === "h") dispatch({ type: "POP_SCREEN" });
8930
- }, { isActive: doc !== void 0 && !anyOverlayOpen(state) });
9783
+ const editable = doc !== void 0 && isEditableDocument(doc);
9784
+ const [editingField, setEditingField] = useState(void 0);
9785
+ const [draft, setDraft] = useState("");
9786
+ const isNavigationActive = doc !== void 0 && !anyOverlayOpen(state) && editingField === void 0;
9787
+ const { selectedIndex } = useNavigationInput({
9788
+ itemCount: editable ? EDITABLE_FIELDS.length : 0,
9789
+ isActive: isNavigationActive,
9790
+ onBack: () => {
9791
+ dispatch({ type: "POP_SCREEN" });
9792
+ },
9793
+ onSelect: (index) => {
9794
+ if (doc === void 0 || !editable) return;
9795
+ const field = EDITABLE_FIELDS[index];
9796
+ if (field === void 0) return;
9797
+ const currentValue = metadataFor(doc)[field.key];
9798
+ setDraft(currentValue === void 0 ? "" : formatMetadataValue(currentValue));
9799
+ setEditingField(field.key);
9800
+ }
9801
+ });
8931
9802
  if (doc === void 0) return /* @__PURE__ */ jsx(Text, {
8932
9803
  color: "red",
8933
9804
  children: "MetadataScreen requires an open document."
@@ -8939,6 +9810,37 @@ function MetadataScreen() {
8939
9810
  } catch (error) {
8940
9811
  errorMessage = error instanceof Error ? error.message : String(error);
8941
9812
  }
9813
+ if (editingField !== void 0) {
9814
+ const field = EDITABLE_FIELDS.find((f) => f.key === editingField);
9815
+ return /* @__PURE__ */ jsxs(Box, {
9816
+ flexDirection: "column",
9817
+ children: [
9818
+ /* @__PURE__ */ jsxs(Text, {
9819
+ bold: true,
9820
+ children: ["Editing ", field?.label ?? editingField]
9821
+ }),
9822
+ editingField === "keywords" ? /* @__PURE__ */ jsx(Text, {
9823
+ dimColor: true,
9824
+ children: "Comma-separated"
9825
+ }) : void 0,
9826
+ /* @__PURE__ */ jsx(TextField, {
9827
+ value: draft,
9828
+ isFocused: true,
9829
+ onChange: setDraft,
9830
+ onSubmit: (text) => {
9831
+ dispatch({
9832
+ type: "SET_METADATA",
9833
+ overrides: overridesFor(editingField, text)
9834
+ });
9835
+ setEditingField(void 0);
9836
+ },
9837
+ onCancel: () => {
9838
+ setEditingField(void 0);
9839
+ }
9840
+ })
9841
+ ]
9842
+ });
9843
+ }
8942
9844
  return /* @__PURE__ */ jsxs(Box, {
8943
9845
  flexDirection: "column",
8944
9846
  children: [
@@ -8957,9 +9859,27 @@ function MetadataScreen() {
8957
9859
  dimColor: true,
8958
9860
  children: "This document carries no metadata."
8959
9861
  }),
8960
- /* @__PURE__ */ jsx(Text, {
9862
+ editable ? /* @__PURE__ */ jsxs(Fragment, { children: [
9863
+ /* @__PURE__ */ jsx(Text, {
9864
+ bold: true,
9865
+ children: "Edit a field"
9866
+ }),
9867
+ /* @__PURE__ */ jsx(ListView, {
9868
+ items: EDITABLE_FIELDS,
9869
+ selectedIndex,
9870
+ renderItem: (field, isSelected) => /* @__PURE__ */ jsx(Text, {
9871
+ color: isSelected ? "cyan" : void 0,
9872
+ inverse: isSelected,
9873
+ children: field.label
9874
+ })
9875
+ }),
9876
+ /* @__PURE__ */ jsx(Text, {
9877
+ dimColor: true,
9878
+ children: "Enter to edit, Esc / ← / h to go back"
9879
+ })
9880
+ ] }) : /* @__PURE__ */ jsx(Text, {
8961
9881
  dimColor: true,
8962
- children: "Esc / ← / h to go back"
9882
+ children: "This document's metadata is read-only. Esc / ← / h to go back"
8963
9883
  })
8964
9884
  ]
8965
9885
  });
@@ -8980,6 +9900,7 @@ function ScreenBody({ screen }) {
8980
9900
  case "newDocumentPicker": return /* @__PURE__ */ jsx(NewDocumentPickerScreen, {});
8981
9901
  case "bodyList":
8982
9902
  if (format === "odt") return /* @__PURE__ */ jsx(OdtBodyListScreen, {});
9903
+ if (format === "doc") return /* @__PURE__ */ jsx(DocBodyListScreen, {});
8983
9904
  return format === "markdown" ? /* @__PURE__ */ jsx(MarkdownBodyListScreen, {}) : /* @__PURE__ */ jsx(DocxBodyListScreen, {});
8984
9905
  case "docxExtras": return /* @__PURE__ */ jsx(DocxExtrasScreen, {});
8985
9906
  case "paragraphDetail": return /* @__PURE__ */ jsx(ParagraphDetailScreen, {});
@@ -8987,13 +9908,15 @@ function ScreenBody({ screen }) {
8987
9908
  case "tableView": return /* @__PURE__ */ jsx(TableViewScreen, {});
8988
9909
  case "tableCellDetail": return /* @__PURE__ */ jsx(TableCellDetailScreen, {});
8989
9910
  case "listEditor": return /* @__PURE__ */ jsx(ListEditorScreen, {});
8990
- case "slideList": return format === "odp" ? /* @__PURE__ */ jsx(OdpSlideListScreen, {}) : /* @__PURE__ */ jsx(PptxSlideListScreen, {});
8991
- case "slideDetail": return /* @__PURE__ */ jsx(SlideDetailScreen, { screen });
9911
+ case "slideList":
9912
+ if (format === "ppt") return /* @__PURE__ */ jsx(PptSlideListScreen, {});
9913
+ return format === "odp" ? /* @__PURE__ */ jsx(OdpSlideListScreen, {}) : /* @__PURE__ */ jsx(PptxSlideListScreen, {});
9914
+ case "slideDetail": return format === "ppt" ? /* @__PURE__ */ jsx(PptSlideDetailScreen, { slideIndex: screen.slideIndex }) : /* @__PURE__ */ jsx(SlideDetailScreen, { screen });
8992
9915
  case "shapeEditor": return /* @__PURE__ */ jsx(ShapeEditorScreen, { screen });
8993
9916
  case "slideTableDetail": return /* @__PURE__ */ jsx(SlideTableDetailScreen, { screen });
8994
9917
  case "notesEditor": return /* @__PURE__ */ jsx(NotesEditorScreen, { screen });
8995
- case "sheetList": return /* @__PURE__ */ jsx(OdsSheetListScreen, {});
8996
- case "spreadsheetGrid": return /* @__PURE__ */ jsx(OdsSpreadsheetGridScreen, {});
9918
+ case "sheetList": return format === "xls" ? /* @__PURE__ */ jsx(XlsSheetListScreen, {}) : /* @__PURE__ */ jsx(OdsSheetListScreen, {});
9919
+ case "spreadsheetGrid": return format === "xls" ? /* @__PURE__ */ jsx(XlsSpreadsheetGridScreen, { sheetIndex: screen.sheetIndex }) : /* @__PURE__ */ jsx(OdsSpreadsheetGridScreen, {});
8997
9920
  case "cellDetail": return /* @__PURE__ */ jsx(Text, { children: "cellDetail is rendered inline by spreadsheetGrid and is never pushed as its own screen." });
8998
9921
  case "printSettingsEditor": return /* @__PURE__ */ jsx(OdsPrintSettingsEditorScreen, {});
8999
9922
  case "pageList": return /* @__PURE__ */ jsx(OdgPageListScreen, {});
@@ -9049,7 +9972,7 @@ function AppShell({ startPath }) {
9049
9972
  const overlayOpen = anyOverlayOpen(state);
9050
9973
  useEffect(() => {
9051
9974
  if (startPath === void 0) return;
9052
- let cancelled = false;
9975
+ const lifecycle = { cancelled: false };
9053
9976
  (async () => {
9054
9977
  try {
9055
9978
  const doc = await openDocumentAtPath(startPath, { onDiagnostic: (diagnostic) => {
@@ -9058,13 +9981,13 @@ function AppShell({ startPath }) {
9058
9981
  diagnostic
9059
9982
  });
9060
9983
  } });
9061
- if (!cancelled) dispatch({
9984
+ if (!lifecycle.cancelled) dispatch({
9062
9985
  type: "OPEN_FILE_SUCCESS",
9063
9986
  path: startPath,
9064
9987
  doc
9065
9988
  });
9066
9989
  } catch (error) {
9067
- if (!cancelled) dispatch({
9990
+ if (!lifecycle.cancelled) dispatch({
9068
9991
  type: "OPEN_FILE_ERROR",
9069
9992
  message: `Could not open ${startPath}`,
9070
9993
  detail: describeError(error)
@@ -9072,7 +9995,7 @@ function AppShell({ startPath }) {
9072
9995
  }
9073
9996
  })();
9074
9997
  return () => {
9075
- cancelled = true;
9998
+ lifecycle.cancelled = true;
9076
9999
  };
9077
10000
  }, [startPath, dispatch]);
9078
10001
  useEffect(() => {