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:
@@ -1,9 +1,9 @@
|
||||
import { DatabaseBlockSchema } from '@blocksuite/affine-model';
|
||||
import {
|
||||
AdapterTextUtils,
|
||||
BlockNotionHtmlAdapterExtension,
|
||||
type BlockNotionHtmlAdapterMatcher,
|
||||
HastUtils,
|
||||
TextUtils,
|
||||
} from '@blocksuite/affine-shared/adapters';
|
||||
import { getTagColor } from '@blocksuite/data-view';
|
||||
import { type BlockSnapshot, nanoid } from '@blocksuite/store';
|
||||
@@ -219,7 +219,7 @@ export const databaseBlockNotionHtmlAdapterMatcher: BlockNotionHtmlAdapterMatche
|
||||
column.type = 'rich-text';
|
||||
row[column.id] = {
|
||||
columnId: column.id,
|
||||
value: TextUtils.createText(text),
|
||||
value: AdapterTextUtils.createText(text),
|
||||
};
|
||||
} else {
|
||||
row[column.id] = {
|
||||
@@ -235,11 +235,11 @@ export const databaseBlockNotionHtmlAdapterMatcher: BlockNotionHtmlAdapterMatche
|
||||
}
|
||||
if (
|
||||
column.type === 'rich-text' &&
|
||||
!TextUtils.isText(row[column.id].value)
|
||||
!AdapterTextUtils.isText(row[column.id].value)
|
||||
) {
|
||||
row[column.id] = {
|
||||
columnId: column.id,
|
||||
value: TextUtils.createText(row[column.id].value),
|
||||
value: AdapterTextUtils.createText(row[column.id].value),
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -197,7 +197,7 @@ async function renderNoteContent(
|
||||
match: ids.map(id => ({ id, viewType: 'display' })),
|
||||
};
|
||||
const previewDoc = doc.doc.getStore({ query });
|
||||
const previewSpec = SpecProvider.getInstance().getSpec('page:preview');
|
||||
const previewSpec = SpecProvider.getInstance().getSpec('preview:page');
|
||||
const previewStd = new BlockStdScope({
|
||||
store: previewDoc,
|
||||
extensions: previewSpec.value,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { EmbedLinkedDocBlockSchema } from '@blocksuite/affine-model';
|
||||
import {
|
||||
AdapterTextUtils,
|
||||
BlockHtmlAdapterExtension,
|
||||
type BlockHtmlAdapterMatcher,
|
||||
TextUtils,
|
||||
} from '@blocksuite/affine-shared/adapters';
|
||||
|
||||
export const embedLinkedDocBlockHtmlAdapterMatcher: BlockHtmlAdapterMatcher = {
|
||||
@@ -18,7 +18,7 @@ export const embedLinkedDocBlockHtmlAdapterMatcher: BlockHtmlAdapterMatcher = {
|
||||
return;
|
||||
}
|
||||
const title = configs.get('title:' + o.node.props.pageId) ?? 'untitled';
|
||||
const url = TextUtils.generateDocUrl(
|
||||
const url = AdapterTextUtils.generateDocUrl(
|
||||
configs.get('docLinkBaseUrl') ?? '',
|
||||
String(o.node.props.pageId),
|
||||
o.node.props.params ?? Object.create(null)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { EmbedLinkedDocBlockSchema } from '@blocksuite/affine-model';
|
||||
import {
|
||||
AdapterTextUtils,
|
||||
BlockMarkdownAdapterExtension,
|
||||
type BlockMarkdownAdapterMatcher,
|
||||
TextUtils,
|
||||
} from '@blocksuite/affine-shared/adapters';
|
||||
|
||||
export const embedLinkedDocBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMatcher =
|
||||
@@ -19,7 +19,7 @@ export const embedLinkedDocBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMatc
|
||||
return;
|
||||
}
|
||||
const title = configs.get('title:' + o.node.props.pageId) ?? 'untitled';
|
||||
const url = TextUtils.generateDocUrl(
|
||||
const url = AdapterTextUtils.generateDocUrl(
|
||||
configs.get('docLinkBaseUrl') ?? '',
|
||||
String(o.node.props.pageId),
|
||||
o.node.props.params ?? Object.create(null)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { EmbedLinkedDocBlockSchema } from '@blocksuite/affine-model';
|
||||
import {
|
||||
AdapterTextUtils,
|
||||
BlockPlainTextAdapterExtension,
|
||||
type BlockPlainTextAdapterMatcher,
|
||||
TextUtils,
|
||||
} from '@blocksuite/affine-shared/adapters';
|
||||
|
||||
export const embedLinkedDocBlockPlainTextAdapterMatcher: BlockPlainTextAdapterMatcher =
|
||||
@@ -19,7 +19,7 @@ export const embedLinkedDocBlockPlainTextAdapterMatcher: BlockPlainTextAdapterMa
|
||||
return;
|
||||
}
|
||||
const title = configs.get('title:' + o.node.props.pageId) ?? 'untitled';
|
||||
const url = TextUtils.generateDocUrl(
|
||||
const url = AdapterTextUtils.generateDocUrl(
|
||||
configs.get('docLinkBaseUrl') ?? '',
|
||||
String(o.node.props.pageId),
|
||||
o.node.props.params ?? Object.create(null)
|
||||
|
||||
@@ -70,7 +70,7 @@ export class EmbedEdgelessSyncedDocBlockComponent extends toEdgelessEmbedBlock(
|
||||
<div class="affine-page-viewport" data-theme=${appTheme}>
|
||||
${new BlockStdScope({
|
||||
store: syncedDoc,
|
||||
extensions: this._buildPreviewSpec('page:preview'),
|
||||
extensions: this._buildPreviewSpec('preview:page'),
|
||||
}).render()}
|
||||
</div>
|
||||
`,
|
||||
@@ -81,7 +81,7 @@ export class EmbedEdgelessSyncedDocBlockComponent extends toEdgelessEmbedBlock(
|
||||
<div class="affine-edgeless-viewport" data-theme=${edgelessTheme}>
|
||||
${new BlockStdScope({
|
||||
store: syncedDoc,
|
||||
extensions: this._buildPreviewSpec('edgeless:preview'),
|
||||
extensions: this._buildPreviewSpec('preview:edgeless'),
|
||||
}).render()}
|
||||
</div>
|
||||
`,
|
||||
|
||||
@@ -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 previewSpecBuilder = SpecProvider.getInstance().getSpec(name);
|
||||
const currentDisposables = this.disposables;
|
||||
@@ -203,7 +203,7 @@ export class EmbedSyncedDocBlockComponent extends EmbedBlockComponent<EmbedSynce
|
||||
<div class="affine-page-viewport" data-theme=${appTheme}>
|
||||
${new BlockStdScope({
|
||||
store: syncedDoc,
|
||||
extensions: this._buildPreviewSpec('page:preview'),
|
||||
extensions: this._buildPreviewSpec('preview:page'),
|
||||
}).render()}
|
||||
</div>
|
||||
`,
|
||||
@@ -214,7 +214,7 @@ export class EmbedSyncedDocBlockComponent extends EmbedBlockComponent<EmbedSynce
|
||||
<div class="affine-edgeless-viewport" data-theme=${edgelessTheme}>
|
||||
${new BlockStdScope({
|
||||
store: syncedDoc,
|
||||
extensions: this._buildPreviewSpec('edgeless:preview'),
|
||||
extensions: this._buildPreviewSpec('preview:edgeless'),
|
||||
}).render()}
|
||||
</div>
|
||||
`,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './frame-block.js';
|
||||
export * from './frame-manager.js';
|
||||
export * from './frame-spec.js';
|
||||
export * from './tool.js';
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { NavigatorMode } from '@blocksuite/affine-block-frame';
|
||||
import { BaseTool } from '@blocksuite/block-std/gfx';
|
||||
|
||||
import type { NavigatorMode } from './frame-manager';
|
||||
|
||||
type PresentToolOption = {
|
||||
mode?: NavigatorMode;
|
||||
};
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './html.js';
|
||||
export * from './markdown.js';
|
||||
export * from './middleware.js';
|
||||
export * from './notion-html.js';
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { ListBlockSchema } from '@blocksuite/affine-model';
|
||||
import {
|
||||
AdapterTextUtils,
|
||||
BlockHtmlAdapterExtension,
|
||||
type BlockHtmlAdapterMatcher,
|
||||
HastUtils,
|
||||
TextUtils,
|
||||
} from '@blocksuite/affine-shared/adapters';
|
||||
import type { DeltaInsert } from '@blocksuite/inline';
|
||||
import { nanoid } from '@blocksuite/store';
|
||||
@@ -124,7 +124,7 @@ export const listBlockHtmlAdapterMatcher: BlockHtmlAdapterMatcher = {
|
||||
Array.isArray(currentTNode.properties.className) &&
|
||||
currentTNode.properties.className.includes('todo-list')
|
||||
) ===
|
||||
TextUtils.isNullish(
|
||||
AdapterTextUtils.isNullish(
|
||||
o.node.props.type === 'todo'
|
||||
? (o.node.props.checked as boolean)
|
||||
: undefined
|
||||
@@ -177,7 +177,7 @@ export const listBlockHtmlAdapterMatcher: BlockHtmlAdapterMatcher = {
|
||||
Array.isArray(previousTNode.properties.className) &&
|
||||
previousTNode.properties.className.includes('todo-list')
|
||||
) ===
|
||||
TextUtils.isNullish(
|
||||
AdapterTextUtils.isNullish(
|
||||
o.node.props.type === 'todo'
|
||||
? (o.node.props.checked as boolean)
|
||||
: undefined
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { ListBlockSchema } from '@blocksuite/affine-model';
|
||||
import {
|
||||
AdapterTextUtils,
|
||||
BlockMarkdownAdapterExtension,
|
||||
type BlockMarkdownAdapterMatcher,
|
||||
type MarkdownAST,
|
||||
TextUtils,
|
||||
} from '@blocksuite/affine-shared/adapters';
|
||||
import type { DeltaInsert } from '@blocksuite/inline';
|
||||
import { nanoid } from '@blocksuite/store';
|
||||
@@ -75,8 +75,8 @@ export const listBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMatcher = {
|
||||
walkerContext.getNodeContext('affine:list:parent') === o.parent &&
|
||||
currentTNode.type === 'list' &&
|
||||
currentTNode.ordered === (o.node.props.type === 'numbered') &&
|
||||
TextUtils.isNullish(currentTNode.children[0].checked) ===
|
||||
TextUtils.isNullish(
|
||||
AdapterTextUtils.isNullish(currentTNode.children[0].checked) ===
|
||||
AdapterTextUtils.isNullish(
|
||||
o.node.props.type === 'todo'
|
||||
? (o.node.props.checked as boolean)
|
||||
: undefined
|
||||
@@ -129,8 +129,8 @@ export const listBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMatcher = {
|
||||
currentTNode.type === 'listItem' &&
|
||||
previousTNode?.type === 'list' &&
|
||||
previousTNode.ordered === (o.node.props.type === 'numbered') &&
|
||||
TextUtils.isNullish(currentTNode.checked) ===
|
||||
TextUtils.isNullish(
|
||||
AdapterTextUtils.isNullish(currentTNode.checked) ===
|
||||
AdapterTextUtils.isNullish(
|
||||
o.node.props.type === 'todo'
|
||||
? (o.node.props.checked as boolean)
|
||||
: undefined
|
||||
|
||||
@@ -118,7 +118,7 @@ export class SurfaceRefNotePortal extends WithDisposable(ShadowlessElement) {
|
||||
query: this.query,
|
||||
readonly: true,
|
||||
});
|
||||
const previewSpec = SpecProvider.getInstance().getSpec('page:preview');
|
||||
const previewSpec = SpecProvider.getInstance().getSpec('preview:page');
|
||||
return new BlockStdScope({
|
||||
store: doc,
|
||||
extensions: previewSpec.value.slice(),
|
||||
|
||||
@@ -240,7 +240,7 @@ export class SurfaceRefBlockComponent extends BlockComponent<SurfaceRefBlockMode
|
||||
private _previewDoc: Store | null = null;
|
||||
|
||||
private readonly _previewSpec =
|
||||
SpecProvider.getInstance().getSpec('edgeless:preview');
|
||||
SpecProvider.getInstance().getSpec('preview:edgeless');
|
||||
|
||||
private _referencedModel: GfxModel | null = null;
|
||||
|
||||
|
||||
@@ -4,7 +4,10 @@ import type {
|
||||
TableColumn,
|
||||
TableRow,
|
||||
} 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 type { DeltaInsert } from '@blocksuite/inline';
|
||||
import { nanoid } from '@blocksuite/store';
|
||||
@@ -155,7 +158,7 @@ export const createTableProps = (rowTextLists: string[][]) => {
|
||||
const cellId = `${row.rowId}:${column.columnId}`;
|
||||
const text = rowTextLists[i]?.[j];
|
||||
cells[cellId] = {
|
||||
text: TextUtils.createText(text ?? ''),
|
||||
text: AdapterTextUtils.createText(text ?? ''),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { InlineHtmlAST } from '@blocksuite/affine-shared/adapters';
|
||||
import {
|
||||
AdapterTextUtils,
|
||||
InlineDeltaToHtmlAdapterExtension,
|
||||
TextUtils,
|
||||
} from '@blocksuite/affine-shared/adapters';
|
||||
import { ThemeProvider } from '@blocksuite/affine-shared/services';
|
||||
|
||||
@@ -90,7 +90,7 @@ export const referenceDeltaToHtmlAdapterMatcher =
|
||||
|
||||
const { configs } = context;
|
||||
const title = configs.get(`title:${reference.pageId}`);
|
||||
const url = TextUtils.generateDocUrl(
|
||||
const url = AdapterTextUtils.generateDocUrl(
|
||||
configs.get('docLinkBaseUrl') ?? '',
|
||||
String(reference.pageId),
|
||||
reference.params ?? Object.create(null)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
AdapterTextUtils,
|
||||
FOOTNOTE_DEFINITION_PREFIX,
|
||||
InlineDeltaToMarkdownAdapterExtension,
|
||||
TextUtils,
|
||||
} from '@blocksuite/affine-shared/adapters';
|
||||
import type { PhrasingContent } from 'mdast';
|
||||
import type RemarkMath from 'remark-math';
|
||||
@@ -74,7 +74,7 @@ export const referenceDeltaToMarkdownAdapterMatcher =
|
||||
const { configs } = context;
|
||||
const title = configs.get(`title:${reference.pageId}`);
|
||||
const params = reference.params ?? {};
|
||||
const url = TextUtils.generateDocUrl(
|
||||
const url = AdapterTextUtils.generateDocUrl(
|
||||
configs.get('docLinkBaseUrl') ?? '',
|
||||
String(reference.pageId),
|
||||
params
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
AdapterTextUtils,
|
||||
InlineDeltaToPlainTextAdapterExtension,
|
||||
type TextBuffer,
|
||||
TextUtils,
|
||||
} from '@blocksuite/affine-shared/adapters';
|
||||
import type { ExtensionType } from '@blocksuite/store';
|
||||
|
||||
@@ -20,7 +20,7 @@ export const referenceDeltaMarkdownAdapterMatch =
|
||||
|
||||
const { configs } = context;
|
||||
const title = configs.get(`title:${reference.pageId}`) ?? '';
|
||||
const url = TextUtils.generateDocUrl(
|
||||
const url = AdapterTextUtils.generateDocUrl(
|
||||
configs.get('docLinkBaseUrl') ?? '',
|
||||
String(reference.pageId),
|
||||
reference.params ?? Object.create(null)
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
export * from './frame-panel';
|
||||
export * from './tool';
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
import type { GfxModel } from '@blocksuite/block-std/gfx';
|
||||
|
||||
import type {
|
||||
BrushElementModel,
|
||||
ConnectorElementModel,
|
||||
GroupElementModel,
|
||||
} from '../elements';
|
||||
|
||||
export type EmbedCardStyle =
|
||||
| 'horizontal'
|
||||
| 'horizontalThin'
|
||||
@@ -17,3 +25,8 @@ export type LinkPreviewData = {
|
||||
image: string | null;
|
||||
title: string | null;
|
||||
};
|
||||
|
||||
export type Connectable = Exclude<
|
||||
GfxModel,
|
||||
ConnectorElementModel | BrushElementModel | GroupElementModel
|
||||
>;
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
type InlineDeltaMatcher,
|
||||
} from '../types/adapter.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>;
|
||||
|
||||
@@ -119,7 +119,7 @@ export class HtmlDeltaConverter extends DeltaASTConverter<
|
||||
options: DeltaASTConverterOptions = Object.create(null)
|
||||
): DeltaInsert<AffineTextAttributes>[] {
|
||||
return this._spreadAstToDelta(ast, options).reduce((acc, cur) => {
|
||||
return TextUtils.mergeDeltas(acc, cur);
|
||||
return AdapterTextUtils.mergeDeltas(acc, cur);
|
||||
}, [] as DeltaInsert<AffineTextAttributes>[]);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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,
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
};
|
||||
@@ -1,4 +1,8 @@
|
||||
export * from './code';
|
||||
export * from './copy';
|
||||
export * from './doc-link';
|
||||
export * from './file-name';
|
||||
export * from './paste';
|
||||
export * from './replace-id';
|
||||
export * from './surface-ref-to-embed';
|
||||
export * from './title';
|
||||
|
||||
200
blocksuite/affine/shared/src/adapters/middlewares/replace-id.ts
Normal file
200
blocksuite/affine/shared/src/adapters/middlewares/replace-id.ts
Normal file
@@ -0,0 +1,200 @@
|
||||
import type {
|
||||
DatabaseBlockModel,
|
||||
EmbedLinkedDocModel,
|
||||
EmbedSyncedDocModel,
|
||||
ListBlockModel,
|
||||
ParagraphBlockModel,
|
||||
SurfaceRefBlockModel,
|
||||
} from '@blocksuite/affine-model';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import type { DeltaOperation, 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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
11
blocksuite/affine/shared/src/adapters/middlewares/title.ts
Normal file
11
blocksuite/affine/shared/src/adapters/middlewares/title.ts
Normal 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);
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -86,7 +86,7 @@ function generateDocUrl(
|
||||
return url;
|
||||
}
|
||||
|
||||
export const TextUtils = {
|
||||
export const AdapterTextUtils = {
|
||||
mergeDeltas,
|
||||
isNullish,
|
||||
createText,
|
||||
|
||||
@@ -3,10 +3,17 @@ import type { ExtensionType } from '@blocksuite/store';
|
||||
|
||||
import { SpecBuilder } from './spec-builder.js';
|
||||
|
||||
type SpecId =
|
||||
| 'store'
|
||||
| 'page'
|
||||
| 'edgeless'
|
||||
| 'preview:page'
|
||||
| 'preview:edgeless';
|
||||
|
||||
export class SpecProvider {
|
||||
static instance: SpecProvider;
|
||||
|
||||
private readonly specMap = new Map<string, ExtensionType[]>();
|
||||
private readonly specMap = new Map<SpecId, ExtensionType[]>();
|
||||
|
||||
private constructor() {}
|
||||
|
||||
@@ -17,17 +24,17 @@ export class SpecProvider {
|
||||
return SpecProvider.instance;
|
||||
}
|
||||
|
||||
addSpec(id: string, spec: ExtensionType[]) {
|
||||
addSpec(id: SpecId, spec: ExtensionType[]) {
|
||||
if (!this.specMap.has(id)) {
|
||||
this.specMap.set(id, spec);
|
||||
}
|
||||
}
|
||||
|
||||
clearSpec(id: string) {
|
||||
clearSpec(id: SpecId) {
|
||||
this.specMap.delete(id);
|
||||
}
|
||||
|
||||
extendSpec(id: string, newSpec: ExtensionType[]) {
|
||||
extendSpec(id: SpecId, newSpec: ExtensionType[]) {
|
||||
const existingSpec = this.specMap.get(id);
|
||||
if (!existingSpec) {
|
||||
console.error(`Spec not found for ${id}`);
|
||||
@@ -36,26 +43,17 @@ export class SpecProvider {
|
||||
this.specMap.set(id, [...existingSpec, ...newSpec]);
|
||||
}
|
||||
|
||||
getSpec(id: string) {
|
||||
getSpec(id: SpecId) {
|
||||
const spec = this.specMap.get(id);
|
||||
assertExists(spec, `Spec not found for ${id}`);
|
||||
return new SpecBuilder(spec);
|
||||
}
|
||||
|
||||
hasSpec(id: string) {
|
||||
hasSpec(id: SpecId) {
|
||||
return this.specMap.has(id);
|
||||
}
|
||||
|
||||
cloneSpec(id: string, targetId: string) {
|
||||
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) {
|
||||
omitSpec(id: SpecId, targetSpec: ExtensionType) {
|
||||
const existingSpec = this.specMap.get(id);
|
||||
if (!existingSpec) {
|
||||
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);
|
||||
if (!existingSpec) {
|
||||
console.error(`Spec not found for ${id}`);
|
||||
|
||||
@@ -103,7 +103,7 @@ export class PreviewHelper {
|
||||
const query = this._calculateQuery(blockIds as string[], mode);
|
||||
const store = widget.doc.doc.getStore({ query });
|
||||
const previewSpec = SpecProvider.getInstance().getSpec(
|
||||
isEdgeless ? 'edgeless:preview' : 'page:preview'
|
||||
isEdgeless ? 'preview:edgeless' : 'preview:page'
|
||||
);
|
||||
const settingSignal = signal({ ...editorSetting });
|
||||
const extensions = [
|
||||
|
||||
Reference in New Issue
Block a user