/** * Copyright 2023-present DreamNum Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import type { Nullable } from '@univerjs/core'; import type { IBaseComment, IThreadComment } from '../types/interfaces/i-thread-comment'; import { createIdentifier, Disposable } from '@univerjs/core'; export type ThreadCommentJSON = { id: string; threadId: string; ref: string; } & Partial>; type Success = boolean; export interface IThreadCommentDataSource { /** * handler for add-comment, throw error means fail and stop the process. */ addComment: (comment: IThreadComment) => Promise; /** * handler for update-comment, throw error means fail and stop the process. */ updateComment: (comment: IThreadComment) => Promise; resolveComment: (comment: IThreadComment) => Promise; /** * handler for delete-comment, throw error means fail and stop the process. */ deleteComment: (unitId: string, subUnitId: string, threadId: string, commentId: string) => Promise; /** * Delete a complete thread. Legacy data sources can omit this method and * fall back to deleting the root comment through `deleteComment`. */ deleteThread?: (unitId: string, subUnitId: string, threadId: string) => Promise; /** * handler for batch-fetch-comment, throw error means fail and stop the process. */ listComments: (unitId: string, subUnitId: string, threadId: string[]) => Promise; saveCommentToSnapshot: (comment: IThreadComment) => ThreadCommentJSON; } export interface IThreadCommentDataSourceService { dataSource: Nullable; /** * should sync update mutations to collaboration-server */ syncUpdateMutationToColla: boolean; /** * handler for add-comment, throw error means fail and stop the process. */ addComment: (comment: IThreadComment) => Promise; /** * handler for update-comment, throw error means fail and stop the process. */ updateComment: (comment: IThreadComment) => Promise; /** * handler for resolve-comment, throw error means fail and stop the process. */ resolveComment: (comment: IThreadComment) => Promise; /** * handler for delete-comment, throw error means fail and stop the process. */ deleteComment: (unitId: string, subUnitId: string, threadId: string, commentId: string) => Promise; deleteThread?: (unitId: string, subUnitId: string, threadId: string) => Promise; saveToSnapshot: (unitComments: Record, unitId: string) => Record; getThreadComment: (unitId: string, subUnitId: string, threadId: string) => Promise>; listThreadComments: (unitId: string, subUnitId: string, threadId: string[]) => Promise; } /** * Preserve for import async comment system */ export class ThreadCommentDataSourceService extends Disposable implements IThreadCommentDataSourceService { private _dataSource: Nullable = null; private readonly _pendingAdds = new Map>(); syncUpdateMutationToColla = true; set dataSource(dataSource: Nullable) { this._dataSource = dataSource; } get dataSource() { return this._dataSource; } constructor() { super(); } async getThreadComment(unitId: string, subUnitId: string, threadId: string): Promise> { if (this._dataSource) { const comments = await this._dataSource.listComments(unitId, subUnitId, [threadId]); return comments[0]; } return null; } addComment(comment: IThreadComment): Promise { const key = `${comment.unitId}\0${comment.subUnitId}\0${comment.id}`; const pending = this._pendingAdds.get(key); if (pending) { return pending; } const request = (async () => { const savedComment = this._dataSource ? await this._dataSource.addComment(comment) : comment; return { ...savedComment, threadId: savedComment.threadId || savedComment.id }; })(); this._pendingAdds.set(key, request); request.finally(() => { if (this._pendingAdds.get(key) === request) { this._pendingAdds.delete(key); } }).catch(() => undefined); return request; } async updateComment(comment: IThreadComment) { if (this._dataSource) { return this._dataSource.updateComment(comment); } return true; } async resolveComment(comment: IThreadComment) { if (this._dataSource) { return this._dataSource.resolveComment(comment); } return true; } async deleteComment(unitId: string, subUnitId: string, threadId: string, commentId: string) { if (this._dataSource) { return this._dataSource.deleteComment(unitId, subUnitId, threadId, commentId); } return true; } async deleteThread(unitId: string, subUnitId: string, threadId: string) { if (this._dataSource?.deleteThread) { return this._dataSource.deleteThread(unitId, subUnitId, threadId); } return this.deleteComment(unitId, subUnitId, threadId, threadId); } async listThreadComments(unitId: string, subUnitId: string, threadIds: string[]) { if (this.dataSource) { return this.dataSource.listComments(unitId, subUnitId, threadIds); } return false; } saveToSnapshot(unitComments: Record, _unitId: string) { if (this._dataSource) { const map: Record = {}; Object.keys(unitComments).forEach((subUnitId) => { const comments = unitComments[subUnitId]; map[subUnitId] = comments.map(this.dataSource!.saveCommentToSnapshot); }); return map; } return unitComments; } } export const IThreadCommentDataSourceService = createIdentifier('univer.thread-comment.data-source-service');