Skip to content

Commit

Permalink
feat(sync): initial @blocksuite/sync
Browse files Browse the repository at this point in the history
  • Loading branch information
EYHN committed Feb 6, 2024
1 parent 3b0a5ec commit 608ac18
Show file tree
Hide file tree
Showing 43 changed files with 1,803 additions and 240 deletions.
1 change: 1 addition & 0 deletions .commitlintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"edgeless",
"database",
"store",
"sync",
"std",
"presets",
"playground",
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"packages/framework/inline",
"packages/framework/lit",
"packages/framework/store",
"packages/framework/sync",
"packages/blocks",
"packages/docs",
"packages/playground",
Expand Down
96 changes: 47 additions & 49 deletions packages/blocks/src/_common/configs/quick-action/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,58 +125,56 @@ export const quickActionConfig: QuickActionConfig[] = [

const page = host.page;
const linkedPage = page.workspace.createPage({});
linkedPage
.load(() => {
const pageBlockId = linkedPage.addBlock('affine:page', {
title: new page.Text(''),
linkedPage.load(() => {
const pageBlockId = linkedPage.addBlock('affine:page', {
title: new page.Text(''),
});
linkedPage.addBlock('affine:surface', {}, pageBlockId);
const noteId = linkedPage.addBlock('affine:note', {}, pageBlockId);

const firstBlock = selectedModels[0];
assertExists(firstBlock);

page.addSiblingBlocks(
firstBlock,
[
{
flavour: 'affine:embed-linked-doc',
pageId: linkedPage.id,
},
],
'before'
);

if (
matchFlavours(firstBlock, ['affine:paragraph']) &&
firstBlock.type.match(/^h[1-6]$/)
) {
const title = firstBlock.text.toString();
linkedPage.workspace.setPageMeta(linkedPage.id, {
title,
});
linkedPage.addBlock('affine:surface', {}, pageBlockId);
const noteId = linkedPage.addBlock('affine:note', {}, pageBlockId);

const firstBlock = selectedModels[0];
assertExists(firstBlock);

page.addSiblingBlocks(
firstBlock,
[
{
flavour: 'affine:embed-linked-doc',
pageId: linkedPage.id,
},
],
'before'
);

if (
matchFlavours(firstBlock, ['affine:paragraph']) &&
firstBlock.type.match(/^h[1-6]$/)
) {
const title = firstBlock.text.toString();
linkedPage.workspace.setPageMeta(linkedPage.id, {
title,
});

const pageBlock = linkedPage.getBlockById(pageBlockId);
assertExists(pageBlock);
linkedPage.updateBlock(pageBlock, {
title: new page.Text(title),
});

page.deleteBlock(firstBlock);
selectedModels.shift();
}

selectedModels.forEach(model => {
const keys = model.keys as (keyof typeof model)[];
const values = keys.map(key => model[key]);
const blockProps = Object.fromEntries(
keys.map((key, i) => [key, values[i]])
);
linkedPage.addBlock(model.flavour, blockProps, noteId);
page.deleteBlock(model);
const pageBlock = linkedPage.getBlockById(pageBlockId);
assertExists(pageBlock);
linkedPage.updateBlock(pageBlock, {
title: new page.Text(title),
});
})
.catch(console.error);

page.deleteBlock(firstBlock);
selectedModels.shift();
}

selectedModels.forEach(model => {
const keys = model.keys as (keyof typeof model)[];
const values = keys.map(key => model[key]);
const blockProps = Object.fromEntries(
keys.map((key, i) => [key, values[i]])
);
linkedPage.addBlock(model.flavour, blockProps, noteId);
page.deleteBlock(model);
});
});

const linkedDocService = host.spec.getService(
'affine:embed-linked-doc'
Expand Down
27 changes: 13 additions & 14 deletions packages/blocks/src/_common/utils/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,20 @@ export async function createDefaultPage(
) {
const page = workspace.createPage({ id: options.id });

await page.load(() => {
const title = options.title ?? '';
const pageBlockId = page.addBlock('affine:page', {
title: new page.Text(title),
});
workspace.setPageMeta(page.id, {
title,
});
page.addBlock('affine:surface', {}, pageBlockId);
const noteId = page.addBlock('affine:note', {}, pageBlockId);
page.addBlock('affine:paragraph', {}, noteId);
// To make sure the content of new page would not be clear
// By undo operation for the first time
page.resetHistory();
page.load();
const title = options.title ?? '';
const pageBlockId = page.addBlock('affine:page', {
title: new page.Text(title),
});
workspace.setPageMeta(page.id, {
title,
});
page.addBlock('affine:surface', {}, pageBlockId);
const noteId = page.addBlock('affine:note', {}, pageBlockId);
page.addBlock('affine:paragraph', {}, noteId);
// To make sure the content of new page would not be clear
// By undo operation for the first time
page.resetHistory();

return page;
}
Original file line number Diff line number Diff line change
Expand Up @@ -114,15 +114,8 @@ export class EmbedLinkedDocBlockComponent extends EmbedBlockElement<
if (linkedDoc.loaded) {
onLoad();
} else {
linkedDoc
.load()
.then(() => onLoad())
.catch(e => {
console.error(
`An error occurred while loading page: ${this.model.pageId}`
);
console.error(e);
});
linkedDoc.load();
onLoad();
}
}

Expand Down
1 change: 1 addition & 0 deletions packages/framework/global/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './assert.js';
export * from './disposable.js';
export * from './function.js';
export * from './logger.js';
export * from './slot.js';
export * from './types.js';
28 changes: 28 additions & 0 deletions packages/framework/global/src/utils/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
export interface Logger {
debug: (message: string, ...args: unknown[]) => void;
info: (message: string, ...args: unknown[]) => void;
warn: (message: string, ...args: unknown[]) => void;
error: (message: string, ...args: unknown[]) => void;
}

export class ConsoleLogger implements Logger {
debug(message: string, ...args: unknown[]) {
console.debug(message, ...args);
}
info(message: string, ...args: unknown[]) {
console.info(message, ...args);
}
warn(message: string, ...args: unknown[]) {
console.warn(message, ...args);
}
error(message: string, ...args: unknown[]) {
console.error(message, ...args);
}
}

export class NoopLogger implements Logger {
debug() {}
info() {}
warn() {}
error() {}
}
1 change: 1 addition & 0 deletions packages/framework/store/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"dependencies": {
"@blocksuite/global": "workspace:*",
"@blocksuite/inline": "workspace:*",
"@blocksuite/sync": "workspace:*",
"@types/flexsearch": "^0.7.6",
"flexsearch": "0.7.43",
"idb-keyval": "^6.2.1",
Expand Down
35 changes: 14 additions & 21 deletions packages/framework/store/src/workspace/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -636,18 +636,27 @@ export class Page extends Space<FlatBlockMap> {
});
}

override async load(initFn?: () => Promise<void> | void) {
override load(initFn?: () => void): this {
if (this.ready) {
throw new Error('Cannot load page more than once');
return this;
}

await super.load();
this._trySyncFromExistingDoc();
super.load();

if (initFn) {
await initFn();
initFn();
}

if ((this.workspace.meta.pages?.length ?? 0) <= 1) {
this._handleVersion();
}

this._initYBlocks();

this._yBlocks.forEach((_, id) => {
this._handleYBlockAdd(id);
});

this._ready = true;
this.slots.ready.emit();

Expand Down Expand Up @@ -792,22 +801,6 @@ export class Page extends Space<FlatBlockMap> {
}
}

private _trySyncFromExistingDoc() {
if (this.ready) {
throw new Error('Cannot sync from existing doc more than once');
}

if ((this.workspace.meta.pages?.length ?? 0) <= 1) {
this._handleVersion();
}

this._initYBlocks();

this._yBlocks.forEach((_, id) => {
this._handleYBlockAdd(id);
});
}

/** @deprecated use page.load() instead */
async waitForLoaded() {
await this.load();
Expand Down
14 changes: 1 addition & 13 deletions packages/framework/store/src/workspace/space.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,21 +54,9 @@ export class Space<
return this._ySpaceDoc;
}

async load() {
if (this.loaded) {
return this;
}

const promise = new Promise(resolve => {
this._onLoadSlot.once(() => {
resolve(undefined);
});
});

load() {
this._ySpaceDoc.load();

await promise;

return this;
}

Expand Down
40 changes: 37 additions & 3 deletions packages/framework/store/src/workspace/store.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
import { type Logger, NoopLogger } from '@blocksuite/global/utils';
import {
AwarenessEngine,
type AwarenessProvider,
SyncEngine,
type SyncStorage,
} from '@blocksuite/sync';
import { MemorySyncStorage } from '@blocksuite/sync/impl/memory.js';
import { merge } from 'merge';
import { Awareness } from 'y-protocols/awareness.js';

Expand Down Expand Up @@ -44,10 +52,15 @@ export interface StoreOptions<
Flags extends Record<string, unknown> = BlockSuiteFlags,
> {
id?: string;
awareness?: Awareness<RawAwarenessState<Flags>>;
idGenerator?: Generator | IdGenerator;
defaultFlags?: Partial<Flags>;
blobStorages?: ((id: string) => BlobStorage)[];
logger?: Logger;
sync?: {
primary: SyncStorage;
secondary?: SyncStorage[];
};
awareness?: AwarenessProvider[];
}

const FLAGS_PRESET = {
Expand All @@ -61,21 +74,42 @@ export class Store {
readonly id: string;
readonly doc: BlockSuiteDoc;
readonly spaces = new Map<string, Space>();
readonly awareness: AwarenessEngine;
readonly awarenessStore: AwarenessStore;
readonly sync: SyncEngine;
readonly idGenerator: IdGenerator;

constructor(
{ id, awareness, idGenerator, defaultFlags }: StoreOptions = {
{
id,
idGenerator,
defaultFlags,
awareness: awarenessProviders = [],
sync = {
primary: new MemorySyncStorage(),
},
logger = new NoopLogger(),
}: StoreOptions = {
id: nanoid(),
}
) {
this.id = id || '';
this.doc = new BlockSuiteDoc({ guid: id });
this.awarenessStore = new AwarenessStore(
this,
awareness ?? new Awareness<RawAwarenessState>(this.doc),
new Awareness<RawAwarenessState>(this.doc),
merge(true, FLAGS_PRESET, defaultFlags)
);
this.awareness = new AwarenessEngine(
this.awarenessStore.awareness,
awarenessProviders
);
this.sync = new SyncEngine(
this.doc,
sync.primary,
sync.secondary ?? [],
logger
);

if (typeof idGenerator === 'function') {
this.idGenerator = idGenerator;
Expand Down
Loading

0 comments on commit 608ac18

Please sign in to comment.