@fgbg/mdocs 1.3.22 → 1.3.24
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 +1 -1
- package/agent-skills/changelog/SKILL.md +13 -0
- package/agent-skills/getting-started-installation/SKILL.md +8 -2
- package/dist/server/agent/Agent/run-pi-agent.js +7 -0
- package/dist/server/agent/Agent/run-pi-agent.js.map +1 -1
- package/dist/server/agent/Agent/session-manager.js +354 -144
- package/dist/server/agent/Agent/session-manager.js.map +1 -1
- package/dist/server/agent/Agent/session-manager.queue.test.js +65 -0
- package/dist/server/agent/Agent/session-manager.queue.test.js.map +1 -0
- package/dist/server/agent/Agent/session-meta-lock.js +73 -0
- package/dist/server/agent/Agent/session-meta-lock.js.map +1 -0
- package/dist/server/agent/Agent/session-meta-lock.test.js +44 -0
- package/dist/server/agent/Agent/session-meta-lock.test.js.map +1 -0
- package/dist/server/agent/Agent/session-repair.test.js +40 -0
- package/dist/server/agent/Agent/session-repair.test.js.map +1 -0
- package/dist/server/agent/Agent/system-prompt.js +3 -3
- package/dist/server/agent/Agent/tool-ui-effects.js +41 -0
- package/dist/server/agent/Agent/tool-ui-effects.js.map +1 -1
- package/dist/server/agent/Agent/tools-documents.js +62 -12
- package/dist/server/agent/Agent/tools-documents.js.map +1 -1
- package/dist/server/agent/Agent/tools-overwrite.js +1 -1
- package/dist/server/agent/Agent/tools-overwrite.js.map +1 -1
- package/dist/server/agent/Agent/tools-registry.test.js +16 -0
- package/dist/server/agent/Agent/tools-registry.test.js.map +1 -1
- package/dist/server/agent/Agent/tools-sheet.js +918 -0
- package/dist/server/agent/Agent/tools-sheet.js.map +1 -0
- package/dist/server/agent/Agent/tools-sheet.test.js +202 -0
- package/dist/server/agent/Agent/tools-sheet.test.js.map +1 -0
- package/dist/server/agent/Agent/tools-template.js +4 -1
- package/dist/server/agent/Agent/tools-template.js.map +1 -1
- package/dist/server/cli/main.js +81 -4
- package/dist/server/cli/main.js.map +1 -1
- package/dist/server/cli/self-update.js +49 -20
- package/dist/server/cli/self-update.js.map +1 -1
- package/dist/server/cli/self-update.test.js +21 -2
- package/dist/server/cli/self-update.test.js.map +1 -1
- package/dist/server/config/index.js +2 -1
- package/dist/server/config/index.js.map +1 -1
- package/dist/server/documents/document.service.integration.test.js +67 -1
- package/dist/server/documents/document.service.integration.test.js.map +1 -1
- package/dist/server/documents/document.service.js +80 -5
- package/dist/server/documents/document.service.js.map +1 -1
- package/dist/server/main.js +6 -0
- package/dist/server/main.js.map +1 -1
- package/dist/server/routes/documents.routes.js +6 -2
- package/dist/server/routes/documents.routes.js.map +1 -1
- package/dist/server/search/embedding-model.js +33 -11
- package/dist/server/search/embedding-model.js.map +1 -1
- package/dist/shared/docPath.js +5 -2
- package/dist/shared/docPath.js.map +1 -1
- package/dist/shared/file-type-policy.js +15 -0
- package/dist/shared/file-type-policy.js.map +1 -1
- package/dist/shared/file-type-policy.test.js +17 -1
- package/dist/shared/file-type-policy.test.js.map +1 -1
- package/dist/shared/file-types.js +2 -0
- package/dist/shared/file-types.js.map +1 -1
- package/dist/shared/sheetDocument.js +781 -0
- package/dist/shared/sheetDocument.js.map +1 -0
- package/dist/shared/sheetDocument.test.js +179 -0
- package/dist/shared/sheetDocument.test.js.map +1 -0
- package/dist/shared/storagePath.js +15 -8
- package/dist/shared/storagePath.js.map +1 -1
- package/dist/web/assets/AgentChatPanel-NcsMAhWM.js +22 -0
- package/dist/web/assets/{AgentSkillFormCard-CzJG8FK4.js → AgentSkillFormCard-B99CncJO.js} +3 -3
- package/dist/web/assets/{AiWriteWorkbench-DiSDDAee.js → AiWriteWorkbench-DomEbwzU.js} +6 -6
- package/dist/web/assets/{DiagramEditor-NtNY3sZL-DfkpQX3P.js → DiagramEditor-NtNY3sZL-BhsjOCtK.js} +1 -1
- package/dist/web/assets/DocChrome-wfHrPEB7.js +11 -0
- package/dist/web/assets/DocumentEditor-CCuqHlmF.js +1 -0
- package/dist/web/assets/HtmlEditor-g2NNYmv2.js +1 -0
- package/dist/web/assets/{MergeView-CbFSN3Bi.js → MergeView-GL7xdFM4.js} +4 -4
- package/dist/web/assets/PlaygroundPage-Bb4X1bvp.js +8 -0
- package/dist/web/assets/SettingsPage-B4INgMK-.js +1 -0
- package/dist/web/assets/SheetEditor-Cka57s62.js +16 -0
- package/dist/web/assets/SheetEditor-gXD-sRv2.css +1 -0
- package/dist/web/assets/{Toolbar-D9FcRwg3.js → Toolbar-Bk3LKZfg.js} +19 -29
- package/dist/web/assets/VisitorPickerModal-zafJ6beK.js +1 -0
- package/dist/web/assets/{check-Bf8YbzH7.js → check-BMeXlsHC.js} +1 -1
- package/dist/web/assets/{copy-SBghRY2T.js → copy-CCsiKd_j.js} +1 -1
- package/dist/web/assets/{diagram-CgEd0hrk.js → diagram-BF8g9yg4.js} +3 -3
- package/dist/web/assets/docPlainCharCount-BjBZaoHy.js +11 -0
- package/dist/web/assets/{editor-CL03se2B.js → editor-DBWWidi3.js} +1 -1
- package/dist/web/assets/file-type-policy-C6Nun42Q.js +1 -0
- package/dist/web/assets/index-3B7_9QOQ.js +299 -0
- package/dist/web/assets/index-BF754bDB.css +1 -0
- package/dist/web/assets/italic-B6bGjo1_.js +11 -0
- package/dist/web/assets/{meta2d-1-g23JOU.js → meta2d-DP4Vt_ZE.js} +1 -1
- package/dist/web/assets/{meta2dEngine-BD2XbzaV-ulfRZEvk.js → meta2dEngine-BD2XbzaV-9HAADQ7Y.js} +1 -1
- package/dist/web/assets/{motion-Dyy8pB0Q.js → motion-BAXVQJfN.js} +1 -1
- package/dist/web/assets/{plus-CEapsZwl.js → plus-BD44y7Zf.js} +1 -1
- package/dist/web/assets/{react-BkX5Koku.js → react-2y9P8xJn.js} +171 -168
- package/dist/web/assets/{react-vendor-BuABiQkA.js → react-vendor-BVS1XoFi.js} +1 -1
- package/dist/web/assets/{ui-ZPt_BNBg.js → ui-CEFkTVnG.js} +200 -181
- package/dist/web/index.html +6 -6
- package/package.json +30 -21
- package/dist/web/assets/AgentChatPanel-BZlAgyrV.js +0 -22
- package/dist/web/assets/DocChrome-QTGER_Af.js +0 -21
- package/dist/web/assets/DocumentEditor-DYSRZnJa.js +0 -1
- package/dist/web/assets/HtmlEditor-oDPrhq3o.js +0 -1
- package/dist/web/assets/PlaygroundPage-DuMH_V-7.js +0 -8
- package/dist/web/assets/SettingsPage-C_RMO-5M.js +0 -1
- package/dist/web/assets/VisitorPickerModal-CM13Y6jQ.js +0 -1
- package/dist/web/assets/file-type-policy-GUk904tm.js +0 -1
- package/dist/web/assets/index-CIgmAX2Q.css +0 -1
- package/dist/web/assets/index-Cwy87sZC.js +0 -293
|
@@ -0,0 +1,781 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `.sheet` 工作簿:一个文件内多个工作表(tabs)。
|
|
3
|
+
* 磁盘 JSON;每表行列有稳定 id,供 Agent patch。
|
|
4
|
+
* 可选 merges + per-cell style(底色/字色/粗斜体/字号)。
|
|
5
|
+
*/
|
|
6
|
+
export const SHEET_FORMAT_VERSION = 2;
|
|
7
|
+
const FONT_SIZE_MIN = 12;
|
|
8
|
+
const FONT_SIZE_MAX = 24;
|
|
9
|
+
const HEX_COLOR_RE = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
|
|
10
|
+
function newId(prefix) {
|
|
11
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
12
|
+
return `${prefix}_${crypto.randomUUID().slice(0, 8)}`;
|
|
13
|
+
}
|
|
14
|
+
return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
15
|
+
}
|
|
16
|
+
export function emptySheetCell(v = "") {
|
|
17
|
+
return { v };
|
|
18
|
+
}
|
|
19
|
+
export function getCellValue(cell) {
|
|
20
|
+
if (cell == null)
|
|
21
|
+
return "";
|
|
22
|
+
if (typeof cell === "string")
|
|
23
|
+
return cell;
|
|
24
|
+
return cell.v ?? "";
|
|
25
|
+
}
|
|
26
|
+
export function getCellStyle(cell) {
|
|
27
|
+
if (cell == null || typeof cell === "string")
|
|
28
|
+
return undefined;
|
|
29
|
+
return cell.style;
|
|
30
|
+
}
|
|
31
|
+
export function parseCellRaw(raw) {
|
|
32
|
+
if (typeof raw === "string")
|
|
33
|
+
return { v: raw };
|
|
34
|
+
if (raw == null)
|
|
35
|
+
return { v: "" };
|
|
36
|
+
if (typeof raw === "object" && !Array.isArray(raw)) {
|
|
37
|
+
const o = raw;
|
|
38
|
+
const v = typeof o.v === "string" ? o.v : o.v == null ? "" : String(o.v);
|
|
39
|
+
const style = normalizeStyle(o.style);
|
|
40
|
+
return style ? { v, style } : { v };
|
|
41
|
+
}
|
|
42
|
+
return { v: String(raw) };
|
|
43
|
+
}
|
|
44
|
+
export function isValidSheetColor(value) {
|
|
45
|
+
return HEX_COLOR_RE.test(value.trim());
|
|
46
|
+
}
|
|
47
|
+
export function normalizeStyle(raw) {
|
|
48
|
+
if (raw == null)
|
|
49
|
+
return undefined;
|
|
50
|
+
if (typeof raw !== "object" || Array.isArray(raw))
|
|
51
|
+
return undefined;
|
|
52
|
+
const o = raw;
|
|
53
|
+
const style = {};
|
|
54
|
+
if (typeof o.bg === "string" && o.bg.trim()) {
|
|
55
|
+
const bg = o.bg.trim();
|
|
56
|
+
if (isValidSheetColor(bg))
|
|
57
|
+
style.bg = bg;
|
|
58
|
+
}
|
|
59
|
+
if (typeof o.color === "string" && o.color.trim()) {
|
|
60
|
+
const color = o.color.trim();
|
|
61
|
+
if (isValidSheetColor(color))
|
|
62
|
+
style.color = color;
|
|
63
|
+
}
|
|
64
|
+
if (o.bold === true)
|
|
65
|
+
style.bold = true;
|
|
66
|
+
if (o.italic === true)
|
|
67
|
+
style.italic = true;
|
|
68
|
+
if (typeof o.fontSize === "number" && Number.isFinite(o.fontSize)) {
|
|
69
|
+
const n = Math.round(o.fontSize);
|
|
70
|
+
if (n >= FONT_SIZE_MIN && n <= FONT_SIZE_MAX)
|
|
71
|
+
style.fontSize = n;
|
|
72
|
+
}
|
|
73
|
+
return Object.keys(style).length > 0 ? style : undefined;
|
|
74
|
+
}
|
|
75
|
+
/** patch 合并进现有 style;传 null 清除 */
|
|
76
|
+
export function mergeCellStyle(current, patch) {
|
|
77
|
+
if (patch === null)
|
|
78
|
+
return undefined;
|
|
79
|
+
if (patch == null)
|
|
80
|
+
return current;
|
|
81
|
+
const next = { ...(current ?? {}) };
|
|
82
|
+
if ("bg" in patch) {
|
|
83
|
+
if (patch.bg == null || patch.bg === "")
|
|
84
|
+
delete next.bg;
|
|
85
|
+
else if (isValidSheetColor(patch.bg))
|
|
86
|
+
next.bg = patch.bg.trim();
|
|
87
|
+
}
|
|
88
|
+
if ("color" in patch) {
|
|
89
|
+
if (patch.color == null || patch.color === "")
|
|
90
|
+
delete next.color;
|
|
91
|
+
else if (isValidSheetColor(patch.color))
|
|
92
|
+
next.color = patch.color.trim();
|
|
93
|
+
}
|
|
94
|
+
if ("bold" in patch) {
|
|
95
|
+
if (patch.bold)
|
|
96
|
+
next.bold = true;
|
|
97
|
+
else
|
|
98
|
+
delete next.bold;
|
|
99
|
+
}
|
|
100
|
+
if ("italic" in patch) {
|
|
101
|
+
if (patch.italic)
|
|
102
|
+
next.italic = true;
|
|
103
|
+
else
|
|
104
|
+
delete next.italic;
|
|
105
|
+
}
|
|
106
|
+
if ("fontSize" in patch) {
|
|
107
|
+
if (patch.fontSize == null)
|
|
108
|
+
delete next.fontSize;
|
|
109
|
+
else {
|
|
110
|
+
const n = Math.round(Number(patch.fontSize));
|
|
111
|
+
if (n >= FONT_SIZE_MIN && n <= FONT_SIZE_MAX)
|
|
112
|
+
next.fontSize = n;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return Object.keys(next).length > 0 ? next : undefined;
|
|
116
|
+
}
|
|
117
|
+
function compactCellForDisk(cell) {
|
|
118
|
+
const style = normalizeStyle(cell.style);
|
|
119
|
+
if (!style)
|
|
120
|
+
return cell.v;
|
|
121
|
+
return { v: cell.v, style };
|
|
122
|
+
}
|
|
123
|
+
function compactTabForDisk(tab) {
|
|
124
|
+
const merges = (tab.merges ?? []).filter(Boolean);
|
|
125
|
+
return {
|
|
126
|
+
id: tab.id,
|
|
127
|
+
name: tab.name,
|
|
128
|
+
columns: tab.columns,
|
|
129
|
+
rows: tab.rows.map((r) => ({
|
|
130
|
+
id: r.id,
|
|
131
|
+
cells: Object.fromEntries(Object.entries(r.cells).map(([k, cell]) => [k, compactCellForDisk(cell)])),
|
|
132
|
+
})),
|
|
133
|
+
...(merges.length > 0 ? { merges } : {}),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
function createEmptyTab(opts) {
|
|
137
|
+
const columnCount = Math.max(1, opts?.columnCount ?? 3);
|
|
138
|
+
const rowCount = Math.max(0, opts?.rowCount ?? 5);
|
|
139
|
+
const columns = [];
|
|
140
|
+
for (let i = 0; i < columnCount; i += 1) {
|
|
141
|
+
columns.push({ id: newId("c"), name: `列${i + 1}` });
|
|
142
|
+
}
|
|
143
|
+
const rows = [];
|
|
144
|
+
for (let i = 0; i < rowCount; i += 1) {
|
|
145
|
+
const cells = {};
|
|
146
|
+
for (const col of columns)
|
|
147
|
+
cells[col.id] = emptySheetCell();
|
|
148
|
+
rows.push({ id: newId("r"), cells });
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
id: newId("s"),
|
|
152
|
+
name: opts?.name?.trim() || "Sheet1",
|
|
153
|
+
columns,
|
|
154
|
+
rows,
|
|
155
|
+
merges: [],
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
export function createEmptySheetDocument(opts) {
|
|
159
|
+
const tab = createEmptyTab(opts);
|
|
160
|
+
return {
|
|
161
|
+
version: SHEET_FORMAT_VERSION,
|
|
162
|
+
sheets: [tab],
|
|
163
|
+
activeSheetId: tab.id,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
export function serializeSheetDocument(doc) {
|
|
167
|
+
const payload = {
|
|
168
|
+
version: SHEET_FORMAT_VERSION,
|
|
169
|
+
activeSheetId: doc.activeSheetId,
|
|
170
|
+
sheets: doc.sheets.map(compactTabForDisk),
|
|
171
|
+
};
|
|
172
|
+
return `${JSON.stringify(payload, null, 2)}\n`;
|
|
173
|
+
}
|
|
174
|
+
function parseMerges(raw, tab) {
|
|
175
|
+
if (!Array.isArray(raw))
|
|
176
|
+
return [];
|
|
177
|
+
const rowIds = new Set(tab.rows.map((r) => r.id));
|
|
178
|
+
const colIds = new Set(tab.columns.map((c) => c.id));
|
|
179
|
+
const out = [];
|
|
180
|
+
for (const m of raw) {
|
|
181
|
+
if (!m || typeof m !== "object")
|
|
182
|
+
continue;
|
|
183
|
+
const o = m;
|
|
184
|
+
const startRowId = typeof o.startRowId === "string" ? o.startRowId : "";
|
|
185
|
+
const startColId = typeof o.startColId === "string" ? o.startColId : "";
|
|
186
|
+
const endRowId = typeof o.endRowId === "string" ? o.endRowId : "";
|
|
187
|
+
const endColId = typeof o.endColId === "string" ? o.endColId : "";
|
|
188
|
+
if (!rowIds.has(startRowId) || !rowIds.has(endRowId))
|
|
189
|
+
continue;
|
|
190
|
+
if (!colIds.has(startColId) || !colIds.has(endColId))
|
|
191
|
+
continue;
|
|
192
|
+
out.push({ startRowId, startColId, endRowId, endColId });
|
|
193
|
+
}
|
|
194
|
+
return out;
|
|
195
|
+
}
|
|
196
|
+
function parseTab(raw, fallbackName) {
|
|
197
|
+
if (!raw || typeof raw !== "object")
|
|
198
|
+
return null;
|
|
199
|
+
const obj = raw;
|
|
200
|
+
const columnsIn = Array.isArray(obj.columns) ? obj.columns : [];
|
|
201
|
+
const rowsIn = Array.isArray(obj.rows) ? obj.rows : [];
|
|
202
|
+
const columns = [];
|
|
203
|
+
for (const c of columnsIn) {
|
|
204
|
+
if (!c || typeof c !== "object")
|
|
205
|
+
continue;
|
|
206
|
+
const col = c;
|
|
207
|
+
const id = typeof col.id === "string" && col.id ? col.id : newId("c");
|
|
208
|
+
const name = typeof col.name === "string" ? col.name : id;
|
|
209
|
+
columns.push({ id, name });
|
|
210
|
+
}
|
|
211
|
+
if (columns.length === 0)
|
|
212
|
+
return null;
|
|
213
|
+
const rows = [];
|
|
214
|
+
for (const r of rowsIn) {
|
|
215
|
+
if (!r || typeof r !== "object")
|
|
216
|
+
continue;
|
|
217
|
+
const row = r;
|
|
218
|
+
const id = typeof row.id === "string" && row.id ? row.id : newId("r");
|
|
219
|
+
const cellsRaw = row.cells && typeof row.cells === "object" && !Array.isArray(row.cells)
|
|
220
|
+
? row.cells
|
|
221
|
+
: {};
|
|
222
|
+
const cells = {};
|
|
223
|
+
for (const col of columns) {
|
|
224
|
+
cells[col.id] = parseCellRaw(cellsRaw[col.id]);
|
|
225
|
+
}
|
|
226
|
+
rows.push({ id, cells });
|
|
227
|
+
}
|
|
228
|
+
const id = typeof obj.id === "string" && obj.id ? obj.id : newId("s");
|
|
229
|
+
const name = typeof obj.name === "string" && obj.name.trim()
|
|
230
|
+
? obj.name.trim()
|
|
231
|
+
: fallbackName;
|
|
232
|
+
const merges = parseMerges(obj.merges, { columns, rows });
|
|
233
|
+
return { id, name, columns, rows, merges };
|
|
234
|
+
}
|
|
235
|
+
/** 兼容 v1:顶层 columns/rows → 单表工作簿。 */
|
|
236
|
+
export function parseSheetDocument(raw) {
|
|
237
|
+
const trimmed = raw.trim();
|
|
238
|
+
if (!trimmed)
|
|
239
|
+
return createEmptySheetDocument();
|
|
240
|
+
let parsed;
|
|
241
|
+
try {
|
|
242
|
+
parsed = JSON.parse(trimmed);
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
return createEmptySheetDocument();
|
|
246
|
+
}
|
|
247
|
+
if (!parsed || typeof parsed !== "object")
|
|
248
|
+
return createEmptySheetDocument();
|
|
249
|
+
const obj = parsed;
|
|
250
|
+
if (Array.isArray(obj.sheets) && obj.sheets.length > 0) {
|
|
251
|
+
const sheets = [];
|
|
252
|
+
for (let i = 0; i < obj.sheets.length; i += 1) {
|
|
253
|
+
const tab = parseTab(obj.sheets[i], `Sheet${i + 1}`);
|
|
254
|
+
if (tab)
|
|
255
|
+
sheets.push(tab);
|
|
256
|
+
}
|
|
257
|
+
if (sheets.length === 0)
|
|
258
|
+
return createEmptySheetDocument();
|
|
259
|
+
const activeRaw = typeof obj.activeSheetId === "string" ? obj.activeSheetId : "";
|
|
260
|
+
const activeSheetId = sheets.some((s) => s.id === activeRaw)
|
|
261
|
+
? activeRaw
|
|
262
|
+
: sheets[0].id;
|
|
263
|
+
return { version: SHEET_FORMAT_VERSION, sheets, activeSheetId };
|
|
264
|
+
}
|
|
265
|
+
const tab = parseTab(obj, "Sheet1");
|
|
266
|
+
if (!tab)
|
|
267
|
+
return createEmptySheetDocument();
|
|
268
|
+
return {
|
|
269
|
+
version: SHEET_FORMAT_VERSION,
|
|
270
|
+
sheets: [tab],
|
|
271
|
+
activeSheetId: tab.id,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
export function resolveSheetTab(doc, sheet) {
|
|
275
|
+
const key = sheet?.trim();
|
|
276
|
+
if (!key) {
|
|
277
|
+
return doc.sheets.find((s) => s.id === doc.activeSheetId) ?? doc.sheets[0] ?? null;
|
|
278
|
+
}
|
|
279
|
+
const byId = doc.sheets.find((s) => s.id === key);
|
|
280
|
+
if (byId)
|
|
281
|
+
return byId;
|
|
282
|
+
const byName = doc.sheets.find((s) => s.name === key);
|
|
283
|
+
if (byName)
|
|
284
|
+
return byName;
|
|
285
|
+
const lower = key.toLowerCase();
|
|
286
|
+
return doc.sheets.find((s) => s.name.toLowerCase() === lower) ?? null;
|
|
287
|
+
}
|
|
288
|
+
export function replaceSheetTab(doc, tab) {
|
|
289
|
+
if (!doc.sheets.some((s) => s.id === tab.id)) {
|
|
290
|
+
throw new Error(`unknown sheet: ${tab.id}`);
|
|
291
|
+
}
|
|
292
|
+
return {
|
|
293
|
+
...doc,
|
|
294
|
+
sheets: doc.sheets.map((s) => (s.id === tab.id ? tab : s)),
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
export function setActiveSheet(doc, sheet) {
|
|
298
|
+
const tab = resolveSheetTab(doc, sheet);
|
|
299
|
+
if (!tab)
|
|
300
|
+
throw new Error(`unknown sheet: ${sheet}`);
|
|
301
|
+
return { ...doc, activeSheetId: tab.id };
|
|
302
|
+
}
|
|
303
|
+
export function addWorkbookSheet(doc, name) {
|
|
304
|
+
let base = (name?.trim() || `Sheet${doc.sheets.length + 1}`);
|
|
305
|
+
const existing = new Set(doc.sheets.map((s) => s.name));
|
|
306
|
+
if (existing.has(base)) {
|
|
307
|
+
let n = 2;
|
|
308
|
+
while (existing.has(`${base}${n}`))
|
|
309
|
+
n += 1;
|
|
310
|
+
base = `${base}${n}`;
|
|
311
|
+
}
|
|
312
|
+
const tab = createEmptyTab({ name: base });
|
|
313
|
+
return {
|
|
314
|
+
doc: {
|
|
315
|
+
...doc,
|
|
316
|
+
sheets: [...doc.sheets, tab],
|
|
317
|
+
activeSheetId: tab.id,
|
|
318
|
+
},
|
|
319
|
+
tab,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
export function renameWorkbookSheet(doc, sheet, name) {
|
|
323
|
+
const tab = resolveSheetTab(doc, sheet);
|
|
324
|
+
if (!tab)
|
|
325
|
+
throw new Error(`unknown sheet: ${sheet}`);
|
|
326
|
+
const nextName = name.trim();
|
|
327
|
+
if (!nextName)
|
|
328
|
+
throw new Error("sheet name is required");
|
|
329
|
+
const clash = doc.sheets.find((s) => s.id !== tab.id && s.name === nextName);
|
|
330
|
+
if (clash)
|
|
331
|
+
throw new Error(`sheet name already exists: ${nextName}`);
|
|
332
|
+
return replaceSheetTab(doc, { ...tab, name: nextName });
|
|
333
|
+
}
|
|
334
|
+
export function removeWorkbookSheet(doc, sheet) {
|
|
335
|
+
const tab = resolveSheetTab(doc, sheet);
|
|
336
|
+
if (!tab)
|
|
337
|
+
throw new Error(`unknown sheet: ${sheet}`);
|
|
338
|
+
if (doc.sheets.length <= 1) {
|
|
339
|
+
throw new Error("cannot delete the last sheet");
|
|
340
|
+
}
|
|
341
|
+
const sheets = doc.sheets.filter((s) => s.id !== tab.id);
|
|
342
|
+
const activeSheetId = doc.activeSheetId === tab.id ? sheets[0].id : doc.activeSheetId;
|
|
343
|
+
return { ...doc, sheets, activeSheetId };
|
|
344
|
+
}
|
|
345
|
+
/* ── merge helpers ── */
|
|
346
|
+
function rowIndex(tab, rowId) {
|
|
347
|
+
return tab.rows.findIndex((r) => r.id === rowId);
|
|
348
|
+
}
|
|
349
|
+
function colIndex(tab, colId) {
|
|
350
|
+
return tab.columns.findIndex((c) => c.id === colId);
|
|
351
|
+
}
|
|
352
|
+
function normalizeMergeRange(tab, range) {
|
|
353
|
+
const r0 = rowIndex(tab, range.startRowId);
|
|
354
|
+
const r1 = rowIndex(tab, range.endRowId);
|
|
355
|
+
const c0 = colIndex(tab, range.startColId);
|
|
356
|
+
const c1 = colIndex(tab, range.endColId);
|
|
357
|
+
if (r0 < 0 || r1 < 0 || c0 < 0 || c1 < 0)
|
|
358
|
+
return null;
|
|
359
|
+
const rs = Math.min(r0, r1);
|
|
360
|
+
const re = Math.max(r0, r1);
|
|
361
|
+
const cs = Math.min(c0, c1);
|
|
362
|
+
const ce = Math.max(c0, c1);
|
|
363
|
+
if (rs === re && cs === ce)
|
|
364
|
+
return null; // 单格不算合并
|
|
365
|
+
return {
|
|
366
|
+
startRowId: tab.rows[rs].id,
|
|
367
|
+
startColId: tab.columns[cs].id,
|
|
368
|
+
endRowId: tab.rows[re].id,
|
|
369
|
+
endColId: tab.columns[ce].id,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
function mergeOverlaps(tab, a, b) {
|
|
373
|
+
const ar0 = rowIndex(tab, a.startRowId);
|
|
374
|
+
const ar1 = rowIndex(tab, a.endRowId);
|
|
375
|
+
const ac0 = colIndex(tab, a.startColId);
|
|
376
|
+
const ac1 = colIndex(tab, a.endColId);
|
|
377
|
+
const br0 = rowIndex(tab, b.startRowId);
|
|
378
|
+
const br1 = rowIndex(tab, b.endRowId);
|
|
379
|
+
const bc0 = colIndex(tab, b.startColId);
|
|
380
|
+
const bc1 = colIndex(tab, b.endColId);
|
|
381
|
+
if (ar0 < 0 || ar1 < 0 || br0 < 0 || br1 < 0)
|
|
382
|
+
return false;
|
|
383
|
+
if (ac0 < 0 || ac1 < 0 || bc0 < 0 || bc1 < 0)
|
|
384
|
+
return false;
|
|
385
|
+
const aR0 = Math.min(ar0, ar1);
|
|
386
|
+
const aR1 = Math.max(ar0, ar1);
|
|
387
|
+
const aC0 = Math.min(ac0, ac1);
|
|
388
|
+
const aC1 = Math.max(ac0, ac1);
|
|
389
|
+
const bR0 = Math.min(br0, br1);
|
|
390
|
+
const bR1 = Math.max(br0, br1);
|
|
391
|
+
const bC0 = Math.min(bc0, bc1);
|
|
392
|
+
const bC1 = Math.max(bc0, bc1);
|
|
393
|
+
return !(aR1 < bR0 || bR1 < aR0 || aC1 < bC0 || bC1 < aC0);
|
|
394
|
+
}
|
|
395
|
+
export function findMergeAt(tab, rowId, columnId) {
|
|
396
|
+
const ri = rowIndex(tab, rowId);
|
|
397
|
+
const ci = colIndex(tab, columnId);
|
|
398
|
+
if (ri < 0 || ci < 0)
|
|
399
|
+
return null;
|
|
400
|
+
for (const m of tab.merges ?? []) {
|
|
401
|
+
const r0 = rowIndex(tab, m.startRowId);
|
|
402
|
+
const r1 = rowIndex(tab, m.endRowId);
|
|
403
|
+
const c0 = colIndex(tab, m.startColId);
|
|
404
|
+
const c1 = colIndex(tab, m.endColId);
|
|
405
|
+
if (r0 < 0 || r1 < 0 || c0 < 0 || c1 < 0)
|
|
406
|
+
continue;
|
|
407
|
+
const rs = Math.min(r0, r1);
|
|
408
|
+
const re = Math.max(r0, r1);
|
|
409
|
+
const cs = Math.min(c0, c1);
|
|
410
|
+
const ce = Math.max(c0, c1);
|
|
411
|
+
if (ri >= rs && ri <= re && ci >= cs && ci <= ce)
|
|
412
|
+
return m;
|
|
413
|
+
}
|
|
414
|
+
return null;
|
|
415
|
+
}
|
|
416
|
+
export function isMergeMaster(tab, rowId, columnId) {
|
|
417
|
+
const m = findMergeAt(tab, rowId, columnId);
|
|
418
|
+
if (!m)
|
|
419
|
+
return true;
|
|
420
|
+
return m.startRowId === rowId && m.startColId === columnId;
|
|
421
|
+
}
|
|
422
|
+
export function isCoveredCell(tab, rowId, columnId) {
|
|
423
|
+
const m = findMergeAt(tab, rowId, columnId);
|
|
424
|
+
if (!m)
|
|
425
|
+
return false;
|
|
426
|
+
return !(m.startRowId === rowId && m.startColId === columnId);
|
|
427
|
+
}
|
|
428
|
+
export function mergeSpan(tab, merge) {
|
|
429
|
+
const r0 = rowIndex(tab, merge.startRowId);
|
|
430
|
+
const r1 = rowIndex(tab, merge.endRowId);
|
|
431
|
+
const c0 = colIndex(tab, merge.startColId);
|
|
432
|
+
const c1 = colIndex(tab, merge.endColId);
|
|
433
|
+
return {
|
|
434
|
+
rowSpan: Math.abs(r1 - r0) + 1,
|
|
435
|
+
colSpan: Math.abs(c1 - c0) + 1,
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
function scrubMerges(tab) {
|
|
439
|
+
const rowIds = new Set(tab.rows.map((r) => r.id));
|
|
440
|
+
const colIds = new Set(tab.columns.map((c) => c.id));
|
|
441
|
+
const merges = (tab.merges ?? []).filter((m) => rowIds.has(m.startRowId) &&
|
|
442
|
+
rowIds.has(m.endRowId) &&
|
|
443
|
+
colIds.has(m.startColId) &&
|
|
444
|
+
colIds.has(m.endColId) &&
|
|
445
|
+
normalizeMergeRange(tab, m) != null);
|
|
446
|
+
return { ...tab, merges };
|
|
447
|
+
}
|
|
448
|
+
export function mergeSheetCells(tab, range) {
|
|
449
|
+
const normalized = normalizeMergeRange(tab, range);
|
|
450
|
+
if (!normalized) {
|
|
451
|
+
throw new Error("invalid merge range (need at least 2 cells)");
|
|
452
|
+
}
|
|
453
|
+
for (const existing of tab.merges ?? []) {
|
|
454
|
+
if (mergeOverlaps(tab, existing, normalized)) {
|
|
455
|
+
throw new Error("merge overlaps existing merge");
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
return {
|
|
459
|
+
...tab,
|
|
460
|
+
merges: [...(tab.merges ?? []), normalized],
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
export function unmergeSheetCells(tab, rowId, columnId) {
|
|
464
|
+
const hit = findMergeAt(tab, rowId, columnId);
|
|
465
|
+
if (!hit)
|
|
466
|
+
return { tab, removed: null };
|
|
467
|
+
return {
|
|
468
|
+
tab: {
|
|
469
|
+
...tab,
|
|
470
|
+
merges: (tab.merges ?? []).filter((m) => m !== hit),
|
|
471
|
+
},
|
|
472
|
+
removed: hit,
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
/** 写 covered 格时重定向到主格 */
|
|
476
|
+
export function resolveWriteTarget(tab, rowId, columnId) {
|
|
477
|
+
const m = findMergeAt(tab, rowId, columnId);
|
|
478
|
+
if (!m || (m.startRowId === rowId && m.startColId === columnId)) {
|
|
479
|
+
return { rowId, columnId, redirected: false };
|
|
480
|
+
}
|
|
481
|
+
return {
|
|
482
|
+
rowId: m.startRowId,
|
|
483
|
+
columnId: m.startColId,
|
|
484
|
+
redirected: true,
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
/* ── 单表(tab)级操作 ── */
|
|
488
|
+
export function addSheetColumn(tab, name) {
|
|
489
|
+
const col = {
|
|
490
|
+
id: newId("c"),
|
|
491
|
+
name: name?.trim() || `列${tab.columns.length + 1}`,
|
|
492
|
+
};
|
|
493
|
+
return {
|
|
494
|
+
...tab,
|
|
495
|
+
columns: [...tab.columns, col],
|
|
496
|
+
rows: tab.rows.map((r) => ({
|
|
497
|
+
...r,
|
|
498
|
+
cells: { ...r.cells, [col.id]: emptySheetCell() },
|
|
499
|
+
})),
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
export function addSheetRow(tab) {
|
|
503
|
+
const cells = {};
|
|
504
|
+
for (const col of tab.columns)
|
|
505
|
+
cells[col.id] = emptySheetCell();
|
|
506
|
+
return {
|
|
507
|
+
...tab,
|
|
508
|
+
rows: [...tab.rows, { id: newId("r"), cells }],
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
export function setSheetCell(tab, rowId, columnId, value) {
|
|
512
|
+
const target = resolveWriteTarget(tab, rowId, columnId);
|
|
513
|
+
return {
|
|
514
|
+
...tab,
|
|
515
|
+
rows: tab.rows.map((r) => {
|
|
516
|
+
if (r.id !== target.rowId)
|
|
517
|
+
return r;
|
|
518
|
+
const prev = r.cells[target.columnId] ?? emptySheetCell();
|
|
519
|
+
return {
|
|
520
|
+
...r,
|
|
521
|
+
cells: {
|
|
522
|
+
...r.cells,
|
|
523
|
+
[target.columnId]: { v: value, ...(prev.style ? { style: prev.style } : {}) },
|
|
524
|
+
},
|
|
525
|
+
};
|
|
526
|
+
}),
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
export function setSheetCellStyle(tab, rowId, columnId, patch) {
|
|
530
|
+
const target = resolveWriteTarget(tab, rowId, columnId);
|
|
531
|
+
return {
|
|
532
|
+
...tab,
|
|
533
|
+
rows: tab.rows.map((r) => {
|
|
534
|
+
if (r.id !== target.rowId)
|
|
535
|
+
return r;
|
|
536
|
+
const prev = r.cells[target.columnId] ?? emptySheetCell();
|
|
537
|
+
const style = mergeCellStyle(prev.style, patch);
|
|
538
|
+
const next = style ? { v: prev.v, style } : { v: prev.v };
|
|
539
|
+
return { ...r, cells: { ...r.cells, [target.columnId]: next } };
|
|
540
|
+
}),
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
export function setSheetCellStyles(tab, updates) {
|
|
544
|
+
let next = tab;
|
|
545
|
+
let applied = 0;
|
|
546
|
+
const errors = [];
|
|
547
|
+
const warnings = [];
|
|
548
|
+
for (const u of updates) {
|
|
549
|
+
const rowId = u.rowId.trim();
|
|
550
|
+
const col = resolveSheetColumn(next, u.column);
|
|
551
|
+
if (!rowId) {
|
|
552
|
+
errors.push("empty rowId");
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
555
|
+
if (!col) {
|
|
556
|
+
errors.push(`unknown column: ${u.column}`);
|
|
557
|
+
continue;
|
|
558
|
+
}
|
|
559
|
+
if (!next.rows.some((r) => r.id === rowId)) {
|
|
560
|
+
errors.push(`unknown rowId: ${rowId}`);
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
563
|
+
const target = resolveWriteTarget(next, rowId, col.id);
|
|
564
|
+
if (target.redirected) {
|
|
565
|
+
warnings.push(`redirected ${rowId}/${col.id} → merge master`);
|
|
566
|
+
}
|
|
567
|
+
next = setSheetCellStyle(next, target.rowId, target.columnId, u.style);
|
|
568
|
+
applied += 1;
|
|
569
|
+
}
|
|
570
|
+
return { tab: next, applied, errors, warnings };
|
|
571
|
+
}
|
|
572
|
+
export function setSheetColumnName(tab, columnId, name) {
|
|
573
|
+
return {
|
|
574
|
+
...tab,
|
|
575
|
+
columns: tab.columns.map((c) => (c.id === columnId ? { ...c, name } : c)),
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
export function removeSheetRow(tab, rowId) {
|
|
579
|
+
return scrubMerges({ ...tab, rows: tab.rows.filter((r) => r.id !== rowId) });
|
|
580
|
+
}
|
|
581
|
+
export function removeSheetRows(tab, rowIds) {
|
|
582
|
+
const want = new Set(rowIds.map((id) => id.trim()).filter(Boolean));
|
|
583
|
+
const missing = [];
|
|
584
|
+
for (const id of want) {
|
|
585
|
+
if (!tab.rows.some((r) => r.id === id))
|
|
586
|
+
missing.push(id);
|
|
587
|
+
}
|
|
588
|
+
const removed = [...want].filter((id) => !missing.includes(id));
|
|
589
|
+
return {
|
|
590
|
+
tab: scrubMerges({ ...tab, rows: tab.rows.filter((r) => !want.has(r.id)) }),
|
|
591
|
+
removed,
|
|
592
|
+
missing,
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
export function removeSheetColumns(tab, columns) {
|
|
596
|
+
const errors = [];
|
|
597
|
+
const toRemove = new Set();
|
|
598
|
+
const removed = [];
|
|
599
|
+
for (const raw of columns) {
|
|
600
|
+
const col = resolveSheetColumn(tab, raw);
|
|
601
|
+
if (!col) {
|
|
602
|
+
errors.push(`unknown column: ${raw}`);
|
|
603
|
+
continue;
|
|
604
|
+
}
|
|
605
|
+
if (toRemove.has(col.id))
|
|
606
|
+
continue;
|
|
607
|
+
toRemove.add(col.id);
|
|
608
|
+
removed.push({ id: col.id, name: col.name });
|
|
609
|
+
}
|
|
610
|
+
if (toRemove.size === 0) {
|
|
611
|
+
return { tab, removed: [], errors };
|
|
612
|
+
}
|
|
613
|
+
if (tab.columns.length - toRemove.size < 1) {
|
|
614
|
+
throw new Error("cannot delete all columns; at least one column required");
|
|
615
|
+
}
|
|
616
|
+
const nextColumns = tab.columns.filter((c) => !toRemove.has(c.id));
|
|
617
|
+
const nextRows = tab.rows.map((r) => {
|
|
618
|
+
const cells = { ...r.cells };
|
|
619
|
+
for (const id of toRemove)
|
|
620
|
+
delete cells[id];
|
|
621
|
+
return { ...r, cells };
|
|
622
|
+
});
|
|
623
|
+
return {
|
|
624
|
+
tab: scrubMerges({ ...tab, columns: nextColumns, rows: nextRows }),
|
|
625
|
+
removed,
|
|
626
|
+
errors,
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
export function resolveSheetColumn(tab, column) {
|
|
630
|
+
const key = column.trim();
|
|
631
|
+
if (!key)
|
|
632
|
+
return null;
|
|
633
|
+
const byId = tab.columns.find((c) => c.id === key);
|
|
634
|
+
if (byId)
|
|
635
|
+
return byId;
|
|
636
|
+
const byName = tab.columns.find((c) => c.name === key);
|
|
637
|
+
if (byName)
|
|
638
|
+
return byName;
|
|
639
|
+
const lower = key.toLowerCase();
|
|
640
|
+
return tab.columns.find((c) => c.name.toLowerCase() === lower) ?? null;
|
|
641
|
+
}
|
|
642
|
+
export function sheetRowAsNamedCells(tab, row) {
|
|
643
|
+
const out = {};
|
|
644
|
+
for (const col of tab.columns) {
|
|
645
|
+
out[col.name] = getCellValue(row.cells[col.id]);
|
|
646
|
+
}
|
|
647
|
+
return out;
|
|
648
|
+
}
|
|
649
|
+
export function setSheetCells(tab, updates) {
|
|
650
|
+
let next = tab;
|
|
651
|
+
let applied = 0;
|
|
652
|
+
const errors = [];
|
|
653
|
+
const warnings = [];
|
|
654
|
+
for (const u of updates) {
|
|
655
|
+
const rowId = u.rowId.trim();
|
|
656
|
+
const col = resolveSheetColumn(next, u.column);
|
|
657
|
+
if (!rowId) {
|
|
658
|
+
errors.push("empty rowId");
|
|
659
|
+
continue;
|
|
660
|
+
}
|
|
661
|
+
if (!col) {
|
|
662
|
+
errors.push(`unknown column: ${u.column}`);
|
|
663
|
+
continue;
|
|
664
|
+
}
|
|
665
|
+
if (!next.rows.some((r) => r.id === rowId)) {
|
|
666
|
+
errors.push(`unknown rowId: ${rowId}`);
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
const target = resolveWriteTarget(next, rowId, col.id);
|
|
670
|
+
if (target.redirected) {
|
|
671
|
+
warnings.push(`redirected ${rowId}/${col.id} → merge master`);
|
|
672
|
+
}
|
|
673
|
+
next = setSheetCell(next, target.rowId, target.columnId, u.value);
|
|
674
|
+
applied += 1;
|
|
675
|
+
}
|
|
676
|
+
return { tab: next, applied, errors, warnings };
|
|
677
|
+
}
|
|
678
|
+
export function upsertSheetRow(tab, cells, rowId) {
|
|
679
|
+
const resolved = {};
|
|
680
|
+
for (const [key, value] of Object.entries(cells)) {
|
|
681
|
+
const col = resolveSheetColumn(tab, key);
|
|
682
|
+
if (!col) {
|
|
683
|
+
throw new Error(`unknown column: ${key}`);
|
|
684
|
+
}
|
|
685
|
+
resolved[col.id] = value;
|
|
686
|
+
}
|
|
687
|
+
const id = rowId?.trim() || newId("r");
|
|
688
|
+
const existing = tab.rows.find((r) => r.id === id);
|
|
689
|
+
if (existing) {
|
|
690
|
+
const mergedCells = { ...existing.cells };
|
|
691
|
+
for (const [cid, v] of Object.entries(resolved)) {
|
|
692
|
+
const prev = mergedCells[cid] ?? emptySheetCell();
|
|
693
|
+
mergedCells[cid] = {
|
|
694
|
+
v,
|
|
695
|
+
...(prev.style ? { style: prev.style } : {}),
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
const row = { id, cells: mergedCells };
|
|
699
|
+
return {
|
|
700
|
+
tab: {
|
|
701
|
+
...tab,
|
|
702
|
+
rows: tab.rows.map((r) => (r.id === id ? row : r)),
|
|
703
|
+
},
|
|
704
|
+
row,
|
|
705
|
+
created: false,
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
const cellsFull = {};
|
|
709
|
+
for (const col of tab.columns) {
|
|
710
|
+
cellsFull[col.id] = emptySheetCell(resolved[col.id] ?? "");
|
|
711
|
+
}
|
|
712
|
+
const row = { id, cells: cellsFull };
|
|
713
|
+
return {
|
|
714
|
+
tab: { ...tab, rows: [...tab.rows, row] },
|
|
715
|
+
row,
|
|
716
|
+
created: true,
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
/** 表末批量追加行;未知列名在写任何行之前失败。 */
|
|
720
|
+
export function insertSheetRows(tab, rows) {
|
|
721
|
+
for (const cells of rows) {
|
|
722
|
+
for (const key of Object.keys(cells)) {
|
|
723
|
+
if (!resolveSheetColumn(tab, key)) {
|
|
724
|
+
throw new Error(`unknown column: ${key}`);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
let next = tab;
|
|
729
|
+
const rowIds = [];
|
|
730
|
+
for (const cells of rows) {
|
|
731
|
+
const result = upsertSheetRow(next, cells);
|
|
732
|
+
next = result.tab;
|
|
733
|
+
rowIds.push(result.row.id);
|
|
734
|
+
}
|
|
735
|
+
return { tab: next, rowIds, inserted: rowIds.length };
|
|
736
|
+
}
|
|
737
|
+
const OVERWRITE_MAX_COLUMNS = 50;
|
|
738
|
+
const OVERWRITE_MAX_ROWS = 500;
|
|
739
|
+
/** 整表替换列与行;保留 tab.id,可选改 name。旧 column/row id 全部重建。 */
|
|
740
|
+
export function overwriteSheetTab(tab, input) {
|
|
741
|
+
const colNames = input.columns.map((c) => String(c ?? "").trim());
|
|
742
|
+
if (colNames.length === 0) {
|
|
743
|
+
throw new Error("columns is required");
|
|
744
|
+
}
|
|
745
|
+
if (colNames.length > OVERWRITE_MAX_COLUMNS) {
|
|
746
|
+
throw new Error(`columns exceeds max ${OVERWRITE_MAX_COLUMNS}`);
|
|
747
|
+
}
|
|
748
|
+
if (colNames.some((c) => !c)) {
|
|
749
|
+
throw new Error("empty column name");
|
|
750
|
+
}
|
|
751
|
+
const seen = new Set();
|
|
752
|
+
for (const name of colNames) {
|
|
753
|
+
if (seen.has(name)) {
|
|
754
|
+
throw new Error(`duplicate column: ${name}`);
|
|
755
|
+
}
|
|
756
|
+
seen.add(name);
|
|
757
|
+
}
|
|
758
|
+
if (input.rows.length > OVERWRITE_MAX_ROWS) {
|
|
759
|
+
throw new Error(`rows exceeds max ${OVERWRITE_MAX_ROWS}`);
|
|
760
|
+
}
|
|
761
|
+
const columns = colNames.map((name) => ({
|
|
762
|
+
id: newId("c"),
|
|
763
|
+
name,
|
|
764
|
+
}));
|
|
765
|
+
const rows = [];
|
|
766
|
+
for (let i = 0; i < input.rows.length; i += 1) {
|
|
767
|
+
const raw = input.rows[i] ?? [];
|
|
768
|
+
if (raw.length > columns.length) {
|
|
769
|
+
throw new Error(`row ${i} has too many cells`);
|
|
770
|
+
}
|
|
771
|
+
const cells = {};
|
|
772
|
+
for (let j = 0; j < columns.length; j += 1) {
|
|
773
|
+
const v = raw[j];
|
|
774
|
+
cells[columns[j].id] = emptySheetCell(v == null ? "" : String(v));
|
|
775
|
+
}
|
|
776
|
+
rows.push({ id: newId("r"), cells });
|
|
777
|
+
}
|
|
778
|
+
const name = input.name?.trim() || tab.name;
|
|
779
|
+
return { id: tab.id, name, columns, rows, merges: [] };
|
|
780
|
+
}
|
|
781
|
+
//# sourceMappingURL=sheetDocument.js.map
|