refactor(editor): extract adapters to shared (#9344)

Extract AttachmentAdapter, ImageAdapter, NotionTextAdapter to shared.
This commit is contained in:
donteatfriedrice
2024-12-27 08:52:03 +00:00
parent 153c1b2c55
commit fee86304ae
9 changed files with 30 additions and 18 deletions

View File

@@ -1,8 +1,8 @@
import { DEFAULT_NOTE_BACKGROUND_COLOR } from '@blocksuite/affine-model';
import { NotionTextAdapter } from '@blocksuite/affine-shared/adapters';
import type { SliceSnapshot } from '@blocksuite/store';
import { describe, expect, test } from 'vitest';
import { NotionTextAdapter } from '../../_common/adapters/notion-text.js';
import { nanoidReplacement } from '../../_common/test-utils/test-utils.js';
import { createJob } from '../utils/create-job.js';

View File

@@ -1,138 +0,0 @@
import { AdapterFactoryIdentifier } from '@blocksuite/affine-shared/adapters';
import type { ExtensionType } from '@blocksuite/block-std';
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import { sha } from '@blocksuite/global/utils';
import {
type AssetsManager,
BaseAdapter,
type BlockSnapshot,
type DocSnapshot,
type FromBlockSnapshotPayload,
type FromBlockSnapshotResult,
type FromDocSnapshotPayload,
type FromDocSnapshotResult,
type FromSliceSnapshotPayload,
type FromSliceSnapshotResult,
type Job,
nanoid,
type SliceSnapshot,
type ToBlockSnapshotPayload,
type ToDocSnapshotPayload,
} from '@blocksuite/store';
export type Attachment = File[];
type AttachmentToSliceSnapshotPayload = {
file: Attachment;
assets?: AssetsManager;
blockVersions: Record<string, number>;
workspaceId: string;
pageId: string;
};
export class AttachmentAdapter extends BaseAdapter<Attachment> {
override fromBlockSnapshot(
_payload: FromBlockSnapshotPayload
): Promise<FromBlockSnapshotResult<Attachment>> {
throw new BlockSuiteError(
ErrorCode.TransformerNotImplementedError,
'AttachmentAdapter.fromBlockSnapshot is not implemented.'
);
}
override fromDocSnapshot(
_payload: FromDocSnapshotPayload
): Promise<FromDocSnapshotResult<Attachment>> {
throw new BlockSuiteError(
ErrorCode.TransformerNotImplementedError,
'AttachmentAdapter.fromDocSnapshot is not implemented.'
);
}
override fromSliceSnapshot(
payload: FromSliceSnapshotPayload
): Promise<FromSliceSnapshotResult<Attachment>> {
const attachments: Attachment = [];
for (const contentSlice of payload.snapshot.content) {
if (contentSlice.type === 'block') {
const { flavour, props } = contentSlice;
if (flavour === 'affine:attachment') {
const { sourceId } = props;
const file = payload.assets?.getAssets().get(sourceId as string) as
| File
| undefined;
if (file) {
attachments.push(file);
}
}
}
}
return Promise.resolve({ file: attachments, assetsIds: [] });
}
override toBlockSnapshot(
_payload: ToBlockSnapshotPayload<Attachment>
): Promise<BlockSnapshot> {
throw new BlockSuiteError(
ErrorCode.TransformerNotImplementedError,
'AttachmentAdapter.toBlockSnapshot is not implemented.'
);
}
override toDocSnapshot(
_payload: ToDocSnapshotPayload<Attachment>
): Promise<DocSnapshot> {
throw new BlockSuiteError(
ErrorCode.TransformerNotImplementedError,
'AttachmentAdapter.toDocSnapshot is not implemented.'
);
}
override async toSliceSnapshot(
payload: AttachmentToSliceSnapshotPayload
): Promise<SliceSnapshot | null> {
const content: SliceSnapshot['content'] = [];
for (const item of payload.file) {
const blobId = await sha(await item.arrayBuffer());
payload.assets?.getAssets().set(blobId, item);
await payload.assets?.writeToBlob(blobId);
content.push({
type: 'block',
flavour: 'affine:attachment',
id: nanoid(),
props: {
name: item.name,
size: item.size,
type: item.type,
embed: false,
style: 'horizontalThin',
index: 'a0',
xywh: '[0,0,0,0]',
rotate: 0,
sourceId: blobId,
},
children: [],
});
}
if (content.length === 0) {
return null;
}
return {
type: 'slice',
content,
workspaceId: payload.workspaceId,
pageId: payload.pageId,
};
}
}
export const AttachmentAdapterFactoryIdentifier =
AdapterFactoryIdentifier('Attachment');
export const AttachmentAdapterFactoryExtension: ExtensionType = {
setup: di => {
di.addImpl(AttachmentAdapterFactoryIdentifier, () => ({
get: (job: Job) => new AttachmentAdapter(job),
}));
},
};

View File

@@ -1,18 +1,18 @@
import {
AttachmentAdapterFactoryExtension,
HtmlAdapterFactoryExtension,
ImageAdapterFactoryExtension,
NotionHtmlAdapterFactoryExtension,
NotionTextAdapterFactoryExtension,
PlainTextAdapterFactoryExtension,
} from '@blocksuite/affine-shared/adapters';
import type { ExtensionType } from '@blocksuite/block-std';
import { AttachmentAdapterFactoryExtension } from './attachment.js';
import { htmlInlineToDeltaMatchers } from './html/delta-converter/html-inline.js';
import { inlineDeltaToHtmlAdapterMatchers } from './html/delta-converter/inline-delta.js';
import { ImageAdapterFactoryExtension } from './image.js';
import { MarkdownAdapterFactoryExtension } from './markdown/markdown.js';
import { MixTextAdapterFactoryExtension } from './mix-text.js';
import { notionHtmlInlineToDeltaMatchers } from './notion-html/delta-converter/html-inline.js';
import { NotionTextAdapterFactoryExtension } from './notion-text.js';
import { inlineDeltaToPlainTextAdapterMatchers } from './plain-text/delta-converter/inline-delta.js';
export const AdapterFactoryExtensions: ExtensionType[] = [

View File

@@ -1,129 +0,0 @@
import { AdapterFactoryIdentifier } from '@blocksuite/affine-shared/adapters';
import type { ExtensionType } from '@blocksuite/block-std';
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import { sha } from '@blocksuite/global/utils';
import {
type AssetsManager,
BaseAdapter,
type BlockSnapshot,
type DocSnapshot,
type FromBlockSnapshotPayload,
type FromBlockSnapshotResult,
type FromDocSnapshotPayload,
type FromDocSnapshotResult,
type FromSliceSnapshotPayload,
type FromSliceSnapshotResult,
type Job,
nanoid,
type SliceSnapshot,
type ToBlockSnapshotPayload,
type ToDocSnapshotPayload,
} from '@blocksuite/store';
export type Image = File[];
type ImageToSliceSnapshotPayload = {
file: Image;
assets?: AssetsManager;
blockVersions: Record<string, number>;
workspaceId: string;
pageId: string;
};
export class ImageAdapter extends BaseAdapter<Image> {
override fromBlockSnapshot(
_payload: FromBlockSnapshotPayload
): Promise<FromBlockSnapshotResult<Image>> {
throw new BlockSuiteError(
ErrorCode.TransformerNotImplementedError,
'ImageAdapter.fromBlockSnapshot is not implemented.'
);
}
override fromDocSnapshot(
_payload: FromDocSnapshotPayload
): Promise<FromDocSnapshotResult<Image>> {
throw new BlockSuiteError(
ErrorCode.TransformerNotImplementedError,
'ImageAdapter.fromDocSnapshot is not implemented.'
);
}
override fromSliceSnapshot(
payload: FromSliceSnapshotPayload
): Promise<FromSliceSnapshotResult<Image>> {
const images: Image = [];
for (const contentSlice of payload.snapshot.content) {
if (contentSlice.type === 'block') {
const { flavour, props } = contentSlice;
if (flavour === 'affine:image') {
const { sourceId } = props;
const file = payload.assets?.getAssets().get(sourceId as string) as
| File
| undefined;
if (file) {
images.push(file);
}
}
}
}
return Promise.resolve({ file: images, assetsIds: [] });
}
override toBlockSnapshot(
_payload: ToBlockSnapshotPayload<Image>
): Promise<BlockSnapshot> {
throw new BlockSuiteError(
ErrorCode.TransformerNotImplementedError,
'ImageAdapter.toBlockSnapshot is not implemented.'
);
}
override toDocSnapshot(
_payload: ToDocSnapshotPayload<Image>
): Promise<DocSnapshot> {
throw new BlockSuiteError(
ErrorCode.TransformerNotImplementedError,
'ImageAdapter.toDocSnapshot is not implemented'
);
}
override async toSliceSnapshot(
payload: ImageToSliceSnapshotPayload
): Promise<SliceSnapshot | null> {
const content: SliceSnapshot['content'] = [];
for (const item of payload.file) {
const blobId = await sha(await item.arrayBuffer());
payload.assets?.getAssets().set(blobId, item);
await payload.assets?.writeToBlob(blobId);
content.push({
type: 'block',
flavour: 'affine:image',
id: nanoid(),
props: {
sourceId: blobId,
},
children: [],
});
}
if (content.length === 0) {
return null;
}
return {
type: 'slice',
content,
workspaceId: payload.workspaceId,
pageId: payload.pageId,
};
}
}
export const ImageAdapterFactoryIdentifier = AdapterFactoryIdentifier('Image');
export const ImageAdapterFactoryExtension: ExtensionType = {
setup: di => {
di.addImpl(ImageAdapterFactoryIdentifier, () => ({
get: (job: Job) => new ImageAdapter(job),
}));
},
};

View File

@@ -1,7 +1,4 @@
export * from './attachment.js';
export * from './extension.js';
export * from './image.js';
export * from './markdown/index.js';
export * from './mix-text.js';
export * from './notion-html/index.js';
export * from './notion-text.js';

View File

@@ -1,169 +0,0 @@
import { DEFAULT_NOTE_BACKGROUND_COLOR } from '@blocksuite/affine-model';
import { AdapterFactoryIdentifier } from '@blocksuite/affine-shared/adapters';
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
import type { ExtensionType } from '@blocksuite/block-std';
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import type { DeltaInsert } from '@blocksuite/inline';
import {
type AssetsManager,
BaseAdapter,
type BlockSnapshot,
type DocSnapshot,
type FromBlockSnapshotResult,
type FromDocSnapshotResult,
type FromSliceSnapshotResult,
type Job,
nanoid,
type SliceSnapshot,
} from '@blocksuite/store';
type NotionEditingStyle = {
0: string;
};
type NotionEditing = {
0: string;
1: Array<NotionEditingStyle>;
};
export type NotionTextSerialized = {
blockType: string;
editing: Array<NotionEditing>;
};
export type NotionText = string;
type NotionHtmlToSliceSnapshotPayload = {
file: NotionText;
assets?: AssetsManager;
workspaceId: string;
pageId: string;
};
export class NotionTextAdapter extends BaseAdapter<NotionText> {
override fromBlockSnapshot():
| FromBlockSnapshotResult<NotionText>
| Promise<FromBlockSnapshotResult<NotionText>> {
throw new BlockSuiteError(
ErrorCode.TransformerNotImplementedError,
'NotionTextAdapter.fromBlockSnapshot is not implemented.'
);
}
override fromDocSnapshot():
| FromDocSnapshotResult<NotionText>
| Promise<FromDocSnapshotResult<NotionText>> {
throw new BlockSuiteError(
ErrorCode.TransformerNotImplementedError,
'NotionTextAdapter.fromDocSnapshot is not implemented.'
);
}
override fromSliceSnapshot():
| FromSliceSnapshotResult<NotionText>
| Promise<FromSliceSnapshotResult<NotionText>> {
return {
file: JSON.stringify({
blockType: 'text',
editing: [
['Notion Text is not supported to be exported from BlockSuite', []],
],
}),
assetsIds: [],
};
}
override toBlockSnapshot(): Promise<BlockSnapshot> | BlockSnapshot {
throw new BlockSuiteError(
ErrorCode.TransformerNotImplementedError,
'NotionTextAdapter.toBlockSnapshot is not implemented.'
);
}
override toDocSnapshot(): Promise<DocSnapshot> | DocSnapshot {
throw new BlockSuiteError(
ErrorCode.TransformerNotImplementedError,
'NotionTextAdapter.toDocSnapshot is not implemented.'
);
}
override toSliceSnapshot(
payload: NotionHtmlToSliceSnapshotPayload
): SliceSnapshot | null {
const notionText = JSON.parse(payload.file) as NotionTextSerialized;
const content: SliceSnapshot['content'] = [];
const deltas: DeltaInsert<AffineTextAttributes>[] = [];
for (const editing of notionText.editing) {
const delta: DeltaInsert<AffineTextAttributes> = {
insert: editing[0],
attributes: Object.create(null),
};
for (const styleElement of editing[1]) {
switch (styleElement[0]) {
case 'b':
delta.attributes!.bold = true;
break;
case 'i':
delta.attributes!.italic = true;
break;
case '_':
delta.attributes!.underline = true;
break;
case 'c':
delta.attributes!.code = true;
break;
case 's':
delta.attributes!.strike = true;
break;
}
}
deltas.push(delta);
}
content.push({
type: 'block',
id: nanoid(),
flavour: 'affine:note',
props: {
xywh: '[0,0,800,95]',
background: DEFAULT_NOTE_BACKGROUND_COLOR,
index: 'a0',
hidden: false,
displayMode: 'both',
},
children: [
{
type: 'block',
id: nanoid(),
flavour: 'affine:paragraph',
props: {
type: 'text',
text: {
'$blocksuite:internal:text$': true,
delta: deltas,
},
},
children: [],
},
],
});
return {
type: 'slice',
content,
workspaceId: payload.workspaceId,
pageId: payload.pageId,
};
}
}
export const NotionTextAdapterFactoryIdentifier =
AdapterFactoryIdentifier('NotionText');
export const NotionTextAdapterFactoryExtension: ExtensionType = {
setup: di => {
di.addImpl(NotionTextAdapterFactoryIdentifier, () => ({
get: (job: Job) => new NotionTextAdapter(job),
}));
},
};

View File

@@ -100,9 +100,18 @@ export {
} from '@blocksuite/affine-components/toolbar';
export * from '@blocksuite/affine-model';
export {
AttachmentAdapter,
AttachmentAdapterFactoryExtension,
AttachmentAdapterFactoryIdentifier,
HtmlAdapter,
HtmlAdapterFactoryExtension,
HtmlAdapterFactoryIdentifier,
ImageAdapter,
ImageAdapterFactoryExtension,
ImageAdapterFactoryIdentifier,
NotionTextAdapter,
NotionTextAdapterFactoryExtension,
NotionTextAdapterFactoryIdentifier,
PlainTextAdapter,
PlainTextAdapterFactoryExtension,
PlainTextAdapterFactoryIdentifier,

View File

@@ -1,14 +1,14 @@
import { HtmlAdapter } from '@blocksuite/affine-shared/adapters';
import {
AttachmentAdapter,
HtmlAdapter,
ImageAdapter,
NotionTextAdapter,
} from '@blocksuite/affine-shared/adapters';
import type { BlockComponent, UIEventHandler } from '@blocksuite/block-std';
import { DisposableGroup } from '@blocksuite/global/utils';
import type { BlockSnapshot, Doc } from '@blocksuite/store';
import {
AttachmentAdapter,
ImageAdapter,
MixTextAdapter,
NotionTextAdapter,
} from '../../_common/adapters/index.js';
import { MixTextAdapter } from '../../_common/adapters/index.js';
import {
defaultImageProxyMiddleware,
replaceIdMiddleware,