New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@blocksuite/store

Package Overview
Dependencies
Maintainers
5
Versions
1311
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@blocksuite/store - npm Package Compare versions

Comparing version 0.5.0-alpha.1 to 0.5.0-alpha.2

src/workspace/database.ts

59

package.json
{
"name": "@blocksuite/store",
"version": "0.5.0-alpha.1",
"version": "0.5.0-alpha.2",
"description": "BlockSuite data store built for general purpose state management.",
"main": "dist/index.js",
"main": "src/index.ts",
"type": "module",
"scripts": {
"serve": "PORT=4444 node node_modules/y-webrtc/bin/server.js",
"build": "tsc",
"test:unit": "vitest --run",
"test:unit:coverage": "vitest run --coverage",
"test:unit:ui": "vitest --ui",
"test:e2e": "playwright test",
"test": "pnpm test:unit && pnpm test:e2e"
},
"keywords": [],

@@ -11,4 +20,4 @@ "author": "toeverything",

"dependencies": {
"@blocksuite/global": "0.5.0-alpha.1",
"@blocksuite/virgo": "0.5.0-alpha.1",
"@blocksuite/global": "workspace:*",
"@blocksuite/virgo": "workspace:*",
"@types/flexsearch": "^0.7.3",

@@ -18,13 +27,13 @@ "buffer": "^6.0.3",

"idb-keyval": "^6.2.0",
"ky": "^0.33.2",
"lib0": "^0.2.63",
"ky": "^0.33.3",
"lib0": "^0.2.68",
"merge": "^2.1.1",
"nanoid": "^4.0.1",
"y-protocols": "^1.0.5",
"y-webrtc": "^10.2.4",
"zod": "^3.20.6"
"y-webrtc": "^10.2.5",
"zod": "^3.21.4"
},
"devDependencies": {
"lit": "^2.6.1",
"yjs": "^13.5.48"
"yjs": "^13.5.50"
},

@@ -35,21 +44,17 @@ "peerDependencies": {

"exports": {
"./src/*": "./dist/*.js",
".": {
"module": "./dist/index.js",
"import": "./dist/index.js"
}
"./src/*": "./src/*.ts",
".": "./src/index.ts"
},
"publishConfig": {
"access": "public"
},
"scripts": {
"serve": "PORT=4444 node node_modules/y-webrtc/bin/server.js",
"build": "tsc",
"test:unit": "vitest --run",
"test:unit:coverage": "vitest run --coverage",
"test:unit:ui": "vitest --ui",
"test:e2e": "playwright test",
"test": "pnpm test:unit && pnpm test:e2e"
},
"types": "dist/index.d.ts"
}
"access": "public",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
"./src/*": "./dist/*.js",
".": {
"module": "./dist/index.js",
"import": "./dist/index.js"
}
}
}
}

@@ -1,2 +0,2 @@

import { expect, Page } from '@playwright/test';
import { expect, type Page } from '@playwright/test';

@@ -3,0 +3,0 @@ import type { TestResult } from './test-utils-dom.js';

/* eslint-disable @typescript-eslint/no-restricted-imports */
// checkout https://vitest.dev/guide/debugging.html for debugging tests
import type { Slot } from '@blocksuite/global/utils';
import { assert, describe, expect, it } from 'vitest';
import { DividerBlockModelSchema } from '../../../blocks/src/divider-block/divider-model.js';
import { FrameBlockModelSchema } from '../../../blocks/src/frame-block/frame-model.js';
import { ListBlockModelSchema } from '../../../blocks/src/list-block/list-model.js';
import { DividerBlockSchema } from '../../../blocks/src/divider-block/divider-model.js';
import { FrameBlockSchema } from '../../../blocks/src/frame-block/frame-model.js';
import { ListBlockSchema } from '../../../blocks/src/list-block/list-model.js';
// Use manual per-module import/export to support vitest environment on Node.js
import { PageBlockModelSchema } from '../../../blocks/src/page-block/page-model.js';
import { ParagraphBlockModelSchema } from '../../../blocks/src/paragraph-block/paragraph-model.js';
import { BaseBlockModel, Generator, Page, Workspace } from '../index.js';
import { PageBlockSchema } from '../../../blocks/src/page-block/page-model.js';
import { ParagraphBlockSchema } from '../../../blocks/src/paragraph-block/paragraph-model.js';
import type { Slot } from '../../../global/src/utils/slot.js';
import type { BaseBlockModel, Page } from '../index.js';
import { Generator, Workspace } from '../index.js';
import type { PageMeta } from '../workspace/index.js';
import { assertExists } from './test-utils-dom.js';

@@ -22,11 +22,14 @@ function createTestOptions() {

// Create BlockSchema manually
export const BlockSchema = [
ParagraphBlockModelSchema,
PageBlockModelSchema,
ListBlockModelSchema,
FrameBlockModelSchema,
DividerBlockModelSchema,
export const BlockSchemas = [
ParagraphBlockSchema,
PageBlockSchema,
ListBlockSchema,
FrameBlockSchema,
DividerBlockSchema,
];
const defaultPageId = 'page0';
const spaceId = `space:${defaultPageId}`;
const spaceMetaId = 'space:meta';
function serialize(page: Page) {

@@ -40,33 +43,16 @@ return page.doc.toJSON();

async function createRoot(page: Page) {
queueMicrotask(() =>
page.addBlockByFlavour('affine:page', {
title: new page.Text(),
})
);
const root = await waitOnce(page.slots.rootAdded);
return root;
function createRoot(page: Page) {
page.addBlock('affine:page');
if (!page.root) throw new Error('root not found');
return page.root;
}
async function createPage(workspace: Workspace, pageId = 'page0') {
queueMicrotask(() => workspace.createPage(pageId));
await waitOnce(workspace.slots.pageAdded);
const page = workspace.getPage(pageId);
assertExists(page);
return page;
}
async function createTestPage() {
function createTestPage(pageId = defaultPageId, parentId?: string) {
const options = createTestOptions();
const workspace = new Workspace(options).register(BlockSchema);
const page = await createPage(workspace);
return page;
const workspace = new Workspace(options).register(BlockSchemas);
return workspace.createPage(pageId, parentId);
}
const defaultPageId = 'page0';
const spaceId = `space:${defaultPageId}`;
const spaceMetaId = 'space:meta';
describe.concurrent('basic', () => {
it('can init workspace', async () => {
describe('basic', () => {
it('can init workspace', () => {
const options = createTestOptions();

@@ -76,3 +62,3 @@ const workspace = new Workspace(options);

const page = await createPage(workspace);
const page = workspace.createPage('page0');
const actual = serialize(page);

@@ -91,2 +77,3 @@ const actualPage = actual[spaceMetaId].pages[0] as PageMeta;

id: 'page0',
subpageIds: [],
title: '',

@@ -102,6 +89,38 @@ },

describe.concurrent('addBlock', () => {
it('can add single model', async () => {
const page = await createTestPage();
page.addBlockByFlavour('affine:page', {
describe('pageMeta', () => {
it('can create subpage', () => {
const options = createTestOptions();
const workspace = new Workspace(options).register(BlockSchemas);
const parentPage = workspace.createPage(defaultPageId);
const subpage = workspace.createPage('subpage0', parentPage.id);
assert.deepEqual(parentPage.meta.subpageIds, [subpage.id]);
});
it('can shift subpage', () => {
const options = createTestOptions();
const workspace = new Workspace(options).register(BlockSchemas);
const page0 = workspace.createPage('page0');
const page1 = workspace.createPage('page1');
const page2 = workspace.createPage('page2');
assert.deepEqual(
workspace.meta.pageMetas.map(m => m.id),
['page0', 'page1', 'page2']
);
workspace.shiftPage(page1.id, 0);
assert.deepEqual(
workspace.meta.pageMetas.map(m => m.id),
['page1', 'page0', 'page2']
);
});
});
describe('addBlock', () => {
it('can add single model', () => {
const page = createTestPage();
page.addBlock('affine:page', {
title: new page.Text(),

@@ -112,4 +131,4 @@ });

'0': {
'meta:tags': {},
'meta:tagSchema': {},
'ext:cells': {},
'ext:columnSchema': {},
'prop:title': '',

@@ -123,10 +142,10 @@ 'sys:children': [],

it('can add model with props', async () => {
const page = await createTestPage();
page.addBlockByFlavour('affine:page', { title: new page.Text('hello') });
it('can add model with props', () => {
const page = createTestPage();
page.addBlock('affine:page', { title: new page.Text('hello') });
assert.deepEqual(serialize(page)[spaceId], {
'0': {
'meta:tags': {},
'meta:tagSchema': {},
'ext:cells': {},
'ext:columnSchema': {},
'sys:children': [],

@@ -140,9 +159,9 @@ 'sys:flavour': 'affine:page',

it('can add multi models', async () => {
const page = await createTestPage();
page.addBlockByFlavour('affine:page', {
it('can add multi models', () => {
const page = createTestPage();
page.addBlock('affine:page', {
title: new page.Text(),
});
page.addBlockByFlavour('affine:paragraph');
page.addBlocksByFlavour([
page.addBlock('affine:paragraph');
page.addBlocks([
{ flavour: 'affine:paragraph', blockProps: { type: 'h1' } },

@@ -154,4 +173,4 @@ { flavour: 'affine:paragraph', blockProps: { type: 'h2' } },

'0': {
'meta:tags': {},
'meta:tagSchema': {},
'ext:cells': {},
'ext:columnSchema': {},
'sys:children': ['1', '2', '3'],

@@ -187,10 +206,10 @@ 'sys:flavour': 'affine:page',

it('can observe slot events', async () => {
const page = await createTestPage();
const page = createTestPage();
queueMicrotask(() =>
page.addBlockByFlavour('affine:page', {
page.addBlock('affine:page', {
title: new page.Text(),
})
);
const block = await waitOnce(page.slots.rootAdded);
const block = (await waitOnce(page.slots.rootAdded)) as BaseBlockModel;
if (Array.isArray(block)) {

@@ -203,17 +222,12 @@ throw new Error('');

it('can add block to root', async () => {
const page = await createTestPage();
const page = createTestPage();
queueMicrotask(() =>
page.addBlockByFlavour('affine:page', {
title: new page.Text(),
})
);
const roots = await waitOnce(page.slots.rootAdded);
const root = Array.isArray(roots) ? roots[0] : roots;
if (Array.isArray(root)) {
throw new Error('');
}
queueMicrotask(() => page.addBlock('affine:page'));
await waitOnce(page.slots.rootAdded);
const { root } = page;
if (!root) throw new Error('root is null');
assert.equal(root.flavour, 'affine:page');
page.addBlockByFlavour('affine:paragraph');
page.addBlock('affine:paragraph');
assert.equal(root.children[0].flavour, 'affine:paragraph');

@@ -227,12 +241,12 @@ assert.equal(root.childMap.get('1'), 0);

it('can add and remove multi pages', async () => {
it('can add and remove multi pages', () => {
const options = createTestOptions();
const workspace = new Workspace(options).register(BlockSchema);
const workspace = new Workspace(options).register(BlockSchemas);
const page0 = await createPage(workspace, 'page0');
const page1 = await createPage(workspace, 'page1');
// @ts-ignore
const page0 = workspace.createPage('page0');
const page1 = workspace.createPage('page1');
// @ts-expect-error
assert.equal(workspace._pages.size, 2);
page0.addBlockByFlavour('affine:page', {
page0.addBlock('affine:page', {
title: new page0.Text(),

@@ -251,5 +265,5 @@ });

it('can set page state', async () => {
it('can set page state', () => {
const options = createTestOptions();
const workspace = new Workspace(options).register(BlockSchema);
const workspace = new Workspace(options).register(BlockSchemas);
workspace.createPage('page0');

@@ -271,3 +285,3 @@

let called = false;
workspace.meta.pagesUpdated.on(() => {
workspace.meta.pageMetasUpdated.on(() => {
called = true;

@@ -308,7 +322,7 @@ });

describe.concurrent('deleteBlock', () => {
it('can delete single model', async () => {
const page = await createTestPage();
describe('deleteBlock', () => {
it('can delete single model', () => {
const page = createTestPage();
page.addBlockByFlavour('affine:page', {
page.addBlock('affine:page', {
title: new page.Text(),

@@ -318,4 +332,4 @@ });

'0': {
'meta:tags': {},
'meta:tagSchema': {},
'ext:cells': {},
'ext:columnSchema': {},
'sys:children': [],

@@ -328,12 +342,11 @@ 'sys:flavour': 'affine:page',

page.deleteBlockById('0');
page.deleteBlock(page.root as BaseBlockModel);
assert.deepEqual(serialize(page)[spaceId], {});
});
it('can delete model with parent', async () => {
const page = await createTestPage();
const roots = await createRoot(page);
const root = Array.isArray(roots) ? roots[0] : roots;
it('can delete model with parent', () => {
const page = createTestPage();
const root = createRoot(page);
page.addBlockByFlavour('affine:paragraph');
page.addBlock('affine:paragraph');

@@ -343,4 +356,4 @@ // before delete

'0': {
'meta:tags': {},
'meta:tagSchema': {},
'ext:cells': {},
'ext:columnSchema': {},
'prop:title': '',

@@ -365,4 +378,4 @@ 'sys:children': ['1'],

'0': {
'meta:tags': {},
'meta:tagSchema': {},
'ext:cells': {},
'ext:columnSchema': {},
'prop:title': '',

@@ -378,10 +391,9 @@ 'sys:children': [],

describe.concurrent('getBlock', () => {
it('can get block by id', async () => {
const page = await createTestPage();
const roots = await createRoot(page);
const root = Array.isArray(roots) ? roots[0] : roots;
describe('getBlock', () => {
it('can get block by id', () => {
const page = createTestPage();
const root = createRoot(page);
page.addBlockByFlavour('affine:paragraph');
page.addBlockByFlavour('affine:paragraph');
page.addBlock('affine:paragraph');
page.addBlock('affine:paragraph');

@@ -396,9 +408,8 @@ const text = page.getBlockById('2') as BaseBlockModel;

it('can get parent', async () => {
const page = await createTestPage();
const roots = await createRoot(page);
const root = Array.isArray(roots) ? roots[0] : roots;
it('can get parent', () => {
const page = createTestPage();
const root = createRoot(page);
page.addBlockByFlavour('affine:paragraph');
page.addBlockByFlavour('affine:paragraph');
page.addBlock('affine:paragraph');
page.addBlock('affine:paragraph');

@@ -412,9 +423,8 @@ const result = page.getParent(root.children[1]) as BaseBlockModel;

it('can get previous sibling', async () => {
const page = await createTestPage();
const roots = await createRoot(page);
const root = Array.isArray(roots) ? roots[0] : roots;
it('can get previous sibling', () => {
const page = createTestPage();
const root = createRoot(page);
page.addBlockByFlavour('affine:paragraph');
page.addBlockByFlavour('affine:paragraph');
page.addBlock('affine:paragraph');
page.addBlock('affine:paragraph');

@@ -430,9 +440,9 @@ const result = page.getPreviousSibling(root.children[1]) as BaseBlockModel;

// Inline snapshot is not supported under describe.parallel config
describe('workspace.exportJSX works', async () => {
it('workspace matches snapshot', async () => {
describe('workspace.exportJSX works', () => {
it('workspace matches snapshot', () => {
const options = createTestOptions();
const workspace = new Workspace(options).register(BlockSchema);
const page = await createPage(workspace);
const workspace = new Workspace(options).register(BlockSchemas);
const page = workspace.createPage('page0');
page.addBlockByFlavour('affine:page', { title: new page.Text('hello') });
page.addBlock('affine:page', { title: new page.Text('hello') });

@@ -446,6 +456,6 @@ expect(workspace.exportJSX()).toMatchInlineSnapshot(`

it('empty workspace matches snapshot', async () => {
it('empty workspace matches snapshot', () => {
const options = createTestOptions();
const workspace = new Workspace(options).register(BlockSchema);
await createPage(workspace);
const workspace = new Workspace(options).register(BlockSchemas);
workspace.createPage('page0');

@@ -455,12 +465,12 @@ expect(workspace.exportJSX()).toMatchInlineSnapshot('null');

it('workspace with multiple blocks children matches snapshot', async () => {
it('workspace with multiple blocks children matches snapshot', () => {
const options = createTestOptions();
const workspace = new Workspace(options).register(BlockSchema);
const page = await createPage(workspace);
const workspace = new Workspace(options).register(BlockSchemas);
const page = workspace.createPage('page0');
page.addBlockByFlavour('affine:page', {
page.addBlock('affine:page', {
title: new page.Text(),
});
page.addBlockByFlavour('affine:paragraph');
page.addBlockByFlavour('affine:paragraph');
page.addBlock('affine:paragraph');
page.addBlock('affine:paragraph');

@@ -480,11 +490,11 @@ expect(workspace.exportJSX()).toMatchInlineSnapshot(/* xml */ `

describe.concurrent('workspace.search works', async () => {
it('workspace search matching', async () => {
describe('workspace.search works', () => {
it('workspace search matching', () => {
const options = createTestOptions();
const workspace = new Workspace(options).register(BlockSchema);
const page = await createPage(workspace);
const workspace = new Workspace(options).register(BlockSchemas);
const page = workspace.createPage('page0');
page.addBlockByFlavour('affine:page', { title: new page.Text('hello') });
page.addBlock('affine:page', { title: new page.Text('hello') });
page.addBlockByFlavour('affine:paragraph', {
page.addBlock('affine:paragraph', {
text: new page.Text(

@@ -495,3 +505,3 @@ '英特尔第13代酷睿i7-1370P移动处理器现身Geekbench,14核心和5GHz'

page.addBlockByFlavour('affine:paragraph', {
page.addBlock('affine:paragraph', {
text: new page.Text(

@@ -504,6 +514,7 @@ '索尼考虑移植《GT赛车7》,又一PlayStation独占IP登陆PC平台'

expect(workspace.search('处理器')).toStrictEqual(new Map([['1', id]]));
expect(workspace.search('索尼')).toStrictEqual(new Map([['2', id]]));
queueMicrotask(() => {
expect(workspace.search('处理器')).toStrictEqual(new Map([['1', id]]));
expect(workspace.search('索尼')).toStrictEqual(new Map([['2', id]]));
});
});
});

@@ -10,3 +10,3 @@ import type { BlockModels } from '@blocksuite/global/types';

const FlavourSchema = z.string();
const TagSchema = z.object({
const ElementTagSchema = z.object({
_$litStatic$: z.string(),

@@ -28,3 +28,3 @@ r: z.symbol(),

flavour: FlavourSchema,
tag: TagSchema,
tag: ElementTagSchema,
props: z

@@ -116,5 +116,4 @@ .function()

children: BaseBlockModel[];
// TODO use schema
tags?: Y.Map<Y.Map<unknown>>;
tagSchema?: Y.Map<unknown>;
cells?: Y.Map<Y.Map<unknown>>;
columnSchema?: Y.Map<unknown>;
text?: Text;

@@ -132,10 +131,10 @@ sourceId?: string;

firstChild() {
const children = this.children;
if (!children?.length) {
return null;
}
return children[0];
isEmpty() {
return this.children.length === 0;
}
firstChild(): BaseBlockModel | null {
return this.children[0] || null;
}
lastChild(): BaseBlockModel | null {

@@ -142,0 +141,0 @@ if (!this.children.length) {

@@ -10,2 +10,4 @@ /// <reference types="@blocksuite/global" />

export type { IdGenerator } from './utils/id-generator.js';
import type * as Y from 'yjs';
export type { Y };
export {

@@ -12,0 +14,0 @@ createAutoIncrementIdGenerator,

@@ -1,4 +0,4 @@

import { getDefaultPlaygroundURL } from '@blocksuite/global/utils';
import { test } from '@playwright/test';
import { defaultPlaygroundURL } from '../../../../../../tests/utils/actions';
import { collectTestResult } from '../../../__tests__/test-utils-node.js';

@@ -9,3 +9,3 @@

'/examples/blob',
getDefaultPlaygroundURL(!!process.env.CI)
defaultPlaygroundURL
).toString();

@@ -12,0 +12,0 @@

@@ -13,4 +13,3 @@ import type * as Y from 'yjs';

// eslint-disable-next-line @typescript-eslint/no-explicit-any
Data extends Record<string, unknown> = Record<string, any>,
Flags extends Record<string, unknown> = BlockSuiteFlags
State extends Record<string, unknown> = Record<string, any>
> {

@@ -21,8 +20,12 @@ /** unprefixed id */

readonly awarenessStore: AwarenessStore;
/**
* @internal
* @protected
* @internal Used for convenient access to the underlying Yjs map,
* can be used interchangeably with ySpace
*/
protected readonly proxy: Data;
protected readonly origin: Y.Map<Data[keyof Data]>;
protected readonly _proxy: State;
/**
* @internal The actual underlying Yjs map
*/
protected readonly _ySpace: Y.Map<State[keyof State]>;

@@ -33,5 +36,5 @@ constructor(id: string, doc: BlockSuiteDoc, awarenessStore: AwarenessStore) {

this.awarenessStore = awarenessStore;
const targetId = this.id.startsWith('space:') ? this.id : this.prefixedId;
this.origin = this.doc.getMap(targetId);
this.proxy = this.doc.getMapProxy<string, Data>(targetId);
const prefixedId = this.id.startsWith('space:') ? this.id : this.prefixedId;
this._ySpace = this.doc.getMap(prefixedId);
this._proxy = this.doc.getMapProxy<string, State>(prefixedId);
}

@@ -38,0 +41,0 @@

import { merge } from 'merge';
import { Awareness } from 'y-protocols/awareness.js';
import { AwarenessStore, RawAwarenessState } from './awareness.js';
import { AwarenessStore, type RawAwarenessState } from './awareness.js';
import type { BlobOptionsGetter } from './persistence/blob/index.js';

@@ -58,2 +58,9 @@ import type {

// TODO Support ReadableStream
export type InlineSuggestionProvider = (context: {
title: string;
text: string;
abortSignal: AbortSignal;
}) => string | Promise<string>; // | Promise<ReadableStream<string>>;
export interface StoreOptions<

@@ -81,2 +88,3 @@ Flags extends Record<string, unknown> = BlockSuiteFlags

enable_block_selection_format_bar: true,
enable_linked_page: false,

@@ -164,12 +172,23 @@ readonly: {},

*/
exportJSX(id = '0') {
exportJSX(pageId: string, blockId?: string) {
const json = serializeYDoc(this.doc) as unknown as SerializedStore;
if (!('space:page0' in json)) {
throw new Error("Failed to convert to JSX: 'space:page0' not found");
const prefixedPageId = pageId.startsWith('space:')
? pageId
: `space:${pageId}`;
const pageJson = json[prefixedPageId];
if (!pageJson) {
throw new Error(`Page ${pageId} doesn't exist`);
}
if (!json['space:page0'][id]) {
if (!blockId) {
const pageBlockId = Object.keys(pageJson).at(0);
if (!pageBlockId) {
return null;
}
blockId = pageBlockId;
}
if (!pageJson[blockId]) {
return null;
}
return yDocToJSXNode(json['space:page0'], id);
return yDocToJSXNode(pageJson, blockId);
}
}

@@ -16,22 +16,2 @@ /* eslint-disable @typescript-eslint/no-explicit-any */

declare module 'yjs' {
interface Text {
/**
* Specific addition used by @blocksuite/store
* When set, we know it hasn't been applied to virgo.
* When specified, we call this a "controlled operation".
*
* Consider renaming this to closer indicate this is simply a "controlled operation",
* since we may not actually use this information.
*/
meta?:
| { split: true }
| { join: true }
| { format: true }
| { delete: true }
| { clear: true }
| { replace: true };
}
}
export class Text {

@@ -165,3 +145,2 @@ private _yText: Y.Text;

this._yText.insert(index, content, attributes);
this._yText.meta = { split: true };
});

@@ -186,3 +165,2 @@ }

}
this._yText.meta = { split: true };
});

@@ -200,3 +178,2 @@ }

this._yText.applyDelta(delta);
this._yText.meta = { join: true };
});

@@ -221,3 +198,2 @@ }

this._yText.format(index, length, format);
this._yText.meta = { format: true };
});

@@ -242,3 +218,2 @@ }

this._yText.delete(index, length);
this._yText.meta = { delete: true };
});

@@ -267,3 +242,2 @@ }

this._yText.insert(index, content, attributes);
this._yText.meta = { replace: true };
});

@@ -278,3 +252,2 @@ }

this._yText.delete(0, this._yText.length);
this._yText.meta = { clear: true };
});

@@ -281,0 +254,0 @@ }

@@ -33,3 +33,3 @@ import * as Y from 'yjs';

const IGNORE_PROPS = [
const IGNORED_PROPS = [
'sys:id',

@@ -39,4 +39,4 @@ 'sys:flavour',

'prop:xywh',
'meta:tags',
'meta:tagSchema',
'ext:cells',
'ext:columnSchema',
];

@@ -62,3 +62,3 @@

const props = Object.fromEntries(
Object.entries(node).filter(([key]) => !IGNORE_PROPS.includes(key))
Object.entries(node).filter(([key]) => !IGNORED_PROPS.includes(key))
);

@@ -65,0 +65,0 @@

@@ -7,3 +7,4 @@ import type { BlockModels } from '@blocksuite/global/types';

import { BaseBlockModel, BlockSchema, internalPrimitives } from '../base.js';
import type { BaseBlockModel, BlockSchema } from '../base.js';
import { internalPrimitives } from '../base.js';
import { Text } from '../text-adapter.js';

@@ -36,4 +37,4 @@ import type { Workspace } from '../workspace/index.js';

if (props.flavour === 'affine:page') {
yBlock.set('meta:tags', new Y.Map());
yBlock.set('meta:tagSchema', new Y.Map());
yBlock.set('ext:cells', new Y.Map());
yBlock.set('ext:columnSchema', new Y.Map());
}

@@ -40,0 +41,0 @@

@@ -1,2 +0,2 @@

import { html, LitElement, PropertyValues } from 'lit';
import { html, LitElement, type PropertyValues } from 'lit';
import { customElement, property, query } from 'lit/decorators.js';

@@ -3,0 +3,0 @@

export { Page } from './page.js';
export type { PageMeta } from './workspace.js';
export type { PageMeta, WorkspaceOptions } from './workspace.js';
export { Workspace } from './workspace.js';

@@ -1,2 +0,1 @@

import type { BlockTag, TagSchema } from '@blocksuite/global/database';
import { debug } from '@blocksuite/global/debug';

@@ -10,3 +9,3 @@ import type { BlockModelProps } from '@blocksuite/global/types';

import { BaseBlockModel, internalPrimitives } from '../base.js';
import { Space, StackItem } from '../space.js';
import { Space, type StackItem } from '../space.js';
import { Text } from '../text-adapter.js';

@@ -21,2 +20,3 @@ import type { IdGenerator } from '../utils/id-generator.js';

import type { BlockSuiteDoc } from '../yjs/index.js';
import { DatabaseManager } from './database.js';
import { tryMigrate } from './migrations.js';

@@ -48,7 +48,7 @@ import type { PageMeta, Workspace } from './workspace.js';

export type PageData = {
type FlatBlockMap = {
[key: string]: YBlock;
};
export class Page extends Space<PageData> {
export class Page extends Space<FlatBlockMap> {
private _workspace: Workspace;

@@ -77,3 +77,9 @@ private _idGenerator: IdGenerator;

}>(),
subpageUpdated: new Slot<{
type: 'add' | 'delete';
id: string;
subpageIds: string[];
}>(),
};
readonly db: DatabaseManager;

@@ -90,2 +96,3 @@ constructor(

this._idGenerator = idGenerator;
this.db = new DatabaseManager(this);
}

@@ -109,14 +116,2 @@

get tags() {
assertExists(this.root);
assertExists(this.root.flavour === 'affine:page');
return this.root.tags as Y.Map<Y.Map<unknown>>;
}
get tagSchema() {
assertExists(this.root);
assertExists(this.root.flavour === 'affine:page');
return this.root.tagSchema as Y.Map<unknown>;
}
get blobs() {

@@ -128,3 +123,3 @@ return this.workspace.blobs;

private get _yBlocks(): YBlocks {
return this.origin;
return this._ySpace;
}

@@ -144,3 +139,3 @@

/** @internal used for getting surface block elements for phasor */
/** @internal Used for getting surface elements for phasor. */
get ySurfaceContainer() {

@@ -183,3 +178,3 @@ assertExists(this.surface);

undo = () => {
undo() {
if (this.readonly) {

@@ -190,5 +185,5 @@ console.error('cannot modify data in readonly mode');

this._history.undo();
};
}
redo = () => {
redo() {
if (this.readonly) {

@@ -199,58 +194,17 @@ console.error('cannot modify data in readonly mode');

this._history.redo();
};
}
/** Capture current operations to undo stack synchronously. */
captureSync = () => {
captureSync() {
this._history.stopCapturing();
};
}
resetHistory = () => {
resetHistory() {
this._history.clear();
};
updateBlockTag<Tag extends BlockTag>(id: BaseBlockModel['id'], tag: Tag) {
const already = this.tags.has(id);
let tags: Y.Map<unknown>;
if (!already) {
tags = new Y.Map();
} else {
tags = this.tags.get(id) as Y.Map<unknown>;
}
this.transact(() => {
if (!already) {
this.tags.set(id, tags);
}
// Related issue: https://github.com/yjs/yjs/issues/255
const tagMap = new Y.Map();
tagMap.set('schemaId', tag.schemaId);
tagMap.set('value', tag.value);
tags.set(tag.schemaId, tagMap);
});
}
getBlockTagByTagSchema(
model: BaseBlockModel,
schema: TagSchema
): BlockTag | null {
const tags = this.tags.get(model.id);
const tagMap = (tags?.get(schema.id) as Y.Map<unknown>) ?? null;
if (!tagMap) {
return null;
}
return {
schemaId: tagMap.get('schemaId') as string,
value: tagMap.get('value') as unknown,
};
createId() {
return this._idGenerator();
}
getTagSchema(id: TagSchema['id']): TagSchema | null {
return (this.tagSchema.get(id) ?? null) as TagSchema | null;
}
setTagSchema(schema: Omit<TagSchema, 'id'>): string {
const id = this._idGenerator();
this.transact(() => this.tagSchema.set(id, { ...schema, id }));
return id;
}
getBlockById(id: string) {

@@ -356,3 +310,3 @@ return this._blockMap.get(id) ?? null;

@debug('CRUD')
public addBlocksByFlavour<
addBlocks<
// eslint-disable-next-line @typescript-eslint/no-explicit-any

@@ -374,3 +328,3 @@ ALLProps extends Record<string, any> = BlockModelProps,

blocks.forEach(block => {
const id = this.addBlockByFlavour<ALLProps, Flavour>(
const id = this.addBlock<ALLProps, Flavour>(
block.flavour,

@@ -389,3 +343,3 @@ block.blockProps ?? {},

@debug('CRUD')
public addBlockByFlavour<
addBlock<
// eslint-disable-next-line @typescript-eslint/no-explicit-any

@@ -440,6 +394,9 @@ ALLProps extends Record<string, any> = BlockModelProps,

if (typeof parent === 'string') {
parent = this._blockMap.get(parent);
parent = this._blockMap.get(parent) ?? null;
}
const parentId = parent === null ? null : parent?.id ?? this.root?.id;
let parentId = null;
if (parent !== null) {
parentId = parent?.id ?? this.root?.id;
}

@@ -452,2 +409,8 @@ if (parentId) {

}
if (flavour === 'affine:page') {
this.workspace.setPageMeta(this.id, {
title: blockProps.title?.toString(),
});
}
});

@@ -463,16 +426,8 @@

updateBlockById(id: string, props: Partial<BlockProps>) {
if (this.readonly) {
console.error('cannot modify data in readonly mode');
return;
}
const model = this._blockMap.get(id) as BaseBlockModel;
this.updateBlock(model, props);
}
@debug('CRUD')
moveBlocks(
blocks: BaseBlockModel[],
targetModel: BaseBlockModel,
top = true
newParent: BaseBlockModel,
newSibling: BaseBlockModel | null = null,
insertBeforeSibling = true
) {

@@ -485,27 +440,28 @@ if (this.readonly) {

const firstBlock = blocks[0];
const currentParentModel = this.getParent(firstBlock);
const currentParent = this.getParent(firstBlock);
// the blocks must have the same parent (siblings)
if (blocks.some(block => this.getParent(block) !== currentParentModel)) {
if (blocks.some(block => this.getParent(block) !== currentParent)) {
console.error('the blocks must have the same parent');
}
const nextParentModel = this.getParent(targetModel);
if (currentParentModel === null || nextParentModel === null) {
throw new Error('cannot find parent model');
if (currentParent === null || newParent === null) {
throw new Error("Can't find parent model");
}
this.transact(() => {
const yParentA = this._yBlocks.get(currentParentModel.id) as YBlock;
const yParentA = this._yBlocks.get(currentParent.id) as YBlock;
const yChildrenA = yParentA.get('sys:children') as Y.Array<string>;
const idx = yChildrenA.toArray().findIndex(id => id === firstBlock.id);
yChildrenA.delete(idx, blocks.length);
const yParentB = this._yBlocks.get(nextParentModel.id) as YBlock;
const yParentB = this._yBlocks.get(newParent.id) as YBlock;
const yChildrenB = yParentB.get('sys:children') as Y.Array<string>;
const nextIdx = yChildrenB
.toArray()
.findIndex(id => id === targetModel.id);
let nextIdx = 0;
if (newSibling) {
nextIdx = yChildrenB.toArray().findIndex(id => id === newSibling.id);
}
const ids = blocks.map(block => block.id);
if (top) {
if (insertBeforeSibling) {
yChildrenB.insert(nextIdx, ids);

@@ -516,4 +472,4 @@ } else {

});
currentParentModel.propsUpdated.emit();
nextParentModel.propsUpdated.emit();
currentParent.propsUpdated.emit();
newParent.propsUpdated.emit();
}

@@ -551,2 +507,4 @@

model.propsUpdated.emit();
this.slots.blockUpdated.emit({

@@ -583,13 +541,7 @@ type: 'update',

});
const ids = this.addBlocksByFlavour(blocks, parent.id, insertIndex);
return ids;
return this.addBlocks(blocks, parent.id, insertIndex);
} else {
assertExists(props[0].flavour);
const { flavour, ...blockProps } = props[0];
const id = this.addBlockByFlavour(
flavour,
blockProps,
parent.id,
insertIndex
);
const id = this.addBlock(flavour, blockProps, parent.id, insertIndex);
return [id];

@@ -599,11 +551,2 @@ }

deleteBlockById(id: string) {
if (this.readonly) {
console.error('cannot modify data in readonly mode');
return;
}
const model = this._blockMap.get(id) as BaseBlockModel;
this.deleteBlock(model);
}
@debug('CRUD')

@@ -630,6 +573,8 @@ deleteBlock(

} else if (options.bringChildrenTo instanceof BaseBlockModel) {
options.bringChildrenTo.children.unshift(...model.children);
options.bringChildrenTo.children.push(...model.children);
}
this._blockMap.delete(model.id);
model.propsUpdated.emit();
this.transact(() => {

@@ -650,3 +595,3 @@ this._yBlocks.delete(model.id);

} else if (options.bringChildrenTo instanceof BaseBlockModel) {
this.updateBlockById(options.bringChildrenTo.id, {
this.updateBlock(options.bringChildrenTo, {
children: options.bringChildrenTo.children,

@@ -664,3 +609,3 @@ });

syncFromExistingDoc() {
trySyncFromExistingDoc() {
if (this._synced) {

@@ -670,5 +615,7 @@ throw new Error('Cannot sync from existing doc more than once');

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

@@ -803,4 +750,4 @@

if (matchFlavours(model, ['affine:page'] as const)) {
model.tags = yBlock.get('meta:tags') as Y.Map<Y.Map<unknown>>;
model.tagSchema = yBlock.get('meta:tagSchema') as Y.Map<unknown>;
model.cells = yBlock.get('ext:cells') as Y.Map<Y.Map<unknown>>;
model.columnSchema = yBlock.get('ext:columnSchema') as Y.Map<unknown>;

@@ -815,3 +762,3 @@ const titleText = yBlock.get('prop:title') as Y.Text;

(model as any).columns = (
yBlock.get('prop:columns') as Y.Array<unknown>
yBlock.get('prop:columns') as Y.Array<string>
).toArray();

@@ -834,4 +781,5 @@ }

const child = this._blockMap.get(id) as BaseBlockModel;
model.children[index as number] = child;
model.children[index as number] = this._blockMap.get(
id
) as BaseBlockModel;
}

@@ -844,2 +792,3 @@ });

this.slots.rootAdded.emit(model);
this.workspace.slots.pageAdded.emit(this.id);
} else if (isSurface) {

@@ -969,5 +918,16 @@ this._root = [this.root as BaseBlockModel, model];

}
} else if (
event.path.includes('ext:columnSchema') ||
event.path.includes('ext:cells')
) {
const blocks = this.getBlockByFlavour('affine:database');
blocks.forEach(block => {
// todo: refactor here
// force update all blocks that used tagSchema, which is not efficient
// but it's ok for now
block.propsUpdated.emit();
});
}
} else {
if (event.path.includes('meta:tags')) {
if (event.path.includes('ext:cells')) {
// todo: refactor here

@@ -974,0 +934,0 @@ const blockId = event.path[2] as string;

import type { DocumentSearchOptions } from 'flexsearch';
import FlexSearch from 'flexsearch';
import { Doc, Map as YMap, Text as YText } from 'yjs';
import type { Doc, Map as YMap } from 'yjs';
import { Text as YText } from 'yjs';

@@ -83,3 +84,3 @@ import type { YBlock } from './page.js';

onCreatePage(pageId: string) {
onPageCreated(pageId: string) {
this._handlePageIndexing(pageId, this._getPage(pageId));

@@ -86,0 +87,0 @@ }

@@ -5,7 +5,8 @@ import { assertExists, Slot } from '@blocksuite/global/utils';

import { AwarenessStore, BlobUploadState } from '../awareness.js';
import type { AwarenessStore } from '../awareness.js';
import { BlobUploadState } from '../awareness.js';
import { BlockSchema, internalPrimitives } from '../base.js';
import type { BlobStorage } from '../persistence/blob/index.js';
import {
BlobOptionsGetter,
BlobStorage,
type BlobOptionsGetter,
BlobSyncState,

@@ -15,7 +16,15 @@ getBlobStorage,

import { Space } from '../space.js';
import { Store, StoreOptions } from '../store.js';
import {
type InlineSuggestionProvider,
Store,
type StoreOptions,
} from '../store.js';
import type { BlockSuiteDoc } from '../yjs/index.js';
import { Page } from './page.js';
import { Indexer, QueryContent } from './search.js';
import { Indexer, type QueryContent } from './search.js';
export type WorkspaceOptions = {
experimentalInlineSuggestionProvider?: InlineSuggestionProvider;
} & StoreOptions;
export interface PageMeta {

@@ -25,7 +34,8 @@ id: string;

createDate: number;
subpageIds: string[];
[key: string]: string | number | boolean;
[key: string]: string | number | boolean | undefined | (string | number)[];
}
type WorkspaceMetaFields = {
type WorkspaceMetaState = {
pages?: Y.Array<unknown>;

@@ -37,9 +47,7 @@ versions?: Y.Map<unknown>;

class WorkspaceMeta<
Flags extends Record<string, unknown> = BlockSuiteFlags
> extends Space<WorkspaceMetaFields, Flags> {
class WorkspaceMeta extends Space<WorkspaceMetaState> {
private _prevPages = new Set<string>();
pageAdded = new Slot<string>();
pageRemoved = new Slot<string>();
pagesUpdated = new Slot();
pageMetaAdded = new Slot<string>();
pageMetaRemoved = new Slot<string>();
pageMetasUpdated = new Slot();
commonFieldsUpdated = new Slot();

@@ -49,15 +57,15 @@

super(id, doc, awarenessStore);
this.origin.observeDeep(this._handleEvents);
this._ySpace.observeDeep(this._handleWorkspaceMetaEvents);
}
get pages() {
return this.proxy.pages;
return this._proxy.pages;
}
get name() {
return this.proxy.name;
return this._proxy.name;
}
get avatar() {
return this.proxy.avatar;
return this._proxy.avatar;
}

@@ -67,3 +75,3 @@

this.doc.transact(() => {
this.proxy.name = name;
this._proxy.name = name;
});

@@ -74,3 +82,3 @@ }

this.doc.transact(() => {
this.proxy.avatar = avatar;
this._proxy.avatar = avatar;
});

@@ -80,3 +88,3 @@ }

get pageMetas() {
return this.proxy.pages?.toJSON() ?? ([] as PageMeta[]);
return (this._proxy.pages?.toJSON() as PageMeta[]) ?? ([] as PageMeta[]);
}

@@ -89,8 +97,5 @@

addPageMeta(page: PageMeta, index?: number) {
const yPage = new Y.Map();
this.doc.transact(() => {
const pages: Y.Array<unknown> = this.pages ?? new Y.Array();
Object.entries(page).forEach(([key, value]) => {
yPage.set(key, value);
});
const yPage = this._transformObjectToYMap(page);
if (index === undefined) {

@@ -102,3 +107,3 @@ pages.push([yPage]);

if (!this.pages) {
this.origin.set('pages', pages);
this._ySpace.set('pages', pages);
}

@@ -114,3 +119,3 @@ });

if (!this.pages) {
this.origin.set('pages', new Y.Array());
this._ySpace.set('pages', new Y.Array());
}

@@ -127,12 +132,18 @@ if (index === -1) return;

removePage(id: string) {
// you cannot delete a page if there's no page
assertExists(this.pages);
const pages = this.pages.toJSON() as PageMeta[];
const index = pages.findIndex((page: PageMeta) => id === page.id);
/** Adjust the index of a page inside the pageMetss list */
shiftPageMeta(pageId: string, newIndex: number) {
const pageMetas = (this.pages ?? new Y.Array()).toJSON() as PageMeta[];
const index = pageMetas.findIndex((page: PageMeta) => pageId === page.id);
if (index === -1) return;
const yPage = this._transformObjectToYMap(pageMetas[index]);
this.doc.transact(() => {
assertExists(this.pages);
if (index !== -1) {
this.pages.delete(index, 1);
this.pages.delete(index, 1);
if (newIndex > this.pages.length) {
this.pages.push([yPage]);
} else {
this.pages.insert(newIndex, [yPage]);
}

@@ -142,2 +153,16 @@ });

removePageMeta(id: string) {
// you cannot delete a page if there's no page
assertExists(this.pages);
const pageMetas = this.pages.toJSON() as PageMeta[];
const index = pageMetas.findIndex((page: PageMeta) => id === page.id);
if (index === -1) {
return;
}
this.doc.transact(() => {
assertExists(this.pages);
this.pages.delete(index, 1);
});
}
/**

@@ -147,3 +172,3 @@ * @internal Only for page initialization

writeVersion(workspace: Workspace) {
let versions = this.proxy.versions;
let versions = this._proxy.versions;
if (!versions) {

@@ -154,3 +179,3 @@ versions = new Y.Map<unknown>();

});
this.origin.set('versions', versions);
this._ySpace.set('versions', versions);
return;

@@ -166,3 +191,3 @@ } else {

validateVersion(workspace: Workspace) {
const versions = this.proxy.versions?.toJSON();
const versions = this._proxy.versions?.toJSON();
if (!versions) {

@@ -201,3 +226,3 @@ throw new Error(

private _handlePageEvent() {
private _handlePageMetaEvent() {
const { pageMetas, _prevPages } = this;

@@ -211,4 +236,3 @@

if (!_prevPages.has(pageMeta.id)) {
// Ensure following YEvent handler could be triggered in correct order.
queueMicrotask(() => this.pageAdded.emit(pageMeta.id));
this.pageMetaAdded.emit(pageMeta.id);
}

@@ -220,3 +244,3 @@ });

if (isRemoved) {
this.pageRemoved.emit(prevPageId);
this.pageMetaRemoved.emit(prevPageId);
}

@@ -228,3 +252,3 @@ });

this.pagesUpdated.emit();
this.pageMetasUpdated.emit();
}

@@ -236,3 +260,3 @@

private _handleEvents = (
private _handleWorkspaceMetaEvents = (
events: Y.YEvent<Y.Array<unknown> | Y.Text | Y.Map<unknown>>[]

@@ -242,3 +266,3 @@ ) => {

const hasKey = (k: string) =>
e.target === this.origin && e.changes.keys.has(k);
e.target === this._ySpace && e.changes.keys.has(k);

@@ -250,3 +274,3 @@ if (

) {
this._handlePageEvent();
this._handlePageMetaEvent();
}

@@ -259,2 +283,10 @@

};
private _transformObjectToYMap(obj: Record<string, unknown>) {
const yMap = new Y.Map();
Object.entries(obj).forEach(([key, value]) => {
yMap.set(key, value);
});
return yMap;
}
}

@@ -273,6 +305,6 @@

slots: {
pagesUpdated: Slot;
pageAdded: Slot<string>;
pageRemoved: Slot<string>;
slots = {
pagesUpdated: new Slot(),
pageAdded: new Slot<string>(),
pageRemoved: new Slot<string>(),
};

@@ -283,34 +315,20 @@

constructor(options: StoreOptions) {
this._store = new Store(options);
readonly inlineSuggestionProvider?: InlineSuggestionProvider;
constructor({
experimentalInlineSuggestionProvider,
...storeOptions
}: WorkspaceOptions) {
this.inlineSuggestionProvider = experimentalInlineSuggestionProvider;
this._store = new Store(storeOptions);
this._indexer = new Indexer(this.doc);
if (options.blobOptionsGetter) {
this._blobOptionsGetter = options.blobOptionsGetter;
if (storeOptions.blobOptionsGetter) {
this._blobOptionsGetter = storeOptions.blobOptionsGetter;
}
if (!options.isSSR) {
this._blobStorage = getBlobStorage(options.id, k => {
if (!storeOptions.isSSR) {
this._blobStorage = getBlobStorage(storeOptions.id, k => {
return this._blobOptionsGetter ? this._blobOptionsGetter(k) : '';
});
this._blobStorage.then(blobStorage => {
blobStorage?.slots.onBlobSyncStateChange.on(state => {
const blobId = state.id;
const syncState = state.state;
if (
syncState === BlobSyncState.Waiting ||
syncState === BlobSyncState.Syncing
) {
this.awarenessStore.setBlobState(blobId, BlobUploadState.Uploading);
return;
}
if (
syncState === BlobSyncState.Success ||
syncState === BlobSyncState.Failed
) {
this.awarenessStore.setBlobState(blobId, BlobUploadState.Uploaded);
return;
}
});
});
this._initBlobStorage();
} else {

@@ -322,10 +340,8 @@ // blob storage is not reachable in server side

this.meta = new WorkspaceMeta('space:meta', this.doc, this.awarenessStore);
this._bindPageMetaEvents();
this.slots = {
pagesUpdated: this.meta.pagesUpdated,
pageAdded: this.meta.pageAdded,
pageRemoved: this.meta.pageRemoved,
};
this._handlePageEvent();
this.slots.pageAdded.on(id => {
// For potentially batch-added blocks, it's best to build index asynchronously
queueMicrotask(() => this._indexer.onPageCreated(id));
});
}

@@ -376,3 +392,3 @@

// the meta space is not included
return this._store.spaces as Map<string, Page>;
return this._store.spaces as Map<`space:${string}`, Page>;
}

@@ -384,2 +400,6 @@

get idGenerator() {
return this._store.idGenerator;
}
register(blockSchema: z.infer<typeof BlockSchema>[]) {

@@ -398,16 +418,39 @@ blockSchema.forEach(schema => {

private _hasPage(pageId: string) {
return this._pages.has('space:' + pageId);
return this._pages.has(`space:${pageId}`);
}
getPage(pageId: string): Page | null {
if (!pageId.startsWith('space:')) {
pageId = 'space:' + pageId;
}
const prefixedPageId = pageId.startsWith('space:')
? (pageId as `space:${string}`)
: (`space:${pageId}` as const);
const page = this._pages.get(pageId) ?? null;
return page;
return this._pages.get(prefixedPageId) ?? null;
}
private _handlePageEvent() {
this.slots.pageAdded.on(pageId => {
private _initBlobStorage() {
this._blobStorage.then(blobStorage => {
blobStorage?.slots.onBlobSyncStateChange.on(state => {
const blobId = state.id;
const syncState = state.state;
if (
syncState === BlobSyncState.Waiting ||
syncState === BlobSyncState.Syncing
) {
this.awarenessStore.setBlobState(blobId, BlobUploadState.Uploading);
return;
}
if (
syncState === BlobSyncState.Success ||
syncState === BlobSyncState.Failed
) {
this.awarenessStore.setBlobState(blobId, BlobUploadState.Uploaded);
return;
}
});
});
}
private _bindPageMetaEvents() {
this.meta.pageMetaAdded.on(pageId => {
const page = new Page(

@@ -421,10 +464,11 @@ this,

this._store.addSpace(page);
page.syncFromExistingDoc();
this._indexer.onCreatePage(pageId);
page.trySyncFromExistingDoc();
});
this.slots.pageRemoved.on(id => {
this.meta.pageMetasUpdated.on(() => this.slots.pagesUpdated.emit());
this.meta.pageMetaRemoved.on(id => {
const page = this.getPage(id) as Page;
page.dispose();
this._store.removeSpace(page);
this.slots.pageRemoved.emit(id);
// TODO remove page from indexer

@@ -434,6 +478,9 @@ });

createPage(pageId: string) {
createPage(pageId: string, parentId?: string) {
if (this._hasPage(pageId)) {
throw new Error('page already exists');
}
if (parentId && !this._hasPage(parentId)) {
throw new Error('parent page not found');
}

@@ -444,3 +491,24 @@ this.meta.addPageMeta({

createDate: +new Date(),
subpageIds: [],
});
if (parentId) {
const parentPage = this.getPage(parentId) as Page;
const parentPageMeta = this.meta.getPageMeta(parentId);
assertExists(parentPageMeta);
// Compatibility process: the old data not has `subpageIds`, it should be an empty array
const subpageIds = [...(parentPageMeta.subpageIds ?? []), pageId];
this.setPageMeta(parentId, {
subpageIds,
});
parentPage.slots.subpageUpdated.emit({
type: 'add',
id: pageId,
subpageIds,
});
}
return this.getPage(pageId) as Page;
}

@@ -453,4 +521,42 @@

shiftPage(pageId: string, newIndex: number) {
this.meta.shiftPageMeta(pageId, newIndex);
}
removePage(pageId: string) {
this.meta.removePage(pageId);
const pageMeta = this.meta.getPageMeta(pageId);
assertExists(pageMeta);
const parentId = this.meta.pageMetas.find(meta =>
meta.subpageIds.includes(pageId)
)?.id;
if (pageMeta.subpageIds?.length) {
pageMeta.subpageIds.forEach((subpageId: string) => {
this.removePage(subpageId);
});
}
if (parentId) {
const parentPageMeta = this.meta.getPageMeta(parentId);
assertExists(parentPageMeta);
const parentPage = this.getPage(parentId) as Page;
const subpageIds = parentPageMeta.subpageIds.filter(
(subpageId: string) => subpageId !== pageMeta.id
);
this.setPageMeta(parentPage.id, {
subpageIds,
});
parentPage.slots.subpageUpdated.emit({
type: 'delete',
id: pageId,
subpageIds,
});
}
const page = this.getPage(pageId);
if (!page) return;
page.dispose();
this.meta.removePageMeta(pageId);
this._store.removeSpace(page);
}

@@ -485,5 +591,6 @@

*/
exportJSX(id = '0') {
return this._store.exportJSX(id);
exportJSX(blockId?: string, pageId = this.meta.pageMetas.at(0)?.id) {
assertExists(pageId);
return this._store.exportJSX(pageId, blockId);
}
}

@@ -5,3 +5,3 @@ import { debug } from '@blocksuite/global/debug';

import { createYMapProxy, ProxyConfig } from './proxy.js';
import { createYMapProxy, type ProxyConfig } from './proxy.js';

@@ -31,5 +31,5 @@ export type BlockSuiteDocAllowedValue =

@debug('transact')
transact(f: (arg0: Transaction) => void, origin?: number) {
super.transact(f, origin);
transact<T>(f: (arg0: Transaction) => T, origin?: number) {
return super.transact(f, origin);
}
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc