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

@@ -0,0 +1,139 @@
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';
import { AdapterFactoryIdentifier } from './types/adapter';
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

@@ -0,0 +1,130 @@
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';
import { AdapterFactoryIdentifier } from './types/adapter';
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,3 +1,4 @@
export * from './attachment';
export {
BlockHtmlAdapterExtension,
type BlockHtmlAdapterMatcher,
@@ -14,6 +15,7 @@ export {
type InlineDeltaToHtmlAdapterMatcher,
InlineDeltaToHtmlAdapterMatcherIdentifier,
} from './html';
export * from './image';
export {
BlockMarkdownAdapterExtension,
type BlockMarkdownAdapterMatcher,
@@ -41,6 +43,7 @@ export {
NotionHtmlASTToDeltaMatcherIdentifier,
NotionHtmlDeltaConverter,
} from './notion-html';
export * from './notion-text';
export {
BlockPlainTextAdapterExtension,
type BlockPlainTextAdapterMatcher,

View File

@@ -0,0 +1,170 @@
import { DEFAULT_NOTE_BACKGROUND_COLOR } from '@blocksuite/affine-model';
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';
import type { AffineTextAttributes } from '../types';
import { AdapterFactoryIdentifier } from './types/adapter';
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),
}));
},
};