document-cli 1.0.1 → 1.2.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/README.md +50 -8
- package/dist/cli.js +223 -24
- package/dist/index.cjs +520 -20
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +522 -22
- package/dist/odb-structure-CX_0zL8_.js +382 -0
- package/dist/{tui-CucYgqmL.js → tui-DD4bQp39.js} +644 -74
- package/package.json +3 -3
- package/dist/io-Cl3MaB0L.js +0 -81
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { d as inferFormatFromExtension, i as formatOdbReportLines, l as loadProvidedFonts, n as describeOdbReport, o as readInput, r as formatOdbFormLines, t as describeOdbForm, u as formatToExtension } from "./odb-structure-CX_0zL8_.js";
|
|
2
2
|
import { readFile, writeFile } from "node:fs/promises";
|
|
3
|
-
import { createDocx, createOdg, createOdp, createOds, createOdt, createPptx, docxToPdf, hsqldbCellDisplayText, odgToPdf, odpToPdf, odsToPdf, odtToPdf, openDocx, openOdg, openOdp, openOds, openOdt, openPptx, pptxToPdf, readOdbTables, readOdgContent, readOdsContent, readPdf, rgbHexToColor } from "documents.js";
|
|
3
|
+
import { createDocx, createOdg, createOdp, createOds, createOdt, createPptx, decodeMarkdownText, docxToPdf, encodeMarkdownText, hsqldbCellDisplayText, markdownToPdf, odgToPdf, odpToPdf, odsToPdf, odtToPdf, openDocx, openOdg, openOdp, openOds, openOdt, openPptx, pptxToPdf, readOdbForms, readOdbReports, readOdbTables, readOdgContent, readOdsContent, readPdf, rgbHexToColor, xlsxToPdf } from "documents.js";
|
|
4
4
|
import { basename, dirname, extname, join } from "node:path";
|
|
5
5
|
import { cellReference, columnIndexToLetters, decodePackage } from "odf.js";
|
|
6
6
|
import { readdirSync } from "node:fs";
|
|
@@ -15,9 +15,17 @@ function describeError(error) {
|
|
|
15
15
|
}
|
|
16
16
|
//#endregion
|
|
17
17
|
//#region src/tui/format/export-pdf.ts
|
|
18
|
-
function toPdfOptions(options) {
|
|
18
|
+
function toPdfOptions(options, fonts) {
|
|
19
19
|
return {
|
|
20
20
|
signal: options.signal,
|
|
21
|
+
fonts,
|
|
22
|
+
onFontSubstitution: (substitution) => {
|
|
23
|
+
const requested = `${substitution.requestedFamily}${substitution.requestedBold ? " bold" : ""}${substitution.requestedItalic ? " italic" : ""}`;
|
|
24
|
+
options.onDiagnostic({
|
|
25
|
+
severity: "info",
|
|
26
|
+
message: `No "${requested}" face available; drew it in "${substitution.resolvedFamily}"`
|
|
27
|
+
});
|
|
28
|
+
},
|
|
21
29
|
onSubstitution: (substitution, context) => {
|
|
22
30
|
options.onDiagnostic({
|
|
23
31
|
severity: "info",
|
|
@@ -29,8 +37,18 @@ function toPdfOptions(options) {
|
|
|
29
37
|
}
|
|
30
38
|
async function exportToPdf(openDocument, destinationPath, options) {
|
|
31
39
|
if (openDocument.format === "odb" || openDocument.format === "pdf") throw new Error(`A ${openDocument.format} document is already a read-only source; there is no export-to-PDF path for it`);
|
|
40
|
+
const pdfOptions = toPdfOptions(options, await loadProvidedFonts(options.fontFiles ?? [], { signal: options.signal }));
|
|
41
|
+
if (openDocument.format === "markdown") {
|
|
42
|
+
const pdfBytes = markdownToPdf(encodeMarkdownText(openDocument.source), pdfOptions);
|
|
43
|
+
await writeFile(destinationPath, pdfBytes);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (openDocument.format === "xlsx") {
|
|
47
|
+
const pdfBytes = xlsxToPdf(openDocument.bytes, pdfOptions);
|
|
48
|
+
await writeFile(destinationPath, pdfBytes);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
32
51
|
const bytes = openDocument.editor.toBytes();
|
|
33
|
-
const pdfOptions = toPdfOptions(options);
|
|
34
52
|
const pdfBytes = convert(openDocument.format, bytes, pdfOptions);
|
|
35
53
|
await writeFile(destinationPath, pdfBytes);
|
|
36
54
|
}
|
|
@@ -59,11 +77,16 @@ function detectFormat(path) {
|
|
|
59
77
|
const ODB_EXTENSION = ".odb";
|
|
60
78
|
async function openDocumentAtPath(path) {
|
|
61
79
|
const bytes = new Uint8Array(await readFile(path));
|
|
62
|
-
if (path.toLowerCase().endsWith(ODB_EXTENSION))
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
80
|
+
if (path.toLowerCase().endsWith(ODB_EXTENSION)) {
|
|
81
|
+
const pkg = decodePackage(bytes);
|
|
82
|
+
return {
|
|
83
|
+
format: "odb",
|
|
84
|
+
tables: readOdbTables(pkg),
|
|
85
|
+
forms: readOdbForms(pkg),
|
|
86
|
+
reports: readOdbReports(pkg),
|
|
87
|
+
path
|
|
88
|
+
};
|
|
89
|
+
}
|
|
67
90
|
const format = detectFormat(path);
|
|
68
91
|
if (format === void 0) throw new Error(`Cannot tell what kind of document ${path} is from its extension`);
|
|
69
92
|
switch (format) {
|
|
@@ -102,7 +125,17 @@ async function openDocumentAtPath(path) {
|
|
|
102
125
|
layout: readPdf(bytes),
|
|
103
126
|
path
|
|
104
127
|
};
|
|
105
|
-
case "
|
|
128
|
+
case "markdown": return {
|
|
129
|
+
format,
|
|
130
|
+
source: decodeMarkdownText(bytes),
|
|
131
|
+
path
|
|
132
|
+
};
|
|
133
|
+
case "xlsx": return {
|
|
134
|
+
format,
|
|
135
|
+
layout: readPdf(xlsxToPdf(bytes)),
|
|
136
|
+
bytes,
|
|
137
|
+
path
|
|
138
|
+
};
|
|
106
139
|
case "odf": throw new Error("A standalone .odf formula document has no editor; convert it to PDF (odfToPdf) instead");
|
|
107
140
|
}
|
|
108
141
|
}
|
|
@@ -141,7 +174,11 @@ function createNewDocument(format) {
|
|
|
141
174
|
}
|
|
142
175
|
}
|
|
143
176
|
async function saveDocumentTo(openDocument, path) {
|
|
144
|
-
if (openDocument.format === "odb" || openDocument.format === "pdf") throw new Error(`A ${openDocument.format} document is opened read-only and cannot be written back`);
|
|
177
|
+
if (openDocument.format === "odb" || openDocument.format === "pdf" || openDocument.format === "xlsx") throw new Error(`A ${openDocument.format} document is opened read-only and cannot be written back`);
|
|
178
|
+
if (openDocument.format === "markdown") {
|
|
179
|
+
await writeFile(path, encodeMarkdownText(openDocument.source));
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
145
182
|
await writeFile(path, openDocument.editor.toBytes());
|
|
146
183
|
}
|
|
147
184
|
//#endregion
|
|
@@ -154,11 +191,18 @@ const EDITABLE_FORMATS = {
|
|
|
154
191
|
ods: true,
|
|
155
192
|
odg: true
|
|
156
193
|
};
|
|
194
|
+
const WRITABLE_FORMATS = {
|
|
195
|
+
...EDITABLE_FORMATS,
|
|
196
|
+
markdown: true
|
|
197
|
+
};
|
|
157
198
|
function isEditableFormat(value) {
|
|
158
199
|
return value in EDITABLE_FORMATS;
|
|
159
200
|
}
|
|
160
|
-
function
|
|
161
|
-
return
|
|
201
|
+
function isWritableFormat(value) {
|
|
202
|
+
return value in WRITABLE_FORMATS;
|
|
203
|
+
}
|
|
204
|
+
function isWritableDocument(document) {
|
|
205
|
+
return isWritableFormat(document.format);
|
|
162
206
|
}
|
|
163
207
|
function selectionKeyFor(screen) {
|
|
164
208
|
switch (screen.kind) {
|
|
@@ -169,9 +213,12 @@ function selectionKeyFor(screen) {
|
|
|
169
213
|
case "sheetList":
|
|
170
214
|
case "pageList":
|
|
171
215
|
case "odbTableList":
|
|
216
|
+
case "odbFormList":
|
|
217
|
+
case "odbReportList":
|
|
172
218
|
case "pdfPageList":
|
|
173
219
|
case "exportOptions":
|
|
174
|
-
case "saveAsPrompt":
|
|
220
|
+
case "saveAsPrompt":
|
|
221
|
+
case "markdownLineList": return screen.kind;
|
|
175
222
|
case "filePicker": return `filePicker:${screen.purpose}:${screen.cwd}`;
|
|
176
223
|
case "paragraphDetail":
|
|
177
224
|
case "tableView":
|
|
@@ -189,6 +236,9 @@ function selectionKeyFor(screen) {
|
|
|
189
236
|
case "shapeOrVectorDetail":
|
|
190
237
|
case "pdfItemDetail": return `${screen.kind}:${screen.pageIndex}:${screen.itemIndex}`;
|
|
191
238
|
case "odbTableRows": return `odbTableRows:${screen.tableName}`;
|
|
239
|
+
case "odbFormDetail": return `odbFormDetail:${screen.formName}`;
|
|
240
|
+
case "odbReportDetail": return `odbReportDetail:${screen.reportName}`;
|
|
241
|
+
case "markdownLineEditor": return `markdownLineEditor:${screen.lineIndex}`;
|
|
192
242
|
}
|
|
193
243
|
}
|
|
194
244
|
function rootScreenForFormat(format) {
|
|
@@ -200,7 +250,9 @@ function rootScreenForFormat(format) {
|
|
|
200
250
|
case "ods": return { kind: "sheetList" };
|
|
201
251
|
case "odg": return { kind: "pageList" };
|
|
202
252
|
case "odb": return { kind: "odbTableList" };
|
|
203
|
-
case "
|
|
253
|
+
case "markdown": return { kind: "markdownLineList" };
|
|
254
|
+
case "pdf":
|
|
255
|
+
case "xlsx": return { kind: "pdfPageList" };
|
|
204
256
|
}
|
|
205
257
|
}
|
|
206
258
|
function currentScreen(state) {
|
|
@@ -315,6 +367,13 @@ function documentWithPath(doc, path) {
|
|
|
315
367
|
case "odb": return {
|
|
316
368
|
format: "odb",
|
|
317
369
|
tables: doc.tables,
|
|
370
|
+
forms: doc.forms,
|
|
371
|
+
reports: doc.reports,
|
|
372
|
+
path
|
|
373
|
+
};
|
|
374
|
+
case "markdown": return {
|
|
375
|
+
format: "markdown",
|
|
376
|
+
source: doc.source,
|
|
318
377
|
path
|
|
319
378
|
};
|
|
320
379
|
case "pdf": return {
|
|
@@ -322,6 +381,12 @@ function documentWithPath(doc, path) {
|
|
|
322
381
|
layout: doc.layout,
|
|
323
382
|
path
|
|
324
383
|
};
|
|
384
|
+
case "xlsx": return {
|
|
385
|
+
format: "xlsx",
|
|
386
|
+
layout: doc.layout,
|
|
387
|
+
bytes: doc.bytes,
|
|
388
|
+
path
|
|
389
|
+
};
|
|
325
390
|
}
|
|
326
391
|
}
|
|
327
392
|
function reopenEditable(doc, bytes) {
|
|
@@ -367,6 +432,18 @@ function mutate(state, doc, apply) {
|
|
|
367
432
|
undoStack: pushSnapshot(state.undoStack, snapshot)
|
|
368
433
|
};
|
|
369
434
|
}
|
|
435
|
+
function mutateMarkdown(state, doc, source) {
|
|
436
|
+
const snapshot = encodeMarkdownText(doc.source);
|
|
437
|
+
return {
|
|
438
|
+
...state,
|
|
439
|
+
openDocument: {
|
|
440
|
+
...doc,
|
|
441
|
+
source
|
|
442
|
+
},
|
|
443
|
+
hasUnsavedChanges: true,
|
|
444
|
+
undoStack: pushSnapshot(state.undoStack, snapshot)
|
|
445
|
+
};
|
|
446
|
+
}
|
|
370
447
|
function wrongDocument(state, expected) {
|
|
371
448
|
return withStatus(state, "warning", `That action needs ${expected}; the open document is ${state.openDocument === void 0 ? "no document" : state.openDocument.format}`);
|
|
372
449
|
}
|
|
@@ -395,6 +472,11 @@ function drawingDocument(state) {
|
|
|
395
472
|
if (doc === void 0) return;
|
|
396
473
|
return doc.format === "odg" ? doc : void 0;
|
|
397
474
|
}
|
|
475
|
+
function markdownDocument(state) {
|
|
476
|
+
const doc = state.openDocument;
|
|
477
|
+
if (doc === void 0) return;
|
|
478
|
+
return doc.format === "markdown" ? doc : void 0;
|
|
479
|
+
}
|
|
398
480
|
function paragraphAt(doc, blockIndex) {
|
|
399
481
|
return doc.editor.paragraphs()[blockIndex];
|
|
400
482
|
}
|
|
@@ -513,7 +595,7 @@ function appReducer(state, action) {
|
|
|
513
595
|
selection: {},
|
|
514
596
|
errorDetail: void 0,
|
|
515
597
|
stack: [rootScreenForFormat(action.doc.format)]
|
|
516
|
-
}, "info", `Opened ${action.path}`);
|
|
598
|
+
}, "info", action.doc.format === "xlsx" ? `Opened ${action.path} as a read-only PDF preview -- press ':' then 'export pdf' to save it as a real PDF` : `Opened ${action.path}`);
|
|
517
599
|
case "OPEN_FILE_ERROR": return withStatus({
|
|
518
600
|
...state,
|
|
519
601
|
errorDetail: {
|
|
@@ -643,6 +725,21 @@ function appReducer(state, action) {
|
|
|
643
725
|
doc.editor.addSlide();
|
|
644
726
|
});
|
|
645
727
|
}
|
|
728
|
+
case "ADD_SLIDE_TABLE": {
|
|
729
|
+
const doc = presentationDocument(state);
|
|
730
|
+
if (doc === void 0) return wrongDocument(state, "a pptx or odp document");
|
|
731
|
+
const slide = doc.editor.slides()[action.slideIndex];
|
|
732
|
+
if (slide === void 0) return withStatus(state, "warning", `There is no slide at index ${action.slideIndex}`);
|
|
733
|
+
return mutate(state, doc, () => {
|
|
734
|
+
slide.addTable({
|
|
735
|
+
frame: action.frame,
|
|
736
|
+
table: {
|
|
737
|
+
rows: action.rows,
|
|
738
|
+
columns: action.columns
|
|
739
|
+
}
|
|
740
|
+
});
|
|
741
|
+
});
|
|
742
|
+
}
|
|
646
743
|
case "ADD_TEXTBOX": {
|
|
647
744
|
const doc = shapeHostDocument(state);
|
|
648
745
|
if (doc === void 0) return wrongDocument(state, "a pptx, odp or odg document");
|
|
@@ -771,6 +868,11 @@ function appReducer(state, action) {
|
|
|
771
868
|
action.vector.stroke = action.stroke;
|
|
772
869
|
});
|
|
773
870
|
}
|
|
871
|
+
case "SET_MARKDOWN_SOURCE": {
|
|
872
|
+
const doc = markdownDocument(state);
|
|
873
|
+
if (doc === void 0) return wrongDocument(state, "a markdown document");
|
|
874
|
+
return mutateMarkdown(state, doc, action.source);
|
|
875
|
+
}
|
|
774
876
|
case "APPEND_DIAGNOSTIC": return {
|
|
775
877
|
...state,
|
|
776
878
|
diagnostics: [...state.diagnostics, action.diagnostic]
|
|
@@ -799,12 +901,16 @@ function appReducer(state, action) {
|
|
|
799
901
|
case "UNDO": {
|
|
800
902
|
const doc = state.openDocument;
|
|
801
903
|
if (doc === void 0) return withStatus(state, "info", "There is nothing to undo");
|
|
802
|
-
if (doc.format === "odb" || doc.format === "pdf") return withStatus(state, "warning", `A ${doc.format} document is read-only, so it has no history to undo`);
|
|
904
|
+
if (doc.format === "odb" || doc.format === "pdf" || doc.format === "xlsx") return withStatus(state, "warning", `A ${doc.format} document is read-only, so it has no history to undo`);
|
|
803
905
|
const snapshot = state.undoStack.at(-1);
|
|
804
906
|
if (snapshot === void 0) return withStatus(state, "info", "There is nothing to undo");
|
|
907
|
+
const restored = doc.format === "markdown" ? {
|
|
908
|
+
...doc,
|
|
909
|
+
source: decodeMarkdownText(snapshot)
|
|
910
|
+
} : reopenEditable(doc, snapshot);
|
|
805
911
|
return withStatus({
|
|
806
912
|
...state,
|
|
807
|
-
openDocument:
|
|
913
|
+
openDocument: restored,
|
|
808
914
|
undoStack: state.undoStack.slice(0, -1),
|
|
809
915
|
hasUnsavedChanges: true
|
|
810
916
|
}, "info", "Undone");
|
|
@@ -2171,29 +2277,33 @@ function DocxBodyListScreen() {
|
|
|
2171
2277
|
return /* @__PURE__ */ jsx(ParagraphFamilyBodyList, { adapter });
|
|
2172
2278
|
}
|
|
2173
2279
|
//#endregion
|
|
2174
|
-
//#region src/tui/screens/editors/
|
|
2175
|
-
function
|
|
2176
|
-
if (openDocument?.format !== "
|
|
2280
|
+
//#region src/tui/screens/editors/markdown/shared.ts
|
|
2281
|
+
function requireMarkdownDocument(openDocument) {
|
|
2282
|
+
if (openDocument?.format !== "markdown") throw new Error("A markdown editing screen rendered without an open markdown document; the app router only reaches this screen group from markdownLineList, which is only ever the root screen of an open markdown document.");
|
|
2177
2283
|
return openDocument;
|
|
2178
2284
|
}
|
|
2179
2285
|
//#endregion
|
|
2180
|
-
//#region src/tui/screens/editors/
|
|
2181
|
-
function
|
|
2286
|
+
//#region src/tui/screens/editors/markdown/line-list.tsx
|
|
2287
|
+
function MarkdownLineListScreen() {
|
|
2182
2288
|
const state = useAppState();
|
|
2183
2289
|
const dispatch = useAppDispatch();
|
|
2184
|
-
const doc =
|
|
2290
|
+
const doc = requireMarkdownDocument(state.openDocument);
|
|
2185
2291
|
const query = state.searchQuery.trim().toLowerCase();
|
|
2186
|
-
const
|
|
2292
|
+
const allLines = doc.source.split("\n").map((line, lineIndex) => ({
|
|
2293
|
+
line,
|
|
2294
|
+
lineIndex
|
|
2295
|
+
}));
|
|
2296
|
+
const lines = query === "" ? allLines : allLines.filter((entry) => entry.line.toLowerCase().includes(query));
|
|
2187
2297
|
const { selectedIndex } = useNavigationInput({
|
|
2188
|
-
itemCount:
|
|
2298
|
+
itemCount: lines.length,
|
|
2189
2299
|
onSelect: (index) => {
|
|
2190
|
-
const
|
|
2191
|
-
if (
|
|
2300
|
+
const entry = lines[index];
|
|
2301
|
+
if (entry === void 0) return;
|
|
2192
2302
|
dispatch({
|
|
2193
2303
|
type: "PUSH_SCREEN",
|
|
2194
2304
|
screen: {
|
|
2195
|
-
kind: "
|
|
2196
|
-
|
|
2305
|
+
kind: "markdownLineEditor",
|
|
2306
|
+
lineIndex: entry.lineIndex
|
|
2197
2307
|
}
|
|
2198
2308
|
});
|
|
2199
2309
|
},
|
|
@@ -2207,32 +2317,410 @@ function OdbTableListScreen() {
|
|
|
2207
2317
|
children: [/* @__PURE__ */ jsxs(Text, {
|
|
2208
2318
|
bold: true,
|
|
2209
2319
|
children: [
|
|
2210
|
-
"
|
|
2211
|
-
|
|
2320
|
+
"Lines (",
|
|
2321
|
+
lines.length,
|
|
2212
2322
|
" of ",
|
|
2213
|
-
|
|
2323
|
+
allLines.length,
|
|
2214
2324
|
")"
|
|
2215
2325
|
]
|
|
2216
2326
|
}), /* @__PURE__ */ jsx(ListView, {
|
|
2217
|
-
items:
|
|
2327
|
+
items: lines,
|
|
2218
2328
|
selectedIndex,
|
|
2219
|
-
emptyMessage: query === "" ? "This
|
|
2220
|
-
renderItem: (
|
|
2329
|
+
emptyMessage: query === "" ? "This document has no lines." : `No lines match "${state.searchQuery}".`,
|
|
2330
|
+
renderItem: ({ line, lineIndex }, isSelected) => /* @__PURE__ */ jsxs(Text, {
|
|
2221
2331
|
color: isSelected ? "cyan" : void 0,
|
|
2222
2332
|
inverse: isSelected,
|
|
2223
2333
|
children: [
|
|
2224
|
-
|
|
2225
|
-
"
|
|
2226
|
-
|
|
2227
|
-
" columns, ",
|
|
2228
|
-
table.rows.length,
|
|
2229
|
-
" rows)"
|
|
2334
|
+
lineIndex + 1,
|
|
2335
|
+
": ",
|
|
2336
|
+
line === "" ? "(blank)" : line
|
|
2230
2337
|
]
|
|
2231
2338
|
})
|
|
2232
2339
|
})]
|
|
2233
2340
|
});
|
|
2234
2341
|
}
|
|
2235
2342
|
//#endregion
|
|
2343
|
+
//#region src/tui/screens/editors/markdown/line-editor.tsx
|
|
2344
|
+
function MarkdownLineEditorScreen() {
|
|
2345
|
+
const state = useAppState();
|
|
2346
|
+
const dispatch = useAppDispatch();
|
|
2347
|
+
const screen = currentScreen(state);
|
|
2348
|
+
const doc = state.openDocument;
|
|
2349
|
+
if (screen.kind !== "markdownLineEditor") return /* @__PURE__ */ jsx(Text, {
|
|
2350
|
+
color: "red",
|
|
2351
|
+
children: "MarkdownLineEditorScreen rendered outside a markdownLineEditor screen."
|
|
2352
|
+
});
|
|
2353
|
+
if (doc?.format !== "markdown") return /* @__PURE__ */ jsx(Text, {
|
|
2354
|
+
color: "red",
|
|
2355
|
+
children: "MarkdownLineEditorScreen requires an open markdown document."
|
|
2356
|
+
});
|
|
2357
|
+
const lines = doc.source.split("\n");
|
|
2358
|
+
const line = lines[screen.lineIndex];
|
|
2359
|
+
if (line === void 0) return /* @__PURE__ */ jsxs(Text, {
|
|
2360
|
+
color: "red",
|
|
2361
|
+
children: [
|
|
2362
|
+
"There is no line at index ",
|
|
2363
|
+
screen.lineIndex,
|
|
2364
|
+
"."
|
|
2365
|
+
]
|
|
2366
|
+
});
|
|
2367
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
2368
|
+
flexDirection: "column",
|
|
2369
|
+
children: [
|
|
2370
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
2371
|
+
bold: true,
|
|
2372
|
+
children: ["Edit line ", screen.lineIndex + 1]
|
|
2373
|
+
}),
|
|
2374
|
+
/* @__PURE__ */ jsx(RunTextEditor, {
|
|
2375
|
+
initialText: line,
|
|
2376
|
+
onCommit: (text) => {
|
|
2377
|
+
const nextLines = [...lines];
|
|
2378
|
+
nextLines[screen.lineIndex] = text;
|
|
2379
|
+
dispatch({
|
|
2380
|
+
type: "SET_MARKDOWN_SOURCE",
|
|
2381
|
+
source: nextLines.join("\n")
|
|
2382
|
+
});
|
|
2383
|
+
dispatch({ type: "POP_SCREEN" });
|
|
2384
|
+
},
|
|
2385
|
+
onCancel: () => {
|
|
2386
|
+
dispatch({ type: "POP_SCREEN" });
|
|
2387
|
+
}
|
|
2388
|
+
}),
|
|
2389
|
+
/* @__PURE__ */ jsx(Text, {
|
|
2390
|
+
dimColor: true,
|
|
2391
|
+
children: "Enter to commit, Esc to discard"
|
|
2392
|
+
})
|
|
2393
|
+
]
|
|
2394
|
+
});
|
|
2395
|
+
}
|
|
2396
|
+
//#endregion
|
|
2397
|
+
//#region src/tui/screens/editors/odb/shared.ts
|
|
2398
|
+
function requireOdbDocument(openDocument) {
|
|
2399
|
+
if (openDocument?.format !== "odb") throw new Error("An .odb browsing screen rendered without an open .odb document; the app router only reaches this screen group from odbTableList, which is only ever the root screen of an open .odb document.");
|
|
2400
|
+
return openDocument;
|
|
2401
|
+
}
|
|
2402
|
+
//#endregion
|
|
2403
|
+
//#region src/tui/screens/editors/odb/form-detail.tsx
|
|
2404
|
+
const FORM_DETAIL_RESERVED_ROWS = 5;
|
|
2405
|
+
function requireForm(forms, formName) {
|
|
2406
|
+
const form = forms.find((candidate) => candidate.name === formName);
|
|
2407
|
+
if (form === void 0) throw new Error(`odbFormDetail was pushed for form "${formName}", but the open .odb document has no form by that name.`);
|
|
2408
|
+
return form;
|
|
2409
|
+
}
|
|
2410
|
+
function OdbFormDetailScreen() {
|
|
2411
|
+
const state = useAppState();
|
|
2412
|
+
const dispatch = useAppDispatch();
|
|
2413
|
+
const doc = requireOdbDocument(state.openDocument);
|
|
2414
|
+
const screen = currentScreen(state);
|
|
2415
|
+
if (screen.kind !== "odbFormDetail") throw new Error(`OdbFormDetailScreen rendered while the current screen is "${screen.kind}", not "odbFormDetail".`);
|
|
2416
|
+
const form = requireForm(doc.forms, screen.formName);
|
|
2417
|
+
const allLines = formatOdbFormLines(form);
|
|
2418
|
+
const query = state.searchQuery.trim().toLowerCase();
|
|
2419
|
+
const lines = query === "" ? allLines : allLines.filter((line) => line.toLowerCase().includes(query));
|
|
2420
|
+
const { selectedIndex } = useNavigationInput({
|
|
2421
|
+
itemCount: lines.length,
|
|
2422
|
+
onSelect: () => {},
|
|
2423
|
+
onBack: () => {
|
|
2424
|
+
dispatch({ type: "POP_SCREEN" });
|
|
2425
|
+
},
|
|
2426
|
+
isActive: !anyOverlayOpen(state)
|
|
2427
|
+
});
|
|
2428
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
2429
|
+
flexDirection: "column",
|
|
2430
|
+
children: [
|
|
2431
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
2432
|
+
bold: true,
|
|
2433
|
+
children: [
|
|
2434
|
+
form.name,
|
|
2435
|
+
" (",
|
|
2436
|
+
lines.length,
|
|
2437
|
+
" of ",
|
|
2438
|
+
allLines.length,
|
|
2439
|
+
" lines)"
|
|
2440
|
+
]
|
|
2441
|
+
}),
|
|
2442
|
+
/* @__PURE__ */ jsx(Text, {
|
|
2443
|
+
dimColor: true,
|
|
2444
|
+
children: form.href
|
|
2445
|
+
}),
|
|
2446
|
+
/* @__PURE__ */ jsx(ListView, {
|
|
2447
|
+
items: lines,
|
|
2448
|
+
selectedIndex,
|
|
2449
|
+
reservedRows: FORM_DETAIL_RESERVED_ROWS,
|
|
2450
|
+
emptyMessage: query === "" ? "This form declares no structure." : `No lines match "${state.searchQuery}".`,
|
|
2451
|
+
renderItem: (line, isSelected) => /* @__PURE__ */ jsx(Text, {
|
|
2452
|
+
color: isSelected ? "cyan" : void 0,
|
|
2453
|
+
inverse: isSelected,
|
|
2454
|
+
children: line
|
|
2455
|
+
})
|
|
2456
|
+
}),
|
|
2457
|
+
/* @__PURE__ */ jsx(Text, {
|
|
2458
|
+
dimColor: true,
|
|
2459
|
+
children: "Esc to go back to the form list"
|
|
2460
|
+
})
|
|
2461
|
+
]
|
|
2462
|
+
});
|
|
2463
|
+
}
|
|
2464
|
+
//#endregion
|
|
2465
|
+
//#region src/tui/screens/editors/odb/form-list.tsx
|
|
2466
|
+
function OdbFormListScreen() {
|
|
2467
|
+
const state = useAppState();
|
|
2468
|
+
const dispatch = useAppDispatch();
|
|
2469
|
+
const doc = requireOdbDocument(state.openDocument);
|
|
2470
|
+
const query = state.searchQuery.trim().toLowerCase();
|
|
2471
|
+
const forms = query === "" ? doc.forms : doc.forms.filter((form) => form.name.toLowerCase().includes(query));
|
|
2472
|
+
const { selectedIndex } = useNavigationInput({
|
|
2473
|
+
itemCount: forms.length,
|
|
2474
|
+
onSelect: (index) => {
|
|
2475
|
+
const form = forms[index];
|
|
2476
|
+
if (form === void 0) return;
|
|
2477
|
+
dispatch({
|
|
2478
|
+
type: "PUSH_SCREEN",
|
|
2479
|
+
screen: {
|
|
2480
|
+
kind: "odbFormDetail",
|
|
2481
|
+
formName: form.name
|
|
2482
|
+
}
|
|
2483
|
+
});
|
|
2484
|
+
},
|
|
2485
|
+
onBack: () => {
|
|
2486
|
+
dispatch({ type: "POP_SCREEN" });
|
|
2487
|
+
},
|
|
2488
|
+
isActive: !anyOverlayOpen(state)
|
|
2489
|
+
});
|
|
2490
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
2491
|
+
flexDirection: "column",
|
|
2492
|
+
children: [
|
|
2493
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
2494
|
+
bold: true,
|
|
2495
|
+
children: [
|
|
2496
|
+
"Forms (",
|
|
2497
|
+
forms.length,
|
|
2498
|
+
" of ",
|
|
2499
|
+
doc.forms.length,
|
|
2500
|
+
")"
|
|
2501
|
+
]
|
|
2502
|
+
}),
|
|
2503
|
+
/* @__PURE__ */ jsx(ListView, {
|
|
2504
|
+
items: forms,
|
|
2505
|
+
selectedIndex,
|
|
2506
|
+
emptyMessage: query === "" ? "This database declares no forms." : `No forms match "${state.searchQuery}".`,
|
|
2507
|
+
renderItem: (form, isSelected) => /* @__PURE__ */ jsx(Text, {
|
|
2508
|
+
color: isSelected ? "cyan" : void 0,
|
|
2509
|
+
inverse: isSelected,
|
|
2510
|
+
children: describeOdbForm(form)
|
|
2511
|
+
})
|
|
2512
|
+
}),
|
|
2513
|
+
/* @__PURE__ */ jsx(Text, {
|
|
2514
|
+
dimColor: true,
|
|
2515
|
+
children: "Enter to open a form, Esc to go back to the tables"
|
|
2516
|
+
})
|
|
2517
|
+
]
|
|
2518
|
+
});
|
|
2519
|
+
}
|
|
2520
|
+
//#endregion
|
|
2521
|
+
//#region src/tui/screens/editors/odb/report-detail.tsx
|
|
2522
|
+
const REPORT_DETAIL_RESERVED_ROWS = 5;
|
|
2523
|
+
function requireReport(reports, reportName) {
|
|
2524
|
+
const report = reports.find((candidate) => candidate.name === reportName);
|
|
2525
|
+
if (report === void 0) throw new Error(`odbReportDetail was pushed for report "${reportName}", but the open .odb document has no report by that name.`);
|
|
2526
|
+
return report;
|
|
2527
|
+
}
|
|
2528
|
+
function OdbReportDetailScreen() {
|
|
2529
|
+
const state = useAppState();
|
|
2530
|
+
const dispatch = useAppDispatch();
|
|
2531
|
+
const doc = requireOdbDocument(state.openDocument);
|
|
2532
|
+
const screen = currentScreen(state);
|
|
2533
|
+
if (screen.kind !== "odbReportDetail") throw new Error(`OdbReportDetailScreen rendered while the current screen is "${screen.kind}", not "odbReportDetail".`);
|
|
2534
|
+
const report = requireReport(doc.reports, screen.reportName);
|
|
2535
|
+
const allLines = formatOdbReportLines(report);
|
|
2536
|
+
const query = state.searchQuery.trim().toLowerCase();
|
|
2537
|
+
const lines = query === "" ? allLines : allLines.filter((line) => line.toLowerCase().includes(query));
|
|
2538
|
+
const { selectedIndex } = useNavigationInput({
|
|
2539
|
+
itemCount: lines.length,
|
|
2540
|
+
onSelect: () => {},
|
|
2541
|
+
onBack: () => {
|
|
2542
|
+
dispatch({ type: "POP_SCREEN" });
|
|
2543
|
+
},
|
|
2544
|
+
isActive: !anyOverlayOpen(state)
|
|
2545
|
+
});
|
|
2546
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
2547
|
+
flexDirection: "column",
|
|
2548
|
+
children: [
|
|
2549
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
2550
|
+
bold: true,
|
|
2551
|
+
children: [
|
|
2552
|
+
report.name,
|
|
2553
|
+
" (",
|
|
2554
|
+
lines.length,
|
|
2555
|
+
" of ",
|
|
2556
|
+
allLines.length,
|
|
2557
|
+
" lines)"
|
|
2558
|
+
]
|
|
2559
|
+
}),
|
|
2560
|
+
/* @__PURE__ */ jsx(Text, {
|
|
2561
|
+
dimColor: true,
|
|
2562
|
+
children: report.href
|
|
2563
|
+
}),
|
|
2564
|
+
/* @__PURE__ */ jsx(ListView, {
|
|
2565
|
+
items: lines,
|
|
2566
|
+
selectedIndex,
|
|
2567
|
+
reservedRows: REPORT_DETAIL_RESERVED_ROWS,
|
|
2568
|
+
emptyMessage: query === "" ? "This report declares no structure." : `No lines match "${state.searchQuery}".`,
|
|
2569
|
+
renderItem: (line, isSelected) => /* @__PURE__ */ jsx(Text, {
|
|
2570
|
+
color: isSelected ? "cyan" : void 0,
|
|
2571
|
+
inverse: isSelected,
|
|
2572
|
+
children: line
|
|
2573
|
+
})
|
|
2574
|
+
}),
|
|
2575
|
+
/* @__PURE__ */ jsx(Text, {
|
|
2576
|
+
dimColor: true,
|
|
2577
|
+
children: "Esc to go back to the report list"
|
|
2578
|
+
})
|
|
2579
|
+
]
|
|
2580
|
+
});
|
|
2581
|
+
}
|
|
2582
|
+
//#endregion
|
|
2583
|
+
//#region src/tui/screens/editors/odb/report-list.tsx
|
|
2584
|
+
function OdbReportListScreen() {
|
|
2585
|
+
const state = useAppState();
|
|
2586
|
+
const dispatch = useAppDispatch();
|
|
2587
|
+
const doc = requireOdbDocument(state.openDocument);
|
|
2588
|
+
const query = state.searchQuery.trim().toLowerCase();
|
|
2589
|
+
const reports = query === "" ? doc.reports : doc.reports.filter((report) => report.name.toLowerCase().includes(query));
|
|
2590
|
+
const { selectedIndex } = useNavigationInput({
|
|
2591
|
+
itemCount: reports.length,
|
|
2592
|
+
onSelect: (index) => {
|
|
2593
|
+
const report = reports[index];
|
|
2594
|
+
if (report === void 0) return;
|
|
2595
|
+
dispatch({
|
|
2596
|
+
type: "PUSH_SCREEN",
|
|
2597
|
+
screen: {
|
|
2598
|
+
kind: "odbReportDetail",
|
|
2599
|
+
reportName: report.name
|
|
2600
|
+
}
|
|
2601
|
+
});
|
|
2602
|
+
},
|
|
2603
|
+
onBack: () => {
|
|
2604
|
+
dispatch({ type: "POP_SCREEN" });
|
|
2605
|
+
},
|
|
2606
|
+
isActive: !anyOverlayOpen(state)
|
|
2607
|
+
});
|
|
2608
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
2609
|
+
flexDirection: "column",
|
|
2610
|
+
children: [
|
|
2611
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
2612
|
+
bold: true,
|
|
2613
|
+
children: [
|
|
2614
|
+
"Reports (",
|
|
2615
|
+
reports.length,
|
|
2616
|
+
" of ",
|
|
2617
|
+
doc.reports.length,
|
|
2618
|
+
")"
|
|
2619
|
+
]
|
|
2620
|
+
}),
|
|
2621
|
+
/* @__PURE__ */ jsx(ListView, {
|
|
2622
|
+
items: reports,
|
|
2623
|
+
selectedIndex,
|
|
2624
|
+
emptyMessage: query === "" ? "This database declares no reports." : `No reports match "${state.searchQuery}".`,
|
|
2625
|
+
renderItem: (report, isSelected) => /* @__PURE__ */ jsx(Text, {
|
|
2626
|
+
color: isSelected ? "cyan" : void 0,
|
|
2627
|
+
inverse: isSelected,
|
|
2628
|
+
children: describeOdbReport(report)
|
|
2629
|
+
})
|
|
2630
|
+
}),
|
|
2631
|
+
/* @__PURE__ */ jsx(Text, {
|
|
2632
|
+
dimColor: true,
|
|
2633
|
+
children: "Enter to open a report, Esc to go back to the tables"
|
|
2634
|
+
})
|
|
2635
|
+
]
|
|
2636
|
+
});
|
|
2637
|
+
}
|
|
2638
|
+
//#endregion
|
|
2639
|
+
//#region src/tui/screens/editors/odb/table-list.tsx
|
|
2640
|
+
const TABLE_LIST_RESERVED_ROWS = 5;
|
|
2641
|
+
function OdbTableListScreen() {
|
|
2642
|
+
const state = useAppState();
|
|
2643
|
+
const dispatch = useAppDispatch();
|
|
2644
|
+
const doc = requireOdbDocument(state.openDocument);
|
|
2645
|
+
const overlayOpen = anyOverlayOpen(state);
|
|
2646
|
+
const query = state.searchQuery.trim().toLowerCase();
|
|
2647
|
+
const tables = query === "" ? doc.tables : doc.tables.filter((table) => table.tableName.toLowerCase().includes(query));
|
|
2648
|
+
const { selectedIndex } = useNavigationInput({
|
|
2649
|
+
itemCount: tables.length,
|
|
2650
|
+
onSelect: (index) => {
|
|
2651
|
+
const table = tables[index];
|
|
2652
|
+
if (table === void 0) return;
|
|
2653
|
+
dispatch({
|
|
2654
|
+
type: "PUSH_SCREEN",
|
|
2655
|
+
screen: {
|
|
2656
|
+
kind: "odbTableRows",
|
|
2657
|
+
tableName: table.tableName
|
|
2658
|
+
}
|
|
2659
|
+
});
|
|
2660
|
+
},
|
|
2661
|
+
onBack: () => {
|
|
2662
|
+
dispatch({ type: "POP_SCREEN" });
|
|
2663
|
+
},
|
|
2664
|
+
isActive: !overlayOpen
|
|
2665
|
+
});
|
|
2666
|
+
useInput((input) => {
|
|
2667
|
+
if (input === "f") {
|
|
2668
|
+
dispatch({
|
|
2669
|
+
type: "PUSH_SCREEN",
|
|
2670
|
+
screen: { kind: "odbFormList" }
|
|
2671
|
+
});
|
|
2672
|
+
return;
|
|
2673
|
+
}
|
|
2674
|
+
if (input === "r") dispatch({
|
|
2675
|
+
type: "PUSH_SCREEN",
|
|
2676
|
+
screen: { kind: "odbReportList" }
|
|
2677
|
+
});
|
|
2678
|
+
}, { isActive: !overlayOpen });
|
|
2679
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
2680
|
+
flexDirection: "column",
|
|
2681
|
+
children: [
|
|
2682
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
2683
|
+
bold: true,
|
|
2684
|
+
children: [
|
|
2685
|
+
"Tables (",
|
|
2686
|
+
tables.length,
|
|
2687
|
+
" of ",
|
|
2688
|
+
doc.tables.length,
|
|
2689
|
+
")"
|
|
2690
|
+
]
|
|
2691
|
+
}),
|
|
2692
|
+
/* @__PURE__ */ jsx(ListView, {
|
|
2693
|
+
items: tables,
|
|
2694
|
+
selectedIndex,
|
|
2695
|
+
reservedRows: TABLE_LIST_RESERVED_ROWS,
|
|
2696
|
+
emptyMessage: query === "" ? "This database has no tables." : `No tables match "${state.searchQuery}".`,
|
|
2697
|
+
renderItem: (table, isSelected) => /* @__PURE__ */ jsxs(Text, {
|
|
2698
|
+
color: isSelected ? "cyan" : void 0,
|
|
2699
|
+
inverse: isSelected,
|
|
2700
|
+
children: [
|
|
2701
|
+
table.tableName,
|
|
2702
|
+
" (",
|
|
2703
|
+
table.columns.length,
|
|
2704
|
+
" columns, ",
|
|
2705
|
+
table.rows.length,
|
|
2706
|
+
" rows)"
|
|
2707
|
+
]
|
|
2708
|
+
})
|
|
2709
|
+
}),
|
|
2710
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
2711
|
+
dimColor: true,
|
|
2712
|
+
children: [
|
|
2713
|
+
"Enter to open a table, f for forms (",
|
|
2714
|
+
doc.forms.length,
|
|
2715
|
+
"), r for reports (",
|
|
2716
|
+
doc.reports.length,
|
|
2717
|
+
")"
|
|
2718
|
+
]
|
|
2719
|
+
})
|
|
2720
|
+
]
|
|
2721
|
+
});
|
|
2722
|
+
}
|
|
2723
|
+
//#endregion
|
|
2236
2724
|
//#region src/tui/screens/editors/odb/table-rows.tsx
|
|
2237
2725
|
const TABLE_ROWS_RESERVED_ROWS = 5;
|
|
2238
2726
|
const CELL_SEPARATOR = " │ ";
|
|
@@ -3548,6 +4036,12 @@ const IMAGE_EXTENSION_TO_FORMAT = {
|
|
|
3548
4036
|
jpg: "jpeg",
|
|
3549
4037
|
jpeg: "jpeg"
|
|
3550
4038
|
};
|
|
4039
|
+
const DEFAULT_TABLE_ROWS = 2;
|
|
4040
|
+
const DEFAULT_TABLE_COLUMNS = 2;
|
|
4041
|
+
function parsePositiveIntField(raw, fallback) {
|
|
4042
|
+
const parsed = Number.parseInt(raw, 10);
|
|
4043
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
|
|
4044
|
+
}
|
|
3551
4045
|
function imageFormatFromPath(path) {
|
|
3552
4046
|
const dotIndex = path.lastIndexOf(".");
|
|
3553
4047
|
if (dotIndex < 0) return;
|
|
@@ -3576,6 +4070,7 @@ function SlideDetailScreen(props) {
|
|
|
3576
4070
|
const [addMode, setAddMode] = useState("closed");
|
|
3577
4071
|
const [draft, setDraft] = useState("");
|
|
3578
4072
|
const [imageError, setImageError] = useState(void 0);
|
|
4073
|
+
const [tableRows, setTableRows] = useState(DEFAULT_TABLE_ROWS);
|
|
3579
4074
|
const formIsOpen = addMode !== "closed";
|
|
3580
4075
|
const { selectedIndex } = useNavigationInput({
|
|
3581
4076
|
itemCount: rows.length,
|
|
@@ -3616,10 +4111,15 @@ function SlideDetailScreen(props) {
|
|
|
3616
4111
|
setDraft("");
|
|
3617
4112
|
setImageError(void 0);
|
|
3618
4113
|
setAddMode("image");
|
|
4114
|
+
return;
|
|
4115
|
+
}
|
|
4116
|
+
if (input === "b") {
|
|
4117
|
+
setDraft(String(DEFAULT_TABLE_ROWS));
|
|
4118
|
+
setAddMode("tableRows");
|
|
3619
4119
|
}
|
|
3620
4120
|
}, { isActive: !overlayOpen && addMode === "chooseKind" });
|
|
3621
4121
|
useInput((input) => {
|
|
3622
|
-
if (input === "n"
|
|
4122
|
+
if (input === "n") dispatch({
|
|
3623
4123
|
type: "PUSH_SCREEN",
|
|
3624
4124
|
screen: {
|
|
3625
4125
|
kind: "notesEditor",
|
|
@@ -3654,6 +4154,22 @@ function SlideDetailScreen(props) {
|
|
|
3654
4154
|
}
|
|
3655
4155
|
})();
|
|
3656
4156
|
};
|
|
4157
|
+
const commitTableRows = (raw) => {
|
|
4158
|
+
setTableRows(parsePositiveIntField(raw, DEFAULT_TABLE_ROWS));
|
|
4159
|
+
setDraft(String(DEFAULT_TABLE_COLUMNS));
|
|
4160
|
+
setAddMode("tableColumns");
|
|
4161
|
+
};
|
|
4162
|
+
const commitTableColumns = (raw) => {
|
|
4163
|
+
const columns = parsePositiveIntField(raw, DEFAULT_TABLE_COLUMNS);
|
|
4164
|
+
dispatch({
|
|
4165
|
+
type: "ADD_SLIDE_TABLE",
|
|
4166
|
+
slideIndex,
|
|
4167
|
+
frame: defaultShapeFrame(doc.editor.slideSize),
|
|
4168
|
+
rows: tableRows,
|
|
4169
|
+
columns
|
|
4170
|
+
});
|
|
4171
|
+
setAddMode("closed");
|
|
4172
|
+
};
|
|
3657
4173
|
return /* @__PURE__ */ jsxs(Box, {
|
|
3658
4174
|
flexDirection: "column",
|
|
3659
4175
|
children: [
|
|
@@ -3690,7 +4206,7 @@ function SlideDetailScreen(props) {
|
|
|
3690
4206
|
}),
|
|
3691
4207
|
addMode === "chooseKind" ? /* @__PURE__ */ jsx(Text, {
|
|
3692
4208
|
color: "cyan",
|
|
3693
|
-
children: "Add shape: t textbox, i image, Esc cancel"
|
|
4209
|
+
children: "Add shape: t textbox, i image, b table, Esc cancel"
|
|
3694
4210
|
}) : void 0,
|
|
3695
4211
|
addMode === "textbox" ? /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, {
|
|
3696
4212
|
color: "cyan",
|
|
@@ -3722,13 +4238,33 @@ function SlideDetailScreen(props) {
|
|
|
3722
4238
|
children: imageError
|
|
3723
4239
|
})]
|
|
3724
4240
|
}) : void 0,
|
|
3725
|
-
/* @__PURE__ */ jsxs(Text, {
|
|
4241
|
+
addMode === "tableRows" ? /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, {
|
|
4242
|
+
color: "cyan",
|
|
4243
|
+
children: "Rows: "
|
|
4244
|
+
}), /* @__PURE__ */ jsx(TextField, {
|
|
4245
|
+
value: draft,
|
|
4246
|
+
isFocused: true,
|
|
4247
|
+
onChange: setDraft,
|
|
4248
|
+
onSubmit: commitTableRows,
|
|
4249
|
+
onCancel: () => {
|
|
4250
|
+
setAddMode("closed");
|
|
4251
|
+
}
|
|
4252
|
+
})] }) : void 0,
|
|
4253
|
+
addMode === "tableColumns" ? /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, {
|
|
4254
|
+
color: "cyan",
|
|
4255
|
+
children: "Columns: "
|
|
4256
|
+
}), /* @__PURE__ */ jsx(TextField, {
|
|
4257
|
+
value: draft,
|
|
4258
|
+
isFocused: true,
|
|
4259
|
+
onChange: setDraft,
|
|
4260
|
+
onSubmit: commitTableColumns,
|
|
4261
|
+
onCancel: () => {
|
|
4262
|
+
setAddMode("closed");
|
|
4263
|
+
}
|
|
4264
|
+
})] }) : void 0,
|
|
4265
|
+
/* @__PURE__ */ jsx(Text, {
|
|
3726
4266
|
dimColor: true,
|
|
3727
|
-
children:
|
|
3728
|
-
"Enter: edit shape a: add shape",
|
|
3729
|
-
doc.format === "odp" ? " n: notes" : "",
|
|
3730
|
-
" Esc: back"
|
|
3731
|
-
]
|
|
4267
|
+
children: "Enter: edit shape a: add shape n: notes Esc: back"
|
|
3732
4268
|
})
|
|
3733
4269
|
]
|
|
3734
4270
|
});
|
|
@@ -3742,15 +4278,11 @@ function PptxSlideListScreen() {
|
|
|
3742
4278
|
}
|
|
3743
4279
|
//#endregion
|
|
3744
4280
|
//#region src/tui/screens/editors/odp/notes-editor.tsx
|
|
3745
|
-
function assertOdpDocument(doc) {
|
|
3746
|
-
if (doc?.format !== "odp") throw new Error("NotesEditorScreen rendered without an open odp document; check the screen router in app.tsx.");
|
|
3747
|
-
return doc;
|
|
3748
|
-
}
|
|
3749
4281
|
function NotesEditorScreen(props) {
|
|
3750
4282
|
const state = useAppState();
|
|
3751
4283
|
const dispatch = useAppDispatch();
|
|
3752
4284
|
const overlayOpen = anyOverlayOpen(state);
|
|
3753
|
-
const doc =
|
|
4285
|
+
const doc = assertPresentationDocument(state.openDocument);
|
|
3754
4286
|
const { slideIndex } = props.screen;
|
|
3755
4287
|
const slide = doc.editor.slides()[slideIndex];
|
|
3756
4288
|
const [draft, setDraft] = useState(() => slide?.notes ?? "");
|
|
@@ -3855,6 +4387,7 @@ const KIND_BADGE = {
|
|
|
3855
4387
|
boolean: "B",
|
|
3856
4388
|
date: "D",
|
|
3857
4389
|
time: "T",
|
|
4390
|
+
dateTime: "@",
|
|
3858
4391
|
error: "E",
|
|
3859
4392
|
empty: "."
|
|
3860
4393
|
};
|
|
@@ -3866,6 +4399,7 @@ const CELL_VALUE_KINDS = [
|
|
|
3866
4399
|
"boolean",
|
|
3867
4400
|
"date",
|
|
3868
4401
|
"time",
|
|
4402
|
+
"dateTime",
|
|
3869
4403
|
"error"
|
|
3870
4404
|
];
|
|
3871
4405
|
function rawEditableText(value) {
|
|
@@ -3874,6 +4408,7 @@ function rawEditableText(value) {
|
|
|
3874
4408
|
case "string":
|
|
3875
4409
|
case "date":
|
|
3876
4410
|
case "time":
|
|
4411
|
+
case "dateTime":
|
|
3877
4412
|
case "error": return value.value;
|
|
3878
4413
|
case "boolean": return value.value ? "TRUE" : "FALSE";
|
|
3879
4414
|
case "number":
|
|
@@ -3944,6 +4479,13 @@ function buildCellValue(kind, text) {
|
|
|
3944
4479
|
value: trimmed
|
|
3945
4480
|
};
|
|
3946
4481
|
}
|
|
4482
|
+
case "dateTime": {
|
|
4483
|
+
const trimmed = text.trim();
|
|
4484
|
+
return trimmed.length === 0 ? void 0 : {
|
|
4485
|
+
kind: "dateTime",
|
|
4486
|
+
value: trimmed
|
|
4487
|
+
};
|
|
4488
|
+
}
|
|
3947
4489
|
case "error": {
|
|
3948
4490
|
const trimmed = text.trim();
|
|
3949
4491
|
return trimmed.length === 0 ? void 0 : {
|
|
@@ -4691,7 +5233,7 @@ function OdtBodyListScreen() {
|
|
|
4691
5233
|
//#endregion
|
|
4692
5234
|
//#region src/tui/screens/editors/pdf/shared.ts
|
|
4693
5235
|
function requirePdfDocument(openDocument) {
|
|
4694
|
-
if (openDocument?.format !== "pdf") throw new Error("A PDF inspection screen rendered without an open PDF document; the app router only reaches this screen group from pdfPageList, which is only ever the root screen of
|
|
5236
|
+
if (openDocument?.format !== "pdf" && openDocument?.format !== "xlsx") throw new Error("A PDF inspection screen rendered without an open PDF or xlsx document; the app router only reaches this screen group from pdfPageList, which is only ever the root screen of one of those two formats.");
|
|
4695
5237
|
return openDocument;
|
|
4696
5238
|
}
|
|
4697
5239
|
function formatSize(widthPt, heightPt) {
|
|
@@ -4990,12 +5532,17 @@ function PdfPageItemsScreen() {
|
|
|
4990
5532
|
function defaultExportDestinationFor(document, cwd) {
|
|
4991
5533
|
return document.path === void 0 ? join(cwd, "untitled.pdf") : defaultPdfPathFor(document.path);
|
|
4992
5534
|
}
|
|
5535
|
+
function parseFontFileField(value) {
|
|
5536
|
+
return value.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
5537
|
+
}
|
|
4993
5538
|
function ExportOptionsScreen() {
|
|
4994
5539
|
const state = useAppState();
|
|
4995
5540
|
const dispatch = useAppDispatch();
|
|
4996
5541
|
const isActive = !anyOverlayOpen(state);
|
|
4997
5542
|
const document = state.openDocument;
|
|
4998
5543
|
const [destination, setDestination] = useState(() => document === void 0 ? "" : defaultExportDestinationFor(document, state.cwd));
|
|
5544
|
+
const [fontFiles, setFontFiles] = useState("");
|
|
5545
|
+
const [field, setField] = useState("destination");
|
|
4999
5546
|
if (document === void 0) return /* @__PURE__ */ jsx(Box, {
|
|
5000
5547
|
flexDirection: "column",
|
|
5001
5548
|
children: /* @__PURE__ */ jsx(Text, {
|
|
@@ -5003,21 +5550,27 @@ function ExportOptionsScreen() {
|
|
|
5003
5550
|
children: "There is no open document to export."
|
|
5004
5551
|
})
|
|
5005
5552
|
});
|
|
5006
|
-
const
|
|
5553
|
+
const cancel = () => {
|
|
5554
|
+
dispatch({ type: "POP_SCREEN" });
|
|
5555
|
+
};
|
|
5556
|
+
const submit = () => {
|
|
5007
5557
|
(async () => {
|
|
5008
5558
|
let diagnosticCount = 0;
|
|
5009
5559
|
try {
|
|
5010
|
-
await exportToPdf(document,
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5560
|
+
await exportToPdf(document, destination, {
|
|
5561
|
+
fontFiles: parseFontFileField(fontFiles),
|
|
5562
|
+
onDiagnostic: (diagnostic) => {
|
|
5563
|
+
diagnosticCount += 1;
|
|
5564
|
+
dispatch({
|
|
5565
|
+
type: "APPEND_DIAGNOSTIC",
|
|
5566
|
+
diagnostic
|
|
5567
|
+
});
|
|
5568
|
+
}
|
|
5569
|
+
});
|
|
5017
5570
|
dispatch({
|
|
5018
5571
|
type: "SET_STATUS",
|
|
5019
5572
|
severity: "info",
|
|
5020
|
-
text: `Exported ${
|
|
5573
|
+
text: `Exported ${destination}`
|
|
5021
5574
|
});
|
|
5022
5575
|
if (diagnosticCount > 0) dispatch({
|
|
5023
5576
|
type: "OPEN_OVERLAY",
|
|
@@ -5027,7 +5580,7 @@ function ExportOptionsScreen() {
|
|
|
5027
5580
|
} catch (error) {
|
|
5028
5581
|
dispatch({
|
|
5029
5582
|
type: "OPEN_FILE_ERROR",
|
|
5030
|
-
message: `Could not export to ${
|
|
5583
|
+
message: `Could not export to ${destination}`,
|
|
5031
5584
|
detail: describeError(error)
|
|
5032
5585
|
});
|
|
5033
5586
|
}
|
|
@@ -5045,17 +5598,28 @@ function ExportOptionsScreen() {
|
|
|
5045
5598
|
children: "Path: "
|
|
5046
5599
|
}), /* @__PURE__ */ jsx(TextField, {
|
|
5047
5600
|
value: destination,
|
|
5048
|
-
isFocused: isActive,
|
|
5601
|
+
isFocused: isActive && field === "destination",
|
|
5049
5602
|
placeholder: "destination path",
|
|
5050
5603
|
onChange: setDestination,
|
|
5604
|
+
onSubmit: () => {
|
|
5605
|
+
setField("fonts");
|
|
5606
|
+
},
|
|
5607
|
+
onCancel: cancel
|
|
5608
|
+
})] }),
|
|
5609
|
+
/* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, {
|
|
5610
|
+
color: "cyan",
|
|
5611
|
+
children: "Fonts: "
|
|
5612
|
+
}), /* @__PURE__ */ jsx(TextField, {
|
|
5613
|
+
value: fontFiles,
|
|
5614
|
+
isFocused: isActive && field === "fonts",
|
|
5615
|
+
placeholder: "optional .ttf/.otf paths, comma-separated",
|
|
5616
|
+
onChange: setFontFiles,
|
|
5051
5617
|
onSubmit: submit,
|
|
5052
|
-
onCancel:
|
|
5053
|
-
dispatch({ type: "POP_SCREEN" });
|
|
5054
|
-
}
|
|
5618
|
+
onCancel: cancel
|
|
5055
5619
|
})] }),
|
|
5056
5620
|
/* @__PURE__ */ jsx(Text, {
|
|
5057
5621
|
dimColor: true,
|
|
5058
|
-
children: "Enter to export, Esc to cancel"
|
|
5622
|
+
children: field === "destination" ? "Enter for fonts, Esc to cancel" : "Enter to export, Esc to cancel. Each font's family is read from the file itself"
|
|
5059
5623
|
})
|
|
5060
5624
|
]
|
|
5061
5625
|
});
|
|
@@ -5093,7 +5657,7 @@ function defaultBasenameFor(purpose, document) {
|
|
|
5093
5657
|
const sourcePath = document?.path;
|
|
5094
5658
|
const stem = sourcePath === void 0 ? "untitled" : basename(sourcePath, extname(sourcePath));
|
|
5095
5659
|
if (purpose === "exportTarget") return `${stem}.pdf`;
|
|
5096
|
-
return document !== void 0 &&
|
|
5660
|
+
return document !== void 0 && isWritableDocument(document) ? `${stem}.${formatToExtension(document.format)}` : stem;
|
|
5097
5661
|
}
|
|
5098
5662
|
function titleFor(purpose) {
|
|
5099
5663
|
switch (purpose) {
|
|
@@ -5307,7 +5871,7 @@ function LauncherScreen() {
|
|
|
5307
5871
|
}),
|
|
5308
5872
|
/* @__PURE__ */ jsx(Text, {
|
|
5309
5873
|
dimColor: true,
|
|
5310
|
-
children: "A terminal editor for docx, pptx, odt, odp, ods, odg, odb and pdf."
|
|
5874
|
+
children: "A terminal editor for docx, pptx, odt, odp, ods, odg, markdown, odb and pdf -- xlsx opens as a read-only PDF preview."
|
|
5311
5875
|
}),
|
|
5312
5876
|
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
5313
5877
|
/* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
|
|
@@ -5410,7 +5974,7 @@ function NewDocumentPickerScreen() {
|
|
|
5410
5974
|
//#endregion
|
|
5411
5975
|
//#region src/tui/screens/save-as-prompt.tsx
|
|
5412
5976
|
function defaultDestinationFor(document, cwd) {
|
|
5413
|
-
const extension =
|
|
5977
|
+
const extension = isWritableDocument(document) ? formatToExtension(document.format) : "bin";
|
|
5414
5978
|
const suggestedName = document.path === void 0 ? `untitled.${extension}` : basename(document.path);
|
|
5415
5979
|
return join(cwd, suggestedName);
|
|
5416
5980
|
}
|
|
@@ -5493,6 +6057,12 @@ function ScreenBody({ screen }) {
|
|
|
5493
6057
|
case "shapeOrVectorDetail": return /* @__PURE__ */ jsx(OdgShapeOrVectorDetailScreen, {});
|
|
5494
6058
|
case "odbTableList": return /* @__PURE__ */ jsx(OdbTableListScreen, {});
|
|
5495
6059
|
case "odbTableRows": return /* @__PURE__ */ jsx(OdbTableRowsScreen, {});
|
|
6060
|
+
case "odbFormList": return /* @__PURE__ */ jsx(OdbFormListScreen, {});
|
|
6061
|
+
case "odbFormDetail": return /* @__PURE__ */ jsx(OdbFormDetailScreen, {});
|
|
6062
|
+
case "odbReportList": return /* @__PURE__ */ jsx(OdbReportListScreen, {});
|
|
6063
|
+
case "odbReportDetail": return /* @__PURE__ */ jsx(OdbReportDetailScreen, {});
|
|
6064
|
+
case "markdownLineList": return /* @__PURE__ */ jsx(MarkdownLineListScreen, {});
|
|
6065
|
+
case "markdownLineEditor": return /* @__PURE__ */ jsx(MarkdownLineEditorScreen, {});
|
|
5496
6066
|
case "pdfPageList": return /* @__PURE__ */ jsx(PdfPageListScreen, {});
|
|
5497
6067
|
case "pdfPageItems": return /* @__PURE__ */ jsx(PdfPageItemsScreen, {});
|
|
5498
6068
|
case "pdfItemDetail": return /* @__PURE__ */ jsx(PdfItemDetailScreen, {});
|