From 1fe07410c0583e40cb55c339dbda349827257d96 Mon Sep 17 00:00:00 2001 From: L-Sun Date: Thu, 10 Jul 2025 11:06:05 +0800 Subject: [PATCH] feat(editor): can highlight resolved comment (#13122) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #### PR Dependency Tree * **PR #13122** 👈 This tree was auto-generated by [Charcoal](https://github.com/danerwilliams/charcoal) ## Summary by CodeRabbit * **New Features** * Inline comments now visually distinguish between unresolved, resolved, and deleted states. * Only unresolved inline comments are interactive and highlighted in the editor. * **Bug Fixes** * Improved accuracy in fetching and displaying all comments, including resolved ones, during initialization. * **Refactor** * Enhanced handling of comment resolution and deletion to provide clearer differentiation in both behavior and appearance. --- .../comment/src/inline-comment-manager.ts | 22 ++++++++++++------ .../inlines/comment/src/inline-comment.ts | 18 ++++++++++++--- .../affine/inlines/comment/src/inline-spec.ts | 18 ++++++++++----- .../block-element-comment-manager.ts | 23 +++++++++++++++---- 4 files changed, 60 insertions(+), 21 deletions(-) diff --git a/blocksuite/affine/inlines/comment/src/inline-comment-manager.ts b/blocksuite/affine/inlines/comment/src/inline-comment-manager.ts index cc753685f9..f3ed63d5ed 100644 --- a/blocksuite/affine/inlines/comment/src/inline-comment-manager.ts +++ b/blocksuite/affine/inlines/comment/src/inline-comment-manager.ts @@ -43,10 +43,14 @@ export class InlineCommentManager extends LifeCycleWatcher { this._disposables.add(provider.onCommentAdded(this._handleAddComment)); this._disposables.add( - provider.onCommentDeleted(this._handleDeleteAndResolve) + provider.onCommentDeleted(id => + this._handleDeleteAndResolve(id, 'delete') + ) ); this._disposables.add( - provider.onCommentResolved(this._handleDeleteAndResolve) + provider.onCommentResolved(id => + this._handleDeleteAndResolve(id, 'resolve') + ) ); this._disposables.add( provider.onCommentHighlighted(this._handleHighlightComment) @@ -64,15 +68,16 @@ export class InlineCommentManager extends LifeCycleWatcher { const provider = this._provider; if (!provider) return; - const commentsInProvider = await provider.getComments('unresolved'); + const commentsInProvider = await provider.getComments('all'); const commentsInEditor = this.getCommentsInEditor(); // remove comments that are in editor but not in provider // which means the comment may be removed or resolved in provider side difference(commentsInEditor, commentsInProvider).forEach(comment => { - this._handleDeleteAndResolve(comment); - this.std.get(BlockElementCommentManager).handleDeleteAndResolve(comment); + this.std + .get(BlockElementCommentManager) + .handleDeleteAndResolve(comment, 'delete'); }); } @@ -162,7 +167,10 @@ export class InlineCommentManager extends LifeCycleWatcher { }); }; - private readonly _handleDeleteAndResolve = (id: CommentId) => { + private readonly _handleDeleteAndResolve = ( + id: CommentId, + type: 'delete' | 'resolve' + ) => { const commentedTexts = findCommentedTexts(this.std.store, id); if (commentedTexts.length === 0) return; @@ -176,7 +184,7 @@ export class InlineCommentManager extends LifeCycleWatcher { inlineEditor?.formatText( selection.from, { - [`comment-${id}`]: null, + [`comment-${id}`]: type === 'delete' ? null : false, }, { withoutTransact: true, diff --git a/blocksuite/affine/inlines/comment/src/inline-comment.ts b/blocksuite/affine/inlines/comment/src/inline-comment.ts index 423dfc211d..37dde139f8 100644 --- a/blocksuite/affine/inlines/comment/src/inline-comment.ts +++ b/blocksuite/affine/inlines/comment/src/inline-comment.ts @@ -22,7 +22,7 @@ import { isEqual } from 'lodash-es'; }) export class InlineComment extends WithDisposable(ShadowlessElement) { static override styles = css` - inline-comment { + inline-comment.unresolved { display: inline-block; background-color: ${unsafeCSSVarV2('block/comment/highlightDefault')}; border-bottom: 2px solid @@ -41,6 +41,9 @@ export class InlineComment extends WithDisposable(ShadowlessElement) { }) accessor commentIds!: string[]; + @property({ attribute: false }) + accessor unresolved = false; + private _index: number = 0; @consume({ context: stdContext }) @@ -54,8 +57,10 @@ export class InlineComment extends WithDisposable(ShadowlessElement) { } private readonly _handleClick = () => { - this._provider?.highlightComment(this.commentIds[this._index]); - this._index = (this._index + 1) % this.commentIds.length; + if (this.unresolved) { + this._provider?.highlightComment(this.commentIds[this._index]); + this._index = (this._index + 1) % this.commentIds.length; + } }; private readonly _handleHighlight = (id: CommentId | null) => { @@ -89,6 +94,13 @@ export class InlineComment extends WithDisposable(ShadowlessElement) { this.classList.remove('highlighted'); } } + if (_changedProperties.has('unresolved')) { + if (this.unresolved) { + this.classList.add('unresolved'); + } else { + this.classList.remove('unresolved'); + } + } } override render() { diff --git a/blocksuite/affine/inlines/comment/src/inline-spec.ts b/blocksuite/affine/inlines/comment/src/inline-spec.ts index be1928f23d..ffcc5f997e 100644 --- a/blocksuite/affine/inlines/comment/src/inline-spec.ts +++ b/blocksuite/affine/inlines/comment/src/inline-spec.ts @@ -21,19 +21,25 @@ export const CommentInlineSpecExtension = ), match: delta => { if (!delta.attributes) return false; - const comments = Object.entries(delta.attributes).filter( - ([key, value]) => isInlineCommendId(key) && value === true - ); + const comments = Object.keys(delta.attributes).filter(isInlineCommendId); return comments.length > 0; }, - renderer: ({ delta, children }) => - html` { + if (!delta.attributes) return html`${nothing}`; + + const unresolved = Object.entries(delta.attributes).some( + ([key, value]) => isInlineCommendId(key) && value === true + ); + return html`${when( children, () => html`${children}`, () => nothing )}`, + >`; + }, wrapper: true, }); diff --git a/blocksuite/affine/shared/src/services/comment-service/block-element-comment-manager.ts b/blocksuite/affine/shared/src/services/comment-service/block-element-comment-manager.ts index 88e268f6c2..ef7b164231 100644 --- a/blocksuite/affine/shared/src/services/comment-service/block-element-comment-manager.ts +++ b/blocksuite/affine/shared/src/services/comment-service/block-element-comment-manager.ts @@ -57,10 +57,12 @@ export class BlockElementCommentManager extends LifeCycleWatcher { this._disposables.add(provider.onCommentAdded(this._handleAddComment)); this._disposables.add( - provider.onCommentDeleted(this.handleDeleteAndResolve) + provider.onCommentDeleted(id => this.handleDeleteAndResolve(id, 'delete')) ); this._disposables.add( - provider.onCommentResolved(this.handleDeleteAndResolve) + provider.onCommentResolved(id => + this.handleDeleteAndResolve(id, 'resolve') + ) ); this._disposables.add( provider.onCommentHighlighted(this._handleHighlightComment) @@ -146,18 +148,29 @@ export class BlockElementCommentManager extends LifeCycleWatcher { } }; - readonly handleDeleteAndResolve = (id: CommentId) => { + readonly handleDeleteAndResolve = ( + id: CommentId, + type: 'delete' | 'resolve' + ) => { const commentedBlocks = findCommentedBlocks(this.std.store, id); this.std.store.withoutTransact(() => { commentedBlocks.forEach(block => { - delete block.props.comments[id]; + if (type === 'delete') { + delete block.props.comments[id]; + } else { + block.props.comments[id] = false; + } }); }); const commentedElements = findCommentedElements(this.std.store, id); this.std.store.withoutTransact(() => { commentedElements.forEach(element => { - delete element.comments[id]; + if (type === 'delete') { + delete element.comments[id]; + } else { + element.comments[id] = false; + } }); }); };