refactor(editor): optimize pasting process of attachments and images (#12276)

Related to: [BS-3146](https://linear.app/affine-design/issue/BS-3146/import-paste-接口改进优化)
This commit is contained in:
fundon
2025-05-18 01:57:42 +00:00
parent f3693a91c3
commit 8726b0e462
14 changed files with 240 additions and 76 deletions

View File

@@ -143,7 +143,11 @@ export class AttachmentBlockComponent extends CaptionedBlockComponent<Attachment
this.disposables.add(this.resourceController.subscribe());
this.disposables.add(this.resourceController);
this.refreshData();
this.disposables.add(
this.model.props.sourceId$.subscribe(() => {
this.refreshData();
})
);
if (!this.model.props.style && !this.store.readonly) {
this.store.withoutTransact(() => {

View File

@@ -7,7 +7,7 @@ import {
} from '@blocksuite/affine-shared/commands';
import { ImageSelection } from '@blocksuite/affine-shared/selection';
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
import { WithDisposable } from '@blocksuite/global/lit';
import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
import type { BlockComponent, UIEventStateContext } from '@blocksuite/std';
import {
BlockSelection,
@@ -15,8 +15,9 @@ import {
TextSelection,
} from '@blocksuite/std';
import type { BaseSelection } from '@blocksuite/store';
import { computed } from '@preact/signals-core';
import { css, html, type PropertyValues } from 'lit';
import { property, query, state } from 'lit/decorators.js';
import { property, query } from 'lit/decorators.js';
import { styleMap } from 'lit/directives/style-map.js';
import { when } from 'lit/directives/when.js';
@@ -25,7 +26,9 @@ import { ImageResizeManager } from '../image-resize-manager';
import { shouldResizeImage } from '../utils';
import { ImageSelectedRect } from './image-selected-rect';
export class ImageBlockPageComponent extends WithDisposable(ShadowlessElement) {
export class ImageBlockPageComponent extends SignalWatcher(
WithDisposable(ShadowlessElement)
) {
static override styles = css`
affine-page-image {
position: relative;
@@ -68,6 +71,8 @@ export class ImageBlockPageComponent extends WithDisposable(ShadowlessElement) {
}
`;
resizeable$ = computed(() => this.block.resizeable$.value);
private _isDragging = false;
private get _doc() {
@@ -134,21 +139,21 @@ export class ImageBlockPageComponent extends WithDisposable(ShadowlessElement) {
return true;
},
Delete: ctx => {
if (this._host.store.readonly || !this._isSelected) return;
if (this._host.store.readonly || !this.resizeable$.peek()) return;
addParagraph(ctx);
this._doc.deleteBlock(this._model);
return true;
},
Backspace: ctx => {
if (this._host.store.readonly || !this._isSelected) return;
if (this._host.store.readonly || !this.resizeable$.peek()) return;
addParagraph(ctx);
this._doc.deleteBlock(this._model);
return true;
},
Enter: ctx => {
if (this._host.store.readonly || !this._isSelected) return;
if (this._host.store.readonly || !this.resizeable$.peek()) return;
addParagraph(ctx);
return true;
@@ -213,19 +218,6 @@ export class ImageBlockPageComponent extends WithDisposable(ShadowlessElement) {
private _handleSelection() {
const selection = this._host.selection;
this._disposables.add(
selection.slots.changed.subscribe(selList => {
this._isSelected = selList.some(
sel => sel.blockId === this.block.blockId && sel.is(ImageSelection)
);
})
);
this._disposables.add(
this._model.propsUpdated.subscribe(() => {
this.requestUpdate();
})
);
this._disposables.addFromEvent(
this.resizeImg,
@@ -249,7 +241,7 @@ export class ImageBlockPageComponent extends WithDisposable(ShadowlessElement) {
this.block.handleEvent(
'click',
() => {
if (!this._isSelected) return;
if (!this.resizeable$.peek()) return;
selection.update(selList =>
selList.filter(
@@ -356,7 +348,7 @@ export class ImageBlockPageComponent extends WithDisposable(ShadowlessElement) {
override render() {
const imageSize = this._normalizeImageSize();
const imageSelectedRect = this._isSelected
const imageSelectedRect = this.resizeable$.value
? ImageSelectedRect(this._doc.readonly)
: null;
@@ -389,9 +381,6 @@ export class ImageBlockPageComponent extends WithDisposable(ShadowlessElement) {
`;
}
@state()
accessor _isSelected = false;
@property({ attribute: false })
accessor block!: ImageBlockComponent;

View File

@@ -4,6 +4,7 @@ import { getLoadingIconWith } from '@blocksuite/affine-components/icons';
import { Peekable } from '@blocksuite/affine-components/peek';
import { ResourceController } from '@blocksuite/affine-components/resource';
import type { ImageBlockModel } from '@blocksuite/affine-model';
import { ImageSelection } from '@blocksuite/affine-shared/selection';
import {
ThemeProvider,
ToolbarRegistryIdentifier,
@@ -30,6 +31,13 @@ import {
enableOn: () => !IS_MOBILE,
})
export class ImageBlockComponent extends CaptionedBlockComponent<ImageBlockModel> {
resizeable$ = computed(() =>
this.std.selection.value.some(
selection =>
selection.is(ImageSelection) && selection.blockId === this.blockId
)
);
resourceController = new ResourceController(
computed(() => this.model.props.sourceId$.value),
'Image'
@@ -104,7 +112,11 @@ export class ImageBlockComponent extends CaptionedBlockComponent<ImageBlockModel
this.disposables.add(this.resourceController.subscribe());
this.disposables.add(this.resourceController);
this.refreshData();
this.disposables.add(
this.model.props.sourceId$.subscribe(() => {
this.refreshData();
})
);
}
override firstUpdated() {

View File

@@ -100,7 +100,11 @@ export class ImageEdgelessBlockComponent extends GfxBlockComponent<ImageBlockMod
this.disposables.add(this.resourceController.subscribe());
this.disposables.add(this.resourceController);
this.refreshData();
this.disposables.add(
this.model.props.sourceId$.subscribe(() => {
this.refreshData();
})
);
}
override renderGfxBlock() {

View File

@@ -3,6 +3,7 @@ import {
pasteMiddleware,
replaceIdMiddleware,
surfaceRefToEmbed,
uploadMiddleware,
} from '@blocksuite/affine-shared/adapters';
import {
clearAndSelectFirstModelCommand,
@@ -34,14 +35,17 @@ export class PageClipboard extends ReadOnlyClipboard {
// When pastina a surface-ref block to another doc
const surfaceRefToEmbedMiddleware = surfaceRefToEmbed(this.std);
const replaceId = replaceIdMiddleware(this.std.store.workspace.idGenerator);
const upload = uploadMiddleware(this.std);
this.std.clipboard.use(paste);
this.std.clipboard.use(surfaceRefToEmbedMiddleware);
this.std.clipboard.use(replaceId);
this.std.clipboard.use(upload);
this._disposables.add({
dispose: () => {
this.std.clipboard.unuse(paste);
this.std.clipboard.unuse(surfaceRefToEmbedMiddleware);
this.std.clipboard.unuse(replaceId);
this.std.clipboard.unuse(upload);
},
});
};