feat(editor): add provider for base adapter (#11169)

This commit is contained in:
Saul-Mirone
2025-03-25 12:09:23 +00:00
parent df057b4c12
commit e84c60f53d
18 changed files with 50 additions and 55 deletions
@@ -7,7 +7,7 @@ import { createJob } from '../utils/create-job.js';
import { getProvider } from '../utils/get-provider.js'; import { getProvider } from '../utils/get-provider.js';
import { nanoidReplacement } from '../utils/nanoid-replacement.js'; import { nanoidReplacement } from '../utils/nanoid-replacement.js';
getProvider(); const provider = getProvider();
describe('notion-text to snapshot', () => { describe('notion-text to snapshot', () => {
test('basic', () => { test('basic', () => {
@@ -98,7 +98,7 @@ describe('notion-text to snapshot', () => {
pageId: '', pageId: '',
}; };
const ntAdapter = new NotionTextAdapter(createJob()); const ntAdapter = new NotionTextAdapter(createJob(), provider);
const target = ntAdapter.toSliceSnapshot({ const target = ntAdapter.toSliceSnapshot({
file: notionText, file: notionText,
workspaceId: '', workspaceId: '',
@@ -165,5 +165,3 @@ export class PageClipboard extends ReadOnlyClipboard {
} }
} }
} }
export { pasteMiddleware };
@@ -1,6 +1,7 @@
import { defaultImageProxyMiddleware } from '@blocksuite/affine-block-image'; import { defaultImageProxyMiddleware } from '@blocksuite/affine-block-image';
import { import {
AttachmentAdapter, AttachmentAdapter,
ClipboardAdapter,
copyMiddleware, copyMiddleware,
HtmlAdapter, HtmlAdapter,
ImageAdapter, ImageAdapter,
@@ -16,8 +17,6 @@ import {
import type { BlockComponent, UIEventHandler } from '@blocksuite/block-std'; import type { BlockComponent, UIEventHandler } from '@blocksuite/block-std';
import { DisposableGroup } from '@blocksuite/global/disposable'; import { DisposableGroup } from '@blocksuite/global/disposable';
import { ClipboardAdapter } from './adapter.js';
/** /**
* ReadOnlyClipboard is a class that provides a read-only clipboard for the root block. * ReadOnlyClipboard is a class that provides a read-only clipboard for the root block.
* It is supported to copy models in the root block. * It is supported to copy models in the root block.
@@ -19,6 +19,11 @@ import {
FrameBlockModel, FrameBlockModel,
MAX_IMAGE_WIDTH, MAX_IMAGE_WIDTH,
} from '@blocksuite/affine-model'; } from '@blocksuite/affine-model';
import {
ClipboardAdapter,
decodeClipboardBlobs,
encodeClipboardBlobs,
} from '@blocksuite/affine-shared/adapters';
import { import {
CANVAS_EXPORT_IGNORE_TAGS, CANVAS_EXPORT_IGNORE_TAGS,
EMBED_CARD_HEIGHT, EMBED_CARD_HEIGHT,
@@ -72,12 +77,7 @@ import {
import DOMPurify from 'dompurify'; import DOMPurify from 'dompurify';
import * as Y from 'yjs'; import * as Y from 'yjs';
import { ClipboardAdapter } from '../../clipboard/adapter.js';
import { PageClipboard } from '../../clipboard/index.js'; import { PageClipboard } from '../../clipboard/index.js';
import {
decodeClipboardBlobs,
encodeClipboardBlobs,
} from '../../clipboard/utils.js';
import { edgelessElementsBoundFromRawData } from '../utils/bound-utils.js'; import { edgelessElementsBoundFromRawData } from '../utils/bound-utils.js';
import { createNewPresentationIndexes } from '../utils/clipboard-utils.js'; import { createNewPresentationIndexes } from '../utils/clipboard-utils.js';
import { import {
@@ -131,8 +131,8 @@ export const AttachmentAdapterFactoryIdentifier =
export const AttachmentAdapterFactoryExtension: ExtensionType = { export const AttachmentAdapterFactoryExtension: ExtensionType = {
setup: di => { setup: di => {
di.addImpl(AttachmentAdapterFactoryIdentifier, () => ({ di.addImpl(AttachmentAdapterFactoryIdentifier, provider => ({
get: (job: Transformer) => new AttachmentAdapter(job), get: (job: Transformer) => new AttachmentAdapter(job, provider),
})); }));
}, },
}; };
@@ -15,6 +15,7 @@ import type {
} from '@blocksuite/store'; } from '@blocksuite/store';
import { BaseAdapter } from '@blocksuite/store'; import { BaseAdapter } from '@blocksuite/store';
import { NotificationProvider } from '../../services/notification-service.js';
import { decodeClipboardBlobs, encodeClipboardBlobs } from './utils.js'; import { decodeClipboardBlobs, encodeClipboardBlobs } from './utils.js';
export type FileSnapshot = { export type FileSnapshot = {
@@ -26,6 +27,13 @@ export type FileSnapshot = {
export class ClipboardAdapter extends BaseAdapter<string> { export class ClipboardAdapter extends BaseAdapter<string> {
static MIME = 'BLOCKSUITE/SNAPSHOT'; static MIME = 'BLOCKSUITE/SNAPSHOT';
private readonly _onError = (message: string) => {
const notification = this.provider.getOptional(NotificationProvider);
if (!notification) return;
notification.toast(message);
};
override fromBlockSnapshot( override fromBlockSnapshot(
_payload: FromBlockSnapshotPayload _payload: FromBlockSnapshotPayload
): Promise<FromBlockSnapshotResult<string>> { ): Promise<FromBlockSnapshotResult<string>> {
@@ -56,7 +64,10 @@ export class ClipboardAdapter extends BaseAdapter<string> {
); );
} }
const map = assets.getAssets(); const map = assets.getAssets();
const blobs: Record<string, FileSnapshot> = await encodeClipboardBlobs(map); const blobs: Record<string, FileSnapshot> = await encodeClipboardBlobs(
map,
this._onError
);
return { return {
file: JSON.stringify({ file: JSON.stringify({
snapshot, snapshot,
@@ -0,0 +1,2 @@
export * from './clipboard';
export * from './utils';
@@ -1,6 +1,4 @@
import { toast } from '@blocksuite/affine-components/toast'; import type { FileSnapshot } from './clipboard.js';
import type { FileSnapshot } from './adapter.js';
const chars = const chars =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
@@ -67,30 +65,21 @@ export const decode = (base64: string): ArrayBuffer => {
return arraybuffer; return arraybuffer;
}; };
export async function encodeClipboardBlobs(map: Map<string, Blob>) { export async function encodeClipboardBlobs(
map: Map<string, Blob>,
onError?: (message: string) => void
) {
const blobs: Record<string, FileSnapshot> = {}; const blobs: Record<string, FileSnapshot> = {};
let sumSize = 0; let sumSize = 0;
await Promise.all( await Promise.all(
Array.from(map.entries()).map(async ([id, blob]) => { Array.from(map.entries()).map(async ([id, blob]) => {
if (blob.size > 4 * 1024 * 1024) { if (blob.size > 4 * 1024 * 1024) {
const host = document.querySelector('editor-host'); onError?.((blob as File).name ?? 'File' + ' is too large to be copied');
if (!host) {
return;
}
toast(
host,
(blob as File).name ?? 'File' + ' is too large to be copied'
);
return; return;
} }
sumSize += blob.size; sumSize += blob.size;
if (sumSize > 6 * 1024 * 1024) { if (sumSize > 6 * 1024 * 1024) {
const host = document.querySelector('editor-host'); onError?.(
if (!host) {
return;
}
toast(
host,
(blob as File).name ?? (blob as File).name ??
'File' + ' cannot be copied due to the clipboard size limit' 'File' + ' cannot be copied due to the clipboard size limit'
); );
@@ -174,11 +174,8 @@ export class HtmlAdapter extends BaseAdapter<Html> {
readonly blockMatchers: BlockHtmlAdapterMatcher[]; readonly blockMatchers: BlockHtmlAdapterMatcher[];
constructor( constructor(job: Transformer, provider: ServiceProvider) {
job: Transformer, super(job, provider);
readonly provider: ServiceProvider
) {
super(job);
const blockMatchers = Array.from( const blockMatchers = Array.from(
provider.getAll(BlockHtmlAdapterMatcherIdentifier).values() provider.getAll(BlockHtmlAdapterMatcherIdentifier).values()
); );
@@ -122,8 +122,8 @@ export const ImageAdapterFactoryIdentifier = AdapterFactoryIdentifier('Image');
export const ImageAdapterFactoryExtension: ExtensionType = { export const ImageAdapterFactoryExtension: ExtensionType = {
setup: di => { setup: di => {
di.addImpl(ImageAdapterFactoryIdentifier, () => ({ di.addImpl(ImageAdapterFactoryIdentifier, provider => ({
get: (job: Transformer) => new ImageAdapter(job), get: (job: Transformer) => new ImageAdapter(job, provider),
})); }));
}, },
}; };
@@ -1,4 +1,5 @@
export * from './attachment'; export * from './attachment';
export * from './clipboard';
export { export {
BlockHtmlAdapterExtension, BlockHtmlAdapterExtension,
type BlockHtmlAdapterMatcher, type BlockHtmlAdapterMatcher,
@@ -170,11 +170,8 @@ export class MarkdownAdapter extends BaseAdapter<Markdown> {
readonly blockMatchers: BlockMarkdownAdapterMatcher[]; readonly blockMatchers: BlockMarkdownAdapterMatcher[];
constructor( constructor(job: Transformer, provider: ServiceProvider) {
job: Transformer, super(job, provider);
readonly provider: ServiceProvider
) {
super(job);
const blockMatchers = Array.from( const blockMatchers = Array.from(
provider.getAll(BlockMarkdownAdapterMatcherIdentifier).values() provider.getAll(BlockMarkdownAdapterMatcherIdentifier).values()
); );
@@ -38,7 +38,7 @@ export class MixTextAdapter extends BaseAdapter<MixText> {
private readonly _markdownAdapter: MarkdownAdapter; private readonly _markdownAdapter: MarkdownAdapter;
constructor(job: Transformer, provider: ServiceProvider) { constructor(job: Transformer, provider: ServiceProvider) {
super(job); super(job, provider);
this._markdownAdapter = new MarkdownAdapter(job, provider); this._markdownAdapter = new MarkdownAdapter(job, provider);
} }
@@ -116,7 +116,7 @@ export class NotionHtmlAdapter extends BaseAdapter<NotionHtml> {
readonly blockMatchers: BlockNotionHtmlAdapterMatcher[]; readonly blockMatchers: BlockNotionHtmlAdapterMatcher[];
constructor(job: Transformer, provider: ServiceProvider) { constructor(job: Transformer, provider: ServiceProvider) {
super(job); super(job, provider);
const blockMatchers = Array.from( const blockMatchers = Array.from(
provider.getAll(BlockNotionHtmlAdapterMatcherIdentifier).values() provider.getAll(BlockNotionHtmlAdapterMatcherIdentifier).values()
); );
@@ -163,8 +163,8 @@ export const NotionTextAdapterFactoryIdentifier =
export const NotionTextAdapterFactoryExtension: ExtensionType = { export const NotionTextAdapterFactoryExtension: ExtensionType = {
setup: di => { setup: di => {
di.addImpl(NotionTextAdapterFactoryIdentifier, () => ({ di.addImpl(NotionTextAdapterFactoryIdentifier, provider => ({
get: (job: Transformer) => new NotionTextAdapter(job), get: (job: Transformer) => new NotionTextAdapter(job, provider),
})); }));
}, },
}; };
@@ -49,11 +49,8 @@ export class PlainTextAdapter extends BaseAdapter<PlainText> {
readonly blockMatchers: BlockPlainTextAdapterMatcher[]; readonly blockMatchers: BlockPlainTextAdapterMatcher[];
constructor( constructor(job: Transformer, provider: ServiceProvider) {
job: Transformer, super(job, provider);
readonly provider: ServiceProvider
) {
super(job);
const blockMatchers = Array.from( const blockMatchers = Array.from(
provider.getAll(BlockPlainTextAdapterMatcherIdentifier).values() provider.getAll(BlockPlainTextAdapterMatcherIdentifier).values()
); );
@@ -1,3 +1,4 @@
import type { ServiceProvider } from '@blocksuite/global/di';
import { BlockSuiteError } from '@blocksuite/global/exceptions'; import { BlockSuiteError } from '@blocksuite/global/exceptions';
import { import {
@@ -73,7 +74,10 @@ export abstract class BaseAdapter<AdapterTarget = unknown> {
return this.job.adapterConfigs; return this.job.adapterConfigs;
} }
constructor(job: Transformer) { constructor(
job: Transformer,
readonly provider: ServiceProvider
) {
this.job = job; this.job = job;
} }
@@ -5,12 +5,12 @@ import {
TextSelection, TextSelection,
} from '@blocksuite/affine/block-std'; } from '@blocksuite/affine/block-std';
import { defaultImageProxyMiddleware } from '@blocksuite/affine/blocks/image'; import { defaultImageProxyMiddleware } from '@blocksuite/affine/blocks/image';
import { pasteMiddleware } from '@blocksuite/affine/blocks/root';
import type { ServiceProvider } from '@blocksuite/affine/global/di'; import type { ServiceProvider } from '@blocksuite/affine/global/di';
import { import {
embedSyncedDocMiddleware, embedSyncedDocMiddleware,
MarkdownAdapter, MarkdownAdapter,
MixTextAdapter, MixTextAdapter,
pasteMiddleware,
PlainTextAdapter, PlainTextAdapter,
titleMiddleware, titleMiddleware,
} from '@blocksuite/affine/shared/adapters'; } from '@blocksuite/affine/shared/adapters';