document-cli 1.5.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +2 -2
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/{tui-B-o3iI9a.js → tui-BzdX4QQg.js} +817 -125
- package/package.json +1 -1
|
@@ -230,6 +230,7 @@ function selectionKeyFor(screen) {
|
|
|
230
230
|
case "slideDetail":
|
|
231
231
|
case "notesEditor": return `${screen.kind}:${screen.slideIndex}`;
|
|
232
232
|
case "shapeEditor": return `shapeEditor:${screen.slideIndex}:${screen.shapeIndex}`;
|
|
233
|
+
case "slideTableDetail": return `slideTableDetail:${screen.slideIndex}:${screen.tableIndex}`;
|
|
233
234
|
case "spreadsheetGrid":
|
|
234
235
|
case "printSettingsEditor": return `${screen.kind}:${screen.sheetIndex}`;
|
|
235
236
|
case "cellDetail": return `cellDetail:${screen.sheetIndex}:${screen.row}:${screen.col}`;
|
|
@@ -435,6 +436,38 @@ function mutate(state, doc, apply) {
|
|
|
435
436
|
undoStack: pushSnapshot(state.undoStack, snapshot)
|
|
436
437
|
};
|
|
437
438
|
}
|
|
439
|
+
function mutateGuarded(state, doc, apply) {
|
|
440
|
+
try {
|
|
441
|
+
return mutate(state, doc, apply);
|
|
442
|
+
} catch (error) {
|
|
443
|
+
return withStatus(state, "warning", error instanceof Error ? error.message : String(error));
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
function mergePptxTableCells(table, startRow, startColumn, rowSpan, colSpan) {
|
|
447
|
+
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}`);
|
|
448
|
+
const rows = table.rows();
|
|
449
|
+
if (startRow + rowSpan > rows.length) throw new Error(`mergeSlideTableCells: rowSpan ${rowSpan} starting at row ${startRow} exceeds this table's own ${rows.length} rows`);
|
|
450
|
+
const anchorRow = rows[startRow];
|
|
451
|
+
if (anchorRow === void 0) throw new Error(`mergeSlideTableCells: row ${startRow} does not exist in this table`);
|
|
452
|
+
const columnCount = anchorRow.cells().length;
|
|
453
|
+
if (startColumn + colSpan > columnCount) throw new Error(`mergeSlideTableCells: colSpan ${colSpan} starting at column ${startColumn} exceeds this table's own ${columnCount} columns`);
|
|
454
|
+
for (let rowOffset = 0; rowOffset < rowSpan; rowOffset++) {
|
|
455
|
+
const row = rows[startRow + rowOffset];
|
|
456
|
+
if (row === void 0) throw new Error(`mergeSlideTableCells: row ${startRow + rowOffset} does not exist in this table`);
|
|
457
|
+
const cells = row.cells();
|
|
458
|
+
for (let columnOffset = 0; columnOffset < colSpan; columnOffset++) {
|
|
459
|
+
const cell = cells[startColumn + columnOffset];
|
|
460
|
+
if (cell === void 0) throw new Error(`mergeSlideTableCells: column ${startColumn + columnOffset} does not exist in row ${startRow + rowOffset}`);
|
|
461
|
+
if (rowOffset === 0 && columnOffset === 0) {
|
|
462
|
+
cell.colSpan = colSpan;
|
|
463
|
+
cell.rowSpan = rowSpan;
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
466
|
+
if (columnOffset > 0) cell.horizontalMerge = true;
|
|
467
|
+
if (rowOffset > 0) cell.verticalMerge = true;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
438
471
|
function mutateMarkdown(state, doc, source) {
|
|
439
472
|
const snapshot = encodeMarkdownText(doc.source);
|
|
440
473
|
return {
|
|
@@ -490,10 +523,6 @@ function shapeAt(doc, containerIndex, shapeIndex) {
|
|
|
490
523
|
if (doc.format === "odg") return doc.editor.pages()[containerIndex]?.shapes()[shapeIndex];
|
|
491
524
|
return doc.editor.slides()[containerIndex]?.shapes()[shapeIndex];
|
|
492
525
|
}
|
|
493
|
-
function rotatableShapeAt(doc, containerIndex, shapeIndex) {
|
|
494
|
-
if (doc.format === "odg") return doc.editor.pages()[containerIndex]?.shapes()[shapeIndex];
|
|
495
|
-
return doc.editor.slides()[containerIndex]?.shapes()[shapeIndex];
|
|
496
|
-
}
|
|
497
526
|
function sheetAt(doc, sheetIndex) {
|
|
498
527
|
return doc.editor.sheets()[sheetIndex];
|
|
499
528
|
}
|
|
@@ -526,8 +555,8 @@ function withSheet(state, sheetIndex, apply) {
|
|
|
526
555
|
apply(sheet);
|
|
527
556
|
});
|
|
528
557
|
}
|
|
529
|
-
function
|
|
530
|
-
const paragraph =
|
|
558
|
+
function setTextContainerText(container, text) {
|
|
559
|
+
const paragraph = container.paragraphs()[0] ?? container.appendParagraph();
|
|
531
560
|
const runs = paragraph.runs();
|
|
532
561
|
const firstRun = runs[0];
|
|
533
562
|
if (firstRun === void 0) {
|
|
@@ -537,6 +566,9 @@ function setCellText(cell, text) {
|
|
|
537
566
|
firstRun.text = text;
|
|
538
567
|
for (const extra of runs.slice(1)) extra.remove();
|
|
539
568
|
}
|
|
569
|
+
function setCellText(cell, text) {
|
|
570
|
+
setTextContainerText(cell, text);
|
|
571
|
+
}
|
|
540
572
|
function appReducer(state, action) {
|
|
541
573
|
switch (action.type) {
|
|
542
574
|
case "PUSH_SCREEN": return {
|
|
@@ -686,11 +718,21 @@ function appReducer(state, action) {
|
|
|
686
718
|
case "APPEND_TABLE": {
|
|
687
719
|
const doc = wordprocessingDocument(state);
|
|
688
720
|
if (doc === void 0) return wrongDocument(state, "a docx or odt document");
|
|
689
|
-
return
|
|
690
|
-
doc.editor.body.appendTable({
|
|
721
|
+
return mutateGuarded(state, doc, () => {
|
|
722
|
+
const table = doc.editor.body.appendTable({
|
|
691
723
|
rows: action.rows,
|
|
692
724
|
columns: action.columns
|
|
693
725
|
});
|
|
726
|
+
if (action.merge !== void 0) table.mergeCells(action.merge.startRow, action.merge.startColumn, action.merge.rowSpan, action.merge.colSpan);
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
case "MERGE_TABLE_CELLS": {
|
|
730
|
+
const doc = wordprocessingDocument(state);
|
|
731
|
+
if (doc === void 0) return wrongDocument(state, "a docx or odt document");
|
|
732
|
+
const table = tableAt(doc, action.tableIndex);
|
|
733
|
+
if (table === void 0) return withStatus(state, "warning", `There is no table at index ${action.tableIndex}`);
|
|
734
|
+
return mutateGuarded(state, doc, () => {
|
|
735
|
+
table.mergeCells(action.startRow, action.startColumn, action.rowSpan, action.colSpan);
|
|
694
736
|
});
|
|
695
737
|
}
|
|
696
738
|
case "SET_TABLE_CELL_TEXT": {
|
|
@@ -721,6 +763,18 @@ function appReducer(state, action) {
|
|
|
721
763
|
appended.list = membership;
|
|
722
764
|
});
|
|
723
765
|
}
|
|
766
|
+
case "SET_LIST_ITEM_TEXT": {
|
|
767
|
+
const doc = wordprocessingDocument(state);
|
|
768
|
+
if (doc === void 0) return wrongDocument(state, "a docx or odt document");
|
|
769
|
+
if (doc.format !== "odt") return wrongDocument(state, "an odt document (lists are an odt-only concept)");
|
|
770
|
+
const list = doc.editor.lists()[action.blockIndex];
|
|
771
|
+
if (list === void 0) return withStatus(state, "warning", `There is no list at index ${action.blockIndex}`);
|
|
772
|
+
const item = list.items()[action.itemIndex];
|
|
773
|
+
if (item === void 0) return withStatus(state, "warning", `List ${action.blockIndex} has no item at index ${action.itemIndex}`);
|
|
774
|
+
return mutate(state, doc, () => {
|
|
775
|
+
setTextContainerText(item, action.text);
|
|
776
|
+
});
|
|
777
|
+
}
|
|
724
778
|
case "ADD_SLIDE": {
|
|
725
779
|
const doc = presentationDocument(state);
|
|
726
780
|
if (doc === void 0) return wrongDocument(state, "a pptx or odp document");
|
|
@@ -743,6 +797,22 @@ function appReducer(state, action) {
|
|
|
743
797
|
});
|
|
744
798
|
});
|
|
745
799
|
}
|
|
800
|
+
case "MERGE_SLIDE_TABLE_CELLS": {
|
|
801
|
+
const doc = presentationDocument(state);
|
|
802
|
+
if (doc === void 0) return wrongDocument(state, "a pptx or odp document");
|
|
803
|
+
if (doc.format === "odp") {
|
|
804
|
+
const entry = doc.editor.slides()[action.slideIndex]?.tables()[action.tableIndex];
|
|
805
|
+
if (entry === void 0) return withStatus(state, "warning", `There is no table at index ${action.tableIndex} on slide ${action.slideIndex}`);
|
|
806
|
+
return mutateGuarded(state, doc, () => {
|
|
807
|
+
entry.table.mergeCells(action.startRow, action.startColumn, action.rowSpan, action.colSpan);
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
const table = doc.editor.slides()[action.slideIndex]?.tables()[action.tableIndex];
|
|
811
|
+
if (table === void 0) return withStatus(state, "warning", `There is no table at index ${action.tableIndex} on slide ${action.slideIndex}`);
|
|
812
|
+
return mutateGuarded(state, doc, () => {
|
|
813
|
+
mergePptxTableCells(table, action.startRow, action.startColumn, action.rowSpan, action.colSpan);
|
|
814
|
+
});
|
|
815
|
+
}
|
|
746
816
|
case "ADD_TEXTBOX": {
|
|
747
817
|
const doc = shapeHostDocument(state);
|
|
748
818
|
if (doc === void 0) return wrongDocument(state, "a pptx, odp or odg document");
|
|
@@ -793,16 +863,9 @@ function appReducer(state, action) {
|
|
|
793
863
|
case "SET_SHAPE_FRAME": return withShape(state, action.containerIndex, action.shapeIndex, (shape) => {
|
|
794
864
|
shape.frame = action.frame;
|
|
795
865
|
});
|
|
796
|
-
case "SET_SHAPE_ROTATION": {
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
if (doc.format === "pptx") return withStatus(state, "warning", "documents.js has no rotation setter for a pptx shape; rotate the shape in odp instead");
|
|
800
|
-
const shape = rotatableShapeAt(doc, action.containerIndex, action.shapeIndex);
|
|
801
|
-
if (shape === void 0) return withStatus(state, "warning", `There is no shape ${action.shapeIndex} at index ${action.containerIndex}`);
|
|
802
|
-
return mutate(state, doc, () => {
|
|
803
|
-
shape.rotationDeg = action.rotationDeg;
|
|
804
|
-
});
|
|
805
|
-
}
|
|
866
|
+
case "SET_SHAPE_ROTATION": return withShape(state, action.containerIndex, action.shapeIndex, (shape) => {
|
|
867
|
+
shape.rotationDeg = action.rotationDeg;
|
|
868
|
+
});
|
|
806
869
|
case "SET_SLIDE_NOTES": {
|
|
807
870
|
const doc = presentationDocument(state);
|
|
808
871
|
if (doc === void 0) return wrongDocument(state, "a pptx or odp document");
|
|
@@ -822,6 +885,15 @@ function appReducer(state, action) {
|
|
|
822
885
|
case "SET_CELL_VALUE": return withSheet(state, action.sheetIndex, (sheet) => {
|
|
823
886
|
sheet.cell(action.row, action.column).value = action.value;
|
|
824
887
|
});
|
|
888
|
+
case "MERGE_CELLS": {
|
|
889
|
+
const doc = spreadsheetDocument(state);
|
|
890
|
+
if (doc === void 0) return wrongDocument(state, "an ods document");
|
|
891
|
+
const sheet = sheetAt(doc, action.sheetIndex);
|
|
892
|
+
if (sheet === void 0) return withStatus(state, "warning", `There is no sheet at index ${action.sheetIndex}`);
|
|
893
|
+
return mutateGuarded(state, doc, () => {
|
|
894
|
+
sheet.mergeCells(action.startRow, action.startColumn, action.rowSpan, action.colSpan);
|
|
895
|
+
});
|
|
896
|
+
}
|
|
825
897
|
case "SET_SHEET_PRINT_SETTINGS": return withSheet(state, action.sheetIndex, (sheet) => {
|
|
826
898
|
sheet.printSettings = action.printSettings;
|
|
827
899
|
});
|
|
@@ -1020,6 +1092,11 @@ const COMMANDS = [
|
|
|
1020
1092
|
usage: ":close",
|
|
1021
1093
|
description: "Close the open document"
|
|
1022
1094
|
},
|
|
1095
|
+
{
|
|
1096
|
+
name: "undo",
|
|
1097
|
+
usage: ":undo",
|
|
1098
|
+
description: "Undo the last change"
|
|
1099
|
+
},
|
|
1023
1100
|
{
|
|
1024
1101
|
name: "help",
|
|
1025
1102
|
usage: ":help",
|
|
@@ -1152,6 +1229,9 @@ async function runCommand(line, state, dispatch) {
|
|
|
1152
1229
|
case "close":
|
|
1153
1230
|
dispatch({ type: "REQUEST_CLOSE" });
|
|
1154
1231
|
return;
|
|
1232
|
+
case "undo":
|
|
1233
|
+
dispatch({ type: "UNDO" });
|
|
1234
|
+
return;
|
|
1155
1235
|
case "help":
|
|
1156
1236
|
dispatch({
|
|
1157
1237
|
type: "OPEN_OVERLAY",
|
|
@@ -1430,6 +1510,10 @@ const GLOBAL_KEYS = [
|
|
|
1430
1510
|
keys: "Ctrl+W",
|
|
1431
1511
|
description: "Close the open document"
|
|
1432
1512
|
},
|
|
1513
|
+
{
|
|
1514
|
+
keys: "Ctrl+Z",
|
|
1515
|
+
description: "Undo the last change"
|
|
1516
|
+
},
|
|
1433
1517
|
{
|
|
1434
1518
|
keys: "q / Ctrl+C",
|
|
1435
1519
|
description: "Quit"
|
|
@@ -1590,6 +1674,14 @@ function truncatePreview(text, maxLength) {
|
|
|
1590
1674
|
if (singleLine.length === 0) return "(empty)";
|
|
1591
1675
|
return singleLine.length > maxLength ? `${singleLine.slice(0, Math.max(0, maxLength - 1))}…` : singleLine;
|
|
1592
1676
|
}
|
|
1677
|
+
function parsePositiveIntField(raw, fallback) {
|
|
1678
|
+
const parsed = Number.parseInt(raw, 10);
|
|
1679
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
|
|
1680
|
+
}
|
|
1681
|
+
function parseNonNegativeIntField(raw, fallback) {
|
|
1682
|
+
const parsed = Number.parseInt(raw, 10);
|
|
1683
|
+
return Number.isInteger(parsed) && parsed >= 0 ? parsed : fallback;
|
|
1684
|
+
}
|
|
1593
1685
|
//#endregion
|
|
1594
1686
|
//#region src/tui/screens/shared/paragraph-family.tsx
|
|
1595
1687
|
function paragraphFamilyDocument(openDocument) {
|
|
@@ -1653,6 +1745,8 @@ function tableSummary(table) {
|
|
|
1653
1745
|
function listSummary(list, index) {
|
|
1654
1746
|
return `List ${index} (${list.itemCount} item${list.itemCount === 1 ? "" : "s"})`;
|
|
1655
1747
|
}
|
|
1748
|
+
const DEFAULT_TABLE_ROWS$1 = 2;
|
|
1749
|
+
const DEFAULT_TABLE_COLUMNS$1 = 2;
|
|
1656
1750
|
function ParagraphFamilyBodyList(props) {
|
|
1657
1751
|
const { adapter } = props;
|
|
1658
1752
|
const state = useAppState();
|
|
@@ -1695,9 +1789,88 @@ function ParagraphFamilyBodyList(props) {
|
|
|
1695
1789
|
if (row.kind !== "header") acc.push(rowIndex);
|
|
1696
1790
|
return acc;
|
|
1697
1791
|
}, []);
|
|
1792
|
+
const [tableWizard, setTableWizard] = useState("closed");
|
|
1793
|
+
const [wizardDraft, setWizardDraft] = useState("");
|
|
1794
|
+
const [wizardRows, setWizardRows] = useState(DEFAULT_TABLE_ROWS$1);
|
|
1795
|
+
const [wizardColumns, setWizardColumns] = useState(DEFAULT_TABLE_COLUMNS$1);
|
|
1796
|
+
const [wizardStartRow, setWizardStartRow] = useState(0);
|
|
1797
|
+
const [wizardStartColumn, setWizardStartColumn] = useState(0);
|
|
1798
|
+
const [wizardRowSpan, setWizardRowSpan] = useState(1);
|
|
1799
|
+
const wizardOpen = tableWizard !== "closed";
|
|
1800
|
+
const closeWizard = () => {
|
|
1801
|
+
setTableWizard("closed");
|
|
1802
|
+
};
|
|
1803
|
+
const commitAppendTable = (merge) => {
|
|
1804
|
+
const newIndex = tables.length;
|
|
1805
|
+
dispatch({
|
|
1806
|
+
type: "APPEND_TABLE",
|
|
1807
|
+
rows: wizardRows,
|
|
1808
|
+
columns: wizardColumns,
|
|
1809
|
+
merge
|
|
1810
|
+
});
|
|
1811
|
+
closeWizard();
|
|
1812
|
+
dispatch({
|
|
1813
|
+
type: "PUSH_SCREEN",
|
|
1814
|
+
screen: {
|
|
1815
|
+
kind: "tableView",
|
|
1816
|
+
blockIndex: newIndex
|
|
1817
|
+
}
|
|
1818
|
+
});
|
|
1819
|
+
};
|
|
1820
|
+
useInput((input) => {
|
|
1821
|
+
if (input === "T") {
|
|
1822
|
+
setWizardDraft(String(DEFAULT_TABLE_ROWS$1));
|
|
1823
|
+
setTableWizard("rows");
|
|
1824
|
+
}
|
|
1825
|
+
}, { isActive: !anyOverlayOpen(state) && !wizardOpen });
|
|
1826
|
+
useInput((input, key) => {
|
|
1827
|
+
if (key.escape) {
|
|
1828
|
+
closeWizard();
|
|
1829
|
+
return;
|
|
1830
|
+
}
|
|
1831
|
+
if (input === "y" || input === "Y") {
|
|
1832
|
+
setWizardDraft("0");
|
|
1833
|
+
setTableWizard("mergeStartRow");
|
|
1834
|
+
return;
|
|
1835
|
+
}
|
|
1836
|
+
if (input === "n" || input === "N" || key.return) commitAppendTable(void 0);
|
|
1837
|
+
}, { isActive: !anyOverlayOpen(state) && tableWizard === "mergePrompt" });
|
|
1838
|
+
const submitWizardRows = (raw) => {
|
|
1839
|
+
setWizardRows(parsePositiveIntField(raw, DEFAULT_TABLE_ROWS$1));
|
|
1840
|
+
setWizardDraft(String(DEFAULT_TABLE_COLUMNS$1));
|
|
1841
|
+
setTableWizard("columns");
|
|
1842
|
+
};
|
|
1843
|
+
const submitWizardColumns = (raw) => {
|
|
1844
|
+
setWizardColumns(parsePositiveIntField(raw, DEFAULT_TABLE_COLUMNS$1));
|
|
1845
|
+
setTableWizard("mergePrompt");
|
|
1846
|
+
};
|
|
1847
|
+
const submitWizardMergeStartRow = (raw) => {
|
|
1848
|
+
setWizardStartRow(Math.min(parseNonNegativeIntField(raw, 0), Math.max(0, wizardRows - 1)));
|
|
1849
|
+
setWizardDraft("0");
|
|
1850
|
+
setTableWizard("mergeStartColumn");
|
|
1851
|
+
};
|
|
1852
|
+
const submitWizardMergeStartColumn = (raw) => {
|
|
1853
|
+
setWizardStartColumn(Math.min(parseNonNegativeIntField(raw, 0), Math.max(0, wizardColumns - 1)));
|
|
1854
|
+
setWizardDraft("1");
|
|
1855
|
+
setTableWizard("mergeRowSpan");
|
|
1856
|
+
};
|
|
1857
|
+
const submitWizardMergeRowSpan = (raw) => {
|
|
1858
|
+
setWizardRowSpan(Math.min(parsePositiveIntField(raw, 1), Math.max(1, wizardRows - wizardStartRow)));
|
|
1859
|
+
setWizardDraft("1");
|
|
1860
|
+
setTableWizard("mergeColSpan");
|
|
1861
|
+
};
|
|
1862
|
+
const submitWizardMergeColSpan = (raw) => {
|
|
1863
|
+
const colSpan = Math.min(parsePositiveIntField(raw, 1), Math.max(1, wizardColumns - wizardStartColumn));
|
|
1864
|
+
commitAppendTable({
|
|
1865
|
+
startRow: wizardStartRow,
|
|
1866
|
+
startColumn: wizardStartColumn,
|
|
1867
|
+
rowSpan: wizardRowSpan,
|
|
1868
|
+
colSpan
|
|
1869
|
+
});
|
|
1870
|
+
};
|
|
1698
1871
|
const { selectedIndex } = usePersistedSelection(selectionKeyFor({ kind: "bodyList" }), {
|
|
1699
1872
|
itemCount: selectableRowIndices.length,
|
|
1700
|
-
isActive: !anyOverlayOpen(state),
|
|
1873
|
+
isActive: !anyOverlayOpen(state) && !wizardOpen,
|
|
1701
1874
|
onBack: () => {
|
|
1702
1875
|
dispatch({ type: "POP_SCREEN" });
|
|
1703
1876
|
},
|
|
@@ -1784,9 +1957,89 @@ function ParagraphFamilyBodyList(props) {
|
|
|
1784
1957
|
});
|
|
1785
1958
|
}
|
|
1786
1959
|
}),
|
|
1960
|
+
tableWizard === "rows" ? /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, {
|
|
1961
|
+
color: "cyan",
|
|
1962
|
+
children: "Rows: "
|
|
1963
|
+
}), /* @__PURE__ */ jsx(TextField, {
|
|
1964
|
+
value: wizardDraft,
|
|
1965
|
+
isFocused: true,
|
|
1966
|
+
onChange: setWizardDraft,
|
|
1967
|
+
onSubmit: submitWizardRows,
|
|
1968
|
+
onCancel: closeWizard
|
|
1969
|
+
})] }) : void 0,
|
|
1970
|
+
tableWizard === "columns" ? /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, {
|
|
1971
|
+
color: "cyan",
|
|
1972
|
+
children: "Columns: "
|
|
1973
|
+
}), /* @__PURE__ */ jsx(TextField, {
|
|
1974
|
+
value: wizardDraft,
|
|
1975
|
+
isFocused: true,
|
|
1976
|
+
onChange: setWizardDraft,
|
|
1977
|
+
onSubmit: submitWizardColumns,
|
|
1978
|
+
onCancel: closeWizard
|
|
1979
|
+
})] }) : void 0,
|
|
1980
|
+
tableWizard === "mergePrompt" ? /* @__PURE__ */ jsx(Text, {
|
|
1981
|
+
color: "cyan",
|
|
1982
|
+
children: "Merge cells now? y/N"
|
|
1983
|
+
}) : void 0,
|
|
1984
|
+
tableWizard === "mergeStartRow" ? /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsxs(Text, {
|
|
1985
|
+
color: "cyan",
|
|
1986
|
+
children: [
|
|
1987
|
+
"Merge start row (0-",
|
|
1988
|
+
Math.max(0, wizardRows - 1),
|
|
1989
|
+
"): "
|
|
1990
|
+
]
|
|
1991
|
+
}), /* @__PURE__ */ jsx(TextField, {
|
|
1992
|
+
value: wizardDraft,
|
|
1993
|
+
isFocused: true,
|
|
1994
|
+
onChange: setWizardDraft,
|
|
1995
|
+
onSubmit: submitWizardMergeStartRow,
|
|
1996
|
+
onCancel: closeWizard
|
|
1997
|
+
})] }) : void 0,
|
|
1998
|
+
tableWizard === "mergeStartColumn" ? /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsxs(Text, {
|
|
1999
|
+
color: "cyan",
|
|
2000
|
+
children: [
|
|
2001
|
+
"Merge start column (0-",
|
|
2002
|
+
Math.max(0, wizardColumns - 1),
|
|
2003
|
+
"): "
|
|
2004
|
+
]
|
|
2005
|
+
}), /* @__PURE__ */ jsx(TextField, {
|
|
2006
|
+
value: wizardDraft,
|
|
2007
|
+
isFocused: true,
|
|
2008
|
+
onChange: setWizardDraft,
|
|
2009
|
+
onSubmit: submitWizardMergeStartColumn,
|
|
2010
|
+
onCancel: closeWizard
|
|
2011
|
+
})] }) : void 0,
|
|
2012
|
+
tableWizard === "mergeRowSpan" ? /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsxs(Text, {
|
|
2013
|
+
color: "cyan",
|
|
2014
|
+
children: [
|
|
2015
|
+
"Merge row span (1-",
|
|
2016
|
+
Math.max(1, wizardRows - wizardStartRow),
|
|
2017
|
+
"): "
|
|
2018
|
+
]
|
|
2019
|
+
}), /* @__PURE__ */ jsx(TextField, {
|
|
2020
|
+
value: wizardDraft,
|
|
2021
|
+
isFocused: true,
|
|
2022
|
+
onChange: setWizardDraft,
|
|
2023
|
+
onSubmit: submitWizardMergeRowSpan,
|
|
2024
|
+
onCancel: closeWizard
|
|
2025
|
+
})] }) : void 0,
|
|
2026
|
+
tableWizard === "mergeColSpan" ? /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsxs(Text, {
|
|
2027
|
+
color: "cyan",
|
|
2028
|
+
children: [
|
|
2029
|
+
"Merge column span (1-",
|
|
2030
|
+
Math.max(1, wizardColumns - wizardStartColumn),
|
|
2031
|
+
"): "
|
|
2032
|
+
]
|
|
2033
|
+
}), /* @__PURE__ */ jsx(TextField, {
|
|
2034
|
+
value: wizardDraft,
|
|
2035
|
+
isFocused: true,
|
|
2036
|
+
onChange: setWizardDraft,
|
|
2037
|
+
onSubmit: submitWizardMergeColSpan,
|
|
2038
|
+
onCancel: closeWizard
|
|
2039
|
+
})] }) : void 0,
|
|
1787
2040
|
/* @__PURE__ */ jsx(Text, {
|
|
1788
2041
|
dimColor: true,
|
|
1789
|
-
children: "Enter to open, a to append a paragraph, Esc back"
|
|
2042
|
+
children: "Enter to open, a to append a paragraph, T to append a table, Esc back"
|
|
1790
2043
|
})
|
|
1791
2044
|
]
|
|
1792
2045
|
});
|
|
@@ -2063,7 +2316,7 @@ function RunEditorScreen() {
|
|
|
2063
2316
|
}
|
|
2064
2317
|
//#endregion
|
|
2065
2318
|
//#region src/tui/screens/editors/docx/table-view.tsx
|
|
2066
|
-
const CELL_WIDTH$
|
|
2319
|
+
const CELL_WIDTH$2 = 16;
|
|
2067
2320
|
function TableViewScreen() {
|
|
2068
2321
|
const state = useAppState();
|
|
2069
2322
|
const dispatch = useAppDispatch();
|
|
@@ -2071,6 +2324,7 @@ function TableViewScreen() {
|
|
|
2071
2324
|
row: 0,
|
|
2072
2325
|
column: 0
|
|
2073
2326
|
});
|
|
2327
|
+
const [mergeAnchor, setMergeAnchor] = useState(void 0);
|
|
2074
2328
|
const screen = currentScreen(state);
|
|
2075
2329
|
const doc = paragraphFamilyDocument(state.openDocument);
|
|
2076
2330
|
const table = screen.kind === "tableView" && doc !== void 0 ? liveTableAt(doc, screen.blockIndex) : void 0;
|
|
@@ -2079,6 +2333,22 @@ function TableViewScreen() {
|
|
|
2079
2333
|
const columnCount = rows[0]?.cells().length ?? 0;
|
|
2080
2334
|
const clampedRow = rowCount === 0 ? 0 : Math.min(cursor.row, rowCount - 1);
|
|
2081
2335
|
const clampedColumn = columnCount === 0 ? 0 : Math.min(cursor.column, columnCount - 1);
|
|
2336
|
+
const commitMerge = (anchor) => {
|
|
2337
|
+
if (screen.kind !== "tableView") return;
|
|
2338
|
+
const startRow = Math.min(anchor.row, clampedRow);
|
|
2339
|
+
const startColumn = Math.min(anchor.column, clampedColumn);
|
|
2340
|
+
const rowSpan = Math.abs(clampedRow - anchor.row) + 1;
|
|
2341
|
+
const colSpan = Math.abs(clampedColumn - anchor.column) + 1;
|
|
2342
|
+
dispatch({
|
|
2343
|
+
type: "MERGE_TABLE_CELLS",
|
|
2344
|
+
tableIndex: screen.blockIndex,
|
|
2345
|
+
startRow,
|
|
2346
|
+
startColumn,
|
|
2347
|
+
rowSpan,
|
|
2348
|
+
colSpan
|
|
2349
|
+
});
|
|
2350
|
+
setMergeAnchor(void 0);
|
|
2351
|
+
};
|
|
2082
2352
|
useInput((input, key) => {
|
|
2083
2353
|
if (table === void 0 || screen.kind !== "tableView") return;
|
|
2084
2354
|
if (key.upArrow || input === "k") {
|
|
@@ -2110,18 +2380,36 @@ function TableViewScreen() {
|
|
|
2110
2380
|
return;
|
|
2111
2381
|
}
|
|
2112
2382
|
if (key.escape) {
|
|
2383
|
+
if (mergeAnchor !== void 0) {
|
|
2384
|
+
setMergeAnchor(void 0);
|
|
2385
|
+
return;
|
|
2386
|
+
}
|
|
2113
2387
|
dispatch({ type: "POP_SCREEN" });
|
|
2114
2388
|
return;
|
|
2115
2389
|
}
|
|
2116
|
-
if (
|
|
2117
|
-
|
|
2118
|
-
screen: {
|
|
2119
|
-
kind: "tableCellDetail",
|
|
2120
|
-
blockIndex: screen.blockIndex,
|
|
2390
|
+
if (input === "m" && rowCount > 0 && columnCount > 0) {
|
|
2391
|
+
if (mergeAnchor === void 0) setMergeAnchor({
|
|
2121
2392
|
row: clampedRow,
|
|
2122
|
-
|
|
2393
|
+
column: clampedColumn
|
|
2394
|
+
});
|
|
2395
|
+
else commitMerge(mergeAnchor);
|
|
2396
|
+
return;
|
|
2397
|
+
}
|
|
2398
|
+
if (key.return && rowCount > 0 && columnCount > 0) {
|
|
2399
|
+
if (mergeAnchor !== void 0) {
|
|
2400
|
+
commitMerge(mergeAnchor);
|
|
2401
|
+
return;
|
|
2123
2402
|
}
|
|
2124
|
-
|
|
2403
|
+
dispatch({
|
|
2404
|
+
type: "PUSH_SCREEN",
|
|
2405
|
+
screen: {
|
|
2406
|
+
kind: "tableCellDetail",
|
|
2407
|
+
blockIndex: screen.blockIndex,
|
|
2408
|
+
row: clampedRow,
|
|
2409
|
+
col: clampedColumn
|
|
2410
|
+
}
|
|
2411
|
+
});
|
|
2412
|
+
}
|
|
2125
2413
|
}, { isActive: !anyOverlayOpen(state) });
|
|
2126
2414
|
if (screen.kind !== "tableView") return /* @__PURE__ */ jsx(Text, {
|
|
2127
2415
|
color: "red",
|
|
@@ -2159,10 +2447,11 @@ function TableViewScreen() {
|
|
|
2159
2447
|
children: "This table has no rows."
|
|
2160
2448
|
}) : rows.map((row, rowIndex) => /* @__PURE__ */ jsx(Box, { children: row.cells().map((cell, columnIndex) => {
|
|
2161
2449
|
const isSelected = rowIndex === clampedRow && columnIndex === clampedColumn;
|
|
2450
|
+
const isAnchor = rowIndex === mergeAnchor?.row && columnIndex === mergeAnchor?.column;
|
|
2162
2451
|
return /* @__PURE__ */ jsx(Box, {
|
|
2163
|
-
width: CELL_WIDTH$
|
|
2452
|
+
width: CELL_WIDTH$2,
|
|
2164
2453
|
borderStyle: "single",
|
|
2165
|
-
borderColor: isSelected ? "cyan" : "gray",
|
|
2454
|
+
borderColor: isSelected ? "cyan" : isAnchor ? "yellow" : "gray",
|
|
2166
2455
|
children: /* @__PURE__ */ jsx(Text, {
|
|
2167
2456
|
color: isSelected ? "cyan" : void 0,
|
|
2168
2457
|
inverse: isSelected,
|
|
@@ -2172,7 +2461,7 @@ function TableViewScreen() {
|
|
|
2172
2461
|
}) }, rowIndex)),
|
|
2173
2462
|
/* @__PURE__ */ jsx(Text, {
|
|
2174
2463
|
dimColor: true,
|
|
2175
|
-
children: "Arrows/hjkl move, Enter to edit a cell, Esc back"
|
|
2464
|
+
children: mergeAnchor === void 0 ? "Arrows/hjkl move, Enter to edit a cell, m to anchor a merge, Esc back" : "Arrows/hjkl to the opposite corner, m/Enter to merge, Esc to cancel"
|
|
2176
2465
|
})
|
|
2177
2466
|
]
|
|
2178
2467
|
});
|
|
@@ -3019,6 +3308,10 @@ function requireShapeOrVectorDetailScreen(state) {
|
|
|
3019
3308
|
if (screen.kind !== "shapeOrVectorDetail") throw new Error(`OdgShapeOrVectorDetailScreen rendered while the top of the stack is '${screen.kind}', not 'shapeOrVectorDetail' -- app.tsx's ScreenBody switch only mounts this component for that screen kind, so this cannot happen without a routing bug.`);
|
|
3020
3309
|
return screen;
|
|
3021
3310
|
}
|
|
3311
|
+
function vectorsParityMatch(liveVectors, contentVectors) {
|
|
3312
|
+
if (liveVectors.length !== contentVectors.length) return false;
|
|
3313
|
+
return liveVectors.every((live, index) => live.kind === contentVectors[index]?.kind);
|
|
3314
|
+
}
|
|
3022
3315
|
function buildPageItems(doc, pageIndex) {
|
|
3023
3316
|
const page = doc.editor.pages()[pageIndex];
|
|
3024
3317
|
if (page === void 0) return [];
|
|
@@ -3026,9 +3319,12 @@ function buildPageItems(doc, pageIndex) {
|
|
|
3026
3319
|
if (content.kind !== "drawing") throw new Error(`readOdgContent(doc.editor.toPackage()) returned a '${content.kind}' ContentDocument for an odg-format open document -- an odg package should always read back as the 'drawing' variant, so this indicates a real inconsistency in documents.js's own reader, not a state this screen should paper over.`);
|
|
3027
3320
|
const contentPage = content.pages[pageIndex];
|
|
3028
3321
|
if (contentPage === void 0) throw new Error(`readOdgContent found no page at index ${pageIndex}, but doc.editor.pages() has a page there -- the two read the same live package, so they should always agree on page count.`);
|
|
3029
|
-
const
|
|
3322
|
+
const liveVectors = page.vectors();
|
|
3323
|
+
const parityOk = vectorsParityMatch(liveVectors, contentPage.vectors);
|
|
3324
|
+
const vectorItems = contentPage.vectors.map((vector, index) => ({
|
|
3030
3325
|
kind: "vector",
|
|
3031
|
-
vector
|
|
3326
|
+
vector,
|
|
3327
|
+
liveVector: parityOk ? liveVectors[index] : void 0
|
|
3032
3328
|
}));
|
|
3033
3329
|
const shapeItems = page.shapes().map((shape, index) => {
|
|
3034
3330
|
return {
|
|
@@ -3320,15 +3616,19 @@ function warnVectorIsViewOnly(dispatch, label) {
|
|
|
3320
3616
|
dispatch({
|
|
3321
3617
|
type: "SET_STATUS",
|
|
3322
3618
|
severity: "info",
|
|
3323
|
-
text: `${label} added --
|
|
3619
|
+
text: `${label} added -- but this page also has a vector element documents.js's OdgPage.vectors() cannot wrap, so every vector on it (including this one) shows read-only in this list rather than risk pairing the wrong live handle to the wrong row.`
|
|
3324
3620
|
});
|
|
3325
3621
|
}
|
|
3622
|
+
function warnIfVectorAddedReadOnly(doc, pageIndex, dispatch, label) {
|
|
3623
|
+
const added = buildPageItems(doc, pageIndex).filter((item) => item.kind === "vector").at(-1);
|
|
3624
|
+
if (added !== void 0 && added.liveVector === void 0) warnVectorIsViewOnly(dispatch, label);
|
|
3625
|
+
}
|
|
3326
3626
|
function inferImageFormat(path) {
|
|
3327
3627
|
const extension = path.slice(path.lastIndexOf(".") + 1).toLowerCase();
|
|
3328
3628
|
if (extension === "png") return "png";
|
|
3329
3629
|
if (extension === "jpg" || extension === "jpeg") return "jpeg";
|
|
3330
3630
|
}
|
|
3331
|
-
async function applyAddKind(kind, pageIndex, values, dispatch) {
|
|
3631
|
+
async function applyAddKind(kind, pageIndex, doc, values, dispatch) {
|
|
3332
3632
|
switch (kind) {
|
|
3333
3633
|
case "rect":
|
|
3334
3634
|
dispatch({
|
|
@@ -3340,7 +3640,7 @@ async function applyAddKind(kind, pageIndex, values, dispatch) {
|
|
|
3340
3640
|
stroke: parseStrokeField(requireFieldValue(values, "stroke"))
|
|
3341
3641
|
}
|
|
3342
3642
|
});
|
|
3343
|
-
|
|
3643
|
+
warnIfVectorAddedReadOnly(doc, pageIndex, dispatch, "Rectangle");
|
|
3344
3644
|
return;
|
|
3345
3645
|
case "ellipse":
|
|
3346
3646
|
dispatch({
|
|
@@ -3352,7 +3652,7 @@ async function applyAddKind(kind, pageIndex, values, dispatch) {
|
|
|
3352
3652
|
stroke: parseStrokeField(requireFieldValue(values, "stroke"))
|
|
3353
3653
|
}
|
|
3354
3654
|
});
|
|
3355
|
-
|
|
3655
|
+
warnIfVectorAddedReadOnly(doc, pageIndex, dispatch, "Ellipse");
|
|
3356
3656
|
return;
|
|
3357
3657
|
case "line":
|
|
3358
3658
|
dispatch({
|
|
@@ -3377,7 +3677,7 @@ async function applyAddKind(kind, pageIndex, values, dispatch) {
|
|
|
3377
3677
|
}
|
|
3378
3678
|
}
|
|
3379
3679
|
});
|
|
3380
|
-
|
|
3680
|
+
warnIfVectorAddedReadOnly(doc, pageIndex, dispatch, "Line");
|
|
3381
3681
|
return;
|
|
3382
3682
|
case "path": {
|
|
3383
3683
|
const frame = readFrame(values);
|
|
@@ -3391,7 +3691,7 @@ async function applyAddKind(kind, pageIndex, values, dispatch) {
|
|
|
3391
3691
|
stroke: parseStrokeField(requireFieldValue(values, "stroke"))
|
|
3392
3692
|
}
|
|
3393
3693
|
});
|
|
3394
|
-
|
|
3694
|
+
warnIfVectorAddedReadOnly(doc, pageIndex, dispatch, "Path");
|
|
3395
3695
|
return;
|
|
3396
3696
|
}
|
|
3397
3697
|
case "textbox":
|
|
@@ -3520,7 +3820,7 @@ function AddItemFlow(props) {
|
|
|
3520
3820
|
fields: fieldsForAddKind(kind),
|
|
3521
3821
|
onCancel: props.onCancel,
|
|
3522
3822
|
onComplete: (values) => {
|
|
3523
|
-
applyAddKind(kind, props.pageIndex, values, dispatch).then(props.onCreated);
|
|
3823
|
+
applyAddKind(kind, props.pageIndex, props.doc, values, dispatch).then(props.onCreated);
|
|
3524
3824
|
}
|
|
3525
3825
|
});
|
|
3526
3826
|
}
|
|
@@ -3574,6 +3874,7 @@ function OdgPageDetailScreen() {
|
|
|
3574
3874
|
});
|
|
3575
3875
|
if (isAdding) return /* @__PURE__ */ jsx(AddItemFlow, {
|
|
3576
3876
|
pageIndex,
|
|
3877
|
+
doc,
|
|
3577
3878
|
isActive: !overlayOpen,
|
|
3578
3879
|
onCancel: () => {
|
|
3579
3880
|
setIsAdding(false);
|
|
@@ -3607,19 +3908,75 @@ function OdgPageDetailScreen() {
|
|
|
3607
3908
|
}
|
|
3608
3909
|
//#endregion
|
|
3609
3910
|
//#region src/tui/screens/editors/odg/shape-or-vector-detail.tsx
|
|
3911
|
+
function buildVectorRows(vector, liveVector, dispatch) {
|
|
3912
|
+
const rows = [];
|
|
3913
|
+
if (vector.kind !== "line" && liveVector.kind !== "line") {
|
|
3914
|
+
const fillTarget = liveVector;
|
|
3915
|
+
rows.push({
|
|
3916
|
+
label: `Fill: ${vector.fill === void 0 ? "none" : formatColor$1(vector.fill)}`,
|
|
3917
|
+
currentValue: vector.fill === void 0 ? "" : `${vector.fill.r} ${vector.fill.g} ${vector.fill.b}`,
|
|
3918
|
+
commit: (raw) => {
|
|
3919
|
+
dispatch({
|
|
3920
|
+
type: "SET_VECTOR_FILL",
|
|
3921
|
+
vector: fillTarget,
|
|
3922
|
+
fill: parseColorField(raw)
|
|
3923
|
+
});
|
|
3924
|
+
}
|
|
3925
|
+
});
|
|
3926
|
+
}
|
|
3927
|
+
rows.push({
|
|
3928
|
+
label: `Stroke: ${vector.stroke === void 0 ? "none" : `${formatColor$1(vector.stroke.color)} ${formatPt(vector.stroke.widthPt)}pt`}`,
|
|
3929
|
+
currentValue: vector.stroke === void 0 ? "" : `${vector.stroke.color.r} ${vector.stroke.color.g} ${vector.stroke.color.b} ${vector.stroke.widthPt}`,
|
|
3930
|
+
commit: (raw) => {
|
|
3931
|
+
const stroke = parseStrokeField(raw);
|
|
3932
|
+
if (stroke === void 0) {
|
|
3933
|
+
dispatch({
|
|
3934
|
+
type: "SET_STATUS",
|
|
3935
|
+
severity: "warning",
|
|
3936
|
+
text: "A vector stroke cannot be cleared to none through this editor -- enter \"r g b widthPt\" (0-1 colour, pt width) instead"
|
|
3937
|
+
});
|
|
3938
|
+
return;
|
|
3939
|
+
}
|
|
3940
|
+
dispatch({
|
|
3941
|
+
type: "SET_VECTOR_STROKE",
|
|
3942
|
+
vector: liveVector,
|
|
3943
|
+
stroke
|
|
3944
|
+
});
|
|
3945
|
+
}
|
|
3946
|
+
});
|
|
3947
|
+
return rows;
|
|
3948
|
+
}
|
|
3610
3949
|
function VectorDetail(props) {
|
|
3611
|
-
|
|
3950
|
+
const dispatch = useAppDispatch();
|
|
3951
|
+
const [editingField, setEditingField] = useState(void 0);
|
|
3952
|
+
const [draft, setDraft] = useState("");
|
|
3953
|
+
const { vector, liveVector, isActive } = props;
|
|
3954
|
+
const rows = liveVector === void 0 ? [] : buildVectorRows(vector, liveVector, dispatch);
|
|
3955
|
+
const { selectedIndex } = useNavigationInput({
|
|
3956
|
+
itemCount: rows.length,
|
|
3957
|
+
isActive: isActive && editingField === void 0,
|
|
3958
|
+
onBack: () => {
|
|
3959
|
+
dispatch({ type: "POP_SCREEN" });
|
|
3960
|
+
},
|
|
3961
|
+
onSelect: (index) => {
|
|
3962
|
+
const row = rows[index];
|
|
3963
|
+
if (row === void 0) return;
|
|
3964
|
+
setDraft(row.currentValue);
|
|
3965
|
+
setEditingField(index);
|
|
3966
|
+
}
|
|
3967
|
+
});
|
|
3968
|
+
if (liveVector === void 0) return /* @__PURE__ */ jsxs(Box, {
|
|
3612
3969
|
flexDirection: "column",
|
|
3613
3970
|
children: [
|
|
3614
3971
|
/* @__PURE__ */ jsxs(Text, {
|
|
3615
3972
|
bold: true,
|
|
3616
|
-
children: [vectorKindLabel(
|
|
3973
|
+
children: [vectorKindLabel(vector.kind), " (view-only)"]
|
|
3617
3974
|
}),
|
|
3618
|
-
/* @__PURE__ */ jsxs(Text, { children: ["Geometry: ", describeVectorGeometry(
|
|
3619
|
-
/* @__PURE__ */ jsx(Text, { children: describeFillStroke(
|
|
3975
|
+
/* @__PURE__ */ jsxs(Text, { children: ["Geometry: ", describeVectorGeometry(vector)] }),
|
|
3976
|
+
/* @__PURE__ */ jsx(Text, { children: describeFillStroke(vector) }),
|
|
3620
3977
|
/* @__PURE__ */ jsx(Text, {
|
|
3621
3978
|
color: "yellow",
|
|
3622
|
-
children: "documents.js's OdgPage has no
|
|
3979
|
+
children: "This page's live vectors (documents.js's `OdgPage.vectors()`) don't line up one-to-one with what odf.js's own reader found here -- likely a `draw:circle`/`polygon`/`polyline`/`custom-shape` element the live accessor has no wrapper for, sitting alongside a plain rect/ellipse/line/path it does. Rather than risk pairing the wrong live handle to this row, every vector on this page is shown read-only until that mismatch is resolved outside this TUI. Open the file in a real ODF editor to change it."
|
|
3623
3980
|
}),
|
|
3624
3981
|
/* @__PURE__ */ jsx(Text, {
|
|
3625
3982
|
dimColor: true,
|
|
@@ -3627,6 +3984,53 @@ function VectorDetail(props) {
|
|
|
3627
3984
|
})
|
|
3628
3985
|
]
|
|
3629
3986
|
});
|
|
3987
|
+
if (editingField !== void 0) {
|
|
3988
|
+
const row = rows[editingField];
|
|
3989
|
+
if (row === void 0) throw new Error(`VectorDetail is editing field index ${editingField}, but there are only ${rows.length} rows -- selecting a row always sets editingField to a valid index from that same rows array, so this indicates a bug in that selection.`);
|
|
3990
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
3991
|
+
flexDirection: "column",
|
|
3992
|
+
borderStyle: "round",
|
|
3993
|
+
paddingX: 1,
|
|
3994
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
3995
|
+
bold: true,
|
|
3996
|
+
children: row.label
|
|
3997
|
+
}), /* @__PURE__ */ jsx(TextField, {
|
|
3998
|
+
value: draft,
|
|
3999
|
+
isFocused: true,
|
|
4000
|
+
onChange: setDraft,
|
|
4001
|
+
onCancel: () => {
|
|
4002
|
+
setEditingField(void 0);
|
|
4003
|
+
},
|
|
4004
|
+
onSubmit: (value) => {
|
|
4005
|
+
row.commit(value);
|
|
4006
|
+
setEditingField(void 0);
|
|
4007
|
+
}
|
|
4008
|
+
})]
|
|
4009
|
+
});
|
|
4010
|
+
}
|
|
4011
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
4012
|
+
flexDirection: "column",
|
|
4013
|
+
children: [
|
|
4014
|
+
/* @__PURE__ */ jsx(Text, {
|
|
4015
|
+
bold: true,
|
|
4016
|
+
children: vectorKindLabel(vector.kind)
|
|
4017
|
+
}),
|
|
4018
|
+
/* @__PURE__ */ jsxs(Text, { children: ["Geometry: ", describeVectorGeometry(vector)] }),
|
|
4019
|
+
/* @__PURE__ */ jsx(ListView, {
|
|
4020
|
+
items: rows,
|
|
4021
|
+
selectedIndex,
|
|
4022
|
+
reservedRows: 5,
|
|
4023
|
+
renderItem: (row, isSelected) => /* @__PURE__ */ jsxs(Text, {
|
|
4024
|
+
color: isSelected ? "cyan" : void 0,
|
|
4025
|
+
children: [isSelected ? "> " : " ", row.label]
|
|
4026
|
+
})
|
|
4027
|
+
}),
|
|
4028
|
+
/* @__PURE__ */ jsx(Text, {
|
|
4029
|
+
dimColor: true,
|
|
4030
|
+
children: "Enter to edit a field, Esc to go back"
|
|
4031
|
+
})
|
|
4032
|
+
]
|
|
4033
|
+
});
|
|
3630
4034
|
}
|
|
3631
4035
|
function ShapeDetail(props) {
|
|
3632
4036
|
const dispatch = useAppDispatch();
|
|
@@ -3796,7 +4200,7 @@ function OdgShapeOrVectorDetailScreen() {
|
|
|
3796
4200
|
const item = items[itemIndex];
|
|
3797
4201
|
useInput((input, key) => {
|
|
3798
4202
|
if (key.escape || key.leftArrow || input === "h") dispatch({ type: "POP_SCREEN" });
|
|
3799
|
-
}, { isActive: !overlayOpen && item
|
|
4203
|
+
}, { isActive: !overlayOpen && item === void 0 });
|
|
3800
4204
|
if (item === void 0) return /* @__PURE__ */ jsxs(Box, {
|
|
3801
4205
|
flexDirection: "column",
|
|
3802
4206
|
children: [/* @__PURE__ */ jsxs(Text, {
|
|
@@ -3813,7 +4217,11 @@ function OdgShapeOrVectorDetailScreen() {
|
|
|
3813
4217
|
children: "Esc to go back"
|
|
3814
4218
|
})]
|
|
3815
4219
|
});
|
|
3816
|
-
if (item.kind === "vector") return /* @__PURE__ */ jsx(VectorDetail, {
|
|
4220
|
+
if (item.kind === "vector") return /* @__PURE__ */ jsx(VectorDetail, {
|
|
4221
|
+
vector: item.vector,
|
|
4222
|
+
liveVector: item.liveVector,
|
|
4223
|
+
isActive: !overlayOpen
|
|
4224
|
+
});
|
|
3817
4225
|
const vectorCount = items.filter((entry) => entry.kind === "vector").length;
|
|
3818
4226
|
return /* @__PURE__ */ jsx(ShapeDetail, {
|
|
3819
4227
|
pageIndex,
|
|
@@ -3955,10 +4363,6 @@ function formatRotationDeg(value) {
|
|
|
3955
4363
|
return value === void 0 ? "(unset)" : `${Math.round(value * ROTATION_DISPLAY_PRECISION) / ROTATION_DISPLAY_PRECISION}°`;
|
|
3956
4364
|
}
|
|
3957
4365
|
function RotationField(props) {
|
|
3958
|
-
if (!props.isEditable) return /* @__PURE__ */ jsx(Text, {
|
|
3959
|
-
dimColor: true,
|
|
3960
|
-
children: "[R] Rotation: not available for pptx shapes"
|
|
3961
|
-
});
|
|
3962
4366
|
if (props.isEditing) return /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, {
|
|
3963
4367
|
color: "cyan",
|
|
3964
4368
|
children: "[R] Rotation (deg, blank to unset): "
|
|
@@ -3992,7 +4396,6 @@ const FIELD_LABELS = {
|
|
|
3992
4396
|
width: "Width (pt)",
|
|
3993
4397
|
height: "Height (pt)"
|
|
3994
4398
|
};
|
|
3995
|
-
const ROTATION_UNAVAILABLE_MESSAGE = "documents.js has no rotation setter for a pptx shape; rotate the shape in odp instead";
|
|
3996
4399
|
const POINT_DISPLAY_PRECISION = 100;
|
|
3997
4400
|
function formatPoints(value) {
|
|
3998
4401
|
return value === void 0 ? "(unset)" : `${Math.round(value * POINT_DISPLAY_PRECISION) / POINT_DISPLAY_PRECISION}pt`;
|
|
@@ -4012,9 +4415,9 @@ function describeFieldValue(key, shape) {
|
|
|
4012
4415
|
function roundForDisplay(value) {
|
|
4013
4416
|
return Math.round(value * POINT_DISPLAY_PRECISION) / POINT_DISPLAY_PRECISION;
|
|
4014
4417
|
}
|
|
4015
|
-
function initialDraftFor(key, shape
|
|
4418
|
+
function initialDraftFor(key, shape) {
|
|
4016
4419
|
if (key === "rotation") {
|
|
4017
|
-
const value =
|
|
4420
|
+
const value = shape.rotationDeg;
|
|
4018
4421
|
return value === void 0 ? "" : String(roundForDisplay(value));
|
|
4019
4422
|
}
|
|
4020
4423
|
if (key === "text") return shape.text;
|
|
@@ -4027,10 +4430,9 @@ function initialDraftFor(key, shape, rotatableShape) {
|
|
|
4027
4430
|
}
|
|
4028
4431
|
}
|
|
4029
4432
|
function FieldRow(props) {
|
|
4030
|
-
const { fieldKey, shape,
|
|
4433
|
+
const { fieldKey, shape, isSelected, isEditing, draft, onDraftChange, onSubmit, onCancel } = props;
|
|
4031
4434
|
if (fieldKey === "rotation") return /* @__PURE__ */ jsx(RotationField, {
|
|
4032
|
-
rotationDeg:
|
|
4033
|
-
isEditable: isOdp,
|
|
4435
|
+
rotationDeg: shape.rotationDeg,
|
|
4034
4436
|
isSelected,
|
|
4035
4437
|
isEditing,
|
|
4036
4438
|
draftValue: draft,
|
|
@@ -4079,9 +4481,7 @@ function ShapeEditorScreen(props) {
|
|
|
4079
4481
|
const overlayOpen = anyOverlayOpen(state);
|
|
4080
4482
|
const doc = assertPresentationDocument(state.openDocument);
|
|
4081
4483
|
const { slideIndex, shapeIndex } = props.screen;
|
|
4082
|
-
const isOdp = doc.format === "odp";
|
|
4083
4484
|
const shape = doc.editor.slides()[slideIndex]?.shapes()[shapeIndex];
|
|
4084
|
-
const rotatableShape = doc.format === "odp" ? doc.editor.slides()[slideIndex]?.shapes()[shapeIndex] : void 0;
|
|
4085
4485
|
const [editingField, setEditingField] = useState(void 0);
|
|
4086
4486
|
const [draft, setDraft] = useState("");
|
|
4087
4487
|
const { selectedIndex } = useNavigationInput({
|
|
@@ -4094,20 +4494,12 @@ function ShapeEditorScreen(props) {
|
|
|
4094
4494
|
if (shape === void 0) return;
|
|
4095
4495
|
const key = FIELD_KEYS[index];
|
|
4096
4496
|
if (key === void 0) return;
|
|
4097
|
-
if (key === "rotation" && !isOdp) {
|
|
4098
|
-
dispatch({
|
|
4099
|
-
type: "SET_STATUS",
|
|
4100
|
-
severity: "warning",
|
|
4101
|
-
text: ROTATION_UNAVAILABLE_MESSAGE
|
|
4102
|
-
});
|
|
4103
|
-
return;
|
|
4104
|
-
}
|
|
4105
4497
|
dispatch({
|
|
4106
4498
|
type: "SET_SELECTION",
|
|
4107
4499
|
key: selectionKeyFor(props.screen),
|
|
4108
4500
|
index
|
|
4109
4501
|
});
|
|
4110
|
-
setDraft(initialDraftFor(key, shape
|
|
4502
|
+
setDraft(initialDraftFor(key, shape));
|
|
4111
4503
|
setEditingField(key);
|
|
4112
4504
|
}
|
|
4113
4505
|
});
|
|
@@ -4232,8 +4624,6 @@ function ShapeEditorScreen(props) {
|
|
|
4232
4624
|
renderItem: (key, isSelected) => /* @__PURE__ */ jsx(FieldRow, {
|
|
4233
4625
|
fieldKey: key,
|
|
4234
4626
|
shape,
|
|
4235
|
-
rotatableShape,
|
|
4236
|
-
isOdp,
|
|
4237
4627
|
isSelected,
|
|
4238
4628
|
isEditing: editingField === key,
|
|
4239
4629
|
draft,
|
|
@@ -4250,6 +4640,30 @@ function ShapeEditorScreen(props) {
|
|
|
4250
4640
|
});
|
|
4251
4641
|
}
|
|
4252
4642
|
//#endregion
|
|
4643
|
+
//#region src/tui/screens/shared/slide-table.ts
|
|
4644
|
+
function resolveSlideTable(doc, slideIndex, tableIndex) {
|
|
4645
|
+
const content = doc.format === "odp" ? readOdpContent(doc.editor.toPackage()) : readPptxContent(doc.editor.toPackage());
|
|
4646
|
+
if (content.kind !== "presentation") throw new Error("readPptxContent/readOdpContent always resolve a presentation package to the presentation ContentDocument variant.");
|
|
4647
|
+
const slide = content.slides[slideIndex];
|
|
4648
|
+
if (slide === void 0) return;
|
|
4649
|
+
return slide.shapes.flatMap((shape) => shape.blocks.filter((block) => block.kind === "table"))[tableIndex];
|
|
4650
|
+
}
|
|
4651
|
+
function slideTableCellText(cell) {
|
|
4652
|
+
return cell.blocks.filter((block) => block.kind === "paragraph").map((paragraph) => paragraph.runs.map((run) => run.text).join("")).join("\n");
|
|
4653
|
+
}
|
|
4654
|
+
function summarizeGridTable(table, index) {
|
|
4655
|
+
const rows = table.rows();
|
|
4656
|
+
return {
|
|
4657
|
+
index,
|
|
4658
|
+
rowCount: rows.length,
|
|
4659
|
+
columnCount: rows[0]?.cells().length ?? 0
|
|
4660
|
+
};
|
|
4661
|
+
}
|
|
4662
|
+
function summarizeSlideTables(doc, slideIndex) {
|
|
4663
|
+
if (doc.format === "odp") return (doc.editor.slides()[slideIndex]?.tables() ?? []).map((entry, index) => summarizeGridTable(entry.table, index));
|
|
4664
|
+
return (doc.editor.slides()[slideIndex]?.tables() ?? []).map((table, index) => summarizeGridTable(table, index));
|
|
4665
|
+
}
|
|
4666
|
+
//#endregion
|
|
4253
4667
|
//#region src/tui/screens/editors/pptx/slide-detail.tsx
|
|
4254
4668
|
const IMAGE_EXTENSION_TO_FORMAT = {
|
|
4255
4669
|
png: "png",
|
|
@@ -4258,10 +4672,6 @@ const IMAGE_EXTENSION_TO_FORMAT = {
|
|
|
4258
4672
|
};
|
|
4259
4673
|
const DEFAULT_TABLE_ROWS = 2;
|
|
4260
4674
|
const DEFAULT_TABLE_COLUMNS = 2;
|
|
4261
|
-
function parsePositiveIntField(raw, fallback) {
|
|
4262
|
-
const parsed = Number.parseInt(raw, 10);
|
|
4263
|
-
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
|
|
4264
|
-
}
|
|
4265
4675
|
function imageFormatFromPath(path) {
|
|
4266
4676
|
const dotIndex = path.lastIndexOf(".");
|
|
4267
4677
|
if (dotIndex < 0) return;
|
|
@@ -4282,34 +4692,63 @@ function SlideDetailScreen(props) {
|
|
|
4282
4692
|
const doc = assertPresentationDocument(state.openDocument);
|
|
4283
4693
|
const { slideIndex } = props.screen;
|
|
4284
4694
|
const slide = doc.editor.slides()[slideIndex];
|
|
4285
|
-
const
|
|
4695
|
+
const shapes = slide === void 0 ? [] : slide.shapes();
|
|
4696
|
+
const tableSummaries = slide === void 0 ? [] : summarizeSlideTables(doc, slideIndex);
|
|
4697
|
+
const rows = [...shapes.map((shape, index) => ({
|
|
4698
|
+
kind: "shape",
|
|
4286
4699
|
index,
|
|
4287
4700
|
text: shape.text,
|
|
4288
4701
|
frame: shape.frame
|
|
4289
|
-
}))
|
|
4702
|
+
})), ...tableSummaries.length > 0 ? [{
|
|
4703
|
+
kind: "tablesHeader",
|
|
4704
|
+
count: tableSummaries.length
|
|
4705
|
+
}, ...tableSummaries.map((summary) => ({
|
|
4706
|
+
kind: "table",
|
|
4707
|
+
index: summary.index,
|
|
4708
|
+
rowCount: summary.rowCount,
|
|
4709
|
+
columnCount: summary.columnCount
|
|
4710
|
+
}))] : []];
|
|
4711
|
+
const selectableRowIndices = rows.reduce((acc, row, index) => {
|
|
4712
|
+
if (row.kind !== "tablesHeader") acc.push(index);
|
|
4713
|
+
return acc;
|
|
4714
|
+
}, []);
|
|
4290
4715
|
const [addMode, setAddMode] = useState("closed");
|
|
4291
4716
|
const [draft, setDraft] = useState("");
|
|
4292
4717
|
const [imageError, setImageError] = useState(void 0);
|
|
4293
4718
|
const [tableRows, setTableRows] = useState(DEFAULT_TABLE_ROWS);
|
|
4294
4719
|
const formIsOpen = addMode !== "closed";
|
|
4295
4720
|
const { selectedIndex } = useNavigationInput({
|
|
4296
|
-
itemCount:
|
|
4721
|
+
itemCount: selectableRowIndices.length,
|
|
4297
4722
|
isActive: !overlayOpen && !formIsOpen,
|
|
4298
4723
|
onBack: () => {
|
|
4299
4724
|
dispatch({ type: "POP_SCREEN" });
|
|
4300
4725
|
},
|
|
4301
4726
|
onSelect: (index) => {
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4727
|
+
const rowIndex = selectableRowIndices[index];
|
|
4728
|
+
const row = rowIndex === void 0 ? void 0 : rows[rowIndex];
|
|
4729
|
+
if (row === void 0) return;
|
|
4730
|
+
if (row.kind === "shape") {
|
|
4731
|
+
dispatch({
|
|
4732
|
+
type: "SET_SELECTION",
|
|
4733
|
+
key: selectionKeyFor(props.screen),
|
|
4734
|
+
index
|
|
4735
|
+
});
|
|
4736
|
+
dispatch({
|
|
4737
|
+
type: "PUSH_SCREEN",
|
|
4738
|
+
screen: {
|
|
4739
|
+
kind: "shapeEditor",
|
|
4740
|
+
slideIndex,
|
|
4741
|
+
shapeIndex: row.index
|
|
4742
|
+
}
|
|
4743
|
+
});
|
|
4744
|
+
return;
|
|
4745
|
+
}
|
|
4746
|
+
if (row.kind === "table") dispatch({
|
|
4308
4747
|
type: "PUSH_SCREEN",
|
|
4309
4748
|
screen: {
|
|
4310
|
-
kind: "
|
|
4749
|
+
kind: "slideTableDetail",
|
|
4311
4750
|
slideIndex,
|
|
4312
|
-
|
|
4751
|
+
tableIndex: row.index
|
|
4313
4752
|
}
|
|
4314
4753
|
});
|
|
4315
4754
|
},
|
|
@@ -4317,6 +4756,7 @@ function SlideDetailScreen(props) {
|
|
|
4317
4756
|
setAddMode("chooseKind");
|
|
4318
4757
|
}
|
|
4319
4758
|
});
|
|
4759
|
+
const listSelectedIndex = selectableRowIndices[selectedIndex] ?? -1;
|
|
4320
4760
|
useInput((input, key) => {
|
|
4321
4761
|
if (key.escape) {
|
|
4322
4762
|
setAddMode("closed");
|
|
@@ -4399,9 +4839,9 @@ function SlideDetailScreen(props) {
|
|
|
4399
4839
|
"Slide ",
|
|
4400
4840
|
slideIndex + 1,
|
|
4401
4841
|
" -- ",
|
|
4402
|
-
|
|
4842
|
+
shapes.length,
|
|
4403
4843
|
" shape",
|
|
4404
|
-
|
|
4844
|
+
shapes.length === 1 ? "" : "s"
|
|
4405
4845
|
]
|
|
4406
4846
|
}),
|
|
4407
4847
|
slide === void 0 ? /* @__PURE__ */ jsx(Text, {
|
|
@@ -4409,20 +4849,45 @@ function SlideDetailScreen(props) {
|
|
|
4409
4849
|
children: "This slide no longer exists -- press Esc to go back"
|
|
4410
4850
|
}) : /* @__PURE__ */ jsx(ListView, {
|
|
4411
4851
|
items: rows,
|
|
4412
|
-
selectedIndex,
|
|
4852
|
+
selectedIndex: listSelectedIndex,
|
|
4413
4853
|
emptyMessage: "No shapes yet -- press 'a' to add one",
|
|
4414
|
-
renderItem: (row, isSelected) =>
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4854
|
+
renderItem: (row, isSelected) => {
|
|
4855
|
+
if (row.kind === "tablesHeader") return /* @__PURE__ */ jsxs(Text, {
|
|
4856
|
+
bold: true,
|
|
4857
|
+
dimColor: true,
|
|
4858
|
+
children: [
|
|
4859
|
+
"Tables (",
|
|
4860
|
+
row.count,
|
|
4861
|
+
")"
|
|
4862
|
+
]
|
|
4863
|
+
});
|
|
4864
|
+
if (row.kind === "table") return /* @__PURE__ */ jsxs(Text, {
|
|
4865
|
+
color: isSelected ? "cyan" : void 0,
|
|
4866
|
+
inverse: isSelected,
|
|
4867
|
+
children: [
|
|
4868
|
+
" ",
|
|
4869
|
+
"Table ",
|
|
4870
|
+
row.index + 1,
|
|
4871
|
+
" (",
|
|
4872
|
+
row.rowCount,
|
|
4873
|
+
"x",
|
|
4874
|
+
row.columnCount,
|
|
4875
|
+
")"
|
|
4876
|
+
]
|
|
4877
|
+
});
|
|
4878
|
+
return /* @__PURE__ */ jsxs(Text, {
|
|
4879
|
+
color: isSelected ? "cyan" : void 0,
|
|
4880
|
+
inverse: isSelected,
|
|
4881
|
+
children: [
|
|
4882
|
+
row.index + 1,
|
|
4883
|
+
". ",
|
|
4884
|
+
describeSlideFamilyShape({
|
|
4885
|
+
text: row.text,
|
|
4886
|
+
frame: row.frame
|
|
4887
|
+
})
|
|
4888
|
+
]
|
|
4889
|
+
});
|
|
4890
|
+
}
|
|
4426
4891
|
}),
|
|
4427
4892
|
addMode === "chooseKind" ? /* @__PURE__ */ jsx(Text, {
|
|
4428
4893
|
color: "cyan",
|
|
@@ -4490,6 +4955,146 @@ function SlideDetailScreen(props) {
|
|
|
4490
4955
|
});
|
|
4491
4956
|
}
|
|
4492
4957
|
//#endregion
|
|
4958
|
+
//#region src/tui/screens/editors/pptx/slide-table-detail.tsx
|
|
4959
|
+
const CELL_WIDTH$1 = 16;
|
|
4960
|
+
function SlideTableDetailScreen(props) {
|
|
4961
|
+
const state = useAppState();
|
|
4962
|
+
const dispatch = useAppDispatch();
|
|
4963
|
+
const doc = assertPresentationDocument(state.openDocument);
|
|
4964
|
+
const { slideIndex, tableIndex } = props.screen;
|
|
4965
|
+
const [cursor, setCursor] = useState({
|
|
4966
|
+
row: 0,
|
|
4967
|
+
column: 0
|
|
4968
|
+
});
|
|
4969
|
+
const [mergeAnchor, setMergeAnchor] = useState(void 0);
|
|
4970
|
+
const table = resolveSlideTable(doc, slideIndex, tableIndex);
|
|
4971
|
+
const rows = table?.rows ?? [];
|
|
4972
|
+
const rowCount = rows.length;
|
|
4973
|
+
const columnCount = rows[0]?.cells.length ?? 0;
|
|
4974
|
+
const clampedRow = rowCount === 0 ? 0 : Math.min(cursor.row, rowCount - 1);
|
|
4975
|
+
const clampedColumn = columnCount === 0 ? 0 : Math.min(cursor.column, columnCount - 1);
|
|
4976
|
+
const commitMerge = (anchor) => {
|
|
4977
|
+
const startRow = Math.min(anchor.row, clampedRow);
|
|
4978
|
+
const startColumn = Math.min(anchor.column, clampedColumn);
|
|
4979
|
+
const rowSpan = Math.abs(clampedRow - anchor.row) + 1;
|
|
4980
|
+
const colSpan = Math.abs(clampedColumn - anchor.column) + 1;
|
|
4981
|
+
dispatch({
|
|
4982
|
+
type: "MERGE_SLIDE_TABLE_CELLS",
|
|
4983
|
+
slideIndex,
|
|
4984
|
+
tableIndex,
|
|
4985
|
+
startRow,
|
|
4986
|
+
startColumn,
|
|
4987
|
+
rowSpan,
|
|
4988
|
+
colSpan
|
|
4989
|
+
});
|
|
4990
|
+
setMergeAnchor(void 0);
|
|
4991
|
+
};
|
|
4992
|
+
useInput((input, key) => {
|
|
4993
|
+
if (table === void 0) return;
|
|
4994
|
+
if (key.upArrow || input === "k") {
|
|
4995
|
+
setCursor({
|
|
4996
|
+
row: Math.max(0, clampedRow - 1),
|
|
4997
|
+
column: clampedColumn
|
|
4998
|
+
});
|
|
4999
|
+
return;
|
|
5000
|
+
}
|
|
5001
|
+
if (key.downArrow || input === "j") {
|
|
5002
|
+
setCursor({
|
|
5003
|
+
row: rowCount === 0 ? 0 : Math.min(rowCount - 1, clampedRow + 1),
|
|
5004
|
+
column: clampedColumn
|
|
5005
|
+
});
|
|
5006
|
+
return;
|
|
5007
|
+
}
|
|
5008
|
+
if (key.leftArrow || input === "h") {
|
|
5009
|
+
setCursor({
|
|
5010
|
+
row: clampedRow,
|
|
5011
|
+
column: Math.max(0, clampedColumn - 1)
|
|
5012
|
+
});
|
|
5013
|
+
return;
|
|
5014
|
+
}
|
|
5015
|
+
if (key.rightArrow || input === "l") {
|
|
5016
|
+
setCursor({
|
|
5017
|
+
row: clampedRow,
|
|
5018
|
+
column: columnCount === 0 ? 0 : Math.min(columnCount - 1, clampedColumn + 1)
|
|
5019
|
+
});
|
|
5020
|
+
return;
|
|
5021
|
+
}
|
|
5022
|
+
if (key.escape) {
|
|
5023
|
+
if (mergeAnchor !== void 0) {
|
|
5024
|
+
setMergeAnchor(void 0);
|
|
5025
|
+
return;
|
|
5026
|
+
}
|
|
5027
|
+
dispatch({ type: "POP_SCREEN" });
|
|
5028
|
+
return;
|
|
5029
|
+
}
|
|
5030
|
+
if (input === "m" || key.return) {
|
|
5031
|
+
if (mergeAnchor === void 0) {
|
|
5032
|
+
setMergeAnchor({
|
|
5033
|
+
row: clampedRow,
|
|
5034
|
+
column: clampedColumn
|
|
5035
|
+
});
|
|
5036
|
+
return;
|
|
5037
|
+
}
|
|
5038
|
+
commitMerge(mergeAnchor);
|
|
5039
|
+
}
|
|
5040
|
+
}, { isActive: !anyOverlayOpen(state) });
|
|
5041
|
+
if (table === void 0) return /* @__PURE__ */ jsxs(Box, {
|
|
5042
|
+
flexDirection: "column",
|
|
5043
|
+
children: [/* @__PURE__ */ jsxs(Text, {
|
|
5044
|
+
bold: true,
|
|
5045
|
+
children: [
|
|
5046
|
+
"Slide ",
|
|
5047
|
+
slideIndex + 1,
|
|
5048
|
+
", table ",
|
|
5049
|
+
tableIndex + 1
|
|
5050
|
+
]
|
|
5051
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
5052
|
+
color: "yellow",
|
|
5053
|
+
children: "This table no longer exists -- press Esc to go back"
|
|
5054
|
+
})]
|
|
5055
|
+
});
|
|
5056
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
5057
|
+
flexDirection: "column",
|
|
5058
|
+
children: [
|
|
5059
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
5060
|
+
bold: true,
|
|
5061
|
+
children: [
|
|
5062
|
+
"Slide ",
|
|
5063
|
+
slideIndex + 1,
|
|
5064
|
+
", table ",
|
|
5065
|
+
tableIndex + 1,
|
|
5066
|
+
" (",
|
|
5067
|
+
rowCount,
|
|
5068
|
+
"x",
|
|
5069
|
+
columnCount,
|
|
5070
|
+
")"
|
|
5071
|
+
]
|
|
5072
|
+
}),
|
|
5073
|
+
rows.length === 0 ? /* @__PURE__ */ jsx(Text, {
|
|
5074
|
+
dimColor: true,
|
|
5075
|
+
children: "This table has no rows."
|
|
5076
|
+
}) : rows.map((row, rowIndex) => /* @__PURE__ */ jsx(Box, { children: row.cells.map((cell, columnIndex) => {
|
|
5077
|
+
const isCursor = rowIndex === clampedRow && columnIndex === clampedColumn;
|
|
5078
|
+
const isAnchor = rowIndex === mergeAnchor?.row && columnIndex === mergeAnchor?.column;
|
|
5079
|
+
return /* @__PURE__ */ jsx(Box, {
|
|
5080
|
+
width: CELL_WIDTH$1,
|
|
5081
|
+
borderStyle: "single",
|
|
5082
|
+
borderColor: isCursor ? "cyan" : isAnchor ? "yellow" : "gray",
|
|
5083
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
5084
|
+
color: isCursor ? "cyan" : void 0,
|
|
5085
|
+
inverse: isCursor,
|
|
5086
|
+
children: truncatePreview(slideTableCellText(cell), 14)
|
|
5087
|
+
})
|
|
5088
|
+
}, columnIndex);
|
|
5089
|
+
}) }, rowIndex)),
|
|
5090
|
+
/* @__PURE__ */ jsx(Text, {
|
|
5091
|
+
dimColor: true,
|
|
5092
|
+
children: mergeAnchor === void 0 ? "Arrows/hjkl move, m to anchor a merge, Esc back" : "Arrows/hjkl to the opposite corner, m/Enter to merge, Esc to cancel"
|
|
5093
|
+
})
|
|
5094
|
+
]
|
|
5095
|
+
});
|
|
5096
|
+
}
|
|
5097
|
+
//#endregion
|
|
4493
5098
|
//#region src/tui/screens/editors/pptx/index.tsx
|
|
4494
5099
|
function PptxSlideListScreen() {
|
|
4495
5100
|
const doc = useAppState().openDocument;
|
|
@@ -5108,7 +5713,8 @@ const RESERVED_LETTERS = /* @__PURE__ */ new Set([
|
|
|
5108
5713
|
"k",
|
|
5109
5714
|
"l",
|
|
5110
5715
|
"p",
|
|
5111
|
-
"t"
|
|
5716
|
+
"t",
|
|
5717
|
+
"m"
|
|
5112
5718
|
]);
|
|
5113
5719
|
function windowStart(cursor, total, viewport) {
|
|
5114
5720
|
const maxStart = Math.max(0, total - viewport);
|
|
@@ -5131,6 +5737,7 @@ function OdsSpreadsheetGridScreen() {
|
|
|
5131
5737
|
const [cursorColumn, setCursorColumn] = useState(0);
|
|
5132
5738
|
const [viewMode, setViewMode] = useState("grid");
|
|
5133
5739
|
const [editSession, setEditSession] = useState(void 0);
|
|
5740
|
+
const [mergeAnchor, setMergeAnchor] = useState(void 0);
|
|
5134
5741
|
const { columns: terminalColumns, rows: terminalRows } = useWindowSize();
|
|
5135
5742
|
const sheet = resolveSheet(doc.editor, sheetIndex);
|
|
5136
5743
|
const { rowCount, columnCount } = sheetExtent(sheet);
|
|
@@ -5149,6 +5756,21 @@ function OdsSpreadsheetGridScreen() {
|
|
|
5149
5756
|
seedKind
|
|
5150
5757
|
});
|
|
5151
5758
|
}
|
|
5759
|
+
function commitMerge(anchor) {
|
|
5760
|
+
const startRow = Math.min(anchor.row, clampedRow);
|
|
5761
|
+
const startColumn = Math.min(anchor.column, clampedColumn);
|
|
5762
|
+
const rowSpan = Math.abs(clampedRow - anchor.row) + 1;
|
|
5763
|
+
const colSpan = Math.abs(clampedColumn - anchor.column) + 1;
|
|
5764
|
+
dispatch({
|
|
5765
|
+
type: "MERGE_CELLS",
|
|
5766
|
+
sheetIndex,
|
|
5767
|
+
startRow,
|
|
5768
|
+
startColumn,
|
|
5769
|
+
rowSpan,
|
|
5770
|
+
colSpan
|
|
5771
|
+
});
|
|
5772
|
+
setMergeAnchor(void 0);
|
|
5773
|
+
}
|
|
5152
5774
|
useInput((input) => {
|
|
5153
5775
|
if (input === "t") {
|
|
5154
5776
|
setViewMode((mode) => mode === "grid" ? "compact" : "grid");
|
|
@@ -5196,10 +5818,26 @@ function OdsSpreadsheetGridScreen() {
|
|
|
5196
5818
|
return;
|
|
5197
5819
|
}
|
|
5198
5820
|
if (key.escape) {
|
|
5821
|
+
if (mergeAnchor !== void 0) {
|
|
5822
|
+
setMergeAnchor(void 0);
|
|
5823
|
+
return;
|
|
5824
|
+
}
|
|
5199
5825
|
dispatch({ type: "POP_SCREEN" });
|
|
5200
5826
|
return;
|
|
5201
5827
|
}
|
|
5828
|
+
if (input === "m") {
|
|
5829
|
+
if (mergeAnchor === void 0) setMergeAnchor({
|
|
5830
|
+
row: clampedRow,
|
|
5831
|
+
column: clampedColumn
|
|
5832
|
+
});
|
|
5833
|
+
else commitMerge(mergeAnchor);
|
|
5834
|
+
return;
|
|
5835
|
+
}
|
|
5202
5836
|
if (key.return) {
|
|
5837
|
+
if (mergeAnchor !== void 0) {
|
|
5838
|
+
commitMerge(mergeAnchor);
|
|
5839
|
+
return;
|
|
5840
|
+
}
|
|
5203
5841
|
const cell = cells.get(cellKey(clampedRow, clampedColumn));
|
|
5204
5842
|
const seedKind = cell === void 0 || cell.value.kind === "empty" ? "string" : cell.value.kind;
|
|
5205
5843
|
beginEdit(cell === void 0 ? "" : rawEditableText(cell.value), seedKind);
|
|
@@ -5282,10 +5920,11 @@ function OdsSpreadsheetGridScreen() {
|
|
|
5282
5920
|
children: [`${row + 1}`.padStart(4), " "]
|
|
5283
5921
|
}), visibleColumns.map((column) => {
|
|
5284
5922
|
const isCursor = row === clampedRow && column === clampedColumn;
|
|
5923
|
+
const isAnchor = row === mergeAnchor?.row && column === mergeAnchor?.column;
|
|
5285
5924
|
const cell = cells.get(cellKey(row, column));
|
|
5286
5925
|
return /* @__PURE__ */ jsx(Text, {
|
|
5287
5926
|
inverse: isCursor,
|
|
5288
|
-
color: isCursor ? "cyan" : void 0,
|
|
5927
|
+
color: isCursor ? "cyan" : isAnchor ? "yellow" : void 0,
|
|
5289
5928
|
children: padCell(cell === void 0 ? "" : cell.displayText, CELL_WIDTH)
|
|
5290
5929
|
}, column);
|
|
5291
5930
|
})] }, row))]
|
|
@@ -5323,13 +5962,9 @@ function OdsSpreadsheetGridScreen() {
|
|
|
5323
5962
|
setEditSession(void 0);
|
|
5324
5963
|
}
|
|
5325
5964
|
}),
|
|
5326
|
-
/* @__PURE__ */
|
|
5965
|
+
/* @__PURE__ */ jsx(Text, {
|
|
5327
5966
|
dimColor: true,
|
|
5328
|
-
children:
|
|
5329
|
-
"hjkl/arrows move, Enter/type to edit, p print settings, t ",
|
|
5330
|
-
viewMode === "grid" ? "compact list" : "grid",
|
|
5331
|
-
" view, Esc back"
|
|
5332
|
-
]
|
|
5967
|
+
children: mergeAnchor === void 0 ? `hjkl/arrows move, Enter/type to edit, m to anchor a merge, p print settings, t ${viewMode === "grid" ? "compact list" : "grid"} view, Esc back` : "hjkl/arrows to the opposite corner, m/Enter to merge, Esc to cancel"
|
|
5333
5968
|
})
|
|
5334
5969
|
]
|
|
5335
5970
|
});
|
|
@@ -5339,28 +5974,40 @@ function OdsSpreadsheetGridScreen() {
|
|
|
5339
5974
|
function ListEditorScreen() {
|
|
5340
5975
|
const state = useAppState();
|
|
5341
5976
|
const dispatch = useAppDispatch();
|
|
5977
|
+
const overlayOpen = anyOverlayOpen(state);
|
|
5342
5978
|
const [isAdding, setIsAdding] = useState(false);
|
|
5343
5979
|
const [newItemText, setNewItemText] = useState("");
|
|
5980
|
+
const [editingIndex, setEditingIndex] = useState(void 0);
|
|
5344
5981
|
const screen = currentScreen(state);
|
|
5345
5982
|
const doc = paragraphFamilyDocument(state.openDocument);
|
|
5346
5983
|
const list = screen.kind === "listEditor" && doc?.format === "odt" ? doc.editor.lists()[screen.blockIndex] : void 0;
|
|
5347
|
-
const
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5984
|
+
const items = list === void 0 ? [] : list.items();
|
|
5985
|
+
const rows = items.map((item, index) => ({
|
|
5986
|
+
item,
|
|
5987
|
+
index
|
|
5988
|
+
}));
|
|
5989
|
+
const itemCount = items.length;
|
|
5990
|
+
const isNavigationActive = !overlayOpen && !isAdding && editingIndex === void 0;
|
|
5991
|
+
const { selectedIndex } = useNavigationInput({
|
|
5992
|
+
itemCount,
|
|
5993
|
+
isActive: isNavigationActive,
|
|
5994
|
+
onBack: () => {
|
|
5351
5995
|
dispatch({ type: "POP_SCREEN" });
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5996
|
+
},
|
|
5997
|
+
onSelect: (index) => {
|
|
5998
|
+
setEditingIndex(index);
|
|
5999
|
+
},
|
|
6000
|
+
onAppend: () => {
|
|
5355
6001
|
setIsAdding(true);
|
|
5356
|
-
return;
|
|
5357
6002
|
}
|
|
6003
|
+
});
|
|
6004
|
+
useInput((input, key) => {
|
|
5358
6005
|
if (key.tab || input === ">" || input === "<") dispatch({
|
|
5359
6006
|
type: "SET_STATUS",
|
|
5360
6007
|
severity: "warning",
|
|
5361
6008
|
text: "Indenting a list item needs a new reducer action this pass didn't add -- OdtListItem.addNestedList() has no wiring yet"
|
|
5362
6009
|
});
|
|
5363
|
-
}, { isActive:
|
|
6010
|
+
}, { isActive: isNavigationActive });
|
|
5364
6011
|
if (screen.kind !== "listEditor") return /* @__PURE__ */ jsx(Text, {
|
|
5365
6012
|
color: "red",
|
|
5366
6013
|
children: "ListEditorScreen rendered outside a listEditor screen."
|
|
@@ -5377,6 +6024,36 @@ function ListEditorScreen() {
|
|
|
5377
6024
|
"."
|
|
5378
6025
|
]
|
|
5379
6026
|
});
|
|
6027
|
+
if (editingIndex !== void 0) {
|
|
6028
|
+
const item = items[editingIndex];
|
|
6029
|
+
if (item === void 0) throw new Error(`ListEditorScreen is editing item index ${editingIndex}, but list ${screen.blockIndex} only has ${items.length} items -- selecting a row always sets editingIndex to a valid index from that same items array, so this indicates a bug in that selection.`);
|
|
6030
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
6031
|
+
flexDirection: "column",
|
|
6032
|
+
children: [/* @__PURE__ */ jsxs(Text, {
|
|
6033
|
+
bold: true,
|
|
6034
|
+
children: [
|
|
6035
|
+
"List ",
|
|
6036
|
+
screen.blockIndex,
|
|
6037
|
+
", item ",
|
|
6038
|
+
editingIndex + 1
|
|
6039
|
+
]
|
|
6040
|
+
}), /* @__PURE__ */ jsx(RunTextEditor, {
|
|
6041
|
+
initialText: item.text,
|
|
6042
|
+
onCommit: (text) => {
|
|
6043
|
+
dispatch({
|
|
6044
|
+
type: "SET_LIST_ITEM_TEXT",
|
|
6045
|
+
blockIndex: screen.blockIndex,
|
|
6046
|
+
itemIndex: editingIndex,
|
|
6047
|
+
text
|
|
6048
|
+
});
|
|
6049
|
+
setEditingIndex(void 0);
|
|
6050
|
+
},
|
|
6051
|
+
onCancel: () => {
|
|
6052
|
+
setEditingIndex(void 0);
|
|
6053
|
+
}
|
|
6054
|
+
})]
|
|
6055
|
+
});
|
|
6056
|
+
}
|
|
5380
6057
|
return /* @__PURE__ */ jsxs(Box, {
|
|
5381
6058
|
flexDirection: "column",
|
|
5382
6059
|
children: [
|
|
@@ -5392,13 +6069,23 @@ function ListEditorScreen() {
|
|
|
5392
6069
|
")"
|
|
5393
6070
|
]
|
|
5394
6071
|
}),
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
|
|
6072
|
+
/* @__PURE__ */ jsx(ListView, {
|
|
6073
|
+
items: rows,
|
|
6074
|
+
selectedIndex,
|
|
6075
|
+
emptyMessage: "This list has no items yet -- press 'a' to add one.",
|
|
6076
|
+
renderItem: (row, isSelected) => {
|
|
6077
|
+
const trimmed = row.item.text.trim();
|
|
6078
|
+
return /* @__PURE__ */ jsxs(Text, {
|
|
6079
|
+
color: isSelected ? "cyan" : void 0,
|
|
6080
|
+
inverse: isSelected,
|
|
6081
|
+
children: [
|
|
6082
|
+
row.index + 1,
|
|
6083
|
+
". ",
|
|
6084
|
+
trimmed.length === 0 ? "(empty)" : row.item.text
|
|
6085
|
+
]
|
|
6086
|
+
});
|
|
6087
|
+
}
|
|
6088
|
+
}),
|
|
5402
6089
|
isAdding ? /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, {
|
|
5403
6090
|
color: "cyan",
|
|
5404
6091
|
children: "+ "
|
|
@@ -5422,7 +6109,7 @@ function ListEditorScreen() {
|
|
|
5422
6109
|
}
|
|
5423
6110
|
})] }) : /* @__PURE__ */ jsx(Text, {
|
|
5424
6111
|
dimColor: true,
|
|
5425
|
-
children: "
|
|
6112
|
+
children: "Enter to edit an item, a to add, Esc back"
|
|
5426
6113
|
})
|
|
5427
6114
|
]
|
|
5428
6115
|
});
|
|
@@ -6329,6 +7016,7 @@ function ScreenBody({ screen }) {
|
|
|
6329
7016
|
case "slideList": return format === "odp" ? /* @__PURE__ */ jsx(OdpSlideListScreen, {}) : /* @__PURE__ */ jsx(PptxSlideListScreen, {});
|
|
6330
7017
|
case "slideDetail": return /* @__PURE__ */ jsx(SlideDetailScreen, { screen });
|
|
6331
7018
|
case "shapeEditor": return /* @__PURE__ */ jsx(ShapeEditorScreen, { screen });
|
|
7019
|
+
case "slideTableDetail": return /* @__PURE__ */ jsx(SlideTableDetailScreen, { screen });
|
|
6332
7020
|
case "notesEditor": return /* @__PURE__ */ jsx(NotesEditorScreen, { screen });
|
|
6333
7021
|
case "sheetList": return /* @__PURE__ */ jsx(OdsSheetListScreen, {});
|
|
6334
7022
|
case "spreadsheetGrid": return /* @__PURE__ */ jsx(OdsSpreadsheetGridScreen, {});
|
|
@@ -6441,6 +7129,10 @@ function AppShell({ startPath }) {
|
|
|
6441
7129
|
dispatch({ type: "REQUEST_CLOSE" });
|
|
6442
7130
|
return;
|
|
6443
7131
|
}
|
|
7132
|
+
if (key.ctrl && input === "z") {
|
|
7133
|
+
dispatch({ type: "UNDO" });
|
|
7134
|
+
return;
|
|
7135
|
+
}
|
|
6444
7136
|
if (input === ":") {
|
|
6445
7137
|
dispatch({
|
|
6446
7138
|
type: "OPEN_OVERLAY",
|