refactor(editor): remove readonly in awareness (#9597)
This commit is contained in:
@@ -1,8 +1,11 @@
|
|||||||
import { IS_IOS } from '@blocksuite/global/env';
|
import { IS_IOS } from '@blocksuite/global/env';
|
||||||
|
import type * as GlobalTypes from '@blocksuite/global/types';
|
||||||
import { DisposableGroup } from '@blocksuite/global/utils';
|
import { DisposableGroup } from '@blocksuite/global/utils';
|
||||||
import { signal } from '@preact/signals-core';
|
import { signal } from '@preact/signals-core';
|
||||||
import type { ReactiveController, ReactiveControllerHost } from 'lit';
|
import type { ReactiveController, ReactiveControllerHost } from 'lit';
|
||||||
|
|
||||||
|
declare type _GLOBAL_ = typeof GlobalTypes;
|
||||||
|
|
||||||
function notSupportedWarning() {
|
function notSupportedWarning() {
|
||||||
console.warn('VirtualKeyboard API and VisualViewport API are not supported');
|
console.warn('VirtualKeyboard API and VisualViewport API are not supported');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ export class EdgelessRootService extends RootService implements SurfaceContext {
|
|||||||
|
|
||||||
let readonly = doc.readonly;
|
let readonly = doc.readonly;
|
||||||
this.disposables.add(
|
this.disposables.add(
|
||||||
doc.awarenessStore.slots.update.on(() => {
|
effect(() => {
|
||||||
if (readonly !== doc.readonly) {
|
if (readonly !== doc.readonly) {
|
||||||
readonly = doc.readonly;
|
readonly = doc.readonly;
|
||||||
slots.readonlyUpdated.emit(readonly);
|
slots.readonlyUpdated.emit(readonly);
|
||||||
|
|||||||
@@ -1,4 +1 @@
|
|||||||
export interface BlockSuiteFlags {
|
|
||||||
readonly: Record<string, boolean>;
|
|
||||||
}
|
|
||||||
export * from './virtual-keyboard.js';
|
export * from './virtual-keyboard.js';
|
||||||
|
|||||||
20
blocksuite/framework/store/shim.d.ts
vendored
20
blocksuite/framework/store/shim.d.ts
vendored
@@ -1,20 +0,0 @@
|
|||||||
declare module 'y-protocols/awareness.js' {
|
|
||||||
import { Awareness as _Awareness } from 'y-protocols/awareness';
|
|
||||||
type UnknownRecord = Record<string, unknown>;
|
|
||||||
export class Awareness<
|
|
||||||
State extends UnknownRecord = UnknownRecord,
|
|
||||||
> extends _Awareness {
|
|
||||||
constructor<State extends UnknownRecord = UnknownRecord>(
|
|
||||||
doc: Y.Doc
|
|
||||||
): Awareness<State>;
|
|
||||||
|
|
||||||
getLocalState(): State;
|
|
||||||
getStates(): Map<number, State>;
|
|
||||||
setLocalState(state: State): void;
|
|
||||||
setLocalStateField<Field extends keyof State>(
|
|
||||||
field: Field,
|
|
||||||
value: State[Field]
|
|
||||||
): void;
|
|
||||||
}
|
|
||||||
export { applyAwarenessUpdate, encodeAwarenessUpdate, modifyAwarenessUpdate, removeAwarenessStates } from 'y-protocols/awareness'
|
|
||||||
}
|
|
||||||
@@ -259,7 +259,7 @@ test('local readonly', () => {
|
|||||||
|
|
||||||
expect(doc1.readonly).toBeTruthy();
|
expect(doc1.readonly).toBeTruthy();
|
||||||
expect(doc2?.readonly).toBeTruthy();
|
expect(doc2?.readonly).toBeTruthy();
|
||||||
expect(doc3?.readonly).toBeTruthy();
|
expect(doc3?.readonly).toBeFalsy();
|
||||||
|
|
||||||
doc1.readonly = false;
|
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 './adapter';
|
||||||
export * from './extension';
|
export * from './extension';
|
||||||
export * from './model';
|
export * from './model';
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ export interface Doc {
|
|||||||
clearQuery(query: Query, readonly?: boolean): void;
|
clearQuery(query: Query, readonly?: boolean): void;
|
||||||
|
|
||||||
get loaded(): boolean;
|
get loaded(): boolean;
|
||||||
get readonly(): boolean;
|
|
||||||
get awarenessStore(): AwarenessStore;
|
get awarenessStore(): AwarenessStore;
|
||||||
|
|
||||||
get workspace(): Workspace;
|
get workspace(): Workspace;
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export class Store {
|
|||||||
mode: 'loose',
|
mode: 'loose',
|
||||||
};
|
};
|
||||||
|
|
||||||
protected _readonly?: boolean;
|
protected _readonly = signal(false);
|
||||||
|
|
||||||
protected readonly _schema: Schema;
|
protected readonly _schema: Schema;
|
||||||
|
|
||||||
@@ -175,10 +175,16 @@ export class Store {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get canRedo() {
|
get canRedo() {
|
||||||
|
if (this.readonly) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return this._doc.canRedo;
|
return this._doc.canRedo;
|
||||||
}
|
}
|
||||||
|
|
||||||
get canUndo() {
|
get canUndo() {
|
||||||
|
if (this.readonly) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return this._doc.canUndo;
|
return this._doc.canUndo;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,18 +220,16 @@ export class Store {
|
|||||||
return this._doc.meta;
|
return this._doc.meta;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get readonly$() {
|
||||||
|
return this._readonly;
|
||||||
|
}
|
||||||
|
|
||||||
get readonly() {
|
get readonly() {
|
||||||
if (this._doc.readonly) {
|
return this._readonly.value === true;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return this._readonly === true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set readonly(value: boolean) {
|
set readonly(value: boolean) {
|
||||||
this._doc.awarenessStore.setReadonly(this._doc, value);
|
this._readonly.value = value;
|
||||||
if (this._readonly !== undefined && this._readonly !== value) {
|
|
||||||
this._readonly = value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get ready() {
|
get ready() {
|
||||||
@@ -233,6 +237,11 @@ export class Store {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get redo() {
|
get redo() {
|
||||||
|
if (this.readonly) {
|
||||||
|
return () => {
|
||||||
|
console.error('cannot undo in readonly mode');
|
||||||
|
};
|
||||||
|
}
|
||||||
return this._doc.redo.bind(this._doc);
|
return this._doc.redo.bind(this._doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,6 +272,11 @@ export class Store {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get undo() {
|
get undo() {
|
||||||
|
if (this.readonly) {
|
||||||
|
return () => {
|
||||||
|
console.error('cannot undo in readonly mode');
|
||||||
|
};
|
||||||
|
}
|
||||||
return this._doc.undo.bind(this._doc);
|
return this._doc.undo.bind(this._doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,7 +314,9 @@ export class Store {
|
|||||||
|
|
||||||
this._crud = new DocCRUD(this._yBlocks, doc.schema);
|
this._crud = new DocCRUD(this._yBlocks, doc.schema);
|
||||||
this._schema = schema;
|
this._schema = schema;
|
||||||
this._readonly = readonly;
|
if (readonly !== undefined) {
|
||||||
|
this._readonly.value = readonly;
|
||||||
|
}
|
||||||
if (query) {
|
if (query) {
|
||||||
this._query = 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 { signal } from '@preact/signals-core';
|
||||||
import * as Y from 'yjs';
|
import * as Y from 'yjs';
|
||||||
|
|
||||||
@@ -17,8 +17,6 @@ type DocOptions = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export class TestDoc implements Doc {
|
export class TestDoc implements Doc {
|
||||||
private _awarenessUpdateDisposable: Disposable | null = null;
|
|
||||||
|
|
||||||
private readonly _canRedo$ = signal(false);
|
private readonly _canRedo$ = signal(false);
|
||||||
|
|
||||||
private readonly _canUndo$ = signal(false);
|
private readonly _canUndo$ = signal(false);
|
||||||
@@ -86,8 +84,8 @@ export class TestDoc implements Doc {
|
|||||||
private _shouldTransact = true;
|
private _shouldTransact = true;
|
||||||
|
|
||||||
private readonly _updateCanUndoRedoSignals = () => {
|
private readonly _updateCanUndoRedoSignals = () => {
|
||||||
const canRedo = this.readonly ? false : this._history.canRedo();
|
const canRedo = this._history.canRedo();
|
||||||
const canUndo = this.readonly ? false : this._history.canUndo();
|
const canUndo = this._history.canUndo();
|
||||||
if (this._canRedo$.peek() !== canRedo) {
|
if (this._canRedo$.peek() !== canRedo) {
|
||||||
this._canRedo$.value = canRedo;
|
this._canRedo$.value = canRedo;
|
||||||
}
|
}
|
||||||
@@ -164,10 +162,6 @@ export class TestDoc implements Doc {
|
|||||||
return this.workspace.meta.getDocMeta(this.id);
|
return this.workspace.meta.getDocMeta(this.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
get readonly(): boolean {
|
|
||||||
return this.awarenessStore.isReadonly(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
get ready() {
|
get ready() {
|
||||||
return this._ready;
|
return this._ready;
|
||||||
}
|
}
|
||||||
@@ -272,7 +266,6 @@ export class TestDoc implements Doc {
|
|||||||
|
|
||||||
dispose() {
|
dispose() {
|
||||||
this.slots.historyUpdated.dispose();
|
this.slots.historyUpdated.dispose();
|
||||||
this._awarenessUpdateDisposable?.dispose();
|
|
||||||
|
|
||||||
if (this.ready) {
|
if (this.ready) {
|
||||||
this._yBlocks.unobserveDeep(this._handleYEvents);
|
this._yBlocks.unobserveDeep(this._handleYEvents);
|
||||||
@@ -322,13 +315,6 @@ export class TestDoc implements Doc {
|
|||||||
this._handleYBlockAdd(id);
|
this._handleYBlockAdd(id);
|
||||||
});
|
});
|
||||||
|
|
||||||
this._awarenessUpdateDisposable = this.awarenessStore.slots.update.on(
|
|
||||||
() => {
|
|
||||||
// change readonly state will affect the undo/redo state
|
|
||||||
this._updateCanUndoRedoSignals();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
initFn?.();
|
initFn?.();
|
||||||
|
|
||||||
this._ready = true;
|
this._ready = true;
|
||||||
@@ -337,18 +323,10 @@ export class TestDoc implements Doc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
redo() {
|
redo() {
|
||||||
if (this.readonly) {
|
|
||||||
console.error('cannot modify data in readonly mode');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._history.redo();
|
this._history.redo();
|
||||||
}
|
}
|
||||||
|
|
||||||
undo() {
|
undo() {
|
||||||
if (this.readonly) {
|
|
||||||
console.error('cannot modify data in readonly mode');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._history.undo();
|
this._history.undo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
|
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
|
||||||
import type { BlockSuiteFlags } from '@blocksuite/global/types';
|
|
||||||
import { NoopLogger, Slot } from '@blocksuite/global/utils';
|
import { NoopLogger, Slot } from '@blocksuite/global/utils';
|
||||||
import {
|
import {
|
||||||
AwarenessEngine,
|
AwarenessEngine,
|
||||||
@@ -11,8 +10,6 @@ import {
|
|||||||
MemoryBlobSource,
|
MemoryBlobSource,
|
||||||
NoopDocSource,
|
NoopDocSource,
|
||||||
} from '@blocksuite/sync';
|
} from '@blocksuite/sync';
|
||||||
import clonedeep from 'lodash.clonedeep';
|
|
||||||
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';
|
||||||
|
|
||||||
@@ -26,7 +23,7 @@ import type {
|
|||||||
} from '../model/index.js';
|
} from '../model/index.js';
|
||||||
import type { Schema } from '../schema/index.js';
|
import type { Schema } from '../schema/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 } from '../yjs/index.js';
|
||||||
import { TestDoc } from './test-doc.js';
|
import { TestDoc } from './test-doc.js';
|
||||||
import { TestMeta } from './test-meta.js';
|
import { TestMeta } from './test-meta.js';
|
||||||
|
|
||||||
@@ -34,7 +31,6 @@ export type DocCollectionOptions = {
|
|||||||
schema: Schema;
|
schema: Schema;
|
||||||
id?: string;
|
id?: string;
|
||||||
idGenerator?: IdGenerator;
|
idGenerator?: IdGenerator;
|
||||||
defaultFlags?: Partial<BlockSuiteFlags>;
|
|
||||||
docSources?: {
|
docSources?: {
|
||||||
main: DocSource;
|
main: DocSource;
|
||||||
shadows?: DocSource[];
|
shadows?: DocSource[];
|
||||||
@@ -46,10 +42,6 @@ export type DocCollectionOptions = {
|
|||||||
awarenessSources?: AwarenessSource[];
|
awarenessSources?: AwarenessSource[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const FLAGS_PRESET = {
|
|
||||||
readonly: {},
|
|
||||||
} satisfies BlockSuiteFlags;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test only
|
* Test only
|
||||||
* Do not use this in production
|
* Do not use this in production
|
||||||
@@ -95,7 +87,6 @@ export class TestWorkspace implements Workspace {
|
|||||||
id,
|
id,
|
||||||
schema,
|
schema,
|
||||||
idGenerator,
|
idGenerator,
|
||||||
defaultFlags,
|
|
||||||
awarenessSources = [],
|
awarenessSources = [],
|
||||||
docSources = {
|
docSources = {
|
||||||
main: new NoopDocSource(),
|
main: new NoopDocSource(),
|
||||||
@@ -108,10 +99,7 @@ export class TestWorkspace implements Workspace {
|
|||||||
|
|
||||||
this.id = id || '';
|
this.id = id || '';
|
||||||
this.doc = new Y.Doc({ guid: id });
|
this.doc = new Y.Doc({ guid: id });
|
||||||
this.awarenessStore = new AwarenessStore(
|
this.awarenessStore = new AwarenessStore(new Awareness(this.doc));
|
||||||
new Awareness<RawAwarenessState>(this.doc),
|
|
||||||
merge(clonedeep(FLAGS_PRESET), defaultFlags)
|
|
||||||
);
|
|
||||||
|
|
||||||
const logger = new NoopLogger();
|
const logger = new NoopLogger();
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
import type { BlockSuiteFlags } from '@blocksuite/global/types';
|
|
||||||
import { Slot } from '@blocksuite/global/utils';
|
import { Slot } from '@blocksuite/global/utils';
|
||||||
import { type Signal, signal } from '@preact/signals-core';
|
import type { Awareness } from 'y-protocols/awareness.js';
|
||||||
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';
|
|
||||||
|
|
||||||
export interface UserInfo {
|
export interface UserInfo {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -17,7 +11,6 @@ type UserSelection = Array<Record<string, unknown>>;
|
|||||||
export type RawAwarenessState = {
|
export type RawAwarenessState = {
|
||||||
user?: UserInfo;
|
user?: UserInfo;
|
||||||
color?: string;
|
color?: string;
|
||||||
flags: BlockSuiteFlags;
|
|
||||||
// use v2 to avoid crush on old clients
|
// use v2 to avoid crush on old clients
|
||||||
selectionV2: Record<string, UserSelection>;
|
selectionV2: Record<string, UserSelection>;
|
||||||
};
|
};
|
||||||
@@ -29,18 +22,14 @@ export interface AwarenessEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class AwarenessStore {
|
export class AwarenessStore {
|
||||||
private readonly _flags: Signal<BlockSuiteFlags>;
|
|
||||||
|
|
||||||
private readonly _onAwarenessChange = (diff: {
|
private readonly _onAwarenessChange = (diff: {
|
||||||
added: number[];
|
added: number[];
|
||||||
removed: number[];
|
removed: number[];
|
||||||
updated: number[];
|
updated: number[];
|
||||||
}) => {
|
}) => {
|
||||||
this._flags.value = this.awareness.getLocalState()?.flags ?? {};
|
|
||||||
|
|
||||||
const { added, removed, updated } = diff;
|
const { added, removed, updated } = diff;
|
||||||
|
|
||||||
const states = this.awareness.getStates();
|
const states = this.getStates();
|
||||||
added.forEach(id => {
|
added.forEach(id => {
|
||||||
this.slots.update.emit({
|
this.slots.update.emit({
|
||||||
id,
|
id,
|
||||||
@@ -63,30 +52,16 @@ export class AwarenessStore {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
readonly awareness: YAwareness<RawAwarenessState>;
|
readonly awareness: Awareness;
|
||||||
|
|
||||||
readonly slots = {
|
readonly slots = {
|
||||||
update: new Slot<AwarenessEvent>(),
|
update: new Slot<AwarenessEvent>(),
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(
|
constructor(awareness: Awareness) {
|
||||||
awareness: YAwareness<RawAwarenessState>,
|
|
||||||
defaultFlags: BlockSuiteFlags
|
|
||||||
) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
destroy() {
|
||||||
@@ -95,10 +70,6 @@ export class AwarenessStore {
|
|||||||
this.awareness.destroy();
|
this.awareness.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
getFlag<Key extends keyof BlockSuiteFlags>(field: Key) {
|
|
||||||
return this._flags.value[field];
|
|
||||||
}
|
|
||||||
|
|
||||||
getLocalSelection(
|
getLocalSelection(
|
||||||
selectionManagerId: string
|
selectionManagerId: string
|
||||||
): ReadonlyArray<Record<string, unknown>> {
|
): ReadonlyArray<Record<string, unknown>> {
|
||||||
@@ -109,24 +80,22 @@ export class AwarenessStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getStates(): Map<number, RawAwarenessState> {
|
getStates(): Map<number, RawAwarenessState> {
|
||||||
return this.awareness.getStates();
|
return this.awareness.getStates() as Map<number, RawAwarenessState>;
|
||||||
}
|
}
|
||||||
|
|
||||||
isReadonly(blockCollection: Doc): boolean {
|
getLocalState(): RawAwarenessState {
|
||||||
const rd = this.getFlag('readonly');
|
return this.awareness.getLocalState() as RawAwarenessState;
|
||||||
if (rd && typeof rd === 'object') {
|
|
||||||
return Boolean((rd as Record<string, boolean>)[blockCollection.id]);
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setFlag<Key extends keyof BlockSuiteFlags>(
|
setLocalState(state: RawAwarenessState): void {
|
||||||
field: Key,
|
this.awareness.setLocalState(state);
|
||||||
value: BlockSuiteFlags[Key]
|
}
|
||||||
) {
|
|
||||||
const oldFlags = this.awareness.getLocalState()?.flags ?? {};
|
setLocalStateField<Field extends keyof RawAwarenessState>(
|
||||||
this.awareness.setLocalStateField('flags', { ...oldFlags, [field]: value });
|
field: Field,
|
||||||
|
value: RawAwarenessState[Field]
|
||||||
|
): void {
|
||||||
|
this.awareness.setLocalStateField(field, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
setLocalSelection(selectionManagerId: string, selection: UserSelection) {
|
setLocalSelection(selectionManagerId: string, selection: UserSelection) {
|
||||||
@@ -136,12 +105,4 @@ export class AwarenessStore {
|
|||||||
[selectionManagerId]: selection,
|
[selectionManagerId]: selection,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setReadonly(blockCollection: Doc, value: boolean): void {
|
|
||||||
const flags = this.getFlag('readonly') ?? {};
|
|
||||||
this.setFlag('readonly', {
|
|
||||||
...flags,
|
|
||||||
[blockCollection.id]: value,
|
|
||||||
} as BlockSuiteFlags['readonly']);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { ShadowlessElement } from '@blocksuite/block-std';
|
|||||||
import type { RichText, RootBlockModel } from '@blocksuite/blocks';
|
import type { RichText, RootBlockModel } from '@blocksuite/blocks';
|
||||||
import { assertExists, WithDisposable } from '@blocksuite/global/utils';
|
import { assertExists, WithDisposable } from '@blocksuite/global/utils';
|
||||||
import type { Store } from '@blocksuite/store';
|
import type { Store } from '@blocksuite/store';
|
||||||
|
import { effect } from '@preact/signals-core';
|
||||||
import { css, html } from 'lit';
|
import { css, html } from 'lit';
|
||||||
import { property, query, state } from 'lit/decorators.js';
|
import { property, query, state } from 'lit/decorators.js';
|
||||||
|
|
||||||
@@ -111,7 +112,7 @@ export class DocTitle extends WithDisposable(ShadowlessElement) {
|
|||||||
|
|
||||||
this._isReadonly = this.doc.readonly;
|
this._isReadonly = this.doc.readonly;
|
||||||
this._disposables.add(
|
this._disposables.add(
|
||||||
this.doc.awarenessStore.slots.update.on(() => {
|
effect(() => {
|
||||||
if (this._isReadonly !== this.doc.readonly) {
|
if (this._isReadonly !== this.doc.readonly) {
|
||||||
this._isReadonly = this.doc.readonly;
|
this._isReadonly = this.doc.readonly;
|
||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import {
|
|||||||
Shape,
|
Shape,
|
||||||
shiftClickView,
|
shiftClickView,
|
||||||
switchEditorMode,
|
switchEditorMode,
|
||||||
toggleEditorReadonly,
|
|
||||||
ZOOM_BAR_RESPONSIVE_SCREEN_WIDTH,
|
ZOOM_BAR_RESPONSIVE_SCREEN_WIDTH,
|
||||||
zoomByMouseWheel,
|
zoomByMouseWheel,
|
||||||
zoomResetByKeyboard,
|
zoomResetByKeyboard,
|
||||||
@@ -33,6 +32,7 @@ import {
|
|||||||
focusRichText,
|
focusRichText,
|
||||||
initEmptyEdgelessState,
|
initEmptyEdgelessState,
|
||||||
redoByClick,
|
redoByClick,
|
||||||
|
switchReadonly,
|
||||||
type,
|
type,
|
||||||
undoByClick,
|
undoByClick,
|
||||||
waitNextFrame,
|
waitNextFrame,
|
||||||
@@ -203,7 +203,7 @@ test('zoom by pinch when edgeless is readonly', async ({ page }) => {
|
|||||||
await zoomResetByKeyboard(page);
|
await zoomResetByKeyboard(page);
|
||||||
await assertZoomLevel(page, 100);
|
await assertZoomLevel(page, 100);
|
||||||
|
|
||||||
await toggleEditorReadonly(page);
|
await switchReadonly(page);
|
||||||
|
|
||||||
const from = [
|
const from = [
|
||||||
{ x: CENTER_X - 100, y: CENTER_Y },
|
{ x: CENTER_X - 100, y: CENTER_Y },
|
||||||
@@ -217,7 +217,8 @@ test('zoom by pinch when edgeless is readonly', async ({ page }) => {
|
|||||||
await multiTouchMove(page, from, to);
|
await multiTouchMove(page, from, to);
|
||||||
await multiTouchUp(page, to);
|
await multiTouchUp(page, to);
|
||||||
|
|
||||||
await toggleEditorReadonly(page);
|
await switchReadonly(page, false);
|
||||||
|
await waitNextFrame(page);
|
||||||
await assertZoomLevel(page, 50);
|
await assertZoomLevel(page, 50);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { IPoint } from '@blocksuite/global/utils';
|
import type { IPoint } from '@blocksuite/global/utils';
|
||||||
import type { Page } from '@playwright/test';
|
import type { Page } from '@playwright/test';
|
||||||
|
import type { Store } from '@store/index.js';
|
||||||
|
|
||||||
import { toViewCoord } from './edgeless.js';
|
import { toViewCoord } from './edgeless.js';
|
||||||
import { waitNextFrame } from './misc.js';
|
import { waitNextFrame } from './misc.js';
|
||||||
@@ -110,14 +111,12 @@ export async function clickTestOperationsMenuItem(page: Page, name: string) {
|
|||||||
export async function switchReadonly(page: Page, value = true) {
|
export async function switchReadonly(page: Page, value = true) {
|
||||||
await page.evaluate(_value => {
|
await page.evaluate(_value => {
|
||||||
const defaultPage = document.querySelector(
|
const defaultPage = document.querySelector(
|
||||||
'affine-page-root'
|
'affine-page-root,affine-edgeless-root'
|
||||||
) as HTMLElement & {
|
) as HTMLElement & {
|
||||||
doc: {
|
doc: Store;
|
||||||
awarenessStore: { setFlag: (key: string, value: unknown) => void };
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
const doc = defaultPage.doc;
|
const doc = defaultPage.doc;
|
||||||
doc.awarenessStore.setFlag('readonly', { 'doc:home': _value });
|
doc.readonly = _value;
|
||||||
}, value);
|
}, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -85,9 +85,6 @@ async function initEmptyEditor({
|
|||||||
await new Promise(resolve => doc.slots.rootAdded.once(resolve));
|
await new Promise(resolve => doc.slots.rootAdded.once(resolve));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(flags)) {
|
|
||||||
doc.awarenessStore.setFlag(key as keyof typeof flags, value);
|
|
||||||
}
|
|
||||||
// add app root from https://github.com/toeverything/blocksuite/commit/947201981daa64c5ceeca5fd549460c34e2dabfa
|
// add app root from https://github.com/toeverything/blocksuite/commit/947201981daa64c5ceeca5fd549460c34e2dabfa
|
||||||
const appRoot = document.querySelector('#app');
|
const appRoot = document.querySelector('#app');
|
||||||
if (!appRoot) {
|
if (!appRoot) {
|
||||||
@@ -95,6 +92,11 @@ async function initEmptyEditor({
|
|||||||
}
|
}
|
||||||
const createEditor = () => {
|
const createEditor = () => {
|
||||||
const editor = document.createElement('affine-editor-container');
|
const editor = document.createElement('affine-editor-container');
|
||||||
|
for (const [key, value] of Object.entries(flags)) {
|
||||||
|
doc
|
||||||
|
.get(window.$blocksuite.blocks.FeatureFlagService)
|
||||||
|
.setFlag(key, value);
|
||||||
|
}
|
||||||
doc
|
doc
|
||||||
.get(window.$blocksuite.blocks.FeatureFlagService)
|
.get(window.$blocksuite.blocks.FeatureFlagService)
|
||||||
.setFlag('enable_advanced_block_visibility', true);
|
.setFlag('enable_advanced_block_visibility', true);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { SpecProvider } from '@blocksuite/affine/blocks';
|
import { SpecProvider } from '@blocksuite/affine/blocks';
|
||||||
import { type Disposable, Slot } from '@blocksuite/affine/global/utils';
|
import { Slot } from '@blocksuite/affine/global/utils';
|
||||||
import {
|
import {
|
||||||
type AwarenessStore,
|
type AwarenessStore,
|
||||||
type Doc,
|
type Doc,
|
||||||
@@ -20,8 +20,6 @@ type DocOptions = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export class DocImpl implements Doc {
|
export class DocImpl implements Doc {
|
||||||
private _awarenessUpdateDisposable: Disposable | null = null;
|
|
||||||
|
|
||||||
private readonly _canRedo = signal(false);
|
private readonly _canRedo = signal(false);
|
||||||
|
|
||||||
private readonly _canUndo = signal(false);
|
private readonly _canUndo = signal(false);
|
||||||
@@ -89,8 +87,8 @@ export class DocImpl implements Doc {
|
|||||||
private _shouldTransact = true;
|
private _shouldTransact = true;
|
||||||
|
|
||||||
private readonly _updateCanUndoRedoSignals = () => {
|
private readonly _updateCanUndoRedoSignals = () => {
|
||||||
const canRedo = this.readonly ? false : this._history.canRedo();
|
const canRedo = this._history.canRedo();
|
||||||
const canUndo = this.readonly ? false : this._history.canUndo();
|
const canUndo = this._history.canUndo();
|
||||||
if (this._canRedo.peek() !== canRedo) {
|
if (this._canRedo.peek() !== canRedo) {
|
||||||
this._canRedo.value = canRedo;
|
this._canRedo.value = canRedo;
|
||||||
}
|
}
|
||||||
@@ -159,10 +157,6 @@ export class DocImpl implements Doc {
|
|||||||
return this.workspace.meta.getDocMeta(this.id);
|
return this.workspace.meta.getDocMeta(this.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
get readonly(): boolean {
|
|
||||||
return this.awarenessStore.isReadonly(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
get ready() {
|
get ready() {
|
||||||
return this._ready;
|
return this._ready;
|
||||||
}
|
}
|
||||||
@@ -267,7 +261,6 @@ export class DocImpl implements Doc {
|
|||||||
|
|
||||||
dispose() {
|
dispose() {
|
||||||
this.slots.historyUpdated.dispose();
|
this.slots.historyUpdated.dispose();
|
||||||
this._awarenessUpdateDisposable?.dispose();
|
|
||||||
|
|
||||||
if (this.ready) {
|
if (this.ready) {
|
||||||
this._yBlocks.unobserveDeep(this._handleYEvents);
|
this._yBlocks.unobserveDeep(this._handleYEvents);
|
||||||
@@ -320,13 +313,6 @@ export class DocImpl implements Doc {
|
|||||||
this._handleYBlockAdd(id);
|
this._handleYBlockAdd(id);
|
||||||
});
|
});
|
||||||
|
|
||||||
this._awarenessUpdateDisposable = this.awarenessStore.slots.update.on(
|
|
||||||
() => {
|
|
||||||
// change readonly state will affect the undo/redo state
|
|
||||||
this._updateCanUndoRedoSignals();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
initFn?.();
|
initFn?.();
|
||||||
|
|
||||||
this._ready = true;
|
this._ready = true;
|
||||||
@@ -335,18 +321,10 @@ export class DocImpl implements Doc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
redo() {
|
redo() {
|
||||||
if (this.readonly) {
|
|
||||||
console.error('cannot modify data in readonly mode');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._history.redo();
|
this._history.redo();
|
||||||
}
|
}
|
||||||
|
|
||||||
undo() {
|
undo() {
|
||||||
if (this.readonly) {
|
|
||||||
console.error('cannot modify data in readonly mode');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._history.undo();
|
this._history.undo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import {
|
|||||||
BlockSuiteError,
|
BlockSuiteError,
|
||||||
ErrorCode,
|
ErrorCode,
|
||||||
} from '@blocksuite/affine/global/exceptions';
|
} from '@blocksuite/affine/global/exceptions';
|
||||||
import type { BlockSuiteFlags } from '@blocksuite/affine/global/types';
|
|
||||||
import { NoopLogger, Slot } from '@blocksuite/affine/global/utils';
|
import { NoopLogger, Slot } from '@blocksuite/affine/global/utils';
|
||||||
import {
|
import {
|
||||||
AwarenessStore,
|
AwarenessStore,
|
||||||
@@ -33,10 +32,6 @@ type WorkspaceOptions = {
|
|||||||
blobSource?: BlobSource;
|
blobSource?: BlobSource;
|
||||||
};
|
};
|
||||||
|
|
||||||
const FLAGS_PRESET = {
|
|
||||||
readonly: {},
|
|
||||||
} satisfies BlockSuiteFlags;
|
|
||||||
|
|
||||||
export class WorkspaceImpl implements Workspace {
|
export class WorkspaceImpl implements Workspace {
|
||||||
protected readonly _schema: Schema;
|
protected readonly _schema: Schema;
|
||||||
|
|
||||||
@@ -73,10 +68,7 @@ export class WorkspaceImpl implements Workspace {
|
|||||||
|
|
||||||
this.id = id || '';
|
this.id = id || '';
|
||||||
this.doc = new Y.Doc({ guid: id });
|
this.doc = new Y.Doc({ guid: id });
|
||||||
this.awarenessStore = new AwarenessStore(new Awareness(this.doc), {
|
this.awarenessStore = new AwarenessStore(new Awareness(this.doc));
|
||||||
...FLAGS_PRESET,
|
|
||||||
readonly: {},
|
|
||||||
});
|
|
||||||
|
|
||||||
blobSource = blobSource ?? new MemoryBlobSource();
|
blobSource = blobSource ?? new MemoryBlobSource();
|
||||||
const logger = new NoopLogger();
|
const logger = new NoopLogger();
|
||||||
|
|||||||
Reference in New Issue
Block a user