chore: merge blocksuite source code (#9213)

This commit is contained in:
Mirone
2024-12-20 15:38:06 +08:00
committed by GitHub
parent 2c9ef916f4
commit 30200ff86d
2031 changed files with 238888 additions and 229 deletions

View File

@@ -0,0 +1,147 @@
import type { BlockSuiteFlags } from '@blocksuite/global/types';
import { Slot } from '@blocksuite/global/utils';
import { type Signal, signal } from '@preact/signals-core';
import clonedeep from 'lodash.clonedeep';
import merge from 'lodash.merge';
import type { Awareness as YAwareness } from 'y-protocols/awareness.js';
import type { BlockCollection } from '../store/index.js';
export interface UserInfo {
name: string;
}
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 interface AwarenessEvent<
Flags extends BlockSuiteFlags = BlockSuiteFlags,
> {
id: number;
type: 'add' | 'update' | 'remove';
state?: RawAwarenessState<Flags>;
}
export class AwarenessStore<Flags extends BlockSuiteFlags = BlockSuiteFlags> {
private _flags: Signal<Flags>;
private _onAwarenessChange = (diff: {
added: number[];
removed: number[];
updated: number[];
}) => {
this._flags.value = this.awareness.getLocalState()?.flags ?? {};
const { added, removed, updated } = diff;
const states = this.awareness.getStates();
added.forEach(id => {
this.slots.update.emit({
id,
type: 'add',
state: states.get(id),
});
});
updated.forEach(id => {
this.slots.update.emit({
id,
type: 'update',
state: states.get(id),
});
});
removed.forEach(id => {
this.slots.update.emit({
id,
type: 'remove',
});
});
};
readonly awareness: YAwareness<RawAwarenessState<Flags>>;
readonly slots = {
update: new Slot<AwarenessEvent<Flags>>(),
};
constructor(
awareness: YAwareness<RawAwarenessState<Flags>>,
defaultFlags: Flags
) {
this._flags = signal<Flags>(defaultFlags);
this.awareness = awareness;
this.awareness.on('change', this._onAwarenessChange);
this.awareness.setLocalStateField('selectionV2', {});
this._initFlags(defaultFlags);
}
private _initFlags(defaultFlags: Flags) {
const upstreamFlags = this.awareness.getLocalState()?.flags;
const flags = clonedeep(defaultFlags);
if (upstreamFlags) {
merge(flags, upstreamFlags);
}
this.awareness.setLocalStateField('flags', flags);
}
destroy() {
this.awareness.off('change', this._onAwarenessChange);
this.slots.update.dispose();
this.awareness.destroy();
}
getFlag<Key extends keyof Flags>(field: Key) {
return this._flags.value[field];
}
getLocalSelection(
selectionManagerId: string
): ReadonlyArray<Record<string, unknown>> {
return (
(this.awareness.getLocalState()?.selectionV2 ?? {})[selectionManagerId] ??
[]
);
}
getStates(): Map<number, RawAwarenessState<Flags>> {
return this.awareness.getStates();
}
isReadonly(blockCollection: BlockCollection): boolean {
const rd = this.getFlag('readonly');
if (rd && typeof rd === 'object') {
return Boolean((rd as Record<string, boolean>)[blockCollection.id]);
} else {
return false;
}
}
setFlag<Key extends keyof Flags>(field: Key, value: Flags[Key]) {
const oldFlags = this.awareness.getLocalState()?.flags ?? {};
this.awareness.setLocalStateField('flags', { ...oldFlags, [field]: value });
}
setLocalSelection(selectionManagerId: string, selection: UserSelection) {
const oldSelection = this.awareness.getLocalState()?.selectionV2 ?? {};
this.awareness.setLocalStateField('selectionV2', {
...oldSelection,
[selectionManagerId]: selection,
});
}
setReadonly(blockCollection: BlockCollection, value: boolean): void {
const flags = this.getFlag('readonly') ?? {};
this.setFlag('readonly', {
...flags,
[blockCollection.id]: value,
} as Flags['readonly']);
}
}

View File

@@ -0,0 +1,58 @@
import type { Transaction } from 'yjs';
import * as Y from 'yjs';
import { createYProxy } from '../reactive/proxy.js';
export type BlockSuiteDocAllowedValue =
| Record<string, unknown>
| unknown[]
| Y.Text;
export type BlockSuiteDocData = Record<string, BlockSuiteDocAllowedValue>;
export class BlockSuiteDoc extends Y.Doc {
private _spaces: Y.Map<Y.Doc> = this.getMap('spaces');
get spaces() {
return this._spaces;
}
getArrayProxy<
Key extends keyof BlockSuiteDocData & string,
Value extends unknown[] = BlockSuiteDocData[Key] extends unknown[]
? BlockSuiteDocData[Key]
: never,
>(key: Key): Value {
const array = super.getArray(key);
return createYProxy(array) as Value;
}
getMapProxy<
Key extends keyof BlockSuiteDocData & string,
Value extends Record<
string,
unknown
> = BlockSuiteDocData[Key] extends Record<string, unknown>
? BlockSuiteDocData[Key]
: never,
>(key: Key): Value {
const map = super.getMap(key);
return createYProxy(map);
}
override toJSON(): Record<string, any> {
const json = super.toJSON();
delete json.spaces;
const spaces: Record<string, unknown> = {};
this.spaces.forEach((doc, key) => {
spaces[key] = doc.toJSON();
});
return {
...json,
spaces,
};
}
override transact<T>(f: (arg0: Transaction) => T, origin?: number | string) {
return super.transact(f, origin);
}
}

View File

@@ -0,0 +1,3 @@
export * from './awareness.js';
export * from './doc.js';
export * from './utils.js';

View File

@@ -0,0 +1,7 @@
import type { Doc as YDoc } from 'yjs';
export type SubdocEvent = {
loaded: Set<YDoc>;
removed: Set<YDoc>;
added: Set<YDoc>;
};