@tita-agent/dsh-data-table 0.0.0-stage → 0.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/LICENSE +21 -0
- package/README.md +172 -2
- package/cordis.patch.yml +7 -0
- package/lib/client.js +16766 -0
- package/lib/contract-envelope.d.ts +91 -0
- package/lib/contract-envelope.js +106 -0
- package/lib/contract-layout.d.ts +130 -0
- package/lib/contract-layout.js +138 -0
- package/lib/contract-refs.d.ts +39 -0
- package/lib/contract-refs.js +9 -0
- package/lib/contract-version.d.ts +311 -0
- package/lib/contract-version.js +270 -0
- package/lib/contract-wire.d.ts +147 -0
- package/lib/contract-wire.js +99 -0
- package/lib/contracts.d.ts +12 -0
- package/lib/contracts.js +12 -0
- package/lib/types/audit.d.ts +42 -0
- package/lib/types/audit.js +39 -0
- package/lib/types/client/ProjectImportMenu.d.ts +30 -0
- package/lib/types/client/ProjectImportMenu.js +70 -0
- package/lib/types/client/SheetPanel.d.ts +33 -0
- package/lib/types/client/SheetPanel.js +1766 -0
- package/lib/types/client/SheetToggle.d.ts +18 -0
- package/lib/types/client/SheetToggle.js +26 -0
- package/lib/types/client/index.d.ts +15 -0
- package/lib/types/client/index.js +110 -0
- package/lib/types/client/persistence/changes.d.ts +15 -0
- package/lib/types/client/persistence/changes.js +51 -0
- package/lib/types/client/persistence/ids.d.ts +34 -0
- package/lib/types/client/persistence/ids.js +48 -0
- package/lib/types/client/persistence/merge.d.ts +35 -0
- package/lib/types/client/persistence/merge.js +362 -0
- package/lib/types/client/store.d.ts +61 -0
- package/lib/types/client/store.js +152 -0
- package/lib/types/client/surface-context.d.ts +27 -0
- package/lib/types/client/surface-context.js +104 -0
- package/lib/types/client/surface-control.d.ts +2 -0
- package/lib/types/client/surface-control.js +12 -0
- package/lib/types/client/transport/workbookApi.d.ts +159 -0
- package/lib/types/client/transport/workbookApi.js +246 -0
- package/lib/types/client/univer/StandaloneWorkbookPage.d.ts +5 -0
- package/lib/types/client/univer/StandaloneWorkbookPage.js +7 -0
- package/lib/types/client/univer/WorkbookCanvas.d.ts +40 -0
- package/lib/types/client/univer/WorkbookCanvas.js +87 -0
- package/lib/types/client/univer/WorkbookView.d.ts +3 -0
- package/lib/types/client/univer/WorkbookView.js +2 -0
- package/lib/types/client/univer/loader.d.ts +18 -0
- package/lib/types/client/univer/loader.js +81 -0
- package/lib/types/client/univer/standalone-entry.d.ts +1 -0
- package/lib/types/client/univer/standalone-entry.js +19 -0
- package/lib/types/client/versions/VersionChrome.d.ts +33 -0
- package/lib/types/client/versions/VersionChrome.js +21 -0
- package/lib/types/client/versions/inspect.d.ts +70 -0
- package/lib/types/client/versions/inspect.js +88 -0
- package/lib/types/client/versions/preview-decoration.d.ts +11 -0
- package/lib/types/client/versions/preview-decoration.js +54 -0
- package/lib/types/client/versions/records.d.ts +19 -0
- package/lib/types/client/versions/records.js +70 -0
- package/lib/types/client/workspace-host.d.ts +61 -0
- package/lib/types/client/workspace-host.js +124 -0
- package/lib/types/collaboration-hub.d.ts +31 -0
- package/lib/types/collaboration-hub.js +69 -0
- package/lib/types/contract/ids.d.ts +24 -0
- package/lib/types/contract/ids.js +46 -0
- package/lib/types/contract/mutations.d.ts +25 -0
- package/lib/types/contract/mutations.js +782 -0
- package/lib/types/contract/operations.d.ts +54 -0
- package/lib/types/contract/operations.js +203 -0
- package/lib/types/contract/snapshots.d.ts +176 -0
- package/lib/types/contract/snapshots.js +351 -0
- package/lib/types/contract/univer-api-catalog.d.ts +29 -0
- package/lib/types/contract/univer-api-catalog.js +264 -0
- package/lib/types/errors.d.ts +8 -0
- package/lib/types/errors.js +12 -0
- package/lib/types/http/assets-routes.d.ts +3 -0
- package/lib/types/http/assets-routes.js +169 -0
- package/lib/types/http/collaboration.d.ts +10 -0
- package/lib/types/http/collaboration.js +154 -0
- package/lib/types/http/history.d.ts +15 -0
- package/lib/types/http/history.js +134 -0
- package/lib/types/http/project-files.d.ts +79 -0
- package/lib/types/http/project-files.js +206 -0
- package/lib/types/http/project-scope.d.ts +50 -0
- package/lib/types/http/project-scope.js +52 -0
- package/lib/types/http/routes.d.ts +98 -0
- package/lib/types/http/routes.js +323 -0
- package/lib/types/http/standalone.d.ts +9 -0
- package/lib/types/http/standalone.js +31 -0
- package/lib/types/http/trunk.d.ts +10 -0
- package/lib/types/http/trunk.js +322 -0
- package/lib/types/http/worktree.d.ts +9 -0
- package/lib/types/http/worktree.js +145 -0
- package/lib/types/index.d.ts +18 -0
- package/lib/types/index.js +156 -0
- package/lib/types/invariant.d.ts +15 -0
- package/lib/types/invariant.js +15 -0
- package/lib/types/request-params.d.ts +21 -0
- package/lib/types/request-params.js +76 -0
- package/lib/types/store/paths.d.ts +68 -0
- package/lib/types/store/paths.js +148 -0
- package/lib/types/store/project.d.ts +88 -0
- package/lib/types/store/project.js +120 -0
- package/lib/types/store/read.d.ts +110 -0
- package/lib/types/store/read.js +404 -0
- package/lib/types/store/retention.d.ts +27 -0
- package/lib/types/store/retention.js +111 -0
- package/lib/types/store/write.d.ts +22 -0
- package/lib/types/store/write.js +210 -0
- package/lib/types/tools/arguments.d.ts +40 -0
- package/lib/types/tools/arguments.js +130 -0
- package/lib/types/tools/cells.d.ts +50 -0
- package/lib/types/tools/cells.js +298 -0
- package/lib/types/tools/index.d.ts +2 -0
- package/lib/types/tools/index.js +250 -0
- package/lib/types/tools/render.d.ts +47 -0
- package/lib/types/tools/render.js +290 -0
- package/lib/types/tools/univer-api.d.ts +18 -0
- package/lib/types/tools/univer-api.js +198 -0
- package/lib/types/tools/workbook-catalog.d.ts +3 -0
- package/lib/types/tools/workbook-catalog.js +83 -0
- package/lib/types/tools/workbook-edit.d.ts +192 -0
- package/lib/types/tools/workbook-edit.js +912 -0
- package/lib/types/tools/workbook-find.d.ts +11 -0
- package/lib/types/tools/workbook-find.js +89 -0
- package/lib/types/tools/workbook-history.d.ts +2 -0
- package/lib/types/tools/workbook-history.js +59 -0
- package/lib/types/tools/workbook-read.d.ts +21 -0
- package/lib/types/tools/workbook-read.js +204 -0
- package/lib/types/types.d.ts +377 -0
- package/lib/types/types.js +34 -0
- package/lib/types/univer/apply-operation.d.ts +14 -0
- package/lib/types/univer/apply-operation.js +398 -0
- package/lib/types/univer/chunk.d.ts +68 -0
- package/lib/types/univer/chunk.js +16 -0
- package/lib/types/univer/decoration.d.ts +34 -0
- package/lib/types/univer/decoration.js +88 -0
- package/lib/types/univer/diagnostics.d.ts +31 -0
- package/lib/types/univer/diagnostics.js +141 -0
- package/lib/types/univer/engine.d.ts +33 -0
- package/lib/types/univer/engine.js +338 -0
- package/lib/types/univer/entry.d.ts +8 -0
- package/lib/types/univer/entry.js +11 -0
- package/lib/types/univer/facade.d.ts +107 -0
- package/lib/types/univer/facade.js +1 -0
- package/lib/types/univer/fonts.d.ts +202 -0
- package/lib/types/univer/fonts.js +140 -0
- package/lib/types/univer/plugins/collaboration.d.ts +23 -0
- package/lib/types/univer/plugins/collaboration.js +47 -0
- package/lib/types/univer/plugins/export.d.ts +39 -0
- package/lib/types/univer/plugins/export.js +246 -0
- package/lib/types/univer/plugins/history.d.ts +24 -0
- package/lib/types/univer/plugins/history.js +50 -0
- package/lib/types/univer/plugins/version.d.ts +13 -0
- package/lib/types/univer/plugins/version.js +13 -0
- package/lib/types/version/diff.d.ts +36 -0
- package/lib/types/version/diff.js +156 -0
- package/lib/types/version/grouping.d.ts +7 -0
- package/lib/types/version/grouping.js +94 -0
- package/lib/types/version/records.d.ts +17 -0
- package/lib/types/version/records.js +102 -0
- package/lib/types/version/session.d.ts +36 -0
- package/lib/types/version/session.js +89 -0
- package/lib/types/version/summary.d.ts +24 -0
- package/lib/types/version/summary.js +111 -0
- package/lib/types/workbook-manager.d.ts +191 -0
- package/lib/types/workbook-manager.js +935 -0
- package/lib/types/xlsx-export-worker.d.ts +5 -0
- package/lib/types/xlsx-export-worker.js +24 -0
- package/lib/types/xlsx-export.d.ts +21 -0
- package/lib/types/xlsx-export.js +371 -0
- package/lib/types/xlsx-import-worker.d.ts +1 -0
- package/lib/types/xlsx-import-worker.js +19 -0
- package/lib/univer-client.js +690 -0
- package/lib/workbook-page.js +23826 -0
- package/lib/xlsx-export-worker.js +104 -0
- package/lib/xlsx-import-worker.js +46 -0
- package/package.json +106 -3
- package/src/audit.ts +58 -0
- package/src/client/ProjectImportMenu.tsx +100 -0
- package/src/client/SheetPanel.module.css +1578 -0
- package/src/client/SheetPanel.tsx +1934 -0
- package/src/client/SheetToggle.module.css +73 -0
- package/src/client/SheetToggle.tsx +65 -0
- package/src/client/contract.d.ts +19 -0
- package/src/client/index.ts +131 -0
- package/src/client/persistence/changes.ts +58 -0
- package/src/client/persistence/ids.ts +60 -0
- package/src/client/persistence/merge.ts +392 -0
- package/src/client/store.ts +188 -0
- package/src/client/surface-context.ts +123 -0
- package/src/client/surface-control.ts +13 -0
- package/src/client/transport/workbookApi.ts +355 -0
- package/src/client/univer/SheetView.module.css +110 -0
- package/src/client/univer/StandaloneWorkbookPage.module.css +8 -0
- package/src/client/univer/StandaloneWorkbookPage.tsx +15 -0
- package/src/client/univer/WorkbookCanvas.tsx +128 -0
- package/src/client/univer/WorkbookView.module.css +30 -0
- package/src/client/univer/WorkbookView.tsx +3 -0
- package/src/client/univer/loader.ts +90 -0
- package/src/client/univer/react-dom-client.d.ts +10 -0
- package/src/client/univer/standalone-entry.tsx +19 -0
- package/src/client/univer/standalone.css +12 -0
- package/src/client/univer/univer-locales.d.ts +89 -0
- package/src/client/univer/univer-modules.d.ts +26 -0
- package/src/client/versions/VersionChrome.tsx +114 -0
- package/src/client/versions/inspect.ts +138 -0
- package/src/client/versions/preview-decoration.ts +54 -0
- package/src/client/versions/records.ts +78 -0
- package/src/client/workspace-host.ts +147 -0
- package/src/collaboration-hub.ts +83 -0
- package/src/contract/ids.ts +50 -0
- package/src/contract/mutations.ts +791 -0
- package/src/contract/operations.ts +221 -0
- package/src/contract/snapshots.ts +383 -0
- package/src/contract/univer-api-catalog.ts +289 -0
- package/src/css-modules.d.ts +6 -0
- package/src/errors.ts +25 -0
- package/src/http/assets-routes.ts +177 -0
- package/src/http/collaboration.ts +144 -0
- package/src/http/history.ts +151 -0
- package/src/http/project-files.ts +227 -0
- package/src/http/project-scope.ts +77 -0
- package/src/http/routes.ts +364 -0
- package/src/http/standalone.ts +32 -0
- package/src/http/trunk.ts +322 -0
- package/src/http/worktree.ts +119 -0
- package/src/index.ts +165 -0
- package/src/invariant.ts +17 -0
- package/src/request-params.ts +92 -0
- package/src/store/paths.ts +165 -0
- package/src/store/project.ts +137 -0
- package/src/store/read.ts +454 -0
- package/src/store/retention.ts +106 -0
- package/src/store/write.ts +230 -0
- package/src/tools/arguments.ts +128 -0
- package/src/tools/cells.ts +298 -0
- package/src/tools/index.ts +274 -0
- package/src/tools/render.ts +301 -0
- package/src/tools/univer-api.ts +243 -0
- package/src/tools/workbook-catalog.ts +94 -0
- package/src/tools/workbook-edit.ts +987 -0
- package/src/tools/workbook-find.ts +80 -0
- package/src/tools/workbook-history.ts +63 -0
- package/src/tools/workbook-read.ts +210 -0
- package/src/types.ts +432 -0
- package/src/univer/apply-operation.ts +369 -0
- package/src/univer/chunk.ts +77 -0
- package/src/univer/decoration.ts +111 -0
- package/src/univer/diagnostics.ts +136 -0
- package/src/univer/engine.ts +370 -0
- package/src/univer/entry.ts +14 -0
- package/src/univer/facade.ts +91 -0
- package/src/univer/fonts.ts +148 -0
- package/src/univer/overrides.css +33 -0
- package/src/univer/plugins/collaboration.ts +47 -0
- package/src/univer/plugins/export.ts +236 -0
- package/src/univer/plugins/history.ts +49 -0
- package/src/univer/plugins/version.ts +13 -0
- package/src/version/diff.ts +169 -0
- package/src/version/grouping.ts +96 -0
- package/src/version/records.ts +113 -0
- package/src/version/session.ts +90 -0
- package/src/version/summary.ts +117 -0
- package/src/workbook-manager.ts +1111 -0
- package/src/xlsx-export-worker.ts +34 -0
- package/src/xlsx-export.ts +373 -0
- package/src/xlsx-import-worker.ts +30 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Univer-side export plugin for dsh-data-table.
|
|
3
|
+
*
|
|
4
|
+
* This follows Univer's normal plugin shape: commands are registered with
|
|
5
|
+
* ICommandService. The browser-facing exporter stays a small adapter over
|
|
6
|
+
* the live Workbook model, so menu actions always export the current
|
|
7
|
+
* in-memory edits.
|
|
8
|
+
* @module dsh-data-table/univer-export-plugin
|
|
9
|
+
*/
|
|
10
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
11
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
12
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
13
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
14
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
15
|
+
};
|
|
16
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
17
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
18
|
+
};
|
|
19
|
+
import { CommandType, Inject, ICommandService, IUniverInstanceService, Injector, Plugin, UniverInstanceType, } from '@univerjs/presets';
|
|
20
|
+
import { downloadBlob, downloadWorkbookCsv, downloadWorkbookXlsx, readWorkbookXlsx } from "../../xlsx-export.js";
|
|
21
|
+
import { UNIVER_PLUGIN_VERSION } from "./version.js";
|
|
22
|
+
function dataSheetAssetPath(path) {
|
|
23
|
+
const transport = globalThis.__DSH_TRANSPORT__;
|
|
24
|
+
return transport?.ownsHost === true
|
|
25
|
+
? `/api/data-sheet?path=${encodeURIComponent(path)}`
|
|
26
|
+
: path;
|
|
27
|
+
}
|
|
28
|
+
export const EXPORT_CSV_COMMAND_ID = 'dsh.data-sheet.export-csv';
|
|
29
|
+
export const EXPORT_XLSX_COMMAND_ID = 'dsh.data-sheet.export-xlsx';
|
|
30
|
+
export const IMPORT_XLSX_COMMAND_ID = 'dsh.data-sheet.import-xlsx';
|
|
31
|
+
/** Univer plugin that owns the datasheet export commands and DSH menu bridge. */
|
|
32
|
+
let DataSheetExportPlugin = class DataSheetExportPlugin extends Plugin {
|
|
33
|
+
_injector;
|
|
34
|
+
_commandService;
|
|
35
|
+
_univerInstanceService;
|
|
36
|
+
static pluginName = 'dsh.data-sheet.export';
|
|
37
|
+
static packageName = 'dsh-data-table';
|
|
38
|
+
static version = UNIVER_PLUGIN_VERSION;
|
|
39
|
+
static type = UniverInstanceType.UNIVER_SHEET;
|
|
40
|
+
constructor(_config, _injector, _commandService, _univerInstanceService) {
|
|
41
|
+
super();
|
|
42
|
+
this._injector = _injector;
|
|
43
|
+
this._commandService = _commandService;
|
|
44
|
+
this._univerInstanceService = _univerInstanceService;
|
|
45
|
+
}
|
|
46
|
+
onStarting() {
|
|
47
|
+
this.disposeWithMe(this._commandService.registerCommand({
|
|
48
|
+
id: EXPORT_CSV_COMMAND_ID,
|
|
49
|
+
type: CommandType.COMMAND,
|
|
50
|
+
handler: () => this.export('csv'),
|
|
51
|
+
}));
|
|
52
|
+
this.disposeWithMe(this._commandService.registerCommand({
|
|
53
|
+
id: EXPORT_XLSX_COMMAND_ID,
|
|
54
|
+
type: CommandType.COMMAND,
|
|
55
|
+
handler: () => this.export('xlsx'),
|
|
56
|
+
}));
|
|
57
|
+
this.disposeWithMe(this._commandService.registerCommand({
|
|
58
|
+
id: IMPORT_XLSX_COMMAND_ID,
|
|
59
|
+
type: CommandType.COMMAND,
|
|
60
|
+
handler: () => {
|
|
61
|
+
this.importFile('xlsx');
|
|
62
|
+
return true;
|
|
63
|
+
},
|
|
64
|
+
}));
|
|
65
|
+
// The compact DSH header owns import/export. Keep the bridge event-based
|
|
66
|
+
// so the React shell does not depend on Univer's private Injector or
|
|
67
|
+
// command service implementation.
|
|
68
|
+
const onExportCsv = () => { void this.export('csv'); };
|
|
69
|
+
const onExportXlsx = () => { void this.export('xlsx'); };
|
|
70
|
+
const onExportJson = () => { void this.export('json'); };
|
|
71
|
+
const onImportXlsx = () => { this.importFile('xlsx'); };
|
|
72
|
+
// The project chooser read the file's bytes on the Host side; parsing is the
|
|
73
|
+
// same pipeline, so it enters here rather than in a second implementation.
|
|
74
|
+
const onImportBytes = (event) => {
|
|
75
|
+
const detail = event.detail;
|
|
76
|
+
if (detail?.data === undefined)
|
|
77
|
+
return;
|
|
78
|
+
void this.importBuffer(detail.data, detail.name ?? '导入文件');
|
|
79
|
+
};
|
|
80
|
+
document.addEventListener('data-sheet:import-bytes', onImportBytes);
|
|
81
|
+
const onImportCsv = () => { this.importFile('csv'); };
|
|
82
|
+
window.addEventListener('data-sheet:request-export-csv', onExportCsv);
|
|
83
|
+
window.addEventListener('data-sheet:request-export-xlsx', onExportXlsx);
|
|
84
|
+
window.addEventListener('data-sheet:request-export-json', onExportJson);
|
|
85
|
+
window.addEventListener('data-sheet:request-import-xlsx', onImportXlsx);
|
|
86
|
+
window.addEventListener('data-sheet:request-import-csv', onImportCsv);
|
|
87
|
+
window.addEventListener('data-sheet:import-bytes', onImportBytes);
|
|
88
|
+
this.disposeWithMe({
|
|
89
|
+
dispose: () => {
|
|
90
|
+
window.removeEventListener('data-sheet:request-export-csv', onExportCsv);
|
|
91
|
+
window.removeEventListener('data-sheet:request-export-xlsx', onExportXlsx);
|
|
92
|
+
window.removeEventListener('data-sheet:request-export-json', onExportJson);
|
|
93
|
+
window.removeEventListener('data-sheet:request-import-xlsx', onImportXlsx);
|
|
94
|
+
window.removeEventListener('data-sheet:request-import-csv', onImportCsv);
|
|
95
|
+
window.removeEventListener('data-sheet:import-bytes', onImportBytes);
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
async export(format) {
|
|
100
|
+
const workbook = this._univerInstanceService.getCurrentUnitOfType(UniverInstanceType.UNIVER_SHEET);
|
|
101
|
+
if (workbook == null)
|
|
102
|
+
return false;
|
|
103
|
+
const snapshot = workbook.getSnapshot();
|
|
104
|
+
const filename = snapshotWorkbookName(snapshot) || workbook.name || 'workbook';
|
|
105
|
+
if (format === 'json') {
|
|
106
|
+
// The workbook snapshot is already the plugin's own persistence format,
|
|
107
|
+
// so a JSON export is a lossless, round-trippable dump.
|
|
108
|
+
downloadBlob(new Blob([JSON.stringify(snapshot, null, 2)], { type: 'application/json;charset=utf-8' }), `${safeFilename(filename)}.json`);
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
const result = await exportInWorker(format, snapshot);
|
|
113
|
+
if (result === null)
|
|
114
|
+
return false;
|
|
115
|
+
if (format === 'csv') {
|
|
116
|
+
downloadBlob(new Blob([result], { type: 'text/csv;charset=utf-8' }), `${safeFilename(filename)}.csv`);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
downloadBlob(new Blob([result], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }), `${safeFilename(filename)}.xlsx`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
// A server started before the worker route existed, or a restricted
|
|
124
|
+
// browser, still gets a functional export through the synchronous path.
|
|
125
|
+
if (format === 'csv')
|
|
126
|
+
downloadWorkbookCsv(snapshot, filename);
|
|
127
|
+
else
|
|
128
|
+
downloadWorkbookXlsx(snapshot, filename);
|
|
129
|
+
}
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
importFile(format) {
|
|
133
|
+
const input = document.createElement('input');
|
|
134
|
+
input.type = 'file';
|
|
135
|
+
input.accept = format === 'csv'
|
|
136
|
+
? '.csv,text/csv'
|
|
137
|
+
: '.xlsx,.xls,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel';
|
|
138
|
+
input.addEventListener('change', () => {
|
|
139
|
+
const file = input.files?.[0];
|
|
140
|
+
if (file === undefined)
|
|
141
|
+
return;
|
|
142
|
+
if (file.size > 25 * 1024 * 1024) {
|
|
143
|
+
window.dispatchEvent(new CustomEvent('data-sheet:import-error', { detail: { message: `${format === 'csv' ? 'CSV' : 'Excel'} 文件过大(最大 25 MB)` } }));
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
void file.arrayBuffer().then(data => this.importBuffer(data, file.name));
|
|
147
|
+
});
|
|
148
|
+
input.click();
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Parse one buffer and hand the workbook to the panel.
|
|
152
|
+
*
|
|
153
|
+
* Shared by both ways in — the OS file dialog and the project chooser, which
|
|
154
|
+
* reads the bytes over HTTP because a browser cannot open a path. Everything
|
|
155
|
+
* after the bytes arrive is one pipeline, so the two entry points cannot drift
|
|
156
|
+
* into two imports.
|
|
157
|
+
*
|
|
158
|
+
* @param data - The file's bytes.
|
|
159
|
+
* @param name - The name the panel reports and the worker partly decides from.
|
|
160
|
+
*/
|
|
161
|
+
async importBuffer(data, name) {
|
|
162
|
+
try {
|
|
163
|
+
const compatibilityCopy = data.slice(0);
|
|
164
|
+
const snapshot = await importInWorker(data, name).catch(error => {
|
|
165
|
+
// Older servers may not expose the worker route. Keep import
|
|
166
|
+
// functional with a synchronous compatibility fallback in that case.
|
|
167
|
+
if (error instanceof Error && /Worker/.test(error.message))
|
|
168
|
+
return readWorkbookXlsx(compatibilityCopy, name);
|
|
169
|
+
throw error;
|
|
170
|
+
});
|
|
171
|
+
window.dispatchEvent(new CustomEvent('data-sheet:import', { detail: { snapshot, filename: name } }));
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
window.dispatchEvent(new CustomEvent('data-sheet:import-error', { detail: { message: error instanceof Error ? error.message : String(error) } }));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
DataSheetExportPlugin = __decorate([
|
|
179
|
+
__param(1, Inject(Injector)),
|
|
180
|
+
__param(2, ICommandService),
|
|
181
|
+
__param(3, IUniverInstanceService)
|
|
182
|
+
], DataSheetExportPlugin);
|
|
183
|
+
export { DataSheetExportPlugin };
|
|
184
|
+
function importInWorker(data, filename) {
|
|
185
|
+
if (typeof Worker === 'undefined')
|
|
186
|
+
return Promise.reject(new Error('当前浏览器不支持 Worker'));
|
|
187
|
+
return new Promise((resolve, reject) => {
|
|
188
|
+
const worker = new Worker(dataSheetAssetPath('/data-sheet/xlsx-import-worker.js'));
|
|
189
|
+
const timeout = window.setTimeout(() => {
|
|
190
|
+
worker.terminate();
|
|
191
|
+
reject(new Error('导入超时'));
|
|
192
|
+
}, 60_000);
|
|
193
|
+
worker.onmessage = event => {
|
|
194
|
+
window.clearTimeout(timeout);
|
|
195
|
+
worker.terminate();
|
|
196
|
+
const result = event.data;
|
|
197
|
+
if (result.ok !== true || typeof result.snapshot !== 'object' || result.snapshot === null) {
|
|
198
|
+
reject(new Error(result.error ?? '导入失败'));
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
resolve(result.snapshot);
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
worker.onerror = event => {
|
|
205
|
+
window.clearTimeout(timeout);
|
|
206
|
+
worker.terminate();
|
|
207
|
+
reject(new Error(event.message || '导入 Worker 加载失败'));
|
|
208
|
+
};
|
|
209
|
+
worker.postMessage({ data, filename }, [data]);
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
function snapshotWorkbookName(snapshot) {
|
|
213
|
+
if (typeof snapshot !== 'object' || snapshot === null)
|
|
214
|
+
return null;
|
|
215
|
+
const name = snapshot.name;
|
|
216
|
+
return typeof name === 'string' && name.trim().length > 0 ? name.trim() : null;
|
|
217
|
+
}
|
|
218
|
+
function exportInWorker(format, snapshot) {
|
|
219
|
+
if (typeof Worker === 'undefined')
|
|
220
|
+
return Promise.reject(new Error('当前浏览器不支持 Worker'));
|
|
221
|
+
return new Promise((resolve, reject) => {
|
|
222
|
+
const worker = new Worker(dataSheetAssetPath('/data-sheet/xlsx-export-worker.js'));
|
|
223
|
+
const timeout = window.setTimeout(() => {
|
|
224
|
+
worker.terminate();
|
|
225
|
+
reject(new Error('导出超时'));
|
|
226
|
+
}, 60_000);
|
|
227
|
+
worker.onmessage = event => {
|
|
228
|
+
window.clearTimeout(timeout);
|
|
229
|
+
worker.terminate();
|
|
230
|
+
const data = event.data;
|
|
231
|
+
if (data.ok !== true)
|
|
232
|
+
reject(new Error(data.error ?? '导出失败'));
|
|
233
|
+
else
|
|
234
|
+
resolve((data.value ?? null));
|
|
235
|
+
};
|
|
236
|
+
worker.onerror = event => {
|
|
237
|
+
window.clearTimeout(timeout);
|
|
238
|
+
worker.terminate();
|
|
239
|
+
reject(new Error(event.message || '导出 Worker 加载失败'));
|
|
240
|
+
};
|
|
241
|
+
worker.postMessage({ format, snapshot });
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
function safeFilename(value) {
|
|
245
|
+
return value.replace(/[\\/:*?"<>|]/g, '-').trim() || 'workbook';
|
|
246
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Datasheet edit-history entry point for the open-source Univer canvas.
|
|
3
|
+
*
|
|
4
|
+
* Univer Pro's history viewer is intentionally not used. The DSH host owns
|
|
5
|
+
* durable operation logs/checkpoints and opens its own history panel when the
|
|
6
|
+
* command is executed.
|
|
7
|
+
*/
|
|
8
|
+
import { ICommandService, Injector, Plugin, UniverInstanceType } from '@univerjs/presets';
|
|
9
|
+
export declare const SHOW_HISTORY_COMMAND_ID = "dsh.data-sheet.show-history";
|
|
10
|
+
export interface DataSheetHistoryPluginConfig {
|
|
11
|
+
onOpenHistory?: () => void;
|
|
12
|
+
}
|
|
13
|
+
/** Registers the DSH history panel command on the Univer ribbon. */
|
|
14
|
+
export declare class DataSheetHistoryPlugin extends Plugin {
|
|
15
|
+
private readonly _config;
|
|
16
|
+
protected readonly _injector: Injector;
|
|
17
|
+
private readonly _commandService;
|
|
18
|
+
static pluginName: string;
|
|
19
|
+
static packageName: string;
|
|
20
|
+
static version: string;
|
|
21
|
+
static type: UniverInstanceType;
|
|
22
|
+
constructor(_config: DataSheetHistoryPluginConfig | undefined, _injector: Injector, _commandService: ICommandService);
|
|
23
|
+
onStarting(): void;
|
|
24
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Datasheet edit-history entry point for the open-source Univer canvas.
|
|
3
|
+
*
|
|
4
|
+
* Univer Pro's history viewer is intentionally not used. The DSH host owns
|
|
5
|
+
* durable operation logs/checkpoints and opens its own history panel when the
|
|
6
|
+
* command is executed.
|
|
7
|
+
*/
|
|
8
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
9
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
10
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
11
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
12
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
13
|
+
};
|
|
14
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
15
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
16
|
+
};
|
|
17
|
+
import { CommandType, Inject, ICommandService, Injector, Plugin, UniverInstanceType, } from '@univerjs/presets';
|
|
18
|
+
import { UNIVER_PLUGIN_VERSION } from "./version.js";
|
|
19
|
+
export const SHOW_HISTORY_COMMAND_ID = 'dsh.data-sheet.show-history';
|
|
20
|
+
/** Registers the DSH history panel command on the Univer ribbon. */
|
|
21
|
+
let DataSheetHistoryPlugin = class DataSheetHistoryPlugin extends Plugin {
|
|
22
|
+
_config;
|
|
23
|
+
_injector;
|
|
24
|
+
_commandService;
|
|
25
|
+
static pluginName = 'dsh.data-sheet.history';
|
|
26
|
+
static packageName = 'dsh-data-table';
|
|
27
|
+
static version = UNIVER_PLUGIN_VERSION;
|
|
28
|
+
static type = UniverInstanceType.UNIVER_SHEET;
|
|
29
|
+
constructor(_config, _injector, _commandService) {
|
|
30
|
+
super();
|
|
31
|
+
this._config = _config;
|
|
32
|
+
this._injector = _injector;
|
|
33
|
+
this._commandService = _commandService;
|
|
34
|
+
}
|
|
35
|
+
onStarting() {
|
|
36
|
+
this.disposeWithMe(this._commandService.registerCommand({
|
|
37
|
+
id: SHOW_HISTORY_COMMAND_ID,
|
|
38
|
+
type: CommandType.COMMAND,
|
|
39
|
+
handler: () => {
|
|
40
|
+
this._config?.onOpenHistory?.();
|
|
41
|
+
return true;
|
|
42
|
+
},
|
|
43
|
+
}));
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
DataSheetHistoryPlugin = __decorate([
|
|
47
|
+
__param(1, Inject(Injector)),
|
|
48
|
+
__param(2, ICommandService)
|
|
49
|
+
], DataSheetHistoryPlugin);
|
|
50
|
+
export { DataSheetHistoryPlugin };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The version every Data Sheet Univer plugin declares.
|
|
3
|
+
*
|
|
4
|
+
* Univer's `PluginService` compares a plugin's declared `version` against its
|
|
5
|
+
* core version and logs `Plugin version mismatch` when they differ. Our three
|
|
6
|
+
* adapters are not `@univerjs/*` packages, but declaring the pinned core
|
|
7
|
+
* version is what keeps that warning out of the console.
|
|
8
|
+
*
|
|
9
|
+
* `tests/univer-plugin-version.test.mjs` pins this constant to the installed
|
|
10
|
+
* `@univerjs/presets` version, so upgrading Univer cannot silently bring the
|
|
11
|
+
* warning back.
|
|
12
|
+
*/
|
|
13
|
+
export declare const UNIVER_PLUGIN_VERSION = "0.25.1";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The version every Data Sheet Univer plugin declares.
|
|
3
|
+
*
|
|
4
|
+
* Univer's `PluginService` compares a plugin's declared `version` against its
|
|
5
|
+
* core version and logs `Plugin version mismatch` when they differ. Our three
|
|
6
|
+
* adapters are not `@univerjs/*` packages, but declaring the pinned core
|
|
7
|
+
* version is what keeps that warning out of the console.
|
|
8
|
+
*
|
|
9
|
+
* `tests/univer-plugin-version.test.mjs` pins this constant to the installed
|
|
10
|
+
* `@univerjs/presets` version, so upgrading Univer cannot silently bring the
|
|
11
|
+
* warning back.
|
|
12
|
+
*/
|
|
13
|
+
export const UNIVER_PLUGIN_VERSION = '0.25.1';
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What changed between two snapshots (docs/issue/0022 P2).
|
|
3
|
+
*
|
|
4
|
+
* Pure, and the only place that decides what counts as a change: a cell's
|
|
5
|
+
* value, a sheet's identity, or the structure around it (merges, freeze,
|
|
6
|
+
* row/column sizes). The Version list and the comparison bar both read this
|
|
7
|
+
* shape, so the answer to "what changed" is defined once.
|
|
8
|
+
*
|
|
9
|
+
* `scopes` narrows the walk to the sheets and ranges an operation actually
|
|
10
|
+
* touched; without it the whole workbook is compared.
|
|
11
|
+
*/
|
|
12
|
+
import type { WorkbookChange, WorkbookOperationPayload, WorkbookOperationType, WorkbookRange } from '../types.ts';
|
|
13
|
+
/**
|
|
14
|
+
* How many differences one diff reports.
|
|
15
|
+
*
|
|
16
|
+
* A bound is necessary — the list travels in an HTTP response and an Agent
|
|
17
|
+
* result — but an *unsignalled* bound is not: a caller receiving exactly this
|
|
18
|
+
* many cannot tell a comparison that found this many from one that stopped
|
|
19
|
+
* looking, so `describeSnapshotReplace` stated `修改 500 个单元格` for a save that
|
|
20
|
+
* touched thousands, and the comparison bar showed the same number as though it
|
|
21
|
+
* were the whole answer. Callers ask {@link diffReachedLimit}.
|
|
22
|
+
*/
|
|
23
|
+
export declare const DIFF_CHANGE_LIMIT = 500;
|
|
24
|
+
/** Whether a diff stopped at the limit, so its count is a lower bound. */
|
|
25
|
+
export declare function diffReachedLimit(changes: readonly WorkbookChange[]): boolean;
|
|
26
|
+
export interface WorkbookDiffScope {
|
|
27
|
+
ref: string;
|
|
28
|
+
wholeSheet: boolean;
|
|
29
|
+
ranges: WorkbookRange[];
|
|
30
|
+
}
|
|
31
|
+
/** Collect the smallest safe regions touched by a proposal. */
|
|
32
|
+
export declare function collectDiffScopes(operations: Array<{
|
|
33
|
+
type: WorkbookOperationType;
|
|
34
|
+
payload: WorkbookOperationPayload;
|
|
35
|
+
}>): Map<string, WorkbookDiffScope>;
|
|
36
|
+
export declare function diffWorkbookSnapshots(before: unknown, after: unknown, scopes?: Map<string, WorkbookDiffScope>): WorkbookChange[];
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How many differences one diff reports.
|
|
3
|
+
*
|
|
4
|
+
* A bound is necessary — the list travels in an HTTP response and an Agent
|
|
5
|
+
* result — but an *unsignalled* bound is not: a caller receiving exactly this
|
|
6
|
+
* many cannot tell a comparison that found this many from one that stopped
|
|
7
|
+
* looking, so `describeSnapshotReplace` stated `修改 500 个单元格` for a save that
|
|
8
|
+
* touched thousands, and the comparison bar showed the same number as though it
|
|
9
|
+
* were the whole answer. Callers ask {@link diffReachedLimit}.
|
|
10
|
+
*/
|
|
11
|
+
export const DIFF_CHANGE_LIMIT = 500;
|
|
12
|
+
/** Whether a diff stopped at the limit, so its count is a lower bound. */
|
|
13
|
+
export function diffReachedLimit(changes) {
|
|
14
|
+
return changes.length >= DIFF_CHANGE_LIMIT;
|
|
15
|
+
}
|
|
16
|
+
import { asWorkbookSnapshot, toCellValue } from "../contract/snapshots.js";
|
|
17
|
+
/** Collect the smallest safe regions touched by a proposal. */
|
|
18
|
+
export function collectDiffScopes(operations) {
|
|
19
|
+
const scopes = new Map();
|
|
20
|
+
const add = (ref, range, wholeSheet = false) => {
|
|
21
|
+
if (ref === undefined || ref.trim().length === 0)
|
|
22
|
+
return;
|
|
23
|
+
const key = ref.trim();
|
|
24
|
+
const scope = scopes.get(key) ?? { ref: key, wholeSheet: false, ranges: [] };
|
|
25
|
+
scope.wholeSheet ||= wholeSheet;
|
|
26
|
+
if (range !== undefined)
|
|
27
|
+
scope.ranges.push(range);
|
|
28
|
+
scopes.set(key, scope);
|
|
29
|
+
};
|
|
30
|
+
for (const operation of operations) {
|
|
31
|
+
const payload = operation.payload;
|
|
32
|
+
const ref = payload.sheetId ?? payload.sheetName ?? (operation.type === 'create_sheet' ? payload.name : undefined);
|
|
33
|
+
if (operation.type === 'batch') {
|
|
34
|
+
for (const child of payload.operations ?? []) {
|
|
35
|
+
const nested = collectDiffScopes([child]);
|
|
36
|
+
for (const scope of nested.values()) {
|
|
37
|
+
const target = scopes.get(scope.ref) ?? { ref: scope.ref, wholeSheet: false, ranges: [] };
|
|
38
|
+
target.wholeSheet ||= scope.wholeSheet;
|
|
39
|
+
target.ranges.push(...scope.ranges);
|
|
40
|
+
scopes.set(scope.ref, target);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (operation.type === 'update_cells') {
|
|
46
|
+
const cells = payload.cells ?? [];
|
|
47
|
+
if (cells.length > 0) {
|
|
48
|
+
add(ref, {
|
|
49
|
+
startRow: Math.min(...cells.map(cell => cell.row)),
|
|
50
|
+
startColumn: Math.min(...cells.map(cell => cell.column)),
|
|
51
|
+
endRow: Math.max(...cells.map(cell => cell.row)),
|
|
52
|
+
endColumn: Math.max(...cells.map(cell => cell.column)),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (operation.type === 'clear_range' || operation.type === 'delete_cells' || operation.type === 'format_cells'
|
|
58
|
+
|| operation.type === 'merge_cells' || operation.type === 'unmerge_cells' || operation.type === 'sort_range'
|
|
59
|
+
|| operation.type === 'set_hyperlink' || operation.type === 'remove_hyperlink' || operation.type === 'remove_duplicates') {
|
|
60
|
+
add(ref, payload.range ?? payload.merge);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (operation.type === 'set_column_width' || operation.type === 'set_row_height' || operation.type === 'freeze_panes'
|
|
64
|
+
|| operation.type === 'append_rows' || operation.type === 'insert_rows' || operation.type === 'delete_rows'
|
|
65
|
+
|| operation.type === 'insert_columns' || operation.type === 'delete_columns') {
|
|
66
|
+
add(ref, undefined, true);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (operation.type === 'create_sheet' || operation.type === 'delete_sheet' || operation.type === 'rename_sheet')
|
|
70
|
+
add(ref, undefined, true);
|
|
71
|
+
}
|
|
72
|
+
return scopes;
|
|
73
|
+
}
|
|
74
|
+
/** No value at either end: `toCellValue` answers `null` for a valueless cell. */
|
|
75
|
+
function isBlankValue(value) {
|
|
76
|
+
return value === null || value === undefined || String(value) === '';
|
|
77
|
+
}
|
|
78
|
+
export function diffWorkbookSnapshots(before, after, scopes) {
|
|
79
|
+
const left = asWorkbookSnapshot(before);
|
|
80
|
+
const right = asWorkbookSnapshot(after);
|
|
81
|
+
const changes = [];
|
|
82
|
+
const sheetIds = new Set([...left.sheetOrder, ...right.sheetOrder]);
|
|
83
|
+
for (const sheetId of sheetIds) {
|
|
84
|
+
const beforeSheet = left.sheets[sheetId];
|
|
85
|
+
const afterSheet = right.sheets[sheetId];
|
|
86
|
+
if (beforeSheet === undefined || afterSheet === undefined) {
|
|
87
|
+
changes.push({ kind: 'sheet', sheetId, sheetName: afterSheet?.name ?? beforeSheet?.name, description: afterSheet === undefined ? '删除 sheet' : '新增 sheet' });
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const scope = scopes === undefined || scopes.size === 0
|
|
91
|
+
? undefined
|
|
92
|
+
: [...scopes.values()].find(candidate => candidate.ref === sheetId || candidate.ref === beforeSheet.name || candidate.ref === afterSheet.name);
|
|
93
|
+
if (scopes !== undefined && scopes.size > 0 && scope === undefined)
|
|
94
|
+
continue;
|
|
95
|
+
if (beforeSheet.name !== afterSheet.name)
|
|
96
|
+
changes.push({ kind: 'sheet', sheetId, sheetName: afterSheet.name, before: beforeSheet.name, after: afterSheet.name, description: '重命名 sheet' });
|
|
97
|
+
for (const key of ['mergeData', 'freeze', 'columnData', 'rowData']) {
|
|
98
|
+
if (JSON.stringify(beforeSheet[key]) !== JSON.stringify(afterSheet[key])) {
|
|
99
|
+
changes.push({ kind: 'structure', sheetId, sheetName: afterSheet.name, before: beforeSheet[key], after: afterSheet[key], description: key === 'mergeData' ? '调整合并单元格' : key === 'freeze' ? '调整冻结窗格' : '调整行列尺寸' });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const rows = scope?.wholeSheet || scope === undefined
|
|
103
|
+
? new Set([...Object.keys(beforeSheet.cellData ?? {}), ...Object.keys(afterSheet.cellData ?? {})])
|
|
104
|
+
: new Set(scope.ranges.flatMap(range => {
|
|
105
|
+
const result = [];
|
|
106
|
+
for (let row = range.startRow; row <= range.endRow; row += 1)
|
|
107
|
+
result.push(String(row));
|
|
108
|
+
return result;
|
|
109
|
+
}));
|
|
110
|
+
for (const row of rows) {
|
|
111
|
+
const columns = scope?.wholeSheet || scope === undefined
|
|
112
|
+
? new Set([...Object.keys(beforeSheet.cellData?.[row] ?? {}), ...Object.keys(afterSheet.cellData?.[row] ?? {})])
|
|
113
|
+
: new Set(scope.ranges.flatMap(range => {
|
|
114
|
+
const result = [];
|
|
115
|
+
for (let column = range.startColumn; column <= range.endColumn; column += 1)
|
|
116
|
+
result.push(String(column));
|
|
117
|
+
return result;
|
|
118
|
+
}));
|
|
119
|
+
for (const column of columns) {
|
|
120
|
+
const beforeCell = beforeSheet.cellData?.[row]?.[column];
|
|
121
|
+
const afterCell = afterSheet.cellData?.[row]?.[column];
|
|
122
|
+
if (JSON.stringify(beforeCell) === JSON.stringify(afterCell))
|
|
123
|
+
continue;
|
|
124
|
+
const beforeValue = toCellValue(beforeCell);
|
|
125
|
+
const afterValue = toCellValue(afterCell);
|
|
126
|
+
// A cell with no value on either side that only gained a style is not a
|
|
127
|
+
// difference worth reporting. Formatting a range writes a style id into
|
|
128
|
+
// every cell it covers, empty ones included, and those entries used to
|
|
129
|
+
// dominate the count: 473 of them against 11 real edits, which made the
|
|
130
|
+
// comparison bar unreadable (GUI feedback 2026-09-19).
|
|
131
|
+
//
|
|
132
|
+
// `toCellValue` answers `null` for a valueless cell, so "blank" here has
|
|
133
|
+
// to mean null/empty and not just `undefined` — testing for `undefined`
|
|
134
|
+
// compiles, passes the unit tests, and silently changes nothing.
|
|
135
|
+
if (isBlankValue(beforeValue) && isBlankValue(afterValue))
|
|
136
|
+
continue;
|
|
137
|
+
// Same value, different cell object: the formatting moved, the content
|
|
138
|
+
// did not. Reported as 样式 rather than as a modification.
|
|
139
|
+
const styleOnly = beforeValue === afterValue;
|
|
140
|
+
changes.push({
|
|
141
|
+
kind: 'cell',
|
|
142
|
+
sheetId,
|
|
143
|
+
sheetName: afterSheet.name,
|
|
144
|
+
row: Number(row),
|
|
145
|
+
column: Number(column),
|
|
146
|
+
before: beforeValue,
|
|
147
|
+
after: afterValue,
|
|
148
|
+
...(styleOnly ? { styleOnly: true } : {}),
|
|
149
|
+
});
|
|
150
|
+
if (changes.length >= DIFF_CHANGE_LIMIT)
|
|
151
|
+
return changes;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return changes;
|
|
156
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { WorkbookHistoryVersion, WorkbookOperation } from '../types.ts';
|
|
2
|
+
export declare function operationVersionId(operation: WorkbookOperation): string;
|
|
3
|
+
export declare function historyVersionStatus(status: WorkbookOperation['versionStatus']): WorkbookHistoryVersion['status'];
|
|
4
|
+
/** Group physical revisions into the logical Version shown to users. A user
|
|
5
|
+
* save can fill one open Version over several requests; an Agent semantic
|
|
6
|
+
* batch is one Version with N replayable Steps. */
|
|
7
|
+
export declare function historyVersionsFromOperations(source: WorkbookOperation[], canReplay: (revision: number) => boolean, activeRevisions: Set<number>): WorkbookHistoryVersion[];
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Physical revisions → the logical Versions a person sees (docs/issue/0022 P2).
|
|
3
|
+
*
|
|
4
|
+
* One user save can fill a single open Version across several requests, and one
|
|
5
|
+
* Agent semantic batch is a Version with N replayable Steps. This is the only
|
|
6
|
+
* place that decides where one Version ends and the next begins, so the list,
|
|
7
|
+
* the comparison bar and the Agent's history tool cannot disagree about it.
|
|
8
|
+
*/
|
|
9
|
+
import { operationSummary, versionKindOfOperations, workbookOperationSteps } from "../contract/operations.js";
|
|
10
|
+
import { describeSessionSteps } from "./summary.js";
|
|
11
|
+
export function operationVersionId(operation) {
|
|
12
|
+
return operation.versionId ?? `revision:${operation.revision}`;
|
|
13
|
+
}
|
|
14
|
+
export function historyVersionStatus(status) {
|
|
15
|
+
return status === 'temporary' || status === undefined ? (status === 'temporary' ? 'open' : 'final') : status;
|
|
16
|
+
}
|
|
17
|
+
/** Group physical revisions into the logical Version shown to users. A user
|
|
18
|
+
* save can fill one open Version over several requests; an Agent semantic
|
|
19
|
+
* batch is one Version with N replayable Steps. */
|
|
20
|
+
export function historyVersionsFromOperations(source, canReplay, activeRevisions) {
|
|
21
|
+
const groups = new Map();
|
|
22
|
+
for (const operation of source.slice().sort((left, right) => left.revision - right.revision)) {
|
|
23
|
+
const id = operationVersionId(operation);
|
|
24
|
+
const group = groups.get(id);
|
|
25
|
+
if (group === undefined)
|
|
26
|
+
groups.set(id, [operation]);
|
|
27
|
+
else
|
|
28
|
+
group.push(operation);
|
|
29
|
+
}
|
|
30
|
+
const entries = [...groups.entries()];
|
|
31
|
+
const numberByVersionId = new Map(entries.map(([versionId], index) => [versionId, index + 1]));
|
|
32
|
+
/** Display number of the Version whose revision range covers `revision`. */
|
|
33
|
+
const numberAtRevision = (revision) => {
|
|
34
|
+
if (revision === undefined)
|
|
35
|
+
return undefined;
|
|
36
|
+
for (const [versionId, operations] of entries) {
|
|
37
|
+
const first = operations[0];
|
|
38
|
+
const last = operations.at(-1);
|
|
39
|
+
if (first === undefined || last === undefined)
|
|
40
|
+
continue;
|
|
41
|
+
if (revision >= first.revision && revision <= last.revision)
|
|
42
|
+
return numberByVersionId.get(versionId);
|
|
43
|
+
}
|
|
44
|
+
return undefined;
|
|
45
|
+
};
|
|
46
|
+
return entries.map(([versionId, operations], index) => {
|
|
47
|
+
const first = operations[0];
|
|
48
|
+
const last = operations.at(-1);
|
|
49
|
+
if (first === undefined || last === undefined)
|
|
50
|
+
throw new Error('历史版本缺少操作');
|
|
51
|
+
const steps = operations.flatMap(operation => workbookOperationSteps(operation));
|
|
52
|
+
const storedSummary = last.versionSummary ?? first.versionSummary ?? (operations.length === 1
|
|
53
|
+
? operationSummary(first)
|
|
54
|
+
: describeSessionSteps(steps));
|
|
55
|
+
const branch = operations.every(operation => activeRevisions.has(operation.revision)) ? 'main' : 'superseded';
|
|
56
|
+
const storedStatus = historyVersionStatus(last.versionStatus);
|
|
57
|
+
const intent = last.versionIntent ?? first.versionIntent;
|
|
58
|
+
const agentRunId = last.agentRunId ?? first.agentRunId;
|
|
59
|
+
const lineage = last.lineage ?? first.lineage;
|
|
60
|
+
const restoredFromRevision = last.restoredFromRevision ?? first.restoredFromRevision;
|
|
61
|
+
const restoredFromNumber = numberAtRevision(restoredFromRevision);
|
|
62
|
+
// A stored `恢复到 revision 48` predates the v3 wording rule; show the
|
|
63
|
+
// Version number instead of the internal revision (PRD v3 §7.3).
|
|
64
|
+
const summary = restoredFromNumber !== undefined && /^恢复到 revision \d+$/.test(storedSummary)
|
|
65
|
+
? `恢复到 V${restoredFromNumber}`
|
|
66
|
+
: storedSummary;
|
|
67
|
+
// v3 tier. Explicit when written; pre-v3 logs are derived from the facts
|
|
68
|
+
// that already separated a deliberate Version from editing-session churn.
|
|
69
|
+
const kind = versionKindOfOperations(operations);
|
|
70
|
+
return {
|
|
71
|
+
versionId,
|
|
72
|
+
// Creation order: a restore lands after every existing Version, so its
|
|
73
|
+
// number is always fresh and old numbers are never reused (PRD §3.2).
|
|
74
|
+
number: index + 1,
|
|
75
|
+
startRevision: first.revision,
|
|
76
|
+
revision: last.revision,
|
|
77
|
+
parentRevision: first.parentRevision ?? Math.max(0, first.revision - 1),
|
|
78
|
+
actor: first.actor,
|
|
79
|
+
timestamp: first.timestamp,
|
|
80
|
+
summary,
|
|
81
|
+
kind,
|
|
82
|
+
...(intent === undefined ? {} : { intent }),
|
|
83
|
+
...(agentRunId === undefined ? {} : { agentRunId }),
|
|
84
|
+
...(lineage === undefined ? {} : { lineage }),
|
|
85
|
+
...(restoredFromRevision === undefined ? {} : { restoredFromRevision }),
|
|
86
|
+
...(restoredFromNumber === undefined ? {} : { restoredFromNumber }),
|
|
87
|
+
stepCount: steps.length,
|
|
88
|
+
steps: steps.map((step, index) => ({ index, opId: step.opId, type: step.type, summary: operationSummary(step), ...(step.revision === undefined ? {} : { revision: step.revision }) })),
|
|
89
|
+
canPreview: canReplay(last.revision),
|
|
90
|
+
status: branch === 'superseded' ? 'superseded' : storedStatus,
|
|
91
|
+
branch,
|
|
92
|
+
};
|
|
93
|
+
});
|
|
94
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { VersionRecord } from '#contracts';
|
|
2
|
+
import type { WorkbookCheckpoint, WorkbookHistoryVersion, WorkbookOperation } from '../types.ts';
|
|
3
|
+
/** Follow logical parents from the current head. Versions skipped by a
|
|
4
|
+
* rollback remain in history but are no longer part of the active main line. */
|
|
5
|
+
export declare function activeHistoryRevisions(currentRevision: number, operations: Map<number, WorkbookOperation>): Set<number>;
|
|
6
|
+
export declare function canReplayHistoryRevision(targetRevision: number, currentRevision: number, checkpoints: number[], operations: Map<number, WorkbookOperation>): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Stable content hash for a Version (PRD §3.2): the checkpoint the Version
|
|
9
|
+
* starts from, plus the ordered opIds applied on top of it. It is a pure
|
|
10
|
+
* function of the Version's inputs, so the history list never has to
|
|
11
|
+
* materialise or copy a workbook snapshot into the browser (PRD §11).
|
|
12
|
+
*/
|
|
13
|
+
export declare function versionContentHash(version: WorkbookHistoryVersion, checkpointHash: (revision: number) => string): string;
|
|
14
|
+
/** Map the Data Sheet history shape onto the shared Version record (PRD §7.1). */
|
|
15
|
+
export declare function toVersionRecords(versions: WorkbookHistoryVersion[], workbookId: string, checkpointHash: (revision: number) => string): VersionRecord[];
|
|
16
|
+
/** Memoized checkpoint hash lookup: hashes each snapshot at most once. */
|
|
17
|
+
export declare function checkpointHasher(checkpoints: WorkbookCheckpoint[]): (revision: number) => string;
|