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

@@ -1,9 +1,9 @@
import { DatabaseBlockSchema } from '@blocksuite/affine-model'; import { DatabaseBlockSchema } from '@blocksuite/affine-model';
import { import {
AdapterTextUtils,
BlockNotionHtmlAdapterExtension, BlockNotionHtmlAdapterExtension,
type BlockNotionHtmlAdapterMatcher, type BlockNotionHtmlAdapterMatcher,
HastUtils, HastUtils,
TextUtils,
} from '@blocksuite/affine-shared/adapters'; } from '@blocksuite/affine-shared/adapters';
import { getTagColor } from '@blocksuite/data-view'; import { getTagColor } from '@blocksuite/data-view';
import { type BlockSnapshot, nanoid } from '@blocksuite/store'; import { type BlockSnapshot, nanoid } from '@blocksuite/store';
@@ -219,7 +219,7 @@ export const databaseBlockNotionHtmlAdapterMatcher: BlockNotionHtmlAdapterMatche
column.type = 'rich-text'; column.type = 'rich-text';
row[column.id] = { row[column.id] = {
columnId: column.id, columnId: column.id,
value: TextUtils.createText(text), value: AdapterTextUtils.createText(text),
}; };
} else { } else {
row[column.id] = { row[column.id] = {
@@ -235,11 +235,11 @@ export const databaseBlockNotionHtmlAdapterMatcher: BlockNotionHtmlAdapterMatche
} }
if ( if (
column.type === 'rich-text' && column.type === 'rich-text' &&
!TextUtils.isText(row[column.id].value) !AdapterTextUtils.isText(row[column.id].value)
) { ) {
row[column.id] = { row[column.id] = {
columnId: column.id, columnId: column.id,
value: TextUtils.createText(row[column.id].value), value: AdapterTextUtils.createText(row[column.id].value),
}; };
} }
}); });

View File

@@ -197,7 +197,7 @@ async function renderNoteContent(
match: ids.map(id => ({ id, viewType: 'display' })), match: ids.map(id => ({ id, viewType: 'display' })),
}; };
const previewDoc = doc.doc.getStore({ query }); const previewDoc = doc.doc.getStore({ query });
const previewSpec = SpecProvider.getInstance().getSpec('page:preview'); const previewSpec = SpecProvider.getInstance().getSpec('preview:page');
const previewStd = new BlockStdScope({ const previewStd = new BlockStdScope({
store: previewDoc, store: previewDoc,
extensions: previewSpec.value, extensions: previewSpec.value,

View File

@@ -1,8 +1,8 @@
import { EmbedLinkedDocBlockSchema } from '@blocksuite/affine-model'; import { EmbedLinkedDocBlockSchema } from '@blocksuite/affine-model';
import { import {
AdapterTextUtils,
BlockHtmlAdapterExtension, BlockHtmlAdapterExtension,
type BlockHtmlAdapterMatcher, type BlockHtmlAdapterMatcher,
TextUtils,
} from '@blocksuite/affine-shared/adapters'; } from '@blocksuite/affine-shared/adapters';
export const embedLinkedDocBlockHtmlAdapterMatcher: BlockHtmlAdapterMatcher = { export const embedLinkedDocBlockHtmlAdapterMatcher: BlockHtmlAdapterMatcher = {
@@ -18,7 +18,7 @@ export const embedLinkedDocBlockHtmlAdapterMatcher: BlockHtmlAdapterMatcher = {
return; return;
} }
const title = configs.get('title:' + o.node.props.pageId) ?? 'untitled'; const title = configs.get('title:' + o.node.props.pageId) ?? 'untitled';
const url = TextUtils.generateDocUrl( const url = AdapterTextUtils.generateDocUrl(
configs.get('docLinkBaseUrl') ?? '', configs.get('docLinkBaseUrl') ?? '',
String(o.node.props.pageId), String(o.node.props.pageId),
o.node.props.params ?? Object.create(null) o.node.props.params ?? Object.create(null)

View File

@@ -1,8 +1,8 @@
import { EmbedLinkedDocBlockSchema } from '@blocksuite/affine-model'; import { EmbedLinkedDocBlockSchema } from '@blocksuite/affine-model';
import { import {
AdapterTextUtils,
BlockMarkdownAdapterExtension, BlockMarkdownAdapterExtension,
type BlockMarkdownAdapterMatcher, type BlockMarkdownAdapterMatcher,
TextUtils,
} from '@blocksuite/affine-shared/adapters'; } from '@blocksuite/affine-shared/adapters';
export const embedLinkedDocBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMatcher = export const embedLinkedDocBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMatcher =
@@ -19,7 +19,7 @@ export const embedLinkedDocBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMatc
return; return;
} }
const title = configs.get('title:' + o.node.props.pageId) ?? 'untitled'; const title = configs.get('title:' + o.node.props.pageId) ?? 'untitled';
const url = TextUtils.generateDocUrl( const url = AdapterTextUtils.generateDocUrl(
configs.get('docLinkBaseUrl') ?? '', configs.get('docLinkBaseUrl') ?? '',
String(o.node.props.pageId), String(o.node.props.pageId),
o.node.props.params ?? Object.create(null) o.node.props.params ?? Object.create(null)

View File

@@ -1,8 +1,8 @@
import { EmbedLinkedDocBlockSchema } from '@blocksuite/affine-model'; import { EmbedLinkedDocBlockSchema } from '@blocksuite/affine-model';
import { import {
AdapterTextUtils,
BlockPlainTextAdapterExtension, BlockPlainTextAdapterExtension,
type BlockPlainTextAdapterMatcher, type BlockPlainTextAdapterMatcher,
TextUtils,
} from '@blocksuite/affine-shared/adapters'; } from '@blocksuite/affine-shared/adapters';
export const embedLinkedDocBlockPlainTextAdapterMatcher: BlockPlainTextAdapterMatcher = export const embedLinkedDocBlockPlainTextAdapterMatcher: BlockPlainTextAdapterMatcher =
@@ -19,7 +19,7 @@ export const embedLinkedDocBlockPlainTextAdapterMatcher: BlockPlainTextAdapterMa
return; return;
} }
const title = configs.get('title:' + o.node.props.pageId) ?? 'untitled'; const title = configs.get('title:' + o.node.props.pageId) ?? 'untitled';
const url = TextUtils.generateDocUrl( const url = AdapterTextUtils.generateDocUrl(
configs.get('docLinkBaseUrl') ?? '', configs.get('docLinkBaseUrl') ?? '',
String(o.node.props.pageId), String(o.node.props.pageId),
o.node.props.params ?? Object.create(null) o.node.props.params ?? Object.create(null)

View File

@@ -70,7 +70,7 @@ export class EmbedEdgelessSyncedDocBlockComponent extends toEdgelessEmbedBlock(
<div class="affine-page-viewport" data-theme=${appTheme}> <div class="affine-page-viewport" data-theme=${appTheme}>
${new BlockStdScope({ ${new BlockStdScope({
store: syncedDoc, store: syncedDoc,
extensions: this._buildPreviewSpec('page:preview'), extensions: this._buildPreviewSpec('preview:page'),
}).render()} }).render()}
</div> </div>
`, `,
@@ -81,7 +81,7 @@ export class EmbedEdgelessSyncedDocBlockComponent extends toEdgelessEmbedBlock(
<div class="affine-edgeless-viewport" data-theme=${edgelessTheme}> <div class="affine-edgeless-viewport" data-theme=${edgelessTheme}>
${new BlockStdScope({ ${new BlockStdScope({
store: syncedDoc, store: syncedDoc,
extensions: this._buildPreviewSpec('edgeless:preview'), extensions: this._buildPreviewSpec('preview:edgeless'),
}).render()} }).render()}
</div> </div>
`, `,

View File

@@ -116,7 +116,7 @@ export class EmbedSyncedDocBlockComponent extends EmbedBlockComponent<EmbedSynce
], ],
}; };
protected _buildPreviewSpec = (name: 'page:preview' | 'edgeless:preview') => { protected _buildPreviewSpec = (name: 'preview:page' | 'preview:edgeless') => {
const nextDepth = this.depth + 1; const nextDepth = this.depth + 1;
const previewSpecBuilder = SpecProvider.getInstance().getSpec(name); const previewSpecBuilder = SpecProvider.getInstance().getSpec(name);
const currentDisposables = this.disposables; const currentDisposables = this.disposables;
@@ -203,7 +203,7 @@ export class EmbedSyncedDocBlockComponent extends EmbedBlockComponent<EmbedSynce
<div class="affine-page-viewport" data-theme=${appTheme}> <div class="affine-page-viewport" data-theme=${appTheme}>
${new BlockStdScope({ ${new BlockStdScope({
store: syncedDoc, store: syncedDoc,
extensions: this._buildPreviewSpec('page:preview'), extensions: this._buildPreviewSpec('preview:page'),
}).render()} }).render()}
</div> </div>
`, `,
@@ -214,7 +214,7 @@ export class EmbedSyncedDocBlockComponent extends EmbedBlockComponent<EmbedSynce
<div class="affine-edgeless-viewport" data-theme=${edgelessTheme}> <div class="affine-edgeless-viewport" data-theme=${edgelessTheme}>
${new BlockStdScope({ ${new BlockStdScope({
store: syncedDoc, store: syncedDoc,
extensions: this._buildPreviewSpec('edgeless:preview'), extensions: this._buildPreviewSpec('preview:edgeless'),
}).render()} }).render()}
</div> </div>
`, `,

View File

@@ -1,3 +1,4 @@
export * from './frame-block.js'; export * from './frame-block.js';
export * from './frame-manager.js'; export * from './frame-manager.js';
export * from './frame-spec.js'; export * from './frame-spec.js';
export * from './tool.js';

View File

@@ -1,6 +1,7 @@
import type { NavigatorMode } from '@blocksuite/affine-block-frame';
import { BaseTool } from '@blocksuite/block-std/gfx'; import { BaseTool } from '@blocksuite/block-std/gfx';
import type { NavigatorMode } from './frame-manager';
type PresentToolOption = { type PresentToolOption = {
mode?: NavigatorMode; mode?: NavigatorMode;
}; };

View File

@@ -1,3 +1,4 @@
export * from './html.js'; export * from './html.js';
export * from './markdown.js'; export * from './markdown.js';
export * from './middleware.js';
export * from './notion-html.js'; export * from './notion-html.js';

View File

@@ -1,9 +1,9 @@
import { ListBlockSchema } from '@blocksuite/affine-model'; import { ListBlockSchema } from '@blocksuite/affine-model';
import { import {
AdapterTextUtils,
BlockHtmlAdapterExtension, BlockHtmlAdapterExtension,
type BlockHtmlAdapterMatcher, type BlockHtmlAdapterMatcher,
HastUtils, HastUtils,
TextUtils,
} from '@blocksuite/affine-shared/adapters'; } from '@blocksuite/affine-shared/adapters';
import type { DeltaInsert } from '@blocksuite/inline'; import type { DeltaInsert } from '@blocksuite/inline';
import { nanoid } from '@blocksuite/store'; import { nanoid } from '@blocksuite/store';
@@ -124,7 +124,7 @@ export const listBlockHtmlAdapterMatcher: BlockHtmlAdapterMatcher = {
Array.isArray(currentTNode.properties.className) && Array.isArray(currentTNode.properties.className) &&
currentTNode.properties.className.includes('todo-list') currentTNode.properties.className.includes('todo-list')
) === ) ===
TextUtils.isNullish( AdapterTextUtils.isNullish(
o.node.props.type === 'todo' o.node.props.type === 'todo'
? (o.node.props.checked as boolean) ? (o.node.props.checked as boolean)
: undefined : undefined
@@ -177,7 +177,7 @@ export const listBlockHtmlAdapterMatcher: BlockHtmlAdapterMatcher = {
Array.isArray(previousTNode.properties.className) && Array.isArray(previousTNode.properties.className) &&
previousTNode.properties.className.includes('todo-list') previousTNode.properties.className.includes('todo-list')
) === ) ===
TextUtils.isNullish( AdapterTextUtils.isNullish(
o.node.props.type === 'todo' o.node.props.type === 'todo'
? (o.node.props.checked as boolean) ? (o.node.props.checked as boolean)
: undefined : undefined

View File

@@ -1,9 +1,9 @@
import { ListBlockSchema } from '@blocksuite/affine-model'; import { ListBlockSchema } from '@blocksuite/affine-model';
import { import {
AdapterTextUtils,
BlockMarkdownAdapterExtension, BlockMarkdownAdapterExtension,
type BlockMarkdownAdapterMatcher, type BlockMarkdownAdapterMatcher,
type MarkdownAST, type MarkdownAST,
TextUtils,
} from '@blocksuite/affine-shared/adapters'; } from '@blocksuite/affine-shared/adapters';
import type { DeltaInsert } from '@blocksuite/inline'; import type { DeltaInsert } from '@blocksuite/inline';
import { nanoid } from '@blocksuite/store'; import { nanoid } from '@blocksuite/store';
@@ -75,8 +75,8 @@ export const listBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMatcher = {
walkerContext.getNodeContext('affine:list:parent') === o.parent && walkerContext.getNodeContext('affine:list:parent') === o.parent &&
currentTNode.type === 'list' && currentTNode.type === 'list' &&
currentTNode.ordered === (o.node.props.type === 'numbered') && currentTNode.ordered === (o.node.props.type === 'numbered') &&
TextUtils.isNullish(currentTNode.children[0].checked) === AdapterTextUtils.isNullish(currentTNode.children[0].checked) ===
TextUtils.isNullish( AdapterTextUtils.isNullish(
o.node.props.type === 'todo' o.node.props.type === 'todo'
? (o.node.props.checked as boolean) ? (o.node.props.checked as boolean)
: undefined : undefined
@@ -129,8 +129,8 @@ export const listBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMatcher = {
currentTNode.type === 'listItem' && currentTNode.type === 'listItem' &&
previousTNode?.type === 'list' && previousTNode?.type === 'list' &&
previousTNode.ordered === (o.node.props.type === 'numbered') && previousTNode.ordered === (o.node.props.type === 'numbered') &&
TextUtils.isNullish(currentTNode.checked) === AdapterTextUtils.isNullish(currentTNode.checked) ===
TextUtils.isNullish( AdapterTextUtils.isNullish(
o.node.props.type === 'todo' o.node.props.type === 'todo'
? (o.node.props.checked as boolean) ? (o.node.props.checked as boolean)
: undefined : undefined

View File

@@ -118,7 +118,7 @@ export class SurfaceRefNotePortal extends WithDisposable(ShadowlessElement) {
query: this.query, query: this.query,
readonly: true, readonly: true,
}); });
const previewSpec = SpecProvider.getInstance().getSpec('page:preview'); const previewSpec = SpecProvider.getInstance().getSpec('preview:page');
return new BlockStdScope({ return new BlockStdScope({
store: doc, store: doc,
extensions: previewSpec.value.slice(), extensions: previewSpec.value.slice(),

View File

@@ -240,7 +240,7 @@ export class SurfaceRefBlockComponent extends BlockComponent<SurfaceRefBlockMode
private _previewDoc: Store | null = null; private _previewDoc: Store | null = null;
private readonly _previewSpec = private readonly _previewSpec =
SpecProvider.getInstance().getSpec('edgeless:preview'); SpecProvider.getInstance().getSpec('preview:edgeless');
private _referencedModel: GfxModel | null = null; private _referencedModel: GfxModel | null = null;

View File

@@ -4,7 +4,10 @@ import type {
TableColumn, TableColumn,
TableRow, TableRow,
} from '@blocksuite/affine-model'; } from '@blocksuite/affine-model';
import { HastUtils, TextUtils } from '@blocksuite/affine-shared/adapters'; import {
AdapterTextUtils,
HastUtils,
} from '@blocksuite/affine-shared/adapters';
import { generateFractionalIndexingKeyBetween } from '@blocksuite/affine-shared/utils'; import { generateFractionalIndexingKeyBetween } from '@blocksuite/affine-shared/utils';
import type { DeltaInsert } from '@blocksuite/inline'; import type { DeltaInsert } from '@blocksuite/inline';
import { nanoid } from '@blocksuite/store'; import { nanoid } from '@blocksuite/store';
@@ -155,7 +158,7 @@ export const createTableProps = (rowTextLists: string[][]) => {
const cellId = `${row.rowId}:${column.columnId}`; const cellId = `${row.rowId}:${column.columnId}`;
const text = rowTextLists[i]?.[j]; const text = rowTextLists[i]?.[j];
cells[cellId] = { cells[cellId] = {
text: TextUtils.createText(text ?? ''), text: AdapterTextUtils.createText(text ?? ''),
}; };
} }
} }

View File

@@ -1,7 +1,7 @@
import type { InlineHtmlAST } from '@blocksuite/affine-shared/adapters'; import type { InlineHtmlAST } from '@blocksuite/affine-shared/adapters';
import { import {
AdapterTextUtils,
InlineDeltaToHtmlAdapterExtension, InlineDeltaToHtmlAdapterExtension,
TextUtils,
} from '@blocksuite/affine-shared/adapters'; } from '@blocksuite/affine-shared/adapters';
import { ThemeProvider } from '@blocksuite/affine-shared/services'; import { ThemeProvider } from '@blocksuite/affine-shared/services';
@@ -90,7 +90,7 @@ export const referenceDeltaToHtmlAdapterMatcher =
const { configs } = context; const { configs } = context;
const title = configs.get(`title:${reference.pageId}`); const title = configs.get(`title:${reference.pageId}`);
const url = TextUtils.generateDocUrl( const url = AdapterTextUtils.generateDocUrl(
configs.get('docLinkBaseUrl') ?? '', configs.get('docLinkBaseUrl') ?? '',
String(reference.pageId), String(reference.pageId),
reference.params ?? Object.create(null) reference.params ?? Object.create(null)

View File

@@ -1,7 +1,7 @@
import { import {
AdapterTextUtils,
FOOTNOTE_DEFINITION_PREFIX, FOOTNOTE_DEFINITION_PREFIX,
InlineDeltaToMarkdownAdapterExtension, InlineDeltaToMarkdownAdapterExtension,
TextUtils,
} from '@blocksuite/affine-shared/adapters'; } from '@blocksuite/affine-shared/adapters';
import type { PhrasingContent } from 'mdast'; import type { PhrasingContent } from 'mdast';
import type RemarkMath from 'remark-math'; import type RemarkMath from 'remark-math';
@@ -74,7 +74,7 @@ export const referenceDeltaToMarkdownAdapterMatcher =
const { configs } = context; const { configs } = context;
const title = configs.get(`title:${reference.pageId}`); const title = configs.get(`title:${reference.pageId}`);
const params = reference.params ?? {}; const params = reference.params ?? {};
const url = TextUtils.generateDocUrl( const url = AdapterTextUtils.generateDocUrl(
configs.get('docLinkBaseUrl') ?? '', configs.get('docLinkBaseUrl') ?? '',
String(reference.pageId), String(reference.pageId),
params params

View File

@@ -1,7 +1,7 @@
import { import {
AdapterTextUtils,
InlineDeltaToPlainTextAdapterExtension, InlineDeltaToPlainTextAdapterExtension,
type TextBuffer, type TextBuffer,
TextUtils,
} from '@blocksuite/affine-shared/adapters'; } from '@blocksuite/affine-shared/adapters';
import type { ExtensionType } from '@blocksuite/store'; import type { ExtensionType } from '@blocksuite/store';
@@ -20,7 +20,7 @@ export const referenceDeltaMarkdownAdapterMatch =
const { configs } = context; const { configs } = context;
const title = configs.get(`title:${reference.pageId}`) ?? ''; const title = configs.get(`title:${reference.pageId}`) ?? '';
const url = TextUtils.generateDocUrl( const url = AdapterTextUtils.generateDocUrl(
configs.get('docLinkBaseUrl') ?? '', configs.get('docLinkBaseUrl') ?? '',
String(reference.pageId), String(reference.pageId),
reference.params ?? Object.create(null) reference.params ?? Object.create(null)

View File

@@ -1,2 +1 @@
export * from './frame-panel'; export * from './frame-panel';
export * from './tool';

View File

@@ -1,3 +1,11 @@
import type { GfxModel } from '@blocksuite/block-std/gfx';
import type {
BrushElementModel,
ConnectorElementModel,
GroupElementModel,
} from '../elements';
export type EmbedCardStyle = export type EmbedCardStyle =
| 'horizontal' | 'horizontal'
| 'horizontalThin' | 'horizontalThin'
@@ -17,3 +25,8 @@ export type LinkPreviewData = {
image: string | null; image: string | null;
title: string | null; title: string | null;
}; };
export type Connectable = Exclude<
GfxModel,
ConnectorElementModel | BrushElementModel | GroupElementModel
>;

View File

@@ -14,7 +14,7 @@ import {
type InlineDeltaMatcher, type InlineDeltaMatcher,
} from '../types/adapter.js'; } from '../types/adapter.js';
import type { HtmlAST, InlineHtmlAST } from '../types/hast.js'; import type { HtmlAST, InlineHtmlAST } from '../types/hast.js';
import { TextUtils } from '../utils/text.js'; import { AdapterTextUtils } from '../utils/text.js';
export type InlineDeltaToHtmlAdapterMatcher = InlineDeltaMatcher<InlineHtmlAST>; export type InlineDeltaToHtmlAdapterMatcher = InlineDeltaMatcher<InlineHtmlAST>;
@@ -119,7 +119,7 @@ export class HtmlDeltaConverter extends DeltaASTConverter<
options: DeltaASTConverterOptions = Object.create(null) options: DeltaASTConverterOptions = Object.create(null)
): DeltaInsert<AffineTextAttributes>[] { ): DeltaInsert<AffineTextAttributes>[] {
return this._spreadAstToDelta(ast, options).reduce((acc, cur) => { return this._spreadAstToDelta(ast, options).reduce((acc, cur) => {
return TextUtils.mergeDeltas(acc, cur); return AdapterTextUtils.mergeDeltas(acc, cur);
}, [] as DeltaInsert<AffineTextAttributes>[]); }, [] as DeltaInsert<AffineTextAttributes>[]);
} }

View File

@@ -0,0 +1,44 @@
import type { TransformerMiddleware } from '@blocksuite/store';
const customDocLinkBaseUrlMiddleware = (
baseUrl: string,
collectionId: string
): TransformerMiddleware => {
return ({ adapterConfigs }) => {
const docLinkBaseUrl = baseUrl
? `${baseUrl}/workspace/${collectionId}`
: '';
adapterConfigs.set('docLinkBaseUrl', docLinkBaseUrl);
};
};
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;
export const embedSyncedDocMiddleware =
(type: 'content'): TransformerMiddleware =>
({ adapterConfigs }) => {
adapterConfigs.set('embedSyncedDocExportType', type);
};

View File

@@ -0,0 +1,23 @@
import type { TransformerMiddleware } from '@blocksuite/store';
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,4 +1,8 @@
export * from './code'; export * from './code';
export * from './copy'; export * from './copy';
export * from './doc-link';
export * from './file-name';
export * from './paste'; export * from './paste';
export * from './replace-id';
export * from './surface-ref-to-embed'; export * from './surface-ref-to-embed';
export * from './title';

View File

@@ -6,13 +6,8 @@ import type {
ParagraphBlockModel, ParagraphBlockModel,
SurfaceRefBlockModel, SurfaceRefBlockModel,
} from '@blocksuite/affine-model'; } from '@blocksuite/affine-model';
import { DEFAULT_IMAGE_PROXY_ENDPOINT } from '@blocksuite/affine-shared/consts';
import { assertExists } from '@blocksuite/global/utils'; import { assertExists } from '@blocksuite/global/utils';
import type { import type { DeltaOperation, TransformerMiddleware } from '@blocksuite/store';
DeltaOperation,
DocMeta,
TransformerMiddleware,
} from '@blocksuite/store';
export const replaceIdMiddleware = export const replaceIdMiddleware =
(idGenerator: () => string): TransformerMiddleware => (idGenerator: () => string): TransformerMiddleware =>
@@ -203,103 +198,3 @@ export const replaceIdMiddleware =
} }
}); });
}; };
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

@@ -0,0 +1,11 @@
import type { DocMeta, TransformerMiddleware } from '@blocksuite/store';
export const titleMiddleware =
(metas: DocMeta[]): TransformerMiddleware =>
({ slots, adapterConfigs }) => {
slots.beforeExport.on(() => {
for (const meta of metas) {
adapterConfigs.set('title:' + meta.id, meta.title);
}
});
};

View File

@@ -86,7 +86,7 @@ function generateDocUrl(
return url; return url;
} }
export const TextUtils = { export const AdapterTextUtils = {
mergeDeltas, mergeDeltas,
isNullish, isNullish,
createText, createText,

View File

@@ -3,10 +3,17 @@ import type { ExtensionType } from '@blocksuite/store';
import { SpecBuilder } from './spec-builder.js'; import { SpecBuilder } from './spec-builder.js';
type SpecId =
| 'store'
| 'page'
| 'edgeless'
| 'preview:page'
| 'preview:edgeless';
export class SpecProvider { export class SpecProvider {
static instance: SpecProvider; static instance: SpecProvider;
private readonly specMap = new Map<string, ExtensionType[]>(); private readonly specMap = new Map<SpecId, ExtensionType[]>();
private constructor() {} private constructor() {}
@@ -17,17 +24,17 @@ export class SpecProvider {
return SpecProvider.instance; return SpecProvider.instance;
} }
addSpec(id: string, spec: ExtensionType[]) { addSpec(id: SpecId, spec: ExtensionType[]) {
if (!this.specMap.has(id)) { if (!this.specMap.has(id)) {
this.specMap.set(id, spec); this.specMap.set(id, spec);
} }
} }
clearSpec(id: string) { clearSpec(id: SpecId) {
this.specMap.delete(id); this.specMap.delete(id);
} }
extendSpec(id: string, newSpec: ExtensionType[]) { extendSpec(id: SpecId, newSpec: ExtensionType[]) {
const existingSpec = this.specMap.get(id); const existingSpec = this.specMap.get(id);
if (!existingSpec) { if (!existingSpec) {
console.error(`Spec not found for ${id}`); console.error(`Spec not found for ${id}`);
@@ -36,26 +43,17 @@ export class SpecProvider {
this.specMap.set(id, [...existingSpec, ...newSpec]); this.specMap.set(id, [...existingSpec, ...newSpec]);
} }
getSpec(id: string) { getSpec(id: SpecId) {
const spec = this.specMap.get(id); const spec = this.specMap.get(id);
assertExists(spec, `Spec not found for ${id}`); assertExists(spec, `Spec not found for ${id}`);
return new SpecBuilder(spec); return new SpecBuilder(spec);
} }
hasSpec(id: string) { hasSpec(id: SpecId) {
return this.specMap.has(id); return this.specMap.has(id);
} }
cloneSpec(id: string, targetId: string) { omitSpec(id: SpecId, targetSpec: ExtensionType) {
const existingSpec = this.specMap.get(id);
if (!existingSpec) {
console.error(`Spec not found for ${id}`);
return;
}
this.specMap.set(targetId, [...existingSpec]);
}
omitSpec(id: string, targetSpec: ExtensionType) {
const existingSpec = this.specMap.get(id); const existingSpec = this.specMap.get(id);
if (!existingSpec) { if (!existingSpec) {
console.error(`Spec not found for ${id}`); console.error(`Spec not found for ${id}`);
@@ -68,7 +66,7 @@ export class SpecProvider {
); );
} }
replaceSpec(id: string, targetSpec: ExtensionType, newSpec: ExtensionType) { replaceSpec(id: SpecId, targetSpec: ExtensionType, newSpec: ExtensionType) {
const existingSpec = this.specMap.get(id); const existingSpec = this.specMap.get(id);
if (!existingSpec) { if (!existingSpec) {
console.error(`Spec not found for ${id}`); console.error(`Spec not found for ${id}`);

View File

@@ -103,7 +103,7 @@ export class PreviewHelper {
const query = this._calculateQuery(blockIds as string[], mode); const query = this._calculateQuery(blockIds as string[], mode);
const store = widget.doc.doc.getStore({ query }); const store = widget.doc.doc.getStore({ query });
const previewSpec = SpecProvider.getInstance().getSpec( const previewSpec = SpecProvider.getInstance().getSpec(
isEdgeless ? 'edgeless:preview' : 'page:preview' isEdgeless ? 'preview:edgeless' : 'preview:page'
); );
const settingSignal = signal({ ...editorSetting }); const settingSignal = signal({ ...editorSetting });
const extensions = [ const extensions = [

View File

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

View File

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

View File

@@ -1,6 +1,9 @@
import { InlineDeltaToPlainTextAdapterExtensions } from '@blocksuite/affine-components/rich-text'; import { InlineDeltaToPlainTextAdapterExtensions } from '@blocksuite/affine-components/rich-text';
import { DefaultTheme, NoteDisplayMode } from '@blocksuite/affine-model'; 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 { Container } from '@blocksuite/global/di';
import type { import type {
BlockSnapshot, BlockSnapshot,
@@ -10,7 +13,6 @@ import type {
import { describe, expect, test } from 'vitest'; import { describe, expect, test } from 'vitest';
import { defaultBlockPlainTextAdapterMatchers } from '../../_common/adapters/plain-text/block-matcher.js'; import { defaultBlockPlainTextAdapterMatchers } from '../../_common/adapters/plain-text/block-matcher.js';
import { embedSyncedDocMiddleware } from '../../_common/transformers/middlewares.js';
import { createJob } from '../utils/create-job.js'; import { createJob } from '../utils/create-job.js';
const container = new Container(); 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 { FeatureFlagService } from '@blocksuite/affine-shared/services';
import { import {
Schema, Schema,
@@ -6,7 +7,6 @@ import {
} from '@blocksuite/store'; } from '@blocksuite/store';
import { TestWorkspace } from '@blocksuite/store/test'; import { TestWorkspace } from '@blocksuite/store/test';
import { defaultImageProxyMiddleware } from '../../_common/transformers/middlewares.js';
import { AffineSchemas } from '../../schemas.js'; import { AffineSchemas } from '../../schemas.js';
declare global { 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,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 type { ExtensionType } from '@blocksuite/store';
import { AdapterFactoryExtensions } from '../_common/adapters/extension.js'; import { AdapterFactoryExtensions } from '../_common/adapters/extension.js';
import {
HtmlAdapterExtension,
MarkdownAdapterExtension,
NotionHtmlAdapterExtension,
} from './preset/adapters.js';
export const CommonBlockSpecs: ExtensionType[] = [ export const CommonBlockSpecs: ExtensionType[] = [
DocDisplayMetaService, DocDisplayMetaService,
@@ -110,4 +115,8 @@ export const StoreExtensions: ExtensionType[] = [
LinkPreviewerService, LinkPreviewerService,
FileSizeLimitService, FileSizeLimitService,
ImageStoreSpec, ImageStoreSpec,
HtmlAdapterExtension,
MarkdownAdapterExtension,
NotionHtmlAdapterExtension,
].flat(); ].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 { import {
EdgelessFrameManager, EdgelessFrameManager,
FrameOverlay, FrameOverlay,
PresentTool,
} from '@blocksuite/affine-block-frame'; } from '@blocksuite/affine-block-frame';
import { ConnectionOverlay } from '@blocksuite/affine-block-surface'; import { ConnectionOverlay } from '@blocksuite/affine-block-surface';
import { PresentTool } from '@blocksuite/affine-fragment-frame-panel';
import type { ExtensionType } from '@blocksuite/store'; import type { ExtensionType } from '@blocksuite/store';
import { EdgelessRootBlockSpec } from '../../root-block/edgeless/edgeless-root-spec.js'; import { EdgelessRootBlockSpec } from '../../root-block/edgeless/edgeless-root-spec.js';

View File

@@ -1,6 +1,6 @@
import { SpecProvider } from '@blocksuite/affine-shared/utils'; 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 { EdgelessEditorBlockSpecs } from './preset/edgeless-specs.js';
import { PageEditorBlockSpecs } from './preset/page-specs.js'; import { PageEditorBlockSpecs } from './preset/page-specs.js';
import { import {
@@ -10,15 +10,14 @@ import {
export function registerSpecs() { export function registerSpecs() {
SpecProvider.getInstance().addSpec('store', StoreExtensions); SpecProvider.getInstance().addSpec('store', StoreExtensions);
SpecProvider.getInstance().addSpec('common', CommonBlockSpecs);
SpecProvider.getInstance().addSpec('page', PageEditorBlockSpecs); SpecProvider.getInstance().addSpec('page', PageEditorBlockSpecs);
SpecProvider.getInstance().addSpec('edgeless', EdgelessEditorBlockSpecs); SpecProvider.getInstance().addSpec('edgeless', EdgelessEditorBlockSpecs);
SpecProvider.getInstance().addSpec( SpecProvider.getInstance().addSpec(
'page:preview', 'preview:page',
PreviewPageEditorBlockSpecs PreviewPageEditorBlockSpecs
); );
SpecProvider.getInstance().addSpec( SpecProvider.getInstance().addSpec(
'edgeless:preview', 'preview:edgeless',
PreviewEdgelessEditorBlockSpecs 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'; import { isCanvasElement } from './root-block/edgeless/utils/query.js';
export * from './_common/adapters/index.js'; export * from './_common/adapters/index.js';
export * from './_common/transformers/index.js';
export * from './_specs/index.js'; export * from './_specs/index.js';
export { EdgelessTemplatePanel } from './root-block/edgeless/components/toolbar/template/template-panel.js'; export { EdgelessTemplatePanel } from './root-block/edgeless/components/toolbar/template/template-panel.js';
export type { export type {
@@ -93,31 +92,7 @@ export {
export * from '@blocksuite/affine-fragment-frame-panel'; export * from '@blocksuite/affine-fragment-frame-panel';
export * from '@blocksuite/affine-fragment-outline'; export * from '@blocksuite/affine-fragment-outline';
export * from '@blocksuite/affine-model'; export * from '@blocksuite/affine-model';
export { export * from '@blocksuite/affine-shared/adapters';
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/commands'; export * from '@blocksuite/affine-shared/commands';
export { HighlightSelection } from '@blocksuite/affine-shared/selection'; export { HighlightSelection } from '@blocksuite/affine-shared/selection';
export * from '@blocksuite/affine-shared/services'; export * from '@blocksuite/affine-shared/services';

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -19,7 +19,7 @@ import {
} from '@blocksuite/block-std/gfx'; } from '@blocksuite/block-std/gfx';
import type { Bound, IVec } from '@blocksuite/global/utils'; 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 { isMindmapNode } from '../../../utils/query.js';
import { DefaultModeDragType, DefaultToolExt, type DragState } from '../ext.js'; import { DefaultModeDragType, DefaultToolExt, type DragState } from '../ext.js';
import { calculateResponseArea } from './drag-utils.js'; import { calculateResponseArea } from './drag-utils.js';

View File

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

View File

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

View File

@@ -2,6 +2,7 @@ import type { CanvasElementWithText } from '@blocksuite/affine-block-surface';
import { import {
type AttachmentBlockModel, type AttachmentBlockModel,
type BookmarkBlockModel, type BookmarkBlockModel,
type Connectable,
ConnectorElementModel, ConnectorElementModel,
type EdgelessTextBlockModel, type EdgelessTextBlockModel,
type EmbedBlockModel, type EmbedBlockModel,
@@ -32,8 +33,6 @@ import type { PointLocation } from '@blocksuite/global/utils';
import { Bound } from '@blocksuite/global/utils'; import { Bound } from '@blocksuite/global/utils';
import type { BlockModel } from '@blocksuite/store'; import type { BlockModel } from '@blocksuite/store';
import type { Connectable } from '../../../_common/types';
export function isMindmapNode(element: GfxBlockElementModel | GfxModel | null) { export function isMindmapNode(element: GfxBlockElementModel | GfxModel | null) {
return element?.group instanceof MindmapElementModel; 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 './preview/preview-root-block.js';
export * from './root-config.js'; export * from './root-config.js';
export { RootService } from './root-service.js'; export { RootService } from './root-service.js';
export * from './transformers/index.js';
export * from './types.js'; export * from './types.js';
export * from './utils/index.js'; export * from './utils/index.js';
export * from './widgets/index.js'; export * from './widgets/index.js';

View File

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

View File

@@ -1,20 +1,16 @@
import { defaultImageProxyMiddleware } from '@blocksuite/affine-block-image';
import { import {
HtmlInlineToDeltaAdapterExtensions, docLinkBaseURLMiddleware,
InlineDeltaToHtmlAdapterExtensions, fileNameMiddleware,
} from '@blocksuite/affine-components/rich-text'; HtmlAdapter,
import { HtmlAdapter } from '@blocksuite/affine-shared/adapters'; titleMiddleware,
} from '@blocksuite/affine-shared/adapters';
import { SpecProvider } from '@blocksuite/affine-shared/utils';
import { Container } from '@blocksuite/global/di'; import { Container } from '@blocksuite/global/di';
import { sha } from '@blocksuite/global/utils'; import { sha } from '@blocksuite/global/utils';
import type { Store, Workspace } from '@blocksuite/store'; import type { Store, Workspace } from '@blocksuite/store';
import { extMimeMap, Transformer } 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'; import { createAssetsArchive, download, Unzip } from './utils.js';
type ImportHTMLToDocOptions = { type ImportHTMLToDocOptions = {
@@ -28,16 +24,14 @@ type ImportHTMLZipOptions = {
imported: Blob; imported: Blob;
}; };
const container = new Container(); function getProvider() {
[ const container = new Container();
...HtmlInlineToDeltaAdapterExtensions, const exts = SpecProvider.getInstance().getSpec('store').value;
...defaultBlockHtmlAdapterMatchers, exts.forEach(ext => {
...InlineDeltaToHtmlAdapterExtensions, ext.setup(container);
].forEach(ext => { });
ext.setup(container); return container.provider();
}); }
const provider = container.provider();
/** /**
* Exports a doc to HTML format. * Exports a doc to HTML format.
@@ -46,6 +40,7 @@ const provider = container.provider();
* @returns A Promise that resolves when the export is complete. * @returns A Promise that resolves when the export is complete.
*/ */
async function exportDoc(doc: Store) { async function exportDoc(doc: Store) {
const provider = getProvider();
const job = new Transformer({ const job = new Transformer({
schema: doc.schema, schema: doc.schema,
blobCRUD: doc.blobSync, blobCRUD: doc.blobSync,
@@ -101,6 +96,7 @@ async function importHTMLToDoc({
html, html,
fileName, fileName,
}: ImportHTMLToDocOptions) { }: ImportHTMLToDocOptions) {
const provider = getProvider();
const job = new Transformer({ const job = new Transformer({
schema: collection.schema, schema: collection.schema,
blobCRUD: collection.blobSync, 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. * @returns A Promise that resolves to an array of IDs of the newly created docs.
*/ */
async function importHTMLZip({ collection, imported }: ImportHTMLZipOptions) { async function importHTMLZip({ collection, imported }: ImportHTMLZipOptions) {
const provider = getProvider();
const unzip = new Unzip(); const unzip = new Unzip();
await unzip.load(imported); 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 { import {
InlineDeltaToMarkdownAdapterExtensions, docLinkBaseURLMiddleware,
MarkdownInlineToDeltaAdapterExtensions, fileNameMiddleware,
} from '@blocksuite/affine-components/rich-text'; MarkdownAdapter,
import { MarkdownAdapter } from '@blocksuite/affine-shared/adapters'; titleMiddleware,
} from '@blocksuite/affine-shared/adapters';
import { SpecProvider } from '@blocksuite/affine-shared/utils';
import { Container } from '@blocksuite/global/di'; import { Container } from '@blocksuite/global/di';
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions'; import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import { assertExists, sha } from '@blocksuite/global/utils'; import { assertExists, sha } from '@blocksuite/global/utils';
import type { Store, Workspace } from '@blocksuite/store'; import type { Store, Workspace } from '@blocksuite/store';
import { extMimeMap, Transformer } 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'; import { createAssetsArchive, download, Unzip } from './utils.js';
const container = new Container(); function getProvider() {
[ const container = new Container();
...MarkdownInlineToDeltaAdapterExtensions, const exts = SpecProvider.getInstance().getSpec('store').value;
...defaultBlockMarkdownAdapterMatchers, exts.forEach(ext => {
...InlineDeltaToMarkdownAdapterExtensions, ext.setup(container);
].forEach(ext => { });
ext.setup(container); return container.provider();
}); }
const provider = container.provider();
type ImportMarkdownToBlockOptions = { type ImportMarkdownToBlockOptions = {
doc: Store; doc: Store;
@@ -52,6 +46,7 @@ type ImportMarkdownZipOptions = {
* @returns A Promise that resolves when the export is complete * @returns A Promise that resolves when the export is complete
*/ */
async function exportDoc(doc: Store) { async function exportDoc(doc: Store) {
const provider = getProvider();
const job = new Transformer({ const job = new Transformer({
schema: doc.schema, schema: doc.schema,
blobCRUD: doc.blobSync, blobCRUD: doc.blobSync,
@@ -111,6 +106,7 @@ async function importMarkdownToBlock({
markdown, markdown,
blockId, blockId,
}: ImportMarkdownToBlockOptions) { }: ImportMarkdownToBlockOptions) {
const provider = getProvider();
const job = new Transformer({ const job = new Transformer({
schema: doc.schema, schema: doc.schema,
blobCRUD: doc.blobSync, blobCRUD: doc.blobSync,
@@ -156,6 +152,7 @@ async function importMarkdownToDoc({
markdown, markdown,
fileName, fileName,
}: ImportMarkdownToDocOptions) { }: ImportMarkdownToDocOptions) {
const provider = getProvider();
const job = new Transformer({ const job = new Transformer({
schema: collection.schema, schema: collection.schema,
blobCRUD: collection.blobSync, blobCRUD: collection.blobSync,
@@ -192,6 +189,7 @@ async function importMarkdownZip({
collection, collection,
imported, imported,
}: ImportMarkdownZipOptions) { }: ImportMarkdownZipOptions) {
const provider = getProvider();
const unzip = new Unzip(); const unzip = new Unzip();
await unzip.load(imported); 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 { NotionHtmlAdapter } from '@blocksuite/affine-shared/adapters';
import { SpecProvider } from '@blocksuite/affine-shared/utils';
import { Container } from '@blocksuite/global/di'; import { Container } from '@blocksuite/global/di';
import { sha } from '@blocksuite/global/utils'; import { sha } from '@blocksuite/global/utils';
import { extMimeMap, Transformer, type Workspace } from '@blocksuite/store'; 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'; import { Unzip } from './utils.js';
type ImportNotionZipOptions = { type ImportNotionZipOptions = {
@@ -13,15 +12,14 @@ type ImportNotionZipOptions = {
imported: Blob; imported: Blob;
}; };
const container = new Container(); function getProvider() {
[ const container = new Container();
...NotionHtmlInlineToDeltaAdapterExtensions, const exts = SpecProvider.getInstance().getSpec('store').value;
...defaultBlockNotionHtmlAdapterMatchers, exts.forEach(ext => {
].forEach(ext => { ext.setup(container);
ext.setup(container); });
}); return container.provider();
}
const provider = container.provider();
/** /**
* Imports a Notion zip file into the BlockSuite collection. * Imports a Notion zip file into the BlockSuite collection.
@@ -40,6 +38,7 @@ async function importNotionZip({
collection, collection,
imported, imported,
}: ImportNotionZipOptions) { }: ImportNotionZipOptions) {
const provider = getProvider();
const pageIds: string[] = []; const pageIds: string[] = [];
let isWorkspaceFile = false; let isWorkspaceFile = false;
let hasMarkdown = 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 { sha } from '@blocksuite/global/utils';
import type { DocSnapshot, Store, Workspace } from '@blocksuite/store'; import type { DocSnapshot, Store, Workspace } from '@blocksuite/store';
import { extMimeMap, getAssetName, Transformer } from '@blocksuite/store'; import { extMimeMap, getAssetName, Transformer } from '@blocksuite/store';
import { download, Unzip, Zip } from '../transformers/utils.js'; import { download, Unzip, Zip } from '../transformers/utils.js';
import { replaceIdMiddleware, titleMiddleware } from './middlewares.js';
async function exportDocs(collection: Workspace, docs: Store[]) { async function exportDocs(collection: Workspace, docs: Store[]) {
const zip = new Zip(); const zip = new Zip();

View File

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

View File

@@ -1,5 +1,4 @@
import { BlockServiceIdentifier } from '@blocksuite/block-std'; import { type SurfaceBlockModel, ZipTransformer } from '@blocksuite/blocks';
import type { PageRootService, SurfaceBlockModel } from '@blocksuite/blocks';
import type { PointLocation } from '@blocksuite/global/utils'; import type { PointLocation } from '@blocksuite/global/utils';
import { beforeEach, expect, test } from 'vitest'; import { beforeEach, expect, test } from 'vitest';
@@ -25,13 +24,7 @@ const fieldChecker: Record<string, (value: any) => boolean> = {
const skipFields = new Set(['_lastXYWH']); const skipFields = new Set(['_lastXYWH']);
const snapshotTest = async (snapshotUrl: string, elementsCount: number) => { const snapshotTest = async (snapshotUrl: string, elementsCount: number) => {
const pageService = window.editor.host!.std.getOptional( const transformer = ZipTransformer;
BlockServiceIdentifier('affine:page')
) as PageRootService;
if (!pageService) {
throw new Error('page service not found');
}
const transformer = pageService.transformers.zip;
const snapshotFile = await fetch(snapshotUrl) const snapshotFile = await fetch(snapshotUrl)
.then(res => res.blob()) .then(res => res.blob())

View File

@@ -209,7 +209,7 @@ export class AISlidesRenderer extends WithDisposable(LitElement) {
${new BlockStdScope({ ${new BlockStdScope({
store: this._doc, store: this._doc,
extensions: extensions:
SpecProvider.getInstance().getSpec('edgeless:preview').value, SpecProvider.getInstance().getSpec('preview:edgeless').value,
}).render()} }).render()}
</div> </div>
<div class="mask"></div> <div class="mask"></div>

View File

@@ -245,10 +245,10 @@ export const extendEdgelessPreviewSpec = (function () {
return _extension; return _extension;
} else { } else {
_extension && _extension &&
SpecProvider.getInstance().omitSpec('edgeless:preview', _extension); SpecProvider.getInstance().omitSpec('preview:edgeless', _extension);
_extension = getThemeExtension(framework); _extension = getThemeExtension(framework);
_framework = framework; _framework = framework;
SpecProvider.getInstance().extendSpec('edgeless:preview', [_extension]); SpecProvider.getInstance().extendSpec('preview:edgeless', [_extension]);
return _extension; return _extension;
} }
}; };

View File

@@ -29,14 +29,17 @@ const CustomSpecs: ExtensionType[] = [
getFontConfigExtension(), getFontConfigExtension(),
].flat(); ].flat();
function patchPreviewSpec(id: string, specs: ExtensionType[]) { function patchPreviewSpec(
id: 'preview:edgeless' | 'preview:page',
specs: ExtensionType[]
) {
const specProvider = SpecProvider.getInstance(); const specProvider = SpecProvider.getInstance();
specProvider.extendSpec(id, specs); specProvider.extendSpec(id, specs);
} }
export function effects() { export function effects() {
// Patch edgeless preview spec for blocksuite surface-ref and embed-synced-doc // Patch edgeless preview spec for blocksuite surface-ref and embed-synced-doc
patchPreviewSpec('edgeless:preview', CustomSpecs); patchPreviewSpec('preview:edgeless', CustomSpecs);
} }
export function getPagePreviewThemeExtension(framework: FrameworkProvider) { export function getPagePreviewThemeExtension(framework: FrameworkProvider) {
@@ -98,7 +101,7 @@ export function createPageModePreviewSpecs(
framework: FrameworkProvider framework: FrameworkProvider
): SpecBuilder { ): SpecBuilder {
const specProvider = SpecProvider.getInstance(); const specProvider = SpecProvider.getInstance();
const pagePreviewSpec = specProvider.getSpec('page:preview'); const pagePreviewSpec = specProvider.getSpec('preview:page');
// Enable theme extension, doc display meta extension and peek view service // Enable theme extension, doc display meta extension and peek view service
const peekViewService = framework.get(PeekViewService); const peekViewService = framework.get(PeekViewService);
pagePreviewSpec.extend([ pagePreviewSpec.extend([

View File

@@ -92,7 +92,7 @@ export const EdgelessSnapshot = (props: Props) => {
const editorHost = new BlockStdScope({ const editorHost = new BlockStdScope({
store: doc, store: doc,
extensions: [ extensions: [
...SpecProvider.getInstance().getSpec('edgeless:preview').value, ...SpecProvider.getInstance().getSpec('preview:edgeless').value,
getThemeExtension(framework), getThemeExtension(framework),
], ],
}).render(); }).render();