doculi 5.12.68 → 5.12.70

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.
@@ -411,315 +411,41 @@ function anyOverlayOpen(state) {
411
411
  return overlays.commandPalette || overlays.search || overlays.help || overlays.confirmQuit || overlays.confirmClose || overlays.diagnosticsPanel || state.errorDetail !== void 0;
412
412
  }
413
413
  //#endregion
414
- //#region src/tui/state/reducer.ts
415
- function createInitialState(options) {
416
- return {
417
- stack: [{ kind: "launcher" }],
418
- openDocument: void 0,
419
- hasUnsavedChanges: false,
420
- overlays: {
421
- commandPalette: false,
422
- search: false,
423
- help: false,
424
- confirmQuit: false,
425
- confirmClose: false,
426
- diagnosticsPanel: false
427
- },
428
- status: void 0,
429
- diagnostics: [],
430
- undoStack: [],
431
- selection: {},
432
- searchQuery: "",
433
- errorDetail: void 0,
434
- isExiting: false,
435
- cwd: options?.cwd ?? process.cwd()
436
- };
414
+ //#region src/tui/state/open-documents.ts
415
+ function wordprocessingDocument(state) {
416
+ const doc = state.openDocument;
417
+ if (doc === void 0) return;
418
+ return doc.format === "docx" || doc.format === "odt" || doc.format === "markdown" || doc.format === "doc" ? doc : void 0;
437
419
  }
438
- function withStatus(state, severity, text) {
439
- return {
440
- ...state,
441
- status: {
442
- severity,
443
- text,
444
- createdAtMs: Date.now()
445
- }
446
- };
420
+ function styledWordprocessingDocument(state) {
421
+ const doc = state.openDocument;
422
+ if (doc === void 0) return;
423
+ return doc.format === "docx" || doc.format === "odt" || doc.format === "doc" ? doc : void 0;
447
424
  }
448
- function setOverlay(overlays, overlay, open) {
449
- switch (overlay) {
450
- case "commandPalette": return {
451
- ...overlays,
452
- commandPalette: open
453
- };
454
- case "search": return {
455
- ...overlays,
456
- search: open
457
- };
458
- case "help": return {
459
- ...overlays,
460
- help: open
461
- };
462
- case "confirmQuit": return {
463
- ...overlays,
464
- confirmQuit: open
465
- };
466
- case "confirmClose": return {
467
- ...overlays,
468
- confirmClose: open
469
- };
470
- case "diagnosticsPanel": return {
471
- ...overlays,
472
- diagnosticsPanel: open
473
- };
474
- }
425
+ function docxOdtDocument(state) {
426
+ const doc = state.openDocument;
427
+ if (doc === void 0) return;
428
+ return doc.format === "docx" || doc.format === "odt" ? doc : void 0;
475
429
  }
476
- function pushSnapshot(stack, snapshot) {
477
- return [...stack, snapshot].slice(-20);
430
+ function richPresentationDocument(state) {
431
+ const doc = state.openDocument;
432
+ if (doc === void 0) return;
433
+ return doc.format === "pptx" || doc.format === "odp" ? doc : void 0;
478
434
  }
479
- function documentWithPath(doc, path) {
480
- switch (doc.format) {
481
- case "docx": return {
482
- format: "docx",
483
- editor: doc.editor,
484
- path
485
- };
486
- case "pptx": return {
487
- format: "pptx",
488
- editor: doc.editor,
489
- path
490
- };
491
- case "odt": return {
492
- format: "odt",
493
- editor: doc.editor,
494
- path
495
- };
496
- case "odp": return {
497
- format: "odp",
498
- editor: doc.editor,
499
- path
500
- };
501
- case "ods": return {
502
- format: "ods",
503
- editor: doc.editor,
504
- path
505
- };
506
- case "odg": return {
507
- format: "odg",
508
- editor: doc.editor,
509
- path
510
- };
511
- case "pdf": return {
512
- format: "pdf",
513
- editor: doc.editor,
514
- layout: doc.layout,
515
- path
516
- };
517
- case "odb": return {
518
- format: "odb",
519
- tables: doc.tables,
520
- forms: doc.forms,
521
- reports: doc.reports,
522
- path
523
- };
524
- case "markdown": return {
525
- format: "markdown",
526
- editor: doc.editor,
527
- originalText: doc.originalText,
528
- path
529
- };
530
- case "xlsx": return {
531
- format: "xlsx",
532
- layout: doc.layout,
533
- bytes: doc.bytes,
534
- path
535
- };
536
- case "csv": return {
537
- format: "csv",
538
- layout: doc.layout,
539
- bytes: doc.bytes,
540
- path
541
- };
542
- case "svg": return {
543
- format: "svg",
544
- layout: doc.layout,
545
- bytes: doc.bytes,
546
- path
547
- };
548
- case "rtf": return {
549
- format: "rtf",
550
- layout: doc.layout,
551
- bytes: doc.bytes,
552
- path
553
- };
554
- case "wpd": return {
555
- format: "wpd",
556
- layout: doc.layout,
557
- bytes: doc.bytes,
558
- path
559
- };
560
- case "doc": return {
561
- format: "doc",
562
- editor: doc.editor,
563
- path
564
- };
565
- case "xls": return {
566
- format: "xls",
567
- editor: doc.editor,
568
- path
569
- };
570
- case "ppt": return {
571
- format: "ppt",
572
- editor: doc.editor,
573
- path
574
- };
575
- case "epub": return {
576
- format: "epub",
577
- layout: doc.layout,
578
- bytes: doc.bytes,
579
- path
580
- };
581
- }
435
+ function presentationDocument(state) {
436
+ const doc = state.openDocument;
437
+ if (doc === void 0) return;
438
+ return doc.format === "pptx" || doc.format === "odp" || doc.format === "ppt" ? doc : void 0;
582
439
  }
583
- function reopenEditable(doc, bytes) {
584
- switch (doc.format) {
585
- case "docx": return {
586
- format: "docx",
587
- editor: openDocx(bytes),
588
- path: doc.path
589
- };
590
- case "pptx": return {
591
- format: "pptx",
592
- editor: openPptx(bytes),
593
- path: doc.path
594
- };
595
- case "odt": return {
596
- format: "odt",
597
- editor: openOdt(bytes),
598
- path: doc.path
599
- };
600
- case "odp": return {
601
- format: "odp",
602
- editor: openOdp(bytes),
603
- path: doc.path
604
- };
605
- case "ods": return {
606
- format: "ods",
607
- editor: openOds(bytes),
608
- path: doc.path
609
- };
610
- case "odg": return {
611
- format: "odg",
612
- editor: openOdg(bytes),
613
- path: doc.path
614
- };
615
- case "doc": return {
616
- format: "doc",
617
- editor: openDoc(bytes),
618
- path: doc.path
619
- };
620
- case "xls": return {
621
- format: "xls",
622
- editor: openXls(bytes),
623
- path: doc.path
624
- };
625
- case "ppt": return {
626
- format: "ppt",
627
- editor: openPpt(bytes),
628
- path: doc.path
629
- };
630
- case "pdf": {
631
- const editor = openPdf(bytes);
632
- return {
633
- format: "pdf",
634
- editor,
635
- layout: editor.toLayoutDocument(),
636
- path: doc.path
637
- };
638
- }
639
- }
440
+ function shapeHostDocument(state) {
441
+ const doc = state.openDocument;
442
+ if (doc === void 0) return;
443
+ return doc.format === "pptx" || doc.format === "odp" || doc.format === "odg" ? doc : void 0;
640
444
  }
641
- function toUndoSnapshot(doc) {
642
- return doc.format === "markdown" ? encodeMarkdownText(doc.editor.toMarkdownText()) : doc.editor.toBytes();
643
- }
644
- function mutate(state, doc, apply) {
645
- const snapshot = toUndoSnapshot(doc);
646
- apply();
647
- return {
648
- ...state,
649
- hasUnsavedChanges: true,
650
- undoStack: pushSnapshot(state.undoStack, snapshot)
651
- };
652
- }
653
- function mutateGuarded(state, doc, apply) {
654
- try {
655
- return mutate(state, doc, apply);
656
- } catch (error) {
657
- return withStatus(state, "warning", error instanceof Error ? error.message : String(error));
658
- }
659
- }
660
- function mergePptxTableCells(table, startRow, startColumn, rowSpan, colSpan) {
661
- if (!Number.isInteger(rowSpan) || rowSpan < 1 || !Number.isInteger(colSpan) || colSpan < 1) throw new Error(`mergeSlideTableCells: rowSpan and colSpan must be positive integers, got rowSpan=${rowSpan}, colSpan=${colSpan}`);
662
- const rows = table.rows();
663
- if (startRow + rowSpan > rows.length) throw new Error(`mergeSlideTableCells: rowSpan ${rowSpan} starting at row ${startRow} exceeds this table's own ${rows.length} rows`);
664
- const anchorRow = rows[startRow];
665
- if (anchorRow === void 0) throw new Error(`mergeSlideTableCells: row ${startRow} does not exist in this table`);
666
- const columnCount = anchorRow.cells().length;
667
- if (startColumn + colSpan > columnCount) throw new Error(`mergeSlideTableCells: colSpan ${colSpan} starting at column ${startColumn} exceeds this table's own ${columnCount} columns`);
668
- for (let rowOffset = 0; rowOffset < rowSpan; rowOffset++) {
669
- const row = rows[startRow + rowOffset];
670
- if (row === void 0) throw new Error(`mergeSlideTableCells: row ${startRow + rowOffset} does not exist in this table`);
671
- const cells = row.cells();
672
- for (let columnOffset = 0; columnOffset < colSpan; columnOffset++) {
673
- const cell = cells[startColumn + columnOffset];
674
- if (cell === void 0) throw new Error(`mergeSlideTableCells: column ${startColumn + columnOffset} does not exist in row ${startRow + rowOffset}`);
675
- if (rowOffset === 0 && columnOffset === 0) {
676
- cell.colSpan = colSpan;
677
- cell.rowSpan = rowSpan;
678
- continue;
679
- }
680
- if (columnOffset > 0) cell.horizontalMerge = true;
681
- if (rowOffset > 0) cell.verticalMerge = true;
682
- cell.setParagraphs([{ runs: [] }]);
683
- }
684
- }
685
- }
686
- function wrongDocument(state, expected) {
687
- return withStatus(state, "warning", `That action needs ${expected}; the open document is ${state.openDocument === void 0 ? "no document" : state.openDocument.format}`);
688
- }
689
- function wordprocessingDocument(state) {
690
- const doc = state.openDocument;
691
- if (doc === void 0) return;
692
- return doc.format === "docx" || doc.format === "odt" || doc.format === "markdown" || doc.format === "doc" ? doc : void 0;
693
- }
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) {
700
- const doc = state.openDocument;
701
- if (doc === void 0) return;
702
- return doc.format === "docx" || doc.format === "odt" ? doc : void 0;
703
- }
704
- function richPresentationDocument(state) {
705
- const doc = state.openDocument;
706
- if (doc === void 0) return;
707
- return doc.format === "pptx" || doc.format === "odp" ? doc : void 0;
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
- }
714
- function shapeHostDocument(state) {
715
- const doc = state.openDocument;
716
- if (doc === void 0) return;
717
- return doc.format === "pptx" || doc.format === "odp" || doc.format === "odg" ? doc : void 0;
718
- }
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;
445
+ function spreadsheetDocument(state) {
446
+ const doc = state.openDocument;
447
+ if (doc === void 0) return;
448
+ return doc.format === "ods" || doc.format === "xls" ? doc : void 0;
723
449
  }
724
450
  function odsDocument$1(state) {
725
451
  const doc = state.openDocument;
@@ -771,6 +497,8 @@ function vectorHostDocument(state) {
771
497
  if (doc === void 0) return;
772
498
  return doc.format === "odg" || doc.format === "odp" ? doc : void 0;
773
499
  }
500
+ //#endregion
501
+ //#region src/tui/state/mutations.ts
774
502
  function paragraphAt(doc, blockIndex) {
775
503
  return doc.editor.paragraphs()[blockIndex];
776
504
  }
@@ -889,157 +617,205 @@ function mutableMathMlNode(node) {
889
617
  content: ""
890
618
  };
891
619
  }
892
- function appReducer(state, action) {
620
+ //#endregion
621
+ //#region src/tui/state/reducer-pdf-actions.ts
622
+ function applyPdfAction(state, action) {
893
623
  switch (action.type) {
894
- case "PUSH_SCREEN": return {
895
- ...state,
896
- stack: [...state.stack, action.screen]
897
- };
898
- case "POP_SCREEN": return state.stack.length <= 1 ? state : {
899
- ...state,
900
- stack: state.stack.slice(0, -1)
901
- };
902
- case "RESET_STACK": return {
903
- ...state,
904
- stack: [action.screen]
905
- };
906
- case "OPEN_OVERLAY": return {
907
- ...state,
908
- overlays: setOverlay(state.overlays, action.overlay, true)
909
- };
910
- case "CLOSE_OVERLAY": return {
911
- ...state,
912
- overlays: setOverlay(state.overlays, action.overlay, false)
913
- };
914
- case "REQUEST_QUIT": return state.hasUnsavedChanges ? {
915
- ...state,
916
- overlays: setOverlay(state.overlays, "confirmQuit", true)
917
- } : {
918
- ...state,
919
- isExiting: true
920
- };
921
- case "CONFIRM_QUIT": return {
922
- ...state,
923
- overlays: setOverlay(state.overlays, "confirmQuit", false),
924
- isExiting: true
925
- };
926
- case "CANCEL_QUIT": return {
927
- ...state,
928
- overlays: setOverlay(state.overlays, "confirmQuit", false)
929
- };
930
- case "REQUEST_CLOSE":
931
- if (state.openDocument === void 0) return withStatus(state, "info", "There is no open document to close");
932
- return state.hasUnsavedChanges ? {
933
- ...state,
934
- overlays: setOverlay(state.overlays, "confirmClose", true)
935
- } : closeDocument(state);
936
- case "CONFIRM_CLOSE": return closeDocument({
937
- ...state,
938
- overlays: setOverlay(state.overlays, "confirmClose", false)
624
+ case "ADD_PDF_TEXT": return withPdfPage(state, action.pageIndex, (page) => {
625
+ page.appendText(action.init);
939
626
  });
940
- case "CANCEL_CLOSE": return {
941
- ...state,
942
- overlays: setOverlay(state.overlays, "confirmClose", false)
943
- };
944
- case "CLOSE_DOCUMENT": return closeDocument(state);
945
- case "OPEN_FILE_SUCCESS": return withStatus({
946
- ...state,
947
- openDocument: action.doc,
948
- hasUnsavedChanges: false,
949
- undoStack: [],
950
- selection: {},
951
- errorDetail: void 0,
952
- stack: [rootScreenForFormat(action.doc.format)]
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}`);
954
- case "OPEN_FILE_ERROR": return withStatus({
955
- ...state,
956
- errorDetail: {
957
- message: action.message,
958
- detail: action.detail
959
- }
960
- }, "error", action.message);
961
- case "CREATE_DOCUMENT": {
962
- const doc = createNewDocument(action.format);
963
- return withStatus({
964
- ...state,
965
- openDocument: doc,
966
- hasUnsavedChanges: false,
967
- undoStack: [],
968
- selection: {},
969
- errorDetail: void 0,
970
- stack: [rootScreenForFormat(action.format)]
971
- }, "info", `New ${action.format} document`);
972
- }
973
- case "SAVE_SUCCESS": {
974
- const doc = state.openDocument;
975
- if (doc === void 0) return withStatus(state, "warning", "Saved, but there is no open document to record the path against");
976
- return withStatus({
977
- ...state,
978
- openDocument: documentWithPath(doc, action.path),
979
- hasUnsavedChanges: false
980
- }, "info", `Saved ${action.path}`);
981
- }
982
- case "SAVE_ERROR": return withStatus(state, "error", action.message);
983
- case "SAVE_AS_REQUEST": return {
984
- ...state,
985
- stack: [...state.stack, { kind: "saveAsPrompt" }]
986
- };
987
- case "SET_SELECTION": return {
988
- ...state,
989
- selection: {
990
- ...state.selection,
991
- [action.key]: action.index
992
- }
993
- };
994
- case "APPEND_PARAGRAPH": {
995
- const doc = wordprocessingDocument(state);
996
- if (doc === void 0) return wrongDocument(state, "a docx, odt or markdown document");
997
- return mutate(state, doc, () => {
998
- doc.editor.body.appendParagraph({
999
- text: action.text,
1000
- styleId: action.styleId,
1001
- alignment: action.alignment
1002
- });
1003
- });
1004
- }
1005
- case "SET_PARAGRAPH_ALIGNMENT": {
1006
- const doc = styledWordprocessingDocument(state);
1007
- if (doc === void 0) return wrongDocument(state, "a docx or odt document");
1008
- const paragraph = doc.editor.paragraphs()[action.blockIndex];
1009
- if (paragraph === void 0) return withStatus(state, "warning", `There is no paragraph at index ${action.blockIndex}`);
1010
- return mutate(state, doc, () => {
1011
- paragraph.alignment = action.alignment;
1012
- });
1013
- }
1014
- case "APPEND_RUN": {
1015
- const doc = wordprocessingDocument(state);
1016
- if (doc === void 0) return wrongDocument(state, "a docx, odt or markdown document");
1017
- const paragraph = paragraphAt(doc, action.blockIndex);
1018
- if (paragraph === void 0) return withStatus(state, "warning", `There is no paragraph at index ${action.blockIndex}`);
627
+ case "ADD_PDF_RECT": return withPdfPage(state, action.pageIndex, (page) => {
628
+ page.appendRect(action.init);
629
+ });
630
+ case "ADD_PDF_ELLIPSE": return withPdfPage(state, action.pageIndex, (page) => {
631
+ page.appendEllipse(action.init);
632
+ });
633
+ case "ADD_PDF_LINE": return withPdfPage(state, action.pageIndex, (page) => {
634
+ page.appendLine(action.init);
635
+ });
636
+ case "ADD_PDF_PATH": return withPdfPage(state, action.pageIndex, (page) => {
637
+ page.appendPath(action.init);
638
+ });
639
+ case "ADD_PDF_IMAGE": return withPdfPage(state, action.pageIndex, (page) => {
640
+ page.appendImage(action.init);
641
+ });
642
+ case "ADD_PDF_LINK": return withPdfPage(state, action.pageIndex, (page) => {
643
+ page.appendLink(action.init);
644
+ });
645
+ case "REMOVE_PDF_ITEM": {
646
+ const doc = pdfDocument(state);
647
+ if (doc === void 0) return wrongDocument(state, "a pdf document");
648
+ const item = pdfItemAt(doc, action.pageIndex, action.itemIndex);
649
+ if (item === void 0) return withStatus(state, "warning", `Page ${action.pageIndex} has no item at index ${action.itemIndex}`);
1019
650
  return mutate(state, doc, () => {
1020
- paragraph.appendRun({ text: action.text });
651
+ item.remove();
1021
652
  });
1022
653
  }
1023
- case "SET_RUN_TEXT": return withRun(state, action.blockIndex, action.runIndex, (run) => {
1024
- run.text = action.text;
654
+ case "SET_PDF_TEXT_TEXT": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfTextItem, "text", (item) => {
655
+ item.text = action.text;
1025
656
  });
1026
- case "TOGGLE_RUN_BOLD": return withRun(state, action.blockIndex, action.runIndex, (run) => {
1027
- run.bold = !run.bold;
657
+ case "SET_PDF_TEXT_POSITION": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfTextItem, "text", (item) => {
658
+ item.xPt = action.xPt;
659
+ item.yPt = action.yPt;
1028
660
  });
1029
- case "TOGGLE_RUN_ITALIC": return withRun(state, action.blockIndex, action.runIndex, (run) => {
1030
- run.italic = !run.italic;
661
+ case "SET_PDF_TEXT_FONT": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfTextItem, "text", (item) => {
662
+ item.font = action.font;
1031
663
  });
1032
- case "TOGGLE_RUN_UNDERLINE": return withStyledRun(state, action.blockIndex, action.runIndex, (run) => {
1033
- run.underline = !run.underline;
664
+ case "SET_PDF_TEXT_SIZE": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfTextItem, "text", (item) => {
665
+ item.sizePt = action.sizePt;
1034
666
  });
1035
- case "SET_RUN_COLOR": return withStyledRun(state, action.blockIndex, action.runIndex, (run) => {
1036
- run.color = action.color;
667
+ case "SET_PDF_TEXT_COLOR": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfTextItem, "text", (item) => {
668
+ item.color = action.color;
1037
669
  });
1038
- case "SET_RUN_FONT_FAMILY": return withStyledRun(state, action.blockIndex, action.runIndex, (run) => {
1039
- run.fontFamily = action.fontFamily;
670
+ case "SET_PDF_TEXT_ROTATION": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfTextItem, "text", (item) => {
671
+ item.rotationDeg = action.rotationDeg;
1040
672
  });
1041
- case "SET_RUN_FONT_SIZE": return withStyledRun(state, action.blockIndex, action.runIndex, (run) => {
1042
- run.sizePt = action.sizePt;
673
+ case "SET_PDF_TEXT_WIDTH": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfTextItem, "text", (item) => {
674
+ item.widthPt = action.widthPt;
675
+ });
676
+ case "TOGGLE_PDF_TEXT_UNDERLINE": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfTextItem, "text", (item) => {
677
+ item.underline = !(item.underline ?? false);
678
+ });
679
+ case "SET_PDF_RECT_FRAME": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfRectItem, "rect", (item) => {
680
+ item.xPt = action.xPt;
681
+ item.yPt = action.yPt;
682
+ item.widthPt = action.widthPt;
683
+ item.heightPt = action.heightPt;
684
+ });
685
+ case "SET_PDF_RECT_FILL": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfRectItem, "rect", (item) => {
686
+ item.fill = action.fill;
687
+ });
688
+ case "SET_PDF_RECT_STROKE": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfRectItem, "rect", (item) => {
689
+ item.stroke = action.stroke;
690
+ });
691
+ case "SET_PDF_ELLIPSE_FRAME": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfEllipseItem, "ellipse", (item) => {
692
+ item.xPt = action.xPt;
693
+ item.yPt = action.yPt;
694
+ item.widthPt = action.widthPt;
695
+ item.heightPt = action.heightPt;
696
+ });
697
+ case "SET_PDF_ELLIPSE_FILL": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfEllipseItem, "ellipse", (item) => {
698
+ item.fill = action.fill;
699
+ });
700
+ case "SET_PDF_ELLIPSE_STROKE": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfEllipseItem, "ellipse", (item) => {
701
+ item.stroke = action.stroke;
702
+ });
703
+ case "SET_PDF_LINE_FROM": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfLineItem, "line", (item) => {
704
+ item.x1Pt = action.x1Pt;
705
+ item.y1Pt = action.y1Pt;
706
+ });
707
+ case "SET_PDF_LINE_TO": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfLineItem, "line", (item) => {
708
+ item.x2Pt = action.x2Pt;
709
+ item.y2Pt = action.y2Pt;
710
+ });
711
+ case "SET_PDF_LINE_COLOR": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfLineItem, "line", (item) => {
712
+ item.color = action.color;
713
+ });
714
+ case "SET_PDF_LINE_WIDTH": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfLineItem, "line", (item) => {
715
+ item.widthPt = action.widthPt;
716
+ });
717
+ case "SET_PDF_PATH_FILL": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfPathItem, "path", (item) => {
718
+ item.fill = action.fill;
719
+ });
720
+ case "SET_PDF_PATH_FILL_RULE": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfPathItem, "path", (item) => {
721
+ item.fillRule = action.fillRule;
722
+ });
723
+ case "SET_PDF_PATH_STROKE": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfPathItem, "path", (item) => {
724
+ item.stroke = action.stroke;
725
+ });
726
+ case "SET_PDF_IMAGE_FRAME": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfImageItem, "image", (item) => {
727
+ item.xPt = action.xPt;
728
+ item.yPt = action.yPt;
729
+ item.widthPt = action.widthPt;
730
+ item.heightPt = action.heightPt;
731
+ });
732
+ case "SET_PDF_IMAGE_ROTATION": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfImageItem, "image", (item) => {
733
+ item.rotationDeg = action.rotationDeg;
734
+ });
735
+ case "SET_PDF_IMAGE_SOURCE": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfImageItem, "image", (item) => {
736
+ item.setImage(action.bytes, action.format);
737
+ });
738
+ case "SET_PDF_LINK_URI": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfLinkItem, "link", (item) => {
739
+ item.uri = action.uri;
740
+ });
741
+ case "SET_PDF_LINK_FRAME": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfLinkItem, "link", (item) => {
742
+ item.xPt = action.xPt;
743
+ item.yPt = action.yPt;
744
+ item.widthPt = action.widthPt;
745
+ item.heightPt = action.heightPt;
746
+ });
747
+ case "SET_PDF_INTERNAL_LINK_DESTINATION": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfInternalLinkItem, "internalLink", (item) => {
748
+ item.destination = action.destination;
749
+ });
750
+ case "SET_PDF_INTERNAL_LINK_FRAME": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfInternalLinkItem, "internalLink", (item) => {
751
+ item.xPt = action.xPt;
752
+ item.yPt = action.yPt;
753
+ item.widthPt = action.widthPt;
754
+ item.heightPt = action.heightPt;
755
+ });
756
+ default: return;
757
+ }
758
+ }
759
+ //#endregion
760
+ //#region src/tui/state/reducer-text-actions.ts
761
+ function applyTextAction(state, action) {
762
+ switch (action.type) {
763
+ case "SET_SELECTION": return {
764
+ ...state,
765
+ selection: {
766
+ ...state.selection,
767
+ [action.key]: action.index
768
+ }
769
+ };
770
+ case "APPEND_PARAGRAPH": {
771
+ const doc = wordprocessingDocument(state);
772
+ if (doc === void 0) return wrongDocument(state, "a docx, odt or markdown document");
773
+ return mutate(state, doc, () => {
774
+ doc.editor.body.appendParagraph({
775
+ text: action.text,
776
+ styleId: action.styleId,
777
+ alignment: action.alignment
778
+ });
779
+ });
780
+ }
781
+ case "SET_PARAGRAPH_ALIGNMENT": {
782
+ const doc = styledWordprocessingDocument(state);
783
+ if (doc === void 0) return wrongDocument(state, "a docx or odt document");
784
+ const paragraph = doc.editor.paragraphs()[action.blockIndex];
785
+ if (paragraph === void 0) return withStatus(state, "warning", `There is no paragraph at index ${action.blockIndex}`);
786
+ return mutate(state, doc, () => {
787
+ paragraph.alignment = action.alignment;
788
+ });
789
+ }
790
+ case "APPEND_RUN": {
791
+ const doc = wordprocessingDocument(state);
792
+ if (doc === void 0) return wrongDocument(state, "a docx, odt or markdown document");
793
+ const paragraph = paragraphAt(doc, action.blockIndex);
794
+ if (paragraph === void 0) return withStatus(state, "warning", `There is no paragraph at index ${action.blockIndex}`);
795
+ return mutate(state, doc, () => {
796
+ paragraph.appendRun({ text: action.text });
797
+ });
798
+ }
799
+ case "SET_RUN_TEXT": return withRun(state, action.blockIndex, action.runIndex, (run) => {
800
+ run.text = action.text;
801
+ });
802
+ case "TOGGLE_RUN_BOLD": return withRun(state, action.blockIndex, action.runIndex, (run) => {
803
+ run.bold = !run.bold;
804
+ });
805
+ case "TOGGLE_RUN_ITALIC": return withRun(state, action.blockIndex, action.runIndex, (run) => {
806
+ run.italic = !run.italic;
807
+ });
808
+ case "TOGGLE_RUN_UNDERLINE": return withStyledRun(state, action.blockIndex, action.runIndex, (run) => {
809
+ run.underline = !run.underline;
810
+ });
811
+ case "SET_RUN_COLOR": return withStyledRun(state, action.blockIndex, action.runIndex, (run) => {
812
+ run.color = action.color;
813
+ });
814
+ case "SET_RUN_FONT_FAMILY": return withStyledRun(state, action.blockIndex, action.runIndex, (run) => {
815
+ run.fontFamily = action.fontFamily;
816
+ });
817
+ case "SET_RUN_FONT_SIZE": return withStyledRun(state, action.blockIndex, action.runIndex, (run) => {
818
+ run.sizePt = action.sizePt;
1043
819
  });
1044
820
  case "APPEND_TABLE": {
1045
821
  const doc = wordprocessingDocument(state);
@@ -1162,6 +938,13 @@ function appReducer(state, action) {
1162
938
  doc.editor.body.appendFormula({ mathml: action.mathml.map(mutableMathMlNode) }, action.frame);
1163
939
  });
1164
940
  }
941
+ default: return;
942
+ }
943
+ }
944
+ //#endregion
945
+ //#region src/tui/state/reducer-slide-actions.ts
946
+ function applySlideAction(state, action) {
947
+ switch (action.type) {
1165
948
  case "ADD_SLIDE": {
1166
949
  const doc = presentationDocument(state);
1167
950
  if (doc === void 0) return wrongDocument(state, "a pptx or odp document");
@@ -1300,18 +1083,398 @@ function appReducer(state, action) {
1300
1083
  });
1301
1084
  });
1302
1085
  }
1303
- case "MERGE_CELLS": {
1304
- const doc = odsDocument$1(state);
1305
- if (doc === void 0) return wrongDocument(state, "an ods document");
1306
- const sheet = odsSheetAt(doc, action.sheetIndex);
1307
- if (sheet === void 0) return withStatus(state, "warning", `There is no sheet at index ${action.sheetIndex}`);
1308
- return mutateGuarded(state, doc, () => {
1309
- sheet.mergeCells(action.startRow, action.startColumn, action.rowSpan, action.colSpan);
1310
- });
1086
+ case "MERGE_CELLS": {
1087
+ const doc = odsDocument$1(state);
1088
+ if (doc === void 0) return wrongDocument(state, "an ods document");
1089
+ const sheet = odsSheetAt(doc, action.sheetIndex);
1090
+ if (sheet === void 0) return withStatus(state, "warning", `There is no sheet at index ${action.sheetIndex}`);
1091
+ return mutateGuarded(state, doc, () => {
1092
+ sheet.mergeCells(action.startRow, action.startColumn, action.rowSpan, action.colSpan);
1093
+ });
1094
+ }
1095
+ case "SET_SHEET_PRINT_SETTINGS": return withSheet(state, action.sheetIndex, (sheet) => {
1096
+ sheet.printSettings = action.printSettings;
1097
+ });
1098
+ default: return;
1099
+ }
1100
+ }
1101
+ //#endregion
1102
+ //#region src/tui/state/reducer.ts
1103
+ function createInitialState(options) {
1104
+ return {
1105
+ stack: [{ kind: "launcher" }],
1106
+ openDocument: void 0,
1107
+ hasUnsavedChanges: false,
1108
+ overlays: {
1109
+ commandPalette: false,
1110
+ search: false,
1111
+ help: false,
1112
+ confirmQuit: false,
1113
+ confirmClose: false,
1114
+ diagnosticsPanel: false
1115
+ },
1116
+ status: void 0,
1117
+ diagnostics: [],
1118
+ undoStack: [],
1119
+ selection: {},
1120
+ searchQuery: "",
1121
+ errorDetail: void 0,
1122
+ isExiting: false,
1123
+ cwd: options?.cwd ?? process.cwd()
1124
+ };
1125
+ }
1126
+ function withStatus(state, severity, text) {
1127
+ return {
1128
+ ...state,
1129
+ status: {
1130
+ severity,
1131
+ text,
1132
+ createdAtMs: Date.now()
1133
+ }
1134
+ };
1135
+ }
1136
+ function setOverlay(overlays, overlay, open) {
1137
+ switch (overlay) {
1138
+ case "commandPalette": return {
1139
+ ...overlays,
1140
+ commandPalette: open
1141
+ };
1142
+ case "search": return {
1143
+ ...overlays,
1144
+ search: open
1145
+ };
1146
+ case "help": return {
1147
+ ...overlays,
1148
+ help: open
1149
+ };
1150
+ case "confirmQuit": return {
1151
+ ...overlays,
1152
+ confirmQuit: open
1153
+ };
1154
+ case "confirmClose": return {
1155
+ ...overlays,
1156
+ confirmClose: open
1157
+ };
1158
+ case "diagnosticsPanel": return {
1159
+ ...overlays,
1160
+ diagnosticsPanel: open
1161
+ };
1162
+ }
1163
+ }
1164
+ function pushSnapshot(stack, snapshot) {
1165
+ return [...stack, snapshot].slice(-20);
1166
+ }
1167
+ function documentWithPath(doc, path) {
1168
+ switch (doc.format) {
1169
+ case "docx": return {
1170
+ format: "docx",
1171
+ editor: doc.editor,
1172
+ path
1173
+ };
1174
+ case "pptx": return {
1175
+ format: "pptx",
1176
+ editor: doc.editor,
1177
+ path
1178
+ };
1179
+ case "odt": return {
1180
+ format: "odt",
1181
+ editor: doc.editor,
1182
+ path
1183
+ };
1184
+ case "odp": return {
1185
+ format: "odp",
1186
+ editor: doc.editor,
1187
+ path
1188
+ };
1189
+ case "ods": return {
1190
+ format: "ods",
1191
+ editor: doc.editor,
1192
+ path
1193
+ };
1194
+ case "odg": return {
1195
+ format: "odg",
1196
+ editor: doc.editor,
1197
+ path
1198
+ };
1199
+ case "pdf": return {
1200
+ format: "pdf",
1201
+ editor: doc.editor,
1202
+ layout: doc.layout,
1203
+ path
1204
+ };
1205
+ case "odb": return {
1206
+ format: "odb",
1207
+ tables: doc.tables,
1208
+ forms: doc.forms,
1209
+ reports: doc.reports,
1210
+ path
1211
+ };
1212
+ case "markdown": return {
1213
+ format: "markdown",
1214
+ editor: doc.editor,
1215
+ originalText: doc.originalText,
1216
+ path
1217
+ };
1218
+ case "xlsx": return {
1219
+ format: "xlsx",
1220
+ layout: doc.layout,
1221
+ bytes: doc.bytes,
1222
+ path
1223
+ };
1224
+ case "csv": return {
1225
+ format: "csv",
1226
+ layout: doc.layout,
1227
+ bytes: doc.bytes,
1228
+ path
1229
+ };
1230
+ case "svg": return {
1231
+ format: "svg",
1232
+ layout: doc.layout,
1233
+ bytes: doc.bytes,
1234
+ path
1235
+ };
1236
+ case "rtf": return {
1237
+ format: "rtf",
1238
+ layout: doc.layout,
1239
+ bytes: doc.bytes,
1240
+ path
1241
+ };
1242
+ case "wpd": return {
1243
+ format: "wpd",
1244
+ layout: doc.layout,
1245
+ bytes: doc.bytes,
1246
+ path
1247
+ };
1248
+ case "doc": return {
1249
+ format: "doc",
1250
+ editor: doc.editor,
1251
+ path
1252
+ };
1253
+ case "xls": return {
1254
+ format: "xls",
1255
+ editor: doc.editor,
1256
+ path
1257
+ };
1258
+ case "ppt": return {
1259
+ format: "ppt",
1260
+ editor: doc.editor,
1261
+ path
1262
+ };
1263
+ case "epub": return {
1264
+ format: "epub",
1265
+ layout: doc.layout,
1266
+ bytes: doc.bytes,
1267
+ path
1268
+ };
1269
+ }
1270
+ }
1271
+ function reopenEditable(doc, bytes) {
1272
+ switch (doc.format) {
1273
+ case "docx": return {
1274
+ format: "docx",
1275
+ editor: openDocx(bytes),
1276
+ path: doc.path
1277
+ };
1278
+ case "pptx": return {
1279
+ format: "pptx",
1280
+ editor: openPptx(bytes),
1281
+ path: doc.path
1282
+ };
1283
+ case "odt": return {
1284
+ format: "odt",
1285
+ editor: openOdt(bytes),
1286
+ path: doc.path
1287
+ };
1288
+ case "odp": return {
1289
+ format: "odp",
1290
+ editor: openOdp(bytes),
1291
+ path: doc.path
1292
+ };
1293
+ case "ods": return {
1294
+ format: "ods",
1295
+ editor: openOds(bytes),
1296
+ path: doc.path
1297
+ };
1298
+ case "odg": return {
1299
+ format: "odg",
1300
+ editor: openOdg(bytes),
1301
+ path: doc.path
1302
+ };
1303
+ case "doc": return {
1304
+ format: "doc",
1305
+ editor: openDoc(bytes),
1306
+ path: doc.path
1307
+ };
1308
+ case "xls": return {
1309
+ format: "xls",
1310
+ editor: openXls(bytes),
1311
+ path: doc.path
1312
+ };
1313
+ case "ppt": return {
1314
+ format: "ppt",
1315
+ editor: openPpt(bytes),
1316
+ path: doc.path
1317
+ };
1318
+ case "pdf": {
1319
+ const editor = openPdf(bytes);
1320
+ return {
1321
+ format: "pdf",
1322
+ editor,
1323
+ layout: editor.toLayoutDocument(),
1324
+ path: doc.path
1325
+ };
1326
+ }
1327
+ }
1328
+ }
1329
+ function toUndoSnapshot(doc) {
1330
+ return doc.format === "markdown" ? encodeMarkdownText(doc.editor.toMarkdownText()) : doc.editor.toBytes();
1331
+ }
1332
+ function mutate(state, doc, apply) {
1333
+ const snapshot = toUndoSnapshot(doc);
1334
+ apply();
1335
+ return {
1336
+ ...state,
1337
+ hasUnsavedChanges: true,
1338
+ undoStack: pushSnapshot(state.undoStack, snapshot)
1339
+ };
1340
+ }
1341
+ function mutateGuarded(state, doc, apply) {
1342
+ try {
1343
+ return mutate(state, doc, apply);
1344
+ } catch (error) {
1345
+ return withStatus(state, "warning", error instanceof Error ? error.message : String(error));
1346
+ }
1347
+ }
1348
+ function mergePptxTableCells(table, startRow, startColumn, rowSpan, colSpan) {
1349
+ if (!Number.isInteger(rowSpan) || rowSpan < 1 || !Number.isInteger(colSpan) || colSpan < 1) throw new Error(`mergeSlideTableCells: rowSpan and colSpan must be positive integers, got rowSpan=${rowSpan}, colSpan=${colSpan}`);
1350
+ const rows = table.rows();
1351
+ if (startRow + rowSpan > rows.length) throw new Error(`mergeSlideTableCells: rowSpan ${rowSpan} starting at row ${startRow} exceeds this table's own ${rows.length} rows`);
1352
+ const anchorRow = rows[startRow];
1353
+ if (anchorRow === void 0) throw new Error(`mergeSlideTableCells: row ${startRow} does not exist in this table`);
1354
+ const columnCount = anchorRow.cells().length;
1355
+ if (startColumn + colSpan > columnCount) throw new Error(`mergeSlideTableCells: colSpan ${colSpan} starting at column ${startColumn} exceeds this table's own ${columnCount} columns`);
1356
+ for (let rowOffset = 0; rowOffset < rowSpan; rowOffset++) {
1357
+ const row = rows[startRow + rowOffset];
1358
+ if (row === void 0) throw new Error(`mergeSlideTableCells: row ${startRow + rowOffset} does not exist in this table`);
1359
+ const cells = row.cells();
1360
+ for (let columnOffset = 0; columnOffset < colSpan; columnOffset++) {
1361
+ const cell = cells[startColumn + columnOffset];
1362
+ if (cell === void 0) throw new Error(`mergeSlideTableCells: column ${startColumn + columnOffset} does not exist in row ${startRow + rowOffset}`);
1363
+ if (rowOffset === 0 && columnOffset === 0) {
1364
+ cell.colSpan = colSpan;
1365
+ cell.rowSpan = rowSpan;
1366
+ continue;
1367
+ }
1368
+ if (columnOffset > 0) cell.horizontalMerge = true;
1369
+ if (rowOffset > 0) cell.verticalMerge = true;
1370
+ cell.setParagraphs([{ runs: [] }]);
1371
+ }
1372
+ }
1373
+ }
1374
+ function wrongDocument(state, expected) {
1375
+ return withStatus(state, "warning", `That action needs ${expected}; the open document is ${state.openDocument === void 0 ? "no document" : state.openDocument.format}`);
1376
+ }
1377
+ function appReducer(state, action) {
1378
+ const pdfHandled = applyPdfAction(state, action);
1379
+ if (pdfHandled !== void 0) return pdfHandled;
1380
+ const textHandled = applyTextAction(state, action);
1381
+ if (textHandled !== void 0) return textHandled;
1382
+ const slideHandled = applySlideAction(state, action);
1383
+ if (slideHandled !== void 0) return slideHandled;
1384
+ switch (action.type) {
1385
+ case "PUSH_SCREEN": return {
1386
+ ...state,
1387
+ stack: [...state.stack, action.screen]
1388
+ };
1389
+ case "POP_SCREEN": return state.stack.length <= 1 ? state : {
1390
+ ...state,
1391
+ stack: state.stack.slice(0, -1)
1392
+ };
1393
+ case "RESET_STACK": return {
1394
+ ...state,
1395
+ stack: [action.screen]
1396
+ };
1397
+ case "OPEN_OVERLAY": return {
1398
+ ...state,
1399
+ overlays: setOverlay(state.overlays, action.overlay, true)
1400
+ };
1401
+ case "CLOSE_OVERLAY": return {
1402
+ ...state,
1403
+ overlays: setOverlay(state.overlays, action.overlay, false)
1404
+ };
1405
+ case "REQUEST_QUIT": return state.hasUnsavedChanges ? {
1406
+ ...state,
1407
+ overlays: setOverlay(state.overlays, "confirmQuit", true)
1408
+ } : {
1409
+ ...state,
1410
+ isExiting: true
1411
+ };
1412
+ case "CONFIRM_QUIT": return {
1413
+ ...state,
1414
+ overlays: setOverlay(state.overlays, "confirmQuit", false),
1415
+ isExiting: true
1416
+ };
1417
+ case "CANCEL_QUIT": return {
1418
+ ...state,
1419
+ overlays: setOverlay(state.overlays, "confirmQuit", false)
1420
+ };
1421
+ case "REQUEST_CLOSE":
1422
+ if (state.openDocument === void 0) return withStatus(state, "info", "There is no open document to close");
1423
+ return state.hasUnsavedChanges ? {
1424
+ ...state,
1425
+ overlays: setOverlay(state.overlays, "confirmClose", true)
1426
+ } : closeDocument(state);
1427
+ case "CONFIRM_CLOSE": return closeDocument({
1428
+ ...state,
1429
+ overlays: setOverlay(state.overlays, "confirmClose", false)
1430
+ });
1431
+ case "CANCEL_CLOSE": return {
1432
+ ...state,
1433
+ overlays: setOverlay(state.overlays, "confirmClose", false)
1434
+ };
1435
+ case "CLOSE_DOCUMENT": return closeDocument(state);
1436
+ case "OPEN_FILE_SUCCESS": return withStatus({
1437
+ ...state,
1438
+ openDocument: action.doc,
1439
+ hasUnsavedChanges: false,
1440
+ undoStack: [],
1441
+ selection: {},
1442
+ errorDetail: void 0,
1443
+ stack: [rootScreenForFormat(action.doc.format)]
1444
+ }, "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}`);
1445
+ case "OPEN_FILE_ERROR": return withStatus({
1446
+ ...state,
1447
+ errorDetail: {
1448
+ message: action.message,
1449
+ detail: action.detail
1450
+ }
1451
+ }, "error", action.message);
1452
+ case "CREATE_DOCUMENT": {
1453
+ const doc = createNewDocument(action.format);
1454
+ return withStatus({
1455
+ ...state,
1456
+ openDocument: doc,
1457
+ hasUnsavedChanges: false,
1458
+ undoStack: [],
1459
+ selection: {},
1460
+ errorDetail: void 0,
1461
+ stack: [rootScreenForFormat(action.format)]
1462
+ }, "info", `New ${action.format} document`);
1463
+ }
1464
+ case "SAVE_SUCCESS": {
1465
+ const doc = state.openDocument;
1466
+ if (doc === void 0) return withStatus(state, "warning", "Saved, but there is no open document to record the path against");
1467
+ return withStatus({
1468
+ ...state,
1469
+ openDocument: documentWithPath(doc, action.path),
1470
+ hasUnsavedChanges: false
1471
+ }, "info", `Saved ${action.path}`);
1311
1472
  }
1312
- case "SET_SHEET_PRINT_SETTINGS": return withSheet(state, action.sheetIndex, (sheet) => {
1313
- sheet.printSettings = action.printSettings;
1314
- });
1473
+ case "SAVE_ERROR": return withStatus(state, "error", action.message);
1474
+ case "SAVE_AS_REQUEST": return {
1475
+ ...state,
1476
+ stack: [...state.stack, { kind: "saveAsPrompt" }]
1477
+ };
1315
1478
  case "ADD_PAGE": {
1316
1479
  const doc = drawingDocument(state);
1317
1480
  if (doc === void 0) return wrongDocument(state, "an odg document");
@@ -1338,199 +1501,67 @@ function appReducer(state, action) {
1338
1501
  return;
1339
1502
  case "ADD_LINE":
1340
1503
  page.addLine(action.init);
1341
- return;
1342
- case "ADD_PATH":
1343
- page.addPath(action.init);
1344
- return;
1345
- }
1346
- });
1347
- }
1348
- const slide = doc.editor.slides()[action.containerIndex];
1349
- if (slide === void 0) return withStatus(state, "warning", `There is no slide at index ${action.containerIndex}`);
1350
- return mutate(state, doc, () => {
1351
- switch (action.type) {
1352
- case "ADD_RECT":
1353
- slide.addVector({
1354
- kind: "rect",
1355
- frame: action.init.frame,
1356
- fill: action.init.fill,
1357
- stroke: action.init.stroke
1358
- });
1359
- return;
1360
- case "ADD_ELLIPSE":
1361
- slide.addVector({
1362
- kind: "ellipse",
1363
- frame: action.init.frame,
1364
- fill: action.init.fill,
1365
- stroke: action.init.stroke
1366
- });
1367
- return;
1368
- case "ADD_LINE":
1369
- slide.addVector({
1370
- kind: "line",
1371
- from: action.init.from,
1372
- to: action.init.to,
1373
- stroke: action.init.stroke
1374
- });
1375
- return;
1376
- case "ADD_PATH":
1377
- slide.addVector({
1378
- kind: "path",
1379
- frame: action.init.frame,
1380
- subpaths: [...action.init.subpaths],
1381
- fill: action.init.fill,
1382
- stroke: action.init.stroke
1383
- });
1384
- return;
1385
- }
1386
- });
1387
- }
1388
- case "SET_VECTOR_FILL": {
1389
- const doc = drawingDocument(state);
1390
- if (doc === void 0) return wrongDocument(state, "an odg document");
1391
- return mutate(state, doc, () => {
1392
- action.vector.fill = action.fill;
1393
- });
1394
- }
1395
- case "SET_VECTOR_STROKE": {
1396
- const doc = drawingDocument(state);
1397
- if (doc === void 0) return wrongDocument(state, "an odg document");
1398
- return mutate(state, doc, () => {
1399
- action.vector.stroke = action.stroke;
1400
- });
1401
- }
1402
- case "ADD_PDF_TEXT": return withPdfPage(state, action.pageIndex, (page) => {
1403
- page.appendText(action.init);
1404
- });
1405
- case "ADD_PDF_RECT": return withPdfPage(state, action.pageIndex, (page) => {
1406
- page.appendRect(action.init);
1407
- });
1408
- case "ADD_PDF_ELLIPSE": return withPdfPage(state, action.pageIndex, (page) => {
1409
- page.appendEllipse(action.init);
1410
- });
1411
- case "ADD_PDF_LINE": return withPdfPage(state, action.pageIndex, (page) => {
1412
- page.appendLine(action.init);
1413
- });
1414
- case "ADD_PDF_PATH": return withPdfPage(state, action.pageIndex, (page) => {
1415
- page.appendPath(action.init);
1416
- });
1417
- case "ADD_PDF_IMAGE": return withPdfPage(state, action.pageIndex, (page) => {
1418
- page.appendImage(action.init);
1419
- });
1420
- case "ADD_PDF_LINK": return withPdfPage(state, action.pageIndex, (page) => {
1421
- page.appendLink(action.init);
1422
- });
1423
- case "REMOVE_PDF_ITEM": {
1424
- const doc = pdfDocument(state);
1425
- if (doc === void 0) return wrongDocument(state, "a pdf document");
1426
- const item = pdfItemAt(doc, action.pageIndex, action.itemIndex);
1427
- if (item === void 0) return withStatus(state, "warning", `Page ${action.pageIndex} has no item at index ${action.itemIndex}`);
1428
- return mutate(state, doc, () => {
1429
- item.remove();
1430
- });
1431
- }
1432
- case "SET_PDF_TEXT_TEXT": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfTextItem, "text", (item) => {
1433
- item.text = action.text;
1434
- });
1435
- case "SET_PDF_TEXT_POSITION": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfTextItem, "text", (item) => {
1436
- item.xPt = action.xPt;
1437
- item.yPt = action.yPt;
1438
- });
1439
- case "SET_PDF_TEXT_FONT": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfTextItem, "text", (item) => {
1440
- item.font = action.font;
1441
- });
1442
- case "SET_PDF_TEXT_SIZE": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfTextItem, "text", (item) => {
1443
- item.sizePt = action.sizePt;
1444
- });
1445
- case "SET_PDF_TEXT_COLOR": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfTextItem, "text", (item) => {
1446
- item.color = action.color;
1447
- });
1448
- case "SET_PDF_TEXT_ROTATION": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfTextItem, "text", (item) => {
1449
- item.rotationDeg = action.rotationDeg;
1450
- });
1451
- case "SET_PDF_TEXT_WIDTH": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfTextItem, "text", (item) => {
1452
- item.widthPt = action.widthPt;
1453
- });
1454
- case "TOGGLE_PDF_TEXT_UNDERLINE": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfTextItem, "text", (item) => {
1455
- item.underline = !(item.underline ?? false);
1456
- });
1457
- case "SET_PDF_RECT_FRAME": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfRectItem, "rect", (item) => {
1458
- item.xPt = action.xPt;
1459
- item.yPt = action.yPt;
1460
- item.widthPt = action.widthPt;
1461
- item.heightPt = action.heightPt;
1462
- });
1463
- case "SET_PDF_RECT_FILL": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfRectItem, "rect", (item) => {
1464
- item.fill = action.fill;
1465
- });
1466
- case "SET_PDF_RECT_STROKE": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfRectItem, "rect", (item) => {
1467
- item.stroke = action.stroke;
1468
- });
1469
- case "SET_PDF_ELLIPSE_FRAME": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfEllipseItem, "ellipse", (item) => {
1470
- item.xPt = action.xPt;
1471
- item.yPt = action.yPt;
1472
- item.widthPt = action.widthPt;
1473
- item.heightPt = action.heightPt;
1474
- });
1475
- case "SET_PDF_ELLIPSE_FILL": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfEllipseItem, "ellipse", (item) => {
1476
- item.fill = action.fill;
1477
- });
1478
- case "SET_PDF_ELLIPSE_STROKE": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfEllipseItem, "ellipse", (item) => {
1479
- item.stroke = action.stroke;
1480
- });
1481
- case "SET_PDF_LINE_FROM": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfLineItem, "line", (item) => {
1482
- item.x1Pt = action.x1Pt;
1483
- item.y1Pt = action.y1Pt;
1484
- });
1485
- case "SET_PDF_LINE_TO": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfLineItem, "line", (item) => {
1486
- item.x2Pt = action.x2Pt;
1487
- item.y2Pt = action.y2Pt;
1488
- });
1489
- case "SET_PDF_LINE_COLOR": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfLineItem, "line", (item) => {
1490
- item.color = action.color;
1491
- });
1492
- case "SET_PDF_LINE_WIDTH": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfLineItem, "line", (item) => {
1493
- item.widthPt = action.widthPt;
1494
- });
1495
- case "SET_PDF_PATH_FILL": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfPathItem, "path", (item) => {
1496
- item.fill = action.fill;
1497
- });
1498
- case "SET_PDF_PATH_FILL_RULE": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfPathItem, "path", (item) => {
1499
- item.fillRule = action.fillRule;
1500
- });
1501
- case "SET_PDF_PATH_STROKE": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfPathItem, "path", (item) => {
1502
- item.stroke = action.stroke;
1503
- });
1504
- case "SET_PDF_IMAGE_FRAME": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfImageItem, "image", (item) => {
1505
- item.xPt = action.xPt;
1506
- item.yPt = action.yPt;
1507
- item.widthPt = action.widthPt;
1508
- item.heightPt = action.heightPt;
1509
- });
1510
- case "SET_PDF_IMAGE_ROTATION": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfImageItem, "image", (item) => {
1511
- item.rotationDeg = action.rotationDeg;
1512
- });
1513
- case "SET_PDF_IMAGE_SOURCE": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfImageItem, "image", (item) => {
1514
- item.setImage(action.bytes, action.format);
1515
- });
1516
- case "SET_PDF_LINK_URI": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfLinkItem, "link", (item) => {
1517
- item.uri = action.uri;
1518
- });
1519
- case "SET_PDF_LINK_FRAME": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfLinkItem, "link", (item) => {
1520
- item.xPt = action.xPt;
1521
- item.yPt = action.yPt;
1522
- item.widthPt = action.widthPt;
1523
- item.heightPt = action.heightPt;
1524
- });
1525
- case "SET_PDF_INTERNAL_LINK_DESTINATION": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfInternalLinkItem, "internalLink", (item) => {
1526
- item.destination = action.destination;
1527
- });
1528
- case "SET_PDF_INTERNAL_LINK_FRAME": return withPdfItemMatching(state, action.pageIndex, action.itemIndex, isPdfInternalLinkItem, "internalLink", (item) => {
1529
- item.xPt = action.xPt;
1530
- item.yPt = action.yPt;
1531
- item.widthPt = action.widthPt;
1532
- item.heightPt = action.heightPt;
1533
- });
1504
+ return;
1505
+ case "ADD_PATH":
1506
+ page.addPath(action.init);
1507
+ return;
1508
+ }
1509
+ });
1510
+ }
1511
+ const slide = doc.editor.slides()[action.containerIndex];
1512
+ if (slide === void 0) return withStatus(state, "warning", `There is no slide at index ${action.containerIndex}`);
1513
+ return mutate(state, doc, () => {
1514
+ switch (action.type) {
1515
+ case "ADD_RECT":
1516
+ slide.addVector({
1517
+ kind: "rect",
1518
+ frame: action.init.frame,
1519
+ fill: action.init.fill,
1520
+ stroke: action.init.stroke
1521
+ });
1522
+ return;
1523
+ case "ADD_ELLIPSE":
1524
+ slide.addVector({
1525
+ kind: "ellipse",
1526
+ frame: action.init.frame,
1527
+ fill: action.init.fill,
1528
+ stroke: action.init.stroke
1529
+ });
1530
+ return;
1531
+ case "ADD_LINE":
1532
+ slide.addVector({
1533
+ kind: "line",
1534
+ from: action.init.from,
1535
+ to: action.init.to,
1536
+ stroke: action.init.stroke
1537
+ });
1538
+ return;
1539
+ case "ADD_PATH":
1540
+ slide.addVector({
1541
+ kind: "path",
1542
+ frame: action.init.frame,
1543
+ subpaths: [...action.init.subpaths],
1544
+ fill: action.init.fill,
1545
+ stroke: action.init.stroke
1546
+ });
1547
+ return;
1548
+ }
1549
+ });
1550
+ }
1551
+ case "SET_VECTOR_FILL": {
1552
+ const doc = drawingDocument(state);
1553
+ if (doc === void 0) return wrongDocument(state, "an odg document");
1554
+ return mutate(state, doc, () => {
1555
+ action.vector.fill = action.fill;
1556
+ });
1557
+ }
1558
+ case "SET_VECTOR_STROKE": {
1559
+ const doc = drawingDocument(state);
1560
+ if (doc === void 0) return wrongDocument(state, "an odg document");
1561
+ return mutate(state, doc, () => {
1562
+ action.vector.stroke = action.stroke;
1563
+ });
1564
+ }
1534
1565
  case "SET_METADATA": {
1535
1566
  const doc = state.openDocument;
1536
1567
  if (doc === void 0 || !isEditableDocument(doc)) return wrongDocument(state, "an editable document");
@@ -1580,6 +1611,7 @@ function appReducer(state, action) {
1580
1611
  hasUnsavedChanges: true
1581
1612
  }, "info", "Undone");
1582
1613
  }
1614
+ default: return state;
1583
1615
  }
1584
1616
  }
1585
1617
  function closeDocument(state) {
@@ -8105,103 +8137,7 @@ function PdfPageListScreen() {
8105
8137
  });
8106
8138
  }
8107
8139
  //#endregion
8108
- //#region src/tui/screens/editors/pdf/item-detail.tsx
8109
- function formatPoint(xPt, yPt) {
8110
- return `(${xPt.toFixed(1)}, ${yPt.toFixed(1)})pt`;
8111
- }
8112
- function fieldsFor(item) {
8113
- const fields = [["Kind", item.kind]];
8114
- switch (item.kind) {
8115
- case "text":
8116
- fields.push(["Text", item.text]);
8117
- fields.push(["Position", formatPoint(item.xPt, item.yPt)]);
8118
- fields.push(["Font family", item.font.family]);
8119
- fields.push(["Font weight", item.font.weight]);
8120
- fields.push(["Font style", item.font.style]);
8121
- fields.push(["Size", `${item.sizePt}pt`]);
8122
- fields.push(["Colour", layoutColorToHex(item.color)]);
8123
- if (item.widthPt !== void 0) fields.push(["Width", `${item.widthPt}pt`]);
8124
- if (item.rotationDeg !== void 0) fields.push(["Rotation", `${item.rotationDeg}°`]);
8125
- if (item.underline !== void 0) fields.push(["Underline", item.underline ? "yes" : "no"]);
8126
- break;
8127
- case "image":
8128
- fields.push(["Image ID", item.imageId]);
8129
- fields.push(["Position", formatPoint(item.xPt, item.yPt)]);
8130
- fields.push(["Size", formatSize(item.widthPt, item.heightPt)]);
8131
- if (item.rotationDeg !== void 0) fields.push(["Rotation", `${item.rotationDeg}°`]);
8132
- break;
8133
- case "rect":
8134
- case "ellipse":
8135
- fields.push(["Position", formatPoint(item.xPt, item.yPt)]);
8136
- fields.push(["Size", formatSize(item.widthPt, item.heightPt)]);
8137
- if (item.fill !== void 0) fields.push(["Fill", layoutColorToHex(item.fill)]);
8138
- if (item.stroke !== void 0) fields.push(["Stroke", formatStroke(item.stroke)]);
8139
- break;
8140
- case "line":
8141
- fields.push(["From", formatPoint(item.x1Pt, item.y1Pt)]);
8142
- fields.push(["To", formatPoint(item.x2Pt, item.y2Pt)]);
8143
- fields.push(["Colour", layoutColorToHex(item.color)]);
8144
- fields.push(["Width", `${item.widthPt}pt`]);
8145
- break;
8146
- case "path":
8147
- fields.push(["Subpaths", `${item.subpaths.length}`]);
8148
- fields.push(["Segments", `${item.subpaths.reduce((total, subpath) => total + subpath.segments.length, 0)}`]);
8149
- if (item.fill !== void 0) fields.push(["Fill", layoutColorToHex(item.fill)]);
8150
- if (item.fillRule !== void 0) fields.push(["Fill rule", item.fillRule]);
8151
- if (item.stroke !== void 0) fields.push(["Stroke", formatStroke(item.stroke)]);
8152
- break;
8153
- case "link":
8154
- fields.push(["URI", item.uri]);
8155
- fields.push(["Position", formatPoint(item.xPt, item.yPt)]);
8156
- fields.push(["Size", formatSize(item.widthPt, item.heightPt)]);
8157
- break;
8158
- case "internalLink":
8159
- fields.push(["Destination", item.destination]);
8160
- if (item.title !== void 0) fields.push(["Title", item.title]);
8161
- fields.push(["Position", formatPoint(item.xPt, item.yPt)]);
8162
- fields.push(["Size", formatSize(item.widthPt, item.heightPt)]);
8163
- break;
8164
- default: return item;
8165
- }
8166
- if (item.kind !== "internalLink" && item.sourcePath !== void 0) fields.push(["Source path", item.sourcePath]);
8167
- return fields;
8168
- }
8169
- function ReadOnlyItemDetail(props) {
8170
- useInput((input, key) => {
8171
- if (key.escape || key.leftArrow || input === "h") props.onBack();
8172
- }, { isActive: props.isActive });
8173
- return /* @__PURE__ */ jsxs(Box, {
8174
- flexDirection: "column",
8175
- children: [
8176
- /* @__PURE__ */ jsxs(Text, {
8177
- bold: true,
8178
- children: [
8179
- "Page ",
8180
- props.pageIndex + 1,
8181
- ", item ",
8182
- props.itemIndex + 1
8183
- ]
8184
- }),
8185
- fieldsFor(props.item).map(([label, value]) => /* @__PURE__ */ jsxs(Text, { children: [
8186
- label,
8187
- ": ",
8188
- value
8189
- ] }, label)),
8190
- /* @__PURE__ */ jsx(Text, {
8191
- dimColor: true,
8192
- children: "Esc / ← / h to go back"
8193
- })
8194
- ]
8195
- });
8196
- }
8197
- function frameOf(item) {
8198
- return {
8199
- xPt: item.xPt,
8200
- yPt: item.yPt,
8201
- widthPt: item.widthPt,
8202
- heightPt: item.heightPt
8203
- };
8204
- }
8140
+ //#region src/tui/screens/editors/pdf/item-detail-rows.tsx
8205
8141
  function buildFrameRows(frame, onFrameChange) {
8206
8142
  return [
8207
8143
  {
@@ -8642,6 +8578,104 @@ function buildRowsFor(item, pageIndex, itemIndex, dispatch, onReplaceImage) {
8642
8578
  case "internalLink": return buildInternalLinkRows(item, pageIndex, itemIndex, dispatch);
8643
8579
  }
8644
8580
  }
8581
+ //#endregion
8582
+ //#region src/tui/screens/editors/pdf/item-detail.tsx
8583
+ function formatPoint(xPt, yPt) {
8584
+ return `(${xPt.toFixed(1)}, ${yPt.toFixed(1)})pt`;
8585
+ }
8586
+ function fieldsFor(item) {
8587
+ const fields = [["Kind", item.kind]];
8588
+ switch (item.kind) {
8589
+ case "text":
8590
+ fields.push(["Text", item.text]);
8591
+ fields.push(["Position", formatPoint(item.xPt, item.yPt)]);
8592
+ fields.push(["Font family", item.font.family]);
8593
+ fields.push(["Font weight", item.font.weight]);
8594
+ fields.push(["Font style", item.font.style]);
8595
+ fields.push(["Size", `${item.sizePt}pt`]);
8596
+ fields.push(["Colour", layoutColorToHex(item.color)]);
8597
+ if (item.widthPt !== void 0) fields.push(["Width", `${item.widthPt}pt`]);
8598
+ if (item.rotationDeg !== void 0) fields.push(["Rotation", `${item.rotationDeg}°`]);
8599
+ if (item.underline !== void 0) fields.push(["Underline", item.underline ? "yes" : "no"]);
8600
+ break;
8601
+ case "image":
8602
+ fields.push(["Image ID", item.imageId]);
8603
+ fields.push(["Position", formatPoint(item.xPt, item.yPt)]);
8604
+ fields.push(["Size", formatSize(item.widthPt, item.heightPt)]);
8605
+ if (item.rotationDeg !== void 0) fields.push(["Rotation", `${item.rotationDeg}°`]);
8606
+ break;
8607
+ case "rect":
8608
+ case "ellipse":
8609
+ fields.push(["Position", formatPoint(item.xPt, item.yPt)]);
8610
+ fields.push(["Size", formatSize(item.widthPt, item.heightPt)]);
8611
+ if (item.fill !== void 0) fields.push(["Fill", layoutColorToHex(item.fill)]);
8612
+ if (item.stroke !== void 0) fields.push(["Stroke", formatStroke(item.stroke)]);
8613
+ break;
8614
+ case "line":
8615
+ fields.push(["From", formatPoint(item.x1Pt, item.y1Pt)]);
8616
+ fields.push(["To", formatPoint(item.x2Pt, item.y2Pt)]);
8617
+ fields.push(["Colour", layoutColorToHex(item.color)]);
8618
+ fields.push(["Width", `${item.widthPt}pt`]);
8619
+ break;
8620
+ case "path":
8621
+ fields.push(["Subpaths", `${item.subpaths.length}`]);
8622
+ fields.push(["Segments", `${item.subpaths.reduce((total, subpath) => total + subpath.segments.length, 0)}`]);
8623
+ if (item.fill !== void 0) fields.push(["Fill", layoutColorToHex(item.fill)]);
8624
+ if (item.fillRule !== void 0) fields.push(["Fill rule", item.fillRule]);
8625
+ if (item.stroke !== void 0) fields.push(["Stroke", formatStroke(item.stroke)]);
8626
+ break;
8627
+ case "link":
8628
+ fields.push(["URI", item.uri]);
8629
+ fields.push(["Position", formatPoint(item.xPt, item.yPt)]);
8630
+ fields.push(["Size", formatSize(item.widthPt, item.heightPt)]);
8631
+ break;
8632
+ case "internalLink":
8633
+ fields.push(["Destination", item.destination]);
8634
+ if (item.title !== void 0) fields.push(["Title", item.title]);
8635
+ fields.push(["Position", formatPoint(item.xPt, item.yPt)]);
8636
+ fields.push(["Size", formatSize(item.widthPt, item.heightPt)]);
8637
+ break;
8638
+ default: return item;
8639
+ }
8640
+ if (item.kind !== "internalLink" && item.sourcePath !== void 0) fields.push(["Source path", item.sourcePath]);
8641
+ return fields;
8642
+ }
8643
+ function ReadOnlyItemDetail(props) {
8644
+ useInput((input, key) => {
8645
+ if (key.escape || key.leftArrow || input === "h") props.onBack();
8646
+ }, { isActive: props.isActive });
8647
+ return /* @__PURE__ */ jsxs(Box, {
8648
+ flexDirection: "column",
8649
+ children: [
8650
+ /* @__PURE__ */ jsxs(Text, {
8651
+ bold: true,
8652
+ children: [
8653
+ "Page ",
8654
+ props.pageIndex + 1,
8655
+ ", item ",
8656
+ props.itemIndex + 1
8657
+ ]
8658
+ }),
8659
+ fieldsFor(props.item).map(([label, value]) => /* @__PURE__ */ jsxs(Text, { children: [
8660
+ label,
8661
+ ": ",
8662
+ value
8663
+ ] }, label)),
8664
+ /* @__PURE__ */ jsx(Text, {
8665
+ dimColor: true,
8666
+ children: "Esc / ← / h to go back"
8667
+ })
8668
+ ]
8669
+ });
8670
+ }
8671
+ function frameOf(item) {
8672
+ return {
8673
+ xPt: item.xPt,
8674
+ yPt: item.yPt,
8675
+ widthPt: item.widthPt,
8676
+ heightPt: item.heightPt
8677
+ };
8678
+ }
8645
8679
  function pathSummary(item) {
8646
8680
  const segmentCount = item.subpaths.reduce((total, subpath) => total + subpath.segments.length, 0);
8647
8681
  return `${item.subpaths.length} subpath${item.subpaths.length === 1 ? "" : "s"}, ${segmentCount} segment${segmentCount === 1 ? "" : "s"} (not editable here — see documents.js's own PdfPathItem doc comment)`;