feat(core): comment panel (#12989)

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

* **New Features**
* Introduced a full-featured document comment system with sidebar for
viewing, filtering, replying, resolving, and managing comments and
replies.
* Added a rich text comment editor supporting editable and read-only
modes.
  * Enabled comment-based navigation and highlighting within documents.
* Integrated comment functionality into the workspace sidebar (excluding
local workspaces).
* Added internationalization support and new UI strings for comment
features.
* Added new feature flag `enable_comment` for toggling comment
functionality.
  * Enhanced editor focus to support comment-related selections.
  * Added snapshot and store helpers for comment content management.
* Implemented backend GraphQL support for comment and reply operations.
* Added services for comment entity management and comment panel
behavior.
* Extended comment configuration to support optional framework
providers.
* Added preview generation from user selections when creating comments.
  * Enabled automatic sidebar opening on new pending comments.
  * Added comment-related query parameter support for navigation.
  * Included inline comment module exports for integration.
* Improved comment provider implementation with full lifecycle
management and UI integration.
* Added comment highlight state tracking and refined selection handling
in inline comments.

* **Style**
* Added comprehensive styles for the comment editor and sidebar
components.

* **Chores**
  * Updated language completeness percentages for multiple locales.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: L-Sun <zover.v@gmail.com>
This commit is contained in:
Peng Xiao
2025-07-02 15:47:00 +00:00
committed by GitHub
co-authored by L-Sun
parent a59448ec4b
commit a21f1c943e
30 changed files with 2572 additions and 68 deletions
+1
View File
@@ -174,6 +174,7 @@
"./inlines/footnote": "./src/inlines/footnote/index.ts",
"./inlines/footnote/view": "./src/inlines/footnote/view.ts",
"./inlines/footnote/store": "./src/inlines/footnote/store.ts",
"./inlines/comment": "./src/inlines/comment/index.ts",
"./inlines/latex": "./src/inlines/latex/index.ts",
"./inlines/latex/store": "./src/inlines/latex/store.ts",
"./inlines/latex/view": "./src/inlines/latex/view.ts",
@@ -0,0 +1 @@
export * from '@blocksuite/affine-inline-comment';
@@ -1 +1,2 @@
export * from './inline-spec';
export * from './utils';
@@ -12,6 +12,7 @@ import {
TextSelection,
} from '@blocksuite/std';
import type { BaseSelection, BlockModel } from '@blocksuite/store';
import { signal } from '@preact/signals-core';
import { extractCommentIdFromDelta, findCommentedTexts } from './utils';
@@ -20,6 +21,8 @@ export class InlineCommentManager extends LifeCycleWatcher {
private readonly _disposables = new DisposableGroup();
private readonly _highlightedCommentId$ = signal<CommentId | null>(null);
private get _provider() {
return this.std.getOptional(CommentProviderIdentifier);
}
@@ -35,6 +38,9 @@ export class InlineCommentManager extends LifeCycleWatcher {
this._disposables.add(
provider.onCommentResolved(this._handleDeleteAndResolve)
);
this._disposables.add(
provider.onCommentHighlighted(this._handleHighlightComment)
);
this._disposables.add(
this.std.selection.slots.changed.subscribe(this._handleSelectionChanged)
);
@@ -131,14 +137,20 @@ export class InlineCommentManager extends LifeCycleWatcher {
});
};
private readonly _handleHighlightComment = (id: CommentId | null) => {
this._highlightedCommentId$.value = id;
};
private readonly _handleSelectionChanged = (selections: BaseSelection[]) => {
const currentHighlightedCommentId = this._highlightedCommentId$.peek();
if (selections.length === 1) {
const selection = selections[0];
// InlineCommentManager only handle text selection
if (!selection.is(TextSelection)) return;
if (!selection.isCollapsed()) {
if (!selection.isCollapsed() && currentHighlightedCommentId !== null) {
this._provider?.highlightComment(null);
return;
}
@@ -156,6 +168,8 @@ export class InlineCommentManager extends LifeCycleWatcher {
if (commentIds.length !== 0) return;
}
this._provider?.highlightComment(null);
if (currentHighlightedCommentId !== null) {
this._provider?.highlightComment(null);
}
};
}
@@ -43,6 +43,7 @@ export const ReferenceParamsSchema = z
databaseId: z.string().optional(),
databaseRowId: z.string().optional(),
xywh: SerializedXYWHSchema.optional(),
commentId: z.string().optional(),
})
.partial();
@@ -21,6 +21,7 @@ export interface BlockSuiteFlags {
enable_table_virtual_scroll: boolean;
enable_turbo_renderer: boolean;
enable_dom_renderer: boolean;
enable_comment: boolean;
}
export class FeatureFlagService extends StoreExtension {
@@ -46,6 +47,7 @@ export class FeatureFlagService extends StoreExtension {
enable_table_virtual_scroll: false,
enable_turbo_renderer: false,
enable_dom_renderer: false,
enable_comment: false,
});
setFlag(key: keyof BlockSuiteFlags, value: boolean) {