mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-15 05:37:32 +00:00
refactor(editor): remove readonly in awareness (#9597)
This commit is contained in:
@@ -259,7 +259,7 @@ test('local readonly', () => {
|
||||
|
||||
expect(doc1.readonly).toBeTruthy();
|
||||
expect(doc2?.readonly).toBeTruthy();
|
||||
expect(doc3?.readonly).toBeTruthy();
|
||||
expect(doc3?.readonly).toBeFalsy();
|
||||
|
||||
doc1.readonly = false;
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// oxlint-disable-next-line @typescript-eslint/triple-slash-reference
|
||||
/// <reference path="../shim.d.ts" />
|
||||
|
||||
export * from './adapter';
|
||||
export * from './extension';
|
||||
export * from './model';
|
||||
|
||||
@@ -54,7 +54,6 @@ export interface Doc {
|
||||
clearQuery(query: Query, readonly?: boolean): void;
|
||||
|
||||
get loaded(): boolean;
|
||||
get readonly(): boolean;
|
||||
get awarenessStore(): AwarenessStore;
|
||||
|
||||
get workspace(): Workspace;
|
||||
|
||||
@@ -47,7 +47,7 @@ export class Store {
|
||||
mode: 'loose',
|
||||
};
|
||||
|
||||
protected _readonly?: boolean;
|
||||
protected _readonly = signal(false);
|
||||
|
||||
protected readonly _schema: Schema;
|
||||
|
||||
@@ -175,10 +175,16 @@ export class Store {
|
||||
}
|
||||
|
||||
get canRedo() {
|
||||
if (this.readonly) {
|
||||
return false;
|
||||
}
|
||||
return this._doc.canRedo;
|
||||
}
|
||||
|
||||
get canUndo() {
|
||||
if (this.readonly) {
|
||||
return false;
|
||||
}
|
||||
return this._doc.canUndo;
|
||||
}
|
||||
|
||||
@@ -214,18 +220,16 @@ export class Store {
|
||||
return this._doc.meta;
|
||||
}
|
||||
|
||||
get readonly$() {
|
||||
return this._readonly;
|
||||
}
|
||||
|
||||
get readonly() {
|
||||
if (this._doc.readonly) {
|
||||
return true;
|
||||
}
|
||||
return this._readonly === true;
|
||||
return this._readonly.value === true;
|
||||
}
|
||||
|
||||
set readonly(value: boolean) {
|
||||
this._doc.awarenessStore.setReadonly(this._doc, value);
|
||||
if (this._readonly !== undefined && this._readonly !== value) {
|
||||
this._readonly = value;
|
||||
}
|
||||
this._readonly.value = value;
|
||||
}
|
||||
|
||||
get ready() {
|
||||
@@ -233,6 +237,11 @@ export class Store {
|
||||
}
|
||||
|
||||
get redo() {
|
||||
if (this.readonly) {
|
||||
return () => {
|
||||
console.error('cannot undo in readonly mode');
|
||||
};
|
||||
}
|
||||
return this._doc.redo.bind(this._doc);
|
||||
}
|
||||
|
||||
@@ -263,6 +272,11 @@ export class Store {
|
||||
}
|
||||
|
||||
get undo() {
|
||||
if (this.readonly) {
|
||||
return () => {
|
||||
console.error('cannot undo in readonly mode');
|
||||
};
|
||||
}
|
||||
return this._doc.undo.bind(this._doc);
|
||||
}
|
||||
|
||||
@@ -300,7 +314,9 @@ export class Store {
|
||||
|
||||
this._crud = new DocCRUD(this._yBlocks, doc.schema);
|
||||
this._schema = schema;
|
||||
this._readonly = readonly;
|
||||
if (readonly !== undefined) {
|
||||
this._readonly.value = readonly;
|
||||
}
|
||||
if (query) {
|
||||
this._query = query;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type Disposable, Slot } from '@blocksuite/global/utils';
|
||||
import { Slot } from '@blocksuite/global/utils';
|
||||
import { signal } from '@preact/signals-core';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
@@ -17,8 +17,6 @@ type DocOptions = {
|
||||
};
|
||||
|
||||
export class TestDoc implements Doc {
|
||||
private _awarenessUpdateDisposable: Disposable | null = null;
|
||||
|
||||
private readonly _canRedo$ = signal(false);
|
||||
|
||||
private readonly _canUndo$ = signal(false);
|
||||
@@ -86,8 +84,8 @@ export class TestDoc implements Doc {
|
||||
private _shouldTransact = true;
|
||||
|
||||
private readonly _updateCanUndoRedoSignals = () => {
|
||||
const canRedo = this.readonly ? false : this._history.canRedo();
|
||||
const canUndo = this.readonly ? false : this._history.canUndo();
|
||||
const canRedo = this._history.canRedo();
|
||||
const canUndo = this._history.canUndo();
|
||||
if (this._canRedo$.peek() !== canRedo) {
|
||||
this._canRedo$.value = canRedo;
|
||||
}
|
||||
@@ -164,10 +162,6 @@ export class TestDoc implements Doc {
|
||||
return this.workspace.meta.getDocMeta(this.id);
|
||||
}
|
||||
|
||||
get readonly(): boolean {
|
||||
return this.awarenessStore.isReadonly(this);
|
||||
}
|
||||
|
||||
get ready() {
|
||||
return this._ready;
|
||||
}
|
||||
@@ -272,7 +266,6 @@ export class TestDoc implements Doc {
|
||||
|
||||
dispose() {
|
||||
this.slots.historyUpdated.dispose();
|
||||
this._awarenessUpdateDisposable?.dispose();
|
||||
|
||||
if (this.ready) {
|
||||
this._yBlocks.unobserveDeep(this._handleYEvents);
|
||||
@@ -322,13 +315,6 @@ export class TestDoc implements Doc {
|
||||
this._handleYBlockAdd(id);
|
||||
});
|
||||
|
||||
this._awarenessUpdateDisposable = this.awarenessStore.slots.update.on(
|
||||
() => {
|
||||
// change readonly state will affect the undo/redo state
|
||||
this._updateCanUndoRedoSignals();
|
||||
}
|
||||
);
|
||||
|
||||
initFn?.();
|
||||
|
||||
this._ready = true;
|
||||
@@ -337,18 +323,10 @@ export class TestDoc implements Doc {
|
||||
}
|
||||
|
||||
redo() {
|
||||
if (this.readonly) {
|
||||
console.error('cannot modify data in readonly mode');
|
||||
return;
|
||||
}
|
||||
this._history.redo();
|
||||
}
|
||||
|
||||
undo() {
|
||||
if (this.readonly) {
|
||||
console.error('cannot modify data in readonly mode');
|
||||
return;
|
||||
}
|
||||
this._history.undo();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
|
||||
import type { BlockSuiteFlags } from '@blocksuite/global/types';
|
||||
import { NoopLogger, Slot } from '@blocksuite/global/utils';
|
||||
import {
|
||||
AwarenessEngine,
|
||||
@@ -11,8 +10,6 @@ import {
|
||||
MemoryBlobSource,
|
||||
NoopDocSource,
|
||||
} from '@blocksuite/sync';
|
||||
import clonedeep from 'lodash.clonedeep';
|
||||
import merge from 'lodash.merge';
|
||||
import { Awareness } from 'y-protocols/awareness.js';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
@@ -26,7 +23,7 @@ import type {
|
||||
} from '../model/index.js';
|
||||
import type { Schema } from '../schema/index.js';
|
||||
import { type IdGenerator, nanoid } from '../utils/id-generator.js';
|
||||
import { AwarenessStore, type RawAwarenessState } from '../yjs/index.js';
|
||||
import { AwarenessStore } from '../yjs/index.js';
|
||||
import { TestDoc } from './test-doc.js';
|
||||
import { TestMeta } from './test-meta.js';
|
||||
|
||||
@@ -34,7 +31,6 @@ export type DocCollectionOptions = {
|
||||
schema: Schema;
|
||||
id?: string;
|
||||
idGenerator?: IdGenerator;
|
||||
defaultFlags?: Partial<BlockSuiteFlags>;
|
||||
docSources?: {
|
||||
main: DocSource;
|
||||
shadows?: DocSource[];
|
||||
@@ -46,10 +42,6 @@ export type DocCollectionOptions = {
|
||||
awarenessSources?: AwarenessSource[];
|
||||
};
|
||||
|
||||
const FLAGS_PRESET = {
|
||||
readonly: {},
|
||||
} satisfies BlockSuiteFlags;
|
||||
|
||||
/**
|
||||
* Test only
|
||||
* Do not use this in production
|
||||
@@ -95,7 +87,6 @@ export class TestWorkspace implements Workspace {
|
||||
id,
|
||||
schema,
|
||||
idGenerator,
|
||||
defaultFlags,
|
||||
awarenessSources = [],
|
||||
docSources = {
|
||||
main: new NoopDocSource(),
|
||||
@@ -108,10 +99,7 @@ export class TestWorkspace implements Workspace {
|
||||
|
||||
this.id = id || '';
|
||||
this.doc = new Y.Doc({ guid: id });
|
||||
this.awarenessStore = new AwarenessStore(
|
||||
new Awareness<RawAwarenessState>(this.doc),
|
||||
merge(clonedeep(FLAGS_PRESET), defaultFlags)
|
||||
);
|
||||
this.awarenessStore = new AwarenessStore(new Awareness(this.doc));
|
||||
|
||||
const logger = new NoopLogger();
|
||||
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
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 { Doc } from '../model/doc.js';
|
||||
import type { Awareness } from 'y-protocols/awareness.js';
|
||||
|
||||
export interface UserInfo {
|
||||
name: string;
|
||||
@@ -17,7 +11,6 @@ type UserSelection = Array<Record<string, unknown>>;
|
||||
export type RawAwarenessState = {
|
||||
user?: UserInfo;
|
||||
color?: string;
|
||||
flags: BlockSuiteFlags;
|
||||
// use v2 to avoid crush on old clients
|
||||
selectionV2: Record<string, UserSelection>;
|
||||
};
|
||||
@@ -29,18 +22,14 @@ export interface AwarenessEvent {
|
||||
}
|
||||
|
||||
export class AwarenessStore {
|
||||
private readonly _flags: Signal<BlockSuiteFlags>;
|
||||
|
||||
private readonly _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();
|
||||
const states = this.getStates();
|
||||
added.forEach(id => {
|
||||
this.slots.update.emit({
|
||||
id,
|
||||
@@ -63,30 +52,16 @@ export class AwarenessStore {
|
||||
});
|
||||
};
|
||||
|
||||
readonly awareness: YAwareness<RawAwarenessState>;
|
||||
readonly awareness: Awareness;
|
||||
|
||||
readonly slots = {
|
||||
update: new Slot<AwarenessEvent>(),
|
||||
};
|
||||
|
||||
constructor(
|
||||
awareness: YAwareness<RawAwarenessState>,
|
||||
defaultFlags: BlockSuiteFlags
|
||||
) {
|
||||
this._flags = signal(defaultFlags);
|
||||
constructor(awareness: Awareness) {
|
||||
this.awareness = awareness;
|
||||
this.awareness.on('change', this._onAwarenessChange);
|
||||
this.awareness.setLocalStateField('selectionV2', {});
|
||||
this._initFlags(defaultFlags);
|
||||
}
|
||||
|
||||
private _initFlags(defaultFlags: BlockSuiteFlags) {
|
||||
const upstreamFlags = this.awareness.getLocalState()?.flags;
|
||||
const flags = clonedeep(defaultFlags);
|
||||
if (upstreamFlags) {
|
||||
merge(flags, upstreamFlags);
|
||||
}
|
||||
this.awareness.setLocalStateField('flags', flags);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
@@ -95,10 +70,6 @@ export class AwarenessStore {
|
||||
this.awareness.destroy();
|
||||
}
|
||||
|
||||
getFlag<Key extends keyof BlockSuiteFlags>(field: Key) {
|
||||
return this._flags.value[field];
|
||||
}
|
||||
|
||||
getLocalSelection(
|
||||
selectionManagerId: string
|
||||
): ReadonlyArray<Record<string, unknown>> {
|
||||
@@ -109,24 +80,22 @@ export class AwarenessStore {
|
||||
}
|
||||
|
||||
getStates(): Map<number, RawAwarenessState> {
|
||||
return this.awareness.getStates();
|
||||
return this.awareness.getStates() as Map<number, RawAwarenessState>;
|
||||
}
|
||||
|
||||
isReadonly(blockCollection: Doc): boolean {
|
||||
const rd = this.getFlag('readonly');
|
||||
if (rd && typeof rd === 'object') {
|
||||
return Boolean((rd as Record<string, boolean>)[blockCollection.id]);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
getLocalState(): RawAwarenessState {
|
||||
return this.awareness.getLocalState() as RawAwarenessState;
|
||||
}
|
||||
|
||||
setFlag<Key extends keyof BlockSuiteFlags>(
|
||||
field: Key,
|
||||
value: BlockSuiteFlags[Key]
|
||||
) {
|
||||
const oldFlags = this.awareness.getLocalState()?.flags ?? {};
|
||||
this.awareness.setLocalStateField('flags', { ...oldFlags, [field]: value });
|
||||
setLocalState(state: RawAwarenessState): void {
|
||||
this.awareness.setLocalState(state);
|
||||
}
|
||||
|
||||
setLocalStateField<Field extends keyof RawAwarenessState>(
|
||||
field: Field,
|
||||
value: RawAwarenessState[Field]
|
||||
): void {
|
||||
this.awareness.setLocalStateField(field, value);
|
||||
}
|
||||
|
||||
setLocalSelection(selectionManagerId: string, selection: UserSelection) {
|
||||
@@ -136,12 +105,4 @@ export class AwarenessStore {
|
||||
[selectionManagerId]: selection,
|
||||
});
|
||||
}
|
||||
|
||||
setReadonly(blockCollection: Doc, value: boolean): void {
|
||||
const flags = this.getFlag('readonly') ?? {};
|
||||
this.setFlag('readonly', {
|
||||
...flags,
|
||||
[blockCollection.id]: value,
|
||||
} as BlockSuiteFlags['readonly']);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user