feat(editor): resolve unassociated comments on init (#13008)
#### PR Dependency Tree * **PR #13008** 👈 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 * **Bug Fixes** * Improved synchronization between comment states in the editor and the comment provider to ensure consistency of inline and block comments. * **Refactor** * Separated and modularized utility functions for finding commented texts and blocks, enhancing maintainability and reusability. * **Style** * Updated styles to remove background color and border from inline comments within embedded note content. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -1,8 +1,10 @@
|
|||||||
import { getInlineEditorByModel } from '@blocksuite/affine-rich-text';
|
import { getInlineEditorByModel } from '@blocksuite/affine-rich-text';
|
||||||
import { getSelectedBlocksCommand } from '@blocksuite/affine-shared/commands';
|
import { getSelectedBlocksCommand } from '@blocksuite/affine-shared/commands';
|
||||||
import {
|
import {
|
||||||
|
BlockCommentManager,
|
||||||
type CommentId,
|
type CommentId,
|
||||||
CommentProviderIdentifier,
|
CommentProviderIdentifier,
|
||||||
|
findAllCommentedBlocks,
|
||||||
} from '@blocksuite/affine-shared/services';
|
} from '@blocksuite/affine-shared/services';
|
||||||
import type { AffineInlineEditor } from '@blocksuite/affine-shared/types';
|
import type { AffineInlineEditor } from '@blocksuite/affine-shared/types';
|
||||||
import { DisposableGroup } from '@blocksuite/global/disposable';
|
import { DisposableGroup } from '@blocksuite/global/disposable';
|
||||||
@@ -13,8 +15,13 @@ import {
|
|||||||
} from '@blocksuite/std';
|
} from '@blocksuite/std';
|
||||||
import type { BaseSelection, BlockModel } from '@blocksuite/store';
|
import type { BaseSelection, BlockModel } from '@blocksuite/store';
|
||||||
import { signal } from '@preact/signals-core';
|
import { signal } from '@preact/signals-core';
|
||||||
|
import difference from 'lodash-es/difference';
|
||||||
|
|
||||||
import { extractCommentIdFromDelta, findCommentedTexts } from './utils';
|
import {
|
||||||
|
extractCommentIdFromDelta,
|
||||||
|
findAllCommentedTexts,
|
||||||
|
findCommentedTexts,
|
||||||
|
} from './utils';
|
||||||
|
|
||||||
export class InlineCommentManager extends LifeCycleWatcher {
|
export class InlineCommentManager extends LifeCycleWatcher {
|
||||||
static override key = 'inline-comment-manager';
|
static override key = 'inline-comment-manager';
|
||||||
@@ -31,6 +38,8 @@ export class InlineCommentManager extends LifeCycleWatcher {
|
|||||||
const provider = this._provider;
|
const provider = this._provider;
|
||||||
if (!provider) return;
|
if (!provider) return;
|
||||||
|
|
||||||
|
this._init();
|
||||||
|
|
||||||
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(this._handleDeleteAndResolve)
|
||||||
@@ -50,6 +59,43 @@ export class InlineCommentManager extends LifeCycleWatcher {
|
|||||||
this._disposables.dispose();
|
this._disposables.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _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 blockComments = findAllCommentedBlocks(this.std.store).flatMap(
|
||||||
|
block => Object.keys(block.props.comments)
|
||||||
|
);
|
||||||
|
|
||||||
|
const commentsInEditor = [
|
||||||
|
...new Set([...inlineComments, ...blockComments]),
|
||||||
|
];
|
||||||
|
|
||||||
|
// resolve comments that are in provider but not in editor
|
||||||
|
// which means the commented content may be deleted
|
||||||
|
difference(commentsInProvider, commentsInEditor).forEach(comment => {
|
||||||
|
provider.resolveComment(comment);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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(BlockCommentManager).handleDeleteAndResolve(comment);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private readonly _handleAddComment = (
|
private readonly _handleAddComment = (
|
||||||
id: CommentId,
|
id: CommentId,
|
||||||
selections: BaseSelection[]
|
selections: BaseSelection[]
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import { type BlockStdScope, TextSelection } from '@blocksuite/std';
|
|||||||
import type { InlineEditor } from '@blocksuite/std/inline';
|
import type { InlineEditor } from '@blocksuite/std/inline';
|
||||||
import type { DeltaInsert } from '@blocksuite/store';
|
import type { DeltaInsert } from '@blocksuite/store';
|
||||||
|
|
||||||
export function findCommentedTexts(std: BlockStdScope, commentId: CommentId) {
|
export function findAllCommentedTexts(std: BlockStdScope) {
|
||||||
const selections: [TextSelection, InlineEditor][] = [];
|
const selections: [TextSelection, InlineEditor<AffineTextAttributes>][] = [];
|
||||||
std.store.getAllModels().forEach(model => {
|
std.store.getAllModels().forEach(model => {
|
||||||
const inlineEditor = getInlineEditorByModel(std, model);
|
const inlineEditor = getInlineEditorByModel(std, model);
|
||||||
if (!inlineEditor) return;
|
if (!inlineEditor) return;
|
||||||
@@ -19,9 +19,7 @@ export function findCommentedTexts(std: BlockStdScope, commentId: CommentId) {
|
|||||||
(delta, rangeIndex) => {
|
(delta, rangeIndex) => {
|
||||||
if (
|
if (
|
||||||
delta.attributes &&
|
delta.attributes &&
|
||||||
Object.keys(delta.attributes).some(
|
Object.keys(delta.attributes).some(key => key.startsWith('comment-'))
|
||||||
key => key === `comment-${commentId}`
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
selections.push([
|
selections.push([
|
||||||
new TextSelection({
|
new TextSelection({
|
||||||
@@ -42,6 +40,18 @@ export function findCommentedTexts(std: BlockStdScope, commentId: CommentId) {
|
|||||||
return selections;
|
return selections;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 extractCommentIdFromDelta(
|
export function extractCommentIdFromDelta(
|
||||||
delta: DeltaInsert<AffineTextAttributes>
|
delta: DeltaInsert<AffineTextAttributes>
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -42,10 +42,10 @@ export class BlockCommentManager 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(this.handleDeleteAndResolve)
|
||||||
);
|
);
|
||||||
this._disposables.add(
|
this._disposables.add(
|
||||||
provider.onCommentResolved(this._handleDeleteAndResolve)
|
provider.onCommentResolved(this.handleDeleteAndResolve)
|
||||||
);
|
);
|
||||||
this._disposables.add(
|
this._disposables.add(
|
||||||
provider.onCommentHighlighted(this._handleHighlightComment)
|
provider.onCommentHighlighted(this._handleHighlightComment)
|
||||||
@@ -103,7 +103,7 @@ export class BlockCommentManager extends LifeCycleWatcher {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly _handleDeleteAndResolve = (id: CommentId) => {
|
readonly handleDeleteAndResolve = (id: CommentId) => {
|
||||||
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 => {
|
||||||
|
|||||||
@@ -5,18 +5,23 @@ import type { BlockModel, Store } from '@blocksuite/store';
|
|||||||
import type { ToolbarAction } from '../toolbar-service';
|
import type { ToolbarAction } from '../toolbar-service';
|
||||||
import { type CommentId, CommentProviderIdentifier } from './comment-provider';
|
import { type CommentId, CommentProviderIdentifier } from './comment-provider';
|
||||||
|
|
||||||
export function findCommentedBlocks(store: Store, commentId: CommentId) {
|
export function findAllCommentedBlocks(store: Store) {
|
||||||
type CommentedBlock = BlockModel<{ comments: Record<CommentId, boolean> }>;
|
type CommentedBlock = BlockModel<{ comments: Record<CommentId, boolean> }>;
|
||||||
return store.getAllModels().filter((block): block is CommentedBlock => {
|
return store.getAllModels().filter((block): block is CommentedBlock => {
|
||||||
return (
|
return (
|
||||||
'comments' in block.props &&
|
'comments' in block.props &&
|
||||||
typeof block.props.comments === 'object' &&
|
typeof block.props.comments === 'object' &&
|
||||||
block.props.comments !== null &&
|
block.props.comments !== null
|
||||||
commentId in block.props.comments
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function findCommentedBlocks(store: Store, commentId: CommentId) {
|
||||||
|
return findAllCommentedBlocks(store).filter(block => {
|
||||||
|
return block.props.comments[commentId];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export const blockCommentToolbarButton: Omit<ToolbarAction, 'id'> = {
|
export const blockCommentToolbarButton: Omit<ToolbarAction, 'id'> = {
|
||||||
tooltip: 'Comment',
|
tooltip: 'Comment',
|
||||||
when: ({ std }) => !!std.getOptional(CommentProviderIdentifier),
|
when: ({ std }) => !!std.getOptional(CommentProviderIdentifier),
|
||||||
|
|||||||
Reference in New Issue
Block a user