@blocksuite/store
Advanced tools
Comparing version 0.0.0-canary-20241014001453 to 0.0.0-canary-20241015001406
# @blocksuite/store | ||
## 0.0.0-canary-20241014001453 | ||
## 0.0.0-canary-20241015001406 | ||
@@ -9,5 +9,5 @@ ### Patch Changes | ||
- Updated dependencies | ||
- @blocksuite/global@0.0.0-canary-20241014001453 | ||
- @blocksuite/inline@0.0.0-canary-20241014001453 | ||
- @blocksuite/sync@0.0.0-canary-20241014001453 | ||
- @blocksuite/global@0.0.0-canary-20241015001406 | ||
- @blocksuite/inline@0.0.0-canary-20241015001406 | ||
- @blocksuite/sync@0.0.0-canary-20241015001406 | ||
@@ -14,0 +14,0 @@ ## 0.17.18 |
@@ -46,3 +46,3 @@ import type { Doc } from '../store/index.js'; | ||
constructor(job: Job); | ||
fromBlock(mode: DraftModel): Promise<FromBlockSnapshotResult<AdapterTarget> | undefined>; | ||
fromBlock(model: DraftModel): Promise<FromBlockSnapshotResult<AdapterTarget> | undefined>; | ||
abstract fromBlockSnapshot(payload: FromBlockSnapshotPayload): Promise<FromBlockSnapshotResult<AdapterTarget>> | FromBlockSnapshotResult<AdapterTarget>; | ||
@@ -49,0 +49,0 @@ fromDoc(doc: Doc): Promise<FromDocSnapshotResult<AdapterTarget> | undefined>; |
@@ -10,5 +10,5 @@ import { assertEquals } from '@blocksuite/global/utils'; | ||
} | ||
async fromBlock(mode) { | ||
async fromBlock(model) { | ||
try { | ||
const blockSnapshot = await this.job.blockToSnapshot(mode); | ||
const blockSnapshot = await this.job.blockToSnapshot(model); | ||
if (!blockSnapshot) | ||
@@ -15,0 +15,0 @@ return; |
@@ -15,3 +15,3 @@ import type { BlockModel, InternalPrimitives } from '../schema/index.js'; | ||
}; | ||
export type SnapshotReturn<Props extends object> = { | ||
export type SnapshotNode<Props extends object> = { | ||
id: string; | ||
@@ -28,3 +28,3 @@ flavour: string; | ||
}; | ||
fromSnapshot({ json, }: FromSnapshotPayload): Promise<SnapshotReturn<Props>> | SnapshotReturn<Props>; | ||
fromSnapshot({ json, }: FromSnapshotPayload): Promise<SnapshotNode<Props>> | SnapshotNode<Props>; | ||
toSnapshot({ model, }: ToSnapshotPayload<Props>): Promise<BlockSnapshotLeaf> | BlockSnapshotLeaf; | ||
@@ -31,0 +31,0 @@ } |
import type { BlockModel } from '../schema/index.js'; | ||
import type { Doc, DocCollection } from '../store/index.js'; | ||
import type { DraftModel } from './draft.js'; | ||
import type { JobMiddleware } from './middleware.js'; | ||
import type { BlockSnapshot, CollectionInfoSnapshot, DocSnapshot, SliceSnapshot } from './type.js'; | ||
import { AssetsManager } from './assets.js'; | ||
import { type DraftModel } from './draft.js'; | ||
import { Slice } from './slice.js'; | ||
@@ -15,7 +15,4 @@ export type JobConfig = { | ||
private readonly _assetsManager; | ||
private _batchCounter; | ||
private readonly _collection; | ||
private readonly _pendingOperations; | ||
private readonly _slots; | ||
private _unblockTimer?; | ||
blockToSnapshot: (model: DraftModel) => Promise<BlockSnapshot | undefined>; | ||
@@ -27,3 +24,3 @@ collectionInfoToSnapshot: () => CollectionInfoSnapshot | undefined; | ||
snapshotToDoc: (snapshot: DocSnapshot) => Promise<Doc | undefined>; | ||
snapshotToModelData: (snapshot: BlockSnapshot) => Promise<import("./base.js").SnapshotReturn<object> | undefined>; | ||
snapshotToModelData: (snapshot: BlockSnapshot) => Promise<import("./base.js").SnapshotNode<object> | undefined>; | ||
snapshotToSlice: (snapshot: SliceSnapshot, doc: Doc, parent?: string, index?: number) => Promise<Slice | undefined>; | ||
@@ -36,11 +33,16 @@ walk: (snapshot: DocSnapshot, callback: (block: BlockSnapshot) => void) => void; | ||
constructor({ collection, middlewares }: JobConfig); | ||
private _batchSnapshotToBlock; | ||
private _blockToSnapshot; | ||
private _convertFlatSnapshots; | ||
private _convertSnapshotToDraftModel; | ||
private _exportDocMeta; | ||
private _flattenSnapshot; | ||
private _getCollectionMeta; | ||
private _getSchema; | ||
private _getTransformer; | ||
private _insertBlockTree; | ||
private _rebuildBlockTree; | ||
private _snapshotToBlock; | ||
private _triggerBeforeImportEvent; | ||
reset(): void; | ||
} | ||
//# sourceMappingURL=job.d.ts.map |
@@ -5,5 +5,19 @@ import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions'; | ||
import { BaseBlockTransformer } from './base.js'; | ||
import { toDraftModel } from './draft.js'; | ||
import { Slice } from './slice.js'; | ||
import { BlockSnapshotSchema, CollectionInfoSnapshotSchema, DocSnapshotSchema, SliceSnapshotSchema, } from './type.js'; | ||
async function nextTick() { | ||
// @ts-ignore | ||
if ('scheduler' in window && 'yield' in window.scheduler) { | ||
// @ts-ignore | ||
return window.scheduler.yield(); | ||
} | ||
else if (typeof requestIdleCallback !== 'undefined') { | ||
return new Promise(resolve => requestIdleCallback(resolve)); | ||
} | ||
else { | ||
return new Promise(resolve => setTimeout(resolve, 0)); | ||
} | ||
} | ||
// The number of blocks to insert in one batch | ||
const BATCH_SIZE = 100; | ||
export class Job { | ||
@@ -24,4 +38,2 @@ get adapterConfigs() { | ||
this._adapterConfigs = new Map(); | ||
this._batchCounter = 0; | ||
this._pendingOperations = []; | ||
this._slots = { | ||
@@ -143,3 +155,5 @@ beforeImport: new Slot(), | ||
BlockSnapshotSchema.parse(snapshot); | ||
const model = await this._batchSnapshotToBlock(snapshot, doc, parent, index); | ||
const model = await this._snapshotToBlock(snapshot, doc, parent, index); | ||
if (!model) | ||
return; | ||
return model; | ||
@@ -201,2 +215,3 @@ } | ||
this.snapshotToSlice = async (snapshot, doc, parent, index) => { | ||
SliceSnapshotSchema.parse(snapshot); | ||
try { | ||
@@ -207,10 +222,23 @@ this._slots.beforeImport.emit({ | ||
}); | ||
SliceSnapshotSchema.parse(snapshot); | ||
const { content, pageVersion, workspaceVersion, workspaceId, pageId } = snapshot; | ||
const contentBlocks = []; | ||
for (const [i, block] of content.entries()) { | ||
contentBlocks.push(await this._batchSnapshotToBlock(block, doc, parent, (index ?? 0) + i)); | ||
// Create a temporary root snapshot to encompass all content blocks | ||
const tmpRootSnapshot = { | ||
id: 'temporary-root', | ||
flavour: 'affine:page', | ||
props: {}, | ||
type: 'block', | ||
children: content, | ||
}; | ||
for (const block of content) { | ||
this._triggerBeforeImportEvent(block, parent, index); | ||
} | ||
const flatSnapshots = []; | ||
this._flattenSnapshot(tmpRootSnapshot, flatSnapshots, parent, index); | ||
const blockTree = await this._convertFlatSnapshots(flatSnapshots); | ||
await this._insertBlockTree(blockTree.children, doc, parent, index); | ||
const contentBlocks = blockTree.children | ||
.map(tree => doc.getBlockById(tree.draft.id)) | ||
.filter(Boolean); | ||
const slice = new Slice({ | ||
content: contentBlocks.map(block => toDraftModel(block)), | ||
content: contentBlocks, | ||
pageVersion, | ||
@@ -260,33 +288,2 @@ workspaceVersion, | ||
} | ||
_batchSnapshotToBlock(snapshot, doc, parent, index) { | ||
return new Promise(resolve => { | ||
if (this._batchCounter < 100) { | ||
resolve(this._snapshotToBlock(snapshot, doc, parent, index)); | ||
} | ||
else { | ||
// This will block the caller function | ||
// so that no further operations can be added to the queue. | ||
// Example: | ||
// for (const snapshot of snapshots) { | ||
// // Block here as it is waiting for the promise to resolve. | ||
// await job.snapshotToBlock(snapshot, doc, id); | ||
// } | ||
this._pendingOperations.push(() => resolve(this._snapshotToBlock(snapshot, doc, parent, index))); | ||
} | ||
this._batchCounter++; | ||
const unblock = () => { | ||
// There should only be one operation in the queue | ||
// as we should create new jobs for each events. | ||
// However, we still need to loop through the list | ||
// to avoid potential bugs. | ||
while (this._pendingOperations.length > 0) { | ||
this._pendingOperations.shift()?.(); | ||
} | ||
this._unblockTimer = undefined; | ||
this._batchCounter = 0; | ||
}; | ||
clearTimeout(this._unblockTimer); | ||
this._unblockTimer = setTimeout(unblock, 10); | ||
}); | ||
} | ||
async _blockToSnapshot(model) { | ||
@@ -318,2 +315,51 @@ this._slots.beforeExport.emit({ | ||
} | ||
async _convertFlatSnapshots(flatSnapshots) { | ||
// Phase 1: Convert snapshots to draft models in series | ||
// This is not time-consuming, this is faster than Promise.all | ||
const draftModels = []; | ||
for (const flat of flatSnapshots) { | ||
const draft = await this._convertSnapshotToDraftModel(flat); | ||
if (draft) { | ||
draft.id = flat.snapshot.id; | ||
} | ||
draftModels.push({ | ||
draft, | ||
snapshot: flat.snapshot, | ||
parentId: flat.parentId, | ||
index: flat.index, | ||
}); | ||
} | ||
// Phase 2: Filter out the models that failed to convert | ||
const validDraftModels = draftModels.filter(item => !!item.draft); | ||
// Phase 3: Rebuild the block trees | ||
const blockTree = this._rebuildBlockTree(validDraftModels); | ||
return blockTree; | ||
} | ||
async _convertSnapshotToDraftModel(flat) { | ||
try { | ||
const { children, flavour } = flat.snapshot; | ||
const schema = this._getSchema(flavour); | ||
const transformer = this._getTransformer(schema); | ||
const { props } = await transformer.fromSnapshot({ | ||
json: { | ||
id: flat.snapshot.id, | ||
flavour: flat.snapshot.flavour, | ||
props: flat.snapshot.props, | ||
}, | ||
assets: this._assetsManager, | ||
children, | ||
}); | ||
return { | ||
id: flat.snapshot.id, | ||
flavour: flat.snapshot.flavour, | ||
children: [], | ||
...props, | ||
}; | ||
} | ||
catch (error) { | ||
console.error(`Error when transforming snapshot to model data:`); | ||
console.error(error); | ||
return; | ||
} | ||
} | ||
_exportDocMeta(doc) { | ||
@@ -331,2 +377,10 @@ const docMeta = doc.meta; | ||
} | ||
_flattenSnapshot(snapshot, flatSnapshots, parentId, index) { | ||
flatSnapshots.push({ snapshot, parentId, index }); | ||
if (snapshot.children) { | ||
snapshot.children.forEach((child, idx) => { | ||
this._flattenSnapshot(child, flatSnapshots, snapshot.id, idx); | ||
}); | ||
} | ||
} | ||
_getCollectionMeta() { | ||
@@ -361,43 +415,76 @@ const { meta } = this._collection; | ||
} | ||
async _snapshotToBlock(snapshot, doc, parent, index) { | ||
this._slots.beforeImport.emit({ | ||
type: 'block', | ||
snapshot, | ||
parent, | ||
index, | ||
async _insertBlockTree(nodes, doc, parentId, startIndex, counter = 0) { | ||
for (let index = 0; index < nodes.length; index++) { | ||
const node = nodes[index]; | ||
const { draft } = node; | ||
const { id, flavour } = draft; | ||
const actualIndex = startIndex !== undefined ? startIndex + index : undefined; | ||
doc.addBlock(flavour, draft, parentId, actualIndex); | ||
const model = doc.getBlockById(id); | ||
if (!model) { | ||
throw new BlockSuiteError(ErrorCode.TransformerError, `Block not found by id ${id}`); | ||
} | ||
this._slots.afterImport.emit({ | ||
type: 'block', | ||
model, | ||
snapshot: node.snapshot, | ||
}); | ||
counter++; | ||
if (counter % BATCH_SIZE === 0) { | ||
await nextTick(); | ||
} | ||
if (node.children.length > 0) { | ||
counter = await this._insertBlockTree(node.children, doc, id, undefined, counter); | ||
} | ||
} | ||
return counter; | ||
} | ||
_rebuildBlockTree(draftModels) { | ||
const nodeMap = new Map(); | ||
// First pass: create nodes and add them to the map | ||
draftModels.forEach(({ draft, snapshot }) => { | ||
nodeMap.set(draft.id, { draft, snapshot, children: [] }); | ||
}); | ||
const { children, flavour, props, id } = snapshot; | ||
const schema = this._getSchema(flavour); | ||
const snapshotLeaf = { | ||
id, | ||
flavour, | ||
props, | ||
}; | ||
const transformer = this._getTransformer(schema); | ||
const modelData = await transformer.fromSnapshot({ | ||
json: snapshotLeaf, | ||
assets: this._assetsManager, | ||
children, | ||
const root = nodeMap.get(draftModels[0].draft.id); | ||
// Second pass: build the tree structure | ||
draftModels.forEach(({ draft, parentId, index }) => { | ||
const node = nodeMap.get(draft.id); | ||
if (!node) | ||
return; | ||
if (parentId) { | ||
const parentNode = nodeMap.get(parentId); | ||
if (parentNode && index !== undefined) { | ||
parentNode.children[index] = node; | ||
} | ||
} | ||
}); | ||
const nextTick = typeof window !== 'undefined' | ||
? window.requestAnimationFrame | ||
: setImmediate; | ||
await new Promise(resolve => nextTick(() => resolve(undefined))); | ||
doc.addBlock(modelData.flavour, { ...modelData.props, id: modelData.id }, parent, index); | ||
for (const [index, child] of children.entries()) { | ||
await this._batchSnapshotToBlock(child, doc, id, index); | ||
if (!root) { | ||
throw new Error('No root node found in the tree'); | ||
} | ||
const model = doc.getBlockById(id); | ||
if (!model) { | ||
throw new BlockSuiteError(ErrorCode.TransformerError, `Block not found by id ${id}`); | ||
} | ||
this._slots.afterImport.emit({ | ||
type: 'block', | ||
snapshot, | ||
model, | ||
parent, | ||
index, | ||
}); | ||
return model; | ||
return root; | ||
} | ||
async _snapshotToBlock(snapshot, doc, parent, index) { | ||
this._triggerBeforeImportEvent(snapshot, parent, index); | ||
const flatSnapshots = []; | ||
this._flattenSnapshot(snapshot, flatSnapshots, parent, index); | ||
const blockTree = await this._convertFlatSnapshots(flatSnapshots); | ||
await this._insertBlockTree([blockTree], doc, parent, index); | ||
return doc.getBlockById(snapshot.id) || null; | ||
} | ||
_triggerBeforeImportEvent(snapshot, parent, index) { | ||
const traverseAndTrigger = (node, parent, index) => { | ||
this._slots.beforeImport.emit({ | ||
type: 'block', | ||
snapshot: node, | ||
parent: parent, | ||
index: index, | ||
}); | ||
if (node.children) { | ||
node.children.forEach((child, idx) => { | ||
traverseAndTrigger(child, node.id, idx); | ||
}); | ||
} | ||
}; | ||
traverseAndTrigger(snapshot, parent, index); | ||
} | ||
reset() { | ||
@@ -404,0 +491,0 @@ this._assetsManager.cleanup(); |
{ | ||
"name": "@blocksuite/store", | ||
"version": "0.0.0-canary-20241014001453", | ||
"version": "0.0.0-canary-20241015001406", | ||
"description": "BlockSuite data store built for general purpose state management.", | ||
@@ -23,5 +23,5 @@ "type": "module", | ||
"dependencies": { | ||
"@blocksuite/global": "0.0.0-canary-20241014001453", | ||
"@blocksuite/inline": "0.0.0-canary-20241014001453", | ||
"@blocksuite/sync": "0.0.0-canary-20241014001453", | ||
"@blocksuite/global": "0.0.0-canary-20241015001406", | ||
"@blocksuite/inline": "0.0.0-canary-20241015001406", | ||
"@blocksuite/sync": "0.0.0-canary-20241015001406", | ||
"@preact/signals-core": "^1.8.0", | ||
@@ -28,0 +28,0 @@ "@types/flexsearch": "^0.7.6", |
@@ -62,5 +62,5 @@ import { assertEquals } from '@blocksuite/global/utils'; | ||
async fromBlock(mode: DraftModel) { | ||
async fromBlock(model: DraftModel) { | ||
try { | ||
const blockSnapshot = await this.job.blockToSnapshot(mode); | ||
const blockSnapshot = await this.job.blockToSnapshot(model); | ||
if (!blockSnapshot) return; | ||
@@ -67,0 +67,0 @@ return await this.fromBlockSnapshot({ |
@@ -25,3 +25,3 @@ import type { BlockModel, InternalPrimitives } from '../schema/index.js'; | ||
export type SnapshotReturn<Props extends object> = { | ||
export type SnapshotNode<Props extends object> = { | ||
id: string; | ||
@@ -55,5 +55,3 @@ flavour: string; | ||
json, | ||
}: FromSnapshotPayload): | ||
| Promise<SnapshotReturn<Props>> | ||
| SnapshotReturn<Props> { | ||
}: FromSnapshotPayload): Promise<SnapshotNode<Props>> | SnapshotNode<Props> { | ||
const { flavour, id, version, props: _props } = json; | ||
@@ -60,0 +58,0 @@ |
@@ -6,2 +6,3 @@ import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions'; | ||
import type { Doc, DocCollection, DocMeta } from '../store/index.js'; | ||
import type { DraftModel } from './draft.js'; | ||
import type { | ||
@@ -23,3 +24,2 @@ BeforeExportPayload, | ||
import { BaseBlockTransformer } from './base.js'; | ||
import { type DraftModel, toDraftModel } from './draft.js'; | ||
import { Slice } from './slice.js'; | ||
@@ -38,2 +38,29 @@ import { | ||
interface FlatSnapshot { | ||
snapshot: BlockSnapshot; | ||
parentId?: string; | ||
index?: number; | ||
} | ||
interface DraftBlockTreeNode { | ||
draft: DraftModel; | ||
snapshot: BlockSnapshot; | ||
children: Array<DraftBlockTreeNode>; | ||
} | ||
async function nextTick() { | ||
// @ts-ignore | ||
if ('scheduler' in window && 'yield' in window.scheduler) { | ||
// @ts-ignore | ||
return window.scheduler.yield(); | ||
} else if (typeof requestIdleCallback !== 'undefined') { | ||
return new Promise(resolve => requestIdleCallback(resolve)); | ||
} else { | ||
return new Promise(resolve => setTimeout(resolve, 0)); | ||
} | ||
} | ||
// The number of blocks to insert in one batch | ||
const BATCH_SIZE = 100; | ||
export class Job { | ||
@@ -44,8 +71,4 @@ private readonly _adapterConfigs = new Map<string, string>(); | ||
private _batchCounter = 0; | ||
private readonly _collection: DocCollection; | ||
private readonly _pendingOperations: (() => void)[] = []; | ||
private readonly _slots: JobSlots = { | ||
@@ -58,4 +81,2 @@ beforeImport: new Slot<BeforeImportPayload>(), | ||
private _unblockTimer?: ReturnType<typeof setTimeout>; | ||
blockToSnapshot = async ( | ||
@@ -188,9 +209,4 @@ model: DraftModel | ||
BlockSnapshotSchema.parse(snapshot); | ||
const model = await this._batchSnapshotToBlock( | ||
snapshot, | ||
doc, | ||
parent, | ||
index | ||
); | ||
const model = await this._snapshotToBlock(snapshot, doc, parent, index); | ||
if (!model) return; | ||
return model; | ||
@@ -260,2 +276,3 @@ } catch (error) { | ||
): Promise<Slice | undefined> => { | ||
SliceSnapshotSchema.parse(snapshot); | ||
try { | ||
@@ -266,13 +283,31 @@ this._slots.beforeImport.emit({ | ||
}); | ||
SliceSnapshotSchema.parse(snapshot); | ||
const { content, pageVersion, workspaceVersion, workspaceId, pageId } = | ||
snapshot; | ||
const contentBlocks: BlockModel[] = []; | ||
for (const [i, block] of content.entries()) { | ||
contentBlocks.push( | ||
await this._batchSnapshotToBlock(block, doc, parent, (index ?? 0) + i) | ||
); | ||
// Create a temporary root snapshot to encompass all content blocks | ||
const tmpRootSnapshot: BlockSnapshot = { | ||
id: 'temporary-root', | ||
flavour: 'affine:page', | ||
props: {}, | ||
type: 'block', | ||
children: content, | ||
}; | ||
for (const block of content) { | ||
this._triggerBeforeImportEvent(block, parent, index); | ||
} | ||
const flatSnapshots: FlatSnapshot[] = []; | ||
this._flattenSnapshot(tmpRootSnapshot, flatSnapshots, parent, index); | ||
const blockTree = await this._convertFlatSnapshots(flatSnapshots); | ||
await this._insertBlockTree(blockTree.children, doc, parent, index); | ||
const contentBlocks = blockTree.children | ||
.map(tree => doc.getBlockById(tree.draft.id)) | ||
.filter(Boolean) as DraftModel[]; | ||
const slice = new Slice({ | ||
content: contentBlocks.map(block => toDraftModel(block)), | ||
content: contentBlocks, | ||
pageVersion, | ||
@@ -283,2 +318,3 @@ workspaceVersion, | ||
}); | ||
this._slots.afterImport.emit({ | ||
@@ -345,40 +381,2 @@ type: 'slice', | ||
private _batchSnapshotToBlock( | ||
snapshot: BlockSnapshot, | ||
doc: Doc, | ||
parent?: string, | ||
index?: number | ||
) { | ||
return new Promise<BlockModel>(resolve => { | ||
if (this._batchCounter < 100) { | ||
resolve(this._snapshotToBlock(snapshot, doc, parent, index)); | ||
} else { | ||
// This will block the caller function | ||
// so that no further operations can be added to the queue. | ||
// Example: | ||
// for (const snapshot of snapshots) { | ||
// // Block here as it is waiting for the promise to resolve. | ||
// await job.snapshotToBlock(snapshot, doc, id); | ||
// } | ||
this._pendingOperations.push(() => | ||
resolve(this._snapshotToBlock(snapshot, doc, parent, index)) | ||
); | ||
} | ||
this._batchCounter++; | ||
const unblock = () => { | ||
// There should only be one operation in the queue | ||
// as we should create new jobs for each events. | ||
// However, we still need to loop through the list | ||
// to avoid potential bugs. | ||
while (this._pendingOperations.length > 0) { | ||
this._pendingOperations.shift()?.(); | ||
} | ||
this._unblockTimer = undefined; | ||
this._batchCounter = 0; | ||
}; | ||
clearTimeout(this._unblockTimer); | ||
this._unblockTimer = setTimeout(unblock, 10); | ||
}); | ||
} | ||
private async _blockToSnapshot(model: DraftModel): Promise<BlockSnapshot> { | ||
@@ -414,2 +412,62 @@ this._slots.beforeExport.emit({ | ||
private async _convertFlatSnapshots(flatSnapshots: FlatSnapshot[]) { | ||
// Phase 1: Convert snapshots to draft models in series | ||
// This is not time-consuming, this is faster than Promise.all | ||
const draftModels = []; | ||
for (const flat of flatSnapshots) { | ||
const draft = await this._convertSnapshotToDraftModel(flat); | ||
if (draft) { | ||
draft.id = flat.snapshot.id; | ||
} | ||
draftModels.push({ | ||
draft, | ||
snapshot: flat.snapshot, | ||
parentId: flat.parentId, | ||
index: flat.index, | ||
}); | ||
} | ||
// Phase 2: Filter out the models that failed to convert | ||
const validDraftModels = draftModels.filter(item => !!item.draft) as { | ||
draft: DraftModel; | ||
snapshot: BlockSnapshot; | ||
parentId?: string; | ||
index?: number; | ||
}[]; | ||
// Phase 3: Rebuild the block trees | ||
const blockTree = this._rebuildBlockTree(validDraftModels); | ||
return blockTree; | ||
} | ||
private async _convertSnapshotToDraftModel( | ||
flat: FlatSnapshot | ||
): Promise<DraftModel | undefined> { | ||
try { | ||
const { children, flavour } = flat.snapshot; | ||
const schema = this._getSchema(flavour); | ||
const transformer = this._getTransformer(schema); | ||
const { props } = await transformer.fromSnapshot({ | ||
json: { | ||
id: flat.snapshot.id, | ||
flavour: flat.snapshot.flavour, | ||
props: flat.snapshot.props, | ||
}, | ||
assets: this._assetsManager, | ||
children, | ||
}); | ||
return { | ||
id: flat.snapshot.id, | ||
flavour: flat.snapshot.flavour, | ||
children: [], | ||
...props, | ||
} as DraftModel; | ||
} catch (error) { | ||
console.error(`Error when transforming snapshot to model data:`); | ||
console.error(error); | ||
return; | ||
} | ||
} | ||
private _exportDocMeta(doc: Doc): DocSnapshot['meta'] { | ||
@@ -432,2 +490,16 @@ const docMeta = doc.meta; | ||
private _flattenSnapshot( | ||
snapshot: BlockSnapshot, | ||
flatSnapshots: FlatSnapshot[], | ||
parentId?: string, | ||
index?: number | ||
) { | ||
flatSnapshots.push({ snapshot, parentId, index }); | ||
if (snapshot.children) { | ||
snapshot.children.forEach((child, idx) => { | ||
this._flattenSnapshot(child, flatSnapshots, snapshot.id, idx); | ||
}); | ||
} | ||
} | ||
private _getCollectionMeta() { | ||
@@ -474,2 +546,86 @@ const { meta } = this._collection; | ||
private async _insertBlockTree( | ||
nodes: DraftBlockTreeNode[], | ||
doc: Doc, | ||
parentId?: string, | ||
startIndex?: number, | ||
counter: number = 0 | ||
) { | ||
for (let index = 0; index < nodes.length; index++) { | ||
const node = nodes[index]; | ||
const { draft } = node; | ||
const { id, flavour } = draft; | ||
const actualIndex = | ||
startIndex !== undefined ? startIndex + index : undefined; | ||
doc.addBlock(flavour as BlockSuite.Flavour, draft, parentId, actualIndex); | ||
const model = doc.getBlockById(id); | ||
if (!model) { | ||
throw new BlockSuiteError( | ||
ErrorCode.TransformerError, | ||
`Block not found by id ${id}` | ||
); | ||
} | ||
this._slots.afterImport.emit({ | ||
type: 'block', | ||
model, | ||
snapshot: node.snapshot, | ||
}); | ||
counter++; | ||
if (counter % BATCH_SIZE === 0) { | ||
await nextTick(); | ||
} | ||
if (node.children.length > 0) { | ||
counter = await this._insertBlockTree( | ||
node.children, | ||
doc, | ||
id, | ||
undefined, | ||
counter | ||
); | ||
} | ||
} | ||
return counter; | ||
} | ||
private _rebuildBlockTree( | ||
draftModels: { | ||
draft: DraftModel; | ||
snapshot: BlockSnapshot; | ||
parentId?: string; | ||
index?: number; | ||
}[] | ||
): DraftBlockTreeNode { | ||
const nodeMap = new Map<string, DraftBlockTreeNode>(); | ||
// First pass: create nodes and add them to the map | ||
draftModels.forEach(({ draft, snapshot }) => { | ||
nodeMap.set(draft.id, { draft, snapshot, children: [] }); | ||
}); | ||
const root = nodeMap.get(draftModels[0].draft.id) as DraftBlockTreeNode; | ||
// Second pass: build the tree structure | ||
draftModels.forEach(({ draft, parentId, index }) => { | ||
const node = nodeMap.get(draft.id); | ||
if (!node) return; | ||
if (parentId) { | ||
const parentNode = nodeMap.get(parentId); | ||
if (parentNode && index !== undefined) { | ||
parentNode.children[index] = node; | ||
} | ||
} | ||
}); | ||
if (!root) { | ||
throw new Error('No root node found in the tree'); | ||
} | ||
return root; | ||
} | ||
private async _snapshotToBlock( | ||
@@ -480,56 +636,38 @@ snapshot: BlockSnapshot, | ||
index?: number | ||
) { | ||
this._slots.beforeImport.emit({ | ||
type: 'block', | ||
snapshot, | ||
parent, | ||
index, | ||
}); | ||
const { children, flavour, props, id } = snapshot; | ||
): Promise<BlockModel | null> { | ||
this._triggerBeforeImportEvent(snapshot, parent, index); | ||
const schema = this._getSchema(flavour); | ||
const snapshotLeaf = { | ||
id, | ||
flavour, | ||
props, | ||
}; | ||
const transformer = this._getTransformer(schema); | ||
const modelData = await transformer.fromSnapshot({ | ||
json: snapshotLeaf, | ||
assets: this._assetsManager, | ||
children, | ||
}); | ||
const flatSnapshots: FlatSnapshot[] = []; | ||
this._flattenSnapshot(snapshot, flatSnapshots, parent, index); | ||
const nextTick = | ||
typeof window !== 'undefined' | ||
? window.requestAnimationFrame | ||
: setImmediate; | ||
await new Promise(resolve => nextTick(() => resolve(undefined))); | ||
doc.addBlock( | ||
modelData.flavour as BlockSuite.Flavour, | ||
{ ...modelData.props, id: modelData.id }, | ||
parent, | ||
index | ||
); | ||
const blockTree = await this._convertFlatSnapshots(flatSnapshots); | ||
for (const [index, child] of children.entries()) { | ||
await this._batchSnapshotToBlock(child, doc, id, index); | ||
} | ||
await this._insertBlockTree([blockTree], doc, parent, index); | ||
const model = doc.getBlockById(id); | ||
if (!model) { | ||
throw new BlockSuiteError( | ||
ErrorCode.TransformerError, | ||
`Block not found by id ${id}` | ||
); | ||
} | ||
this._slots.afterImport.emit({ | ||
type: 'block', | ||
snapshot, | ||
model, | ||
parent, | ||
index, | ||
}); | ||
return doc.getBlockById(snapshot.id) || null; | ||
} | ||
return model; | ||
private _triggerBeforeImportEvent( | ||
snapshot: BlockSnapshot, | ||
parent?: string, | ||
index?: number | ||
) { | ||
const traverseAndTrigger = ( | ||
node: BlockSnapshot, | ||
parent?: string, | ||
index?: number | ||
) => { | ||
this._slots.beforeImport.emit({ | ||
type: 'block', | ||
snapshot: node, | ||
parent: parent, | ||
index: index, | ||
}); | ||
if (node.children) { | ||
node.children.forEach((child, idx) => { | ||
traverseAndTrigger(child, node.id, idx); | ||
}); | ||
} | ||
}; | ||
traverseAndTrigger(snapshot, parent, index); | ||
} | ||
@@ -536,0 +674,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
784920
11612
+ Added@blocksuite/global@0.0.0-canary-20241015001406(transitive)
+ Added@blocksuite/inline@0.0.0-canary-20241015001406(transitive)
+ Added@blocksuite/sync@0.0.0-canary-20241015001406(transitive)
+ Addedyjs@13.6.20(transitive)
- Removed@blocksuite/global@0.0.0-canary-20241014001453(transitive)
- Removed@blocksuite/inline@0.0.0-canary-20241014001453(transitive)
- Removed@blocksuite/sync@0.0.0-canary-20241014001453(transitive)
- Removedyjs@13.6.21(transitive)