document-cli 1.6.0 → 1.8.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-CqYlr4nN.js → tui-BCl5_L4H.js} +1033 -118
- package/package.json +2 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { c as formatDocxExtrasLines, f as loadProvidedFonts, i as formatOdbReportLines, l as readInput, m as inferFormatFromExtension, n as describeOdbReport, o as formatMetadataLines, p as formatToExtension, r as formatOdbFormLines, t as describeOdbForm } from "./odb-structure-Du1w_hOU.js";
|
|
2
2
|
import { readFile, writeFile } from "node:fs/promises";
|
|
3
|
-
import { buildDocxPackage, buildOdtPackage, convertWordprocessingToLayout, createDocx, createFontMeasurer, createFontRegistry, createOdg, createOdp, createOds, createOdt, createPptx, decodeMarkdownText, docxToPdf, encodeMarkdownText, encodePackage, hsqldbCellDisplayText, markdownToPdf, odgToPdf, odpToPdf, odsToPdf, odtToPdf, openDocx, openOdg, openOdp, openOds, openOdt, openPptx, pptxToPdf, readDocxContent, readDocxExtras, readMarkdownContent, readOdbForms, readOdbReportContent, readOdbReports, readOdbTables, readOdgContent, readOdpContent, readOdsContent, readOdtContent, readPdf, readPptxContent, rgbHexToColor, writePdf, xlsxToPdf } from "documents.js";
|
|
3
|
+
import { buildDocxPackage, buildOdtPackage, convertWordprocessingToLayout, createDocx, createFontMeasurer, createFontRegistry, createOdg, createOdp, createOds, createOdt, createPptx, decodeMarkdownText, docxToPdf, encodeMarkdownText, encodePackage, hsqldbCellDisplayText, markdownToPdf, odgToPdf, odpToPdf, odsToPdf, odtToPdf, openDocx, openOdg, openOdp, openOds, openOdt, openPptx, parseXml, pptxToPdf, readDocxContent, readDocxExtras, readMarkdownContent, readOdbForms, readOdbReportContent, readOdbReports, readOdbTables, readOdgContent, readOdpContent, readOdsContent, readOdtContent, readPdf, readPptxContent, rgbHexToColor, writePdf, xlsxToPdf } from "documents.js";
|
|
4
4
|
import { basename, dirname, extname, join } from "node:path";
|
|
5
5
|
import { cellReference, columnIndexToLetters, decodePackage as decodePackage$1, encodePackage as encodePackage$1 } from "odf.js";
|
|
6
6
|
import { readdirSync } from "node:fs";
|
|
@@ -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 {
|
|
@@ -536,6 +569,31 @@ function setTextContainerText(container, text) {
|
|
|
536
569
|
function setCellText(cell, text) {
|
|
537
570
|
setTextContainerText(cell, text);
|
|
538
571
|
}
|
|
572
|
+
function mutableMathMlNode(node) {
|
|
573
|
+
if (node.type === "element") return {
|
|
574
|
+
type: "element",
|
|
575
|
+
tag: node.tag,
|
|
576
|
+
attributes: [...node.attributes],
|
|
577
|
+
children: node.children.map(mutableMathMlNode)
|
|
578
|
+
};
|
|
579
|
+
if (node.type === "text") return {
|
|
580
|
+
type: "text",
|
|
581
|
+
value: node.value
|
|
582
|
+
};
|
|
583
|
+
if (node.type === "cdata" || node.type === "comment") return {
|
|
584
|
+
type: node.type,
|
|
585
|
+
value: ""
|
|
586
|
+
};
|
|
587
|
+
if (node.type === "declaration") return {
|
|
588
|
+
type: "declaration",
|
|
589
|
+
attributes: []
|
|
590
|
+
};
|
|
591
|
+
return {
|
|
592
|
+
type: "pi",
|
|
593
|
+
target: "",
|
|
594
|
+
content: ""
|
|
595
|
+
};
|
|
596
|
+
}
|
|
539
597
|
function appReducer(state, action) {
|
|
540
598
|
switch (action.type) {
|
|
541
599
|
case "PUSH_SCREEN": return {
|
|
@@ -685,11 +743,21 @@ function appReducer(state, action) {
|
|
|
685
743
|
case "APPEND_TABLE": {
|
|
686
744
|
const doc = wordprocessingDocument(state);
|
|
687
745
|
if (doc === void 0) return wrongDocument(state, "a docx or odt document");
|
|
688
|
-
return
|
|
689
|
-
doc.editor.body.appendTable({
|
|
746
|
+
return mutateGuarded(state, doc, () => {
|
|
747
|
+
const table = doc.editor.body.appendTable({
|
|
690
748
|
rows: action.rows,
|
|
691
749
|
columns: action.columns
|
|
692
750
|
});
|
|
751
|
+
if (action.merge !== void 0) table.mergeCells(action.merge.startRow, action.merge.startColumn, action.merge.rowSpan, action.merge.colSpan);
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
case "MERGE_TABLE_CELLS": {
|
|
755
|
+
const doc = wordprocessingDocument(state);
|
|
756
|
+
if (doc === void 0) return wrongDocument(state, "a docx or odt document");
|
|
757
|
+
const table = tableAt(doc, action.tableIndex);
|
|
758
|
+
if (table === void 0) return withStatus(state, "warning", `There is no table at index ${action.tableIndex}`);
|
|
759
|
+
return mutateGuarded(state, doc, () => {
|
|
760
|
+
table.mergeCells(action.startRow, action.startColumn, action.rowSpan, action.colSpan);
|
|
693
761
|
});
|
|
694
762
|
}
|
|
695
763
|
case "SET_TABLE_CELL_TEXT": {
|
|
@@ -732,6 +800,39 @@ function appReducer(state, action) {
|
|
|
732
800
|
setTextContainerText(item, action.text);
|
|
733
801
|
});
|
|
734
802
|
}
|
|
803
|
+
case "INSERT_PARAGRAPH_IMAGE": {
|
|
804
|
+
const doc = wordprocessingDocument(state);
|
|
805
|
+
if (doc === void 0) return wrongDocument(state, "a docx or odt document");
|
|
806
|
+
const paragraph = paragraphAt(doc, action.blockIndex);
|
|
807
|
+
if (paragraph === void 0) return withStatus(state, "warning", `There is no paragraph at index ${action.blockIndex}`);
|
|
808
|
+
return mutate(state, doc, () => {
|
|
809
|
+
paragraph.insertImageAfter({
|
|
810
|
+
format: action.format,
|
|
811
|
+
bytes: action.bytes,
|
|
812
|
+
widthPt: action.widthPt,
|
|
813
|
+
heightPt: action.heightPt,
|
|
814
|
+
altText: action.altText
|
|
815
|
+
});
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
case "INSERT_DOCX_FORMULA": {
|
|
819
|
+
const doc = state.openDocument;
|
|
820
|
+
if (doc?.format !== "docx") return wrongDocument(state, "a docx document");
|
|
821
|
+
const paragraph = doc.editor.paragraphs()[action.blockIndex];
|
|
822
|
+
if (paragraph === void 0) return withStatus(state, "warning", `There is no paragraph at index ${action.blockIndex}`);
|
|
823
|
+
let written = true;
|
|
824
|
+
const nextState = mutate(state, doc, () => {
|
|
825
|
+
written = paragraph.appendOfficeMath(action.mathml).written;
|
|
826
|
+
});
|
|
827
|
+
return written ? nextState : withStatus(nextState, "warning", "The formula produced no OMML content and was not written");
|
|
828
|
+
}
|
|
829
|
+
case "INSERT_ODT_FORMULA": {
|
|
830
|
+
const doc = state.openDocument;
|
|
831
|
+
if (doc?.format !== "odt") return wrongDocument(state, "an odt document");
|
|
832
|
+
return mutate(state, doc, () => {
|
|
833
|
+
doc.editor.body.appendFormula({ mathml: action.mathml.map(mutableMathMlNode) }, action.frame);
|
|
834
|
+
});
|
|
835
|
+
}
|
|
735
836
|
case "ADD_SLIDE": {
|
|
736
837
|
const doc = presentationDocument(state);
|
|
737
838
|
if (doc === void 0) return wrongDocument(state, "a pptx or odp document");
|
|
@@ -754,6 +855,22 @@ function appReducer(state, action) {
|
|
|
754
855
|
});
|
|
755
856
|
});
|
|
756
857
|
}
|
|
858
|
+
case "MERGE_SLIDE_TABLE_CELLS": {
|
|
859
|
+
const doc = presentationDocument(state);
|
|
860
|
+
if (doc === void 0) return wrongDocument(state, "a pptx or odp document");
|
|
861
|
+
if (doc.format === "odp") {
|
|
862
|
+
const entry = doc.editor.slides()[action.slideIndex]?.tables()[action.tableIndex];
|
|
863
|
+
if (entry === void 0) return withStatus(state, "warning", `There is no table at index ${action.tableIndex} on slide ${action.slideIndex}`);
|
|
864
|
+
return mutateGuarded(state, doc, () => {
|
|
865
|
+
entry.table.mergeCells(action.startRow, action.startColumn, action.rowSpan, action.colSpan);
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
const table = doc.editor.slides()[action.slideIndex]?.tables()[action.tableIndex];
|
|
869
|
+
if (table === void 0) return withStatus(state, "warning", `There is no table at index ${action.tableIndex} on slide ${action.slideIndex}`);
|
|
870
|
+
return mutateGuarded(state, doc, () => {
|
|
871
|
+
mergePptxTableCells(table, action.startRow, action.startColumn, action.rowSpan, action.colSpan);
|
|
872
|
+
});
|
|
873
|
+
}
|
|
757
874
|
case "ADD_TEXTBOX": {
|
|
758
875
|
const doc = shapeHostDocument(state);
|
|
759
876
|
if (doc === void 0) return wrongDocument(state, "a pptx, odp or odg document");
|
|
@@ -826,6 +943,15 @@ function appReducer(state, action) {
|
|
|
826
943
|
case "SET_CELL_VALUE": return withSheet(state, action.sheetIndex, (sheet) => {
|
|
827
944
|
sheet.cell(action.row, action.column).value = action.value;
|
|
828
945
|
});
|
|
946
|
+
case "MERGE_CELLS": {
|
|
947
|
+
const doc = spreadsheetDocument(state);
|
|
948
|
+
if (doc === void 0) return wrongDocument(state, "an ods document");
|
|
949
|
+
const sheet = sheetAt(doc, action.sheetIndex);
|
|
950
|
+
if (sheet === void 0) return withStatus(state, "warning", `There is no sheet at index ${action.sheetIndex}`);
|
|
951
|
+
return mutateGuarded(state, doc, () => {
|
|
952
|
+
sheet.mergeCells(action.startRow, action.startColumn, action.rowSpan, action.colSpan);
|
|
953
|
+
});
|
|
954
|
+
}
|
|
829
955
|
case "SET_SHEET_PRINT_SETTINGS": return withSheet(state, action.sheetIndex, (sheet) => {
|
|
830
956
|
sheet.printSettings = action.printSettings;
|
|
831
957
|
});
|
|
@@ -1600,12 +1726,238 @@ function StatusLine() {
|
|
|
1600
1726
|
] });
|
|
1601
1727
|
}
|
|
1602
1728
|
//#endregion
|
|
1729
|
+
//#region src/tui/screens/shared/field-wizard.tsx
|
|
1730
|
+
function requireFieldValue(values, key) {
|
|
1731
|
+
const value = values[key];
|
|
1732
|
+
if (value === void 0) throw new Error(`Field wizard field '${key}' was never recorded before building the action.`);
|
|
1733
|
+
return value;
|
|
1734
|
+
}
|
|
1735
|
+
function FieldWizard(props) {
|
|
1736
|
+
const [stepIndex, setStepIndex] = useState(0);
|
|
1737
|
+
const [collected, setCollected] = useState({});
|
|
1738
|
+
const initialField = props.fields[0];
|
|
1739
|
+
const [draft, setDraft] = useState(initialField === void 0 ? "" : initialField.defaultValue);
|
|
1740
|
+
const field = props.fields[stepIndex];
|
|
1741
|
+
if (field === void 0) throw new Error(`FieldWizard stepIndex ${stepIndex} is out of range for ${props.fields.length} fields -- onComplete always fires before stepIndex can advance past the last field, so this indicates a bug in that advance.`);
|
|
1742
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
1743
|
+
flexDirection: "column",
|
|
1744
|
+
borderStyle: "round",
|
|
1745
|
+
paddingX: 1,
|
|
1746
|
+
children: [
|
|
1747
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1748
|
+
bold: true,
|
|
1749
|
+
children: field.label
|
|
1750
|
+
}),
|
|
1751
|
+
/* @__PURE__ */ jsx(TextField, {
|
|
1752
|
+
value: draft,
|
|
1753
|
+
isFocused: true,
|
|
1754
|
+
onChange: setDraft,
|
|
1755
|
+
onCancel: props.onCancel,
|
|
1756
|
+
onSubmit: (value) => {
|
|
1757
|
+
const recorded = {
|
|
1758
|
+
...collected,
|
|
1759
|
+
[field.key]: value
|
|
1760
|
+
};
|
|
1761
|
+
const nextIndex = stepIndex + 1;
|
|
1762
|
+
const nextField = props.fields[nextIndex];
|
|
1763
|
+
if (nextField === void 0) {
|
|
1764
|
+
props.onComplete(recorded);
|
|
1765
|
+
return;
|
|
1766
|
+
}
|
|
1767
|
+
setCollected(recorded);
|
|
1768
|
+
setDraft(nextField.defaultValue);
|
|
1769
|
+
setStepIndex(nextIndex);
|
|
1770
|
+
}
|
|
1771
|
+
}),
|
|
1772
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
1773
|
+
dimColor: true,
|
|
1774
|
+
children: [
|
|
1775
|
+
"Step ",
|
|
1776
|
+
stepIndex + 1,
|
|
1777
|
+
" of ",
|
|
1778
|
+
props.fields.length,
|
|
1779
|
+
" -- Enter to continue, Esc to cancel"
|
|
1780
|
+
]
|
|
1781
|
+
})
|
|
1782
|
+
]
|
|
1783
|
+
});
|
|
1784
|
+
}
|
|
1785
|
+
//#endregion
|
|
1786
|
+
//#region src/tui/screens/shared/formula-presets.ts
|
|
1787
|
+
function element(tag, children = []) {
|
|
1788
|
+
return {
|
|
1789
|
+
type: "element",
|
|
1790
|
+
tag,
|
|
1791
|
+
attributes: [],
|
|
1792
|
+
children
|
|
1793
|
+
};
|
|
1794
|
+
}
|
|
1795
|
+
function text(value) {
|
|
1796
|
+
return {
|
|
1797
|
+
type: "text",
|
|
1798
|
+
value
|
|
1799
|
+
};
|
|
1800
|
+
}
|
|
1801
|
+
function mi(name) {
|
|
1802
|
+
return element("mi", [text(name)]);
|
|
1803
|
+
}
|
|
1804
|
+
function mn(value) {
|
|
1805
|
+
return element("mn", [text(value)]);
|
|
1806
|
+
}
|
|
1807
|
+
function mo(operator) {
|
|
1808
|
+
return element("mo", [text(operator)]);
|
|
1809
|
+
}
|
|
1810
|
+
const FORMULA_PRESETS = [
|
|
1811
|
+
{
|
|
1812
|
+
label: "Fraction: x / 2",
|
|
1813
|
+
mathml: [element("mfrac", [mi("x"), mn("2")])]
|
|
1814
|
+
},
|
|
1815
|
+
{
|
|
1816
|
+
label: "Power: x^2",
|
|
1817
|
+
mathml: [element("msup", [mi("x"), mn("2")])]
|
|
1818
|
+
},
|
|
1819
|
+
{
|
|
1820
|
+
label: "Subscript: x_i",
|
|
1821
|
+
mathml: [element("msub", [mi("x"), mi("i")])]
|
|
1822
|
+
},
|
|
1823
|
+
{
|
|
1824
|
+
label: "Square root: sqrt(x)",
|
|
1825
|
+
mathml: [element("msqrt", [mi("x")])]
|
|
1826
|
+
},
|
|
1827
|
+
{
|
|
1828
|
+
label: "Summation: sum(i=1..n) i",
|
|
1829
|
+
mathml: [element("munderover", [
|
|
1830
|
+
mo("∑"),
|
|
1831
|
+
element("mrow", [
|
|
1832
|
+
mi("i"),
|
|
1833
|
+
mo("="),
|
|
1834
|
+
mn("1")
|
|
1835
|
+
]),
|
|
1836
|
+
mi("n")
|
|
1837
|
+
])]
|
|
1838
|
+
},
|
|
1839
|
+
{
|
|
1840
|
+
label: "Quadratic formula",
|
|
1841
|
+
mathml: [element("mrow", [
|
|
1842
|
+
mi("x"),
|
|
1843
|
+
mo("="),
|
|
1844
|
+
element("mfrac", [element("mrow", [
|
|
1845
|
+
mo("-"),
|
|
1846
|
+
mi("b"),
|
|
1847
|
+
mo("±"),
|
|
1848
|
+
element("msqrt", [element("mrow", [
|
|
1849
|
+
element("msup", [mi("b"), mn("2")]),
|
|
1850
|
+
mo("-"),
|
|
1851
|
+
mn("4"),
|
|
1852
|
+
mi("a"),
|
|
1853
|
+
mi("c")
|
|
1854
|
+
])])
|
|
1855
|
+
]), element("mrow", [mn("2"), mi("a")])])
|
|
1856
|
+
])]
|
|
1857
|
+
}
|
|
1858
|
+
];
|
|
1859
|
+
//#endregion
|
|
1860
|
+
//#region src/tui/screens/shared/formula-picker.tsx
|
|
1861
|
+
const RAW_ENTRY_LABEL = "Raw MathML...";
|
|
1862
|
+
const PICKER_ROWS = [...FORMULA_PRESETS.map((preset) => ({
|
|
1863
|
+
label: preset.label,
|
|
1864
|
+
mathml: preset.mathml
|
|
1865
|
+
})), {
|
|
1866
|
+
label: RAW_ENTRY_LABEL,
|
|
1867
|
+
mathml: void 0
|
|
1868
|
+
}];
|
|
1869
|
+
function FormulaPicker(props) {
|
|
1870
|
+
const [rawInput, setRawInput] = useState(void 0);
|
|
1871
|
+
const { selectedIndex } = useNavigationInput({
|
|
1872
|
+
itemCount: PICKER_ROWS.length,
|
|
1873
|
+
isActive: props.isActive && rawInput === void 0,
|
|
1874
|
+
onBack: props.onCancel,
|
|
1875
|
+
onSelect: (index) => {
|
|
1876
|
+
const row = PICKER_ROWS[index];
|
|
1877
|
+
if (row === void 0) return;
|
|
1878
|
+
if (row.mathml === void 0) {
|
|
1879
|
+
setRawInput("");
|
|
1880
|
+
return;
|
|
1881
|
+
}
|
|
1882
|
+
props.onMathml(row.mathml);
|
|
1883
|
+
}
|
|
1884
|
+
});
|
|
1885
|
+
if (rawInput !== void 0) return /* @__PURE__ */ jsxs(Box, {
|
|
1886
|
+
flexDirection: "column",
|
|
1887
|
+
borderStyle: "round",
|
|
1888
|
+
paddingX: 1,
|
|
1889
|
+
children: [
|
|
1890
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1891
|
+
bold: true,
|
|
1892
|
+
children: "Raw MathML (the children of the <math> root, e.g. <mfrac>...</mfrac>)"
|
|
1893
|
+
}),
|
|
1894
|
+
/* @__PURE__ */ jsx(TextField, {
|
|
1895
|
+
value: rawInput,
|
|
1896
|
+
isFocused: true,
|
|
1897
|
+
onChange: setRawInput,
|
|
1898
|
+
onCancel: () => {
|
|
1899
|
+
setRawInput(void 0);
|
|
1900
|
+
},
|
|
1901
|
+
onSubmit: (value) => {
|
|
1902
|
+
try {
|
|
1903
|
+
const parsed = parseXml(value);
|
|
1904
|
+
props.onMathml(parsed);
|
|
1905
|
+
} catch (error) {
|
|
1906
|
+
props.onInvalidRawMathml(describeError(error));
|
|
1907
|
+
}
|
|
1908
|
+
setRawInput(void 0);
|
|
1909
|
+
}
|
|
1910
|
+
}),
|
|
1911
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1912
|
+
dimColor: true,
|
|
1913
|
+
children: "Enter to insert, Esc to cancel"
|
|
1914
|
+
})
|
|
1915
|
+
]
|
|
1916
|
+
});
|
|
1917
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
1918
|
+
flexDirection: "column",
|
|
1919
|
+
borderStyle: "round",
|
|
1920
|
+
paddingX: 1,
|
|
1921
|
+
children: [
|
|
1922
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1923
|
+
bold: true,
|
|
1924
|
+
children: "Insert formula -- choose a preset or write raw MathML"
|
|
1925
|
+
}),
|
|
1926
|
+
/* @__PURE__ */ jsx(ListView, {
|
|
1927
|
+
items: PICKER_ROWS,
|
|
1928
|
+
selectedIndex,
|
|
1929
|
+
reservedRows: PICKER_ROWS.length + 2,
|
|
1930
|
+
renderItem: (row, isSelected) => /* @__PURE__ */ jsxs(Text, {
|
|
1931
|
+
color: isSelected ? "cyan" : void 0,
|
|
1932
|
+
children: [isSelected ? "> " : " ", row.label]
|
|
1933
|
+
})
|
|
1934
|
+
}),
|
|
1935
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1936
|
+
dimColor: true,
|
|
1937
|
+
children: "Enter to choose, Esc to cancel"
|
|
1938
|
+
})
|
|
1939
|
+
]
|
|
1940
|
+
});
|
|
1941
|
+
}
|
|
1942
|
+
//#endregion
|
|
1603
1943
|
//#region src/tui/screens/shared/text.ts
|
|
1604
1944
|
function truncatePreview(text, maxLength) {
|
|
1605
1945
|
const singleLine = text.replace(/\s+/gu, " ").trim();
|
|
1606
1946
|
if (singleLine.length === 0) return "(empty)";
|
|
1607
1947
|
return singleLine.length > maxLength ? `${singleLine.slice(0, Math.max(0, maxLength - 1))}…` : singleLine;
|
|
1608
1948
|
}
|
|
1949
|
+
function parsePositiveIntField(raw, fallback) {
|
|
1950
|
+
const parsed = Number.parseInt(raw, 10);
|
|
1951
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
|
|
1952
|
+
}
|
|
1953
|
+
function parseNonNegativeIntField(raw, fallback) {
|
|
1954
|
+
const parsed = Number.parseInt(raw, 10);
|
|
1955
|
+
return Number.isInteger(parsed) && parsed >= 0 ? parsed : fallback;
|
|
1956
|
+
}
|
|
1957
|
+
function parseNumberField(raw, fallback) {
|
|
1958
|
+
const parsed = Number.parseFloat(raw);
|
|
1959
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
1960
|
+
}
|
|
1609
1961
|
//#endregion
|
|
1610
1962
|
//#region src/tui/screens/shared/paragraph-family.tsx
|
|
1611
1963
|
function paragraphFamilyDocument(openDocument) {
|
|
@@ -1669,6 +2021,38 @@ function tableSummary(table) {
|
|
|
1669
2021
|
function listSummary(list, index) {
|
|
1670
2022
|
return `List ${index} (${list.itemCount} item${list.itemCount === 1 ? "" : "s"})`;
|
|
1671
2023
|
}
|
|
2024
|
+
const DEFAULT_TABLE_ROWS$1 = 2;
|
|
2025
|
+
const DEFAULT_TABLE_COLUMNS$1 = 2;
|
|
2026
|
+
const FORMULA_FRAME_FIELDS = [
|
|
2027
|
+
{
|
|
2028
|
+
key: "xPt",
|
|
2029
|
+
label: "X (pt)",
|
|
2030
|
+
defaultValue: "40"
|
|
2031
|
+
},
|
|
2032
|
+
{
|
|
2033
|
+
key: "yPt",
|
|
2034
|
+
label: "Y (pt)",
|
|
2035
|
+
defaultValue: "40"
|
|
2036
|
+
},
|
|
2037
|
+
{
|
|
2038
|
+
key: "widthPt",
|
|
2039
|
+
label: "Width (pt)",
|
|
2040
|
+
defaultValue: "120"
|
|
2041
|
+
},
|
|
2042
|
+
{
|
|
2043
|
+
key: "heightPt",
|
|
2044
|
+
label: "Height (pt)",
|
|
2045
|
+
defaultValue: "40"
|
|
2046
|
+
}
|
|
2047
|
+
];
|
|
2048
|
+
function readFormulaFrame(values) {
|
|
2049
|
+
return {
|
|
2050
|
+
xPt: parseNumberField(requireFieldValue(values, "xPt"), 0),
|
|
2051
|
+
yPt: parseNumberField(requireFieldValue(values, "yPt"), 0),
|
|
2052
|
+
widthPt: parseNumberField(requireFieldValue(values, "widthPt"), 120),
|
|
2053
|
+
heightPt: parseNumberField(requireFieldValue(values, "heightPt"), 40)
|
|
2054
|
+
};
|
|
2055
|
+
}
|
|
1672
2056
|
function ParagraphFamilyBodyList(props) {
|
|
1673
2057
|
const { adapter } = props;
|
|
1674
2058
|
const state = useAppState();
|
|
@@ -1711,9 +2095,94 @@ function ParagraphFamilyBodyList(props) {
|
|
|
1711
2095
|
if (row.kind !== "header") acc.push(rowIndex);
|
|
1712
2096
|
return acc;
|
|
1713
2097
|
}, []);
|
|
2098
|
+
const [tableWizard, setTableWizard] = useState("closed");
|
|
2099
|
+
const [wizardDraft, setWizardDraft] = useState("");
|
|
2100
|
+
const [wizardRows, setWizardRows] = useState(DEFAULT_TABLE_ROWS$1);
|
|
2101
|
+
const [wizardColumns, setWizardColumns] = useState(DEFAULT_TABLE_COLUMNS$1);
|
|
2102
|
+
const [wizardStartRow, setWizardStartRow] = useState(0);
|
|
2103
|
+
const [wizardStartColumn, setWizardStartColumn] = useState(0);
|
|
2104
|
+
const [wizardRowSpan, setWizardRowSpan] = useState(1);
|
|
2105
|
+
const wizardOpen = tableWizard !== "closed";
|
|
2106
|
+
const [formulaFlow, setFormulaFlow] = useState("closed");
|
|
2107
|
+
const [pendingFormulaMathml, setPendingFormulaMathml] = useState(void 0);
|
|
2108
|
+
const formulaFlowOpen = formulaFlow !== "closed";
|
|
2109
|
+
const closeWizard = () => {
|
|
2110
|
+
setTableWizard("closed");
|
|
2111
|
+
};
|
|
2112
|
+
const commitAppendTable = (merge) => {
|
|
2113
|
+
const newIndex = tables.length;
|
|
2114
|
+
dispatch({
|
|
2115
|
+
type: "APPEND_TABLE",
|
|
2116
|
+
rows: wizardRows,
|
|
2117
|
+
columns: wizardColumns,
|
|
2118
|
+
merge
|
|
2119
|
+
});
|
|
2120
|
+
closeWizard();
|
|
2121
|
+
dispatch({
|
|
2122
|
+
type: "PUSH_SCREEN",
|
|
2123
|
+
screen: {
|
|
2124
|
+
kind: "tableView",
|
|
2125
|
+
blockIndex: newIndex
|
|
2126
|
+
}
|
|
2127
|
+
});
|
|
2128
|
+
};
|
|
2129
|
+
useInput((input) => {
|
|
2130
|
+
if (input === "T") {
|
|
2131
|
+
setWizardDraft(String(DEFAULT_TABLE_ROWS$1));
|
|
2132
|
+
setTableWizard("rows");
|
|
2133
|
+
}
|
|
2134
|
+
}, { isActive: !anyOverlayOpen(state) && !wizardOpen && !formulaFlowOpen });
|
|
2135
|
+
useInput((input) => {
|
|
2136
|
+
if (input === "m" && adapter.formatLabel === "odt") setFormulaFlow("picking");
|
|
2137
|
+
}, { isActive: !anyOverlayOpen(state) && !wizardOpen && !formulaFlowOpen });
|
|
2138
|
+
useInput((input, key) => {
|
|
2139
|
+
if (key.escape) {
|
|
2140
|
+
closeWizard();
|
|
2141
|
+
return;
|
|
2142
|
+
}
|
|
2143
|
+
if (input === "y" || input === "Y") {
|
|
2144
|
+
setWizardDraft("0");
|
|
2145
|
+
setTableWizard("mergeStartRow");
|
|
2146
|
+
return;
|
|
2147
|
+
}
|
|
2148
|
+
if (input === "n" || input === "N" || key.return) commitAppendTable(void 0);
|
|
2149
|
+
}, { isActive: !anyOverlayOpen(state) && tableWizard === "mergePrompt" });
|
|
2150
|
+
const submitWizardRows = (raw) => {
|
|
2151
|
+
setWizardRows(parsePositiveIntField(raw, DEFAULT_TABLE_ROWS$1));
|
|
2152
|
+
setWizardDraft(String(DEFAULT_TABLE_COLUMNS$1));
|
|
2153
|
+
setTableWizard("columns");
|
|
2154
|
+
};
|
|
2155
|
+
const submitWizardColumns = (raw) => {
|
|
2156
|
+
setWizardColumns(parsePositiveIntField(raw, DEFAULT_TABLE_COLUMNS$1));
|
|
2157
|
+
setTableWizard("mergePrompt");
|
|
2158
|
+
};
|
|
2159
|
+
const submitWizardMergeStartRow = (raw) => {
|
|
2160
|
+
setWizardStartRow(Math.min(parseNonNegativeIntField(raw, 0), Math.max(0, wizardRows - 1)));
|
|
2161
|
+
setWizardDraft("0");
|
|
2162
|
+
setTableWizard("mergeStartColumn");
|
|
2163
|
+
};
|
|
2164
|
+
const submitWizardMergeStartColumn = (raw) => {
|
|
2165
|
+
setWizardStartColumn(Math.min(parseNonNegativeIntField(raw, 0), Math.max(0, wizardColumns - 1)));
|
|
2166
|
+
setWizardDraft("1");
|
|
2167
|
+
setTableWizard("mergeRowSpan");
|
|
2168
|
+
};
|
|
2169
|
+
const submitWizardMergeRowSpan = (raw) => {
|
|
2170
|
+
setWizardRowSpan(Math.min(parsePositiveIntField(raw, 1), Math.max(1, wizardRows - wizardStartRow)));
|
|
2171
|
+
setWizardDraft("1");
|
|
2172
|
+
setTableWizard("mergeColSpan");
|
|
2173
|
+
};
|
|
2174
|
+
const submitWizardMergeColSpan = (raw) => {
|
|
2175
|
+
const colSpan = Math.min(parsePositiveIntField(raw, 1), Math.max(1, wizardColumns - wizardStartColumn));
|
|
2176
|
+
commitAppendTable({
|
|
2177
|
+
startRow: wizardStartRow,
|
|
2178
|
+
startColumn: wizardStartColumn,
|
|
2179
|
+
rowSpan: wizardRowSpan,
|
|
2180
|
+
colSpan
|
|
2181
|
+
});
|
|
2182
|
+
};
|
|
1714
2183
|
const { selectedIndex } = usePersistedSelection(selectionKeyFor({ kind: "bodyList" }), {
|
|
1715
2184
|
itemCount: selectableRowIndices.length,
|
|
1716
|
-
isActive: !anyOverlayOpen(state),
|
|
2185
|
+
isActive: !anyOverlayOpen(state) && !wizardOpen && !formulaFlowOpen,
|
|
1717
2186
|
onBack: () => {
|
|
1718
2187
|
dispatch({ type: "POP_SCREEN" });
|
|
1719
2188
|
},
|
|
@@ -1800,9 +2269,126 @@ function ParagraphFamilyBodyList(props) {
|
|
|
1800
2269
|
});
|
|
1801
2270
|
}
|
|
1802
2271
|
}),
|
|
1803
|
-
/* @__PURE__ */ jsx(Text, {
|
|
2272
|
+
tableWizard === "rows" ? /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, {
|
|
2273
|
+
color: "cyan",
|
|
2274
|
+
children: "Rows: "
|
|
2275
|
+
}), /* @__PURE__ */ jsx(TextField, {
|
|
2276
|
+
value: wizardDraft,
|
|
2277
|
+
isFocused: true,
|
|
2278
|
+
onChange: setWizardDraft,
|
|
2279
|
+
onSubmit: submitWizardRows,
|
|
2280
|
+
onCancel: closeWizard
|
|
2281
|
+
})] }) : void 0,
|
|
2282
|
+
tableWizard === "columns" ? /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, {
|
|
2283
|
+
color: "cyan",
|
|
2284
|
+
children: "Columns: "
|
|
2285
|
+
}), /* @__PURE__ */ jsx(TextField, {
|
|
2286
|
+
value: wizardDraft,
|
|
2287
|
+
isFocused: true,
|
|
2288
|
+
onChange: setWizardDraft,
|
|
2289
|
+
onSubmit: submitWizardColumns,
|
|
2290
|
+
onCancel: closeWizard
|
|
2291
|
+
})] }) : void 0,
|
|
2292
|
+
tableWizard === "mergePrompt" ? /* @__PURE__ */ jsx(Text, {
|
|
2293
|
+
color: "cyan",
|
|
2294
|
+
children: "Merge cells now? y/N"
|
|
2295
|
+
}) : void 0,
|
|
2296
|
+
tableWizard === "mergeStartRow" ? /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsxs(Text, {
|
|
2297
|
+
color: "cyan",
|
|
2298
|
+
children: [
|
|
2299
|
+
"Merge start row (0-",
|
|
2300
|
+
Math.max(0, wizardRows - 1),
|
|
2301
|
+
"): "
|
|
2302
|
+
]
|
|
2303
|
+
}), /* @__PURE__ */ jsx(TextField, {
|
|
2304
|
+
value: wizardDraft,
|
|
2305
|
+
isFocused: true,
|
|
2306
|
+
onChange: setWizardDraft,
|
|
2307
|
+
onSubmit: submitWizardMergeStartRow,
|
|
2308
|
+
onCancel: closeWizard
|
|
2309
|
+
})] }) : void 0,
|
|
2310
|
+
tableWizard === "mergeStartColumn" ? /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsxs(Text, {
|
|
2311
|
+
color: "cyan",
|
|
2312
|
+
children: [
|
|
2313
|
+
"Merge start column (0-",
|
|
2314
|
+
Math.max(0, wizardColumns - 1),
|
|
2315
|
+
"): "
|
|
2316
|
+
]
|
|
2317
|
+
}), /* @__PURE__ */ jsx(TextField, {
|
|
2318
|
+
value: wizardDraft,
|
|
2319
|
+
isFocused: true,
|
|
2320
|
+
onChange: setWizardDraft,
|
|
2321
|
+
onSubmit: submitWizardMergeStartColumn,
|
|
2322
|
+
onCancel: closeWizard
|
|
2323
|
+
})] }) : void 0,
|
|
2324
|
+
tableWizard === "mergeRowSpan" ? /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsxs(Text, {
|
|
2325
|
+
color: "cyan",
|
|
2326
|
+
children: [
|
|
2327
|
+
"Merge row span (1-",
|
|
2328
|
+
Math.max(1, wizardRows - wizardStartRow),
|
|
2329
|
+
"): "
|
|
2330
|
+
]
|
|
2331
|
+
}), /* @__PURE__ */ jsx(TextField, {
|
|
2332
|
+
value: wizardDraft,
|
|
2333
|
+
isFocused: true,
|
|
2334
|
+
onChange: setWizardDraft,
|
|
2335
|
+
onSubmit: submitWizardMergeRowSpan,
|
|
2336
|
+
onCancel: closeWizard
|
|
2337
|
+
})] }) : void 0,
|
|
2338
|
+
tableWizard === "mergeColSpan" ? /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsxs(Text, {
|
|
2339
|
+
color: "cyan",
|
|
2340
|
+
children: [
|
|
2341
|
+
"Merge column span (1-",
|
|
2342
|
+
Math.max(1, wizardColumns - wizardStartColumn),
|
|
2343
|
+
"): "
|
|
2344
|
+
]
|
|
2345
|
+
}), /* @__PURE__ */ jsx(TextField, {
|
|
2346
|
+
value: wizardDraft,
|
|
2347
|
+
isFocused: true,
|
|
2348
|
+
onChange: setWizardDraft,
|
|
2349
|
+
onSubmit: submitWizardMergeColSpan,
|
|
2350
|
+
onCancel: closeWizard
|
|
2351
|
+
})] }) : void 0,
|
|
2352
|
+
formulaFlow === "picking" ? /* @__PURE__ */ jsx(FormulaPicker, {
|
|
2353
|
+
isActive: !anyOverlayOpen(state),
|
|
2354
|
+
onCancel: () => {
|
|
2355
|
+
setFormulaFlow("closed");
|
|
2356
|
+
},
|
|
2357
|
+
onMathml: (mathml) => {
|
|
2358
|
+
setPendingFormulaMathml(mathml);
|
|
2359
|
+
setFormulaFlow("frame");
|
|
2360
|
+
},
|
|
2361
|
+
onInvalidRawMathml: (message) => {
|
|
2362
|
+
dispatch({
|
|
2363
|
+
type: "SET_STATUS",
|
|
2364
|
+
severity: "warning",
|
|
2365
|
+
text: `Could not parse MathML: ${message}`
|
|
2366
|
+
});
|
|
2367
|
+
}
|
|
2368
|
+
}) : void 0,
|
|
2369
|
+
formulaFlow === "frame" ? /* @__PURE__ */ jsx(FieldWizard, {
|
|
2370
|
+
fields: FORMULA_FRAME_FIELDS,
|
|
2371
|
+
onCancel: () => {
|
|
2372
|
+
setFormulaFlow("closed");
|
|
2373
|
+
setPendingFormulaMathml(void 0);
|
|
2374
|
+
},
|
|
2375
|
+
onComplete: (values) => {
|
|
2376
|
+
if (pendingFormulaMathml !== void 0) dispatch({
|
|
2377
|
+
type: "INSERT_ODT_FORMULA",
|
|
2378
|
+
mathml: pendingFormulaMathml,
|
|
2379
|
+
frame: readFormulaFrame(values)
|
|
2380
|
+
});
|
|
2381
|
+
setFormulaFlow("closed");
|
|
2382
|
+
setPendingFormulaMathml(void 0);
|
|
2383
|
+
}
|
|
2384
|
+
}) : void 0,
|
|
2385
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
1804
2386
|
dimColor: true,
|
|
1805
|
-
children:
|
|
2387
|
+
children: [
|
|
2388
|
+
"Enter to open, a to append a paragraph, T to append a table",
|
|
2389
|
+
adapter.formatLabel === "odt" ? ", m to insert a formula" : "",
|
|
2390
|
+
", Esc back"
|
|
2391
|
+
]
|
|
1806
2392
|
})
|
|
1807
2393
|
]
|
|
1808
2394
|
});
|
|
@@ -1822,6 +2408,66 @@ function isValidHexColorInput(input) {
|
|
|
1822
2408
|
}
|
|
1823
2409
|
//#endregion
|
|
1824
2410
|
//#region src/tui/screens/editors/docx/paragraph-detail.tsx
|
|
2411
|
+
const IMAGE_FIELDS = [
|
|
2412
|
+
{
|
|
2413
|
+
key: "path",
|
|
2414
|
+
label: "Image file path (.png/.jpg/.jpeg)",
|
|
2415
|
+
defaultValue: ""
|
|
2416
|
+
},
|
|
2417
|
+
{
|
|
2418
|
+
key: "widthPt",
|
|
2419
|
+
label: "Width (pt)",
|
|
2420
|
+
defaultValue: "100"
|
|
2421
|
+
},
|
|
2422
|
+
{
|
|
2423
|
+
key: "heightPt",
|
|
2424
|
+
label: "Height (pt)",
|
|
2425
|
+
defaultValue: "60"
|
|
2426
|
+
},
|
|
2427
|
+
{
|
|
2428
|
+
key: "altText",
|
|
2429
|
+
label: "Alt text, blank for none",
|
|
2430
|
+
defaultValue: ""
|
|
2431
|
+
}
|
|
2432
|
+
];
|
|
2433
|
+
function inferImageFormat$1(path) {
|
|
2434
|
+
const extension = path.slice(path.lastIndexOf(".") + 1).toLowerCase();
|
|
2435
|
+
if (extension === "png") return "png";
|
|
2436
|
+
if (extension === "jpg" || extension === "jpeg") return "jpeg";
|
|
2437
|
+
}
|
|
2438
|
+
async function applyInsertImage(blockIndex, values, dispatch) {
|
|
2439
|
+
const path = requireFieldValue(values, "path");
|
|
2440
|
+
const format = inferImageFormat$1(path);
|
|
2441
|
+
if (format === void 0) {
|
|
2442
|
+
dispatch({
|
|
2443
|
+
type: "SET_STATUS",
|
|
2444
|
+
severity: "warning",
|
|
2445
|
+
text: `${path} is not a .png or .jpg/.jpeg file -- image not inserted`
|
|
2446
|
+
});
|
|
2447
|
+
return;
|
|
2448
|
+
}
|
|
2449
|
+
try {
|
|
2450
|
+
const bytes = new Uint8Array(await readInput(path));
|
|
2451
|
+
const widthPt = parseNumberField(requireFieldValue(values, "widthPt"), 100);
|
|
2452
|
+
const heightPt = parseNumberField(requireFieldValue(values, "heightPt"), 60);
|
|
2453
|
+
const altTextRaw = requireFieldValue(values, "altText").trim();
|
|
2454
|
+
dispatch({
|
|
2455
|
+
type: "INSERT_PARAGRAPH_IMAGE",
|
|
2456
|
+
blockIndex,
|
|
2457
|
+
format,
|
|
2458
|
+
bytes,
|
|
2459
|
+
widthPt,
|
|
2460
|
+
heightPt,
|
|
2461
|
+
altText: altTextRaw.length === 0 ? void 0 : altTextRaw
|
|
2462
|
+
});
|
|
2463
|
+
} catch (error) {
|
|
2464
|
+
dispatch({
|
|
2465
|
+
type: "SET_STATUS",
|
|
2466
|
+
severity: "error",
|
|
2467
|
+
text: `Could not read ${path}: ${describeError(error)}`
|
|
2468
|
+
});
|
|
2469
|
+
}
|
|
2470
|
+
}
|
|
1825
2471
|
function ParagraphRunsView(props) {
|
|
1826
2472
|
if (props.runs.length === 0) return /* @__PURE__ */ jsx(Text, {
|
|
1827
2473
|
dimColor: true,
|
|
@@ -1841,6 +2487,8 @@ function ParagraphDetailScreen() {
|
|
|
1841
2487
|
const dispatch = useAppDispatch();
|
|
1842
2488
|
const [runIndex, setRunIndex] = useState(0);
|
|
1843
2489
|
const [colorInput, setColorInput] = useState(void 0);
|
|
2490
|
+
const [imageWizardOpen, setImageWizardOpen] = useState(false);
|
|
2491
|
+
const [formulaPickerOpen, setFormulaPickerOpen] = useState(false);
|
|
1844
2492
|
const screen = currentScreen(state);
|
|
1845
2493
|
const doc = paragraphFamilyDocument(state.openDocument);
|
|
1846
2494
|
const paragraph = screen.kind === "paragraphDetail" && doc !== void 0 ? liveParagraphAt(doc, screen.blockIndex) : void 0;
|
|
@@ -1903,6 +2551,14 @@ function ParagraphDetailScreen() {
|
|
|
1903
2551
|
});
|
|
1904
2552
|
return;
|
|
1905
2553
|
}
|
|
2554
|
+
if (input === "I") {
|
|
2555
|
+
setImageWizardOpen(true);
|
|
2556
|
+
return;
|
|
2557
|
+
}
|
|
2558
|
+
if (input === "m" && doc?.format === "docx") {
|
|
2559
|
+
setFormulaPickerOpen(true);
|
|
2560
|
+
return;
|
|
2561
|
+
}
|
|
1906
2562
|
if (selectedRun === void 0) return;
|
|
1907
2563
|
if (input === "b") {
|
|
1908
2564
|
dispatch({
|
|
@@ -1929,7 +2585,7 @@ function ParagraphDetailScreen() {
|
|
|
1929
2585
|
return;
|
|
1930
2586
|
}
|
|
1931
2587
|
if (input === "c") setColorInput(selectedRun.color === void 0 ? "" : layoutColorToHex(selectedRun.color).slice(1));
|
|
1932
|
-
}, { isActive: !anyOverlayOpen(state) && colorInput === void 0 });
|
|
2588
|
+
}, { isActive: !anyOverlayOpen(state) && colorInput === void 0 && !imageWizardOpen && !formulaPickerOpen });
|
|
1933
2589
|
if (screen.kind !== "paragraphDetail") return /* @__PURE__ */ jsx(Text, {
|
|
1934
2590
|
color: "red",
|
|
1935
2591
|
children: "ParagraphDetailScreen rendered outside a paragraphDetail screen."
|
|
@@ -1987,9 +2643,45 @@ function ParagraphDetailScreen() {
|
|
|
1987
2643
|
setColorInput(void 0);
|
|
1988
2644
|
}
|
|
1989
2645
|
})] }),
|
|
1990
|
-
/* @__PURE__ */ jsx(
|
|
2646
|
+
imageWizardOpen ? /* @__PURE__ */ jsx(FieldWizard, {
|
|
2647
|
+
fields: IMAGE_FIELDS,
|
|
2648
|
+
onCancel: () => {
|
|
2649
|
+
setImageWizardOpen(false);
|
|
2650
|
+
},
|
|
2651
|
+
onComplete: (values) => {
|
|
2652
|
+
applyInsertImage(blockIndex, values, dispatch).then(() => {
|
|
2653
|
+
setImageWizardOpen(false);
|
|
2654
|
+
});
|
|
2655
|
+
}
|
|
2656
|
+
}) : void 0,
|
|
2657
|
+
formulaPickerOpen ? /* @__PURE__ */ jsx(FormulaPicker, {
|
|
2658
|
+
isActive: !anyOverlayOpen(state),
|
|
2659
|
+
onCancel: () => {
|
|
2660
|
+
setFormulaPickerOpen(false);
|
|
2661
|
+
},
|
|
2662
|
+
onMathml: (mathml) => {
|
|
2663
|
+
dispatch({
|
|
2664
|
+
type: "INSERT_DOCX_FORMULA",
|
|
2665
|
+
blockIndex,
|
|
2666
|
+
mathml
|
|
2667
|
+
});
|
|
2668
|
+
setFormulaPickerOpen(false);
|
|
2669
|
+
},
|
|
2670
|
+
onInvalidRawMathml: (message) => {
|
|
2671
|
+
dispatch({
|
|
2672
|
+
type: "SET_STATUS",
|
|
2673
|
+
severity: "warning",
|
|
2674
|
+
text: `Could not parse MathML: ${message}`
|
|
2675
|
+
});
|
|
2676
|
+
}
|
|
2677
|
+
}) : void 0,
|
|
2678
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
1991
2679
|
dimColor: true,
|
|
1992
|
-
children:
|
|
2680
|
+
children: [
|
|
2681
|
+
"<- / -> move, Enter edit text, b/i/u toggle, c colour, a append run, I image",
|
|
2682
|
+
doc.format === "docx" ? ", m formula" : "",
|
|
2683
|
+
", Esc back"
|
|
2684
|
+
]
|
|
1993
2685
|
})
|
|
1994
2686
|
]
|
|
1995
2687
|
});
|
|
@@ -2079,7 +2771,7 @@ function RunEditorScreen() {
|
|
|
2079
2771
|
}
|
|
2080
2772
|
//#endregion
|
|
2081
2773
|
//#region src/tui/screens/editors/docx/table-view.tsx
|
|
2082
|
-
const CELL_WIDTH$
|
|
2774
|
+
const CELL_WIDTH$2 = 16;
|
|
2083
2775
|
function TableViewScreen() {
|
|
2084
2776
|
const state = useAppState();
|
|
2085
2777
|
const dispatch = useAppDispatch();
|
|
@@ -2087,6 +2779,7 @@ function TableViewScreen() {
|
|
|
2087
2779
|
row: 0,
|
|
2088
2780
|
column: 0
|
|
2089
2781
|
});
|
|
2782
|
+
const [mergeAnchor, setMergeAnchor] = useState(void 0);
|
|
2090
2783
|
const screen = currentScreen(state);
|
|
2091
2784
|
const doc = paragraphFamilyDocument(state.openDocument);
|
|
2092
2785
|
const table = screen.kind === "tableView" && doc !== void 0 ? liveTableAt(doc, screen.blockIndex) : void 0;
|
|
@@ -2095,6 +2788,22 @@ function TableViewScreen() {
|
|
|
2095
2788
|
const columnCount = rows[0]?.cells().length ?? 0;
|
|
2096
2789
|
const clampedRow = rowCount === 0 ? 0 : Math.min(cursor.row, rowCount - 1);
|
|
2097
2790
|
const clampedColumn = columnCount === 0 ? 0 : Math.min(cursor.column, columnCount - 1);
|
|
2791
|
+
const commitMerge = (anchor) => {
|
|
2792
|
+
if (screen.kind !== "tableView") return;
|
|
2793
|
+
const startRow = Math.min(anchor.row, clampedRow);
|
|
2794
|
+
const startColumn = Math.min(anchor.column, clampedColumn);
|
|
2795
|
+
const rowSpan = Math.abs(clampedRow - anchor.row) + 1;
|
|
2796
|
+
const colSpan = Math.abs(clampedColumn - anchor.column) + 1;
|
|
2797
|
+
dispatch({
|
|
2798
|
+
type: "MERGE_TABLE_CELLS",
|
|
2799
|
+
tableIndex: screen.blockIndex,
|
|
2800
|
+
startRow,
|
|
2801
|
+
startColumn,
|
|
2802
|
+
rowSpan,
|
|
2803
|
+
colSpan
|
|
2804
|
+
});
|
|
2805
|
+
setMergeAnchor(void 0);
|
|
2806
|
+
};
|
|
2098
2807
|
useInput((input, key) => {
|
|
2099
2808
|
if (table === void 0 || screen.kind !== "tableView") return;
|
|
2100
2809
|
if (key.upArrow || input === "k") {
|
|
@@ -2126,18 +2835,36 @@ function TableViewScreen() {
|
|
|
2126
2835
|
return;
|
|
2127
2836
|
}
|
|
2128
2837
|
if (key.escape) {
|
|
2838
|
+
if (mergeAnchor !== void 0) {
|
|
2839
|
+
setMergeAnchor(void 0);
|
|
2840
|
+
return;
|
|
2841
|
+
}
|
|
2129
2842
|
dispatch({ type: "POP_SCREEN" });
|
|
2130
2843
|
return;
|
|
2131
2844
|
}
|
|
2132
|
-
if (
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2845
|
+
if (input === "m" && rowCount > 0 && columnCount > 0) {
|
|
2846
|
+
if (mergeAnchor === void 0) setMergeAnchor({
|
|
2847
|
+
row: clampedRow,
|
|
2848
|
+
column: clampedColumn
|
|
2849
|
+
});
|
|
2850
|
+
else commitMerge(mergeAnchor);
|
|
2851
|
+
return;
|
|
2852
|
+
}
|
|
2853
|
+
if (key.return && rowCount > 0 && columnCount > 0) {
|
|
2854
|
+
if (mergeAnchor !== void 0) {
|
|
2855
|
+
commitMerge(mergeAnchor);
|
|
2856
|
+
return;
|
|
2139
2857
|
}
|
|
2140
|
-
|
|
2858
|
+
dispatch({
|
|
2859
|
+
type: "PUSH_SCREEN",
|
|
2860
|
+
screen: {
|
|
2861
|
+
kind: "tableCellDetail",
|
|
2862
|
+
blockIndex: screen.blockIndex,
|
|
2863
|
+
row: clampedRow,
|
|
2864
|
+
col: clampedColumn
|
|
2865
|
+
}
|
|
2866
|
+
});
|
|
2867
|
+
}
|
|
2141
2868
|
}, { isActive: !anyOverlayOpen(state) });
|
|
2142
2869
|
if (screen.kind !== "tableView") return /* @__PURE__ */ jsx(Text, {
|
|
2143
2870
|
color: "red",
|
|
@@ -2175,10 +2902,11 @@ function TableViewScreen() {
|
|
|
2175
2902
|
children: "This table has no rows."
|
|
2176
2903
|
}) : rows.map((row, rowIndex) => /* @__PURE__ */ jsx(Box, { children: row.cells().map((cell, columnIndex) => {
|
|
2177
2904
|
const isSelected = rowIndex === clampedRow && columnIndex === clampedColumn;
|
|
2905
|
+
const isAnchor = rowIndex === mergeAnchor?.row && columnIndex === mergeAnchor?.column;
|
|
2178
2906
|
return /* @__PURE__ */ jsx(Box, {
|
|
2179
|
-
width: CELL_WIDTH$
|
|
2907
|
+
width: CELL_WIDTH$2,
|
|
2180
2908
|
borderStyle: "single",
|
|
2181
|
-
borderColor: isSelected ? "cyan" : "gray",
|
|
2909
|
+
borderColor: isSelected ? "cyan" : isAnchor ? "yellow" : "gray",
|
|
2182
2910
|
children: /* @__PURE__ */ jsx(Text, {
|
|
2183
2911
|
color: isSelected ? "cyan" : void 0,
|
|
2184
2912
|
inverse: isSelected,
|
|
@@ -2188,7 +2916,7 @@ function TableViewScreen() {
|
|
|
2188
2916
|
}) }, rowIndex)),
|
|
2189
2917
|
/* @__PURE__ */ jsx(Text, {
|
|
2190
2918
|
dimColor: true,
|
|
2191
|
-
children: "Arrows/hjkl move, Enter to edit a cell, Esc back"
|
|
2919
|
+
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"
|
|
2192
2920
|
})
|
|
2193
2921
|
]
|
|
2194
2922
|
});
|
|
@@ -3092,10 +3820,6 @@ function describeFillStroke(vector) {
|
|
|
3092
3820
|
if (vector.stroke !== void 0) parts.push(`stroke ${formatColor$1(vector.stroke.color)} ${formatPt(vector.stroke.widthPt)}pt`);
|
|
3093
3821
|
return parts.length === 0 ? "no fill or stroke" : parts.join(", ");
|
|
3094
3822
|
}
|
|
3095
|
-
function parseNumberField(raw, fallback) {
|
|
3096
|
-
const parsed = Number.parseFloat(raw);
|
|
3097
|
-
return Number.isFinite(parsed) ? parsed : fallback;
|
|
3098
|
-
}
|
|
3099
3823
|
function parseColorField(raw) {
|
|
3100
3824
|
const trimmed = raw.trim();
|
|
3101
3825
|
if (trimmed.length === 0) return;
|
|
@@ -3152,11 +3876,6 @@ function defaultTriangleSubpaths(widthPt, heightPt) {
|
|
|
3152
3876
|
closed: true
|
|
3153
3877
|
}];
|
|
3154
3878
|
}
|
|
3155
|
-
function requireFieldValue(values, key) {
|
|
3156
|
-
const value = values[key];
|
|
3157
|
-
if (value === void 0) throw new Error(`Add-item field '${key}' was never recorded before building the action.`);
|
|
3158
|
-
return value;
|
|
3159
|
-
}
|
|
3160
3879
|
//#endregion
|
|
3161
3880
|
//#region src/tui/screens/editors/odg/page-list.tsx
|
|
3162
3881
|
function OdgPageListScreen() {
|
|
@@ -3463,56 +4182,6 @@ async function applyAddKind(kind, pageIndex, doc, values, dispatch) {
|
|
|
3463
4182
|
}
|
|
3464
4183
|
}
|
|
3465
4184
|
}
|
|
3466
|
-
function FieldWizard(props) {
|
|
3467
|
-
const [stepIndex, setStepIndex] = useState(0);
|
|
3468
|
-
const [collected, setCollected] = useState({});
|
|
3469
|
-
const initialField = props.fields[0];
|
|
3470
|
-
const [draft, setDraft] = useState(initialField === void 0 ? "" : initialField.defaultValue);
|
|
3471
|
-
const field = props.fields[stepIndex];
|
|
3472
|
-
if (field === void 0) throw new Error(`FieldWizard stepIndex ${stepIndex} is out of range for ${props.fields.length} fields -- onComplete always fires before stepIndex can advance past the last field, so this indicates a bug in that advance.`);
|
|
3473
|
-
return /* @__PURE__ */ jsxs(Box, {
|
|
3474
|
-
flexDirection: "column",
|
|
3475
|
-
borderStyle: "round",
|
|
3476
|
-
paddingX: 1,
|
|
3477
|
-
children: [
|
|
3478
|
-
/* @__PURE__ */ jsx(Text, {
|
|
3479
|
-
bold: true,
|
|
3480
|
-
children: field.label
|
|
3481
|
-
}),
|
|
3482
|
-
/* @__PURE__ */ jsx(TextField, {
|
|
3483
|
-
value: draft,
|
|
3484
|
-
isFocused: true,
|
|
3485
|
-
onChange: setDraft,
|
|
3486
|
-
onCancel: props.onCancel,
|
|
3487
|
-
onSubmit: (value) => {
|
|
3488
|
-
const recorded = {
|
|
3489
|
-
...collected,
|
|
3490
|
-
[field.key]: value
|
|
3491
|
-
};
|
|
3492
|
-
const nextIndex = stepIndex + 1;
|
|
3493
|
-
const nextField = props.fields[nextIndex];
|
|
3494
|
-
if (nextField === void 0) {
|
|
3495
|
-
props.onComplete(recorded);
|
|
3496
|
-
return;
|
|
3497
|
-
}
|
|
3498
|
-
setCollected(recorded);
|
|
3499
|
-
setDraft(nextField.defaultValue);
|
|
3500
|
-
setStepIndex(nextIndex);
|
|
3501
|
-
}
|
|
3502
|
-
}),
|
|
3503
|
-
/* @__PURE__ */ jsxs(Text, {
|
|
3504
|
-
dimColor: true,
|
|
3505
|
-
children: [
|
|
3506
|
-
"Step ",
|
|
3507
|
-
stepIndex + 1,
|
|
3508
|
-
" of ",
|
|
3509
|
-
props.fields.length,
|
|
3510
|
-
" -- Enter to continue, Esc to cancel"
|
|
3511
|
-
]
|
|
3512
|
-
})
|
|
3513
|
-
]
|
|
3514
|
-
});
|
|
3515
|
-
}
|
|
3516
4185
|
function AddItemFlow(props) {
|
|
3517
4186
|
const dispatch = useAppDispatch();
|
|
3518
4187
|
const [kind, setKind] = useState(void 0);
|
|
@@ -4367,6 +5036,30 @@ function ShapeEditorScreen(props) {
|
|
|
4367
5036
|
});
|
|
4368
5037
|
}
|
|
4369
5038
|
//#endregion
|
|
5039
|
+
//#region src/tui/screens/shared/slide-table.ts
|
|
5040
|
+
function resolveSlideTable(doc, slideIndex, tableIndex) {
|
|
5041
|
+
const content = doc.format === "odp" ? readOdpContent(doc.editor.toPackage()) : readPptxContent(doc.editor.toPackage());
|
|
5042
|
+
if (content.kind !== "presentation") throw new Error("readPptxContent/readOdpContent always resolve a presentation package to the presentation ContentDocument variant.");
|
|
5043
|
+
const slide = content.slides[slideIndex];
|
|
5044
|
+
if (slide === void 0) return;
|
|
5045
|
+
return slide.shapes.flatMap((shape) => shape.blocks.filter((block) => block.kind === "table"))[tableIndex];
|
|
5046
|
+
}
|
|
5047
|
+
function slideTableCellText(cell) {
|
|
5048
|
+
return cell.blocks.filter((block) => block.kind === "paragraph").map((paragraph) => paragraph.runs.map((run) => run.text).join("")).join("\n");
|
|
5049
|
+
}
|
|
5050
|
+
function summarizeGridTable(table, index) {
|
|
5051
|
+
const rows = table.rows();
|
|
5052
|
+
return {
|
|
5053
|
+
index,
|
|
5054
|
+
rowCount: rows.length,
|
|
5055
|
+
columnCount: rows[0]?.cells().length ?? 0
|
|
5056
|
+
};
|
|
5057
|
+
}
|
|
5058
|
+
function summarizeSlideTables(doc, slideIndex) {
|
|
5059
|
+
if (doc.format === "odp") return (doc.editor.slides()[slideIndex]?.tables() ?? []).map((entry, index) => summarizeGridTable(entry.table, index));
|
|
5060
|
+
return (doc.editor.slides()[slideIndex]?.tables() ?? []).map((table, index) => summarizeGridTable(table, index));
|
|
5061
|
+
}
|
|
5062
|
+
//#endregion
|
|
4370
5063
|
//#region src/tui/screens/editors/pptx/slide-detail.tsx
|
|
4371
5064
|
const IMAGE_EXTENSION_TO_FORMAT = {
|
|
4372
5065
|
png: "png",
|
|
@@ -4375,10 +5068,6 @@ const IMAGE_EXTENSION_TO_FORMAT = {
|
|
|
4375
5068
|
};
|
|
4376
5069
|
const DEFAULT_TABLE_ROWS = 2;
|
|
4377
5070
|
const DEFAULT_TABLE_COLUMNS = 2;
|
|
4378
|
-
function parsePositiveIntField(raw, fallback) {
|
|
4379
|
-
const parsed = Number.parseInt(raw, 10);
|
|
4380
|
-
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
|
|
4381
|
-
}
|
|
4382
5071
|
function imageFormatFromPath(path) {
|
|
4383
5072
|
const dotIndex = path.lastIndexOf(".");
|
|
4384
5073
|
if (dotIndex < 0) return;
|
|
@@ -4399,34 +5088,63 @@ function SlideDetailScreen(props) {
|
|
|
4399
5088
|
const doc = assertPresentationDocument(state.openDocument);
|
|
4400
5089
|
const { slideIndex } = props.screen;
|
|
4401
5090
|
const slide = doc.editor.slides()[slideIndex];
|
|
4402
|
-
const
|
|
5091
|
+
const shapes = slide === void 0 ? [] : slide.shapes();
|
|
5092
|
+
const tableSummaries = slide === void 0 ? [] : summarizeSlideTables(doc, slideIndex);
|
|
5093
|
+
const rows = [...shapes.map((shape, index) => ({
|
|
5094
|
+
kind: "shape",
|
|
4403
5095
|
index,
|
|
4404
5096
|
text: shape.text,
|
|
4405
5097
|
frame: shape.frame
|
|
4406
|
-
}))
|
|
5098
|
+
})), ...tableSummaries.length > 0 ? [{
|
|
5099
|
+
kind: "tablesHeader",
|
|
5100
|
+
count: tableSummaries.length
|
|
5101
|
+
}, ...tableSummaries.map((summary) => ({
|
|
5102
|
+
kind: "table",
|
|
5103
|
+
index: summary.index,
|
|
5104
|
+
rowCount: summary.rowCount,
|
|
5105
|
+
columnCount: summary.columnCount
|
|
5106
|
+
}))] : []];
|
|
5107
|
+
const selectableRowIndices = rows.reduce((acc, row, index) => {
|
|
5108
|
+
if (row.kind !== "tablesHeader") acc.push(index);
|
|
5109
|
+
return acc;
|
|
5110
|
+
}, []);
|
|
4407
5111
|
const [addMode, setAddMode] = useState("closed");
|
|
4408
5112
|
const [draft, setDraft] = useState("");
|
|
4409
5113
|
const [imageError, setImageError] = useState(void 0);
|
|
4410
5114
|
const [tableRows, setTableRows] = useState(DEFAULT_TABLE_ROWS);
|
|
4411
5115
|
const formIsOpen = addMode !== "closed";
|
|
4412
5116
|
const { selectedIndex } = useNavigationInput({
|
|
4413
|
-
itemCount:
|
|
5117
|
+
itemCount: selectableRowIndices.length,
|
|
4414
5118
|
isActive: !overlayOpen && !formIsOpen,
|
|
4415
5119
|
onBack: () => {
|
|
4416
5120
|
dispatch({ type: "POP_SCREEN" });
|
|
4417
5121
|
},
|
|
4418
5122
|
onSelect: (index) => {
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
5123
|
+
const rowIndex = selectableRowIndices[index];
|
|
5124
|
+
const row = rowIndex === void 0 ? void 0 : rows[rowIndex];
|
|
5125
|
+
if (row === void 0) return;
|
|
5126
|
+
if (row.kind === "shape") {
|
|
5127
|
+
dispatch({
|
|
5128
|
+
type: "SET_SELECTION",
|
|
5129
|
+
key: selectionKeyFor(props.screen),
|
|
5130
|
+
index
|
|
5131
|
+
});
|
|
5132
|
+
dispatch({
|
|
5133
|
+
type: "PUSH_SCREEN",
|
|
5134
|
+
screen: {
|
|
5135
|
+
kind: "shapeEditor",
|
|
5136
|
+
slideIndex,
|
|
5137
|
+
shapeIndex: row.index
|
|
5138
|
+
}
|
|
5139
|
+
});
|
|
5140
|
+
return;
|
|
5141
|
+
}
|
|
5142
|
+
if (row.kind === "table") dispatch({
|
|
4425
5143
|
type: "PUSH_SCREEN",
|
|
4426
5144
|
screen: {
|
|
4427
|
-
kind: "
|
|
5145
|
+
kind: "slideTableDetail",
|
|
4428
5146
|
slideIndex,
|
|
4429
|
-
|
|
5147
|
+
tableIndex: row.index
|
|
4430
5148
|
}
|
|
4431
5149
|
});
|
|
4432
5150
|
},
|
|
@@ -4434,6 +5152,7 @@ function SlideDetailScreen(props) {
|
|
|
4434
5152
|
setAddMode("chooseKind");
|
|
4435
5153
|
}
|
|
4436
5154
|
});
|
|
5155
|
+
const listSelectedIndex = selectableRowIndices[selectedIndex] ?? -1;
|
|
4437
5156
|
useInput((input, key) => {
|
|
4438
5157
|
if (key.escape) {
|
|
4439
5158
|
setAddMode("closed");
|
|
@@ -4516,9 +5235,9 @@ function SlideDetailScreen(props) {
|
|
|
4516
5235
|
"Slide ",
|
|
4517
5236
|
slideIndex + 1,
|
|
4518
5237
|
" -- ",
|
|
4519
|
-
|
|
5238
|
+
shapes.length,
|
|
4520
5239
|
" shape",
|
|
4521
|
-
|
|
5240
|
+
shapes.length === 1 ? "" : "s"
|
|
4522
5241
|
]
|
|
4523
5242
|
}),
|
|
4524
5243
|
slide === void 0 ? /* @__PURE__ */ jsx(Text, {
|
|
@@ -4526,20 +5245,45 @@ function SlideDetailScreen(props) {
|
|
|
4526
5245
|
children: "This slide no longer exists -- press Esc to go back"
|
|
4527
5246
|
}) : /* @__PURE__ */ jsx(ListView, {
|
|
4528
5247
|
items: rows,
|
|
4529
|
-
selectedIndex,
|
|
5248
|
+
selectedIndex: listSelectedIndex,
|
|
4530
5249
|
emptyMessage: "No shapes yet -- press 'a' to add one",
|
|
4531
|
-
renderItem: (row, isSelected) =>
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
5250
|
+
renderItem: (row, isSelected) => {
|
|
5251
|
+
if (row.kind === "tablesHeader") return /* @__PURE__ */ jsxs(Text, {
|
|
5252
|
+
bold: true,
|
|
5253
|
+
dimColor: true,
|
|
5254
|
+
children: [
|
|
5255
|
+
"Tables (",
|
|
5256
|
+
row.count,
|
|
5257
|
+
")"
|
|
5258
|
+
]
|
|
5259
|
+
});
|
|
5260
|
+
if (row.kind === "table") return /* @__PURE__ */ jsxs(Text, {
|
|
5261
|
+
color: isSelected ? "cyan" : void 0,
|
|
5262
|
+
inverse: isSelected,
|
|
5263
|
+
children: [
|
|
5264
|
+
" ",
|
|
5265
|
+
"Table ",
|
|
5266
|
+
row.index + 1,
|
|
5267
|
+
" (",
|
|
5268
|
+
row.rowCount,
|
|
5269
|
+
"x",
|
|
5270
|
+
row.columnCount,
|
|
5271
|
+
")"
|
|
5272
|
+
]
|
|
5273
|
+
});
|
|
5274
|
+
return /* @__PURE__ */ jsxs(Text, {
|
|
5275
|
+
color: isSelected ? "cyan" : void 0,
|
|
5276
|
+
inverse: isSelected,
|
|
5277
|
+
children: [
|
|
5278
|
+
row.index + 1,
|
|
5279
|
+
". ",
|
|
5280
|
+
describeSlideFamilyShape({
|
|
5281
|
+
text: row.text,
|
|
5282
|
+
frame: row.frame
|
|
5283
|
+
})
|
|
5284
|
+
]
|
|
5285
|
+
});
|
|
5286
|
+
}
|
|
4543
5287
|
}),
|
|
4544
5288
|
addMode === "chooseKind" ? /* @__PURE__ */ jsx(Text, {
|
|
4545
5289
|
color: "cyan",
|
|
@@ -4607,6 +5351,146 @@ function SlideDetailScreen(props) {
|
|
|
4607
5351
|
});
|
|
4608
5352
|
}
|
|
4609
5353
|
//#endregion
|
|
5354
|
+
//#region src/tui/screens/editors/pptx/slide-table-detail.tsx
|
|
5355
|
+
const CELL_WIDTH$1 = 16;
|
|
5356
|
+
function SlideTableDetailScreen(props) {
|
|
5357
|
+
const state = useAppState();
|
|
5358
|
+
const dispatch = useAppDispatch();
|
|
5359
|
+
const doc = assertPresentationDocument(state.openDocument);
|
|
5360
|
+
const { slideIndex, tableIndex } = props.screen;
|
|
5361
|
+
const [cursor, setCursor] = useState({
|
|
5362
|
+
row: 0,
|
|
5363
|
+
column: 0
|
|
5364
|
+
});
|
|
5365
|
+
const [mergeAnchor, setMergeAnchor] = useState(void 0);
|
|
5366
|
+
const table = resolveSlideTable(doc, slideIndex, tableIndex);
|
|
5367
|
+
const rows = table?.rows ?? [];
|
|
5368
|
+
const rowCount = rows.length;
|
|
5369
|
+
const columnCount = rows[0]?.cells.length ?? 0;
|
|
5370
|
+
const clampedRow = rowCount === 0 ? 0 : Math.min(cursor.row, rowCount - 1);
|
|
5371
|
+
const clampedColumn = columnCount === 0 ? 0 : Math.min(cursor.column, columnCount - 1);
|
|
5372
|
+
const commitMerge = (anchor) => {
|
|
5373
|
+
const startRow = Math.min(anchor.row, clampedRow);
|
|
5374
|
+
const startColumn = Math.min(anchor.column, clampedColumn);
|
|
5375
|
+
const rowSpan = Math.abs(clampedRow - anchor.row) + 1;
|
|
5376
|
+
const colSpan = Math.abs(clampedColumn - anchor.column) + 1;
|
|
5377
|
+
dispatch({
|
|
5378
|
+
type: "MERGE_SLIDE_TABLE_CELLS",
|
|
5379
|
+
slideIndex,
|
|
5380
|
+
tableIndex,
|
|
5381
|
+
startRow,
|
|
5382
|
+
startColumn,
|
|
5383
|
+
rowSpan,
|
|
5384
|
+
colSpan
|
|
5385
|
+
});
|
|
5386
|
+
setMergeAnchor(void 0);
|
|
5387
|
+
};
|
|
5388
|
+
useInput((input, key) => {
|
|
5389
|
+
if (table === void 0) return;
|
|
5390
|
+
if (key.upArrow || input === "k") {
|
|
5391
|
+
setCursor({
|
|
5392
|
+
row: Math.max(0, clampedRow - 1),
|
|
5393
|
+
column: clampedColumn
|
|
5394
|
+
});
|
|
5395
|
+
return;
|
|
5396
|
+
}
|
|
5397
|
+
if (key.downArrow || input === "j") {
|
|
5398
|
+
setCursor({
|
|
5399
|
+
row: rowCount === 0 ? 0 : Math.min(rowCount - 1, clampedRow + 1),
|
|
5400
|
+
column: clampedColumn
|
|
5401
|
+
});
|
|
5402
|
+
return;
|
|
5403
|
+
}
|
|
5404
|
+
if (key.leftArrow || input === "h") {
|
|
5405
|
+
setCursor({
|
|
5406
|
+
row: clampedRow,
|
|
5407
|
+
column: Math.max(0, clampedColumn - 1)
|
|
5408
|
+
});
|
|
5409
|
+
return;
|
|
5410
|
+
}
|
|
5411
|
+
if (key.rightArrow || input === "l") {
|
|
5412
|
+
setCursor({
|
|
5413
|
+
row: clampedRow,
|
|
5414
|
+
column: columnCount === 0 ? 0 : Math.min(columnCount - 1, clampedColumn + 1)
|
|
5415
|
+
});
|
|
5416
|
+
return;
|
|
5417
|
+
}
|
|
5418
|
+
if (key.escape) {
|
|
5419
|
+
if (mergeAnchor !== void 0) {
|
|
5420
|
+
setMergeAnchor(void 0);
|
|
5421
|
+
return;
|
|
5422
|
+
}
|
|
5423
|
+
dispatch({ type: "POP_SCREEN" });
|
|
5424
|
+
return;
|
|
5425
|
+
}
|
|
5426
|
+
if (input === "m" || key.return) {
|
|
5427
|
+
if (mergeAnchor === void 0) {
|
|
5428
|
+
setMergeAnchor({
|
|
5429
|
+
row: clampedRow,
|
|
5430
|
+
column: clampedColumn
|
|
5431
|
+
});
|
|
5432
|
+
return;
|
|
5433
|
+
}
|
|
5434
|
+
commitMerge(mergeAnchor);
|
|
5435
|
+
}
|
|
5436
|
+
}, { isActive: !anyOverlayOpen(state) });
|
|
5437
|
+
if (table === void 0) return /* @__PURE__ */ jsxs(Box, {
|
|
5438
|
+
flexDirection: "column",
|
|
5439
|
+
children: [/* @__PURE__ */ jsxs(Text, {
|
|
5440
|
+
bold: true,
|
|
5441
|
+
children: [
|
|
5442
|
+
"Slide ",
|
|
5443
|
+
slideIndex + 1,
|
|
5444
|
+
", table ",
|
|
5445
|
+
tableIndex + 1
|
|
5446
|
+
]
|
|
5447
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
5448
|
+
color: "yellow",
|
|
5449
|
+
children: "This table no longer exists -- press Esc to go back"
|
|
5450
|
+
})]
|
|
5451
|
+
});
|
|
5452
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
5453
|
+
flexDirection: "column",
|
|
5454
|
+
children: [
|
|
5455
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
5456
|
+
bold: true,
|
|
5457
|
+
children: [
|
|
5458
|
+
"Slide ",
|
|
5459
|
+
slideIndex + 1,
|
|
5460
|
+
", table ",
|
|
5461
|
+
tableIndex + 1,
|
|
5462
|
+
" (",
|
|
5463
|
+
rowCount,
|
|
5464
|
+
"x",
|
|
5465
|
+
columnCount,
|
|
5466
|
+
")"
|
|
5467
|
+
]
|
|
5468
|
+
}),
|
|
5469
|
+
rows.length === 0 ? /* @__PURE__ */ jsx(Text, {
|
|
5470
|
+
dimColor: true,
|
|
5471
|
+
children: "This table has no rows."
|
|
5472
|
+
}) : rows.map((row, rowIndex) => /* @__PURE__ */ jsx(Box, { children: row.cells.map((cell, columnIndex) => {
|
|
5473
|
+
const isCursor = rowIndex === clampedRow && columnIndex === clampedColumn;
|
|
5474
|
+
const isAnchor = rowIndex === mergeAnchor?.row && columnIndex === mergeAnchor?.column;
|
|
5475
|
+
return /* @__PURE__ */ jsx(Box, {
|
|
5476
|
+
width: CELL_WIDTH$1,
|
|
5477
|
+
borderStyle: "single",
|
|
5478
|
+
borderColor: isCursor ? "cyan" : isAnchor ? "yellow" : "gray",
|
|
5479
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
5480
|
+
color: isCursor ? "cyan" : void 0,
|
|
5481
|
+
inverse: isCursor,
|
|
5482
|
+
children: truncatePreview(slideTableCellText(cell), 14)
|
|
5483
|
+
})
|
|
5484
|
+
}, columnIndex);
|
|
5485
|
+
}) }, rowIndex)),
|
|
5486
|
+
/* @__PURE__ */ jsx(Text, {
|
|
5487
|
+
dimColor: true,
|
|
5488
|
+
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"
|
|
5489
|
+
})
|
|
5490
|
+
]
|
|
5491
|
+
});
|
|
5492
|
+
}
|
|
5493
|
+
//#endregion
|
|
4610
5494
|
//#region src/tui/screens/editors/pptx/index.tsx
|
|
4611
5495
|
function PptxSlideListScreen() {
|
|
4612
5496
|
const doc = useAppState().openDocument;
|
|
@@ -5225,7 +6109,8 @@ const RESERVED_LETTERS = /* @__PURE__ */ new Set([
|
|
|
5225
6109
|
"k",
|
|
5226
6110
|
"l",
|
|
5227
6111
|
"p",
|
|
5228
|
-
"t"
|
|
6112
|
+
"t",
|
|
6113
|
+
"m"
|
|
5229
6114
|
]);
|
|
5230
6115
|
function windowStart(cursor, total, viewport) {
|
|
5231
6116
|
const maxStart = Math.max(0, total - viewport);
|
|
@@ -5248,6 +6133,7 @@ function OdsSpreadsheetGridScreen() {
|
|
|
5248
6133
|
const [cursorColumn, setCursorColumn] = useState(0);
|
|
5249
6134
|
const [viewMode, setViewMode] = useState("grid");
|
|
5250
6135
|
const [editSession, setEditSession] = useState(void 0);
|
|
6136
|
+
const [mergeAnchor, setMergeAnchor] = useState(void 0);
|
|
5251
6137
|
const { columns: terminalColumns, rows: terminalRows } = useWindowSize();
|
|
5252
6138
|
const sheet = resolveSheet(doc.editor, sheetIndex);
|
|
5253
6139
|
const { rowCount, columnCount } = sheetExtent(sheet);
|
|
@@ -5266,6 +6152,21 @@ function OdsSpreadsheetGridScreen() {
|
|
|
5266
6152
|
seedKind
|
|
5267
6153
|
});
|
|
5268
6154
|
}
|
|
6155
|
+
function commitMerge(anchor) {
|
|
6156
|
+
const startRow = Math.min(anchor.row, clampedRow);
|
|
6157
|
+
const startColumn = Math.min(anchor.column, clampedColumn);
|
|
6158
|
+
const rowSpan = Math.abs(clampedRow - anchor.row) + 1;
|
|
6159
|
+
const colSpan = Math.abs(clampedColumn - anchor.column) + 1;
|
|
6160
|
+
dispatch({
|
|
6161
|
+
type: "MERGE_CELLS",
|
|
6162
|
+
sheetIndex,
|
|
6163
|
+
startRow,
|
|
6164
|
+
startColumn,
|
|
6165
|
+
rowSpan,
|
|
6166
|
+
colSpan
|
|
6167
|
+
});
|
|
6168
|
+
setMergeAnchor(void 0);
|
|
6169
|
+
}
|
|
5269
6170
|
useInput((input) => {
|
|
5270
6171
|
if (input === "t") {
|
|
5271
6172
|
setViewMode((mode) => mode === "grid" ? "compact" : "grid");
|
|
@@ -5313,10 +6214,26 @@ function OdsSpreadsheetGridScreen() {
|
|
|
5313
6214
|
return;
|
|
5314
6215
|
}
|
|
5315
6216
|
if (key.escape) {
|
|
6217
|
+
if (mergeAnchor !== void 0) {
|
|
6218
|
+
setMergeAnchor(void 0);
|
|
6219
|
+
return;
|
|
6220
|
+
}
|
|
5316
6221
|
dispatch({ type: "POP_SCREEN" });
|
|
5317
6222
|
return;
|
|
5318
6223
|
}
|
|
6224
|
+
if (input === "m") {
|
|
6225
|
+
if (mergeAnchor === void 0) setMergeAnchor({
|
|
6226
|
+
row: clampedRow,
|
|
6227
|
+
column: clampedColumn
|
|
6228
|
+
});
|
|
6229
|
+
else commitMerge(mergeAnchor);
|
|
6230
|
+
return;
|
|
6231
|
+
}
|
|
5319
6232
|
if (key.return) {
|
|
6233
|
+
if (mergeAnchor !== void 0) {
|
|
6234
|
+
commitMerge(mergeAnchor);
|
|
6235
|
+
return;
|
|
6236
|
+
}
|
|
5320
6237
|
const cell = cells.get(cellKey(clampedRow, clampedColumn));
|
|
5321
6238
|
const seedKind = cell === void 0 || cell.value.kind === "empty" ? "string" : cell.value.kind;
|
|
5322
6239
|
beginEdit(cell === void 0 ? "" : rawEditableText(cell.value), seedKind);
|
|
@@ -5399,10 +6316,11 @@ function OdsSpreadsheetGridScreen() {
|
|
|
5399
6316
|
children: [`${row + 1}`.padStart(4), " "]
|
|
5400
6317
|
}), visibleColumns.map((column) => {
|
|
5401
6318
|
const isCursor = row === clampedRow && column === clampedColumn;
|
|
6319
|
+
const isAnchor = row === mergeAnchor?.row && column === mergeAnchor?.column;
|
|
5402
6320
|
const cell = cells.get(cellKey(row, column));
|
|
5403
6321
|
return /* @__PURE__ */ jsx(Text, {
|
|
5404
6322
|
inverse: isCursor,
|
|
5405
|
-
color: isCursor ? "cyan" : void 0,
|
|
6323
|
+
color: isCursor ? "cyan" : isAnchor ? "yellow" : void 0,
|
|
5406
6324
|
children: padCell(cell === void 0 ? "" : cell.displayText, CELL_WIDTH)
|
|
5407
6325
|
}, column);
|
|
5408
6326
|
})] }, row))]
|
|
@@ -5440,13 +6358,9 @@ function OdsSpreadsheetGridScreen() {
|
|
|
5440
6358
|
setEditSession(void 0);
|
|
5441
6359
|
}
|
|
5442
6360
|
}),
|
|
5443
|
-
/* @__PURE__ */
|
|
6361
|
+
/* @__PURE__ */ jsx(Text, {
|
|
5444
6362
|
dimColor: true,
|
|
5445
|
-
children:
|
|
5446
|
-
"hjkl/arrows move, Enter/type to edit, p print settings, t ",
|
|
5447
|
-
viewMode === "grid" ? "compact list" : "grid",
|
|
5448
|
-
" view, Esc back"
|
|
5449
|
-
]
|
|
6363
|
+
children: mergeAnchor === void 0 ? `hjkl/arrows move, Enter/type to edit, m to anchor a merge, p print settings, t ${viewMode === "grid" ? "compact list" : "grid"} view, Esc back` : "hjkl/arrows to the opposite corner, m/Enter to merge, Esc to cancel"
|
|
5450
6364
|
})
|
|
5451
6365
|
]
|
|
5452
6366
|
});
|
|
@@ -6498,6 +7412,7 @@ function ScreenBody({ screen }) {
|
|
|
6498
7412
|
case "slideList": return format === "odp" ? /* @__PURE__ */ jsx(OdpSlideListScreen, {}) : /* @__PURE__ */ jsx(PptxSlideListScreen, {});
|
|
6499
7413
|
case "slideDetail": return /* @__PURE__ */ jsx(SlideDetailScreen, { screen });
|
|
6500
7414
|
case "shapeEditor": return /* @__PURE__ */ jsx(ShapeEditorScreen, { screen });
|
|
7415
|
+
case "slideTableDetail": return /* @__PURE__ */ jsx(SlideTableDetailScreen, { screen });
|
|
6501
7416
|
case "notesEditor": return /* @__PURE__ */ jsx(NotesEditorScreen, { screen });
|
|
6502
7417
|
case "sheetList": return /* @__PURE__ */ jsx(OdsSheetListScreen, {});
|
|
6503
7418
|
case "spreadsheetGrid": return /* @__PURE__ */ jsx(OdsSpreadsheetGridScreen, {});
|