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

View File

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