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:
@@ -43,10 +43,14 @@ export class InlineCommentManager extends LifeCycleWatcher {
|
|||||||
|
|
||||||
this._disposables.add(provider.onCommentAdded(this._handleAddComment));
|
this._disposables.add(provider.onCommentAdded(this._handleAddComment));
|
||||||
this._disposables.add(
|
this._disposables.add(
|
||||||
provider.onCommentDeleted(this._handleDeleteAndResolve)
|
provider.onCommentDeleted(id =>
|
||||||
|
this._handleDeleteAndResolve(id, 'delete')
|
||||||
|
)
|
||||||
);
|
);
|
||||||
this._disposables.add(
|
this._disposables.add(
|
||||||
provider.onCommentResolved(this._handleDeleteAndResolve)
|
provider.onCommentResolved(id =>
|
||||||
|
this._handleDeleteAndResolve(id, 'resolve')
|
||||||
|
)
|
||||||
);
|
);
|
||||||
this._disposables.add(
|
this._disposables.add(
|
||||||
provider.onCommentHighlighted(this._handleHighlightComment)
|
provider.onCommentHighlighted(this._handleHighlightComment)
|
||||||
@@ -64,15 +68,16 @@ export class InlineCommentManager extends LifeCycleWatcher {
|
|||||||
const provider = this._provider;
|
const provider = this._provider;
|
||||||
if (!provider) return;
|
if (!provider) return;
|
||||||
|
|
||||||
const commentsInProvider = await provider.getComments('unresolved');
|
const commentsInProvider = await provider.getComments('all');
|
||||||
|
|
||||||
const commentsInEditor = this.getCommentsInEditor();
|
const commentsInEditor = this.getCommentsInEditor();
|
||||||
|
|
||||||
// remove comments that are in editor but not in provider
|
// remove comments that are in editor but not in provider
|
||||||
// which means the comment may be removed or resolved in provider side
|
// which means the comment may be removed or resolved in provider side
|
||||||
difference(commentsInEditor, commentsInProvider).forEach(comment => {
|
difference(commentsInEditor, commentsInProvider).forEach(comment => {
|
||||||
this._handleDeleteAndResolve(comment);
|
this.std
|
||||||
this.std.get(BlockElementCommentManager).handleDeleteAndResolve(comment);
|
.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);
|
const commentedTexts = findCommentedTexts(this.std.store, id);
|
||||||
if (commentedTexts.length === 0) return;
|
if (commentedTexts.length === 0) return;
|
||||||
|
|
||||||
@@ -176,7 +184,7 @@ export class InlineCommentManager extends LifeCycleWatcher {
|
|||||||
inlineEditor?.formatText(
|
inlineEditor?.formatText(
|
||||||
selection.from,
|
selection.from,
|
||||||
{
|
{
|
||||||
[`comment-${id}`]: null,
|
[`comment-${id}`]: type === 'delete' ? null : false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
withoutTransact: true,
|
withoutTransact: true,
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import { isEqual } from 'lodash-es';
|
|||||||
})
|
})
|
||||||
export class InlineComment extends WithDisposable(ShadowlessElement) {
|
export class InlineComment extends WithDisposable(ShadowlessElement) {
|
||||||
static override styles = css`
|
static override styles = css`
|
||||||
inline-comment {
|
inline-comment.unresolved {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
background-color: ${unsafeCSSVarV2('block/comment/highlightDefault')};
|
background-color: ${unsafeCSSVarV2('block/comment/highlightDefault')};
|
||||||
border-bottom: 2px solid
|
border-bottom: 2px solid
|
||||||
@@ -41,6 +41,9 @@ export class InlineComment extends WithDisposable(ShadowlessElement) {
|
|||||||
})
|
})
|
||||||
accessor commentIds!: string[];
|
accessor commentIds!: string[];
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor unresolved = false;
|
||||||
|
|
||||||
private _index: number = 0;
|
private _index: number = 0;
|
||||||
|
|
||||||
@consume({ context: stdContext })
|
@consume({ context: stdContext })
|
||||||
@@ -54,8 +57,10 @@ export class InlineComment extends WithDisposable(ShadowlessElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private readonly _handleClick = () => {
|
private readonly _handleClick = () => {
|
||||||
this._provider?.highlightComment(this.commentIds[this._index]);
|
if (this.unresolved) {
|
||||||
this._index = (this._index + 1) % this.commentIds.length;
|
this._provider?.highlightComment(this.commentIds[this._index]);
|
||||||
|
this._index = (this._index + 1) % this.commentIds.length;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly _handleHighlight = (id: CommentId | null) => {
|
private readonly _handleHighlight = (id: CommentId | null) => {
|
||||||
@@ -89,6 +94,13 @@ export class InlineComment extends WithDisposable(ShadowlessElement) {
|
|||||||
this.classList.remove('highlighted');
|
this.classList.remove('highlighted');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (_changedProperties.has('unresolved')) {
|
||||||
|
if (this.unresolved) {
|
||||||
|
this.classList.add('unresolved');
|
||||||
|
} else {
|
||||||
|
this.classList.remove('unresolved');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override render() {
|
override render() {
|
||||||
|
|||||||
@@ -21,19 +21,25 @@ export const CommentInlineSpecExtension =
|
|||||||
),
|
),
|
||||||
match: delta => {
|
match: delta => {
|
||||||
if (!delta.attributes) return false;
|
if (!delta.attributes) return false;
|
||||||
const comments = Object.entries(delta.attributes).filter(
|
const comments = Object.keys(delta.attributes).filter(isInlineCommendId);
|
||||||
([key, value]) => isInlineCommendId(key) && value === true
|
|
||||||
);
|
|
||||||
return comments.length > 0;
|
return comments.length > 0;
|
||||||
},
|
},
|
||||||
renderer: ({ delta, children }) =>
|
renderer: ({ delta, children }) => {
|
||||||
html`<inline-comment .commentIds=${extractCommentIdFromDelta(delta)}
|
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(
|
>${when(
|
||||||
children,
|
children,
|
||||||
() => html`${children}`,
|
() => html`${children}`,
|
||||||
() => nothing
|
() => nothing
|
||||||
)}</inline-comment
|
)}</inline-comment
|
||||||
>`,
|
>`;
|
||||||
|
},
|
||||||
wrapper: true,
|
wrapper: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -57,10 +57,12 @@ export class BlockElementCommentManager extends LifeCycleWatcher {
|
|||||||
|
|
||||||
this._disposables.add(provider.onCommentAdded(this._handleAddComment));
|
this._disposables.add(provider.onCommentAdded(this._handleAddComment));
|
||||||
this._disposables.add(
|
this._disposables.add(
|
||||||
provider.onCommentDeleted(this.handleDeleteAndResolve)
|
provider.onCommentDeleted(id => this.handleDeleteAndResolve(id, 'delete'))
|
||||||
);
|
);
|
||||||
this._disposables.add(
|
this._disposables.add(
|
||||||
provider.onCommentResolved(this.handleDeleteAndResolve)
|
provider.onCommentResolved(id =>
|
||||||
|
this.handleDeleteAndResolve(id, 'resolve')
|
||||||
|
)
|
||||||
);
|
);
|
||||||
this._disposables.add(
|
this._disposables.add(
|
||||||
provider.onCommentHighlighted(this._handleHighlightComment)
|
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);
|
const commentedBlocks = findCommentedBlocks(this.std.store, id);
|
||||||
this.std.store.withoutTransact(() => {
|
this.std.store.withoutTransact(() => {
|
||||||
commentedBlocks.forEach(block => {
|
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);
|
const commentedElements = findCommentedElements(this.std.store, id);
|
||||||
this.std.store.withoutTransact(() => {
|
this.std.store.withoutTransact(() => {
|
||||||
commentedElements.forEach(element => {
|
commentedElements.forEach(element => {
|
||||||
delete element.comments[id];
|
if (type === 'delete') {
|
||||||
|
delete element.comments[id];
|
||||||
|
} else {
|
||||||
|
element.comments[id] = false;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user