mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 12:28:42 +00:00
refactor(editor): reorg code structure of store package (#9525)
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
|
||||
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 { computed } from '@preact/signals-core';
|
||||
import { nothing, type TemplateResult } from 'lit';
|
||||
@@ -187,9 +187,9 @@ export class BlockComponent<
|
||||
|
||||
private _renderViewType(content: unknown) {
|
||||
return choose(this.viewType, [
|
||||
[BlockViewType.Display, () => content],
|
||||
[BlockViewType.Hidden, () => nothing],
|
||||
[BlockViewType.Bypass, () => this.renderChildren(this.model)],
|
||||
['display', () => content],
|
||||
['hidden', () => nothing],
|
||||
['bypass', () => this.renderChildren(this.model)],
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -310,7 +310,7 @@ export class BlockComponent<
|
||||
accessor doc!: Blocks;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor viewType: BlockViewType = BlockViewType.Display;
|
||||
accessor viewType: BlockViewType = 'display';
|
||||
|
||||
@property({
|
||||
attribute: false,
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
handleError,
|
||||
} from '@blocksuite/global/exceptions';
|
||||
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 { css, LitElement, nothing, type TemplateResult } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
@@ -44,7 +44,7 @@ export class EditorHost extends SignalWatcher(
|
||||
private readonly _renderModel = (model: BlockModel): TemplateResult => {
|
||||
const { flavour } = model;
|
||||
const block = this.doc.getBlock(model.id);
|
||||
if (!block || block.blockViewType === BlockViewType.Hidden) {
|
||||
if (!block || block.blockViewType === 'hidden') {
|
||||
return html`${nothing}`;
|
||||
}
|
||||
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 {
|
||||
Block,
|
||||
defineBlockSchema,
|
||||
internalPrimitives,
|
||||
Schema,
|
||||
type SchemaToModel,
|
||||
} from '../schema/index.js';
|
||||
import { Block, type YBlock } from '../store/doc/block/index.js';
|
||||
} from '../model/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 { createAutoIncrementIdGenerator } from '../utils/id-generator.js';
|
||||
|
||||
const pageSchema = defineBlockSchema({
|
||||
flavour: 'page',
|
||||
|
||||
@@ -4,13 +4,11 @@ import type { Slot } from '@blocksuite/global/utils';
|
||||
import { assert, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { applyUpdate, type Doc, encodeStateAsUpdate } from 'yjs';
|
||||
|
||||
import { COLLECTION_VERSION, PAGE_VERSION } from '../consts.js';
|
||||
import type { BlockModel, Blocks, BlockSchemaType } from '../index.js';
|
||||
import type { BlockModel, Blocks, BlockSchemaType, DocMeta } from '../index.js';
|
||||
import { Schema } from '../index.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 { createAutoIncrementIdGenerator } from '../utils/id-generator.js';
|
||||
import {
|
||||
NoteBlockSchema,
|
||||
ParagraphBlockSchema,
|
||||
@@ -115,8 +113,8 @@ describe('basic', () => {
|
||||
tags: [],
|
||||
},
|
||||
],
|
||||
workspaceVersion: COLLECTION_VERSION,
|
||||
pageVersion: PAGE_VERSION,
|
||||
workspaceVersion: 2,
|
||||
pageVersion: 2,
|
||||
blockVersions: {
|
||||
'affine:note': 1,
|
||||
'affine:page': 2,
|
||||
|
||||
@@ -2,9 +2,8 @@ import { expect, test, vi } from 'vitest';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
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 { createAutoIncrementIdGenerator } from '../utils/id-generator.js';
|
||||
import {
|
||||
DividerBlockSchema,
|
||||
ListBlockSchema,
|
||||
@@ -220,7 +219,7 @@ test('query', () => {
|
||||
match: [
|
||||
{
|
||||
flavour: 'affine:list',
|
||||
viewType: BlockViewType.Hidden,
|
||||
viewType: 'hidden',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -233,14 +232,14 @@ test('query', () => {
|
||||
const paragraph1 = doc1.addBlock('affine:paragraph', {}, note);
|
||||
const list1 = doc1.addBlock('affine:list' as never, {}, note);
|
||||
|
||||
expect(doc2?.getBlock(paragraph1)?.blockViewType).toBe(BlockViewType.Display);
|
||||
expect(doc2?.getBlock(list1)?.blockViewType).toBe(BlockViewType.Display);
|
||||
expect(doc3?.getBlock(list1)?.blockViewType).toBe(BlockViewType.Hidden);
|
||||
expect(doc2?.getBlock(paragraph1)?.blockViewType).toBe('display');
|
||||
expect(doc2?.getBlock(list1)?.blockViewType).toBe('display');
|
||||
expect(doc3?.getBlock(list1)?.blockViewType).toBe('hidden');
|
||||
|
||||
const list2 = doc1.addBlock('affine:list' as never, {}, note);
|
||||
|
||||
expect(doc2?.getBlock(list2)?.blockViewType).toBe(BlockViewType.Display);
|
||||
expect(doc3?.getBlock(list2)?.blockViewType).toBe(BlockViewType.Hidden);
|
||||
expect(doc2?.getBlock(list2)?.blockViewType).toBe('display');
|
||||
expect(doc3?.getBlock(list2)?.blockViewType).toBe('hidden');
|
||||
});
|
||||
|
||||
test('local readonly', () => {
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { literal } from 'lit/static-html.js';
|
||||
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 { type BlockModel, defineBlockSchema } from '../schema/base.js';
|
||||
import { SchemaValidateError } from '../schema/error.js';
|
||||
import { Schema } from '../schema/index.js';
|
||||
import { createAutoIncrementIdGenerator } from '../test/index.js';
|
||||
import { TestWorkspace } from '../test/test-workspace.js';
|
||||
import { createAutoIncrementIdGenerator } from '../utils/id-generator.js';
|
||||
import {
|
||||
DividerBlockSchema,
|
||||
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({
|
||||
flavour: 'affine:page',
|
||||
|
||||
@@ -2,16 +2,13 @@ import { expect, test } from 'vitest';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
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 {
|
||||
type BlockModel,
|
||||
defineBlockSchema,
|
||||
Schema,
|
||||
type SchemaToModel,
|
||||
} from '../schema/index.js';
|
||||
import { Schema } from '../schema/index.js';
|
||||
import { createAutoIncrementIdGenerator } from '../test/index.js';
|
||||
import { TestWorkspace } from '../test/test-workspace.js';
|
||||
import { AssetsManager, BaseBlockTransformer } from '../transformer/index.js';
|
||||
import { createAutoIncrementIdGenerator } from '../utils/id-generator.js';
|
||||
|
||||
const docSchema = defineBlockSchema({
|
||||
flavour: 'page',
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
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 { DraftModel, Job, Slice } from '../transformer/index.js';
|
||||
import type { Job, Slice } from '../transformer/index.js';
|
||||
import type {
|
||||
BlockSnapshot,
|
||||
DocSnapshot,
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
export const COLLECTION_VERSION = 2;
|
||||
|
||||
export const PAGE_VERSION = 2;
|
||||
|
||||
export const SCHEMA_NOT_FOUND_MESSAGE =
|
||||
'Schema not found. The block flavour may not be registered.';
|
||||
|
||||
|
||||
@@ -2,12 +2,11 @@
|
||||
/// <reference path="../shim.d.ts" />
|
||||
|
||||
export * from './adapter/index.js';
|
||||
export * from './model/index.js';
|
||||
export * from './reactive/index.js';
|
||||
export * from './schema/index.js';
|
||||
export * from './store/index.js';
|
||||
export * from './transformer/index.js';
|
||||
export { type IdGenerator, nanoid, uuidv4 } from './utils/id-generator.js';
|
||||
export * as Utils from './utils/utils.js';
|
||||
export * from './yjs/index.js';
|
||||
|
||||
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 { BlockViewType } from '../consts.js';
|
||||
import type { Blocks } from '../doc.js';
|
||||
import type { Schema } from '../../schema/index.js';
|
||||
import type { Blocks } from '../blocks/blocks.js';
|
||||
import { SyncController } from './sync-controller.js';
|
||||
import type { BlockOptions, YBlock } from './types.js';
|
||||
|
||||
export * from './types.js';
|
||||
export type BlockViewType = 'bypass' | 'display' | 'hidden';
|
||||
|
||||
export class Block {
|
||||
private readonly _syncController: SyncController;
|
||||
|
||||
blockViewType: BlockViewType = BlockViewType.Display;
|
||||
blockViewType: BlockViewType = 'display';
|
||||
|
||||
get 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';
|
||||
|
||||
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,
|
||||
type UnRecord,
|
||||
y2Native,
|
||||
} from '../../../reactive/index.js';
|
||||
import { BlockModel, internalPrimitives } from '../../../schema/base.js';
|
||||
import type { Schema } from '../../../schema/schema.js';
|
||||
import type { Blocks } from '../doc.js';
|
||||
} from '../../reactive/index.js';
|
||||
import type { Schema } from '../../schema/schema.js';
|
||||
import type { Blocks } from '../blocks/blocks.js';
|
||||
import { BlockModel } from './block-model.js';
|
||||
import type { YBlock } from './types.js';
|
||||
import { internalPrimitives } from './zod.js';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@@ -1,5 +1,6 @@
|
||||
import type * as Y from 'yjs';
|
||||
|
||||
import type { BlockModel } from './block-model.js';
|
||||
import type { Block } from './index.js';
|
||||
|
||||
export type YBlock = Y.Map<unknown> & {
|
||||
@@ -11,3 +12,10 @@ export type YBlock = Y.Map<unknown> & {
|
||||
export type BlockOptions = {
|
||||
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 { signal } from '@preact/signals-core';
|
||||
|
||||
import type { BlockModel, Schema } from '../../schema/index.js';
|
||||
import type { DraftModel } from '../../transformer/index.js';
|
||||
import { syncBlockProps } from '../../utils/utils.js';
|
||||
import type { BlockProps, Doc } from '../workspace.js';
|
||||
import type { BlockOptions } from './block/index.js';
|
||||
import { Block } from './block/index.js';
|
||||
import type { Schema } from '../../schema/index.js';
|
||||
import {
|
||||
Block,
|
||||
type BlockModel,
|
||||
type BlockOptions,
|
||||
type BlockProps,
|
||||
type DraftModel,
|
||||
} from '../block/index.js';
|
||||
import type { Doc } from '../doc.js';
|
||||
import { DocCRUD } from './crud.js';
|
||||
import { type Query, runQuery } from './query.js';
|
||||
import { syncBlockProps } from './utils.js';
|
||||
|
||||
type DocOptions = {
|
||||
schema: Schema;
|
||||
@@ -2,12 +2,10 @@ import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
import { native2Y } from '../../reactive/index.js';
|
||||
import {
|
||||
type BlockModel,
|
||||
internalPrimitives,
|
||||
type Schema,
|
||||
} from '../../schema/index.js';
|
||||
import type { YBlock } from './index.js';
|
||||
import type { Schema } from '../../schema/index.js';
|
||||
import type { BlockModel } from '../block/block-model.js';
|
||||
import type { YBlock } from '../block/types.js';
|
||||
import { internalPrimitives } from '../block/zod.js';
|
||||
|
||||
export class DocCRUD {
|
||||
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 type { BlockModel } from '../../schema/index.js';
|
||||
import type { Block } from './block/index.js';
|
||||
import { BlockViewType } from './consts.js';
|
||||
import type { Block, BlockModel, BlockViewType } from '../block/index.js';
|
||||
|
||||
export type QueryMatch = {
|
||||
id?: string;
|
||||
@@ -27,7 +25,7 @@ export function runQuery(query: Query, block: Block) {
|
||||
const blockViewType = getBlockViewType(query, block);
|
||||
block.blockViewType = blockViewType;
|
||||
|
||||
if (blockViewType !== BlockViewType.Hidden) {
|
||||
if (blockViewType !== 'hidden') {
|
||||
const queryMode = query.mode;
|
||||
setAncestorsToDisplayIfHidden(queryMode, block);
|
||||
}
|
||||
@@ -46,8 +44,8 @@ function getBlockViewType(query: Query, block: Block): BlockViewType {
|
||||
},
|
||||
{} as Record<string, unknown>
|
||||
);
|
||||
let blockViewType =
|
||||
queryMode === 'loose' ? BlockViewType.Display : BlockViewType.Hidden;
|
||||
let blockViewType: BlockViewType =
|
||||
queryMode === 'loose' ? 'display' : 'hidden';
|
||||
|
||||
query.match.some(queryObject => {
|
||||
const {
|
||||
@@ -76,9 +74,8 @@ function setAncestorsToDisplayIfHidden(mode: QueryMode, block: Block) {
|
||||
let parent = doc.getParent(block.model);
|
||||
while (parent) {
|
||||
const parentBlock = doc.getBlock(parent.id);
|
||||
if (parentBlock && parentBlock.blockViewType === BlockViewType.Hidden) {
|
||||
parentBlock.blockViewType =
|
||||
mode === 'include' ? BlockViewType.Display : BlockViewType.Bypass;
|
||||
if (parentBlock && parentBlock.blockViewType === 'hidden') {
|
||||
parentBlock.blockViewType = mode === 'include' ? 'display' : 'bypass';
|
||||
}
|
||||
parent = doc.getParent(parent);
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { z } from 'zod';
|
||||
|
||||
import { SYS_KEYS } from '../consts.js';
|
||||
import { native2Y } from '../reactive/index.js';
|
||||
import type { BlockModel, BlockSchema } from '../schema/base.js';
|
||||
import { internalPrimitives } from '../schema/base.js';
|
||||
import type { YBlock } from '../store/doc/block/index.js';
|
||||
import type { BlockProps } from '../store/workspace.js';
|
||||
import { SYS_KEYS } from '../../consts.js';
|
||||
import { native2Y } from '../../reactive/index.js';
|
||||
import type { BlockModel } from '../block/block-model.js';
|
||||
import type { BlockProps, YBlock } from '../block/types.js';
|
||||
import type { BlockSchema } from '../block/zod.js';
|
||||
import { internalPrimitives } from '../block/zod.js';
|
||||
|
||||
export function syncBlockProps(
|
||||
schema: z.infer<typeof BlockSchema>,
|
||||
@@ -35,13 +35,3 @@ export function syncBlockProps(
|
||||
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';
|
||||
|
||||
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 {
|
||||
constructor(flavour: string, message: string) {
|
||||
super(
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
export * from './base.js';
|
||||
export { Schema } from './schema.js';
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { minimatch } from 'minimatch';
|
||||
|
||||
import { SCHEMA_NOT_FOUND_MESSAGE } from '../consts.js';
|
||||
import type { BlockSchemaType } from './base.js';
|
||||
import { BlockSchema } from './base.js';
|
||||
import { BlockSchema, type BlockSchemaType } from '../model/index.js';
|
||||
import { SchemaValidateError } from './error.js';
|
||||
|
||||
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-meta.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 * as Y from 'yjs';
|
||||
|
||||
import { Blocks } from '../store/doc/doc.js';
|
||||
import type { YBlock } from '../store/doc/index.js';
|
||||
import type { Query } from '../store/doc/query.js';
|
||||
import type { Doc, GetBlocksOptions, Workspace } from '../store/workspace.js';
|
||||
import type { YBlock } from '../model/block/types.js';
|
||||
import { Blocks } from '../model/blocks/blocks.js';
|
||||
import type { Query } from '../model/blocks/query.js';
|
||||
import type { Doc, GetBlocksOptions, Workspace } from '../model/index.js';
|
||||
import type { AwarenessStore } from '../yjs/index.js';
|
||||
|
||||
type DocOptions = {
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import { Slot } from '@blocksuite/global/utils';
|
||||
import type * as Y from 'yjs';
|
||||
|
||||
import { COLLECTION_VERSION, PAGE_VERSION } from '../consts.js';
|
||||
import { createYProxy } from '../reactive/proxy.js';
|
||||
import type {
|
||||
DocMeta,
|
||||
DocsPropertiesMeta,
|
||||
Workspace,
|
||||
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[];
|
||||
properties?: DocsPropertiesMeta;
|
||||
workspaceVersion?: number;
|
||||
|
||||
@@ -16,14 +16,14 @@ import merge from 'lodash.merge';
|
||||
import { Awareness } from 'y-protocols/awareness.js';
|
||||
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 Blocks,
|
||||
type CreateBlocksOptions,
|
||||
type GetBlocksOptions,
|
||||
type Workspace,
|
||||
type WorkspaceMeta,
|
||||
} from '../store/index.js';
|
||||
import { type IdGenerator, nanoid } from '../utils/id-generator.js';
|
||||
import { AwarenessStore, type RawAwarenessState } from '../yjs/index.js';
|
||||
import { TestDoc } from './test-doc.js';
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import type { BlockModel, InternalPrimitives } from '../schema/index.js';
|
||||
import { internalPrimitives } from '../schema/index.js';
|
||||
import type { BlockModel } from '../model/block/block-model.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 { DraftModel } from './draft.js';
|
||||
import { fromJSON, toJSON } from './json.js';
|
||||
import type { BlockSnapshot } from './type.js';
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
export * from './assets.js';
|
||||
export * from './base.js';
|
||||
export * from './draft.js';
|
||||
export * from './job.js';
|
||||
export * from './json.js';
|
||||
export * from './middleware.js';
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
|
||||
import { nextTick, Slot } from '@blocksuite/global/utils';
|
||||
|
||||
import type { BlockModel, BlockSchemaType, Schema } from '../schema/index.js';
|
||||
import type { Blocks } from '../store/index.js';
|
||||
import type {
|
||||
BlockModel,
|
||||
Blocks,
|
||||
BlockSchemaType,
|
||||
DraftModel,
|
||||
} from '../model/index.js';
|
||||
import type { Schema } from '../schema/index.js';
|
||||
import { AssetsManager } from './assets.js';
|
||||
import { BaseBlockTransformer } from './base.js';
|
||||
import type { DraftModel } from './draft.js';
|
||||
import type {
|
||||
BeforeExportPayload,
|
||||
BeforeImportPayload,
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
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 { DraftModel } from './draft.js';
|
||||
import type { Slice } from './slice.js';
|
||||
import type {
|
||||
BlockSnapshot,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { Blocks } from '../store/index.js';
|
||||
import type { DraftModel } from './draft.js';
|
||||
import type { Blocks, DraftModel } from '../model/index.js';
|
||||
|
||||
type SliceData = {
|
||||
content: DraftModel[];
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { Blocks } from '../store/doc/doc.js';
|
||||
import type { DocMeta, DocsPropertiesMeta } from '../store/workspace.js';
|
||||
import type { Blocks } from '../model/blocks/blocks.js';
|
||||
import type { DocMeta, DocsPropertiesMeta } from '../model/workspace-meta.js';
|
||||
|
||||
export type BlockSnapshot = {
|
||||
type: 'block';
|
||||
|
||||
@@ -3,11 +3,6 @@ import { nanoid as nanoidGenerator } from 'nanoid';
|
||||
|
||||
export type IdGenerator = () => string;
|
||||
|
||||
export function createAutoIncrementIdGenerator(): IdGenerator {
|
||||
let i = 0;
|
||||
return () => (i++).toString();
|
||||
}
|
||||
|
||||
export const uuidv4: IdGenerator = () => {
|
||||
return uuidv4IdGenerator();
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@ import clonedeep from 'lodash.clonedeep';
|
||||
import merge from 'lodash.merge';
|
||||
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 {
|
||||
name: string;
|
||||
@@ -14,25 +14,22 @@ export interface UserInfo {
|
||||
type UserSelection = Array<Record<string, unknown>>;
|
||||
|
||||
// Raw JSON state in awareness CRDT
|
||||
export type RawAwarenessState<Flags extends BlockSuiteFlags = BlockSuiteFlags> =
|
||||
{
|
||||
user?: UserInfo;
|
||||
color?: string;
|
||||
flags: Flags;
|
||||
// use v2 to avoid crush on old clients
|
||||
selectionV2: Record<string, UserSelection>;
|
||||
};
|
||||
export type RawAwarenessState = {
|
||||
user?: UserInfo;
|
||||
color?: string;
|
||||
flags: BlockSuiteFlags;
|
||||
// use v2 to avoid crush on old clients
|
||||
selectionV2: Record<string, UserSelection>;
|
||||
};
|
||||
|
||||
export interface AwarenessEvent<
|
||||
Flags extends BlockSuiteFlags = BlockSuiteFlags,
|
||||
> {
|
||||
export interface AwarenessEvent {
|
||||
id: number;
|
||||
type: 'add' | 'update' | 'remove';
|
||||
state?: RawAwarenessState<Flags>;
|
||||
state?: RawAwarenessState;
|
||||
}
|
||||
|
||||
export class AwarenessStore<Flags extends BlockSuiteFlags = BlockSuiteFlags> {
|
||||
private readonly _flags: Signal<Flags>;
|
||||
export class AwarenessStore {
|
||||
private readonly _flags: Signal<BlockSuiteFlags>;
|
||||
|
||||
private readonly _onAwarenessChange = (diff: {
|
||||
added: number[];
|
||||
@@ -66,24 +63,24 @@ export class AwarenessStore<Flags extends BlockSuiteFlags = BlockSuiteFlags> {
|
||||
});
|
||||
};
|
||||
|
||||
readonly awareness: YAwareness<RawAwarenessState<Flags>>;
|
||||
readonly awareness: YAwareness<RawAwarenessState>;
|
||||
|
||||
readonly slots = {
|
||||
update: new Slot<AwarenessEvent<Flags>>(),
|
||||
update: new Slot<AwarenessEvent>(),
|
||||
};
|
||||
|
||||
constructor(
|
||||
awareness: YAwareness<RawAwarenessState<Flags>>,
|
||||
defaultFlags: Flags
|
||||
awareness: YAwareness<RawAwarenessState>,
|
||||
defaultFlags: BlockSuiteFlags
|
||||
) {
|
||||
this._flags = signal<Flags>(defaultFlags);
|
||||
this._flags = signal(defaultFlags);
|
||||
this.awareness = awareness;
|
||||
this.awareness.on('change', this._onAwarenessChange);
|
||||
this.awareness.setLocalStateField('selectionV2', {});
|
||||
this._initFlags(defaultFlags);
|
||||
}
|
||||
|
||||
private _initFlags(defaultFlags: Flags) {
|
||||
private _initFlags(defaultFlags: BlockSuiteFlags) {
|
||||
const upstreamFlags = this.awareness.getLocalState()?.flags;
|
||||
const flags = clonedeep(defaultFlags);
|
||||
if (upstreamFlags) {
|
||||
@@ -98,7 +95,7 @@ export class AwarenessStore<Flags extends BlockSuiteFlags = BlockSuiteFlags> {
|
||||
this.awareness.destroy();
|
||||
}
|
||||
|
||||
getFlag<Key extends keyof Flags>(field: Key) {
|
||||
getFlag<Key extends keyof BlockSuiteFlags>(field: Key) {
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -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 ?? {};
|
||||
this.awareness.setLocalStateField('flags', { ...oldFlags, [field]: value });
|
||||
}
|
||||
@@ -142,6 +142,6 @@ export class AwarenessStore<Flags extends BlockSuiteFlags = BlockSuiteFlags> {
|
||||
this.setFlag('readonly', {
|
||||
...flags,
|
||||
[blockCollection.id]: value,
|
||||
} as Flags['readonly']);
|
||||
} as BlockSuiteFlags['readonly']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,3 +5,7 @@ export type SubdocEvent = {
|
||||
removed: Set<YDoc>;
|
||||
added: Set<YDoc>;
|
||||
};
|
||||
|
||||
export interface StackItem {
|
||||
meta: Map<'selection-state', unknown>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user