refactor(editor): replace-id middlware (#12250)

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **Refactor**
  - Improved the internal structure and clarity of ID handling during import processes, leading to more maintainable and modular code. No changes to user-facing functionality.
- **Chores**
  - Enhanced type definitions for import events to improve code readability and maintainability. No impact on end-user experience.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
fundon
2025-05-13 12:27:52 +00:00
parent 08d6c5a97c
commit e4f32cd61e
2 changed files with 79 additions and 53 deletions

View File

@@ -1,4 +1,4 @@
import type {
import {
DatabaseBlockModel,
EmbedLinkedDocModel,
EmbedSyncedDocModel,
@@ -7,32 +7,50 @@ import type {
SurfaceRefBlockModel,
} from '@blocksuite/affine-model';
import { BlockSuiteError } from '@blocksuite/global/exceptions';
import type { DeltaOperation, TransformerMiddleware } from '@blocksuite/store';
import type {
AfterImportBlockPayload,
BeforeImportBlockPayload,
DeltaOperation,
TransformerMiddleware,
} from '@blocksuite/store';
import { filter, map } from 'rxjs';
import { matchModels } from '../../utils';
export const replaceIdMiddleware =
(idGenerator: () => string): TransformerMiddleware =>
({ slots, docCRUD }) => {
const idMap = new Map<string, string>();
slots.afterImport.subscribe(payload => {
if (
payload.type === 'block' &&
payload.snapshot.flavour === 'affine:database'
) {
const model = payload.model as DatabaseBlockModel;
// After Import
const afterImportBlock$ = slots.afterImport.pipe(
filter(
(payload): payload is AfterImportBlockPayload =>
payload.type === 'block'
),
map(({ model }) => model)
);
afterImportBlock$
.pipe(filter(model => matchModels(model, [DatabaseBlockModel])))
.subscribe(model => {
Object.keys(model.props.cells).forEach(cellId => {
if (idMap.has(cellId)) {
model.props.cells[idMap.get(cellId)!] = model.props.cells[cellId];
delete model.props.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;
// replace LinkedPage pageId with new id in paragraph blocks
afterImportBlock$
.pipe(
filter(model =>
matchModels(model, [ParagraphBlockModel, ListBlockModel])
)
)
.subscribe(model => {
let prev = 0;
const delta: DeltaOperation[] = [];
for (const d of model.props.text.toDelta()) {
@@ -64,13 +82,11 @@ export const replaceIdMiddleware =
if (delta.length > 0) {
model.props.text.applyDelta(delta);
}
}
});
if (
payload.type === 'block' &&
payload.snapshot.flavour === 'affine:surface-ref'
) {
const model = payload.model as SurfaceRefBlockModel;
afterImportBlock$
.pipe(filter(model => matchModels(model, [SurfaceRefBlockModel])))
.subscribe(model => {
const original = model.props.reference;
// If there exists a replacement, replace the reference with the new id.
// Otherwise,
@@ -86,18 +102,16 @@ export const replaceIdMiddleware =
idMap.set(original, newId);
model.props.reference = newId;
}
}
});
// TODO(@fundon): process linked block/element
if (
payload.type === 'block' &&
['affine:embed-linked-doc', 'affine:embed-synced-doc'].includes(
payload.snapshot.flavour
// TODO(@fundon): process linked block/element
afterImportBlock$
.pipe(
filter(model =>
matchModels(model, [EmbedLinkedDocModel, EmbedSyncedDocModel])
)
) {
const model = payload.model as
| EmbedLinkedDocModel
| EmbedSyncedDocModel;
)
.subscribe(model => {
const original = model.props.pageId;
// If the pageId is not in the doc, generate a new id.
// If we already have a replacement, use it.
@@ -110,10 +124,13 @@ export const replaceIdMiddleware =
model.props.pageId = newId;
}
}
}
});
slots.beforeImport.subscribe(payload => {
if (payload.type === 'page') {
});
// Before Import
slots.beforeImport
.pipe(filter(payload => payload.type === 'page'))
.subscribe(payload => {
if (idMap.has(payload.snapshot.meta.id)) {
payload.snapshot.meta.id = idMap.get(payload.snapshot.meta.id)!;
return;
@@ -121,10 +138,16 @@ export const replaceIdMiddleware =
const newId = idGenerator();
idMap.set(payload.snapshot.meta.id, newId);
payload.snapshot.meta.id = newId;
return;
}
});
if (payload.type === 'block') {
slots.beforeImport
.pipe(
filter(
(payload): payload is BeforeImportBlockPayload =>
payload.type === 'block'
)
)
.subscribe(payload => {
const { snapshot } = payload;
if (snapshot.flavour === 'affine:page') {
const index = snapshot.children.findIndex(
@@ -210,6 +233,5 @@ export const replaceIdMiddleware =
}
});
}
}
});
});
};

View File

@@ -11,13 +11,15 @@ import type {
SliceSnapshot,
} from './type.js';
export type BeforeImportBlockPayload = {
snapshot: BlockSnapshot;
type: 'block';
parent?: string;
index?: number;
};
export type BeforeImportPayload =
| {
snapshot: BlockSnapshot;
type: 'block';
parent?: string;
index?: number;
}
| BeforeImportBlockPayload
| {
snapshot: SliceSnapshot;
type: 'slice';
@@ -71,14 +73,16 @@ export type AfterExportPayload =
type: 'info';
};
export type AfterImportBlockPayload = {
snapshot: BlockSnapshot;
type: 'block';
model: BlockModel;
parent?: string;
index?: number;
};
export type AfterImportPayload =
| {
snapshot: BlockSnapshot;
type: 'block';
model: BlockModel;
parent?: string;
index?: number;
}
| AfterImportBlockPayload
| {
snapshot: DocSnapshot;
type: 'page';