mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-24 09:52:49 +08:00
refactor(editor): reorg code structure of store package (#9525)
This commit is contained in:
@@ -13,7 +13,6 @@ import {
|
|||||||
type BlockModel,
|
type BlockModel,
|
||||||
type Blocks,
|
type Blocks,
|
||||||
type BlockSnapshot,
|
type BlockSnapshot,
|
||||||
BlockViewType,
|
|
||||||
type DraftModel,
|
type DraftModel,
|
||||||
type Query,
|
type Query,
|
||||||
Slice,
|
Slice,
|
||||||
@@ -194,7 +193,7 @@ async function renderNoteContent(
|
|||||||
});
|
});
|
||||||
const query: Query = {
|
const query: Query = {
|
||||||
mode: 'strict',
|
mode: 'strict',
|
||||||
match: ids.map(id => ({ id, viewType: BlockViewType.Display })),
|
match: ids.map(id => ({ id, viewType: 'display' })),
|
||||||
};
|
};
|
||||||
const previewDoc = doc.doc.getBlocks({ query });
|
const previewDoc = doc.doc.getBlocks({ query });
|
||||||
const previewSpec = SpecProvider.getInstance().getSpec('page:preview');
|
const previewSpec = SpecProvider.getInstance().getSpec('page:preview');
|
||||||
|
|||||||
@@ -25,12 +25,7 @@ import {
|
|||||||
} from '@blocksuite/block-std';
|
} from '@blocksuite/block-std';
|
||||||
import { GfxControllerIdentifier } from '@blocksuite/block-std/gfx';
|
import { GfxControllerIdentifier } from '@blocksuite/block-std/gfx';
|
||||||
import { assertExists, Bound, getCommonBound } from '@blocksuite/global/utils';
|
import { assertExists, Bound, getCommonBound } from '@blocksuite/global/utils';
|
||||||
import {
|
import { type GetBlocksOptions, type Query, Text } from '@blocksuite/store';
|
||||||
BlockViewType,
|
|
||||||
type GetBlocksOptions,
|
|
||||||
type Query,
|
|
||||||
Text,
|
|
||||||
} from '@blocksuite/store';
|
|
||||||
import { computed } from '@preact/signals-core';
|
import { computed } from '@preact/signals-core';
|
||||||
import { html, type PropertyValues } from 'lit';
|
import { html, type PropertyValues } from 'lit';
|
||||||
import { query, state } from 'lit/decorators.js';
|
import { query, state } from 'lit/decorators.js';
|
||||||
@@ -106,7 +101,7 @@ export class EmbedSyncedDocBlockComponent extends EmbedBlockComponent<EmbedSynce
|
|||||||
props: {
|
props: {
|
||||||
displayMode: NoteDisplayMode.EdgelessOnly,
|
displayMode: NoteDisplayMode.EdgelessOnly,
|
||||||
},
|
},
|
||||||
viewType: BlockViewType.Hidden,
|
viewType: 'hidden',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
ShadowlessElement,
|
ShadowlessElement,
|
||||||
} from '@blocksuite/block-std';
|
} from '@blocksuite/block-std';
|
||||||
import { deserializeXYWH, WithDisposable } from '@blocksuite/global/utils';
|
import { deserializeXYWH, WithDisposable } from '@blocksuite/global/utils';
|
||||||
import { type BlockModel, BlockViewType, type Query } from '@blocksuite/store';
|
import { type BlockModel, type Query } from '@blocksuite/store';
|
||||||
import { css, nothing } from 'lit';
|
import { css, nothing } from 'lit';
|
||||||
import { property } from 'lit/decorators.js';
|
import { property } from 'lit/decorators.js';
|
||||||
import { styleMap } from 'lit/directives/style-map.js';
|
import { styleMap } from 'lit/directives/style-map.js';
|
||||||
@@ -48,7 +48,7 @@ export class SurfaceRefNotePortal extends WithDisposable(ShadowlessElement) {
|
|||||||
mode: 'include',
|
mode: 'include',
|
||||||
match: Array.from(ancestors).map(id => ({
|
match: Array.from(ancestors).map(id => ({
|
||||||
id,
|
id,
|
||||||
viewType: BlockViewType.Display,
|
viewType: 'display',
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
this.query = query;
|
this.query = query;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
type DndEventState,
|
type DndEventState,
|
||||||
} from '@blocksuite/block-std';
|
} from '@blocksuite/block-std';
|
||||||
import { Point } from '@blocksuite/global/utils';
|
import { Point } from '@blocksuite/global/utils';
|
||||||
import { BlockViewType, type Query } from '@blocksuite/store';
|
import type { BlockViewType, Query } from '@blocksuite/store';
|
||||||
|
|
||||||
import { DragPreview } from '../components/drag-preview.js';
|
import { DragPreview } from '../components/drag-preview.js';
|
||||||
import type { AffineDragHandleWidget } from '../drag-handle.js';
|
import type { AffineDragHandleWidget } from '../drag-handle.js';
|
||||||
@@ -24,7 +24,7 @@ export class PreviewHelper {
|
|||||||
const ids: Array<{ id: string; viewType: BlockViewType }> = selectedIds.map(
|
const ids: Array<{ id: string; viewType: BlockViewType }> = selectedIds.map(
|
||||||
id => ({
|
id => ({
|
||||||
id,
|
id,
|
||||||
viewType: BlockViewType.Display,
|
viewType: 'display',
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ export class PreviewHelper {
|
|||||||
let parent: string | null = block;
|
let parent: string | null = block;
|
||||||
do {
|
do {
|
||||||
if (!selectedIds.includes(parent)) {
|
if (!selectedIds.includes(parent)) {
|
||||||
ids.push({ viewType: BlockViewType.Bypass, id: parent });
|
ids.push({ viewType: 'bypass', id: parent });
|
||||||
}
|
}
|
||||||
parent = this.widget.doc.getParent(parent)?.id ?? null;
|
parent = this.widget.doc.getParent(parent)?.id ?? null;
|
||||||
} while (parent && !ids.map(({ id }) => id).includes(parent));
|
} while (parent && !ids.map(({ id }) => id).includes(parent));
|
||||||
@@ -43,7 +43,7 @@ export class PreviewHelper {
|
|||||||
const addChildren = (id: string) => {
|
const addChildren = (id: string) => {
|
||||||
const children = this.widget.doc.getBlock(id)?.model.children ?? [];
|
const children = this.widget.doc.getBlock(id)?.model.children ?? [];
|
||||||
children.forEach(child => {
|
children.forEach(child => {
|
||||||
ids.push({ viewType: BlockViewType.Display, id: child.id });
|
ids.push({ viewType: 'display', id: child.id });
|
||||||
addChildren(child.id);
|
addChildren(child.id);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
DisposableGroup,
|
DisposableGroup,
|
||||||
WithDisposable,
|
WithDisposable,
|
||||||
} from '@blocksuite/global/utils';
|
} from '@blocksuite/global/utils';
|
||||||
import { type Blocks, BlockViewType, type Query } from '@blocksuite/store';
|
import { type Blocks, type Query } from '@blocksuite/store';
|
||||||
import { css, html, nothing, type PropertyValues } from 'lit';
|
import { css, html, nothing, type PropertyValues } from 'lit';
|
||||||
import { property, query, state } from 'lit/decorators.js';
|
import { property, query, state } from 'lit/decorators.js';
|
||||||
import { styleMap } from 'lit/directives/style-map.js';
|
import { styleMap } from 'lit/directives/style-map.js';
|
||||||
@@ -71,7 +71,7 @@ export class FramePreview extends WithDisposable(ShadowlessElement) {
|
|||||||
match: [
|
match: [
|
||||||
{
|
{
|
||||||
flavour: 'affine:frame',
|
flavour: 'affine:frame',
|
||||||
viewType: BlockViewType.Hidden,
|
viewType: 'hidden',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
|
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
|
||||||
import { SignalWatcher, WithDisposable } from '@blocksuite/global/utils';
|
import { SignalWatcher, WithDisposable } from '@blocksuite/global/utils';
|
||||||
import { type BlockModel, Blocks, BlockViewType } from '@blocksuite/store';
|
import { type BlockModel, Blocks, type BlockViewType } from '@blocksuite/store';
|
||||||
import { consume, provide } from '@lit/context';
|
import { consume, provide } from '@lit/context';
|
||||||
import { computed } from '@preact/signals-core';
|
import { computed } from '@preact/signals-core';
|
||||||
import { nothing, type TemplateResult } from 'lit';
|
import { nothing, type TemplateResult } from 'lit';
|
||||||
@@ -187,9 +187,9 @@ export class BlockComponent<
|
|||||||
|
|
||||||
private _renderViewType(content: unknown) {
|
private _renderViewType(content: unknown) {
|
||||||
return choose(this.viewType, [
|
return choose(this.viewType, [
|
||||||
[BlockViewType.Display, () => content],
|
['display', () => content],
|
||||||
[BlockViewType.Hidden, () => nothing],
|
['hidden', () => nothing],
|
||||||
[BlockViewType.Bypass, () => this.renderChildren(this.model)],
|
['bypass', () => this.renderChildren(this.model)],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,7 +310,7 @@ export class BlockComponent<
|
|||||||
accessor doc!: Blocks;
|
accessor doc!: Blocks;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor viewType: BlockViewType = BlockViewType.Display;
|
accessor viewType: BlockViewType = 'display';
|
||||||
|
|
||||||
@property({
|
@property({
|
||||||
attribute: false,
|
attribute: false,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
handleError,
|
handleError,
|
||||||
} from '@blocksuite/global/exceptions';
|
} from '@blocksuite/global/exceptions';
|
||||||
import { SignalWatcher, Slot, WithDisposable } from '@blocksuite/global/utils';
|
import { SignalWatcher, Slot, WithDisposable } from '@blocksuite/global/utils';
|
||||||
import { type BlockModel, Blocks, BlockViewType } from '@blocksuite/store';
|
import { type BlockModel, Blocks } from '@blocksuite/store';
|
||||||
import { createContext, provide } from '@lit/context';
|
import { createContext, provide } from '@lit/context';
|
||||||
import { css, LitElement, nothing, type TemplateResult } from 'lit';
|
import { css, LitElement, nothing, type TemplateResult } from 'lit';
|
||||||
import { property } from 'lit/decorators.js';
|
import { property } from 'lit/decorators.js';
|
||||||
@@ -44,7 +44,7 @@ export class EditorHost extends SignalWatcher(
|
|||||||
private readonly _renderModel = (model: BlockModel): TemplateResult => {
|
private readonly _renderModel = (model: BlockModel): TemplateResult => {
|
||||||
const { flavour } = model;
|
const { flavour } = model;
|
||||||
const block = this.doc.getBlock(model.id);
|
const block = this.doc.getBlock(model.id);
|
||||||
if (!block || block.blockViewType === BlockViewType.Hidden) {
|
if (!block || block.blockViewType === 'hidden') {
|
||||||
return html`${nothing}`;
|
return html`${nothing}`;
|
||||||
}
|
}
|
||||||
const schema = this.doc.schema.flavourSchemaMap.get(flavour);
|
const schema = this.doc.schema.flavourSchemaMap.get(flavour);
|
||||||
|
|||||||
@@ -3,14 +3,15 @@ import { describe, expect, test, vi } from 'vitest';
|
|||||||
import * as Y from 'yjs';
|
import * as Y from 'yjs';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
Block,
|
||||||
defineBlockSchema,
|
defineBlockSchema,
|
||||||
internalPrimitives,
|
internalPrimitives,
|
||||||
Schema,
|
|
||||||
type SchemaToModel,
|
type SchemaToModel,
|
||||||
} from '../schema/index.js';
|
} from '../model/block/index.js';
|
||||||
import { Block, type YBlock } from '../store/doc/block/index.js';
|
import type { YBlock } from '../model/block/types.js';
|
||||||
|
import { Schema } from '../schema/index.js';
|
||||||
|
import { createAutoIncrementIdGenerator } from '../test/index.js';
|
||||||
import { TestWorkspace } from '../test/test-workspace.js';
|
import { TestWorkspace } from '../test/test-workspace.js';
|
||||||
import { createAutoIncrementIdGenerator } from '../utils/id-generator.js';
|
|
||||||
|
|
||||||
const pageSchema = defineBlockSchema({
|
const pageSchema = defineBlockSchema({
|
||||||
flavour: 'page',
|
flavour: 'page',
|
||||||
|
|||||||
@@ -4,13 +4,11 @@ import type { Slot } from '@blocksuite/global/utils';
|
|||||||
import { assert, beforeEach, describe, expect, it, vi } from 'vitest';
|
import { assert, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
import { applyUpdate, type Doc, encodeStateAsUpdate } from 'yjs';
|
import { applyUpdate, type Doc, encodeStateAsUpdate } from 'yjs';
|
||||||
|
|
||||||
import { COLLECTION_VERSION, PAGE_VERSION } from '../consts.js';
|
import type { BlockModel, Blocks, BlockSchemaType, DocMeta } from '../index.js';
|
||||||
import type { BlockModel, Blocks, BlockSchemaType } from '../index.js';
|
|
||||||
import { Schema } from '../index.js';
|
import { Schema } from '../index.js';
|
||||||
import { Text } from '../reactive/text.js';
|
import { Text } from '../reactive/text.js';
|
||||||
import type { DocMeta } from '../store/workspace.js';
|
import { createAutoIncrementIdGenerator } from '../test/index.js';
|
||||||
import { TestWorkspace } from '../test/test-workspace.js';
|
import { TestWorkspace } from '../test/test-workspace.js';
|
||||||
import { createAutoIncrementIdGenerator } from '../utils/id-generator.js';
|
|
||||||
import {
|
import {
|
||||||
NoteBlockSchema,
|
NoteBlockSchema,
|
||||||
ParagraphBlockSchema,
|
ParagraphBlockSchema,
|
||||||
@@ -115,8 +113,8 @@ describe('basic', () => {
|
|||||||
tags: [],
|
tags: [],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
workspaceVersion: COLLECTION_VERSION,
|
workspaceVersion: 2,
|
||||||
pageVersion: PAGE_VERSION,
|
pageVersion: 2,
|
||||||
blockVersions: {
|
blockVersions: {
|
||||||
'affine:note': 1,
|
'affine:note': 1,
|
||||||
'affine:page': 2,
|
'affine:page': 2,
|
||||||
|
|||||||
@@ -2,9 +2,8 @@ import { expect, test, vi } from 'vitest';
|
|||||||
import * as Y from 'yjs';
|
import * as Y from 'yjs';
|
||||||
|
|
||||||
import { Schema } from '../schema/index.js';
|
import { Schema } from '../schema/index.js';
|
||||||
import { BlockViewType } from '../store/index.js';
|
import { createAutoIncrementIdGenerator } from '../test/index.js';
|
||||||
import { TestWorkspace } from '../test/test-workspace.js';
|
import { TestWorkspace } from '../test/test-workspace.js';
|
||||||
import { createAutoIncrementIdGenerator } from '../utils/id-generator.js';
|
|
||||||
import {
|
import {
|
||||||
DividerBlockSchema,
|
DividerBlockSchema,
|
||||||
ListBlockSchema,
|
ListBlockSchema,
|
||||||
@@ -220,7 +219,7 @@ test('query', () => {
|
|||||||
match: [
|
match: [
|
||||||
{
|
{
|
||||||
flavour: 'affine:list',
|
flavour: 'affine:list',
|
||||||
viewType: BlockViewType.Hidden,
|
viewType: 'hidden',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -233,14 +232,14 @@ test('query', () => {
|
|||||||
const paragraph1 = doc1.addBlock('affine:paragraph', {}, note);
|
const paragraph1 = doc1.addBlock('affine:paragraph', {}, note);
|
||||||
const list1 = doc1.addBlock('affine:list' as never, {}, note);
|
const list1 = doc1.addBlock('affine:list' as never, {}, note);
|
||||||
|
|
||||||
expect(doc2?.getBlock(paragraph1)?.blockViewType).toBe(BlockViewType.Display);
|
expect(doc2?.getBlock(paragraph1)?.blockViewType).toBe('display');
|
||||||
expect(doc2?.getBlock(list1)?.blockViewType).toBe(BlockViewType.Display);
|
expect(doc2?.getBlock(list1)?.blockViewType).toBe('display');
|
||||||
expect(doc3?.getBlock(list1)?.blockViewType).toBe(BlockViewType.Hidden);
|
expect(doc3?.getBlock(list1)?.blockViewType).toBe('hidden');
|
||||||
|
|
||||||
const list2 = doc1.addBlock('affine:list' as never, {}, note);
|
const list2 = doc1.addBlock('affine:list' as never, {}, note);
|
||||||
|
|
||||||
expect(doc2?.getBlock(list2)?.blockViewType).toBe(BlockViewType.Display);
|
expect(doc2?.getBlock(list2)?.blockViewType).toBe('display');
|
||||||
expect(doc3?.getBlock(list2)?.blockViewType).toBe(BlockViewType.Hidden);
|
expect(doc3?.getBlock(list2)?.blockViewType).toBe('hidden');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('local readonly', () => {
|
test('local readonly', () => {
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { literal } from 'lit/static-html.js';
|
import { literal } from 'lit/static-html.js';
|
||||||
import { describe, expect, it, vi } from 'vitest';
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
|
import type { BlockModel } from '../model/block/block-model.js';
|
||||||
|
import { defineBlockSchema } from '../model/block/zod.js';
|
||||||
// import some blocks
|
// import some blocks
|
||||||
import { type BlockModel, defineBlockSchema } from '../schema/base.js';
|
|
||||||
import { SchemaValidateError } from '../schema/error.js';
|
import { SchemaValidateError } from '../schema/error.js';
|
||||||
import { Schema } from '../schema/index.js';
|
import { Schema } from '../schema/index.js';
|
||||||
|
import { createAutoIncrementIdGenerator } from '../test/index.js';
|
||||||
import { TestWorkspace } from '../test/test-workspace.js';
|
import { TestWorkspace } from '../test/test-workspace.js';
|
||||||
import { createAutoIncrementIdGenerator } from '../utils/id-generator.js';
|
|
||||||
import {
|
import {
|
||||||
DividerBlockSchema,
|
DividerBlockSchema,
|
||||||
ListBlockSchema,
|
ListBlockSchema,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { defineBlockSchema, type SchemaToModel } from '../schema/index.js';
|
import { defineBlockSchema, type SchemaToModel } from '../model/index.js';
|
||||||
|
|
||||||
export const RootBlockSchema = defineBlockSchema({
|
export const RootBlockSchema = defineBlockSchema({
|
||||||
flavour: 'affine:page',
|
flavour: 'affine:page',
|
||||||
|
|||||||
@@ -2,16 +2,13 @@ import { expect, test } from 'vitest';
|
|||||||
import * as Y from 'yjs';
|
import * as Y from 'yjs';
|
||||||
|
|
||||||
import { MemoryBlobCRUD } from '../adapter/index.js';
|
import { MemoryBlobCRUD } from '../adapter/index.js';
|
||||||
|
import type { BlockModel } from '../model/block/block-model.js';
|
||||||
|
import { defineBlockSchema, type SchemaToModel } from '../model/block/zod.js';
|
||||||
import { Text } from '../reactive/index.js';
|
import { Text } from '../reactive/index.js';
|
||||||
import {
|
import { Schema } from '../schema/index.js';
|
||||||
type BlockModel,
|
import { createAutoIncrementIdGenerator } from '../test/index.js';
|
||||||
defineBlockSchema,
|
|
||||||
Schema,
|
|
||||||
type SchemaToModel,
|
|
||||||
} from '../schema/index.js';
|
|
||||||
import { TestWorkspace } from '../test/test-workspace.js';
|
import { TestWorkspace } from '../test/test-workspace.js';
|
||||||
import { AssetsManager, BaseBlockTransformer } from '../transformer/index.js';
|
import { AssetsManager, BaseBlockTransformer } from '../transformer/index.js';
|
||||||
import { createAutoIncrementIdGenerator } from '../utils/id-generator.js';
|
|
||||||
|
|
||||||
const docSchema = defineBlockSchema({
|
const docSchema = defineBlockSchema({
|
||||||
flavour: 'page',
|
flavour: 'page',
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { BlockSuiteError } from '@blocksuite/global/exceptions';
|
import { BlockSuiteError } from '@blocksuite/global/exceptions';
|
||||||
|
|
||||||
import type { Blocks } from '../store/index.js';
|
import type { Blocks, DraftModel } from '../model/index.js';
|
||||||
import type { AssetsManager } from '../transformer/assets.js';
|
import type { AssetsManager } from '../transformer/assets.js';
|
||||||
import type { DraftModel, Job, Slice } from '../transformer/index.js';
|
import type { Job, Slice } from '../transformer/index.js';
|
||||||
import type {
|
import type {
|
||||||
BlockSnapshot,
|
BlockSnapshot,
|
||||||
DocSnapshot,
|
DocSnapshot,
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
export const COLLECTION_VERSION = 2;
|
|
||||||
|
|
||||||
export const PAGE_VERSION = 2;
|
|
||||||
|
|
||||||
export const SCHEMA_NOT_FOUND_MESSAGE =
|
export const SCHEMA_NOT_FOUND_MESSAGE =
|
||||||
'Schema not found. The block flavour may not be registered.';
|
'Schema not found. The block flavour may not be registered.';
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,11 @@
|
|||||||
/// <reference path="../shim.d.ts" />
|
/// <reference path="../shim.d.ts" />
|
||||||
|
|
||||||
export * from './adapter/index.js';
|
export * from './adapter/index.js';
|
||||||
|
export * from './model/index.js';
|
||||||
export * from './reactive/index.js';
|
export * from './reactive/index.js';
|
||||||
export * from './schema/index.js';
|
export * from './schema/index.js';
|
||||||
export * from './store/index.js';
|
|
||||||
export * from './transformer/index.js';
|
export * from './transformer/index.js';
|
||||||
export { type IdGenerator, nanoid, uuidv4 } from './utils/id-generator.js';
|
export { type IdGenerator, nanoid, uuidv4 } from './utils/id-generator.js';
|
||||||
export * as Utils from './utils/utils.js';
|
|
||||||
export * from './yjs/index.js';
|
export * from './yjs/index.js';
|
||||||
|
|
||||||
const env =
|
const env =
|
||||||
|
|||||||
152
blocksuite/framework/store/src/model/block/block-model.ts
Normal file
152
blocksuite/framework/store/src/model/block/block-model.ts
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
import { type Disposable, Slot } from '@blocksuite/global/utils';
|
||||||
|
import { computed, type Signal, signal } from '@preact/signals-core';
|
||||||
|
|
||||||
|
import type { Text } from '../../reactive/index.js';
|
||||||
|
import type { Blocks } from '../blocks/blocks.js';
|
||||||
|
import type { YBlock } from './types.js';
|
||||||
|
import type { RoleType } from './zod.js';
|
||||||
|
|
||||||
|
type SignaledProps<Props> = Props & {
|
||||||
|
[P in keyof Props & string as `${P}$`]: Signal<Props[P]>;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* The MagicProps function is used to append the props to the class.
|
||||||
|
* For example:
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* class MyBlock extends MagicProps()<{ foo: string }> {}
|
||||||
|
* const myBlock = new MyBlock();
|
||||||
|
* // You'll get type checking for the foo prop
|
||||||
|
* myBlock.foo = 'bar';
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
function MagicProps(): {
|
||||||
|
new <Props>(): Props;
|
||||||
|
} {
|
||||||
|
return class {} as never;
|
||||||
|
}
|
||||||
|
|
||||||
|
const modelLabel = Symbol('model_label');
|
||||||
|
|
||||||
|
// @ts-expect-error allow magic props
|
||||||
|
export class BlockModel<
|
||||||
|
Props extends object = object,
|
||||||
|
PropsSignal extends object = SignaledProps<Props>,
|
||||||
|
> extends MagicProps()<PropsSignal> {
|
||||||
|
private readonly _children = signal<string[]>([]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated use doc instead
|
||||||
|
*/
|
||||||
|
page!: Blocks;
|
||||||
|
|
||||||
|
private readonly _childModels = computed(() => {
|
||||||
|
const value: BlockModel[] = [];
|
||||||
|
this._children.value.forEach(id => {
|
||||||
|
const block = this.page.getBlock$(id);
|
||||||
|
if (block) {
|
||||||
|
value.push(block.model);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
|
||||||
|
private readonly _onCreated: Disposable;
|
||||||
|
|
||||||
|
private readonly _onDeleted: Disposable;
|
||||||
|
|
||||||
|
childMap = computed(() =>
|
||||||
|
this._children.value.reduce((map, id, index) => {
|
||||||
|
map.set(id, index);
|
||||||
|
return map;
|
||||||
|
}, new Map<string, number>())
|
||||||
|
);
|
||||||
|
|
||||||
|
created = new Slot();
|
||||||
|
|
||||||
|
deleted = new Slot();
|
||||||
|
|
||||||
|
flavour!: string;
|
||||||
|
|
||||||
|
id!: string;
|
||||||
|
|
||||||
|
isEmpty = computed(() => {
|
||||||
|
return this._children.value.length === 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
keys!: string[];
|
||||||
|
|
||||||
|
// This is used to avoid https://stackoverflow.com/questions/55886792/infer-typescript-generic-class-type
|
||||||
|
[modelLabel]: Props = 'type_info_label' as never;
|
||||||
|
|
||||||
|
pop!: (prop: keyof Props & string) => void;
|
||||||
|
|
||||||
|
propsUpdated = new Slot<{ key: string }>();
|
||||||
|
|
||||||
|
role!: RoleType;
|
||||||
|
|
||||||
|
stash!: (prop: keyof Props & string) => void;
|
||||||
|
|
||||||
|
// text is optional
|
||||||
|
text?: Text;
|
||||||
|
|
||||||
|
version!: number;
|
||||||
|
|
||||||
|
yBlock!: YBlock;
|
||||||
|
|
||||||
|
get children() {
|
||||||
|
return this._childModels.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get doc() {
|
||||||
|
return this.page;
|
||||||
|
}
|
||||||
|
|
||||||
|
set doc(doc: Blocks) {
|
||||||
|
this.page = doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
get parent() {
|
||||||
|
return this.doc.getParent(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this._onCreated = this.created.once(() => {
|
||||||
|
this._children.value = this.yBlock.get('sys:children').toArray();
|
||||||
|
this.yBlock.get('sys:children').observe(event => {
|
||||||
|
this._children.value = event.target.toArray();
|
||||||
|
});
|
||||||
|
this.yBlock.observe(event => {
|
||||||
|
if (event.keysChanged.has('sys:children')) {
|
||||||
|
this._children.value = this.yBlock.get('sys:children').toArray();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this._onDeleted = this.deleted.once(() => {
|
||||||
|
this._onCreated.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose() {
|
||||||
|
this.created.dispose();
|
||||||
|
this.deleted.dispose();
|
||||||
|
this.propsUpdated.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
firstChild(): BlockModel | null {
|
||||||
|
return this.children[0] || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastChild(): BlockModel | null {
|
||||||
|
if (!this.children.length) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
return this.children[this.children.length - 1].lastChild();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
this._onCreated.dispose();
|
||||||
|
this._onDeleted.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +1,14 @@
|
|||||||
import type { Schema } from '../../../schema/index.js';
|
import type { Schema } from '../../schema/index.js';
|
||||||
import { BlockViewType } from '../consts.js';
|
import type { Blocks } from '../blocks/blocks.js';
|
||||||
import type { Blocks } from '../doc.js';
|
|
||||||
import { SyncController } from './sync-controller.js';
|
import { SyncController } from './sync-controller.js';
|
||||||
import type { BlockOptions, YBlock } from './types.js';
|
import type { BlockOptions, YBlock } from './types.js';
|
||||||
|
|
||||||
export * from './types.js';
|
export type BlockViewType = 'bypass' | 'display' | 'hidden';
|
||||||
|
|
||||||
export class Block {
|
export class Block {
|
||||||
private readonly _syncController: SyncController;
|
private readonly _syncController: SyncController;
|
||||||
|
|
||||||
blockViewType: BlockViewType = BlockViewType.Display;
|
blockViewType: BlockViewType = 'display';
|
||||||
|
|
||||||
get flavour() {
|
get flavour() {
|
||||||
return this._syncController.flavour;
|
return this._syncController.flavour;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { BlockModel } from '../schema/base.js';
|
import type { BlockModel } from './block-model.js';
|
||||||
|
|
||||||
type PropsInDraft = 'version' | 'flavour' | 'role' | 'id' | 'keys' | 'text';
|
type PropsInDraft = 'version' | 'flavour' | 'role' | 'id' | 'keys' | 'text';
|
||||||
|
|
||||||
5
blocksuite/framework/store/src/model/block/index.ts
Normal file
5
blocksuite/framework/store/src/model/block/index.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export * from './block.js';
|
||||||
|
export * from './block-model.js';
|
||||||
|
export * from './draft.js';
|
||||||
|
export * from './types.js';
|
||||||
|
export * from './zod.js';
|
||||||
@@ -9,11 +9,12 @@ import {
|
|||||||
native2Y,
|
native2Y,
|
||||||
type UnRecord,
|
type UnRecord,
|
||||||
y2Native,
|
y2Native,
|
||||||
} from '../../../reactive/index.js';
|
} from '../../reactive/index.js';
|
||||||
import { BlockModel, internalPrimitives } from '../../../schema/base.js';
|
import type { Schema } from '../../schema/schema.js';
|
||||||
import type { Schema } from '../../../schema/schema.js';
|
import type { Blocks } from '../blocks/blocks.js';
|
||||||
import type { Blocks } from '../doc.js';
|
import { BlockModel } from './block-model.js';
|
||||||
import type { YBlock } from './types.js';
|
import type { YBlock } from './types.js';
|
||||||
|
import { internalPrimitives } from './zod.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import type * as Y from 'yjs';
|
import type * as Y from 'yjs';
|
||||||
|
|
||||||
|
import type { BlockModel } from './block-model.js';
|
||||||
import type { Block } from './index.js';
|
import type { Block } from './index.js';
|
||||||
|
|
||||||
export type YBlock = Y.Map<unknown> & {
|
export type YBlock = Y.Map<unknown> & {
|
||||||
@@ -11,3 +12,10 @@ export type YBlock = Y.Map<unknown> & {
|
|||||||
export type BlockOptions = {
|
export type BlockOptions = {
|
||||||
onChange?: (block: Block, key: string, value: unknown) => void;
|
onChange?: (block: Block, key: string, value: unknown) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type BlockSysProps = {
|
||||||
|
id: string;
|
||||||
|
flavour: string;
|
||||||
|
children?: BlockModel[];
|
||||||
|
};
|
||||||
|
export type BlockProps = BlockSysProps & Record<string, unknown>;
|
||||||
124
blocksuite/framework/store/src/model/block/zod.ts
Normal file
124
blocksuite/framework/store/src/model/block/zod.ts
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
import type * as Y from 'yjs';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { Boxed, Text } from '../../reactive/index.js';
|
||||||
|
import type { BaseBlockTransformer } from '../../transformer/base.js';
|
||||||
|
import type { BlockModel } from './block-model.js';
|
||||||
|
|
||||||
|
const FlavourSchema = z.string();
|
||||||
|
const ParentSchema = z.array(z.string()).optional();
|
||||||
|
const ContentSchema = z.array(z.string()).optional();
|
||||||
|
const role = ['root', 'hub', 'content'] as const;
|
||||||
|
const RoleSchema = z.enum(role);
|
||||||
|
|
||||||
|
export type RoleType = (typeof role)[number];
|
||||||
|
|
||||||
|
export interface InternalPrimitives {
|
||||||
|
Text: (input?: Y.Text | string) => Text;
|
||||||
|
Boxed: <T>(input: T) => Boxed<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const internalPrimitives: InternalPrimitives = Object.freeze({
|
||||||
|
Text: (input: Y.Text | string = '') => new Text(input),
|
||||||
|
Boxed: <T>(input: T) => new Boxed(input),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const BlockSchema = z.object({
|
||||||
|
version: z.number(),
|
||||||
|
model: z.object({
|
||||||
|
role: RoleSchema,
|
||||||
|
flavour: FlavourSchema,
|
||||||
|
parent: ParentSchema,
|
||||||
|
children: ContentSchema,
|
||||||
|
props: z
|
||||||
|
.function()
|
||||||
|
.args(z.custom<InternalPrimitives>())
|
||||||
|
.returns(z.record(z.any()))
|
||||||
|
.optional(),
|
||||||
|
toModel: z.function().args().returns(z.custom<BlockModel>()).optional(),
|
||||||
|
}),
|
||||||
|
transformer: z
|
||||||
|
.function()
|
||||||
|
.args()
|
||||||
|
.returns(z.custom<BaseBlockTransformer>())
|
||||||
|
.optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type BlockSchemaType = z.infer<typeof BlockSchema>;
|
||||||
|
|
||||||
|
export type PropsGetter<Props> = (
|
||||||
|
internalPrimitives: InternalPrimitives
|
||||||
|
) => Props;
|
||||||
|
|
||||||
|
export type SchemaToModel<
|
||||||
|
Schema extends {
|
||||||
|
model: {
|
||||||
|
props: PropsGetter<object>;
|
||||||
|
flavour: string;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
> = BlockModel<ReturnType<Schema['model']['props']>> &
|
||||||
|
ReturnType<Schema['model']['props']> & {
|
||||||
|
flavour: Schema['model']['flavour'];
|
||||||
|
};
|
||||||
|
|
||||||
|
export function defineBlockSchema<
|
||||||
|
Flavour extends string,
|
||||||
|
Role extends RoleType,
|
||||||
|
Props extends object,
|
||||||
|
Metadata extends Readonly<{
|
||||||
|
version: number;
|
||||||
|
role: Role;
|
||||||
|
parent?: string[];
|
||||||
|
children?: string[];
|
||||||
|
}>,
|
||||||
|
Model extends BlockModel<Props>,
|
||||||
|
Transformer extends BaseBlockTransformer<Props>,
|
||||||
|
>(options: {
|
||||||
|
flavour: Flavour;
|
||||||
|
metadata: Metadata;
|
||||||
|
props?: (internalPrimitives: InternalPrimitives) => Props;
|
||||||
|
toModel?: () => Model;
|
||||||
|
transformer?: () => Transformer;
|
||||||
|
}): {
|
||||||
|
version: number;
|
||||||
|
model: {
|
||||||
|
props: PropsGetter<Props>;
|
||||||
|
flavour: Flavour;
|
||||||
|
} & Metadata;
|
||||||
|
transformer?: () => Transformer;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function defineBlockSchema({
|
||||||
|
flavour,
|
||||||
|
props,
|
||||||
|
metadata,
|
||||||
|
toModel,
|
||||||
|
transformer,
|
||||||
|
}: {
|
||||||
|
flavour: string;
|
||||||
|
metadata: {
|
||||||
|
version: number;
|
||||||
|
role: RoleType;
|
||||||
|
parent?: string[];
|
||||||
|
children?: string[];
|
||||||
|
};
|
||||||
|
props?: (internalPrimitives: InternalPrimitives) => Record<string, unknown>;
|
||||||
|
toModel?: () => BlockModel;
|
||||||
|
transformer?: () => BaseBlockTransformer;
|
||||||
|
}): BlockSchemaType {
|
||||||
|
const schema = {
|
||||||
|
version: metadata.version,
|
||||||
|
model: {
|
||||||
|
role: metadata.role,
|
||||||
|
parent: metadata.parent,
|
||||||
|
children: metadata.children,
|
||||||
|
flavour,
|
||||||
|
props,
|
||||||
|
toModel,
|
||||||
|
},
|
||||||
|
transformer,
|
||||||
|
} satisfies z.infer<typeof BlockSchema>;
|
||||||
|
BlockSchema.parse(schema);
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
@@ -2,14 +2,18 @@ import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
|
|||||||
import { type Disposable, Slot } from '@blocksuite/global/utils';
|
import { type Disposable, Slot } from '@blocksuite/global/utils';
|
||||||
import { signal } from '@preact/signals-core';
|
import { signal } from '@preact/signals-core';
|
||||||
|
|
||||||
import type { BlockModel, Schema } from '../../schema/index.js';
|
import type { Schema } from '../../schema/index.js';
|
||||||
import type { DraftModel } from '../../transformer/index.js';
|
import {
|
||||||
import { syncBlockProps } from '../../utils/utils.js';
|
Block,
|
||||||
import type { BlockProps, Doc } from '../workspace.js';
|
type BlockModel,
|
||||||
import type { BlockOptions } from './block/index.js';
|
type BlockOptions,
|
||||||
import { Block } from './block/index.js';
|
type BlockProps,
|
||||||
|
type DraftModel,
|
||||||
|
} from '../block/index.js';
|
||||||
|
import type { Doc } from '../doc.js';
|
||||||
import { DocCRUD } from './crud.js';
|
import { DocCRUD } from './crud.js';
|
||||||
import { type Query, runQuery } from './query.js';
|
import { type Query, runQuery } from './query.js';
|
||||||
|
import { syncBlockProps } from './utils.js';
|
||||||
|
|
||||||
type DocOptions = {
|
type DocOptions = {
|
||||||
schema: Schema;
|
schema: Schema;
|
||||||
@@ -2,12 +2,10 @@ import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
|
|||||||
import * as Y from 'yjs';
|
import * as Y from 'yjs';
|
||||||
|
|
||||||
import { native2Y } from '../../reactive/index.js';
|
import { native2Y } from '../../reactive/index.js';
|
||||||
import {
|
import type { Schema } from '../../schema/index.js';
|
||||||
type BlockModel,
|
import type { BlockModel } from '../block/block-model.js';
|
||||||
internalPrimitives,
|
import type { YBlock } from '../block/types.js';
|
||||||
type Schema,
|
import { internalPrimitives } from '../block/zod.js';
|
||||||
} from '../../schema/index.js';
|
|
||||||
import type { YBlock } from './index.js';
|
|
||||||
|
|
||||||
export class DocCRUD {
|
export class DocCRUD {
|
||||||
get root(): string | null {
|
get root(): string | null {
|
||||||
2
blocksuite/framework/store/src/model/blocks/index.ts
Normal file
2
blocksuite/framework/store/src/model/blocks/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './blocks.js';
|
||||||
|
export * from './query.js';
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
import isMatch from 'lodash.ismatch';
|
import isMatch from 'lodash.ismatch';
|
||||||
|
|
||||||
import type { BlockModel } from '../../schema/index.js';
|
import type { Block, BlockModel, BlockViewType } from '../block/index.js';
|
||||||
import type { Block } from './block/index.js';
|
|
||||||
import { BlockViewType } from './consts.js';
|
|
||||||
|
|
||||||
export type QueryMatch = {
|
export type QueryMatch = {
|
||||||
id?: string;
|
id?: string;
|
||||||
@@ -27,7 +25,7 @@ export function runQuery(query: Query, block: Block) {
|
|||||||
const blockViewType = getBlockViewType(query, block);
|
const blockViewType = getBlockViewType(query, block);
|
||||||
block.blockViewType = blockViewType;
|
block.blockViewType = blockViewType;
|
||||||
|
|
||||||
if (blockViewType !== BlockViewType.Hidden) {
|
if (blockViewType !== 'hidden') {
|
||||||
const queryMode = query.mode;
|
const queryMode = query.mode;
|
||||||
setAncestorsToDisplayIfHidden(queryMode, block);
|
setAncestorsToDisplayIfHidden(queryMode, block);
|
||||||
}
|
}
|
||||||
@@ -46,8 +44,8 @@ function getBlockViewType(query: Query, block: Block): BlockViewType {
|
|||||||
},
|
},
|
||||||
{} as Record<string, unknown>
|
{} as Record<string, unknown>
|
||||||
);
|
);
|
||||||
let blockViewType =
|
let blockViewType: BlockViewType =
|
||||||
queryMode === 'loose' ? BlockViewType.Display : BlockViewType.Hidden;
|
queryMode === 'loose' ? 'display' : 'hidden';
|
||||||
|
|
||||||
query.match.some(queryObject => {
|
query.match.some(queryObject => {
|
||||||
const {
|
const {
|
||||||
@@ -76,9 +74,8 @@ function setAncestorsToDisplayIfHidden(mode: QueryMode, block: Block) {
|
|||||||
let parent = doc.getParent(block.model);
|
let parent = doc.getParent(block.model);
|
||||||
while (parent) {
|
while (parent) {
|
||||||
const parentBlock = doc.getBlock(parent.id);
|
const parentBlock = doc.getBlock(parent.id);
|
||||||
if (parentBlock && parentBlock.blockViewType === BlockViewType.Hidden) {
|
if (parentBlock && parentBlock.blockViewType === 'hidden') {
|
||||||
parentBlock.blockViewType =
|
parentBlock.blockViewType = mode === 'include' ? 'display' : 'bypass';
|
||||||
mode === 'include' ? BlockViewType.Display : BlockViewType.Bypass;
|
|
||||||
}
|
}
|
||||||
parent = doc.getParent(parent);
|
parent = doc.getParent(parent);
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import type { z } from 'zod';
|
import type { z } from 'zod';
|
||||||
|
|
||||||
import { SYS_KEYS } from '../consts.js';
|
import { SYS_KEYS } from '../../consts.js';
|
||||||
import { native2Y } from '../reactive/index.js';
|
import { native2Y } from '../../reactive/index.js';
|
||||||
import type { BlockModel, BlockSchema } from '../schema/base.js';
|
import type { BlockModel } from '../block/block-model.js';
|
||||||
import { internalPrimitives } from '../schema/base.js';
|
import type { BlockProps, YBlock } from '../block/types.js';
|
||||||
import type { YBlock } from '../store/doc/block/index.js';
|
import type { BlockSchema } from '../block/zod.js';
|
||||||
import type { BlockProps } from '../store/workspace.js';
|
import { internalPrimitives } from '../block/zod.js';
|
||||||
|
|
||||||
export function syncBlockProps(
|
export function syncBlockProps(
|
||||||
schema: z.infer<typeof BlockSchema>,
|
schema: z.infer<typeof BlockSchema>,
|
||||||
@@ -35,13 +35,3 @@ export function syncBlockProps(
|
|||||||
model[key] = native2Y(value);
|
model[key] = native2Y(value);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export const hash = (str: string) => {
|
|
||||||
return str
|
|
||||||
.split('')
|
|
||||||
.reduce(
|
|
||||||
(prevHash, currVal) =>
|
|
||||||
((prevHash << 5) - prevHash + currVal.charCodeAt(0)) | 0,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
};
|
|
||||||
68
blocksuite/framework/store/src/model/doc.ts
Normal file
68
blocksuite/framework/store/src/model/doc.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import type { Slot } from '@blocksuite/global/utils';
|
||||||
|
import type * as Y from 'yjs';
|
||||||
|
|
||||||
|
import type { Schema } from '../schema/schema.js';
|
||||||
|
import type { AwarenessStore } from '../yjs/awareness.js';
|
||||||
|
import type { YBlock } from './block/types.js';
|
||||||
|
import type { Blocks } from './blocks/blocks.js';
|
||||||
|
import type { Query } from './blocks/query.js';
|
||||||
|
import type { Workspace } from './workspace.js';
|
||||||
|
import type { DocMeta } from './workspace-meta.js';
|
||||||
|
|
||||||
|
export type GetBlocksOptions = {
|
||||||
|
query?: Query;
|
||||||
|
readonly?: boolean;
|
||||||
|
};
|
||||||
|
export type CreateBlocksOptions = GetBlocksOptions & {
|
||||||
|
id?: string;
|
||||||
|
};
|
||||||
|
export type YBlocks = Y.Map<YBlock>;
|
||||||
|
|
||||||
|
export interface Doc {
|
||||||
|
readonly id: string;
|
||||||
|
get meta(): DocMeta | undefined;
|
||||||
|
get schema(): Schema;
|
||||||
|
|
||||||
|
remove(): void;
|
||||||
|
load(initFn?: () => void): void;
|
||||||
|
get ready(): boolean;
|
||||||
|
dispose(): void;
|
||||||
|
|
||||||
|
slots: {
|
||||||
|
historyUpdated: Slot;
|
||||||
|
yBlockUpdated: Slot<
|
||||||
|
| {
|
||||||
|
type: 'add';
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'delete';
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
};
|
||||||
|
|
||||||
|
get history(): Y.UndoManager;
|
||||||
|
get canRedo(): boolean;
|
||||||
|
get canUndo(): boolean;
|
||||||
|
undo(): void;
|
||||||
|
redo(): void;
|
||||||
|
resetHistory(): void;
|
||||||
|
transact(fn: () => void, shouldTransact?: boolean): void;
|
||||||
|
withoutTransact(fn: () => void): void;
|
||||||
|
|
||||||
|
captureSync(): void;
|
||||||
|
clear(): void;
|
||||||
|
getBlocks(options?: GetBlocksOptions): Blocks;
|
||||||
|
clearQuery(query: Query, readonly?: boolean): void;
|
||||||
|
|
||||||
|
get loaded(): boolean;
|
||||||
|
get readonly(): boolean;
|
||||||
|
get awarenessStore(): AwarenessStore;
|
||||||
|
|
||||||
|
get workspace(): Workspace;
|
||||||
|
|
||||||
|
get rootDoc(): Y.Doc;
|
||||||
|
get spaceDoc(): Y.Doc;
|
||||||
|
get yBlocks(): Y.Map<YBlock>;
|
||||||
|
}
|
||||||
19
blocksuite/framework/store/src/model/index.ts
Normal file
19
blocksuite/framework/store/src/model/index.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import type { BlockModel } from './block/block-model.js';
|
||||||
|
|
||||||
|
export * from './block/index.js';
|
||||||
|
export * from './blocks/index.js';
|
||||||
|
export * from './doc.js';
|
||||||
|
export * from './workspace.js';
|
||||||
|
export * from './workspace-meta.js';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
namespace BlockSuite {
|
||||||
|
interface BlockModels {}
|
||||||
|
|
||||||
|
type Flavour = string & keyof BlockModels;
|
||||||
|
|
||||||
|
type ModelProps<Model> = Partial<
|
||||||
|
Model extends BlockModel<infer U> ? U : never
|
||||||
|
>;
|
||||||
|
}
|
||||||
|
}
|
||||||
50
blocksuite/framework/store/src/model/workspace-meta.ts
Normal file
50
blocksuite/framework/store/src/model/workspace-meta.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import type { Slot } from '@blocksuite/global/utils';
|
||||||
|
|
||||||
|
import type { Workspace } from './workspace.js';
|
||||||
|
|
||||||
|
export type Tag = {
|
||||||
|
id: string;
|
||||||
|
value: string;
|
||||||
|
color: string;
|
||||||
|
};
|
||||||
|
export type DocsPropertiesMeta = {
|
||||||
|
tags?: {
|
||||||
|
options: Tag[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export interface DocMeta {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
tags: string[];
|
||||||
|
createDate: number;
|
||||||
|
updatedDate?: number;
|
||||||
|
favorite?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WorkspaceMeta {
|
||||||
|
get docMetas(): DocMeta[];
|
||||||
|
|
||||||
|
addDocMeta(props: DocMeta, index?: number): void;
|
||||||
|
getDocMeta(id: string): DocMeta | undefined;
|
||||||
|
setDocMeta(id: string, props: Partial<DocMeta>): void;
|
||||||
|
removeDocMeta(id: string): void;
|
||||||
|
|
||||||
|
get properties(): DocsPropertiesMeta;
|
||||||
|
setProperties(meta: DocsPropertiesMeta): void;
|
||||||
|
|
||||||
|
get avatar(): string | undefined;
|
||||||
|
setAvatar(avatar: string): void;
|
||||||
|
|
||||||
|
get name(): string | undefined;
|
||||||
|
setName(name: string): void;
|
||||||
|
|
||||||
|
hasVersion: boolean;
|
||||||
|
writeVersion(workspace: Workspace): void;
|
||||||
|
get docs(): unknown[] | undefined;
|
||||||
|
initialize(): void;
|
||||||
|
|
||||||
|
commonFieldsUpdated: Slot;
|
||||||
|
docMetaAdded: Slot<string>;
|
||||||
|
docMetaRemoved: Slot<string>;
|
||||||
|
docMetaUpdated: Slot;
|
||||||
|
}
|
||||||
35
blocksuite/framework/store/src/model/workspace.ts
Normal file
35
blocksuite/framework/store/src/model/workspace.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import type { Slot } from '@blocksuite/global/utils';
|
||||||
|
import type { BlobEngine, DocEngine } from '@blocksuite/sync';
|
||||||
|
import type * as Y from 'yjs';
|
||||||
|
|
||||||
|
import type { Schema } from '../schema/schema.js';
|
||||||
|
import type { IdGenerator } from '../utils/id-generator.js';
|
||||||
|
import type { AwarenessStore } from '../yjs/awareness.js';
|
||||||
|
import type { Blocks } from './blocks/blocks.js';
|
||||||
|
import type { CreateBlocksOptions, Doc, GetBlocksOptions } from './doc.js';
|
||||||
|
import type { WorkspaceMeta } from './workspace-meta.js';
|
||||||
|
|
||||||
|
export interface Workspace {
|
||||||
|
readonly id: string;
|
||||||
|
readonly meta: WorkspaceMeta;
|
||||||
|
readonly idGenerator: IdGenerator;
|
||||||
|
readonly docSync: DocEngine;
|
||||||
|
readonly blobSync: BlobEngine;
|
||||||
|
readonly awarenessStore: AwarenessStore;
|
||||||
|
|
||||||
|
get schema(): Schema;
|
||||||
|
get doc(): Y.Doc;
|
||||||
|
get docs(): Map<string, Doc>;
|
||||||
|
|
||||||
|
slots: {
|
||||||
|
docListUpdated: Slot;
|
||||||
|
docCreated: Slot<string>;
|
||||||
|
docRemoved: Slot<string>;
|
||||||
|
};
|
||||||
|
|
||||||
|
createDoc(options?: CreateBlocksOptions): Blocks;
|
||||||
|
getDoc(docId: string, options?: GetBlocksOptions): Blocks | null;
|
||||||
|
removeDoc(docId: string): void;
|
||||||
|
|
||||||
|
dispose(): void;
|
||||||
|
}
|
||||||
@@ -1,274 +0,0 @@
|
|||||||
import { type Disposable, Slot } from '@blocksuite/global/utils';
|
|
||||||
import type { Signal } from '@preact/signals-core';
|
|
||||||
import { computed, signal } from '@preact/signals-core';
|
|
||||||
import type * as Y from 'yjs';
|
|
||||||
import { z } from 'zod';
|
|
||||||
|
|
||||||
import { Boxed } from '../reactive/boxed.js';
|
|
||||||
import { Text } from '../reactive/text.js';
|
|
||||||
import type { YBlock } from '../store/doc/block/index.js';
|
|
||||||
import type { Blocks } from '../store/index.js';
|
|
||||||
import type { BaseBlockTransformer } from '../transformer/base.js';
|
|
||||||
|
|
||||||
const FlavourSchema = z.string();
|
|
||||||
const ParentSchema = z.array(z.string()).optional();
|
|
||||||
const ContentSchema = z.array(z.string()).optional();
|
|
||||||
const role = ['root', 'hub', 'content'] as const;
|
|
||||||
const RoleSchema = z.enum(role);
|
|
||||||
|
|
||||||
export type RoleType = (typeof role)[number];
|
|
||||||
|
|
||||||
export interface InternalPrimitives {
|
|
||||||
Text: (input?: Y.Text | string) => Text;
|
|
||||||
Boxed: <T>(input: T) => Boxed<T>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const internalPrimitives: InternalPrimitives = Object.freeze({
|
|
||||||
Text: (input: Y.Text | string = '') => new Text(input),
|
|
||||||
Boxed: <T>(input: T) => new Boxed(input),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const BlockSchema = z.object({
|
|
||||||
version: z.number(),
|
|
||||||
model: z.object({
|
|
||||||
role: RoleSchema,
|
|
||||||
flavour: FlavourSchema,
|
|
||||||
parent: ParentSchema,
|
|
||||||
children: ContentSchema,
|
|
||||||
props: z
|
|
||||||
.function()
|
|
||||||
.args(z.custom<InternalPrimitives>())
|
|
||||||
.returns(z.record(z.any()))
|
|
||||||
.optional(),
|
|
||||||
toModel: z.function().args().returns(z.custom<BlockModel>()).optional(),
|
|
||||||
}),
|
|
||||||
transformer: z
|
|
||||||
.function()
|
|
||||||
.args()
|
|
||||||
.returns(z.custom<BaseBlockTransformer>())
|
|
||||||
.optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type BlockSchemaType = z.infer<typeof BlockSchema>;
|
|
||||||
|
|
||||||
export type PropsGetter<Props> = (
|
|
||||||
internalPrimitives: InternalPrimitives
|
|
||||||
) => Props;
|
|
||||||
|
|
||||||
export type SchemaToModel<
|
|
||||||
Schema extends {
|
|
||||||
model: {
|
|
||||||
props: PropsGetter<object>;
|
|
||||||
flavour: string;
|
|
||||||
};
|
|
||||||
},
|
|
||||||
> = BlockModel<ReturnType<Schema['model']['props']>> &
|
|
||||||
ReturnType<Schema['model']['props']> & {
|
|
||||||
flavour: Schema['model']['flavour'];
|
|
||||||
};
|
|
||||||
|
|
||||||
export function defineBlockSchema<
|
|
||||||
Flavour extends string,
|
|
||||||
Role extends RoleType,
|
|
||||||
Props extends object,
|
|
||||||
Metadata extends Readonly<{
|
|
||||||
version: number;
|
|
||||||
role: Role;
|
|
||||||
parent?: string[];
|
|
||||||
children?: string[];
|
|
||||||
}>,
|
|
||||||
Model extends BlockModel<Props>,
|
|
||||||
Transformer extends BaseBlockTransformer<Props>,
|
|
||||||
>(options: {
|
|
||||||
flavour: Flavour;
|
|
||||||
metadata: Metadata;
|
|
||||||
props?: (internalPrimitives: InternalPrimitives) => Props;
|
|
||||||
toModel?: () => Model;
|
|
||||||
transformer?: () => Transformer;
|
|
||||||
}): {
|
|
||||||
version: number;
|
|
||||||
model: {
|
|
||||||
props: PropsGetter<Props>;
|
|
||||||
flavour: Flavour;
|
|
||||||
} & Metadata;
|
|
||||||
transformer?: () => Transformer;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function defineBlockSchema({
|
|
||||||
flavour,
|
|
||||||
props,
|
|
||||||
metadata,
|
|
||||||
toModel,
|
|
||||||
transformer,
|
|
||||||
}: {
|
|
||||||
flavour: string;
|
|
||||||
metadata: {
|
|
||||||
version: number;
|
|
||||||
role: RoleType;
|
|
||||||
parent?: string[];
|
|
||||||
children?: string[];
|
|
||||||
};
|
|
||||||
props?: (internalPrimitives: InternalPrimitives) => Record<string, unknown>;
|
|
||||||
toModel?: () => BlockModel;
|
|
||||||
transformer?: () => BaseBlockTransformer;
|
|
||||||
}): BlockSchemaType {
|
|
||||||
const schema = {
|
|
||||||
version: metadata.version,
|
|
||||||
model: {
|
|
||||||
role: metadata.role,
|
|
||||||
parent: metadata.parent,
|
|
||||||
children: metadata.children,
|
|
||||||
flavour,
|
|
||||||
props,
|
|
||||||
toModel,
|
|
||||||
},
|
|
||||||
transformer,
|
|
||||||
} satisfies z.infer<typeof BlockSchema>;
|
|
||||||
BlockSchema.parse(schema);
|
|
||||||
return schema;
|
|
||||||
}
|
|
||||||
|
|
||||||
type SignaledProps<Props> = Props & {
|
|
||||||
[P in keyof Props & string as `${P}$`]: Signal<Props[P]>;
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* The MagicProps function is used to append the props to the class.
|
|
||||||
* For example:
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* class MyBlock extends MagicProps()<{ foo: string }> {}
|
|
||||||
* const myBlock = new MyBlock();
|
|
||||||
* // You'll get type checking for the foo prop
|
|
||||||
* myBlock.foo = 'bar';
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
function MagicProps(): {
|
|
||||||
new <Props>(): Props;
|
|
||||||
} {
|
|
||||||
return class {} as never;
|
|
||||||
}
|
|
||||||
|
|
||||||
const modelLabel = Symbol('model_label');
|
|
||||||
|
|
||||||
// @ts-expect-error allow magic props
|
|
||||||
export class BlockModel<
|
|
||||||
Props extends object = object,
|
|
||||||
PropsSignal extends object = SignaledProps<Props>,
|
|
||||||
> extends MagicProps()<PropsSignal> {
|
|
||||||
private readonly _children = signal<string[]>([]);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated use doc instead
|
|
||||||
*/
|
|
||||||
page!: Blocks;
|
|
||||||
|
|
||||||
private readonly _childModels = computed(() => {
|
|
||||||
const value: BlockModel[] = [];
|
|
||||||
this._children.value.forEach(id => {
|
|
||||||
const block = this.page.getBlock$(id);
|
|
||||||
if (block) {
|
|
||||||
value.push(block.model);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return value;
|
|
||||||
});
|
|
||||||
|
|
||||||
private readonly _onCreated: Disposable;
|
|
||||||
|
|
||||||
private readonly _onDeleted: Disposable;
|
|
||||||
|
|
||||||
childMap = computed(() =>
|
|
||||||
this._children.value.reduce((map, id, index) => {
|
|
||||||
map.set(id, index);
|
|
||||||
return map;
|
|
||||||
}, new Map<string, number>())
|
|
||||||
);
|
|
||||||
|
|
||||||
created = new Slot();
|
|
||||||
|
|
||||||
deleted = new Slot();
|
|
||||||
|
|
||||||
flavour!: string;
|
|
||||||
|
|
||||||
id!: string;
|
|
||||||
|
|
||||||
isEmpty = computed(() => {
|
|
||||||
return this._children.value.length === 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
keys!: string[];
|
|
||||||
|
|
||||||
// This is used to avoid https://stackoverflow.com/questions/55886792/infer-typescript-generic-class-type
|
|
||||||
[modelLabel]: Props = 'type_info_label' as never;
|
|
||||||
|
|
||||||
pop!: (prop: keyof Props & string) => void;
|
|
||||||
|
|
||||||
propsUpdated = new Slot<{ key: string }>();
|
|
||||||
|
|
||||||
role!: RoleType;
|
|
||||||
|
|
||||||
stash!: (prop: keyof Props & string) => void;
|
|
||||||
|
|
||||||
// text is optional
|
|
||||||
text?: Text;
|
|
||||||
|
|
||||||
version!: number;
|
|
||||||
|
|
||||||
yBlock!: YBlock;
|
|
||||||
|
|
||||||
get children() {
|
|
||||||
return this._childModels.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
get doc() {
|
|
||||||
return this.page;
|
|
||||||
}
|
|
||||||
|
|
||||||
set doc(doc: Blocks) {
|
|
||||||
this.page = doc;
|
|
||||||
}
|
|
||||||
|
|
||||||
get parent() {
|
|
||||||
return this.doc.getParent(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this._onCreated = this.created.once(() => {
|
|
||||||
this._children.value = this.yBlock.get('sys:children').toArray();
|
|
||||||
this.yBlock.get('sys:children').observe(event => {
|
|
||||||
this._children.value = event.target.toArray();
|
|
||||||
});
|
|
||||||
this.yBlock.observe(event => {
|
|
||||||
if (event.keysChanged.has('sys:children')) {
|
|
||||||
this._children.value = this.yBlock.get('sys:children').toArray();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
this._onDeleted = this.deleted.once(() => {
|
|
||||||
this._onCreated.dispose();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
dispose() {
|
|
||||||
this.created.dispose();
|
|
||||||
this.deleted.dispose();
|
|
||||||
this.propsUpdated.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
firstChild(): BlockModel | null {
|
|
||||||
return this.children[0] || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastChild(): BlockModel | null {
|
|
||||||
if (!this.children.length) {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
return this.children[this.children.length - 1].lastChild();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Symbol.dispose]() {
|
|
||||||
this._onCreated.dispose();
|
|
||||||
this._onDeleted.dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +1,5 @@
|
|||||||
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
|
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
|
||||||
|
|
||||||
export class MigrationError extends BlockSuiteError {
|
|
||||||
constructor(description: string) {
|
|
||||||
super(
|
|
||||||
ErrorCode.MigrationError,
|
|
||||||
`Migration failed. Please report to https://github.com/toeverything/blocksuite/issues
|
|
||||||
${description}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SchemaValidateError extends BlockSuiteError {
|
export class SchemaValidateError extends BlockSuiteError {
|
||||||
constructor(flavour: string, message: string) {
|
constructor(flavour: string, message: string) {
|
||||||
super(
|
super(
|
||||||
|
|||||||
@@ -1,2 +1 @@
|
|||||||
export * from './base.js';
|
|
||||||
export { Schema } from './schema.js';
|
export { Schema } from './schema.js';
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { minimatch } from 'minimatch';
|
import { minimatch } from 'minimatch';
|
||||||
|
|
||||||
import { SCHEMA_NOT_FOUND_MESSAGE } from '../consts.js';
|
import { SCHEMA_NOT_FOUND_MESSAGE } from '../consts.js';
|
||||||
import type { BlockSchemaType } from './base.js';
|
import { BlockSchema, type BlockSchemaType } from '../model/index.js';
|
||||||
import { BlockSchema } from './base.js';
|
|
||||||
import { SchemaValidateError } from './error.js';
|
import { SchemaValidateError } from './error.js';
|
||||||
|
|
||||||
export class Schema {
|
export class Schema {
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
export enum BlockViewType {
|
|
||||||
Bypass = 'bypass',
|
|
||||||
Display = 'display',
|
|
||||||
Hidden = 'hidden',
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
export * from './block/index.js';
|
|
||||||
export * from './consts.js';
|
|
||||||
export * from './doc.js';
|
|
||||||
export * from './query.js';
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
export * from './doc/index.js';
|
|
||||||
export * from './workspace.js';
|
|
||||||
@@ -1,166 +0,0 @@
|
|||||||
import type { Slot } from '@blocksuite/global/utils';
|
|
||||||
import type { BlobEngine, DocEngine } from '@blocksuite/sync';
|
|
||||||
import type * as Y from 'yjs';
|
|
||||||
|
|
||||||
import type { BlockModel } from '../schema/base.js';
|
|
||||||
import type { Schema } from '../schema/schema.js';
|
|
||||||
import type { IdGenerator } from '../utils/id-generator.js';
|
|
||||||
import type { AwarenessStore } from '../yjs/awareness.js';
|
|
||||||
import type { YBlock } from './doc/block/types.js';
|
|
||||||
import type { Blocks } from './doc/doc.js';
|
|
||||||
import type { Query } from './doc/query.js';
|
|
||||||
|
|
||||||
export type Tag = {
|
|
||||||
id: string;
|
|
||||||
value: string;
|
|
||||||
color: string;
|
|
||||||
};
|
|
||||||
export type DocsPropertiesMeta = {
|
|
||||||
tags?: {
|
|
||||||
options: Tag[];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
export interface DocMeta {
|
|
||||||
id: string;
|
|
||||||
title: string;
|
|
||||||
tags: string[];
|
|
||||||
createDate: number;
|
|
||||||
updatedDate?: number;
|
|
||||||
favorite?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type GetBlocksOptions = {
|
|
||||||
query?: Query;
|
|
||||||
readonly?: boolean;
|
|
||||||
};
|
|
||||||
export type CreateBlocksOptions = GetBlocksOptions & {
|
|
||||||
id?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface WorkspaceMeta {
|
|
||||||
get docMetas(): DocMeta[];
|
|
||||||
|
|
||||||
addDocMeta(props: DocMeta, index?: number): void;
|
|
||||||
getDocMeta(id: string): DocMeta | undefined;
|
|
||||||
setDocMeta(id: string, props: Partial<DocMeta>): void;
|
|
||||||
removeDocMeta(id: string): void;
|
|
||||||
|
|
||||||
get properties(): DocsPropertiesMeta;
|
|
||||||
setProperties(meta: DocsPropertiesMeta): void;
|
|
||||||
|
|
||||||
get avatar(): string | undefined;
|
|
||||||
setAvatar(avatar: string): void;
|
|
||||||
|
|
||||||
get name(): string | undefined;
|
|
||||||
setName(name: string): void;
|
|
||||||
|
|
||||||
hasVersion: boolean;
|
|
||||||
writeVersion(workspace: Workspace): void;
|
|
||||||
get docs(): unknown[] | undefined;
|
|
||||||
initialize(): void;
|
|
||||||
|
|
||||||
commonFieldsUpdated: Slot;
|
|
||||||
docMetaAdded: Slot<string>;
|
|
||||||
docMetaRemoved: Slot<string>;
|
|
||||||
docMetaUpdated: Slot;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Workspace {
|
|
||||||
readonly id: string;
|
|
||||||
readonly meta: WorkspaceMeta;
|
|
||||||
readonly idGenerator: IdGenerator;
|
|
||||||
readonly docSync: DocEngine;
|
|
||||||
readonly blobSync: BlobEngine;
|
|
||||||
readonly awarenessStore: AwarenessStore;
|
|
||||||
|
|
||||||
get schema(): Schema;
|
|
||||||
get doc(): Y.Doc;
|
|
||||||
get docs(): Map<string, Doc>;
|
|
||||||
|
|
||||||
slots: {
|
|
||||||
docListUpdated: Slot;
|
|
||||||
docCreated: Slot<string>;
|
|
||||||
docRemoved: Slot<string>;
|
|
||||||
};
|
|
||||||
|
|
||||||
createDoc(options?: CreateBlocksOptions): Blocks;
|
|
||||||
getDoc(docId: string, options?: GetBlocksOptions): Blocks | null;
|
|
||||||
removeDoc(docId: string): void;
|
|
||||||
|
|
||||||
dispose(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Doc {
|
|
||||||
readonly id: string;
|
|
||||||
get meta(): DocMeta | undefined;
|
|
||||||
get schema(): Schema;
|
|
||||||
|
|
||||||
remove(): void;
|
|
||||||
load(initFn?: () => void): void;
|
|
||||||
get ready(): boolean;
|
|
||||||
dispose(): void;
|
|
||||||
|
|
||||||
slots: {
|
|
||||||
historyUpdated: Slot;
|
|
||||||
yBlockUpdated: Slot<
|
|
||||||
| {
|
|
||||||
type: 'add';
|
|
||||||
id: string;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: 'delete';
|
|
||||||
id: string;
|
|
||||||
}
|
|
||||||
>;
|
|
||||||
};
|
|
||||||
|
|
||||||
get history(): Y.UndoManager;
|
|
||||||
get canRedo(): boolean;
|
|
||||||
get canUndo(): boolean;
|
|
||||||
undo(): void;
|
|
||||||
redo(): void;
|
|
||||||
resetHistory(): void;
|
|
||||||
transact(fn: () => void, shouldTransact?: boolean): void;
|
|
||||||
withoutTransact(fn: () => void): void;
|
|
||||||
|
|
||||||
captureSync(): void;
|
|
||||||
clear(): void;
|
|
||||||
getBlocks(options?: GetBlocksOptions): Blocks;
|
|
||||||
clearQuery(query: Query, readonly?: boolean): void;
|
|
||||||
|
|
||||||
get loaded(): boolean;
|
|
||||||
get readonly(): boolean;
|
|
||||||
get awarenessStore(): AwarenessStore;
|
|
||||||
|
|
||||||
get workspace(): Workspace;
|
|
||||||
|
|
||||||
get rootDoc(): Y.Doc;
|
|
||||||
get spaceDoc(): Y.Doc;
|
|
||||||
get yBlocks(): Y.Map<YBlock>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface StackItem {
|
|
||||||
meta: Map<'selection-state', unknown>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type YBlocks = Y.Map<YBlock>;
|
|
||||||
|
|
||||||
/** JSON-serializable properties of a block */
|
|
||||||
export type BlockSysProps = {
|
|
||||||
id: string;
|
|
||||||
flavour: string;
|
|
||||||
children?: BlockModel[];
|
|
||||||
};
|
|
||||||
export type BlockProps = BlockSysProps & Record<string, unknown>;
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
namespace BlockSuite {
|
|
||||||
interface BlockModels {}
|
|
||||||
|
|
||||||
type Flavour = string & keyof BlockModels;
|
|
||||||
|
|
||||||
type ModelProps<Model> = Partial<
|
|
||||||
Model extends BlockModel<infer U> ? U : never
|
|
||||||
>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,10 @@
|
|||||||
export { createAutoIncrementIdGenerator } from '../utils/id-generator.js';
|
import type { IdGenerator } from '../utils/id-generator.js';
|
||||||
|
|
||||||
export * from './test-doc.js';
|
export * from './test-doc.js';
|
||||||
export * from './test-meta.js';
|
export * from './test-meta.js';
|
||||||
export * from './test-workspace.js';
|
export * from './test-workspace.js';
|
||||||
|
|
||||||
|
export function createAutoIncrementIdGenerator(): IdGenerator {
|
||||||
|
let i = 0;
|
||||||
|
return () => (i++).toString();
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import { type Disposable, Slot } from '@blocksuite/global/utils';
|
|||||||
import { signal } from '@preact/signals-core';
|
import { signal } from '@preact/signals-core';
|
||||||
import * as Y from 'yjs';
|
import * as Y from 'yjs';
|
||||||
|
|
||||||
import { Blocks } from '../store/doc/doc.js';
|
import type { YBlock } from '../model/block/types.js';
|
||||||
import type { YBlock } from '../store/doc/index.js';
|
import { Blocks } from '../model/blocks/blocks.js';
|
||||||
import type { Query } from '../store/doc/query.js';
|
import type { Query } from '../model/blocks/query.js';
|
||||||
import type { Doc, GetBlocksOptions, Workspace } from '../store/workspace.js';
|
import type { Doc, GetBlocksOptions, Workspace } from '../model/index.js';
|
||||||
import type { AwarenessStore } from '../yjs/index.js';
|
import type { AwarenessStore } from '../yjs/index.js';
|
||||||
|
|
||||||
type DocOptions = {
|
type DocOptions = {
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
import { Slot } from '@blocksuite/global/utils';
|
import { Slot } from '@blocksuite/global/utils';
|
||||||
import type * as Y from 'yjs';
|
import type * as Y from 'yjs';
|
||||||
|
|
||||||
import { COLLECTION_VERSION, PAGE_VERSION } from '../consts.js';
|
|
||||||
import { createYProxy } from '../reactive/proxy.js';
|
|
||||||
import type {
|
import type {
|
||||||
DocMeta,
|
DocMeta,
|
||||||
DocsPropertiesMeta,
|
DocsPropertiesMeta,
|
||||||
Workspace,
|
Workspace,
|
||||||
WorkspaceMeta,
|
WorkspaceMeta,
|
||||||
} from '../store/workspace.js';
|
} from '../model/index.js';
|
||||||
|
import { createYProxy } from '../reactive/proxy.js';
|
||||||
|
|
||||||
export type DocCollectionMetaState = {
|
const COLLECTION_VERSION = 2;
|
||||||
|
const PAGE_VERSION = 2;
|
||||||
|
|
||||||
|
type DocCollectionMetaState = {
|
||||||
pages?: unknown[];
|
pages?: unknown[];
|
||||||
properties?: DocsPropertiesMeta;
|
properties?: DocsPropertiesMeta;
|
||||||
workspaceVersion?: number;
|
workspaceVersion?: number;
|
||||||
|
|||||||
@@ -16,14 +16,14 @@ import merge from 'lodash.merge';
|
|||||||
import { Awareness } from 'y-protocols/awareness.js';
|
import { Awareness } from 'y-protocols/awareness.js';
|
||||||
import * as Y from 'yjs';
|
import * as Y from 'yjs';
|
||||||
|
|
||||||
|
import type {
|
||||||
|
Blocks,
|
||||||
|
CreateBlocksOptions,
|
||||||
|
GetBlocksOptions,
|
||||||
|
Workspace,
|
||||||
|
WorkspaceMeta,
|
||||||
|
} from '../model/index.js';
|
||||||
import type { Schema } from '../schema/index.js';
|
import type { Schema } from '../schema/index.js';
|
||||||
import {
|
|
||||||
type Blocks,
|
|
||||||
type CreateBlocksOptions,
|
|
||||||
type GetBlocksOptions,
|
|
||||||
type Workspace,
|
|
||||||
type WorkspaceMeta,
|
|
||||||
} from '../store/index.js';
|
|
||||||
import { type IdGenerator, nanoid } from '../utils/id-generator.js';
|
import { type IdGenerator, nanoid } from '../utils/id-generator.js';
|
||||||
import { AwarenessStore, type RawAwarenessState } from '../yjs/index.js';
|
import { AwarenessStore, type RawAwarenessState } from '../yjs/index.js';
|
||||||
import { TestDoc } from './test-doc.js';
|
import { TestDoc } from './test-doc.js';
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import type { BlockModel, InternalPrimitives } from '../schema/index.js';
|
import type { BlockModel } from '../model/block/block-model.js';
|
||||||
import { internalPrimitives } from '../schema/index.js';
|
import type { DraftModel } from '../model/block/draft.js';
|
||||||
|
import {
|
||||||
|
type InternalPrimitives,
|
||||||
|
internalPrimitives,
|
||||||
|
} from '../model/block/zod.js';
|
||||||
import type { AssetsManager } from './assets.js';
|
import type { AssetsManager } from './assets.js';
|
||||||
import type { DraftModel } from './draft.js';
|
|
||||||
import { fromJSON, toJSON } from './json.js';
|
import { fromJSON, toJSON } from './json.js';
|
||||||
import type { BlockSnapshot } from './type.js';
|
import type { BlockSnapshot } from './type.js';
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
export * from './assets.js';
|
export * from './assets.js';
|
||||||
export * from './base.js';
|
export * from './base.js';
|
||||||
export * from './draft.js';
|
|
||||||
export * from './job.js';
|
export * from './job.js';
|
||||||
export * from './json.js';
|
export * from './json.js';
|
||||||
export * from './middleware.js';
|
export * from './middleware.js';
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
|
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
|
||||||
import { nextTick, Slot } from '@blocksuite/global/utils';
|
import { nextTick, Slot } from '@blocksuite/global/utils';
|
||||||
|
|
||||||
import type { BlockModel, BlockSchemaType, Schema } from '../schema/index.js';
|
import type {
|
||||||
import type { Blocks } from '../store/index.js';
|
BlockModel,
|
||||||
|
Blocks,
|
||||||
|
BlockSchemaType,
|
||||||
|
DraftModel,
|
||||||
|
} from '../model/index.js';
|
||||||
|
import type { Schema } from '../schema/index.js';
|
||||||
import { AssetsManager } from './assets.js';
|
import { AssetsManager } from './assets.js';
|
||||||
import { BaseBlockTransformer } from './base.js';
|
import { BaseBlockTransformer } from './base.js';
|
||||||
import type { DraftModel } from './draft.js';
|
|
||||||
import type {
|
import type {
|
||||||
BeforeExportPayload,
|
BeforeExportPayload,
|
||||||
BeforeImportPayload,
|
BeforeImportPayload,
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import type { Slot } from '@blocksuite/global/utils';
|
import type { Slot } from '@blocksuite/global/utils';
|
||||||
|
|
||||||
import type { Blocks } from '../store/index.js';
|
import type { Blocks, DraftModel } from '../model/index.js';
|
||||||
import type { AssetsManager } from './assets.js';
|
import type { AssetsManager } from './assets.js';
|
||||||
import type { DraftModel } from './draft.js';
|
|
||||||
import type { Slice } from './slice.js';
|
import type { Slice } from './slice.js';
|
||||||
import type {
|
import type {
|
||||||
BlockSnapshot,
|
BlockSnapshot,
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import type { Blocks } from '../store/index.js';
|
import type { Blocks, DraftModel } from '../model/index.js';
|
||||||
import type { DraftModel } from './draft.js';
|
|
||||||
|
|
||||||
type SliceData = {
|
type SliceData = {
|
||||||
content: DraftModel[];
|
content: DraftModel[];
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import type { Blocks } from '../store/doc/doc.js';
|
import type { Blocks } from '../model/blocks/blocks.js';
|
||||||
import type { DocMeta, DocsPropertiesMeta } from '../store/workspace.js';
|
import type { DocMeta, DocsPropertiesMeta } from '../model/workspace-meta.js';
|
||||||
|
|
||||||
export type BlockSnapshot = {
|
export type BlockSnapshot = {
|
||||||
type: 'block';
|
type: 'block';
|
||||||
|
|||||||
@@ -3,11 +3,6 @@ import { nanoid as nanoidGenerator } from 'nanoid';
|
|||||||
|
|
||||||
export type IdGenerator = () => string;
|
export type IdGenerator = () => string;
|
||||||
|
|
||||||
export function createAutoIncrementIdGenerator(): IdGenerator {
|
|
||||||
let i = 0;
|
|
||||||
return () => (i++).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
export const uuidv4: IdGenerator = () => {
|
export const uuidv4: IdGenerator = () => {
|
||||||
return uuidv4IdGenerator();
|
return uuidv4IdGenerator();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import clonedeep from 'lodash.clonedeep';
|
|||||||
import merge from 'lodash.merge';
|
import merge from 'lodash.merge';
|
||||||
import type { Awareness as YAwareness } from 'y-protocols/awareness.js';
|
import type { Awareness as YAwareness } from 'y-protocols/awareness.js';
|
||||||
|
|
||||||
import type { Doc } from '../store/index.js';
|
import type { Doc } from '../model/doc.js';
|
||||||
|
|
||||||
export interface UserInfo {
|
export interface UserInfo {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -14,25 +14,22 @@ export interface UserInfo {
|
|||||||
type UserSelection = Array<Record<string, unknown>>;
|
type UserSelection = Array<Record<string, unknown>>;
|
||||||
|
|
||||||
// Raw JSON state in awareness CRDT
|
// Raw JSON state in awareness CRDT
|
||||||
export type RawAwarenessState<Flags extends BlockSuiteFlags = BlockSuiteFlags> =
|
export type RawAwarenessState = {
|
||||||
{
|
user?: UserInfo;
|
||||||
user?: UserInfo;
|
color?: string;
|
||||||
color?: string;
|
flags: BlockSuiteFlags;
|
||||||
flags: Flags;
|
// use v2 to avoid crush on old clients
|
||||||
// use v2 to avoid crush on old clients
|
selectionV2: Record<string, UserSelection>;
|
||||||
selectionV2: Record<string, UserSelection>;
|
};
|
||||||
};
|
|
||||||
|
|
||||||
export interface AwarenessEvent<
|
export interface AwarenessEvent {
|
||||||
Flags extends BlockSuiteFlags = BlockSuiteFlags,
|
|
||||||
> {
|
|
||||||
id: number;
|
id: number;
|
||||||
type: 'add' | 'update' | 'remove';
|
type: 'add' | 'update' | 'remove';
|
||||||
state?: RawAwarenessState<Flags>;
|
state?: RawAwarenessState;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AwarenessStore<Flags extends BlockSuiteFlags = BlockSuiteFlags> {
|
export class AwarenessStore {
|
||||||
private readonly _flags: Signal<Flags>;
|
private readonly _flags: Signal<BlockSuiteFlags>;
|
||||||
|
|
||||||
private readonly _onAwarenessChange = (diff: {
|
private readonly _onAwarenessChange = (diff: {
|
||||||
added: number[];
|
added: number[];
|
||||||
@@ -66,24 +63,24 @@ export class AwarenessStore<Flags extends BlockSuiteFlags = BlockSuiteFlags> {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
readonly awareness: YAwareness<RawAwarenessState<Flags>>;
|
readonly awareness: YAwareness<RawAwarenessState>;
|
||||||
|
|
||||||
readonly slots = {
|
readonly slots = {
|
||||||
update: new Slot<AwarenessEvent<Flags>>(),
|
update: new Slot<AwarenessEvent>(),
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
awareness: YAwareness<RawAwarenessState<Flags>>,
|
awareness: YAwareness<RawAwarenessState>,
|
||||||
defaultFlags: Flags
|
defaultFlags: BlockSuiteFlags
|
||||||
) {
|
) {
|
||||||
this._flags = signal<Flags>(defaultFlags);
|
this._flags = signal(defaultFlags);
|
||||||
this.awareness = awareness;
|
this.awareness = awareness;
|
||||||
this.awareness.on('change', this._onAwarenessChange);
|
this.awareness.on('change', this._onAwarenessChange);
|
||||||
this.awareness.setLocalStateField('selectionV2', {});
|
this.awareness.setLocalStateField('selectionV2', {});
|
||||||
this._initFlags(defaultFlags);
|
this._initFlags(defaultFlags);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _initFlags(defaultFlags: Flags) {
|
private _initFlags(defaultFlags: BlockSuiteFlags) {
|
||||||
const upstreamFlags = this.awareness.getLocalState()?.flags;
|
const upstreamFlags = this.awareness.getLocalState()?.flags;
|
||||||
const flags = clonedeep(defaultFlags);
|
const flags = clonedeep(defaultFlags);
|
||||||
if (upstreamFlags) {
|
if (upstreamFlags) {
|
||||||
@@ -98,7 +95,7 @@ export class AwarenessStore<Flags extends BlockSuiteFlags = BlockSuiteFlags> {
|
|||||||
this.awareness.destroy();
|
this.awareness.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
getFlag<Key extends keyof Flags>(field: Key) {
|
getFlag<Key extends keyof BlockSuiteFlags>(field: Key) {
|
||||||
return this._flags.value[field];
|
return this._flags.value[field];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,7 +108,7 @@ export class AwarenessStore<Flags extends BlockSuiteFlags = BlockSuiteFlags> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getStates(): Map<number, RawAwarenessState<Flags>> {
|
getStates(): Map<number, RawAwarenessState> {
|
||||||
return this.awareness.getStates();
|
return this.awareness.getStates();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,7 +121,10 @@ export class AwarenessStore<Flags extends BlockSuiteFlags = BlockSuiteFlags> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setFlag<Key extends keyof Flags>(field: Key, value: Flags[Key]) {
|
setFlag<Key extends keyof BlockSuiteFlags>(
|
||||||
|
field: Key,
|
||||||
|
value: BlockSuiteFlags[Key]
|
||||||
|
) {
|
||||||
const oldFlags = this.awareness.getLocalState()?.flags ?? {};
|
const oldFlags = this.awareness.getLocalState()?.flags ?? {};
|
||||||
this.awareness.setLocalStateField('flags', { ...oldFlags, [field]: value });
|
this.awareness.setLocalStateField('flags', { ...oldFlags, [field]: value });
|
||||||
}
|
}
|
||||||
@@ -142,6 +142,6 @@ export class AwarenessStore<Flags extends BlockSuiteFlags = BlockSuiteFlags> {
|
|||||||
this.setFlag('readonly', {
|
this.setFlag('readonly', {
|
||||||
...flags,
|
...flags,
|
||||||
[blockCollection.id]: value,
|
[blockCollection.id]: value,
|
||||||
} as Flags['readonly']);
|
} as BlockSuiteFlags['readonly']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,3 +5,7 @@ export type SubdocEvent = {
|
|||||||
removed: Set<YDoc>;
|
removed: Set<YDoc>;
|
||||||
added: Set<YDoc>;
|
added: Set<YDoc>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface StackItem {
|
||||||
|
meta: Map<'selection-state', unknown>;
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import { BLOCK_ID_ATTR } from '@blocksuite/block-std';
|
|||||||
import { assertExists } from '@blocksuite/global/utils';
|
import { assertExists } from '@blocksuite/global/utils';
|
||||||
import type { InlineRootElement } from '@inline/inline-editor.js';
|
import type { InlineRootElement } from '@inline/inline-editor.js';
|
||||||
import { expect, type Locator, type Page } from '@playwright/test';
|
import { expect, type Locator, type Page } from '@playwright/test';
|
||||||
import { COLLECTION_VERSION, PAGE_VERSION } from '@store/consts.js';
|
|
||||||
import type { BlockModel } from '@store/index.js';
|
import type { BlockModel } from '@store/index.js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -88,8 +87,8 @@ export const defaultStore = {
|
|||||||
'affine:surface-ref': 1,
|
'affine:surface-ref': 1,
|
||||||
'affine:edgeless-text': 1,
|
'affine:edgeless-text': 1,
|
||||||
},
|
},
|
||||||
workspaceVersion: COLLECTION_VERSION,
|
workspaceVersion: 2,
|
||||||
pageVersion: PAGE_VERSION,
|
pageVersion: 2,
|
||||||
},
|
},
|
||||||
spaces: {
|
spaces: {
|
||||||
'doc:home': {
|
'doc:home': {
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import { Container, type ServiceProvider } from '@blocksuite/affine/global/di';
|
|||||||
import { WithDisposable } from '@blocksuite/affine/global/utils';
|
import { WithDisposable } from '@blocksuite/affine/global/utils';
|
||||||
import {
|
import {
|
||||||
type Blocks,
|
type Blocks,
|
||||||
BlockViewType,
|
|
||||||
type JobMiddleware,
|
type JobMiddleware,
|
||||||
type Query,
|
type Query,
|
||||||
type Schema,
|
type Schema,
|
||||||
@@ -186,7 +185,7 @@ export class TextRenderer extends WithDisposable(ShadowlessElement) {
|
|||||||
'affine:code',
|
'affine:code',
|
||||||
'affine:list',
|
'affine:list',
|
||||||
'affine:divider',
|
'affine:divider',
|
||||||
].map(flavour => ({ flavour, viewType: BlockViewType.Display })),
|
].map(flavour => ({ flavour, viewType: 'display' })),
|
||||||
};
|
};
|
||||||
|
|
||||||
private _timer?: ReturnType<typeof setInterval> | null = null;
|
private _timer?: ReturnType<typeof setInterval> | null = null;
|
||||||
|
|||||||
Reference in New Issue
Block a user