mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-07 01:09:54 +08:00
fix(editor): time issues of comment initialization (#13031)
#### PR Dependency Tree * **PR #13031** 👈 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** * Added the ability to filter comments by their resolution status (resolved, unresolved, or all) when viewing or managing comments. * **Refactor** * Improved the way commented text is identified and retrieved, resulting in more reliable comment selection and filtering. * Enhanced comment retrieval to support asynchronous data loading, ensuring up-to-date comment information. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -38,7 +38,7 @@ export class InlineCommentManager extends LifeCycleWatcher {
|
|||||||
const provider = this._provider;
|
const provider = this._provider;
|
||||||
if (!provider) return;
|
if (!provider) return;
|
||||||
|
|
||||||
this._init();
|
this._init().catch(console.error);
|
||||||
|
|
||||||
this._disposables.add(provider.onCommentAdded(this._handleAddComment));
|
this._disposables.add(provider.onCommentAdded(this._handleAddComment));
|
||||||
this._disposables.add(
|
this._disposables.add(
|
||||||
@@ -59,20 +59,12 @@ export class InlineCommentManager extends LifeCycleWatcher {
|
|||||||
this._disposables.dispose();
|
this._disposables.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _init() {
|
private async _init() {
|
||||||
const provider = this._provider;
|
const provider = this._provider;
|
||||||
if (!provider) return;
|
if (!provider) return;
|
||||||
|
|
||||||
const commentsInProvider = provider.getComments();
|
const commentsInProvider = await provider.getComments('unresolved');
|
||||||
const inlineComments = findAllCommentedTexts(this.std).flatMap(
|
const inlineComments = [...findAllCommentedTexts(this.std.store).values()];
|
||||||
([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(
|
const blockComments = findAllCommentedBlocks(this.std.store).flatMap(
|
||||||
block => Object.keys(block.props.comments)
|
block => Object.keys(block.props.comments)
|
||||||
@@ -165,12 +157,17 @@ export class InlineCommentManager extends LifeCycleWatcher {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private readonly _handleDeleteAndResolve = (id: CommentId) => {
|
private readonly _handleDeleteAndResolve = (id: CommentId) => {
|
||||||
const commentedTexts = findCommentedTexts(this.std, id);
|
const commentedTexts = findCommentedTexts(this.std.store, id);
|
||||||
if (commentedTexts.length === 0) return;
|
if (commentedTexts.length === 0) return;
|
||||||
|
|
||||||
this.std.store.withoutTransact(() => {
|
this.std.store.withoutTransact(() => {
|
||||||
commentedTexts.forEach(([selection, inlineEditor]) => {
|
commentedTexts.forEach(selection => {
|
||||||
inlineEditor.formatText(
|
const inlineEditor = getInlineEditorByModel(
|
||||||
|
this.std,
|
||||||
|
selection.from.blockId
|
||||||
|
);
|
||||||
|
|
||||||
|
inlineEditor?.formatText(
|
||||||
selection.from,
|
selection.from,
|
||||||
{
|
{
|
||||||
[`comment-${id}`]: null,
|
[`comment-${id}`]: null,
|
||||||
|
|||||||
@@ -1,55 +1,56 @@
|
|||||||
import { getInlineEditorByModel } from '@blocksuite/affine-rich-text';
|
|
||||||
import type { CommentId } from '@blocksuite/affine-shared/services';
|
import type { CommentId } from '@blocksuite/affine-shared/services';
|
||||||
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
|
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
|
||||||
import { type BlockStdScope, TextSelection } from '@blocksuite/std';
|
import { TextSelection } from '@blocksuite/std';
|
||||||
import type { InlineEditor } from '@blocksuite/std/inline';
|
import type { DeltaInsert, Store } from '@blocksuite/store';
|
||||||
import type { DeltaInsert } from '@blocksuite/store';
|
|
||||||
|
|
||||||
export function findAllCommentedTexts(std: BlockStdScope) {
|
export function findAllCommentedTexts(
|
||||||
const selections: [TextSelection, InlineEditor<AffineTextAttributes>][] = [];
|
store: Store
|
||||||
std.store.getAllModels().forEach(model => {
|
): Map<TextSelection, CommentId> {
|
||||||
const inlineEditor = getInlineEditorByModel(std, model);
|
const result = new Map<TextSelection, CommentId>();
|
||||||
if (!inlineEditor) return;
|
|
||||||
|
|
||||||
inlineEditor.mapDeltasInInlineRange(
|
store.getAllModels().forEach(model => {
|
||||||
{
|
if (!model.text) return;
|
||||||
index: 0,
|
|
||||||
length: inlineEditor.yTextLength,
|
let index = 0;
|
||||||
},
|
model.text.toDelta().forEach(delta => {
|
||||||
(delta, rangeIndex) => {
|
if (!delta.insert) return;
|
||||||
if (
|
|
||||||
delta.attributes &&
|
const length = delta.insert.length;
|
||||||
Object.keys(delta.attributes).some(key => key.startsWith('comment-'))
|
|
||||||
) {
|
if (!delta.attributes) {
|
||||||
selections.push([
|
index += length;
|
||||||
new TextSelection({
|
return;
|
||||||
from: {
|
|
||||||
blockId: model.id,
|
|
||||||
index: rangeIndex,
|
|
||||||
length: delta.insert.length,
|
|
||||||
},
|
|
||||||
to: null,
|
|
||||||
}),
|
|
||||||
inlineEditor,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
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) {
|
export function findCommentedTexts(
|
||||||
return findAllCommentedTexts(std).filter(([selection, inlineEditor]) => {
|
store: Store,
|
||||||
const deltas = inlineEditor.getDeltasByInlineRange({
|
commentId: CommentId
|
||||||
index: selection.from.index,
|
): TextSelection[] {
|
||||||
length: selection.from.length,
|
return [...findAllCommentedTexts(store).entries()]
|
||||||
});
|
.filter(([_, id]) => id === commentId)
|
||||||
return deltas
|
.map(([selection]) => selection);
|
||||||
.flatMap(([delta]) => extractCommentIdFromDelta(delta))
|
|
||||||
.includes(commentId);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function extractCommentIdFromDelta(
|
export function extractCommentIdFromDelta(
|
||||||
|
|||||||
@@ -15,7 +15,10 @@ export interface CommentProvider {
|
|||||||
addComment: (selections: BaseSelection[]) => void;
|
addComment: (selections: BaseSelection[]) => void;
|
||||||
resolveComment: (id: CommentId) => void;
|
resolveComment: (id: CommentId) => void;
|
||||||
highlightComment: (id: CommentId | null) => void;
|
highlightComment: (id: CommentId | null) => void;
|
||||||
getComments: () => CommentId[];
|
|
||||||
|
getComments: (
|
||||||
|
type: 'resolved' | 'unresolved' | 'all'
|
||||||
|
) => Promise<CommentId[]> | CommentId[];
|
||||||
|
|
||||||
onCommentAdded: (
|
onCommentAdded: (
|
||||||
callback: (id: CommentId, selections: BaseSelection[]) => void
|
callback: (id: CommentId, selections: BaseSelection[]) => void
|
||||||
|
|||||||
@@ -244,8 +244,14 @@ export function mockCommentProvider() {
|
|||||||
this.commentHighlightSubject.next(id);
|
this.commentHighlightSubject.next(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
getComments() {
|
getComments(type: 'resolved' | 'unresolved' | 'all' = 'all') {
|
||||||
return Array.from(this.comments.keys());
|
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(
|
onCommentAdded(
|
||||||
|
|||||||
@@ -138,8 +138,10 @@ class AffineCommentService implements CommentProvider {
|
|||||||
this.commentEntity.highlightComment(id);
|
this.commentEntity.highlightComment(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
getComments(): string[] {
|
async getComments(
|
||||||
return this.commentEntity.getComments();
|
type: 'resolved' | 'unresolved' | 'all' = 'all'
|
||||||
|
): Promise<string[]> {
|
||||||
|
return this.commentEntity.getComments(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
onCommentAdded(callback: (id: string, selections: BaseSelection[]) => void) {
|
onCommentAdded(callback: (id: string, selections: BaseSelection[]) => void) {
|
||||||
|
|||||||
@@ -14,7 +14,16 @@ import {
|
|||||||
onStart,
|
onStart,
|
||||||
} from '@toeverything/infra';
|
} from '@toeverything/infra';
|
||||||
import { nanoid } from 'nanoid';
|
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 { type DocDisplayMetaService } from '../../doc-display-meta';
|
||||||
import { GlobalContextService } from '../../global-context';
|
import { GlobalContextService } from '../../global-context';
|
||||||
@@ -280,8 +289,30 @@ export class DocCommentEntity extends Entity<{
|
|||||||
this.commentHighlighted$.next(id);
|
this.commentHighlighted$.next(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
getComments(): CommentId[] {
|
async getComments(
|
||||||
return this.comments$.value.map(comment => comment.id);
|
type: 'resolved' | 'unresolved' | 'all' = 'all'
|
||||||
|
): Promise<CommentId[]> {
|
||||||
|
return new Promise<CommentId[]>(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(
|
onCommentAdded(
|
||||||
|
|||||||
@@ -214,9 +214,12 @@ export class Editor extends Entity {
|
|||||||
const std = editorContainer.host.std;
|
const std = editorContainer.host.std;
|
||||||
|
|
||||||
// First try to find inline commented texts
|
// First try to find inline commented texts
|
||||||
const inlineCommentedSelections = findCommentedTexts(std, commentId);
|
const inlineCommentedSelections = findCommentedTexts(
|
||||||
|
std.store,
|
||||||
|
commentId
|
||||||
|
);
|
||||||
if (inlineCommentedSelections.length > 0) {
|
if (inlineCommentedSelections.length > 0) {
|
||||||
const firstSelection = inlineCommentedSelections[0][0];
|
const firstSelection = inlineCommentedSelections[0];
|
||||||
finalId = firstSelection.from.blockId;
|
finalId = firstSelection.from.blockId;
|
||||||
finalKey = 'blockIds';
|
finalKey = 'blockIds';
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user