snapshot of https://github.com/dream-num/univer.git (dev, 2026-09-16; blobs > 131072B omitted)
Some checks failed
📦 Build / build (push) Has been cancelled
🧪 CI Test / test shard (1/4) (push) Has been cancelled
🧪 CI Test / test shard (2/4) (push) Has been cancelled
🧪 CI Test / test shard (3/4) (push) Has been cancelled
🧪 CI Test / test shard (4/4) (push) Has been cancelled
🧪 CI Test / test (push) Has been cancelled
🧪 CI Test / eslint (push) Has been cancelled
🧪 CI Test / typecheck (push) Has been cancelled
🔫 Sync to Pro / dispatch-sync (push) Has been cancelled
🔮 Sync Mirror / sync (push) Has been cancelled
Some checks failed
📦 Build / build (push) Has been cancelled
🧪 CI Test / test shard (1/4) (push) Has been cancelled
🧪 CI Test / test shard (2/4) (push) Has been cancelled
🧪 CI Test / test shard (3/4) (push) Has been cancelled
🧪 CI Test / test shard (4/4) (push) Has been cancelled
🧪 CI Test / test (push) Has been cancelled
🧪 CI Test / eslint (push) Has been cancelled
🧪 CI Test / typecheck (push) Has been cancelled
🔫 Sync to Pro / dispatch-sync (push) Has been cancelled
🔮 Sync Mirror / sync (push) Has been cancelled
This commit is contained in:
41
packages/sheets-note/README.md
Normal file
41
packages/sheets-note/README.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# @univerjs/sheets-note
|
||||
|
||||
[](https://npmjs.com/package/@univerjs/sheets-note)
|
||||
[](https://npmjs.com/package/@univerjs/sheets-note)
|
||||
[](https://npmjs.com/package/@univerjs/sheets-note)
|
||||
|
||||
`@univerjs/sheets-note` adds the core note model and commands for cell notes in Univer Sheets.
|
||||
|
||||
## Package Overview
|
||||
|
||||
| Package | UMD global | CSS | Locales | Facade entry |
|
||||
| --- | --- | :---: | :---: | :---: |
|
||||
| `@univerjs/sheets-note` | `UniverSheetsNote` | No | No | Yes |
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
pnpm add @univerjs/sheets-note
|
||||
# or
|
||||
npm install @univerjs/sheets-note
|
||||
```
|
||||
|
||||
Keep all `@univerjs/*` packages on the same version.
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { UniverSheetsNotePlugin } from '@univerjs/sheets-note';
|
||||
|
||||
univer.registerPlugin(UniverSheetsNotePlugin);
|
||||
```
|
||||
|
||||
## Integration Notes
|
||||
|
||||
Use this package with `@univerjs/sheets-note-ui` when users need note editing UI.
|
||||
|
||||
## Resources
|
||||
|
||||
- [Documentation](https://docs.univer.ai)
|
||||
- [NPM package](https://npmjs.com/package/@univerjs/sheets-note)
|
||||
- [GitHub repository](https://github.com/dream-num/univer)
|
||||
93
packages/sheets-note/package.json
Normal file
93
packages/sheets-note/package.json
Normal file
@@ -0,0 +1,93 @@
|
||||
{
|
||||
"name": "@univerjs/sheets-note",
|
||||
"version": "1.0.0-rc.0",
|
||||
"private": false,
|
||||
"description": "Cell note model and commands for Univer Sheets.",
|
||||
"author": "DreamNum Co., Ltd. <developer@univer.ai>",
|
||||
"license": "Apache-2.0",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/univer"
|
||||
},
|
||||
"homepage": "https://univer.ai",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dream-num/univer"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/dream-num/univer/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"univer",
|
||||
"sheets",
|
||||
"note",
|
||||
"annotation",
|
||||
"plugin"
|
||||
],
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./*": "./src/*",
|
||||
"./facade": "./src/facade/index.ts"
|
||||
},
|
||||
"main": "./src/index.ts",
|
||||
"types": "./lib/types/index.d.ts",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "./lib/es/index.js",
|
||||
"module": "./lib/es/index.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./lib/es/index.js",
|
||||
"require": "./lib/cjs/index.js",
|
||||
"types": "./lib/types/index.d.ts"
|
||||
},
|
||||
"./*": {
|
||||
"import": "./lib/es/*",
|
||||
"require": "./lib/cjs/*",
|
||||
"types": "./lib/types/index.d.ts"
|
||||
},
|
||||
"./facade": {
|
||||
"import": "./lib/es/facade.js",
|
||||
"require": "./lib/cjs/facade.js",
|
||||
"types": "./lib/types/facade/index.d.ts"
|
||||
},
|
||||
"./lib/facade": {
|
||||
"import": "./lib/es/facade.js",
|
||||
"require": "./lib/cjs/facade.js",
|
||||
"types": "./lib/types/facade/index.d.ts"
|
||||
},
|
||||
"./lib/*": "./lib/*"
|
||||
}
|
||||
},
|
||||
"directories": {
|
||||
"lib": "lib"
|
||||
},
|
||||
"files": [
|
||||
"lib"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"coverage": "vitest run --coverage",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"build:bundle": "univer-cli build",
|
||||
"build:types": "tsc -p tsconfig.node.json",
|
||||
"build": "pnpm run build:bundle && pnpm run build:types"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"rxjs": ">=7.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@univerjs/core": "workspace:*",
|
||||
"@univerjs/sheets": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@univerjs-infra/shared": "workspace:*",
|
||||
"@univerjs/engine-formula": "workspace:*",
|
||||
"@univerjs/engine-render": "workspace:*",
|
||||
"@univerjs/network": "workspace:*",
|
||||
"rxjs": "^7.8.2",
|
||||
"typescript": "^6.0.3",
|
||||
"vitest": "^5.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { IWorkbookData } from '@univerjs/core';
|
||||
import type { ISelectionWithStyle } from '@univerjs/sheets';
|
||||
import type { ISheetNote } from '../../../models/sheets-note.model';
|
||||
import {
|
||||
ICommandService,
|
||||
ILogService,
|
||||
Inject,
|
||||
Injector,
|
||||
IUndoRedoService,
|
||||
IUniverInstanceService,
|
||||
LocaleType,
|
||||
LogLevel,
|
||||
Plugin,
|
||||
RANGE_TYPE,
|
||||
touchDependencies,
|
||||
UndoCommand,
|
||||
Univer,
|
||||
UniverInstanceType,
|
||||
} from '@univerjs/core';
|
||||
import { SheetsSelectionsService } from '@univerjs/sheets';
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
import { SheetsNoteModel } from '../../../models/sheets-note.model';
|
||||
import { RemoveNoteMutation, ToggleNotePopupMutation, UpdateNoteMutation } from '../../mutations/note.mutation';
|
||||
import { SheetDeleteNoteCommand, SheetToggleNotePopupCommand, SheetUpdateNoteCommand } from '../note.command';
|
||||
|
||||
const unitId = 'test';
|
||||
const subUnitId = 'sheet1';
|
||||
|
||||
const TEST_WORKBOOK_DATA: IWorkbookData = {
|
||||
id: unitId,
|
||||
sheetOrder: [subUnitId],
|
||||
name: '',
|
||||
appVersion: '0.15.4',
|
||||
locale: LocaleType.EN_US,
|
||||
styles: {},
|
||||
sheets: {
|
||||
[subUnitId]: {
|
||||
id: subUnitId,
|
||||
name: 'Sheet1',
|
||||
rowCount: 10,
|
||||
columnCount: 10,
|
||||
cellData: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function createCommandTestBed() {
|
||||
const univer = new Univer();
|
||||
const injector = univer.__getInjector();
|
||||
const get = injector.get.bind(injector);
|
||||
|
||||
class TestPlugin extends Plugin {
|
||||
static override pluginName = 'test-plugin';
|
||||
static override type = UniverInstanceType.UNIVER_SHEET;
|
||||
|
||||
constructor(
|
||||
_config: undefined,
|
||||
@Inject(Injector) override readonly _injector: Injector
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
override onStarting(): void {
|
||||
// Ensure undo/redo exists and registers commands.
|
||||
this._injector.get(IUndoRedoService);
|
||||
|
||||
this._injector.add([SheetsSelectionsService]);
|
||||
this._injector.add([SheetsNoteModel]);
|
||||
|
||||
touchDependencies(this._injector, [
|
||||
[SheetsSelectionsService],
|
||||
[SheetsNoteModel],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
univer.registerPlugin(TestPlugin);
|
||||
univer.createUnit(UniverInstanceType.UNIVER_SHEET, TEST_WORKBOOK_DATA);
|
||||
get(IUniverInstanceService).focusUnit(unitId);
|
||||
|
||||
const logService = get(ILogService);
|
||||
logService.setLogLevel(LogLevel.SILENT);
|
||||
|
||||
const commandService = get(ICommandService);
|
||||
commandService.registerCommand(UpdateNoteMutation);
|
||||
commandService.registerCommand(RemoveNoteMutation);
|
||||
commandService.registerCommand(ToggleNotePopupMutation);
|
||||
commandService.registerCommand(SheetUpdateNoteCommand);
|
||||
commandService.registerCommand(SheetDeleteNoteCommand);
|
||||
commandService.registerCommand(SheetToggleNotePopupCommand);
|
||||
|
||||
return {
|
||||
univer,
|
||||
get,
|
||||
injector,
|
||||
commandService,
|
||||
};
|
||||
}
|
||||
|
||||
function setSingleCellSelection(get: Injector['get'], row: number, col: number) {
|
||||
const selections = get(SheetsSelectionsService);
|
||||
const selection: ISelectionWithStyle = {
|
||||
range: {
|
||||
startRow: row,
|
||||
endRow: row + 1,
|
||||
startColumn: col,
|
||||
endColumn: col + 1,
|
||||
rangeType: RANGE_TYPE.NORMAL,
|
||||
},
|
||||
primary: {
|
||||
startRow: row,
|
||||
endRow: row + 1,
|
||||
startColumn: col,
|
||||
endColumn: col + 1,
|
||||
actualRow: row,
|
||||
actualColumn: col,
|
||||
isMerged: false,
|
||||
isMergedMainCell: false,
|
||||
},
|
||||
style: null,
|
||||
};
|
||||
|
||||
selections.setSelections([selection]);
|
||||
}
|
||||
|
||||
describe('sheets-note commands', () => {
|
||||
let univer: Univer;
|
||||
let get: Injector['get'];
|
||||
let commandService: ICommandService;
|
||||
|
||||
beforeEach(() => {
|
||||
const testBed = createCommandTestBed();
|
||||
univer = testBed.univer;
|
||||
get = testBed.get;
|
||||
commandService = testBed.commandService;
|
||||
});
|
||||
|
||||
afterEach(() => univer.dispose());
|
||||
|
||||
it('SheetUpdateNoteCommand pushes undo correctly (new note -> undo removes)', async () => {
|
||||
const note: ISheetNote = { id: 'n1', row: 1, col: 2, width: 160, height: 72, note: 'hello' };
|
||||
|
||||
const res = commandService.syncExecuteCommand(SheetUpdateNoteCommand.id, {
|
||||
unitId,
|
||||
sheetId: subUnitId,
|
||||
row: 1,
|
||||
col: 2,
|
||||
note,
|
||||
});
|
||||
expect(res).toBeTruthy();
|
||||
|
||||
const model = get(SheetsNoteModel);
|
||||
expect(model.getNote(unitId, subUnitId, { row: 1, col: 2 })?.note).toBe('hello');
|
||||
|
||||
// Undo should remove the note.
|
||||
expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy();
|
||||
expect(model.getNote(unitId, subUnitId, { row: 1, col: 2 })).toBeUndefined();
|
||||
});
|
||||
|
||||
it('SheetUpdateNoteCommand pushes undo correctly (existing note -> undo restores)', async () => {
|
||||
const model = get(SheetsNoteModel);
|
||||
model.updateNote(unitId, subUnitId, 1, 2, { id: 'n2', row: 1, col: 2, width: 160, height: 72, note: 'old' });
|
||||
|
||||
const note: ISheetNote = { id: 'n2', row: 1, col: 2, width: 160, height: 72, note: 'new' };
|
||||
const res = commandService.syncExecuteCommand(SheetUpdateNoteCommand.id, {
|
||||
unitId,
|
||||
sheetId: subUnitId,
|
||||
row: 1,
|
||||
col: 2,
|
||||
note,
|
||||
});
|
||||
expect(res).toBeTruthy();
|
||||
expect(model.getNote(unitId, subUnitId, { row: 1, col: 2 })?.note).toBe('new');
|
||||
|
||||
expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy();
|
||||
expect(model.getNote(unitId, subUnitId, { row: 1, col: 2 })?.note).toBe('old');
|
||||
});
|
||||
|
||||
it('SheetDeleteNoteCommand removes note at current selection and supports undo', async () => {
|
||||
const model = get(SheetsNoteModel);
|
||||
model.updateNote(unitId, subUnitId, 2, 3, { id: 'n3', row: 2, col: 3, width: 160, height: 72, note: 'to delete' });
|
||||
setSingleCellSelection(get, 2, 3);
|
||||
|
||||
const res = commandService.syncExecuteCommand(SheetDeleteNoteCommand.id);
|
||||
expect(res).toBeTruthy();
|
||||
expect(model.getNote(unitId, subUnitId, { row: 2, col: 3 })).toBeUndefined();
|
||||
|
||||
expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy();
|
||||
expect(model.getNote(unitId, subUnitId, { row: 2, col: 3 })?.note).toBe('to delete');
|
||||
});
|
||||
|
||||
it('SheetToggleNotePopupCommand toggles show state at current selection and supports undo', async () => {
|
||||
const model = get(SheetsNoteModel);
|
||||
model.updateNote(unitId, subUnitId, 4, 4, { id: 'n4', row: 4, col: 4, width: 160, height: 72, note: 'toggle', show: false });
|
||||
setSingleCellSelection(get, 4, 4);
|
||||
|
||||
const res = commandService.syncExecuteCommand(SheetToggleNotePopupCommand.id);
|
||||
expect(res).toBeTruthy();
|
||||
expect(model.getNote(unitId, subUnitId, { row: 4, col: 4 })?.show).toBe(true);
|
||||
|
||||
expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy();
|
||||
expect(model.getNote(unitId, subUnitId, { row: 4, col: 4 })?.show).toBe(false);
|
||||
});
|
||||
|
||||
// Avoid directly asserting pure exports without business value.
|
||||
});
|
||||
192
packages/sheets-note/src/commands/commands/note.command.ts
Normal file
192
packages/sheets-note/src/commands/commands/note.command.ts
Normal file
@@ -0,0 +1,192 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { ICommand, IMutationInfo } from '@univerjs/core';
|
||||
import type { IRemoveNoteMutationParams, IToggleNotePopupMutationParams, IUpdateNoteMutationParams } from '../mutations/note.mutation';
|
||||
import { CommandType, ICommandService, IUndoRedoService, IUniverInstanceService } from '@univerjs/core';
|
||||
import { getSheetCommandTarget, SheetsSelectionsService } from '@univerjs/sheets';
|
||||
import { SheetsNoteModel } from '../../models/sheets-note.model';
|
||||
import { RemoveNoteMutation, ToggleNotePopupMutation, UpdateNoteMutation } from '../mutations/note.mutation';
|
||||
|
||||
export const SheetDeleteNoteCommand: ICommand = {
|
||||
id: 'sheet.command.delete-note',
|
||||
type: CommandType.COMMAND,
|
||||
handler: (accessor) => {
|
||||
const target = getSheetCommandTarget(accessor.get(IUniverInstanceService));
|
||||
if (!target) return false;
|
||||
|
||||
const sheetsSelectionsService = accessor.get(SheetsSelectionsService);
|
||||
const selection = sheetsSelectionsService.getCurrentLastSelection();
|
||||
if (!selection?.primary) return false;
|
||||
|
||||
const sheetsNoteModel = accessor.get(SheetsNoteModel);
|
||||
const { unitId, subUnitId } = target;
|
||||
const { actualColumn, actualRow } = selection.primary;
|
||||
const note = sheetsNoteModel.getNote(unitId, subUnitId, { row: actualRow, col: actualColumn });
|
||||
if (!note) return false;
|
||||
|
||||
const commandService = accessor.get(ICommandService);
|
||||
const undoRedoService = accessor.get(IUndoRedoService);
|
||||
|
||||
const redoMutation: IMutationInfo<IRemoveNoteMutationParams> = {
|
||||
id: RemoveNoteMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
sheetId: subUnitId,
|
||||
noteId: note.id,
|
||||
},
|
||||
};
|
||||
const undoMutation: IMutationInfo<IUpdateNoteMutationParams> = {
|
||||
id: UpdateNoteMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
sheetId: subUnitId,
|
||||
row: actualRow,
|
||||
col: actualColumn,
|
||||
note: { ...note },
|
||||
},
|
||||
};
|
||||
|
||||
const result = commandService.syncExecuteCommand(redoMutation.id, redoMutation.params);
|
||||
if (result) {
|
||||
undoRedoService.pushUndoRedo({
|
||||
unitID: unitId,
|
||||
redoMutations: [redoMutation],
|
||||
undoMutations: [undoMutation],
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
};
|
||||
|
||||
export const SheetToggleNotePopupCommand: ICommand = {
|
||||
id: 'sheet.command.toggle-note-popup',
|
||||
type: CommandType.COMMAND,
|
||||
handler: (accessor) => {
|
||||
const target = getSheetCommandTarget(accessor.get(IUniverInstanceService));
|
||||
if (!target) return false;
|
||||
|
||||
const sheetsSelectionsService = accessor.get(SheetsSelectionsService);
|
||||
const selection = sheetsSelectionsService.getCurrentLastSelection();
|
||||
if (!selection?.primary) return false;
|
||||
|
||||
const sheetsNoteModel = accessor.get(SheetsNoteModel);
|
||||
const { unitId, subUnitId } = target;
|
||||
const { actualColumn, actualRow } = selection.primary;
|
||||
const note = sheetsNoteModel.getNote(unitId, subUnitId, { row: actualRow, col: actualColumn });
|
||||
if (!note) return false;
|
||||
|
||||
const commandService = accessor.get(ICommandService);
|
||||
const undoRedoService = accessor.get(IUndoRedoService);
|
||||
|
||||
const redoMutation: IMutationInfo<IToggleNotePopupMutationParams> = {
|
||||
id: ToggleNotePopupMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
sheetId: subUnitId,
|
||||
noteId: note.id,
|
||||
},
|
||||
};
|
||||
const undoMutation: IMutationInfo<IToggleNotePopupMutationParams> = {
|
||||
id: ToggleNotePopupMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
sheetId: subUnitId,
|
||||
noteId: note.id,
|
||||
},
|
||||
};
|
||||
|
||||
const result = commandService.syncExecuteCommand(redoMutation.id, redoMutation.params);
|
||||
if (result) {
|
||||
undoRedoService.pushUndoRedo({
|
||||
unitID: unitId,
|
||||
redoMutations: [redoMutation],
|
||||
undoMutations: [undoMutation],
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
};
|
||||
|
||||
export const SheetUpdateNoteCommand: ICommand<IUpdateNoteMutationParams> = {
|
||||
id: 'sheet.command.update-note',
|
||||
type: CommandType.COMMAND,
|
||||
handler: (accessor, params: IUpdateNoteMutationParams) => {
|
||||
const target = getSheetCommandTarget(accessor.get(IUniverInstanceService), params);
|
||||
if (!target) return false;
|
||||
|
||||
const commandService = accessor.get(ICommandService);
|
||||
const undoRedoService = accessor.get(IUndoRedoService);
|
||||
const sheetsNoteModel = accessor.get(SheetsNoteModel);
|
||||
|
||||
const { unitId, subUnitId } = target;
|
||||
const { row, col, note } = params;
|
||||
const oldNote = sheetsNoteModel.getNote(unitId, subUnitId, { noteId: note.id, row, col });
|
||||
|
||||
const redoMutation: IMutationInfo<IUpdateNoteMutationParams> = {
|
||||
id: UpdateNoteMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
sheetId: subUnitId,
|
||||
row,
|
||||
col,
|
||||
note,
|
||||
},
|
||||
};
|
||||
const undoMutations: IMutationInfo[] = [];
|
||||
|
||||
if (oldNote) {
|
||||
const undoMutation: IMutationInfo<IUpdateNoteMutationParams> = {
|
||||
id: UpdateNoteMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
sheetId: subUnitId,
|
||||
row,
|
||||
col,
|
||||
note: { ...oldNote },
|
||||
},
|
||||
};
|
||||
undoMutations.push(undoMutation);
|
||||
} else {
|
||||
const undoMutation: IMutationInfo<IRemoveNoteMutationParams> = {
|
||||
id: RemoveNoteMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
sheetId: subUnitId,
|
||||
row,
|
||||
col,
|
||||
},
|
||||
};
|
||||
undoMutations.push(undoMutation);
|
||||
}
|
||||
|
||||
const result = commandService.syncExecuteCommand(redoMutation.id, redoMutation.params);
|
||||
if (result) {
|
||||
undoRedoService.pushUndoRedo({
|
||||
unitID: unitId,
|
||||
redoMutations: [redoMutation],
|
||||
undoMutations,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
};
|
||||
104
packages/sheets-note/src/commands/mutations/note.mutation.ts
Normal file
104
packages/sheets-note/src/commands/mutations/note.mutation.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { IAccessor, IMutation } from '@univerjs/core';
|
||||
import type { ISheetNote } from '../../models/sheets-note.model';
|
||||
import { CommandType } from '@univerjs/core';
|
||||
import { SheetsNoteModel } from '../../models/sheets-note.model';
|
||||
|
||||
export interface IUpdateNoteMutationParams {
|
||||
unitId: string;
|
||||
sheetId: string;
|
||||
row: number;
|
||||
col: number;
|
||||
note: ISheetNote;
|
||||
silent?: boolean;
|
||||
}
|
||||
|
||||
export const UpdateNoteMutation: IMutation<IUpdateNoteMutationParams> = {
|
||||
id: 'sheet.mutation.update-note',
|
||||
type: CommandType.MUTATION,
|
||||
handler: (accessor: IAccessor, params: IUpdateNoteMutationParams) => {
|
||||
const { unitId, sheetId, row, col, note, silent } = params;
|
||||
const sheetsNoteModel = accessor.get(SheetsNoteModel);
|
||||
sheetsNoteModel.updateNote(unitId, sheetId, row, col, note, silent);
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
export interface IRemoveNoteMutationParams {
|
||||
unitId: string;
|
||||
sheetId: string;
|
||||
noteId?: string;
|
||||
row?: number;
|
||||
col?: number;
|
||||
silent?: boolean;
|
||||
}
|
||||
|
||||
export const RemoveNoteMutation: IMutation<IRemoveNoteMutationParams> = {
|
||||
id: 'sheet.mutation.remove-note',
|
||||
type: CommandType.MUTATION,
|
||||
handler: (accessor: IAccessor, params: IRemoveNoteMutationParams) => {
|
||||
const { unitId, sheetId, noteId, row, col, silent } = params;
|
||||
const sheetsNoteModel = accessor.get(SheetsNoteModel);
|
||||
sheetsNoteModel.removeNote(unitId, sheetId, { noteId, row, col, silent });
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
export interface IToggleNotePopupMutationParams {
|
||||
unitId: string;
|
||||
sheetId: string;
|
||||
noteId?: string;
|
||||
row?: number;
|
||||
col?: number;
|
||||
silent?: boolean;
|
||||
}
|
||||
|
||||
export const ToggleNotePopupMutation: IMutation<IToggleNotePopupMutationParams> = {
|
||||
id: 'sheet.mutation.toggle-note-popup',
|
||||
type: CommandType.MUTATION,
|
||||
handler: (accessor: IAccessor, params: IToggleNotePopupMutationParams) => {
|
||||
const { unitId, sheetId, noteId, row, col, silent } = params;
|
||||
const sheetsNoteModel = accessor.get(SheetsNoteModel);
|
||||
sheetsNoteModel.toggleNotePopup(unitId, sheetId, { noteId, row, col, silent });
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
export interface IUpdateNotePositionMutationParams {
|
||||
unitId: string;
|
||||
sheetId: string;
|
||||
noteId?: string;
|
||||
row?: number;
|
||||
col?: number;
|
||||
newPosition: {
|
||||
row: number;
|
||||
col: number;
|
||||
};
|
||||
silent?: boolean;
|
||||
}
|
||||
|
||||
export const UpdateNotePositionMutation: IMutation<IUpdateNotePositionMutationParams> = {
|
||||
id: 'sheet.mutation.update-note-position',
|
||||
type: CommandType.MUTATION,
|
||||
handler: (accessor: IAccessor, params: IUpdateNotePositionMutationParams) => {
|
||||
const { unitId, sheetId, noteId, row, col, newPosition, silent } = params;
|
||||
const sheetsNoteModel = accessor.get(SheetsNoteModel);
|
||||
sheetsNoteModel.updateNotePosition(unitId, sheetId, { noteId, row, col, newRow: newPosition.row, newCol: newPosition.col, silent });
|
||||
return true;
|
||||
},
|
||||
};
|
||||
23
packages/sheets-note/src/config/config.ts
Normal file
23
packages/sheets-note/src/config/config.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export const SHEETS_NOTE_PLUGIN_CONFIG_KEY = 'sheets-note.config';
|
||||
|
||||
export const configSymbol = Symbol(SHEETS_NOTE_PLUGIN_CONFIG_KEY);
|
||||
|
||||
export interface IUniverSheetsNoteConfig {}
|
||||
|
||||
export const defaultPluginConfig: IUniverSheetsNoteConfig = {};
|
||||
17
packages/sheets-note/src/const.ts
Normal file
17
packages/sheets-note/src/const.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export const PLUGIN_NAME = 'SHEET_NOTE_PLUGIN';
|
||||
@@ -0,0 +1,257 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Dependency, IWorkbookData, Nullable, Workbook } from '@univerjs/core';
|
||||
import type { IInsertColCommandParams, IInsertRowCommandParams, IRemoveColByRangeCommandParams, IRemoveRowByRangeCommandParams } from '@univerjs/sheets';
|
||||
import type { ISheetNote } from '../../models/sheets-note.model';
|
||||
import { Direction, ICommandService, ILogService, Inject, Injector, IUndoRedoService, IUniverInstanceService, LocaleType, LogLevel, Plugin, touchDependencies, UndoCommand, Univer, UniverInstanceType } from '@univerjs/core';
|
||||
import {
|
||||
InsertColByRangeCommand,
|
||||
InsertColMutation,
|
||||
InsertRowByRangeCommand,
|
||||
InsertRowMutation,
|
||||
RefRangeService,
|
||||
RemoveColByRangeCommand,
|
||||
RemoveColMutation,
|
||||
RemoveRowByRangeCommand,
|
||||
RemoveRowMutation,
|
||||
SetRangeValuesMutation,
|
||||
SetSelectionsOperation,
|
||||
SheetInterceptorService,
|
||||
SheetsSelectionsService,
|
||||
} from '@univerjs/sheets';
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
import { SheetsNoteModel } from '../../models/sheets-note.model';
|
||||
import { SheetsNoteRefRangeController } from '../sheets-note-ref-range.controller';
|
||||
import { SheetsNoteResourceController } from '../sheets-note-resource.controller';
|
||||
import { SheetsNoteController } from '../sheets.note.controller';
|
||||
|
||||
const unitId = 'test';
|
||||
const subUnitId = 'sheet1';
|
||||
const TEST_WORKBOOK_DATA: IWorkbookData = {
|
||||
id: unitId,
|
||||
sheetOrder: [subUnitId],
|
||||
name: '',
|
||||
appVersion: '0.15.4',
|
||||
locale: LocaleType.EN_US,
|
||||
styles: {},
|
||||
sheets: {
|
||||
[subUnitId]: {
|
||||
id: subUnitId,
|
||||
name: 'Sheet1',
|
||||
rowCount: 10,
|
||||
columnCount: 10,
|
||||
cellData: {},
|
||||
},
|
||||
},
|
||||
resources: [
|
||||
{
|
||||
name: 'SHEET_NOTE_PLUGIN',
|
||||
data: '{"sheet1":{"1":{"1":{"width":160,"height":72,"note":"1","row":1,"col":1,"id":"cwvCsB"}},"2":{"2":{"width":160,"height":72,"note":"2","row":2,"col":2,"id":"URAqOO"}},"3":{"3":{"width":160,"height":72,"note":"3","row":3,"col":3,"id":"tCk4Qd"}},"4":{"4":{"width":160,"height":72,"note":"4","row":4,"col":4,"id":"nIasaz"}}}}',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export function createRefRangeTestBed() {
|
||||
const univer = new Univer();
|
||||
const injector = univer.__getInjector();
|
||||
const get = injector.get.bind(injector);
|
||||
|
||||
class TestPlugin extends Plugin {
|
||||
static override pluginName = 'test-plugin';
|
||||
static override type = UniverInstanceType.UNIVER_SHEET;
|
||||
|
||||
constructor(
|
||||
_config: undefined,
|
||||
@Inject(Injector) override readonly _injector: Injector
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
override onStarting(): void {
|
||||
this._injector.get(IUndoRedoService);
|
||||
|
||||
([
|
||||
[SheetInterceptorService],
|
||||
[RefRangeService],
|
||||
[SheetsSelectionsService],
|
||||
[SheetsNoteModel],
|
||||
[SheetsNoteController],
|
||||
[SheetsNoteResourceController],
|
||||
[SheetsNoteRefRangeController],
|
||||
] as Dependency[]).forEach((d) => this._injector.add(d));
|
||||
|
||||
touchDependencies(this._injector, [
|
||||
[SheetsNoteModel],
|
||||
[SheetsNoteController],
|
||||
[SheetsNoteResourceController],
|
||||
]);
|
||||
}
|
||||
|
||||
override onReady(): void {
|
||||
touchDependencies(this._injector, [
|
||||
[SheetsNoteRefRangeController],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
univer.registerPlugin(TestPlugin);
|
||||
const sheet = univer.createUnit<IWorkbookData, Workbook>(UniverInstanceType.UNIVER_SHEET, TEST_WORKBOOK_DATA);
|
||||
|
||||
const univerInstanceService = get(IUniverInstanceService);
|
||||
univerInstanceService.focusUnit('test');
|
||||
|
||||
const logService = get(ILogService);
|
||||
logService.setLogLevel(LogLevel.SILENT);
|
||||
|
||||
return {
|
||||
univer,
|
||||
get,
|
||||
sheet,
|
||||
};
|
||||
}
|
||||
|
||||
describe('test note ref range controller', () => {
|
||||
let univer: Univer;
|
||||
let get: Injector['get'];
|
||||
let commandService: ICommandService;
|
||||
let sheetsNoteModel: SheetsNoteModel;
|
||||
let getNoteByPosition: (row: number, col: number) => Nullable<ISheetNote>;
|
||||
|
||||
beforeEach(() => {
|
||||
const testBed = createRefRangeTestBed();
|
||||
univer = testBed.univer;
|
||||
get = testBed.get;
|
||||
|
||||
commandService = get(ICommandService);
|
||||
commandService.registerCommand(InsertRowByRangeCommand);
|
||||
commandService.registerCommand(InsertColByRangeCommand);
|
||||
commandService.registerCommand(RemoveRowByRangeCommand);
|
||||
commandService.registerCommand(RemoveColByRangeCommand);
|
||||
commandService.registerCommand(InsertRowMutation);
|
||||
commandService.registerCommand(InsertColMutation);
|
||||
commandService.registerCommand(RemoveRowMutation);
|
||||
commandService.registerCommand(RemoveColMutation);
|
||||
commandService.registerCommand(SetRangeValuesMutation);
|
||||
commandService.registerCommand(SetSelectionsOperation);
|
||||
|
||||
sheetsNoteModel = get(SheetsNoteModel);
|
||||
|
||||
getNoteByPosition = (row: number, col: number) => sheetsNoteModel.getNote(unitId, subUnitId, { row, col });
|
||||
});
|
||||
|
||||
afterEach(() => univer.dispose());
|
||||
|
||||
describe('test note ref range change', () => {
|
||||
it('test D4 cell note when insert row', async () => {
|
||||
const D4CellNote = {
|
||||
width: 160,
|
||||
height: 72,
|
||||
note: '3',
|
||||
row: 3,
|
||||
col: 3,
|
||||
id: 'tCk4Qd',
|
||||
};
|
||||
|
||||
const params: IInsertRowCommandParams = {
|
||||
unitId,
|
||||
subUnitId,
|
||||
range: { startRow: 3, endRow: 3, startColumn: 0, endColumn: 9 },
|
||||
direction: Direction.DOWN,
|
||||
};
|
||||
expect(commandService.syncExecuteCommand(InsertRowByRangeCommand.id, params)).toBeTruthy();
|
||||
expect(getNoteByPosition(3, 3)).toBeUndefined();
|
||||
expect(getNoteByPosition(4, 3)).toStrictEqual({ ...D4CellNote, row: 4, col: 3 });
|
||||
|
||||
expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy();
|
||||
expect(getNoteByPosition(3, 3)).toStrictEqual(D4CellNote);
|
||||
expect(getNoteByPosition(4, 3)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('test D4 cell note when insert column', async () => {
|
||||
const D4CellNote = {
|
||||
width: 160,
|
||||
height: 72,
|
||||
note: '3',
|
||||
row: 3,
|
||||
col: 3,
|
||||
id: 'tCk4Qd',
|
||||
};
|
||||
|
||||
const params: IInsertColCommandParams = {
|
||||
unitId,
|
||||
subUnitId,
|
||||
range: { startRow: 0, endRow: 9, startColumn: 3, endColumn: 3 },
|
||||
direction: Direction.RIGHT,
|
||||
};
|
||||
expect(commandService.syncExecuteCommand(InsertColByRangeCommand.id, params)).toBeTruthy();
|
||||
expect(getNoteByPosition(3, 3)).toBeUndefined();
|
||||
expect(getNoteByPosition(3, 4)).toStrictEqual({ ...D4CellNote, row: 3, col: 4 });
|
||||
|
||||
expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy();
|
||||
expect(getNoteByPosition(3, 3)).toStrictEqual(D4CellNote);
|
||||
expect(getNoteByPosition(3, 4)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('test D4 cell note when remove row', async () => {
|
||||
const D4CellNote = {
|
||||
width: 160,
|
||||
height: 72,
|
||||
note: '3',
|
||||
row: 3,
|
||||
col: 3,
|
||||
id: 'tCk4Qd',
|
||||
};
|
||||
|
||||
const params: IRemoveRowByRangeCommandParams = {
|
||||
unitId,
|
||||
subUnitId,
|
||||
range: { startRow: 2, endRow: 2, startColumn: 0, endColumn: 9 },
|
||||
};
|
||||
expect(commandService.syncExecuteCommand(RemoveRowByRangeCommand.id, params)).toBeTruthy();
|
||||
expect(getNoteByPosition(3, 3)).toBeUndefined();
|
||||
expect(getNoteByPosition(2, 3)).toStrictEqual({ ...D4CellNote, row: 2, col: 3 });
|
||||
|
||||
expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy();
|
||||
expect(getNoteByPosition(3, 3)).toStrictEqual(D4CellNote);
|
||||
expect(getNoteByPosition(2, 3)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('test D4 cell note when remove column', async () => {
|
||||
const D4CellNote = {
|
||||
width: 160,
|
||||
height: 72,
|
||||
note: '3',
|
||||
row: 3,
|
||||
col: 3,
|
||||
id: 'tCk4Qd',
|
||||
};
|
||||
|
||||
const params: IRemoveColByRangeCommandParams = {
|
||||
unitId,
|
||||
subUnitId,
|
||||
range: { startRow: 0, endRow: 9, startColumn: 2, endColumn: 2 },
|
||||
};
|
||||
expect(commandService.syncExecuteCommand(RemoveColByRangeCommand.id, params)).toBeTruthy();
|
||||
expect(getNoteByPosition(3, 3)).toBeUndefined();
|
||||
expect(getNoteByPosition(3, 2)).toStrictEqual({ ...D4CellNote, row: 3, col: 2 });
|
||||
|
||||
expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy();
|
||||
expect(getNoteByPosition(3, 3)).toStrictEqual(D4CellNote);
|
||||
expect(getNoteByPosition(3, 2)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,258 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Dependency, IWorkbookData, Workbook } from '@univerjs/core';
|
||||
import type { ICopySheetCommandParams, IRemoveSheetCommandParams } from '@univerjs/sheets';
|
||||
import type { ISheetNote } from '../../models/sheets-note.model';
|
||||
import {
|
||||
ICommandService,
|
||||
ILogService,
|
||||
Inject,
|
||||
Injector,
|
||||
IResourceManagerService,
|
||||
IUndoRedoService,
|
||||
IUniverInstanceService,
|
||||
LocaleService,
|
||||
LocaleType,
|
||||
LogLevel,
|
||||
Plugin,
|
||||
touchDependencies,
|
||||
UndoCommand,
|
||||
Univer,
|
||||
UniverInstanceType,
|
||||
} from '@univerjs/core';
|
||||
import {
|
||||
CopySheetCommand,
|
||||
CopyWorksheetEndMutation,
|
||||
InsertColByRangeCommand,
|
||||
InsertColMutation,
|
||||
InsertRowByRangeCommand,
|
||||
InsertRowMutation,
|
||||
InsertSheetMutation,
|
||||
RemoveColByRangeCommand,
|
||||
RemoveColMutation,
|
||||
RemoveRowByRangeCommand,
|
||||
RemoveRowMutation,
|
||||
RemoveSheetCommand,
|
||||
RemoveSheetMutation,
|
||||
SetRangeValuesMutation,
|
||||
SetSelectionsOperation,
|
||||
SetWorksheetActivateCommand,
|
||||
SetWorksheetActiveOperation,
|
||||
SheetInterceptorService,
|
||||
SheetLazyExecuteScheduleService,
|
||||
SheetsSelectionsService,
|
||||
} from '@univerjs/sheets';
|
||||
import enUS from '@univerjs/sheets/locale/en-US';
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
// Note mutations are registered by SheetsNoteController.
|
||||
import { PLUGIN_NAME } from '../../const';
|
||||
import { SheetsNoteModel } from '../../models/sheets-note.model';
|
||||
import { SheetsNoteResourceController } from '../sheets-note-resource.controller';
|
||||
import { SheetsNoteController } from '../sheets.note.controller';
|
||||
|
||||
const unitId = 'test';
|
||||
const subUnitId = 'sheet1';
|
||||
|
||||
const TEST_WORKBOOK_DATA: IWorkbookData = {
|
||||
id: unitId,
|
||||
sheetOrder: [subUnitId],
|
||||
name: '',
|
||||
appVersion: '0.15.4',
|
||||
locale: LocaleType.EN_US,
|
||||
styles: {},
|
||||
sheets: {
|
||||
[subUnitId]: {
|
||||
id: subUnitId,
|
||||
name: 'Sheet1',
|
||||
rowCount: 10,
|
||||
columnCount: 10,
|
||||
cellData: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function createTestBed(resources?: Array<{ name: string; data: string }>) {
|
||||
const univer = new Univer();
|
||||
const injector = univer.__getInjector();
|
||||
const get = injector.get.bind(injector);
|
||||
|
||||
class TestPlugin extends Plugin {
|
||||
static override pluginName = 'test-plugin';
|
||||
static override type = UniverInstanceType.UNIVER_SHEET;
|
||||
|
||||
constructor(
|
||||
_config: undefined,
|
||||
@Inject(Injector) override readonly _injector: Injector
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
override onStarting(): void {
|
||||
// Ensure undo/redo exists and registers commands.
|
||||
this._injector.get(IUndoRedoService);
|
||||
|
||||
([
|
||||
[SheetInterceptorService],
|
||||
[SheetsSelectionsService],
|
||||
[SheetLazyExecuteScheduleService],
|
||||
[SheetsNoteModel],
|
||||
[SheetsNoteController],
|
||||
[SheetsNoteResourceController],
|
||||
] as Dependency[]).forEach((d) => this._injector.add(d));
|
||||
|
||||
touchDependencies(this._injector, [
|
||||
[SheetsNoteModel],
|
||||
[SheetsNoteController],
|
||||
[SheetsNoteResourceController],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
univer.registerPlugin(TestPlugin);
|
||||
univer.createUnit(UniverInstanceType.UNIVER_SHEET, {
|
||||
...TEST_WORKBOOK_DATA,
|
||||
resources: resources ?? [],
|
||||
});
|
||||
|
||||
get(IUniverInstanceService).focusUnit(unitId);
|
||||
|
||||
const logService = get(ILogService);
|
||||
logService.setLogLevel(LogLevel.SILENT);
|
||||
|
||||
const localeService = get(LocaleService);
|
||||
localeService.load({ enUS });
|
||||
localeService.setLocale(LocaleType.EN_US);
|
||||
|
||||
const commandService = get(ICommandService);
|
||||
// Register sheet structure commands/mutations that resource controller intercepts.
|
||||
commandService.registerCommand(RemoveSheetCommand);
|
||||
commandService.registerCommand(CopySheetCommand);
|
||||
commandService.registerCommand(RemoveSheetMutation);
|
||||
commandService.registerCommand(InsertSheetMutation);
|
||||
commandService.registerCommand(CopyWorksheetEndMutation);
|
||||
commandService.registerCommand(SetRangeValuesMutation);
|
||||
commandService.registerCommand(SetSelectionsOperation);
|
||||
commandService.registerCommand(SetWorksheetActivateCommand);
|
||||
commandService.registerCommand(SetWorksheetActiveOperation);
|
||||
// Keep parity with existing ref-range test bed (some sheet commands expect these).
|
||||
commandService.registerCommand(InsertRowByRangeCommand);
|
||||
commandService.registerCommand(InsertColByRangeCommand);
|
||||
commandService.registerCommand(RemoveRowByRangeCommand);
|
||||
commandService.registerCommand(RemoveColByRangeCommand);
|
||||
commandService.registerCommand(InsertRowMutation);
|
||||
commandService.registerCommand(InsertColMutation);
|
||||
commandService.registerCommand(RemoveRowMutation);
|
||||
commandService.registerCommand(RemoveColMutation);
|
||||
// Note mutations are registered by SheetsNoteController.
|
||||
|
||||
return {
|
||||
univer,
|
||||
get,
|
||||
injector,
|
||||
commandService,
|
||||
};
|
||||
}
|
||||
|
||||
describe('SheetsNoteResourceController', () => {
|
||||
let univer: Univer;
|
||||
let get: Injector['get'];
|
||||
let commandService: ICommandService;
|
||||
|
||||
beforeEach(() => {
|
||||
// Create empty bed by default.
|
||||
const testBed = createTestBed();
|
||||
univer = testBed.univer;
|
||||
get = testBed.get;
|
||||
commandService = testBed.commandService;
|
||||
});
|
||||
|
||||
afterEach(() => univer.dispose());
|
||||
|
||||
it('loads notes from snapshot resources', () => {
|
||||
univer.dispose();
|
||||
const note: ISheetNote = { id: 'n1', row: 1, col: 1, width: 160, height: 72, note: 'from snapshot' };
|
||||
const resources = [{
|
||||
name: PLUGIN_NAME,
|
||||
data: JSON.stringify({ [subUnitId]: { 1: { 1: note } } }),
|
||||
}];
|
||||
|
||||
const testBed = createTestBed(resources);
|
||||
univer = testBed.univer;
|
||||
get = testBed.get;
|
||||
|
||||
const model = get(SheetsNoteModel);
|
||||
expect(model.getNote(unitId, subUnitId, { row: 1, col: 1 })?.note).toBe('from snapshot');
|
||||
});
|
||||
|
||||
it('clears unit notes on resource unload', () => {
|
||||
const model = get(SheetsNoteModel);
|
||||
model.updateNote(unitId, subUnitId, 2, 2, { id: 'n2', row: 2, col: 2, width: 160, height: 72, note: 'note' });
|
||||
expect(model.getSheetNotes(unitId, subUnitId)?.size).toBe(1);
|
||||
|
||||
const resourceManager = get(IResourceManagerService);
|
||||
resourceManager.unloadResources(unitId, UniverInstanceType.UNIVER_SHEET);
|
||||
expect(model.getUnitNotes(unitId)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('intercepts RemoveSheetCommand to remove notes and supports undo', async () => {
|
||||
const model = get(SheetsNoteModel);
|
||||
model.updateNote(unitId, subUnitId, 3, 4, { id: 'n3', row: 3, col: 4, width: 160, height: 72, note: 'note' });
|
||||
|
||||
// Need at least 2 sheets for RemoveSheetCommand to pass.
|
||||
const workbook = get(IUniverInstanceService).getCurrentUnitOfType<Workbook>(UniverInstanceType.UNIVER_SHEET)!;
|
||||
workbook.addWorksheet('sheet2', 1, { id: 'sheet2', name: 'Sheet2', rowCount: 10, columnCount: 10, cellData: {} });
|
||||
|
||||
expect(await commandService.executeCommand(SetWorksheetActivateCommand.id, { unitId, subUnitId })).toBeTruthy();
|
||||
|
||||
const params: IRemoveSheetCommandParams = { unitId, subUnitId };
|
||||
expect(commandService.syncExecuteCommand(RemoveSheetCommand.id, params)).toBeTruthy();
|
||||
expect(model.getSheetNotes(unitId, subUnitId)?.size).toBe(0);
|
||||
|
||||
// Undo should restore the sheet and its notes via UpdateNoteMutation in undo list.
|
||||
expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy();
|
||||
expect(model.getNote(unitId, subUnitId, { row: 3, col: 4 })?.id).toBe('n3');
|
||||
});
|
||||
|
||||
it('intercepts CopySheetCommand to copy notes to target sheet and supports undo', async () => {
|
||||
const model = get(SheetsNoteModel);
|
||||
model.updateNote(unitId, subUnitId, 1, 2, { id: 'n4', row: 1, col: 2, width: 160, height: 72, note: 'note' });
|
||||
|
||||
// Need at least 2 sheets for RemoveSheetCommand used by undo in CopySheetCommand to pass.
|
||||
const workbook = get(IUniverInstanceService).getCurrentUnitOfType<Workbook>(UniverInstanceType.UNIVER_SHEET)!;
|
||||
workbook.addWorksheet('sheet2', 1, { id: 'sheet2', name: 'Sheet2', rowCount: 10, columnCount: 10, cellData: {} });
|
||||
|
||||
expect(await commandService.executeCommand(SetWorksheetActivateCommand.id, { unitId, subUnitId })).toBeTruthy();
|
||||
|
||||
const params: ICopySheetCommandParams = { unitId, subUnitId };
|
||||
expect(commandService.syncExecuteCommand(CopySheetCommand.id, params)).toBeTruthy();
|
||||
|
||||
const copiedSheetId = workbook.getSheets()[1].getSheetId();
|
||||
expect(copiedSheetId).not.toBe(subUnitId);
|
||||
|
||||
expect(model.getSheetNotes(unitId, copiedSheetId)?.size).toBe(1);
|
||||
|
||||
const copiedNotes = Array.from(model.getSheetNotes(unitId, copiedSheetId)!.values());
|
||||
expect(copiedNotes[0].row).toBe(1);
|
||||
expect(copiedNotes[0].col).toBe(2);
|
||||
expect(copiedNotes[0].note).toBe('note');
|
||||
// Copied note should have a new id.
|
||||
expect(copiedNotes[0].id).not.toBe('n4');
|
||||
|
||||
expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy();
|
||||
expect(model.getSheetNotes(unitId, copiedSheetId)?.size).toBe(0);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,204 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { IDisposable, IRange, Nullable } from '@univerjs/core';
|
||||
import type { EffectRefRangeParams } from '@univerjs/sheets';
|
||||
import type { ISheetNote } from '../models/sheets-note.model';
|
||||
import { Disposable, ICommandService, Inject, sequenceExecuteAsync } from '@univerjs/core';
|
||||
import { handleCommonRangeChangeWithEffectRefCommandsSkipNoInterests, RefRangeService, SheetsSelectionsService } from '@univerjs/sheets';
|
||||
import { RemoveNoteMutation, UpdateNoteMutation, UpdateNotePositionMutation } from '../commands/mutations/note.mutation';
|
||||
import { SheetsNoteModel } from '../models/sheets-note.model';
|
||||
|
||||
export class SheetsNoteRefRangeController extends Disposable {
|
||||
private _disposableMap = new Map<string, IDisposable>();
|
||||
private _watcherMap = new Map<string, IDisposable>();
|
||||
|
||||
constructor(
|
||||
@Inject(RefRangeService) private readonly _refRangeService: RefRangeService,
|
||||
@Inject(SheetsNoteModel) private readonly _sheetsNoteModel: SheetsNoteModel,
|
||||
@Inject(SheetsSelectionsService) private readonly _selectionManagerService: SheetsSelectionsService,
|
||||
@ICommandService private readonly _commandService: ICommandService
|
||||
) {
|
||||
super();
|
||||
this._initData();
|
||||
this._initRefRange();
|
||||
}
|
||||
|
||||
private _getIdWithUnitId(unitId: string, subUnitId: string, row: number, col: number) {
|
||||
return `${unitId}-${subUnitId}-${row}-${col}`;
|
||||
}
|
||||
|
||||
private _handleRangeChange = (unitId: string, subUnitId: string, note: ISheetNote, row: number, col: number, resultRange: Nullable<IRange>, silent?: boolean) => {
|
||||
if (!resultRange) {
|
||||
return {
|
||||
redos: [{
|
||||
id: RemoveNoteMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
sheetId: subUnitId,
|
||||
noteId: note.id,
|
||||
},
|
||||
}],
|
||||
undos: [{
|
||||
id: UpdateNoteMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
sheetId: subUnitId,
|
||||
row,
|
||||
col,
|
||||
note,
|
||||
},
|
||||
}],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
redos: [{
|
||||
id: UpdateNotePositionMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
sheetId: subUnitId,
|
||||
noteId: note.id,
|
||||
newPosition: {
|
||||
row: resultRange.startRow,
|
||||
col: resultRange.startColumn,
|
||||
},
|
||||
silent,
|
||||
},
|
||||
}],
|
||||
undos: [{
|
||||
id: UpdateNotePositionMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
sheetId: subUnitId,
|
||||
noteId: note.id,
|
||||
newPosition: {
|
||||
row,
|
||||
col,
|
||||
},
|
||||
note,
|
||||
silent,
|
||||
},
|
||||
}],
|
||||
};
|
||||
};
|
||||
|
||||
private _register(unitId: string, subUnitId: string, note: ISheetNote, row: number, col: number) {
|
||||
const oldRange: IRange = {
|
||||
startColumn: col,
|
||||
endColumn: col,
|
||||
startRow: row,
|
||||
endRow: row,
|
||||
};
|
||||
|
||||
this._disposableMap.set(
|
||||
this._getIdWithUnitId(unitId, subUnitId, row, col),
|
||||
this._refRangeService.registerRefRange(oldRange, (commandInfo: EffectRefRangeParams) => {
|
||||
const resultRanges = handleCommonRangeChangeWithEffectRefCommandsSkipNoInterests(oldRange, commandInfo, { selectionManagerService: this._selectionManagerService });
|
||||
const resultRange = Array.isArray(resultRanges) ? resultRanges[0] : resultRanges;
|
||||
if (resultRange && resultRange.startColumn === oldRange.startColumn && resultRange.startRow === oldRange.startRow) {
|
||||
return {
|
||||
undos: [],
|
||||
redos: [],
|
||||
};
|
||||
}
|
||||
const res = this._handleRangeChange(unitId, subUnitId, note, row, col, resultRange, false);
|
||||
return res;
|
||||
}, unitId, subUnitId)
|
||||
);
|
||||
}
|
||||
|
||||
private _watch(unitId: string, subUnitId: string, note: ISheetNote, row: number, col: number) {
|
||||
const oldRange: IRange = {
|
||||
startColumn: col,
|
||||
endColumn: col,
|
||||
startRow: row,
|
||||
endRow: row,
|
||||
};
|
||||
|
||||
this._watcherMap.set(
|
||||
this._getIdWithUnitId(unitId, subUnitId, row, col),
|
||||
this._refRangeService.watchRange(unitId, subUnitId, oldRange, (before, after) => {
|
||||
const { redos } = this._handleRangeChange(unitId, subUnitId, note, before.startRow, before.startColumn, after, true);
|
||||
sequenceExecuteAsync(redos, this._commandService, { onlyLocal: true });
|
||||
}, true)
|
||||
);
|
||||
}
|
||||
|
||||
private _unwatch(unitId: string, subUnitId: string, row: number, col: number) {
|
||||
const id = this._getIdWithUnitId(unitId, subUnitId, row, col);
|
||||
this._watcherMap.get(id)?.dispose();
|
||||
this._watcherMap.delete(id);
|
||||
}
|
||||
|
||||
private _unregister(unitId: string, subUnitId: string, row: number, col: number) {
|
||||
const id = this._getIdWithUnitId(unitId, subUnitId, row, col);
|
||||
this._disposableMap.get(id)?.dispose();
|
||||
this._disposableMap.delete(id);
|
||||
}
|
||||
|
||||
private _initData() {
|
||||
const unitNotes = this._sheetsNoteModel.getNotes();
|
||||
for (const [unitId, unitNote] of unitNotes) {
|
||||
for (const [subUnitId, notes] of unitNote) {
|
||||
notes.forEach((note) => {
|
||||
this._register(unitId, subUnitId, note, note.row, note.col);
|
||||
this._watch(unitId, subUnitId, note, note.row, note.col);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _initRefRange() {
|
||||
this.disposeWithMe(
|
||||
this._sheetsNoteModel.change$.subscribe((option) => {
|
||||
switch (option.type) {
|
||||
case 'update': {
|
||||
const { unitId, subUnitId, oldNote, newNote } = option;
|
||||
const row = newNote ? newNote.row : oldNote!.row;
|
||||
const col = newNote ? newNote.col : oldNote!.col;
|
||||
const id = this._getIdWithUnitId(unitId, subUnitId, row, col);
|
||||
if (newNote) {
|
||||
if (!this._disposableMap.has(id)) {
|
||||
this._register(unitId, subUnitId, newNote, row, col);
|
||||
this._watch(unitId, subUnitId, newNote, row, col);
|
||||
}
|
||||
} else {
|
||||
this._unregister(unitId, subUnitId, row, col);
|
||||
this._unwatch(unitId, subUnitId, row, col);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'ref': {
|
||||
const { unitId, subUnitId, oldNote, newNote, silent } = option;
|
||||
const { row: oldRow, col: oldCol } = oldNote!;
|
||||
const { row: newRow, col: newCol } = newNote;
|
||||
|
||||
this._unregister(unitId, subUnitId, oldRow, oldCol);
|
||||
|
||||
if (!silent) {
|
||||
this._unwatch(unitId, subUnitId, oldRow, oldCol);
|
||||
this._watch(unitId, subUnitId, newNote, newRow, newCol);
|
||||
}
|
||||
|
||||
this._register(unitId, subUnitId, newNote, newRow, newCol);
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { IMutationInfo, Workbook } from '@univerjs/core';
|
||||
import type { ICopySheetCommandParams, IRemoveSheetCommandParams } from '@univerjs/sheets';
|
||||
import type { ISheetNote } from '../models/sheets-note.model';
|
||||
import { Disposable, generateRandomId, Inject, IResourceManagerService, IUniverInstanceService, UniverInstanceType } from '@univerjs/core';
|
||||
import { CopySheetCommand, RemoveSheetCommand, SheetInterceptorService } from '@univerjs/sheets';
|
||||
import { RemoveNoteMutation, UpdateNoteMutation } from '../commands/mutations/note.mutation';
|
||||
import { PLUGIN_NAME } from '../const';
|
||||
import { SheetsNoteModel } from '../models/sheets-note.model';
|
||||
|
||||
interface INoteData {
|
||||
[sheetId: string]: {
|
||||
[row: number]: {
|
||||
[col: number]: ISheetNote;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export class SheetsNoteResourceController extends Disposable {
|
||||
constructor(
|
||||
@IResourceManagerService private readonly _resourceManagerService: IResourceManagerService,
|
||||
@IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService,
|
||||
@Inject(SheetInterceptorService) private _sheetInterceptorService: SheetInterceptorService,
|
||||
@Inject(SheetsNoteModel) private readonly _sheetsNoteModel: SheetsNoteModel
|
||||
) {
|
||||
super();
|
||||
this._initSnapshot();
|
||||
this._initSheetChange();
|
||||
}
|
||||
|
||||
private _initSnapshot() {
|
||||
const toJson = (unitId: string) => {
|
||||
const unitMap = this._sheetsNoteModel.getUnitNotes(unitId);
|
||||
if (!unitMap) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const result: INoteData = {};
|
||||
|
||||
unitMap.forEach((subUnitMap, subUnitId) => {
|
||||
const sheetNotes: INoteData[string] = {};
|
||||
|
||||
subUnitMap.forEach((note) => {
|
||||
const { row, col } = note;
|
||||
if (!sheetNotes[row]) {
|
||||
sheetNotes[row] = {};
|
||||
}
|
||||
sheetNotes[row][col] = note;
|
||||
});
|
||||
|
||||
if (Object.keys(sheetNotes).length > 0) {
|
||||
result[subUnitId] = sheetNotes;
|
||||
}
|
||||
});
|
||||
|
||||
return JSON.stringify(result);
|
||||
};
|
||||
|
||||
const parseJson = (json: string): INoteData => {
|
||||
if (!json) {
|
||||
return {};
|
||||
}
|
||||
try {
|
||||
return JSON.parse(json);
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
this.disposeWithMe(
|
||||
this._resourceManagerService.registerPluginResource<INoteData>({
|
||||
pluginName: PLUGIN_NAME,
|
||||
businesses: [UniverInstanceType.UNIVER_SHEET],
|
||||
toJson: (unitId) => toJson(unitId),
|
||||
parseJson: (json) => parseJson(json),
|
||||
onUnLoad: (unitId) => {
|
||||
// Clear all notes for this unit
|
||||
this._sheetsNoteModel.deleteUnitNotes(unitId);
|
||||
},
|
||||
onLoad: (unitId, value) => {
|
||||
// Load notes from the parsed data
|
||||
Object.entries(value).forEach(([sheetId, sheetNotes]) => {
|
||||
Object.entries(sheetNotes).forEach(([row, colNotes]) => {
|
||||
Object.entries(colNotes).forEach(([col, note]) => {
|
||||
this._sheetsNoteModel.updateNote(
|
||||
unitId,
|
||||
sheetId,
|
||||
Number(row),
|
||||
Number(col),
|
||||
note
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
private _initSheetChange() {
|
||||
this.disposeWithMe(
|
||||
this._sheetInterceptorService.interceptCommand({
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
getMutations: (commandInfo) => {
|
||||
if (commandInfo.id === RemoveSheetCommand.id) {
|
||||
const params = commandInfo.params as IRemoveSheetCommandParams;
|
||||
const unitId = params.unitId || this._univerInstanceService.getCurrentUnitOfType<Workbook>(UniverInstanceType.UNIVER_SHEET)!.getUnitId();
|
||||
const subUnitId = params.subUnitId || this._univerInstanceService.getCurrentUnitOfType<Workbook>(UniverInstanceType.UNIVER_SHEET)!.getActiveSheet()?.getSheetId();
|
||||
|
||||
if (!unitId || !subUnitId) {
|
||||
return { redos: [], undos: [] };
|
||||
}
|
||||
|
||||
const sheetNotes = this._sheetsNoteModel.getSheetNotes(unitId, subUnitId);
|
||||
|
||||
if (!sheetNotes) {
|
||||
return { redos: [], undos: [] };
|
||||
}
|
||||
|
||||
const redos: IMutationInfo[] = [];
|
||||
const undos: IMutationInfo[] = [];
|
||||
|
||||
sheetNotes.forEach((note) => {
|
||||
redos.push({
|
||||
id: RemoveNoteMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
sheetId: subUnitId,
|
||||
noteId: note.id,
|
||||
row: note.row,
|
||||
col: note.col,
|
||||
},
|
||||
});
|
||||
undos.push({
|
||||
id: UpdateNoteMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
sheetId: subUnitId,
|
||||
row: note.row,
|
||||
col: note.col,
|
||||
note,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return { redos, undos };
|
||||
} else if (commandInfo.id === CopySheetCommand.id) {
|
||||
const params = commandInfo.params as ICopySheetCommandParams & { targetSubUnitId: string };
|
||||
const { unitId, subUnitId, targetSubUnitId } = params;
|
||||
|
||||
if (!unitId || !subUnitId || !targetSubUnitId) {
|
||||
return { redos: [], undos: [] };
|
||||
}
|
||||
|
||||
const sheetNotes = this._sheetsNoteModel.getSheetNotes(unitId, subUnitId);
|
||||
|
||||
if (!sheetNotes) {
|
||||
return { redos: [], undos: [] };
|
||||
}
|
||||
|
||||
const redos: IMutationInfo[] = [];
|
||||
const undos: IMutationInfo[] = [];
|
||||
|
||||
sheetNotes.forEach((note) => {
|
||||
const newNote = { ...note, id: generateRandomId(6) }; // Generate new ID for the copied note
|
||||
|
||||
redos.push({
|
||||
id: UpdateNoteMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
sheetId: targetSubUnitId,
|
||||
row: newNote.row,
|
||||
col: newNote.col,
|
||||
note: newNote,
|
||||
},
|
||||
});
|
||||
undos.push({
|
||||
id: RemoveNoteMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
sheetId: targetSubUnitId,
|
||||
noteId: newNote.id,
|
||||
row: newNote.row,
|
||||
col: newNote.col,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return { redos, undos };
|
||||
}
|
||||
return { redos: [], undos: [] };
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Disposable, ICommandService } from '@univerjs/core';
|
||||
import { SheetDeleteNoteCommand, SheetToggleNotePopupCommand, SheetUpdateNoteCommand } from '../commands/commands/note.command';
|
||||
import { RemoveNoteMutation, ToggleNotePopupMutation, UpdateNoteMutation, UpdateNotePositionMutation } from '../commands/mutations/note.mutation';
|
||||
|
||||
export class SheetsNoteController extends Disposable {
|
||||
constructor(
|
||||
@ICommandService private readonly _commandService: ICommandService
|
||||
) {
|
||||
super();
|
||||
|
||||
this._initialize();
|
||||
}
|
||||
|
||||
private _initialize(): void {
|
||||
[
|
||||
UpdateNotePositionMutation,
|
||||
ToggleNotePopupMutation,
|
||||
UpdateNoteMutation,
|
||||
RemoveNoteMutation,
|
||||
SheetDeleteNoteCommand,
|
||||
SheetToggleNotePopupCommand,
|
||||
SheetUpdateNoteCommand,
|
||||
].forEach((command) => {
|
||||
this.disposeWithMe(
|
||||
this._commandService.registerCommand(command)
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Dependency, IWorkbookData, UnitModel } from '@univerjs/core';
|
||||
import type { ISelectionWithStyle } from '@univerjs/sheets';
|
||||
import {
|
||||
ICommandService,
|
||||
ILogService,
|
||||
Inject,
|
||||
Injector,
|
||||
IUndoRedoService,
|
||||
IUniverInstanceService,
|
||||
LocaleType,
|
||||
LogLevel,
|
||||
Plugin,
|
||||
RANGE_TYPE,
|
||||
touchDependencies,
|
||||
Univer,
|
||||
UniverInstanceType,
|
||||
} from '@univerjs/core';
|
||||
import { FUniver } from '@univerjs/core/facade';
|
||||
import {
|
||||
DefinedNamesService,
|
||||
FormulaDataModel,
|
||||
FunctionService,
|
||||
IDefinedNamesService,
|
||||
IFunctionService,
|
||||
LexerTreeBuilder,
|
||||
} from '@univerjs/engine-formula';
|
||||
import { IRenderManagerService, RenderManagerService } from '@univerjs/engine-render';
|
||||
import { ISocketService, WebSocketService } from '@univerjs/network';
|
||||
import {
|
||||
RangeProtectionRuleModel,
|
||||
RefRangeService,
|
||||
SheetInterceptorService,
|
||||
SheetLazyExecuteScheduleService,
|
||||
SheetsSelectionsService,
|
||||
WorkbookPermissionService,
|
||||
WorksheetPermissionService,
|
||||
WorksheetProtectionPointModel,
|
||||
WorksheetProtectionRuleModel,
|
||||
} from '@univerjs/sheets';
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
import {
|
||||
SheetDeleteNoteCommand,
|
||||
SheetToggleNotePopupCommand,
|
||||
SheetUpdateNoteCommand,
|
||||
} from '../../commands/commands/note.command';
|
||||
import { SheetsNoteController } from '../../controllers/sheets.note.controller';
|
||||
import { SheetsNoteModel } from '../../models/sheets-note.model';
|
||||
import '@univerjs/sheets/facade';
|
||||
import '../../facade';
|
||||
|
||||
const unitId = 'test';
|
||||
const subUnitId = 'sheet1';
|
||||
|
||||
const TEST_WORKBOOK_DATA: IWorkbookData = {
|
||||
id: unitId,
|
||||
sheetOrder: [subUnitId],
|
||||
name: '',
|
||||
appVersion: '0.15.4',
|
||||
locale: LocaleType.EN_US,
|
||||
styles: {},
|
||||
sheets: {
|
||||
[subUnitId]: {
|
||||
id: subUnitId,
|
||||
name: 'Sheet1',
|
||||
rowCount: 10,
|
||||
columnCount: 10,
|
||||
cellData: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function createFacadeTestBed(workbookData?: IWorkbookData, dependencies?: Dependency[]) {
|
||||
const univer = new Univer();
|
||||
const injector = univer.__getInjector();
|
||||
|
||||
class TestPlugin extends Plugin {
|
||||
static override pluginName = 'test-plugin';
|
||||
static override type = UniverInstanceType.UNIVER_SHEET;
|
||||
|
||||
constructor(
|
||||
_config: undefined,
|
||||
@Inject(Injector) override readonly _injector: Injector
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
override onStarting(): void {
|
||||
// Ensure undo/redo exists.
|
||||
this._injector.get(IUndoRedoService);
|
||||
|
||||
(dependencies ?? []).forEach((d) => this._injector.add(d));
|
||||
|
||||
// Minimal set of dependencies required by Sheets facade classes.
|
||||
this._injector.add([SheetsSelectionsService]);
|
||||
this._injector.add([SheetInterceptorService]);
|
||||
this._injector.add([IFunctionService, { useClass: FunctionService }]);
|
||||
this._injector.add([ISocketService, { useClass: WebSocketService }]);
|
||||
this._injector.add([IRenderManagerService, { useClass: RenderManagerService }]);
|
||||
this._injector.add([FormulaDataModel]);
|
||||
this._injector.add([LexerTreeBuilder]);
|
||||
this._injector.add([RefRangeService]);
|
||||
this._injector.add([WorksheetPermissionService]);
|
||||
this._injector.add([WorkbookPermissionService]);
|
||||
this._injector.add([WorksheetProtectionPointModel]);
|
||||
this._injector.add([RangeProtectionRuleModel]);
|
||||
this._injector.add([WorksheetProtectionRuleModel]);
|
||||
this._injector.add([IDefinedNamesService, { useClass: DefinedNamesService }]);
|
||||
this._injector.add([SheetLazyExecuteScheduleService]);
|
||||
|
||||
([
|
||||
[SheetsNoteModel],
|
||||
[SheetsNoteController],
|
||||
] as Dependency[]).forEach((d) => this._injector.add(d));
|
||||
|
||||
touchDependencies(this._injector, [
|
||||
[SheetsSelectionsService],
|
||||
[SheetInterceptorService],
|
||||
[SheetsNoteModel],
|
||||
[SheetsNoteController],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
univer.registerPlugin(TestPlugin);
|
||||
const sheet = univer.createUnit<IWorkbookData, UnitModel<IWorkbookData>>(UniverInstanceType.UNIVER_SHEET, workbookData ?? TEST_WORKBOOK_DATA);
|
||||
const univerInstanceService = injector.get(IUniverInstanceService);
|
||||
univerInstanceService.focusUnit(unitId);
|
||||
|
||||
const logService = injector.get(ILogService);
|
||||
logService.setLogLevel(LogLevel.SILENT);
|
||||
|
||||
const univerAPI = FUniver.newAPI(injector);
|
||||
|
||||
return {
|
||||
univer,
|
||||
injector,
|
||||
sheet,
|
||||
univerAPI,
|
||||
};
|
||||
}
|
||||
|
||||
function setSingleCellSelection(injector: Injector, row: number, col: number) {
|
||||
const selection: ISelectionWithStyle = {
|
||||
range: {
|
||||
startRow: row,
|
||||
endRow: row,
|
||||
startColumn: col,
|
||||
endColumn: col,
|
||||
rangeType: RANGE_TYPE.NORMAL,
|
||||
},
|
||||
primary: {
|
||||
startRow: row,
|
||||
endRow: row,
|
||||
startColumn: col,
|
||||
endColumn: col,
|
||||
actualRow: row,
|
||||
actualColumn: col,
|
||||
isMerged: false,
|
||||
isMergedMainCell: false,
|
||||
},
|
||||
style: null,
|
||||
};
|
||||
|
||||
injector.get(SheetsSelectionsService).setSelections([selection]);
|
||||
}
|
||||
|
||||
describe('sheets-note facade mixins', () => {
|
||||
let univer: Univer;
|
||||
let injector: Injector;
|
||||
let univerAPI: FUniver;
|
||||
|
||||
beforeEach(() => {
|
||||
const testBed = createFacadeTestBed();
|
||||
univer = testBed.univer;
|
||||
injector = testBed.injector;
|
||||
univerAPI = testBed.univerAPI;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
univer.dispose();
|
||||
});
|
||||
|
||||
it('creates/reads/deletes note through FRange and reads all notes via FWorksheet', () => {
|
||||
const workbook = univerAPI.getActiveWorkbook();
|
||||
expect(workbook).toBeTruthy();
|
||||
const sheet = workbook!.getActiveSheet();
|
||||
|
||||
const range = sheet.getRange(1, 1, 1, 1);
|
||||
expect(range.getNote()).toBeUndefined();
|
||||
|
||||
range.createOrUpdateNote({ width: 160, height: 72, note: 'hello', show: true });
|
||||
|
||||
expect(range.getNote()).toMatchObject({
|
||||
col: 1,
|
||||
height: 72,
|
||||
note: 'hello',
|
||||
row: 1,
|
||||
show: true,
|
||||
width: 160,
|
||||
});
|
||||
expect(sheet.getNotes()).toHaveLength(1);
|
||||
|
||||
range.deleteNote();
|
||||
expect(range.getNote()).toBeUndefined();
|
||||
expect(sheet.getNotes()).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('emits business events while notes are added, updated, shown, hidden and deleted', () => {
|
||||
const workbook = univerAPI.getActiveWorkbook()!;
|
||||
const sheet = workbook.getActiveSheet();
|
||||
const commandService = injector.get(ICommandService);
|
||||
const events: string[] = [];
|
||||
const disposables = [
|
||||
univerAPI.addEvent(univerAPI.Event.BeforeSheetNoteAdd, (event) => events.push(`before-add:${event.row}:${event.col}:${event.note.note}`)),
|
||||
univerAPI.addEvent(univerAPI.Event.SheetNoteAdd, (event) => events.push(`add:${event.row}:${event.col}:${event.note.note}`)),
|
||||
univerAPI.addEvent(univerAPI.Event.BeforeSheetNoteUpdate, (event) => events.push(`before-update:${event.row}:${event.col}:${event.oldNote.note}->${event.note.note}`)),
|
||||
univerAPI.addEvent(univerAPI.Event.SheetNoteUpdate, (event) => events.push(`update:${event.row}:${event.col}:${event.oldNote.note}->${event.note.note}`)),
|
||||
univerAPI.addEvent(univerAPI.Event.BeforeSheetNoteShow, (event) => events.push(`before-show:${event.row}:${event.col}`)),
|
||||
univerAPI.addEvent(univerAPI.Event.SheetNoteShow, (event) => events.push(`show:${event.row}:${event.col}`)),
|
||||
univerAPI.addEvent(univerAPI.Event.BeforeSheetNoteHide, (event) => events.push(`before-hide:${event.row}:${event.col}`)),
|
||||
univerAPI.addEvent(univerAPI.Event.SheetNoteHide, (event) => events.push(`hide:${event.row}:${event.col}`)),
|
||||
univerAPI.addEvent(univerAPI.Event.BeforeSheetNoteDelete, (event) => events.push(`before-delete:${event.row}:${event.col}:${event.oldNote.note}`)),
|
||||
univerAPI.addEvent(univerAPI.Event.SheetNoteDelete, (event) => events.push(`delete:${event.row}:${event.col}:${event.oldNote.note}`)),
|
||||
];
|
||||
|
||||
expect(commandService.syncExecuteCommand(SheetUpdateNoteCommand.id, {
|
||||
unitId,
|
||||
sheetId: subUnitId,
|
||||
row: 1,
|
||||
col: 1,
|
||||
note: { id: 'note-1', row: 1, col: 1, width: 160, height: 72, note: 'hello', show: false },
|
||||
})).toBe(true);
|
||||
expect(commandService.syncExecuteCommand(SheetUpdateNoteCommand.id, {
|
||||
unitId,
|
||||
sheetId: subUnitId,
|
||||
row: 1,
|
||||
col: 1,
|
||||
note: { id: 'note-1', row: 1, col: 1, width: 160, height: 72, note: 'updated', show: false },
|
||||
})).toBe(true);
|
||||
|
||||
setSingleCellSelection(injector, 1, 1);
|
||||
expect(commandService.syncExecuteCommand(SheetToggleNotePopupCommand.id)).toBe(true);
|
||||
expect(sheet.getRange(1, 1).getNote()?.show).toBe(true);
|
||||
|
||||
expect(commandService.syncExecuteCommand(SheetToggleNotePopupCommand.id)).toBe(true);
|
||||
expect(sheet.getRange(1, 1).getNote()?.show).toBe(false);
|
||||
|
||||
expect(commandService.syncExecuteCommand(SheetDeleteNoteCommand.id)).toBe(true);
|
||||
expect(sheet.getRange(1, 1).getNote()).toBeUndefined();
|
||||
|
||||
expect(events).toEqual(expect.arrayContaining([
|
||||
'before-add:1:1:hello',
|
||||
'add:1:1:hello',
|
||||
'before-update:1:1:hello->updated',
|
||||
'update:1:1:hello->updated',
|
||||
'before-show:1:1',
|
||||
'show:1:1',
|
||||
'before-hide:1:1',
|
||||
'hide:1:1',
|
||||
'before-delete:1:1:updated',
|
||||
'delete:1:1:updated',
|
||||
]));
|
||||
|
||||
disposables.forEach((disposable) => disposable.dispose());
|
||||
});
|
||||
});
|
||||
311
packages/sheets-note/src/facade/f-event.ts
Normal file
311
packages/sheets-note/src/facade/f-event.ts
Normal file
@@ -0,0 +1,311 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { ISheetNote } from '@univerjs/sheets-note';
|
||||
import type { FWorkbook, FWorksheet } from '@univerjs/sheets/facade';
|
||||
import { FEventName } from '@univerjs/core/facade';
|
||||
|
||||
export interface ISheetNoteAddEventParams {
|
||||
workbook: FWorkbook;
|
||||
worksheet: FWorksheet;
|
||||
row: number;
|
||||
col: number;
|
||||
note: ISheetNote;
|
||||
cancel?: boolean;
|
||||
}
|
||||
|
||||
export interface ISheetNoteDeleteEventParams {
|
||||
workbook: FWorkbook;
|
||||
worksheet: FWorksheet;
|
||||
row: number;
|
||||
col: number;
|
||||
oldNote: ISheetNote;
|
||||
cancel?: boolean;
|
||||
}
|
||||
|
||||
export interface ISheetNoteUpdateEventParams {
|
||||
workbook: FWorkbook;
|
||||
worksheet: FWorksheet;
|
||||
row: number;
|
||||
col: number;
|
||||
note: ISheetNote;
|
||||
oldNote: ISheetNote;
|
||||
cancel?: boolean;
|
||||
}
|
||||
|
||||
export interface ISheetNoteShowEventParams {
|
||||
workbook: FWorkbook;
|
||||
worksheet: FWorksheet;
|
||||
row: number;
|
||||
col: number;
|
||||
cancel?: boolean;
|
||||
}
|
||||
|
||||
export interface ISheetNoteHideEventParams {
|
||||
workbook: FWorkbook;
|
||||
worksheet: FWorksheet;
|
||||
row: number;
|
||||
col: number;
|
||||
cancel?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
export interface ISheetsNoteEventParamConfig {
|
||||
SheetNoteAdd: ISheetNoteAddEventParams;
|
||||
SheetNoteDelete: ISheetNoteDeleteEventParams;
|
||||
SheetNoteUpdate: ISheetNoteUpdateEventParams;
|
||||
SheetNoteShow: ISheetNoteShowEventParams;
|
||||
SheetNoteHide: ISheetNoteHideEventParams;
|
||||
|
||||
BeforeSheetNoteAdd: ISheetNoteAddEventParams;
|
||||
BeforeSheetNoteDelete: ISheetNoteDeleteEventParams;
|
||||
BeforeSheetNoteUpdate: ISheetNoteUpdateEventParams;
|
||||
BeforeSheetNoteShow: ISheetNoteShowEventParams;
|
||||
BeforeSheetNoteHide: ISheetNoteHideEventParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
interface IFSheetsNoteEventNameMixin {
|
||||
/**
|
||||
* Event fired when a note is added
|
||||
* @see {@link ISheetNoteAddEventParams}
|
||||
* @example
|
||||
* ```ts
|
||||
* const disposable = univerAPI.addEvent(univerAPI.Event.SheetNoteAdd, (params) => {
|
||||
* const { workbook, worksheet, row, col, note } = params;
|
||||
* console.log(params);
|
||||
* });
|
||||
*
|
||||
* // Remove the event listener, use `disposable.dispose()`
|
||||
* ```
|
||||
*/
|
||||
readonly SheetNoteAdd: 'SheetNoteAdd';
|
||||
/**
|
||||
* Event fired when a note is deleted
|
||||
* @see {@link ISheetNoteDeleteEventParams}
|
||||
* @example
|
||||
* ```ts
|
||||
* const disposable = univerAPI.addEvent(univerAPI.Event.SheetNoteDelete, (params) => {
|
||||
* const { workbook, worksheet, row, col, oldNote } = params;
|
||||
* console.log(params);
|
||||
* });
|
||||
*
|
||||
* // Remove the event listener, use `disposable.dispose()`
|
||||
* ```
|
||||
*/
|
||||
readonly SheetNoteDelete: 'SheetNoteDelete';
|
||||
/**
|
||||
* Event fired when a note is updated
|
||||
* @see {@link ISheetNoteUpdateEventParams}
|
||||
* @example
|
||||
* ```ts
|
||||
* const disposable = univerAPI.addEvent(univerAPI.Event.SheetNoteUpdate, (params) => {
|
||||
* const { workbook, worksheet, row, col, note, oldNote } = params;
|
||||
* console.log(params);
|
||||
* });
|
||||
*
|
||||
* // Remove the event listener, use `disposable.dispose()`
|
||||
* ```
|
||||
*/
|
||||
readonly SheetNoteUpdate: 'SheetNoteUpdate';
|
||||
/**
|
||||
* Event fired when a note is shown
|
||||
* @see {@link ISheetNoteShowEventParams}
|
||||
* @example
|
||||
* ```ts
|
||||
* const disposable = univerAPI.addEvent(univerAPI.Event.SheetNoteShow, (params) => {
|
||||
* const { workbook, worksheet, row, col } = params;
|
||||
* console.log(params);
|
||||
* });
|
||||
*
|
||||
* // Remove the event listener, use `disposable.dispose()`
|
||||
* ```
|
||||
*/
|
||||
readonly SheetNoteShow: 'SheetNoteShow';
|
||||
/**
|
||||
* Event fired when a note is hidden
|
||||
* @see {@link ISheetNoteHideEventParams}
|
||||
* @example
|
||||
* ```ts
|
||||
* const disposable = univerAPI.addEvent(univerAPI.Event.SheetNoteHide, (params) => {
|
||||
* const { workbook, worksheet, row, col } = params;
|
||||
* console.log(params);
|
||||
* });
|
||||
*
|
||||
* // Remove the event listener, use `disposable.dispose()`
|
||||
* ```
|
||||
*/
|
||||
readonly SheetNoteHide: 'SheetNoteHide';
|
||||
|
||||
/**
|
||||
* Event fired before a note is added
|
||||
* @see {@link ISheetNoteAddEventParams}
|
||||
* @example
|
||||
* ```ts
|
||||
* const disposable = univerAPI.addEvent(univerAPI.Event.BeforeSheetNoteAdd, (params) => {
|
||||
* const { workbook, worksheet, row, col, note } = params;
|
||||
* console.log(params);
|
||||
*
|
||||
* // Cancel the note addition operation
|
||||
* params.cancel = true;
|
||||
* });
|
||||
*
|
||||
* // Remove the event listener, use `disposable.dispose()`
|
||||
* ```
|
||||
*/
|
||||
readonly BeforeSheetNoteAdd: 'BeforeSheetNoteAdd';
|
||||
/**
|
||||
* Event fired before a note is deleted
|
||||
* @see {@link ISheetNoteDeleteEventParams}
|
||||
* @example
|
||||
* ```ts
|
||||
* const disposable = univerAPI.addEvent(univerAPI.Event.BeforeSheetNoteDelete, (params) => {
|
||||
* const { workbook, worksheet, row, col, oldNote } = params;
|
||||
* console.log(params);
|
||||
*
|
||||
* // Cancel the note deletion operation
|
||||
* params.cancel = true;
|
||||
* });
|
||||
*
|
||||
* // Remove the event listener, use `disposable.dispose()`
|
||||
* ```
|
||||
*/
|
||||
readonly BeforeSheetNoteDelete: 'BeforeSheetNoteDelete';
|
||||
/**
|
||||
* Event fired before a note is updated
|
||||
* @see {@link ISheetNoteUpdateEventParams}
|
||||
* @example
|
||||
* ```ts
|
||||
* const disposable = univerAPI.addEvent(univerAPI.Event.BeforeSheetNoteUpdate, (params) => {
|
||||
* const { workbook, worksheet, row, col, note, oldNote } = params;
|
||||
* console.log(params);
|
||||
*
|
||||
* // Cancel the note update operation
|
||||
* params.cancel = true;
|
||||
* });
|
||||
*
|
||||
* // Remove the event listener, use `disposable.dispose()`
|
||||
* ```
|
||||
*/
|
||||
readonly BeforeSheetNoteUpdate: 'BeforeSheetNoteUpdate';
|
||||
/**
|
||||
* Event fired before a note is shown
|
||||
* @see {@link ISheetNoteShowEventParams}
|
||||
* @example
|
||||
* ```ts
|
||||
* const disposable = univerAPI.addEvent(univerAPI.Event.BeforeSheetNoteShow, (params) => {
|
||||
* const { workbook, worksheet, row, col } = params;
|
||||
* console.log(params);
|
||||
*
|
||||
* // Cancel the note show operation
|
||||
* params.cancel = true;
|
||||
* });
|
||||
*
|
||||
* // Remove the event listener, use `disposable.dispose()`
|
||||
* ```
|
||||
*/
|
||||
readonly BeforeSheetNoteShow: 'BeforeSheetNoteShow';
|
||||
/**
|
||||
* Event fired before a note is hidden
|
||||
* @see {@link ISheetNoteHideEventParams}
|
||||
* @example
|
||||
* ```ts
|
||||
* const disposable = univerAPI.addEvent(univerAPI.Event.BeforeSheetNoteHide, (params) => {
|
||||
* const { workbook, worksheet, row, col } = params;
|
||||
* console.log(params);
|
||||
*
|
||||
* // Cancel the note hide operation
|
||||
* params.cancel = true;
|
||||
* });
|
||||
*
|
||||
* // Remove the event listener, use `disposable.dispose()`
|
||||
* ```
|
||||
*/
|
||||
readonly BeforeSheetNoteHide: 'BeforeSheetNoteHide';
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
export class FSheetsNoteEventNameMixin extends FEventName implements IFSheetsNoteEventNameMixin {
|
||||
override get SheetNoteAdd(): 'SheetNoteAdd' {
|
||||
return 'SheetNoteAdd' as const;
|
||||
}
|
||||
|
||||
override get SheetNoteDelete(): 'SheetNoteDelete' {
|
||||
return 'SheetNoteDelete' as const;
|
||||
}
|
||||
|
||||
override get SheetNoteUpdate(): 'SheetNoteUpdate' {
|
||||
return 'SheetNoteUpdate' as const;
|
||||
}
|
||||
|
||||
override get SheetNoteShow(): 'SheetNoteShow' {
|
||||
return 'SheetNoteShow' as const;
|
||||
}
|
||||
|
||||
override get SheetNoteHide(): 'SheetNoteHide' {
|
||||
return 'SheetNoteHide' as const;
|
||||
}
|
||||
|
||||
override get BeforeSheetNoteAdd(): 'BeforeSheetNoteAdd' {
|
||||
return 'BeforeSheetNoteAdd' as const;
|
||||
}
|
||||
|
||||
override get BeforeSheetNoteDelete(): 'BeforeSheetNoteDelete' {
|
||||
return 'BeforeSheetNoteDelete' as const;
|
||||
}
|
||||
|
||||
override get BeforeSheetNoteUpdate(): 'BeforeSheetNoteUpdate' {
|
||||
return 'BeforeSheetNoteUpdate' as const;
|
||||
}
|
||||
|
||||
override get BeforeSheetNoteShow(): 'BeforeSheetNoteShow' {
|
||||
return 'BeforeSheetNoteShow' as const;
|
||||
}
|
||||
|
||||
override get BeforeSheetNoteHide(): 'BeforeSheetNoteHide' {
|
||||
return 'BeforeSheetNoteHide' as const;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
export interface ISheetsNoteEventParamConfig {
|
||||
SheetNoteAdd: ISheetNoteAddEventParams;
|
||||
SheetNoteDelete: ISheetNoteDeleteEventParams;
|
||||
SheetNoteUpdate: ISheetNoteUpdateEventParams;
|
||||
SheetNoteShow: ISheetNoteShowEventParams;
|
||||
SheetNoteHide: ISheetNoteHideEventParams;
|
||||
|
||||
BeforeSheetNoteAdd: ISheetNoteAddEventParams;
|
||||
BeforeSheetNoteDelete: ISheetNoteDeleteEventParams;
|
||||
BeforeSheetNoteUpdate: ISheetNoteUpdateEventParams;
|
||||
BeforeSheetNoteShow: ISheetNoteShowEventParams;
|
||||
BeforeSheetNoteHide: ISheetNoteHideEventParams;
|
||||
}
|
||||
|
||||
FEventName.extend(FSheetsNoteEventNameMixin);
|
||||
declare module '@univerjs/core/facade' {
|
||||
// eslint-disable-next-line ts/naming-convention
|
||||
interface FEventName extends IFSheetsNoteEventNameMixin {}
|
||||
interface IEventParamConfig extends ISheetsNoteEventParamConfig {}
|
||||
}
|
||||
128
packages/sheets-note/src/facade/f-range.ts
Normal file
128
packages/sheets-note/src/facade/f-range.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Nullable } from '@univerjs/core';
|
||||
import type { ISheetNote } from '@univerjs/sheets-note';
|
||||
import { RemoveNoteMutation, SheetsNoteModel, UpdateNoteMutation } from '@univerjs/sheets-note';
|
||||
import { FRange } from '@univerjs/sheets/facade';
|
||||
|
||||
export interface ICreateOrUpdateNoteOptions {
|
||||
id?: string;
|
||||
width: number;
|
||||
height: number;
|
||||
note: string;
|
||||
show?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
export interface IFRangeSheetsNoteMixin {
|
||||
/**
|
||||
* Get the annotation of the top-left cell in the range
|
||||
* @returns {Nullable<ISheetNote>} The annotation of the top-left cell in the range
|
||||
* @example
|
||||
* ```ts
|
||||
* const fWorkbook = univerAPI.getActiveWorkbook();
|
||||
* const fWorksheet = fWorkbook.getSheetByName('Sheet1');
|
||||
* if (!fWorksheet) return;
|
||||
* const fRange = fWorksheet.getRange('A1:D10');
|
||||
* const note = fRange.getNote();
|
||||
* console.log(note);
|
||||
* ```
|
||||
*/
|
||||
getNote(): Nullable<ISheetNote>;
|
||||
/**
|
||||
* Create or update the annotation of the top-left cell in the range
|
||||
* @param {ICreateOrUpdateNoteOptions} note The annotation to create or update
|
||||
* @returns {FRange} This range for method chaining
|
||||
* @example
|
||||
* ```ts
|
||||
* const fWorkbook = univerAPI.getActiveWorkbook();
|
||||
* const fWorksheet = fWorkbook.getSheetByName('Sheet1');
|
||||
* if (!fWorksheet) return;
|
||||
* const fRange = fWorksheet.getRange('A1');
|
||||
* fRange.createOrUpdateNote({
|
||||
* note: 'This is a note',
|
||||
* width: 160,
|
||||
* height: 100,
|
||||
* show: true,
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
createOrUpdateNote(note: ICreateOrUpdateNoteOptions): FRange;
|
||||
/**
|
||||
* Delete the annotation of the top-left cell in the range
|
||||
* @returns {FRange} This range for method chaining
|
||||
* @example
|
||||
* ```ts
|
||||
* const fWorkbook = univerAPI.getActiveWorkbook();
|
||||
* const fWorksheet = fWorkbook.getSheetByName('Sheet1');
|
||||
* if (!fWorksheet) return;
|
||||
* const notes = fWorksheet.getNotes();
|
||||
* console.log(notes);
|
||||
*
|
||||
* if (notes.length > 0) {
|
||||
* // Delete the first note
|
||||
* const { row, col } = notes[0];
|
||||
* fWorksheet.getRange(row, col).deleteNote();
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
deleteNote(): FRange;
|
||||
}
|
||||
|
||||
export class FRangeSheetsNoteMixin extends FRange implements IFRangeSheetsNoteMixin {
|
||||
override createOrUpdateNote(note: ICreateOrUpdateNoteOptions): FRange {
|
||||
this._commandService.syncExecuteCommand(
|
||||
UpdateNoteMutation.id,
|
||||
{
|
||||
unitId: this.getUnitId(),
|
||||
sheetId: this.getSheetId(),
|
||||
row: this.getRow(),
|
||||
col: this.getColumn(),
|
||||
note,
|
||||
}
|
||||
);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
override deleteNote(): FRange {
|
||||
this._commandService.syncExecuteCommand(
|
||||
RemoveNoteMutation.id,
|
||||
{
|
||||
unitId: this.getUnitId(),
|
||||
sheetId: this.getSheetId(),
|
||||
row: this.getRow(),
|
||||
col: this.getColumn(),
|
||||
}
|
||||
);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
override getNote(): Nullable<ISheetNote> {
|
||||
const model = this._injector.get(SheetsNoteModel);
|
||||
return model.getNote(this.getUnitId(), this.getSheetId(), { row: this.getRow(), col: this.getColumn() });
|
||||
}
|
||||
}
|
||||
|
||||
FRange.extend(FRangeSheetsNoteMixin);
|
||||
declare module '@univerjs/sheets/facade' {
|
||||
// eslint-disable-next-line ts/naming-convention
|
||||
interface FRange extends IFRangeSheetsNoteMixin { }
|
||||
}
|
||||
331
packages/sheets-note/src/facade/f-univer.ts
Normal file
331
packages/sheets-note/src/facade/f-univer.ts
Normal file
@@ -0,0 +1,331 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Injector } from '@univerjs/core';
|
||||
import type { IUpdateNoteMutationParams } from '@univerjs/sheets-note';
|
||||
import type { ISheetNoteAddEventParams, ISheetNoteDeleteEventParams, ISheetNoteHideEventParams, ISheetNoteShowEventParams, ISheetNoteUpdateEventParams } from './f-event';
|
||||
import { CanceledError, ICommandService } from '@univerjs/core';
|
||||
import { FUniver } from '@univerjs/core/facade';
|
||||
import { SheetsSelectionsService } from '@univerjs/sheets';
|
||||
import { SheetDeleteNoteCommand, SheetsNoteModel, SheetToggleNotePopupCommand, SheetUpdateNoteCommand } from '@univerjs/sheets-note';
|
||||
|
||||
export class FUniverSheetsNoteMixin extends FUniver {
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
override _initialize(injector: Injector): void {
|
||||
const commandService = injector.get(ICommandService);
|
||||
|
||||
this.disposeWithMe(
|
||||
this.registerEventHandler(
|
||||
this.Event.SheetNoteAdd,
|
||||
() => {
|
||||
const model = injector.get(SheetsNoteModel);
|
||||
return model.change$.subscribe((change) => {
|
||||
if (change.type === 'update' && !change.oldNote && change.newNote) {
|
||||
const { unitId, subUnitId, newNote } = change;
|
||||
const target = this.getSheetCommandTarget({ unitId, subUnitId });
|
||||
if (!target) return;
|
||||
|
||||
const { workbook, worksheet } = target;
|
||||
|
||||
const eventParams: ISheetNoteAddEventParams = {
|
||||
workbook,
|
||||
worksheet,
|
||||
row: newNote.row,
|
||||
col: newNote.col,
|
||||
note: newNote,
|
||||
};
|
||||
this.fireEvent(this.Event.SheetNoteAdd, eventParams);
|
||||
}
|
||||
});
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
this.disposeWithMe(
|
||||
this.registerEventHandler(
|
||||
this.Event.SheetNoteDelete,
|
||||
() => {
|
||||
const model = injector.get(SheetsNoteModel);
|
||||
return model.change$.subscribe((change) => {
|
||||
if (change.type === 'update' && change.oldNote && !change.newNote) {
|
||||
const { unitId, subUnitId, oldNote } = change;
|
||||
const target = this.getSheetCommandTarget({ unitId, subUnitId });
|
||||
if (!target) return;
|
||||
|
||||
const { workbook, worksheet } = target;
|
||||
|
||||
const eventParams: ISheetNoteDeleteEventParams = {
|
||||
workbook,
|
||||
worksheet,
|
||||
row: oldNote.row,
|
||||
col: oldNote.col,
|
||||
oldNote,
|
||||
};
|
||||
this.fireEvent(this.Event.SheetNoteDelete, eventParams);
|
||||
}
|
||||
});
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
this.disposeWithMe(
|
||||
this.registerEventHandler(
|
||||
this.Event.SheetNoteUpdate,
|
||||
() => {
|
||||
const model = injector.get(SheetsNoteModel);
|
||||
return model.change$.subscribe((change) => {
|
||||
if (change.type === 'update' && change.oldNote && change.newNote) {
|
||||
const { unitId, subUnitId, oldNote, newNote } = change;
|
||||
const target = this.getSheetCommandTarget({ unitId, subUnitId });
|
||||
if (!target) return;
|
||||
|
||||
const { workbook, worksheet } = target;
|
||||
|
||||
const eventParams: ISheetNoteUpdateEventParams = {
|
||||
workbook,
|
||||
worksheet,
|
||||
row: newNote.row,
|
||||
col: newNote.col,
|
||||
note: newNote,
|
||||
oldNote,
|
||||
};
|
||||
this.fireEvent(this.Event.SheetNoteUpdate, eventParams);
|
||||
}
|
||||
});
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
this.disposeWithMe(
|
||||
this.registerEventHandler(
|
||||
this.Event.SheetNoteShow,
|
||||
() => {
|
||||
const model = injector.get(SheetsNoteModel);
|
||||
return model.change$.subscribe((change) => {
|
||||
if (change.type === 'update' && change.oldNote && change.newNote && !change.oldNote.show && change.newNote.show) {
|
||||
const { unitId, subUnitId, newNote } = change;
|
||||
const target = this.getSheetCommandTarget({ unitId, subUnitId });
|
||||
if (!target) return;
|
||||
|
||||
const { workbook, worksheet } = target;
|
||||
|
||||
const eventParams: ISheetNoteShowEventParams = {
|
||||
workbook,
|
||||
worksheet,
|
||||
row: newNote.row,
|
||||
col: newNote.col,
|
||||
};
|
||||
this.fireEvent(this.Event.SheetNoteShow, eventParams);
|
||||
}
|
||||
});
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
this.disposeWithMe(
|
||||
this.registerEventHandler(
|
||||
this.Event.SheetNoteHide,
|
||||
() => {
|
||||
const model = injector.get(SheetsNoteModel);
|
||||
return model.change$.subscribe((change) => {
|
||||
if (change.type === 'update' && change.oldNote && change.newNote && change.oldNote.show && !change.newNote.show) {
|
||||
const { unitId, subUnitId, newNote } = change;
|
||||
const target = this.getSheetCommandTarget({ unitId, subUnitId });
|
||||
if (!target) return;
|
||||
|
||||
const { workbook, worksheet } = target;
|
||||
|
||||
const eventParams: ISheetNoteHideEventParams = {
|
||||
workbook,
|
||||
worksheet,
|
||||
row: newNote.row,
|
||||
col: newNote.col,
|
||||
};
|
||||
this.fireEvent(this.Event.SheetNoteHide, eventParams);
|
||||
}
|
||||
});
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
this.disposeWithMe(
|
||||
this.registerEventHandler(
|
||||
this.Event.BeforeSheetNoteAdd,
|
||||
() => commandService.beforeCommandExecuted((command) => {
|
||||
if (command.id === SheetUpdateNoteCommand.id) {
|
||||
const params = command.params as IUpdateNoteMutationParams;
|
||||
const target = this.getSheetCommandTarget(params);
|
||||
if (!target) return;
|
||||
|
||||
const { workbook, worksheet, unitId, subUnitId } = target;
|
||||
const { row, col, note } = params;
|
||||
|
||||
const oldNote = injector.get(SheetsNoteModel).getNote(unitId, subUnitId, { noteId: note.id, row, col });
|
||||
if (oldNote) return;
|
||||
|
||||
const eventParams: ISheetNoteAddEventParams = {
|
||||
workbook,
|
||||
worksheet,
|
||||
row,
|
||||
col,
|
||||
note,
|
||||
};
|
||||
const cancel = this.fireEvent(this.Event.BeforeSheetNoteAdd, eventParams);
|
||||
|
||||
if (cancel) {
|
||||
throw new CanceledError();
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
this.disposeWithMe(
|
||||
this.registerEventHandler(
|
||||
this.Event.BeforeSheetNoteDelete,
|
||||
() => commandService.beforeCommandExecuted((command) => {
|
||||
if (command.id === SheetDeleteNoteCommand.id) {
|
||||
const target = this.getSheetCommandTarget();
|
||||
if (!target) return;
|
||||
|
||||
const selection = injector.get(SheetsSelectionsService).getCurrentLastSelection();
|
||||
if (!selection?.primary) return;
|
||||
|
||||
const { workbook, worksheet, unitId, subUnitId } = target;
|
||||
const { actualRow: row, actualColumn: col } = selection.primary;
|
||||
|
||||
const oldNote = injector.get(SheetsNoteModel).getNote(unitId, subUnitId, { row, col });
|
||||
if (!oldNote) return;
|
||||
|
||||
const eventParams: ISheetNoteDeleteEventParams = {
|
||||
workbook,
|
||||
worksheet,
|
||||
row,
|
||||
col,
|
||||
oldNote,
|
||||
};
|
||||
const cancel = this.fireEvent(this.Event.BeforeSheetNoteDelete, eventParams);
|
||||
|
||||
if (cancel) {
|
||||
throw new CanceledError();
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
this.disposeWithMe(
|
||||
this.registerEventHandler(
|
||||
this.Event.BeforeSheetNoteUpdate,
|
||||
() => commandService.beforeCommandExecuted((command) => {
|
||||
if (command.id === SheetUpdateNoteCommand.id) {
|
||||
const params = command.params as IUpdateNoteMutationParams;
|
||||
const target = this.getSheetCommandTarget(params);
|
||||
if (!target) return;
|
||||
|
||||
const { workbook, worksheet, unitId, subUnitId } = target;
|
||||
const { row, col, note } = params;
|
||||
|
||||
const oldNote = injector.get(SheetsNoteModel).getNote(unitId, subUnitId, { row, col });
|
||||
if (!oldNote) return;
|
||||
|
||||
const eventParams: ISheetNoteUpdateEventParams = {
|
||||
workbook,
|
||||
worksheet,
|
||||
row,
|
||||
col,
|
||||
note,
|
||||
oldNote,
|
||||
};
|
||||
const cancel = this.fireEvent(this.Event.BeforeSheetNoteUpdate, eventParams);
|
||||
|
||||
if (cancel) {
|
||||
throw new CanceledError();
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
this.disposeWithMe(
|
||||
this.registerEventHandler(
|
||||
this.Event.BeforeSheetNoteShow,
|
||||
() => commandService.beforeCommandExecuted((command) => {
|
||||
if (command.id === SheetToggleNotePopupCommand.id) {
|
||||
const target = this.getSheetCommandTarget();
|
||||
if (!target) return;
|
||||
|
||||
const selection = injector.get(SheetsSelectionsService).getCurrentLastSelection();
|
||||
if (!selection?.primary) return;
|
||||
|
||||
const { workbook, worksheet, unitId, subUnitId } = target;
|
||||
const { actualRow: row, actualColumn: col } = selection.primary;
|
||||
|
||||
const note = injector.get(SheetsNoteModel).getNote(unitId, subUnitId, { row, col });
|
||||
if (!note || note.show) return;
|
||||
|
||||
const eventParams: ISheetNoteShowEventParams = {
|
||||
workbook,
|
||||
worksheet,
|
||||
row,
|
||||
col,
|
||||
};
|
||||
const cancel = this.fireEvent(this.Event.BeforeSheetNoteShow, eventParams);
|
||||
|
||||
if (cancel) {
|
||||
throw new CanceledError();
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
this.disposeWithMe(
|
||||
this.registerEventHandler(
|
||||
this.Event.BeforeSheetNoteHide,
|
||||
() => commandService.beforeCommandExecuted((command) => {
|
||||
if (command.id === SheetToggleNotePopupCommand.id) {
|
||||
const target = this.getSheetCommandTarget();
|
||||
if (!target) return;
|
||||
|
||||
const selection = injector.get(SheetsSelectionsService).getCurrentLastSelection();
|
||||
if (!selection?.primary) return;
|
||||
|
||||
const { workbook, worksheet, unitId, subUnitId } = target;
|
||||
const { actualRow: row, actualColumn: col } = selection.primary;
|
||||
|
||||
const note = injector.get(SheetsNoteModel).getNote(unitId, subUnitId, { row, col });
|
||||
if (!note || !note.show) return;
|
||||
|
||||
const eventParams: ISheetNoteHideEventParams = {
|
||||
workbook,
|
||||
worksheet,
|
||||
row,
|
||||
col,
|
||||
};
|
||||
const cancel = this.fireEvent(this.Event.BeforeSheetNoteHide, eventParams);
|
||||
|
||||
if (cancel) {
|
||||
throw new CanceledError();
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
FUniver.extend(FUniverSheetsNoteMixin);
|
||||
60
packages/sheets-note/src/facade/f-worksheet.ts
Normal file
60
packages/sheets-note/src/facade/f-worksheet.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { ISheetNote } from '@univerjs/sheets-note';
|
||||
import { SheetsNoteModel } from '@univerjs/sheets-note';
|
||||
import { FWorksheet } from '@univerjs/sheets/facade';
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
export interface IFWorksheetNoteMixin {
|
||||
/**
|
||||
* Get all annotations in the worksheet
|
||||
* @returns {ISheetNote[]} An array of all annotations in the worksheet
|
||||
* @example
|
||||
* ```ts
|
||||
* const fWorkbook = univerAPI.getActiveWorkbook();
|
||||
* const fWorksheet = fWorkbook.getSheetByName('Sheet1');
|
||||
* if (!fWorksheet) return;
|
||||
* const notes = fWorksheet.getNotes();
|
||||
* console.log(notes);
|
||||
*
|
||||
* notes.forEach((item) => {
|
||||
* const { row, col, note } = item;
|
||||
* console.log(`Cell ${fWorksheet.getRange(row, col).getA1Notation()} has a note: ${note}`);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
getNotes(): ISheetNote[];
|
||||
}
|
||||
|
||||
export class FWorksheetNoteMixin extends FWorksheet implements IFWorksheetNoteMixin {
|
||||
override getNotes(): ISheetNote[] {
|
||||
const model = this._injector.get(SheetsNoteModel);
|
||||
const notes = model.getSheetNotes(this.getWorkbook().getUnitId(), this.getSheetId());
|
||||
if (!notes) {
|
||||
return [];
|
||||
}
|
||||
return Array.from(notes.values()).map((note) => ({ ...note }));
|
||||
}
|
||||
}
|
||||
|
||||
FWorksheet.extend(FWorksheetNoteMixin);
|
||||
declare module '@univerjs/sheets/facade' {
|
||||
// eslint-disable-next-line ts/naming-convention
|
||||
interface FWorksheet extends IFWorksheetNoteMixin { }
|
||||
}
|
||||
25
packages/sheets-note/src/facade/index.ts
Normal file
25
packages/sheets-note/src/facade/index.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import './f-event';
|
||||
import './f-range';
|
||||
import './f-univer';
|
||||
import './f-worksheet';
|
||||
|
||||
export * from './f-event';
|
||||
export * from './f-range';
|
||||
export * from './f-univer';
|
||||
export * from './f-worksheet';
|
||||
22
packages/sheets-note/src/index.ts
Normal file
22
packages/sheets-note/src/index.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export * from './commands/commands/note.command';
|
||||
export * from './commands/mutations/note.mutation';
|
||||
export type { IUniverSheetsNoteConfig } from './config/config';
|
||||
export * from './controllers/sheets-note-resource.controller';
|
||||
export * from './models/sheets-note.model';
|
||||
export { UniverSheetsNotePlugin } from './plugin';
|
||||
@@ -0,0 +1,160 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { ISheetNote } from '../sheets-note.model';
|
||||
import { Injector } from '@univerjs/core';
|
||||
import { filter, firstValueFrom, take, toArray } from 'rxjs';
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
import { SheetsNoteModel } from '../sheets-note.model';
|
||||
|
||||
describe('SheetsNoteModel', () => {
|
||||
let injector: Injector;
|
||||
let model: SheetsNoteModel;
|
||||
|
||||
const unitId = 'unit-1';
|
||||
const subUnitId = 'sheet-1';
|
||||
|
||||
beforeEach(() => {
|
||||
injector = new Injector([
|
||||
[SheetsNoteModel],
|
||||
]);
|
||||
model = injector.get(SheetsNoteModel);
|
||||
});
|
||||
|
||||
it('updates a note and emits change events', async () => {
|
||||
const noteInput: ISheetNote = {
|
||||
id: 'note-1',
|
||||
row: 0,
|
||||
col: 0,
|
||||
width: 160,
|
||||
height: 72,
|
||||
note: 'hello',
|
||||
show: false,
|
||||
};
|
||||
|
||||
const firstChangePromise = firstValueFrom(model.change$.pipe(take(1)));
|
||||
model.updateNote(unitId, subUnitId, 0, 0, noteInput);
|
||||
const firstChange = await firstChangePromise;
|
||||
|
||||
expect(firstChange.type).toBe('update');
|
||||
expect(firstChange.oldNote == null).toBe(true);
|
||||
expect(firstChange.newNote?.note).toBe('hello');
|
||||
|
||||
const secondChangePromise = firstValueFrom(model.change$.pipe(take(1)));
|
||||
model.updateNote(unitId, subUnitId, 0, 0, { ...noteInput, note: 'updated' });
|
||||
const secondChange = await secondChangePromise;
|
||||
|
||||
expect(secondChange.type).toBe('update');
|
||||
expect(secondChange.oldNote?.note).toBe('hello');
|
||||
expect(secondChange.newNote?.note).toBe('updated');
|
||||
|
||||
const stored = model.getNote(unitId, subUnitId, { row: 0, col: 0 });
|
||||
expect(stored?.note).toBe('updated');
|
||||
});
|
||||
|
||||
it('toggles note popup state and exposes show notes stream', async () => {
|
||||
const noteInput: ISheetNote = {
|
||||
id: 'note-2',
|
||||
row: 2,
|
||||
col: 3,
|
||||
width: 160,
|
||||
height: 72,
|
||||
note: 'note',
|
||||
show: false,
|
||||
};
|
||||
|
||||
model.updateNote(unitId, subUnitId, 2, 3, noteInput);
|
||||
|
||||
const showNotesPromise = firstValueFrom(
|
||||
model.getSheetShowNotes$(unitId, subUnitId).pipe(
|
||||
take(2),
|
||||
toArray()
|
||||
)
|
||||
);
|
||||
|
||||
model.toggleNotePopup(unitId, subUnitId, { row: 2, col: 3 });
|
||||
model.toggleNotePopup(unitId, subUnitId, { row: 2, col: 3 });
|
||||
|
||||
const [afterShow, afterHide] = await showNotesPromise;
|
||||
expect(afterShow).toHaveLength(1);
|
||||
expect(afterShow[0].note.show).toBe(true);
|
||||
expect(afterShow[0].loc.row).toBe(2);
|
||||
expect(afterShow[0].loc.col).toBe(3);
|
||||
|
||||
expect(afterHide).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('updates note position and emits ref change', async () => {
|
||||
const noteInput: ISheetNote = {
|
||||
id: 'note-3',
|
||||
row: 1,
|
||||
col: 1,
|
||||
width: 160,
|
||||
height: 72,
|
||||
note: 'note',
|
||||
};
|
||||
|
||||
model.updateNote(unitId, subUnitId, 1, 1, noteInput);
|
||||
|
||||
const refChangePromise = firstValueFrom(
|
||||
model.change$.pipe(
|
||||
filter((c) => c.type === 'ref'),
|
||||
take(1)
|
||||
)
|
||||
);
|
||||
|
||||
model.updateNotePosition(unitId, subUnitId, { row: 1, col: 1, newRow: 4, newCol: 5 });
|
||||
const refChange = await refChangePromise;
|
||||
|
||||
expect(refChange.oldNote?.row).toBe(1);
|
||||
expect(refChange.oldNote?.col).toBe(1);
|
||||
expect(refChange.newNote.row).toBe(4);
|
||||
expect(refChange.newNote.col).toBe(5);
|
||||
|
||||
expect(model.getNote(unitId, subUnitId, { row: 1, col: 1 })).toBeUndefined();
|
||||
expect(model.getNote(unitId, subUnitId, { row: 4, col: 5 })?.id).toBe('note-3');
|
||||
});
|
||||
|
||||
it('removes notes and does nothing if note is missing', async () => {
|
||||
const noteInput: ISheetNote = {
|
||||
id: 'note-4',
|
||||
row: 6,
|
||||
col: 7,
|
||||
width: 160,
|
||||
height: 72,
|
||||
note: 'note',
|
||||
};
|
||||
model.updateNote(unitId, subUnitId, 6, 7, noteInput);
|
||||
|
||||
const removeChangePromise = firstValueFrom(model.change$.pipe(take(1)));
|
||||
model.removeNote(unitId, subUnitId, { row: 6, col: 7 });
|
||||
const removeChange = await removeChangePromise;
|
||||
|
||||
expect(removeChange.type).toBe('update');
|
||||
expect(removeChange.oldNote?.id).toBe('note-4');
|
||||
expect(removeChange.newNote).toBeNull();
|
||||
expect(model.getNote(unitId, subUnitId, { row: 6, col: 7 })).toBeUndefined();
|
||||
|
||||
let emitted = false;
|
||||
const subscription = model.change$.subscribe(() => {
|
||||
emitted = true;
|
||||
});
|
||||
|
||||
model.removeNote(unitId, subUnitId, { row: 99, col: 99 });
|
||||
expect(emitted).toBe(false);
|
||||
subscription.unsubscribe();
|
||||
});
|
||||
});
|
||||
206
packages/sheets-note/src/models/sheets-note.model.ts
Normal file
206
packages/sheets-note/src/models/sheets-note.model.ts
Normal file
@@ -0,0 +1,206 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Nullable } from '@univerjs/core';
|
||||
import type { ISheetLocationBase } from '@univerjs/sheets';
|
||||
import { Disposable, generateRandomId } from '@univerjs/core';
|
||||
import { filter, map, Subject } from 'rxjs';
|
||||
|
||||
export interface ISheetNote {
|
||||
id: string;
|
||||
row: number;
|
||||
col: number;
|
||||
width: number;
|
||||
height: number;
|
||||
note: string;
|
||||
show?: boolean;
|
||||
}
|
||||
|
||||
export type ISheetNoteChange = {
|
||||
unitId: string;
|
||||
subUnitId: string;
|
||||
oldNote: Nullable<ISheetNote>;
|
||||
silent?: boolean;
|
||||
} & ({
|
||||
type: 'update';
|
||||
newNote: Nullable<ISheetNote>;
|
||||
} | {
|
||||
type: 'ref';
|
||||
newNote: ISheetNote;
|
||||
});
|
||||
|
||||
export class SheetsNoteModel extends Disposable {
|
||||
private _notesMap: Map<string, Map<string, Map<string, ISheetNote>>> = new Map();
|
||||
private readonly _change$ = new Subject<ISheetNoteChange>();
|
||||
readonly change$ = this._change$.asObservable();
|
||||
|
||||
private _ensureNotesMap(unitId: string, subUnitId: string): Map<string, ISheetNote> {
|
||||
let unitMap = this._notesMap.get(unitId);
|
||||
if (!unitMap) {
|
||||
unitMap = new Map();
|
||||
this._notesMap.set(unitId, unitMap);
|
||||
}
|
||||
|
||||
let subUnitMap = unitMap.get(subUnitId);
|
||||
if (!subUnitMap) {
|
||||
subUnitMap = new Map();
|
||||
unitMap.set(subUnitId, subUnitMap);
|
||||
}
|
||||
|
||||
return subUnitMap;
|
||||
}
|
||||
|
||||
private _getNoteByPosition(unitId: string, subUnitId: string, row: number, col: number): Nullable<ISheetNote> {
|
||||
const subUnitMap = this._ensureNotesMap(unitId, subUnitId);
|
||||
|
||||
for (const [_, note] of subUnitMap) {
|
||||
if (note.row === row && note.col === col) {
|
||||
return note;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _getNoteById(unitId: string, subUnitId: string, id: string): Nullable<ISheetNote> {
|
||||
const subUnitMap = this._ensureNotesMap(unitId, subUnitId);
|
||||
return subUnitMap.get(id);
|
||||
}
|
||||
|
||||
private _getNoteByParams(unitId: string, subUnitId: string, params: { noteId?: string; row?: number; col?: number }): Nullable<ISheetNote> {
|
||||
const { noteId, row, col } = params;
|
||||
|
||||
if (noteId) {
|
||||
return this._getNoteById(unitId, subUnitId, noteId);
|
||||
}
|
||||
|
||||
if (row !== undefined && col !== undefined) {
|
||||
return this._getNoteByPosition(unitId, subUnitId, row, col);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
getSheetShowNotes$(unitId: string, subUnitId: string) {
|
||||
return this._change$.pipe(
|
||||
filter(({ unitId: u, subUnitId: s }) => u === unitId && s === subUnitId),
|
||||
map(() => {
|
||||
const subUnitMap = this._ensureNotesMap(unitId, subUnitId);
|
||||
const notes: { loc: ISheetLocationBase; note: ISheetNote }[] = [];
|
||||
|
||||
for (const [_, note] of subUnitMap) {
|
||||
if (note.show) {
|
||||
notes.push({ loc: { row: note.row, col: note.col, unitId, subUnitId }, note });
|
||||
}
|
||||
}
|
||||
|
||||
return notes;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
getCellNoteChange$(unitId: string, subUnitId: string, row: number, col: number) {
|
||||
return this._change$.pipe(
|
||||
filter(({ unitId: u, subUnitId: s, oldNote }) => {
|
||||
if (u !== unitId || s !== subUnitId || !oldNote) {
|
||||
return false;
|
||||
}
|
||||
return oldNote.row === row && oldNote.col === col;
|
||||
}),
|
||||
map((newNote) => newNote)
|
||||
);
|
||||
}
|
||||
|
||||
updateNote(unitId: string, subUnitId: string, row: number, col: number, note: Partial<ISheetNote>, silent?: boolean): void {
|
||||
const oldNote = this._getNoteByParams(unitId, subUnitId, { noteId: note?.id, row, col });
|
||||
const subUnitMap = this._ensureNotesMap(unitId, subUnitId);
|
||||
const newNote = {
|
||||
...note,
|
||||
id: oldNote?.id || note.id || generateRandomId(6),
|
||||
row,
|
||||
col,
|
||||
} as ISheetNote;
|
||||
subUnitMap.set(newNote.id, newNote);
|
||||
|
||||
this._change$.next({ unitId, subUnitId, oldNote, type: 'update', newNote, silent });
|
||||
}
|
||||
|
||||
removeNote(unitId: string, subUnitId: string, params: { noteId?: string; row?: number; col?: number; silent?: boolean }): void {
|
||||
const { noteId, row, col, silent } = params;
|
||||
const oldNote = this._getNoteByParams(unitId, subUnitId, { noteId, row, col });
|
||||
|
||||
if (!oldNote) {
|
||||
return;
|
||||
}
|
||||
|
||||
const subUnitMap = this._ensureNotesMap(unitId, subUnitId);
|
||||
subUnitMap.delete(oldNote.id);
|
||||
|
||||
this._change$.next({ unitId, subUnitId, oldNote, type: 'update', newNote: null, silent });
|
||||
}
|
||||
|
||||
toggleNotePopup(unitId: string, subUnitId: string, params: { noteId?: string; row?: number; col?: number; silent?: boolean }): void {
|
||||
const { noteId, row, col, silent } = params;
|
||||
const oldNote = this._getNoteByParams(unitId, subUnitId, { noteId, row, col });
|
||||
|
||||
if (!oldNote) {
|
||||
return;
|
||||
}
|
||||
|
||||
const subUnitMap = this._ensureNotesMap(unitId, subUnitId);
|
||||
const newNote = { ...oldNote, show: !oldNote.show };
|
||||
subUnitMap.set(newNote.id, newNote);
|
||||
|
||||
this._change$.next({ unitId, subUnitId, oldNote, type: 'update', newNote, silent });
|
||||
}
|
||||
|
||||
updateNotePosition(unitId: string, subUnitId: string, params: { noteId?: string; row?: number; col?: number; newRow: number; newCol: number; silent?: boolean }): void {
|
||||
const { noteId, row, col, newRow, newCol, silent } = params;
|
||||
const oldNote = this._getNoteByParams(unitId, subUnitId, { noteId, row, col });
|
||||
|
||||
if (!oldNote) {
|
||||
return;
|
||||
}
|
||||
|
||||
const subUnitMap = this._ensureNotesMap(unitId, subUnitId);
|
||||
const newNote = { ...oldNote, row: newRow, col: newCol };
|
||||
subUnitMap.set(newNote.id, newNote);
|
||||
|
||||
this._change$.next({ unitId, subUnitId, oldNote, type: 'ref', newNote, silent });
|
||||
}
|
||||
|
||||
getNote(unitId: string, subUnitId: string, params: { noteId?: string; row?: number; col?: number }): Nullable<ISheetNote> {
|
||||
return this._getNoteByParams(unitId, subUnitId, params);
|
||||
}
|
||||
|
||||
getNotes() {
|
||||
return this._notesMap;
|
||||
}
|
||||
|
||||
getUnitNotes(unitId: string): Map<string, Map<string, ISheetNote>> | undefined {
|
||||
return this._notesMap.get(unitId);
|
||||
}
|
||||
|
||||
getSheetNotes(unitId: string, subUnitId: string): Map<string, ISheetNote> | undefined {
|
||||
const unitMap = this._notesMap.get(unitId);
|
||||
if (!unitMap) {
|
||||
return undefined;
|
||||
}
|
||||
return unitMap.get(subUnitId);
|
||||
}
|
||||
|
||||
deleteUnitNotes(unitId: string): void {
|
||||
this._notesMap.delete(unitId);
|
||||
}
|
||||
}
|
||||
74
packages/sheets-note/src/plugin.ts
Normal file
74
packages/sheets-note/src/plugin.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Dependency } from '@univerjs/core';
|
||||
import type { IUniverSheetsNoteConfig } from './config/config';
|
||||
import { DependentOn, IConfigService, Inject, Injector, merge, Plugin, touchDependencies, UniverInstanceType } from '@univerjs/core';
|
||||
import { UniverSheetsPlugin } from '@univerjs/sheets';
|
||||
import pkg from '../package.json';
|
||||
import { defaultPluginConfig, SHEETS_NOTE_PLUGIN_CONFIG_KEY } from './config/config';
|
||||
import { PLUGIN_NAME } from './const';
|
||||
import { SheetsNoteRefRangeController } from './controllers/sheets-note-ref-range.controller';
|
||||
import { SheetsNoteResourceController } from './controllers/sheets-note-resource.controller';
|
||||
import { SheetsNoteController } from './controllers/sheets.note.controller';
|
||||
import { SheetsNoteModel } from './models/sheets-note.model';
|
||||
|
||||
@DependentOn(UniverSheetsPlugin)
|
||||
export class UniverSheetsNotePlugin extends Plugin {
|
||||
static override pluginName = PLUGIN_NAME;
|
||||
static override packageName = pkg.name;
|
||||
static override version = pkg.version;
|
||||
static override type = UniverInstanceType.UNIVER_SHEET;
|
||||
|
||||
constructor(
|
||||
private readonly _config: IUniverSheetsNoteConfig = defaultPluginConfig,
|
||||
@IConfigService private readonly _configService: IConfigService,
|
||||
@Inject(Injector) protected override readonly _injector: Injector
|
||||
) {
|
||||
super();
|
||||
// Manage the plugin configuration.
|
||||
const { ...rest } = merge(
|
||||
{},
|
||||
defaultPluginConfig,
|
||||
this._config
|
||||
);
|
||||
|
||||
this._configService.setConfig(SHEETS_NOTE_PLUGIN_CONFIG_KEY, rest);
|
||||
}
|
||||
|
||||
override onStarting(): void {
|
||||
([
|
||||
[SheetsNoteModel],
|
||||
[SheetsNoteController],
|
||||
[SheetsNoteResourceController],
|
||||
[SheetsNoteRefRangeController],
|
||||
] as Dependency[]).forEach((dependency) => {
|
||||
this._injector.add(dependency);
|
||||
});
|
||||
|
||||
touchDependencies(this._injector, [
|
||||
[SheetsNoteModel],
|
||||
[SheetsNoteController],
|
||||
[SheetsNoteResourceController],
|
||||
]);
|
||||
}
|
||||
|
||||
override onReady(): void {
|
||||
touchDependencies(this._injector, [
|
||||
[SheetsNoteRefRangeController],
|
||||
]);
|
||||
}
|
||||
}
|
||||
8
packages/sheets-note/tsconfig.json
Normal file
8
packages/sheets-note/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "@univerjs-infra/shared/tsconfigs/base",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "lib/types"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
18
packages/sheets-note/tsconfig.node.json
Normal file
18
packages/sheets-note/tsconfig.node.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": "@univerjs-infra/shared/tsconfigs/node",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"declaration": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"noEmit": false,
|
||||
"outDir": "lib/types"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"exclude": [
|
||||
"src/**/__tests__/**",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.test.ts"
|
||||
]
|
||||
}
|
||||
3
packages/sheets-note/vitest.config.ts
Normal file
3
packages/sheets-note/vitest.config.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import createConfig from '@univerjs-infra/shared/vitest';
|
||||
|
||||
export default createConfig();
|
||||
Reference in New Issue
Block a user