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:
51
packages/sheets-thread-comment-ui/README.md
Normal file
51
packages/sheets-thread-comment-ui/README.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# @univerjs/sheets-thread-comment-ui
|
||||
|
||||
[](https://npmjs.com/package/@univerjs/sheets-thread-comment-ui)
|
||||
[](https://npmjs.com/package/@univerjs/sheets-thread-comment-ui)
|
||||
[](https://npmjs.com/package/@univerjs/sheets-thread-comment-ui)
|
||||
|
||||
`@univerjs/sheets-thread-comment-ui` adds thread comment UI integration for Univer Sheets.
|
||||
|
||||
## Package Overview
|
||||
|
||||
| Package | UMD global | CSS | Locales | Facade entry |
|
||||
| --- | --- | :---: | :---: | :---: |
|
||||
| `@univerjs/sheets-thread-comment-ui` | `UniverSheetsThreadCommentUi` | Yes | Yes | No |
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
pnpm add @univerjs/sheets-thread-comment-ui @univerjs/thread-comment-ui
|
||||
# or
|
||||
npm install @univerjs/sheets-thread-comment-ui @univerjs/thread-comment-ui
|
||||
```
|
||||
|
||||
Keep all `@univerjs/*` packages on the same version.
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { LocaleType, mergeLocales, Univer } from '@univerjs/core';
|
||||
import { UniverSheetsThreadCommentUIPlugin } from '@univerjs/sheets-thread-comment-ui';
|
||||
import SheetsThreadCommentUIEnUS from '@univerjs/sheets-thread-comment-ui/locale/en-US';
|
||||
import { UniverThreadCommentUIPlugin } from '@univerjs/thread-comment-ui';
|
||||
import ThreadCommentUIEnUS from '@univerjs/thread-comment-ui/locale/en-US';
|
||||
import '@univerjs/sheets-thread-comment-ui/lib/index.css';
|
||||
import '@univerjs/thread-comment-ui/lib/index.css';
|
||||
|
||||
const univer = new Univer({
|
||||
locale: LocaleType.EN_US,
|
||||
locales: {
|
||||
[LocaleType.EN_US]: mergeLocales(SheetsThreadCommentUIEnUS, ThreadCommentUIEnUS),
|
||||
},
|
||||
});
|
||||
|
||||
univer.registerPlugin(UniverThreadCommentUIPlugin);
|
||||
univer.registerPlugin(UniverSheetsThreadCommentUIPlugin);
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [Documentation](https://docs.univer.ai)
|
||||
- [NPM package](https://npmjs.com/package/@univerjs/sheets-thread-comment-ui)
|
||||
- [GitHub repository](https://github.com/dream-num/univer)
|
||||
98
packages/sheets-thread-comment-ui/package.json
Normal file
98
packages/sheets-thread-comment-ui/package.json
Normal file
@@ -0,0 +1,98 @@
|
||||
{
|
||||
"name": "@univerjs/sheets-thread-comment-ui",
|
||||
"version": "1.0.0-rc.0",
|
||||
"private": false,
|
||||
"description": "Thread comment UI integration 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",
|
||||
"comment",
|
||||
"thread-comment",
|
||||
"ui"
|
||||
],
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./*": "./src/*",
|
||||
"./locale/*": "./src/locale/*.ts"
|
||||
},
|
||||
"main": "./src/index.ts",
|
||||
"types": "./lib/types/index.d.ts",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "./lib/es/index.js",
|
||||
"module": "./lib/es/index.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./lib/es/index.js",
|
||||
"require": "./lib/cjs/index.js",
|
||||
"types": "./lib/types/index.d.ts"
|
||||
},
|
||||
"./*": {
|
||||
"import": "./lib/es/*",
|
||||
"require": "./lib/cjs/*",
|
||||
"types": "./lib/types/index.d.ts"
|
||||
},
|
||||
"./locale/*": {
|
||||
"import": "./lib/es/locale/*.js",
|
||||
"require": "./lib/cjs/locale/*.js",
|
||||
"types": "./lib/types/locale/*.d.ts"
|
||||
},
|
||||
"./lib/*": "./lib/*"
|
||||
}
|
||||
},
|
||||
"directories": {
|
||||
"lib": "lib"
|
||||
},
|
||||
"files": [
|
||||
"lib"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"coverage": "vitest run --coverage",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"build:bundle": "univer-cli build",
|
||||
"build:types": "tsc -p tsconfig.node.json",
|
||||
"build": "pnpm run build:bundle && pnpm run build:types"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"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/drawing": "workspace:*",
|
||||
"@univerjs/engine-formula": "workspace:*",
|
||||
"@univerjs/engine-render": "workspace:*",
|
||||
"@univerjs/icons": "1.38.0",
|
||||
"@univerjs/sheets": "workspace:*",
|
||||
"@univerjs/sheets-thread-comment": "workspace:*",
|
||||
"@univerjs/sheets-ui": "workspace:*",
|
||||
"@univerjs/thread-comment": "workspace:*",
|
||||
"@univerjs/thread-comment-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-thread-comment-ui/postcss.config.mjs
Normal file
1
packages/sheets-thread-comment-ui/postcss.config.mjs
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from '@univerjs-infra/shared/postcss';
|
||||
@@ -0,0 +1,137 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Dependency, IWorkbookData, Workbook } from '@univerjs/core';
|
||||
import type { ISelectionWithStyle } from '@univerjs/sheets';
|
||||
import {
|
||||
ICommandService,
|
||||
ILogService,
|
||||
Inject,
|
||||
Injector,
|
||||
IUniverInstanceService,
|
||||
LifecycleService,
|
||||
LifecycleStages,
|
||||
LocaleType,
|
||||
LogLevel,
|
||||
Plugin,
|
||||
Tools,
|
||||
Univer,
|
||||
UniverInstanceType,
|
||||
} from '@univerjs/core';
|
||||
import { SheetsThreadCommentModel } from '@univerjs/sheets-thread-comment';
|
||||
import { CellPopupManagerService, ISheetClipboardService, SheetCanvasPopManagerService } from '@univerjs/sheets-ui';
|
||||
import { AddCommentMutation, DeleteCommentMutation, IThreadCommentDataSourceService, ThreadCommentDataSourceService, ThreadCommentModel } from '@univerjs/thread-comment';
|
||||
import { vi } from 'vitest';
|
||||
|
||||
const TEST_WORKBOOK_DATA: IWorkbookData = {
|
||||
id: 'test',
|
||||
appVersion: '3.0.0-alpha',
|
||||
locale: LocaleType.EN_US,
|
||||
name: '',
|
||||
sheetOrder: ['sheet1', 'sheet2'],
|
||||
styles: {},
|
||||
sheets: {
|
||||
sheet1: {
|
||||
id: 'sheet1',
|
||||
name: 'Sheet1',
|
||||
rowCount: 20,
|
||||
columnCount: 20,
|
||||
cellData: {},
|
||||
},
|
||||
sheet2: {
|
||||
id: 'sheet2',
|
||||
name: 'Sheet2',
|
||||
rowCount: 20,
|
||||
columnCount: 20,
|
||||
cellData: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export function createThreadCommentUiTestBed(workbookData?: IWorkbookData, dependencies?: Dependency[]) {
|
||||
const univer = new Univer();
|
||||
const injector = univer.__getInjector();
|
||||
const get = injector.get.bind(injector);
|
||||
|
||||
let clipboardHook: {
|
||||
onBeforeCopy: (unitId: string, subUnitId: string, range: ISelectionWithStyle['range']) => void;
|
||||
onPasteCells: (...args: any[]) => { redos: Array<{ id: string; params: unknown }>; undos: Array<{ id: string; params: unknown }> };
|
||||
} | undefined;
|
||||
|
||||
const popupDisposable = {
|
||||
dispose: vi.fn(),
|
||||
};
|
||||
const cellPopupManagerService = {
|
||||
showPopup: vi.fn(() => popupDisposable),
|
||||
};
|
||||
const sheetClipboardService = {
|
||||
addClipboardHook: vi.fn((hook) => {
|
||||
clipboardHook = hook;
|
||||
return {
|
||||
dispose: vi.fn(),
|
||||
};
|
||||
}),
|
||||
};
|
||||
|
||||
class TestPlugin extends Plugin {
|
||||
static override pluginName = 'test-plugin';
|
||||
static override type = UniverInstanceType.UNIVER_SHEET;
|
||||
|
||||
constructor(
|
||||
_config: undefined,
|
||||
@Inject(Injector) override readonly _injector: Injector
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
override onStarting(): void {
|
||||
([
|
||||
[IThreadCommentDataSourceService, { useClass: ThreadCommentDataSourceService }],
|
||||
[ThreadCommentModel],
|
||||
[SheetsThreadCommentModel],
|
||||
[ISheetClipboardService, { useValue: sheetClipboardService as unknown as ISheetClipboardService }],
|
||||
[CellPopupManagerService, { useValue: cellPopupManagerService as unknown as CellPopupManagerService }],
|
||||
[SheetCanvasPopManagerService, { useValue: {} as SheetCanvasPopManagerService }],
|
||||
] as Dependency[]).forEach((dependency) => this._injector.add(dependency));
|
||||
|
||||
dependencies?.forEach((dependency) => this._injector.add(dependency));
|
||||
}
|
||||
}
|
||||
|
||||
univer.registerPlugin(TestPlugin);
|
||||
|
||||
const workbook = univer.createUnit<IWorkbookData, Workbook>(
|
||||
UniverInstanceType.UNIVER_SHEET,
|
||||
Tools.deepClone(workbookData ?? TEST_WORKBOOK_DATA)
|
||||
);
|
||||
|
||||
get(ILogService).setLogLevel(LogLevel.SILENT);
|
||||
get(IUniverInstanceService).focusUnit(workbook.getUnitId());
|
||||
get(LifecycleService).stage = LifecycleStages.Rendered;
|
||||
get(ICommandService).registerCommand(AddCommentMutation);
|
||||
get(ICommandService).registerCommand(DeleteCommentMutation);
|
||||
|
||||
return {
|
||||
univer,
|
||||
injector,
|
||||
get,
|
||||
workbook,
|
||||
commandService: get(ICommandService),
|
||||
cellPopupManagerService,
|
||||
popupDisposable,
|
||||
getClipboardHook: () => clipboardHook,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { IWorkbookData } from '@univerjs/core';
|
||||
import type { ISelectionWithStyle } from '@univerjs/sheets';
|
||||
import type { IThreadComment } from '@univerjs/thread-comment';
|
||||
import {
|
||||
DrawingTypeEnum,
|
||||
ICommandService,
|
||||
IUniverInstanceService,
|
||||
LifecycleService,
|
||||
LifecycleStages,
|
||||
LocaleType,
|
||||
toDisposable,
|
||||
Univer,
|
||||
UniverInstanceType,
|
||||
} from '@univerjs/core';
|
||||
import { DrawingManagerService, IDrawingManagerService } from '@univerjs/drawing';
|
||||
import { SheetsSelectionsService } from '@univerjs/sheets';
|
||||
import { SheetsThreadCommentModel } from '@univerjs/sheets-thread-comment';
|
||||
import { CellPopupManagerService, SheetCanvasPopManagerService } from '@univerjs/sheets-ui';
|
||||
import {
|
||||
AddCommentMutation,
|
||||
IThreadCommentDataSourceService,
|
||||
ThreadCommentDataSourceService,
|
||||
ThreadCommentModel,
|
||||
} from '@univerjs/thread-comment';
|
||||
import { ThreadCommentPanelService } from '@univerjs/thread-comment-ui';
|
||||
import { DesktopSidebarService, ISidebarService } from '@univerjs/ui';
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
import { SheetsThreadCommentPopupService } from '../../../services/sheets-thread-comment-popup.service';
|
||||
import { SHEETS_THREAD_COMMENT_PANEL } from '../../../types/const';
|
||||
import { AddSheetDrawingCommentOperation, ShowAddSheetCommentModalOperation, ToggleSheetCommentPanelOperation } from '../comment.operation';
|
||||
|
||||
const unitId = 'comment-workbook';
|
||||
const subUnitId = 'sheet-1';
|
||||
|
||||
const workbookData: IWorkbookData = {
|
||||
id: unitId,
|
||||
appVersion: '3.0.0-alpha',
|
||||
name: 'comment workbook',
|
||||
locale: LocaleType.EN_US,
|
||||
sheetOrder: [subUnitId],
|
||||
styles: {},
|
||||
sheets: {
|
||||
[subUnitId]: {
|
||||
id: subUnitId,
|
||||
name: 'Sheet 1',
|
||||
cellData: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
class TestCellPopupManagerService {
|
||||
showPopup() {
|
||||
return toDisposable(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
class TestSheetCanvasPopManagerService {}
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
function createComment(id: string, ref: string): IThreadComment {
|
||||
return {
|
||||
id,
|
||||
threadId: id,
|
||||
ref,
|
||||
unitId,
|
||||
subUnitId,
|
||||
dT: '2026-06-17T00:00:00.000Z',
|
||||
personId: 'user-1',
|
||||
text: {
|
||||
dataStream: 'Review this cell.\r\n',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe('sheet thread comment operations', () => {
|
||||
let univer: Univer;
|
||||
let commandService: ICommandService;
|
||||
let selectionsService: SheetsSelectionsService;
|
||||
let popupService: SheetsThreadCommentPopupService;
|
||||
let panelService: ThreadCommentPanelService;
|
||||
let sidebarService: ISidebarService;
|
||||
|
||||
beforeEach(() => {
|
||||
univer = new Univer();
|
||||
const injector = univer.__getInjector();
|
||||
injector.add([SheetsSelectionsService]);
|
||||
injector.add([IThreadCommentDataSourceService, { useClass: ThreadCommentDataSourceService }]);
|
||||
injector.add([ThreadCommentModel]);
|
||||
injector.add([SheetsThreadCommentModel]);
|
||||
injector.add([CellPopupManagerService, { useClass: TestCellPopupManagerService as never }]);
|
||||
injector.add([SheetCanvasPopManagerService, { useClass: TestSheetCanvasPopManagerService as never }]);
|
||||
injector.add([SheetsThreadCommentPopupService]);
|
||||
injector.add([ISidebarService, { useClass: DesktopSidebarService }]);
|
||||
injector.add([ThreadCommentPanelService]);
|
||||
|
||||
univer.createUnit(UniverInstanceType.UNIVER_SHEET, workbookData);
|
||||
injector.get(IUniverInstanceService).focusUnit(unitId);
|
||||
injector.get(LifecycleService).stage = LifecycleStages.Rendered;
|
||||
|
||||
commandService = injector.get(ICommandService);
|
||||
commandService.registerCommand(AddCommentMutation);
|
||||
commandService.registerCommand(ShowAddSheetCommentModalOperation);
|
||||
commandService.registerCommand(ToggleSheetCommentPanelOperation);
|
||||
selectionsService = injector.get(SheetsSelectionsService);
|
||||
popupService = injector.get(SheetsThreadCommentPopupService);
|
||||
panelService = injector.get(ThreadCommentPanelService);
|
||||
sidebarService = injector.get(ISidebarService);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
univer.dispose();
|
||||
});
|
||||
|
||||
it('opens the add-comment popup at the active cell and activates the existing thread', async () => {
|
||||
await commandService.executeCommand(AddCommentMutation.id, {
|
||||
unitId,
|
||||
subUnitId,
|
||||
comment: createComment('comment-1', 'D3'),
|
||||
});
|
||||
selectionsService.setSelections(unitId, subUnitId, [createSelection(2, 3)]);
|
||||
|
||||
const result = await commandService.executeCommand(ShowAddSheetCommentModalOperation.id);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(popupService.activePopup).toMatchObject({
|
||||
unitId,
|
||||
subUnitId,
|
||||
row: 2,
|
||||
col: 3,
|
||||
});
|
||||
expect(panelService.activeCommentId).toEqual({
|
||||
unitId,
|
||||
subUnitId,
|
||||
commentId: 'comment-1',
|
||||
trigger: 'context-menu',
|
||||
});
|
||||
});
|
||||
|
||||
it('does not open the add-comment popup without an active cell', async () => {
|
||||
const result = await commandService.executeCommand(ShowAddSheetCommentModalOperation.id);
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(popupService.activePopup).toBeUndefined();
|
||||
expect(panelService.activeCommentId).toBeUndefined();
|
||||
});
|
||||
|
||||
it('toggles the sheet comment panel through the sidebar', async () => {
|
||||
const openResult = await commandService.executeCommand(ToggleSheetCommentPanelOperation.id);
|
||||
|
||||
expect(openResult).toBe(true);
|
||||
expect(panelService.panelVisible).toBe(true);
|
||||
expect(sidebarService.visible).toBe(true);
|
||||
expect(sidebarService.options.children?.label).toBe(SHEETS_THREAD_COMMENT_PANEL);
|
||||
|
||||
const closeResult = await commandService.executeCommand(ToggleSheetCommentPanelOperation.id);
|
||||
|
||||
expect(closeResult).toBe(true);
|
||||
expect(panelService.panelVisible).toBe(false);
|
||||
expect(sidebarService.visible).toBe(false);
|
||||
});
|
||||
|
||||
it('rejects a focused drawing from another workbook or worksheet', () => {
|
||||
const injector = univer.__getInjector();
|
||||
const drawing = {
|
||||
unitId: 'other-book',
|
||||
subUnitId,
|
||||
drawingId: 'drawing-1',
|
||||
drawingType: DrawingTypeEnum.DRAWING_SHAPE,
|
||||
};
|
||||
const drawingManager = new DrawingManagerService();
|
||||
drawingManager.registerDrawingData(drawing.unitId, {
|
||||
[drawing.subUnitId]: { data: { [drawing.drawingId]: drawing }, order: [drawing.drawingId] },
|
||||
});
|
||||
drawingManager.focusDrawing([drawing]);
|
||||
injector.add([IDrawingManagerService, {
|
||||
useValue: drawingManager,
|
||||
}]);
|
||||
|
||||
expect(AddSheetDrawingCommentOperation.handler(injector, {})).toBe(false);
|
||||
drawingManager.dispose();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* 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 { IOperation, Workbook } from '@univerjs/core';
|
||||
import type { ISheetLocation } from '@univerjs/sheets';
|
||||
import { CommandType, IUniverInstanceService, UniverInstanceType } from '@univerjs/core';
|
||||
import { IDrawingManagerService } from '@univerjs/drawing';
|
||||
import { getSheetCommandTarget, SheetsSelectionsService } from '@univerjs/sheets';
|
||||
import { SheetsThreadCommentModel } from '@univerjs/sheets-thread-comment';
|
||||
import { ThreadCommentAnchorKind } from '@univerjs/thread-comment';
|
||||
import { ThreadCommentDraftService, ThreadCommentPanelService } from '@univerjs/thread-comment-ui';
|
||||
import { ISidebarService } from '@univerjs/ui';
|
||||
import { SheetsThreadCommentPopupService } from '../../services/sheets-thread-comment-popup.service';
|
||||
import { SHEETS_THREAD_COMMENT_PANEL, SHOW_ADD_SHEET_COMMENT_OPERATION_ID } from '../../types/const';
|
||||
|
||||
export const ShowAddSheetCommentModalOperation: IOperation = {
|
||||
type: CommandType.OPERATION,
|
||||
id: SHOW_ADD_SHEET_COMMENT_OPERATION_ID,
|
||||
handler(accessor) {
|
||||
const selectionManagerService = accessor.get(SheetsSelectionsService);
|
||||
const univerInstanceService = accessor.get(IUniverInstanceService);
|
||||
|
||||
const sheetsThreadCommentPopupService = accessor.get(SheetsThreadCommentPopupService);
|
||||
const threadCommentPanelService = accessor.get(ThreadCommentPanelService);
|
||||
const activeCell = selectionManagerService.getCurrentLastSelection()?.primary;
|
||||
const model = accessor.get(SheetsThreadCommentModel);
|
||||
|
||||
if (!activeCell) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const result = getSheetCommandTarget(univerInstanceService);
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { workbook, worksheet, unitId, subUnitId } = result;
|
||||
const location: ISheetLocation = {
|
||||
workbook,
|
||||
worksheet,
|
||||
unitId,
|
||||
subUnitId,
|
||||
row: activeCell.startRow,
|
||||
col: activeCell.startColumn,
|
||||
};
|
||||
|
||||
sheetsThreadCommentPopupService.showPopup(location);
|
||||
const rootId = model.getByLocation(unitId, subUnitId, activeCell.startRow, activeCell.startColumn);
|
||||
if (rootId) {
|
||||
threadCommentPanelService.setActiveComment({
|
||||
unitId,
|
||||
subUnitId,
|
||||
commentId: rootId,
|
||||
trigger: 'context-menu',
|
||||
});
|
||||
}
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
export const ToggleSheetCommentPanelOperation: IOperation = {
|
||||
id: 'sheet.operation.toggle-comment-panel',
|
||||
type: CommandType.OPERATION,
|
||||
handler(accessor) {
|
||||
const sidebarService = accessor.get(ISidebarService);
|
||||
const panelService = accessor.get(ThreadCommentPanelService);
|
||||
|
||||
if (panelService.panelVisible) {
|
||||
sidebarService.close();
|
||||
panelService.setPanelVisible(false);
|
||||
} else {
|
||||
openSheetCommentPanel(sidebarService, panelService);
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
export const OpenSheetCommentPanelOperation: IOperation = {
|
||||
id: 'sheet.operation.open-comment-panel',
|
||||
type: CommandType.OPERATION,
|
||||
handler(accessor) {
|
||||
openSheetCommentPanel(
|
||||
accessor.get(ISidebarService),
|
||||
accessor.get(ThreadCommentPanelService)
|
||||
);
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
export function openSheetCommentPanel(
|
||||
sidebarService: ISidebarService,
|
||||
panelService: ThreadCommentPanelService
|
||||
): void {
|
||||
if (!panelService.panelVisible || sidebarService.options.children?.label !== SHEETS_THREAD_COMMENT_PANEL) {
|
||||
sidebarService.open({
|
||||
header: { title: 'sheets-thread-comment-ui.panel.title' },
|
||||
children: { label: SHEETS_THREAD_COMMENT_PANEL },
|
||||
width: 360,
|
||||
onClose: () => panelService.setPanelVisible(false),
|
||||
});
|
||||
}
|
||||
panelService.setPanelVisible(true);
|
||||
}
|
||||
|
||||
export const AddSheetDrawingCommentOperation: IOperation = {
|
||||
id: 'sheet.operation.add-drawing-comment',
|
||||
type: CommandType.OPERATION,
|
||||
handler(accessor) {
|
||||
const drawing = accessor.get(IDrawingManagerService).getFocusDrawings()[0];
|
||||
const workbook = accessor.get(IUniverInstanceService)
|
||||
.getCurrentUnitOfType<Workbook>(UniverInstanceType.UNIVER_SHEET);
|
||||
if (
|
||||
!drawing
|
||||
|| !workbook
|
||||
|| drawing.unitId !== workbook.getUnitId()
|
||||
|| drawing.subUnitId !== workbook.getActiveSheet()?.getSheetId()
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
accessor.get(ThreadCommentDraftService).place({
|
||||
unitId: drawing.unitId,
|
||||
subUnitId: drawing.subUnitId,
|
||||
anchor: {
|
||||
kind: ThreadCommentAnchorKind.SHEET_DRAWING,
|
||||
pageId: drawing.subUnitId,
|
||||
elementId: drawing.drawingId,
|
||||
},
|
||||
});
|
||||
const panelService = accessor.get(ThreadCommentPanelService);
|
||||
accessor.get(ISidebarService).open({
|
||||
header: { title: 'sheets-thread-comment-ui.panel.title' },
|
||||
children: { label: SHEETS_THREAD_COMMENT_PANEL },
|
||||
width: 360,
|
||||
onClose: () => panelService.setPanelVisible(false),
|
||||
});
|
||||
panelService.setPanelVisible(true);
|
||||
return true;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* 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 { IOperation } from '@univerjs/core';
|
||||
import { CommandType, IUniverInstanceService } from '@univerjs/core';
|
||||
import { getSheetCommandTarget, SheetsSelectionsService } from '@univerjs/sheets';
|
||||
import { SheetsThreadCommentModel } from '@univerjs/sheets-thread-comment';
|
||||
import { ThreadCommentDraftService, ThreadCommentPanelService } from '@univerjs/thread-comment-ui';
|
||||
import { ISidebarService } from '@univerjs/ui';
|
||||
import { MobileSheetCommentDraftService } from '../../../services/mobile/mobile-sheet-comment-draft.service';
|
||||
import { SHOW_ADD_SHEET_COMMENT_OPERATION_ID } from '../../../types/const';
|
||||
import { openSheetCommentPanel } from '../comment.operation';
|
||||
|
||||
export const ShowAddSheetCommentMobileOperation: IOperation = {
|
||||
type: CommandType.OPERATION,
|
||||
id: SHOW_ADD_SHEET_COMMENT_OPERATION_ID,
|
||||
handler(accessor) {
|
||||
const activeCell = accessor.get(SheetsSelectionsService).getCurrentLastSelection()?.primary;
|
||||
if (!activeCell) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const target = getSheetCommandTarget(accessor.get(IUniverInstanceService));
|
||||
if (!target) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { unitId, subUnitId } = target;
|
||||
const row = activeCell.startRow;
|
||||
const column = activeCell.startColumn;
|
||||
const panelService = accessor.get(ThreadCommentPanelService);
|
||||
const mobileDraftService = accessor.get(MobileSheetCommentDraftService);
|
||||
const rootId = accessor.get(SheetsThreadCommentModel).getByLocation(unitId, subUnitId, row, column);
|
||||
|
||||
accessor.get(ThreadCommentDraftService).cancel();
|
||||
if (rootId) {
|
||||
mobileDraftService.cancel();
|
||||
panelService.setActiveComment({
|
||||
unitId,
|
||||
subUnitId,
|
||||
commentId: rootId,
|
||||
});
|
||||
} else {
|
||||
panelService.setActiveComment(undefined);
|
||||
mobileDraftService.place({ unitId, subUnitId, row, column });
|
||||
}
|
||||
|
||||
openSheetCommentPanel(accessor.get(ISidebarService), panelService);
|
||||
return true;
|
||||
},
|
||||
};
|
||||
29
packages/sheets-thread-comment-ui/src/config/config.ts
Normal file
29
packages/sheets-thread-comment-ui/src/config/config.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 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 { DependencyOverride } from '@univerjs/core';
|
||||
import type { MenuConfig } from '@univerjs/ui';
|
||||
|
||||
export const SHEETS_THREAD_COMMENT_UI_PLUGIN_CONFIG_KEY = 'sheets-thread-comment.config';
|
||||
|
||||
export const configSymbol = Symbol(SHEETS_THREAD_COMMENT_UI_PLUGIN_CONFIG_KEY);
|
||||
|
||||
export interface IUniverSheetsThreadCommentUIConfig {
|
||||
menu?: MenuConfig;
|
||||
overrides?: DependencyOverride;
|
||||
}
|
||||
|
||||
export const defaultPluginConfig: IUniverSheetsThreadCommentUIConfig = {};
|
||||
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* 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 { COPY_TYPE } from '@univerjs/sheets-ui';
|
||||
import { AddCommentMutation, DeleteCommentMutation } from '@univerjs/thread-comment';
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
import { createThreadCommentUiTestBed } from '../../__tests__/create-thread-comment-ui-test-bed';
|
||||
import { SheetsThreadCommentCopyPasteController } from '../sheets-thread-comment-copy-paste.controller';
|
||||
|
||||
function createRootComment() {
|
||||
return {
|
||||
id: 'comment-1',
|
||||
threadId: 'thread-1',
|
||||
ref: 'A1',
|
||||
dT: '2025-01-01T00:00:00.000Z',
|
||||
personId: 'user-1',
|
||||
text: { dataStream: 'hello\r\n' },
|
||||
attachments: [],
|
||||
unitId: 'test',
|
||||
subUnitId: 'sheet1',
|
||||
};
|
||||
}
|
||||
|
||||
describe('SheetsThreadCommentCopyPasteController', () => {
|
||||
let testBed: ReturnType<typeof createThreadCommentUiTestBed>;
|
||||
|
||||
beforeEach(() => {
|
||||
testBed = createThreadCommentUiTestBed();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
testBed.univer.dispose();
|
||||
});
|
||||
|
||||
it('creates delete/add mutations when cutting comments to another sheet', async () => {
|
||||
await testBed.commandService.executeCommand(AddCommentMutation.id, {
|
||||
unitId: 'test',
|
||||
subUnitId: 'sheet1',
|
||||
comment: createRootComment(),
|
||||
});
|
||||
|
||||
const controller = testBed.injector.createInstance(SheetsThreadCommentCopyPasteController);
|
||||
const hook = testBed.getClipboardHook();
|
||||
|
||||
expect(hook).toBeDefined();
|
||||
|
||||
hook!.onBeforeCopy('test', 'sheet1', {
|
||||
startRow: 0,
|
||||
endRow: 0,
|
||||
startColumn: 0,
|
||||
endColumn: 0,
|
||||
});
|
||||
|
||||
const result = hook!.onPasteCells(
|
||||
null,
|
||||
{
|
||||
unitId: 'test',
|
||||
subUnitId: 'sheet2',
|
||||
range: {
|
||||
rows: [2],
|
||||
cols: [3],
|
||||
},
|
||||
},
|
||||
null,
|
||||
{
|
||||
copyType: COPY_TYPE.CUT,
|
||||
}
|
||||
);
|
||||
|
||||
expect(result.redos).toEqual([
|
||||
{
|
||||
id: DeleteCommentMutation.id,
|
||||
params: {
|
||||
unitId: 'test',
|
||||
subUnitId: 'sheet1',
|
||||
commentId: 'comment-1',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: AddCommentMutation.id,
|
||||
params: expect.objectContaining({
|
||||
unitId: 'test',
|
||||
subUnitId: 'sheet2',
|
||||
sync: true,
|
||||
comment: expect.objectContaining({
|
||||
id: 'comment-1',
|
||||
ref: expect.stringContaining('D3'),
|
||||
unitId: 'test',
|
||||
subUnitId: 'sheet2',
|
||||
}),
|
||||
}),
|
||||
},
|
||||
]);
|
||||
expect(result.undos).toEqual([
|
||||
{
|
||||
id: DeleteCommentMutation.id,
|
||||
params: {
|
||||
unitId: 'test',
|
||||
subUnitId: 'sheet2',
|
||||
commentId: 'comment-1',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: AddCommentMutation.id,
|
||||
params: expect.objectContaining({
|
||||
unitId: 'test',
|
||||
subUnitId: 'sheet1',
|
||||
sync: true,
|
||||
comment: expect.objectContaining({
|
||||
id: 'comment-1',
|
||||
ref: 'A1',
|
||||
}),
|
||||
}),
|
||||
},
|
||||
]);
|
||||
|
||||
controller.dispose();
|
||||
});
|
||||
|
||||
it('does not implicitly clone a comment during a normal cell copy', async () => {
|
||||
await testBed.commandService.executeCommand(AddCommentMutation.id, {
|
||||
unitId: 'test',
|
||||
subUnitId: 'sheet1',
|
||||
comment: createRootComment(),
|
||||
});
|
||||
const controller = testBed.injector.createInstance(SheetsThreadCommentCopyPasteController);
|
||||
const hook = testBed.getClipboardHook()!;
|
||||
hook.onBeforeCopy('test', 'sheet1', {
|
||||
startRow: 0,
|
||||
endRow: 0,
|
||||
startColumn: 0,
|
||||
endColumn: 0,
|
||||
});
|
||||
|
||||
expect(hook.onPasteCells(
|
||||
null,
|
||||
{ unitId: 'test', subUnitId: 'sheet2', range: { rows: [2], cols: [3] } },
|
||||
null,
|
||||
{ copyType: COPY_TYPE.COPY }
|
||||
)).toEqual({ redos: [], undos: [] });
|
||||
|
||||
controller.dispose();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* 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 { Subject } from 'rxjs';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { SheetsThreadCommentHoverController } from '../sheets-thread-comment-hover.controller';
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
function createController(options?: { activePopup?: unknown; canView?: boolean; resolved?: boolean }) {
|
||||
const currentCell$ = new Subject<any>();
|
||||
const popupService = {
|
||||
activePopup: options?.activePopup ?? null,
|
||||
showPopup: vi.fn(),
|
||||
hidePopup: vi.fn(),
|
||||
};
|
||||
const controller = new SheetsThreadCommentHoverController(
|
||||
{ currentCell$ } as never,
|
||||
popupService as never,
|
||||
{
|
||||
getByLocation: vi.fn(() => 'comment-1'),
|
||||
getComment: vi.fn(() => ({ id: 'comment-1', resolved: options?.resolved ?? false })),
|
||||
} as never,
|
||||
{ permissionCheckWithRanges: vi.fn(() => options?.canView ?? true) } as never
|
||||
);
|
||||
|
||||
return { controller, currentCell$, popupService };
|
||||
}
|
||||
|
||||
describe('SheetsThreadCommentHoverController', () => {
|
||||
it('shows a temporary popup when hovering an unresolved visible comment marker', async () => {
|
||||
vi.useFakeTimers();
|
||||
const { controller, currentCell$, popupService } = createController();
|
||||
|
||||
currentCell$.next({
|
||||
location: {
|
||||
unitId: 'unit-1',
|
||||
subUnitId: 'sheet-1',
|
||||
row: 2,
|
||||
col: 3,
|
||||
},
|
||||
});
|
||||
await vi.advanceTimersByTimeAsync(120);
|
||||
|
||||
expect(popupService.showPopup).toHaveBeenCalledWith({
|
||||
unitId: 'unit-1',
|
||||
subUnitId: 'sheet-1',
|
||||
row: 2,
|
||||
col: 3,
|
||||
commentId: 'comment-1',
|
||||
temp: true,
|
||||
});
|
||||
|
||||
controller.dispose();
|
||||
});
|
||||
|
||||
it('does not replace a persistent popup and hides an existing temp popup when no marker exists', async () => {
|
||||
vi.useFakeTimers();
|
||||
const { controller, currentCell$, popupService } = createController({ activePopup: { temp: false } });
|
||||
currentCell$.next({
|
||||
location: {
|
||||
unitId: 'unit-1',
|
||||
subUnitId: 'sheet-1',
|
||||
row: 2,
|
||||
col: 3,
|
||||
},
|
||||
});
|
||||
await vi.advanceTimersByTimeAsync(120);
|
||||
|
||||
expect(popupService.showPopup).not.toHaveBeenCalled();
|
||||
controller.dispose();
|
||||
|
||||
const noMarkerCell$ = new Subject<any>();
|
||||
const tempPopupService = {
|
||||
activePopup: { temp: true },
|
||||
showPopup: vi.fn(),
|
||||
hidePopup: vi.fn(),
|
||||
};
|
||||
const noMarkerController = new SheetsThreadCommentHoverController(
|
||||
{ currentCell$: noMarkerCell$ } as never,
|
||||
tempPopupService as never,
|
||||
{ getByLocation: vi.fn(() => null), getComment: vi.fn() } as never,
|
||||
{ permissionCheckWithRanges: vi.fn(() => true) } as never
|
||||
);
|
||||
|
||||
noMarkerCell$.next({ location: { unitId: 'unit-1', subUnitId: 'sheet-1', row: 2, col: 3 } });
|
||||
await vi.advanceTimersByTimeAsync(120);
|
||||
|
||||
expect(tempPopupService.hidePopup).toHaveBeenCalled();
|
||||
noMarkerController.dispose();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* 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 { ICommandService, Injector, LocaleService } from '@univerjs/core';
|
||||
import { SheetPermissionCheckController } from '@univerjs/sheets';
|
||||
import { SheetsThreadCommentModel } from '@univerjs/sheets-thread-comment';
|
||||
import {
|
||||
AddCommentCommand,
|
||||
DeleteCommentCommand,
|
||||
serializeThreadCommentAnchor,
|
||||
ThreadCommentAnchorKind,
|
||||
UpdateCommentCommand,
|
||||
} from '@univerjs/thread-comment';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { AddSheetDrawingCommentOperation, ShowAddSheetCommentModalOperation } from '../../commands/operations/comment.operation';
|
||||
import { SheetsThreadCommentPermissionController } from '../sheets-thread-comment-permission.controller';
|
||||
|
||||
type BeforeCommandHandler = (command: { id: string; params?: unknown }) => void;
|
||||
|
||||
function createTestBed(permissionCheck: object, commentModel: object = { getComment: vi.fn() }) {
|
||||
let beforeCommandHandler: BeforeCommandHandler | undefined;
|
||||
const injector = new Injector([
|
||||
[ICommandService, { useValue: {
|
||||
beforeCommandExecuted: vi.fn((handler: BeforeCommandHandler) => {
|
||||
beforeCommandHandler = handler;
|
||||
return { dispose: vi.fn() };
|
||||
}),
|
||||
} }],
|
||||
[LocaleService, { useValue: { t: (key: string) => key } }],
|
||||
[SheetPermissionCheckController, { useValue: permissionCheck }],
|
||||
[SheetsThreadCommentModel, { useValue: commentModel }],
|
||||
[SheetsThreadCommentPermissionController],
|
||||
]);
|
||||
|
||||
return {
|
||||
controller: injector.get(SheetsThreadCommentPermissionController),
|
||||
getBeforeCommandHandler: () => beforeCommandHandler,
|
||||
};
|
||||
}
|
||||
|
||||
describe('SheetsThreadCommentPermissionController', () => {
|
||||
it('blocks comment panel, add, update and delete actions when comment permissions are denied', () => {
|
||||
const permissionCheck = {
|
||||
permissionCheckWithoutRange: vi.fn(() => false),
|
||||
permissionCheckWithRanges: vi.fn(() => false),
|
||||
blockExecuteWithoutPermission: vi.fn(),
|
||||
};
|
||||
const { controller, getBeforeCommandHandler } = createTestBed(
|
||||
permissionCheck,
|
||||
{
|
||||
getComment: vi.fn(() => ({ id: 'comment-1', ref: 'C4' })),
|
||||
}
|
||||
);
|
||||
const beforeCommandHandler = getBeforeCommandHandler();
|
||||
|
||||
beforeCommandHandler?.({ id: ShowAddSheetCommentModalOperation.id });
|
||||
beforeCommandHandler?.({ id: AddSheetDrawingCommentOperation.id });
|
||||
beforeCommandHandler?.({
|
||||
id: AddCommentCommand.id,
|
||||
params: {
|
||||
unitId: 'unit-1',
|
||||
subUnitId: 'sheet-1',
|
||||
comment: { ref: 'B2' },
|
||||
},
|
||||
});
|
||||
beforeCommandHandler?.({
|
||||
id: UpdateCommentCommand.id,
|
||||
params: {
|
||||
unitId: 'unit-1',
|
||||
subUnitId: 'sheet-1',
|
||||
payload: { commentId: 'comment-1' },
|
||||
},
|
||||
});
|
||||
beforeCommandHandler?.({
|
||||
id: DeleteCommentCommand.id,
|
||||
params: {
|
||||
unitId: 'unit-1',
|
||||
subUnitId: 'sheet-1',
|
||||
commentId: 'comment-1',
|
||||
},
|
||||
});
|
||||
|
||||
expect(permissionCheck.permissionCheckWithoutRange).toHaveBeenCalledTimes(2);
|
||||
expect(permissionCheck.permissionCheckWithRanges).toHaveBeenCalledWith(expect.any(Object), [{
|
||||
startRow: 1,
|
||||
startColumn: 1,
|
||||
endRow: 1,
|
||||
endColumn: 1,
|
||||
}], 'unit-1', 'sheet-1');
|
||||
expect(permissionCheck.permissionCheckWithRanges).toHaveBeenCalledWith(expect.any(Object), [{
|
||||
startRow: 3,
|
||||
startColumn: 2,
|
||||
endRow: 3,
|
||||
endColumn: 2,
|
||||
}], 'unit-1', 'sheet-1');
|
||||
expect(permissionCheck.blockExecuteWithoutPermission).toHaveBeenCalledTimes(5);
|
||||
expect(permissionCheck.blockExecuteWithoutPermission).toHaveBeenLastCalledWith('sheets-thread-comment-ui.permission.commentErr');
|
||||
|
||||
controller.dispose();
|
||||
});
|
||||
|
||||
it('rechecks drawing comment permissions without creating an invalid cell range', () => {
|
||||
const permissionCheck = {
|
||||
permissionCheckWithoutRange: vi.fn()
|
||||
.mockReturnValueOnce(true)
|
||||
.mockReturnValueOnce(false),
|
||||
permissionCheckWithRanges: vi.fn(),
|
||||
blockExecuteWithoutPermission: vi.fn(),
|
||||
};
|
||||
const { controller, getBeforeCommandHandler } = createTestBed(permissionCheck);
|
||||
const beforeCommandHandler = getBeforeCommandHandler();
|
||||
const command = {
|
||||
id: AddCommentCommand.id,
|
||||
params: {
|
||||
unitId: 'unit-1',
|
||||
subUnitId: 'sheet-1',
|
||||
comment: {
|
||||
ref: serializeThreadCommentAnchor({
|
||||
kind: ThreadCommentAnchorKind.SHEET_DRAWING,
|
||||
elementId: 'drawing-1',
|
||||
}),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
beforeCommandHandler?.(command);
|
||||
beforeCommandHandler?.(command);
|
||||
|
||||
expect(permissionCheck.permissionCheckWithoutRange).toHaveBeenCalledTimes(2);
|
||||
expect(permissionCheck.permissionCheckWithoutRange).toHaveBeenLastCalledWith(
|
||||
expect.any(Object),
|
||||
'unit-1',
|
||||
'sheet-1'
|
||||
);
|
||||
expect(permissionCheck.permissionCheckWithRanges).not.toHaveBeenCalled();
|
||||
expect(permissionCheck.blockExecuteWithoutPermission).toHaveBeenCalledTimes(1);
|
||||
controller.dispose();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* 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 { describe, expect, it } from 'vitest';
|
||||
import { resolveFocusedSheetComment } from '../sheets-thread-comment-popup.controller';
|
||||
|
||||
describe('resolveFocusedSheetComment', () => {
|
||||
const activeComment = {
|
||||
unitId: 'book-1',
|
||||
subUnitId: 'sheet-1',
|
||||
commentId: 'active-comment',
|
||||
};
|
||||
|
||||
it('prefers a hovered comment from the current sheet', () => {
|
||||
const hoveredComment = {
|
||||
unitId: 'book-1',
|
||||
subUnitId: 'sheet-1',
|
||||
commentId: 'hovered-comment',
|
||||
};
|
||||
|
||||
expect(resolveFocusedSheetComment(activeComment, hoveredComment, 'book-1', 'sheet-1'))
|
||||
.toEqual(hoveredComment);
|
||||
});
|
||||
|
||||
it('keeps the active highlight when hover belongs to another sheet or workbook', () => {
|
||||
expect(resolveFocusedSheetComment(activeComment, {
|
||||
unitId: 'book-1',
|
||||
subUnitId: 'sheet-2',
|
||||
commentId: 'foreign-hover',
|
||||
}, 'book-1', 'sheet-1')).toEqual(activeComment);
|
||||
expect(resolveFocusedSheetComment(activeComment, {
|
||||
unitId: 'book-2',
|
||||
subUnitId: 'sheet-1',
|
||||
commentId: 'foreign-hover',
|
||||
}, 'book-1', 'sheet-1')).toEqual(activeComment);
|
||||
});
|
||||
|
||||
it('does not draw a stale highlight outside the current sheet context', () => {
|
||||
expect(resolveFocusedSheetComment(activeComment, undefined, 'book-1', 'sheet-2')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* 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 { CommentIcon, InsertCommentDoubleIcon } from '@univerjs/icons';
|
||||
import { ComponentManager, IconManager } from '@univerjs/ui';
|
||||
import { SHEETS_THREAD_COMMENT_MODAL, SHEETS_THREAD_COMMENT_PANEL } from '../types/const';
|
||||
import { SheetsThreadCommentCell } from '../views/SheetsThreadCommentCell';
|
||||
import { SheetsThreadCommentPanel } from '../views/SheetsThreadCommentPanel';
|
||||
|
||||
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 {
|
||||
([
|
||||
[SHEETS_THREAD_COMMENT_MODAL, SheetsThreadCommentCell],
|
||||
[SHEETS_THREAD_COMMENT_PANEL, SheetsThreadCommentPanel],
|
||||
] as const).forEach(([key, comp]) => {
|
||||
this.disposeWithMe(
|
||||
this._componentManager.register(key, comp)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private _registerIcons(): void {
|
||||
this.disposeWithMe(this._iconManager.register({
|
||||
CommentIcon,
|
||||
InsertCommentDoubleIcon,
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* 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 { CommentIcon, InsertCommentDoubleIcon } from '@univerjs/icons';
|
||||
import { ComponentManager, IconManager } from '@univerjs/ui';
|
||||
import { SHEETS_THREAD_COMMENT_PANEL } from '../../types/const';
|
||||
import { MobileSheetsThreadCommentPanel } from '../../views/mobile/MobileSheetsThreadCommentPanel';
|
||||
|
||||
export class MobileComponentsController extends Disposable {
|
||||
constructor(
|
||||
@Inject(ComponentManager) private readonly _componentManager: ComponentManager,
|
||||
@Inject(IconManager) private readonly _iconManager: IconManager
|
||||
) {
|
||||
super();
|
||||
|
||||
this.disposeWithMe(this._componentManager.register(SHEETS_THREAD_COMMENT_PANEL, MobileSheetsThreadCommentPanel));
|
||||
this.disposeWithMe(this._iconManager.register({ CommentIcon, InsertCommentDoubleIcon }));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* 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 } from '@univerjs/core';
|
||||
import { IMenuManagerService } from '@univerjs/ui';
|
||||
import { mobileMenuSchema } from '../../menu/mobile-schema';
|
||||
|
||||
export class SheetsThreadCommentMobileController extends Disposable {
|
||||
constructor(
|
||||
@IMenuManagerService menuManagerService: IMenuManagerService
|
||||
) {
|
||||
super();
|
||||
menuManagerService.mergeMenu(mobileMenuSchema);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { getDrawingShapeKeyByDrawingSearch } from '@univerjs/drawing';
|
||||
import { Vector2 } from '@univerjs/engine-render';
|
||||
import { serializeThreadCommentAnchor, ThreadCommentAnchorKind } from '@univerjs/thread-comment';
|
||||
import { BehaviorSubject, Subject } from 'rxjs';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { SheetsThreadCommentDrawingRenderController } from '../drawing.render-controller';
|
||||
|
||||
describe('SheetsThreadCommentDrawingRenderController', () => {
|
||||
it('aggregates drawing roots, follows transforms, and retains the remaining root after deletion', () => {
|
||||
const worksheet = { getSheetId: () => 'sheet-1', getZoomRatio: () => 2 };
|
||||
const activeSheet$ = new BehaviorSubject(worksheet);
|
||||
const workbook = { activeSheet$, getActiveSheet: () => worksheet };
|
||||
const drawingKey = getDrawingShapeKeyByDrawingSearch({
|
||||
unitId: 'book-1',
|
||||
subUnitId: 'sheet-1',
|
||||
drawingId: 'shape-1',
|
||||
});
|
||||
let bounds = { left: 20, top: 40, width: 80, height: 60 };
|
||||
let roots = ['first-comment', 'newest-comment'];
|
||||
const scene = {
|
||||
addObject: vi.fn(),
|
||||
getObjectIncludeInGroup: vi.fn((key: string) => key === drawingKey ? { getRealBound: () => bounds } : null),
|
||||
getObject: vi.fn(),
|
||||
};
|
||||
const context = {
|
||||
unitId: 'book-1',
|
||||
unit: workbook,
|
||||
scene,
|
||||
engine: { onTransformChange$: { subscribeEvent: vi.fn(() => ({ dispose: vi.fn() })) } },
|
||||
};
|
||||
const commentUpdate$ = new Subject<{ unitId: string }>();
|
||||
const ref = serializeThreadCommentAnchor({
|
||||
kind: ThreadCommentAnchorKind.SHEET_DRAWING,
|
||||
pageId: 'sheet-1',
|
||||
elementId: 'shape-1',
|
||||
});
|
||||
const commentModel = {
|
||||
commentUpdate$,
|
||||
query: vi.fn(() => roots.map((id) => ({ root: { id, ref } }))),
|
||||
getComment: vi.fn((_unitId: string, _subUnitId: string, commentId: string) => ({ id: commentId, ref })),
|
||||
};
|
||||
const activeCommentId$ = new BehaviorSubject({
|
||||
unitId: 'book-1',
|
||||
subUnitId: 'sheet-1',
|
||||
commentId: 'first-comment',
|
||||
});
|
||||
const hoveredCommentId$ = new BehaviorSubject<undefined>(undefined);
|
||||
const panelService = {
|
||||
activeCommentId: activeCommentId$.value,
|
||||
hoveredCommentId: hoveredCommentId$.value,
|
||||
activeCommentId$,
|
||||
hoveredCommentId$,
|
||||
setActiveComment: vi.fn(),
|
||||
};
|
||||
const drawingManager = { add$: new Subject(), update$: new Subject(), remove$: new Subject() };
|
||||
const themeService = {
|
||||
currentTheme$: new Subject(),
|
||||
getColorFromTheme: vi.fn((token: string) => token),
|
||||
};
|
||||
const controller = new SheetsThreadCommentDrawingRenderController(
|
||||
context as never,
|
||||
{ executeCommand: vi.fn(() => Promise.resolve(true)) } as never,
|
||||
drawingManager as never,
|
||||
commentModel as never,
|
||||
panelService as never,
|
||||
themeService as never
|
||||
);
|
||||
const overlay = scene.addObject.mock.calls[0][0];
|
||||
|
||||
expect(overlay.isHit(new Vector2(60, 101))).toBe(true);
|
||||
expect(overlay.hitCommentId).toBe('newest-comment');
|
||||
|
||||
roots = ['first-comment'];
|
||||
commentUpdate$.next({ unitId: 'book-1' });
|
||||
expect(overlay.isHit(new Vector2(60, 101))).toBe(true);
|
||||
expect(overlay.hitCommentId).toBe('first-comment');
|
||||
|
||||
bounds = { left: 120, top: 140, width: 100, height: 70 };
|
||||
drawingManager.update$.next([{ unitId: 'book-1' }]);
|
||||
expect(overlay.isHit(new Vector2(60, 101))).toBe(false);
|
||||
expect(overlay.isHit(new Vector2(170, 211))).toBe(true);
|
||||
|
||||
roots = [];
|
||||
commentUpdate$.next({ unitId: 'book-1' });
|
||||
expect(overlay.isHit(new Vector2(170, 211))).toBe(false);
|
||||
controller.dispose();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,161 @@
|
||||
/**
|
||||
* 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 { EventState, Workbook } from '@univerjs/core';
|
||||
import type { IMouseEvent, IPointerEvent, IRenderContext, IRenderModule } from '@univerjs/engine-render';
|
||||
import type { IThreadCommentCanvasOutline, IThreadCommentCanvasUnderline } from '@univerjs/thread-comment-ui';
|
||||
import { ICommandService, Inject, RxDisposable, ThemeService, toDisposable } from '@univerjs/core';
|
||||
import { getDrawingShapeKeyByDrawingSearch, IDrawingManagerService } from '@univerjs/drawing';
|
||||
import { deserializeThreadCommentAnchor, ThreadCommentAnchorKind, ThreadCommentModel } from '@univerjs/thread-comment';
|
||||
import { ThreadCommentCanvasOverlay, ThreadCommentPanelService } from '@univerjs/thread-comment-ui';
|
||||
import { OpenSheetCommentPanelOperation } from '../../commands/operations/comment.operation';
|
||||
|
||||
const SHEET_COMMENT_DRAWING_OVERLAY_KEY = 'sheet-thread-comment-drawing-overlay';
|
||||
const SHEET_COMMENT_DRAWING_OVERLAY_LAYER_INDEX = 10_100;
|
||||
|
||||
export class SheetsThreadCommentDrawingRenderController extends RxDisposable implements IRenderModule {
|
||||
private readonly _overlay: ThreadCommentCanvasOverlay;
|
||||
|
||||
constructor(
|
||||
private readonly _context: IRenderContext<Workbook>,
|
||||
@ICommandService private readonly _commandService: ICommandService,
|
||||
@IDrawingManagerService private readonly _drawingManagerService: IDrawingManagerService,
|
||||
@Inject(ThreadCommentModel) private readonly _commentModel: ThreadCommentModel,
|
||||
@Inject(ThreadCommentPanelService) private readonly _panelService: ThreadCommentPanelService,
|
||||
@Inject(ThemeService) private readonly _themeService: ThemeService
|
||||
) {
|
||||
super();
|
||||
this._overlay = new ThreadCommentCanvasOverlay(SHEET_COMMENT_DRAWING_OVERLAY_KEY, {
|
||||
...this._getColors(),
|
||||
zoomRatio: 1,
|
||||
markers: [],
|
||||
underlines: [],
|
||||
});
|
||||
this._context.scene.addObject(this._overlay, SHEET_COMMENT_DRAWING_OVERLAY_LAYER_INDEX);
|
||||
this.disposeWithMe(toDisposable(this._overlay.onPointerDown$.subscribeEvent(
|
||||
(_event: IPointerEvent | IMouseEvent, state: EventState) => this._onOverlayPointerDown(state)
|
||||
)));
|
||||
this.disposeWithMe(toDisposable(
|
||||
this._context.engine.onTransformChange$.subscribeEvent(() => this._syncOverlay())
|
||||
));
|
||||
this.disposeWithMe(this._context.unit.activeSheet$.subscribe(() => this._syncOverlay()));
|
||||
this.disposeWithMe(this._commentModel.commentUpdate$.subscribe((update) => {
|
||||
if (update.unitId === this._context.unitId) {
|
||||
this._syncOverlay();
|
||||
}
|
||||
}));
|
||||
[this._panelService.activeCommentId$, this._panelService.hoveredCommentId$]
|
||||
.forEach((observable) => this.disposeWithMe(observable.subscribe(() => this._syncOverlay())));
|
||||
[this._drawingManagerService.add$, this._drawingManagerService.update$, this._drawingManagerService.remove$]
|
||||
.forEach((observable) => this.disposeWithMe(observable.subscribe((drawings) => {
|
||||
if (drawings.some((drawing) => drawing.unitId === this._context.unitId)) {
|
||||
this._syncOverlay();
|
||||
}
|
||||
})));
|
||||
this.disposeWithMe(this._themeService.currentTheme$.subscribe(() => this._syncOverlay()));
|
||||
this._syncOverlay();
|
||||
}
|
||||
|
||||
private _onOverlayPointerDown(state: EventState): void {
|
||||
const commentId = this._overlay.hitCommentId;
|
||||
const subUnitId = this._context.unit.getActiveSheet()?.getSheetId();
|
||||
if (!commentId || !subUnitId) {
|
||||
return;
|
||||
}
|
||||
state.stopPropagation();
|
||||
this._panelService.setActiveComment({
|
||||
unitId: this._context.unitId,
|
||||
subUnitId,
|
||||
commentId,
|
||||
trigger: 'sheet-canvas',
|
||||
});
|
||||
this._commandService.executeCommand(OpenSheetCommentPanelOperation.id).catch(() => undefined);
|
||||
}
|
||||
|
||||
private _syncOverlay(): void {
|
||||
const worksheet = this._context.unit.getActiveSheet();
|
||||
const subUnitId = worksheet?.getSheetId();
|
||||
if (!worksheet || !subUnitId) {
|
||||
this._overlay.updateState({ markers: [], underlines: [], focusedCommentIds: [], focusOutlines: [] });
|
||||
return;
|
||||
}
|
||||
const underlines = new Map<string, IThreadCommentCanvasUnderline>();
|
||||
this._commentModel.query({
|
||||
unitIds: [this._context.unitId],
|
||||
subUnitIds: [subUnitId],
|
||||
anchorKinds: [ThreadCommentAnchorKind.SHEET_DRAWING],
|
||||
resolved: false,
|
||||
}).forEach(({ root }) => {
|
||||
const anchor = deserializeThreadCommentAnchor(root.ref);
|
||||
if (anchor?.kind !== ThreadCommentAnchorKind.SHEET_DRAWING) {
|
||||
return;
|
||||
}
|
||||
const outline = this._getDrawingOutline(subUnitId, anchor.elementId);
|
||||
if (outline) {
|
||||
underlines.set(anchor.elementId, {
|
||||
commentId: root.id,
|
||||
left: outline.left,
|
||||
top: outline.top + outline.height + 2 / worksheet.getZoomRatio(),
|
||||
width: outline.width,
|
||||
});
|
||||
}
|
||||
});
|
||||
const focusedCommentIds: string[] = [];
|
||||
const focusOutlines = new Map<string, IThreadCommentCanvasOutline>();
|
||||
[this._panelService.activeCommentId, this._panelService.hoveredCommentId].forEach((target) => {
|
||||
if (!target || target.unitId !== this._context.unitId || target.subUnitId !== subUnitId) {
|
||||
return;
|
||||
}
|
||||
const comment = this._commentModel.getComment(target.unitId, target.subUnitId, target.commentId);
|
||||
const anchor = comment && deserializeThreadCommentAnchor(comment.ref);
|
||||
if (anchor?.kind !== ThreadCommentAnchorKind.SHEET_DRAWING) {
|
||||
return;
|
||||
}
|
||||
focusedCommentIds.push(target.commentId);
|
||||
const outline = this._getDrawingOutline(subUnitId, anchor.elementId);
|
||||
if (outline) {
|
||||
focusOutlines.set(anchor.elementId, outline);
|
||||
}
|
||||
});
|
||||
this._overlay.updateState({
|
||||
...this._getColors(),
|
||||
zoomRatio: worksheet.getZoomRatio(),
|
||||
markers: [],
|
||||
underlines: Array.from(underlines.values()),
|
||||
focusedCommentIds,
|
||||
focusOutlines: Array.from(focusOutlines.values()),
|
||||
});
|
||||
}
|
||||
|
||||
private _getDrawingOutline(subUnitId: string, drawingId: string): IThreadCommentCanvasOutline | null {
|
||||
const objectKey = getDrawingShapeKeyByDrawingSearch({ unitId: this._context.unitId, subUnitId, drawingId });
|
||||
const object = this._context.scene.getObjectIncludeInGroup?.(objectKey)
|
||||
?? this._context.scene.getObject(objectKey);
|
||||
if (!object) {
|
||||
return null;
|
||||
}
|
||||
const bounds = object.getRealBound();
|
||||
return { left: bounds.left, top: bounds.top, width: bounds.width, height: bounds.height };
|
||||
}
|
||||
|
||||
private _getColors(): { accentColor: string; foregroundColor: string; outlineColor: string } {
|
||||
return {
|
||||
accentColor: this._themeService.getColorFromTheme('yellow.400'),
|
||||
foregroundColor: this._themeService.getColorFromTheme('gray.900'),
|
||||
outlineColor: this._themeService.getColorFromTheme('white'),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* 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 { SheetsThreadCommentModel } from '@univerjs/sheets-thread-comment';
|
||||
import { debounceTime } from 'rxjs';
|
||||
|
||||
export class SheetsThreadCommentRenderController extends Disposable {
|
||||
constructor(
|
||||
@Inject(SheetInterceptorService) private readonly _sheetInterceptorService: SheetInterceptorService,
|
||||
@Inject(SheetsThreadCommentModel) private readonly _sheetsThreadCommentModel: SheetsThreadCommentModel,
|
||||
@IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService,
|
||||
@IRenderManagerService private readonly _renderManagerService: IRenderManagerService
|
||||
) {
|
||||
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;
|
||||
if (this._sheetsThreadCommentModel.showCommentMarker(unitId, subUnitId, row, col)) {
|
||||
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._sheetsThreadCommentModel.commentUpdate$.pipe(debounceTime(16)).subscribe(() => {
|
||||
markSkeletonDirty();
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { IMutationInfo, IRange, Nullable } from '@univerjs/core';
|
||||
import type { IThreadComment } from '@univerjs/thread-comment';
|
||||
import { Disposable, Inject, Range } from '@univerjs/core';
|
||||
import { serializeRange, singleReferenceToGrid } from '@univerjs/engine-formula';
|
||||
import { SheetsThreadCommentModel } from '@univerjs/sheets-thread-comment';
|
||||
import { COPY_TYPE, ISheetClipboardService } from '@univerjs/sheets-ui';
|
||||
import { AddCommentMutation, DeleteCommentMutation, IThreadCommentDataSourceService } from '@univerjs/thread-comment';
|
||||
import { PLUGIN_NAME } from '../types/const';
|
||||
|
||||
const transformRef = (ref: string, source: { row: number; column: number }, target: { row: number; column: number }) => {
|
||||
const refObj = singleReferenceToGrid(ref);
|
||||
const offsetRow = target.row - source.row;
|
||||
const offsetCol = target.column - source.column;
|
||||
const targetRange = {
|
||||
startColumn: refObj.column + offsetCol,
|
||||
startRow: refObj.row + offsetRow,
|
||||
endColumn: refObj.column + offsetCol,
|
||||
endRow: refObj.row + offsetRow,
|
||||
};
|
||||
return serializeRange(targetRange);
|
||||
};
|
||||
|
||||
export class SheetsThreadCommentCopyPasteController extends Disposable {
|
||||
private _copyInfo: Nullable<{
|
||||
unitId: string;
|
||||
subUnitId: string;
|
||||
range: IRange;
|
||||
}>;
|
||||
|
||||
constructor(
|
||||
@Inject(ISheetClipboardService) private _sheetClipboardService: ISheetClipboardService,
|
||||
@Inject(SheetsThreadCommentModel) private _sheetsThreadCommentModel: SheetsThreadCommentModel,
|
||||
@IThreadCommentDataSourceService private _threadCommentDataSourceService: IThreadCommentDataSourceService
|
||||
) {
|
||||
super();
|
||||
this._initClipboardHook();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
private _initClipboardHook() {
|
||||
this.disposeWithMe(
|
||||
this._sheetClipboardService.addClipboardHook({
|
||||
id: PLUGIN_NAME,
|
||||
onBeforeCopy: (unitId, subUnitId, range) => {
|
||||
this._copyInfo = {
|
||||
unitId,
|
||||
subUnitId,
|
||||
range,
|
||||
};
|
||||
},
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
onPasteCells: (_pasteFrom, pasteTo, _data, payload) => {
|
||||
const { unitId: targetUnitId, subUnitId: targetSubUnitId, range } = pasteTo;
|
||||
const targetPos = {
|
||||
row: range.rows[0],
|
||||
column: range.cols[0],
|
||||
};
|
||||
if (payload.copyType === COPY_TYPE.CUT && this._copyInfo) {
|
||||
const { range, unitId: sourceUnitId, subUnitId: sourceSubUnitId } = this._copyInfo;
|
||||
const sourcePos = {
|
||||
row: range.startRow,
|
||||
column: range.startColumn,
|
||||
};
|
||||
if (!(targetUnitId === sourceUnitId && targetSubUnitId === sourceSubUnitId)) {
|
||||
const roots: IThreadComment[] = [];
|
||||
|
||||
Range.foreach(range, (row, col) => {
|
||||
const comments = this._sheetsThreadCommentModel.getAllByLocation(sourceUnitId, sourceSubUnitId, row, col);
|
||||
if (this._threadCommentDataSourceService.syncUpdateMutationToColla) {
|
||||
comments.forEach((comment) => {
|
||||
roots.push(comment);
|
||||
});
|
||||
} else {
|
||||
comments.forEach(({ children, ...comment }) => {
|
||||
if (!comment.parentId) {
|
||||
roots.push(comment);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const sourceRedos: IMutationInfo[] = [];
|
||||
const sourceUndos: IMutationInfo[] = [];
|
||||
const targetRedos: IMutationInfo[] = [];
|
||||
const targetUndos: IMutationInfo[] = [];
|
||||
|
||||
const handleCommentItem = (item: IThreadComment) => {
|
||||
sourceRedos.unshift({
|
||||
id: DeleteCommentMutation.id,
|
||||
params: {
|
||||
unitId: sourceUnitId,
|
||||
subUnitId: sourceSubUnitId,
|
||||
commentId: item.id,
|
||||
},
|
||||
});
|
||||
targetRedos.push({
|
||||
id: AddCommentMutation.id,
|
||||
params: {
|
||||
unitId: targetUnitId,
|
||||
subUnitId: targetSubUnitId,
|
||||
comment: {
|
||||
...item,
|
||||
ref: transformRef(item.ref, sourcePos, targetPos),
|
||||
unitId: targetUnitId,
|
||||
subUnitId: targetSubUnitId,
|
||||
},
|
||||
sync: true,
|
||||
},
|
||||
});
|
||||
sourceUndos.push({
|
||||
id: AddCommentMutation.id,
|
||||
params: {
|
||||
unitId: sourceUnitId,
|
||||
subUnitId: sourceSubUnitId,
|
||||
comment: item,
|
||||
sync: true,
|
||||
},
|
||||
});
|
||||
targetUndos.unshift({
|
||||
id: DeleteCommentMutation.id,
|
||||
params: {
|
||||
unitId: targetUnitId,
|
||||
subUnitId: targetSubUnitId,
|
||||
commentId: item.id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
roots.forEach((root) => {
|
||||
handleCommentItem(root);
|
||||
});
|
||||
|
||||
return {
|
||||
redos: [...sourceRedos, ...targetRedos],
|
||||
undos: [...targetUndos, ...sourceUndos],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
redos: [],
|
||||
undos: [],
|
||||
};
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Disposable, Inject } from '@univerjs/core';
|
||||
import { RangeProtectionPermissionViewPoint, SheetPermissionCheckController, WorkbookCommentPermission, WorksheetViewPermission } from '@univerjs/sheets';
|
||||
import { SheetsThreadCommentModel } from '@univerjs/sheets-thread-comment';
|
||||
import { HoverManagerService } from '@univerjs/sheets-ui';
|
||||
import { debounceTime } from 'rxjs';
|
||||
import { SheetsThreadCommentPopupService } from '../services/sheets-thread-comment-popup.service';
|
||||
|
||||
export class SheetsThreadCommentHoverController extends Disposable {
|
||||
constructor(
|
||||
@Inject(HoverManagerService) private readonly _hoverManagerService: HoverManagerService,
|
||||
@Inject(SheetsThreadCommentPopupService) private readonly _sheetsThreadCommentPopupService: SheetsThreadCommentPopupService,
|
||||
@Inject(SheetsThreadCommentModel) private readonly _sheetsThreadCommentModel: SheetsThreadCommentModel,
|
||||
@Inject(SheetPermissionCheckController) private readonly _sheetPermissionCheckController: SheetPermissionCheckController
|
||||
) {
|
||||
super();
|
||||
this._initHoverEvent();
|
||||
}
|
||||
|
||||
private _initHoverEvent() {
|
||||
this.disposeWithMe(
|
||||
this._hoverManagerService.currentCell$.pipe(debounceTime(100)).subscribe((cell) => {
|
||||
const currentPopup = this._sheetsThreadCommentPopupService.activePopup;
|
||||
if (cell && ((currentPopup && currentPopup.temp) || !currentPopup)) {
|
||||
const { location } = cell;
|
||||
const { unitId, subUnitId, row, col } = location;
|
||||
|
||||
const commentId = this._sheetsThreadCommentModel.getByLocation(unitId, subUnitId, row, col);
|
||||
|
||||
if (commentId) {
|
||||
const commentPermission = this._sheetPermissionCheckController.permissionCheckWithRanges({
|
||||
workbookTypes: [WorkbookCommentPermission],
|
||||
worksheetTypes: [WorksheetViewPermission],
|
||||
rangeTypes: [RangeProtectionPermissionViewPoint],
|
||||
}, [{ startRow: row, startColumn: col, endRow: row, endColumn: col }], unitId, subUnitId);
|
||||
if (!commentPermission) {
|
||||
return;
|
||||
}
|
||||
const comment = this._sheetsThreadCommentModel.getComment(unitId, subUnitId, commentId);
|
||||
if (comment && !comment.resolved) {
|
||||
this._sheetsThreadCommentPopupService.showPopup({
|
||||
unitId,
|
||||
subUnitId,
|
||||
row,
|
||||
col,
|
||||
commentId,
|
||||
temp: true,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (currentPopup) {
|
||||
this._sheetsThreadCommentPopupService.hidePopup();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* 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 { ICommandInfo } from '@univerjs/core';
|
||||
import type {
|
||||
IAddCommentCommandParams,
|
||||
IDeleteCommentCommandParams,
|
||||
IDeleteCommentTreeCommandParams,
|
||||
IResolveCommentCommandParams,
|
||||
IUpdateCommentCommandParams,
|
||||
} from '@univerjs/thread-comment';
|
||||
import type { LocaleKey } from '../locale/types';
|
||||
import { Disposable, ICommandService, Inject, LocaleService } from '@univerjs/core';
|
||||
import { singleReferenceToGrid } from '@univerjs/engine-formula';
|
||||
import {
|
||||
RangeProtectionPermissionViewPoint,
|
||||
SheetPermissionCheckController,
|
||||
WorkbookCommentPermission,
|
||||
WorksheetViewPermission,
|
||||
} from '@univerjs/sheets';
|
||||
import { SheetsThreadCommentModel } from '@univerjs/sheets-thread-comment';
|
||||
import {
|
||||
AddCommentCommand,
|
||||
DeleteCommentCommand,
|
||||
DeleteCommentTreeCommand,
|
||||
deserializeThreadCommentAnchor,
|
||||
ResolveCommentCommand,
|
||||
ThreadCommentAnchorKind,
|
||||
UpdateCommentCommand,
|
||||
} from '@univerjs/thread-comment';
|
||||
import { AddSheetDrawingCommentOperation, ShowAddSheetCommentModalOperation, ToggleSheetCommentPanelOperation } from '../commands/operations/comment.operation';
|
||||
|
||||
export class SheetsThreadCommentPermissionController extends Disposable {
|
||||
constructor(
|
||||
@Inject(LocaleService) private _localeService: LocaleService,
|
||||
@ICommandService private readonly _commandService: ICommandService,
|
||||
@Inject(SheetPermissionCheckController) private readonly _sheetPermissionCheckController: SheetPermissionCheckController,
|
||||
@Inject(SheetsThreadCommentModel) private readonly _sheetsThreadCommentModel: SheetsThreadCommentModel
|
||||
) {
|
||||
super();
|
||||
|
||||
this._commandExecutedListener();
|
||||
}
|
||||
|
||||
private _commandExecutedListener() {
|
||||
this.disposeWithMe(
|
||||
this._commandService.beforeCommandExecuted((command: ICommandInfo) => {
|
||||
const { id } = command;
|
||||
|
||||
if (id === AddSheetDrawingCommentOperation.id || id === ShowAddSheetCommentModalOperation.id || id === ToggleSheetCommentPanelOperation.id) {
|
||||
const permission = this._sheetPermissionCheckController.permissionCheckWithoutRange({
|
||||
workbookTypes: [WorkbookCommentPermission],
|
||||
worksheetTypes: [WorksheetViewPermission],
|
||||
});
|
||||
if (!permission) {
|
||||
this._sheetPermissionCheckController.blockExecuteWithoutPermission(this._localeService.t<LocaleKey>('sheets-thread-comment-ui.permission.commentErr'));
|
||||
}
|
||||
} else if (id === AddCommentCommand.id) {
|
||||
const params = command.params as IAddCommentCommandParams;
|
||||
const { unitId, subUnitId, comment } = params;
|
||||
if (!this._hasCommentPermission(unitId, subUnitId, comment.ref)) {
|
||||
this._sheetPermissionCheckController.blockExecuteWithoutPermission(this._localeService.t<LocaleKey>('sheets-thread-comment-ui.permission.commentErr'));
|
||||
}
|
||||
} else if (id === UpdateCommentCommand.id) {
|
||||
const params = command.params as IUpdateCommentCommandParams;
|
||||
const { unitId, subUnitId, payload } = params;
|
||||
const { commentId } = payload;
|
||||
const comment = this._sheetsThreadCommentModel.getComment(unitId, subUnitId, commentId);
|
||||
|
||||
if (comment && !this._hasCommentPermission(unitId, subUnitId, comment.ref)) {
|
||||
this._sheetPermissionCheckController.blockExecuteWithoutPermission(this._localeService.t<LocaleKey>('sheets-thread-comment-ui.permission.commentErr'));
|
||||
}
|
||||
} else if (id === ResolveCommentCommand.id || id === DeleteCommentCommand.id || id === DeleteCommentTreeCommand.id) {
|
||||
const params = command.params as IResolveCommentCommandParams | IDeleteCommentCommandParams | IDeleteCommentTreeCommandParams;
|
||||
const { unitId, subUnitId, commentId } = params;
|
||||
const comment = this._sheetsThreadCommentModel.getComment(unitId, subUnitId, commentId);
|
||||
|
||||
if (comment && !this._hasCommentPermission(unitId, subUnitId, comment.ref)) {
|
||||
this._sheetPermissionCheckController.blockExecuteWithoutPermission(this._localeService.t<LocaleKey>('sheets-thread-comment-ui.permission.commentErr'));
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private _hasCommentPermission(unitId: string, subUnitId: string, ref: string): boolean {
|
||||
const permissionTypes = {
|
||||
workbookTypes: [WorkbookCommentPermission],
|
||||
worksheetTypes: [WorksheetViewPermission],
|
||||
};
|
||||
const anchor = deserializeThreadCommentAnchor(ref);
|
||||
if (anchor?.kind === ThreadCommentAnchorKind.SHEET_DRAWING) {
|
||||
return this._sheetPermissionCheckController.permissionCheckWithoutRange(
|
||||
permissionTypes,
|
||||
unitId,
|
||||
subUnitId
|
||||
);
|
||||
}
|
||||
|
||||
const { row, column } = singleReferenceToGrid(ref);
|
||||
if (!Number.isFinite(row) || !Number.isFinite(column)) {
|
||||
return false;
|
||||
}
|
||||
return this._sheetPermissionCheckController.permissionCheckWithRanges({
|
||||
...permissionTypes,
|
||||
rangeTypes: [RangeProtectionPermissionViewPoint],
|
||||
}, [{ startRow: row, startColumn: column, endRow: row, endColumn: column }], unitId, subUnitId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
/**
|
||||
* 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, Workbook } from '@univerjs/core';
|
||||
import type { ISelectionWithStyle } from '@univerjs/sheets';
|
||||
import type { IDeleteCommentMutationParams } from '@univerjs/thread-comment';
|
||||
import type { ActiveCommentInfo } from '@univerjs/thread-comment-ui';
|
||||
import { Disposable, ICommandService, Inject, IUniverInstanceService, RANGE_TYPE, Rectangle, UniverInstanceType } from '@univerjs/core';
|
||||
import { singleReferenceToGrid } from '@univerjs/engine-formula';
|
||||
import { IRenderManagerService } from '@univerjs/engine-render';
|
||||
import { RangeProtectionPermissionViewPoint, SetWorksheetActiveOperation, SheetPermissionCheckController, SheetsSelectionsService, WorkbookCommentPermission, WorksheetViewPermission } from '@univerjs/sheets';
|
||||
import { SheetsThreadCommentModel } from '@univerjs/sheets-thread-comment';
|
||||
import { IEditorBridgeService, IMarkSelectionService, ScrollToRangeOperation, SheetSkeletonManagerService } from '@univerjs/sheets-ui';
|
||||
import { DeleteCommentMutation, deserializeThreadCommentAnchor, ThreadCommentAnchorKind } from '@univerjs/thread-comment';
|
||||
import { SetActiveCommentOperation, ThreadCommentPanelService } from '@univerjs/thread-comment-ui';
|
||||
import { combineLatest, debounceTime } from 'rxjs';
|
||||
import { SheetsThreadCommentPopupService } from '../services/sheets-thread-comment-popup.service';
|
||||
|
||||
interface ISelectionShapeInfo {
|
||||
shapeId: string;
|
||||
unitId: string;
|
||||
subUnitId: string;
|
||||
commentId: string;
|
||||
}
|
||||
|
||||
export function resolveFocusedSheetComment(
|
||||
activeComment: ActiveCommentInfo,
|
||||
hoveredComment: ActiveCommentInfo,
|
||||
unitId: string | undefined,
|
||||
subUnitId: string | undefined
|
||||
): Exclude<ActiveCommentInfo, null | undefined> | undefined {
|
||||
for (const comment of [hoveredComment, activeComment]) {
|
||||
if (comment && comment.unitId === unitId && comment.subUnitId === subUnitId) {
|
||||
return comment;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export class SheetsThreadCommentPopupController extends Disposable {
|
||||
private _isSwitchToCommenting = false;
|
||||
private _selectionShapeInfo: Nullable<ISelectionShapeInfo> = null;
|
||||
|
||||
constructor(
|
||||
@ICommandService private readonly _commandService: ICommandService,
|
||||
@Inject(SheetsThreadCommentPopupService) private readonly _sheetsThreadCommentPopupService: SheetsThreadCommentPopupService,
|
||||
@Inject(SheetsThreadCommentModel) private readonly _sheetsThreadCommentModel: SheetsThreadCommentModel,
|
||||
@Inject(ThreadCommentPanelService) private readonly _threadCommentPanelService: ThreadCommentPanelService,
|
||||
@IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService,
|
||||
@Inject(SheetPermissionCheckController) private readonly _sheetPermissionCheckController: SheetPermissionCheckController,
|
||||
@IMarkSelectionService private readonly _markSelectionService: IMarkSelectionService,
|
||||
@Inject(SheetsSelectionsService) private readonly _sheetSelectionService: SheetsSelectionsService,
|
||||
@IEditorBridgeService private readonly _editorBridgeService: IEditorBridgeService,
|
||||
@IRenderManagerService private readonly _renderManagerService: IRenderManagerService
|
||||
) {
|
||||
super();
|
||||
|
||||
this._initCommandListener();
|
||||
this._initPanelListener();
|
||||
this._initMarkSelection();
|
||||
this._initSelectionUpdateListener();
|
||||
this._initEditorBridge();
|
||||
}
|
||||
|
||||
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))) {
|
||||
if (this._threadCommentPanelService.activeCommentId) {
|
||||
this._commandService.executeCommand(SetActiveCommentOperation.id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const row = actualCell.actualRow;
|
||||
const col = actualCell.actualColumn;
|
||||
if (!this._sheetsThreadCommentModel.showCommentMarker(unitId, subUnitId, row, col)) {
|
||||
if (this._threadCommentPanelService.activeCommentId) {
|
||||
this._commandService.executeCommand(SetActiveCommentOperation.id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const commentId = this._sheetsThreadCommentModel.getByLocation(unitId, subUnitId, row, col);
|
||||
if (commentId) {
|
||||
this._commandService.executeCommand(SetActiveCommentOperation.id, {
|
||||
unitId,
|
||||
subUnitId,
|
||||
commentId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _initSelectionUpdateListener() {
|
||||
this.disposeWithMe(
|
||||
this._sheetSelectionService.selectionMoveEnd$.subscribe((selections) => {
|
||||
if (this._isSwitchToCommenting) {
|
||||
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._sheetsThreadCommentPopupService.hidePopup();
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private _initCommandListener() {
|
||||
this._commandService.onCommandExecuted((commandInfo) => {
|
||||
if (commandInfo.id === DeleteCommentMutation.id) {
|
||||
const params = commandInfo.params as IDeleteCommentMutationParams;
|
||||
const active = this._sheetsThreadCommentPopupService.activePopup;
|
||||
if (!active) {
|
||||
return;
|
||||
}
|
||||
const { unitId, subUnitId, commentId } = active;
|
||||
if (params.unitId === unitId && params.subUnitId === subUnitId && params.commentId === commentId) {
|
||||
this._sheetsThreadCommentPopupService.hidePopup();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _initPanelListener() {
|
||||
this.disposeWithMe(this._threadCommentPanelService.activeCommentId$.subscribe(async (commentInfo) => {
|
||||
if (commentInfo) {
|
||||
const { unitId, subUnitId, commentId, trigger } = commentInfo;
|
||||
const comment = this._sheetsThreadCommentModel.getComment(unitId, subUnitId, commentId);
|
||||
if (!comment || comment.resolved) {
|
||||
return;
|
||||
}
|
||||
const anchor = deserializeThreadCommentAnchor(comment.ref);
|
||||
if (anchor?.kind === ThreadCommentAnchorKind.SHEET_DRAWING) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentUnit = this._univerInstanceService.getCurrentUnitOfType<Workbook>(UniverInstanceType.UNIVER_SHEET);
|
||||
if (!currentUnit) {
|
||||
return;
|
||||
}
|
||||
const currentUnitId = currentUnit.getUnitId();
|
||||
if (currentUnitId !== unitId) {
|
||||
return;
|
||||
}
|
||||
this._isSwitchToCommenting = true;
|
||||
const currentSheetId = currentUnit.getActiveSheet()?.getSheetId();
|
||||
if (currentSheetId !== subUnitId) {
|
||||
await this._commandService.executeCommand(SetWorksheetActiveOperation.id, {
|
||||
unitId,
|
||||
subUnitId,
|
||||
});
|
||||
}
|
||||
this._isSwitchToCommenting = false;
|
||||
|
||||
const location = singleReferenceToGrid(comment.ref);
|
||||
|
||||
const { row, column: col } = location;
|
||||
const commentPermission = this._sheetPermissionCheckController.permissionCheckWithRanges({
|
||||
workbookTypes: [WorkbookCommentPermission],
|
||||
worksheetTypes: [WorksheetViewPermission],
|
||||
rangeTypes: [RangeProtectionPermissionViewPoint],
|
||||
}, [{ startRow: row, startColumn: col, endRow: row, endColumn: col }], unitId, subUnitId);
|
||||
|
||||
if (!commentPermission) {
|
||||
return;
|
||||
}
|
||||
|
||||
const GAP = 1;
|
||||
await this._commandService.executeCommand(ScrollToRangeOperation.id, {
|
||||
range: {
|
||||
startRow: Math.max(location.row - GAP, 0),
|
||||
endRow: location.row + GAP,
|
||||
startColumn: Math.max(location.column - GAP, 0),
|
||||
endColumn: location.column + GAP,
|
||||
},
|
||||
});
|
||||
if (this._editorBridgeService.isVisible().visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._sheetsThreadCommentPopupService.showPopup({
|
||||
unitId,
|
||||
subUnitId,
|
||||
row: location.row,
|
||||
col: location.column,
|
||||
commentId: comment.id,
|
||||
trigger,
|
||||
});
|
||||
} else {
|
||||
this._sheetsThreadCommentPopupService.hidePopup();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private _initMarkSelection() {
|
||||
this.disposeWithMe(combineLatest([
|
||||
this._threadCommentPanelService.activeCommentId$,
|
||||
this._threadCommentPanelService.hoveredCommentId$,
|
||||
]).pipe(debounceTime(100)).subscribe(([activeComment, hoveredComment]) => {
|
||||
const currentUnit = this._univerInstanceService.getCurrentUnitOfType<Workbook>(UniverInstanceType.UNIVER_SHEET);
|
||||
const currentUnitId = currentUnit?.getUnitId();
|
||||
const currentSheetId = currentUnit?.getActiveSheet()?.getSheetId();
|
||||
const focusedComment = resolveFocusedSheetComment(
|
||||
activeComment,
|
||||
hoveredComment,
|
||||
currentUnitId,
|
||||
currentSheetId
|
||||
);
|
||||
if (!focusedComment) {
|
||||
if (this._selectionShapeInfo) {
|
||||
this._markSelectionService.removeShape(this._selectionShapeInfo.shapeId);
|
||||
this._selectionShapeInfo = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const { unitId, subUnitId, commentId } = focusedComment;
|
||||
if (this._selectionShapeInfo) {
|
||||
this._markSelectionService.removeShape(this._selectionShapeInfo.shapeId);
|
||||
this._selectionShapeInfo = null;
|
||||
}
|
||||
|
||||
const comment = this._sheetsThreadCommentModel.getComment(unitId, subUnitId, commentId);
|
||||
if (!comment) {
|
||||
return;
|
||||
}
|
||||
|
||||
const location = singleReferenceToGrid(comment.ref);
|
||||
|
||||
const { row, column } = location;
|
||||
if (Number.isNaN(row) || Number.isNaN(column)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const worksheet = this._univerInstanceService.getCurrentUnitOfType<Workbook>(UniverInstanceType.UNIVER_SHEET)?.getSheetBySheetId(subUnitId);
|
||||
|
||||
const mergeInfo = worksheet?.getMergedCell(row, column) ?? {
|
||||
startColumn: column,
|
||||
endColumn: column,
|
||||
startRow: row,
|
||||
endRow: row,
|
||||
};
|
||||
|
||||
// TODO: use evented: false to solve this problem later
|
||||
const shapeId = this._markSelectionService.addShape(
|
||||
{
|
||||
range: mergeInfo,
|
||||
style: {
|
||||
// hasAutoFill: false,
|
||||
fill: 'rgba(255, 189, 55, 0.35)',
|
||||
strokeWidth: 1,
|
||||
stroke: '#FFBD37',
|
||||
widgets: {},
|
||||
},
|
||||
primary: null,
|
||||
},
|
||||
[],
|
||||
-1
|
||||
);
|
||||
if (!shapeId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._selectionShapeInfo = {
|
||||
...focusedComment,
|
||||
shapeId,
|
||||
};
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -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 } from '@univerjs/core';
|
||||
import { IMenuManagerService, IShortcutService } from '@univerjs/ui';
|
||||
import { AddCommentShortcut } from '../menu/menu';
|
||||
import { menuSchema } from '../menu/schema';
|
||||
|
||||
export class SheetsThreadCommentController extends Disposable {
|
||||
constructor(
|
||||
@IMenuManagerService private readonly _menuManagerService: IMenuManagerService,
|
||||
@IShortcutService private readonly _shortcutService: IShortcutService
|
||||
) {
|
||||
super();
|
||||
this._initMenu();
|
||||
this._initShortcut();
|
||||
}
|
||||
|
||||
private _initShortcut() {
|
||||
this._shortcutService.registerShortcut(AddCommentShortcut);
|
||||
}
|
||||
|
||||
private _initMenu() {
|
||||
this._menuManagerService.mergeMenu(menuSchema);
|
||||
}
|
||||
}
|
||||
2
packages/sheets-thread-comment-ui/src/global.css
Normal file
2
packages/sheets-thread-comment-ui/src/global.css
Normal file
@@ -0,0 +1,2 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
27
packages/sheets-thread-comment-ui/src/index.ts
Normal file
27
packages/sheets-thread-comment-ui/src/index.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 './global.css';
|
||||
|
||||
export {
|
||||
ShowAddSheetCommentModalOperation,
|
||||
ToggleSheetCommentPanelOperation,
|
||||
} from './commands/operations/comment.operation';
|
||||
export type { IUniverSheetsThreadCommentUIConfig } from './config/config';
|
||||
export { menuSchema as SheetsThreadCommentUIMenuSchema } from './menu/schema';
|
||||
export { UniverSheetsThreadCommentMobileUIPlugin } from './mobile-plugin';
|
||||
export { UniverSheetsThreadCommentUIPlugin } from './plugin';
|
||||
export { SheetsThreadCommentPopupService } from './services/sheets-thread-comment-popup.service';
|
||||
35
packages/sheets-thread-comment-ui/src/locale/ar-SA.ts
Normal file
35
packages/sheets-thread-comment-ui/src/locale/ar-SA.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 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-thread-comment-ui': {
|
||||
panel: {
|
||||
title: 'إدارة التعليقات',
|
||||
},
|
||||
permission: {
|
||||
commentErr: 'ليس لديك إذن لاستخدام التعليقات.',
|
||||
},
|
||||
menu: {
|
||||
addComment: 'إضافة تعليق',
|
||||
openComments: 'فتح التعليقات',
|
||||
commentManagement: 'إدارة التعليقات',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
35
packages/sheets-thread-comment-ui/src/locale/ca-ES.ts
Normal file
35
packages/sheets-thread-comment-ui/src/locale/ca-ES.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 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-thread-comment-ui': {
|
||||
panel: {
|
||||
title: 'Gestió de comentaris',
|
||||
},
|
||||
permission: {
|
||||
commentErr: 'No teniu permís per utilitzar comentaris.',
|
||||
},
|
||||
menu: {
|
||||
addComment: 'Afegeix un comentari',
|
||||
openComments: 'Obre els comentaris',
|
||||
commentManagement: 'Gestió de comentaris',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
35
packages/sheets-thread-comment-ui/src/locale/de-DE.ts
Normal file
35
packages/sheets-thread-comment-ui/src/locale/de-DE.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 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-thread-comment-ui': {
|
||||
panel: {
|
||||
title: 'Kommentarverwaltung',
|
||||
},
|
||||
permission: {
|
||||
commentErr: 'Sie haben keine Berechtigung, Kommentare zu verwenden.',
|
||||
},
|
||||
menu: {
|
||||
addComment: 'Kommentar hinzufügen',
|
||||
openComments: 'Kommentare öffnen',
|
||||
commentManagement: 'Kommentarverwaltung',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
33
packages/sheets-thread-comment-ui/src/locale/en-US.ts
Normal file
33
packages/sheets-thread-comment-ui/src/locale/en-US.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* 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-thread-comment-ui': {
|
||||
panel: {
|
||||
title: 'Comment Management',
|
||||
},
|
||||
permission: {
|
||||
commentErr: 'You do not have permission to use comments.',
|
||||
},
|
||||
menu: {
|
||||
addComment: 'Add Comment',
|
||||
openComments: 'Open Comments',
|
||||
commentManagement: 'Comment Management',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
35
packages/sheets-thread-comment-ui/src/locale/es-ES.ts
Normal file
35
packages/sheets-thread-comment-ui/src/locale/es-ES.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 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-thread-comment-ui': {
|
||||
panel: {
|
||||
title: 'Gestión de comentarios',
|
||||
},
|
||||
permission: {
|
||||
commentErr: 'No tiene permiso para usar comentarios.',
|
||||
},
|
||||
menu: {
|
||||
addComment: 'Añadir comentario',
|
||||
openComments: 'Abrir comentarios',
|
||||
commentManagement: 'Gestión de comentarios',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
35
packages/sheets-thread-comment-ui/src/locale/fa-IR.ts
Normal file
35
packages/sheets-thread-comment-ui/src/locale/fa-IR.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 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-thread-comment-ui': {
|
||||
panel: {
|
||||
title: 'مدیریت نظر',
|
||||
},
|
||||
permission: {
|
||||
commentErr: 'شما مجوز استفاده از نظرات را ندارید.',
|
||||
},
|
||||
menu: {
|
||||
addComment: 'افزودن نظر',
|
||||
openComments: 'باز کردن نظرات',
|
||||
commentManagement: 'مدیریت نظر',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
35
packages/sheets-thread-comment-ui/src/locale/fr-FR.ts
Normal file
35
packages/sheets-thread-comment-ui/src/locale/fr-FR.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 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-thread-comment-ui': {
|
||||
panel: {
|
||||
title: 'Gestion des commentaires',
|
||||
},
|
||||
permission: {
|
||||
commentErr: 'Vous n’avez pas l’autorisation d’utiliser les commentaires.',
|
||||
},
|
||||
menu: {
|
||||
addComment: 'Ajouter un commentaire',
|
||||
openComments: 'Ouvrir les commentaires',
|
||||
commentManagement: 'Gestion des commentaires',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
35
packages/sheets-thread-comment-ui/src/locale/id-ID.ts
Normal file
35
packages/sheets-thread-comment-ui/src/locale/id-ID.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 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-thread-comment-ui': {
|
||||
panel: {
|
||||
title: 'Manajemen Komentar',
|
||||
},
|
||||
permission: {
|
||||
commentErr: 'Anda tidak memiliki izin untuk menggunakan komentar.',
|
||||
},
|
||||
menu: {
|
||||
addComment: 'Tambah Komentar',
|
||||
openComments: 'Buka Komentar',
|
||||
commentManagement: 'Manajemen Komentar',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
35
packages/sheets-thread-comment-ui/src/locale/it-IT.ts
Normal file
35
packages/sheets-thread-comment-ui/src/locale/it-IT.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 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-thread-comment-ui': {
|
||||
panel: {
|
||||
title: 'Gestione commenti',
|
||||
},
|
||||
permission: {
|
||||
commentErr: 'Non hai l’autorizzazione per usare i commenti.',
|
||||
},
|
||||
menu: {
|
||||
addComment: 'Aggiungi commento',
|
||||
openComments: 'Apri commenti',
|
||||
commentManagement: 'Gestione commenti',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
35
packages/sheets-thread-comment-ui/src/locale/ja-JP.ts
Normal file
35
packages/sheets-thread-comment-ui/src/locale/ja-JP.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 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-thread-comment-ui': {
|
||||
panel: {
|
||||
title: 'コメント管理',
|
||||
},
|
||||
permission: {
|
||||
commentErr: 'コメントを使用する権限がありません。',
|
||||
},
|
||||
menu: {
|
||||
addComment: 'コメントを追加',
|
||||
openComments: 'コメントを開く',
|
||||
commentManagement: 'コメント管理',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
35
packages/sheets-thread-comment-ui/src/locale/ko-KR.ts
Normal file
35
packages/sheets-thread-comment-ui/src/locale/ko-KR.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 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-thread-comment-ui': {
|
||||
panel: {
|
||||
title: '댓글 관리',
|
||||
},
|
||||
permission: {
|
||||
commentErr: '댓글을 사용할 권한이 없습니다.',
|
||||
},
|
||||
menu: {
|
||||
addComment: '댓글 추가',
|
||||
openComments: '댓글 열기',
|
||||
commentManagement: '댓글 관리',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
35
packages/sheets-thread-comment-ui/src/locale/pl-PL.ts
Normal file
35
packages/sheets-thread-comment-ui/src/locale/pl-PL.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 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-thread-comment-ui': {
|
||||
panel: {
|
||||
title: 'Zarządzanie komentarzami',
|
||||
},
|
||||
permission: {
|
||||
commentErr: 'Nie masz uprawnień do używania komentarzy.',
|
||||
},
|
||||
menu: {
|
||||
addComment: 'Dodaj komentarz',
|
||||
openComments: 'Otwórz komentarze',
|
||||
commentManagement: 'Zarządzanie komentarzami',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
35
packages/sheets-thread-comment-ui/src/locale/pt-BR.ts
Normal file
35
packages/sheets-thread-comment-ui/src/locale/pt-BR.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 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-thread-comment-ui': {
|
||||
panel: {
|
||||
title: 'Gerenciamento de Comentários',
|
||||
},
|
||||
permission: {
|
||||
commentErr: 'Você não tem permissão para usar comentários.',
|
||||
},
|
||||
menu: {
|
||||
addComment: 'Adicionar Comentário',
|
||||
openComments: 'Abrir comentários',
|
||||
commentManagement: 'Gerenciamento de Comentários',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
35
packages/sheets-thread-comment-ui/src/locale/ru-RU.ts
Normal file
35
packages/sheets-thread-comment-ui/src/locale/ru-RU.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 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-thread-comment-ui': {
|
||||
panel: {
|
||||
title: 'Управление комментариями',
|
||||
},
|
||||
permission: {
|
||||
commentErr: 'У вас нет разрешения на использование комментариев.',
|
||||
},
|
||||
menu: {
|
||||
addComment: 'Добавить комментарий',
|
||||
openComments: 'Открыть комментарии',
|
||||
commentManagement: 'Управление комментариями',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
35
packages/sheets-thread-comment-ui/src/locale/sk-SK.ts
Normal file
35
packages/sheets-thread-comment-ui/src/locale/sk-SK.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 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-thread-comment-ui': {
|
||||
panel: {
|
||||
title: 'Správa komentárov',
|
||||
},
|
||||
permission: {
|
||||
commentErr: 'Nemáte povolenie používať komentáre.',
|
||||
},
|
||||
menu: {
|
||||
addComment: 'Pridať komentár',
|
||||
openComments: 'Otvoriť komentáre',
|
||||
commentManagement: 'Správa komentárov',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
20
packages/sheets-thread-comment-ui/src/locale/types.ts
Normal file
20
packages/sheets-thread-comment-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>;
|
||||
35
packages/sheets-thread-comment-ui/src/locale/vi-VN.ts
Normal file
35
packages/sheets-thread-comment-ui/src/locale/vi-VN.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 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-thread-comment-ui': {
|
||||
panel: {
|
||||
title: 'Quản lý bình luận',
|
||||
},
|
||||
permission: {
|
||||
commentErr: 'Bạn không có quyền sử dụng bình luận.',
|
||||
},
|
||||
menu: {
|
||||
addComment: 'Thêm bình luận',
|
||||
openComments: 'Mở bình luận',
|
||||
commentManagement: 'Quản lý bình luận',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
35
packages/sheets-thread-comment-ui/src/locale/zh-CN.ts
Normal file
35
packages/sheets-thread-comment-ui/src/locale/zh-CN.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 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-thread-comment-ui': {
|
||||
panel: {
|
||||
title: '评论管理',
|
||||
},
|
||||
permission: {
|
||||
commentErr: '你没有权限使用评论。',
|
||||
},
|
||||
menu: {
|
||||
addComment: '添加评论',
|
||||
openComments: '打开评论',
|
||||
commentManagement: '评论管理',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
35
packages/sheets-thread-comment-ui/src/locale/zh-HK.ts
Normal file
35
packages/sheets-thread-comment-ui/src/locale/zh-HK.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 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-thread-comment-ui': {
|
||||
panel: {
|
||||
title: '評論管理',
|
||||
},
|
||||
permission: {
|
||||
commentErr: '你沒有權限使用評論。',
|
||||
},
|
||||
menu: {
|
||||
addComment: '新增評論',
|
||||
openComments: '開啟評論',
|
||||
commentManagement: '評論管理',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
35
packages/sheets-thread-comment-ui/src/locale/zh-TW.ts
Normal file
35
packages/sheets-thread-comment-ui/src/locale/zh-TW.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 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-thread-comment-ui': {
|
||||
panel: {
|
||||
title: '評論管理',
|
||||
},
|
||||
permission: {
|
||||
commentErr: '你沒有權限使用評論。',
|
||||
},
|
||||
menu: {
|
||||
addComment: '新增評論',
|
||||
openComments: '開啟評論',
|
||||
commentManagement: '評論管理',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* 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 {
|
||||
FOCUSING_COMMON_DRAWINGS,
|
||||
IContextService,
|
||||
Injector,
|
||||
IPermissionService,
|
||||
IUniverInstanceService,
|
||||
UserManagerService,
|
||||
} from '@univerjs/core';
|
||||
import { RangeProtectionRuleModel, SheetsSelectionsService, WorksheetProtectionRuleModel } from '@univerjs/sheets';
|
||||
import { BehaviorSubject, of, Subject } from 'rxjs';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { drawingCommentMenuFactory, threadCommentMenuFactory } from '../menu';
|
||||
|
||||
describe('drawingCommentMenuFactory', () => {
|
||||
it('stays enabled for a focused drawing until comment permission is denied', () => {
|
||||
const permissionValues$ = new BehaviorSubject([{ value: true }, { value: true }]);
|
||||
const workbook = {
|
||||
getUnitId: () => 'unit-1',
|
||||
activeSheet$: of({ getSheetId: () => 'sheet-1' }),
|
||||
};
|
||||
const injector = new Injector([
|
||||
[IContextService, {
|
||||
useValue: {
|
||||
subscribeContextValue$: (key: string) => of(key === FOCUSING_COMMON_DRAWINGS),
|
||||
},
|
||||
}],
|
||||
[IPermissionService, {
|
||||
useValue: {
|
||||
permissionPointUpdate$: new Subject(),
|
||||
composePermission$: () => permissionValues$,
|
||||
},
|
||||
}],
|
||||
[IUniverInstanceService, {
|
||||
useValue: {
|
||||
focused$: new Subject(),
|
||||
getCurrentTypeOfUnit$: () => of(workbook),
|
||||
},
|
||||
}],
|
||||
[RangeProtectionRuleModel, { useValue: { getSubunitRuleList: () => [] } }],
|
||||
[SheetsSelectionsService, {
|
||||
useValue: {
|
||||
selectionChanged$: new Subject(),
|
||||
getWorkbookSelections: () => ({ getSelectionsOfWorksheet: () => [] }),
|
||||
},
|
||||
}],
|
||||
[UserManagerService, { useValue: { currentUser$: of(null) } }],
|
||||
[WorksheetProtectionRuleModel, { useValue: { getRule: () => null } }],
|
||||
]);
|
||||
const disabledValues: boolean[] = [];
|
||||
const subscription = drawingCommentMenuFactory(injector).disabled$.subscribe((disabled) => {
|
||||
disabledValues.push(disabled);
|
||||
});
|
||||
|
||||
expect(disabledValues).toEqual([false]);
|
||||
|
||||
permissionValues$.next([{ value: false }, { value: true }]);
|
||||
|
||||
expect(disabledValues).toEqual([false, true]);
|
||||
subscription.unsubscribe();
|
||||
injector.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
describe('thread comment menu', () => {
|
||||
it('shows the add comment label in its tooltip', () => {
|
||||
const service = {
|
||||
currentUser$: of(null),
|
||||
focused$: of(null),
|
||||
getCurrentTypeOfUnit$: () => of(null),
|
||||
subscribeContextValue$: () => of(false),
|
||||
};
|
||||
const accessor = new Injector([
|
||||
[IContextService, { useValue: service }],
|
||||
[IUniverInstanceService, { useValue: service }],
|
||||
[UserManagerService, { useValue: service }],
|
||||
]);
|
||||
|
||||
expect(threadCommentMenuFactory(accessor)).toMatchObject({
|
||||
title: 'sheets-thread-comment-ui.menu.addComment',
|
||||
tooltip: 'sheets-thread-comment-ui.menu.addComment',
|
||||
});
|
||||
|
||||
accessor.dispose();
|
||||
});
|
||||
});
|
||||
87
packages/sheets-thread-comment-ui/src/menu/menu.ts
Normal file
87
packages/sheets-thread-comment-ui/src/menu/menu.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* 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 { IShortcutItem } from '@univerjs/ui';
|
||||
import { IPermissionService, IUniverInstanceService, UniverInstanceType } from '@univerjs/core';
|
||||
import { RangeProtectionPermissionViewPoint, WorkbookCommentPermission, WorksheetViewPermission } from '@univerjs/sheets';
|
||||
import { deriveStateFromActiveSheet$, getCurrentRangeDisable$, whenSheetEditorFocused } from '@univerjs/sheets-ui';
|
||||
import { getMenuHiddenObservable, KeyCode, MenuItemType, MetaKeys } from '@univerjs/ui';
|
||||
import { map } from 'rxjs';
|
||||
import { AddSheetDrawingCommentOperation, ShowAddSheetCommentModalOperation, ToggleSheetCommentPanelOperation } from '../commands/operations/comment.operation';
|
||||
|
||||
function getDrawingCommentDisable$(accessor: IAccessor) {
|
||||
const univerInstanceService = accessor.get(IUniverInstanceService);
|
||||
const permissionService = accessor.get(IPermissionService);
|
||||
|
||||
return deriveStateFromActiveSheet$(univerInstanceService, true, ({ workbook, worksheet }) => {
|
||||
const unitId = workbook.getUnitId();
|
||||
const subUnitId = worksheet.getSheetId();
|
||||
|
||||
return permissionService.composePermission$([
|
||||
new WorkbookCommentPermission(unitId).id,
|
||||
new WorksheetViewPermission(unitId, subUnitId).id,
|
||||
]).pipe(map((permissions) => permissions.some((permission) => permission.value === false)));
|
||||
});
|
||||
}
|
||||
|
||||
export const drawingCommentMenuFactory = (accessor: IAccessor) => ({
|
||||
id: AddSheetDrawingCommentOperation.id,
|
||||
type: MenuItemType.BUTTON,
|
||||
icon: 'InsertCommentDoubleIcon',
|
||||
title: 'sheets-thread-comment-ui.menu.addComment',
|
||||
tooltip: 'sheets-thread-comment-ui.menu.addComment',
|
||||
hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET),
|
||||
disabled$: getDrawingCommentDisable$(accessor),
|
||||
});
|
||||
|
||||
export const threadCommentMenuFactory = (accessor: IAccessor) => {
|
||||
return {
|
||||
id: ShowAddSheetCommentModalOperation.id,
|
||||
type: MenuItemType.BUTTON,
|
||||
icon: 'InsertCommentDoubleIcon',
|
||||
title: 'sheets-thread-comment-ui.menu.addComment',
|
||||
tooltip: 'sheets-thread-comment-ui.menu.addComment',
|
||||
hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET),
|
||||
disabled$: getCurrentRangeDisable$(accessor, {
|
||||
workbookTypes: [WorkbookCommentPermission],
|
||||
worksheetTypes: [WorksheetViewPermission],
|
||||
rangeTypes: [RangeProtectionPermissionViewPoint],
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
export const threadPanelMenuFactory = (accessor: IAccessor) => {
|
||||
return {
|
||||
id: ToggleSheetCommentPanelOperation.id,
|
||||
type: MenuItemType.BUTTON,
|
||||
icon: 'CommentIcon',
|
||||
title: 'sheets-thread-comment-ui.menu.openComments',
|
||||
tooltip: 'sheets-thread-comment-ui.menu.openComments',
|
||||
disabled$: getCurrentRangeDisable$(accessor, {
|
||||
workbookTypes: [WorkbookCommentPermission],
|
||||
worksheetTypes: [WorksheetViewPermission],
|
||||
rangeTypes: [RangeProtectionPermissionViewPoint],
|
||||
}),
|
||||
hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET),
|
||||
};
|
||||
};
|
||||
|
||||
export const AddCommentShortcut: IShortcutItem = {
|
||||
id: ShowAddSheetCommentModalOperation.id,
|
||||
binding: KeyCode.M | MetaKeys.CTRL_COMMAND | MetaKeys.ALT,
|
||||
preconditions: whenSheetEditorFocused,
|
||||
};
|
||||
61
packages/sheets-thread-comment-ui/src/menu/mobile-menu.ts
Normal file
61
packages/sheets-thread-comment-ui/src/menu/mobile-menu.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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 { LocaleKey } from '../locale/types';
|
||||
import { UniverInstanceType } from '@univerjs/core';
|
||||
import { RangeProtectionPermissionViewPoint, SheetsSelectionsService, WorkbookCommentPermission, WorksheetViewPermission } from '@univerjs/sheets';
|
||||
import { SheetsThreadCommentModel } from '@univerjs/sheets-thread-comment';
|
||||
import { getCurrentRangeDisable$ } from '@univerjs/sheets-ui';
|
||||
import { getMenuHiddenObservable, MenuItemType } from '@univerjs/ui';
|
||||
import { ShowAddSheetCommentMobileOperation } from '../commands/operations/mobile/show-add-sheet-comment.operation';
|
||||
|
||||
export function getMobileThreadCommentMenuTitle(accessor: IAccessor): LocaleKey {
|
||||
const selectionService = accessor.get(SheetsSelectionsService);
|
||||
const selection = selectionService.getCurrentLastSelection()?.primary;
|
||||
const selectionParam = selectionService.currentSelectionParam;
|
||||
if (!selection || !selectionParam) {
|
||||
return 'sheets-thread-comment-ui.menu.addComment';
|
||||
}
|
||||
|
||||
const commentId = accessor.get(SheetsThreadCommentModel).getByLocation(
|
||||
selectionParam.unitId,
|
||||
selectionParam.sheetId,
|
||||
selection.startRow,
|
||||
selection.startColumn
|
||||
);
|
||||
return commentId
|
||||
? 'sheets-thread-comment-ui.menu.openComments'
|
||||
: 'sheets-thread-comment-ui.menu.addComment';
|
||||
}
|
||||
|
||||
export const mobileThreadCommentMenuFactory = (accessor: IAccessor) => ({
|
||||
id: ShowAddSheetCommentMobileOperation.id,
|
||||
type: MenuItemType.BUTTON,
|
||||
icon: 'InsertCommentDoubleIcon',
|
||||
title: 'sheets-thread-comment-ui.menu.addComment',
|
||||
hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET),
|
||||
disabled$: getCurrentRangeDisable$(accessor, {
|
||||
workbookTypes: [WorkbookCommentPermission],
|
||||
worksheetTypes: [WorksheetViewPermission],
|
||||
rangeTypes: [RangeProtectionPermissionViewPoint],
|
||||
}),
|
||||
});
|
||||
|
||||
export const mobileContextThreadCommentMenuFactory = (accessor: IAccessor) => ({
|
||||
...mobileThreadCommentMenuFactory(accessor),
|
||||
title: getMobileThreadCommentMenuTitle(accessor),
|
||||
});
|
||||
59
packages/sheets-thread-comment-ui/src/menu/mobile-schema.ts
Normal file
59
packages/sheets-thread-comment-ui/src/menu/mobile-schema.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* 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 { ContextMenuGroup, ContextMenuPosition, FloatingObjectToolbarPosition, RibbonInsertGroup } from '@univerjs/ui';
|
||||
import { AddSheetDrawingCommentOperation, ToggleSheetCommentPanelOperation } from '../commands/operations/comment.operation';
|
||||
import { ShowAddSheetCommentMobileOperation } from '../commands/operations/mobile/show-add-sheet-comment.operation';
|
||||
import { drawingCommentMenuFactory, threadPanelMenuFactory } from './menu';
|
||||
import { mobileContextThreadCommentMenuFactory, mobileThreadCommentMenuFactory } from './mobile-menu';
|
||||
|
||||
export const mobileMenuSchema: MenuSchemaType = {
|
||||
[RibbonInsertGroup.MEDIA]: {
|
||||
[ToggleSheetCommentPanelOperation.id]: {
|
||||
order: 2,
|
||||
gridLayout: { row: 1, column: 3, showLabel: true },
|
||||
menuItemFactory: threadPanelMenuFactory,
|
||||
},
|
||||
[ShowAddSheetCommentMobileOperation.id]: {
|
||||
order: 2.1,
|
||||
gridLayout: { row: 2, column: 3, showLabel: true },
|
||||
menuItemFactory: mobileThreadCommentMenuFactory,
|
||||
},
|
||||
},
|
||||
[ContextMenuPosition.MAIN_AREA]: {
|
||||
[ContextMenuGroup.OTHERS]: {
|
||||
[ShowAddSheetCommentMobileOperation.id]: {
|
||||
order: 0,
|
||||
menuItemFactory: mobileContextThreadCommentMenuFactory,
|
||||
},
|
||||
},
|
||||
},
|
||||
[FloatingObjectToolbarPosition.SHEET]: {
|
||||
[AddSheetDrawingCommentOperation.id]: {
|
||||
order: 10,
|
||||
menuItemFactory: drawingCommentMenuFactory,
|
||||
},
|
||||
},
|
||||
[ContextMenuPosition.DRAWING]: {
|
||||
[ContextMenuGroup.OTHERS]: {
|
||||
[AddSheetDrawingCommentOperation.id]: {
|
||||
order: 0,
|
||||
menuItemFactory: drawingCommentMenuFactory,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
57
packages/sheets-thread-comment-ui/src/menu/schema.ts
Normal file
57
packages/sheets-thread-comment-ui/src/menu/schema.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 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 { ContextMenuGroup, ContextMenuPosition, FloatingObjectToolbarPosition, RibbonInsertGroup } from '@univerjs/ui';
|
||||
import { AddSheetDrawingCommentOperation, ShowAddSheetCommentModalOperation, ToggleSheetCommentPanelOperation } from '../commands/operations/comment.operation';
|
||||
import { drawingCommentMenuFactory, threadCommentMenuFactory, threadPanelMenuFactory } from './menu';
|
||||
|
||||
export const menuSchema: MenuSchemaType = {
|
||||
[RibbonInsertGroup.MEDIA]: {
|
||||
[ToggleSheetCommentPanelOperation.id]: {
|
||||
order: 2,
|
||||
gridLayout: { row: 1, column: 3, showLabel: true },
|
||||
menuItemFactory: threadPanelMenuFactory,
|
||||
},
|
||||
[ShowAddSheetCommentModalOperation.id]: {
|
||||
order: 2.1,
|
||||
gridLayout: { row: 2, column: 3, showLabel: true },
|
||||
menuItemFactory: threadCommentMenuFactory,
|
||||
},
|
||||
},
|
||||
[ContextMenuPosition.MAIN_AREA]: {
|
||||
[ContextMenuGroup.OTHERS]: {
|
||||
[ShowAddSheetCommentModalOperation.id]: {
|
||||
order: 0,
|
||||
menuItemFactory: threadCommentMenuFactory,
|
||||
},
|
||||
},
|
||||
},
|
||||
[FloatingObjectToolbarPosition.SHEET]: {
|
||||
[AddSheetDrawingCommentOperation.id]: {
|
||||
order: 10,
|
||||
menuItemFactory: drawingCommentMenuFactory,
|
||||
},
|
||||
},
|
||||
[ContextMenuPosition.DRAWING]: {
|
||||
[ContextMenuGroup.OTHERS]: {
|
||||
[AddSheetDrawingCommentOperation.id]: {
|
||||
order: 0,
|
||||
menuItemFactory: drawingCommentMenuFactory,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
104
packages/sheets-thread-comment-ui/src/mobile-plugin.ts
Normal file
104
packages/sheets-thread-comment-ui/src/mobile-plugin.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Dependency } from '@univerjs/core';
|
||||
import type { IUniverSheetsThreadCommentUIConfig } from './config/config';
|
||||
import { DependentOn, ICommandService, IConfigService, Inject, Injector, merge, Plugin, UniverInstanceType } from '@univerjs/core';
|
||||
import { UniverRenderEnginePlugin } from '@univerjs/engine-render';
|
||||
import { UniverSheetsPlugin } from '@univerjs/sheets';
|
||||
import { UniverSheetsThreadCommentPlugin } from '@univerjs/sheets-thread-comment';
|
||||
import { UniverSheetsMobileUIPlugin } from '@univerjs/sheets-ui';
|
||||
import { UniverThreadCommentPlugin } from '@univerjs/thread-comment';
|
||||
import { UniverThreadCommentMobileUIPlugin } from '@univerjs/thread-comment-ui';
|
||||
import { UniverMobileUIPlugin } from '@univerjs/ui';
|
||||
import pkg from '../package.json';
|
||||
import {
|
||||
AddSheetDrawingCommentOperation,
|
||||
OpenSheetCommentPanelOperation,
|
||||
ToggleSheetCommentPanelOperation,
|
||||
} from './commands/operations/comment.operation';
|
||||
import { ShowAddSheetCommentMobileOperation } from './commands/operations/mobile/show-add-sheet-comment.operation';
|
||||
import { defaultPluginConfig, SHEETS_THREAD_COMMENT_UI_PLUGIN_CONFIG_KEY } from './config/config';
|
||||
import { MobileComponentsController } from './controllers/mobile/components.controller';
|
||||
import { SheetsThreadCommentMobileController } from './controllers/mobile/sheets-thread-comment.controller';
|
||||
import { SheetsThreadCommentRenderController } from './controllers/render-controllers/render.controller';
|
||||
import { SheetsThreadCommentCopyPasteController } from './controllers/sheets-thread-comment-copy-paste.controller';
|
||||
import { SheetsThreadCommentPermissionController } from './controllers/sheets-thread-comment-permission.controller';
|
||||
import { MobileSheetCommentDraftService } from './services/mobile/mobile-sheet-comment-draft.service';
|
||||
import { SheetsThreadCommentPopupService } from './services/sheets-thread-comment-popup.service';
|
||||
import { PLUGIN_NAME } from './types/const';
|
||||
|
||||
@DependentOn(
|
||||
UniverRenderEnginePlugin,
|
||||
UniverThreadCommentPlugin,
|
||||
UniverSheetsPlugin,
|
||||
UniverThreadCommentMobileUIPlugin,
|
||||
UniverSheetsThreadCommentPlugin,
|
||||
UniverMobileUIPlugin,
|
||||
UniverSheetsMobileUIPlugin
|
||||
)
|
||||
export class UniverSheetsThreadCommentMobileUIPlugin extends Plugin {
|
||||
static override pluginName = PLUGIN_NAME;
|
||||
static override packageName = pkg.name;
|
||||
static override version = pkg.version;
|
||||
static override type = UniverInstanceType.UNIVER_SHEET;
|
||||
|
||||
constructor(
|
||||
private readonly _config: Partial<IUniverSheetsThreadCommentUIConfig> = defaultPluginConfig,
|
||||
@Inject(Injector) protected override _injector: Injector,
|
||||
@Inject(ICommandService) private readonly _commandService: ICommandService,
|
||||
@IConfigService private readonly _configService: IConfigService
|
||||
) {
|
||||
super();
|
||||
|
||||
const { menu, ...rest } = merge({}, defaultPluginConfig, this._config);
|
||||
if (menu) {
|
||||
this._configService.setConfig('menu', menu, { merge: true });
|
||||
}
|
||||
this._configService.setConfig(SHEETS_THREAD_COMMENT_UI_PLUGIN_CONFIG_KEY, rest);
|
||||
}
|
||||
|
||||
override onStarting(): void {
|
||||
([
|
||||
[MobileComponentsController],
|
||||
[SheetsThreadCommentMobileController],
|
||||
[SheetsThreadCommentRenderController],
|
||||
[SheetsThreadCommentCopyPasteController],
|
||||
[SheetsThreadCommentPopupService],
|
||||
[SheetsThreadCommentPermissionController],
|
||||
[MobileSheetCommentDraftService],
|
||||
] as Dependency[]).forEach((dependency) => this._injector.add(dependency));
|
||||
this._injector.get(MobileComponentsController);
|
||||
|
||||
[
|
||||
AddSheetDrawingCommentOperation,
|
||||
OpenSheetCommentPanelOperation,
|
||||
ShowAddSheetCommentMobileOperation,
|
||||
ToggleSheetCommentPanelOperation,
|
||||
].forEach((command) => this._commandService.registerCommand(command));
|
||||
|
||||
this._injector.get(SheetsThreadCommentMobileController);
|
||||
}
|
||||
|
||||
override onReady(): void {
|
||||
this._injector.get(SheetsThreadCommentRenderController);
|
||||
}
|
||||
|
||||
override onRendered(): void {
|
||||
this._injector.get(SheetsThreadCommentCopyPasteController);
|
||||
this._injector.get(SheetsThreadCommentPermissionController);
|
||||
}
|
||||
}
|
||||
118
packages/sheets-thread-comment-ui/src/plugin.ts
Normal file
118
packages/sheets-thread-comment-ui/src/plugin.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* 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 { IUniverSheetsThreadCommentUIConfig } from './config/config';
|
||||
import { DependentOn, ICommandService, IConfigService, Inject, Injector, merge, Plugin, UniverInstanceType } from '@univerjs/core';
|
||||
import { UniverDrawingPlugin } from '@univerjs/drawing';
|
||||
import { IRenderManagerService, UniverRenderEnginePlugin } from '@univerjs/engine-render';
|
||||
import { UniverSheetsPlugin } from '@univerjs/sheets';
|
||||
import { UniverSheetsThreadCommentPlugin } from '@univerjs/sheets-thread-comment';
|
||||
import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui';
|
||||
import { UniverThreadCommentPlugin } from '@univerjs/thread-comment';
|
||||
import { UniverThreadCommentUIPlugin } from '@univerjs/thread-comment-ui';
|
||||
import pkg from '../package.json';
|
||||
import { AddSheetDrawingCommentOperation, OpenSheetCommentPanelOperation, ShowAddSheetCommentModalOperation, ToggleSheetCommentPanelOperation } from './commands/operations/comment.operation';
|
||||
import { defaultPluginConfig, SHEETS_THREAD_COMMENT_UI_PLUGIN_CONFIG_KEY } from './config/config';
|
||||
import { ComponentsController } from './controllers/components.controller';
|
||||
import { SheetsThreadCommentDrawingRenderController } from './controllers/render-controllers/drawing.render-controller';
|
||||
import { SheetsThreadCommentRenderController } from './controllers/render-controllers/render.controller';
|
||||
import { SheetsThreadCommentCopyPasteController } from './controllers/sheets-thread-comment-copy-paste.controller';
|
||||
import { SheetsThreadCommentHoverController } from './controllers/sheets-thread-comment-hover.controller';
|
||||
import { SheetsThreadCommentPermissionController } from './controllers/sheets-thread-comment-permission.controller';
|
||||
import { SheetsThreadCommentPopupController } from './controllers/sheets-thread-comment-popup.controller';
|
||||
import { SheetsThreadCommentController } from './controllers/sheets-thread-comment.controller';
|
||||
import { SheetsThreadCommentPopupService } from './services/sheets-thread-comment-popup.service';
|
||||
import { PLUGIN_NAME } from './types/const';
|
||||
|
||||
@DependentOn(
|
||||
UniverRenderEnginePlugin,
|
||||
UniverThreadCommentPlugin,
|
||||
UniverDrawingPlugin,
|
||||
UniverSheetsPlugin,
|
||||
UniverThreadCommentUIPlugin,
|
||||
UniverSheetsThreadCommentPlugin,
|
||||
UniverSheetsUIPlugin
|
||||
)
|
||||
export class UniverSheetsThreadCommentUIPlugin extends Plugin {
|
||||
static override pluginName = PLUGIN_NAME;
|
||||
static override packageName = pkg.name;
|
||||
static override version = pkg.version;
|
||||
static override type = UniverInstanceType.UNIVER_SHEET;
|
||||
|
||||
constructor(
|
||||
private readonly _config: Partial<IUniverSheetsThreadCommentUIConfig> = defaultPluginConfig,
|
||||
@Inject(Injector) protected override _injector: Injector,
|
||||
@Inject(ICommandService) protected _commandService: ICommandService,
|
||||
@IRenderManagerService private readonly _renderManagerService: IRenderManagerService,
|
||||
@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_THREAD_COMMENT_UI_PLUGIN_CONFIG_KEY, rest);
|
||||
}
|
||||
|
||||
override onStarting(): void {
|
||||
this._injector.add([ComponentsController]);
|
||||
this._injector.get(ComponentsController);
|
||||
([
|
||||
[SheetsThreadCommentController],
|
||||
[SheetsThreadCommentRenderController],
|
||||
[SheetsThreadCommentCopyPasteController],
|
||||
[SheetsThreadCommentHoverController],
|
||||
[SheetsThreadCommentPopupController],
|
||||
[SheetsThreadCommentPopupService],
|
||||
[SheetsThreadCommentPermissionController],
|
||||
] as Dependency[]).forEach((dep) => {
|
||||
this._injector.add(dep);
|
||||
});
|
||||
|
||||
[
|
||||
AddSheetDrawingCommentOperation,
|
||||
OpenSheetCommentPanelOperation,
|
||||
ShowAddSheetCommentModalOperation,
|
||||
ToggleSheetCommentPanelOperation,
|
||||
].forEach((command) => {
|
||||
this._commandService.registerCommand(command);
|
||||
});
|
||||
|
||||
this._injector.get(SheetsThreadCommentController);
|
||||
}
|
||||
|
||||
override onReady(): void {
|
||||
this._injector.get(SheetsThreadCommentRenderController);
|
||||
}
|
||||
|
||||
override onRendered(): void {
|
||||
this._renderManagerService.registerRenderModule(
|
||||
UniverInstanceType.UNIVER_SHEET,
|
||||
[SheetsThreadCommentDrawingRenderController]
|
||||
);
|
||||
this._injector.get(SheetsThreadCommentCopyPasteController);
|
||||
this._injector.get(SheetsThreadCommentHoverController);
|
||||
this._injector.get(SheetsThreadCommentPopupController);
|
||||
this._injector.get(SheetsThreadCommentPermissionController);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
* 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 } from '@univerjs/core';
|
||||
import { Inject, Injector } from '@univerjs/core';
|
||||
import { CellPopupManagerService, SheetCanvasPopManagerService } from '@univerjs/sheets-ui';
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
import { SHEETS_THREAD_COMMENT_MODAL } from '../../types/const';
|
||||
import { SheetsThreadCommentPopupService } from '../sheets-thread-comment-popup.service';
|
||||
|
||||
class TestSheetCanvasPopManagerService {}
|
||||
|
||||
class TestCellPopupManagerService extends CellPopupManagerService {
|
||||
disposed = false;
|
||||
lastLocation?: Parameters<CellPopupManagerService['showPopup']>[0];
|
||||
lastPopup?: Parameters<CellPopupManagerService['showPopup']>[1];
|
||||
|
||||
constructor(
|
||||
@Inject(SheetCanvasPopManagerService) sheetCanvasPopManagerService: SheetCanvasPopManagerService
|
||||
) {
|
||||
super(sheetCanvasPopManagerService);
|
||||
}
|
||||
|
||||
override showPopup(
|
||||
location: Parameters<CellPopupManagerService['showPopup']>[0],
|
||||
popup: Parameters<CellPopupManagerService['showPopup']>[1]
|
||||
): IDisposable {
|
||||
this.lastLocation = location;
|
||||
this.lastPopup = popup;
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
this.disposed = true;
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
describe('SheetsThreadCommentPopupService', () => {
|
||||
let injector: Injector;
|
||||
let service: SheetsThreadCommentPopupService;
|
||||
let cellPopupManagerService: TestCellPopupManagerService;
|
||||
let hideCount: number;
|
||||
|
||||
beforeEach(() => {
|
||||
hideCount = 0;
|
||||
injector = new Injector();
|
||||
injector.add([SheetCanvasPopManagerService, { useClass: TestSheetCanvasPopManagerService }]);
|
||||
injector.add([CellPopupManagerService, { useClass: TestCellPopupManagerService }]);
|
||||
injector.add([SheetsThreadCommentPopupService]);
|
||||
|
||||
service = injector.get(SheetsThreadCommentPopupService);
|
||||
cellPopupManagerService = injector.get(CellPopupManagerService) as unknown as TestCellPopupManagerService;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
service.dispose();
|
||||
});
|
||||
|
||||
it('shows, persists and hides the active popup through the popup manager boundary', () => {
|
||||
service.showPopup({
|
||||
unitId: 'test',
|
||||
subUnitId: 'sheet1',
|
||||
row: 1,
|
||||
col: 2,
|
||||
commentId: 'comment-1',
|
||||
temp: true,
|
||||
trigger: 'hover',
|
||||
}, () => {
|
||||
hideCount += 1;
|
||||
});
|
||||
|
||||
expect(service.activePopup).toEqual({
|
||||
unitId: 'test',
|
||||
subUnitId: 'sheet1',
|
||||
row: 1,
|
||||
col: 2,
|
||||
commentId: 'comment-1',
|
||||
temp: true,
|
||||
trigger: 'hover',
|
||||
});
|
||||
expect(cellPopupManagerService.lastLocation).toEqual({
|
||||
unitId: 'test',
|
||||
subUnitId: 'sheet1',
|
||||
row: 1,
|
||||
col: 2,
|
||||
});
|
||||
expect(cellPopupManagerService.lastPopup).toEqual(expect.objectContaining({
|
||||
componentKey: SHEETS_THREAD_COMMENT_MODAL,
|
||||
direction: 'horizontal',
|
||||
}));
|
||||
|
||||
service.persistPopup();
|
||||
expect(service.activePopup).toEqual(expect.objectContaining({
|
||||
temp: false,
|
||||
}));
|
||||
|
||||
service.hidePopup();
|
||||
expect(service.activePopup).toBeNull();
|
||||
expect(cellPopupManagerService.disposed).toBe(true);
|
||||
expect(hideCount).toBe(1);
|
||||
});
|
||||
|
||||
it('updates the active popup in place when the same cell is targeted again', () => {
|
||||
service.showPopup({
|
||||
unitId: 'test',
|
||||
subUnitId: 'sheet1',
|
||||
row: 1,
|
||||
col: 2,
|
||||
commentId: 'comment-1',
|
||||
});
|
||||
service.showPopup({
|
||||
unitId: 'test',
|
||||
subUnitId: 'sheet1',
|
||||
row: 1,
|
||||
col: 2,
|
||||
commentId: 'comment-2',
|
||||
trigger: 'cell',
|
||||
});
|
||||
|
||||
expect(service.activePopup).toEqual({
|
||||
unitId: 'test',
|
||||
subUnitId: 'sheet1',
|
||||
row: 1,
|
||||
col: 2,
|
||||
commentId: 'comment-2',
|
||||
trigger: 'cell',
|
||||
});
|
||||
expect(cellPopupManagerService.disposed).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* 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 { Disposable } from '@univerjs/core';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
export interface IMobileSheetCommentDraft {
|
||||
unitId: string;
|
||||
subUnitId: string;
|
||||
row: number;
|
||||
column: number;
|
||||
}
|
||||
|
||||
export class MobileSheetCommentDraftService extends Disposable {
|
||||
private readonly _draft$ = new BehaviorSubject<Nullable<IMobileSheetCommentDraft>>(null);
|
||||
|
||||
readonly draft$ = this._draft$.asObservable();
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.disposeWithMe(() => this._draft$.complete());
|
||||
}
|
||||
|
||||
get draft(): Nullable<IMobileSheetCommentDraft> {
|
||||
return this._draft$.getValue();
|
||||
}
|
||||
|
||||
place(draft: IMobileSheetCommentDraft): void {
|
||||
this._draft$.next(draft);
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
this._draft$.next(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* 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, SheetCanvasPopManagerService } from '@univerjs/sheets-ui';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { SHEETS_THREAD_COMMENT_MODAL } from '../types/const';
|
||||
|
||||
export interface IThreadCommentPopup extends ISheetLocationBase {
|
||||
commentId?: string;
|
||||
// when triggered by hover, temp is set to be `true`
|
||||
temp?: boolean;
|
||||
trigger?: string;
|
||||
}
|
||||
|
||||
export class SheetsThreadCommentPopupService extends Disposable {
|
||||
private _lastPopup: Nullable<IDisposable> = null;
|
||||
private _activePopup: Nullable<IThreadCommentPopup>;
|
||||
private _activePopup$ = new BehaviorSubject<Nullable<IThreadCommentPopup>>(null);
|
||||
|
||||
activePopup$ = this._activePopup$.asObservable();
|
||||
|
||||
get activePopup() {
|
||||
return this._activePopup;
|
||||
}
|
||||
|
||||
constructor(
|
||||
@Inject(SheetCanvasPopManagerService) private readonly _canvasPopupManagerService: SheetCanvasPopManagerService,
|
||||
@Inject(CellPopupManagerService) private readonly _cellPopupManagerService: CellPopupManagerService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.disposeWithMe(() => {
|
||||
this._activePopup$.complete();
|
||||
});
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
super.dispose();
|
||||
this.hidePopup();
|
||||
}
|
||||
|
||||
showPopup(location: IThreadCommentPopup, 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(
|
||||
{
|
||||
row,
|
||||
col,
|
||||
unitId,
|
||||
subUnitId,
|
||||
},
|
||||
{
|
||||
componentKey: SHEETS_THREAD_COMMENT_MODAL,
|
||||
onClickOutside: () => {
|
||||
this.hidePopup();
|
||||
},
|
||||
direction: 'horizontal',
|
||||
excludeOutside: [
|
||||
...Array.from(document.querySelectorAll('.univer-thread-comment')),
|
||||
document.getElementById('thread-comment-add'),
|
||||
].filter(Boolean) as HTMLElement[],
|
||||
priority: 2,
|
||||
}
|
||||
);
|
||||
|
||||
if (!popupDisposable) {
|
||||
throw new Error('[SheetsThreadCommentPopupService]: cannot show popup!');
|
||||
}
|
||||
|
||||
const disposableCollection = new DisposableCollection();
|
||||
disposableCollection.add(popupDisposable);
|
||||
disposableCollection.add({
|
||||
dispose: () => {
|
||||
onHide?.();
|
||||
},
|
||||
});
|
||||
|
||||
this._lastPopup = disposableCollection;
|
||||
}
|
||||
|
||||
hidePopup() {
|
||||
if (!this._activePopup) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
21
packages/sheets-thread-comment-ui/src/types/const.ts
Normal file
21
packages/sheets-thread-comment-ui/src/types/const.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export const SHEETS_THREAD_COMMENT_MODAL = 'univer.sheet.thread-comment-modal';
|
||||
export const SHEETS_THREAD_COMMENT_PANEL = 'univer.sheet.thread-comment-panel';
|
||||
export const SHOW_ADD_SHEET_COMMENT_OPERATION_ID = 'sheet.operation.show-comment-modal';
|
||||
|
||||
export const PLUGIN_NAME = 'SHEET_THREAD_COMMENT_UI_PLUGIN';
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { IThreadComment } from '@univerjs/thread-comment';
|
||||
|
||||
export interface ISheetThreadComment extends IThreadComment {
|
||||
row: number;
|
||||
column: number;
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* 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 { IUniverInstanceService, Tools, UniverInstanceType } from '@univerjs/core';
|
||||
import { SheetsThreadCommentModel } from '@univerjs/sheets-thread-comment';
|
||||
import { ThreadCommentTree, ThreadCommentTreeLocation } from '@univerjs/thread-comment-ui';
|
||||
import { useDependency, useObservable } from '@univerjs/ui';
|
||||
import { map, startWith } from 'rxjs';
|
||||
import { SheetsThreadCommentPopupService } from '../services/sheets-thread-comment-popup.service';
|
||||
|
||||
export const SheetsThreadCommentCell = () => {
|
||||
const univerInstanceService = useDependency(IUniverInstanceService);
|
||||
const sheetsThreadCommentPopupService = useDependency(SheetsThreadCommentPopupService);
|
||||
const activePopup = useObservable(sheetsThreadCommentPopupService.activePopup$);
|
||||
const sheetThreadCommentModel = useDependency(SheetsThreadCommentModel);
|
||||
const getRootId = () => {
|
||||
if (!activePopup) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const { unitId, subUnitId, row, col, commentId } = activePopup;
|
||||
const activeComment = commentId
|
||||
? sheetThreadCommentModel.getAllByLocation(unitId, subUnitId, row, col).find((comment) => comment.id === commentId)
|
||||
: undefined;
|
||||
|
||||
return activeComment && !activeComment.resolved
|
||||
? activeComment.id
|
||||
: sheetThreadCommentModel.getByLocation(unitId, subUnitId, row, col);
|
||||
};
|
||||
const rootId = useObservable(
|
||||
activePopup
|
||||
? () => sheetThreadCommentModel.commentUpdate$.pipe(
|
||||
map(() => getRootId()),
|
||||
startWith(getRootId())
|
||||
)
|
||||
: null,
|
||||
undefined,
|
||||
false,
|
||||
[activePopup, sheetThreadCommentModel]
|
||||
);
|
||||
if (!activePopup) {
|
||||
return null;
|
||||
}
|
||||
const { row, col, unitId, subUnitId, trigger } = activePopup;
|
||||
const ref = `${Tools.chatAtABC(col)}${row + 1}`;
|
||||
const onClose = () => {
|
||||
sheetsThreadCommentPopupService.hidePopup();
|
||||
};
|
||||
|
||||
const getSubUnitName = (id: string) => {
|
||||
return univerInstanceService.getCurrentUnitOfType<Workbook>(UniverInstanceType.UNIVER_SHEET)?.getSheetBySheetId(id)?.getName() ?? '';
|
||||
};
|
||||
|
||||
return (
|
||||
<ThreadCommentTree
|
||||
onClick={() => {
|
||||
sheetsThreadCommentPopupService.persistPopup();
|
||||
}}
|
||||
location={ThreadCommentTreeLocation.CELL}
|
||||
id={rootId}
|
||||
unitId={unitId}
|
||||
subUnitId={subUnitId}
|
||||
type={UniverInstanceType.UNIVER_SHEET}
|
||||
refStr={ref}
|
||||
onClose={onClose}
|
||||
getSubUnitName={getSubUnitName}
|
||||
autoFocus={trigger === 'context-menu'}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,197 @@
|
||||
/**
|
||||
* 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 type { IThreadComment } from '@univerjs/thread-comment';
|
||||
import { ICommandService, IUniverInstanceService, UniverInstanceType, UserManagerService } from '@univerjs/core';
|
||||
import { singleReferenceToGrid } from '@univerjs/engine-formula';
|
||||
import { IMarkSelectionService } from '@univerjs/sheets-ui';
|
||||
import { deserializeThreadCommentAnchor, serializeThreadCommentAnchor, ThreadCommentAnchorKind } from '@univerjs/thread-comment';
|
||||
import { ThreadCommentDraftService, ThreadCommentPanel, ThreadCommentPanelService } from '@univerjs/thread-comment-ui';
|
||||
import { useDependency, useObservable } from '@univerjs/ui';
|
||||
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import { map } from 'rxjs';
|
||||
import { ShowAddSheetCommentModalOperation } from '../commands/operations/comment.operation';
|
||||
import { SheetsThreadCommentPopupService } from '../services/sheets-thread-comment-popup.service';
|
||||
|
||||
export interface ISheetsThreadCommentPanelProps {
|
||||
ThreadCommentPanelComponent?: typeof ThreadCommentPanel;
|
||||
}
|
||||
|
||||
export const SheetsThreadCommentPanel = ({
|
||||
ThreadCommentPanelComponent = ThreadCommentPanel,
|
||||
}: ISheetsThreadCommentPanelProps = {}) => {
|
||||
const markSelectionService = useDependency(IMarkSelectionService);
|
||||
const univerInstanceService = useDependency(IUniverInstanceService);
|
||||
const sheetsThreadCommentPopupService = useDependency(SheetsThreadCommentPopupService);
|
||||
const workbook = univerInstanceService.getCurrentUnitOfType<Workbook>(UniverInstanceType.UNIVER_SHEET)!;
|
||||
const unitId = workbook.getUnitId();
|
||||
const commandService = useDependency(ICommandService);
|
||||
const draftService = useDependency(ThreadCommentDraftService);
|
||||
const userManagerService = useDependency(UserManagerService);
|
||||
const draft = useObservable(draftService.draft$, draftService.draft);
|
||||
const tempDrawingComment = draft?.anchor.kind === ThreadCommentAnchorKind.SHEET_DRAWING && draft.unitId === unitId
|
||||
? {
|
||||
id: '',
|
||||
threadId: '',
|
||||
unitId: draft.unitId,
|
||||
subUnitId: draft.subUnitId,
|
||||
ref: serializeThreadCommentAnchor(draft.anchor),
|
||||
dT: '',
|
||||
personId: userManagerService.getCurrentUser().userID,
|
||||
text: { dataStream: '\r\n' },
|
||||
}
|
||||
: null;
|
||||
const subUnitId$ = useMemo(() => workbook.activeSheet$.pipe(map((i) => i?.getSheetId())), [workbook.activeSheet$]);
|
||||
const subUnitId = useObservable(subUnitId$, workbook.getActiveSheet()?.getSheetId());
|
||||
const hoverShapeId = useRef<string | null>(null);
|
||||
const panelService = useDependency(ThreadCommentPanelService);
|
||||
const activeCommentId = useObservable(panelService.activeCommentId$);
|
||||
const panelVisible = useObservable(panelService.panelVisible$, panelService.panelVisible);
|
||||
const sortComments = useCallback((comments: IThreadComment[]) => {
|
||||
const worksheets = workbook.getSheets();
|
||||
const sheetIndex: Record<string, number> = {};
|
||||
worksheets.forEach((sheet, i) => {
|
||||
sheetIndex[sheet.getSheetId()] = i;
|
||||
});
|
||||
|
||||
const sort = (comments: IThreadComment[]) => {
|
||||
return comments.map((comment) => {
|
||||
const anchor = deserializeThreadCommentAnchor(comment.ref);
|
||||
if (anchor?.kind === ThreadCommentAnchorKind.SHEET_DRAWING) {
|
||||
return { ...comment, p: [sheetIndex[comment.subUnitId] ?? 0, Number.MAX_SAFE_INTEGER, 0] };
|
||||
}
|
||||
const ref = singleReferenceToGrid(comment.ref);
|
||||
const p = [sheetIndex[comment.subUnitId] ?? 0, ref.row, ref.column];
|
||||
return { ...comment, p };
|
||||
}).sort((pre, aft) => {
|
||||
if (pre.p[0] === aft.p[0]) {
|
||||
if (pre.p[1] === aft.p[1]) {
|
||||
return pre.p[2] - aft.p[2];
|
||||
}
|
||||
return pre.p[1] - aft.p[1];
|
||||
}
|
||||
|
||||
return pre.p[0] - aft.p[0];
|
||||
});
|
||||
};
|
||||
|
||||
return [
|
||||
...sort(comments.filter((comment) => !comment.resolved)),
|
||||
...sort(comments.filter((comment) => comment.resolved)),
|
||||
];
|
||||
}, [workbook]);
|
||||
|
||||
const showShape = useCallback((comment: IThreadComment) => {
|
||||
if (deserializeThreadCommentAnchor(comment.ref)) {
|
||||
return null;
|
||||
}
|
||||
if (comment.unitId === unitId && comment.subUnitId === subUnitId && !comment.resolved) {
|
||||
const { row, column } = singleReferenceToGrid(comment.ref);
|
||||
const worksheet = workbook.getSheetBySheetId(comment.subUnitId);
|
||||
const mergeInfo = worksheet?.getMergedCell(row, column) ?? {
|
||||
startColumn: column,
|
||||
endColumn: column,
|
||||
startRow: row,
|
||||
endRow: row,
|
||||
};
|
||||
if (!Number.isNaN(row) && !Number.isNaN(column)) {
|
||||
return markSelectionService.addShape({
|
||||
range: mergeInfo,
|
||||
style: {
|
||||
// hasAutoFill: false,
|
||||
fill: 'rgb(255, 189, 55, 0.35)',
|
||||
strokeWidth: 1,
|
||||
stroke: '#FFBD37',
|
||||
widgets: {},
|
||||
},
|
||||
primary: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}, [markSelectionService, subUnitId, unitId]);
|
||||
|
||||
const getSubUnitName = (id: string) => {
|
||||
return workbook.getSheetBySheetId(id)?.getName() ?? '';
|
||||
};
|
||||
|
||||
const handleAdd = () => {
|
||||
commandService.executeCommand(ShowAddSheetCommentModalOperation.id);
|
||||
};
|
||||
|
||||
const handleHover = (comment: IThreadComment) => {
|
||||
if (hoverShapeId.current) {
|
||||
markSelectionService.removeShape(hoverShapeId.current);
|
||||
hoverShapeId.current = null;
|
||||
}
|
||||
|
||||
if (
|
||||
activeCommentId &&
|
||||
activeCommentId.unitId === comment.unitId &&
|
||||
activeCommentId.subUnitId === comment.subUnitId &&
|
||||
activeCommentId.commentId === comment.id
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
hoverShapeId.current = showShape(comment);
|
||||
};
|
||||
|
||||
const handleLeave = () => {
|
||||
if (hoverShapeId.current) {
|
||||
markSelectionService.removeShape(hoverShapeId.current);
|
||||
hoverShapeId.current = null;
|
||||
}
|
||||
};
|
||||
|
||||
const handleResolve = (id: string, resolved: boolean) => {
|
||||
if (resolved) {
|
||||
sheetsThreadCommentPopupService.hidePopup();
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!panelVisible && hoverShapeId.current) {
|
||||
markSelectionService.removeShape(hoverShapeId.current);
|
||||
hoverShapeId.current = null;
|
||||
}
|
||||
}, [markSelectionService, panelVisible]);
|
||||
|
||||
return (
|
||||
<ThreadCommentPanelComponent
|
||||
unitId={unitId}
|
||||
subUnitId$={subUnitId$}
|
||||
type={UniverInstanceType.UNIVER_SHEET}
|
||||
onAdd={handleAdd}
|
||||
getSubUnitName={getSubUnitName}
|
||||
onResolve={handleResolve}
|
||||
sortComments={sortComments}
|
||||
onItemEnter={handleHover}
|
||||
onItemLeave={handleLeave}
|
||||
onDeleteComment={() => {
|
||||
handleLeave();
|
||||
return true;
|
||||
}}
|
||||
tempComment={tempDrawingComment}
|
||||
onTempCommentClose={() => draftService.cancel()}
|
||||
formatRef={(comment) => {
|
||||
const anchor = deserializeThreadCommentAnchor(comment.ref);
|
||||
return anchor?.kind === ThreadCommentAnchorKind.SHEET_DRAWING ? `#${anchor.elementId}` : comment.ref;
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* 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 { IThreadComment } from '@univerjs/thread-comment';
|
||||
import type { IThreadCommentPanelProps } from '@univerjs/thread-comment-ui';
|
||||
import { Tools, UserManagerService } from '@univerjs/core';
|
||||
import { MobileThreadCommentPanel } from '@univerjs/thread-comment-ui';
|
||||
import { useDependency, useObservable } from '@univerjs/ui';
|
||||
import { MobileSheetCommentDraftService } from '../../services/mobile/mobile-sheet-comment-draft.service';
|
||||
import { SheetsThreadCommentPanel } from '../SheetsThreadCommentPanel';
|
||||
|
||||
function MobileSheetsThreadCommentPanelContent(props: IThreadCommentPanelProps) {
|
||||
const draftService = useDependency(MobileSheetCommentDraftService);
|
||||
const userManagerService = useDependency(UserManagerService);
|
||||
const draft = useObservable(draftService.draft$, draftService.draft);
|
||||
const tempCellComment: IThreadComment | null = draft?.unitId === props.unitId
|
||||
? {
|
||||
id: '',
|
||||
threadId: '',
|
||||
unitId: draft.unitId,
|
||||
subUnitId: draft.subUnitId,
|
||||
ref: `${Tools.chatAtABC(draft.column)}${draft.row + 1}`,
|
||||
dT: '',
|
||||
personId: userManagerService.getCurrentUser().userID,
|
||||
text: { dataStream: '\r\n' },
|
||||
}
|
||||
: null;
|
||||
|
||||
const handleTempCommentClose = () => {
|
||||
draftService.cancel();
|
||||
props.onTempCommentClose?.();
|
||||
};
|
||||
|
||||
return (
|
||||
<MobileThreadCommentPanel
|
||||
{...props}
|
||||
tempComment={props.tempComment ?? tempCellComment}
|
||||
onTempCommentClose={handleTempCommentClose}
|
||||
autoFocusActiveComment
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function MobileSheetsThreadCommentPanel() {
|
||||
return <SheetsThreadCommentPanel ThreadCommentPanelComponent={MobileSheetsThreadCommentPanelContent} />;
|
||||
}
|
||||
27
packages/sheets-thread-comment-ui/tailwind.config.ts
Normal file
27
packages/sheets-thread-comment-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-thread-comment-ui/tsconfig.json
Normal file
8
packages/sheets-thread-comment-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-thread-comment-ui/tsconfig.node.json
Normal file
18
packages/sheets-thread-comment-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-thread-comment-ui/vitest.config.ts
Normal file
3
packages/sheets-thread-comment-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