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:
39
packages/docs-thread-comment/README.md
Normal file
39
packages/docs-thread-comment/README.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# @univerjs/docs-thread-comment
|
||||
|
||||
`@univerjs/docs-thread-comment` provides model commands and Facade APIs for comments anchored to fixed document text ranges. It has no UI or rendering dependency and can be used in Node environments.
|
||||
|
||||
## Package Overview
|
||||
|
||||
| Package | UMD global | CSS | Locales | Facade entry |
|
||||
| --- | --- | :---: | :---: | :---: |
|
||||
| `@univerjs/docs-thread-comment` | `UniverDocsThreadComment` | No | No | Yes |
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
pnpm add @univerjs/docs-thread-comment
|
||||
# or
|
||||
npm install @univerjs/docs-thread-comment
|
||||
```
|
||||
|
||||
Keep all `@univerjs/*` packages on the same version.
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { UniverDocsThreadCommentPlugin } from '@univerjs/docs-thread-comment';
|
||||
import '@univerjs/docs-thread-comment/facade';
|
||||
|
||||
univer.registerPlugin(UniverDocsThreadCommentPlugin);
|
||||
|
||||
const range = univerAPI.getActiveDocument()?.getTextRange(0, 12);
|
||||
await range?.createCommentAsync('Verify this introduction.', { id: 'review-intro' });
|
||||
```
|
||||
|
||||
Use `@univerjs/docs-thread-comment-ui` separately when menus, canvas decorations, or the shared side panel are required.
|
||||
|
||||
## Resources
|
||||
|
||||
- [Documentation](https://docs.univer.ai)
|
||||
- [NPM package](https://npmjs.com/package/@univerjs/docs-thread-comment)
|
||||
- [GitHub repository](https://github.com/dream-num/univer)
|
||||
87
packages/docs-thread-comment/package.json
Normal file
87
packages/docs-thread-comment/package.json
Normal file
@@ -0,0 +1,87 @@
|
||||
{
|
||||
"name": "@univerjs/docs-thread-comment",
|
||||
"version": "1.0.0-rc.0",
|
||||
"private": false,
|
||||
"description": "Thread comment model integration and Facade APIs for Univer Docs.",
|
||||
"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",
|
||||
"docs",
|
||||
"comment",
|
||||
"thread-comment"
|
||||
],
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./facade": "./src/facade/index.ts",
|
||||
"./*": "./src/*"
|
||||
},
|
||||
"main": "./src/index.ts",
|
||||
"types": "./lib/types/index.d.ts",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "./lib/es/index.js",
|
||||
"module": "./lib/es/index.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./lib/es/index.js",
|
||||
"require": "./lib/cjs/index.js",
|
||||
"types": "./lib/types/index.d.ts"
|
||||
},
|
||||
"./*": {
|
||||
"import": "./lib/es/*",
|
||||
"require": "./lib/cjs/*",
|
||||
"types": "./lib/types/index.d.ts"
|
||||
},
|
||||
"./facade": {
|
||||
"import": "./lib/es/facade.js",
|
||||
"require": "./lib/cjs/facade.js",
|
||||
"types": "./lib/types/facade/index.d.ts"
|
||||
},
|
||||
"./lib/facade": {
|
||||
"import": "./lib/es/facade.js",
|
||||
"require": "./lib/cjs/facade.js",
|
||||
"types": "./lib/types/facade/index.d.ts"
|
||||
},
|
||||
"./lib/*": "./lib/*"
|
||||
}
|
||||
},
|
||||
"directories": {
|
||||
"lib": "lib"
|
||||
},
|
||||
"files": [
|
||||
"lib"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"coverage": "vitest run --coverage",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"build:bundle": "univer-cli build",
|
||||
"build:types": "tsc -p tsconfig.node.json",
|
||||
"build": "pnpm run build:bundle && pnpm run build:types"
|
||||
},
|
||||
"dependencies": {
|
||||
"@univerjs/core": "workspace:*",
|
||||
"@univerjs/docs": "workspace:*",
|
||||
"@univerjs/thread-comment": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@univerjs-infra/shared": "workspace:*",
|
||||
"rxjs": "^7.8.2",
|
||||
"typescript": "^6.0.3",
|
||||
"vitest": "^5.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* 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 { DocumentDataModel, IDocumentData } from '@univerjs/core';
|
||||
import {
|
||||
|
||||
ICommandService,
|
||||
|
||||
IUniverInstanceService,
|
||||
Univer,
|
||||
UniverInstanceType,
|
||||
} from '@univerjs/core';
|
||||
import { FDocument } from '@univerjs/docs/facade';
|
||||
import {
|
||||
AddCommentMutation,
|
||||
IThreadCommentDataSourceService,
|
||||
ThreadCommentDataSourceService,
|
||||
ThreadCommentFacadeService,
|
||||
ThreadCommentModel,
|
||||
} from '@univerjs/thread-comment';
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
import { DEFAULT_DOC_SUBUNIT_ID } from '../../../common/const';
|
||||
import {
|
||||
AddDocCommentDecorationMutation,
|
||||
CreateDocTextRangeCommentCommand,
|
||||
} from '../create-doc-text-range-comment.command';
|
||||
import '@univerjs/docs-thread-comment/facade';
|
||||
|
||||
const DOC_ID = 'headless-doc-comment-test';
|
||||
|
||||
function createDocData(): IDocumentData {
|
||||
return {
|
||||
id: DOC_ID,
|
||||
body: {
|
||||
dataStream: 'Hello world\r\n',
|
||||
textRuns: [],
|
||||
paragraphs: [],
|
||||
sectionBreaks: [],
|
||||
customBlocks: [],
|
||||
customDecorations: [],
|
||||
},
|
||||
documentStyle: {
|
||||
pageSize: { width: 594.3, height: 840.51 },
|
||||
marginTop: 72,
|
||||
marginBottom: 72,
|
||||
marginRight: 90,
|
||||
marginLeft: 90,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe('CreateDocTextRangeCommentCommand', () => {
|
||||
let univer: Univer;
|
||||
|
||||
beforeEach(() => {
|
||||
univer = new Univer();
|
||||
const injector = univer.__getInjector();
|
||||
injector.add([IThreadCommentDataSourceService, { useClass: ThreadCommentDataSourceService }]);
|
||||
injector.add([ThreadCommentModel]);
|
||||
injector.add([ThreadCommentFacadeService]);
|
||||
univer.createUnit(UniverInstanceType.UNIVER_DOC, createDocData());
|
||||
|
||||
const commandService = injector.get(ICommandService);
|
||||
commandService.registerCommand(CreateDocTextRangeCommentCommand);
|
||||
commandService.registerCommand(AddCommentMutation);
|
||||
commandService.registerCommand(AddDocCommentDecorationMutation);
|
||||
});
|
||||
|
||||
afterEach(() => univer.dispose());
|
||||
|
||||
it('creates and queries a fixed text comment without UI services', async () => {
|
||||
const injector = univer.__getInjector();
|
||||
const commandService = injector.get(ICommandService);
|
||||
await expect(commandService.executeCommand(CreateDocTextRangeCommentCommand.id, {
|
||||
unitId: DOC_ID,
|
||||
range: { startOffset: 0, endOffset: 5 },
|
||||
content: 'Check the greeting.',
|
||||
id: 'agent-comment-1',
|
||||
personId: 'agent-user-1',
|
||||
})).resolves.toBe(true);
|
||||
|
||||
const documentModel = injector.get(IUniverInstanceService)
|
||||
.getUnit<DocumentDataModel>(DOC_ID, UniverInstanceType.UNIVER_DOC);
|
||||
if (!documentModel) {
|
||||
throw new Error('Test document was not created.');
|
||||
}
|
||||
const range = injector.createInstance(FDocument, documentModel).getTextRange(0, 5);
|
||||
expect(range.getComments()).toHaveLength(1);
|
||||
expect(injector.get(ThreadCommentModel).getThread(
|
||||
DOC_ID,
|
||||
DEFAULT_DOC_SUBUNIT_ID,
|
||||
'agent-comment-1'
|
||||
)?.root).toMatchObject({
|
||||
ref: 'Hello',
|
||||
startOffset: 0,
|
||||
endOffset: 5,
|
||||
personId: 'agent-user-1',
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,206 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { DocumentDataModel, IAccessor, ICommand, IMutation, IMutationInfo, ITextRangeParam, JSONXActions } from '@univerjs/core';
|
||||
import type { IThreadComment, ThreadCommentContent } from '@univerjs/thread-comment';
|
||||
import {
|
||||
BuildTextUtils,
|
||||
CommandType,
|
||||
CustomDecorationType,
|
||||
|
||||
generateRandomId,
|
||||
|
||||
ICommandService,
|
||||
|
||||
IUniverInstanceService,
|
||||
JSONX,
|
||||
|
||||
sequenceExecute,
|
||||
UniverInstanceType,
|
||||
UserManagerService,
|
||||
} from '@univerjs/core';
|
||||
import {
|
||||
AddCommentMutation,
|
||||
getDT,
|
||||
|
||||
IThreadCommentDataSourceService,
|
||||
normalizeThreadCommentContent,
|
||||
|
||||
} from '@univerjs/thread-comment';
|
||||
import { DEFAULT_DOC_SUBUNIT_ID } from '../../common/const';
|
||||
|
||||
export interface IAddDocTextRangeCommentParams {
|
||||
unitId: string;
|
||||
comment: IThreadComment;
|
||||
range: ITextRangeParam;
|
||||
}
|
||||
|
||||
/** Parameters accepted by the document text-range Facade. */
|
||||
export interface ICreateDocTextRangeCommentParams {
|
||||
unitId: string;
|
||||
range: ITextRangeParam;
|
||||
content: ThreadCommentContent;
|
||||
attachments?: string[];
|
||||
id?: string;
|
||||
threadId?: string;
|
||||
personId?: string;
|
||||
dateTime?: Date;
|
||||
}
|
||||
|
||||
interface IValidTextRange extends ITextRangeParam {
|
||||
startOffset: number;
|
||||
endOffset: number;
|
||||
}
|
||||
|
||||
export interface IPreparedDocTextRangeComment {
|
||||
comment: IThreadComment;
|
||||
commentMutation: IMutationInfo;
|
||||
decorationMutationParams: IDocCommentDecorationMutationParams;
|
||||
}
|
||||
|
||||
export interface IDocCommentDecorationMutationParams {
|
||||
unitId: string;
|
||||
actions: JSONXActions;
|
||||
segmentId?: string;
|
||||
}
|
||||
|
||||
/** Applies document comment decoration actions without render or selection services. */
|
||||
export const AddDocCommentDecorationMutation: IMutation<
|
||||
IDocCommentDecorationMutationParams,
|
||||
IDocCommentDecorationMutationParams | false
|
||||
> = {
|
||||
id: 'docs-thread-comment.mutation.add-decoration',
|
||||
type: CommandType.MUTATION,
|
||||
handler(accessor, params) {
|
||||
if (!params || JSONX.isNoop(params.actions)) {
|
||||
return false;
|
||||
}
|
||||
const document = accessor.get(IUniverInstanceService)
|
||||
.getUnit<DocumentDataModel>(params.unitId, UniverInstanceType.UNIVER_DOC);
|
||||
if (!document) {
|
||||
return false;
|
||||
}
|
||||
const undoActions = JSONX.invertWithDoc(params.actions, document.getSnapshot());
|
||||
document.apply(params.actions);
|
||||
return { ...params, actions: undoActions };
|
||||
},
|
||||
};
|
||||
|
||||
function getValidRange(accessor: IAccessor, unitId: string, range: ITextRangeParam): IValidTextRange | null {
|
||||
const { startOffset, endOffset } = range;
|
||||
if (!Number.isInteger(startOffset) || !Number.isInteger(endOffset) || startOffset < 0 || endOffset <= startOffset) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const document = accessor.get(IUniverInstanceService).getUnit<DocumentDataModel>(unitId, UniverInstanceType.UNIVER_DOC);
|
||||
const body = document?.getSelfOrHeaderFooterModel(range.segmentId)?.getBody();
|
||||
if (!body || endOffset > body.dataStream.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return { ...range, startOffset, endOffset, collapsed: false };
|
||||
}
|
||||
|
||||
/** Builds the model mutations shared by headless and UI document comment commands. */
|
||||
export async function prepareDocTextRangeComment(
|
||||
accessor: IAccessor,
|
||||
params: IAddDocTextRangeCommentParams
|
||||
): Promise<IPreparedDocTextRangeComment | null> {
|
||||
const range = getValidRange(accessor, params.unitId, params.range);
|
||||
if (!range) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const savedComment = await accessor.get(IThreadCommentDataSourceService).addComment(params.comment);
|
||||
const comment: IThreadComment = {
|
||||
...params.comment,
|
||||
...savedComment,
|
||||
unitId: params.unitId,
|
||||
subUnitId: DEFAULT_DOC_SUBUNIT_ID,
|
||||
startOffset: range.startOffset,
|
||||
endOffset: range.endOffset,
|
||||
segmentId: range.segmentId,
|
||||
collapsed: false,
|
||||
};
|
||||
const textX = BuildTextUtils.customDecoration.add({
|
||||
ranges: [range],
|
||||
id: comment.id,
|
||||
type: CustomDecorationType.COMMENT,
|
||||
});
|
||||
const decorationMutationParams: IDocCommentDecorationMutationParams = {
|
||||
unitId: params.unitId,
|
||||
actions: JSONX.getInstance().editOp(textX.serialize()),
|
||||
segmentId: range.segmentId,
|
||||
};
|
||||
|
||||
return {
|
||||
comment,
|
||||
commentMutation: {
|
||||
id: AddCommentMutation.id,
|
||||
params: { unitId: params.unitId, subUnitId: DEFAULT_DOC_SUBUNIT_ID, comment },
|
||||
},
|
||||
decorationMutationParams,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a root comment and document decoration for an explicit text range.
|
||||
* This model command is safe to execute without loading document UI packages.
|
||||
*/
|
||||
export const CreateDocTextRangeCommentCommand: ICommand<ICreateDocTextRangeCommentParams> = {
|
||||
id: 'docs.command.create-text-range-comment',
|
||||
type: CommandType.COMMAND,
|
||||
async handler(accessor, params) {
|
||||
if (!params) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const range = getValidRange(accessor, params.unitId, params.range);
|
||||
if (!range) {
|
||||
return false;
|
||||
}
|
||||
const document = accessor.get(IUniverInstanceService).getUnit<DocumentDataModel>(params.unitId, UniverInstanceType.UNIVER_DOC);
|
||||
const body = document?.getSelfOrHeaderFooterModel(range.segmentId)?.getBody();
|
||||
if (!body) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const id = params.id ?? generateRandomId();
|
||||
const comment: IThreadComment = {
|
||||
id,
|
||||
threadId: params.threadId ?? id,
|
||||
unitId: params.unitId,
|
||||
subUnitId: DEFAULT_DOC_SUBUNIT_ID,
|
||||
ref: BuildTextUtils.transform.getPlainText(body.dataStream.slice(range.startOffset, range.endOffset)),
|
||||
text: normalizeThreadCommentContent(params.content),
|
||||
attachments: params.attachments ?? [],
|
||||
dT: getDT(params.dateTime),
|
||||
personId: params.personId ?? accessor.get(UserManagerService).getCurrentUser().userID,
|
||||
startOffset: range.startOffset,
|
||||
endOffset: range.endOffset,
|
||||
segmentId: range.segmentId,
|
||||
collapsed: false,
|
||||
};
|
||||
const prepared = await prepareDocTextRangeComment(accessor, { unitId: params.unitId, range, comment });
|
||||
if (!prepared) {
|
||||
return false;
|
||||
}
|
||||
return (await sequenceExecute([
|
||||
prepared.commentMutation,
|
||||
{ id: AddDocCommentDecorationMutation.id, params: prepared.decorationMutationParams },
|
||||
], accessor.get(ICommandService))).result;
|
||||
},
|
||||
};
|
||||
20
packages/docs-thread-comment/src/common/const.ts
Normal file
20
packages/docs-thread-comment/src/common/const.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.
|
||||
*/
|
||||
|
||||
/** Stable subunit used by document text comments. */
|
||||
export const DEFAULT_DOC_SUBUNIT_ID = 'default_doc';
|
||||
|
||||
export const DOCS_THREAD_COMMENT_PLUGIN_NAME = 'DOC_THREAD_COMMENT_PLUGIN';
|
||||
24
packages/docs-thread-comment/src/config/config.ts
Normal file
24
packages/docs-thread-comment/src/config/config.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* 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 DOCS_THREAD_COMMENT_PLUGIN_CONFIG_KEY = 'docs-thread-comment.config';
|
||||
|
||||
export const configSymbol = Symbol(DOCS_THREAD_COMMENT_PLUGIN_CONFIG_KEY);
|
||||
|
||||
export interface IUniverDocsThreadCommentConfig {
|
||||
}
|
||||
|
||||
export const defaultPluginConfig: IUniverDocsThreadCommentConfig = {};
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Disposable, Inject, IResourceManagerService, UniverInstanceType } from '@univerjs/core';
|
||||
import {
|
||||
createThreadCommentResourceHook,
|
||||
IThreadCommentDataSourceService,
|
||||
TC_PLUGIN_NAME,
|
||||
ThreadCommentModel,
|
||||
} from '@univerjs/thread-comment';
|
||||
|
||||
export const DOC_UNIVER_THREAD_COMMENT_PLUGIN = `DOC_${TC_PLUGIN_NAME}`;
|
||||
|
||||
export class DocsThreadCommentResourceController extends Disposable {
|
||||
constructor(
|
||||
@IResourceManagerService private readonly _resourceManagerService: IResourceManagerService,
|
||||
@Inject(ThreadCommentModel) private readonly _threadCommentModel: ThreadCommentModel,
|
||||
@IThreadCommentDataSourceService private readonly _threadCommentDataSourceService: IThreadCommentDataSourceService
|
||||
) {
|
||||
super();
|
||||
this.disposeWithMe(this._resourceManagerService.registerPluginResource(createThreadCommentResourceHook(
|
||||
this._threadCommentModel,
|
||||
this._threadCommentDataSourceService,
|
||||
DOC_UNIVER_THREAD_COMMENT_PLUGIN,
|
||||
[UniverInstanceType.UNIVER_DOC]
|
||||
)));
|
||||
}
|
||||
}
|
||||
130
packages/docs-thread-comment/src/facade/f-document-text-range.ts
Normal file
130
packages/docs-thread-comment/src/facade/f-document-text-range.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Injector } from '@univerjs/core';
|
||||
import { CustomDecorationType, ICommandService } from '@univerjs/core';
|
||||
import * as DocsThreadComment from '@univerjs/docs-thread-comment';
|
||||
import * as DocsFacade from '@univerjs/docs/facade';
|
||||
import * as ThreadComment from '@univerjs/thread-comment';
|
||||
|
||||
export type IDocumentTextRangeCommentCreateOptions = Omit<
|
||||
DocsThreadComment.ICreateDocTextRangeCommentParams,
|
||||
'unitId' | 'range' | 'content'
|
||||
>;
|
||||
|
||||
/** Comment methods added to a fixed document text range. */
|
||||
export interface IFDocumentTextRangeThreadCommentMixin {
|
||||
/**
|
||||
* Creates a comment on this text range.
|
||||
* @param content Plain text or a Univer document body for rich comment content.
|
||||
* @param [options] Optional stable IDs, author, attachments, and creation time.
|
||||
* @returns {Promise<boolean>} `true` when the create command succeeds; otherwise, `false`.
|
||||
* @throws {TypeError} If the content is empty.
|
||||
* @example
|
||||
* ```ts
|
||||
* const range = univerAPI.getActiveDocument()?.getTextRange(0, 12);
|
||||
* await range?.createCommentAsync('Verify this introduction.', { id: 'review-intro' });
|
||||
* ```
|
||||
*/
|
||||
createCommentAsync(
|
||||
content: ThreadComment.ThreadCommentContent,
|
||||
options?: IDocumentTextRangeCommentCreateOptions
|
||||
): Promise<boolean>;
|
||||
/**
|
||||
* Returns locally loaded comments whose comment decorations overlap this text range.
|
||||
* @returns Matching comment threads. The returned anchors use `DOC_TEXT_RANGE`.
|
||||
* @example
|
||||
* ```ts
|
||||
* const range = univerAPI.getActiveDocument()?.getTextRange(0, 12);
|
||||
* const comments = range?.getComments() ?? [];
|
||||
* comments.forEach(({ root, children }) => console.log(root.id, children.length));
|
||||
* ```
|
||||
*/
|
||||
getComments(): ThreadComment.IFacadeThreadCommentInfo[];
|
||||
/**
|
||||
* Synchronizes known document threads and returns comments whose decorations overlap this text range.
|
||||
* @returns {Promise<ThreadComment.IFacadeThreadCommentInfo[]>} A promise resolving to the synchronized matching comment threads.
|
||||
* @example
|
||||
* ```ts
|
||||
* const range = univerAPI.getActiveDocument()?.getTextRange(0, 12);
|
||||
* const comments = range ? await range.listCommentsAsync() : [];
|
||||
* console.log(comments.length);
|
||||
* ```
|
||||
*/
|
||||
listCommentsAsync(): Promise<ThreadComment.IFacadeThreadCommentInfo[]>;
|
||||
}
|
||||
|
||||
export class FDocumentTextRangeThreadCommentMixin extends DocsFacade.FDocumentTextRange implements IFDocumentTextRangeThreadCommentMixin {
|
||||
declare private _threadCommentCommandService: ICommandService;
|
||||
declare private _threadCommentFacadeService: ThreadComment.ThreadCommentFacadeService;
|
||||
|
||||
override _initialize(injector: Injector): void {
|
||||
this._threadCommentCommandService = injector.get(ICommandService);
|
||||
let commentService: ThreadComment.ThreadCommentFacadeService | undefined;
|
||||
Object.defineProperty(this, '_threadCommentFacadeService', {
|
||||
get: () => commentService ??= injector.get(ThreadComment.ThreadCommentFacadeService),
|
||||
});
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
override createCommentAsync(
|
||||
content: ThreadComment.ThreadCommentContent,
|
||||
options: IDocumentTextRangeCommentCreateOptions = {}
|
||||
): Promise<boolean> {
|
||||
return this._threadCommentCommandService.executeCommand(DocsThreadComment.CreateDocTextRangeCommentCommand.id, {
|
||||
...options,
|
||||
unitId: this._document.getId(),
|
||||
range: { ...this.getRange(), collapsed: false },
|
||||
content,
|
||||
});
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
override getComments(): ThreadComment.IFacadeThreadCommentInfo[] {
|
||||
const range = this.getRange();
|
||||
const commentIds = this._getOverlappingCommentIds(range);
|
||||
return this._threadCommentFacadeService.getComments({
|
||||
unitIds: [this._document.getId()],
|
||||
subUnitIds: [DocsThreadComment.DEFAULT_DOC_SUBUNIT_ID],
|
||||
anchorKinds: [ThreadComment.ThreadCommentAnchorKind.DOC_TEXT_RANGE],
|
||||
}).filter((comment) => commentIds.has(comment.root.id));
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
override async listCommentsAsync(): Promise<ThreadComment.IFacadeThreadCommentInfo[]> {
|
||||
const range = this.getRange();
|
||||
const commentIds = this._getOverlappingCommentIds(range);
|
||||
const comments = await this._threadCommentFacadeService.listCommentsAsync({
|
||||
unitIds: [this._document.getId()],
|
||||
subUnitIds: [DocsThreadComment.DEFAULT_DOC_SUBUNIT_ID],
|
||||
anchorKinds: [ThreadComment.ThreadCommentAnchorKind.DOC_TEXT_RANGE],
|
||||
});
|
||||
return comments.filter((comment) => commentIds.has(comment.root.id));
|
||||
}
|
||||
|
||||
private _getOverlappingCommentIds(range: DocsFacade.IFDocumentTextRange): Set<string> {
|
||||
const decorations = this._document.getBody(range.segmentId).customDecorations ?? [];
|
||||
return new Set(decorations.filter((decoration) => decoration.type === CustomDecorationType.COMMENT
|
||||
&& decoration.startIndex < range.endOffset
|
||||
&& decoration.endIndex >= range.startOffset).map((decoration) => decoration.id));
|
||||
}
|
||||
}
|
||||
|
||||
DocsFacade.FDocumentTextRange.extend(FDocumentTextRangeThreadCommentMixin);
|
||||
|
||||
declare module '@univerjs/docs/facade' {
|
||||
interface FDocumentTextRange extends IFDocumentTextRangeThreadCommentMixin {}
|
||||
}
|
||||
22
packages/docs-thread-comment/src/facade/index.ts
Normal file
22
packages/docs-thread-comment/src/facade/index.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import './f-document-text-range';
|
||||
|
||||
export type {
|
||||
IDocumentTextRangeCommentCreateOptions,
|
||||
IFDocumentTextRangeThreadCommentMixin,
|
||||
} from './f-document-text-range';
|
||||
30
packages/docs-thread-comment/src/index.ts
Normal file
30
packages/docs-thread-comment/src/index.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export {
|
||||
AddDocCommentDecorationMutation,
|
||||
CreateDocTextRangeCommentCommand,
|
||||
prepareDocTextRangeComment,
|
||||
} from './commands/commands/create-doc-text-range-comment.command';
|
||||
export type {
|
||||
IAddDocTextRangeCommentParams,
|
||||
ICreateDocTextRangeCommentParams,
|
||||
IDocCommentDecorationMutationParams,
|
||||
IPreparedDocTextRangeComment,
|
||||
} from './commands/commands/create-doc-text-range-comment.command';
|
||||
export { DEFAULT_DOC_SUBUNIT_ID } from './common/const';
|
||||
export type { IUniverDocsThreadCommentConfig } from './config/config';
|
||||
export { UniverDocsThreadCommentPlugin } from './plugin';
|
||||
68
packages/docs-thread-comment/src/plugin.ts
Normal file
68
packages/docs-thread-comment/src/plugin.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* 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 { IUniverDocsThreadCommentConfig } from './config/config';
|
||||
import {
|
||||
DependentOn,
|
||||
ICommandService,
|
||||
IConfigService,
|
||||
Inject,
|
||||
Injector,
|
||||
merge,
|
||||
Plugin,
|
||||
UniverInstanceType,
|
||||
} from '@univerjs/core';
|
||||
import { UniverDocsPlugin } from '@univerjs/docs';
|
||||
import { UniverThreadCommentPlugin } from '@univerjs/thread-comment';
|
||||
import pkg from '../package.json';
|
||||
import {
|
||||
AddDocCommentDecorationMutation,
|
||||
CreateDocTextRangeCommentCommand,
|
||||
} from './commands/commands/create-doc-text-range-comment.command';
|
||||
import { DOCS_THREAD_COMMENT_PLUGIN_NAME } from './common/const';
|
||||
import { defaultPluginConfig, DOCS_THREAD_COMMENT_PLUGIN_CONFIG_KEY } from './config/config';
|
||||
import { DocsThreadCommentResourceController } from './controllers/docs-thread-comment-resource.controller';
|
||||
|
||||
@DependentOn(UniverDocsPlugin, UniverThreadCommentPlugin)
|
||||
export class UniverDocsThreadCommentPlugin extends Plugin {
|
||||
static override pluginName = DOCS_THREAD_COMMENT_PLUGIN_NAME;
|
||||
static override packageName = pkg.name;
|
||||
static override version = pkg.version;
|
||||
static override type = UniverInstanceType.UNIVER_DOC;
|
||||
|
||||
constructor(
|
||||
private readonly _config: Partial<IUniverDocsThreadCommentConfig> = defaultPluginConfig,
|
||||
@Inject(Injector) protected override _injector: Injector,
|
||||
@IConfigService private readonly _configService: IConfigService,
|
||||
@ICommandService private readonly _commandService: ICommandService
|
||||
) {
|
||||
super();
|
||||
|
||||
const { ...rest } = merge(
|
||||
{},
|
||||
defaultPluginConfig,
|
||||
this._config
|
||||
);
|
||||
this._configService.setConfig(DOCS_THREAD_COMMENT_PLUGIN_CONFIG_KEY, rest);
|
||||
}
|
||||
|
||||
override onStarting(): void {
|
||||
this._injector.add([DocsThreadCommentResourceController]);
|
||||
this._injector.get(DocsThreadCommentResourceController);
|
||||
this.disposeWithMe(this._commandService.registerCommand(CreateDocTextRangeCommentCommand));
|
||||
this.disposeWithMe(this._commandService.registerCommand(AddDocCommentDecorationMutation));
|
||||
}
|
||||
}
|
||||
8
packages/docs-thread-comment/tsconfig.json
Normal file
8
packages/docs-thread-comment/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "@univerjs-infra/shared/tsconfigs/base",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "lib/types"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
18
packages/docs-thread-comment/tsconfig.node.json
Normal file
18
packages/docs-thread-comment/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/docs-thread-comment/vitest.config.ts
Normal file
3
packages/docs-thread-comment/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