mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-24 04:27:27 +08:00
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:
@@ -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',
|
||||||
|
|||||||
@@ -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 => {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,54 +103,52 @@ 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()
|
.pipe(getSelectedBlocksCommand, {
|
||||||
.pipe(getSelectedBlocksCommand, {
|
textSelection: selection,
|
||||||
textSelection: selection,
|
})
|
||||||
})
|
.run();
|
||||||
.run();
|
|
||||||
|
|
||||||
if (!selectedBlocks) return [];
|
if (!selectedBlocks) return [];
|
||||||
|
|
||||||
type MakeRequired<T, K extends keyof T> = T & {
|
type MakeRequired<T, K extends keyof T> = T & {
|
||||||
[key in K]: NonNullable<T[key]>;
|
[key in K]: NonNullable<T[key]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
return selectedBlocks
|
return selectedBlocks
|
||||||
.map(
|
.map(
|
||||||
({ model }) =>
|
({ model }) =>
|
||||||
[model, getInlineEditorByModel(this.std, model)] as const
|
[model, getInlineEditorByModel(this.std, model)] as const
|
||||||
)
|
)
|
||||||
.filter(
|
.filter(
|
||||||
(
|
(
|
||||||
pair
|
pair
|
||||||
): pair is [MakeRequired<BlockModel, 'text'>, AffineInlineEditor] =>
|
): pair is [MakeRequired<BlockModel, 'text'>, AffineInlineEditor] =>
|
||||||
!!pair[0].text && !!pair[1]
|
!!pair[0].text && !!pair[1]
|
||||||
)
|
)
|
||||||
.map(([model, inlineEditor]) => {
|
.map(([model, inlineEditor]) => {
|
||||||
let from: TextRangePoint;
|
let from: TextRangePoint;
|
||||||
let to: TextRangePoint | null;
|
let to: TextRangePoint | null;
|
||||||
if (model.id === selection.from.blockId) {
|
if (model.id === selection.from.blockId) {
|
||||||
from = selection.from;
|
from = selection.from;
|
||||||
to = null;
|
to = null;
|
||||||
} else if (model.id === selection.to?.blockId) {
|
} else if (model.id === selection.to?.blockId) {
|
||||||
from = selection.to;
|
from = selection.to;
|
||||||
to = null;
|
to = null;
|
||||||
} else {
|
} else {
|
||||||
from = {
|
from = {
|
||||||
blockId: model.id,
|
blockId: model.id,
|
||||||
index: 0,
|
index: 0,
|
||||||
length: model.text.yText.length,
|
length: model.text.yText.length,
|
||||||
};
|
};
|
||||||
to = null;
|
to = null;
|
||||||
}
|
}
|
||||||
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;
|
||||||
|
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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])];
|
||||||
|
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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++;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)">
|
||||||
|
|||||||
@@ -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)));
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 = {
|
||||||
|
|||||||
Reference in New Issue
Block a user