@4399ywkf/editor 0.4.4 → 0.6.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/csv.d.ts ADDED
@@ -0,0 +1,83 @@
1
+ import { W as WorkbookData } from './types-C37MAt8o.js';
2
+ export { C as CellData, a as WorksheetData } from './types-C37MAt8o.js';
3
+
4
+ /**
5
+ * Univer 快照 → CSV。
6
+ *
7
+ * **csv 是单表格式,工作簿是多表的**,这是产品决定不是技术决定。本实现的口径:
8
+ * 导**一张**表,其余的在 `warnings` 里**逐个点名**,和 xlsx 导出「如实报告丢了什么」
9
+ * 保持一致 —— 静悄悄只给一张表,用户往往到对账时才发现少了东西。
10
+ *
11
+ * 「哪张是活动表」只有运行时的 Univer 实例知道,**快照里根本没有这个字段**。
12
+ * 所以:传 `univerAPI` 时取真正的活动表;只传快照时取 `sheetOrder` 的第一张,
13
+ * 并且照样把这件事写进 warnings。要指定就用 `options.sheet`(表 id 或表名)。
14
+ */
15
+
16
+ interface CsvOptions {
17
+ /** 导哪张表:工作表 id 或表名。不传则用活动表(拿不到就用第一张) */
18
+ sheet?: string;
19
+ /** 分隔符,默认 `,`。用 `\t` 就是 tsv */
20
+ delimiter?: string;
21
+ /** 行分隔,默认 `\r\n`(RFC 4180 规定,Excel 也最稳) */
22
+ newline?: string;
23
+ /**
24
+ * 是否加 UTF-8 BOM,默认 **true**。
25
+ * 不加的话 Excel 会按本地编码(简中系统是 GBK)解析,中文全是乱码 ——
26
+ * 这是 csv 导出最常见的投诉,默认就得挡掉。
27
+ */
28
+ bom?: boolean;
29
+ /** 公式格写 Univer 算好的值(默认)还是写公式文本 */
30
+ formulas?: "value" | "formula";
31
+ }
32
+ interface CsvResult {
33
+ /** 文本内容,不含 BOM */
34
+ text: string;
35
+ /** 可直接落盘/上传的字节,按 `bom` 决定是否带 BOM */
36
+ bytes: Uint8Array;
37
+ /** 导出时丢掉/降级了什么。**要展示给用户** */
38
+ warnings: string[];
39
+ stats: {
40
+ sheetName: string;
41
+ rows: number;
42
+ columns: number;
43
+ cells: number;
44
+ /** 没被导出的工作表数 */
45
+ skippedSheets: number;
46
+ };
47
+ }
48
+ declare const CSV_MIME = "text/csv;charset=utf-8";
49
+ declare function csvFromSnapshot(workbook: WorkbookData, options?: CsvOptions): CsvResult;
50
+
51
+ /**
52
+ * `@4399ywkf/editor/csv` —— 表格导出 CSV。
53
+ *
54
+ * 独立子路径而不是塞进 `./xlsx`:csv 这条路**一个依赖都不需要**
55
+ * (xlsx 那边要 fflate 打 zip),几 KB 就够。只要 csv 的宿主不该为 zip 买单。
56
+ *
57
+ * ```ts
58
+ * import { downloadCsv } from "@4399ywkf/editor/csv"
59
+ *
60
+ * const { warnings } = downloadCsv(univerAPI, "明细.csv")
61
+ * if (warnings.length) toast(warnings.join("\n")) // ← 里面会点名哪些表没导
62
+ * ```
63
+ *
64
+ * **csv 是单表格式,工作簿是多表的。** 本实现只导一张表,其余的在 `warnings`
65
+ * 里逐个点名。传 `univerAPI` 时导真正的活动表;只传快照时导第一张 ——
66
+ * 快照里根本没有「活动表」这个字段。要指定就用 `options.sheet`(表 id 或表名)。
67
+ */
68
+
69
+ /**
70
+ * Univer 实例或快照 → CSV。
71
+ *
72
+ * 传实例时会自动锁定**当前活动表** —— 这也是唯一能拿到活动表的途径。
73
+ */
74
+ declare function exportCsv(source: WorkbookData | unknown, options?: CsvOptions): CsvResult;
75
+ /** Univer 实例或快照 → Blob,便于交给上传接口 */
76
+ declare function csvBlob(source: WorkbookData | unknown, options?: CsvOptions): Blob;
77
+ /**
78
+ * 触发浏览器下载。返回 warnings / stats —— 别丢,
79
+ * 「另外几张表没导出」只能从这里知道。
80
+ */
81
+ declare function downloadCsv(source: WorkbookData | unknown, filename?: string, options?: CsvOptions): CsvResult;
82
+
83
+ export { CSV_MIME, type CsvOptions, type CsvResult, WorkbookData, csvBlob, csvFromSnapshot, downloadCsv, exportCsv };
package/dist/csv.js ADDED
@@ -0,0 +1,153 @@
1
+ import { numericKeys, richTextToPlain, looksLikeDateFormat, toSnapshot } from './chunk-32WQX3NR.js';
2
+
3
+ // src/xlsx/csv.ts
4
+ var CSV_MIME = "text/csv;charset=utf-8";
5
+ function escapeField(value, delimiter) {
6
+ if (!value) return "";
7
+ const needsQuote = value.includes(delimiter) || value.includes('"') || value.includes("\n") || value.includes("\r");
8
+ return needsQuote ? `"${value.replace(/"/g, '""')}"` : value;
9
+ }
10
+ function csvFromSnapshot(workbook, options = {}) {
11
+ const { delimiter = ",", newline = "\r\n", bom = true, formulas = "value" } = options;
12
+ const warnings = [];
13
+ const order = (workbook.sheetOrder?.length ? workbook.sheetOrder : Object.keys(workbook.sheets ?? {})).filter((id) => workbook.sheets?.[id]);
14
+ if (!order.length) {
15
+ return {
16
+ text: "",
17
+ bytes: encode("", bom),
18
+ warnings: ["工作簿里没有工作表"],
19
+ stats: { sheetName: "", rows: 0, columns: 0, cells: 0, skippedSheets: 0 }
20
+ };
21
+ }
22
+ const wanted = options.sheet;
23
+ const pickedId = wanted && (order.find((id) => id === wanted) ?? order.find((id) => workbook.sheets?.[id]?.name === wanted)) || order[0];
24
+ if (wanted && pickedId !== wanted && workbook.sheets?.[pickedId]?.name !== wanted) {
25
+ warnings.push(
26
+ `找不到工作表「${wanted}」,已改导「${workbook.sheets?.[pickedId]?.name ?? pickedId}」`
27
+ );
28
+ }
29
+ const sheet = workbook.sheets?.[pickedId];
30
+ const sheetName = sheet?.name ?? pickedId;
31
+ const skipped = order.filter((id) => id !== pickedId);
32
+ if (skipped.length) {
33
+ const names = skipped.map((id) => `「${workbook.sheets?.[id]?.name ?? id}」`);
34
+ warnings.push(
35
+ `csv 是单表格式,只导出了「${sheetName}」。未导出的 ${skipped.length} 张表:${names.join("")}`
36
+ );
37
+ }
38
+ const styleMap = workbook.styles ?? {};
39
+ const resolveStyle = (s) => {
40
+ if (!s) return void 0;
41
+ if (typeof s === "string") return styleMap[s] ?? void 0;
42
+ return s;
43
+ };
44
+ const cellData = sheet?.cellData ?? {};
45
+ const rows = numericKeys(cellData);
46
+ let maxCol = -1;
47
+ for (const r of rows) {
48
+ const cols = numericKeys(cellData[r]);
49
+ const last = cols[cols.length - 1];
50
+ if (last !== void 0 && last > maxCol) maxCol = last;
51
+ }
52
+ const maxRow = rows.length ? rows[rows.length - 1] : -1;
53
+ let cells = 0;
54
+ let sawDateFormat = false;
55
+ let sawRichText = false;
56
+ const lines = [];
57
+ for (let r = 0; r <= maxRow; r++) {
58
+ const row = cellData[r];
59
+ const fields = [];
60
+ for (let c = 0; c <= maxCol; c++) {
61
+ const cell = row?.[c];
62
+ const text2 = cell ? cellToText(cell) : "";
63
+ if (text2) cells++;
64
+ fields.push(escapeField(text2, delimiter));
65
+ }
66
+ lines.push(fields.join(delimiter));
67
+ }
68
+ function cellToText(cell) {
69
+ if (formulas === "formula" && cell.f) return cell.f;
70
+ const rich = richTextToPlain(cell);
71
+ if (rich !== void 0) {
72
+ sawRichText = true;
73
+ return rich;
74
+ }
75
+ const v = cell.v;
76
+ if (v === void 0 || v === null) return "";
77
+ if (typeof v === "boolean") return v ? "TRUE" : "FALSE";
78
+ if (typeof v === "number") {
79
+ if (looksLikeDateFormat(resolveStyle(cell.s)?.n?.pattern)) sawDateFormat = true;
80
+ return String(v);
81
+ }
82
+ return String(v);
83
+ }
84
+ if (sawDateFormat) {
85
+ warnings.push(
86
+ "有单元格用的是日期/时间格式,csv 里会写成 Excel 序列号(如 45678)而不是日期文本 —— csv 不带格式信息,导入方需要自己按日期列解析"
87
+ );
88
+ }
89
+ if (sawRichText) warnings.push("富文本单元格已压平成纯文本,段内的字体与颜色不保留");
90
+ if (sheet?.mergeData?.length) {
91
+ warnings.push(`${sheet.mergeData.length} 处合并单元格已拆开:值留在左上角,其余为空`);
92
+ }
93
+ if (formulas === "value") {
94
+ const hasFormula = rows.some((r) => numericKeys(cellData[r]).some((c) => cellData[r]?.[c]?.f));
95
+ if (hasFormula) warnings.push("公式已写成 Univer 算出的值,公式本身不保留");
96
+ }
97
+ const text = lines.join(newline);
98
+ return {
99
+ text,
100
+ bytes: encode(text, bom),
101
+ warnings,
102
+ stats: {
103
+ sheetName,
104
+ rows: maxRow + 1,
105
+ columns: maxCol + 1,
106
+ cells,
107
+ skippedSheets: skipped.length
108
+ }
109
+ };
110
+ }
111
+ function encode(text, bom) {
112
+ const body = new TextEncoder().encode(text);
113
+ if (!bom) return body;
114
+ const out = new Uint8Array(body.length + 3);
115
+ out.set([239, 187, 191]);
116
+ out.set(body, 3);
117
+ return out;
118
+ }
119
+ function activeSheetId(univerAPI) {
120
+ const book = univerAPI?.getActiveWorkbook?.();
121
+ const sheet = book?.getActiveSheet?.();
122
+ const id = sheet?.getSheetId?.();
123
+ return typeof id === "string" && id ? id : void 0;
124
+ }
125
+
126
+ // src/csv.ts
127
+ function exportCsv(source, options = {}) {
128
+ const snapshot = toSnapshot(source);
129
+ const sheet = options.sheet ?? (snapshot === source ? void 0 : activeSheetId(source));
130
+ return csvFromSnapshot(snapshot, { ...options, sheet });
131
+ }
132
+ function csvBlob(source, options) {
133
+ return new Blob([exportCsv(source, options).bytes], { type: CSV_MIME });
134
+ }
135
+ function downloadCsv(source, filename = "表格.csv", options) {
136
+ const result = exportCsv(source, options);
137
+ if (typeof document === "undefined") {
138
+ throw new Error("downloadCsv 需要浏览器环境,服务端请改用 exportCsv 自己落盘");
139
+ }
140
+ const url = URL.createObjectURL(new Blob([result.bytes], { type: CSV_MIME }));
141
+ const a = document.createElement("a");
142
+ a.href = url;
143
+ a.download = filename.endsWith(".csv") ? filename : `${filename}.csv`;
144
+ document.body.appendChild(a);
145
+ a.click();
146
+ a.remove();
147
+ setTimeout(() => URL.revokeObjectURL(url), 1e4);
148
+ return result;
149
+ }
150
+
151
+ export { CSV_MIME, csvBlob, csvFromSnapshot, downloadCsv, exportCsv };
152
+ //# sourceMappingURL=csv.js.map
153
+ //# sourceMappingURL=csv.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/xlsx/csv.ts","../src/csv.ts"],"names":["text"],"mappings":"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","file":"csv.js","sourcesContent":["/**\n * Univer 快照 → CSV。\n *\n * **csv 是单表格式,工作簿是多表的**,这是产品决定不是技术决定。本实现的口径:\n * 导**一张**表,其余的在 `warnings` 里**逐个点名**,和 xlsx 导出「如实报告丢了什么」\n * 保持一致 —— 静悄悄只给一张表,用户往往到对账时才发现少了东西。\n *\n * 「哪张是活动表」只有运行时的 Univer 实例知道,**快照里根本没有这个字段**。\n * 所以:传 `univerAPI` 时取真正的活动表;只传快照时取 `sheetOrder` 的第一张,\n * 并且照样把这件事写进 warnings。要指定就用 `options.sheet`(表 id 或表名)。\n */\n\nimport { looksLikeDateFormat, numericKeys, richTextToPlain } from \"./cell.ts\"\nimport type { CellData, StyleData, WorkbookData, WorksheetData } from \"./types.ts\"\n\nexport interface CsvOptions {\n /** 导哪张表:工作表 id 或表名。不传则用活动表(拿不到就用第一张) */\n sheet?: string\n /** 分隔符,默认 `,`。用 `\\t` 就是 tsv */\n delimiter?: string\n /** 行分隔,默认 `\\r\\n`(RFC 4180 规定,Excel 也最稳) */\n newline?: string\n /**\n * 是否加 UTF-8 BOM,默认 **true**。\n * 不加的话 Excel 会按本地编码(简中系统是 GBK)解析,中文全是乱码 ——\n * 这是 csv 导出最常见的投诉,默认就得挡掉。\n */\n bom?: boolean\n /** 公式格写 Univer 算好的值(默认)还是写公式文本 */\n formulas?: \"value\" | \"formula\"\n}\n\nexport interface CsvResult {\n /** 文本内容,不含 BOM */\n text: string\n /** 可直接落盘/上传的字节,按 `bom` 决定是否带 BOM */\n bytes: Uint8Array\n /** 导出时丢掉/降级了什么。**要展示给用户** */\n warnings: string[]\n stats: {\n sheetName: string\n rows: number\n columns: number\n cells: number\n /** 没被导出的工作表数 */\n skippedSheets: number\n }\n}\n\nexport const CSV_MIME = \"text/csv;charset=utf-8\"\n\n/** RFC 4180:字段含分隔符、引号或换行时整体加引号,内部引号翻倍 */\nfunction escapeField(value: string, delimiter: string): string {\n if (!value) return \"\"\n const needsQuote =\n value.includes(delimiter) || value.includes('\"') || value.includes(\"\\n\") || value.includes(\"\\r\")\n return needsQuote ? `\"${value.replace(/\"/g, '\"\"')}\"` : value\n}\n\nexport function csvFromSnapshot(workbook: WorkbookData, options: CsvOptions = {}): CsvResult {\n const { delimiter = \",\", newline = \"\\r\\n\", bom = true, formulas = \"value\" } = options\n\n const warnings: string[] = []\n const order = (\n workbook.sheetOrder?.length ? workbook.sheetOrder : Object.keys(workbook.sheets ?? {})\n ).filter((id) => workbook.sheets?.[id])\n\n if (!order.length) {\n return {\n text: \"\",\n bytes: encode(\"\", bom),\n warnings: [\"工作簿里没有工作表\"],\n stats: { sheetName: \"\", rows: 0, columns: 0, cells: 0, skippedSheets: 0 },\n }\n }\n\n // 指定 → 按 id 或表名找;没指定 → 第一张\n const wanted = options.sheet\n const pickedId =\n (wanted &&\n (order.find((id) => id === wanted) ??\n order.find((id) => (workbook.sheets?.[id] as WorksheetData)?.name === wanted))) ||\n (order[0] as string)\n if (\n wanted &&\n pickedId !== wanted &&\n (workbook.sheets?.[pickedId] as WorksheetData)?.name !== wanted\n ) {\n warnings.push(\n `找不到工作表「${wanted}」,已改导「${(workbook.sheets?.[pickedId] as WorksheetData)?.name ?? pickedId}」`,\n )\n }\n\n const sheet = workbook.sheets?.[pickedId] as WorksheetData\n const sheetName = sheet?.name ?? pickedId\n\n // 点名没导出的表 —— 这条是 csv 导出最容易被忽略的损失\n const skipped = order.filter((id) => id !== pickedId)\n if (skipped.length) {\n const names = skipped.map((id) => `「${(workbook.sheets?.[id] as WorksheetData)?.name ?? id}」`)\n warnings.push(\n `csv 是单表格式,只导出了「${sheetName}」。未导出的 ${skipped.length} 张表:${names.join(\"\")}`,\n )\n }\n\n const styleMap = workbook.styles ?? {}\n const resolveStyle = (s: StyleData | string | null | undefined): StyleData | undefined => {\n if (!s) return undefined\n if (typeof s === \"string\") return styleMap[s] ?? undefined\n return s\n }\n\n const cellData = sheet?.cellData ?? {}\n const rows = numericKeys(cellData)\n let maxCol = -1\n for (const r of rows) {\n const cols = numericKeys(cellData[r])\n const last = cols[cols.length - 1]\n if (last !== undefined && last > maxCol) maxCol = last\n }\n const maxRow = rows.length ? (rows[rows.length - 1] as number) : -1\n\n let cells = 0\n let sawDateFormat = false\n let sawRichText = false\n\n const lines: string[] = []\n for (let r = 0; r <= maxRow; r++) {\n const row = cellData[r]\n const fields: string[] = []\n for (let c = 0; c <= maxCol; c++) {\n const cell = row?.[c]\n const text = cell ? cellToText(cell) : \"\"\n if (text) cells++\n fields.push(escapeField(text, delimiter))\n }\n lines.push(fields.join(delimiter))\n }\n\n function cellToText(cell: CellData): string {\n if (formulas === \"formula\" && cell.f) return cell.f\n\n const rich = richTextToPlain(cell)\n if (rich !== undefined) {\n sawRichText = true\n return rich\n }\n\n const v = cell.v\n if (v === undefined || v === null) return \"\"\n if (typeof v === \"boolean\") return v ? \"TRUE\" : \"FALSE\"\n if (typeof v === \"number\") {\n // 日期在 Univer 里是「序列号 + 数字格式」,值本身就是个数字。\n // 这里不猜着转,只记下来在 warnings 里说清楚\n if (looksLikeDateFormat(resolveStyle(cell.s)?.n?.pattern)) sawDateFormat = true\n return String(v)\n }\n return String(v)\n }\n\n if (sawDateFormat) {\n warnings.push(\n \"有单元格用的是日期/时间格式,csv 里会写成 Excel 序列号(如 45678)而不是日期文本 —— \" +\n \"csv 不带格式信息,导入方需要自己按日期列解析\",\n )\n }\n if (sawRichText) warnings.push(\"富文本单元格已压平成纯文本,段内的字体与颜色不保留\")\n if (sheet?.mergeData?.length) {\n warnings.push(`${sheet.mergeData.length} 处合并单元格已拆开:值留在左上角,其余为空`)\n }\n if (formulas === \"value\") {\n const hasFormula = rows.some((r) => numericKeys(cellData[r]).some((c) => cellData[r]?.[c]?.f))\n if (hasFormula) warnings.push(\"公式已写成 Univer 算出的值,公式本身不保留\")\n }\n\n const text = lines.join(newline)\n return {\n text,\n bytes: encode(text, bom),\n warnings,\n stats: {\n sheetName,\n rows: maxRow + 1,\n columns: maxCol + 1,\n cells,\n skippedSheets: skipped.length,\n },\n }\n}\n\nfunction encode(text: string, bom: boolean): Uint8Array {\n const body = new TextEncoder().encode(text)\n if (!bom) return body\n const out = new Uint8Array(body.length + 3)\n out.set([0xef, 0xbb, 0xbf])\n out.set(body, 3)\n return out\n}\n\n/**\n * 从 Univer 实例上问出当前活动表的 id。\n *\n * 参数是 `unknown` 而不是 `FUniver`:`@univerjs/presets` 是可选 peer,\n * 写具体类型会让没装它的项目 typecheck 直接红。\n */\nexport function activeSheetId(univerAPI: unknown): string | undefined {\n const book = (univerAPI as { getActiveWorkbook?: () => unknown } | null)?.getActiveWorkbook?.()\n const sheet = (book as { getActiveSheet?: () => unknown } | null)?.getActiveSheet?.()\n const id = (sheet as { getSheetId?: () => string } | null)?.getSheetId?.()\n return typeof id === \"string\" && id ? id : undefined\n}\n","/**\n * `@4399ywkf/editor/csv` —— 表格导出 CSV。\n *\n * 独立子路径而不是塞进 `./xlsx`:csv 这条路**一个依赖都不需要**\n * (xlsx 那边要 fflate 打 zip),几 KB 就够。只要 csv 的宿主不该为 zip 买单。\n *\n * ```ts\n * import { downloadCsv } from \"@4399ywkf/editor/csv\"\n *\n * const { warnings } = downloadCsv(univerAPI, \"明细.csv\")\n * if (warnings.length) toast(warnings.join(\"\\n\")) // ← 里面会点名哪些表没导\n * ```\n *\n * **csv 是单表格式,工作簿是多表的。** 本实现只导一张表,其余的在 `warnings`\n * 里逐个点名。传 `univerAPI` 时导真正的活动表;只传快照时导第一张 ——\n * 快照里根本没有「活动表」这个字段。要指定就用 `options.sheet`(表 id 或表名)。\n */\n\nimport {\n activeSheetId,\n CSV_MIME,\n type CsvOptions,\n type CsvResult,\n csvFromSnapshot,\n} from \"./xlsx/csv\"\nimport { toSnapshot } from \"./xlsx/snapshot\"\nimport type { WorkbookData } from \"./xlsx/types\"\n\nexport type { CsvOptions, CsvResult } from \"./xlsx/csv\"\nexport { CSV_MIME, csvFromSnapshot } from \"./xlsx/csv\"\nexport type { CellData, WorkbookData, WorksheetData } from \"./xlsx/types\"\n\n/**\n * Univer 实例或快照 → CSV。\n *\n * 传实例时会自动锁定**当前活动表** —— 这也是唯一能拿到活动表的途径。\n */\nexport function exportCsv(source: WorkbookData | unknown, options: CsvOptions = {}): CsvResult {\n const snapshot = toSnapshot(source)\n // 已显式指定就不去猜;否则只在传的是实例时才问得出活动表\n const sheet = options.sheet ?? (snapshot === source ? undefined : activeSheetId(source))\n return csvFromSnapshot(snapshot, { ...options, sheet })\n}\n\n/** Univer 实例或快照 → Blob,便于交给上传接口 */\nexport function csvBlob(source: WorkbookData | unknown, options?: CsvOptions): Blob {\n return new Blob([exportCsv(source, options).bytes as BlobPart], { type: CSV_MIME })\n}\n\n/**\n * 触发浏览器下载。返回 warnings / stats —— 别丢,\n * 「另外几张表没导出」只能从这里知道。\n */\nexport function downloadCsv(\n source: WorkbookData | unknown,\n filename = \"表格.csv\",\n options?: CsvOptions,\n): CsvResult {\n const result = exportCsv(source, options)\n if (typeof document === \"undefined\") {\n throw new Error(\"downloadCsv 需要浏览器环境,服务端请改用 exportCsv 自己落盘\")\n }\n const url = URL.createObjectURL(new Blob([result.bytes as BlobPart], { type: CSV_MIME }))\n const a = document.createElement(\"a\")\n a.href = url\n a.download = filename.endsWith(\".csv\") ? filename : `${filename}.csv`\n document.body.appendChild(a)\n a.click()\n a.remove()\n // 立刻 revoke 会让部分浏览器(Safari)来不及取数据\n setTimeout(() => URL.revokeObjectURL(url), 10_000)\n return result\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { D as DocumentViewProps, a as DocumentFormat } from './registry-CvGg65s9.js';
2
- export { F as FormatAdapter, b as FormatCapabilities, d as detectFormat, g as getFormat, l as listFormats, r as registerFormat } from './registry-CvGg65s9.js';
3
1
  import * as react from 'react';
4
2
  import { ReactNode } from 'react';
3
+ import { D as DocumentViewProps, a as DocumentFormat } from './registry-BBL8yeIT.js';
4
+ export { F as FormatAdapter, b as FormatCapabilities, S as SheetOptions, c as SheetUiOptions, d as detectFormat, g as getFormat, l as listFormats, r as registerFormat } from './registry-BBL8yeIT.js';
5
5
 
6
6
  interface DocumentViewComponentProps extends DocumentViewProps {
7
7
  /** 文档格式。不传则尝试从 source 的扩展名推断 */
package/dist/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import { useMemo, lazy, Suspense } from 'react';
2
2
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
3
3
 
4
+ // src/DocumentView.tsx
5
+
4
6
  // src/registry.ts
5
7
  var registry = /* @__PURE__ */ new Map();
6
8
  function registerFormat(adapter) {
@@ -41,9 +43,11 @@ registerFormat({
41
43
  collaborative: false,
42
44
  // Univer 协同属于 Pro,OSS 核心不含
43
45
  importExtensions: [".xlsx", ".csv"],
46
+ // 导出实现分别在 ./xlsx 与 ./csv 两个子路径。csv 是单表格式,
47
+ // 只导活动表、其余表在 warnings 里点名
44
48
  exportFormats: ["xlsx", "csv"]
45
49
  },
46
- load: () => import('./SheetView-2EID6B2I.js')
50
+ load: () => import('./SheetView-DSADIZNS.js')
47
51
  });
48
52
  registerFormat({
49
53
  format: "pdf",
@@ -59,6 +63,19 @@ registerFormat({
59
63
  },
60
64
  load: () => import('./PdfView-JDCHTG7Y.js')
61
65
  });
66
+ registerFormat({
67
+ format: "pptx",
68
+ label: "演示文稿",
69
+ extensions: [".pptx", ".ppsx", ".potx", ".pptm", ".ppsm", ".potm"],
70
+ capabilities: {
71
+ editable: false,
72
+ aiOperable: false,
73
+ collaborative: false,
74
+ importExtensions: [".pptx"],
75
+ exportFormats: []
76
+ },
77
+ load: () => import('./PptxView-ZH4FZKNO.js')
78
+ });
62
79
  function DocumentView({
63
80
  format,
64
81
  source,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/registry.ts","../src/DocumentView.tsx","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * 格式注册表 —— 整个包的骨架。\n *\n * 设计取自腾讯 WorkBuddy 的三通道结构:**按「文档住在哪 / 是什么」分通道,\n * 每个通道一个独立引擎,上面加一层路由**。它没有试图用一套编辑器覆盖全部格式,\n * 我们也不该。\n *\n * 三条硬约束:\n *\n * 1. **引擎必须懒加载。** Univer sheets 核心 gzip 后 871KB、pdf.js 也有几百 KB。\n * 塞进主包会毁掉阅读侧的首屏 —— 只有用户真的打开一个表格 / PDF 时才该下载。\n *\n * 2. **能力要可申报,不能靠猜。** `capabilities` 明确说清每种格式能不能编辑、\n * 能不能被 AI 结构化操作。上层据此决定给什么 UI,而不是先渲染再报错。\n *\n * 3. **宿主可注册自有引擎。** 内置覆盖不了的格式(如 pptx)留给宿主接,\n * 而不是我们假装支持。\n */\nimport type { ComponentType } from \"react\"\n\nexport type DocumentFormat = \"doc\" | \"sheet\" | \"pdf\" | \"pptx\" | (string & {})\n\nexport interface FormatCapabilities {\n /** 能否编辑。false 表示只读预览 */\n editable: boolean\n /** 能否被 AI 通过结构化工具面操作(读结构、按 id 定位、增删改) */\n aiOperable: boolean\n /** 能否协同编辑 */\n collaborative: boolean\n /** 支持导入的扩展名 */\n importExtensions: string[]\n /** 支持导出的格式 */\n exportFormats: string[]\n}\n\nexport interface FormatAdapter {\n format: DocumentFormat\n /** 展示名,给 UI 用 */\n label: string\n capabilities: FormatCapabilities\n /** 该格式认领哪些扩展名(小写、带点) */\n extensions: string[]\n /**\n * 懒加载视图组件。**必须是动态 import**,否则引擎会被打进主包。\n */\n load: () => Promise<{ default: ComponentType<DocumentViewProps> }>\n}\n\nexport interface DocumentViewProps {\n /** 文档来源:URL / File / 已有内容 */\n source?: string | Blob | File\n /** 已有内容(doc 走 tiptap JSON 或 HTML;sheet 走 Univer snapshot) */\n content?: unknown\n readOnly?: boolean\n /** 协同房间号,仅 collaborative 为 true 的格式有意义 */\n room?: string\n className?: string\n /** 引擎就绪回调,拿到底层实例(tiptap Editor / Univer API / PDFDocumentProxy) */\n onReady?: (instance: unknown) => void\n onError?: (error: Error) => void\n}\n\nconst registry = new Map<DocumentFormat, FormatAdapter>()\n\nexport function registerFormat(adapter: FormatAdapter): void {\n registry.set(adapter.format, adapter)\n}\n\nexport function getFormat(format: DocumentFormat): FormatAdapter | undefined {\n return registry.get(format)\n}\n\nexport function listFormats(): FormatAdapter[] {\n return [...registry.values()]\n}\n\n/** 按扩展名判定格式。传文件名、路径或裸扩展名都行。 */\nexport function detectFormat(nameOrExt: string): DocumentFormat | undefined {\n const m = /\\.([a-z0-9]+)$/i.exec(nameOrExt.trim())\n const ext = `.${(m ? m[1] : nameOrExt.replace(/^\\./, \"\")).toLowerCase()}`\n for (const a of registry.values()) if (a.extensions.includes(ext)) return a.format\n return undefined\n}\n\n/* --------------------------- 内置格式 --------------------------- */\n\nregisterFormat({\n format: \"doc\",\n label: \"文档\",\n extensions: [\".doc\", \".docx\", \".dot\", \".dotx\", \".docm\", \".dotm\", \".wps\", \".wpt\", \".md\", \".html\"],\n capabilities: {\n editable: true,\n aiOperable: true,\n collaborative: true,\n importExtensions: [\".docx\", \".md\", \".html\"],\n exportFormats: [\"docx\", \"html\", \"json\", \"markdown\"],\n },\n load: () => import(\"./views/DocView\"),\n})\n\nregisterFormat({\n format: \"sheet\",\n label: \"表格\",\n extensions: [\".xls\", \".xlsx\", \".xlt\", \".xltx\", \".xlsm\", \".xltm\", \".csv\", \".tsv\"],\n capabilities: {\n editable: true,\n aiOperable: true,\n collaborative: false, // Univer 协同属于 Pro,OSS 核心不含\n importExtensions: [\".xlsx\", \".csv\"],\n exportFormats: [\"xlsx\", \"csv\"],\n },\n load: () => import(\"./views/SheetView\"),\n})\n\nregisterFormat({\n format: \"pdf\",\n label: \"PDF\",\n extensions: [\".pdf\"],\n capabilities: {\n editable: false, // 只读预览。PDF 编辑是 OOXML 引擎那一档的事\n aiOperable: false,\n collaborative: false,\n importExtensions: [\".pdf\"],\n exportFormats: [],\n },\n load: () => import(\"./views/PdfView\"),\n})\n\n/**\n * pptx 刻意**不内置**。\n *\n * 调研结论:Univer Slides 官方文档原文写着「请勿用于生产环境」,\n * `@univerjs/slides` 只有 0.17MB 骨架;开源世界里没有第二个可用的\n * 前端 pptx 编辑/渲染引擎。真要做只有 OnlyOffice / Collabora 那一档\n * (AGPL,需法务口径 + 独立部署)。\n *\n * 与其假装支持,不如把口子留给宿主:\n *\n * ```ts\n * registerFormat({\n * format: \"pptx\",\n * label: \"演示文稿\",\n * extensions: [\".ppt\", \".pptx\"],\n * capabilities: { editable: false, aiOperable: false, collaborative: false,\n * importExtensions: [], exportFormats: [] },\n * load: () => import(\"./MyOnlyOfficeSlideView\"),\n * })\n * ```\n */\n","import { Suspense, lazy, useMemo, type ComponentType, type ReactNode } from \"react\"\nimport { detectFormat, getFormat, type DocumentFormat, type DocumentViewProps } from \"./registry\"\n\nexport interface DocumentViewComponentProps extends DocumentViewProps {\n /** 文档格式。不传则尝试从 source 的扩展名推断 */\n format?: DocumentFormat\n /** 引擎加载中的占位 */\n fallback?: ReactNode\n /** 格式不支持时的兜底渲染。不传则渲染一段说明文字 */\n unsupported?: (format: string) => ReactNode\n}\n\n/**\n * 按格式路由到对应引擎,引擎按需下载。\n *\n * 为什么用 registry + lazy 而不是 if/else 直接 import:\n * 直接 import 会让 Univer(871KB gzip)和 pdf.js 无条件进主包,\n * 哪怕用户从头到尾只看文章。这在阅读侧是不可接受的 ——\n * 本仓刚做完「服务端直出 588KB」的优化,一个表格引擎就能全毁掉。\n */\nexport function DocumentView({\n format,\n source,\n fallback,\n unsupported,\n ...rest\n}: DocumentViewComponentProps) {\n const resolved = useMemo<DocumentFormat | undefined>(() => {\n if (format) return format\n const name = typeof source === \"string\" ? source : (source as File | undefined)?.name\n if (!name) return undefined\n return detectFormat(name)\n }, [format, source])\n\n const adapter = resolved ? getFormat(resolved) : undefined\n\n const View = useMemo<ComponentType<DocumentViewProps> | null>(() => {\n if (!adapter) return null\n return lazy(adapter.load)\n }, [adapter])\n\n if (!adapter || !View) {\n return (\n <>\n {unsupported?.(String(resolved ?? \"unknown\")) ?? (\n <div data-editor-unsupported={String(resolved ?? \"unknown\")}>\n 暂不支持该格式{resolved ? `(${resolved})` : \"\"}。\n 可通过 registerFormat() 注册自有引擎。\n </div>\n )}\n </>\n )\n }\n\n return (\n <Suspense fallback={fallback ?? <div data-editor-loading={adapter.format} />}>\n <View source={source} {...rest} />\n </Suspense>\n )\n}\n","/**\n * @4399ywkf/editor —— 多格式文档编辑器\n *\n * 主入口只放**格式路由与能力申报**,具体引擎全部懒加载。\n * 直接 import 这里不会把 tiptap / Univer / pdf.js 打进你的主包。\n *\n * ```tsx\n * import { DocumentView, detectFormat } from \"@4399ywkf/editor\"\n * import \"@4399ywkf/editor/styles\"\n *\n * <DocumentView format={detectFormat(\"年度报告.docx\")} content={html} />\n * ```\n *\n * 需要直接用某个引擎时走子路径,能拿到完整 API:\n *\n * ```ts\n * import { NotionEditor } from \"@4399ywkf/editor/doc\"\n * import { ImportDocx, ExportDocx } from \"@4399ywkf/editor/docx\"\n * ```\n */\nexport {\n detectFormat,\n getFormat,\n listFormats,\n registerFormat,\n type DocumentFormat,\n type DocumentViewProps,\n type FormatAdapter,\n type FormatCapabilities,\n} from \"./registry\"\n\nexport { DocumentView, type DocumentViewComponentProps } from \"./DocumentView\"\n\nexport const VERSION = \"0.1.0\"\n"]}
1
+ {"version":3,"sources":["../src/registry.ts","../src/DocumentView.tsx","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * 格式注册表 —— 整个包的骨架。\n *\n * 设计取自腾讯 WorkBuddy 的三通道结构:**按「文档住在哪 / 是什么」分通道,\n * 每个通道一个独立引擎,上面加一层路由**。它没有试图用一套编辑器覆盖全部格式,\n * 我们也不该。\n *\n * 三条硬约束:\n *\n * 1. **引擎必须懒加载。** Univer sheets 核心 gzip 后 871KB、pdf.js 也有几百 KB。\n * 塞进主包会毁掉阅读侧的首屏 —— 只有用户真的打开一个表格 / PDF 时才该下载。\n *\n * 2. **能力要可申报,不能靠猜。** `capabilities` 明确说清每种格式能不能编辑、\n * 能不能被 AI 结构化操作。上层据此决定给什么 UI,而不是先渲染再报错。\n *\n * 3. **宿主可注册自有引擎。** 内置覆盖不了的格式(如 pptx)留给宿主接,\n * 而不是我们假装支持。\n */\nimport type { ComponentType } from \"react\"\nimport type { SheetOptions } from \"./sheet-options\"\n\nexport type DocumentFormat = \"doc\" | \"sheet\" | \"pdf\" | \"pptx\" | (string & {})\n\nexport interface FormatCapabilities {\n /** 能否编辑。false 表示只读预览 */\n editable: boolean\n /** 能否被 AI 通过结构化工具面操作(读结构、按 id 定位、增删改) */\n aiOperable: boolean\n /** 能否协同编辑 */\n collaborative: boolean\n /** 支持导入的扩展名 */\n importExtensions: string[]\n /** 支持导出的格式 */\n exportFormats: string[]\n}\n\nexport interface FormatAdapter {\n format: DocumentFormat\n /** 展示名,给 UI 用 */\n label: string\n capabilities: FormatCapabilities\n /** 该格式认领哪些扩展名(小写、带点) */\n extensions: string[]\n /**\n * 懒加载视图组件。**必须是动态 import**,否则引擎会被打进主包。\n */\n load: () => Promise<{ default: ComponentType<DocumentViewProps> }>\n}\n\nexport interface DocumentViewProps {\n /** 文档来源:URL / File / 已有内容 */\n source?: string | Blob | File\n /** 已有内容(doc 走 tiptap JSON 或 HTML;sheet 走 Univer snapshot) */\n content?: unknown\n readOnly?: boolean\n /** 协同房间号,仅 collaborative 为 true 的格式有意义 */\n room?: string\n className?: string\n /**\n * sheet 通道专属配置(Univer preset 与 UI 开关),其它格式忽略。\n *\n * 放在这里而不是只给 `SheetView`,是因为宿主通常只见得到 `DocumentView`\n * 这一层路由 —— 配置进不来的话,只读预览就只能顶着整条编辑工具条。\n */\n sheet?: SheetOptions\n /** 引擎就绪回调,拿到底层实例(tiptap Editor / Univer API / PDFDocumentProxy) */\n onReady?: (instance: unknown) => void\n onError?: (error: Error) => void\n}\n\nconst registry = new Map<DocumentFormat, FormatAdapter>()\n\nexport function registerFormat(adapter: FormatAdapter): void {\n registry.set(adapter.format, adapter)\n}\n\nexport function getFormat(format: DocumentFormat): FormatAdapter | undefined {\n return registry.get(format)\n}\n\nexport function listFormats(): FormatAdapter[] {\n return [...registry.values()]\n}\n\n/** 按扩展名判定格式。传文件名、路径或裸扩展名都行。 */\nexport function detectFormat(nameOrExt: string): DocumentFormat | undefined {\n const m = /\\.([a-z0-9]+)$/i.exec(nameOrExt.trim())\n const ext = `.${(m ? m[1] : nameOrExt.replace(/^\\./, \"\")).toLowerCase()}`\n for (const a of registry.values()) if (a.extensions.includes(ext)) return a.format\n return undefined\n}\n\n/* --------------------------- 内置格式 --------------------------- */\n\nregisterFormat({\n format: \"doc\",\n label: \"文档\",\n extensions: [\".doc\", \".docx\", \".dot\", \".dotx\", \".docm\", \".dotm\", \".wps\", \".wpt\", \".md\", \".html\"],\n capabilities: {\n editable: true,\n aiOperable: true,\n collaborative: true,\n importExtensions: [\".docx\", \".md\", \".html\"],\n exportFormats: [\"docx\", \"html\", \"json\", \"markdown\"],\n },\n load: () => import(\"./views/DocView\"),\n})\n\nregisterFormat({\n format: \"sheet\",\n label: \"表格\",\n extensions: [\".xls\", \".xlsx\", \".xlt\", \".xltx\", \".xlsm\", \".xltm\", \".csv\", \".tsv\"],\n capabilities: {\n editable: true,\n aiOperable: true,\n collaborative: false, // Univer 协同属于 Pro,OSS 核心不含\n importExtensions: [\".xlsx\", \".csv\"],\n // 导出实现分别在 ./xlsx 与 ./csv 两个子路径。csv 是单表格式,\n // 只导活动表、其余表在 warnings 里点名\n exportFormats: [\"xlsx\", \"csv\"],\n },\n load: () => import(\"./views/SheetView\"),\n})\n\nregisterFormat({\n format: \"pdf\",\n label: \"PDF\",\n extensions: [\".pdf\"],\n capabilities: {\n editable: false, // 只读预览。PDF 编辑是 OOXML 引擎那一档的事\n aiOperable: false,\n collaborative: false,\n importExtensions: [\".pdf\"],\n exportFormats: [],\n },\n load: () => import(\"./views/PdfView\"),\n})\n\n/**\n * pptx **只读预览**,引擎是 `pptx-preview`(可选 peer)。\n *\n * 只申报 OOXML 那几个扩展名。`.ppt` / `.pps` / `.pot` 是二进制复合文档、\n * 根本不是 zip,该库解不了 —— 列进来就是假申报,宁可让它落到「暂不支持」。\n *\n * **编辑仍然不做。** Univer Slides 官方文档原文写着「请勿用于生产环境」,\n * 开源世界里没有第二个可用的前端 pptx 编辑引擎;真要做只有\n * OnlyOffice / Collabora 那一档(AGPL,需法务口径 + 独立部署)。\n *\n * 预览的保真度边界见 `views/PptxView` 的注释 —— 尤其**图表渲染不出数据**。\n * 需要更高保真的宿主可以用同名 `registerFormat` 覆盖掉本通道:\n *\n * ```ts\n * registerFormat({ format: \"pptx\", label: \"演示文稿\", extensions: [\".pptx\"],\n * capabilities: { editable: false, aiOperable: false, collaborative: false,\n * importExtensions: [], exportFormats: [] },\n * load: () => import(\"./MyOnlyOfficeSlideView\") })\n * ```\n */\nregisterFormat({\n format: \"pptx\",\n label: \"演示文稿\",\n extensions: [\".pptx\", \".ppsx\", \".potx\", \".pptm\", \".ppsm\", \".potm\"],\n capabilities: {\n editable: false,\n aiOperable: false,\n collaborative: false,\n importExtensions: [\".pptx\"],\n exportFormats: [],\n },\n load: () => import(\"./views/PptxView\"),\n})\n","import { Suspense, lazy, useMemo, type ComponentType, type ReactNode } from \"react\"\nimport { detectFormat, getFormat, type DocumentFormat, type DocumentViewProps } from \"./registry\"\n\nexport interface DocumentViewComponentProps extends DocumentViewProps {\n /** 文档格式。不传则尝试从 source 的扩展名推断 */\n format?: DocumentFormat\n /** 引擎加载中的占位 */\n fallback?: ReactNode\n /** 格式不支持时的兜底渲染。不传则渲染一段说明文字 */\n unsupported?: (format: string) => ReactNode\n}\n\n/**\n * 按格式路由到对应引擎,引擎按需下载。\n *\n * 为什么用 registry + lazy 而不是 if/else 直接 import:\n * 直接 import 会让 Univer(871KB gzip)和 pdf.js 无条件进主包,\n * 哪怕用户从头到尾只看文章。这在阅读侧是不可接受的 ——\n * 本仓刚做完「服务端直出 588KB」的优化,一个表格引擎就能全毁掉。\n */\nexport function DocumentView({\n format,\n source,\n fallback,\n unsupported,\n ...rest\n}: DocumentViewComponentProps) {\n const resolved = useMemo<DocumentFormat | undefined>(() => {\n if (format) return format\n const name = typeof source === \"string\" ? source : (source as File | undefined)?.name\n if (!name) return undefined\n return detectFormat(name)\n }, [format, source])\n\n const adapter = resolved ? getFormat(resolved) : undefined\n\n const View = useMemo<ComponentType<DocumentViewProps> | null>(() => {\n if (!adapter) return null\n return lazy(adapter.load)\n }, [adapter])\n\n if (!adapter || !View) {\n return (\n <>\n {unsupported?.(String(resolved ?? \"unknown\")) ?? (\n <div data-editor-unsupported={String(resolved ?? \"unknown\")}>\n 暂不支持该格式{resolved ? `(${resolved})` : \"\"}。\n 可通过 registerFormat() 注册自有引擎。\n </div>\n )}\n </>\n )\n }\n\n return (\n <Suspense fallback={fallback ?? <div data-editor-loading={adapter.format} />}>\n <View source={source} {...rest} />\n </Suspense>\n )\n}\n","/**\n * @4399ywkf/editor —— 多格式文档编辑器\n *\n * 主入口只放**格式路由与能力申报**,具体引擎全部懒加载。\n * 直接 import 这里不会把 tiptap / Univer / pdf.js 打进你的主包。\n *\n * ```tsx\n * import { DocumentView, detectFormat } from \"@4399ywkf/editor\"\n * import \"@4399ywkf/editor/styles\"\n *\n * <DocumentView format={detectFormat(\"年度报告.docx\")} content={html} />\n * ```\n *\n * 需要直接用某个引擎时走子路径,能拿到完整 API:\n *\n * ```ts\n * import { NotionEditor } from \"@4399ywkf/editor/doc\"\n * import { ImportDocx, ExportDocx } from \"@4399ywkf/editor/docx\"\n * ```\n */\n\nexport { DocumentView, type DocumentViewComponentProps } from \"./DocumentView\"\nexport {\n type DocumentFormat,\n type DocumentViewProps,\n detectFormat,\n type FormatAdapter,\n type FormatCapabilities,\n getFormat,\n listFormats,\n registerFormat,\n} from \"./registry\"\n\nexport type { SheetOptions, SheetUiOptions } from \"./sheet-options\"\n\nexport const VERSION = \"0.1.0\"\n"]}
package/dist/pdf.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as react from 'react';
2
- import { D as DocumentViewProps } from './registry-CvGg65s9.js';
2
+ import { D as DocumentViewProps } from './registry-BBL8yeIT.js';
3
3
 
4
4
  /**
5
5
  * pdf 通道:pdf.js 只读预览。
package/dist/pptx.d.ts ADDED
@@ -0,0 +1,36 @@
1
+ import { D as DocumentViewProps } from './registry-BBL8yeIT.js';
2
+ import * as react from 'react';
3
+
4
+ /**
5
+ * pptx 的「幽灵 slideMaster」修复。
6
+ *
7
+ * 很多 pptx 的 `[Content_Types].xml` 里声明了 zip 中**并不存在**的
8
+ * `ppt/slideMasters/slideMasterN.xml`。PowerPoint 自己不介意,但 `pptx-preview`
9
+ * 会因为解析不到这些部件而整份失败 —— 表现是 `slideCount === 0`、面板一片空白,
10
+ * 不抛错、不报警。
11
+ *
12
+ * 这不是边缘情况:在 17 份真实文件上实测,**12 份中招**(AI 生成的和
13
+ * PowerPoint 16 存出来的都有)。裸接这个库只有 5/17 能打开,加上本修复后 17/17。
14
+ *
15
+ * 做法与腾讯 WorkBuddy 内置 pptx 预览里的 `checkPPTXFile` 一致:删掉指向缺失
16
+ * 部件的 `<Override>` 再重新打包。只处理 slideMaster —— 其它部件缺失时该库
17
+ * 能自己降级,动它反而可能改坏。
18
+ */
19
+ interface RepairPptxResult {
20
+ /** 修好的字节;没问题时**原样返回入参**,不做无谓的解压重打包 */
21
+ bytes: Uint8Array;
22
+ /** 删掉了几条幽灵声明。0 表示这份文件本来就是好的 */
23
+ removed: number;
24
+ /** 修复过程中放弃的原因(文件不是 zip、没有 [Content_Types].xml 等) */
25
+ skipped?: string;
26
+ }
27
+ /**
28
+ * @param input pptx 原始字节
29
+ * @returns 可交给 `pptx-preview` 的字节。**任何异常都退回原字节** ——
30
+ * 修不了也不该让预览失败,让引擎自己去报它的错。
31
+ */
32
+ declare function repairPptx(input: Uint8Array): RepairPptxResult;
33
+
34
+ declare function PptxView({ source, content, className, onReady, onError, }: DocumentViewProps): react.JSX.Element;
35
+
36
+ export { DocumentViewProps, PptxView, type RepairPptxResult, repairPptx };
package/dist/pptx.js ADDED
@@ -0,0 +1,3 @@
1
+ export { PptxView, repairPptx } from './chunk-5XG4YMAB.js';
2
+ //# sourceMappingURL=pptx.js.map
3
+ //# sourceMappingURL=pptx.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"pptx.js"}
@@ -0,0 +1,148 @@
1
+ import { ComponentType } from 'react';
2
+
3
+ /**
4
+ * sheet 通道的初始化配置。
5
+ *
6
+ * 单独一个文件是为了让 `registry.ts` 能只 `import type` 拿到它 ——
7
+ * 类型在构建期就被擦掉,不会把 `views/SheetView` 从懒加载里拽出来。
8
+ *
9
+ * **这些值只在 Univer 实例创建时读一次。** 挂载后再改不会热更新,
10
+ * 也不会触发重建(重建一次 = 重下 871KB 引擎 + 丢掉全部选区/滚动位置,
11
+ * 而宿主几乎总是传内联字面量,一进依赖数组就是每次渲染重建)。
12
+ * 要换配置请换 `key`。
13
+ */
14
+ /** 对应 Univer 的 `IWorkbenchOptions` / `IUniverSheetsUIConfig`,只透传只读预览会用到的那些。 */
15
+ interface SheetUiOptions {
16
+ /** 顶部区域总开关(关掉连带工具栏一起没)。默认 true */
17
+ header?: boolean;
18
+ /**
19
+ * 工具栏(功能区)。
20
+ * **`readOnly` 时默认 false** —— 整条 ribbon 在只读预览里全是死按钮,
21
+ * 在窄面板里还特别挤。显式传值一律以宿主为准。
22
+ */
23
+ toolbar?: boolean;
24
+ /** 功能区形态:`collapsed` 收起 / `simple` 精简 / `classic` 完整 */
25
+ ribbonType?: "collapsed" | "simple" | "classic";
26
+ /**
27
+ * 底栏(工作表标签 + 状态栏)。默认 true。
28
+ * 多工作表时它是唯一的切表入口 —— 只读预览也别关,否则用户只能看见第一张表。
29
+ */
30
+ footer?: boolean;
31
+ /** 编辑栏 / 公式栏。默认 true */
32
+ formulaBar?: boolean;
33
+ /** 右键菜单。默认 true(只读下仍保留复制) */
34
+ contextMenu?: boolean;
35
+ /** 底部状态栏的求和 / 计数统计。默认 true */
36
+ statusBarStatistic?: boolean;
37
+ /** 逐个菜单项的显隐与禁用,key 是 Univer 的命令 id。透传给 `IUniverUIConfig["menu"]` */
38
+ menu?: Record<string, unknown>;
39
+ }
40
+ interface SheetOptions {
41
+ /** UI 开关,直接透传给 `UniverSheetsCorePreset` */
42
+ ui?: SheetUiOptions;
43
+ /**
44
+ * 内置查找替换(Ctrl+F)。
45
+ *
46
+ * 不需要宿主额外装包:`preset-sheets-find-replace` 是 `@univerjs/presets`
47
+ * 自己的 dependency,这里走子路径动态 import,只在开了的时候才下载。
48
+ */
49
+ findReplace?: boolean;
50
+ /**
51
+ * 追加任意其它 Univer preset(筛选、排序、条件格式、图片……)。
52
+ *
53
+ * 宿主自己 import 工厂函数、自己调用,把返回值传进来 —— 本包不代劳,
54
+ * 因为每个 preset 都是一笔独立体积,该由用它的人显式付出。
55
+ *
56
+ * ```ts
57
+ * import { UniverSheetsFilterPreset } from "@univerjs/presets/preset-sheets-filter"
58
+ * import zhCN from "@univerjs/presets/preset-sheets-filter/locales/zh-CN"
59
+ *
60
+ * <DocumentView format="sheet" content={snapshot}
61
+ * sheet={{ presets: [UniverSheetsFilterPreset()], locales: [zhCN] }} />
62
+ * ```
63
+ *
64
+ * 类型是 `unknown[]`:`@univerjs/presets` 是可选 peer,没装的项目里
65
+ * `IPreset` 根本解析不到,写死类型会让这些人 typecheck 直接红。
66
+ */
67
+ presets?: unknown[];
68
+ /**
69
+ * 额外的 locale 包,与内置 zh-CN 深合并。
70
+ * `createUniver` **不会**自动收集 preset 自带的 locales —— 加了 preset 就得配套加这个,
71
+ * 否则菜单上会显示成 `sheets-filter.command.title` 这样的原始 key。
72
+ */
73
+ locales?: unknown[];
74
+ }
75
+
76
+ /**
77
+ * 格式注册表 —— 整个包的骨架。
78
+ *
79
+ * 设计取自腾讯 WorkBuddy 的三通道结构:**按「文档住在哪 / 是什么」分通道,
80
+ * 每个通道一个独立引擎,上面加一层路由**。它没有试图用一套编辑器覆盖全部格式,
81
+ * 我们也不该。
82
+ *
83
+ * 三条硬约束:
84
+ *
85
+ * 1. **引擎必须懒加载。** Univer sheets 核心 gzip 后 871KB、pdf.js 也有几百 KB。
86
+ * 塞进主包会毁掉阅读侧的首屏 —— 只有用户真的打开一个表格 / PDF 时才该下载。
87
+ *
88
+ * 2. **能力要可申报,不能靠猜。** `capabilities` 明确说清每种格式能不能编辑、
89
+ * 能不能被 AI 结构化操作。上层据此决定给什么 UI,而不是先渲染再报错。
90
+ *
91
+ * 3. **宿主可注册自有引擎。** 内置覆盖不了的格式(如 pptx)留给宿主接,
92
+ * 而不是我们假装支持。
93
+ */
94
+
95
+ type DocumentFormat = "doc" | "sheet" | "pdf" | "pptx" | (string & {});
96
+ interface FormatCapabilities {
97
+ /** 能否编辑。false 表示只读预览 */
98
+ editable: boolean;
99
+ /** 能否被 AI 通过结构化工具面操作(读结构、按 id 定位、增删改) */
100
+ aiOperable: boolean;
101
+ /** 能否协同编辑 */
102
+ collaborative: boolean;
103
+ /** 支持导入的扩展名 */
104
+ importExtensions: string[];
105
+ /** 支持导出的格式 */
106
+ exportFormats: string[];
107
+ }
108
+ interface FormatAdapter {
109
+ format: DocumentFormat;
110
+ /** 展示名,给 UI 用 */
111
+ label: string;
112
+ capabilities: FormatCapabilities;
113
+ /** 该格式认领哪些扩展名(小写、带点) */
114
+ extensions: string[];
115
+ /**
116
+ * 懒加载视图组件。**必须是动态 import**,否则引擎会被打进主包。
117
+ */
118
+ load: () => Promise<{
119
+ default: ComponentType<DocumentViewProps>;
120
+ }>;
121
+ }
122
+ interface DocumentViewProps {
123
+ /** 文档来源:URL / File / 已有内容 */
124
+ source?: string | Blob | File;
125
+ /** 已有内容(doc 走 tiptap JSON 或 HTML;sheet 走 Univer snapshot) */
126
+ content?: unknown;
127
+ readOnly?: boolean;
128
+ /** 协同房间号,仅 collaborative 为 true 的格式有意义 */
129
+ room?: string;
130
+ className?: string;
131
+ /**
132
+ * sheet 通道专属配置(Univer preset 与 UI 开关),其它格式忽略。
133
+ *
134
+ * 放在这里而不是只给 `SheetView`,是因为宿主通常只见得到 `DocumentView`
135
+ * 这一层路由 —— 配置进不来的话,只读预览就只能顶着整条编辑工具条。
136
+ */
137
+ sheet?: SheetOptions;
138
+ /** 引擎就绪回调,拿到底层实例(tiptap Editor / Univer API / PDFDocumentProxy) */
139
+ onReady?: (instance: unknown) => void;
140
+ onError?: (error: Error) => void;
141
+ }
142
+ declare function registerFormat(adapter: FormatAdapter): void;
143
+ declare function getFormat(format: DocumentFormat): FormatAdapter | undefined;
144
+ declare function listFormats(): FormatAdapter[];
145
+ /** 按扩展名判定格式。传文件名、路径或裸扩展名都行。 */
146
+ declare function detectFormat(nameOrExt: string): DocumentFormat | undefined;
147
+
148
+ export { type DocumentViewProps as D, type FormatAdapter as F, type SheetOptions as S, type DocumentFormat as a, type FormatCapabilities as b, type SheetUiOptions as c, detectFormat as d, getFormat as g, listFormats as l, registerFormat as r };
package/dist/sheet.d.ts CHANGED
@@ -1,5 +1,6 @@
1
+ import { D as DocumentViewProps } from './registry-BBL8yeIT.js';
2
+ export { S as SheetOptions, c as SheetUiOptions } from './registry-BBL8yeIT.js';
1
3
  import * as react from 'react';
2
- import { D as DocumentViewProps } from './registry-CvGg65s9.js';
3
4
 
4
5
  /**
5
6
  * sheet 通道:Univer 适配。
@@ -8,9 +9,12 @@ import { D as DocumentViewProps } from './registry-CvGg65s9.js';
8
9
  * 前者要自建 Univer Server 且需商业授权,后者 63MB、license 字段为 None、
9
10
  * 90/91 个 JS 被混淆、还会向 univer.ai 上报遥测 —— 不能进生产链路。
10
11
  *
11
- * xlsx 出入建议走 SheetJS(Apache-2.0)在宿主侧做,接受公式/图表保真损失,
12
- * 换零采购与代码可控。
12
+ * xlsx **导出**用本包的 `./xlsx`:直接读 Univer 快照拼 OOXML,不引 SheetJS
13
+ * **导入**仍在宿主侧自理(SheetJS 或后端解析),本包不做 xlsx → 快照那一向。
14
+ *
15
+ * 配置见 `SheetOptions`:UI 开关直接透传给 preset(Univer 的 class 是
16
+ * `univer-` 前缀的原子类,没有稳定选择器,CSS 硬藏靠不住),额外能力靠追加 preset。
13
17
  */
14
- declare function SheetView({ content, readOnly, className, onReady, onError }: DocumentViewProps): react.JSX.Element;
18
+ declare function SheetView({ content, readOnly, className, sheet, onReady, onError, }: DocumentViewProps): react.JSX.Element;
15
19
 
16
20
  export { DocumentViewProps, SheetView };
package/dist/sheet.js CHANGED
@@ -1,3 +1,3 @@
1
- export { SheetView } from './chunk-RJUDOUEZ.js';
1
+ export { SheetView } from './chunk-L6ZR5HHW.js';
2
2
  //# sourceMappingURL=sheet.js.map
3
3
  //# sourceMappingURL=sheet.js.map