diff --git a/blocksuite/affine/inlines/comment/src/inline-comment-manager.ts b/blocksuite/affine/inlines/comment/src/inline-comment-manager.ts index b171799aa..6568b42fa 100644 --- a/blocksuite/affine/inlines/comment/src/inline-comment-manager.ts +++ b/blocksuite/affine/inlines/comment/src/inline-comment-manager.ts @@ -38,7 +38,7 @@ export class InlineCommentManager extends LifeCycleWatcher { const provider = this._provider; if (!provider) return; - this._init(); + this._init().catch(console.error); this._disposables.add(provider.onCommentAdded(this._handleAddComment)); this._disposables.add( @@ -59,20 +59,12 @@ export class InlineCommentManager extends LifeCycleWatcher { this._disposables.dispose(); } - private _init() { + private async _init() { const provider = this._provider; if (!provider) return; - const commentsInProvider = provider.getComments(); - const inlineComments = findAllCommentedTexts(this.std).flatMap( - ([selection, inlineEditor]) => { - const deltas = inlineEditor.getDeltasByInlineRange({ - index: selection.from.index, - length: selection.from.length, - }); - return deltas.flatMap(([delta]) => extractCommentIdFromDelta(delta)); - } - ); + const commentsInProvider = await provider.getComments('unresolved'); + const inlineComments = [...findAllCommentedTexts(this.std.store).values()]; const blockComments = findAllCommentedBlocks(this.std.store).flatMap( block => Object.keys(block.props.comments) @@ -165,12 +157,17 @@ export class InlineCommentManager extends LifeCycleWatcher { }; private readonly _handleDeleteAndResolve = (id: CommentId) => { - const commentedTexts = findCommentedTexts(this.std, id); + const commentedTexts = findCommentedTexts(this.std.store, id); if (commentedTexts.length === 0) return; this.std.store.withoutTransact(() => { - commentedTexts.forEach(([selection, inlineEditor]) => { - inlineEditor.formatText( + commentedTexts.forEach(selection => { + const inlineEditor = getInlineEditorByModel( + this.std, + selection.from.blockId + ); + + inlineEditor?.formatText( selection.from, { [`comment-${id}`]: null, diff --git a/blocksuite/affine/inlines/comment/src/utils.ts b/blocksuite/affine/inlines/comment/src/utils.ts index 2954bac7f..653c2bfd9 100644 --- a/blocksuite/affine/inlines/comment/src/utils.ts +++ b/blocksuite/affine/inlines/comment/src/utils.ts @@ -1,55 +1,56 @@ -import { getInlineEditorByModel } from '@blocksuite/affine-rich-text'; import type { CommentId } from '@blocksuite/affine-shared/services'; import type { AffineTextAttributes } from '@blocksuite/affine-shared/types'; -import { type BlockStdScope, TextSelection } from '@blocksuite/std'; -import type { InlineEditor } from '@blocksuite/std/inline'; -import type { DeltaInsert } from '@blocksuite/store'; +import { TextSelection } from '@blocksuite/std'; +import type { DeltaInsert, Store } from '@blocksuite/store'; -export function findAllCommentedTexts(std: BlockStdScope) { - const selections: [TextSelection, InlineEditor][] = []; - std.store.getAllModels().forEach(model => { - const inlineEditor = getInlineEditorByModel(std, model); - if (!inlineEditor) return; +export function findAllCommentedTexts( + store: Store +): Map { + const result = new Map(); - inlineEditor.mapDeltasInInlineRange( - { - index: 0, - length: inlineEditor.yTextLength, - }, - (delta, rangeIndex) => { - if ( - delta.attributes && - Object.keys(delta.attributes).some(key => key.startsWith('comment-')) - ) { - selections.push([ - new TextSelection({ - from: { - blockId: model.id, - index: rangeIndex, - length: delta.insert.length, - }, - to: null, - }), - inlineEditor, - ]); - } + store.getAllModels().forEach(model => { + if (!model.text) return; + + let index = 0; + model.text.toDelta().forEach(delta => { + if (!delta.insert) return; + + const length = delta.insert.length; + + if (!delta.attributes) { + index += length; + return; } - ); + + Object.keys(delta.attributes) + .filter(key => key.startsWith('comment-')) + .forEach(key => { + const commentId = key.replace('comment-', ''); + const selection = new TextSelection({ + from: { + blockId: model.id, + index, + length, + }, + to: null, + }); + result.set(selection, commentId); + }); + + index += length; + }); }); - return selections; + return result; } -export function findCommentedTexts(std: BlockStdScope, commentId: CommentId) { - return findAllCommentedTexts(std).filter(([selection, inlineEditor]) => { - const deltas = inlineEditor.getDeltasByInlineRange({ - index: selection.from.index, - length: selection.from.length, - }); - return deltas - .flatMap(([delta]) => extractCommentIdFromDelta(delta)) - .includes(commentId); - }); +export function findCommentedTexts( + store: Store, + commentId: CommentId +): TextSelection[] { + return [...findAllCommentedTexts(store).entries()] + .filter(([_, id]) => id === commentId) + .map(([selection]) => selection); } export function extractCommentIdFromDelta( diff --git a/blocksuite/affine/shared/src/services/comment-service/comment-provider.ts b/blocksuite/affine/shared/src/services/comment-service/comment-provider.ts index de9106f8a..dd8199a99 100644 --- a/blocksuite/affine/shared/src/services/comment-service/comment-provider.ts +++ b/blocksuite/affine/shared/src/services/comment-service/comment-provider.ts @@ -15,7 +15,10 @@ export interface CommentProvider { addComment: (selections: BaseSelection[]) => void; resolveComment: (id: CommentId) => void; highlightComment: (id: CommentId | null) => void; - getComments: () => CommentId[]; + + getComments: ( + type: 'resolved' | 'unresolved' | 'all' + ) => Promise | CommentId[]; onCommentAdded: ( callback: (id: CommentId, selections: BaseSelection[]) => void diff --git a/blocksuite/playground/apps/_common/mock-services.ts b/blocksuite/playground/apps/_common/mock-services.ts index d2b08322e..e7785c7b1 100644 --- a/blocksuite/playground/apps/_common/mock-services.ts +++ b/blocksuite/playground/apps/_common/mock-services.ts @@ -244,8 +244,14 @@ export function mockCommentProvider() { this.commentHighlightSubject.next(id); } - getComments() { - return Array.from(this.comments.keys()); + getComments(type: 'resolved' | 'unresolved' | 'all' = 'all') { + return Array.from(this.comments.entries()) + .filter(([_, comment]) => { + if (type === 'all') return true; + if (type === 'resolved') return comment.resolved; + return !comment.resolved; + }) + .map(([id]) => id); } onCommentAdded( diff --git a/packages/frontend/core/src/blocksuite/view-extensions/comment/comment-provider.ts b/packages/frontend/core/src/blocksuite/view-extensions/comment/comment-provider.ts index a1d0152f1..c0f1c10d3 100644 --- a/packages/frontend/core/src/blocksuite/view-extensions/comment/comment-provider.ts +++ b/packages/frontend/core/src/blocksuite/view-extensions/comment/comment-provider.ts @@ -138,8 +138,10 @@ class AffineCommentService implements CommentProvider { this.commentEntity.highlightComment(id); } - getComments(): string[] { - return this.commentEntity.getComments(); + async getComments( + type: 'resolved' | 'unresolved' | 'all' = 'all' + ): Promise { + return this.commentEntity.getComments(type); } onCommentAdded(callback: (id: string, selections: BaseSelection[]) => void) { diff --git a/packages/frontend/core/src/modules/comment/entities/doc-comment.ts b/packages/frontend/core/src/modules/comment/entities/doc-comment.ts index 6df0a456f..c60440696 100644 --- a/packages/frontend/core/src/modules/comment/entities/doc-comment.ts +++ b/packages/frontend/core/src/modules/comment/entities/doc-comment.ts @@ -14,7 +14,16 @@ import { onStart, } from '@toeverything/infra'; import { nanoid } from 'nanoid'; -import { catchError, of, Subject, switchMap, tap, timer } from 'rxjs'; +import { + catchError, + filter, + first, + of, + Subject, + switchMap, + tap, + timer, +} from 'rxjs'; import { type DocDisplayMetaService } from '../../doc-display-meta'; import { GlobalContextService } from '../../global-context'; @@ -280,8 +289,30 @@ export class DocCommentEntity extends Entity<{ this.commentHighlighted$.next(id); } - getComments(): CommentId[] { - return this.comments$.value.map(comment => comment.id); + async getComments( + type: 'resolved' | 'unresolved' | 'all' = 'all' + ): Promise { + return new Promise(resolve => { + this.revalidate(); + this.loading$ + .pipe( + filter(loading => !loading), + first() + ) + .subscribe(() => { + resolve( + this.comments$.value + .filter(comment => + type === 'all' + ? true + : type === 'resolved' + ? comment.resolved + : !comment.resolved + ) + .map(comment => comment.id) + ); + }); + }); } onCommentAdded( diff --git a/packages/frontend/core/src/modules/editor/entities/editor.ts b/packages/frontend/core/src/modules/editor/entities/editor.ts index e2761498c..cd0f7856a 100644 --- a/packages/frontend/core/src/modules/editor/entities/editor.ts +++ b/packages/frontend/core/src/modules/editor/entities/editor.ts @@ -214,9 +214,12 @@ export class Editor extends Entity { const std = editorContainer.host.std; // First try to find inline commented texts - const inlineCommentedSelections = findCommentedTexts(std, commentId); + const inlineCommentedSelections = findCommentedTexts( + std.store, + commentId + ); if (inlineCommentedSelections.length > 0) { - const firstSelection = inlineCommentedSelections[0][0]; + const firstSelection = inlineCommentedSelections[0]; finalId = firstSelection.from.blockId; finalKey = 'blockIds'; } else {