fix(editor): add comment entire to inner toolbar (#13304)

Close
[BS-3624](https://linear.app/affine-design/issue/BS-3624/page模式单选图片的时候希望有comment-按钮)




#### PR Dependency Tree


* **PR #13304** 👈

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 a comment button to the image and surface reference block
toolbars for easier commenting.

* **Refactor**
* Simplified array flattening operations across multiple components and
utilities by replacing `.map(...).flat()` with `.flatMap(...)`,
improving code readability and maintainability.

* **Bug Fixes**
* Improved comment creation logic to allow adding comments even when
selections exist.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
L-Sun
2025-07-29 13:21:56 +08:00
committed by GitHub
parent bd161c54b2
commit 091bac1047
15 changed files with 82 additions and 83 deletions

View File

@@ -1,6 +1,7 @@
import { ImageBlockModel } from '@blocksuite/affine-model'; import { ImageBlockModel } from '@blocksuite/affine-model';
import { import {
ActionPlacement, ActionPlacement,
blockCommentToolbarButton,
type ToolbarModuleConfig, type ToolbarModuleConfig,
ToolbarModuleExtension, ToolbarModuleExtension,
} from '@blocksuite/affine-shared/services'; } from '@blocksuite/affine-shared/services';
@@ -49,6 +50,10 @@ const builtinToolbarConfig = {
}); });
}, },
}, },
{
id: 'c.comment',
...blockCommentToolbarButton,
},
{ {
placement: ActionPlacement.More, placement: ActionPlacement.More,
id: 'a.clipboard', id: 'a.clipboard',

View File

@@ -634,9 +634,9 @@ export class EdgelessPageKeyboardManager extends PageKeyboardManager {
const movedElements = new Set([ const movedElements = new Set([
...selectedElements, ...selectedElements,
...selectedElements ...selectedElements.flatMap(el =>
.map(el => (isGfxGroupCompatibleModel(el) ? el.descendantElements : [])) isGfxGroupCompatibleModel(el) ? el.descendantElements : []
.flat(), ),
]); ]);
movedElements.forEach(element => { movedElements.forEach(element => {

View File

@@ -5,6 +5,7 @@ import {
} from '@blocksuite/affine-shared/commands'; } from '@blocksuite/affine-shared/commands';
import { import {
ActionPlacement, ActionPlacement,
blockCommentToolbarButton,
type ToolbarModuleConfig, type ToolbarModuleConfig,
} from '@blocksuite/affine-shared/services'; } from '@blocksuite/affine-shared/services';
import { CaptionIcon, CopyIcon, DeleteIcon } from '@blocksuite/icons/lit'; import { CaptionIcon, CopyIcon, DeleteIcon } from '@blocksuite/icons/lit';
@@ -61,6 +62,10 @@ export const surfaceRefToolbarModuleConfig: ToolbarModuleConfig = {
surfaceRefBlock.captionElement.show(); surfaceRefBlock.captionElement.show();
}, },
}, },
{
id: 'e.comment',
...blockCommentToolbarButton,
},
{ {
id: 'a.clipboard', id: 'a.clipboard',
placement: ActionPlacement.More, placement: ActionPlacement.More,

View File

@@ -68,5 +68,5 @@ export function getHeadingBlocksFromDoc(
ignoreEmpty = false ignoreEmpty = false
) { ) {
const notes = getNotesFromStore(store, modes); const notes = getNotesFromStore(store, modes);
return notes.map(note => getHeadingBlocksFromNote(note, ignoreEmpty)).flat(); return notes.flatMap(note => getHeadingBlocksFromNote(note, ignoreEmpty));
} }

View File

@@ -103,8 +103,7 @@ export class InlineCommentManager extends LifeCycleWatcher {
id: CommentId, id: CommentId,
selections: BaseSelection[] selections: BaseSelection[]
) => { ) => {
const needCommentTexts = selections const needCommentTexts = selections.flatMap(selection => {
.map(selection => {
if (!selection.is(TextSelection)) return []; if (!selection.is(TextSelection)) return [];
const [_, { selectedBlocks }] = this.std.command const [_, { selectedBlocks }] = this.std.command
.chain() .chain()
@@ -149,8 +148,7 @@ export class InlineCommentManager extends LifeCycleWatcher {
} }
return [new TextSelection({ from, to }), inlineEditor] as const; return [new TextSelection({ from, to }), inlineEditor] as const;
}); });
}) });
.flat();
if (needCommentTexts.length === 0) return; if (needCommentTexts.length === 0) return;

View File

@@ -64,7 +64,7 @@ export const blockCommentToolbarButton: Omit<ToolbarAction, 'id'> = {
// may be hover on a block or element, in this case // may be hover on a block or element, in this case
// the selection is empty, so we need to get the current model // the selection is empty, so we need to get the current model
if (model && selections.length === 0) { if (model) {
if (model instanceof BlockModel) { if (model instanceof BlockModel) {
commentProvider.addComment([ commentProvider.addComment([
new BlockSelection({ new BlockSelection({

View File

@@ -11,14 +11,12 @@ export function getSelectedRect(selected: GfxModel[]): DOMRect {
return new DOMRect(); return new DOMRect();
} }
const lockedElementsByFrame = selected const lockedElementsByFrame = selected.flatMap(selectable => {
.map(selectable => {
if (selectable instanceof FrameBlockModel && selectable.isLocked()) { if (selectable instanceof FrameBlockModel && selectable.isLocked()) {
return selectable.descendantElements; return selectable.descendantElements;
} }
return []; return [];
}) });
.flat();
selected = [...new Set([...selected, ...lockedElementsByFrame])]; selected = [...new Set([...selected, ...lockedElementsByFrame])];

View File

@@ -113,11 +113,9 @@ export class LinkedDocPopover extends SignalWatcher(
} }
private get _flattenActionList() { private get _flattenActionList() {
return this._actionGroup return this._actionGroup.flatMap(group =>
.map(group =>
group.items.map(item => ({ ...item, groupName: group.name })) group.items.map(item => ({ ...item, groupName: group.name }))
) );
.flat();
} }
private get _query() { private get _query() {

View File

@@ -142,15 +142,13 @@ export class SlashMenu extends WithDisposable(LitElement) {
// We search first and second layer // We search first and second layer
if (this._filteredItems.length !== 0 && depth >= 1) break; if (this._filteredItems.length !== 0 && depth >= 1) break;
queue = queue queue = queue.flatMap(item => {
.map<typeof queue>(item => {
if (isSubMenuItem(item)) { if (isSubMenuItem(item)) {
return item.subMenu; return item.subMenu;
} else { } else {
return []; return [];
} }
}) });
.flat();
depth++; depth++;
} }

View File

@@ -418,9 +418,9 @@ export class AffineToolbarWidget extends WidgetComponent {
return; return;
} }
const elementIds = selections const elementIds = selections.flatMap(s =>
.map(s => (s.editing || s.inoperable ? [] : s.elements)) s.editing || s.inoperable ? [] : s.elements
.flat(); );
const count = elementIds.length; const count = elementIds.length;
const activated = context.activated && Boolean(count); const activated = context.activated && Boolean(count);

View File

@@ -229,8 +229,7 @@ export function renderToolbar(
? module.config.when(context) ? module.config.when(context)
: (module.config.when ?? true) : (module.config.when ?? true)
) )
.map<ToolbarActions>(module => module.config.actions) .flatMap(module => module.config.actions);
.flat();
const combined = combine(actions, context); const combined = combine(actions, context);

View File

@@ -255,8 +255,7 @@ export abstract class GeminiProvider<T> extends CopilotProvider<T> {
); );
return embeddings return embeddings
.map(e => (e.status === 'fulfilled' ? e.value.embeddings : null)) .flatMap(e => (e.status === 'fulfilled' ? e.value.embeddings : null))
.flat()
.filter((v): v is number[] => !!v && Array.isArray(v)); .filter((v): v is number[] => !!v && Array.isArray(v));
} catch (e: any) { } catch (e: any) {
metrics.ai metrics.ai

View File

@@ -19,7 +19,7 @@ import {
} from '../widgets/ai-panel/ai-panel'; } from '../widgets/ai-panel/ai-panel';
export function AiSlashMenuConfigExtension() { export function AiSlashMenuConfigExtension() {
const AIItems = pageAIGroups.map(group => group.items).flat(); const AIItems = pageAIGroups.flatMap(group => group.items);
const iconWrapper = (icon: AIItemConfig['icon']) => { const iconWrapper = (icon: AIItemConfig['icon']) => {
return html`<div style="color: var(--affine-primary-color)"> return html`<div style="color: var(--affine-primary-color)">

View File

@@ -66,7 +66,7 @@ export class Collection extends Entity<{ id: string }> {
}, },
], ],
}) })
.pipe(map(result => result.groups.map(group => group.items).flat())); .pipe(map(result => result.groups.flatMap(group => group.items)));
}) })
); );
} }

View File

@@ -51,8 +51,7 @@ import stickerContent${id} from './stickers/${category}/Content/${sticker}';`,
} }
const importStatements = Object.values(data) const importStatements = Object.values(data)
.map(v => Object.values(v).map(v => v.importStatement)) .flatMap(v => Object.values(v).map(v => v.importStatement))
.flat()
.join('\n'); .join('\n');
const templates = `const templates = { const templates = `const templates = {