refactor: move image proxy middleware and adapter extensions (#10345)

### TL;DR
Moved image proxy middleware and adapter extensions to their respective packages and introduced a new spec provider for adapter registration.

### What changed?
- Relocated `defaultImageProxyMiddleware` from blocks to `@blocksuite/affine-block-image`
- Moved `PresentTool` from fragment-frame-panel to block-frame
- Created new adapter extension specs for HTML, Markdown, and Notion HTML
- Introduced a spec provider pattern for adapter registration
- Removed direct transformer references from RootService
- Updated imports across affected files to use new locations

### How to test?
1. Verify image proxy functionality works in exports and imports
2. Test HTML, Markdown, and Notion HTML adapters still function correctly
3. Confirm presentation mode works with the relocated PresentTool
4. Check that all file import/export operations continue to work as expected

### Why make this change?
This reorganization improves code modularity by placing features in their logical packages and introduces a more maintainable pattern for adapter registration. The spec provider pattern makes it easier to manage and extend adapter functionality while reducing coupling between components.
This commit is contained in:
Saul-Mirone
2025-02-21 09:01:57 +00:00
parent 296d88f721
commit 22e4bd8c20
64 changed files with 301 additions and 329 deletions

View File

@@ -3,7 +3,10 @@ import {
InlineDeltaToHtmlAdapterExtensions,
} from '@blocksuite/affine-components/rich-text';
import { DefaultTheme, NoteDisplayMode } from '@blocksuite/affine-model';
import { HtmlAdapter } from '@blocksuite/affine-shared/adapters';
import {
embedSyncedDocMiddleware,
HtmlAdapter,
} from '@blocksuite/affine-shared/adapters';
import { Container } from '@blocksuite/global/di';
import type {
BlockSnapshot,
@@ -14,7 +17,6 @@ import { AssetsManager, MemoryBlobCRUD } from '@blocksuite/store';
import { describe, expect, test } from 'vitest';
import { defaultBlockHtmlAdapterMatchers } from '../../_common/adapters/html/block-matcher.js';
import { embedSyncedDocMiddleware } from '../../_common/transformers/middlewares.js';
import { createJob } from '../utils/create-job.js';
import { nanoidReplacement } from '../utils/nanoid-replacement.js';

View File

@@ -7,7 +7,10 @@ import {
NoteDisplayMode,
TableModelFlavour,
} from '@blocksuite/affine-model';
import { MarkdownAdapter } from '@blocksuite/affine-shared/adapters';
import {
embedSyncedDocMiddleware,
MarkdownAdapter,
} from '@blocksuite/affine-shared/adapters';
import { Container } from '@blocksuite/global/di';
import type {
BlockSnapshot,
@@ -19,7 +22,6 @@ import { AssetsManager, MemoryBlobCRUD } from '@blocksuite/store';
import { describe, expect, test } from 'vitest';
import { defaultBlockMarkdownAdapterMatchers } from '../../_common/adapters/markdown/block-matcher.js';
import { embedSyncedDocMiddleware } from '../../_common/transformers/middlewares.js';
import { createJob } from '../utils/create-job.js';
import { nanoidReplacement } from '../utils/nanoid-replacement.js';

View File

@@ -1,6 +1,9 @@
import { InlineDeltaToPlainTextAdapterExtensions } from '@blocksuite/affine-components/rich-text';
import { DefaultTheme, NoteDisplayMode } from '@blocksuite/affine-model';
import { PlainTextAdapter } from '@blocksuite/affine-shared/adapters';
import {
embedSyncedDocMiddleware,
PlainTextAdapter,
} from '@blocksuite/affine-shared/adapters';
import { Container } from '@blocksuite/global/di';
import type {
BlockSnapshot,
@@ -10,7 +13,6 @@ import type {
import { describe, expect, test } from 'vitest';
import { defaultBlockPlainTextAdapterMatchers } from '../../_common/adapters/plain-text/block-matcher.js';
import { embedSyncedDocMiddleware } from '../../_common/transformers/middlewares.js';
import { createJob } from '../utils/create-job.js';
const container = new Container();

View File

@@ -1,3 +1,4 @@
import { defaultImageProxyMiddleware } from '@blocksuite/affine-block-image';
import { FeatureFlagService } from '@blocksuite/affine-shared/services';
import {
Schema,
@@ -6,7 +7,6 @@ import {
} from '@blocksuite/store';
import { TestWorkspace } from '@blocksuite/store/test';
import { defaultImageProxyMiddleware } from '../../_common/transformers/middlewares.js';
import { AffineSchemas } from '../../schemas.js';
declare global {

View File

@@ -1,15 +0,0 @@
export { HtmlTransformer } from './html.js';
export { MarkdownTransformer } from './markdown.js';
export {
customImageProxyMiddleware,
defaultImageProxyMiddleware,
docLinkBaseURLMiddleware,
docLinkBaseURLMiddlewareBuilder,
embedSyncedDocMiddleware,
replaceIdMiddleware,
setImageProxyMiddlewareURL,
titleMiddleware,
} from './middlewares.js';
export { NotionHtmlTransformer } from './notion-html.js';
export { createAssetsArchive, download } from './utils.js';
export { ZipTransformer } from './zip.js';

View File

@@ -1,305 +0,0 @@
import type {
DatabaseBlockModel,
EmbedLinkedDocModel,
EmbedSyncedDocModel,
ListBlockModel,
ParagraphBlockModel,
SurfaceRefBlockModel,
} from '@blocksuite/affine-model';
import { DEFAULT_IMAGE_PROXY_ENDPOINT } from '@blocksuite/affine-shared/consts';
import { assertExists } from '@blocksuite/global/utils';
import type {
DeltaOperation,
DocMeta,
TransformerMiddleware,
} from '@blocksuite/store';
export const replaceIdMiddleware =
(idGenerator: () => string): TransformerMiddleware =>
({ slots, docCRUD }) => {
const idMap = new Map<string, string>();
slots.afterImport.on(payload => {
if (
payload.type === 'block' &&
payload.snapshot.flavour === 'affine:database'
) {
const model = payload.model as DatabaseBlockModel;
Object.keys(model.cells).forEach(cellId => {
if (idMap.has(cellId)) {
model.cells[idMap.get(cellId)!] = model.cells[cellId];
delete model.cells[cellId];
}
});
}
// replace LinkedPage pageId with new id in paragraph blocks
if (
payload.type === 'block' &&
['affine:list', 'affine:paragraph'].includes(payload.snapshot.flavour)
) {
const model = payload.model as ParagraphBlockModel | ListBlockModel;
let prev = 0;
const delta: DeltaOperation[] = [];
for (const d of model.text.toDelta()) {
if (d.attributes?.reference?.pageId) {
const newId = idMap.get(d.attributes.reference.pageId);
if (!newId) {
prev += d.insert?.length ?? 0;
continue;
}
if (prev > 0) {
delta.push({ retain: prev });
}
delta.push({
retain: d.insert?.length ?? 0,
attributes: {
reference: {
...d.attributes.reference,
pageId: newId,
},
},
});
prev = 0;
} else {
prev += d.insert?.length ?? 0;
}
}
if (delta.length > 0) {
model.text.applyDelta(delta);
}
}
if (
payload.type === 'block' &&
payload.snapshot.flavour === 'affine:surface-ref'
) {
const model = payload.model as SurfaceRefBlockModel;
const original = model.reference;
// If there exists a replacement, replace the reference with the new id.
// Otherwise,
// 1. If the reference is an affine:frame not in doc, generate a new id.
// 2. If the reference is graph, keep the original id.
if (idMap.has(original)) {
model.reference = idMap.get(original)!;
} else if (
model.refFlavour === 'affine:frame' &&
!model.doc.hasBlock(original)
) {
const newId = idGenerator();
idMap.set(original, newId);
model.reference = newId;
}
}
// TODO(@fundon): process linked block/element
if (
payload.type === 'block' &&
['affine:embed-linked-doc', 'affine:embed-synced-doc'].includes(
payload.snapshot.flavour
)
) {
const model = payload.model as
| EmbedLinkedDocModel
| EmbedSyncedDocModel;
const original = model.pageId;
// If the pageId is not in the doc, generate a new id.
// If we already have a replacement, use it.
if (!docCRUD.get(original)) {
if (idMap.has(original)) {
model.pageId = idMap.get(original)!;
} else {
const newId = idGenerator();
idMap.set(original, newId);
model.pageId = newId;
}
}
}
});
slots.beforeImport.on(payload => {
if (payload.type === 'page') {
if (idMap.has(payload.snapshot.meta.id)) {
payload.snapshot.meta.id = idMap.get(payload.snapshot.meta.id)!;
return;
}
const newId = idGenerator();
idMap.set(payload.snapshot.meta.id, newId);
payload.snapshot.meta.id = newId;
return;
}
if (payload.type === 'block') {
const { snapshot } = payload;
if (snapshot.flavour === 'affine:page') {
const index = snapshot.children.findIndex(
c => c.flavour === 'affine:surface'
);
if (index !== -1) {
const [surface] = snapshot.children.splice(index, 1);
snapshot.children.push(surface);
}
}
const original = snapshot.id;
let newId: string;
if (idMap.has(original)) {
newId = idMap.get(original)!;
} else {
newId = idGenerator();
idMap.set(original, newId);
}
snapshot.id = newId;
if (snapshot.flavour === 'affine:surface') {
// Generate new IDs for images and frames in advance.
snapshot.children.forEach(child => {
const original = child.id;
if (idMap.has(original)) {
newId = idMap.get(original)!;
} else {
newId = idGenerator();
idMap.set(original, newId);
}
});
Object.entries(
snapshot.props.elements as Record<string, Record<string, unknown>>
).forEach(([_, value]) => {
switch (value.type) {
case 'connector': {
let connection = value.source as Record<string, string>;
if (idMap.has(connection.id)) {
const newId = idMap.get(connection.id);
assertExists(newId, 'reference id must exist');
connection.id = newId;
}
connection = value.target as Record<string, string>;
if (idMap.has(connection.id)) {
const newId = idMap.get(connection.id);
assertExists(newId, 'reference id must exist');
connection.id = newId;
}
break;
}
case 'group': {
const json = (value.children as Record<string, unknown>)
.json as Record<string, unknown>;
Object.entries(json).forEach(([key, value]) => {
if (idMap.has(key)) {
delete json[key];
const newKey = idMap.get(key);
assertExists(newKey, 'reference id must exist');
json[newKey] = value;
}
});
break;
}
default:
break;
}
});
}
}
});
};
export const customImageProxyMiddleware = (
imageProxyURL: string
): TransformerMiddleware => {
return ({ adapterConfigs }) => {
adapterConfigs.set('imageProxy', imageProxyURL);
};
};
const customDocLinkBaseUrlMiddleware = (
baseUrl: string,
collectionId: string
): TransformerMiddleware => {
return ({ adapterConfigs }) => {
const docLinkBaseUrl = baseUrl
? `${baseUrl}/workspace/${collectionId}`
: '';
adapterConfigs.set('docLinkBaseUrl', docLinkBaseUrl);
};
};
export const titleMiddleware =
(metas: DocMeta[]): TransformerMiddleware =>
({ slots, adapterConfigs }) => {
slots.beforeExport.on(() => {
for (const meta of metas) {
adapterConfigs.set('title:' + meta.id, meta.title);
}
});
};
export const docLinkBaseURLMiddlewareBuilder = (
baseUrl: string,
collectionId: string
) => {
let middleware = customDocLinkBaseUrlMiddleware(baseUrl, collectionId);
return {
get: () => middleware,
set: (url: string) => {
middleware = customDocLinkBaseUrlMiddleware(url, collectionId);
},
};
};
const defaultDocLinkBaseURLMiddlewareBuilder = (collectionId: string) =>
docLinkBaseURLMiddlewareBuilder(
typeof window !== 'undefined' ? window.location.origin : '.',
collectionId
);
export const docLinkBaseURLMiddleware = (collectionId: string) =>
defaultDocLinkBaseURLMiddlewareBuilder(collectionId).get();
export const setDocLinkBaseURLMiddleware = (collectionId: string) =>
defaultDocLinkBaseURLMiddlewareBuilder(collectionId).set;
const imageProxyMiddlewareBuilder = () => {
let middleware = customImageProxyMiddleware(DEFAULT_IMAGE_PROXY_ENDPOINT);
return {
get: () => middleware,
set: (url: string) => {
middleware = customImageProxyMiddleware(url);
},
};
};
const defaultImageProxyMiddlewarBuilder = imageProxyMiddlewareBuilder();
export const setImageProxyMiddlewareURL = defaultImageProxyMiddlewarBuilder.set;
export const defaultImageProxyMiddleware =
defaultImageProxyMiddlewarBuilder.get();
export const embedSyncedDocMiddleware =
(type: 'content'): TransformerMiddleware =>
({ adapterConfigs }) => {
adapterConfigs.set('embedSyncedDocExportType', type);
};
export const fileNameMiddleware =
(fileName?: string): TransformerMiddleware =>
({ slots }) => {
slots.beforeImport.on(payload => {
if (payload.type !== 'page') {
return;
}
if (!fileName) {
return;
}
payload.snapshot.meta.title = fileName;
payload.snapshot.blocks.props.title = {
'$blocksuite:internal:text$': true,
delta: [
{
insert: fileName,
},
],
};
});
};

View File

@@ -1,11 +0,0 @@
import type {
BrushElementModel,
ConnectorElementModel,
GroupElementModel,
} from '@blocksuite/affine-model';
import type { GfxModel } from '@blocksuite/block-std/gfx';
export type Connectable = Exclude<
GfxModel,
ConnectorElementModel | BrushElementModel | GroupElementModel
>;

View File

@@ -56,6 +56,11 @@ import {
import type { ExtensionType } from '@blocksuite/store';
import { AdapterFactoryExtensions } from '../_common/adapters/extension.js';
import {
HtmlAdapterExtension,
MarkdownAdapterExtension,
NotionHtmlAdapterExtension,
} from './preset/adapters.js';
export const CommonBlockSpecs: ExtensionType[] = [
DocDisplayMetaService,
@@ -110,4 +115,8 @@ export const StoreExtensions: ExtensionType[] = [
LinkPreviewerService,
FileSizeLimitService,
ImageStoreSpec,
HtmlAdapterExtension,
MarkdownAdapterExtension,
NotionHtmlAdapterExtension,
].flat();

View File

@@ -0,0 +1,31 @@
import {
HtmlInlineToDeltaAdapterExtensions,
InlineDeltaToHtmlAdapterExtensions,
InlineDeltaToMarkdownAdapterExtensions,
MarkdownInlineToDeltaAdapterExtensions,
NotionHtmlInlineToDeltaAdapterExtensions,
} from '@blocksuite/affine-components/rich-text';
import type { ExtensionType } from '@blocksuite/store';
import {
defaultBlockHtmlAdapterMatchers,
defaultBlockMarkdownAdapterMatchers,
defaultBlockNotionHtmlAdapterMatchers,
} from '../../_common/adapters';
export const HtmlAdapterExtension: ExtensionType[] = [
...HtmlInlineToDeltaAdapterExtensions,
...defaultBlockHtmlAdapterMatchers,
...InlineDeltaToHtmlAdapterExtensions,
];
export const MarkdownAdapterExtension: ExtensionType[] = [
...MarkdownInlineToDeltaAdapterExtensions,
...defaultBlockMarkdownAdapterMatchers,
...InlineDeltaToMarkdownAdapterExtensions,
];
export const NotionHtmlAdapterExtension: ExtensionType[] = [
...NotionHtmlInlineToDeltaAdapterExtensions,
...defaultBlockNotionHtmlAdapterMatchers,
];

View File

@@ -1,9 +1,9 @@
import {
EdgelessFrameManager,
FrameOverlay,
PresentTool,
} from '@blocksuite/affine-block-frame';
import { ConnectionOverlay } from '@blocksuite/affine-block-surface';
import { PresentTool } from '@blocksuite/affine-fragment-frame-panel';
import type { ExtensionType } from '@blocksuite/store';
import { EdgelessRootBlockSpec } from '../../root-block/edgeless/edgeless-root-spec.js';

View File

@@ -1,6 +1,6 @@
import { SpecProvider } from '@blocksuite/affine-shared/utils';
import { CommonBlockSpecs, StoreExtensions } from './common.js';
import { StoreExtensions } from './common.js';
import { EdgelessEditorBlockSpecs } from './preset/edgeless-specs.js';
import { PageEditorBlockSpecs } from './preset/page-specs.js';
import {
@@ -10,15 +10,14 @@ import {
export function registerSpecs() {
SpecProvider.getInstance().addSpec('store', StoreExtensions);
SpecProvider.getInstance().addSpec('common', CommonBlockSpecs);
SpecProvider.getInstance().addSpec('page', PageEditorBlockSpecs);
SpecProvider.getInstance().addSpec('edgeless', EdgelessEditorBlockSpecs);
SpecProvider.getInstance().addSpec(
'page:preview',
'preview:page',
PreviewPageEditorBlockSpecs
);
SpecProvider.getInstance().addSpec(
'edgeless:preview',
'preview:edgeless',
PreviewEdgelessEditorBlockSpecs
);
}

View File

@@ -7,7 +7,6 @@ import { splitElements } from './root-block/edgeless/utils/clipboard-utils.js';
import { isCanvasElement } from './root-block/edgeless/utils/query.js';
export * from './_common/adapters/index.js';
export * from './_common/transformers/index.js';
export * from './_specs/index.js';
export { EdgelessTemplatePanel } from './root-block/edgeless/components/toolbar/template/template-panel.js';
export type {
@@ -93,31 +92,7 @@ export {
export * from '@blocksuite/affine-fragment-frame-panel';
export * from '@blocksuite/affine-fragment-outline';
export * from '@blocksuite/affine-model';
export {
AttachmentAdapter,
AttachmentAdapterFactoryExtension,
AttachmentAdapterFactoryIdentifier,
codeBlockWrapMiddleware,
FetchUtils,
HtmlAdapter,
HtmlAdapterFactoryExtension,
HtmlAdapterFactoryIdentifier,
ImageAdapter,
ImageAdapterFactoryExtension,
ImageAdapterFactoryIdentifier,
MarkdownAdapter,
MarkdownAdapterFactoryExtension,
MarkdownAdapterFactoryIdentifier,
MixTextAdapter,
MixTextAdapterFactoryExtension,
MixTextAdapterFactoryIdentifier,
NotionTextAdapter,
NotionTextAdapterFactoryExtension,
NotionTextAdapterFactoryIdentifier,
PlainTextAdapter,
PlainTextAdapterFactoryExtension,
PlainTextAdapterFactoryIdentifier,
} from '@blocksuite/affine-shared/adapters';
export * from '@blocksuite/affine-shared/adapters';
export * from '@blocksuite/affine-shared/commands';
export { HighlightSelection } from '@blocksuite/affine-shared/selection';
export * from '@blocksuite/affine-shared/services';

View File

@@ -1,6 +1,7 @@
import { deleteTextCommand } from '@blocksuite/affine-components/rich-text';
import {
pasteMiddleware,
replaceIdMiddleware,
surfaceRefToEmbed,
} from '@blocksuite/affine-shared/adapters';
import {
@@ -17,7 +18,6 @@ import type { UIEventHandler } from '@blocksuite/block-std';
import { DisposableGroup } from '@blocksuite/global/utils';
import type { BlockSnapshot, Store } from '@blocksuite/store';
import { replaceIdMiddleware } from '../../_common/transformers/middlewares';
import { ReadOnlyClipboard } from './readonly-clipboard';
/**

View File

@@ -1,3 +1,4 @@
import { defaultImageProxyMiddleware } from '@blocksuite/affine-block-image';
import {
AttachmentAdapter,
copyMiddleware,
@@ -5,6 +6,7 @@ import {
ImageAdapter,
MixTextAdapter,
NotionTextAdapter,
titleMiddleware,
} from '@blocksuite/affine-shared/adapters';
import {
copySelectedModelsCommand,
@@ -14,10 +16,6 @@ import {
import type { BlockComponent, UIEventHandler } from '@blocksuite/block-std';
import { DisposableGroup } from '@blocksuite/global/utils';
import {
defaultImageProxyMiddleware,
titleMiddleware,
} from '../../_common/transformers/middlewares.js';
import { ClipboardAdapter } from './adapter.js';
/**

View File

@@ -81,7 +81,7 @@ export class FramePreview extends WithDisposable(ShadowlessElement) {
private _previewDoc: Store | null = null;
private readonly _previewSpec =
SpecProvider.getInstance().getSpec('edgeless:preview');
SpecProvider.getInstance().getSpec('preview:edgeless');
private readonly _updateFrameViewportWH = () => {
const [, , w, h] = deserializeXYWH(this.frame.xywh);

View File

@@ -31,11 +31,6 @@ import {
import { IS_MAC } from '@blocksuite/global/env';
import { Bound, getCommonBound } from '@blocksuite/global/utils';
import {
getNearestTranslation,
isElementOutsideViewport,
isSingleMindMapNode,
} from '../../_common/edgeless/mindmap/index.js';
import { PageKeyboardManager } from '../keyboard/keyboard-manager.js';
import type { EdgelessRootBlockComponent } from './edgeless-root-block.js';
import { CopilotTool } from './gfx-tool/copilot-tool.js';
@@ -48,6 +43,11 @@ import {
} from './utils/consts.js';
import { deleteElements } from './utils/crud.js';
import { getNextShapeType } from './utils/hotkey-utils.js';
import {
getNearestTranslation,
isElementOutsideViewport,
isSingleMindMapNode,
} from './utils/mindmap.js';
import { isCanvasElement } from './utils/query.js';
import {
mountConnectorLabelEditor,

View File

@@ -45,12 +45,12 @@ import { css, html } from 'lit';
import { query } from 'lit/decorators.js';
import { repeat } from 'lit/directives/repeat.js';
import { isSingleMindMapNode } from '../../_common/edgeless/mindmap/index.js';
import type { EdgelessRootBlockWidgetName } from '../types.js';
import { EdgelessClipboardController } from './clipboard/clipboard.js';
import type { EdgelessSelectedRectWidget } from './components/rects/edgeless-selected-rect.js';
import { EdgelessPageKeyboardManager } from './edgeless-keyboard.js';
import type { EdgelessRootService } from './edgeless-root-service.js';
import { isSingleMindMapNode } from './utils/mindmap.js';
import { isCanvasElement } from './utils/query.js';
import { mountShapeTextEditor } from './utils/text.js';

View File

@@ -19,7 +19,7 @@ import {
} from '@blocksuite/block-std/gfx';
import type { Bound, IVec } from '@blocksuite/global/utils';
import { isSingleMindMapNode } from '../../../../../_common/edgeless/mindmap/index.js';
import { isSingleMindMapNode } from '../../../utils/mindmap.js';
import { isMindmapNode } from '../../../utils/query.js';
import { DefaultModeDragType, DefaultToolExt, type DragState } from '../ext.js';
import { calculateResponseArea } from './drag-utils.js';

View File

@@ -53,9 +53,9 @@ import {
} from '@blocksuite/global/utils';
import { effect } from '@preact/signals-core';
import { isSingleMindMapNode } from '../../../_common/edgeless/mindmap/index.js';
import type { EdgelessRootBlockComponent } from '../edgeless-root-block.js';
import { prepareCloneData } from '../utils/clone-utils.js';
import { isSingleMindMapNode } from '../utils/mindmap.js';
import { calPanDelta } from '../utils/panning-utils.js';
import { isCanvasElement, isEdgelessTextBlock } from '../utils/query.js';
import type { EdgelessSnapManager } from '../utils/snap-manager.js';

View File

@@ -1,7 +1,7 @@
import { isNoteBlock } from '@blocksuite/affine-block-surface';
import type { Connectable } from '@blocksuite/affine-model';
import type { GfxModel } from '@blocksuite/block-std/gfx';
import type { Connectable } from '../../../_common/types.js';
import type { EdgelessRootBlockComponent } from '../index.js';
import { isConnectable } from './query.js';

View File

@@ -2,6 +2,7 @@ import type { CanvasElementWithText } from '@blocksuite/affine-block-surface';
import {
type AttachmentBlockModel,
type BookmarkBlockModel,
type Connectable,
ConnectorElementModel,
type EdgelessTextBlockModel,
type EmbedBlockModel,
@@ -32,8 +33,6 @@ import type { PointLocation } from '@blocksuite/global/utils';
import { Bound } from '@blocksuite/global/utils';
import type { BlockModel } from '@blocksuite/store';
import type { Connectable } from '../../../_common/types';
export function isMindmapNode(element: GfxBlockElementModel | GfxModel | null) {
return element?.group instanceof MindmapElementModel;
}

View File

@@ -11,6 +11,7 @@ export * from './page/page-root-spec.js';
export * from './preview/preview-root-block.js';
export * from './root-config.js';
export { RootService } from './root-service.js';
export * from './transformers/index.js';
export * from './types.js';
export * from './utils/index.js';
export * from './widgets/index.js';

View File

@@ -8,22 +8,11 @@ import {
import type { BlockComponent } from '@blocksuite/block-std';
import { BlockService } from '@blocksuite/block-std';
import {
HtmlTransformer,
MarkdownTransformer,
ZipTransformer,
} from '../_common/transformers/index.js';
import type { RootBlockComponent } from './types.js';
export abstract class RootService extends BlockService {
static override readonly flavour = RootBlockSchema.model.flavour;
transformers = {
markdown: MarkdownTransformer,
html: HtmlTransformer,
zip: ZipTransformer,
};
get selectedBlocks() {
let result: BlockComponent[] = [];
this.std.command

View File

@@ -1,20 +1,16 @@
import { defaultImageProxyMiddleware } from '@blocksuite/affine-block-image';
import {
HtmlInlineToDeltaAdapterExtensions,
InlineDeltaToHtmlAdapterExtensions,
} from '@blocksuite/affine-components/rich-text';
import { HtmlAdapter } from '@blocksuite/affine-shared/adapters';
docLinkBaseURLMiddleware,
fileNameMiddleware,
HtmlAdapter,
titleMiddleware,
} from '@blocksuite/affine-shared/adapters';
import { SpecProvider } from '@blocksuite/affine-shared/utils';
import { Container } from '@blocksuite/global/di';
import { sha } from '@blocksuite/global/utils';
import type { Store, Workspace } from '@blocksuite/store';
import { extMimeMap, Transformer } from '@blocksuite/store';
import { defaultBlockHtmlAdapterMatchers } from '../adapters/html/block-matcher.js';
import {
defaultImageProxyMiddleware,
docLinkBaseURLMiddleware,
fileNameMiddleware,
titleMiddleware,
} from './middlewares.js';
import { createAssetsArchive, download, Unzip } from './utils.js';
type ImportHTMLToDocOptions = {
@@ -28,16 +24,14 @@ type ImportHTMLZipOptions = {
imported: Blob;
};
const container = new Container();
[
...HtmlInlineToDeltaAdapterExtensions,
...defaultBlockHtmlAdapterMatchers,
...InlineDeltaToHtmlAdapterExtensions,
].forEach(ext => {
ext.setup(container);
});
const provider = container.provider();
function getProvider() {
const container = new Container();
const exts = SpecProvider.getInstance().getSpec('store').value;
exts.forEach(ext => {
ext.setup(container);
});
return container.provider();
}
/**
* Exports a doc to HTML format.
@@ -46,6 +40,7 @@ const provider = container.provider();
* @returns A Promise that resolves when the export is complete.
*/
async function exportDoc(doc: Store) {
const provider = getProvider();
const job = new Transformer({
schema: doc.schema,
blobCRUD: doc.blobSync,
@@ -101,6 +96,7 @@ async function importHTMLToDoc({
html,
fileName,
}: ImportHTMLToDocOptions) {
const provider = getProvider();
const job = new Transformer({
schema: collection.schema,
blobCRUD: collection.blobSync,
@@ -135,6 +131,7 @@ async function importHTMLToDoc({
* @returns A Promise that resolves to an array of IDs of the newly created docs.
*/
async function importHTMLZip({ collection, imported }: ImportHTMLZipOptions) {
const provider = getProvider();
const unzip = new Unzip();
await unzip.load(imported);

View File

@@ -0,0 +1,5 @@
export { HtmlTransformer } from './html.js';
export { MarkdownTransformer } from './markdown.js';
export { NotionHtmlTransformer } from './notion-html.js';
export { createAssetsArchive, download } from './utils.js';
export { ZipTransformer } from './zip.js';

View File

@@ -1,33 +1,27 @@
import { defaultImageProxyMiddleware } from '@blocksuite/affine-block-image';
import {
InlineDeltaToMarkdownAdapterExtensions,
MarkdownInlineToDeltaAdapterExtensions,
} from '@blocksuite/affine-components/rich-text';
import { MarkdownAdapter } from '@blocksuite/affine-shared/adapters';
docLinkBaseURLMiddleware,
fileNameMiddleware,
MarkdownAdapter,
titleMiddleware,
} from '@blocksuite/affine-shared/adapters';
import { SpecProvider } from '@blocksuite/affine-shared/utils';
import { Container } from '@blocksuite/global/di';
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import { assertExists, sha } from '@blocksuite/global/utils';
import type { Store, Workspace } from '@blocksuite/store';
import { extMimeMap, Transformer } from '@blocksuite/store';
import { defaultBlockMarkdownAdapterMatchers } from '../adapters/index.js';
import {
defaultImageProxyMiddleware,
docLinkBaseURLMiddleware,
fileNameMiddleware,
titleMiddleware,
} from './middlewares.js';
import { createAssetsArchive, download, Unzip } from './utils.js';
const container = new Container();
[
...MarkdownInlineToDeltaAdapterExtensions,
...defaultBlockMarkdownAdapterMatchers,
...InlineDeltaToMarkdownAdapterExtensions,
].forEach(ext => {
ext.setup(container);
});
const provider = container.provider();
function getProvider() {
const container = new Container();
const exts = SpecProvider.getInstance().getSpec('store').value;
exts.forEach(ext => {
ext.setup(container);
});
return container.provider();
}
type ImportMarkdownToBlockOptions = {
doc: Store;
@@ -52,6 +46,7 @@ type ImportMarkdownZipOptions = {
* @returns A Promise that resolves when the export is complete
*/
async function exportDoc(doc: Store) {
const provider = getProvider();
const job = new Transformer({
schema: doc.schema,
blobCRUD: doc.blobSync,
@@ -111,6 +106,7 @@ async function importMarkdownToBlock({
markdown,
blockId,
}: ImportMarkdownToBlockOptions) {
const provider = getProvider();
const job = new Transformer({
schema: doc.schema,
blobCRUD: doc.blobSync,
@@ -156,6 +152,7 @@ async function importMarkdownToDoc({
markdown,
fileName,
}: ImportMarkdownToDocOptions) {
const provider = getProvider();
const job = new Transformer({
schema: collection.schema,
blobCRUD: collection.blobSync,
@@ -192,6 +189,7 @@ async function importMarkdownZip({
collection,
imported,
}: ImportMarkdownZipOptions) {
const provider = getProvider();
const unzip = new Unzip();
await unzip.load(imported);

View File

@@ -1,11 +1,10 @@
import { NotionHtmlInlineToDeltaAdapterExtensions } from '@blocksuite/affine-components/rich-text';
import { defaultImageProxyMiddleware } from '@blocksuite/affine-block-image';
import { NotionHtmlAdapter } from '@blocksuite/affine-shared/adapters';
import { SpecProvider } from '@blocksuite/affine-shared/utils';
import { Container } from '@blocksuite/global/di';
import { sha } from '@blocksuite/global/utils';
import { extMimeMap, Transformer, type Workspace } from '@blocksuite/store';
import { defaultBlockNotionHtmlAdapterMatchers } from '../adapters/notion-html/block-matcher.js';
import { defaultImageProxyMiddleware } from './middlewares.js';
import { Unzip } from './utils.js';
type ImportNotionZipOptions = {
@@ -13,15 +12,14 @@ type ImportNotionZipOptions = {
imported: Blob;
};
const container = new Container();
[
...NotionHtmlInlineToDeltaAdapterExtensions,
...defaultBlockNotionHtmlAdapterMatchers,
].forEach(ext => {
ext.setup(container);
});
const provider = container.provider();
function getProvider() {
const container = new Container();
const exts = SpecProvider.getInstance().getSpec('store').value;
exts.forEach(ext => {
ext.setup(container);
});
return container.provider();
}
/**
* Imports a Notion zip file into the BlockSuite collection.
@@ -40,6 +38,7 @@ async function importNotionZip({
collection,
imported,
}: ImportNotionZipOptions) {
const provider = getProvider();
const pageIds: string[] = [];
let isWorkspaceFile = false;
let hasMarkdown = false;

View File

@@ -1,9 +1,12 @@
import {
replaceIdMiddleware,
titleMiddleware,
} from '@blocksuite/affine-shared/adapters';
import { sha } from '@blocksuite/global/utils';
import type { DocSnapshot, Store, Workspace } from '@blocksuite/store';
import { extMimeMap, getAssetName, Transformer } from '@blocksuite/store';
import { download, Unzip, Zip } from '../transformers/utils.js';
import { replaceIdMiddleware, titleMiddleware } from './middlewares.js';
async function exportDocs(collection: Workspace, docs: Store[]) {
const zip = new Zip();

View File

@@ -12,9 +12,9 @@ import type { Workspace } from '@blocksuite/store';
import { html, LitElement, type PropertyValues } from 'lit';
import { query, state } from 'lit/decorators.js';
import { HtmlTransformer } from '../../../../_common/transformers/html.js';
import { MarkdownTransformer } from '../../../../_common/transformers/markdown.js';
import { NotionHtmlTransformer } from '../../../../_common/transformers/notion-html.js';
import { HtmlTransformer } from '../../../transformers/html.js';
import { MarkdownTransformer } from '../../../transformers/markdown.js';
import { NotionHtmlTransformer } from '../../../transformers/notion-html.js';
import { styles } from './styles.js';
export type OnSuccessHandler = (