feat(editor): can highlight resolved comment (#13122)

#### PR Dependency Tree


* **PR #13122** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## 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.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
L-Sun
2025-07-10 11:06:05 +08:00
committed by GitHub
parent 0f3066f7d0
commit 1fe07410c0
4 changed files with 60 additions and 21 deletions
@@ -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,
@@ -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() {
@@ -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`<inline-comment .commentIds=${extractCommentIdFromDelta(delta)}
renderer: ({ delta, children }) => {
if (!delta.attributes) return html`${nothing}`;
const unresolved = Object.entries(delta.attributes).some(
([key, value]) => isInlineCommendId(key) && value === true
);
return html`<inline-comment
.unresolved=${unresolved}
.commentIds=${extractCommentIdFromDelta(delta)}
>${when(
children,
() => html`${children}`,
() => nothing
)}</inline-comment
>`,
>`;
},
wrapper: true,
});
@@ -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;
}
});
});
};