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-ui/README.md
Normal file
41
packages/sheets-note-ui/README.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# @univerjs/sheets-note-ui
|
||||
|
||||
[](https://npmjs.com/package/@univerjs/sheets-note-ui)
|
||||
[](https://npmjs.com/package/@univerjs/sheets-note-ui)
|
||||
[](https://npmjs.com/package/@univerjs/sheets-note-ui)
|
||||
|
||||
`@univerjs/sheets-note-ui` adds the UI for viewing and editing cell notes in Univer Sheets.
|
||||
|
||||
## Package Overview
|
||||
|
||||
| Package | UMD global | CSS | Locales | Facade entry |
|
||||
| --- | --- | :---: | :---: | :---: |
|
||||
| `@univerjs/sheets-note-ui` | `UniverSheetsNoteUi` | Yes | Yes | No |
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
pnpm add @univerjs/sheets-note-ui
|
||||
# or
|
||||
npm install @univerjs/sheets-note-ui
|
||||
```
|
||||
|
||||
Keep all `@univerjs/*` packages on the same version.
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import '@univerjs/sheets-note-ui/lib/index.css';
|
||||
import EnUS from '@univerjs/sheets-note-ui/locale/en-US';
|
||||
import { UniverSheetsNoteUIPlugin } from '@univerjs/sheets-note-ui';
|
||||
|
||||
univer.registerPlugin(UniverSheetsNoteUIPlugin);
|
||||
|
||||
// Merge EnUS into your Univer locale map when this package contributes UI text.
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [Documentation](https://docs.univer.ai)
|
||||
- [NPM package](https://npmjs.com/package/@univerjs/sheets-note-ui)
|
||||
- [GitHub repository](https://github.com/dream-num/univer)
|
||||
94
packages/sheets-note-ui/package.json
Normal file
94
packages/sheets-note-ui/package.json
Normal file
@@ -0,0 +1,94 @@
|
||||
{
|
||||
"name": "@univerjs/sheets-note-ui",
|
||||
"version": "1.0.0-rc.0",
|
||||
"private": false,
|
||||
"description": "Cell note UI 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",
|
||||
"ui"
|
||||
],
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./*": "./src/*",
|
||||
"./locale/*": "./src/locale/*.ts",
|
||||
"./facade": "./src/facade/index.ts"
|
||||
},
|
||||
"main": "./lib/index.js",
|
||||
"module": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"files": [
|
||||
"lib"
|
||||
],
|
||||
"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"
|
||||
},
|
||||
"./locale/*": {
|
||||
"import": "./lib/es/locale/*.js",
|
||||
"require": "./lib/cjs/locale/*.js",
|
||||
"types": "./lib/types/locale/*.d.ts"
|
||||
},
|
||||
"./lib/*": "./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": {
|
||||
"react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc",
|
||||
"rxjs": ">=7.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@univerjs/core": "workspace:*",
|
||||
"@univerjs/design": "workspace:*",
|
||||
"@univerjs/engine-render": "workspace:*",
|
||||
"@univerjs/icons": "1.38.0",
|
||||
"@univerjs/sheets": "workspace:*",
|
||||
"@univerjs/sheets-note": "workspace:*",
|
||||
"@univerjs/sheets-ui": "workspace:*",
|
||||
"@univerjs/ui": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@univerjs-infra/shared": "workspace:*",
|
||||
"postcss": "^8.5.28",
|
||||
"react": "18.3.1",
|
||||
"rxjs": "^7.8.2",
|
||||
"tailwindcss": "3.4.18",
|
||||
"typescript": "^6.0.3",
|
||||
"vitest": "^5.0.0"
|
||||
}
|
||||
}
|
||||
1
packages/sheets-note-ui/postcss.config.mjs
Normal file
1
packages/sheets-note-ui/postcss.config.mjs
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from '@univerjs-infra/shared/postcss';
|
||||
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* 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 { ICommandService, IUniverInstanceService, LocaleType, toDisposable, Univer, UniverInstanceType } from '@univerjs/core';
|
||||
import { SheetsSelectionsService } from '@univerjs/sheets';
|
||||
import { CellPopupManagerService } from '@univerjs/sheets-ui';
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
import { SheetsNotePopupService } from '../../../services/sheets-note-popup.service';
|
||||
import { AddNotePopupOperation } from '../add-note-popup.operation';
|
||||
|
||||
const workbookData: IWorkbookData = {
|
||||
id: 'note-workbook',
|
||||
appVersion: '3.0.0-alpha',
|
||||
name: 'note workbook',
|
||||
locale: LocaleType.EN_US,
|
||||
sheetOrder: ['sheet-1'],
|
||||
styles: {},
|
||||
sheets: {
|
||||
'sheet-1': {
|
||||
id: 'sheet-1',
|
||||
name: 'Sheet 1',
|
||||
cellData: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
class TestCellPopupManagerService {
|
||||
showPopup() {
|
||||
return toDisposable(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
function createSelection(row: number, col: number): ISelectionWithStyle {
|
||||
return {
|
||||
range: {
|
||||
startRow: row,
|
||||
endRow: row,
|
||||
startColumn: col,
|
||||
endColumn: col,
|
||||
},
|
||||
primary: {
|
||||
startRow: row,
|
||||
endRow: row,
|
||||
startColumn: col,
|
||||
endColumn: col,
|
||||
actualRow: row,
|
||||
actualColumn: col,
|
||||
isMerged: false,
|
||||
isMergedMainCell: false,
|
||||
},
|
||||
style: null,
|
||||
};
|
||||
}
|
||||
|
||||
describe('AddNotePopupOperation', () => {
|
||||
let univer: Univer;
|
||||
let commandService: ICommandService;
|
||||
let selectionsService: SheetsSelectionsService;
|
||||
let notePopupService: SheetsNotePopupService;
|
||||
|
||||
beforeEach(() => {
|
||||
univer = new Univer();
|
||||
const injector = univer.__getInjector();
|
||||
injector.add([SheetsSelectionsService]);
|
||||
injector.add([CellPopupManagerService, { useClass: TestCellPopupManagerService as never }]);
|
||||
injector.add([SheetsNotePopupService]);
|
||||
|
||||
univer.createUnit(UniverInstanceType.UNIVER_SHEET, workbookData);
|
||||
injector.get(IUniverInstanceService).focusUnit('note-workbook');
|
||||
commandService = injector.get(ICommandService);
|
||||
commandService.registerCommand(AddNotePopupOperation);
|
||||
selectionsService = injector.get(SheetsSelectionsService);
|
||||
notePopupService = injector.get(SheetsNotePopupService);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
univer.dispose();
|
||||
});
|
||||
|
||||
it('opens a note popup at the active cell in the current worksheet', async () => {
|
||||
selectionsService.setSelections('note-workbook', 'sheet-1', [createSelection(2, 3)]);
|
||||
|
||||
const result = await commandService.executeCommand(AddNotePopupOperation.id, { trigger: 'context-menu' });
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(notePopupService.activePopup).toEqual({
|
||||
unitId: 'note-workbook',
|
||||
subUnitId: 'sheet-1',
|
||||
row: 2,
|
||||
col: 3,
|
||||
temp: false,
|
||||
trigger: 'context-menu',
|
||||
});
|
||||
});
|
||||
|
||||
it('does not open a note popup when there is no active cell selection', async () => {
|
||||
const result = await commandService.executeCommand(AddNotePopupOperation.id);
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(notePopupService.activePopup).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* 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, ICommand, Workbook } from '@univerjs/core';
|
||||
import { CommandType, IUniverInstanceService, UniverInstanceType } from '@univerjs/core';
|
||||
import { SheetsSelectionsService } from '@univerjs/sheets';
|
||||
import { SheetsNotePopupService } from '../../services/sheets-note-popup.service';
|
||||
|
||||
export interface IAddNotePopupOperationParams {
|
||||
trigger?: string;
|
||||
}
|
||||
|
||||
export const AddNotePopupOperation: ICommand = {
|
||||
id: 'sheet.operation.add-note-popup',
|
||||
type: CommandType.OPERATION,
|
||||
handler: async (accessor: IAccessor, params?: IAddNotePopupOperationParams) => {
|
||||
const selectionService = accessor.get(SheetsSelectionsService);
|
||||
const notePopupService = accessor.get(SheetsNotePopupService);
|
||||
const univerInstanceService = accessor.get(IUniverInstanceService);
|
||||
|
||||
const workbook = univerInstanceService.getCurrentUnitOfType<Workbook>(UniverInstanceType.UNIVER_SHEET);
|
||||
if (!workbook) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const worksheet = workbook.getActiveSheet();
|
||||
const lastSelection = selectionService.getCurrentLastSelection();
|
||||
if (!lastSelection?.primary) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { primary } = lastSelection;
|
||||
|
||||
notePopupService.showPopup({
|
||||
unitId: workbook.getUnitId(),
|
||||
subUnitId: worksheet.getSheetId(),
|
||||
row: primary.actualRow,
|
||||
col: primary.actualColumn,
|
||||
temp: false,
|
||||
trigger: params?.trigger ?? 'add-note',
|
||||
});
|
||||
|
||||
return true;
|
||||
},
|
||||
};
|
||||
32
packages/sheets-note-ui/src/config/config.ts
Normal file
32
packages/sheets-note-ui/src/config/config.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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 { MenuConfig } from '@univerjs/ui';
|
||||
|
||||
export const SHEETS_NOTE_UI_PLUGIN_CONFIG_KEY = 'sheets-note-ui.config';
|
||||
|
||||
export const configSymbol = Symbol(SHEETS_NOTE_UI_PLUGIN_CONFIG_KEY);
|
||||
|
||||
export interface IUniverSheetsNoteUIConfig {
|
||||
menu?: MenuConfig;
|
||||
|
||||
defaultNoteSize?: {
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
}
|
||||
|
||||
export const defaultPluginConfig: IUniverSheetsNoteUIConfig = {};
|
||||
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* 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 { IUniverInstanceService, LocaleType, toDisposable, Univer, UniverInstanceType } from '@univerjs/core';
|
||||
import { SheetsNoteModel } from '@univerjs/sheets-note';
|
||||
import { CellPopupManagerService } from '@univerjs/sheets-ui';
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
import { SheetsNotePopupService } from '../../services/sheets-note-popup.service';
|
||||
import { SheetsNoteAttachmentController } from '../sheets-note-attachment.controller';
|
||||
|
||||
const WORKBOOK_DATA: IWorkbookData = {
|
||||
id: 'note-host',
|
||||
appVersion: '3.0.0-alpha',
|
||||
name: 'Note host',
|
||||
locale: LocaleType.EN_US,
|
||||
sheetOrder: ['sheet-1'],
|
||||
styles: {},
|
||||
sheets: {
|
||||
'sheet-1': {
|
||||
id: 'sheet-1',
|
||||
name: 'Sheet 1',
|
||||
cellData: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
class RecordingCellPopupManagerService {
|
||||
static activeCount = 0;
|
||||
|
||||
showPopup() {
|
||||
RecordingCellPopupManagerService.activeCount += 1;
|
||||
return toDisposable(() => {
|
||||
RecordingCellPopupManagerService.activeCount -= 1;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
describe('SheetsNoteAttachmentController', () => {
|
||||
let univer: Univer;
|
||||
|
||||
beforeEach(() => {
|
||||
RecordingCellPopupManagerService.activeCount = 0;
|
||||
univer = new Univer();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
univer.dispose();
|
||||
});
|
||||
|
||||
it('hides persistent Notes while a host is suppressed and restores current model state afterwards', () => {
|
||||
const injector = univer.__getInjector();
|
||||
injector.add([SheetsNoteModel]);
|
||||
injector.add([CellPopupManagerService, { useClass: RecordingCellPopupManagerService as never }]);
|
||||
injector.add([SheetsNotePopupService]);
|
||||
injector.add([SheetsNoteAttachmentController]);
|
||||
univer.createUnit(UniverInstanceType.UNIVER_SHEET, WORKBOOK_DATA);
|
||||
injector.get(IUniverInstanceService).focusUnit(WORKBOOK_DATA.id);
|
||||
|
||||
const noteModel = injector.get(SheetsNoteModel);
|
||||
noteModel.updateNote(WORKBOOK_DATA.id, 'sheet-1', 1, 2, {
|
||||
id: 'note-1',
|
||||
width: 160,
|
||||
height: 60,
|
||||
note: 'First note',
|
||||
show: true,
|
||||
});
|
||||
const controller = injector.get(SheetsNoteAttachmentController);
|
||||
expect(RecordingCellPopupManagerService.activeCount).toBe(1);
|
||||
|
||||
controller.setPopupSuppressed(WORKBOOK_DATA.id, true);
|
||||
expect(RecordingCellPopupManagerService.activeCount).toBe(0);
|
||||
|
||||
noteModel.updateNote(WORKBOOK_DATA.id, 'sheet-1', 3, 4, {
|
||||
id: 'note-2',
|
||||
width: 160,
|
||||
height: 60,
|
||||
note: 'Second note',
|
||||
show: true,
|
||||
});
|
||||
expect(RecordingCellPopupManagerService.activeCount).toBe(0);
|
||||
|
||||
controller.setPopupSuppressed(WORKBOOK_DATA.id, false);
|
||||
expect(RecordingCellPopupManagerService.activeCount).toBe(2);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* 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, Inject } from '@univerjs/core';
|
||||
import { AddNoteIcon, DeleteNoteIcon, HideNoteIcon } from '@univerjs/icons';
|
||||
import { ComponentManager, IconManager } from '@univerjs/ui';
|
||||
import { SHEET_NOTE_COMPONENT } from '../views/config';
|
||||
import { SheetsNote } from '../views/Note';
|
||||
|
||||
export class ComponentsController extends Disposable {
|
||||
constructor(
|
||||
@Inject(ComponentManager) private readonly _componentManager: ComponentManager,
|
||||
@Inject(IconManager) private readonly _iconManager: IconManager
|
||||
) {
|
||||
super();
|
||||
|
||||
this._registerComponents();
|
||||
this._registerIcons();
|
||||
}
|
||||
|
||||
private _registerComponents(): void {
|
||||
([
|
||||
[SHEET_NOTE_COMPONENT, SheetsNote],
|
||||
] as const).forEach(([key, comp]) => {
|
||||
this.disposeWithMe(
|
||||
this._componentManager.register(key, comp)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private _registerIcons(): void {
|
||||
this.disposeWithMe(this._iconManager.register({
|
||||
AddNoteIcon,
|
||||
DeleteNoteIcon,
|
||||
HideNoteIcon,
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* 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 { Workbook } from '@univerjs/core';
|
||||
import { Disposable, Inject, InterceptorEffectEnum, IUniverInstanceService, UniverInstanceType } from '@univerjs/core';
|
||||
import { IRenderManagerService } from '@univerjs/engine-render';
|
||||
import { INTERCEPTOR_POINT, SheetInterceptorService } from '@univerjs/sheets';
|
||||
import { SheetsNoteModel } from '@univerjs/sheets-note';
|
||||
import { debounceTime } from 'rxjs';
|
||||
|
||||
export class SheetsCellContentController extends Disposable {
|
||||
constructor(
|
||||
@Inject(SheetInterceptorService) private readonly _sheetInterceptorService: SheetInterceptorService,
|
||||
@Inject(SheetsNoteModel) private readonly _sheetsNoteModel: SheetsNoteModel,
|
||||
@IRenderManagerService private readonly _renderManagerService: IRenderManagerService,
|
||||
@IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService
|
||||
) {
|
||||
super();
|
||||
this._initViewModelIntercept();
|
||||
this._initSkeletonChange();
|
||||
}
|
||||
|
||||
private _initViewModelIntercept() {
|
||||
this.disposeWithMe(
|
||||
this._sheetInterceptorService.intercept(
|
||||
INTERCEPTOR_POINT.CELL_CONTENT,
|
||||
{
|
||||
effect: InterceptorEffectEnum.Style,
|
||||
handler: (cell, pos, next) => {
|
||||
const { row, col, unitId, subUnitId } = pos;
|
||||
const note = this._sheetsNoteModel.getNote(unitId, subUnitId, { row, col });
|
||||
if (note) {
|
||||
if (!cell || cell === pos.rawData) {
|
||||
cell = { ...pos.rawData };
|
||||
}
|
||||
|
||||
cell.markers = {
|
||||
...cell?.markers,
|
||||
tr: {
|
||||
color: '#FFBD37',
|
||||
size: 6,
|
||||
},
|
||||
};
|
||||
|
||||
return next(cell);
|
||||
}
|
||||
|
||||
return next(cell);
|
||||
},
|
||||
priority: 100,
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private _initSkeletonChange() {
|
||||
const markSkeletonDirty = () => {
|
||||
const workbook = this._univerInstanceService.getCurrentUnitOfType<Workbook>(UniverInstanceType.UNIVER_SHEET);
|
||||
if (!workbook) return;
|
||||
const unitId = workbook.getUnitId();
|
||||
const currentRender = this._renderManagerService.getRenderUnitById(unitId);
|
||||
currentRender?.mainComponent?.makeForceDirty();
|
||||
};
|
||||
|
||||
this.disposeWithMe(this._sheetsNoteModel.change$.pipe(debounceTime(16)).subscribe(() => {
|
||||
markSkeletonDirty();
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
/**
|
||||
* 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, Nullable, Workbook } from '@univerjs/core';
|
||||
import type { ISheetLocationBase } from '@univerjs/sheets';
|
||||
import type { ISheetNote } from '@univerjs/sheets-note';
|
||||
import type { Subscription } from 'rxjs';
|
||||
import { Disposable, Inject, IUniverInstanceService, ObjectMatrix, UniverInstanceType } from '@univerjs/core';
|
||||
import { SheetsNoteModel } from '@univerjs/sheets-note';
|
||||
import { CellPopupManagerService } from '@univerjs/sheets-ui';
|
||||
import { of, switchMap } from 'rxjs';
|
||||
import { SheetsNotePopupService } from '../services/sheets-note-popup.service';
|
||||
import { SHEET_NOTE_COMPONENT } from '../views/config';
|
||||
|
||||
export class SheetsNoteAttachmentController extends Disposable {
|
||||
private _noteMatrix = new ObjectMatrix<IDisposable>();
|
||||
private readonly _suppressedUnitIds = new Set<string>();
|
||||
private _activeUnitId: Nullable<string> = null;
|
||||
private _activeSheetId: Nullable<string> = null;
|
||||
private _noteChangeSubscription: Nullable<Subscription> = null;
|
||||
|
||||
constructor(
|
||||
@Inject(SheetsNoteModel) private readonly _sheetsNoteModel: SheetsNoteModel,
|
||||
@Inject(IUniverInstanceService) private readonly _univerInstanceService: IUniverInstanceService,
|
||||
@Inject(CellPopupManagerService) private readonly _cellPopupManagerService: CellPopupManagerService,
|
||||
@Inject(SheetsNotePopupService) private readonly _sheetsNotePopupService: SheetsNotePopupService
|
||||
) {
|
||||
super();
|
||||
|
||||
this._initNoteChangeListener();
|
||||
}
|
||||
|
||||
private _showPopup(unitId: string, sheetId: string, row: number, col: number) {
|
||||
this._sheetsNotePopupService.hidePopup(true);
|
||||
|
||||
return this._cellPopupManagerService.showPopup(
|
||||
{
|
||||
unitId,
|
||||
subUnitId: sheetId,
|
||||
row,
|
||||
col,
|
||||
},
|
||||
{
|
||||
componentKey: SHEET_NOTE_COMPONENT,
|
||||
direction: 'horizontal',
|
||||
extraProps: {
|
||||
location: {
|
||||
unitId,
|
||||
subUnitId: sheetId,
|
||||
row,
|
||||
col,
|
||||
} as ISheetLocationBase,
|
||||
},
|
||||
priority: 3,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
super.dispose();
|
||||
this._clearNoteMatrix();
|
||||
this._suppressedUnitIds.clear();
|
||||
this._activeUnitId = null;
|
||||
this._activeSheetId = null;
|
||||
this._noteChangeSubscription?.unsubscribe();
|
||||
this._noteChangeSubscription = null;
|
||||
}
|
||||
|
||||
setPopupSuppressed(unitId: string, suppressed: boolean): void {
|
||||
if (suppressed) {
|
||||
this._suppressedUnitIds.add(unitId);
|
||||
} else {
|
||||
this._suppressedUnitIds.delete(unitId);
|
||||
}
|
||||
|
||||
if (unitId !== this._activeUnitId || !this._activeSheetId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (suppressed) {
|
||||
this._clearNoteMatrix();
|
||||
} else {
|
||||
this._showPersistentNotes(unitId, this._activeSheetId);
|
||||
}
|
||||
}
|
||||
|
||||
private _initSheet(targetUnitId: string, targetSheetId: string) {
|
||||
this._clearNoteMatrix();
|
||||
this._activeUnitId = targetUnitId;
|
||||
this._activeSheetId = targetSheetId;
|
||||
const handleNote = (unitId: string, sheetId: string, row: number, col: number, note: Nullable<ISheetNote>) => {
|
||||
const matrix = this._noteMatrix;
|
||||
const disposable = matrix.getValue(row, col);
|
||||
if (note?.show) {
|
||||
if (this._suppressedUnitIds.has(unitId)) {
|
||||
disposable?.dispose();
|
||||
matrix.realDeleteValue(row, col);
|
||||
return;
|
||||
}
|
||||
if (!disposable) {
|
||||
const newDisposable = this._showPopup(unitId, sheetId, row, col);
|
||||
if (newDisposable) {
|
||||
matrix.setValue(row, col, newDisposable);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (disposable) {
|
||||
disposable.dispose();
|
||||
matrix.realDeleteValue(row, col);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!this._suppressedUnitIds.has(targetUnitId)) {
|
||||
this._showPersistentNotes(targetUnitId, targetSheetId);
|
||||
}
|
||||
|
||||
return this._sheetsNoteModel.change$.subscribe((change) => {
|
||||
if (change.unitId !== targetUnitId || change.subUnitId !== targetSheetId) {
|
||||
return;
|
||||
}
|
||||
switch (change.type) {
|
||||
case 'ref': {
|
||||
const { unitId, subUnitId, oldNote, newNote } = change;
|
||||
if (!newNote.show) return;
|
||||
|
||||
const matrix = this._noteMatrix;
|
||||
const { row: oldRow, col: oldCol } = oldNote!;
|
||||
const { row: newRow, col: newCol } = newNote;
|
||||
|
||||
const disposable = matrix.getValue(oldRow, oldCol);
|
||||
if (disposable) {
|
||||
disposable.dispose();
|
||||
matrix.realDeleteValue(oldRow, oldCol);
|
||||
}
|
||||
handleNote(unitId, subUnitId, newRow, newCol, newNote);
|
||||
break;
|
||||
}
|
||||
case 'update': {
|
||||
const { unitId, subUnitId, oldNote, newNote } = change;
|
||||
const row = newNote ? newNote.row : oldNote!.row;
|
||||
const col = newNote ? newNote.col : oldNote!.col;
|
||||
handleNote(unitId, subUnitId, row, col, newNote);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _initNoteChangeListener() {
|
||||
this.disposeWithMe(
|
||||
this._univerInstanceService.getCurrentTypeOfUnit$<Workbook>(UniverInstanceType.UNIVER_SHEET).pipe(
|
||||
switchMap((workbook) => workbook?.activeSheet$ ?? of(null))
|
||||
).subscribe((sheet) => {
|
||||
this._noteChangeSubscription?.unsubscribe();
|
||||
this._noteChangeSubscription = null;
|
||||
if (sheet) {
|
||||
this._noteChangeSubscription = this._initSheet(sheet.getUnitId(), sheet.getSheetId());
|
||||
} else {
|
||||
this._clearNoteMatrix();
|
||||
this._activeUnitId = null;
|
||||
this._activeSheetId = null;
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private _showPersistentNotes(unitId: string, sheetId: string): void {
|
||||
this._sheetsNoteModel.getSheetNotes(unitId, sheetId)?.forEach((note) => {
|
||||
if (!note.show || this._noteMatrix.getValue(note.row, note.col)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const disposable = this._showPopup(unitId, sheetId, note.row, note.col);
|
||||
if (disposable) {
|
||||
this._noteMatrix.setValue(note.row, note.col, disposable);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _clearNoteMatrix(): void {
|
||||
this._noteMatrix.forValue((_, __, disposable) => {
|
||||
disposable.dispose();
|
||||
});
|
||||
this._noteMatrix = new ObjectMatrix();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
/**
|
||||
* 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 { ISelectionWithStyle } from '@univerjs/sheets';
|
||||
import type { IHoverCellPosition } from '@univerjs/sheets-ui';
|
||||
import { Disposable, Inject, RANGE_TYPE, Rectangle } from '@univerjs/core';
|
||||
import { IRenderManagerService } from '@univerjs/engine-render';
|
||||
import { SheetsSelectionsService } from '@univerjs/sheets';
|
||||
import { SheetsNoteModel } from '@univerjs/sheets-note';
|
||||
import { HoverManagerService, IEditorBridgeService, SheetSkeletonManagerService } from '@univerjs/sheets-ui';
|
||||
import { debounceTime } from 'rxjs/operators';
|
||||
import { SheetsNotePopupService } from '../services/sheets-note-popup.service';
|
||||
|
||||
export class SheetsNotePopupController extends Disposable {
|
||||
private _isSwitchingSheet = false;
|
||||
|
||||
constructor(
|
||||
@Inject(SheetsNotePopupService) private readonly _sheetsNotePopupService: SheetsNotePopupService,
|
||||
@Inject(SheetsNoteModel) private readonly _sheetsNoteModel: SheetsNoteModel,
|
||||
@Inject(SheetsSelectionsService) private readonly _sheetSelectionService: SheetsSelectionsService,
|
||||
@IEditorBridgeService private readonly _editorBridgeService: IEditorBridgeService,
|
||||
@IRenderManagerService private readonly _renderManagerService: IRenderManagerService,
|
||||
@Inject(HoverManagerService) private readonly _hoverManagerService: HoverManagerService
|
||||
) {
|
||||
super();
|
||||
|
||||
this._initSelectionUpdateListener();
|
||||
this._initEditorBridge();
|
||||
this._initHoverEvent();
|
||||
this._initDeleteNoteListener();
|
||||
}
|
||||
|
||||
private _handleSelectionChange(selections: ISelectionWithStyle[], unitId: string, subUnitId: string) {
|
||||
const range = selections[0]?.range;
|
||||
const render = this._renderManagerService.getRenderUnitById(unitId);
|
||||
const skeleton = render?.with(SheetSkeletonManagerService).getSkeletonParam(subUnitId)?.skeleton;
|
||||
if (!skeleton) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!range) {
|
||||
return;
|
||||
}
|
||||
const actualCell = skeleton.getCellWithCoordByIndex(range.startRow, range.startColumn);
|
||||
|
||||
const rangeType = range.rangeType ?? RANGE_TYPE.NORMAL;
|
||||
if ((rangeType !== RANGE_TYPE.NORMAL || range.endColumn - range.startColumn > 0 || range.endRow - range.startRow > 0) && !((actualCell.isMerged || actualCell.isMergedMainCell) && Rectangle.equals(actualCell.mergeInfo, range))) {
|
||||
this._sheetsNotePopupService.hidePopup();
|
||||
return;
|
||||
}
|
||||
|
||||
const row = actualCell.actualRow;
|
||||
const col = actualCell.actualColumn;
|
||||
const note = this._sheetsNoteModel.getNote(unitId, subUnitId, { row, col });
|
||||
if (note?.show) return;
|
||||
|
||||
if (note) {
|
||||
this._sheetsNotePopupService.showPopup({
|
||||
unitId,
|
||||
subUnitId,
|
||||
noteId: note.id,
|
||||
row,
|
||||
col,
|
||||
});
|
||||
} else {
|
||||
this._sheetsNotePopupService.hidePopup(true);
|
||||
}
|
||||
}
|
||||
|
||||
private _initSelectionUpdateListener() {
|
||||
this.disposeWithMe(
|
||||
this._sheetSelectionService.selectionMoveEnd$.subscribe((selections) => {
|
||||
if (this._isSwitchingSheet) {
|
||||
return;
|
||||
}
|
||||
const current = this._sheetSelectionService.currentSelectionParam;
|
||||
if (!current) {
|
||||
return;
|
||||
}
|
||||
this._handleSelectionChange(selections, current.unitId, current.sheetId);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private _initEditorBridge() {
|
||||
this.disposeWithMe(
|
||||
this._editorBridgeService.visible$.subscribe((visible) => {
|
||||
if (visible.visible) {
|
||||
this._sheetsNotePopupService.hidePopup(true);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private _initHoverEvent() {
|
||||
this.disposeWithMe(
|
||||
this._hoverManagerService.currentCell$.pipe(debounceTime(100)).subscribe((cell: Nullable<IHoverCellPosition>) => {
|
||||
if (!cell?.location) return;
|
||||
|
||||
const { unitId, subUnitId, row, col } = cell.location;
|
||||
const render = this._renderManagerService.getRenderUnitById(unitId);
|
||||
const skeleton = render?.with(SheetSkeletonManagerService).getSkeletonParam(subUnitId)?.skeleton;
|
||||
|
||||
let targetRow = row;
|
||||
let targetCol = col;
|
||||
let note = this._sheetsNoteModel.getNote(unitId, subUnitId, { row: targetRow, col: targetCol });
|
||||
if (!note && skeleton) {
|
||||
const actualCell = skeleton.getCellWithCoordByIndex(row, col);
|
||||
const { startRow, endRow, startColumn, endColumn } = actualCell.mergeInfo;
|
||||
if (startRow !== endRow || startColumn !== endColumn) {
|
||||
const sheetNotes = this._sheetsNoteModel.getSheetNotes(unitId, subUnitId);
|
||||
if (sheetNotes) {
|
||||
for (const [_id, _note] of sheetNotes) {
|
||||
if (_note.row >= startRow && _note.row <= endRow && _note.col >= startColumn && _note.col <= endColumn) {
|
||||
note = _note;
|
||||
targetRow = _note.row;
|
||||
targetCol = _note.col;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (note?.show) return;
|
||||
if (note) {
|
||||
this._sheetsNotePopupService.showPopup({
|
||||
unitId,
|
||||
subUnitId,
|
||||
noteId: note.id,
|
||||
row: targetRow,
|
||||
col: targetCol,
|
||||
temp: true,
|
||||
});
|
||||
} else {
|
||||
this._sheetsNotePopupService.hidePopup();
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private _initDeleteNoteListener() {
|
||||
this.disposeWithMe(
|
||||
this._sheetsNoteModel.change$.subscribe((change) => {
|
||||
if (!this._sheetsNotePopupService.activePopup) return;
|
||||
|
||||
const { unitId, subUnitId, noteId, row, col } = this._sheetsNotePopupService.activePopup;
|
||||
const { oldNote, newNote } = change;
|
||||
|
||||
if (newNote === null && change.unitId === unitId && change.subUnitId === subUnitId && ((oldNote?.id && oldNote.id === noteId) || (oldNote?.row === row && oldNote.col === col))) {
|
||||
this._sheetsNotePopupService.hidePopup(true);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
39
packages/sheets-note-ui/src/controllers/ui.controller.ts
Normal file
39
packages/sheets-note-ui/src/controllers/ui.controller.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* 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, Inject } from '@univerjs/core';
|
||||
import { IMenuManagerService } from '@univerjs/ui';
|
||||
import { AddNotePopupOperation } from '../commands/operations/add-note-popup.operation';
|
||||
import { menuSchema } from '../menu/schema';
|
||||
|
||||
export class SheetsNoteUIController extends Disposable {
|
||||
constructor(
|
||||
@Inject(IMenuManagerService) private readonly _menuManagerService: IMenuManagerService,
|
||||
@ICommandService private readonly _commandService: ICommandService
|
||||
) {
|
||||
super();
|
||||
this._initMenu();
|
||||
this._initCommands();
|
||||
}
|
||||
|
||||
private _initMenu() {
|
||||
this._menuManagerService.mergeMenu(menuSchema);
|
||||
}
|
||||
|
||||
private _initCommands() {
|
||||
this._commandService.registerCommand(AddNotePopupOperation);
|
||||
}
|
||||
}
|
||||
2
packages/sheets-note-ui/src/global.css
Normal file
2
packages/sheets-note-ui/src/global.css
Normal file
@@ -0,0 +1,2 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
25
packages/sheets-note-ui/src/index.ts
Normal file
25
packages/sheets-note-ui/src/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 './global.css';
|
||||
|
||||
export type { IUniverSheetsNoteUIConfig } from './config/config';
|
||||
export { SheetsCellContentController } from './controllers/sheets-cell-content.controller';
|
||||
export { SheetsNoteAttachmentController } from './controllers/sheets-note-attachment.controller';
|
||||
export { SheetsNotePopupController } from './controllers/sheets-note-popup.controller';
|
||||
export { menuSchema as SheetsNoteUIMenuSchema } from './menu/schema';
|
||||
export { UniverSheetsNoteUIPlugin } from './plugin';
|
||||
export { SheetsNotePopupService } from './services/sheets-note-popup.service';
|
||||
32
packages/sheets-note-ui/src/locale/ar-SA.ts
Normal file
32
packages/sheets-note-ui/src/locale/ar-SA.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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 enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-note-ui': {
|
||||
note: {
|
||||
placeholder: 'اكتب هنا',
|
||||
},
|
||||
rightClick: {
|
||||
addNote: 'إضافة ملاحظة',
|
||||
deleteNote: 'حذف الملاحظة',
|
||||
toggleNote: 'إظهار/إخفاء الملاحظة',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
32
packages/sheets-note-ui/src/locale/ca-ES.ts
Normal file
32
packages/sheets-note-ui/src/locale/ca-ES.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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 enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-note-ui': {
|
||||
note: {
|
||||
placeholder: 'Escriu aquí',
|
||||
},
|
||||
rightClick: {
|
||||
addNote: 'Afegeix nota',
|
||||
deleteNote: 'Elimina nota',
|
||||
toggleNote: 'Mostra/Amaga nota',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
32
packages/sheets-note-ui/src/locale/de-DE.ts
Normal file
32
packages/sheets-note-ui/src/locale/de-DE.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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 enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-note-ui': {
|
||||
note: {
|
||||
placeholder: 'Hier eingeben',
|
||||
},
|
||||
rightClick: {
|
||||
addNote: 'Notiz hinzufügen',
|
||||
deleteNote: 'Notiz löschen',
|
||||
toggleNote: 'Notiz anzeigen/ausblenden',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
30
packages/sheets-note-ui/src/locale/en-US.ts
Normal file
30
packages/sheets-note-ui/src/locale/en-US.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const locale = {
|
||||
'sheets-note-ui': {
|
||||
note: {
|
||||
placeholder: 'Type here',
|
||||
},
|
||||
rightClick: {
|
||||
addNote: 'Add Note',
|
||||
deleteNote: 'Delete Note',
|
||||
toggleNote: 'Show/Hide Note',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
32
packages/sheets-note-ui/src/locale/es-ES.ts
Normal file
32
packages/sheets-note-ui/src/locale/es-ES.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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 enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-note-ui': {
|
||||
note: {
|
||||
placeholder: 'Escribe aquí',
|
||||
},
|
||||
rightClick: {
|
||||
addNote: 'Agregar nota',
|
||||
deleteNote: 'Eliminar nota',
|
||||
toggleNote: 'Mostrar/Ocultar nota',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
32
packages/sheets-note-ui/src/locale/fa-IR.ts
Normal file
32
packages/sheets-note-ui/src/locale/fa-IR.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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 enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-note-ui': {
|
||||
note: {
|
||||
placeholder: 'اینجا تایپ کنید',
|
||||
},
|
||||
rightClick: {
|
||||
addNote: 'افزودن یادداشت',
|
||||
deleteNote: 'حذف یادداشت',
|
||||
toggleNote: 'نمایش/پنهان کردن یادداشت',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
32
packages/sheets-note-ui/src/locale/fr-FR.ts
Normal file
32
packages/sheets-note-ui/src/locale/fr-FR.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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 enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-note-ui': {
|
||||
note: {
|
||||
placeholder: 'Écrivez ici',
|
||||
},
|
||||
rightClick: {
|
||||
addNote: 'Ajouter une note',
|
||||
deleteNote: 'Supprimer la note',
|
||||
toggleNote: 'Afficher/Masquer la note',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
32
packages/sheets-note-ui/src/locale/id-ID.ts
Normal file
32
packages/sheets-note-ui/src/locale/id-ID.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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 enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-note-ui': {
|
||||
note: {
|
||||
placeholder: 'Ketik di sini',
|
||||
},
|
||||
rightClick: {
|
||||
addNote: 'Tambah Catatan',
|
||||
deleteNote: 'Hapus Catatan',
|
||||
toggleNote: 'Tampilkan/Sembunyikan Catatan',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
32
packages/sheets-note-ui/src/locale/it-IT.ts
Normal file
32
packages/sheets-note-ui/src/locale/it-IT.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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 enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-note-ui': {
|
||||
note: {
|
||||
placeholder: 'Scrivi qui',
|
||||
},
|
||||
rightClick: {
|
||||
addNote: 'Aggiungi nota',
|
||||
deleteNote: 'Elimina nota',
|
||||
toggleNote: 'Mostra/Nascondi nota',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
32
packages/sheets-note-ui/src/locale/ja-JP.ts
Normal file
32
packages/sheets-note-ui/src/locale/ja-JP.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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 enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-note-ui': {
|
||||
note: {
|
||||
placeholder: 'ここに入力',
|
||||
},
|
||||
rightClick: {
|
||||
addNote: 'コメントを追加',
|
||||
deleteNote: 'コメントを削除',
|
||||
toggleNote: 'コメントの表示/非表示',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
32
packages/sheets-note-ui/src/locale/ko-KR.ts
Normal file
32
packages/sheets-note-ui/src/locale/ko-KR.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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 enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-note-ui': {
|
||||
note: {
|
||||
placeholder: '여기에 입력',
|
||||
},
|
||||
rightClick: {
|
||||
addNote: '노트 추가',
|
||||
deleteNote: '노트 삭제',
|
||||
toggleNote: '노트 표시/숨기기',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
32
packages/sheets-note-ui/src/locale/pl-PL.ts
Normal file
32
packages/sheets-note-ui/src/locale/pl-PL.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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 enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-note-ui': {
|
||||
note: {
|
||||
placeholder: 'Wpisz tutaj',
|
||||
},
|
||||
rightClick: {
|
||||
addNote: 'Dodaj notatkę',
|
||||
deleteNote: 'Usuń notatkę',
|
||||
toggleNote: 'Pokaż/Ukryj notatkę',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
32
packages/sheets-note-ui/src/locale/pt-BR.ts
Normal file
32
packages/sheets-note-ui/src/locale/pt-BR.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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 enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-note-ui': {
|
||||
note: {
|
||||
placeholder: 'Digite aqui',
|
||||
},
|
||||
rightClick: {
|
||||
addNote: 'Adicionar Nota',
|
||||
deleteNote: 'Excluir Nota',
|
||||
toggleNote: 'Exibir/Ocultar Nota',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
32
packages/sheets-note-ui/src/locale/ru-RU.ts
Normal file
32
packages/sheets-note-ui/src/locale/ru-RU.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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 enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-note-ui': {
|
||||
note: {
|
||||
placeholder: 'Введите текст здесь',
|
||||
},
|
||||
rightClick: {
|
||||
addNote: 'Добавить примечание',
|
||||
deleteNote: 'Удалить примечание',
|
||||
toggleNote: 'Показать/Скрыть примечание',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
32
packages/sheets-note-ui/src/locale/sk-SK.ts
Normal file
32
packages/sheets-note-ui/src/locale/sk-SK.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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 enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-note-ui': {
|
||||
note: {
|
||||
placeholder: 'Píšte sem',
|
||||
},
|
||||
rightClick: {
|
||||
addNote: 'Pridať poznámku',
|
||||
deleteNote: 'Odstrániť poznámku',
|
||||
toggleNote: 'Zobraziť/Skryť poznámku',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
20
packages/sheets-note-ui/src/locale/types.ts
Normal file
20
packages/sheets-note-ui/src/locale/types.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* 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 { LocaleLeafKeys } from '@univerjs/core';
|
||||
import type enUS from './en-US';
|
||||
|
||||
export type LocaleKey = LocaleLeafKeys<typeof enUS>;
|
||||
32
packages/sheets-note-ui/src/locale/vi-VN.ts
Normal file
32
packages/sheets-note-ui/src/locale/vi-VN.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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 enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-note-ui': {
|
||||
note: {
|
||||
placeholder: 'Nhập tại đây',
|
||||
},
|
||||
rightClick: {
|
||||
addNote: 'Thêm ghi chú',
|
||||
deleteNote: 'Xóa ghi chú',
|
||||
toggleNote: 'Hiện/Ẩn ghi chú',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
32
packages/sheets-note-ui/src/locale/zh-CN.ts
Normal file
32
packages/sheets-note-ui/src/locale/zh-CN.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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 enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-note-ui': {
|
||||
note: {
|
||||
placeholder: '在此输入',
|
||||
},
|
||||
rightClick: {
|
||||
addNote: '添加批注',
|
||||
deleteNote: '删除批注',
|
||||
toggleNote: '显示/隐藏批注',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
32
packages/sheets-note-ui/src/locale/zh-HK.ts
Normal file
32
packages/sheets-note-ui/src/locale/zh-HK.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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 enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-note-ui': {
|
||||
note: {
|
||||
placeholder: '在此輸入',
|
||||
},
|
||||
rightClick: {
|
||||
addNote: '新增批註',
|
||||
deleteNote: '刪除批註',
|
||||
toggleNote: '顯示/隱藏批註',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
32
packages/sheets-note-ui/src/locale/zh-TW.ts
Normal file
32
packages/sheets-note-ui/src/locale/zh-TW.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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 enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-note-ui': {
|
||||
note: {
|
||||
placeholder: '在此輸入',
|
||||
},
|
||||
rightClick: {
|
||||
addNote: '新增批註',
|
||||
deleteNote: '刪除批註',
|
||||
toggleNote: '顯示/隱藏批註',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
89
packages/sheets-note-ui/src/menu/note.menu.ts
Normal file
89
packages/sheets-note-ui/src/menu/note.menu.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* 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 } from '@univerjs/core';
|
||||
import type { IMenuButtonItem } from '@univerjs/ui';
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { LocaleKey } from '../locale/types';
|
||||
import { IUniverInstanceService, UniverInstanceType } from '@univerjs/core';
|
||||
import {
|
||||
getSheetCommandTarget,
|
||||
SheetsSelectionsService,
|
||||
WorkbookEditablePermission,
|
||||
WorksheetEditPermission,
|
||||
} from '@univerjs/sheets';
|
||||
import { SheetDeleteNoteCommand, SheetsNoteModel, SheetToggleNotePopupCommand } from '@univerjs/sheets-note';
|
||||
import { getCurrentRangeDisable$ } from '@univerjs/sheets-ui';
|
||||
import { getMenuHiddenObservable, MenuItemType } from '@univerjs/ui';
|
||||
import { combineLatest, map } from 'rxjs';
|
||||
import { AddNotePopupOperation } from '../commands/operations/add-note-popup.operation';
|
||||
|
||||
export const SHEET_NOTE_CONTEXT_MENU_ID = 'sheet.menu.note';
|
||||
|
||||
function getHasNote$(accessor: IAccessor): Observable<boolean> {
|
||||
const sheetsSelectionsService = accessor.get(SheetsSelectionsService);
|
||||
const univerInstanceService = accessor.get(IUniverInstanceService);
|
||||
return sheetsSelectionsService.selectionMoveEnd$.pipe(map(() => {
|
||||
const selection = sheetsSelectionsService.getCurrentLastSelection();
|
||||
if (!selection?.primary) return false;
|
||||
const target = getSheetCommandTarget(univerInstanceService);
|
||||
if (!target) return false;
|
||||
const { actualColumn, actualRow } = selection.primary;
|
||||
const noteModel = accessor.get(SheetsNoteModel);
|
||||
return Boolean(noteModel.getNote(target.unitId, target.subUnitId, { row: actualRow, col: actualColumn }));
|
||||
}));
|
||||
}
|
||||
|
||||
export function sheetNoteContextMenuFactory(accessor: IAccessor): IMenuButtonItem<LocaleKey> {
|
||||
return {
|
||||
id: AddNotePopupOperation.id,
|
||||
type: MenuItemType.BUTTON,
|
||||
title: 'sheets-note-ui.rightClick.addNote',
|
||||
icon: 'AddNoteIcon',
|
||||
hidden$: combineLatest([
|
||||
getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET),
|
||||
getHasNote$(accessor),
|
||||
]).pipe(map(([hidden, hasNote]) => hidden || hasNote)),
|
||||
disabled$: getCurrentRangeDisable$(accessor, {
|
||||
workbookTypes: [WorkbookEditablePermission],
|
||||
worksheetTypes: [WorksheetEditPermission],
|
||||
}),
|
||||
commandId: AddNotePopupOperation.id,
|
||||
};
|
||||
}
|
||||
|
||||
export function sheetDeleteNoteMenuFactory(accessor: IAccessor): IMenuButtonItem<LocaleKey> {
|
||||
return {
|
||||
id: SheetDeleteNoteCommand.id,
|
||||
type: MenuItemType.BUTTON,
|
||||
title: 'sheets-note-ui.rightClick.deleteNote',
|
||||
icon: 'DeleteNoteIcon',
|
||||
hidden$: getHasNote$(accessor).pipe(map((hasNote) => !hasNote)),
|
||||
disabled$: getCurrentRangeDisable$(accessor, {
|
||||
workbookTypes: [WorkbookEditablePermission],
|
||||
worksheetTypes: [WorksheetEditPermission],
|
||||
}),
|
||||
};
|
||||
}
|
||||
export function sheetNoteToggleMenuFactory(accessor: IAccessor): IMenuButtonItem<LocaleKey> {
|
||||
return {
|
||||
id: SheetToggleNotePopupCommand.id,
|
||||
type: MenuItemType.BUTTON,
|
||||
title: 'sheets-note-ui.rightClick.toggleNote',
|
||||
icon: 'HideNoteIcon',
|
||||
hidden$: getHasNote$(accessor).pipe(map((hasNote) => !hasNote)),
|
||||
};
|
||||
}
|
||||
41
packages/sheets-note-ui/src/menu/schema.ts
Normal file
41
packages/sheets-note-ui/src/menu/schema.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* 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 { MenuSchemaType } from '@univerjs/ui';
|
||||
import { SheetDeleteNoteCommand, SheetToggleNotePopupCommand } from '@univerjs/sheets-note';
|
||||
import { ContextMenuGroup, ContextMenuPosition } from '@univerjs/ui';
|
||||
import { AddNotePopupOperation } from '../commands/operations/add-note-popup.operation';
|
||||
import { sheetDeleteNoteMenuFactory, sheetNoteContextMenuFactory, sheetNoteToggleMenuFactory } from './note.menu';
|
||||
|
||||
export const menuSchema: MenuSchemaType = {
|
||||
[ContextMenuPosition.MAIN_AREA]: {
|
||||
[ContextMenuGroup.OTHERS]: {
|
||||
order: 0,
|
||||
[AddNotePopupOperation.id]: {
|
||||
order: 0,
|
||||
menuItemFactory: sheetNoteContextMenuFactory,
|
||||
},
|
||||
[SheetDeleteNoteCommand.id]: {
|
||||
order: 0,
|
||||
menuItemFactory: sheetDeleteNoteMenuFactory,
|
||||
},
|
||||
[SheetToggleNotePopupCommand.id]: {
|
||||
order: 0,
|
||||
menuItemFactory: sheetNoteToggleMenuFactory,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
99
packages/sheets-note-ui/src/plugin.ts
Normal file
99
packages/sheets-note-ui/src/plugin.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* 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 { IUniverSheetsNoteUIConfig } from './config/config';
|
||||
import {
|
||||
DependentOn,
|
||||
IConfigService,
|
||||
Inject,
|
||||
Injector,
|
||||
merge,
|
||||
Plugin,
|
||||
touchDependencies,
|
||||
UniverInstanceType,
|
||||
} from '@univerjs/core';
|
||||
import { UniverRenderEnginePlugin } from '@univerjs/engine-render';
|
||||
import { UniverSheetsPlugin } from '@univerjs/sheets';
|
||||
import { UniverSheetsNotePlugin } from '@univerjs/sheets-note';
|
||||
import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui';
|
||||
import pkg from '../package.json';
|
||||
import { defaultPluginConfig, SHEETS_NOTE_UI_PLUGIN_CONFIG_KEY } from './config/config';
|
||||
import { ComponentsController } from './controllers/components.controller';
|
||||
import { SheetsCellContentController } from './controllers/sheets-cell-content.controller';
|
||||
import { SheetsNoteAttachmentController } from './controllers/sheets-note-attachment.controller';
|
||||
import { SheetsNotePopupController } from './controllers/sheets-note-popup.controller';
|
||||
import { SheetsNoteUIController } from './controllers/ui.controller';
|
||||
import { SheetsNotePopupService } from './services/sheets-note-popup.service';
|
||||
|
||||
@DependentOn(
|
||||
UniverRenderEnginePlugin,
|
||||
UniverSheetsPlugin,
|
||||
UniverSheetsNotePlugin,
|
||||
UniverSheetsUIPlugin
|
||||
)
|
||||
export class UniverSheetsNoteUIPlugin extends Plugin {
|
||||
static override pluginName = 'SHEET_NOTE_UI_PLUGIN';
|
||||
static override packageName = pkg.name;
|
||||
static override version = pkg.version;
|
||||
static override type = UniverInstanceType.UNIVER_SHEET;
|
||||
|
||||
constructor(
|
||||
private readonly _config: Partial<IUniverSheetsNoteUIConfig> = defaultPluginConfig,
|
||||
@Inject(Injector) protected override readonly _injector: Injector,
|
||||
@IConfigService private readonly _configService: IConfigService
|
||||
) {
|
||||
super();
|
||||
// Manage the plugin configuration.
|
||||
const { menu, ...rest } = merge(
|
||||
{},
|
||||
defaultPluginConfig,
|
||||
this._config
|
||||
);
|
||||
if (menu) {
|
||||
this._configService.setConfig('menu', menu, { merge: true });
|
||||
}
|
||||
this._configService.setConfig(SHEETS_NOTE_UI_PLUGIN_CONFIG_KEY, rest);
|
||||
}
|
||||
|
||||
override onStarting(): void {
|
||||
this._injector.add([ComponentsController]);
|
||||
this._injector.get(ComponentsController);
|
||||
([
|
||||
[SheetsNotePopupService],
|
||||
[SheetsCellContentController],
|
||||
[SheetsNotePopupController],
|
||||
[SheetsNoteUIController],
|
||||
[SheetsNoteAttachmentController],
|
||||
] as Dependency[]).forEach((dependency) => {
|
||||
this._injector.add(dependency);
|
||||
});
|
||||
}
|
||||
|
||||
override onReady(): void {
|
||||
touchDependencies(this._injector, [
|
||||
[SheetsNoteUIController],
|
||||
[SheetsCellContentController],
|
||||
]);
|
||||
}
|
||||
|
||||
override onRendered(): void {
|
||||
touchDependencies(this._injector, [
|
||||
[SheetsNotePopupController],
|
||||
[SheetsNoteAttachmentController],
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* 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 { Injector } from '@univerjs/core';
|
||||
import { CellPopupManagerService } from '@univerjs/sheets-ui';
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
import { SheetsNotePopupService } from '../sheets-note-popup.service';
|
||||
|
||||
class TestCellPopupManagerService {
|
||||
static disposedCount = 0;
|
||||
|
||||
static reset() {
|
||||
this.disposedCount = 0;
|
||||
}
|
||||
|
||||
showPopup() {
|
||||
return {
|
||||
dispose: () => {
|
||||
TestCellPopupManagerService.disposedCount += 1;
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
describe('SheetsNotePopupService', () => {
|
||||
let service: SheetsNotePopupService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestCellPopupManagerService.reset();
|
||||
const injector = new Injector();
|
||||
injector.add([CellPopupManagerService, { useClass: TestCellPopupManagerService as never }]);
|
||||
injector.add([SheetsNotePopupService]);
|
||||
service = injector.get(SheetsNotePopupService);
|
||||
});
|
||||
|
||||
it('shows a temporary note popup and hides it when requested', () => {
|
||||
const active: unknown[] = [];
|
||||
service.activePopup$.subscribe((popup) => active.push(popup));
|
||||
|
||||
service.showPopup({ unitId: 'book-1', subUnitId: 'sheet-1', row: 1, col: 2, noteId: 'note-1', temp: true });
|
||||
service.hidePopup();
|
||||
|
||||
expect(service.activePopup).toBeNull();
|
||||
expect(active.at(-2)).toEqual({ unitId: 'book-1', subUnitId: 'sheet-1', row: 1, col: 2, noteId: 'note-1', temp: true });
|
||||
expect(active.at(-1)).toBeNull();
|
||||
expect(TestCellPopupManagerService.disposedCount).toBe(1);
|
||||
});
|
||||
|
||||
it('persists an opened temporary note popup', () => {
|
||||
service.showPopup({ unitId: 'book-1', subUnitId: 'sheet-1', row: 1, col: 2, temp: true });
|
||||
|
||||
service.persistPopup();
|
||||
|
||||
expect(service.activePopup?.temp).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* 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, Nullable } from '@univerjs/core';
|
||||
import type { ISheetLocationBase } from '@univerjs/sheets';
|
||||
import { Disposable, DisposableCollection, Inject } from '@univerjs/core';
|
||||
import { CellPopupManagerService } from '@univerjs/sheets-ui';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { SHEET_NOTE_COMPONENT } from '../views/config';
|
||||
|
||||
interface INotePopup extends ISheetLocationBase {
|
||||
noteId?: string;
|
||||
temp?: boolean;
|
||||
trigger?: string;
|
||||
}
|
||||
|
||||
export class SheetsNotePopupService extends Disposable {
|
||||
private _lastPopup: Nullable<IDisposable> = null;
|
||||
private _activePopup: Nullable<INotePopup>;
|
||||
private _activePopup$ = new BehaviorSubject<Nullable<INotePopup>>(null);
|
||||
|
||||
activePopup$ = this._activePopup$.asObservable();
|
||||
|
||||
get activePopup() {
|
||||
return this._activePopup;
|
||||
}
|
||||
|
||||
constructor(
|
||||
@Inject(CellPopupManagerService) private readonly _cellPopupManagerService: CellPopupManagerService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.disposeWithMe(() => {
|
||||
this._activePopup$.complete();
|
||||
});
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
super.dispose();
|
||||
this.hidePopup();
|
||||
}
|
||||
|
||||
showPopup(location: INotePopup, onHide?: () => void) {
|
||||
const { row, col, unitId, subUnitId } = location;
|
||||
if (
|
||||
this._activePopup &&
|
||||
row === this._activePopup.row &&
|
||||
col === this._activePopup.col &&
|
||||
unitId === this._activePopup.unitId &&
|
||||
subUnitId === this.activePopup?.subUnitId
|
||||
) {
|
||||
this._activePopup = location;
|
||||
this._activePopup$.next(location);
|
||||
return;
|
||||
}
|
||||
if (this._lastPopup) {
|
||||
this._lastPopup.dispose();
|
||||
};
|
||||
|
||||
this._activePopup = location;
|
||||
this._activePopup$.next(location);
|
||||
|
||||
const popupDisposable = this._cellPopupManagerService.showPopup(
|
||||
{
|
||||
unitId,
|
||||
subUnitId,
|
||||
row,
|
||||
col,
|
||||
},
|
||||
{
|
||||
componentKey: SHEET_NOTE_COMPONENT,
|
||||
onClickOutside: () => {
|
||||
this.hidePopup();
|
||||
},
|
||||
direction: 'horizontal',
|
||||
extraProps: {
|
||||
location,
|
||||
},
|
||||
priority: 3,
|
||||
}
|
||||
);
|
||||
|
||||
if (!popupDisposable) {
|
||||
throw new Error('[SheetsNotePopupService]: cannot show popup!');
|
||||
}
|
||||
|
||||
const disposableCollection = new DisposableCollection();
|
||||
disposableCollection.add(popupDisposable);
|
||||
disposableCollection.add({
|
||||
dispose: () => {
|
||||
onHide?.();
|
||||
},
|
||||
});
|
||||
|
||||
this._lastPopup = disposableCollection;
|
||||
}
|
||||
|
||||
hidePopup(force?: boolean) {
|
||||
if (!this._activePopup) {
|
||||
return;
|
||||
}
|
||||
if (!force && !this._activePopup.temp) return;
|
||||
if (this._lastPopup) {
|
||||
this._lastPopup.dispose();
|
||||
}
|
||||
this._lastPopup = null;
|
||||
|
||||
this._activePopup = null;
|
||||
this._activePopup$.next(null);
|
||||
}
|
||||
|
||||
persistPopup() {
|
||||
if (!this._activePopup || !this._activePopup.temp) {
|
||||
return;
|
||||
}
|
||||
this._activePopup = {
|
||||
...this._activePopup,
|
||||
temp: false,
|
||||
};
|
||||
|
||||
this._activePopup$.next(this._activePopup);
|
||||
}
|
||||
}
|
||||
174
packages/sheets-note-ui/src/views/Note.tsx
Normal file
174
packages/sheets-note-ui/src/views/Note.tsx
Normal file
@@ -0,0 +1,174 @@
|
||||
/**
|
||||
* 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 { ISheetLocationBase } from '@univerjs/sheets';
|
||||
import type { ISheetNote } from '@univerjs/sheets-note';
|
||||
import type { IPopup } from '@univerjs/ui';
|
||||
import type { IUniverSheetsNoteUIConfig } from '../config/config';
|
||||
import type { LocaleKey } from '../locale/types';
|
||||
import { generateRandomId, ICommandService, LocaleService } from '@univerjs/core';
|
||||
import { clsx, Textarea } from '@univerjs/design';
|
||||
import { IRenderManagerService } from '@univerjs/engine-render';
|
||||
import { SheetsNoteModel, SheetUpdateNoteCommand } from '@univerjs/sheets-note';
|
||||
import { useConfigValue, useDebounceFn, useDependency } from '@univerjs/ui';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { SHEETS_NOTE_UI_PLUGIN_CONFIG_KEY } from '../config/config';
|
||||
import { SheetsNotePopupService } from '../services/sheets-note-popup.service';
|
||||
|
||||
type INotePopupLocation = ISheetLocationBase & {
|
||||
temp?: boolean;
|
||||
trigger?: string;
|
||||
};
|
||||
|
||||
export const SheetsNote = (props: { popup: IPopup<{ location: INotePopupLocation }> }) => {
|
||||
const activePopup = props.popup.extraProps?.location;
|
||||
|
||||
if (!activePopup) {
|
||||
console.error('Popup extraProps or location is undefined.');
|
||||
return null;
|
||||
}
|
||||
|
||||
return <SheetsNoteContent activePopup={activePopup} />;
|
||||
};
|
||||
|
||||
function SheetsNoteContent({ activePopup }: { activePopup: INotePopupLocation }) {
|
||||
const noteModel = useDependency(SheetsNoteModel);
|
||||
const localeService = useDependency(LocaleService);
|
||||
const renderManagerService = useDependency(IRenderManagerService);
|
||||
const notePopupService = useDependency(SheetsNotePopupService);
|
||||
const config = useConfigValue<IUniverSheetsNoteUIConfig>(SHEETS_NOTE_UI_PLUGIN_CONFIG_KEY);
|
||||
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
const initialWidthRef = useRef<number | null>(null);
|
||||
const currentRender = renderManagerService.getRenderUnitById(activePopup.unitId)!;
|
||||
|
||||
const [note, setNote] = useState<Partial<ISheetNote> | null>(null);
|
||||
const applyResizeOffset = useCallback((width: number) => {
|
||||
const textarea = textareaRef.current;
|
||||
if (!textarea) return;
|
||||
|
||||
if (localeService.getDirection() === 'rtl' && initialWidthRef.current != null) {
|
||||
const offset = initialWidthRef.current - width;
|
||||
textarea.style.transform = offset ? `translateX(${offset}px)` : '';
|
||||
} else {
|
||||
textarea.style.transform = '';
|
||||
}
|
||||
}, [localeService]);
|
||||
|
||||
useEffect(() => {
|
||||
const { unitId, subUnitId, row, col } = activePopup;
|
||||
const note = noteModel.getNote(unitId, subUnitId, { row, col });
|
||||
const width = note?.width ?? config?.defaultNoteSize?.width ?? 160;
|
||||
const height = note?.height ?? config?.defaultNoteSize?.height ?? 72;
|
||||
|
||||
if (!note) {
|
||||
const initNote: Partial<ISheetNote> = {
|
||||
id: generateRandomId(6),
|
||||
width,
|
||||
height,
|
||||
note: '',
|
||||
};
|
||||
setNote(initNote);
|
||||
updateNote(initNote);
|
||||
} else {
|
||||
setNote(note);
|
||||
}
|
||||
|
||||
if (textareaRef.current) {
|
||||
initialWidthRef.current = width;
|
||||
textareaRef.current.style.width = `${width}px`;
|
||||
textareaRef.current.style.height = `${height}px`;
|
||||
textareaRef.current.style.transform = '';
|
||||
}
|
||||
}, [activePopup, textareaRef]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!activePopup || activePopup.temp || !activePopup.trigger) return;
|
||||
if (!textareaRef.current) return;
|
||||
|
||||
const focusId = requestAnimationFrame(() => {
|
||||
textareaRef.current?.focus();
|
||||
});
|
||||
|
||||
return () => cancelAnimationFrame(focusId);
|
||||
}, [activePopup]);
|
||||
|
||||
const commandService = useDependency(ICommandService);
|
||||
const updateNote = useDebounceFn((newNote: Partial<ISheetNote>) => {
|
||||
if (!activePopup) return;
|
||||
|
||||
const { unitId, subUnitId, row, col } = activePopup;
|
||||
const result = commandService.syncExecuteCommand(SheetUpdateNoteCommand.id, {
|
||||
unitId,
|
||||
sheetId: subUnitId,
|
||||
row,
|
||||
col,
|
||||
note: newNote,
|
||||
});
|
||||
|
||||
// If the update fails
|
||||
if (!result) {
|
||||
const oldNote = noteModel.getNote(unitId, subUnitId, { noteId: newNote.id, row, col });
|
||||
|
||||
if (oldNote) {
|
||||
// Revert to old note
|
||||
setNote(oldNote);
|
||||
} else {
|
||||
// Hide popup if no old note exists
|
||||
notePopupService.hidePopup(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const handleNoteChange = useCallback((value: string) => {
|
||||
if (!note) return;
|
||||
if (value === note.note) return;
|
||||
|
||||
const newNote = { ...note, note: value };
|
||||
setNote(newNote);
|
||||
updateNote(newNote);
|
||||
}, [note]);
|
||||
|
||||
const handleResize = useCallback((width: number, height: number) => {
|
||||
if (!note) return;
|
||||
applyResizeOffset(width);
|
||||
if (width === note.width && height === note.height) return;
|
||||
|
||||
const newNote = { ...note, width, height };
|
||||
setNote(newNote);
|
||||
updateNote(newNote);
|
||||
}, [applyResizeOffset, note]);
|
||||
|
||||
return (
|
||||
<Textarea
|
||||
ref={textareaRef}
|
||||
data-u-comp="note-textarea"
|
||||
className={clsx(`
|
||||
univer-ml-px univer-min-h-1 univer-min-w-1 univer-bg-gray-0 !univer-text-sm univer-shadow
|
||||
dark:!univer-bg-gray-800
|
||||
`)}
|
||||
value={note?.note}
|
||||
placeholder={localeService.t<LocaleKey>('sheets-note-ui.note.placeholder')}
|
||||
onResize={handleResize}
|
||||
onValueChange={handleNoteChange}
|
||||
onWheel={(e) => {
|
||||
if (document.activeElement !== textareaRef.current) {
|
||||
currentRender.engine.getCanvasElement().dispatchEvent(new WheelEvent(e.type, e.nativeEvent));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
355
packages/sheets-note-ui/src/views/__tests__/Note.spec.tsx
Normal file
355
packages/sheets-note-ui/src/views/__tests__/Note.spec.tsx
Normal file
@@ -0,0 +1,355 @@
|
||||
/**
|
||||
* 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, IWorkbookData, Workbook } from '@univerjs/core';
|
||||
import type { IPopup } from '@univerjs/ui';
|
||||
import type { Root } from 'react-dom/client';
|
||||
import {
|
||||
awaitTime,
|
||||
CommandType,
|
||||
ICommandService,
|
||||
IConfigService,
|
||||
ILogService,
|
||||
IUndoRedoService,
|
||||
IUniverInstanceService,
|
||||
LocaleService,
|
||||
LocaleType,
|
||||
LogLevel,
|
||||
toDisposable,
|
||||
Univer,
|
||||
UniverInstanceType,
|
||||
} from '@univerjs/core';
|
||||
import { IRenderManagerService } from '@univerjs/engine-render';
|
||||
import {
|
||||
RemoveNoteMutation,
|
||||
SheetsNoteModel,
|
||||
SheetUpdateNoteCommand,
|
||||
UpdateNoteMutation,
|
||||
} from '@univerjs/sheets-note';
|
||||
import { CellPopupManagerService } from '@univerjs/sheets-ui';
|
||||
import { RediContext } from '@univerjs/ui';
|
||||
import { act } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { afterEach, describe, expect, it } from 'vitest';
|
||||
import { SHEETS_NOTE_UI_PLUGIN_CONFIG_KEY } from '../../config/config';
|
||||
import { SheetsNotePopupService } from '../../services/sheets-note-popup.service';
|
||||
import { SheetsNote } from '../Note';
|
||||
|
||||
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
||||
|
||||
const UNIT_ID = 'note-view-workbook';
|
||||
const SUB_UNIT_ID = 'sheet-1';
|
||||
|
||||
const FailingUpdateNoteMutation = {
|
||||
id: UpdateNoteMutation.id,
|
||||
type: CommandType.MUTATION,
|
||||
handler: () => false,
|
||||
};
|
||||
|
||||
class TestRenderManagerService {
|
||||
getRenderUnitById() {
|
||||
return {
|
||||
engine: {
|
||||
getCanvasElement() {
|
||||
return document.createElement('canvas');
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class TestCellPopupManagerService {
|
||||
showPopup(): IDisposable {
|
||||
return toDisposable(() => undefined);
|
||||
}
|
||||
}
|
||||
|
||||
class TestResizeObserver {
|
||||
static callback: ResizeObserverCallback | undefined;
|
||||
|
||||
constructor(callback: ResizeObserverCallback) {
|
||||
TestResizeObserver.callback = callback;
|
||||
}
|
||||
|
||||
observe(): void {}
|
||||
|
||||
unobserve(): void {}
|
||||
|
||||
disconnect(): void {}
|
||||
}
|
||||
|
||||
function createWorkbookData(): IWorkbookData {
|
||||
return {
|
||||
id: UNIT_ID,
|
||||
appVersion: '3.0.0-alpha',
|
||||
name: 'note workbook',
|
||||
locale: LocaleType.EN_US,
|
||||
sheetOrder: [SUB_UNIT_ID],
|
||||
styles: {},
|
||||
sheets: {
|
||||
[SUB_UNIT_ID]: {
|
||||
id: SUB_UNIT_ID,
|
||||
name: 'Sheet 1',
|
||||
cellData: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createNoteLocation(row: number, col: number) {
|
||||
return {
|
||||
unitId: UNIT_ID,
|
||||
subUnitId: SUB_UNIT_ID,
|
||||
row,
|
||||
col,
|
||||
trigger: 'context-menu',
|
||||
};
|
||||
}
|
||||
|
||||
function createNotePopup(row: number, col: number): IPopup<{ location: { unitId: string; subUnitId: string; row: number; col: number; trigger: string } }> {
|
||||
return {
|
||||
extraProps: {
|
||||
location: createNoteLocation(row, col),
|
||||
},
|
||||
} as IPopup<{ location: { unitId: string; subUnitId: string; row: number; col: number; trigger: string } }>;
|
||||
}
|
||||
|
||||
function createNoteViewTestBed() {
|
||||
const univer = new Univer();
|
||||
const injector = univer.__getInjector();
|
||||
|
||||
injector.add([SheetsNoteModel]);
|
||||
injector.add([IRenderManagerService, { useClass: TestRenderManagerService as never }]);
|
||||
injector.add([CellPopupManagerService, { useClass: TestCellPopupManagerService as never }]);
|
||||
injector.add([SheetsNotePopupService]);
|
||||
injector.get(IUndoRedoService);
|
||||
injector.get(IConfigService).setConfig(SHEETS_NOTE_UI_PLUGIN_CONFIG_KEY, {
|
||||
defaultNoteSize: {
|
||||
width: 180,
|
||||
height: 96,
|
||||
},
|
||||
});
|
||||
injector.get(LocaleService).load({
|
||||
[LocaleType.EN_US]: {
|
||||
'sheets-note-ui': {
|
||||
note: {
|
||||
placeholder: 'Write a note',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
injector.get(LocaleService).setLocale(LocaleType.EN_US);
|
||||
|
||||
const workbook = univer.createUnit<IWorkbookData, Workbook>(UniverInstanceType.UNIVER_SHEET, createWorkbookData());
|
||||
injector.get(IUniverInstanceService).focusUnit(UNIT_ID);
|
||||
injector.get(ILogService).setLogLevel(LogLevel.SILENT);
|
||||
|
||||
const commandService = injector.get(ICommandService);
|
||||
commandService.registerCommand(UpdateNoteMutation);
|
||||
commandService.registerCommand(RemoveNoteMutation);
|
||||
commandService.registerCommand(SheetUpdateNoteCommand);
|
||||
|
||||
return {
|
||||
univer,
|
||||
injector,
|
||||
workbook,
|
||||
noteModel: injector.get(SheetsNoteModel),
|
||||
commandService,
|
||||
notePopupService: injector.get(SheetsNotePopupService),
|
||||
};
|
||||
}
|
||||
|
||||
function inputText(textarea: HTMLTextAreaElement, value: string) {
|
||||
const valueSetter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value')?.set;
|
||||
valueSetter?.call(textarea, value);
|
||||
textarea.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
}
|
||||
|
||||
function resizeTextarea(textarea: HTMLTextAreaElement, width: number, height: number) {
|
||||
textarea.style.width = `${width}px`;
|
||||
textarea.style.height = `${height}px`;
|
||||
TestResizeObserver.callback?.([{
|
||||
target: {
|
||||
getBoundingClientRect: () => ({ width, height }),
|
||||
},
|
||||
} as unknown as ResizeObserverEntry], {} as ResizeObserver);
|
||||
}
|
||||
|
||||
async function renderNote(root: Root, testBed: ReturnType<typeof createNoteViewTestBed>, row = 2, col = 3) {
|
||||
await act(async () => {
|
||||
root.render(
|
||||
<RediContext.Provider value={{ injector: testBed.injector }}>
|
||||
<SheetsNote popup={createNotePopup(row, col)} />
|
||||
</RediContext.Provider>
|
||||
);
|
||||
await awaitTime(20);
|
||||
});
|
||||
}
|
||||
|
||||
describe('SheetsNote', () => {
|
||||
let root: Root | undefined;
|
||||
let container: HTMLDivElement | undefined;
|
||||
let currentTestBed: ReturnType<typeof createNoteViewTestBed> | undefined;
|
||||
let resizeObserverDescriptor: PropertyDescriptor | undefined;
|
||||
|
||||
afterEach(() => {
|
||||
act(() => {
|
||||
root?.unmount();
|
||||
});
|
||||
container?.remove();
|
||||
currentTestBed?.univer.dispose();
|
||||
if (resizeObserverDescriptor) {
|
||||
Object.defineProperty(globalThis, 'ResizeObserver', resizeObserverDescriptor);
|
||||
} else {
|
||||
delete (globalThis as { ResizeObserver?: unknown }).ResizeObserver;
|
||||
}
|
||||
root = undefined;
|
||||
container = undefined;
|
||||
currentTestBed = undefined;
|
||||
resizeObserverDescriptor = undefined;
|
||||
});
|
||||
|
||||
function createMountedNote() {
|
||||
resizeObserverDescriptor = Object.getOwnPropertyDescriptor(globalThis, 'ResizeObserver');
|
||||
Object.defineProperty(globalThis, 'ResizeObserver', {
|
||||
configurable: true,
|
||||
value: TestResizeObserver,
|
||||
});
|
||||
currentTestBed = createNoteViewTestBed();
|
||||
container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
root = createRoot(container);
|
||||
|
||||
return currentTestBed;
|
||||
}
|
||||
|
||||
it('creates a note with the configured default size when an empty cell popup opens', async () => {
|
||||
const testBed = createMountedNote();
|
||||
|
||||
await renderNote(root!, testBed);
|
||||
await act(async () => {
|
||||
await awaitTime(350);
|
||||
});
|
||||
|
||||
const note = testBed.noteModel.getNote(UNIT_ID, SUB_UNIT_ID, { row: 2, col: 3 });
|
||||
expect(note?.note).toBe('');
|
||||
expect(note?.width).toBe(180);
|
||||
expect(note?.height).toBe(96);
|
||||
expect(container!.querySelector('textarea')?.placeholder).toBe('Write a note');
|
||||
});
|
||||
|
||||
it('persists edited note text through the sheet note command pipeline', async () => {
|
||||
const testBed = createMountedNote();
|
||||
|
||||
await renderNote(root!, testBed);
|
||||
await act(async () => {
|
||||
await awaitTime(350);
|
||||
});
|
||||
|
||||
const textarea = container!.querySelector('textarea') as HTMLTextAreaElement;
|
||||
|
||||
await act(async () => {
|
||||
inputText(textarea, 'Follow up with finance');
|
||||
await awaitTime(350);
|
||||
});
|
||||
|
||||
const note = testBed.noteModel.getNote(UNIT_ID, SUB_UNIT_ID, { row: 2, col: 3 });
|
||||
expect(note?.note).toBe('Follow up with finance');
|
||||
expect(note?.width).toBe(180);
|
||||
expect(note?.height).toBe(96);
|
||||
});
|
||||
|
||||
it('moves an RTL note left while resizing wider, but reopens at the anchored position with the saved size', async () => {
|
||||
const testBed = createMountedNote();
|
||||
testBed.injector.get(LocaleService).setDirection('rtl');
|
||||
|
||||
await renderNote(root!, testBed);
|
||||
await act(async () => {
|
||||
await awaitTime(350);
|
||||
});
|
||||
|
||||
let textarea = container!.querySelector('textarea') as HTMLTextAreaElement;
|
||||
|
||||
await act(async () => {
|
||||
resizeTextarea(textarea, 220, 96);
|
||||
await awaitTime(350);
|
||||
});
|
||||
|
||||
expect(textarea.style.transform).toBe('translateX(-40px)');
|
||||
expect(testBed.noteModel.getNote(UNIT_ID, SUB_UNIT_ID, { row: 2, col: 3 })?.width).toBe(220);
|
||||
|
||||
act(() => {
|
||||
root!.unmount();
|
||||
});
|
||||
root = createRoot(container!);
|
||||
|
||||
await renderNote(root!, testBed);
|
||||
|
||||
textarea = container!.querySelector('textarea') as HTMLTextAreaElement;
|
||||
expect(textarea.style.width).toBe('220px');
|
||||
expect(textarea.style.transform).toBe('');
|
||||
});
|
||||
|
||||
it('closes the popup when creating the initial note cannot be saved', async () => {
|
||||
const testBed = createMountedNote();
|
||||
testBed.commandService.unregisterCommand(UpdateNoteMutation.id);
|
||||
testBed.commandService.registerCommand(FailingUpdateNoteMutation);
|
||||
testBed.notePopupService.showPopup(createNoteLocation(2, 3));
|
||||
|
||||
await renderNote(root!, testBed);
|
||||
await act(async () => {
|
||||
await awaitTime(350);
|
||||
});
|
||||
|
||||
expect(testBed.noteModel.getNote(UNIT_ID, SUB_UNIT_ID, { row: 2, col: 3 })).toBeUndefined();
|
||||
expect(testBed.notePopupService.activePopup).toBeNull();
|
||||
});
|
||||
|
||||
it('restores the previous note text when an edit cannot be saved', async () => {
|
||||
const testBed = createMountedNote();
|
||||
|
||||
testBed.commandService.syncExecuteCommand(SheetUpdateNoteCommand.id, {
|
||||
unitId: UNIT_ID,
|
||||
sheetId: SUB_UNIT_ID,
|
||||
row: 2,
|
||||
col: 3,
|
||||
note: {
|
||||
id: 'existing-note',
|
||||
width: 180,
|
||||
height: 96,
|
||||
note: 'Original note',
|
||||
},
|
||||
});
|
||||
testBed.commandService.unregisterCommand(UpdateNoteMutation.id);
|
||||
testBed.commandService.registerCommand(FailingUpdateNoteMutation);
|
||||
|
||||
await renderNote(root!, testBed);
|
||||
await act(async () => {
|
||||
await awaitTime(350);
|
||||
});
|
||||
|
||||
const textarea = container!.querySelector('textarea') as HTMLTextAreaElement;
|
||||
|
||||
await act(async () => {
|
||||
inputText(textarea, 'Unsaved edit');
|
||||
await awaitTime(350);
|
||||
});
|
||||
|
||||
const note = testBed.noteModel.getNote(UNIT_ID, SUB_UNIT_ID, { row: 2, col: 3 });
|
||||
expect(note?.note).toBe('Original note');
|
||||
expect(textarea.value).toBe('Original note');
|
||||
});
|
||||
});
|
||||
17
packages/sheets-note-ui/src/views/config.ts
Normal file
17
packages/sheets-note-ui/src/views/config.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 SHEET_NOTE_COMPONENT = 'SHEET_NOTE_COMPONENT';
|
||||
27
packages/sheets-note-ui/tailwind.config.ts
Normal file
27
packages/sheets-note-ui/tailwind.config.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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 { Config } from 'tailwindcss';
|
||||
import preset from '@univerjs-infra/shared/tailwind';
|
||||
|
||||
const config: Config = {
|
||||
presets: [preset],
|
||||
content: [
|
||||
'./src/**/*.{js,ts,jsx,tsx}',
|
||||
],
|
||||
};
|
||||
|
||||
export default config;
|
||||
8
packages/sheets-note-ui/tsconfig.json
Normal file
8
packages/sheets-note-ui/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-ui/tsconfig.node.json
Normal file
18
packages/sheets-note-ui/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-ui/vitest.config.ts
Normal file
3
packages/sheets-note-ui/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