From cbaf35df0bff80b20a71c6e242e07191ff9b7ab1 Mon Sep 17 00:00:00 2001 From: EYHN Date: Fri, 20 Dec 2024 08:01:23 +0000 Subject: [PATCH 01/17] feat(nbstore): add nbstore worker (#9185) --- packages/common/infra/src/op/consumer.ts | 10 + packages/common/nbstore/package.json | 2 +- .../nbstore/src/__tests__/frontend.spec.ts | 18 +- .../nbstore/src/connection/connection.ts | 35 ++- .../src/connection/shared-connection.ts | 6 +- .../common/nbstore/src/frontend/awareness.ts | 4 +- packages/common/nbstore/src/frontend/blob.ts | 4 +- packages/common/nbstore/src/frontend/doc.ts | 3 +- .../src/impls/broadcast-channel/awareness.ts | 50 +-- .../src/impls/broadcast-channel/channel.ts | 4 +- .../nbstore/src/impls/cloud/awareness.ts | 24 +- .../common/nbstore/src/impls/cloud/blob.ts | 23 +- .../common/nbstore/src/impls/cloud/doc.ts | 151 +++++---- .../common/nbstore/src/impls/cloud/socket.ts | 7 +- packages/common/nbstore/src/impls/idb/blob.ts | 4 +- packages/common/nbstore/src/impls/idb/db.ts | 4 +- packages/common/nbstore/src/impls/idb/doc.ts | 4 +- packages/common/nbstore/src/impls/idb/sync.ts | 4 +- .../common/nbstore/src/impls/idb/v1/blob.ts | 4 +- .../common/nbstore/src/impls/idb/v1/db.ts | 10 +- .../common/nbstore/src/impls/idb/v1/doc.ts | 8 +- packages/common/nbstore/src/impls/index.ts | 14 +- .../common/nbstore/src/impls/sqlite/blob.ts | 4 +- .../common/nbstore/src/impls/sqlite/db.ts | 4 +- .../common/nbstore/src/impls/sqlite/doc.ts | 4 +- .../common/nbstore/src/impls/sqlite/sync.ts | 4 +- .../nbstore/src/impls/sqlite/v1/blob.ts | 8 +- .../common/nbstore/src/impls/sqlite/v1/doc.ts | 12 +- packages/common/nbstore/src/op/consumer.ts | 128 -------- packages/common/nbstore/src/op/index.ts | 53 ---- packages/common/nbstore/src/op/ops.ts | 58 ---- packages/common/nbstore/src/op/worker.ts | 11 - .../common/nbstore/src/storage/awareness.ts | 26 +- packages/common/nbstore/src/storage/blob.ts | 24 +- packages/common/nbstore/src/storage/doc.ts | 97 +++--- .../common/nbstore/src/storage/history.ts | 4 +- packages/common/nbstore/src/storage/index.ts | 9 +- .../common/nbstore/src/storage/storage.ts | 25 +- packages/common/nbstore/src/storage/sync.ts | 29 +- .../nbstore/src/sync/awareness/index.ts | 13 +- .../common/nbstore/src/sync/blob/index.ts | 10 +- packages/common/nbstore/src/sync/doc/index.ts | 8 +- packages/common/nbstore/src/sync/doc/peer.ts | 7 +- packages/common/nbstore/src/sync/index.ts | 26 +- packages/common/nbstore/src/worker/client.ts | 294 ++++++++++++++++++ .../common/nbstore/src/worker/consumer.ts | 256 +++++++++++++++ packages/common/nbstore/src/worker/ops.ts | 122 ++++++++ .../apps/electron/src/helper/nbstore/blob.ts | 4 +- .../apps/electron/src/helper/nbstore/db.ts | 4 +- .../apps/electron/src/helper/nbstore/doc.ts | 4 +- .../apps/electron/src/helper/nbstore/sync.ts | 4 +- 51 files changed, 1144 insertions(+), 501 deletions(-) delete mode 100644 packages/common/nbstore/src/op/consumer.ts delete mode 100644 packages/common/nbstore/src/op/index.ts delete mode 100644 packages/common/nbstore/src/op/ops.ts delete mode 100644 packages/common/nbstore/src/op/worker.ts create mode 100644 packages/common/nbstore/src/worker/client.ts create mode 100644 packages/common/nbstore/src/worker/consumer.ts create mode 100644 packages/common/nbstore/src/worker/ops.ts diff --git a/packages/common/infra/src/op/consumer.ts b/packages/common/infra/src/op/consumer.ts index 0b24c4b5ea7c9..4a0d2cc28370a 100644 --- a/packages/common/infra/src/op/consumer.ts +++ b/packages/common/infra/src/op/consumer.ts @@ -126,6 +126,16 @@ export class OpConsumer extends AutoMessageHandler { this.registeredOpHandlers.set(op, handler); } + registerAll( + handlers: OpNames extends string + ? { [K in OpNames]: OpHandler } + : never + ) { + for (const [op, handler] of Object.entries(handlers)) { + this.register(op as any, handler as any); + } + } + before>( op: Op, handler: (...input: OpInput) => void diff --git a/packages/common/nbstore/package.json b/packages/common/nbstore/package.json index 5a9c66757ce47..21bf934bb2658 100644 --- a/packages/common/nbstore/package.json +++ b/packages/common/nbstore/package.json @@ -6,7 +6,7 @@ "sideEffects": false, "exports": { ".": "./src/index.ts", - "./op": "./src/op/index.ts", + "./worker": "./src/worker/index.ts", "./idb": "./src/impls/idb/index.ts", "./idb/v1": "./src/impls/idb/v1/index.ts", "./cloud": "./src/impls/cloud/index.ts", diff --git a/packages/common/nbstore/src/__tests__/frontend.spec.ts b/packages/common/nbstore/src/__tests__/frontend.spec.ts index cedd7ad903150..9acfca646f98a 100644 --- a/packages/common/nbstore/src/__tests__/frontend.spec.ts +++ b/packages/common/nbstore/src/__tests__/frontend.spec.ts @@ -8,7 +8,7 @@ import { AwarenessFrontend } from '../frontend/awareness'; import { DocFrontend } from '../frontend/doc'; import { BroadcastChannelAwarenessStorage } from '../impls/broadcast-channel/awareness'; import { IndexedDBDocStorage } from '../impls/idb'; -import { AwarenessSync } from '../sync/awareness'; +import { AwarenessSyncImpl } from '../sync/awareness'; import { expectYjsEqual } from './utils'; test('doc', async () => { @@ -23,9 +23,9 @@ test('doc', async () => { type: 'workspace', }); - docStorage.connect(); + docStorage.connection.connect(); - await docStorage.waitForConnected(); + await docStorage.connection.waitForConnected(); const frontend1 = new DocFrontend(docStorage, null); frontend1.start(); @@ -68,11 +68,11 @@ test('awareness', async () => { type: 'workspace', }); - storage1.connect(); - storage2.connect(); + storage1.connection.connect(); + storage2.connection.connect(); - await storage1.waitForConnected(); - await storage2.waitForConnected(); + await storage1.connection.waitForConnected(); + await storage2.connection.waitForConnected(); // peer a const docA = new YDoc({ guid: 'test-doc' }); @@ -90,13 +90,13 @@ test('awareness', async () => { const awarenessC = new Awareness(docC); { - const sync = new AwarenessSync(storage1, [storage2]); + const sync = new AwarenessSyncImpl(storage1, [storage2]); const frontend = new AwarenessFrontend(sync); frontend.connect(awarenessA); frontend.connect(awarenessB); } { - const sync = new AwarenessSync(storage2, [storage1]); + const sync = new AwarenessSyncImpl(storage2, [storage1]); const frontend = new AwarenessFrontend(sync); frontend.connect(awarenessC); } diff --git a/packages/common/nbstore/src/connection/connection.ts b/packages/common/nbstore/src/connection/connection.ts index 0df9900521433..cd15248d32624 100644 --- a/packages/common/nbstore/src/connection/connection.ts +++ b/packages/common/nbstore/src/connection/connection.ts @@ -8,7 +8,20 @@ export type ConnectionStatus = | 'error' | 'closed'; -export abstract class Connection { +export interface Connection { + readonly status: ConnectionStatus; + readonly inner: T; + connect(): void; + disconnect(): void; + waitForConnected(signal?: AbortSignal): Promise; + onStatusChanged( + cb: (status: ConnectionStatus, error?: Error) => void + ): () => void; +} + +export abstract class AutoReconnectConnection + implements Connection +{ private readonly event = new EventEmitter2(); private _inner: T | null = null; private _status: ConnectionStatus = 'idle'; @@ -160,12 +173,22 @@ export abstract class Connection { }; } -export class DummyConnection extends Connection { - doConnect() { - return Promise.resolve(undefined); - } +export class DummyConnection implements Connection { + readonly status: ConnectionStatus = 'connected'; + readonly inner: undefined; - doDisconnect() { + connect(): void { return; } + disconnect(): void { + return; + } + waitForConnected(_signal?: AbortSignal): Promise { + return Promise.resolve(); + } + onStatusChanged( + _cb: (status: ConnectionStatus, error?: Error) => void + ): () => void { + return () => {}; + } } diff --git a/packages/common/nbstore/src/connection/shared-connection.ts b/packages/common/nbstore/src/connection/shared-connection.ts index da69e849050e1..1f11bb4a43bda 100644 --- a/packages/common/nbstore/src/connection/shared-connection.ts +++ b/packages/common/nbstore/src/connection/shared-connection.ts @@ -1,7 +1,7 @@ -import type { Connection } from './connection'; +import type { AutoReconnectConnection } from './connection'; -const CONNECTIONS: Map> = new Map(); -export function share>(conn: T): T { +const CONNECTIONS: Map> = new Map(); +export function share>(conn: T): T { if (!conn.shareId) { throw new Error( `Connection ${conn.constructor.name} is not shareable.\nIf you want to make it shareable, please override [shareId].` diff --git a/packages/common/nbstore/src/frontend/awareness.ts b/packages/common/nbstore/src/frontend/awareness.ts index 19a092789bc42..524c790fba2f9 100644 --- a/packages/common/nbstore/src/frontend/awareness.ts +++ b/packages/common/nbstore/src/frontend/awareness.ts @@ -51,10 +51,10 @@ export class AwarenessFrontend { applyAwarenessUpdate(awareness, update.bin, origin); }; const handleSyncCollect = () => { - return { + return Promise.resolve({ docId: awareness.doc.guid, bin: encodeAwarenessUpdate(awareness, [awareness.clientID]), - }; + }); }; const unsubscribe = this.sync.subscribeUpdate( awareness.doc.guid, diff --git a/packages/common/nbstore/src/frontend/blob.ts b/packages/common/nbstore/src/frontend/blob.ts index 40af3f7773f30..7bfc76ab452e7 100644 --- a/packages/common/nbstore/src/frontend/blob.ts +++ b/packages/common/nbstore/src/frontend/blob.ts @@ -17,7 +17,7 @@ export class BlobFrontend { return this.sync ? this.sync.uploadBlob(blob) : this.storage.set(blob); } - addPriority(id: string, priority: number) { - return this.sync?.addPriority(id, priority); + addPriority(_id: string, _priority: number) { + // not support yet } } diff --git a/packages/common/nbstore/src/frontend/doc.ts b/packages/common/nbstore/src/frontend/doc.ts index c95aee1c18195..9cbfc301b76a6 100644 --- a/packages/common/nbstore/src/frontend/doc.ts +++ b/packages/common/nbstore/src/frontend/doc.ts @@ -37,7 +37,7 @@ interface DocFrontendOptions { } export class DocFrontend { - private readonly uniqueId = `frontend:${this.storage.peer}:${nanoid()}`; + private readonly uniqueId = `frontend:${nanoid()}`; private readonly prioritySettings = new Map(); @@ -88,7 +88,6 @@ export class DocFrontend { }), ]); - // eslint-disable-next-line no-constant-condition while (true) { throwIfAborted(signal); const docId = await this.status.jobDocQueue.asyncPop(signal); diff --git a/packages/common/nbstore/src/impls/broadcast-channel/awareness.ts b/packages/common/nbstore/src/impls/broadcast-channel/awareness.ts index 837dc04784f91..b4ec7a67deba3 100644 --- a/packages/common/nbstore/src/impls/broadcast-channel/awareness.ts +++ b/packages/common/nbstore/src/impls/broadcast-channel/awareness.ts @@ -1,9 +1,6 @@ import { nanoid } from 'nanoid'; -import { - type AwarenessRecord, - AwarenessStorage, -} from '../../storage/awareness'; +import { type AwarenessRecord, AwarenessStorageBase } from '../../storage'; import { BroadcastChannelConnection } from './channel'; type ChannelMessage = @@ -19,13 +16,13 @@ type ChannelMessage = collectId: string; } | { - type: 'awareness-collect-fallback'; + type: 'awareness-collect-feedback'; docId: string; bin: Uint8Array; collectId: string; }; -export class BroadcastChannelAwarenessStorage extends AwarenessStorage { +export class BroadcastChannelAwarenessStorage extends AwarenessStorageBase { override readonly storageType = 'awareness'; override readonly connection = new BroadcastChannelConnection(this.options); get channel() { @@ -36,7 +33,7 @@ export class BroadcastChannelAwarenessStorage extends AwarenessStorage { string, Set<{ onUpdate: (update: AwarenessRecord, origin?: string) => void; - onCollect: () => AwarenessRecord; + onCollect: () => Promise; }> >(); @@ -57,12 +54,20 @@ export class BroadcastChannelAwarenessStorage extends AwarenessStorage { override subscribeUpdate( id: string, onUpdate: (update: AwarenessRecord, origin?: string) => void, - onCollect: () => AwarenessRecord + onCollect: () => Promise ): () => void { const subscribers = this.subscriptions.get(id) ?? new Set(); subscribers.forEach(subscriber => { - const fallback = subscriber.onCollect(); - onUpdate(fallback); + subscriber + .onCollect() + .then(awareness => { + if (awareness) { + onUpdate(awareness); + } + }) + .catch(error => { + console.error('error in on collect awareness', error); + }); }); const collectUniqueId = nanoid(); @@ -84,18 +89,23 @@ export class BroadcastChannelAwarenessStorage extends AwarenessStorage { message.data.type === 'awareness-collect' && message.data.docId === id ) { - const fallback = onCollect(); - if (fallback) { - this.channel.postMessage({ - type: 'awareness-collect-fallback', - docId: message.data.docId, - bin: fallback.bin, - collectId: collectUniqueId, - } satisfies ChannelMessage); - } + onCollect() + .then(awareness => { + if (awareness) { + this.channel.postMessage({ + type: 'awareness-collect-feedback', + docId: message.data.docId, + bin: awareness.bin, + collectId: collectUniqueId, + } satisfies ChannelMessage); + } + }) + .catch(error => { + console.error('error in on collect awareness', error); + }); } if ( - message.data.type === 'awareness-collect-fallback' && + message.data.type === 'awareness-collect-feedback' && message.data.docId === id && message.data.collectId === collectUniqueId ) { diff --git a/packages/common/nbstore/src/impls/broadcast-channel/channel.ts b/packages/common/nbstore/src/impls/broadcast-channel/channel.ts index cd40bd7f21e34..fae9fbb75054f 100644 --- a/packages/common/nbstore/src/impls/broadcast-channel/channel.ts +++ b/packages/common/nbstore/src/impls/broadcast-channel/channel.ts @@ -1,7 +1,7 @@ -import { Connection } from '../../connection'; +import { AutoReconnectConnection } from '../../connection'; import type { StorageOptions } from '../../storage'; -export class BroadcastChannelConnection extends Connection { +export class BroadcastChannelConnection extends AutoReconnectConnection { readonly channelName = `channel:${this.opts.peer}:${this.opts.type}:${this.opts.id}`; constructor(private readonly opts: StorageOptions) { diff --git a/packages/common/nbstore/src/impls/cloud/awareness.ts b/packages/common/nbstore/src/impls/cloud/awareness.ts index be9a56ef45720..5ee8ccd0e96ba 100644 --- a/packages/common/nbstore/src/impls/cloud/awareness.ts +++ b/packages/common/nbstore/src/impls/cloud/awareness.ts @@ -3,7 +3,7 @@ import type { SocketOptions } from 'socket.io-client'; import { share } from '../../connection'; import { type AwarenessRecord, - AwarenessStorage, + AwarenessStorageBase, type AwarenessStorageOptions, } from '../../storage/awareness'; import { @@ -16,7 +16,7 @@ interface CloudAwarenessStorageOptions extends AwarenessStorageOptions { socketOptions: SocketOptions; } -export class CloudAwarenessStorage extends AwarenessStorage { +export class CloudAwarenessStorage extends AwarenessStorageBase { connection = share( new SocketConnection(this.peer, this.options.socketOptions) ); @@ -38,7 +38,7 @@ export class CloudAwarenessStorage extends AwarenessStorage void, - onCollect: () => AwarenessRecord + onCollect: () => Promise ): () => void { // TODO: handle disconnect // leave awareness @@ -92,14 +92,16 @@ export class CloudAwarenessStorage extends AwarenessStorage { - const record = onCollect(); - const encodedUpdate = await uint8ArrayToBase64(record.bin); - this.socket.emit('space:update-awareness', { - spaceType: this.spaceType, - spaceId: this.spaceId, - docId: record.docId, - awarenessUpdate: encodedUpdate, - }); + const record = await onCollect(); + if (record) { + const encodedUpdate = await uint8ArrayToBase64(record.bin); + this.socket.emit('space:update-awareness', { + spaceType: this.spaceType, + spaceId: this.spaceId, + docId: record.docId, + awarenessUpdate: encodedUpdate, + }); + } })().catch(err => console.error('awareness upload failed', err)); } }; diff --git a/packages/common/nbstore/src/impls/cloud/blob.ts b/packages/common/nbstore/src/impls/cloud/blob.ts index 91cf113b9c776..2dac5f6ed944d 100644 --- a/packages/common/nbstore/src/impls/cloud/blob.ts +++ b/packages/common/nbstore/src/impls/cloud/blob.ts @@ -7,16 +7,31 @@ import { } from '@affine/graphql'; import { DummyConnection } from '../../connection'; -import { type BlobRecord, BlobStorage } from '../../storage'; +import { + type BlobRecord, + BlobStorageBase, + type BlobStorageOptions, +} from '../../storage'; + +interface CloudBlobStorageOptions extends BlobStorageOptions { + apiBaseUrl: string; +} -export class CloudBlobStorage extends BlobStorage { - private readonly gql = gqlFetcherFactory(this.options.peer + '/graphql'); +export class CloudBlobStorage extends BlobStorageBase { + private readonly gql = gqlFetcherFactory( + this.options.apiBaseUrl + '/graphql' + ); override connection = new DummyConnection(); override async get(key: string) { const res = await fetch( - this.options.peer + '/api/workspaces/' + this.spaceId + '/blobs/' + key, + this.options.apiBaseUrl + + '/api/workspaces/' + + this.spaceId + + '/blobs/' + + key, { + cache: 'default', headers: { 'x-affine-version': BUILD_CONFIG.appVersion, }, diff --git a/packages/common/nbstore/src/impls/cloud/doc.ts b/packages/common/nbstore/src/impls/cloud/doc.ts index 7be0d02cb289a..757b93351c513 100644 --- a/packages/common/nbstore/src/impls/cloud/doc.ts +++ b/packages/common/nbstore/src/impls/cloud/doc.ts @@ -1,10 +1,14 @@ -import type { SocketOptions } from 'socket.io-client'; +import type { Socket, SocketOptions } from 'socket.io-client'; -import { share } from '../../connection'; +import { + type Connection, + type ConnectionStatus, + share, +} from '../../connection'; import { type DocClock, type DocClocks, - DocStorage, + DocStorageBase, type DocStorageOptions, type DocUpdate, } from '../../storage'; @@ -17,63 +21,14 @@ import { interface CloudDocStorageOptions extends DocStorageOptions { socketOptions: SocketOptions; + serverBaseUrl: string; } -export class CloudDocStorage extends DocStorage { - connection = share( - new SocketConnection(this.peer, this.options.socketOptions) - ); - - private disposeConnectionStatusListener?: () => void; - - private get socket() { +export class CloudDocStorage extends DocStorageBase { + get socket() { return this.connection.inner; } - override connect() { - if (!this.disposeConnectionStatusListener) { - this.disposeConnectionStatusListener = this.connection.onStatusChanged( - status => { - if (status === 'connected') { - this.join().catch(err => { - console.error('doc storage join failed', err); - }); - this.socket.on('space:broadcast-doc-update', this.onServerUpdate); - } - } - ); - } - super.connect(); - } - - override disconnect() { - if (this.disposeConnectionStatusListener) { - this.disposeConnectionStatusListener(); - } - this.socket.emit('space:leave', { - spaceType: this.spaceType, - spaceId: this.spaceId, - }); - this.socket.off('space:broadcast-doc-update', this.onServerUpdate); - super.disconnect(); - } - - async join() { - try { - const res = await this.socket.emitWithAck('space:join', { - spaceType: this.spaceType, - spaceId: this.spaceId, - clientVersion: BUILD_CONFIG.appVersion, - }); - - if ('error' in res) { - this.connection.setStatus('closed', new Error(res.error.message)); - } - } catch (e) { - this.connection.setStatus('error', e as Error); - } - } - onServerUpdate: ServerEventsMap['space:broadcast-doc-update'] = message => { if ( this.spaceType === message.spaceType && @@ -88,6 +43,11 @@ export class CloudDocStorage extends DocStorage { } }; + readonly connection = new CloudDocStorageConnection( + this.options, + this.onServerUpdate + ); + override async getDocSnapshot(docId: string) { const response = await this.socket.emitWithAck('space:load-doc', { spaceType: this.spaceType, @@ -207,3 +167,84 @@ export class CloudDocStorage extends DocStorage { return 0; } } + +class CloudDocStorageConnection implements Connection { + connection = share( + new SocketConnection( + `${this.options.serverBaseUrl}/`, + this.options.socketOptions + ) + ); + + private disposeConnectionStatusListener?: () => void; + + private get socket() { + return this.connection.inner; + } + + constructor( + private readonly options: CloudDocStorageOptions, + private readonly onServerUpdate: ServerEventsMap['space:broadcast-doc-update'] + ) {} + + get status() { + return this.connection.status; + } + + get inner() { + return this.connection.inner; + } + + connect(): void { + if (!this.disposeConnectionStatusListener) { + this.disposeConnectionStatusListener = this.connection.onStatusChanged( + status => { + if (status === 'connected') { + this.join().catch(err => { + console.error('doc storage join failed', err); + }); + this.socket.on('space:broadcast-doc-update', this.onServerUpdate); + } + } + ); + } + return this.connection.connect(); + } + + async join() { + try { + const res = await this.socket.emitWithAck('space:join', { + spaceType: this.options.type, + spaceId: this.options.id, + clientVersion: BUILD_CONFIG.appVersion, + }); + + if ('error' in res) { + this.connection.setStatus('closed', new Error(res.error.message)); + } + } catch (e) { + this.connection.setStatus('error', e as Error); + } + } + + disconnect() { + if (this.disposeConnectionStatusListener) { + this.disposeConnectionStatusListener(); + } + this.socket.emit('space:leave', { + spaceType: this.options.type, + spaceId: this.options.id, + }); + this.socket.off('space:broadcast-doc-update', this.onServerUpdate); + this.connection.disconnect(); + } + + waitForConnected(signal?: AbortSignal): Promise { + return this.connection.waitForConnected(signal); + } + onStatusChanged( + cb: (status: ConnectionStatus, error?: Error) => void + ): () => void { + return this.connection.onStatusChanged(cb); + } +} diff --git a/packages/common/nbstore/src/impls/cloud/socket.ts b/packages/common/nbstore/src/impls/cloud/socket.ts index 79d31057ce2bd..558afb56f27c8 100644 --- a/packages/common/nbstore/src/impls/cloud/socket.ts +++ b/packages/common/nbstore/src/impls/cloud/socket.ts @@ -4,7 +4,10 @@ import { type SocketOptions, } from 'socket.io-client'; -import { Connection, type ConnectionStatus } from '../../connection'; +import { + AutoReconnectConnection, + type ConnectionStatus, +} from '../../connection'; // TODO(@forehalo): use [UserFriendlyError] interface EventError { @@ -150,7 +153,7 @@ export function base64ToUint8Array(base64: string) { return new Uint8Array(binaryArray); } -export class SocketConnection extends Connection { +export class SocketConnection extends AutoReconnectConnection { manager = new SocketIOManager(this.endpoint, { autoConnect: false, transports: ['websocket'], diff --git a/packages/common/nbstore/src/impls/idb/blob.ts b/packages/common/nbstore/src/impls/idb/blob.ts index 02c67267d5a37..0ea7fc7821f60 100644 --- a/packages/common/nbstore/src/impls/idb/blob.ts +++ b/packages/common/nbstore/src/impls/idb/blob.ts @@ -1,12 +1,12 @@ import { share } from '../../connection'; import { type BlobRecord, - BlobStorage, + BlobStorageBase, type ListedBlobRecord, } from '../../storage'; import { IDBConnection } from './db'; -export class IndexedDBBlobStorage extends BlobStorage { +export class IndexedDBBlobStorage extends BlobStorageBase { readonly connection = share(new IDBConnection(this.options)); get db() { diff --git a/packages/common/nbstore/src/impls/idb/db.ts b/packages/common/nbstore/src/impls/idb/db.ts index 52d3d088c8932..c7ba4282aa2d1 100644 --- a/packages/common/nbstore/src/impls/idb/db.ts +++ b/packages/common/nbstore/src/impls/idb/db.ts @@ -1,10 +1,10 @@ import { type IDBPDatabase, openDB } from 'idb'; -import { Connection } from '../../connection'; +import { AutoReconnectConnection } from '../../connection'; import type { StorageOptions } from '../../storage'; import { type DocStorageSchema, migrator } from './schema'; -export class IDBConnection extends Connection<{ +export class IDBConnection extends AutoReconnectConnection<{ db: IDBPDatabase; channel: BroadcastChannel; }> { diff --git a/packages/common/nbstore/src/impls/idb/doc.ts b/packages/common/nbstore/src/impls/idb/doc.ts index b188b5b84b82b..086b4ca29af9f 100644 --- a/packages/common/nbstore/src/impls/idb/doc.ts +++ b/packages/common/nbstore/src/impls/idb/doc.ts @@ -2,7 +2,7 @@ import { type DocClock, type DocClocks, type DocRecord, - DocStorage, + DocStorageBase, type DocStorageOptions, type DocUpdate, } from '../../storage'; @@ -15,7 +15,7 @@ interface ChannelMessage { origin?: string; } -export class IndexedDBDocStorage extends DocStorage { +export class IndexedDBDocStorage extends DocStorageBase { readonly connection = new IDBConnection(this.options); get db() { diff --git a/packages/common/nbstore/src/impls/idb/sync.ts b/packages/common/nbstore/src/impls/idb/sync.ts index b359a1554db54..77c9568bdc093 100644 --- a/packages/common/nbstore/src/impls/idb/sync.ts +++ b/packages/common/nbstore/src/impls/idb/sync.ts @@ -1,7 +1,7 @@ import { share } from '../../connection'; -import { type DocClock, type DocClocks, SyncStorage } from '../../storage'; +import { BasicSyncStorage, type DocClock, type DocClocks } from '../../storage'; import { IDBConnection } from './db'; -export class IndexedDBSyncStorage extends SyncStorage { +export class IndexedDBSyncStorage extends BasicSyncStorage { readonly connection = share(new IDBConnection(this.options)); get db() { diff --git a/packages/common/nbstore/src/impls/idb/v1/blob.ts b/packages/common/nbstore/src/impls/idb/v1/blob.ts index fcd370f62b0ea..508bb851e62b5 100644 --- a/packages/common/nbstore/src/impls/idb/v1/blob.ts +++ b/packages/common/nbstore/src/impls/idb/v1/blob.ts @@ -1,11 +1,11 @@ import { share } from '../../../connection'; -import { BlobStorage, type ListedBlobRecord } from '../../../storage'; +import { BlobStorageBase, type ListedBlobRecord } from '../../../storage'; import { BlobIDBConnection } from './db'; /** * @deprecated readonly */ -export class IndexedDBV1BlobStorage extends BlobStorage { +export class IndexedDBV1BlobStorage extends BlobStorageBase { readonly connection = share(new BlobIDBConnection(this.spaceId)); get db() { diff --git a/packages/common/nbstore/src/impls/idb/v1/db.ts b/packages/common/nbstore/src/impls/idb/v1/db.ts index b12dbf6d0461c..b0934fd21d545 100644 --- a/packages/common/nbstore/src/impls/idb/v1/db.ts +++ b/packages/common/nbstore/src/impls/idb/v1/db.ts @@ -1,6 +1,6 @@ import { type DBSchema, type IDBPDatabase, openDB } from 'idb'; -import { Connection } from '../../../connection'; +import { AutoReconnectConnection } from '../../../connection'; export interface DocDBSchema extends DBSchema { workspace: { @@ -15,7 +15,9 @@ export interface DocDBSchema extends DBSchema { }; } -export class DocIDBConnection extends Connection> { +export class DocIDBConnection extends AutoReconnectConnection< + IDBPDatabase +> { override get shareId() { return 'idb(old):affine-local'; } @@ -40,7 +42,9 @@ export interface BlobDBSchema extends DBSchema { }; } -export class BlobIDBConnection extends Connection> { +export class BlobIDBConnection extends AutoReconnectConnection< + IDBPDatabase +> { constructor(private readonly workspaceId: string) { super(); } diff --git a/packages/common/nbstore/src/impls/idb/v1/doc.ts b/packages/common/nbstore/src/impls/idb/v1/doc.ts index 7dc538830b8c7..cd21db55276e4 100644 --- a/packages/common/nbstore/src/impls/idb/v1/doc.ts +++ b/packages/common/nbstore/src/impls/idb/v1/doc.ts @@ -1,11 +1,15 @@ import { share } from '../../../connection'; -import { type DocRecord, DocStorage, type DocUpdate } from '../../../storage'; +import { + type DocRecord, + DocStorageBase, + type DocUpdate, +} from '../../../storage'; import { DocIDBConnection } from './db'; /** * @deprecated readonly */ -export class IndexedDBV1DocStorage extends DocStorage { +export class IndexedDBV1DocStorage extends DocStorageBase { readonly connection = share(new DocIDBConnection()); get db() { diff --git a/packages/common/nbstore/src/impls/index.ts b/packages/common/nbstore/src/impls/index.ts index bff2874ad4467..e43dd7df34ea3 100644 --- a/packages/common/nbstore/src/impls/index.ts +++ b/packages/common/nbstore/src/impls/index.ts @@ -1,5 +1,10 @@ import type { Storage } from '../storage'; -import { CloudBlobStorage, CloudDocStorage } from './cloud'; +import { BroadcastChannelAwarenessStorage } from './broadcast-channel/awareness'; +import { + CloudAwarenessStorage, + CloudBlobStorage, + CloudDocStorage, +} from './cloud'; import { IndexedDBBlobStorage, IndexedDBDocStorage, @@ -13,6 +18,7 @@ const idb: StorageConstructor[] = [ IndexedDBDocStorage, IndexedDBBlobStorage, IndexedDBSyncStorage, + BroadcastChannelAwarenessStorage, ]; const idbv1: StorageConstructor[] = [ @@ -20,7 +26,11 @@ const idbv1: StorageConstructor[] = [ IndexedDBV1BlobStorage, ]; -const cloud: StorageConstructor[] = [CloudDocStorage, CloudBlobStorage]; +const cloud: StorageConstructor[] = [ + CloudDocStorage, + CloudBlobStorage, + CloudAwarenessStorage, +]; export const storages: StorageConstructor[] = cloud.concat(idbv1, idb); diff --git a/packages/common/nbstore/src/impls/sqlite/blob.ts b/packages/common/nbstore/src/impls/sqlite/blob.ts index 803f433fa501d..40f9f44cc0163 100644 --- a/packages/common/nbstore/src/impls/sqlite/blob.ts +++ b/packages/common/nbstore/src/impls/sqlite/blob.ts @@ -1,8 +1,8 @@ import { share } from '../../connection'; -import { type BlobRecord, BlobStorage } from '../../storage'; +import { type BlobRecord, BlobStorageBase } from '../../storage'; import { NativeDBConnection } from './db'; -export class SqliteBlobStorage extends BlobStorage { +export class SqliteBlobStorage extends BlobStorageBase { override connection = share( new NativeDBConnection(this.peer, this.spaceType, this.spaceId) ); diff --git a/packages/common/nbstore/src/impls/sqlite/db.ts b/packages/common/nbstore/src/impls/sqlite/db.ts index e7f94a1e891fe..861d41ed127f4 100644 --- a/packages/common/nbstore/src/impls/sqlite/db.ts +++ b/packages/common/nbstore/src/impls/sqlite/db.ts @@ -1,6 +1,6 @@ import { apis } from '@affine/electron-api'; -import { Connection } from '../../connection'; +import { AutoReconnectConnection } from '../../connection'; import { type SpaceType, universalId } from '../../storage'; type NativeDBApis = NonNullable['nbstore'] extends infer APIs @@ -13,7 +13,7 @@ type NativeDBApis = NonNullable['nbstore'] extends infer APIs } : never; -export class NativeDBConnection extends Connection { +export class NativeDBConnection extends AutoReconnectConnection { readonly apis: NativeDBApis; constructor( diff --git a/packages/common/nbstore/src/impls/sqlite/doc.ts b/packages/common/nbstore/src/impls/sqlite/doc.ts index 3147130e63784..1c2bd4f0df657 100644 --- a/packages/common/nbstore/src/impls/sqlite/doc.ts +++ b/packages/common/nbstore/src/impls/sqlite/doc.ts @@ -1,8 +1,8 @@ import { share } from '../../connection'; -import { type DocClock, DocStorage, type DocUpdate } from '../../storage'; +import { type DocClock, DocStorageBase, type DocUpdate } from '../../storage'; import { NativeDBConnection } from './db'; -export class SqliteDocStorage extends DocStorage { +export class SqliteDocStorage extends DocStorageBase { override connection = share( new NativeDBConnection(this.peer, this.spaceType, this.spaceId) ); diff --git a/packages/common/nbstore/src/impls/sqlite/sync.ts b/packages/common/nbstore/src/impls/sqlite/sync.ts index 26da3f63779bd..6344fdb52698f 100644 --- a/packages/common/nbstore/src/impls/sqlite/sync.ts +++ b/packages/common/nbstore/src/impls/sqlite/sync.ts @@ -1,8 +1,8 @@ import { share } from '../../connection'; -import { type DocClock, SyncStorage } from '../../storage'; +import { BasicSyncStorage, type DocClock } from '../../storage'; import { NativeDBConnection } from './db'; -export class SqliteSyncStorage extends SyncStorage { +export class SqliteSyncStorage extends BasicSyncStorage { override connection = share( new NativeDBConnection(this.peer, this.spaceType, this.spaceId) ); diff --git a/packages/common/nbstore/src/impls/sqlite/v1/blob.ts b/packages/common/nbstore/src/impls/sqlite/v1/blob.ts index d01ab5819850e..7f3de15bb5ec8 100644 --- a/packages/common/nbstore/src/impls/sqlite/v1/blob.ts +++ b/packages/common/nbstore/src/impls/sqlite/v1/blob.ts @@ -1,13 +1,13 @@ import { apis } from '@affine/electron-api'; -import { DummyConnection, share } from '../../../connection'; -import { BlobStorage } from '../../../storage'; +import { DummyConnection } from '../../../connection'; +import { BlobStorageBase } from '../../../storage'; /** * @deprecated readonly */ -export class SqliteV1BlobStorage extends BlobStorage { - override connection = share(new DummyConnection()); +export class SqliteV1BlobStorage extends BlobStorageBase { + override connection = new DummyConnection(); get db() { if (!apis) { diff --git a/packages/common/nbstore/src/impls/sqlite/v1/doc.ts b/packages/common/nbstore/src/impls/sqlite/v1/doc.ts index 085a76ce415fc..bcf108cbb998f 100644 --- a/packages/common/nbstore/src/impls/sqlite/v1/doc.ts +++ b/packages/common/nbstore/src/impls/sqlite/v1/doc.ts @@ -1,13 +1,17 @@ import { apis } from '@affine/electron-api'; -import { DummyConnection, share } from '../../../connection'; -import { type DocRecord, DocStorage, type DocUpdate } from '../../../storage'; +import { DummyConnection } from '../../../connection'; +import { + type DocRecord, + DocStorageBase, + type DocUpdate, +} from '../../../storage'; /** * @deprecated readonly */ -export class SqliteV1DocStorage extends DocStorage { - override connection = share(new DummyConnection()); +export class SqliteV1DocStorage extends DocStorageBase { + override connection = new DummyConnection(); get db() { if (!apis) { diff --git a/packages/common/nbstore/src/op/consumer.ts b/packages/common/nbstore/src/op/consumer.ts deleted file mode 100644 index 812af778ed48a..0000000000000 --- a/packages/common/nbstore/src/op/consumer.ts +++ /dev/null @@ -1,128 +0,0 @@ -import type { OpConsumer } from '@toeverything/infra/op'; -import { Observable } from 'rxjs'; - -import { getAvailableStorageImplementations } from '../impls'; -import { - BlobStorage, - DocStorage, - HistoricalDocStorage, - SpaceStorage, - type Storage, - type StorageOptions, - SyncStorage, -} from '../storage'; -import type { SpaceStorageOps } from './ops'; - -export class SpaceStorageConsumer extends SpaceStorage { - constructor(private readonly consumer: OpConsumer) { - super([]); - this.registerConnectionHandlers(); - this.listen(); - } - - listen() { - this.consumer.listen(); - } - - add(name: string, options: StorageOptions) { - const Storage = getAvailableStorageImplementations(name); - const storage = new Storage(options); - this.storages.set(storage.storageType, storage); - this.registerStorageHandlers(storage); - } - - override async destroy() { - await super.destroy(); - this.consumer.destroy(); - } - - private registerConnectionHandlers() { - this.consumer.register('addStorage', ({ name, opts }) => { - this.add(name, opts); - }); - this.consumer.register('connect', this.connect.bind(this)); - this.consumer.register('disconnect', this.disconnect.bind(this)); - this.consumer.register('destroy', this.destroy.bind(this)); - } - - private registerStorageHandlers(storage: Storage) { - if (storage instanceof DocStorage) { - this.registerDocHandlers(storage); - } else if (storage instanceof BlobStorage) { - this.registerBlobHandlers(storage); - } else if (storage instanceof SyncStorage) { - this.registerSyncHandlers(storage); - } - } - - private registerDocHandlers(storage: DocStorage) { - this.consumer.register('getDoc', storage.getDoc.bind(storage)); - this.consumer.register('getDocDiff', ({ docId, state }) => { - return storage.getDocDiff(docId, state); - }); - this.consumer.register('pushDocUpdate', ({ update, origin }) => { - return storage.pushDocUpdate(update, origin); - }); - this.consumer.register( - 'getDocTimestamps', - storage.getDocTimestamps.bind(storage) - ); - this.consumer.register('deleteDoc', storage.deleteDoc.bind(storage)); - this.consumer.register('subscribeDocUpdate', () => { - return new Observable(subscriber => { - subscriber.add( - storage.subscribeDocUpdate((update, origin) => { - subscriber.next({ update, origin }); - }) - ); - }); - }); - - if (storage instanceof HistoricalDocStorage) { - this.consumer.register('listHistory', ({ docId, filter }) => { - return storage.listHistories(docId, filter); - }); - this.consumer.register('getHistory', ({ docId, timestamp }) => { - return storage.getHistory(docId, timestamp); - }); - this.consumer.register('deleteHistory', ({ docId, timestamp }) => { - return storage.deleteHistory(docId, timestamp); - }); - this.consumer.register('rollbackDoc', ({ docId, timestamp }) => { - return storage.rollbackDoc(docId, timestamp); - }); - } - } - - private registerBlobHandlers(storage: BlobStorage) { - this.consumer.register('getBlob', key => { - return storage.get(key); - }); - this.consumer.register('setBlob', blob => { - return storage.set(blob); - }); - this.consumer.register('deleteBlob', ({ key, permanently }) => { - return storage.delete(key, permanently); - }); - this.consumer.register('listBlobs', storage.list.bind(storage)); - this.consumer.register('releaseBlobs', storage.release.bind(storage)); - } - - private registerSyncHandlers(storage: SyncStorage) { - this.consumer.register( - 'getPeerClocks', - storage.getPeerRemoteClocks.bind(storage) - ); - this.consumer.register('setPeerClock', ({ peer, ...clock }) => { - return storage.setPeerRemoteClock(peer, clock); - }); - this.consumer.register( - 'getPeerPushedClocks', - storage.getPeerPushedClocks.bind(storage) - ); - this.consumer.register('setPeerPushedClock', ({ peer, ...clock }) => { - return storage.setPeerPushedClock(peer, clock); - }); - this.consumer.register('clearClocks', storage.clearClocks.bind(storage)); - } -} diff --git a/packages/common/nbstore/src/op/index.ts b/packages/common/nbstore/src/op/index.ts deleted file mode 100644 index f07cbee357be2..0000000000000 --- a/packages/common/nbstore/src/op/index.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { OpClient } from '@toeverything/infra/op'; - -import type { Storage } from '../storage'; -import type { SpaceStorageOps } from './ops'; - -export { SpaceStorageConsumer } from './consumer'; - -export class SpaceStorageClient extends OpClient { - /** - * Adding a storage implementation to the backend. - * - * NOTE: - * Because the storage beckend might be put behind a worker, we cant pass the instance but only - * the constructor name and its options to let the backend construct the instance. - */ - async addStorage Storage>( - Impl: T, - ...opts: ConstructorParameters - ) { - await this.call('addStorage', { name: Impl.name, opts: opts[0] }); - } - - async connect() { - await this.call('connect'); - } - - async disconnect() { - await this.call('disconnect'); - } - - override destroy() { - this.call('destroy').catch(console.error); - super.destroy(); - } - - connection$() { - return this.ob$('connection'); - } -} - -export class SpaceStorageWorkerClient extends SpaceStorageClient { - private readonly worker: Worker; - constructor() { - const worker = new Worker(new URL('./worker.ts', import.meta.url)); - super(worker); - this.worker = worker; - } - - override destroy() { - super.destroy(); - this.worker.terminate(); - } -} diff --git a/packages/common/nbstore/src/op/ops.ts b/packages/common/nbstore/src/op/ops.ts deleted file mode 100644 index 6509acc5f474a..0000000000000 --- a/packages/common/nbstore/src/op/ops.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { type OpSchema } from '@toeverything/infra/op'; - -import type { ConnectionStatus } from '../connection'; -import type { - BlobRecord, - DocClock, - DocClocks, - DocDiff, - DocRecord, - DocUpdate, - HistoryFilter, - ListedBlobRecord, - ListedHistory, - StorageOptions, - StorageType, -} from '../storage'; - -export interface SpaceStorageOps extends OpSchema { - // init - addStorage: [{ name: string; opts: StorageOptions }, void]; - - // connection - connect: [void, void]; - disconnect: [void, void]; - connection: [ - void, - { storage: StorageType; status: ConnectionStatus; error?: Error }, - ]; - destroy: [void, void]; - - // doc - getDoc: [string, DocRecord | null]; - getDocDiff: [{ docId: string; state?: Uint8Array }, DocDiff | null]; - pushDocUpdate: [{ update: DocUpdate; origin?: string }, DocClock]; - getDocTimestamps: [Date, DocClocks]; - deleteDoc: [string, void]; - subscribeDocUpdate: [void, { update: DocRecord; origin?: string }]; - - // history - listHistory: [{ docId: string; filter?: HistoryFilter }, ListedHistory[]]; - getHistory: [DocClock, DocRecord | null]; - deleteHistory: [DocClock, void]; - rollbackDoc: [DocClock & { editor?: string }, void]; - - // blob - getBlob: [string, BlobRecord | null]; - setBlob: [BlobRecord, void]; - deleteBlob: [{ key: string; permanently: boolean }, void]; - releaseBlobs: [void, void]; - listBlobs: [void, ListedBlobRecord[]]; - - // sync - getPeerClocks: [string, DocClocks]; - setPeerClock: [{ peer: string } & DocClock, void]; - getPeerPushedClocks: [string, DocClocks]; - setPeerPushedClock: [{ peer: string } & DocClock, void]; - clearClocks: [void, void]; -} diff --git a/packages/common/nbstore/src/op/worker.ts b/packages/common/nbstore/src/op/worker.ts deleted file mode 100644 index 62b85b2c5b2c2..0000000000000 --- a/packages/common/nbstore/src/op/worker.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { OpConsumer } from '@toeverything/infra/op'; - -import { SpaceStorageConsumer } from './consumer'; -import type { SpaceStorageOps } from './ops'; - -const consumer = new SpaceStorageConsumer( - // @ts-expect-error safe - new OpConsumer(self) -); - -consumer.listen(); diff --git a/packages/common/nbstore/src/storage/awareness.ts b/packages/common/nbstore/src/storage/awareness.ts index 5b47f3a450105..489de1a0aa1ca 100644 --- a/packages/common/nbstore/src/storage/awareness.ts +++ b/packages/common/nbstore/src/storage/awareness.ts @@ -1,4 +1,4 @@ -import { Storage, type StorageOptions } from './storage'; +import { type Storage, StorageBase, type StorageOptions } from './storage'; export interface AwarenessStorageOptions extends StorageOptions {} @@ -7,21 +7,35 @@ export type AwarenessRecord = { bin: Uint8Array; }; -export abstract class AwarenessStorage< - Options extends AwarenessStorageOptions = AwarenessStorageOptions, -> extends Storage { - override readonly storageType = 'awareness'; +export interface AwarenessStorage extends Storage { + readonly storageType: 'awareness'; /** * Update the awareness record. * * @param origin - Internal identifier to recognize the source in the "update" event. Will not be stored or transferred. */ + update(record: AwarenessRecord, origin?: string): Promise; + subscribeUpdate( + id: string, + onUpdate: (update: AwarenessRecord, origin?: string) => void, + onCollect: () => Promise + ): () => void; +} + +export abstract class AwarenessStorageBase< + Options extends AwarenessStorageOptions = AwarenessStorageOptions, + > + extends StorageBase + implements AwarenessStorage +{ + override readonly storageType = 'awareness'; + abstract update(record: AwarenessRecord, origin?: string): Promise; abstract subscribeUpdate( id: string, onUpdate: (update: AwarenessRecord, origin?: string) => void, - onCollect: () => AwarenessRecord + onCollect: () => Promise ): () => void; } diff --git a/packages/common/nbstore/src/storage/blob.ts b/packages/common/nbstore/src/storage/blob.ts index 8625e4b5c294e..4ad70517eee98 100644 --- a/packages/common/nbstore/src/storage/blob.ts +++ b/packages/common/nbstore/src/storage/blob.ts @@ -1,4 +1,4 @@ -import { Storage, type StorageOptions } from './storage'; +import { type Storage, StorageBase, type StorageOptions } from './storage'; export interface BlobStorageOptions extends StorageOptions {} @@ -16,9 +16,25 @@ export interface ListedBlobRecord { createdAt?: Date; } -export abstract class BlobStorage< - Options extends BlobStorageOptions = BlobStorageOptions, -> extends Storage { +export interface BlobStorage extends Storage { + readonly storageType: 'blob'; + get(key: string, signal?: AbortSignal): Promise; + set(blob: BlobRecord, signal?: AbortSignal): Promise; + delete( + key: string, + permanently: boolean, + signal?: AbortSignal + ): Promise; + release(signal?: AbortSignal): Promise; + list(signal?: AbortSignal): Promise; +} + +export abstract class BlobStorageBase< + Options extends BlobStorageOptions = BlobStorageOptions, + > + extends StorageBase + implements BlobStorage +{ override readonly storageType = 'blob'; abstract get(key: string, signal?: AbortSignal): Promise; diff --git a/packages/common/nbstore/src/storage/doc.ts b/packages/common/nbstore/src/storage/doc.ts index 3f5bfabe070bc..9a2c5a0818607 100644 --- a/packages/common/nbstore/src/storage/doc.ts +++ b/packages/common/nbstore/src/storage/doc.ts @@ -4,7 +4,7 @@ import { diffUpdate, encodeStateVectorFromUpdate, mergeUpdates } from 'yjs'; import { isEmptyUpdate } from '../utils/is-empty-update'; import type { Locker } from './lock'; import { SingletonLocker } from './lock'; -import { Storage, type StorageOptions } from './storage'; +import { type Storage, StorageBase, type StorageOptions } from './storage'; export interface DocClock { docId: string; @@ -37,17 +37,67 @@ export interface DocStorageOptions extends StorageOptions { mergeUpdates?: (updates: Uint8Array[]) => Promise | Uint8Array; } -export abstract class DocStorage< - Opts extends DocStorageOptions = DocStorageOptions, -> extends Storage { - private readonly event = new EventEmitter2(); - override readonly storageType = 'doc'; - protected readonly locker: Locker = new SingletonLocker(); +export interface DocStorage extends Storage { + readonly storageType: 'doc'; - // REGION: open apis by Op system /** * Get a doc record with latest binary. */ + getDoc(docId: string): Promise; + /** + * Get a yjs binary diff with the given state vector. + */ + getDocDiff(docId: string, state?: Uint8Array): Promise; + /** + * Push updates into storage + * + * @param origin - Internal identifier to recognize the source in the "update" event. Will not be stored or transferred. + */ + pushDocUpdate(update: DocUpdate, origin?: string): Promise; + + /** + * Get the timestamp of the latest update of a doc. + */ + getDocTimestamp(docId: string): Promise; + + /** + * Get all docs timestamps info. especially for useful in sync process. + */ + getDocTimestamps(after?: Date): Promise; + + /** + * Delete a specific doc data with all snapshots and updates + */ + deleteDoc(docId: string): Promise; + + /** + * Subscribe on doc updates emitted from storage itself. + * + * NOTE: + * + * There is not always update emitted from storage itself. + * + * For example, in Sqlite storage, the update will only come from user's updating on docs, + * in other words, the update will never somehow auto generated in storage internally. + * + * But for Cloud storage, there will be updates broadcasted from other clients, + * so the storage will emit updates to notify the client to integrate them. + */ + subscribeDocUpdate( + callback: (update: DocRecord, origin?: string) => void + ): () => void; +} + +export abstract class DocStorageBase< + Opts extends DocStorageOptions = DocStorageOptions, + > + extends StorageBase + implements DocStorage +{ + private readonly event = new EventEmitter2(); + override readonly storageType = 'doc'; + protected readonly locker: Locker = new SingletonLocker(); + async getDoc(docId: string) { await using _lock = await this.lockDocForUpdate(docId); @@ -78,9 +128,6 @@ export abstract class DocStorage< return snapshot; } - /** - * Get a yjs binary diff with the given state vector. - */ async getDocDiff(docId: string, state?: Uint8Array) { const doc = await this.getDoc(docId); @@ -96,41 +143,14 @@ export abstract class DocStorage< }; } - /** - * Push updates into storage - * - * @param origin - Internal identifier to recognize the source in the "update" event. Will not be stored or transferred. - */ abstract pushDocUpdate(update: DocUpdate, origin?: string): Promise; - /** - * Get the timestamp of the latest update of a doc. - */ abstract getDocTimestamp(docId: string): Promise; - /** - * Get all docs timestamps info. especially for useful in sync process. - */ abstract getDocTimestamps(after?: Date): Promise; - /** - * Delete a specific doc data with all snapshots and updates - */ abstract deleteDoc(docId: string): Promise; - /** - * Subscribe on doc updates emitted from storage itself. - * - * NOTE: - * - * There is not always update emitted from storage itself. - * - * For example, in Sqlite storage, the update will only come from user's updating on docs, - * in other words, the update will never somehow auto generated in storage internally. - * - * But for Cloud storage, there will be updates broadcasted from other clients, - * so the storage will emit updates to notify the client to integrate them. - */ subscribeDocUpdate(callback: (update: DocRecord, origin?: string) => void) { this.event.on('update', callback); @@ -138,7 +158,6 @@ export abstract class DocStorage< this.event.off('update', callback); }; } - // ENDREGION // REGION: api for internal usage protected on( diff --git a/packages/common/nbstore/src/storage/history.ts b/packages/common/nbstore/src/storage/history.ts index f112b3c83be3e..5908b75f708fd 100644 --- a/packages/common/nbstore/src/storage/history.ts +++ b/packages/common/nbstore/src/storage/history.ts @@ -7,7 +7,7 @@ import { UndoManager, } from 'yjs'; -import { type DocRecord, DocStorage, type DocStorageOptions } from './doc'; +import { type DocRecord, DocStorageBase, type DocStorageOptions } from './doc'; export interface HistoryFilter { before?: Date; @@ -21,7 +21,7 @@ export interface ListedHistory { export abstract class HistoricalDocStorage< Options extends DocStorageOptions = DocStorageOptions, -> extends DocStorage { +> extends DocStorageBase { constructor(opts: Options) { super(opts); diff --git a/packages/common/nbstore/src/storage/index.ts b/packages/common/nbstore/src/storage/index.ts index 46da1f065c99b..eaea342bb93a5 100644 --- a/packages/common/nbstore/src/storage/index.ts +++ b/packages/common/nbstore/src/storage/index.ts @@ -40,20 +40,20 @@ export class SpaceStorage { connect() { Array.from(this.storages.values()).forEach(storage => { - storage.connect(); + storage.connection.connect(); }); } disconnect() { Array.from(this.storages.values()).forEach(storage => { - storage.disconnect(); + storage.connection.disconnect(); }); } - async waitForConnected() { + async waitForConnected(signal?: AbortSignal) { await Promise.all( Array.from(this.storages.values()).map(storage => - storage.waitForConnected() + storage.connection.waitForConnected(signal) ) ); } @@ -65,6 +65,7 @@ export class SpaceStorage { } } +export * from './awareness'; export * from './blob'; export * from './doc'; export * from './history'; diff --git a/packages/common/nbstore/src/storage/storage.ts b/packages/common/nbstore/src/storage/storage.ts index b1c3ac7e27ed4..8cc5a7e87eb62 100644 --- a/packages/common/nbstore/src/storage/storage.ts +++ b/packages/common/nbstore/src/storage/storage.ts @@ -80,7 +80,18 @@ export function parseUniversalId(id: string) { return result as any; } -export abstract class Storage { +export interface Storage { + readonly storageType: StorageType; + readonly connection: Connection; + readonly peer: string; + readonly spaceType: string; + readonly spaceId: string; + readonly universalId: string; +} + +export abstract class StorageBase + implements Storage +{ abstract readonly storageType: StorageType; abstract readonly connection: Connection; @@ -101,16 +112,4 @@ export abstract class Storage { } constructor(public readonly options: Opts) {} - - connect() { - this.connection.connect(); - } - - disconnect() { - this.connection.disconnect(); - } - - async waitForConnected() { - await this.connection.waitForConnected(); - } } diff --git a/packages/common/nbstore/src/storage/sync.ts b/packages/common/nbstore/src/storage/sync.ts index cc10c3cf41420..9edda9b6305cc 100644 --- a/packages/common/nbstore/src/storage/sync.ts +++ b/packages/common/nbstore/src/storage/sync.ts @@ -1,11 +1,32 @@ import type { DocClock, DocClocks } from './doc'; -import { Storage, type StorageOptions } from './storage'; +import { type Storage, StorageBase, type StorageOptions } from './storage'; export interface SyncStorageOptions extends StorageOptions {} -export abstract class SyncStorage< - Opts extends SyncStorageOptions = SyncStorageOptions, -> extends Storage { +export interface SyncStorage extends Storage { + readonly storageType: 'sync'; + + getPeerRemoteClock(peer: string, docId: string): Promise; + getPeerRemoteClocks(peer: string): Promise; + setPeerRemoteClock(peer: string, clock: DocClock): Promise; + getPeerPulledRemoteClock( + peer: string, + docId: string + ): Promise; + getPeerPulledRemoteClocks(peer: string): Promise; + setPeerPulledRemoteClock(peer: string, clock: DocClock): Promise; + getPeerPushedClock(peer: string, docId: string): Promise; + getPeerPushedClocks(peer: string): Promise; + setPeerPushedClock(peer: string, clock: DocClock): Promise; + clearClocks(): Promise; +} + +export abstract class BasicSyncStorage< + Opts extends SyncStorageOptions = SyncStorageOptions, + > + extends StorageBase + implements SyncStorage +{ override readonly storageType = 'sync'; abstract getPeerRemoteClock( diff --git a/packages/common/nbstore/src/sync/awareness/index.ts b/packages/common/nbstore/src/sync/awareness/index.ts index cfdcbf9047e20..51971448f3279 100644 --- a/packages/common/nbstore/src/sync/awareness/index.ts +++ b/packages/common/nbstore/src/sync/awareness/index.ts @@ -3,7 +3,16 @@ import type { AwarenessStorage, } from '../../storage/awareness'; -export class AwarenessSync { +export interface AwarenessSync { + update(record: AwarenessRecord, origin?: string): Promise; + subscribeUpdate( + id: string, + onUpdate: (update: AwarenessRecord, origin?: string) => void, + onCollect: () => Promise + ): () => void; +} + +export class AwarenessSyncImpl implements AwarenessSync { constructor( readonly local: AwarenessStorage, readonly remotes: AwarenessStorage[] @@ -18,7 +27,7 @@ export class AwarenessSync { subscribeUpdate( id: string, onUpdate: (update: AwarenessRecord, origin?: string) => void, - onCollect: () => AwarenessRecord + onCollect: () => Promise ): () => void { const unsubscribes = [this.local, ...this.remotes].map(peer => peer.subscribeUpdate(id, onUpdate, onCollect) diff --git a/packages/common/nbstore/src/sync/blob/index.ts b/packages/common/nbstore/src/sync/blob/index.ts index 7a337b5bef7d6..8cafa4816ba98 100644 --- a/packages/common/nbstore/src/sync/blob/index.ts +++ b/packages/common/nbstore/src/sync/blob/index.ts @@ -3,7 +3,15 @@ import { difference } from 'lodash-es'; import type { BlobRecord, BlobStorage } from '../../storage'; import { MANUALLY_STOP, throwIfAborted } from '../../utils/throw-if-aborted'; -export class BlobSync { +export interface BlobSync { + downloadBlob( + blobId: string, + signal?: AbortSignal + ): Promise; + uploadBlob(blob: BlobRecord, signal?: AbortSignal): Promise; +} + +export class BlobSyncImpl implements BlobSync { private abort: AbortController | null = null; constructor( diff --git a/packages/common/nbstore/src/sync/doc/index.ts b/packages/common/nbstore/src/sync/doc/index.ts index 0728487bda05a..e5465051f903e 100644 --- a/packages/common/nbstore/src/sync/doc/index.ts +++ b/packages/common/nbstore/src/sync/doc/index.ts @@ -17,7 +17,13 @@ export interface DocSyncDocState { errorMessage: string | null; } -export class DocSync { +export interface DocSync { + readonly state$: Observable; + docState$(docId: string): Observable; + addPriority(id: string, priority: number): () => void; +} + +export class DocSyncImpl implements DocSync { private readonly peers: DocSyncPeer[] = this.remotes.map( remote => new DocSyncPeer(this.local, this.sync, remote) ); diff --git a/packages/common/nbstore/src/sync/doc/peer.ts b/packages/common/nbstore/src/sync/doc/peer.ts index b11d65cdb6583..2ffff7299a088 100644 --- a/packages/common/nbstore/src/sync/doc/peer.ts +++ b/packages/common/nbstore/src/sync/doc/peer.ts @@ -92,7 +92,7 @@ export class DocSyncPeer { /** * random unique id for recognize self in "update" event */ - private readonly uniqueId = `sync:${this.local.peer}:${this.remote.peer}:${nanoid()}`; + private readonly uniqueId = `sync:${this.local.universalId}:${this.remote.universalId}:${nanoid()}`; private readonly prioritySettings = new Map(); constructor( @@ -435,7 +435,6 @@ export class DocSyncPeer { }; async mainLoop(signal?: AbortSignal) { - // eslint-disable-next-line no-constant-condition while (true) { try { await this.retryLoop(signal); @@ -594,12 +593,12 @@ export class DocSyncPeer { } // begin to process jobs - // eslint-disable-next-line no-constant-condition + while (true) { throwIfAborted(signal); const docId = await this.status.jobDocQueue.asyncPop(signal); - // eslint-disable-next-line no-constant-condition + while (true) { // batch process jobs for the same doc const jobs = this.status.jobMap.get(docId); diff --git a/packages/common/nbstore/src/sync/index.ts b/packages/common/nbstore/src/sync/index.ts index 00c76dbb52026..d787f1ff9800a 100644 --- a/packages/common/nbstore/src/sync/index.ts +++ b/packages/common/nbstore/src/sync/index.ts @@ -1,19 +1,23 @@ import { combineLatest, map, type Observable, of } from 'rxjs'; -import type { BlobStorage, DocStorage, SpaceStorage } from '../storage'; -import type { AwarenessStorage } from '../storage/awareness'; -import { AwarenessSync } from './awareness'; -import { BlobSync } from './blob'; -import { DocSync, type DocSyncState } from './doc'; +import type { + AwarenessStorage, + BlobStorage, + DocStorage, + SpaceStorage, +} from '../storage'; +import { AwarenessSyncImpl } from './awareness'; +import { BlobSyncImpl } from './blob'; +import { DocSyncImpl, type DocSyncState } from './doc'; export interface SyncState { doc?: DocSyncState; } export class Sync { - readonly doc: DocSync | null; - readonly blob: BlobSync | null; - readonly awareness: AwarenessSync | null; + readonly doc: DocSyncImpl | null; + readonly blob: BlobSyncImpl | null; + readonly awareness: AwarenessSyncImpl | null; readonly state$: Observable; @@ -28,7 +32,7 @@ export class Sync { this.doc = doc && sync - ? new DocSync( + ? new DocSyncImpl( doc, sync, peers @@ -37,7 +41,7 @@ export class Sync { ) : null; this.blob = blob - ? new BlobSync( + ? new BlobSyncImpl( blob, peers .map(peer => peer.tryGet('blob')) @@ -45,7 +49,7 @@ export class Sync { ) : null; this.awareness = awareness - ? new AwarenessSync( + ? new AwarenessSyncImpl( awareness, peers .map(peer => peer.tryGet('awareness')) diff --git a/packages/common/nbstore/src/worker/client.ts b/packages/common/nbstore/src/worker/client.ts new file mode 100644 index 0000000000000..f4f8ef8603d26 --- /dev/null +++ b/packages/common/nbstore/src/worker/client.ts @@ -0,0 +1,294 @@ +import type { OpClient } from '@toeverything/infra/op'; + +import { DummyConnection } from '../connection'; +import { DocFrontend } from '../frontend/doc'; +import { + type AwarenessRecord, + type AwarenessStorage, + type BlobRecord, + type BlobStorage, + type DocRecord, + type DocStorage, + type DocUpdate, + type ListedBlobRecord, + type StorageOptions, + universalId, +} from '../storage'; +import type { AwarenessSync } from '../sync/awareness'; +import type { BlobSync } from '../sync/blob'; +import type { DocSync } from '../sync/doc'; +import type { WorkerOps } from './ops'; + +export class WorkerClient { + constructor( + private readonly client: OpClient, + private readonly options: StorageOptions + ) {} + + readonly docStorage = new WorkerDocStorage(this.client, this.options); + readonly blobStorage = new WorkerBlobStorage(this.client, this.options); + readonly awarenessStorage = new WorkerAwarenessStorage( + this.client, + this.options + ); + readonly docSync = new WorkerDocSync(this.client); + readonly blobSync = new WorkerBlobSync(this.client); + readonly awarenessSync = new WorkerAwarenessSync(this.client); + + readonly docFrontend = new DocFrontend(this.docStorage, this.docSync); +} + +class WorkerDocStorage implements DocStorage { + constructor( + private readonly client: OpClient, + private readonly options: StorageOptions + ) {} + + readonly peer = this.options.peer; + readonly spaceType = this.options.type; + readonly spaceId = this.options.id; + readonly universalId = universalId(this.options); + readonly storageType = 'doc'; + + async getDoc(docId: string) { + return this.client.call('docStorage.getDoc', docId); + } + + async getDocDiff(docId: string, state?: Uint8Array) { + return this.client.call('docStorage.getDocDiff', { docId, state }); + } + + async pushDocUpdate(update: DocUpdate, origin?: string) { + return this.client.call('docStorage.pushDocUpdate', { update, origin }); + } + + async getDocTimestamp(docId: string) { + return this.client.call('docStorage.getDocTimestamp', docId); + } + + async getDocTimestamps(after?: Date) { + return this.client.call('docStorage.getDocTimestamps', after ?? null); + } + + async deleteDoc(docId: string) { + return this.client.call('docStorage.deleteDoc', docId); + } + + subscribeDocUpdate(callback: (update: DocRecord, origin?: string) => void) { + const subscription = this.client + .ob$('docStorage.subscribeDocUpdate') + .subscribe(value => { + callback(value.update, value.origin); + }); + return () => { + subscription.unsubscribe(); + }; + } + + connection = new WorkerDocConnection(this.client); +} + +class WorkerDocConnection extends DummyConnection { + constructor(private readonly client: OpClient) { + super(); + } + + override waitForConnected(signal?: AbortSignal): Promise { + return new Promise((resolve, reject) => { + const abortListener = () => { + reject(signal?.reason); + subscription.unsubscribe(); + }; + + signal?.addEventListener('abort', abortListener); + + const subscription = this.client + .ob$('docStorage.waitForConnected') + .subscribe({ + next() { + signal?.removeEventListener('abort', abortListener); + resolve(); + }, + error(err) { + signal?.removeEventListener('abort', abortListener); + reject(err); + }, + }); + }); + } +} + +class WorkerBlobStorage implements BlobStorage { + constructor( + private readonly client: OpClient, + private readonly options: StorageOptions + ) {} + + readonly storageType = 'blob'; + readonly peer = this.options.peer; + readonly spaceType = this.options.type; + readonly spaceId = this.options.id; + readonly universalId = universalId(this.options); + + get(key: string, _signal?: AbortSignal): Promise { + return this.client.call('blobStorage.getBlob', key); + } + set(blob: BlobRecord, _signal?: AbortSignal): Promise { + return this.client.call('blobStorage.setBlob', blob); + } + + delete( + key: string, + permanently: boolean, + _signal?: AbortSignal + ): Promise { + return this.client.call('blobStorage.deleteBlob', { key, permanently }); + } + + release(_signal?: AbortSignal): Promise { + return this.client.call('blobStorage.releaseBlobs'); + } + + list(_signal?: AbortSignal): Promise { + return this.client.call('blobStorage.listBlobs'); + } + + connection = new DummyConnection(); +} + +class WorkerAwarenessStorage implements AwarenessStorage { + constructor( + private readonly client: OpClient, + private readonly options: StorageOptions + ) {} + + readonly storageType = 'awareness'; + readonly peer = this.options.peer; + readonly spaceType = this.options.type; + readonly spaceId = this.options.id; + readonly universalId = universalId(this.options); + + update(record: AwarenessRecord, origin?: string): Promise { + return this.client.call('awarenessStorage.update', { + awareness: record, + origin, + }); + } + subscribeUpdate( + id: string, + onUpdate: (update: AwarenessRecord, origin?: string) => void, + onCollect: () => Promise + ): () => void { + const subscription = this.client + .ob$('awarenessStorage.subscribeUpdate', id) + .subscribe({ + next: update => { + if (update.type === 'awareness-update') { + onUpdate(update.awareness, update.origin); + } + if (update.type === 'awareness-collect') { + onCollect() + .then(record => { + if (record) { + this.client + .call('awarenessStorage.collect', { + awareness: record, + collectId: update.collectId, + }) + .catch(err => { + console.error('error feedback collected awareness', err); + }); + } + }) + .catch(err => { + console.error('error collecting awareness', err); + }); + } + }, + }); + return () => { + subscription.unsubscribe(); + }; + } + connection = new DummyConnection(); +} + +class WorkerDocSync implements DocSync { + constructor(private readonly client: OpClient) {} + + readonly state$ = this.client.ob$('docSync.state'); + + docState$(docId: string) { + return this.client.ob$('docSync.docState', docId); + } + + addPriority(docId: string, priority: number) { + const subscription = this.client + .ob$('docSync.addPriority', { docId, priority }) + .subscribe(); + return () => { + subscription.unsubscribe(); + }; + } +} + +class WorkerBlobSync implements BlobSync { + constructor(private readonly client: OpClient) {} + downloadBlob( + blobId: string, + _signal?: AbortSignal + ): Promise { + return this.client.call('blobSync.downloadBlob', blobId); + } + uploadBlob(blob: BlobRecord, _signal?: AbortSignal): Promise { + return this.client.call('blobSync.uploadBlob', blob); + } +} + +class WorkerAwarenessSync implements AwarenessSync { + constructor(private readonly client: OpClient) {} + + update(record: AwarenessRecord, origin?: string): Promise { + return this.client.call('awarenessSync.update', { + awareness: record, + origin, + }); + } + + subscribeUpdate( + id: string, + onUpdate: (update: AwarenessRecord, origin?: string) => void, + onCollect: () => Promise + ): () => void { + const subscription = this.client + .ob$('awarenessSync.subscribeUpdate', id) + .subscribe({ + next: update => { + if (update.type === 'awareness-update') { + onUpdate(update.awareness, update.origin); + } + if (update.type === 'awareness-collect') { + onCollect() + .then(record => { + if (record) { + this.client + .call('awarenessSync.collect', { + awareness: record, + collectId: update.collectId, + }) + .catch(err => { + console.error('error feedback collected awareness', err); + }); + } + }) + .catch(err => { + console.error('error collecting awareness', err); + }); + } + }, + }); + return () => { + subscription.unsubscribe(); + }; + } +} diff --git a/packages/common/nbstore/src/worker/consumer.ts b/packages/common/nbstore/src/worker/consumer.ts new file mode 100644 index 0000000000000..f83e94f435304 --- /dev/null +++ b/packages/common/nbstore/src/worker/consumer.ts @@ -0,0 +1,256 @@ +import type { OpConsumer } from '@toeverything/infra/op'; +import { Observable } from 'rxjs'; + +import { getAvailableStorageImplementations } from '../impls'; +import { SpaceStorage, type StorageOptions } from '../storage'; +import type { AwarenessRecord } from '../storage/awareness'; +import { Sync } from '../sync'; +import type { WorkerOps } from './ops'; + +export class WorkerConsumer { + private remotes: SpaceStorage[] = []; + private local: SpaceStorage | null = null; + private sync: Sync | null = null; + + get ensureLocal() { + if (!this.local) { + throw new Error('Not initialized'); + } + return this.local; + } + + get ensureSync() { + if (!this.sync) { + throw new Error('Not initialized'); + } + return this.sync; + } + + get docStorage() { + return this.ensureLocal.get('doc'); + } + + get docSync() { + const docSync = this.ensureSync.doc; + if (!docSync) { + throw new Error('Doc sync not initialized'); + } + return docSync; + } + + get blobStorage() { + return this.ensureLocal.get('blob'); + } + + get blobSync() { + const blobSync = this.ensureSync.blob; + if (!blobSync) { + throw new Error('Blob sync not initialized'); + } + return blobSync; + } + + get syncStorage() { + return this.ensureLocal.get('sync'); + } + + get awarenessStorage() { + return this.ensureLocal.get('awareness'); + } + + get awarenessSync() { + const awarenessSync = this.ensureSync.awareness; + if (!awarenessSync) { + throw new Error('Awareness sync not initialized'); + } + return awarenessSync; + } + + constructor(private readonly consumer: OpConsumer) {} + + listen() { + this.registerHandlers(); + this.consumer.listen(); + } + + async init(init: { + local: { name: string; opts: StorageOptions }[]; + remotes: { name: string; opts: StorageOptions }[][]; + }) { + this.local = new SpaceStorage( + init.local.map(opt => { + const Storage = getAvailableStorageImplementations(opt.name); + return new Storage(opt.opts); + }) + ); + this.remotes = init.remotes.map(opts => { + return new SpaceStorage( + opts.map(opt => { + const Storage = getAvailableStorageImplementations(opt.name); + return new Storage(opt.opts); + }) + ); + }); + this.sync = new Sync(this.local, this.remotes); + this.local.connect(); + for (const remote of this.remotes) { + remote.connect(); + } + this.sync.start(); + } + + async destroy() { + this.sync?.stop(); + this.local?.disconnect(); + await this.local?.destroy(); + for (const remote of this.remotes) { + remote.disconnect(); + await remote.destroy(); + } + } + + private registerHandlers() { + const collectJobs = new Map< + string, + (awareness: AwarenessRecord | null) => void + >(); + let collectId = 0; + this.consumer.registerAll({ + 'worker.init': this.init.bind(this), + 'worker.destroy': this.destroy.bind(this), + 'docStorage.getDoc': (docId: string) => this.docStorage.getDoc(docId), + 'docStorage.getDocDiff': ({ docId, state }) => + this.docStorage.getDocDiff(docId, state), + 'docStorage.pushDocUpdate': ({ update, origin }) => + this.docStorage.pushDocUpdate(update, origin), + 'docStorage.getDocTimestamps': after => + this.docStorage.getDocTimestamps(after ?? undefined), + 'docStorage.getDocTimestamp': docId => + this.docStorage.getDocTimestamp(docId), + 'docStorage.deleteDoc': (docId: string) => + this.docStorage.deleteDoc(docId), + 'docStorage.subscribeDocUpdate': () => + new Observable(subscriber => { + return this.docStorage.subscribeDocUpdate((update, origin) => { + subscriber.next({ update, origin }); + }); + }), + 'docStorage.waitForConnected': () => + new Observable(subscriber => { + const abortController = new AbortController(); + this.docStorage.connection + .waitForConnected(abortController.signal) + .then(() => { + subscriber.next(true); + subscriber.complete(); + }) + .catch(error => { + subscriber.error(error); + }); + return () => abortController.abort(); + }), + 'blobStorage.getBlob': key => this.blobStorage.get(key), + 'blobStorage.setBlob': blob => this.blobStorage.set(blob), + 'blobStorage.deleteBlob': ({ key, permanently }) => + this.blobStorage.delete(key, permanently), + 'blobStorage.releaseBlobs': () => this.blobStorage.release(), + 'blobStorage.listBlobs': () => this.blobStorage.list(), + 'syncStorage.clearClocks': () => this.syncStorage.clearClocks(), + 'syncStorage.getPeerPulledRemoteClock': ({ peer, docId }) => + this.syncStorage.getPeerPulledRemoteClock(peer, docId), + 'syncStorage.getPeerPulledRemoteClocks': ({ peer }) => + this.syncStorage.getPeerPulledRemoteClocks(peer), + 'syncStorage.setPeerPulledRemoteClock': ({ peer, clock }) => + this.syncStorage.setPeerPulledRemoteClock(peer, clock), + 'syncStorage.getPeerRemoteClock': ({ peer, docId }) => + this.syncStorage.getPeerRemoteClock(peer, docId), + 'syncStorage.getPeerRemoteClocks': ({ peer }) => + this.syncStorage.getPeerRemoteClocks(peer), + 'syncStorage.setPeerRemoteClock': ({ peer, clock }) => + this.syncStorage.setPeerRemoteClock(peer, clock), + 'syncStorage.getPeerPushedClock': ({ peer, docId }) => + this.syncStorage.getPeerPushedClock(peer, docId), + 'syncStorage.getPeerPushedClocks': ({ peer }) => + this.syncStorage.getPeerPushedClocks(peer), + 'syncStorage.setPeerPushedClock': ({ peer, clock }) => + this.syncStorage.setPeerPushedClock(peer, clock), + 'awarenessStorage.update': ({ awareness, origin }) => + this.awarenessStorage.update(awareness, origin), + 'awarenessStorage.subscribeUpdate': docId => + new Observable(subscriber => { + return this.awarenessStorage.subscribeUpdate( + docId, + (update, origin) => { + subscriber.next({ + type: 'awareness-update', + awareness: update, + origin, + }); + }, + () => { + const currentCollectId = collectId++; + const promise = new Promise(resolve => { + collectJobs.set(currentCollectId.toString(), awareness => { + resolve(awareness); + collectJobs.delete(currentCollectId.toString()); + }); + }); + return promise; + } + ); + }), + 'awarenessStorage.collect': ({ collectId, awareness }) => + collectJobs.get(collectId)?.(awareness), + 'docSync.state': () => + new Observable(subscriber => { + const subscription = this.docSync.state$.subscribe(state => { + subscriber.next(state); + }); + return () => subscription.unsubscribe(); + }), + 'docSync.docState': docId => + new Observable(subscriber => { + const subscription = this.docSync + .docState$(docId) + .subscribe(state => { + subscriber.next(state); + }); + return () => subscription.unsubscribe(); + }), + 'docSync.addPriority': ({ docId, priority }) => + new Observable(() => { + const undo = this.docSync.addPriority(docId, priority); + return () => undo(); + }), + 'blobSync.downloadBlob': key => this.blobSync.downloadBlob(key), + 'blobSync.uploadBlob': blob => this.blobSync.uploadBlob(blob), + 'awarenessSync.update': ({ awareness, origin }) => + this.awarenessSync.update(awareness, origin), + 'awarenessSync.subscribeUpdate': docId => + new Observable(subscriber => { + return this.awarenessStorage.subscribeUpdate( + docId, + (update, origin) => { + subscriber.next({ + type: 'awareness-update', + awareness: update, + origin, + }); + }, + () => { + const currentCollectId = collectId++; + const promise = new Promise(resolve => { + collectJobs.set(currentCollectId.toString(), awareness => { + resolve(awareness); + collectJobs.delete(currentCollectId.toString()); + }); + }); + return promise; + } + ); + }), + 'awarenessSync.collect': ({ collectId, awareness }) => + collectJobs.get(collectId)?.(awareness), + }); + } +} diff --git a/packages/common/nbstore/src/worker/ops.ts b/packages/common/nbstore/src/worker/ops.ts new file mode 100644 index 0000000000000..aabf7c2889a4f --- /dev/null +++ b/packages/common/nbstore/src/worker/ops.ts @@ -0,0 +1,122 @@ +import type { + BlobRecord, + DocClock, + DocClocks, + DocDiff, + DocRecord, + DocUpdate, + ListedBlobRecord, + StorageOptions, +} from '../storage'; +import type { AwarenessRecord } from '../storage/awareness'; +import type { DocSyncDocState, DocSyncState } from '../sync/doc'; + +interface GroupedWorkerOps { + worker: { + init: [ + { + local: { name: string; opts: StorageOptions }[]; + remotes: { name: string; opts: StorageOptions }[][]; + }, + void, + ]; + destroy: [void, void]; + }; + + docStorage: { + getDoc: [string, DocRecord | null]; + getDocDiff: [{ docId: string; state?: Uint8Array }, DocDiff | null]; + pushDocUpdate: [{ update: DocUpdate; origin?: string }, DocClock]; + getDocTimestamps: [Date | null, DocClocks]; + getDocTimestamp: [string, DocClock | null]; + deleteDoc: [string, void]; + subscribeDocUpdate: [void, { update: DocRecord; origin?: string }]; + waitForConnected: [void, boolean]; + }; + + blobStorage: { + getBlob: [string, BlobRecord | null]; + setBlob: [BlobRecord, void]; + deleteBlob: [{ key: string; permanently: boolean }, void]; + releaseBlobs: [void, void]; + listBlobs: [void, ListedBlobRecord[]]; + }; + + syncStorage: { + getPeerPulledRemoteClocks: [{ peer: string }, DocClocks]; + getPeerPulledRemoteClock: [ + { peer: string; docId: string }, + DocClock | null, + ]; + setPeerPulledRemoteClock: [{ peer: string; clock: DocClock }, void]; + getPeerRemoteClocks: [{ peer: string }, DocClocks]; + getPeerRemoteClock: [{ peer: string; docId: string }, DocClock | null]; + setPeerRemoteClock: [{ peer: string; clock: DocClock }, void]; + getPeerPushedClocks: [{ peer: string }, DocClocks]; + getPeerPushedClock: [{ peer: string; docId: string }, DocClock | null]; + setPeerPushedClock: [{ peer: string; clock: DocClock }, void]; + clearClocks: [void, void]; + }; + + awarenessStorage: { + update: [{ awareness: AwarenessRecord; origin?: string }, void]; + subscribeUpdate: [ + string, + ( + | { + type: 'awareness-update'; + awareness: AwarenessRecord; + origin?: string; + } + | { type: 'awareness-collect'; collectId: string } + ), + ]; + collect: [{ collectId: string; awareness: AwarenessRecord }, void]; + }; + + docSync: { + state: [void, DocSyncState]; + docState: [string, DocSyncDocState]; + addPriority: [{ docId: string; priority: number }, boolean]; + }; + + blobSync: { + downloadBlob: [string, BlobRecord | null]; + uploadBlob: [BlobRecord, void]; + }; + + awarenessSync: { + update: [{ awareness: AwarenessRecord; origin?: string }, void]; + subscribeUpdate: [ + string, + ( + | { + type: 'awareness-update'; + awareness: AwarenessRecord; + origin?: string; + } + | { type: 'awareness-collect'; collectId: string } + ), + ]; + collect: [{ collectId: string; awareness: AwarenessRecord }, void]; + }; +} + +type Values = T extends { [k in keyof T]: any } ? T[keyof T] : never; +type UnionToIntersection = (U extends any ? (x: U) => void : never) extends ( + x: infer I +) => void + ? I + : never; + +export type WorkerOps = UnionToIntersection< + Values< + Values<{ + [k in keyof GroupedWorkerOps]: { + [k2 in keyof GroupedWorkerOps[k]]: k2 extends string + ? Record<`${k}.${k2}`, GroupedWorkerOps[k][k2]> + : never; + }; + }> + > +>; diff --git a/packages/frontend/apps/electron/src/helper/nbstore/blob.ts b/packages/frontend/apps/electron/src/helper/nbstore/blob.ts index 6e41097b45dc6..c1e0641db9bf0 100644 --- a/packages/frontend/apps/electron/src/helper/nbstore/blob.ts +++ b/packages/frontend/apps/electron/src/helper/nbstore/blob.ts @@ -1,8 +1,8 @@ -import { type BlobRecord, BlobStorage, share } from '@affine/nbstore'; +import { type BlobRecord, BlobStorageBase, share } from '@affine/nbstore'; import { NativeDBConnection } from './db'; -export class SqliteBlobStorage extends BlobStorage { +export class SqliteBlobStorage extends BlobStorageBase { override connection = share( new NativeDBConnection(this.peer, this.spaceType, this.spaceId) ); diff --git a/packages/frontend/apps/electron/src/helper/nbstore/db.ts b/packages/frontend/apps/electron/src/helper/nbstore/db.ts index af03271a746f7..d5edb4c33326b 100644 --- a/packages/frontend/apps/electron/src/helper/nbstore/db.ts +++ b/packages/frontend/apps/electron/src/helper/nbstore/db.ts @@ -1,13 +1,13 @@ import path from 'node:path'; import { DocStorage as NativeDocStorage } from '@affine/native'; -import { Connection, type SpaceType } from '@affine/nbstore'; +import { AutoReconnectConnection, type SpaceType } from '@affine/nbstore'; import fs from 'fs-extra'; import { logger } from '../logger'; import { getSpaceDBPath } from '../workspace/meta'; -export class NativeDBConnection extends Connection { +export class NativeDBConnection extends AutoReconnectConnection { constructor( private readonly peer: string, private readonly type: SpaceType, diff --git a/packages/frontend/apps/electron/src/helper/nbstore/doc.ts b/packages/frontend/apps/electron/src/helper/nbstore/doc.ts index 016ef9efd610d..4078f50513d1d 100644 --- a/packages/frontend/apps/electron/src/helper/nbstore/doc.ts +++ b/packages/frontend/apps/electron/src/helper/nbstore/doc.ts @@ -1,14 +1,14 @@ import { type DocClocks, type DocRecord, - DocStorage, + DocStorageBase, type DocUpdate, share, } from '@affine/nbstore'; import { NativeDBConnection } from './db'; -export class SqliteDocStorage extends DocStorage { +export class SqliteDocStorage extends DocStorageBase { override connection = share( new NativeDBConnection(this.peer, this.spaceType, this.spaceId) ); diff --git a/packages/frontend/apps/electron/src/helper/nbstore/sync.ts b/packages/frontend/apps/electron/src/helper/nbstore/sync.ts index 2ffcf259e9153..2942371b59be2 100644 --- a/packages/frontend/apps/electron/src/helper/nbstore/sync.ts +++ b/packages/frontend/apps/electron/src/helper/nbstore/sync.ts @@ -1,13 +1,13 @@ import { + BasicSyncStorage, type DocClock, type DocClocks, share, - SyncStorage, } from '@affine/nbstore'; import { NativeDBConnection } from './db'; -export class SqliteSyncStorage extends SyncStorage { +export class SqliteSyncStorage extends BasicSyncStorage { override connection = share( new NativeDBConnection(this.peer, this.spaceType, this.spaceId) ); From bdbefd3e2803623a2f5b6bab42fbad8afb0049bd Mon Sep 17 00:00:00 2001 From: pengx17 Date: Fri, 20 Dec 2024 09:06:46 +0000 Subject: [PATCH 02/17] fix(core): backlinks styles (#9126) fix AF-1946 --- .../bi-directional-link-panel.css.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-editor/bi-directional-link-panel.css.ts b/packages/frontend/core/src/components/blocksuite/block-suite-editor/bi-directional-link-panel.css.ts index 2c185be276cbf..330ce6336aa30 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-editor/bi-directional-link-panel.css.ts +++ b/packages/frontend/core/src/components/blocksuite/block-suite-editor/bi-directional-link-panel.css.ts @@ -79,6 +79,10 @@ export const link = style({ alignItems: 'center', gap: '4px', whiteSpace: 'nowrap', + borderRadius: '4px', + ':hover': { + backgroundColor: cssVarV2('layer/background/hoverOverlay'), + }, }); globalStyle(`${link} .affine-reference-title`, { @@ -88,19 +92,22 @@ globalStyle(`${link} .affine-reference-title`, { export const linkPreviewContainer = style({ display: 'flex', flexDirection: 'column', + gap: '12px', + marginTop: '4px', + marginBottom: '16px', }); export const linkPreview = style({ - border: `1px solid ${cssVarV2('layer/insideBorder/border')}`, + border: `0.5px solid ${cssVarV2('backlinks/blockBorder')}`, borderRadius: '8px', padding: '8px', - marginBottom: '8px', color: cssVarV2('text/primary'), vars: { [cssVar('fontFamily')]: cssVar('fontSansFamily'), }, + backgroundColor: cssVarV2('backlinks/blockBackgroundColor'), ':hover': { - backgroundColor: cssVarV2('layer/background/hoverOverlay'), + backgroundColor: cssVarV2('backlinks/blockHover'), }, }); From f788fdd0a410b47e3158859c6f152e23018a77a1 Mon Sep 17 00:00:00 2001 From: EYHN Date: Fri, 20 Dec 2024 09:31:52 +0000 Subject: [PATCH 03/17] fix(core): revalidate quota and subscription when subscribing (#9220) --- .../quota-reached-modal/cloud-quota-modal.tsx | 28 +++++++++++++------ .../general-setting/plans/checkout-slot.tsx | 27 +++++++++--------- .../members/cloud-members-panel.tsx | 2 +- .../workspace-quota.tsx | 2 +- .../core/src/modules/quota/entities/quota.ts | 16 ++++++++--- 5 files changed, 47 insertions(+), 28 deletions(-) diff --git a/packages/frontend/core/src/components/affine/quota-reached-modal/cloud-quota-modal.tsx b/packages/frontend/core/src/components/affine/quota-reached-modal/cloud-quota-modal.tsx index aa8a649c4e68c..de300bb9a4910 100644 --- a/packages/frontend/core/src/components/affine/quota-reached-modal/cloud-quota-modal.tsx +++ b/packages/frontend/core/src/components/affine/quota-reached-modal/cloud-quota-modal.tsx @@ -7,10 +7,10 @@ import { WorkspaceQuotaService } from '@affine/core/modules/quota'; import { type I18nString, useI18n } from '@affine/i18n'; import { track } from '@affine/track'; import { useLiveData, useService, WorkspaceService } from '@toeverything/infra'; -import bytes from 'bytes'; import { useAtom } from 'jotai'; import { useCallback, useEffect, useMemo } from 'react'; +import { useAsyncCallback } from '../../hooks/affine-async-hooks'; import * as styles from './cloud-quota-modal.css'; export const CloudQuotaModal = () => { @@ -66,22 +66,32 @@ export const CloudQuotaModal = () => { } }, [userQuota, isOwner, workspaceQuota, t]); + const onAbortLargeBlob = useAsyncCallback( + async (blob: Blob) => { + // wait for quota revalidation + await workspaceQuotaService.quota.waitForRevalidation(); + if ( + blob.size > (workspaceQuotaService.quota.quota$.value?.blobLimit ?? 0) + ) { + setOpen(true); + } + }, + [setOpen, workspaceQuotaService] + ); + useEffect(() => { if (!workspaceQuota) { return; } - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - currentWorkspace.engine.blob.singleBlobSizeLimit = bytes.parse( - workspaceQuota.blobLimit.toString() - )!; - const disposable = currentWorkspace.engine.blob.onAbortLargeBlob.on(() => { - setOpen(true); - }); + currentWorkspace.engine.blob.singleBlobSizeLimit = workspaceQuota.blobLimit; + + const disposable = + currentWorkspace.engine.blob.onAbortLargeBlob.on(onAbortLargeBlob); return () => { disposable?.dispose(); }; - }, [currentWorkspace.engine.blob, setOpen, workspaceQuota]); + }, [currentWorkspace.engine.blob, onAbortLargeBlob, workspaceQuota]); return ( { + subscriptionService.subscription.revalidate(); + userQuotaService.quota.revalidate(); + }, [subscriptionService, userQuotaService]); - useEffect(() => { - subscriptionService.prices.revalidate(); - }, [subscriptionService]); useEffect(() => { if (isOpenedExternalWindow) { // when the external window is opened, revalidate the subscription when window get focus - window.addEventListener( - 'focus', - subscriptionService.subscription.revalidate - ); + window.addEventListener('focus', revalidate); return () => { - window.removeEventListener( - 'focus', - subscriptionService.subscription.revalidate - ); + window.removeEventListener('focus', revalidate); }; } return; - }, [isOpenedExternalWindow, subscriptionService]); + }, [isOpenedExternalWindow, revalidate, subscriptionService]); const subscribe = useAsyncCallback(async () => { setMutating(true); diff --git a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/members/cloud-members-panel.tsx b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/members/cloud-members-panel.tsx index e0890e1e3f72e..57fdd7a573b48 100644 --- a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/members/cloud-members-panel.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/members/cloud-members-panel.tsx @@ -75,7 +75,7 @@ export const CloudWorkspaceMembersPanel = ({ useEffect(() => { workspaceQuotaService.quota.revalidate(); }, [workspaceQuotaService]); - const isLoading = useLiveData(workspaceQuotaService.quota.isLoading$); + const isLoading = useLiveData(workspaceQuotaService.quota.isRevalidating$); const error = useLiveData(workspaceQuotaService.quota.error$); const workspaceQuota = useLiveData(workspaceQuotaService.quota.quota$); const subscriptionService = useService(SubscriptionService); diff --git a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/workspace-quota.tsx b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/workspace-quota.tsx index db53628eaf40b..bca196db92384 100644 --- a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/workspace-quota.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/workspace-quota.tsx @@ -29,7 +29,7 @@ export const StorageProgress = () => { ).permission; const workspaceQuotaService = useService(WorkspaceQuotaService).quota; const isTeam = useLiveData(workspacePermissionService.isTeam$); - const isLoading = useLiveData(workspaceQuotaService.isLoading$); + const isLoading = useLiveData(workspaceQuotaService.isRevalidating$); const usedFormatted = useLiveData(workspaceQuotaService.usedFormatted$); const maxFormatted = useLiveData(workspaceQuotaService.maxFormatted$); const percent = useLiveData(workspaceQuotaService.percent$); diff --git a/packages/frontend/core/src/modules/quota/entities/quota.ts b/packages/frontend/core/src/modules/quota/entities/quota.ts index 8b8d763afaa51..6f2ac774fec5c 100644 --- a/packages/frontend/core/src/modules/quota/entities/quota.ts +++ b/packages/frontend/core/src/modules/quota/entities/quota.ts @@ -25,7 +25,7 @@ const logger = new DebugLogger('affine:workspace-permission'); export class WorkspaceQuota extends Entity { quota$ = new LiveData(null); - isLoading$ = new LiveData(false); + isRevalidating$ = new LiveData(false); error$ = new LiveData(null); /** Used storage in bytes */ @@ -106,18 +106,26 @@ export class WorkspaceQuota extends Entity { catchErrorInto(this.error$, error => { logger.error('Failed to fetch workspace quota', error); }), - onStart(() => this.isLoading$.setValue(true)), - onComplete(() => this.isLoading$.setValue(false)) + onStart(() => this.isRevalidating$.setValue(true)), + onComplete(() => this.isRevalidating$.setValue(false)) ); } ) ); + waitForRevalidation(signal?: AbortSignal) { + this.revalidate(); + return this.isRevalidating$.waitFor( + isRevalidating => !isRevalidating, + signal + ); + } + reset() { this.quota$.next(null); this.used$.next(null); this.error$.next(null); - this.isLoading$.next(false); + this.isRevalidating$.next(false); } override dispose(): void { From bfcc53dc1f4e4e7291f8b36c32f5e6593d027763 Mon Sep 17 00:00:00 2001 From: Saul-Mirone Date: Fri, 20 Dec 2024 11:08:21 +0000 Subject: [PATCH 04/17] chore: migrate blocksuite test (#9222) --- .github/workflows/build-test.yml | 32 + .prettierignore | 4 +- blocksuite/affine/all/vitest.config.ts | 2 +- .../affine/block-embed/vitest.config.ts | 2 +- blocksuite/affine/block-list/vitest.config.ts | 2 +- .../affine/block-paragraph/vitest.config.ts | 2 +- .../affine/block-surface/vitest.config.ts | 2 +- blocksuite/affine/components/vitest.config.ts | 2 +- blocksuite/affine/data-view/vitest.config.ts | 2 +- blocksuite/affine/model/vitest.config.ts | 2 +- blocksuite/affine/shared/vitest.config.ts | 2 +- .../widget-scroll-anchoring/vitest.config.ts | 2 +- blocksuite/blocks/vitest.config.ts | 2 +- blocksuite/framework/block-std/package.json | 1 + blocksuite/playground/package.json | 4 +- .../src/__tests__/main/snapshot.spec.ts | 6 +- blocksuite/tests-legacy/attachment.spec.ts | 769 +++++++ blocksuite/tests-legacy/basic.spec.ts | 590 +++++ blocksuite/tests-legacy/bookmark.spec.ts | 461 ++++ .../tests-legacy/clipboard/clipboard.spec.ts | 409 ++++ .../tests-legacy/clipboard/image.spec.ts | 58 + .../tests-legacy/clipboard/list.spec.ts | 714 ++++++ .../tests-legacy/clipboard/markdown.spec.ts | 170 ++ .../tests-legacy/code/copy-paste.spec.ts | 149 ++ blocksuite/tests-legacy/code/crud.spec.ts | 668 ++++++ blocksuite/tests-legacy/code/readonly.spec.ts | 59 + .../tests-legacy/code/selections.spec.ts | 195 ++ blocksuite/tests-legacy/code/utils.ts | 61 + blocksuite/tests-legacy/database/actions.ts | 588 +++++ .../tests-legacy/database/clipboard.spec.ts | 176 ++ .../tests-legacy/database/column.spec.ts | 599 +++++ .../tests-legacy/database/database.spec.ts | 670 ++++++ .../tests-legacy/database/selection.spec.ts | 567 +++++ blocksuite/tests-legacy/database/sort.spec.ts | 112 + .../tests-legacy/database/statistics.spec.ts | 103 + .../tests-legacy/database/title.spec.ts | 19 + blocksuite/tests-legacy/drag.spec.ts | 768 +++++++ .../tests-legacy/edgeless/align.spec.ts | 435 ++++ .../edgeless/auto-complete.spec.ts | 244 +++ .../edgeless/auto-connect.spec.ts | 180 ++ .../tests-legacy/edgeless/basic.spec.ts | 358 +++ .../tests-legacy/edgeless/brush.spec.ts | 193 ++ .../tests-legacy/edgeless/clipboard.spec.ts | 235 ++ .../edgeless/color-picker.spec.ts | 368 ++++ .../edgeless/connector/clipboard.spec.ts | 142 ++ .../edgeless/connector/connector.spec.ts | 320 +++ .../edgeless/connector/elbow.spec.ts | 206 ++ .../edgeless/connector/group.spec.ts | 107 + .../edgeless/connector/label.spec.ts | 334 +++ .../edgeless/edgeless-text.spec.ts | 596 +++++ .../edgeless/element-toolbar.spec.ts | 88 + .../tests-legacy/edgeless/eraser.spec.ts | 49 + .../edgeless/frame/clipboard.spec.ts | 157 ++ .../edgeless/frame/frame-mindmap.spec.ts | 224 ++ .../edgeless/frame/frame-title.spec.ts | 158 ++ .../tests-legacy/edgeless/frame/frame.spec.ts | 414 ++++ .../tests-legacy/edgeless/frame/layer.spec.ts | 64 + .../edgeless/frame/selection.spec.ts | 158 ++ .../edgeless/group/clipboard.spec.ts | 152 ++ .../edgeless/group/group-and-ungroup.spec.ts | 123 ++ .../tests-legacy/edgeless/group/group.spec.ts | 260 +++ .../edgeless/group/release.spec.ts | 116 + .../tests-legacy/edgeless/group/title.spec.ts | 72 + .../tests-legacy/edgeless/lasso.spec.ts | 262 +++ .../tests-legacy/edgeless/linked-doc.spec.ts | 354 +++ blocksuite/tests-legacy/edgeless/lock.spec.ts | 559 +++++ .../tests-legacy/edgeless/mindmap.spec.ts | 126 ++ .../edgeless/note/drag-handle.spec.ts | 155 ++ .../tests-legacy/edgeless/note/mode.spec.ts | 80 + .../tests-legacy/edgeless/note/note.spec.ts | 531 +++++ .../tests-legacy/edgeless/note/resize.spec.ts | 254 +++ .../tests-legacy/edgeless/note/scale.spec.ts | 146 ++ .../tests-legacy/edgeless/note/slicer.spec.ts | 156 ++ .../edgeless/note/undo-redo.spec.ts | 140 ++ blocksuite/tests-legacy/edgeless/pan.spec.ts | 263 +++ .../tests-legacy/edgeless/paste-block.spec.ts | 127 ++ .../edgeless/presentation.spec.ts | 249 +++ .../tests-legacy/edgeless/reordering.spec.ts | 448 ++++ .../tests-legacy/edgeless/resizing.spec.ts | 199 ++ .../tests-legacy/edgeless/rotation.spec.ts | 227 ++ .../edgeless/selection/connector.spec.ts | 94 + .../edgeless/selection/keyboard.spec.ts | 265 +++ .../edgeless/selection/selection.spec.ts | 466 ++++ .../tests-legacy/edgeless/shape.spec.ts | 727 +++++++ .../tests-legacy/edgeless/shortcut.spec.ts | 324 +++ blocksuite/tests-legacy/edgeless/snap.spec.ts | 45 + blocksuite/tests-legacy/edgeless/text.spec.ts | 316 +++ .../tests-legacy/embed-synced-doc.spec.ts | 268 +++ blocksuite/tests-legacy/fixtures/smile.png | Bin 0 -> 96 bytes blocksuite/tests-legacy/format-bar.spec.ts | 1027 +++++++++ .../fragments/frame-panel.spec.ts | 356 +++ .../fragments/outline/outline-panel.spec.ts | 361 ++++ .../fragments/outline/toc-viewer.spec.ts | 208 ++ .../tests-legacy/fragments/outline/utils.ts | 27 + .../tests-legacy/hotkey/bracket.spec.ts | 93 + blocksuite/tests-legacy/hotkey/hotkey.spec.ts | 474 ++++ .../tests-legacy/hotkey/multiline.spec.ts | 176 ++ blocksuite/tests-legacy/hotkey/title.spec.ts | 43 + blocksuite/tests-legacy/image/image.spec.ts | 154 ++ blocksuite/tests-legacy/image/keymap.spec.ts | 79 + blocksuite/tests-legacy/image/load.spec.ts | 168 ++ blocksuite/tests-legacy/image/menu.spec.ts | 90 + .../tests-legacy/inline/inline-editor.spec.ts | 1013 +++++++++ blocksuite/tests-legacy/latex/block.spec.ts | 62 + blocksuite/tests-legacy/latex/inline.spec.ts | 337 +++ blocksuite/tests-legacy/link.spec.ts | 543 +++++ blocksuite/tests-legacy/linked-page.spec.ts | 1220 +++++++++++ blocksuite/tests-legacy/list.spec.ts | 842 ++++++++ blocksuite/tests-legacy/markdown.spec.ts | 535 +++++ .../multiple-editors/edgeless.spec.ts | 43 + .../multiple-editors/selection.spec.ts | 33 + blocksuite/tests-legacy/package.json | 20 + blocksuite/tests-legacy/paragraph.spec.ts | 1334 ++++++++++++ blocksuite/tests-legacy/playwright.config.ts | 46 + blocksuite/tests-legacy/quote.spec.ts | 102 + .../tests-legacy/selection/block.spec.ts | 1425 ++++++++++++ .../tests-legacy/selection/native.spec.ts | 1788 +++++++++++++++ blocksuite/tests-legacy/slash-menu.spec.ts | 990 +++++++++ .../automatic-identify-url-text-final.json | 66 + .../basic.spec.ts/basic-test-default.json | 57 + ...opy-bookmark-url-by-copy-button-final.json | 80 + ...reate-bookmark-in-edgeless-mode-final.json | 87 + ...to-create-bookmark-in-page-mode-final.json | 77 + ...ert-bookmark-block-to-link-text-final.json | 60 + .../create-bookmark-by-slash-menu-final.json | 58 + .../bookmark.spec.ts/embed-figma.json | 56 + .../bookmark.spec.ts/embed-youtube.json | 61 + .../bookmark.spec.ts/horizontal-figma.json | 58 + .../bookmark.spec.ts/horizontal-youtube.json | 58 + ...rk-block-directly-after-add-paragraph.json | 113 + ...ng-bookmark-block-directly-after-drag.json | 113 + ...dragging-bookmark-block-directly-init.json | 56 + .../auto-identify-url-final.json | 63 + ...clipboard-copy-nested-items-clipboard.html | 9 + ...clipboard-copy-nested-items-clipboard.json | 49 + .../clipboard-copy-nested-items-clipboard.md | 2 + ...lipboard-copy-nested-items-clipboard2.html | 8 + ...lipboard-copy-nested-items-clipboard2.json | 48 + .../clipboard-copy-nested-items-clipboard2.md | 2 + ...ard-data-should-be-complete-clipboard.html | 11 + ...ard-data-should-be-complete-clipboard.json | 75 + ...board-data-should-be-complete-clipboard.md | 3 + ...-copy-will-reappear-content-after-cut.json | 55 + ...opy-will-reappear-content-after-paste.json | 123 ++ ...-block-except-type-text-after-paste-1.json | 57 + ...-block-except-type-text-after-paste-2.json | 76 + ...opy-menu-copy-whole-code-block-pasted.json | 78 + ...menu-copy-the-empty-code-block-pasted.json | 70 + .../delete-code-block-in-more-menu-final.json | 37 + .../duplicate-code-block-final.json | 78 + .../format-text-in-code-block-format.json | 85 + .../format-text-in-code-block-init.json | 58 + .../format-text-in-code-block-link.json | 96 + ...own-syntax-can-create-code-block-init.json | 97 + ...can-create-code-block-markdown-syntax.json | 112 + ...level-in-multi-level-nesting-drag-3-4.json | 187 ++ ...level-in-multi-level-nesting-drag-3-9.json | 187 ++ ...level-in-multi-level-nesting-drag-4-3.json | 187 ++ ...ach-level-in-multi-level-nesting-init.json | 187 ++ ...ultiple-blocks-to-nested-block-finial.json | 186 ++ ...-multiple-blocks-to-nested-block-init.json | 186 ++ ...-limit-for-embed-block-add-linked-doc.json | 110 + .../min-width-limit-for-embed-block-drag.json | 101 + .../min-width-limit-for-embed-block-init.json | 100 + ...or-embed-block-link-to-card-min-width.json | 101 + ...th-limit-for-embed-block-link-to-card.json | 101 + ...-line-when-edgeless-text-exist-finial.json | 108 + ...e-when-edgeless-text-exist-note-empty.json | 88 + ...en-edgeless-text-exist-note-not-empty.json | 104 + ...-from-block-selection-with-format-bar.json | 54 + ...change-background-color-default-color.json | 98 + ...-able-to-change-background-color-init.json | 98 + ...to-change-background-color-select-all.json | 101 + ...ge-to-heading-paragraph-type-bulleted.json | 97 + ...ange-to-heading-paragraph-type-finial.json | 95 + ...change-to-heading-paragraph-type-init.json | 95 + ...ick-bar-be-able-to-format-text-finial.json | 99 + ...quick-bar-be-able-to-format-text-init.json | 102 + ...text-when-select-multiple-line-finial.json | 95 + ...t-text-when-select-multiple-line-init.json | 104 + ...quick-bar-be-able-to-link-text-finial.json | 95 + ...t-quick-bar-be-able-to-link-text-init.json | 98 + ...-bar-show-after-convert-to-code-block.json | 58 + ...on-works-when-update-block-type-final.json | 95 + ...ion-works-when-update-block-type-init.json | 101 + ...-bar-work-in-multiple-block-selection.json | 107 + ...ck-bar-work-in-single-block-selection.json | 100 + ...ted-after-setting-heading-by-shortkey.json | 75 + ...ine-rich-text-inline-code-hotkey-init.json | 109 + ...ine-rich-text-inline-code-hotkey-redo.json | 109 + ...ine-rich-text-inline-code-hotkey-undo.json | 94 + .../should-cut-work-multiple-line-init.json | 56 + .../should-cut-work-multiple-line-undo.json | 94 + .../should-cut-work-single-line-init.json | 56 + .../should-cut-work-single-line-undo.json | 56 + .../should-hotkey-work-in-paragraph-init.json | 56 + ...ould-hotkey-work-in-paragraph-press-0.json | 56 + ...ould-hotkey-work-in-paragraph-press-6.json | 56 + ...ould-hotkey-work-in-paragraph-press-8.json | 58 + ...ould-hotkey-work-in-paragraph-press-9.json | 58 + ...ould-hotkey-work-in-paragraph-press-d.json | 79 + ...ltiple-line-format-hotkey-work-finial.json | 94 + ...multiple-line-format-hotkey-work-init.json | 118 + ...single-line-format-hotkey-work-finial.json | 56 + ...d-single-line-format-hotkey-work-init.json | 68 + ...formatted-cursor-with-hotkey-bold-ggg.json | 84 + ...formatted-cursor-with-hotkey-bold-hhh.json | 84 + ...use-formatted-cursor-with-hotkey-bold.json | 84 + ...use-formatted-cursor-with-hotkey-init.json | 78 + ...ket-complete-with-backtick-works-undo.json | 57 + ...-bracket-complete-with-backtick-works.json | 66 + ...ted-after-setting-heading-by-shortkey.json | 76 + .../should-cut-work-single-line-init.json | 57 + .../should-cut-work-single-line-undo.json | 57 + .../should-hotkey-work-in-paragraph-init.json | 57 + ...ould-hotkey-work-in-paragraph-press-0.json | 57 + ...ould-hotkey-work-in-paragraph-press-6.json | 57 + ...ould-hotkey-work-in-paragraph-press-8.json | 59 + ...ould-hotkey-work-in-paragraph-press-9.json | 59 + ...ould-hotkey-work-in-paragraph-press-d.json | 80 + ...single-line-format-hotkey-work-finial.json | 57 + ...d-single-line-format-hotkey-work-init.json | 69 + .../type-character-jump-out-code-node-1.json | 60 + .../type-character-jump-out-code-node-2.json | 63 + ...cursor-with-hotkey-at-empty-line-bold.json | 60 + ...formatted-cursor-with-hotkey-bold-ggg.json | 85 + ...formatted-cursor-with-hotkey-bold-hhh.json | 85 + ...use-formatted-cursor-with-hotkey-bold.json | 85 + ...use-formatted-cursor-with-hotkey-init.json | 79 + ...ine-rich-text-inline-code-hotkey-init.json | 110 + ...ine-rich-text-inline-code-hotkey-redo.json | 110 + ...ine-rich-text-inline-code-hotkey-undo.json | 95 + .../should-cut-work-multiple-line-init.json | 57 + .../should-cut-work-multiple-line-undo.json | 95 + ...ltiple-line-format-hotkey-work-finial.json | 95 + ...multiple-line-format-hotkey-work-init.json | 119 + ...g-markdown-shortcut-with-enter-finial.json | 68 + ...ing-markdown-shortcut-with-enter-init.json | 53 + ...g-markdown-shortcut-with-space-finial.json | 68 + ...ing-markdown-shortcut-with-space-init.json | 53 + ...d-latex-block-using-slash-menu-finial.json | 53 + ...add-latex-block-using-slash-menu-init.json | 53 + .../snapshots/link.spec.ts/basic-link.json | 60 + .../link.spec.ts/convert-link-to-card.json | 85 + ...can-create-linked-page-and-jump-final.json | 67 + .../can-create-linked-page-and-jump-init.json | 67 + ...nked-page-should-paste-as-linked-page.json | 88 + ...nked-page-should-paste-as-linked-page.json | 63 + ...uld-create-and-switch-page-work-final.json | 61 + ...ould-create-and-switch-page-work-init.json | 61 + ...c-indent-and-unindent-after-shift-tab.json | 76 + .../basic-indent-and-unindent-after-tab.json | 77 + .../basic-indent-and-unindent-init.json | 76 + ...ggle-in-readonly-mode-before-readonly.json | 102 + ...oggle-icon-should-collapsed-list-init.json | 102 + ...gle-icon-should-collapsed-list-toggle.json | 102 + ...onvert-nested-paragraph-to-list-final.json | 81 + ...convert-nested-paragraph-to-list-init.json | 77 + .../enter-list-block-with-empty-text-1.json | 90 + .../enter-list-block-with-empty-text-2.json | 90 + .../enter-list-block-with-empty-text-3.json | 88 + .../enter-list-block-with-empty-text-4.json | 90 + .../enter-list-block-with-empty-text-5.json | 88 + ...enter-list-block-with-empty-text-init.json | 89 + ...dent-item-should-expand-toggle-finial.json | 123 ++ ...indent-item-should-expand-toggle-init.json | 123 ++ ...dent-item-should-expand-toggle-toggle.json | 123 ++ .../nested-list-blocks-finial.json | 102 + .../list.spec.ts/nested-list-blocks-init.json | 103 + ...todo-block-preserve-todo-status-final.json | 78 + ...-todo-block-preserve-todo-status-init.json | 79 + ...ks-when-following-custom-blocks-final.json | 84 + ...cks-when-following-custom-blocks-init.json | 104 + ...in-line-start-after-press-backspace-2.json | 96 + ...in-line-start-after-press-backspace-3.json | 96 + ...e-in-line-start-after-press-backspace.json | 115 + ...-indent-and-delete-in-line-start-init.json | 135 ++ ...hild-block-should-work-at-enter-final.json | 96 + ...child-block-should-work-at-enter-init.json | 77 + ...hold-cursor-in-correct-position-final.json | 76 + ...-hold-cursor-in-correct-position-init.json | 77 + ...unindent-works-with-children-indent-2.json | 134 ++ ...unindent-works-with-children-indent-3.json | 135 ++ ...unindent-works-with-children-indent-4.json | 136 ++ ...d-unindent-works-with-children-indent.json | 134 ++ ...and-unindent-works-with-children-init.json | 133 ++ ...indent-works-with-children-unindent-1.json | 135 ++ ...indent-works-with-children-unindent-2.json | 135 ++ ...indent-works-with-children-unindent-3.json | 133 ++ ...or-should-insert-a-new-editable-block.json | 71 + .../should-indent-multi-selection-block.json | 96 + ...selected-blocks-when-entering-tab-key.json | 96 + ...-unindent-multi-selection-block-final.json | 95 + ...d-unindent-multi-selection-block-init.json | 96 + ...multi-selection-block-after-shift-tab.json | 114 + ...ative-multi-selection-block-after-tab.json | 115 + ...ge-delete-with-indent-after-backspace.json | 76 + ...e-range-delete-with-indent-after-redo.json | 76 + ...e-range-delete-with-indent-after-undo.json | 156 ++ .../native-range-delete-with-indent-init.json | 156 ++ ...-by-slash-menu-should-remove-children.json | 59 + blocksuite/tests-legacy/tsconfig.json | 15 + .../tests-legacy/utils/actions/block.ts | 25 + .../tests-legacy/utils/actions/click.ts | 130 ++ blocksuite/tests-legacy/utils/actions/drag.ts | 271 +++ .../tests-legacy/utils/actions/edgeless.ts | 1918 +++++++++++++++++ .../tests-legacy/utils/actions/index.ts | 7 + .../tests-legacy/utils/actions/keyboard.ts | 241 +++ .../tests-legacy/utils/actions/linked-doc.ts | 70 + blocksuite/tests-legacy/utils/actions/misc.ts | 1464 +++++++++++++ .../tests-legacy/utils/actions/selection.ts | 45 + blocksuite/tests-legacy/utils/asserts.ts | 1344 ++++++++++++ .../tests-legacy/utils/declare-test-window.ts | 48 + blocksuite/tests-legacy/utils/ignore.ts | 28 + .../tests-legacy/utils/inline-editor.ts | 26 + .../tests-legacy/utils/multiple-editor.ts | 15 + blocksuite/tests-legacy/utils/playwright.ts | 110 + blocksuite/tests-legacy/utils/query.ts | 125 ++ blocksuite/tests-legacy/worker.spec.ts | 33 + blocksuite/tests-legacy/zero-width.spec.ts | 145 ++ oxlint.json | 9 + package.json | 1 + scripts/vitest-global.js | 3 + vitest.workspace.ts | 6 +- yarn.lock | 111 +- 325 files changed, 55958 insertions(+), 26 deletions(-) create mode 100644 blocksuite/tests-legacy/attachment.spec.ts create mode 100644 blocksuite/tests-legacy/basic.spec.ts create mode 100644 blocksuite/tests-legacy/bookmark.spec.ts create mode 100644 blocksuite/tests-legacy/clipboard/clipboard.spec.ts create mode 100644 blocksuite/tests-legacy/clipboard/image.spec.ts create mode 100644 blocksuite/tests-legacy/clipboard/list.spec.ts create mode 100644 blocksuite/tests-legacy/clipboard/markdown.spec.ts create mode 100644 blocksuite/tests-legacy/code/copy-paste.spec.ts create mode 100644 blocksuite/tests-legacy/code/crud.spec.ts create mode 100644 blocksuite/tests-legacy/code/readonly.spec.ts create mode 100644 blocksuite/tests-legacy/code/selections.spec.ts create mode 100644 blocksuite/tests-legacy/code/utils.ts create mode 100644 blocksuite/tests-legacy/database/actions.ts create mode 100644 blocksuite/tests-legacy/database/clipboard.spec.ts create mode 100644 blocksuite/tests-legacy/database/column.spec.ts create mode 100644 blocksuite/tests-legacy/database/database.spec.ts create mode 100644 blocksuite/tests-legacy/database/selection.spec.ts create mode 100644 blocksuite/tests-legacy/database/sort.spec.ts create mode 100644 blocksuite/tests-legacy/database/statistics.spec.ts create mode 100644 blocksuite/tests-legacy/database/title.spec.ts create mode 100644 blocksuite/tests-legacy/drag.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/align.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/auto-complete.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/auto-connect.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/basic.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/brush.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/clipboard.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/color-picker.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/connector/clipboard.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/connector/connector.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/connector/elbow.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/connector/group.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/connector/label.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/edgeless-text.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/element-toolbar.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/eraser.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/frame/clipboard.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/frame/frame-mindmap.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/frame/frame-title.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/frame/frame.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/frame/layer.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/frame/selection.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/group/clipboard.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/group/group-and-ungroup.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/group/group.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/group/release.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/group/title.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/lasso.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/linked-doc.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/lock.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/mindmap.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/note/drag-handle.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/note/mode.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/note/note.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/note/resize.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/note/scale.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/note/slicer.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/note/undo-redo.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/pan.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/paste-block.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/presentation.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/reordering.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/resizing.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/rotation.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/selection/connector.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/selection/keyboard.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/selection/selection.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/shape.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/shortcut.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/snap.spec.ts create mode 100644 blocksuite/tests-legacy/edgeless/text.spec.ts create mode 100644 blocksuite/tests-legacy/embed-synced-doc.spec.ts create mode 100644 blocksuite/tests-legacy/fixtures/smile.png create mode 100644 blocksuite/tests-legacy/format-bar.spec.ts create mode 100644 blocksuite/tests-legacy/fragments/frame-panel.spec.ts create mode 100644 blocksuite/tests-legacy/fragments/outline/outline-panel.spec.ts create mode 100644 blocksuite/tests-legacy/fragments/outline/toc-viewer.spec.ts create mode 100644 blocksuite/tests-legacy/fragments/outline/utils.ts create mode 100644 blocksuite/tests-legacy/hotkey/bracket.spec.ts create mode 100644 blocksuite/tests-legacy/hotkey/hotkey.spec.ts create mode 100644 blocksuite/tests-legacy/hotkey/multiline.spec.ts create mode 100644 blocksuite/tests-legacy/hotkey/title.spec.ts create mode 100644 blocksuite/tests-legacy/image/image.spec.ts create mode 100644 blocksuite/tests-legacy/image/keymap.spec.ts create mode 100644 blocksuite/tests-legacy/image/load.spec.ts create mode 100644 blocksuite/tests-legacy/image/menu.spec.ts create mode 100644 blocksuite/tests-legacy/inline/inline-editor.spec.ts create mode 100644 blocksuite/tests-legacy/latex/block.spec.ts create mode 100644 blocksuite/tests-legacy/latex/inline.spec.ts create mode 100644 blocksuite/tests-legacy/link.spec.ts create mode 100644 blocksuite/tests-legacy/linked-page.spec.ts create mode 100644 blocksuite/tests-legacy/list.spec.ts create mode 100644 blocksuite/tests-legacy/markdown.spec.ts create mode 100644 blocksuite/tests-legacy/multiple-editors/edgeless.spec.ts create mode 100644 blocksuite/tests-legacy/multiple-editors/selection.spec.ts create mode 100644 blocksuite/tests-legacy/package.json create mode 100644 blocksuite/tests-legacy/paragraph.spec.ts create mode 100644 blocksuite/tests-legacy/playwright.config.ts create mode 100644 blocksuite/tests-legacy/quote.spec.ts create mode 100644 blocksuite/tests-legacy/selection/block.spec.ts create mode 100644 blocksuite/tests-legacy/selection/native.spec.ts create mode 100644 blocksuite/tests-legacy/slash-menu.spec.ts create mode 100644 blocksuite/tests-legacy/snapshots/basic.spec.ts/automatic-identify-url-text-final.json create mode 100644 blocksuite/tests-legacy/snapshots/basic.spec.ts/basic-test-default.json create mode 100644 blocksuite/tests-legacy/snapshots/bookmark.spec.ts/copy-bookmark-url-by-copy-button-final.json create mode 100644 blocksuite/tests-legacy/snapshots/bookmark.spec.ts/copy-url-to-create-bookmark-in-edgeless-mode-final.json create mode 100644 blocksuite/tests-legacy/snapshots/bookmark.spec.ts/copy-url-to-create-bookmark-in-page-mode-final.json create mode 100644 blocksuite/tests-legacy/snapshots/bookmark.spec.ts/covert-bookmark-block-to-link-text-final.json create mode 100644 blocksuite/tests-legacy/snapshots/bookmark.spec.ts/create-bookmark-by-slash-menu-final.json create mode 100644 blocksuite/tests-legacy/snapshots/bookmark.spec.ts/embed-figma.json create mode 100644 blocksuite/tests-legacy/snapshots/bookmark.spec.ts/embed-youtube.json create mode 100644 blocksuite/tests-legacy/snapshots/bookmark.spec.ts/horizontal-figma.json create mode 100644 blocksuite/tests-legacy/snapshots/bookmark.spec.ts/horizontal-youtube.json create mode 100644 blocksuite/tests-legacy/snapshots/bookmark.spec.ts/support-dragging-bookmark-block-directly-after-add-paragraph.json create mode 100644 blocksuite/tests-legacy/snapshots/bookmark.spec.ts/support-dragging-bookmark-block-directly-after-drag.json create mode 100644 blocksuite/tests-legacy/snapshots/bookmark.spec.ts/support-dragging-bookmark-block-directly-init.json create mode 100644 blocksuite/tests-legacy/snapshots/clipboard/clipboard.spec.ts/auto-identify-url-final.json create mode 100644 blocksuite/tests-legacy/snapshots/clipboard/clipboard.spec.ts/clipboard-copy-nested-items-clipboard.html create mode 100644 blocksuite/tests-legacy/snapshots/clipboard/clipboard.spec.ts/clipboard-copy-nested-items-clipboard.json create mode 100644 blocksuite/tests-legacy/snapshots/clipboard/clipboard.spec.ts/clipboard-copy-nested-items-clipboard.md create mode 100644 blocksuite/tests-legacy/snapshots/clipboard/clipboard.spec.ts/clipboard-copy-nested-items-clipboard2.html create mode 100644 blocksuite/tests-legacy/snapshots/clipboard/clipboard.spec.ts/clipboard-copy-nested-items-clipboard2.json create mode 100644 blocksuite/tests-legacy/snapshots/clipboard/clipboard.spec.ts/clipboard-copy-nested-items-clipboard2.md create mode 100644 blocksuite/tests-legacy/snapshots/clipboard/list.spec.ts/copy-a-nested-list-by-clicking-button-the-clipboard-data-should-be-complete-clipboard.html create mode 100644 blocksuite/tests-legacy/snapshots/clipboard/list.spec.ts/copy-a-nested-list-by-clicking-button-the-clipboard-data-should-be-complete-clipboard.json create mode 100644 blocksuite/tests-legacy/snapshots/clipboard/list.spec.ts/copy-a-nested-list-by-clicking-button-the-clipboard-data-should-be-complete-clipboard.md create mode 100644 blocksuite/tests-legacy/snapshots/clipboard/list.spec.ts/cut-will-delete-all-content-and-copy-will-reappear-content-after-cut.json create mode 100644 blocksuite/tests-legacy/snapshots/clipboard/list.spec.ts/cut-will-delete-all-content-and-copy-will-reappear-content-after-paste.json create mode 100644 blocksuite/tests-legacy/snapshots/clipboard/list.spec.ts/should-keep-paragraph-block-s-type-when-pasting-at-the-start-of-empty-paragraph-block-except-type-text-after-paste-1.json create mode 100644 blocksuite/tests-legacy/snapshots/clipboard/list.spec.ts/should-keep-paragraph-block-s-type-when-pasting-at-the-start-of-empty-paragraph-block-except-type-text-after-paste-2.json create mode 100644 blocksuite/tests-legacy/snapshots/code/copy-paste.spec.ts/code-block-has-content-click-code-block-copy-menu-copy-whole-code-block-pasted.json create mode 100644 blocksuite/tests-legacy/snapshots/code/copy-paste.spec.ts/code-block-is-empty-click-code-block-copy-menu-copy-the-empty-code-block-pasted.json create mode 100644 blocksuite/tests-legacy/snapshots/code/crud.spec.ts/delete-code-block-in-more-menu-final.json create mode 100644 blocksuite/tests-legacy/snapshots/code/crud.spec.ts/duplicate-code-block-final.json create mode 100644 blocksuite/tests-legacy/snapshots/code/crud.spec.ts/format-text-in-code-block-format.json create mode 100644 blocksuite/tests-legacy/snapshots/code/crud.spec.ts/format-text-in-code-block-init.json create mode 100644 blocksuite/tests-legacy/snapshots/code/crud.spec.ts/format-text-in-code-block-link.json create mode 100644 blocksuite/tests-legacy/snapshots/code/crud.spec.ts/use-markdown-syntax-can-create-code-block-init.json create mode 100644 blocksuite/tests-legacy/snapshots/code/crud.spec.ts/use-markdown-syntax-can-create-code-block-markdown-syntax.json create mode 100644 blocksuite/tests-legacy/snapshots/drag.spec.ts/move-to-the-last-block-of-each-level-in-multi-level-nesting-drag-3-4.json create mode 100644 blocksuite/tests-legacy/snapshots/drag.spec.ts/move-to-the-last-block-of-each-level-in-multi-level-nesting-drag-3-9.json create mode 100644 blocksuite/tests-legacy/snapshots/drag.spec.ts/move-to-the-last-block-of-each-level-in-multi-level-nesting-drag-4-3.json create mode 100644 blocksuite/tests-legacy/snapshots/drag.spec.ts/move-to-the-last-block-of-each-level-in-multi-level-nesting-init.json create mode 100644 blocksuite/tests-legacy/snapshots/drag.spec.ts/should-be-able-to-drag-drop-multiple-blocks-to-nested-block-finial.json create mode 100644 blocksuite/tests-legacy/snapshots/drag.spec.ts/should-be-able-to-drag-drop-multiple-blocks-to-nested-block-init.json create mode 100644 blocksuite/tests-legacy/snapshots/edgeless/edgeless-text.spec.ts/min-width-limit-for-embed-block-add-linked-doc.json create mode 100644 blocksuite/tests-legacy/snapshots/edgeless/edgeless-text.spec.ts/min-width-limit-for-embed-block-drag.json create mode 100644 blocksuite/tests-legacy/snapshots/edgeless/edgeless-text.spec.ts/min-width-limit-for-embed-block-init.json create mode 100644 blocksuite/tests-legacy/snapshots/edgeless/edgeless-text.spec.ts/min-width-limit-for-embed-block-link-to-card-min-width.json create mode 100644 blocksuite/tests-legacy/snapshots/edgeless/edgeless-text.spec.ts/min-width-limit-for-embed-block-link-to-card.json create mode 100644 blocksuite/tests-legacy/snapshots/edgeless/edgeless-text.spec.ts/press-backspace-at-the-start-of-first-line-when-edgeless-text-exist-finial.json create mode 100644 blocksuite/tests-legacy/snapshots/edgeless/edgeless-text.spec.ts/press-backspace-at-the-start-of-first-line-when-edgeless-text-exist-note-empty.json create mode 100644 blocksuite/tests-legacy/snapshots/edgeless/edgeless-text.spec.ts/press-backspace-at-the-start-of-first-line-when-edgeless-text-exist-note-not-empty.json create mode 100644 blocksuite/tests-legacy/snapshots/format-bar.spec.ts/create-linked-doc-from-block-selection-with-format-bar.json create mode 100644 blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-change-background-color-default-color.json create mode 100644 blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-change-background-color-init.json create mode 100644 blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-change-background-color-select-all.json create mode 100644 blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-change-to-heading-paragraph-type-bulleted.json create mode 100644 blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-change-to-heading-paragraph-type-finial.json create mode 100644 blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-change-to-heading-paragraph-type-init.json create mode 100644 blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-format-text-finial.json create mode 100644 blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-format-text-init.json create mode 100644 blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-format-text-when-select-multiple-line-finial.json create mode 100644 blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-format-text-when-select-multiple-line-init.json create mode 100644 blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-link-text-finial.json create mode 100644 blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-link-text-init.json create mode 100644 blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-show-after-convert-to-code-block.json create mode 100644 blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-with-block-selection-works-when-update-block-type-final.json create mode 100644 blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-with-block-selection-works-when-update-block-type-init.json create mode 100644 blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-work-in-multiple-block-selection.json create mode 100644 blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-work-in-single-block-selection.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey.spec.ts/Enter-key-should-as-expected-after-setting-heading-by-shortkey.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey.spec.ts/multi-line-rich-text-inline-code-hotkey-init.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey.spec.ts/multi-line-rich-text-inline-code-hotkey-redo.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey.spec.ts/multi-line-rich-text-inline-code-hotkey-undo.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-cut-work-multiple-line-init.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-cut-work-multiple-line-undo.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-cut-work-single-line-init.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-cut-work-single-line-undo.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-hotkey-work-in-paragraph-init.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-hotkey-work-in-paragraph-press-0.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-hotkey-work-in-paragraph-press-6.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-hotkey-work-in-paragraph-press-8.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-hotkey-work-in-paragraph-press-9.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-hotkey-work-in-paragraph-press-d.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-multiple-line-format-hotkey-work-finial.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-multiple-line-format-hotkey-work-init.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-single-line-format-hotkey-work-finial.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-single-line-format-hotkey-work-init.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey.spec.ts/use-formatted-cursor-with-hotkey-bold-ggg.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey.spec.ts/use-formatted-cursor-with-hotkey-bold-hhh.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey.spec.ts/use-formatted-cursor-with-hotkey-bold.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey.spec.ts/use-formatted-cursor-with-hotkey-init.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey/bracket.spec.ts/should-bracket-complete-with-backtick-works-undo.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey/bracket.spec.ts/should-bracket-complete-with-backtick-works.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/Enter-key-should-as-expected-after-setting-heading-by-shortkey.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/should-cut-work-single-line-init.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/should-cut-work-single-line-undo.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/should-hotkey-work-in-paragraph-init.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/should-hotkey-work-in-paragraph-press-0.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/should-hotkey-work-in-paragraph-press-6.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/should-hotkey-work-in-paragraph-press-8.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/should-hotkey-work-in-paragraph-press-9.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/should-hotkey-work-in-paragraph-press-d.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/should-single-line-format-hotkey-work-finial.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/should-single-line-format-hotkey-work-init.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/type-character-jump-out-code-node-1.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/type-character-jump-out-code-node-2.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/use-formatted-cursor-with-hotkey-at-empty-line-bold.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/use-formatted-cursor-with-hotkey-bold-ggg.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/use-formatted-cursor-with-hotkey-bold-hhh.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/use-formatted-cursor-with-hotkey-bold.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/use-formatted-cursor-with-hotkey-init.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey/multiline.spec.ts/multi-line-rich-text-inline-code-hotkey-init.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey/multiline.spec.ts/multi-line-rich-text-inline-code-hotkey-redo.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey/multiline.spec.ts/multi-line-rich-text-inline-code-hotkey-undo.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey/multiline.spec.ts/should-cut-work-multiple-line-init.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey/multiline.spec.ts/should-cut-work-multiple-line-undo.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey/multiline.spec.ts/should-multiple-line-format-hotkey-work-finial.json create mode 100644 blocksuite/tests-legacy/snapshots/hotkey/multiline.spec.ts/should-multiple-line-format-hotkey-work-init.json create mode 100644 blocksuite/tests-legacy/snapshots/latex/block.spec.ts/add-latex-block-using-markdown-shortcut-with-enter-finial.json create mode 100644 blocksuite/tests-legacy/snapshots/latex/block.spec.ts/add-latex-block-using-markdown-shortcut-with-enter-init.json create mode 100644 blocksuite/tests-legacy/snapshots/latex/block.spec.ts/add-latex-block-using-markdown-shortcut-with-space-finial.json create mode 100644 blocksuite/tests-legacy/snapshots/latex/block.spec.ts/add-latex-block-using-markdown-shortcut-with-space-init.json create mode 100644 blocksuite/tests-legacy/snapshots/latex/block.spec.ts/add-latex-block-using-slash-menu-finial.json create mode 100644 blocksuite/tests-legacy/snapshots/latex/block.spec.ts/add-latex-block-using-slash-menu-init.json create mode 100644 blocksuite/tests-legacy/snapshots/link.spec.ts/basic-link.json create mode 100644 blocksuite/tests-legacy/snapshots/link.spec.ts/convert-link-to-card.json create mode 100644 blocksuite/tests-legacy/snapshots/linked-page.spec.ts/can-create-linked-page-and-jump-final.json create mode 100644 blocksuite/tests-legacy/snapshots/linked-page.spec.ts/can-create-linked-page-and-jump-init.json create mode 100644 blocksuite/tests-legacy/snapshots/linked-page.spec.ts/duplicated-linked-page-should-paste-as-linked-page.json create mode 100644 blocksuite/tests-legacy/snapshots/linked-page.spec.ts/paste-linked-page-should-paste-as-linked-page.json create mode 100644 blocksuite/tests-legacy/snapshots/linked-page.spec.ts/should-create-and-switch-page-work-final.json create mode 100644 blocksuite/tests-legacy/snapshots/linked-page.spec.ts/should-create-and-switch-page-work-init.json create mode 100644 blocksuite/tests-legacy/snapshots/list.spec.ts/basic-indent-and-unindent-after-shift-tab.json create mode 100644 blocksuite/tests-legacy/snapshots/list.spec.ts/basic-indent-and-unindent-after-tab.json create mode 100644 blocksuite/tests-legacy/snapshots/list.spec.ts/basic-indent-and-unindent-init.json create mode 100644 blocksuite/tests-legacy/snapshots/list.spec.ts/can-expand-toggle-in-readonly-mode-before-readonly.json create mode 100644 blocksuite/tests-legacy/snapshots/list.spec.ts/click-toggle-icon-should-collapsed-list-init.json create mode 100644 blocksuite/tests-legacy/snapshots/list.spec.ts/click-toggle-icon-should-collapsed-list-toggle.json create mode 100644 blocksuite/tests-legacy/snapshots/list.spec.ts/convert-nested-paragraph-to-list-final.json create mode 100644 blocksuite/tests-legacy/snapshots/list.spec.ts/convert-nested-paragraph-to-list-init.json create mode 100644 blocksuite/tests-legacy/snapshots/list.spec.ts/enter-list-block-with-empty-text-1.json create mode 100644 blocksuite/tests-legacy/snapshots/list.spec.ts/enter-list-block-with-empty-text-2.json create mode 100644 blocksuite/tests-legacy/snapshots/list.spec.ts/enter-list-block-with-empty-text-3.json create mode 100644 blocksuite/tests-legacy/snapshots/list.spec.ts/enter-list-block-with-empty-text-4.json create mode 100644 blocksuite/tests-legacy/snapshots/list.spec.ts/enter-list-block-with-empty-text-5.json create mode 100644 blocksuite/tests-legacy/snapshots/list.spec.ts/enter-list-block-with-empty-text-init.json create mode 100644 blocksuite/tests-legacy/snapshots/list.spec.ts/indent-item-should-expand-toggle-finial.json create mode 100644 blocksuite/tests-legacy/snapshots/list.spec.ts/indent-item-should-expand-toggle-init.json create mode 100644 blocksuite/tests-legacy/snapshots/list.spec.ts/indent-item-should-expand-toggle-toggle.json create mode 100644 blocksuite/tests-legacy/snapshots/list.spec.ts/nested-list-blocks-finial.json create mode 100644 blocksuite/tests-legacy/snapshots/list.spec.ts/nested-list-blocks-init.json create mode 100644 blocksuite/tests-legacy/snapshots/list.spec.ts/should-indent-todo-block-preserve-todo-status-final.json create mode 100644 blocksuite/tests-legacy/snapshots/list.spec.ts/should-indent-todo-block-preserve-todo-status-init.json create mode 100644 blocksuite/tests-legacy/snapshots/paragraph.spec.ts/delete-empty-text-paragraph-block-should-keep-children-blocks-when-following-custom-blocks-final.json create mode 100644 blocksuite/tests-legacy/snapshots/paragraph.spec.ts/delete-empty-text-paragraph-block-should-keep-children-blocks-when-following-custom-blocks-init.json create mode 100644 blocksuite/tests-legacy/snapshots/paragraph.spec.ts/paragraph-indent-and-delete-in-line-start-after-press-backspace-2.json create mode 100644 blocksuite/tests-legacy/snapshots/paragraph.spec.ts/paragraph-indent-and-delete-in-line-start-after-press-backspace-3.json create mode 100644 blocksuite/tests-legacy/snapshots/paragraph.spec.ts/paragraph-indent-and-delete-in-line-start-after-press-backspace.json create mode 100644 blocksuite/tests-legacy/snapshots/paragraph.spec.ts/paragraph-indent-and-delete-in-line-start-init.json create mode 100644 blocksuite/tests-legacy/snapshots/paragraph.spec.ts/paragraph-with-child-block-should-work-at-enter-final.json create mode 100644 blocksuite/tests-legacy/snapshots/paragraph.spec.ts/paragraph-with-child-block-should-work-at-enter-init.json create mode 100644 blocksuite/tests-legacy/snapshots/paragraph.spec.ts/should-delete-paragraph-block-child-can-hold-cursor-in-correct-position-final.json create mode 100644 blocksuite/tests-legacy/snapshots/paragraph.spec.ts/should-delete-paragraph-block-child-can-hold-cursor-in-correct-position-init.json create mode 100644 blocksuite/tests-legacy/snapshots/paragraph.spec.ts/should-indent-and-unindent-works-with-children-indent-2.json create mode 100644 blocksuite/tests-legacy/snapshots/paragraph.spec.ts/should-indent-and-unindent-works-with-children-indent-3.json create mode 100644 blocksuite/tests-legacy/snapshots/paragraph.spec.ts/should-indent-and-unindent-works-with-children-indent-4.json create mode 100644 blocksuite/tests-legacy/snapshots/paragraph.spec.ts/should-indent-and-unindent-works-with-children-indent.json create mode 100644 blocksuite/tests-legacy/snapshots/paragraph.spec.ts/should-indent-and-unindent-works-with-children-init.json create mode 100644 blocksuite/tests-legacy/snapshots/paragraph.spec.ts/should-indent-and-unindent-works-with-children-unindent-1.json create mode 100644 blocksuite/tests-legacy/snapshots/paragraph.spec.ts/should-indent-and-unindent-works-with-children-unindent-2.json create mode 100644 blocksuite/tests-legacy/snapshots/paragraph.spec.ts/should-indent-and-unindent-works-with-children-unindent-3.json create mode 100644 blocksuite/tests-legacy/snapshots/selection/block.spec.ts/click-bottom-of-page-and-if-the-last-is-embed-block-editor-should-insert-a-new-editable-block.json create mode 100644 blocksuite/tests-legacy/snapshots/selection/block.spec.ts/should-indent-multi-selection-block.json create mode 100644 blocksuite/tests-legacy/snapshots/selection/block.spec.ts/should-not-draw-rect-for-sub-selected-blocks-when-entering-tab-key.json create mode 100644 blocksuite/tests-legacy/snapshots/selection/block.spec.ts/should-unindent-multi-selection-block-final.json create mode 100644 blocksuite/tests-legacy/snapshots/selection/block.spec.ts/should-unindent-multi-selection-block-init.json create mode 100644 blocksuite/tests-legacy/snapshots/selection/native.spec.ts/indent-native-multi-selection-block-after-shift-tab.json create mode 100644 blocksuite/tests-legacy/snapshots/selection/native.spec.ts/indent-native-multi-selection-block-after-tab.json create mode 100644 blocksuite/tests-legacy/snapshots/selection/native.spec.ts/native-range-delete-with-indent-after-backspace.json create mode 100644 blocksuite/tests-legacy/snapshots/selection/native.spec.ts/native-range-delete-with-indent-after-redo.json create mode 100644 blocksuite/tests-legacy/snapshots/selection/native.spec.ts/native-range-delete-with-indent-after-undo.json create mode 100644 blocksuite/tests-legacy/snapshots/selection/native.spec.ts/native-range-delete-with-indent-init.json create mode 100644 blocksuite/tests-legacy/snapshots/slash-menu.spec.ts/delete-block-by-slash-menu-should-remove-children.json create mode 100644 blocksuite/tests-legacy/tsconfig.json create mode 100644 blocksuite/tests-legacy/utils/actions/block.ts create mode 100644 blocksuite/tests-legacy/utils/actions/click.ts create mode 100644 blocksuite/tests-legacy/utils/actions/drag.ts create mode 100644 blocksuite/tests-legacy/utils/actions/edgeless.ts create mode 100644 blocksuite/tests-legacy/utils/actions/index.ts create mode 100644 blocksuite/tests-legacy/utils/actions/keyboard.ts create mode 100644 blocksuite/tests-legacy/utils/actions/linked-doc.ts create mode 100644 blocksuite/tests-legacy/utils/actions/misc.ts create mode 100644 blocksuite/tests-legacy/utils/actions/selection.ts create mode 100644 blocksuite/tests-legacy/utils/asserts.ts create mode 100644 blocksuite/tests-legacy/utils/declare-test-window.ts create mode 100644 blocksuite/tests-legacy/utils/ignore.ts create mode 100644 blocksuite/tests-legacy/utils/inline-editor.ts create mode 100644 blocksuite/tests-legacy/utils/multiple-editor.ts create mode 100644 blocksuite/tests-legacy/utils/playwright.ts create mode 100644 blocksuite/tests-legacy/utils/query.ts create mode 100644 blocksuite/tests-legacy/worker.spec.ts create mode 100644 blocksuite/tests-legacy/zero-width.spec.ts create mode 100644 scripts/vitest-global.js diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index c6d34e549e316..54835030a3bd3 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -112,6 +112,36 @@ jobs: yarn set version $(node -e "console.log(require('./package.json').packageManager.split('@')[1])") git diff --exit-code + e2e-legacy-blocksuite-test: + name: Legacy Blocksuite E2E Test + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + steps: + - uses: actions/checkout@v4 + - name: Setup Node.js + uses: ./.github/actions/setup-node + with: + playwright-install: true + electron-install: false + full-cache: true + + - name: Run playground build + run: yarn workspace @blocksuite/playground build + + - name: Run playwright tests + run: yarn workspace @blocksuite/legacy-e2e test --forbid-only --shard=${{ matrix.shard }}/${{ strategy.job-total }} + + - name: Upload test results + if: ${{ failure() }} + uses: actions/upload-artifact@v4 + with: + name: test-results-e2e-legacy-bs-${{ matrix.shard }} + path: ./test-results + if-no-files-found: ignore + e2e-test: name: E2E Test runs-on: ubuntu-latest @@ -185,6 +215,7 @@ jobs: uses: ./.github/actions/setup-node with: electron-install: true + playwright-install: true full-cache: true - name: Download affine.linux-x64-gnu.node @@ -767,6 +798,7 @@ jobs: - lint - check-yarn-binary - e2e-test + - e2e-legacy-blocksuite-test - e2e-mobile-test - unit-test - build-native diff --git a/.prettierignore b/.prettierignore index 6aa0948e1fdde..489bc0a065375 100644 --- a/.prettierignore +++ b/.prettierignore @@ -25,4 +25,6 @@ packages/frontend/templates/onboarding packages/backend/native/index.d.ts packages/frontend/native/index.d.ts packages/frontend/native/index.js -compose.yaml \ No newline at end of file +compose.yaml + +blocksuite/tests-legacy/snapshots diff --git a/blocksuite/affine/all/vitest.config.ts b/blocksuite/affine/all/vitest.config.ts index 0be7ff0fecb3c..700f600c649af 100644 --- a/blocksuite/affine/all/vitest.config.ts +++ b/blocksuite/affine/all/vitest.config.ts @@ -5,7 +5,7 @@ export default defineConfig({ target: 'es2018', }, test: { - globalSetup: '../../../scripts/vitest-global.ts', + globalSetup: '../../../scripts/vitest-global.js', include: ['src/__tests__/**/*.unit.spec.ts'], testTimeout: 1000, coverage: { diff --git a/blocksuite/affine/block-embed/vitest.config.ts b/blocksuite/affine/block-embed/vitest.config.ts index b86624acc9b42..c0330b2ab1951 100644 --- a/blocksuite/affine/block-embed/vitest.config.ts +++ b/blocksuite/affine/block-embed/vitest.config.ts @@ -5,7 +5,7 @@ export default defineConfig({ target: 'es2018', }, test: { - globalSetup: '../../../scripts/vitest-global.ts', + globalSetup: '../../../scripts/vitest-global.js', include: ['src/__tests__/**/*.unit.spec.ts'], testTimeout: 1000, coverage: { diff --git a/blocksuite/affine/block-list/vitest.config.ts b/blocksuite/affine/block-list/vitest.config.ts index b86624acc9b42..c0330b2ab1951 100644 --- a/blocksuite/affine/block-list/vitest.config.ts +++ b/blocksuite/affine/block-list/vitest.config.ts @@ -5,7 +5,7 @@ export default defineConfig({ target: 'es2018', }, test: { - globalSetup: '../../../scripts/vitest-global.ts', + globalSetup: '../../../scripts/vitest-global.js', include: ['src/__tests__/**/*.unit.spec.ts'], testTimeout: 1000, coverage: { diff --git a/blocksuite/affine/block-paragraph/vitest.config.ts b/blocksuite/affine/block-paragraph/vitest.config.ts index fb99961c008a2..b39a4f9ea873d 100644 --- a/blocksuite/affine/block-paragraph/vitest.config.ts +++ b/blocksuite/affine/block-paragraph/vitest.config.ts @@ -5,7 +5,7 @@ export default defineConfig({ target: 'es2018', }, test: { - globalSetup: '../../../scripts/vitest-global.ts', + globalSetup: '../../../scripts/vitest-global.js', include: ['src/__tests__/**/*.unit.spec.ts'], testTimeout: 1000, coverage: { diff --git a/blocksuite/affine/block-surface/vitest.config.ts b/blocksuite/affine/block-surface/vitest.config.ts index 3bb7c2cc2d0eb..a45195590e4f1 100644 --- a/blocksuite/affine/block-surface/vitest.config.ts +++ b/blocksuite/affine/block-surface/vitest.config.ts @@ -5,7 +5,7 @@ export default defineConfig({ target: 'es2018', }, test: { - globalSetup: '../../../scripts/vitest-global.ts', + globalSetup: '../../../scripts/vitest-global.js', include: ['src/__tests__/**/*.unit.spec.ts'], testTimeout: 1000, coverage: { diff --git a/blocksuite/affine/components/vitest.config.ts b/blocksuite/affine/components/vitest.config.ts index e2eab294b3642..3243b60ffb700 100644 --- a/blocksuite/affine/components/vitest.config.ts +++ b/blocksuite/affine/components/vitest.config.ts @@ -5,7 +5,7 @@ export default defineConfig({ target: 'es2018', }, test: { - globalSetup: '../../../scripts/vitest-global.ts', + globalSetup: '../../../scripts/vitest-global.js', include: ['src/__tests__/**/*.unit.spec.ts'], testTimeout: 1000, coverage: { diff --git a/blocksuite/affine/data-view/vitest.config.ts b/blocksuite/affine/data-view/vitest.config.ts index 1e76565bf5f7c..9ae9d1cc50554 100644 --- a/blocksuite/affine/data-view/vitest.config.ts +++ b/blocksuite/affine/data-view/vitest.config.ts @@ -5,7 +5,7 @@ export default defineConfig({ target: 'es2018', }, test: { - globalSetup: '../../scripts/vitest-global.ts', + globalSetup: '../../scripts/vitest-global.js', include: ['src/__tests__/**/*.unit.spec.ts'], testTimeout: 1000, coverage: { diff --git a/blocksuite/affine/model/vitest.config.ts b/blocksuite/affine/model/vitest.config.ts index a1bcb95c66e1c..9faa866c54031 100644 --- a/blocksuite/affine/model/vitest.config.ts +++ b/blocksuite/affine/model/vitest.config.ts @@ -5,7 +5,7 @@ export default defineConfig({ target: 'es2018', }, test: { - globalSetup: '../../scripts/vitest-global.ts', + globalSetup: '../../scripts/vitest-global.js', include: ['src/__tests__/**/*.unit.spec.ts'], testTimeout: 1000, coverage: { diff --git a/blocksuite/affine/shared/vitest.config.ts b/blocksuite/affine/shared/vitest.config.ts index a4b8d7fdcc7c0..9c1c45d368616 100644 --- a/blocksuite/affine/shared/vitest.config.ts +++ b/blocksuite/affine/shared/vitest.config.ts @@ -5,7 +5,7 @@ export default defineConfig({ target: 'es2018', }, test: { - globalSetup: '../../../scripts/vitest-global.ts', + globalSetup: '../../../scripts/vitest-global.js', include: ['src/__tests__/**/*.unit.spec.ts'], testTimeout: 1000, coverage: { diff --git a/blocksuite/affine/widget-scroll-anchoring/vitest.config.ts b/blocksuite/affine/widget-scroll-anchoring/vitest.config.ts index 287861e3498de..04dcaa6f15a1d 100644 --- a/blocksuite/affine/widget-scroll-anchoring/vitest.config.ts +++ b/blocksuite/affine/widget-scroll-anchoring/vitest.config.ts @@ -5,7 +5,7 @@ export default defineConfig({ target: 'es2018', }, test: { - globalSetup: '../../../scripts/vitest-global.ts', + globalSetup: '../../../scripts/vitest-global.js', include: ['src/__tests__/**/*.unit.spec.ts'], testTimeout: 1000, coverage: { diff --git a/blocksuite/blocks/vitest.config.ts b/blocksuite/blocks/vitest.config.ts index 235c0dbde91f2..8567a9f37a5fa 100644 --- a/blocksuite/blocks/vitest.config.ts +++ b/blocksuite/blocks/vitest.config.ts @@ -5,7 +5,7 @@ export default defineConfig({ target: 'es2018', }, test: { - globalSetup: '../../scripts/vitest-global.ts', + globalSetup: '../../scripts/vitest-global.js', include: ['src/__tests__/**/*.unit.spec.ts'], testTimeout: 1000, coverage: { diff --git a/blocksuite/framework/block-std/package.json b/blocksuite/framework/block-std/package.json index b8d8dcb064866..c1795ea3b0595 100644 --- a/blocksuite/framework/block-std/package.json +++ b/blocksuite/framework/block-std/package.json @@ -20,6 +20,7 @@ "@lit/context": "^1.1.2", "@preact/signals-core": "^1.8.0", "@types/hast": "^3.0.4", + "dompurify": "^3.1.6", "fractional-indexing": "^3.2.0", "lib0": "^0.2.97", "lit": "^3.2.0", diff --git a/blocksuite/playground/package.json b/blocksuite/playground/package.json index 6120359211115..47c397ffedc0c 100644 --- a/blocksuite/playground/package.json +++ b/blocksuite/playground/package.json @@ -5,7 +5,7 @@ "scripts": { "dev": "vite --host", "dev:hmr": "WC_HMR=1 vite", - "build": "tsc && nx vite:build", + "build": "vite build", "preview": "vite preview" }, "dependencies": { @@ -40,6 +40,8 @@ "@types/micromatch": "^4.0.9", "graphql": "^16.9.0", "magic-string": "^0.30.11", + "vite": "^6.0.3", + "vite-plugin-istanbul": "^6.0.2", "vite-plugin-wasm": "^3.3.0", "vite-plugin-web-components-hmr": "^0.1.3" } diff --git a/blocksuite/presets/src/__tests__/main/snapshot.spec.ts b/blocksuite/presets/src/__tests__/main/snapshot.spec.ts index c5d802f23bd94..f286c8c0e8b99 100644 --- a/blocksuite/presets/src/__tests__/main/snapshot.spec.ts +++ b/blocksuite/presets/src/__tests__/main/snapshot.spec.ts @@ -89,11 +89,13 @@ beforeEach(async () => { const xywhPattern = /\[(\s*-?\d+(\.\d+)?\s*,){3}(\s*-?\d+(\.\d+)?\s*)\]/; -test('snapshot 1 importing', async () => { +// FIXME: snapshot tests +test.skip('snapshot 1 importing', async () => { await snapshotTest('https://test.affineassets.com/test-snapshot-1.zip', 25); }); -test('snapshot 2 importing', async () => { +// FIXME: snapshot tests +test.skip('snapshot 2 importing', async () => { await snapshotTest( 'https://test.affineassets.com/test-snapshot-2%20(onboarding).zip', 174 diff --git a/blocksuite/tests-legacy/attachment.spec.ts b/blocksuite/tests-legacy/attachment.spec.ts new file mode 100644 index 0000000000000..9de5a4509a16f --- /dev/null +++ b/blocksuite/tests-legacy/attachment.spec.ts @@ -0,0 +1,769 @@ +import { sleep } from '@blocksuite/global/utils'; +import { expect, type Page } from '@playwright/test'; +import { switchEditorMode } from 'utils/actions/edgeless.js'; + +import { dragBlockToPoint, popImageMoreMenu } from './utils/actions/drag.js'; +import { + pressArrowDown, + pressArrowUp, + pressBackspace, + pressEnter, + pressEscape, + pressShiftTab, + pressTab, + redoByKeyboard, + SHORT_KEY, + type, + undoByKeyboard, +} from './utils/actions/keyboard.js'; +import { + captureHistory, + enterPlaygroundRoom, + focusRichText, + initEmptyEdgelessState, + initEmptyParagraphState, + resetHistory, + waitNextFrame, +} from './utils/actions/misc.js'; +import { + assertBlockChildrenIds, + assertBlockCount, + assertBlockFlavour, + assertBlockSelections, + assertKeyboardWorkInInput, + assertParentBlockFlavour, + assertRichImage, + assertRichTextInlineRange, + assertStoreMatchJSX, +} from './utils/asserts.js'; +import { test } from './utils/playwright.js'; + +const FILE_NAME = 'test-card-1.png'; +const FILE_PATH = `../playground/public/${FILE_NAME}`; +const FILE_ID = 'ejImogf-Tb7AuKY-v94uz1zuOJbClqK-tWBxVr_ksGA='; +const FILE_SIZE = 45801; + +function getAttachment(page: Page) { + const attachment = page.locator('affine-attachment'); + const loading = attachment.locator('.affine-attachment-card.loading'); + const toolbar = page.locator('.affine-attachment-toolbar'); + const switchViewButton = toolbar.getByRole('button', { name: 'Switch view' }); + const renameBtn = toolbar.getByRole('button', { name: 'Rename' }); + const renameInput = page.locator('.affine-attachment-rename-container input'); + + const insertAttachment = async () => { + await page.evaluate(() => { + // Force fallback to input[type=file] in tests + // See https://github.com/microsoft/playwright/issues/8850 + window.showOpenFilePicker = undefined; + }); + + const slashMenu = page.locator(`.slash-menu`); + await waitNextFrame(page); + await type(page, '/'); + await resetHistory(page); + await expect(slashMenu).toBeVisible(); + await type(page, 'file', 100); + await expect(slashMenu).toBeVisible(); + + const fileChooser = page.waitForEvent('filechooser'); + await pressEnter(page); + await sleep(100); + await (await fileChooser).setFiles(FILE_PATH); + + // Try to break the undo redo test + await captureHistory(page); + + await expect(attachment).toBeVisible(); + }; + + const getName = () => + attachment.locator('.affine-attachment-content-title-text').innerText(); + + return { + // locators + attachment, + toolbar, + switchViewButton, + renameBtn, + renameInput, + + // actions + insertAttachment, + /** + * Wait for the attachment upload to finish + */ + waitLoading: () => loading.waitFor({ state: 'hidden' }), + getName, + getSize: () => + attachment.locator('.affine-attachment-content-info').innerText(), + + turnToEmbed: async () => { + await expect(switchViewButton).toBeVisible(); + await switchViewButton.click(); + await page.getByRole('button', { name: 'Embed view' }).click(); + await assertRichImage(page, 1); + }, + rename: async (newName: string) => { + await attachment.hover(); + await expect(toolbar).toBeVisible(); + await renameBtn.click(); + await page.keyboard.press(`${SHORT_KEY}+a`, { delay: 50 }); + await pressBackspace(page); + await type(page, newName); + await pressEnter(page); + expect(await getName()).toContain(newName); + }, + + // external + turnImageToCard: async () => { + const { turnIntoCardButton } = await popImageMoreMenu(page); + await turnIntoCardButton.click(); + await expect(attachment).toBeVisible(); + }, + }; +} + +test('can insert attachment from slash menu', async ({ page }) => { + await enterPlaygroundRoom(page); + const { noteId } = await initEmptyParagraphState(page); + + const { insertAttachment, waitLoading, getName, getSize } = + getAttachment(page); + + await focusRichText(page); + await insertAttachment(); + + // Wait for the attachment to be uploaded + await waitLoading(); + + expect(await getName()).toBe(FILE_NAME); + expect(await getSize()).toBe('45.8 kB'); + + await assertStoreMatchJSX( + page, + ` + + +`, + noteId + ); +}); + +test('should undo/redo works for attachment', async ({ page }) => { + await enterPlaygroundRoom(page); + const { noteId } = await initEmptyParagraphState(page); + + const { insertAttachment, waitLoading } = getAttachment(page); + + await focusRichText(page); + await insertAttachment(); + + // Wait for the attachment to be uploaded + await waitLoading(); + + await assertStoreMatchJSX( + page, + ` + +`, + noteId + ); + + await undoByKeyboard(page); + await waitNextFrame(page); + // The loading/error state should not be restored after undo + await assertStoreMatchJSX( + page, + ` + + +`, + noteId + ); + + await redoByKeyboard(page); + await waitNextFrame(page); + await assertStoreMatchJSX( + page, + ` + + +`, + noteId + ); +}); + +test('should rename attachment works', async ({ page }) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/toeverything/blocksuite/issues/4534', + }); + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + const { + attachment, + renameBtn, + renameInput, + insertAttachment, + waitLoading, + getName, + rename, + } = getAttachment(page); + + await focusRichText(page); + await insertAttachment(); + // Wait for the attachment to be uploaded + await waitLoading(); + + expect(await getName()).toBe(FILE_NAME); + + await attachment.hover(); + await expect(renameBtn).toBeVisible(); + await renameBtn.click(); + await assertKeyboardWorkInInput(page, renameInput); + await pressEscape(page); + await expect(renameInput).not.toBeVisible(); + + await rename('new-name'); + expect(await getName()).toBe('new-name.png'); + await rename(''); + expect(await getName()).toBe('.png'); + await rename('abc'); + expect(await getName()).toBe('abc'); +}); + +test('should turn attachment to image works', async ({ page }) => { + await enterPlaygroundRoom(page); + const { noteId } = await initEmptyParagraphState(page); + const { insertAttachment, waitLoading, turnToEmbed, turnImageToCard } = + getAttachment(page); + + await focusRichText(page); + await insertAttachment(); + // Wait for the attachment to be uploaded + await waitLoading(); + + await turnToEmbed(); + + await assertStoreMatchJSX( + page, + ` + + +`, + noteId + ); + await turnImageToCard(); + await assertStoreMatchJSX( + page, + ` + + +`, + noteId + ); +}); + +test('should attachment can be deleted', async ({ page }) => { + await enterPlaygroundRoom(page); + const { noteId } = await initEmptyParagraphState(page); + const { attachment, insertAttachment, waitLoading } = getAttachment(page); + + await focusRichText(page); + await insertAttachment(); + // Wait for the attachment to be uploaded + await waitLoading(); + + await attachment.click(); + await pressBackspace(page); + await assertStoreMatchJSX( + page, + ` + + +`, + noteId + ); +}); + +test.fixme(`support dragging attachment block directly`, async ({ page }) => { + await enterPlaygroundRoom(page); + const { noteId } = await initEmptyParagraphState(page); + + const { insertAttachment, waitLoading, getName, getSize } = + getAttachment(page); + + await focusRichText(page); + await insertAttachment(); + + // Wait for the attachment to be uploaded + await waitLoading(); + + expect(await getName()).toBe(FILE_NAME); + expect(await getSize()).toBe('45.8 kB'); + + await assertStoreMatchJSX( + page, + ` + +`, + noteId + ); + + const attachmentBlock = page.locator('affine-attachment'); + const rect = await attachmentBlock.boundingBox(); + if (!rect) { + throw new Error('image not found'); + } + + // add new paragraph blocks + await page.mouse.click(rect.x + 20, rect.y + rect.height + 20); + await focusRichText(page); + await type(page, '111'); + await page.waitForTimeout(200); + await pressEnter(page); + + await type(page, '222'); + await page.waitForTimeout(200); + await pressEnter(page); + + await type(page, '333'); + await page.waitForTimeout(200); + + await page.waitForTimeout(200); + await assertStoreMatchJSX( + page, + /*xml*/ ` + + + + + + +` + ); + + // drag bookmark block + await page.mouse.move(rect.x + 20, rect.y + 20); + await page.mouse.down(); + await page.mouse.move(rect.x + 40, rect.y + rect.height + 80, { steps: 20 }); + await page.mouse.up(); + + const rects = page.locator('affine-block-selection').locator('visible=true'); + await expect(rects).toHaveCount(1); + + await assertStoreMatchJSX( + page, + /*xml*/ ` + + + + + + +` + ); +}); + +test('press backspace after bookmark block can select bookmark block', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + const { insertAttachment, waitLoading } = getAttachment(page); + + await focusRichText(page); + await pressEnter(page); + await pressArrowUp(page); + await insertAttachment(); + // Wait for the attachment to be uploaded + await waitLoading(); + + await focusRichText(page); + await assertBlockCount(page, 'paragraph', 1); + await assertRichTextInlineRange(page, 0, 0); + await pressBackspace(page); + await assertBlockSelections(page, ['4']); + await assertBlockCount(page, 'paragraph', 0); +}); + +test('cancel file picker with input element resolves', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + + const { attachment } = getAttachment(page); + + await focusRichText(page); + await pressEnter(page); + await pressArrowUp(page); + + await page.evaluate(() => { + // Force fallback to input[type=file] + window.showOpenFilePicker = undefined; + }); + + const slashMenu = page.locator(`.slash-menu`); + await waitNextFrame(page); + await type(page, '/file', 100); + await expect(slashMenu).toBeVisible(); + + const fileChooser = page.waitForEvent('filechooser'); + await pressEnter(page); + const inputFile = page.locator("input[type='file']"); + await expect(inputFile).toHaveCount(1); + + // This does not trigger `cancel` event and, + // therefore, the test isn't representative. + // Waiting for https://github.com/microsoft/playwright/issues/27524 + await (await fileChooser).setFiles([]); + + await expect(attachment).toHaveCount(0); + await expect(inputFile).toHaveCount(0); +}); + +test('indent attachment block to paragraph', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + const { insertAttachment, waitLoading } = getAttachment(page); + + await focusRichText(page); + await pressEnter(page); + await insertAttachment(); + // Wait for the attachment to be uploaded + await waitLoading(); + + await assertBlockChildrenIds(page, '1', ['2', '4']); + await assertBlockFlavour(page, '1', 'affine:note'); + await assertBlockFlavour(page, '2', 'affine:paragraph'); + await assertBlockFlavour(page, '4', 'affine:attachment'); + + await focusRichText(page); + await pressArrowDown(page); + await assertBlockSelections(page, ['4']); + await pressTab(page); + await assertBlockChildrenIds(page, '1', ['2']); + await assertBlockChildrenIds(page, '2', ['4']); + + await pressShiftTab(page); + await assertBlockChildrenIds(page, '1', ['2', '4']); +}); + +test('indent attachment block to list', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + const { insertAttachment, waitLoading } = getAttachment(page); + + await focusRichText(page); + await type(page, '- a'); + await pressEnter(page); + await insertAttachment(); + // Wait for the attachment to be uploaded + await waitLoading(); + + await assertBlockChildrenIds(page, '1', ['3', '5']); + await assertBlockFlavour(page, '1', 'affine:note'); + await assertBlockFlavour(page, '3', 'affine:list'); + await assertBlockFlavour(page, '5', 'affine:attachment'); + + await focusRichText(page); + await pressArrowDown(page); + await assertBlockSelections(page, ['5']); + await pressTab(page); + await assertBlockChildrenIds(page, '1', ['3']); + await assertBlockChildrenIds(page, '3', ['5']); + + await pressShiftTab(page); + await assertBlockChildrenIds(page, '1', ['3', '5']); +}); + +test('attachment can be dragged from note to surface top level block', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + const { insertAttachment, waitLoading } = getAttachment(page); + + await focusRichText(page); + await insertAttachment(); + + // Wait for the attachment to be uploaded + await waitLoading(); + + await switchEditorMode(page); + await page.mouse.dblclick(450, 450); + + await dragBlockToPoint(page, '4', { x: 200, y: 200 }); + + await waitNextFrame(page); + await assertParentBlockFlavour(page, '4', 'affine:surface'); +}); diff --git a/blocksuite/tests-legacy/basic.spec.ts b/blocksuite/tests-legacy/basic.spec.ts new file mode 100644 index 0000000000000..eb089a68cf17f --- /dev/null +++ b/blocksuite/tests-legacy/basic.spec.ts @@ -0,0 +1,590 @@ +import type { DeltaInsert } from '@inline/types.js'; +import { expect } from '@playwright/test'; + +import { + addNoteByClick, + captureHistory, + click, + disconnectByClick, + enterPlaygroundRoom, + focusRichText, + focusTitle, + getCurrentEditorTheme, + getCurrentHTMLTheme, + getPageSnapshot, + initEmptyEdgelessState, + initEmptyParagraphState, + pressArrowLeft, + pressArrowRight, + pressBackspace, + pressEnter, + pressForwardDelete, + pressForwardDeleteWord, + pressShiftEnter, + redoByClick, + redoByKeyboard, + setSelection, + switchEditorMode, + toggleDarkMode, + type, + undoByClick, + undoByKeyboard, + waitDefaultPageLoaded, + waitNextFrame, +} from './utils/actions/index.js'; +import { + assertBlockChildrenIds, + assertEmpty, + assertRichTextInlineDeltas, + assertRichTexts, + assertText, + assertTitle, +} from './utils/asserts.js'; +import { scoped, test } from './utils/playwright.js'; +import { getFormatBar } from './utils/query.js'; + +const BASIC_DEFAULT_SNAPSHOT = 'basic test default'; + +test(scoped`basic input`, async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'hello'); + + await test.expect(page).toHaveTitle(/BlockSuite/); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${BASIC_DEFAULT_SNAPSHOT}.json` + ); + await assertText(page, 'hello'); +}); + +test(scoped`basic init with external text`, async ({ page }) => { + await enterPlaygroundRoom(page); + + await page.evaluate(() => { + const { doc } = window; + const rootId = doc.addBlock('affine:page', { + title: new doc.Text('hello'), + }); + const note = doc.addBlock('affine:note', {}, rootId); + + const text = new doc.Text('world'); + doc.addBlock('affine:paragraph', { text }, note); + + const delta = [ + { insert: 'foo ' }, + { insert: 'bar', attributes: { bold: true } }, + ]; + doc.addBlock( + 'affine:paragraph', + { + text: new doc.Text(delta as DeltaInsert[]), + }, + note + ); + }); + + await assertTitle(page, 'hello'); + await assertRichTexts(page, ['world', 'foo bar']); + await focusRichText(page); +}); + +test(scoped`basic multi user state`, async ({ context, page: pageA }) => { + const room = await enterPlaygroundRoom(pageA); + await initEmptyParagraphState(pageA); + await waitNextFrame(pageA); + await waitDefaultPageLoaded(pageA); + await focusTitle(pageA); + await type(pageA, 'hello'); + + const pageB = await context.newPage(); + await enterPlaygroundRoom(pageB, { + flags: {}, + room, + noInit: true, + }); + await waitDefaultPageLoaded(pageB); + await focusTitle(pageB); + await assertTitle(pageB, 'hello'); + + await type(pageB, ' world'); + await assertTitle(pageA, 'hello world'); +}); + +test( + scoped`A open and edit, then joins B`, + async ({ context, page: pageA }) => { + const room = await enterPlaygroundRoom(pageA); + await initEmptyParagraphState(pageA); + await waitNextFrame(pageA); + await focusRichText(pageA); + await type(pageA, 'hello'); + + const pageB = await context.newPage(); + await enterPlaygroundRoom(pageB, { + flags: {}, + room, + noInit: true, + }); + + // wait until pageB content updated + await assertText(pageB, 'hello'); + await Promise.all([ + assertText(pageA, 'hello'), + expect(await getPageSnapshot(pageA, true)).toMatchSnapshot( + `${BASIC_DEFAULT_SNAPSHOT}.json` + ), + expect(await getPageSnapshot(pageB, true)).toMatchSnapshot( + `${BASIC_DEFAULT_SNAPSHOT}.json` + ), + assertBlockChildrenIds(pageA, '0', ['1']), + assertBlockChildrenIds(pageB, '0', ['1']), + ]); + } +); + +test(scoped`A first open, B first edit`, async ({ context, page: pageA }) => { + const room = await enterPlaygroundRoom(pageA); + await initEmptyParagraphState(pageA); + await waitNextFrame(pageA); + await focusRichText(pageA); + + const pageB = await context.newPage(); + await enterPlaygroundRoom(pageB, { + room, + noInit: true, + }); + await pageB.waitForTimeout(500); + await focusRichText(pageB); + + await waitNextFrame(pageA); + await waitNextFrame(pageB); + await type(pageB, 'hello'); + await pageA.waitForTimeout(500); + + // wait until pageA content updated + await assertText(pageA, 'hello'); + await assertText(pageB, 'hello'); + await Promise.all([ + expect(await getPageSnapshot(pageA, true)).toMatchSnapshot( + `${BASIC_DEFAULT_SNAPSHOT}.json` + ), + expect(await getPageSnapshot(pageB, true)).toMatchSnapshot( + `${BASIC_DEFAULT_SNAPSHOT}.json` + ), + ]); +}); + +test( + scoped`does not sync when disconnected`, + async ({ browser, page: pageA }) => { + test.fail(); + + const room = await enterPlaygroundRoom(pageA); + const pageB = await browser.newPage(); + await enterPlaygroundRoom(pageB, { flags: {}, room }); + + await disconnectByClick(pageA); + await disconnectByClick(pageB); + + // click together, both init with default id should lead to conflicts + await initEmptyParagraphState(pageA); + await initEmptyParagraphState(pageB); + + await waitNextFrame(pageA); + await focusRichText(pageA); + await waitNextFrame(pageB); + await focusRichText(pageB); + await waitNextFrame(pageA); + + await type(pageA, ''); + await waitNextFrame(pageB); + await type(pageB, ''); + await waitNextFrame(pageA); + await type(pageA, 'hello'); + await waitNextFrame(pageB); + + await assertText(pageB, 'hello'); + await assertText(pageA, 'hello'); // actually '\n' + } +); + +test(scoped`basic paired undo/redo`, async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'hello'); + + await assertText(page, 'hello'); + await undoByClick(page); + await assertEmpty(page); + await redoByClick(page); + await assertText(page, 'hello'); + + await undoByClick(page); + await assertEmpty(page); + await redoByClick(page); + await assertText(page, 'hello'); +}); + +test(scoped`undo/redo with keyboard`, async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'hello'); + + await assertText(page, 'hello'); + await undoByKeyboard(page); + await assertEmpty(page); + await redoByClick(page); + await assertText(page, 'hello'); +}); + +test(scoped`undo after adding block twice`, async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + + await focusRichText(page); + await type(page, 'hello'); + await pressEnter(page); + await type(page, 'world'); + + await undoByKeyboard(page); + await assertRichTexts(page, ['hello']); + await redoByKeyboard(page); + await assertRichTexts(page, ['hello', 'world']); +}); + +test(scoped`undo/redo twice after adding block twice`, async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'hello'); + await pressEnter(page); + await type(page, 'world'); + await assertRichTexts(page, ['hello', 'world']); + + await undoByKeyboard(page); + await assertRichTexts(page, ['hello']); + + await undoByKeyboard(page); + await assertRichTexts(page, ['']); + + await redoByClick(page); + await assertRichTexts(page, ['hello']); + + await redoByKeyboard(page); + await assertRichTexts(page, ['hello', 'world']); +}); + +test(scoped`should undo/redo works on title`, async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await waitNextFrame(page); + await focusTitle(page); + await type(page, 'title'); + await focusRichText(page); + await type(page, 'hello world'); + + await assertTitle(page, 'title'); + await assertRichTexts(page, ['hello world']); + + await captureHistory(page); + await pressBackspace(page, 5); + await captureHistory(page); + await focusTitle(page); + await type(page, ' something'); + + await assertTitle(page, 'title something'); + await assertRichTexts(page, ['hello ']); + + await focusRichText(page); + await undoByKeyboard(page); + await assertTitle(page, 'title'); + await assertRichTexts(page, ['hello ']); + await undoByKeyboard(page); + await assertTitle(page, 'title'); + await assertRichTexts(page, ['hello world']); + + await redoByKeyboard(page); + await assertTitle(page, 'title'); + await assertRichTexts(page, ['hello ']); + await redoByKeyboard(page); + await assertTitle(page, 'title something'); + await assertRichTexts(page, ['hello ']); +}); + +test(scoped`undo multi notes`, async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await addNoteByClick(page); + await assertRichTexts(page, ['', '']); + + await undoByClick(page); + await assertRichTexts(page, ['']); + + await redoByClick(page); + await assertRichTexts(page, ['', '']); +}); + +test(scoped`change theme`, async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + const currentTheme = await getCurrentHTMLTheme(page); + await toggleDarkMode(page); + const expectNextTheme = currentTheme === 'light' ? 'dark' : 'light'; + const nextHTMLTheme = await getCurrentHTMLTheme(page); + expect(nextHTMLTheme).toBe(expectNextTheme); + + const nextEditorTheme = await getCurrentEditorTheme(page); + expect(nextEditorTheme).toBe(expectNextTheme); +}); + +test( + scoped`should be able to delete an emoji completely by pressing backspace once`, + async ({ page }) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/toeverything/blocksuite/issues/2138', + }); + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, '🌷🙅‍♂️🏳️‍🌈'); + await pressBackspace(page); + await pressBackspace(page); + await pressBackspace(page); + await assertText(page, ''); + } +); + +test(scoped`delete emoji in the middle of the text`, async ({ page }) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/toeverything/blocksuite/issues/2138', + }); + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, '1🌷1🙅‍♂️1🏳️‍🌈1👨‍👩‍👧‍👦1'); + await pressArrowLeft(page, 1); + await pressBackspace(page); + await pressArrowLeft(page, 1); + await pressBackspace(page); + await pressArrowLeft(page, 1); + await pressBackspace(page); + await pressArrowLeft(page, 1); + await pressBackspace(page); + await assertText(page, '11111'); +}); + +test(scoped`delete emoji forward`, async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, '1🌷1🙅‍♂️1🏳️‍🌈1👨‍👩‍👧‍👦1'); + await pressArrowLeft(page, 8); + await pressForwardDelete(page); + await pressArrowRight(page, 1); + await pressForwardDelete(page); + await pressArrowRight(page, 1); + await pressForwardDelete(page); + await pressArrowRight(page, 1); + await pressForwardDelete(page); + await assertText(page, '11111'); +}); + +test( + scoped`ZERO_WIDTH_SPACE should be counted by one cursor position`, + async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await pressShiftEnter(page); + await type(page, 'asdfg'); + await pressEnter(page); + await undoByKeyboard(page); + await page.waitForTimeout(300); + await pressBackspace(page); + await assertRichTexts(page, ['\nasdf']); + } +); + +test('when no note block, click editing area auto add a new note block', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + + await switchEditorMode(page); + await page.locator('affine-edgeless-note').click({ force: true }); + await pressBackspace(page); + await switchEditorMode(page); + const edgelessNote = await page.evaluate(() => { + return document.querySelector('affine-edgeless-note'); + }); + expect(edgelessNote).toBeNull(); + await click(page, { x: 200, y: 280 }); + + const pageNote = await page.evaluate(() => { + return document.querySelector('affine-note'); + }); + expect(pageNote).not.toBeNull(); +}); + +test(scoped`automatic identify url text`, async ({ page }, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'abc https://google.com '); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_final.json` + ); +}); + +test('ctrl+delete to delete one word forward', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'aaa bbb ccc'); + await pressArrowLeft(page, 8); + await pressForwardDeleteWord(page); + await assertText(page, 'aaa ccc'); +}); + +test('extended inline format', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'aaabbbaaa'); + + const { boldBtn, italicBtn, underlineBtn, strikeBtn, codeBtn } = + getFormatBar(page); + await setSelection(page, 0, 3, 0, 6); + await boldBtn.click(); + await italicBtn.click(); + await underlineBtn.click(); + await strikeBtn.click(); + await codeBtn.click(); + await assertRichTextInlineDeltas(page, [ + { + insert: 'aaa', + }, + { + insert: 'bbb', + attributes: { + bold: true, + italic: true, + underline: true, + strike: true, + code: true, + }, + }, + { + insert: 'aaa', + }, + ]); + + // aaa|bbbccc + await setSelection(page, 2, 3, 2, 3); + await captureHistory(page); + await type(page, 'c'); + await assertRichTextInlineDeltas(page, [ + { + insert: 'aaac', + }, + { + insert: 'bbb', + attributes: { + bold: true, + italic: true, + underline: true, + strike: true, + code: true, + }, + }, + { + insert: 'aaa', + }, + ]); + await undoByKeyboard(page); + + // aaab|bbccc + await setSelection(page, 2, 4, 2, 4); + await type(page, 'c'); + await assertRichTextInlineDeltas(page, [ + { + insert: 'aaa', + }, + { + insert: 'bcbb', + attributes: { + bold: true, + italic: true, + underline: true, + strike: true, + code: true, + }, + }, + { + insert: 'aaa', + }, + ]); + await undoByKeyboard(page); + + // aaab|b|bccc + await setSelection(page, 2, 4, 2, 5); + await type(page, 'c'); + await assertRichTextInlineDeltas(page, [ + { + insert: 'aaa', + }, + { + insert: 'bcb', + attributes: { + bold: true, + italic: true, + underline: true, + strike: true, + code: true, + }, + }, + { + insert: 'aaa', + }, + ]); + await undoByKeyboard(page); + + // aaabbb|ccc + await setSelection(page, 2, 6, 2, 6); + await type(page, 'c'); + await assertRichTextInlineDeltas(page, [ + { + insert: 'aaa', + }, + { + insert: 'bbb', + attributes: { + bold: true, + italic: true, + underline: true, + strike: true, + code: true, + }, + }, + { + insert: 'c', + attributes: { + bold: true, + italic: true, + underline: true, + strike: true, + }, + }, + { + insert: 'aaa', + }, + ]); +}); diff --git a/blocksuite/tests-legacy/bookmark.spec.ts b/blocksuite/tests-legacy/bookmark.spec.ts new file mode 100644 index 0000000000000..f7f7909872557 --- /dev/null +++ b/blocksuite/tests-legacy/bookmark.spec.ts @@ -0,0 +1,461 @@ +import './utils/declare-test-window.js'; + +import type { Page } from '@playwright/test'; +import { expect } from '@playwright/test'; +import type { BlockSnapshot } from '@store/index.js'; +import { ignoreSnapshotId } from 'utils/ignore.js'; +import { getEmbedCardToolbar } from 'utils/query.js'; + +import { + activeNoteInEdgeless, + copyByKeyboard, + dragBlockToPoint, + enterPlaygroundRoom, + expectConsoleMessage, + focusRichText, + getPageSnapshot, + initEmptyEdgelessState, + initEmptyParagraphState, + pasteByKeyboard, + pressArrowDown, + pressArrowRight, + pressArrowUp, + pressBackspace, + pressEnter, + pressShiftTab, + pressTab, + selectAllByKeyboard, + setInlineRangeInSelectedRichText, + SHORT_KEY, + switchEditorMode, + type, + waitForInlineEditorStateUpdated, + waitNextFrame, +} from './utils/actions/index.js'; +import { + assertAlmostEqual, + assertBlockChildrenIds, + assertBlockCount, + assertBlockFlavour, + assertBlockSelections, + assertExists, + assertParentBlockFlavour, + assertRichTextInlineRange, +} from './utils/asserts.js'; +import { scoped, test } from './utils/playwright.js'; + +const LOCAL_HOST_URL = 'http://localhost'; + +const YOUTUBE_URL = 'https://www.youtube.com/watch?v=fakeid'; + +const FIGMA_URL = 'https://www.figma.com/design/JuXs6uOAICwf4I4tps0xKZ123'; + +test.beforeEach(async ({ page }) => { + await page.route( + 'https://affine-worker.toeverything.workers.dev/api/worker/link-preview', + async route => { + await route.fulfill({ + json: {}, + }); + } + ); +}); + +const createBookmarkBlockBySlashMenu = async ( + page: Page, + url = LOCAL_HOST_URL +) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await page.waitForTimeout(100); + await type(page, '/link', 100); + await pressEnter(page); + await page.waitForTimeout(100); + await type(page, url); + await pressEnter(page); +}; + +test(scoped`create bookmark by slash menu`, async ({ page }, testInfo) => { + await createBookmarkBlockBySlashMenu(page); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_final.json` + ); +}); + +test(scoped`covert bookmark block to link text`, async ({ page }, testInfo) => { + await createBookmarkBlockBySlashMenu(page); + const bookmark = page.locator('affine-bookmark'); + await bookmark.click(); + await page.waitForTimeout(100); + await page.getByRole('button', { name: 'Switch view' }).click(); + await page.getByRole('button', { name: 'Inline view' }).click(); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_final.json` + ); +}); + +test( + scoped`copy url to create bookmark in page mode`, + async ({ page }, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + await type(page, LOCAL_HOST_URL); + await setInlineRangeInSelectedRichText(page, 0, LOCAL_HOST_URL.length); + await copyByKeyboard(page); + await focusRichText(page); + await type(page, '/link'); + await pressEnter(page); + await page.keyboard.press(`${SHORT_KEY}+v`); + await pressEnter(page); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_final.json` + ); + } +); + +test( + scoped`copy url to create bookmark in edgeless mode`, + async ({ page }, testInfo) => { + await enterPlaygroundRoom(page); + const ids = await initEmptyEdgelessState(page); + await focusRichText(page); + await type(page, LOCAL_HOST_URL); + + await switchEditorMode(page); + + await activeNoteInEdgeless(page, ids.noteId); + await waitForInlineEditorStateUpdated(page); + await selectAllByKeyboard(page); + await copyByKeyboard(page); + await pressArrowRight(page); + await waitNextFrame(page); + await type(page, '/link', 100); + await pressEnter(page); + await page.waitForTimeout(100); + await waitNextFrame(page); + await page.keyboard.press(`${SHORT_KEY}+v`); + await pressEnter(page); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_final.json` + ); + } +); + +test.fixme( + scoped`support dragging bookmark block directly`, + async ({ page }, testInfo) => { + await createBookmarkBlockBySlashMenu(page); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_init.json` + ); + + const bookmark = page.locator('affine-bookmark'); + const rect = await bookmark.boundingBox(); + if (!rect) { + throw new Error('image not found'); + } + + // add new paragraph blocks + await page.mouse.click(rect.x + 20, rect.y + rect.height + 20); + await focusRichText(page); + await type(page, '111'); + await page.waitForTimeout(200); + await pressEnter(page); + + await type(page, '222'); + await page.waitForTimeout(200); + await pressEnter(page); + + await type(page, '333'); + await page.waitForTimeout(200); + + await page.waitForTimeout(200); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_after_add_paragraph.json` + ); + + // drag bookmark block + await page.mouse.move(rect.x + 20, rect.y + 20); + await page.mouse.down(); + await page.waitForTimeout(200); + + await page.mouse.move(rect.x + 40, rect.y + rect.height + 80, { + steps: 5, + }); + await page.waitForTimeout(200); + + await page.mouse.up(); + await page.waitForTimeout(200); + + const rects = page + .locator('affine-block-selection') + .locator('visible=true'); + await expect(rects).toHaveCount(1); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_after_drag.json` + ); + } +); + +test('press backspace after bookmark block can select bookmark block', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + await pressEnter(page); + await pressArrowUp(page); + await type(page, '/link'); + await pressEnter(page); + await page.waitForTimeout(100); + await type(page, LOCAL_HOST_URL); + await pressEnter(page); + + await focusRichText(page); + await assertBlockCount(page, 'paragraph', 1); + await assertRichTextInlineRange(page, 0, 0); + await pressBackspace(page); + await assertBlockSelections(page, ['4']); + await assertBlockCount(page, 'paragraph', 0); +}); + +test.describe('embed card toolbar', () => { + async function showEmbedCardToolbar(page: Page) { + await createBookmarkBlockBySlashMenu(page); + const bookmark = page.locator('affine-bookmark'); + await bookmark.click(); + await page.waitForTimeout(100); + const { embedCardToolbar } = getEmbedCardToolbar(page); + await expect(embedCardToolbar).toBeVisible(); + } + + test('show toolbar when bookmark selected', async ({ page }) => { + await showEmbedCardToolbar(page); + }); + + test('copy bookmark url by copy button', async ({ page }, testInfo) => { + await showEmbedCardToolbar(page); + const { copyButton } = getEmbedCardToolbar(page); + await copyButton.click(); + await page.mouse.click(600, 600); + await waitNextFrame(page); + + await pasteByKeyboard(page); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_final.json` + ); + }); + + test('change card style', async ({ page }) => { + await showEmbedCardToolbar(page); + const bookmark = page.locator('affine-bookmark'); + const { openCardStyleMenu } = getEmbedCardToolbar(page); + await openCardStyleMenu(); + const { cardStyleHorizontalButton, cardStyleListButton } = + getEmbedCardToolbar(page); + await cardStyleListButton.click(); + await waitNextFrame(page); + const listStyleBookmarkBox = await bookmark.boundingBox(); + assertExists(listStyleBookmarkBox); + assertAlmostEqual(listStyleBookmarkBox.width, 752, 2); + assertAlmostEqual(listStyleBookmarkBox.height, 46, 2); + + await openCardStyleMenu(); + await cardStyleHorizontalButton.click(); + await waitNextFrame(page); + const horizontalStyleBookmarkBox = await bookmark.boundingBox(); + assertExists(horizontalStyleBookmarkBox); + assertAlmostEqual(horizontalStyleBookmarkBox.width, 752, 2); + assertAlmostEqual(horizontalStyleBookmarkBox.height, 116, 2); + }); +}); + +test('indent bookmark block to paragraph', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + + await focusRichText(page); + await pressEnter(page); + await type(page, '/link', 100); + await pressEnter(page); + await type(page, LOCAL_HOST_URL); + await pressEnter(page); + + await assertBlockChildrenIds(page, '1', ['2', '4']); + await assertBlockFlavour(page, '1', 'affine:note'); + await assertBlockFlavour(page, '2', 'affine:paragraph'); + await assertBlockFlavour(page, '4', 'affine:bookmark'); + + await focusRichText(page); + await pressArrowDown(page); + await assertBlockSelections(page, ['4']); + await pressTab(page); + await assertBlockChildrenIds(page, '1', ['2']); + await assertBlockChildrenIds(page, '2', ['4']); + + await pressShiftTab(page); + await assertBlockChildrenIds(page, '1', ['2', '4']); +}); + +test('indent bookmark block to list', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + + await focusRichText(page); + await type(page, '- a'); + await pressEnter(page); + await type(page, '/link', 100); + await pressEnter(page); + await type(page, LOCAL_HOST_URL); + await pressEnter(page); + + await assertBlockChildrenIds(page, '1', ['3', '5']); + await assertBlockFlavour(page, '1', 'affine:note'); + await assertBlockFlavour(page, '3', 'affine:list'); + await assertBlockFlavour(page, '5', 'affine:bookmark'); + + await focusRichText(page); + await pressArrowDown(page); + await assertBlockSelections(page, ['5']); + await pressTab(page); + await assertBlockChildrenIds(page, '1', ['3']); + await assertBlockChildrenIds(page, '3', ['5']); + + await pressShiftTab(page); + await assertBlockChildrenIds(page, '1', ['3', '5']); +}); + +test('bookmark can be dragged from note to surface top level block', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await focusRichText(page); + await page.waitForTimeout(100); + await type(page, '/link', 100); + await pressEnter(page); + await page.waitForTimeout(100); + await type(page, LOCAL_HOST_URL); + await pressEnter(page); + + await switchEditorMode(page); + await page.mouse.dblclick(450, 450); + + await dragBlockToPoint(page, '4', { x: 200, y: 200 }); + + await waitNextFrame(page); + await assertParentBlockFlavour(page, '4', 'affine:surface'); +}); + +test.describe('embed youtube card', () => { + test(scoped`create youtube card by slash menu`, async ({ page }) => { + expectConsoleMessage(page, /Unrecognized feature/, 'warning'); + expectConsoleMessage(page, /Failed to load resource/); + await createBookmarkBlockBySlashMenu(page, YOUTUBE_URL); + const snapshot = (await getPageSnapshot(page)) as BlockSnapshot; + expect(ignoreSnapshotId(snapshot)).toMatchSnapshot('embed-youtube.json'); + }); + + test(scoped`change youtube card style`, async ({ page }) => { + expectConsoleMessage(page, /Unrecognized feature/, 'warning'); + expectConsoleMessage(page, /Failed to load resource/); + + await createBookmarkBlockBySlashMenu(page, YOUTUBE_URL); + const youtube = page.locator('affine-embed-youtube-block'); + await youtube.click(); + await page.waitForTimeout(100); + + // change to card view + const embedToolbar = page.locator('affine-embed-card-toolbar'); + await expect(embedToolbar).toBeVisible(); + const embedView = page.locator('editor-menu-button', { + hasText: 'embed view', + }); + await expect(embedView).toBeVisible(); + await embedView.click(); + const cardView = page.locator('editor-menu-action', { + hasText: 'card view', + }); + await expect(cardView).toBeVisible(); + await cardView.click(); + const snapshot = (await getPageSnapshot(page)) as BlockSnapshot; + expect(ignoreSnapshotId(snapshot)).toMatchSnapshot( + 'horizontal-youtube.json' + ); + + // change to embed view + const bookmark = page.locator('affine-bookmark'); + await bookmark.click(); + await page.waitForTimeout(100); + const cardView2 = page.locator('editor-icon-button', { + hasText: 'card view', + }); + await expect(cardView2).toBeVisible(); + await cardView2.click(); + const embedView2 = page.locator('editor-menu-action', { + hasText: 'embed view', + }); + await expect(embedView2).toBeVisible(); + await embedView2.click(); + const snapshot2 = (await getPageSnapshot(page)) as BlockSnapshot; + expect(ignoreSnapshotId(snapshot2)).toMatchSnapshot('embed-youtube.json'); + }); +}); + +test.describe('embed figma card', () => { + test(scoped`create figma card by slash menu`, async ({ page }) => { + expectConsoleMessage(page, /Failed to load resource/); + expectConsoleMessage(page, /Refused to frame/); + await createBookmarkBlockBySlashMenu(page, FIGMA_URL); + const snapshot = (await getPageSnapshot(page)) as BlockSnapshot; + expect(ignoreSnapshotId(snapshot)).toMatchSnapshot('embed-figma.json'); + }); + + test(scoped`change figma card style`, async ({ page }) => { + expectConsoleMessage(page, /Failed to load resource/); + expectConsoleMessage(page, /Refused to frame/); + expectConsoleMessage(page, /Running frontend commit/, 'log'); + await createBookmarkBlockBySlashMenu(page, FIGMA_URL); + const youtube = page.locator('affine-embed-figma-block'); + await youtube.click(); + await page.waitForTimeout(100); + + // change to card view + const embedToolbar = page.locator('affine-embed-card-toolbar'); + await expect(embedToolbar).toBeVisible(); + const embedView = page.locator('editor-menu-button', { + hasText: 'embed view', + }); + await expect(embedView).toBeVisible(); + await embedView.click(); + const cardView = page.locator('editor-menu-action', { + hasText: 'card view', + }); + await expect(cardView).toBeVisible(); + await cardView.click(); + const snapshot = (await getPageSnapshot(page)) as BlockSnapshot; + expect(ignoreSnapshotId(snapshot)).toMatchSnapshot('horizontal-figma.json'); + + // change to embed view + const bookmark = page.locator('affine-bookmark'); + await bookmark.click(); + await page.waitForTimeout(100); + const cardView2 = page.locator('editor-icon-button', { + hasText: 'card view', + }); + await expect(cardView2).toBeVisible(); + await cardView2.click(); + const embedView2 = page.locator('editor-menu-action', { + hasText: 'embed view', + }); + await expect(embedView2).toBeVisible(); + await embedView2.click(); + const snapshot2 = (await getPageSnapshot(page)) as BlockSnapshot; + expect(ignoreSnapshotId(snapshot2)).toMatchSnapshot('embed-figma.json'); + }); +}); diff --git a/blocksuite/tests-legacy/clipboard/clipboard.spec.ts b/blocksuite/tests-legacy/clipboard/clipboard.spec.ts new file mode 100644 index 0000000000000..f7519838c8d18 --- /dev/null +++ b/blocksuite/tests-legacy/clipboard/clipboard.spec.ts @@ -0,0 +1,409 @@ +import '../utils/declare-test-window.js'; + +import { expect } from '@playwright/test'; + +import { + captureHistory, + copyByKeyboard, + dragBetweenCoords, + dragOverTitle, + enterPlaygroundRoom, + focusRichText, + focusTitle, + getClipboardHTML, + getClipboardSnapshot, + getClipboardText, + getCurrentEditorDocId, + getEditorLocator, + getPageSnapshot, + initEmptyParagraphState, + mockParseDocUrlService, + pasteByKeyboard, + pasteContent, + pressEnter, + pressShiftTab, + pressTab, + resetHistory, + setInlineRangeInSelectedRichText, + setSelection, + SHORT_KEY, + type, + undoByClick, + waitNextFrame, +} from '../utils/actions/index.js'; +import { + assertBlockTypes, + assertClipItems, + assertExists, + assertRichTexts, + assertText, + assertTitle, +} from '../utils/asserts.js'; +import { scoped, test } from '../utils/playwright.js'; + +test(scoped`clipboard copy paste`, async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + await type(page, 'test'); + await setInlineRangeInSelectedRichText(page, 0, 3); + await waitNextFrame(page); + await copyByKeyboard(page); + await focusRichText(page); + await page.keyboard.press(`${SHORT_KEY}+v`); + await assertText(page, 'testtes'); +}); + +test(scoped`clipboard copy paste title`, async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusTitle(page); + + await type(page, 'test'); + await dragOverTitle(page); + await waitNextFrame(page); + await copyByKeyboard(page); + await focusTitle(page); + await page.keyboard.press(`${SHORT_KEY}+v`); + await assertTitle(page, 'testtest'); +}); + +test(scoped`clipboard paste html`, async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + // set up clipboard data using html + const clipData = { + 'text/html': `aaabbbcccddd`, + }; + await waitNextFrame(page); + await page.evaluate( + ({ clipData }) => { + const dT = new DataTransfer(); + const e = new ClipboardEvent('paste', { clipboardData: dT }); + Object.defineProperty(e, 'target', { + writable: false, + value: document, + }); + e.clipboardData?.setData('text/html', clipData['text/html']); + document.dispatchEvent(e); + }, + { clipData } + ); + await assertText(page, 'aaabbbcccddd'); +}); + +test(scoped`split block when paste`, async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await resetHistory(page); + + const clipData = { + 'text/plain': `# text +# h1 +`, + }; + await type(page, 'abc'); + await captureHistory(page); + + await setInlineRangeInSelectedRichText(page, 1, 1); + await pasteContent(page, clipData); + await waitNextFrame(page); + + await assertRichTexts(page, ['atext', 'h1c']); + + await undoByClick(page); + await assertRichTexts(page, ['abc']); + + await type(page, 'aa'); + await pressEnter(page); + await type(page, 'bb'); + const topLeft123 = await getEditorLocator(page) + .locator('[data-block-id="2"] .inline-editor') + .boundingBox(); + const bottomRight789 = await getEditorLocator(page) + .locator('[data-block-id="4"] .inline-editor') + .boundingBox(); + assertExists(topLeft123); + assertExists(bottomRight789); + await dragBetweenCoords(page, topLeft123, bottomRight789); + + // FIXME see https://github.com/toeverything/blocksuite/pull/878 + // await pasteContent(page, clipData); + // await assertRichTexts(page, ['aaa', 'bbc', 'text', 'h1']); +}); + +test(scoped`copy clipItems format`, async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await captureHistory(page); + + const clipData = ` +- aa + - bb + - cc + - dd +`; + + await pasteContent(page, { 'text/plain': clipData }); + await page.waitForTimeout(100); + await setSelection(page, 4, 1, 5, 1); + assertClipItems(page, 'text/plain', 'bc'); + assertClipItems(page, 'text/html', '
  • b
    • c
'); + await undoByClick(page); + await assertRichTexts(page, ['']); +}); + +test(scoped`copy partially selected text`, async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + await type(page, '123 456 789'); + + // select 456 + await setInlineRangeInSelectedRichText(page, 4, 3); + await copyByKeyboard(page); + assertClipItems(page, 'text/plain', '456'); + + // move to line end + await setInlineRangeInSelectedRichText(page, 11, 0); + await pressEnter(page); + await pasteByKeyboard(page); + await waitNextFrame(page); + + await assertRichTexts(page, ['123 456 789', '456']); +}); + +test(scoped`copy & paste outside editor`, async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await page.evaluate(() => { + const input = document.createElement('input'); + input.setAttribute('id', 'input-test'); + input.value = '123'; + document.body.querySelector('#app')?.append(input); + }); + await page.focus('#input-test'); + await page.dblclick('#input-test'); + await copyByKeyboard(page); + await focusRichText(page); + await pasteByKeyboard(page); + await waitNextFrame(page); + await assertRichTexts(page, ['123']); +}); + +test('should keep first line format when pasted into a new line', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + const clipData = ` +- [ ] aaa +`; + + await pasteContent(page, { 'text/plain': clipData }); + await waitNextFrame(page); + await assertRichTexts(page, ['aaa']); + await assertBlockTypes(page, ['todo']); +}); + +test(scoped`auto identify url`, async ({ page }, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + // set up clipboard data using html + const clipData = { + 'text/plain': `test https://www.google.com`, + }; + await waitNextFrame(page); + await page.evaluate( + ({ clipData }) => { + const dT = new DataTransfer(); + const e = new ClipboardEvent('paste', { clipboardData: dT }); + Object.defineProperty(e, 'target', { + writable: false, + value: document, + }); + e.clipboardData?.setData('text/plain', clipData['text/plain']); + document.dispatchEvent(e); + }, + { clipData } + ); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_final.json` + ); +}); + +test(scoped`pasting internal url`, async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusTitle(page); + await type(page, 'test page'); + + await focusRichText(page); + const docId = await getCurrentEditorDocId(page); + await mockParseDocUrlService(page, { + 'http://workspace/doc-id': docId, + }); + await pasteContent(page, { + 'text/plain': 'http://workspace/doc-id', + }); + await expect(page.locator('affine-reference')).toContainText('test page'); +}); + +test(scoped`pasting internal url with params`, async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusTitle(page); + await type(page, 'test page'); + + await focusRichText(page); + const docId = await getCurrentEditorDocId(page); + await mockParseDocUrlService(page, { + 'http://workspace/doc-id?mode=page&blockIds=rL2_GXbtLU2SsJVfCSmh_': docId, + }); + await pasteContent(page, { + 'text/plain': + 'http://workspace/doc-id?mode=page&blockIds=rL2_GXbtLU2SsJVfCSmh_', + }); + await expect(page.locator('affine-reference')).toContainText('test page'); +}); + +test( + scoped`pasting an external URL from clipboard to automatically creating a link from selection`, + async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusTitle(page); + await type(page, 'test page'); + + await focusRichText(page); + await type(page, 'title alias'); + await setSelection(page, 1, 6, 1, 11); + + await pasteContent(page, { + 'text/plain': 'https://affine.pro/', + }); + await expect(page.locator('affine-link')).toContainText('alias'); + } +); + +test( + scoped`pasting an internal URL from clipboard to automatically creating a link from selection`, + async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusTitle(page); + await type(page, 'test page'); + + await focusRichText(page); + await type(page, 'title alias'); + await setSelection(page, 1, 6, 1, 11); + + const docId = await getCurrentEditorDocId(page); + await mockParseDocUrlService(page, { + 'http://workspace/doc-id': docId, + }); + await pasteContent(page, { + 'text/plain': 'http://workspace/doc-id', + }); + await expect(page.locator('affine-reference')).toContainText('alias'); + } +); + +test(scoped`paste parent block`, async ({ page }) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/toeverything/blocksuite/issues/3153', + }); + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'This is parent'); + await page.keyboard.press('Enter'); + await page.keyboard.press('Tab'); + await type(page, 'This is child 1'); + await page.keyboard.press('Enter'); + await page.keyboard.press('Tab'); + await type(page, 'This is child 2'); + await setInlineRangeInSelectedRichText(page, 0, 3); + await copyByKeyboard(page); + await focusRichText(page, 2); + await page.keyboard.press(`${SHORT_KEY}+v`); + await assertRichTexts(page, [ + 'This is parent', + 'This is child 1', + 'This is child 2Thi', + ]); +}); + +test(scoped`clipboard copy multi selection`, async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + await type(page, 'abc'); + await pressEnter(page); + await type(page, 'def'); + await setSelection(page, 2, 1, 3, 1); + await waitNextFrame(page); + await copyByKeyboard(page); + await waitNextFrame(page); + await focusRichText(page, 1); + await pasteByKeyboard(page); + await waitNextFrame(page); + await type(page, 'cursor'); + await waitNextFrame(page); + await assertRichTexts(page, ['abc', 'defbc', 'dcursor']); +}); + +test(scoped`clipboard copy nested items`, async ({ page }, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + await type(page, 'abc'); + await pressEnter(page); + await pressTab(page); + await type(page, 'def'); + await pressEnter(page); + await pressTab(page); + await type(page, 'ghi'); + await pressEnter(page); + await pressShiftTab(page); + await pressShiftTab(page); + await type(page, 'jkl'); + await setSelection(page, 2, 1, 3, 1); + await waitNextFrame(page); + await copyByKeyboard(page); + + const text = await getClipboardText(page); + const html = await getClipboardHTML(page); + const snapshot = await getClipboardSnapshot(page); + expect(text).toMatchSnapshot(`${testInfo.title}-clipboard.md`); + expect(JSON.stringify(snapshot.snapshot.content, null, 2)).toMatchSnapshot( + `${testInfo.title}-clipboard.json` + ); + expect(html).toMatchSnapshot(`${testInfo.title}-clipboard.html`); + + await setSelection(page, 4, 1, 5, 1); + await waitNextFrame(page); + await copyByKeyboard(page); + + const text2 = await getClipboardText(page); + const html2 = await getClipboardHTML(page); + const snapshot2 = await getClipboardSnapshot(page); + expect(text2).toMatchSnapshot(`${testInfo.title}-clipboard2.md`); + expect(JSON.stringify(snapshot2.snapshot.content, null, 2)).toMatchSnapshot( + `${testInfo.title}-clipboard2.json` + ); + expect(html2).toMatchSnapshot(`${testInfo.title}-clipboard2.html`); +}); diff --git a/blocksuite/tests-legacy/clipboard/image.spec.ts b/blocksuite/tests-legacy/clipboard/image.spec.ts new file mode 100644 index 0000000000000..05ff21d442ac2 --- /dev/null +++ b/blocksuite/tests-legacy/clipboard/image.spec.ts @@ -0,0 +1,58 @@ +import { + enterPlaygroundRoom, + focusRichText, + initEmptyParagraphState, + pasteContent, + pressArrowDown, + pressArrowUp, + pressEscape, + waitEmbedLoaded, +} from '../utils/actions/index.js'; +import { assertRichImage, assertText } from '../utils/asserts.js'; +import { scoped, test } from '../utils/playwright.js'; + +test( + scoped`clipboard paste end with image, the cursor should be controlled by up/down keys`, + async ({ page }) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/toeverything/blocksuite/issues/3639', + }); + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + // set up clipboard data using html + const clipData = { + 'text/html': `

Lorem Ipsum placeholder text.

+
+ `, + }; + await page.evaluate( + ({ clipData }) => { + const dT = new DataTransfer(); + const e = new ClipboardEvent('paste', { clipboardData: dT }); + Object.defineProperty(e, 'target', { + writable: false, + value: document, + }); + e.clipboardData?.setData('text/html', clipData['text/html']); + document.dispatchEvent(e); + }, + { clipData } + ); + const str = 'Lorem Ipsum placeholder text.'; + await waitEmbedLoaded(page); + await assertRichImage(page, 1); + await pressEscape(page); + await pressArrowUp(page, 1); + await pasteContent(page, clipData); + await assertRichImage(page, 2); + await assertText(page, str + str); + await pressArrowDown(page, 1); + await pressEscape(page); + await pasteContent(page, clipData); + await assertRichImage(page, 3); + await assertText(page, 'Lorem Ipsum placeholder text.', 1); + } +); diff --git a/blocksuite/tests-legacy/clipboard/list.spec.ts b/blocksuite/tests-legacy/clipboard/list.spec.ts new file mode 100644 index 0000000000000..ab8ee5c56cf8a --- /dev/null +++ b/blocksuite/tests-legacy/clipboard/list.spec.ts @@ -0,0 +1,714 @@ +import { expect } from '@playwright/test'; + +import { initDatabaseColumn } from '../database/actions.js'; +import { + activeNoteInEdgeless, + changeEdgelessNoteBackground, + copyByKeyboard, + createShapeElement, + cutByKeyboard, + dragBetweenCoords, + enterPlaygroundRoom, + focusRichText, + getAllNoteIds, + getClipboardHTML, + getClipboardSnapshot, + getClipboardText, + getEdgelessSelectedRectModel, + getInlineSelectionIndex, + getInlineSelectionText, + getPageSnapshot, + getRichTextBoundingBox, + initDatabaseDynamicRowWithData, + initEmptyDatabaseWithParagraphState, + initEmptyEdgelessState, + initEmptyParagraphState, + initThreeParagraphs, + pasteByKeyboard, + pasteContent, + pressArrowLeft, + pressArrowRight, + pressEnter, + pressEscape, + pressShiftTab, + pressSpace, + pressTab, + selectAllByKeyboard, + selectNoteInEdgeless, + setInlineRangeInSelectedRichText, + SHORT_KEY, + switchEditorMode, + toViewCoord, + triggerComponentToolbarAction, + type, + undoByKeyboard, + waitForInlineEditorStateUpdated, + waitNextFrame, +} from '../utils/actions/index.js'; +import { + assertBlockTypes, + assertEdgelessNoteBackground, + assertEdgelessSelectedModelRect, + assertExists, + assertRichTextModelType, + assertRichTexts, + assertStoreMatchJSX, + assertText, +} from '../utils/asserts.js'; +import { scoped, test } from '../utils/playwright.js'; + +test('paste a non-nested list to a non-nested list', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + const clipData = { + 'text/plain': ` +- a +`, + }; + await type(page, '-'); + await pressSpace(page); + await type(page, '123'); + await page.keyboard.press('Control+ArrowLeft'); + + // paste on start + await waitNextFrame(page); + await pasteContent(page, clipData); + await pressArrowLeft(page); + await assertRichTexts(page, ['a123']); + + // paste in middle + await pressArrowRight(page, 2); + await pasteContent(page, clipData); + await pressArrowRight(page); + await assertRichTexts(page, ['a1a23']); + + // paste on end + await pressArrowRight(page); + await pasteContent(page, clipData); + await waitNextFrame(page); + await assertRichTexts(page, ['a1a23a']); + + await assertBlockTypes(page, ['bulleted']); +}); + +test('copy a nested list by clicking button, the clipboard data should be complete', async ({ + page, +}, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + const clipData = { + 'text/plain': ` +- aaa + - bbb + - ccc +`, + }; + await pasteContent(page, clipData); + + const rootListBound = await page.locator('affine-list').first().boundingBox(); + assertExists(rootListBound); + + // use drag element to test. + await dragBetweenCoords( + page, + { x: rootListBound.x + 1, y: rootListBound.y - 1 }, + { x: rootListBound.x + 1, y: rootListBound.y + rootListBound.height - 1 } + ); + await copyByKeyboard(page); + + const text = await getClipboardText(page); + const html = await getClipboardHTML(page); + const snapshot = await getClipboardSnapshot(page); + expect(text).toMatchSnapshot(`${testInfo.title}-clipboard.md`); + expect(JSON.stringify(snapshot.snapshot.content, null, 2)).toMatchSnapshot( + `${testInfo.title}-clipboard.json` + ); + expect(html).toMatchSnapshot(`${testInfo.title}-clipboard.html`); +}); + +test('paste a nested list to a nested list', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + const clipData = { + 'text/plain': ` +- aaa + - bbb + - ccc +`, + }; + await pasteContent(page, clipData); + await focusRichText(page, 1); + + // paste on start + await page.keyboard.press('Control+ArrowLeft'); + + /** + * - aaa + * - |bbb + * - ccc + */ + await pasteContent(page, clipData); + /** + * - aaa + * - aaa + * - bbb + * - ccc|bbb + * -ccc + */ + + await assertRichTexts(page, ['aaa', 'aaa', 'bbb', 'cccbbb', 'ccc']); + expect(await getInlineSelectionText(page)).toEqual('cccbbb'); + expect(await getInlineSelectionIndex(page)).toEqual(3); + + // paste in middle + await undoByKeyboard(page); + await pressArrowRight(page); + + /** + * - aaa + * - b|bb + * - ccc + */ + await pasteContent(page, clipData); + /** + * - aaa + * - baaa + * - bbb + * - ccc|bb + * - ccc + */ + + await assertRichTexts(page, ['aaa', 'baaa', 'bbb', 'cccbb', 'ccc']); + expect(await getInlineSelectionText(page)).toEqual('cccbb'); + expect(await getInlineSelectionIndex(page)).toEqual(3); + + // paste on end + await undoByKeyboard(page); + await page.keyboard.press('Control+ArrowRight'); + + /** + * - aaa + * - bbb| + * - ccc + */ + await pasteContent(page, clipData); + /** + * - aaa + * - bbbaaa + * - bbb + * - ccc| + * - ccc + */ + + await assertRichTexts(page, ['aaa', 'bbbaaa', 'bbb', 'ccc', 'ccc']); + expect(await getInlineSelectionText(page)).toEqual('ccc'); + expect(await getInlineSelectionIndex(page)).toEqual(3); +}); + +test('paste nested lists to a nested list', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + const clipData = { + 'text/plain': ` +- aaa + - bbb + - ccc +`, + }; + await pasteContent(page, clipData); + await focusRichText(page, 1); + + const clipData2 = { + 'text/plain': ` +- 111 + - 222 +- 111 + - 222 +`, + }; + + // paste on start + await page.keyboard.press('Control+ArrowLeft'); + + /** + * - aaa + * - |bbb + * - ccc + */ + await pasteContent(page, clipData2); + /** + * - aaa + * - 111 + * - 222 + * - 111 + * - 222|bbb + * - ccc + */ + + await assertRichTexts(page, ['aaa', '111', '222', '111', '222bbb', 'ccc']); + expect(await getInlineSelectionText(page)).toEqual('222bbb'); + expect(await getInlineSelectionIndex(page)).toEqual(3); + + // paste in middle + await undoByKeyboard(page); + await pressArrowRight(page); + + /** + * - aaa + * - b|bb + * - ccc + */ + await pasteContent(page, clipData2); + /** + * - aaa + * - b111 + * - 222 + * - 111 + * - 222|bb + * - ccc + */ + + await assertRichTexts(page, ['aaa', 'b111', '222', '111', '222bb', 'ccc']); + expect(await getInlineSelectionText(page)).toEqual('222bb'); + expect(await getInlineSelectionIndex(page)).toEqual(3); + + // paste on end + await undoByKeyboard(page); + await page.keyboard.press('Control+ArrowRight'); + + /** + * - aaa + * - bbb| + * - ccc + */ + await pasteContent(page, clipData2); + /** + * - aaa + * - bbb111 + * - 222 + * - 111 + * - 222| + * - ccc + */ + + await assertRichTexts(page, ['aaa', 'bbb111', '222', '111', '222', 'ccc']); + expect(await getInlineSelectionText(page)).toEqual('222'); + expect(await getInlineSelectionIndex(page)).toEqual(3); +}); + +test('paste non-nested lists to a nested list', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + const clipData = { + 'text/plain': ` +- aaa + - bbb +`, + }; + await pasteContent(page, clipData); + await focusRichText(page, 0); + + const clipData2 = { + 'text/plain': ` +- 123 +- 456 +`, + }; + + // paste on start + await page.keyboard.press('Control+ArrowLeft'); + + /** + * - |aaa + * - bbb + */ + await pasteContent(page, clipData2); + /** + * - 123 + * - 456|aaa + * - bbb + */ + + await assertRichTexts(page, ['123', '456aaa', 'bbb']); + expect(await getInlineSelectionText(page)).toEqual('456aaa'); + expect(await getInlineSelectionIndex(page)).toEqual(3); +}); + +test(scoped`cut should work for multi-block selection`, async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + await type(page, 'a'); + await pressEnter(page); + await type(page, 'b'); + await pressEnter(page); + await type(page, 'c'); + await selectAllByKeyboard(page); + await selectAllByKeyboard(page); + await selectAllByKeyboard(page); + await cutByKeyboard(page); + await page.locator('.affine-page-viewport').click(); + await waitNextFrame(page); + await assertText(page, ''); +}); + +test( + scoped`pasting into empty list should not convert the list into paragraph`, + async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'test'); + await setInlineRangeInSelectedRichText(page, 0, 4); + await copyByKeyboard(page); + await type(page, '- '); + await page.keyboard.press(`${SHORT_KEY}+v`); + await assertRichTexts(page, ['test']); + await assertRichTextModelType(page, 'bulleted'); + } +); + +test('cut will delete all content, and copy will reappear content', async ({ + page, +}, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, '-'); + await pressSpace(page); + await type(page, '1'); + await pressEnter(page); + await pressTab(page); + await type(page, '2'); + await pressEnter(page); + await type(page, '3'); + await pressEnter(page); + await pressShiftTab(page); + await type(page, '4'); + + const box123 = await getRichTextBoundingBox(page, '1'); + const inside123 = { x: box123.left + 1, y: box123.top + 1 }; + + const box789 = await getRichTextBoundingBox(page, '6'); + const inside789 = { x: box789.right - 1, y: box789.bottom - 1 }; + // from top to bottom + await dragBetweenCoords(page, inside123, inside789); + + await cutByKeyboard(page); + await waitNextFrame(page); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_after-cut.json` + ); + await waitNextFrame(page); + await focusRichText(page); + + await pasteByKeyboard(page); + await waitNextFrame(page); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_after-paste.json` + ); +}); + +test(scoped`should copy and paste of database work`, async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseWithParagraphState(page); + + // init database columns and rows + await initDatabaseColumn(page); + await initDatabaseDynamicRowWithData(page, 'abc', true); + await pressEscape(page); + await focusRichText(page, 1); + await selectAllByKeyboard(page); + await selectAllByKeyboard(page); + await copyByKeyboard(page); + await pressEnter(page); + await pasteByKeyboard(page); + await page.waitForTimeout(100); + + await assertStoreMatchJSX( + page, + /*xml*/ ` + + + + + + + + + + +` + ); + + await undoByKeyboard(page); + await assertStoreMatchJSX( + page, + /*xml*/ ` + + + + + + + +` + ); +}); + +test(`copy canvas element and text note in edgeless mode`, async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + await initThreeParagraphs(page); + await createShapeElement(page, [0, 0], [100, 100]); + await selectAllByKeyboard(page); + const bound = await getEdgelessSelectedRectModel(page); + await copyByKeyboard(page); + const coord = await toViewCoord(page, [ + bound[0] + bound[2] / 2, + bound[1] + bound[3] / 2 + 200, + ]); + await page.mouse.move(coord[0], coord[1]); + await page.waitForTimeout(300); + await pasteByKeyboard(page, false); + bound[1] = bound[1] + 200; + await assertEdgelessSelectedModelRect(page, bound); +}); + +test(scoped`copy when text note active in edgeless`, async ({ page }) => { + await enterPlaygroundRoom(page); + const ids = await initEmptyEdgelessState(page); + await focusRichText(page); + await type(page, '1234'); + + await switchEditorMode(page); + + await activeNoteInEdgeless(page, ids.noteId); + await waitForInlineEditorStateUpdated(page); + await setInlineRangeInSelectedRichText(page, 0, 4); + await copyByKeyboard(page); + await pressArrowRight(page); + await type(page, '555'); + await pasteByKeyboard(page, false); + await assertText(page, '12345551234'); +}); + +test(scoped`paste note block with background`, async ({ page }) => { + await enterPlaygroundRoom(page); + const ids = await initEmptyEdgelessState(page); + await focusRichText(page); + await type(page, '1234'); + + await switchEditorMode(page); + await selectNoteInEdgeless(page, ids.noteId); + + await triggerComponentToolbarAction(page, 'changeNoteColor'); + const color = '--affine-note-background-grey'; + await changeEdgelessNoteBackground(page, color); + await assertEdgelessNoteBackground(page, ids.noteId, color); + + await copyByKeyboard(page); + + await page.mouse.move(0, 0); + await pasteByKeyboard(page, false); + const noteIds = await getAllNoteIds(page); + for (const noteId of noteIds) { + await assertEdgelessNoteBackground(page, noteId, color); + } +}); + +test(scoped`copy and paste to selection block selection`, async ({ page }) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/toeverything/blocksuite/issues/2265', + }); + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, '1234'); + + await selectAllByKeyboard(page); + await copyByKeyboard(page); + await pressArrowRight(page); + await pasteByKeyboard(page, false); + await waitNextFrame(page); + await assertRichTexts(page, ['12341234']); +}); + +test( + scoped`should keep paragraph block's type when pasting at the start of empty paragraph block except type text`, + async ({ page }, testInfo) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/toeverything/blocksuite/issues/2336', + }); + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await focusRichText(page); + await type(page, '>'); + await page.keyboard.press('Space', { delay: 50 }); + + await page.evaluate(() => { + const input = document.createElement('input'); + input.setAttribute('id', 'input-test'); + input.value = '123'; + document.body.querySelector('#app')?.append(input); + }); + await page.focus('#input-test'); + await page.dblclick('#input-test'); + await copyByKeyboard(page); + await focusRichText(page); + await pasteByKeyboard(page); + await waitNextFrame(page); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_after-paste-1.json` + ); + + await pressEnter(page); + await waitNextFrame(page); + await pressEnter(page); + await waitNextFrame(page); + await pasteByKeyboard(page, false); + await waitNextFrame(page); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_after-paste-2.json` + ); + } +); + +test(scoped`paste from FeiShu list format`, async ({ page }) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/toeverything/blocksuite/issues/2438', + }); + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + // set up clipboard data using html + const clipData = { + 'text/html': `
  • aaaa
  • `, + }; + await waitNextFrame(page); + await page.evaluate( + ({ clipData }) => { + const dT = new DataTransfer(); + const e = new ClipboardEvent('paste', { clipboardData: dT }); + Object.defineProperty(e, 'target', { + writable: false, + value: document, + }); + e.clipboardData?.setData('text/html', clipData['text/html']); + document.dispatchEvent(e); + }, + { clipData } + ); + await assertText(page, 'aaaa'); + await assertBlockTypes(page, ['bulleted']); +}); + +test(scoped`paste in list format`, async ({ page }) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/toeverything/blocksuite/issues/2281', + }); + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + await type(page, '- test'); + await focusRichText(page); + + const clipData = { + 'text/html': `
    • 111
      • 222
    `, + }; + await waitNextFrame(page); + await page.evaluate( + ({ clipData }) => { + const dT = new DataTransfer(); + const e = new ClipboardEvent('paste', { clipboardData: dT }); + Object.defineProperty(e, 'target', { + writable: false, + value: document, + }); + e.clipboardData?.setData('text/html', clipData['text/html']); + document.dispatchEvent(e); + }, + { clipData } + ); + await assertRichTexts(page, ['test111', '222']); +}); diff --git a/blocksuite/tests-legacy/clipboard/markdown.spec.ts b/blocksuite/tests-legacy/clipboard/markdown.spec.ts new file mode 100644 index 0000000000000..1484fdb6ae5e6 --- /dev/null +++ b/blocksuite/tests-legacy/clipboard/markdown.spec.ts @@ -0,0 +1,170 @@ +import { + enterPlaygroundRoom, + focusRichText, + initEmptyParagraphState, + pasteContent, + resetHistory, + undoByClick, + waitEmbedLoaded, + waitNextFrame, +} from '../utils/actions/index.js'; +import { + assertBlockTypes, + assertRichImage, + assertRichTexts, + assertTextFormats, +} from '../utils/asserts.js'; +import { scoped, test } from '../utils/playwright.js'; + +test(scoped`markdown format parse`, async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await resetHistory(page); + + let clipData = { + 'text/plain': `# h1 + +## h2 + +### h3 + +#### h4 + +##### h5 + +###### h6 + +- [ ] todo + +- [ ] todo + +- [x] todo + +* bulleted + +- bulleted + +1. numbered + +> quote +`, + }; + await waitNextFrame(page); + await pasteContent(page, clipData); + await page.waitForTimeout(200); + await assertBlockTypes(page, [ + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'todo', + 'todo', + 'todo', + 'bulleted', + 'bulleted', + 'numbered', + 'quote', + ]); + await assertRichTexts(page, [ + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'todo', + 'todo', + 'todo', + 'bulleted', + 'bulleted', + 'numbered', + 'quote', + ]); + await undoByClick(page); + await assertRichTexts(page, ['']); + await focusRichText(page); + + clipData = { + 'text/plain': `# ***bolditalic*** +# **bold** + +*italic* + +~~strikethrough~~ + +[link](linktest) + +\`code\` +`, + }; + await waitNextFrame(page); + await pasteContent(page, clipData); + await page.waitForTimeout(200); + await assertTextFormats(page, [ + { bold: true, italic: true }, + { bold: true }, + { italic: true }, + { strike: true }, + { link: 'linktest' }, + { code: true }, + ]); + await undoByClick(page); + await assertRichTexts(page, ['']); +}); + +test(scoped`import markdown`, async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await resetHistory(page); + const clipData = `# text +# h1 +`; + await pasteContent(page, { 'text/plain': clipData }); + await page.waitForTimeout(100); + await assertRichTexts(page, ['text', 'h1']); + await undoByClick(page); + await assertRichTexts(page, ['']); +}); + +test( + scoped`clipboard paste HTML containing markdown syntax code and image `, + async ({ page }) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/toeverything/blocksuite/issues/2855', + }); + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + // set up clipboard data using html + const clipData = { + 'text/html': `

    符合 Markdown 格式的 URL 放到笔记中,此时需要的格式如下:

    +
    md [任务管理这件事 - 少数派](https://sspai.com/post/61092)
    +

    (将一段文字包裹在[[]]中)此时需要的格式如下:

    +
    +

    上图中,当我们处在 Obsidian 的「预览模式」时,点击这个「双向链接」

    + `, + }; + await page.evaluate( + ({ clipData }) => { + const dT = new DataTransfer(); + const e = new ClipboardEvent('paste', { clipboardData: dT }); + Object.defineProperty(e, 'target', { + writable: false, + value: document, + }); + e.clipboardData?.setData('text/html', clipData['text/html']); + document.dispatchEvent(e); + }, + { clipData } + ); + await waitEmbedLoaded(page); + // await page.waitForTimeout(500); + await assertRichImage(page, 1); + } +); diff --git a/blocksuite/tests-legacy/code/copy-paste.spec.ts b/blocksuite/tests-legacy/code/copy-paste.spec.ts new file mode 100644 index 0000000000000..9e7a9cd3d46d9 --- /dev/null +++ b/blocksuite/tests-legacy/code/copy-paste.spec.ts @@ -0,0 +1,149 @@ +import { expect } from '@playwright/test'; + +import { + copyByKeyboard, + pasteByKeyboard, + pressArrowLeft, + pressEnter, + pressEnterWithShortkey, + selectAllByKeyboard, + type, +} from '../utils/actions/keyboard.js'; +import { + enterPlaygroundRoom, + focusRichText, + getInlineSelectionText, + getPageSnapshot, + initEmptyCodeBlockState, + setSelection, +} from '../utils/actions/misc.js'; +import { assertRichTextInlineRange } from '../utils/asserts.js'; +import { test } from '../utils/playwright.js'; +import { getCodeBlock } from './utils.js'; + +test('keyboard selection and copy paste', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyCodeBlockState(page); + await focusRichText(page); + + await type(page, 'use'); + await page.keyboard.down('Shift'); + await pressArrowLeft(page, 'use'.length); + await page.keyboard.up('Shift'); + await copyByKeyboard(page); + await pressArrowLeft(page, 1); + await pasteByKeyboard(page); + + const content = await getInlineSelectionText(page); + expect(content).toBe('useuse'); + + await assertRichTextInlineRange(page, 0, 3, 0); +}); + +test('paste with more than one continuous breakline should remain in code block, ', async ({ + page, +}) => { + await page.setContent(`
    use super::*; +use fern::{ + colors::{Color, ColoredLevelConfig}, + Dispatch, +}; +

    +#[inline]
    `); + await page.focus('div'); + await selectAllByKeyboard(page); + await copyByKeyboard(page); + + await enterPlaygroundRoom(page); + await initEmptyCodeBlockState(page); + await focusRichText(page); + await pasteByKeyboard(page); + + const locator = page.locator('affine-paragraph'); + await expect(locator).toBeHidden(); +}); + +test('drag copy paste', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyCodeBlockState(page); + await focusRichText(page); + + await type(page, 'use'); + + await setSelection(page, 2, 0, 2, 3); + await copyByKeyboard(page); + await pressArrowLeft(page); + await pasteByKeyboard(page); + + const content = await getInlineSelectionText(page); + expect(content).toBe('useuse'); + + await assertRichTextInlineRange(page, 0, 3, 0); +}); + +test.skip('use keyboard copy inside code block copy', async ({ + page, +}, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyCodeBlockState(page); + await focusRichText(page); + + await type(page, 'use'); + await page.keyboard.down('Shift'); + // eslint-disable-next-line @typescript-eslint/prefer-for-of + for (let i = 0; i < 'use'.length; i++) { + await page.keyboard.press('ArrowLeft'); + } + await page.keyboard.up('Shift'); + await copyByKeyboard(page); + await page.keyboard.press('ArrowRight'); + await pressEnter(page); + await pressEnter(page); + await pasteByKeyboard(page); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_pasted.json` + ); +}); + +test('code block has content, click code block copy menu, copy whole code block', async ({ + page, +}, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyCodeBlockState(page, { language: 'javascript' }); + await focusRichText(page); + await page.keyboard.type('use'); + await pressEnterWithShortkey(page); + + const codeBlockController = getCodeBlock(page); + await codeBlockController.codeBlock.hover(); + + await expect(codeBlockController.copyButton).toBeVisible(); + await codeBlockController.copyButton.click(); + + await focusRichText(page, 1); + await pasteByKeyboard(page); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_pasted.json` + ); +}); + +test('code block is empty, click code block copy menu, copy the empty code block', async ({ + page, +}, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyCodeBlockState(page, { language: 'javascript' }); + await focusRichText(page); + + await pressEnterWithShortkey(page); + + const codeBlockController = getCodeBlock(page); + await codeBlockController.codeBlock.hover(); + await expect(codeBlockController.copyButton).toBeVisible(); + await codeBlockController.copyButton.click(); + + await focusRichText(page, 1); + await pasteByKeyboard(page); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_pasted.json` + ); +}); diff --git a/blocksuite/tests-legacy/code/crud.spec.ts b/blocksuite/tests-legacy/code/crud.spec.ts new file mode 100644 index 0000000000000..fc8cc5a5a04a9 --- /dev/null +++ b/blocksuite/tests-legacy/code/crud.spec.ts @@ -0,0 +1,668 @@ +import { expect } from '@playwright/test'; +import { dragBetweenIndices } from 'utils/actions/drag.js'; +import { getFormatBar } from 'utils/query.js'; + +import { updateBlockType } from '../utils/actions/block.js'; +import { + createCodeBlock, + pressArrowLeft, + pressArrowUp, + pressBackspace, + pressEnter, + pressEscape, + pressShiftTab, + pressTab, + redoByKeyboard, + type, + undoByKeyboard, +} from '../utils/actions/keyboard.js'; +import { + enterPlaygroundRoom, + focusRichText, + focusRichTextEnd, + getPageSnapshot, + initEmptyCodeBlockState, + initEmptyParagraphState, + setSelection, + waitNextFrame, +} from '../utils/actions/misc.js'; +import { + assertBlockCount, + assertRichTexts, + assertStoreMatchJSX, + assertTitle, +} from '../utils/asserts.js'; +import { test } from '../utils/playwright.js'; +import { getCodeBlock } from './utils.js'; + +test('use debug menu can create code block', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + + await focusRichText(page); + await updateBlockType(page, 'affine:code'); + + const locator = page.locator('affine-code'); + await expect(locator).toBeVisible(); +}); + +test('use markdown syntax can create code block', async ({ + page, +}, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'aaa'); + await pressEnter(page); + await type(page, 'bbb'); + await pressTab(page); + await pressEnter(page); + await type(page, 'ccc'); + await pressTab(page); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_init.json` + ); + + await setSelection(page, 2, 0, 2, 0); + // |aaa + // bbb + // ccc + + await type(page, '``` '); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_markdown_syntax.json` + ); +}); + +test('use markdown syntax with trailing characters can create code block', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + + await focusRichText(page); + await type(page, '```JavaScript'); + await type(page, ' '); + + const locator = page.locator('affine-code'); + await expect(locator).toBeVisible(); +}); + +test('support ```[lang] to add code block with language', async ({ page }) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/toeverything/blocksuite/issues/1314', + }); + + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + + await focusRichText(page); + await type(page, '```ts'); + await type(page, ' '); + + const codeBlockController = getCodeBlock(page); + const codeLocator = codeBlockController.codeBlock; + await expect(codeLocator).toBeVisible(); + + const codeRect = await codeLocator.boundingBox(); + if (!codeRect) { + throw new Error('Failed to get bounding box of code block.'); + } + const position = { + x: codeRect.x + codeRect.width / 2, + y: codeRect.y + codeRect.height / 2, + }; + await page.mouse.move(position.x, position.y); + + const languageButton = codeBlockController.languageButton; + await expect(languageButton).toBeVisible(); + await expect(languageButton).toHaveText('TypeScript'); +}); + +test('use more than three backticks can not create code block', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + + await focusRichText(page); + await type(page, '`````'); + await type(page, ' '); + + const codeBlockLocator = page.locator('affine-code'); + await expect(codeBlockLocator).toBeHidden(); + const inlineCodelocator = page.getByText('```'); + await expect(inlineCodelocator).toBeVisible(); + expect(await inlineCodelocator.count()).toEqual(1); +}); + +test('use shortcut can create code block', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + + await focusRichText(page); + await createCodeBlock(page); + + const locator = page.locator('affine-code'); + await expect(locator).toBeVisible(); +}); + +test('change code language can work', async ({ page }) => { + await enterPlaygroundRoom(page); + const { codeBlockId } = await initEmptyCodeBlockState(page); + await focusRichText(page); + + const codeBlockController = getCodeBlock(page); + await codeBlockController.codeBlock.hover(); + await codeBlockController.clickLanguageButton(); + const locator = codeBlockController.langList; + await expect(locator).toBeVisible(); + + await type(page, 'rust'); + await page.click( + '.affine-filterable-list > .items-container > icon-button:nth-child(1)' + ); + await expect(locator).toBeHidden(); + + await codeBlockController.codeBlock.hover(); + await expect(codeBlockController.languageButton).toHaveText('Rust'); + + await assertStoreMatchJSX( + page, + /*xml*/ ` +`, + codeBlockId + ); + await undoByKeyboard(page); + await assertStoreMatchJSX( + page, + /*xml*/ ` +`, + codeBlockId + ); + + // Can switch to another language + await codeBlockController.clickLanguageButton(); + await type(page, 'ty'); + await pressEnter(page); + await expect(locator).toBeHidden(); + await expect(codeBlockController.languageButton).toHaveText('TypeScript'); +}); + +test('duplicate code block', async ({ page }, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyCodeBlockState(page, { language: 'javascript' }); + + const codeBlockController = getCodeBlock(page); + await codeBlockController.codeBlock.hover(); + + // change language + await codeBlockController.clickLanguageButton(); + const langLocator = codeBlockController.langList; + await expect(langLocator).toBeVisible(); + await type(page, 'rust'); + await page.click( + '.affine-filterable-list > .items-container > icon-button:nth-child(1)' + ); + + // add text + await focusRichTextEnd(page); + await type(page, 'let a: u8 = 7'); + await pressEscape(page); + await waitNextFrame(page, 100); + + // add a caption + await codeBlockController.codeBlock.hover(); + await codeBlockController.captionButton.click(); + await type(page, 'BlockSuite'); + await pressEnter(page); + await pressBackspace(page); // remove paragraph + await waitNextFrame(page, 100); + + // turn on wrap + await codeBlockController.codeBlock.hover(); + await (await codeBlockController.openMore()).wrapButton.click(); + + // duplicate + await codeBlockController.codeBlock.hover(); + await (await codeBlockController.openMore()).duplicateButton.click(); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_final.json` + ); +}); + +test('delete code block in more menu', async ({ page }, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyCodeBlockState(page, { language: 'javascript' }); + + const codeBlockController = getCodeBlock(page); + await codeBlockController.codeBlock.hover(); + const moreMenu = await codeBlockController.openMore(); + + await expect(moreMenu.menu).toBeVisible(); + await moreMenu.deleteButton.click(); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_final.json` + ); +}); + +test('undo and redo works in code block', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyCodeBlockState(page); + await focusRichText(page); + + await type(page, 'const a = 10;'); + await assertRichTexts(page, ['const a = 10;']); + await undoByKeyboard(page); + await assertRichTexts(page, ['']); + + await redoByKeyboard(page); + await assertRichTexts(page, ['const a = 10;']); +}); + +test('toggle code block wrap can work', async ({ page }) => { + await enterPlaygroundRoom(page); + const { codeBlockId } = await initEmptyCodeBlockState(page); + + const codeBlockController = getCodeBlock(page); + await assertStoreMatchJSX( + page, + /*xml*/ ` +`, + codeBlockId + ); + + await codeBlockController.codeBlock.hover(); + await (await codeBlockController.openMore()).wrapButton.click(); + + await assertStoreMatchJSX( + page, + /*xml*/ ` +`, + codeBlockId + ); + + await codeBlockController.codeBlock.hover(); + await (await codeBlockController.openMore()).cancelWrapButton.click(); + + await assertStoreMatchJSX( + page, + /*xml*/ ` +`, + codeBlockId + ); +}); + +test('add caption works', async ({ page }) => { + await enterPlaygroundRoom(page); + const { codeBlockId } = await initEmptyCodeBlockState(page); + + const codeBlockController = getCodeBlock(page); + await codeBlockController.codeBlock.hover(); + await codeBlockController.captionButton.click(); + await type(page, 'BlockSuite'); + await pressEnter(page); + await waitNextFrame(page, 100); + + await assertStoreMatchJSX( + page, + /*xml*/ ` +`, + codeBlockId + ); +}); + +test('undo code block wrap can work', async ({ page }) => { + await enterPlaygroundRoom(page); + const { codeBlockId } = await initEmptyCodeBlockState(page); + await focusRichText(page); + + const codeBlockController = getCodeBlock(page); + await assertStoreMatchJSX( + page, + /*xml*/ ` +`, + codeBlockId + ); + + await codeBlockController.codeBlock.hover(); + await (await codeBlockController.openMore()).wrapButton.click(); + await assertStoreMatchJSX( + page, + /*xml*/ ` +`, + codeBlockId + ); + + await focusRichText(page); + await undoByKeyboard(page); + await assertStoreMatchJSX( + page, + /*xml*/ ` +`, + codeBlockId + ); +}); + +test('code block toolbar widget can appear and disappear during mousemove', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyCodeBlockState(page); + await focusRichText(page); + + const position = await page.locator('affine-code').boundingBox(); + if (!position) throw new Error('Failed to get affine code position'); + await page.mouse.move(position.x, position.y); + + const locator = page.locator('.code-toolbar-container'); + const toolbarPosition = await locator.boundingBox(); + if (!toolbarPosition) throw new Error('Failed to get option position'); + await page.mouse.move(toolbarPosition.x, toolbarPosition.y); + await expect(locator).toBeVisible(); + await page.mouse.move(position.x - 10, position.y - 10); + await expect(locator).toBeHidden(); +}); + +test('should tab works in code block', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyCodeBlockState(page); + await focusRichText(page); + + await type(page, 'const a = 10;'); + await assertRichTexts(page, ['const a = 10;']); + await page.keyboard.press('Tab', { delay: 50 }); + await assertRichTexts(page, [' const a = 10;']); + await page.keyboard.press(`Shift+Tab`, { delay: 50 }); + await assertRichTexts(page, ['const a = 10;']); + + await page.keyboard.press('Enter', { delay: 50 }); + await type(page, 'const b = "NothingToSay'); + await page.keyboard.press('ArrowUp', { delay: 50 }); + await page.keyboard.press('Enter', { delay: 50 }); + await page.keyboard.press('Tab', { delay: 50 }); + await assertRichTexts(page, ['const a = 10;\n \nconst b = "NothingToSay"']); +}); + +test('should open more menu and close on selecting', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyCodeBlockState(page); + await focusRichText(page); + + const codeBlockController = getCodeBlock(page); + await codeBlockController.codeBlock.hover(); + await expect(codeBlockController.codeToolbar).toBeVisible(); + const moreMenu = await codeBlockController.openMore(); + + await expect(moreMenu.menu).toBeVisible(); + await moreMenu.wrapButton.click(); + await expect(moreMenu.menu).toBeHidden(); +}); + +test('should code block lang input supports alias', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyCodeBlockState(page); + await focusRichText(page); + + const codeBlockController = getCodeBlock(page); + const codeBlock = codeBlockController.codeBlock; + await codeBlock.hover(); + await codeBlockController.clickLanguageButton(); + await expect(codeBlockController.langList).toBeVisible(); + await type(page, '文言'); + await pressEnter(page); + await expect(codeBlockController.languageButton).toHaveText('Wenyan'); +}); + +test('multi-line indent', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyCodeBlockState(page); + await focusRichText(page); + + await type(page, 'aaa'); + await pressEnter(page); + + await type(page, 'bbb'); + await pressEnter(page); + + await type(page, 'ccc'); + + await page.keyboard.down('Shift'); + await pressArrowUp(page, 2); + await page.keyboard.up('Shift'); + + await pressTab(page); + + await assertRichTexts(page, [' aaa\n bbb\n ccc']); + + await pressShiftTab(page); + + await assertRichTexts(page, ['aaa\nbbb\nccc']); + + await pressShiftTab(page); + + await assertRichTexts(page, ['aaa\nbbb\nccc']); +}); + +test('should bracket complete works in code block', async ({ page }) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/toeverything/blocksuite/issues/1800', + }); + await enterPlaygroundRoom(page); + await initEmptyCodeBlockState(page); + await focusRichText(page); + + await type(page, 'const a = "'); + await assertRichTexts(page, ['const a = ""']); + + await type(page, 'str'); + await assertRichTexts(page, ['const a = "str"']); + await type(page, '('); + await assertRichTexts(page, ['const a = "str()"']); + await type(page, ']'); + await assertRichTexts(page, ['const a = "str(])"']); +}); + +test('auto scroll horizontally when typing', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + + await focusRichText(page); + await type(page, '``` '); + + for (let i = 0; i < 100; i++) { + await type(page, String(i)); + } + + const richTextScrollLeft1 = await page.evaluate(() => { + const richText = document.querySelector('affine-code rich-text'); + if (!richText) { + throw new Error('Failed to get rich text'); + } + + return richText.scrollLeft; + }); + expect(richTextScrollLeft1).toBeGreaterThan(200); + + await pressArrowLeft(page, 5); + await type(page, 'aa'); + + const richTextScrollLeft2 = await page.evaluate(() => { + const richText = document.querySelector('affine-code rich-text'); + if (!richText) { + throw new Error('Failed to get rich text'); + } + + return richText.scrollLeft; + }); + + expect(richTextScrollLeft2).toEqual(richTextScrollLeft1); +}); + +test('code hotkey should not effect in global', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + await pressEnter(page); + await type(page, '``` '); + + await assertTitle(page, ''); + await assertBlockCount(page, 'paragraph', 1); + await assertBlockCount(page, 'code', 1); + + await pressArrowUp(page); + await pressBackspace(page); + await type(page, 'aaa'); + + await assertTitle(page, 'aaa'); + await assertBlockCount(page, 'paragraph', 0); + await assertBlockCount(page, 'code', 1); +}); + +test('language selection list should not close when hovering out of code block', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyCodeBlockState(page, { language: 'javascript' }); + + const codeBlockController = getCodeBlock(page); + await codeBlockController.codeBlock.hover(); + + await codeBlockController.clickLanguageButton(); + const langLocator = codeBlockController.langList; + await expect(langLocator).toBeVisible(); + + const bBox = await codeBlockController.codeBlock.boundingBox(); + if (!bBox) throw new Error('Expected bounding box'); + + const { x, y, width, height } = bBox; + + // hovering inside the code block should keep the list open + await page.mouse.move(x + width / 2, y + height / 2); + await expect(langLocator).toBeVisible(); + + // hovering out should not close the list + await page.mouse.move(x - 10, y - 10); + await waitNextFrame(page); + await expect(langLocator).toBeVisible(); +}); + +test('language selection list should not change when hovering over its elements', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyCodeBlockState(page); + + const codeBlockController = getCodeBlock(page); + await codeBlockController.codeBlock.hover(); + await codeBlockController.clickLanguageButton(); + await waitNextFrame(page, 100); + + const langListLocator = codeBlockController.langList; + const langItemsLocator = langListLocator.locator('icon-button'); + + // checking first 4 language list items + for (let i = 0; i < 3; i++) { + const item = langItemsLocator.nth(i); // current item in language list + const nextItem = langItemsLocator.nth(i + 1); // next item in language list + + await item.hover(); + + const initialItemText = await item.textContent(); + const initialNextItemText = await nextItem.textContent(); + + await nextItem.hover(); + + const currentItemText = await item.textContent(); + const currentNextItemText = await nextItem.textContent(); + + // text content should remain unchanged after next item receives focus + expect(initialItemText).toBe(currentItemText); + expect(initialNextItemText).toBe(currentNextItemText); + } +}); + +test('format text in code block', async ({ page }, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, '```ts '); + await waitNextFrame(page, 100); + await type(page, 'const aaa = 1000;'); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_init.json` + ); + + const line = page.locator('affine-code rich-text v-line > div'); + expect(await line.innerText()).toBe('const aaa = 1000;'); + + const { boldBtn, linkBtn } = getFormatBar(page); + + await dragBetweenIndices(page, [0, 1], [0, 2]); + await boldBtn.click(); + expect(await line.innerText()).toBe('const aaa = 1000;'); + await dragBetweenIndices(page, [0, 4], [0, 7]); + await boldBtn.click(); + expect(await line.innerText()).toBe('const aaa = 1000;'); + await dragBetweenIndices(page, [0, 8], [0, 16]); + await boldBtn.click(); + expect(await line.innerText()).toBe('const aaa = 1000;'); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_format.json` + ); + + await dragBetweenIndices(page, [0, 4], [0, 10]); + await linkBtn.click(); + await type(page, 'https://www.baidu.com'); + await pressEnter(page); + + expect(await line.innerText()).toBe('const aaa = 1000;'); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_link.json` + ); +}); diff --git a/blocksuite/tests-legacy/code/readonly.spec.ts b/blocksuite/tests-legacy/code/readonly.spec.ts new file mode 100644 index 0000000000000..aada3d552f6e4 --- /dev/null +++ b/blocksuite/tests-legacy/code/readonly.spec.ts @@ -0,0 +1,59 @@ +import { expect } from '@playwright/test'; + +import { switchReadonly } from '../utils/actions/click.js'; +import { + pressBackspace, + pressEnter, + pressTab, + type, +} from '../utils/actions/keyboard.js'; +import { + enterPlaygroundRoom, + focusRichText, + focusRichTextEnd, + initEmptyCodeBlockState, +} from '../utils/actions/misc.js'; +import { assertRichTexts } from '../utils/asserts.js'; +import { test } from '../utils/playwright.js'; +import { getCodeBlock } from './utils.js'; + +test('should code block widget be disabled in read only mode', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyCodeBlockState(page); + await focusRichTextEnd(page); + + await page.waitForTimeout(300); + await switchReadonly(page); + + const codeBlockController = getCodeBlock(page); + const codeBlock = codeBlockController.codeBlock; + await codeBlock.hover(); + await codeBlockController.clickLanguageButton(); + await expect(codeBlockController.langList).toBeHidden(); + + await codeBlock.hover(); + await expect(codeBlockController.codeToolbar).toBeVisible(); + await expect(codeBlockController.moreButton).toHaveAttribute('disabled'); + + await expect(codeBlockController.copyButton).toBeVisible(); + await expect(codeBlockController.moreMenu).toBeHidden(); +}); + +test('should not be able to modify code block in readonly mode', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyCodeBlockState(page); + await focusRichText(page); + + await type(page, 'const a = 10;'); + await assertRichTexts(page, ['const a = 10;']); + + await switchReadonly(page); + await pressBackspace(page, 3); + await pressTab(page, 3); + await pressEnter(page, 2); + await assertRichTexts(page, ['const a = 10;']); +}); diff --git a/blocksuite/tests-legacy/code/selections.spec.ts b/blocksuite/tests-legacy/code/selections.spec.ts new file mode 100644 index 0000000000000..3a6960a17de04 --- /dev/null +++ b/blocksuite/tests-legacy/code/selections.spec.ts @@ -0,0 +1,195 @@ +import { expect } from '@playwright/test'; + +import { dragBetweenCoords } from '../utils/actions/drag.js'; +import { + pressArrowLeft, + pressBackspace, + pressEnter, + pressEnterWithShortkey, + redoByKeyboard, + type, + undoByKeyboard, +} from '../utils/actions/keyboard.js'; +import { + enterPlaygroundRoom, + focusRichText, + getInlineSelectionIndex, + getInlineSelectionText, + initEmptyCodeBlockState, +} from '../utils/actions/misc.js'; +import { + assertBlockCount, + assertBlockSelections, + assertRichTextInlineRange, + assertRichTexts, +} from '../utils/asserts.js'; +import { test } from '../utils/playwright.js'; +import { getCodeBlock } from './utils.js'; + +test('click outside should close language list', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyCodeBlockState(page); + await focusRichText(page); + + const codeBlock = getCodeBlock(page); + await codeBlock.clickLanguageButton(); + const locator = codeBlock.langList; + await expect(locator).toBeVisible(); + + const rect = await page.locator('affine-filterable-list').boundingBox(); + if (!rect) throw new Error('Failed to get bounding box of code block.'); + await page.mouse.click(rect.x - 10, rect.y - 10); + + await expect(locator).toBeHidden(); +}); + +test('split code by enter', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyCodeBlockState(page); + await focusRichText(page); + + await type(page, 'hello'); + + // he|llo + await pressArrowLeft(page, 3); + + await pressEnter(page); + await assertRichTexts(page, ['he\nllo']); + + await undoByKeyboard(page); + await assertRichTexts(page, ['hello']); + + await redoByKeyboard(page); + await assertRichTexts(page, ['he\nllo']); +}); + +test('split code with selection by enter', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyCodeBlockState(page); + await focusRichText(page); + + await type(page, 'hello'); + + // select 'll' + await pressArrowLeft(page, 1); + await page.keyboard.down('Shift'); + await pressArrowLeft(page, 2); + await page.keyboard.up('Shift'); + + await pressEnter(page); + await assertRichTexts(page, ['he\no']); + + await undoByKeyboard(page); + await assertRichTexts(page, ['hello']); + + await redoByKeyboard(page); + await assertRichTexts(page, ['he\no']); +}); + +test('drag select code block can delete it', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyCodeBlockState(page); + await focusRichText(page); + + const codeBlock = page.locator('affine-code'); + const bbox = await codeBlock.boundingBox(); + if (!bbox) { + throw new Error("Failed to get code block's bounding box"); + } + const position = { + startX: bbox.x - 10, + startY: bbox.y - 10, + endX: bbox.x + bbox.width, + endY: bbox.y + bbox.height / 2, + }; + await dragBetweenCoords( + page, + { x: position.startX, y: position.startY }, + { x: position.endX, y: position.endY }, + { steps: 20 } + ); + await page.waitForTimeout(10); + await page.keyboard.press('Backspace'); + const locator = page.locator('affine-code'); + await expect(locator).toBeHidden(); +}); + +test('press short key and enter at end of code block can jump out', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyCodeBlockState(page); + await focusRichText(page); + + await pressEnterWithShortkey(page); + + const locator = page.locator('affine-paragraph'); + await expect(locator).toBeVisible(); +}); + +test('press short key and enter at end of code block with content can jump out', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyCodeBlockState(page); + await focusRichText(page); + + await type(page, 'const a = 10;'); + await pressEnterWithShortkey(page); + + const locator = page.locator('affine-paragraph'); + await expect(locator).toBeVisible(); +}); + +test('press backspace inside should select code block', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyCodeBlockState(page); + await focusRichText(page); + const codeBlock = page.locator('affine-code'); + const selectedRects = page + .locator('affine-block-selection') + .locator('visible=true'); + await page.keyboard.press('Backspace'); + await expect(selectedRects).toHaveCount(1); + await expect(codeBlock).toBeVisible(); + await page.keyboard.press('Backspace'); + await expect(selectedRects).toHaveCount(0); + await expect(codeBlock).toBeHidden(); +}); + +test('press backspace after code block can select code block', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyCodeBlockState(page); + await focusRichText(page); + const code = 'const a = 1;'; + await type(page, code); + + await assertRichTextInlineRange(page, 0, 12); + await pressEnterWithShortkey(page); + await assertRichTextInlineRange(page, 1, 0); + await assertBlockCount(page, 'paragraph', 1); + await pressBackspace(page); + await assertBlockSelections(page, ['2']); + await assertBlockCount(page, 'paragraph', 0); +}); + +test('press ArrowUp after code block can enter code block', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyCodeBlockState(page); + await focusRichText(page); + const code = 'const a = 1;'; + await type(page, code); + + await pressEnterWithShortkey(page); + await page.keyboard.press('ArrowUp'); + + const index = await getInlineSelectionIndex(page); + expect(index).toBe(0); + + const text = await getInlineSelectionText(page); + expect(text).toBe(code); +}); diff --git a/blocksuite/tests-legacy/code/utils.ts b/blocksuite/tests-legacy/code/utils.ts new file mode 100644 index 0000000000000..c9783c9c3c729 --- /dev/null +++ b/blocksuite/tests-legacy/code/utils.ts @@ -0,0 +1,61 @@ +import type { Page } from '@playwright/test'; + +/** + * @example + * ```ts + * const codeBlockController = getCodeBlock(page); + * const codeBlock = codeBlockController.codeBlock; + * ``` + */ +export function getCodeBlock(page: Page) { + const codeBlock = page.locator('affine-code'); + const languageButton = page.getByTestId('lang-button'); + + const clickLanguageButton = async () => { + await codeBlock.hover(); + await languageButton.click({ delay: 50 }); + }; + + const langList = page.locator('affine-filterable-list'); + const langFilterInput = langList.locator('#filter-input'); + + const codeToolbar = page.locator('affine-code-toolbar'); + + const copyButton = codeToolbar.getByRole('button', { name: 'Copy code' }); + const captionButton = codeToolbar.getByRole('button', { name: 'Caption' }); + const moreButton = codeToolbar.getByRole('button', { name: 'More' }); + + const menu = page.locator('.more-popup-menu'); + + const openMore = async () => { + await moreButton.click(); + + const wrapButton = menu.getByRole('button', { name: 'Wrap' }); + const cancelWrapButton = menu.getByRole('button', { name: 'Cancel wrap' }); + const duplicateButton = menu.getByRole('button', { name: 'Duplicate' }); + const deleteButton = menu.getByRole('button', { name: 'Delete' }); + + return { + menu, + wrapButton, + cancelWrapButton, + duplicateButton, + deleteButton, + }; + }; + + return { + codeBlock, + codeToolbar, + captionButton, + languageButton, + langList, + copyButton, + moreButton, + langFilterInput, + moreMenu: menu, + + openMore, + clickLanguageButton, + }; +} diff --git a/blocksuite/tests-legacy/database/actions.ts b/blocksuite/tests-legacy/database/actions.ts new file mode 100644 index 0000000000000..e78627ad5e268 --- /dev/null +++ b/blocksuite/tests-legacy/database/actions.ts @@ -0,0 +1,588 @@ +import type { + RichTextCell, + RichTextCellEditing, +} from '@blocks/database-block/properties/rich-text/cell-renderer.js'; +import { press } from '@inline/__tests__/utils.js'; +import { ZERO_WIDTH_SPACE } from '@inline/consts.js'; +import { expect, type Locator, type Page } from '@playwright/test'; + +import { + pressEnter, + selectAllByKeyboard, + type, +} from '../utils/actions/keyboard.js'; +import { + getBoundingBox, + getBoundingClientRect, + getEditorLocator, + waitNextFrame, +} from '../utils/actions/misc.js'; + +export async function initDatabaseColumn(page: Page, title = '') { + const editor = getEditorLocator(page); + await editor.locator('affine-data-view-table-group').first().hover(); + const columnAddBtn = editor.locator('.header-add-column-button'); + await columnAddBtn.click(); + await waitNextFrame(page, 200); + + if (title) { + await selectAllByKeyboard(page); + await type(page, title); + await waitNextFrame(page); + await pressEnter(page); + } else { + await pressEnter(page); + } +} + +export const renameColumn = async (page: Page, name: string) => { + const column = page.locator('affine-database-header-column', { + hasText: name, + }); + await column.click(); +}; + +export async function performColumnAction( + page: Page, + name: string, + action: string +) { + await renameColumn(page, name); + + const actionMenu = page.locator(`.affine-menu-button`, { hasText: action }); + await actionMenu.click(); +} + +export async function switchColumnType( + page: Page, + columnType: string, + columnIndex = 1 +) { + const { typeIcon } = await getDatabaseHeaderColumn(page, columnIndex); + await typeIcon.click(); + + await clickColumnType(page, columnType); +} + +export function clickColumnType(page: Page, columnType: string) { + const typeMenu = page.locator(`.affine-menu-button`, { + hasText: new RegExp(`${columnType}`), + }); + return typeMenu.click(); +} + +export function getDatabaseBodyRows(page: Page) { + const rowContainer = page.locator('.affine-database-block-rows'); + return rowContainer.locator('.database-row'); +} + +export function getDatabaseBodyRow(page: Page, rowIndex = 0) { + const rows = getDatabaseBodyRows(page); + return rows.nth(rowIndex); +} + +export function getDatabaseTableContainer(page: Page) { + const container = page.locator('.affine-database-table-container'); + return container; +} + +export async function assertDatabaseTitleColumnText( + page: Page, + title: string, + index = 0 +) { + const text = await page.evaluate(index => { + const rowContainer = document.querySelector('.affine-database-block-rows'); + const row = rowContainer?.querySelector( + `.database-row:nth-child(${index + 1})` + ); + const titleColumnCell = row?.querySelector('.database-cell:nth-child(1)'); + const titleSpan = titleColumnCell?.querySelector( + '.data-view-header-area-rich-text' + ) as HTMLElement; + if (!titleSpan) throw new Error('Cannot find database title column editor'); + return titleSpan.innerText; + }, index); + + if (title === '') { + expect(text).toMatch(new RegExp(`^(|[${ZERO_WIDTH_SPACE}])$`)); + } else { + expect(text).toBe(title); + } +} + +export function getDatabaseBodyCell( + page: Page, + { + rowIndex, + columnIndex, + }: { + rowIndex: number; + columnIndex: number; + } +) { + const row = getDatabaseBodyRow(page, rowIndex); + const cell = row.locator('.database-cell').nth(columnIndex); + return cell; +} + +export function getDatabaseBodyCellContent( + page: Page, + { + rowIndex, + columnIndex, + cellClass, + }: { + rowIndex: number; + columnIndex: number; + cellClass: string; + } +) { + const cell = getDatabaseBodyCell(page, { rowIndex, columnIndex }); + const cellContent = cell.locator(`.${cellClass}`); + return cellContent; +} + +export function getFirstColumnCell(page: Page, cellClass: string) { + const cellContent = getDatabaseBodyCellContent(page, { + rowIndex: 0, + columnIndex: 1, + cellClass, + }); + return cellContent; +} + +export async function clickSelectOption(page: Page, index = 0) { + await page.locator('.select-option-icon').nth(index).click(); +} + +export async function performSelectColumnTagAction( + page: Page, + name: string, + index = 0 +) { + await clickSelectOption(page, index); + await page + .locator('.affine-menu-button', { hasText: new RegExp(name) }) + .click(); +} + +export async function assertSelectedStyle( + page: Page, + key: keyof CSSStyleDeclaration, + value: string +) { + const style = await getElementStyle(page, '.select-selected', key); + expect(style).toBe(value); +} + +export async function clickDatabaseOutside(page: Page) { + const docTitle = page.locator('.doc-title-container'); + await docTitle.click(); +} + +export async function assertColumnWidth(locator: Locator, width: number) { + const box = await getBoundingBox(locator); + expect(box.width).toBe(width + 1); + return box; +} + +export async function assertDatabaseCellRichTexts( + page: Page, + { + rowIndex = 0, + columnIndex = 1, + text, + }: { + rowIndex?: number; + columnIndex?: number; + text: string; + } +) { + const cellContainer = page.locator( + `affine-database-cell-container[data-row-index='${rowIndex}'][data-column-index='${columnIndex}']` + ); + + const cellEditing = cellContainer.locator( + 'affine-database-rich-text-cell-editing' + ); + const cell = cellContainer.locator('affine-database-rich-text-cell'); + + const richText = (await cellEditing.count()) === 0 ? cell : cellEditing; + const actualTexts = await richText.evaluate(ele => { + return (ele as RichTextCellEditing).inlineEditor?.yTextString; + }); + expect(actualTexts).toEqual(text); +} + +export async function assertDatabaseCellNumber( + page: Page, + { + rowIndex = 0, + columnIndex = 1, + text, + }: { + rowIndex?: number; + columnIndex?: number; + text: string; + } +) { + const actualText = await page + .locator('.affine-database-block-rows') + .locator('.database-row') + .nth(rowIndex) + .locator('.database-cell') + .nth(columnIndex) + .locator('.number') + .textContent(); + expect(actualText?.trim()).toEqual(text); +} + +export async function assertDatabaseCellLink( + page: Page, + { + rowIndex = 0, + columnIndex = 1, + text, + }: { + rowIndex?: number; + columnIndex?: number; + text: string; + } +) { + const actualTexts = await page.evaluate( + ({ rowIndex, columnIndex }) => { + const rows = document.querySelector('.affine-database-block-rows'); + const row = rows?.querySelector( + `.database-row:nth-child(${rowIndex + 1})` + ); + const cell = row?.querySelector( + `.database-cell:nth-child(${columnIndex + 1})` + ); + const richText = + cell?.querySelector('affine-database-link-cell') ?? + cell?.querySelector( + 'affine-database-link-cell-editing' + ); + if (!richText) throw new Error('Missing database rich text cell'); + return richText.inlineEditor.yText.toString(); + }, + { rowIndex, columnIndex } + ); + expect(actualTexts).toEqual(text); +} + +export async function assertDatabaseTitleText(page: Page, text: string) { + const dbTitle = page.locator('[data-block-is-database-title="true"]'); + expect(await dbTitle.inputValue()).toEqual(text); +} + +export async function waitSearchTransitionEnd(page: Page) { + await waitNextFrame(page, 400); +} + +export async function assertDatabaseSearching( + page: Page, + isSearching: boolean +) { + const searchExpand = page.locator('.search-container-expand'); + const count = await searchExpand.count(); + expect(count).toBe(isSearching ? 1 : 0); +} + +export async function focusDatabaseSearch(page: Page) { + await (await getDatabaseMouse(page)).mouseOver(); + + const searchExpand = page.locator('.search-container-expand'); + const count = await searchExpand.count(); + if (count === 1) { + const input = page.locator('.affine-database-search-input'); + await input.click(); + } else { + const searchIcon = page.locator('.affine-database-search-input-icon'); + await searchIcon.click(); + await waitSearchTransitionEnd(page); + } +} + +export async function blurDatabaseSearch(page: Page) { + const dbTitle = page.locator('[data-block-is-database-title="true"]'); + await dbTitle.click(); +} + +export async function focusDatabaseHeader(page: Page, columnIndex = 0) { + const column = page.locator('.affine-database-column').nth(columnIndex); + const box = await getBoundingBox(column); + await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2); + await waitNextFrame(page); + return column; +} + +export async function getDatabaseMouse(page: Page) { + const databaseRect = await getBoundingClientRect( + page, + '.affine-database-table' + ); + return { + mouseOver: async () => { + await page.mouse.move(databaseRect.x, databaseRect.y); + }, + mouseLeave: async () => { + await page.mouse.move(databaseRect.x - 1, databaseRect.y - 1); + }, + }; +} + +export async function getDatabaseHeaderColumn(page: Page, index = 0) { + const column = page.locator('.affine-database-column').nth(index); + const box = await getBoundingBox(column); + const textElement = column.locator('.affine-database-column-text-input'); + const text = await textElement.innerText(); + const typeIcon = column.locator('.affine-database-column-type-icon'); + + return { + column, + box, + text, + textElement, + typeIcon, + }; +} + +export async function assertRowsSelection( + page: Page, + rowIndexes: [start: number, end: number] +) { + const rows = page.locator('data-view-table-row'); + const startIndex = rowIndexes[0]; + const endIndex = rowIndexes[1]; + for (let i = startIndex; i <= endIndex; i++) { + const row = rows.nth(i); + await row.locator('.row-select-checkbox .selected').isVisible(); + } +} + +export async function assertCellsSelection( + page: Page, + cellIndexes: { + start: [rowIndex: number, columnIndex: number]; + end?: [rowIndex: number, columnIndex: number]; + } +) { + const { start, end } = cellIndexes; + + if (!end) { + // single cell + const focus = page.locator('.database-focus'); + const focusBox = await getBoundingBox(focus); + + const [rowIndex, columnIndex] = start; + const cell = getDatabaseBodyCell(page, { rowIndex, columnIndex }); + const cellBox = await getBoundingBox(cell); + expect(focusBox).toEqual({ + x: cellBox.x, + y: cellBox.y - 1, + height: cellBox.height + 2, + width: cellBox.width + 1, + }); + } else { + // multi cells + const selection = page.locator('.database-selection'); + const selectionBox = await getBoundingBox(selection); + + const [startRowIndex, startColumnIndex] = start; + const [endRowIndex, endColumnIndex] = end; + + const rowIndexStart = Math.min(startRowIndex, endRowIndex); + const rowIndexEnd = Math.max(startRowIndex, endRowIndex); + const columnIndexStart = Math.min(startColumnIndex, endColumnIndex); + const columnIndexEnd = Math.max(startColumnIndex, endColumnIndex); + + let height = 0; + let width = 0; + let x = 0; + let y = 0; + for (let i = rowIndexStart; i <= rowIndexEnd; i++) { + const cell = getDatabaseBodyCell(page, { + rowIndex: i, + columnIndex: columnIndexStart, + }); + const box = await getBoundingBox(cell); + height += box.height + 1; + if (i === rowIndexStart) { + y = box.y; + } + } + + for (let j = columnIndexStart; j <= columnIndexEnd; j++) { + const cell = getDatabaseBodyCell(page, { + rowIndex: rowIndexStart, + columnIndex: j, + }); + const box = await getBoundingBox(cell); + width += box.width; + if (j === columnIndexStart) { + x = box.x; + } + } + + expect(selectionBox).toEqual({ + x, + y, + height, + width: width + 1, + }); + } +} + +export async function getElementStyle( + page: Page, + selector: string, + key: keyof CSSStyleDeclaration +) { + const style = await page.evaluate( + ({ key, selector }) => { + const el = document.querySelector(selector); + if (!el) throw new Error(`Missing ${selector} tag`); + // @ts-ignore + return el.style[key]; + }, + { + key, + selector, + } + ); + + return style; +} + +export async function focusKanbanCardHeader(page: Page, index = 0) { + const cardHeader = page.locator('data-view-header-area-text').nth(index); + await cardHeader.click(); +} + +export async function clickKanbanCardHeader(page: Page, index = 0) { + const cardHeader = page.locator('data-view-header-area-text').nth(index); + await cardHeader.click(); + await cardHeader.click(); +} + +export async function assertKanbanCardHeaderText( + page: Page, + text: string, + index = 0 +) { + const cardHeader = page.locator('data-view-header-area-text').nth(index); + + await expect(cardHeader).toHaveText(text); +} + +export async function assertKanbanCellSelected( + page: Page, + { + groupIndex, + cardIndex, + cellIndex, + }: { + groupIndex: number; + cardIndex: number; + cellIndex: number; + } +) { + const border = await page.evaluate( + ({ groupIndex, cardIndex, cellIndex }) => { + const group = document.querySelector( + `affine-data-view-kanban-group:nth-child(${groupIndex + 1})` + ); + const card = group?.querySelector( + `affine-data-view-kanban-card:nth-child(${cardIndex + 1})` + ); + const cells = Array.from( + card?.querySelectorAll(`affine-data-view-kanban-cell`) ?? + [] + ); + const cell = cells[cellIndex]; + if (!cell) throw new Error(`Missing cell tag`); + return cell.style.border; + }, + { + groupIndex, + cardIndex, + cellIndex, + } + ); + + expect(border).toEqual('1px solid var(--affine-primary-color)'); +} + +export async function assertKanbanCardSelected( + page: Page, + { + groupIndex, + cardIndex, + }: { + groupIndex: number; + cardIndex: number; + } +) { + const border = await page.evaluate( + ({ groupIndex, cardIndex }) => { + const group = document.querySelector( + `affine-data-view-kanban-group:nth-child(${groupIndex + 1})` + ); + const card = group?.querySelector( + `affine-data-view-kanban-card:nth-child(${cardIndex + 1})` + ); + if (!card) throw new Error(`Missing card tag`); + return card.style.border; + }, + { + groupIndex, + cardIndex, + } + ); + + expect(border).toEqual('1px solid var(--affine-primary-color)'); +} + +export function getKanbanCard( + page: Page, + { + groupIndex, + cardIndex, + }: { + groupIndex: number; + cardIndex: number; + } +) { + const group = page.locator('affine-data-view-kanban-group').nth(groupIndex); + const card = group.locator('affine-data-view-kanban-card').nth(cardIndex); + return card; +} +export const moveToCenterOf = async (page: Page, locator: Locator) => { + const box = (await locator.boundingBox())!; + expect(box).toBeDefined(); + await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2); +}; +export const changeColumnType = async ( + page: Page, + column: number, + name: string +) => { + await waitNextFrame(page); + await page.locator('affine-database-header-column').nth(column).click(); + await waitNextFrame(page, 200); + await pressKey(page, 'Escape'); + await pressKey(page, 'ArrowDown'); + await pressKey(page, 'Enter'); + await type(page, name); + await pressKey(page, 'ArrowDown'); + await pressKey(page, 'Enter'); +}; +export const pressKey = async (page: Page, key: string, count: number = 1) => { + for (let i = 0; i < count; i++) { + await waitNextFrame(page); + await press(page, key); + } + await waitNextFrame(page); +}; diff --git a/blocksuite/tests-legacy/database/clipboard.spec.ts b/blocksuite/tests-legacy/database/clipboard.spec.ts new file mode 100644 index 0000000000000..d8dd54906086b --- /dev/null +++ b/blocksuite/tests-legacy/database/clipboard.spec.ts @@ -0,0 +1,176 @@ +import { expect } from '@playwright/test'; + +import { dragBetweenCoords } from '../utils/actions/drag.js'; +import { + copyByKeyboard, + pasteByKeyboard, + pressArrowDown, + pressArrowUp, + pressEnter, + pressEscape, + type, +} from '../utils/actions/keyboard.js'; +import { + enterPlaygroundRoom, + focusRichText, + getBoundingBox, + initDatabaseDynamicRowWithData, + initDatabaseRowWithData, + initEmptyDatabaseState, + initEmptyDatabaseWithParagraphState, + waitNextFrame, +} from '../utils/actions/misc.js'; +import { assertRichTexts } from '../utils/asserts.js'; +import { test } from '../utils/playwright.js'; +import { + assertDatabaseTitleColumnText, + getDatabaseBodyCell, + getElementStyle, + initDatabaseColumn, + switchColumnType, +} from './actions.js'; + +test.describe('copy&paste when editing', () => { + test.skip('should support copy&paste of the title column', async ({ + page, + }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseWithParagraphState(page); + + await initDatabaseColumn(page); + await initDatabaseRowWithData(page, 'abc123'); + await pressEscape(page); + + await pressEnter(page); + await page.keyboard.down('Shift'); + for (let i = 0; i < 4; i++) { + await page.keyboard.press('ArrowLeft'); + } + await page.keyboard.up('Shift'); + await copyByKeyboard(page); + + const bgValue = await getElementStyle(page, '.database-focus', 'boxShadow'); + expect(bgValue).not.toBe('unset'); + + await focusRichText(page, 1); + await pasteByKeyboard(page); + await assertRichTexts(page, ['Database 1', 'c123']); + }); +}); + +test.describe('copy&paste when selecting', () => { + test.skip('should support copy&paste of a single cell', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page); + await initDatabaseRowWithData(page, 'abc123'); + await initDatabaseRowWithData(page, ''); + await pressEscape(page); + await waitNextFrame(page, 100); + await pressArrowUp(page); + + await copyByKeyboard(page); + await pressArrowDown(page); + await pasteByKeyboard(page, false); + await waitNextFrame(page); + await assertDatabaseTitleColumnText(page, 'abc123', 1); + }); + + test.skip('should support copy&paste of multi cells', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page); + await initDatabaseRowWithData(page, 'text1'); + await initDatabaseDynamicRowWithData(page, '123', false); + await pressEscape(page); + await initDatabaseRowWithData(page, 'text2'); + await initDatabaseDynamicRowWithData(page, 'a', false); + await pressEscape(page); + + await initDatabaseRowWithData(page, ''); + await initDatabaseRowWithData(page, ''); + + const startCell = getDatabaseBodyCell(page, { + rowIndex: 0, + columnIndex: 0, + }); + const startCellBox = await getBoundingBox(startCell); + const endCell = getDatabaseBodyCell(page, { rowIndex: 1, columnIndex: 1 }); + const endCellBox = await getBoundingBox(endCell); + const startX = startCellBox.x + startCellBox.width / 2; + const startY = startCellBox.y + startCellBox.height / 2; + const endX = endCellBox.x + endCellBox.width / 2; + const endY = endCellBox.y + endCellBox.height / 2; + await dragBetweenCoords( + page, + { x: startX, y: startY }, + { x: endX, y: endY }, + { + steps: 50, + } + ); + await copyByKeyboard(page); + + await pressArrowDown(page); + await pressArrowDown(page); + await waitNextFrame(page); + await pasteByKeyboard(page, false); + + await assertDatabaseTitleColumnText(page, 'text1', 2); + await assertDatabaseTitleColumnText(page, 'text2', 3); + const selectCell21 = getDatabaseBodyCell(page, { + rowIndex: 2, + columnIndex: 1, + }); + const selectCell31 = getDatabaseBodyCell(page, { + rowIndex: 3, + columnIndex: 1, + }); + expect(await selectCell21.innerText()).toBe('123'); + expect(await selectCell31.innerText()).toBe('a'); + }); + + test.skip('should support copy&paste of a single row', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page); + await initDatabaseRowWithData(page, 'text1'); + await pressEscape(page); + await waitNextFrame(page, 100); + await initDatabaseDynamicRowWithData(page, 'abc', false); + await pressEscape(page); + await waitNextFrame(page, 100); + await initDatabaseColumn(page); + await switchColumnType(page, 'Number', 2); + const numberCell = getDatabaseBodyCell(page, { + rowIndex: 0, + columnIndex: 2, + }); + await numberCell.click(); + await waitNextFrame(page); + await type(page, '123'); + await pressEscape(page); + await pressEscape(page); + await copyByKeyboard(page); + + await initDatabaseRowWithData(page, ''); + await pressEscape(page); + await pasteByKeyboard(page, false); + await waitNextFrame(page); + + await assertDatabaseTitleColumnText(page, 'text1', 1); + const selectCell = getDatabaseBodyCell(page, { + rowIndex: 1, + columnIndex: 1, + }); + expect(await selectCell.innerText()).toBe('abc'); + const selectNumberCell = getDatabaseBodyCell(page, { + rowIndex: 1, + columnIndex: 2, + }); + expect(await selectNumberCell.innerText()).toBe('123'); + }); +}); diff --git a/blocksuite/tests-legacy/database/column.spec.ts b/blocksuite/tests-legacy/database/column.spec.ts new file mode 100644 index 0000000000000..199a5d7f31e29 --- /dev/null +++ b/blocksuite/tests-legacy/database/column.spec.ts @@ -0,0 +1,599 @@ +import { expect } from '@playwright/test'; + +import { + assertDatabaseColumnOrder, + dragBetweenCoords, + enterPlaygroundRoom, + getBoundingBox, + initDatabaseDynamicRowWithData, + initEmptyDatabaseState, + pressArrowRight, + pressArrowUp, + pressArrowUpWithShiftKey, + pressBackspace, + pressEnter, + pressEscape, + redoByClick, + selectAllByKeyboard, + type, + undoByClick, + waitNextFrame, +} from '../utils/actions/index.js'; +import { test } from '../utils/playwright.js'; +import { + assertDatabaseCellNumber, + assertDatabaseCellRichTexts, + assertSelectedStyle, + changeColumnType, + clickDatabaseOutside, + clickSelectOption, + getDatabaseHeaderColumn, + getFirstColumnCell, + initDatabaseColumn, + performColumnAction, + performSelectColumnTagAction, + switchColumnType, +} from './actions.js'; + +test.describe('column operations', () => { + test('should support rename column', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page, 'abc'); + + const { textElement } = await getDatabaseHeaderColumn(page, 1); + expect(await textElement.innerText()).toBe('abc'); + await textElement.click(); + await waitNextFrame(page, 200); + await pressArrowRight(page); + await type(page, '123'); + await pressEnter(page); + expect(await textElement.innerText()).toBe('abc123'); + + await undoByClick(page); + expect(await textElement.innerText()).toBe('abc'); + await redoByClick(page); + expect(await textElement.innerText()).toBe('abc123'); + }); + + test('should support add new column', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page); + await initDatabaseDynamicRowWithData(page, '123', true); + await pressEscape(page); + const { text: title1 } = await getDatabaseHeaderColumn(page, 1); + expect(title1).toBe('Column 1'); + + const selected = getFirstColumnCell(page, 'select-selected'); + expect(await selected.innerText()).toBe('123'); + + await initDatabaseColumn(page, 'abc'); + const { text: title2 } = await getDatabaseHeaderColumn(page, 2); + expect(title2).toBe('abc'); + + await initDatabaseColumn(page); + const { text: title3 } = await getDatabaseHeaderColumn(page, 3); + expect(title3).toBe('Column 2'); + }); + + test('should support right insert column', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page, '1'); + + await performColumnAction(page, '1', 'Insert right'); + await selectAllByKeyboard(page); + await type(page, '2'); + await pressEnter(page); + const columns = page.locator('.affine-database-column'); + expect(await columns.count()).toBe(3); + + await assertDatabaseColumnOrder(page, ['1', '2']); + }); + + test('should support left insert column', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page, '1'); + + await performColumnAction(page, '1', 'Insert left'); + await selectAllByKeyboard(page); + await type(page, '2'); + await pressEnter(page); + const columns = page.locator('.affine-database-column'); + expect(await columns.count()).toBe(3); + + await assertDatabaseColumnOrder(page, ['2', '1']); + }); + + test('should support delete column', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page, '1'); + + const columns = page.locator('.affine-database-column'); + expect(await columns.count()).toBe(2); + + await performColumnAction(page, '1', 'Delete'); + expect(await columns.count()).toBe(1); + }); + + test('should support duplicate column', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page, '1'); + await initDatabaseDynamicRowWithData(page, '123', true); + await pressEscape(page); + await performColumnAction(page, '1', 'duplicate'); + await pressEscape(page); + const cells = page.locator('affine-database-multi-select-cell'); + expect(await cells.count()).toBe(2); + + const secondCell = cells.nth(1); + const selected = secondCell.locator('.select-selected'); + expect(await selected.innerText()).toBe('123'); + }); + + test('should support move column right', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page, '1'); + await initDatabaseDynamicRowWithData(page, '123', true); + await pressEscape(page); + await initDatabaseColumn(page, '2'); + await initDatabaseDynamicRowWithData(page, 'abc', false, 1); + await pressEscape(page); + await assertDatabaseColumnOrder(page, ['1', '2']); + await waitNextFrame(page, 350); + await performColumnAction(page, '1', 'Move right'); + await assertDatabaseColumnOrder(page, ['2', '1']); + + await undoByClick(page); + const { column } = await getDatabaseHeaderColumn(page, 2); + await column.click(); + const moveLeft = page.locator('.action', { hasText: 'Move right' }); + expect(await moveLeft.count()).toBe(0); + }); + + test('should support move column left', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page, '1'); + await initDatabaseDynamicRowWithData(page, '123', true); + await pressEscape(page); + await initDatabaseColumn(page, '2'); + await initDatabaseDynamicRowWithData(page, 'abc', false, 1); + await pressEscape(page); + await assertDatabaseColumnOrder(page, ['1', '2']); + + const { column } = await getDatabaseHeaderColumn(page, 0); + await column.click(); + const moveLeft = page.locator('.action', { hasText: 'Move left' }); + expect(await moveLeft.count()).toBe(0); + await waitNextFrame(page, 200); + await pressEscape(page); + await pressEscape(page); + + await performColumnAction(page, '2', 'Move left'); + await assertDatabaseColumnOrder(page, ['2', '1']); + }); +}); + +test.describe('switch column type', () => { + test('switch to number', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page); + await initDatabaseDynamicRowWithData(page, '123abc', true); + await pressEscape(page); + await changeColumnType(page, 1, 'Number'); + + const cell = getFirstColumnCell(page, 'number'); + await assertDatabaseCellNumber(page, { + text: '', + }); + await pressEnter(page); + await type(page, '123abc'); + await pressEscape(page); + expect((await cell.textContent())?.trim()).toBe('123'); + }); + + test('switch to rich-text', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page); + await initDatabaseDynamicRowWithData(page, '123abc', true); + await pressEscape(page); + await switchColumnType(page, 'Text'); + + // For now, rich-text will only be initialized on click + // Therefore, for the time being, here is to detect whether there is '.affine-database-rich-text' + const cell = getFirstColumnCell(page, 'affine-database-rich-text'); + expect(await cell.count()).toBe(1); + await pressEnter(page); + await type(page, '123'); + await pressEscape(page); + await pressEnter(page); + await type(page, 'abc'); + await pressEscape(page); + await assertDatabaseCellRichTexts(page, { text: '123abc123abc' }); + }); + + test('switch between multi-select and select', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page); + await initDatabaseDynamicRowWithData(page, '123', true); + await type(page, 'abc'); + await pressEnter(page); + await pressEscape(page); + const cell = getFirstColumnCell(page, 'select-selected'); + expect(await cell.count()).toBe(2); + + await switchColumnType(page, 'Select', 1); + expect(await cell.count()).toBe(1); + expect(await cell.innerText()).toBe('123'); + + await pressEnter(page); + await type(page, 'def'); + await pressEnter(page); + expect(await cell.innerText()).toBe('def'); + + await switchColumnType(page, 'Multi-select'); + await pressEnter(page); + await type(page, '666'); + await pressEnter(page); + await pressEscape(page); + expect(await cell.count()).toBe(2); + expect(await cell.nth(0).innerText()).toBe('def'); + expect(await cell.nth(1).innerText()).toBe('666'); + + await switchColumnType(page, 'Select'); + expect(await cell.count()).toBe(1); + expect(await cell.innerText()).toBe('def'); + + await pressEnter(page); + await type(page, '888'); + await pressEnter(page); + expect(await cell.innerText()).toBe('888'); + }); + + test('switch between number and rich-text', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page); + await switchColumnType(page, 'Number'); + + await initDatabaseDynamicRowWithData(page, '123abc', true); + await assertDatabaseCellNumber(page, { + text: '123', + }); + + await switchColumnType(page, 'Text'); + await pressEnter(page); + await type(page, 'abc'); + await pressEscape(page); + await assertDatabaseCellRichTexts(page, { text: '123abc' }); + + await switchColumnType(page, 'Number'); + await assertDatabaseCellNumber(page, { + text: '123', + }); + }); + + test('switch number to select', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page); + await switchColumnType(page, 'Number'); + + await initDatabaseDynamicRowWithData(page, '123', true); + const cell = getFirstColumnCell(page, 'number'); + expect((await cell.textContent())?.trim()).toBe('123'); + + await switchColumnType(page, 'Select'); + await initDatabaseDynamicRowWithData(page, 'abc'); + const selectCell = getFirstColumnCell(page, 'select-selected'); + expect(await selectCell.innerText()).toBe('abc'); + + await switchColumnType(page, 'Number'); + await assertDatabaseCellNumber(page, { + text: '', + }); + }); + + test('switch to checkbox', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page); + await initDatabaseDynamicRowWithData(page, '', true); + await pressEscape(page); + await changeColumnType(page, 1, 'Checkbox'); + + const checkbox = getFirstColumnCell(page, 'checkbox'); + await expect(checkbox).not.toHaveClass('checked'); + + await waitNextFrame(page, 500); + await checkbox.click(); + await expect(checkbox).toHaveClass(/checked/); + + await undoByClick(page); + await expect(checkbox).not.toHaveClass('checked'); + }); + + test('checkbox to text', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page); + await initDatabaseDynamicRowWithData(page, '', true); + await pressEscape(page); + await changeColumnType(page, 1, 'Checkbox'); + + let checkbox = getFirstColumnCell(page, 'checkbox'); + await expect(checkbox).not.toHaveClass('checked'); + + // checked + await checkbox.click(); + await changeColumnType(page, 1, 'Text'); + await clickDatabaseOutside(page); + await waitNextFrame(page, 100); + await assertDatabaseCellRichTexts(page, { text: 'Yes' }); + await clickDatabaseOutside(page); + await waitNextFrame(page, 100); + await changeColumnType(page, 1, 'Checkbox'); + checkbox = getFirstColumnCell(page, 'checkbox'); + await expect(checkbox).toHaveClass(/checked/); + + // not checked + await checkbox.click(); + await changeColumnType(page, 1, 'Text'); + await clickDatabaseOutside(page); + await waitNextFrame(page, 100); + await assertDatabaseCellRichTexts(page, { text: 'No' }); + await clickDatabaseOutside(page); + await waitNextFrame(page, 100); + await changeColumnType(page, 1, 'Checkbox'); + checkbox = getFirstColumnCell(page, 'checkbox'); + await expect(checkbox).not.toHaveClass('checked'); + }); + + test('switch to progress', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page); + await initDatabaseDynamicRowWithData(page, '', true); + await pressEscape(page); + await switchColumnType(page, 'Progress'); + + const progress = getFirstColumnCell(page, 'progress'); + expect(await progress.textContent()).toBe('0'); + await waitNextFrame(page, 500); + const progressBg = page.locator('.affine-database-progress-bg'); + const { + x: progressBgX, + y: progressBgY, + width: progressBgWidth, + } = await getBoundingBox(progressBg); + await page.mouse.move(progressBgX, progressBgY); + await page.mouse.click(progressBgX, progressBgY); + const dragHandle = page.locator('.affine-database-progress-drag-handle'); + const { + x: dragX, + y: dragY, + width, + height, + } = await getBoundingBox(dragHandle); + const dragCenterX = dragX + width / 2; + const dragCenterY = dragY + height / 2; + await page.mouse.move(dragCenterX, dragCenterY); + + const endX = dragCenterX + progressBgWidth; + await dragBetweenCoords( + page, + { x: dragCenterX, y: dragCenterY }, + { x: endX, y: dragCenterY } + ); + expect(await progress.textContent()).toBe('100'); + await pressEscape(page); + await undoByClick(page); + expect(await progress.textContent()).toBe('0'); + }); + + test('switch to link', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page); + await initDatabaseDynamicRowWithData(page, '', true); + await pressEscape(page); + + await switchColumnType(page, 'Link'); + + const linkText = 'http://example.com'; + const cell = getFirstColumnCell(page, 'affine-database-link'); + await pressEnter(page); + await type(page, linkText); + await pressEscape(page); + const link = cell.locator('affine-database-link-node > a'); + const linkContent = link.locator('.link-node-text'); + await expect(link).toHaveAttribute('href', linkText); + expect(await linkContent.textContent()).toBe(linkText); + + // not link text + await cell.hover(); + const linkEdit = getFirstColumnCell(page, 'affine-database-link-icon'); + await linkEdit.click(); + await selectAllByKeyboard(page); + await type(page, 'abc'); + await pressEnter(page); + await expect(link).toBeHidden(); + }); +}); + +test.describe('select column tag action', () => { + test('should support select tag renaming', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page); + await initDatabaseDynamicRowWithData(page, '123', true); + await type(page, 'abc'); + await pressEnter(page); + await clickSelectOption(page); + await waitNextFrame(page); + await pressArrowRight(page); + await type(page, '4567abc00'); + await pressEnter(page); + const options = page.locator('.select-options-container .tag-text'); + expect(await options.nth(0).innerText()).toBe('abc4567abc00'); + expect(await options.nth(1).innerText()).toBe('123'); + }); + + test('should select tag renaming support shortcut key', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page); + await initDatabaseDynamicRowWithData(page, '123', true); + await clickSelectOption(page); + await waitNextFrame(page); + await pressArrowRight(page); + await type(page, '456'); + // esc + await pressEscape(page); + await pressEscape(page); + const options = page.locator('.select-options-container .tag-text'); + const option1 = options.nth(0); + expect(await option1.innerText()).toBe('123456'); + }); + + test('should support select tag deletion', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page); + await initDatabaseDynamicRowWithData(page, '123', true); + await performSelectColumnTagAction(page, 'Delete'); + const options = page.locator('.select-option-name'); + expect(await options.count()).toBe(0); + }); + + test('should support modifying select tag color', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page); + await initDatabaseDynamicRowWithData(page, '123', true); + await performSelectColumnTagAction(page, 'Red'); + await pressEscape(page); + await assertSelectedStyle( + page, + 'backgroundColor', + 'var(--affine-v2-chip-label-red)' + ); + }); +}); + +test.describe('drag-to-fill', () => { + test('should show when cell in focus and hide on blur', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page); + await initDatabaseDynamicRowWithData(page, '', true); + + await pressEscape(page); + + const dragToFillHandle = page.locator('.drag-to-fill'); + + await expect(dragToFillHandle).toBeVisible(); + + await pressEscape(page); + + await expect(dragToFillHandle).toBeHidden(); + }); + + test('should not show in multi (row or column) selection', async ({ + page, + }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page); + + await initDatabaseDynamicRowWithData(page, '', true); + await pressEscape(page); + + await initDatabaseDynamicRowWithData(page, '', true); + await pressEscape(page); + + const dragToFillHandle = page.locator('.drag-to-fill'); + + await expect(dragToFillHandle).toBeVisible(); + + await pressArrowUpWithShiftKey(page); + + await expect(dragToFillHandle).toBeHidden(); + await pressArrowUp(page); + + await expect(dragToFillHandle).toBeVisible(); + }); + + test('should fill columns with data', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page); + + await initDatabaseDynamicRowWithData(page, 'thing', true); + await pressEscape(page); + + await initDatabaseDynamicRowWithData(page, '', true); + await pressBackspace(page); + await type(page, 'aaa'); + await pressEnter(page); + await pressEnter(page); + + await pressEscape(page); + await pressArrowUp(page); + + const cells = page.locator('affine-database-multi-select-cell'); + + expect(await cells.nth(0).innerText()).toBe('thing'); + expect(await cells.nth(1).innerText()).toBe('aaa'); + + const dragToFillHandle = page.locator('.drag-to-fill'); + + await expect(dragToFillHandle).toBeVisible(); + + const bbox = await getBoundingBox(dragToFillHandle); + + if (!bbox) throw new Error('Expected a bounding box'); + + await dragBetweenCoords( + page, + { x: bbox.x + bbox.width / 2, y: bbox.y + bbox.height / 2 }, + { x: bbox.x, y: bbox.y + 200 } + ); + + expect(await cells.nth(0).innerText()).toBe('thing'); + expect(await cells.nth(1).innerText()).toBe('thing'); + }); +}); diff --git a/blocksuite/tests-legacy/database/database.spec.ts b/blocksuite/tests-legacy/database/database.spec.ts new file mode 100644 index 0000000000000..3404738cae437 --- /dev/null +++ b/blocksuite/tests-legacy/database/database.spec.ts @@ -0,0 +1,670 @@ +import { expect } from '@playwright/test'; + +import { + dragBetweenCoords, + enterPlaygroundRoom, + focusDatabaseTitle, + getBoundingBox, + initDatabaseDynamicRowWithData, + initDatabaseRowWithData, + initEmptyDatabaseState, + pressArrowLeft, + pressArrowRight, + pressBackspace, + pressEnter, + pressEscape, + pressShiftEnter, + redoByKeyboard, + selectAllByKeyboard, + setInlineRangeInInlineEditor, + switchReadonly, + type, + undoByClick, + undoByKeyboard, + waitNextFrame, +} from '../utils/actions/index.js'; +import { + assertBlockProps, + assertInlineEditorDeltas, + assertRowCount, +} from '../utils/asserts.js'; +import { test } from '../utils/playwright.js'; +import { getFormatBar } from '../utils/query.js'; +import { + assertColumnWidth, + assertDatabaseCellRichTexts, + assertDatabaseSearching, + assertDatabaseTitleText, + blurDatabaseSearch, + clickColumnType, + clickDatabaseOutside, + focusDatabaseHeader, + focusDatabaseSearch, + getDatabaseBodyCell, + getDatabaseHeaderColumn, + getFirstColumnCell, + initDatabaseColumn, + switchColumnType, +} from './actions.js'; + +test('edit database block title and create new rows', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + const locator = page.locator('affine-database'); + await expect(locator).toBeVisible(); + const dbTitle = 'Database 1'; + await assertBlockProps(page, '2', { + title: dbTitle, + }); + await focusDatabaseTitle(page); + await selectAllByKeyboard(page); + await pressBackspace(page); + + const expected = 'hello'; + await type(page, expected); + await assertBlockProps(page, '2', { + title: 'hello', + }); + await undoByClick(page); + await assertBlockProps(page, '2', { + title: 'Database 1', + }); + await initDatabaseRowWithData(page, ''); + await initDatabaseRowWithData(page, ''); + await assertRowCount(page, 2); + await waitNextFrame(page, 100); + await pressEscape(page); + await undoByClick(page); + await undoByClick(page); + await assertRowCount(page, 0); +}); + +test('edit column title', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page, '1'); + + // first added column + const { column } = await getDatabaseHeaderColumn(page, 1); + expect(await column.innerText()).toBe('1'); + + await undoByClick(page); + expect(await column.innerText()).toBe('Column 1'); +}); + +test('should modify the value when the input loses focus', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page); + await switchColumnType(page, 'Number'); + await initDatabaseDynamicRowWithData(page, '1', true); + + await clickDatabaseOutside(page); + const cell = getFirstColumnCell(page, 'number'); + const text = await cell.textContent(); + expect(text?.trim()).toBe('1'); +}); + +test('should rich-text column support soft enter', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page); + await switchColumnType(page, 'Text'); + await initDatabaseDynamicRowWithData(page, '123', true); + + const cell = getFirstColumnCell(page, 'affine-database-rich-text'); + await cell.click(); + await pressArrowLeft(page); + await pressEnter(page); + await assertDatabaseCellRichTexts(page, { text: '123' }); + + await cell.click(); + await pressArrowRight(page); + await pressArrowLeft(page); + await pressShiftEnter(page); + await pressEnter(page); + await assertDatabaseCellRichTexts(page, { text: '12\n3' }); +}); + +test('should the multi-select mode work correctly', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page); + await initDatabaseDynamicRowWithData(page, '1', true); + await pressEscape(page); + await initDatabaseDynamicRowWithData(page, '2'); + await pressEscape(page); + const cell = getFirstColumnCell(page, 'select-selected'); + expect(await cell.count()).toBe(2); + expect(await cell.nth(0).innerText()).toBe('1'); + expect(await cell.nth(1).innerText()).toBe('2'); +}); + +test('should database search work', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page); + await initDatabaseRowWithData(page, 'text1'); + await initDatabaseDynamicRowWithData(page, '123', false); + await pressEscape(page); + await initDatabaseRowWithData(page, 'text2'); + await initDatabaseDynamicRowWithData(page, 'a', false); + await pressEscape(page); + await initDatabaseRowWithData(page, 'text3'); + await initDatabaseDynamicRowWithData(page, '26', false); + await pressEscape(page); + // search for '2' + await focusDatabaseSearch(page); + await type(page, '2'); + const rows = page.locator('.affine-database-block-row'); + expect(await rows.count()).toBe(3); + + // search for '23' + await type(page, '3'); + expect(await rows.count()).toBe(1); + + const cell = page.locator('.select-selected'); + expect(await cell.innerText()).toBe('123'); + + // clear search input + const closeIcon = page.locator('.close-icon'); + await closeIcon.click(); + expect(await rows.count()).toBe(3); +}); + +test('should database search input displayed correctly', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await focusDatabaseSearch(page); + await blurDatabaseSearch(page); + await assertDatabaseSearching(page, false); + + await focusDatabaseSearch(page); + await type(page, '2'); + await blurDatabaseSearch(page); + await assertDatabaseSearching(page, true); + + await focusDatabaseSearch(page); + await pressBackspace(page); + await blurDatabaseSearch(page); + await assertDatabaseSearching(page, false); + + await focusDatabaseSearch(page); + await type(page, '2'); + const closeIcon = page.locator('.close-icon'); + await closeIcon.click(); + await blurDatabaseSearch(page); + await assertDatabaseSearching(page, false); + + await focusDatabaseSearch(page); + await type(page, '2'); + await pressEscape(page); + await blurDatabaseSearch(page); + await assertDatabaseSearching(page, false); +}); + +test('should database title and rich-text support undo/redo', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page); + await switchColumnType(page, 'Text'); + await initDatabaseDynamicRowWithData(page, '123', true); + await undoByKeyboard(page); + await assertDatabaseCellRichTexts(page, { text: '' }); + await pressEscape(page); + await redoByKeyboard(page); + await assertDatabaseCellRichTexts(page, { text: '123' }); + + await focusDatabaseTitle(page); + await type(page, 'abc'); + await assertDatabaseTitleText(page, 'Database 1abc'); + await undoByKeyboard(page); + await assertDatabaseTitleText(page, 'Database 1'); + await redoByKeyboard(page); + await assertDatabaseTitleText(page, 'Database 1abc'); +}); + +test('should support drag to change column width', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page); + const headerColumns = page.locator('.affine-database-column'); + const titleColumn = headerColumns.nth(0); + const normalColumn = headerColumns.nth(1); + + const dragDistance = 100; + const titleColumnWidth = 260; + const normalColumnWidth = 180; + + await assertColumnWidth(titleColumn, titleColumnWidth - 1); + const box = await assertColumnWidth(normalColumn, normalColumnWidth - 1); + + await dragBetweenCoords( + page, + { x: box.x, y: box.y }, + { x: box.x + dragDistance, y: box.y }, + { + steps: 50, + beforeMouseUp: async () => { + await waitNextFrame(page); + }, + } + ); + + await assertColumnWidth(titleColumn, titleColumnWidth + dragDistance); + await assertColumnWidth(normalColumn, normalColumnWidth - 1); + + await undoByClick(page); + await assertColumnWidth(titleColumn, titleColumnWidth - 1); + await assertColumnWidth(normalColumn, normalColumnWidth - 1); +}); + +test('should display the add column button on the right side of database correctly', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page); + const normalColumn = page.locator('.affine-database-column').nth(1); + + const addColumnBtn = page.locator('.header-add-column-button'); + + const box = await getBoundingBox(normalColumn); + await dragBetweenCoords( + page, + { x: box.x, y: box.y }, + { x: box.x + 400, y: box.y }, + { + steps: 50, + beforeMouseUp: async () => { + await waitNextFrame(page); + }, + } + ); + await focusDatabaseHeader(page); + await expect(addColumnBtn).toBeVisible(); +}); + +test('should support drag and drop to move columns', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page, 'column1'); + await initDatabaseColumn(page, 'column2'); + await initDatabaseColumn(page, 'column3'); + + const column1 = await focusDatabaseHeader(page, 1); + const moveIcon = column1.locator('.affine-database-column-move'); + const moveIconBox = await getBoundingBox(moveIcon); + const x = moveIconBox.x + moveIconBox.width / 2; + const y = moveIconBox.y + moveIconBox.height / 2; + + await dragBetweenCoords( + page, + { x, y }, + { x: x + 100, y }, + { + steps: 50, + beforeMouseUp: async () => { + await waitNextFrame(page); + const indicator = page.locator('.vertical-indicator').first(); + await expect(indicator).toBeVisible(); + + const { box } = await getDatabaseHeaderColumn(page, 2); + const indicatorBox = await getBoundingBox(indicator); + expect(box.x + box.width - indicatorBox.x < 10).toBe(true); + }, + } + ); + + const { text } = await getDatabaseHeaderColumn(page, 2); + expect(text).toBe('column1'); +}); + +test('should title column support quick renaming', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page); + await initDatabaseDynamicRowWithData(page, 'a', true); + await pressEscape(page); + await focusDatabaseHeader(page, 1); + const { textElement } = await getDatabaseHeaderColumn(page, 1); + await textElement.click(); + await waitNextFrame(page); + await selectAllByKeyboard(page); + await type(page, '123'); + await pressEnter(page); + expect(await textElement.innerText()).toBe('123'); + + await undoByClick(page); + expect(await textElement.innerText()).toBe('Column 1'); + await textElement.click(); + await waitNextFrame(page); + await selectAllByKeyboard(page); + await type(page, '123'); + await pressEnter(page); + expect(await textElement.innerText()).toBe('123'); +}); + +test('should title column support quick changing of column type', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page); + await initDatabaseDynamicRowWithData(page, 'a', true); + await pressEscape(page); + await initDatabaseDynamicRowWithData(page, 'b'); + await pressEscape(page); + await focusDatabaseHeader(page, 1); + const { typeIcon } = await getDatabaseHeaderColumn(page, 1); + await typeIcon.click(); + await waitNextFrame(page); + await clickColumnType(page, 'Select'); + const cell = getFirstColumnCell(page, 'select-selected'); + expect(await cell.count()).toBe(1); +}); + +test('database format-bar in header and text column', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page); + await switchColumnType(page, 'Text'); + await initDatabaseDynamicRowWithData(page, 'column', true); + await pressArrowLeft(page); + await pressEnter(page); + await type(page, 'header'); + // Title | Column1 + // ---------------- + // header | column + + const formatBar = getFormatBar(page); + await setInlineRangeInInlineEditor(page, { index: 1, length: 4 }, 1); + expect(await formatBar.formatBar.isVisible()).toBe(true); + // Title | Column1 + // ---------------- + // h|eade|r | column + + await assertInlineEditorDeltas( + page, + [ + { + insert: 'header', + }, + ], + 1 + ); + await formatBar.boldBtn.click(); + await assertInlineEditorDeltas( + page, + [ + { + insert: 'h', + }, + { + insert: 'eade', + attributes: { + bold: true, + }, + }, + { + insert: 'r', + }, + ], + 1 + ); + + await pressEscape(page); + await pressArrowRight(page); + await pressEnter(page); + await setInlineRangeInInlineEditor(page, { index: 2, length: 2 }, 2); + expect(await formatBar.formatBar.isVisible()).toBe(true); + // Title | Column1 + // ---------------- + // header | co|lu|mn + + await assertInlineEditorDeltas( + page, + [ + { + insert: 'column', + }, + ], + 2 + ); + await formatBar.boldBtn.click(); + await assertInlineEditorDeltas( + page, + [ + { + insert: 'co', + }, + { + insert: 'lu', + attributes: { + bold: true, + }, + }, + { + insert: 'mn', + }, + ], + 2 + ); +}); + +test.describe('readonly mode', () => { + test('database title should not be edited in readonly mode', async ({ + page, + }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + const locator = page.locator('affine-database'); + await expect(locator).toBeVisible(); + + const dbTitle = 'Database 1'; + await assertBlockProps(page, '2', { + title: dbTitle, + }); + + await focusDatabaseTitle(page); + await selectAllByKeyboard(page); + await pressBackspace(page); + + await type(page, 'hello'); + await assertBlockProps(page, '2', { + title: 'hello', + }); + + await switchReadonly(page); + + await type(page, ' world'); + await assertBlockProps(page, '2', { + title: 'hello', + }); + + await pressBackspace(page, 'hello world'.length); + await assertBlockProps(page, '2', { + title: 'hello', + }); + }); + + test('should rich-text not be edited in readonly mode', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page); + await switchColumnType(page, 'Text'); + await initDatabaseDynamicRowWithData(page, '', true); + + const cell = getFirstColumnCell(page, 'affine-database-rich-text'); + await cell.click(); + await type(page, '123'); + await assertDatabaseCellRichTexts(page, { text: '123' }); + + await switchReadonly(page); + await pressBackspace(page); + await type(page, '789'); + await assertDatabaseCellRichTexts(page, { text: '123' }); + }); + + test('should hide edit widget after switch to readonly mode', async ({ + page, + }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page); + await switchColumnType(page, 'Text'); + await initDatabaseDynamicRowWithData(page, '', true); + + const database = page.locator('affine-database'); + await expect(database).toBeVisible(); + + const databaseMenu = database.locator('.database-ops'); + await expect(databaseMenu).toBeVisible(); + + const addViewButton = database.getByTestId('database-add-view-button'); + await expect(addViewButton).toBeVisible(); + + const titleHeader = page.locator('affine-database-header-column').filter({ + hasText: 'Title', + }); + await titleHeader.hover(); + const columnDragBar = titleHeader.locator('.control-r'); + await expect(columnDragBar).toBeVisible(); + + const filter = database.locator('data-view-header-tools-filter'); + const search = database.locator('data-view-header-tools-search'); + const options = database.locator('data-view-header-tools-view-options'); + const headerAddRow = database.locator('data-view-header-tools-add-row'); + + await database.hover(); + await expect(filter).toBeVisible(); + await expect(search).toBeVisible(); + await expect(options).toBeVisible(); + await expect(headerAddRow).toBeVisible(); + + const row = database.locator('data-view-table-row'); + const rowOptions = row.locator('.row-op'); + const rowDragBar = row.locator('.data-view-table-view-drag-handler>div'); + await row.hover(); + await expect(rowOptions).toHaveCount(2); + await expect(rowOptions.nth(0)).toBeVisible(); + await expect(rowOptions.nth(1)).toBeVisible(); + await expect(rowDragBar).toBeVisible(); + + const addRow = database.locator('.data-view-table-group-add-row'); + await expect(addRow).toBeVisible(); + + // Readonly Mode + { + await switchReadonly(page); + await expect(databaseMenu).toBeHidden(); + await expect(addViewButton).toBeHidden(); + + await titleHeader.hover(); + await expect(columnDragBar).toBeHidden(); + + await database.hover(); + await expect(filter).toBeHidden(); + await expect(search).toBeVisible(); // Note the search should not be hidden + await expect(options).toBeHidden(); + await expect(headerAddRow).toBeHidden(); + + await row.hover(); + await expect(rowOptions.nth(0)).toBeHidden(); + await expect(rowOptions.nth(1)).toBeHidden(); + await expect(rowDragBar).toBeHidden(); + + await expect(addRow).toBeHidden(); + } + }); + + test('should hide focus border after switch to readonly mode', async ({ + page, + }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page); + await switchColumnType(page, 'Text'); + await initDatabaseDynamicRowWithData(page, '', true); + + const database = page.locator('affine-database'); + await expect(database).toBeVisible(); + + const cell = getFirstColumnCell(page, 'affine-database-rich-text'); + await cell.click(); + + const focusBorder = database.locator( + 'data-view-table-selection .database-focus' + ); + await expect(focusBorder).toBeVisible(); + + await switchReadonly(page); + await expect(focusBorder).toBeHidden(); + }); + + test('should hide selection after switch to readonly mode', async ({ + page, + }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page); + await switchColumnType(page, 'Text'); + await initDatabaseDynamicRowWithData(page, '', true); + + const database = page.locator('affine-database'); + await expect(database).toBeVisible(); + + const startCell = getDatabaseBodyCell(page, { + rowIndex: 0, + columnIndex: 0, + }); + const endCell = getDatabaseBodyCell(page, { + rowIndex: 0, + columnIndex: 1, + }); + + const startBox = await getBoundingBox(startCell); + const endBox = await getBoundingBox(endCell); + + const startX = startBox.x + startBox.width / 2; + const startY = startBox.y + startBox.height / 2; + const endX = endBox.x + endBox.width / 2; + const endY = endBox.y + endBox.height / 2; + + await dragBetweenCoords( + page, + { x: startX, y: startY }, + { x: endX, y: endY } + ); + + const selection = database.locator( + 'data-view-table-selection .database-selection' + ); + + await expect(selection).toBeVisible(); + + await switchReadonly(page); + await expect(selection).toBeHidden(); + }); +}); diff --git a/blocksuite/tests-legacy/database/selection.spec.ts b/blocksuite/tests-legacy/database/selection.spec.ts new file mode 100644 index 0000000000000..e0911a1b7c683 --- /dev/null +++ b/blocksuite/tests-legacy/database/selection.spec.ts @@ -0,0 +1,567 @@ +import { expect } from '@playwright/test'; + +import { dragBetweenCoords } from '../utils/actions/drag.js'; +import { shiftClick } from '../utils/actions/edgeless.js'; +import { + pressArrowDown, + pressArrowDownWithShiftKey, + pressArrowLeft, + pressArrowRight, + pressArrowUp, + pressArrowUpWithShiftKey, + pressBackspace, + pressEnter, + pressEscape, + type, +} from '../utils/actions/keyboard.js'; +import { + enterPlaygroundRoom, + getBoundingBox, + initDatabaseDynamicRowWithData, + initDatabaseRowWithData, + initEmptyDatabaseState, + initKanbanViewState, + waitNextFrame, +} from '../utils/actions/misc.js'; +import { test } from '../utils/playwright.js'; +import { + assertCellsSelection, + assertDatabaseTitleColumnText, + assertKanbanCardHeaderText, + assertKanbanCardSelected, + assertKanbanCellSelected, + assertRowsSelection, + clickKanbanCardHeader, + focusKanbanCardHeader, + getDatabaseBodyCell, + getKanbanCard, + initDatabaseColumn, + switchColumnType, +} from './actions.js'; + +test.describe('focus', () => { + test('should support move focus by arrow key', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page); + await initDatabaseDynamicRowWithData(page, '123', true); + await pressEscape(page); + await waitNextFrame(page, 100); + await pressEscape(page); + await assertRowsSelection(page, [0, 0]); + }); + + test('should support multi row selection', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page); + await initDatabaseDynamicRowWithData(page, '', true); + await pressEscape(page); + await switchColumnType(page, 'Number'); + await initDatabaseDynamicRowWithData(page, '123', true); + + const selectColumn = getDatabaseBodyCell(page, { + rowIndex: 1, + columnIndex: 1, + }); + + const endBox = await getBoundingBox(selectColumn); + const endX = endBox.x + endBox.width / 2; + + await dragBetweenCoords( + page, + { x: endX, y: endBox.y }, + { x: endX, y: endBox.y + endBox.height } + ); + await pressEscape(page); + await assertRowsSelection(page, [0, 1]); + }); + + test('should support row selection with dynamic height', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page); + await initDatabaseDynamicRowWithData(page, '123123', true); + await type(page, '456456'); + await pressEnter(page); + await type(page, 'abcabc'); + await pressEnter(page); + await type(page, 'defdef'); + await pressEnter(page); + await pressEscape(page); + + await pressEscape(page); + await assertRowsSelection(page, [0, 0]); + }); +}); + +test.describe('row-level selection', () => { + test('should support title selection', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page); + await initDatabaseRowWithData(page, 'title'); + await pressEscape(page); + await waitNextFrame(page, 100); + await assertCellsSelection(page, { + start: [0, 0], + }); + + await pressEscape(page); + await assertRowsSelection(page, [0, 0]); + }); + + test('should support pressing esc to trigger row selection', async ({ + page, + }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page); + await initDatabaseDynamicRowWithData(page, '123', true); + await pressEscape(page); + await waitNextFrame(page, 100); + await pressEscape(page); + await assertRowsSelection(page, [0, 0]); + }); + + test('should support multi row selection', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page); + await initDatabaseDynamicRowWithData(page, '', true); + await pressEscape(page); + await switchColumnType(page, 'Number'); + await initDatabaseDynamicRowWithData(page, '123', true); + + const selectColumn = getDatabaseBodyCell(page, { + rowIndex: 1, + columnIndex: 1, + }); + + const endBox = await getBoundingBox(selectColumn); + const endX = endBox.x + endBox.width / 2; + + await dragBetweenCoords( + page, + { x: endX, y: endBox.y }, + { x: endX, y: endBox.y + endBox.height } + ); + await pressEscape(page); + await assertRowsSelection(page, [0, 1]); + }); + + test('should support row selection with dynamic height', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page); + await initDatabaseDynamicRowWithData(page, '123123', true); + await type(page, '456456'); + await pressEnter(page); + await type(page, 'abcabc'); + await pressEnter(page); + await type(page, 'defdef'); + await pressEnter(page); + await pressEscape(page); + + await pressEscape(page); + await assertRowsSelection(page, [0, 0]); + }); + + test('move row selection with (up | down)', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page); + + // add two rows + await initDatabaseDynamicRowWithData(page, '123123', true); + await pressEscape(page); + + await initDatabaseDynamicRowWithData(page, '123123', true); + await pressEscape(page); + + await pressEscape(page); // switch to row selection + + await assertRowsSelection(page, [1, 1]); + + await pressArrowUp(page); + await assertRowsSelection(page, [0, 0]); + + // should not allow under selection + await pressArrowUp(page); + await assertRowsSelection(page, [0, 0]); + + await pressArrowDown(page); + await assertRowsSelection(page, [1, 1]); + + // should not allow over selection + await pressArrowDown(page); + await assertRowsSelection(page, [1, 1]); + }); + + test('increment decrement row selection with shift+(up | down)', async ({ + page, + }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page); + + // add two rows + await initDatabaseDynamicRowWithData(page, '123123', true); + await pressEscape(page); + + await initDatabaseDynamicRowWithData(page, '123123', true); + await pressEscape(page); + + await pressEscape(page); // switch to row selection + + await pressArrowUpWithShiftKey(page); + await assertRowsSelection(page, [0, 1]); + + await pressArrowDownWithShiftKey(page); + await assertRowsSelection(page, [1, 1]); // should decrement back + + await pressArrowUp(page); // go to first row + + await pressArrowDownWithShiftKey(page); + await assertRowsSelection(page, [0, 1]); + + await pressArrowUpWithShiftKey(page); + await assertRowsSelection(page, [0, 0]); + }); +}); + +test.describe('cell-level selection', () => { + test('should support multi cell selection', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page); + await initDatabaseDynamicRowWithData(page, '', true); + await pressEscape(page); + await switchColumnType(page, 'Number'); + await initDatabaseDynamicRowWithData(page, '123', true); + + const startCell = getDatabaseBodyCell(page, { + rowIndex: 0, + columnIndex: 0, + }); + const endCell = getDatabaseBodyCell(page, { + rowIndex: 1, + columnIndex: 1, + }); + + const startBox = await getBoundingBox(startCell); + const endBox = await getBoundingBox(endCell); + + const startX = startBox.x + startBox.width / 2; + const startY = startBox.y + startBox.height / 2; + const endX = endBox.x + endBox.width / 2; + const endY = endBox.y + endBox.height / 2; + + await dragBetweenCoords( + page, + { x: startX, y: startY }, + { x: endX, y: endY } + ); + + await assertCellsSelection(page, { + start: [0, 0], + end: [1, 1], + }); + }); + + test("should support backspace key to delete cell's content", async ({ + page, + }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + await initDatabaseColumn(page); + await initDatabaseRowWithData(page, 'row1'); + await initDatabaseDynamicRowWithData(page, 'abc', false); + await pressEscape(page); + await initDatabaseRowWithData(page, 'row2'); + await initDatabaseDynamicRowWithData(page, '123', false); + await pressEscape(page); + + const startCell = getDatabaseBodyCell(page, { + rowIndex: 0, + columnIndex: 0, + }); + const endCell = getDatabaseBodyCell(page, { + rowIndex: 1, + columnIndex: 1, + }); + + const startBox = await getBoundingBox(startCell); + const endBox = await getBoundingBox(endCell); + + const startX = startBox.x + startBox.width / 2; + const startY = startBox.y + startBox.height / 2; + const endX = endBox.x + endBox.width / 2; + const endY = endBox.y + endBox.height / 2; + + await dragBetweenCoords( + page, + { x: startX, y: startY }, + { x: endX, y: endY } + ); + + await pressBackspace(page); + await assertDatabaseTitleColumnText(page, '', 0); + await assertDatabaseTitleColumnText(page, '', 1); + const selectCell1 = getDatabaseBodyCell(page, { + rowIndex: 0, + columnIndex: 1, + }); + expect(await selectCell1.innerText()).toBe(''); + const selectCell2 = getDatabaseBodyCell(page, { + rowIndex: 1, + columnIndex: 1, + }); + expect(await selectCell2.innerText()).toBe(''); + }); +}); + +test.describe('kanban view selection', () => { + test("should support move cell's focus by arrow key(up&down) within a card", async ({ + page, + }) => { + await enterPlaygroundRoom(page); + await initKanbanViewState(page, { + rows: ['row1'], + columns: [ + { + type: 'number', + value: [1], + }, + { + type: 'rich-text', + value: ['text'], + }, + ], + }); + + await focusKanbanCardHeader(page); + await assertKanbanCellSelected(page, { + // group by `number` column, the first(groupIndex: 0) group is `Ungroups` + groupIndex: 1, + cardIndex: 0, + cellIndex: 0, + }); + + await pressArrowDown(page, 3); + await assertKanbanCellSelected(page, { + groupIndex: 1, + cardIndex: 0, + cellIndex: 0, + }); + + await pressArrowUp(page); + await assertKanbanCellSelected(page, { + groupIndex: 1, + cardIndex: 0, + cellIndex: 2, + }); + }); + + test("should support move cell's focus by arrow key(up&down) within multi cards", async ({ + page, + }) => { + await enterPlaygroundRoom(page); + await initKanbanViewState(page, { + rows: ['row1', 'row2'], + columns: [ + { + type: 'number', + value: [1, 2], + }, + { + type: 'rich-text', + value: ['text'], + }, + ], + }); + + await focusKanbanCardHeader(page); + await pressArrowUp(page); + await assertKanbanCellSelected(page, { + groupIndex: 1, + cardIndex: 1, + cellIndex: 2, + }); + + await pressArrowDown(page); + await assertKanbanCellSelected(page, { + groupIndex: 1, + cardIndex: 0, + cellIndex: 0, + }); + }); + + test("should support move cell's focus by arrow key(left&right)", async ({ + page, + }) => { + await enterPlaygroundRoom(page); + await initKanbanViewState(page, { + rows: ['row1', 'row2', 'row3'], + columns: [ + { + type: 'number', + value: [undefined, 1, 10], + }, + ], + }); + + await focusKanbanCardHeader(page); + + await pressArrowRight(page, 3); + await assertKanbanCellSelected(page, { + groupIndex: 0, + cardIndex: 0, + cellIndex: 0, + }); + + await pressArrowLeft(page); + await assertKanbanCellSelected(page, { + groupIndex: 2, + cardIndex: 0, + cellIndex: 0, + }); + }); + + test("should support move card's focus by arrow key(up&down)", async ({ + page, + }) => { + await enterPlaygroundRoom(page); + await initKanbanViewState(page, { + rows: ['row1', 'row2', 'row3'], + columns: [ + { + type: 'number', + value: [undefined, undefined, undefined], + }, + ], + }); + + await focusKanbanCardHeader(page); + await pressEscape(page); + await pressEscape(page); + await assertKanbanCardSelected(page, { + groupIndex: 0, + cardIndex: 0, + }); + + await pressArrowDown(page, 3); + await assertKanbanCardSelected(page, { + groupIndex: 0, + cardIndex: 0, + }); + + await pressArrowUp(page); + await assertKanbanCardSelected(page, { + groupIndex: 0, + cardIndex: 2, + }); + }); + + test("should support move card's focus by arrow key(left&right)", async ({ + page, + }) => { + await enterPlaygroundRoom(page); + await initKanbanViewState(page, { + rows: ['row1', 'row2', 'row3'], + columns: [ + { + type: 'number', + value: [undefined, 1, 10], + }, + ], + }); + + await focusKanbanCardHeader(page); + await pressEscape(page); + await pressEscape(page); + + await pressArrowRight(page, 3); + await assertKanbanCardSelected(page, { + groupIndex: 0, + cardIndex: 0, + }); + + await pressArrowLeft(page); + await assertKanbanCardSelected(page, { + groupIndex: 2, + cardIndex: 0, + }); + }); + + test('should support multi card selection', async ({ page }) => { + await enterPlaygroundRoom(page); + await initKanbanViewState(page, { + rows: ['row1', 'row2'], + columns: [ + { + type: 'number', + value: [undefined, 1], + }, + ], + }); + + await focusKanbanCardHeader(page); + await pressEscape(page); + await pressEscape(page); + + const card = getKanbanCard(page, { + groupIndex: 1, + cardIndex: 0, + }); + const box = await getBoundingBox(card); + await shiftClick(page, { + x: box.x + box.width / 2, + y: box.y + box.height / 2, + }); + + await assertKanbanCardSelected(page, { + groupIndex: 0, + cardIndex: 0, + }); + await assertKanbanCardSelected(page, { + groupIndex: 1, + cardIndex: 0, + }); + }); + + test("should support move cursor in card's title by arrow key(left&right)", async ({ + page, + }) => { + await enterPlaygroundRoom(page); + await initKanbanViewState(page, { + rows: ['row1'], + columns: [ + { + type: 'rich-text', + value: ['text'], + }, + ], + }); + + await clickKanbanCardHeader(page); + await type(page, 'abc'); + await pressArrowLeft(page, 2); + await pressArrowRight(page); + await pressBackspace(page); + await pressEscape(page); + + await assertKanbanCardHeaderText(page, 'row1ac'); + }); +}); diff --git a/blocksuite/tests-legacy/database/sort.spec.ts b/blocksuite/tests-legacy/database/sort.spec.ts new file mode 100644 index 0000000000000..8c3eea01eef64 --- /dev/null +++ b/blocksuite/tests-legacy/database/sort.spec.ts @@ -0,0 +1,112 @@ +import { expect, type Locator } from '@playwright/test'; + +import { + enterPlaygroundRoom, + initDatabaseDynamicRowWithData, + initEmptyDatabaseState, + waitNextFrame, +} from '../utils/actions/index.js'; +import { test } from '../utils/playwright.js'; +import { initDatabaseColumn, switchColumnType } from './actions.js'; + +test('database sort with multiple rules', async ({ page }) => { + // Initialize database + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + + // Add test columns: Name (text) and Age (number) + await initDatabaseColumn(page, 'Name'); + await switchColumnType(page, 'Text', 1); + await initDatabaseColumn(page, 'Age'); + await switchColumnType(page, 'Number', 2); + + // Add test data + const testData = [ + { name: 'Alice', age: '25' }, + { name: 'Bob', age: '30' }, + { name: 'Alice', age: '20' }, + { name: 'Charlie', age: '25' }, + ]; + + for (const data of testData) { + await initDatabaseDynamicRowWithData(page, data.name, true, 0); + await initDatabaseDynamicRowWithData(page, data.age, false, 1); + } + + // Open sort menu + const sortButton = page.locator('data-view-header-tools-sort'); + await sortButton.click(); + + // Add first sort rule: Name ascending + await page.locator('affine-menu').getByText('Name').click(); + await waitNextFrame(page); + + // Add second sort rule: Age ascending + await page.getByText('Add sort').click(); + await page.locator('affine-menu').getByText('Age').click(); + await waitNextFrame(page); + + // Get all rows after sorting + const rows = await page.locator('affine-database-row').all(); + const getCellText = async (row: Locator, index: number) => { + const cell = row.locator('.cell').nth(index); + return cell.innerText(); + }; + + // Verify sorting results + // Should be sorted by Name first, then by Age + const expectedOrder = [ + { name: 'Alice', age: '20' }, + { name: 'Alice', age: '25' }, + { name: 'Bob', age: '30' }, + { name: 'Charlie', age: '25' }, + ]; + + for (let i = 0; i < rows.length; i++) { + const name = await getCellText(rows[i], 1); + const age = await getCellText(rows[i], 2); + expect(name).toBe(expectedOrder[i].name); + expect(age).toBe(expectedOrder[i].age); + } + + // Change sort order of Name to descending + await page.locator('.sort-item').first().getByText('Ascending').click(); + await page.getByText('Descending').click(); + await waitNextFrame(page); + + // Verify new sorting results + const expectedOrderDesc = [ + { name: 'Charlie', age: '25' }, + { name: 'Bob', age: '30' }, + { name: 'Alice', age: '20' }, + { name: 'Alice', age: '25' }, + ]; + + const rowsAfterDesc = await page.locator('affine-database-row').all(); + for (let i = 0; i < rowsAfterDesc.length; i++) { + const name = await getCellText(rowsAfterDesc[i], 1); + const age = await getCellText(rowsAfterDesc[i], 2); + expect(name).toBe(expectedOrderDesc[i].name); + expect(age).toBe(expectedOrderDesc[i].age); + } + + // Remove first sort rule + await page.locator('.sort-item').first().getByRole('img').last().click(); + await waitNextFrame(page); + + // Verify sorting now only by Age + const expectedOrderAgeOnly = [ + { name: 'Alice', age: '20' }, + { name: 'Alice', age: '25' }, + { name: 'Charlie', age: '25' }, + { name: 'Bob', age: '30' }, + ]; + + const rowsAfterRemove = await page.locator('affine-database-row').all(); + for (let i = 0; i < rowsAfterRemove.length; i++) { + const name = await getCellText(rowsAfterRemove[i], 1); + const age = await getCellText(rowsAfterRemove[i], 2); + expect(name).toBe(expectedOrderAgeOnly[i].name); + expect(age).toBe(expectedOrderAgeOnly[i].age); + } +}); diff --git a/blocksuite/tests-legacy/database/statistics.spec.ts b/blocksuite/tests-legacy/database/statistics.spec.ts new file mode 100644 index 0000000000000..536db36ca74f3 --- /dev/null +++ b/blocksuite/tests-legacy/database/statistics.spec.ts @@ -0,0 +1,103 @@ +import { press } from '@inline/__tests__/utils.js'; +import { expect, type Page } from '@playwright/test'; + +import { type } from '../utils/actions/index.js'; +import { + enterPlaygroundRoom, + getAddRow, + initEmptyDatabaseState, + waitNextFrame, +} from '../utils/actions/misc.js'; +import { test } from '../utils/playwright.js'; +import { changeColumnType, moveToCenterOf, pressKey } from './actions.js'; + +const addRow = async (page: Page, count: number = 1) => { + await waitNextFrame(page); + const addRow = getAddRow(page); + for (let i = 0; i < count; i++) { + await addRow.click(); + } + await press(page, 'Escape'); + await waitNextFrame(page); +}; +const insertRightColumn = async (page: Page, index = 0) => { + await waitNextFrame(page); + await page.locator('affine-database-header-column').nth(index).click(); + await waitNextFrame(page, 200); + await pressKey(page, 'Escape'); + const menu = page.locator('.affine-menu-button', { + hasText: new RegExp('Insert Right'), + }); + await menu.click(); + await waitNextFrame(page, 200); + await pressKey(page, 'Enter'); +}; +const menuSelect = async (page: Page, selectors: string[]) => { + await waitNextFrame(page); + for (const name of selectors) { + const menu = page.locator('.affine-menu-button', { + hasText: new RegExp(name), + }); + await menu.click(); + } +}; +test.describe('title', () => { + test('empty count', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + await addRow(page, 3); + const statCell = page.locator('affine-database-column-stats-cell').nth(0); + await moveToCenterOf(page, statCell); + await statCell.click(); + await menuSelect(page, ['Count', 'Count Empty']); + const value = statCell.locator('.value'); + expect((await value.textContent())?.trim()).toBe('3'); + await page.locator('affine-database-cell-container').nth(0).click(); + await pressKey(page, 'Enter'); + await type(page, 'asd'); + await pressKey(page, 'Escape'); + expect((await value.textContent())?.trim()).toBe('2'); + }); +}); + +test.describe('rich-text', () => { + test('empty count', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + await addRow(page, 3); + await insertRightColumn(page); + await changeColumnType(page, 1, 'text'); + const statCell = page.locator('affine-database-column-stats-cell').nth(1); + await moveToCenterOf(page, statCell); + await statCell.click(); + await menuSelect(page, ['Count', 'Count Empty']); + const value = statCell.locator('.value'); + expect((await value.textContent())?.trim()).toBe('3'); + await page.locator('affine-database-cell-container').nth(1).click(); + await pressKey(page, 'Enter'); + await type(page, 'asd'); + await pressKey(page, 'Escape'); + expect((await value.textContent())?.trim()).toBe('2'); + }); +}); + +test.describe('select', () => { + test('empty count', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + await addRow(page, 3); + await insertRightColumn(page); + await changeColumnType(page, 1, 'select'); + const statCell = page.locator('affine-database-column-stats-cell').nth(1); + await moveToCenterOf(page, statCell); + await statCell.click(); + await menuSelect(page, ['Count', 'Count Empty']); + const value = statCell.locator('.value'); + expect((await value.textContent())?.trim()).toBe('3'); + await page.locator('affine-database-cell-container').nth(1).click(); + await pressKey(page, 'Enter'); + await type(page, 'select'); + await pressKey(page, 'Enter'); + expect((await value.textContent())?.trim()).toBe('2'); + }); +}); diff --git a/blocksuite/tests-legacy/database/title.spec.ts b/blocksuite/tests-legacy/database/title.spec.ts new file mode 100644 index 0000000000000..5c5e68440490d --- /dev/null +++ b/blocksuite/tests-legacy/database/title.spec.ts @@ -0,0 +1,19 @@ +import { press } from '@inline/__tests__/utils.js'; +import { expect } from '@playwright/test'; + +import { + enterPlaygroundRoom, + initDatabaseDynamicRowWithData, + initEmptyDatabaseState, +} from '../utils/actions/misc.js'; +import { test } from '../utils/playwright.js'; + +test.describe('title', () => { + test('should able to link doc by press @', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyDatabaseState(page); + await initDatabaseDynamicRowWithData(page, '123', true); + await press(page, '@'); + await expect(page.locator('.linked-doc-popover')).toBeVisible(); + }); +}); diff --git a/blocksuite/tests-legacy/drag.spec.ts b/blocksuite/tests-legacy/drag.spec.ts new file mode 100644 index 0000000000000..1c9146371e310 --- /dev/null +++ b/blocksuite/tests-legacy/drag.spec.ts @@ -0,0 +1,768 @@ +import { BLOCK_CHILDREN_CONTAINER_PADDING_LEFT } from '@blocks/_common/consts.js'; +import { expect } from '@playwright/test'; + +import { + dragBetweenCoords, + dragBetweenIndices, + dragHandleFromBlockToBlockBottomById, + enterPlaygroundRoom, + focusRichText, + initEmptyParagraphState, + initThreeLists, + initThreeParagraphs, + pressEnter, + pressShiftTab, + pressTab, + type, +} from './utils/actions/index.js'; +import { + getBoundingClientRect, + getEditorHostLocator, + getPageSnapshot, + initParagraphsByCount, +} from './utils/actions/misc.js'; +import { assertRichTexts } from './utils/asserts.js'; +import { test } from './utils/playwright.js'; + +test('only have one drag handle in screen', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + const topLeft = await page.evaluate(() => { + const paragraph = document.querySelector('[data-block-id="2"]'); + const box = paragraph?.getBoundingClientRect(); + if (!box) { + throw new Error(); + } + return { x: box.left, y: box.top + 2 }; + }, []); + + const bottomRight = await page.evaluate(() => { + const paragraph = document.querySelector('[data-block-id="4"]'); + const box = paragraph?.getBoundingClientRect(); + if (!box) { + throw new Error(); + } + return { x: box.right, y: box.bottom - 2 }; + }, []); + + await page.mouse.move(topLeft.x, topLeft.y); + const length1 = await page.evaluate(() => { + const handles = document.querySelectorAll('affine-drag-handle-widget'); + return handles.length; + }, []); + expect(length1).toBe(1); + await page.mouse.move(bottomRight.x, bottomRight.y); + const length2 = await page.evaluate(() => { + const handles = document.querySelectorAll('affine-drag-handle-widget'); + return handles.length; + }, []); + expect(length2).toBe(1); +}); + +test('move drag handle in paragraphs', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + await dragHandleFromBlockToBlockBottomById(page, '2', '4'); + await expect(page.locator('.affine-drag-indicator')).toBeHidden(); + await assertRichTexts(page, ['456', '789', '123']); +}); + +test('move drag handle in list', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeLists(page); + await assertRichTexts(page, ['123', '456', '789']); + await dragHandleFromBlockToBlockBottomById(page, '5', '3', false); + await expect(page.locator('.affine-drag-indicator')).toBeHidden(); + await assertRichTexts(page, ['789', '123', '456']); +}); + +test('move drag handle in nested block', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + + await focusRichText(page); + await type(page, '-'); + await page.keyboard.press('Space', { delay: 50 }); + await type(page, '1'); + await pressEnter(page); + await type(page, '2'); + + await pressEnter(page); + await pressTab(page); + await type(page, '21'); + await pressEnter(page); + await type(page, '22'); + await pressEnter(page); + await type(page, '23'); + await pressEnter(page); + await pressShiftTab(page); + + await type(page, '3'); + + await assertRichTexts(page, ['1', '2', '21', '22', '23', '3']); + + await dragHandleFromBlockToBlockBottomById(page, '5', '7'); + await expect(page.locator('.affine-drag-indicator')).toBeHidden(); + await assertRichTexts(page, ['1', '2', '22', '23', '21', '3']); + + // FIXME(DND) + // await dragHandleFromBlockToBlockBottomById(page, '3', '8'); + // await expect(page.locator('.affine-drag-indicator')).toBeHidden(); + // await assertRichTexts(page, ['2', '22', '23', '21', '3', '1']); +}); + +test('move drag handle into another block', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + + await focusRichText(page); + await type(page, '-'); + await page.keyboard.press('Space', { delay: 50 }); + await type(page, '1'); + await pressEnter(page); + await type(page, '2'); + + await pressEnter(page); + await pressTab(page); + await type(page, '21'); + await pressEnter(page); + await type(page, '22'); + await pressEnter(page); + await type(page, '23'); + await pressEnter(page); + await pressShiftTab(page); + + await type(page, '3'); + + await assertRichTexts(page, ['1', '2', '21', '22', '23', '3']); + + await dragHandleFromBlockToBlockBottomById( + page, + '5', + '7', + true, + 2 * BLOCK_CHILDREN_CONTAINER_PADDING_LEFT + ); + await expect(page.locator('.affine-drag-indicator')).toBeHidden(); + await assertRichTexts(page, ['1', '2', '22', '23', '21', '3']); + // FIXME(DND) + // await assertBlockChildrenIds(page, '7', ['5']); + + // await dragHandleFromBlockToBlockBottomById( + // page, + // '3', + // '8', + // true, + // 2 * BLOCK_CHILDREN_CONTAINER_PADDING_LEFT + // ); + // await expect(page.locator('.affine-drag-indicator')).toBeHidden(); + // await assertRichTexts(page, ['2', '22', '23', '21', '3', '1']); + // await assertBlockChildrenIds(page, '8', ['3']); +}); + +test('move to the last block of each level in multi-level nesting', async ({ + page, +}, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + + await focusRichText(page); + await type(page, '-'); + await page.keyboard.press('Space', { delay: 50 }); + await type(page, 'A'); + await pressEnter(page); + await type(page, 'B'); + await pressEnter(page); + await type(page, 'C'); + await pressEnter(page); + await pressTab(page); + await type(page, 'D'); + await pressEnter(page); + await type(page, 'E'); + await pressEnter(page); + await pressTab(page); + await type(page, 'F'); + await pressEnter(page); + await type(page, 'G'); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_init.json` + ); + + await dragHandleFromBlockToBlockBottomById(page, '3', '9'); + await expect(page.locator('.affine-drag-indicator')).toBeHidden(); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_drag_3_9.json` + ); + + // FIXME(DND) + // await dragHandleFromBlockToBlockBottomById( + // page, + // '4', + // '3', + // true, + // -(1 * BLOCK_CHILDREN_CONTAINER_PADDING_LEFT) + // ); + // await expect(page.locator('.affine-drag-indicator')).toBeHidden(); + // + // expect(await getPageSnapshot(page, true)).toMatchSnapshot( + // `${testInfo.title}_drag_4_3.json` + // ); + // + // await assertRichTexts(page, ['C', 'D', 'E', 'F', 'G', 'A', 'B']); + // await dragHandleFromBlockToBlockBottomById( + // page, + // '3', + // '4', + // true, + // -(2 * BLOCK_CHILDREN_CONTAINER_PADDING_LEFT) + // ); + // await expect(page.locator('.affine-drag-indicator')).toBeHidden(); + // + // expect(await getPageSnapshot(page, true)).toMatchSnapshot( + // `${testInfo.title}_drag_3_4.json` + // ); + // + // await assertRichTexts(page, ['C', 'D', 'E', 'F', 'G', 'B', 'A']); +}); + +test('should sync selected-blocks to session-manager when clicking drag handle', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + await focusRichText(page, 1); + const rect = await getBoundingClientRect(page, '[data-block-id="1"]'); + if (!rect) { + throw new Error(); + } + await page.mouse.move(rect.x + 10, rect.y + 10, { steps: 2 }); + + const handle = page.locator('.affine-drag-handle-container'); + await handle.click(); + + await page.keyboard.press('Backspace'); + await assertRichTexts(page, ['', '456', '789']); +}); + +test.fixme( + 'should be able to drag & drop multiple blocks', + async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + await dragBetweenIndices( + page, + [0, 0], + [1, 3], + { x: -60, y: 0 }, + { x: 80, y: 0 }, + { + steps: 50, + } + ); + + const blockSelections = page + .locator('affine-block-selection') + .locator('visible=true'); + await expect(blockSelections).toHaveCount(2); + + await dragHandleFromBlockToBlockBottomById(page, '2', '4', true); + await expect(page.locator('.affine-drag-indicator')).toBeHidden(); + + await assertRichTexts(page, ['789', '123', '456']); + + // Selection is still 2 after drop + await expect(blockSelections).toHaveCount(2); + } +); + +test.fixme( + 'should be able to drag & drop multiple blocks to nested block', + async ({ page }, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + + await focusRichText(page); + await type(page, '-'); + await page.keyboard.press('Space', { delay: 50 }); + await type(page, 'A'); + await pressEnter(page); + await type(page, 'B'); + await pressEnter(page); + await type(page, 'C'); + await pressEnter(page); + await pressTab(page); + await type(page, 'D'); + await pressEnter(page); + await type(page, 'E'); + await pressEnter(page); + await pressTab(page); + await type(page, 'F'); + await pressEnter(page); + await type(page, 'G'); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_init.json` + ); + + await dragBetweenIndices( + page, + [0, 0], + [1, 1], + { x: -80, y: 0 }, + { x: 80, y: 0 }, + { + steps: 50, + } + ); + + const blockSelections = page + .locator('affine-block-selection') + .locator('visible=true'); + await expect(blockSelections).toHaveCount(2); + + await dragHandleFromBlockToBlockBottomById(page, '3', '8'); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_finial.json` + ); + } +); + +test('should blur rich-text first on starting block selection', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + await expect(page.locator('*:focus')).toHaveCount(1); + + await dragHandleFromBlockToBlockBottomById(page, '2', '4'); + await expect(page.locator('.affine-drag-indicator')).toBeHidden(); + await assertRichTexts(page, ['456', '789', '123']); + + await expect(page.locator('*:focus')).toHaveCount(0); +}); + +test('hide drag handle when mouse is hovering over the title', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + + const rect = await getBoundingClientRect( + page, + '.affine-note-block-container' + ); + const dragHandle = page.locator('.affine-drag-handle-container'); + // When there is a gap between paragraph blocks, it is the correct behavior for the drag handle to appear + // when the mouse is over the gap. Therefore, we use rect.y - 20 to make the Y offset greater than the gap between the + // paragraph blocks. + await page.mouse.move(rect.x, rect.y - 20, { steps: 2 }); + await expect(dragHandle).toBeHidden(); + + await page.mouse.move(rect.x, rect.y, { steps: 2 }); + expect(await dragHandle.isVisible()).toBe(true); + await expect(dragHandle).toBeVisible(); +}); + +test.fixme('should create preview when dragging', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + const dragPreview = page.locator('affine-drag-preview'); + + await dragBetweenIndices( + page, + [0, 0], + [1, 3], + { x: -60, y: 0 }, + { x: 80, y: 0 }, + { + steps: 50, + } + ); + + const blockSelections = page + .locator('affine-block-selection') + .locator('visible=true'); + await expect(blockSelections).toHaveCount(2); + + await dragHandleFromBlockToBlockBottomById( + page, + '2', + '4', + true, + undefined, + async () => { + await expect(dragPreview).toBeVisible(); + await expect(dragPreview.locator('[data-block-id]')).toHaveCount(4); + } + ); +}); + +test.fixme( + 'should drag and drop blocks under block-level selection', + async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + await dragBetweenIndices( + page, + [0, 0], + [1, 3], + { x: -60, y: 0 }, + { x: 80, y: 0 }, + { + steps: 50, + } + ); + + const blockSelections = page + .locator('affine-block-selection') + .locator('visible=true'); + await expect(blockSelections).toHaveCount(2); + + const editorHost = getEditorHostLocator(page); + const editors = editorHost.locator('rich-text'); + const editorRect0 = await editors.nth(0).boundingBox(); + const editorRect2 = await editors.nth(2).boundingBox(); + if (!editorRect0 || !editorRect2) { + throw new Error(); + } + + await dragBetweenCoords( + page, + { + x: editorRect0.x - 10, + y: editorRect0.y + editorRect0.height / 2, + }, + { + x: editorRect2.x + 10, + y: editorRect2.y + editorRect2.height / 2 + 10, + }, + { + steps: 50, + } + ); + + await assertRichTexts(page, ['789', '123', '456']); + await expect(blockSelections).toHaveCount(2); + } +); + +test('should trigger click event on editor container when clicking on blocks under block-level selection', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + await dragBetweenIndices( + page, + [0, 0], + [1, 3], + { x: -60, y: 0 }, + { x: 80, y: 0 }, + { + steps: 50, + } + ); + + const blockSelections = page + .locator('affine-block-selection') + .locator('visible=true'); + await expect(blockSelections).toHaveCount(2); + await expect(page.locator('*:focus')).toHaveCount(0); + + const editorHost = getEditorHostLocator(page); + const editors = editorHost.locator('rich-text'); + const editorRect0 = await editors.nth(0).boundingBox(); + if (!editorRect0) { + throw new Error(); + } + + await page.mouse.move( + editorRect0.x + 10, + editorRect0.y + editorRect0.height / 2 + ); + await page.mouse.down(); + await page.mouse.up(); + await expect(blockSelections).toHaveCount(0); + await expect(page.locator('*:focus')).toHaveCount(1); +}); + +test('should get to selected block when dragging unselected block', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, '123'); + await pressEnter(page); + await type(page, '456'); + await assertRichTexts(page, ['123', '456']); + + const editorHost = getEditorHostLocator(page); + const editors = editorHost.locator('rich-text'); + const editorRect0 = await editors.nth(0).boundingBox(); + const editorRect1 = await editors.nth(1).boundingBox(); + + if (!editorRect0 || !editorRect1) { + throw new Error(); + } + + await page.mouse.move(editorRect1.x - 5, editorRect0.y); + await page.mouse.down(); + await page.mouse.up(); + + const blockSelections = page + .locator('affine-block-selection') + .locator('visible=true'); + await expect(blockSelections).toHaveCount(1); + + await page.mouse.move(editorRect1.x - 5, editorRect0.y); + await page.mouse.down(); + await page.mouse.move( + editorRect1.x - 5, + editorRect1.y + editorRect1.height / 2 + 1, + { + steps: 10, + } + ); + await page.mouse.up(); + + await expect(blockSelections).toHaveCount(1); + + // FIXME(DND) + // await assertRichTexts(page, ['456', '123']); +}); + +test.fixme( + 'should clear the currently selected block when clicked again', + async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, '123'); + await pressEnter(page); + await type(page, '456'); + await assertRichTexts(page, ['123', '456']); + + const editorHost = getEditorHostLocator(page); + const editors = editorHost.locator('rich-text'); + const editorRect0 = await editors.nth(0).boundingBox(); + const editorRect1 = await editors.nth(1).boundingBox(); + + if (!editorRect0 || !editorRect1) { + throw new Error(); + } + + await page.mouse.move( + editorRect1.x + 5, + editorRect1.y + editorRect1.height / 2 + ); + + await page.mouse.move( + editorRect1.x - 10, + editorRect1.y + editorRect1.height / 2 + ); + await page.mouse.down(); + await page.mouse.up(); + + const blockSelections = page + .locator('affine-block-selection') + .locator('visible=true'); + await expect(blockSelections).toHaveCount(1); + + let selectedBlockRect = await blockSelections.nth(0).boundingBox(); + + if (!selectedBlockRect) { + throw new Error(); + } + + expect(editorRect1).toEqual(selectedBlockRect); + + await page.mouse.move( + editorRect0.x - 10, + editorRect0.y + editorRect0.height / 2 + ); + await page.mouse.down(); + await page.mouse.up(); + + await expect(blockSelections).toHaveCount(1); + + selectedBlockRect = await blockSelections.nth(0).boundingBox(); + + if (!selectedBlockRect) { + throw new Error(); + } + + expect(editorRect0).toEqual(selectedBlockRect); + } +); + +test.fixme( + 'should support moving blocks from multiple notes', + async ({ page }) => { + await enterPlaygroundRoom(page); + await page.evaluate(() => { + const { doc } = window; + + const rootId = doc.addBlock('affine:page', { + title: new doc.Text(), + }); + doc.addBlock('affine:surface', {}, rootId); + + ['123', '456', '789', '987', '654', '321'].forEach(text => { + const noteId = doc.addBlock('affine:note', {}, rootId); + doc.addBlock( + 'affine:paragraph', + { + text: new doc.Text(text), + }, + noteId + ); + }); + + doc.resetHistory(); + }); + + await dragBetweenIndices( + page, + [1, 0], + [2, 3], + { x: -60, y: 0 }, + { x: 80, y: 0 }, + { + steps: 50, + } + ); + + const blockSelections = page + .locator('affine-block-selection') + .locator('visible=true'); + await expect(blockSelections).toHaveCount(2); + + const editorHost = getEditorHostLocator(page); + const editors = editorHost.locator('rich-text'); + const editorRect1 = await editors.nth(1).boundingBox(); + const editorRect3 = await editors.nth(3).boundingBox(); + if (!editorRect1 || !editorRect3) { + throw new Error(); + } + + await dragBetweenCoords( + page, + { + x: editorRect1.x - 10, + y: editorRect1.y + editorRect1.height / 2, + }, + { + x: editorRect3.x + 10, + y: editorRect3.y + editorRect3.height / 2 + 10, + }, + { + steps: 50, + } + ); + + await assertRichTexts(page, ['123', '987', '456', '789', '654', '321']); + await expect(blockSelections).toHaveCount(2); + + await dragBetweenIndices( + page, + [5, 0], + [4, 3], + { x: -60, y: 0 }, + { x: 80, y: 0 }, + { + steps: 50, + } + ); + + const editorRect0 = await editors.nth(0).boundingBox(); + const editorRect5 = await editors.nth(5).boundingBox(); + if (!editorRect0 || !editorRect5) { + throw new Error(); + } + + await dragBetweenCoords( + page, + { + x: editorRect5.x - 10, + y: editorRect5.y + editorRect5.height / 2, + }, + { + x: editorRect0.x + 10, + y: editorRect0.y + editorRect0.height / 2 - 5, + }, + { + steps: 50, + } + ); + + await assertRichTexts(page, ['654', '321', '123', '987', '456', '789']); + await expect(blockSelections).toHaveCount(2); + } +); + +test('drag handle should show on right block when scroll viewport', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initParagraphsByCount(page, 30); + + await page.mouse.wheel(0, 200); + + const editorHost = getEditorHostLocator(page); + const editors = editorHost.locator('rich-text'); + const blockRect28 = await editors.nth(28).boundingBox(); + if (!blockRect28) { + throw new Error(); + } + + await page.mouse.move(blockRect28.x + 10, blockRect28.y + 10); + const dragHandle = page.locator('.affine-drag-handle-container'); + await expect(dragHandle).toBeVisible(); + + await page.mouse.move( + blockRect28.x - 10, + blockRect28.y + blockRect28.height / 2 + ); + await page.mouse.down(); + await page.mouse.up(); + + const blockSelections = page + .locator('affine-block-selection') + .locator('visible=true'); + await expect(blockSelections).toHaveCount(1); + + const selectedBlockRect = await blockSelections.nth(0).boundingBox(); + + if (!selectedBlockRect) { + throw new Error(); + } + + expect(blockRect28).toEqual(selectedBlockRect); +}); diff --git a/blocksuite/tests-legacy/edgeless/align.spec.ts b/blocksuite/tests-legacy/edgeless/align.spec.ts new file mode 100644 index 0000000000000..45b9a9936d80b --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/align.spec.ts @@ -0,0 +1,435 @@ +import { expect } from '@playwright/test'; + +import { + addBasicBrushElement, + createConnectorElement, + createFrameElement, + createNote, + createShapeElement, + setEdgelessTool, + Shape, + toViewCoord, + triggerComponentToolbarAction, +} from '../utils/actions/edgeless.js'; +import { + clickView, + edgelessCommonSetup as commonSetup, + selectAllByKeyboard, + type, + waitNextFrame, +} from '../utils/actions/index.js'; +import { + assertEdgelessSelectedModelRect, + getSelectedRect, +} from '../utils/asserts.js'; +import { test } from '../utils/playwright.js'; + +test.describe('auto arrange align', () => { + test('arrange shapes', async ({ page }) => { + await commonSetup(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Diamond); + await createShapeElement(page, [100, -100], [300, 100], Shape.Ellipse); + await createShapeElement(page, [200, 300], [300, 400], Shape.Square); + await createShapeElement(page, [400, 100], [500, 200], Shape.Triangle); + await createShapeElement( + page, + [0, 200], + [100, 300], + Shape['Rounded rectangle'] + ); + + await page.mouse.click(0, 0); + await selectAllByKeyboard(page); + await assertEdgelessSelectedModelRect(page, [0, -100, 500, 500]); + + // arrange + await triggerComponentToolbarAction(page, 'autoArrange'); + await waitNextFrame(page, 200); + await assertEdgelessSelectedModelRect(page, [0, 0, 560, 320]); + }); + + test('arrange rotated shapes', async ({ page }) => { + await commonSetup(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Ellipse); + await createShapeElement(page, [100, 100], [200, 200], Shape.Square); + + const point = await toViewCoord(page, [100, 100]); + await page.mouse.click(point[0] + 50, point[1] + 50); + await page.mouse.move(point[0] - 5, point[1] - 5); + await page.mouse.down(); + await page.mouse.move(point[0] - 5, point[1] + 45); + await page.mouse.up(); + + await selectAllByKeyboard(page); + await assertEdgelessSelectedModelRect(page, [0, 0, 220, 220]); + + // arrange + await triggerComponentToolbarAction(page, 'autoArrange'); + await waitNextFrame(page, 200); + await assertEdgelessSelectedModelRect(page, [0, 0, 261, 141]); + }); + + test('arrange connected shapes', async ({ page }) => { + await commonSetup(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await createShapeElement(page, [100, 100], [200, 200], Shape.Ellipse); + await createConnectorElement(page, [50, 100], [150, 100]); + + await selectAllByKeyboard(page); + await assertEdgelessSelectedModelRect(page, [0, 0, 200, 200]); + + // arrange + await triggerComponentToolbarAction(page, 'autoArrange'); + await waitNextFrame(page, 200); + await assertEdgelessSelectedModelRect(page, [0, -21, 220, 141.4]); + }); + + test('arrange connector', async ({ page }) => { + await commonSetup(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await createConnectorElement(page, [200, 200], [300, 200]); + + await selectAllByKeyboard(page); + await assertEdgelessSelectedModelRect(page, [0, 0, 300, 200]); + + // arrange + await triggerComponentToolbarAction(page, 'autoArrange'); + await waitNextFrame(page, 200); + await assertEdgelessSelectedModelRect(page, [0, 0, 220, 100]); + }); + + test('arrange edgeless text', async ({ page }) => { + await commonSetup(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + + const point = await toViewCoord(page, [200, -100]); + await setEdgelessTool(page, 'default'); + await page.mouse.dblclick(point[0], point[1], { + delay: 100, + }); + await waitNextFrame(page); + await type(page, 'a'); + await page.mouse.click(0, 0); + + await selectAllByKeyboard(page); + await assertEdgelessSelectedModelRect(page, [0, -125, 225, 225]); + + // arrange + await triggerComponentToolbarAction(page, 'autoArrange'); + await waitNextFrame(page, 200); + await assertEdgelessSelectedModelRect(page, [0, 0, 170, 100]); + }); + + test('arrange note', async ({ page }) => { + await commonSetup(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await createNote(page, [200, 200], 'Hello World'); + await page.mouse.click(0, 0); + + await selectAllByKeyboard(page); + await assertEdgelessSelectedModelRect(page, [0, 0, 668, 252]); + + // arrange + await triggerComponentToolbarAction(page, 'autoArrange'); + await waitNextFrame(page, 200); + await assertEdgelessSelectedModelRect(page, [0, 0, 618, 100]); + }); + + test('arrange group', async ({ page }) => { + await commonSetup(page); + await createShapeElement(page, [200, 300], [300, 400], Shape.Square); + await createShapeElement(page, [400, 100], [500, 200], Shape.Triangle); + await selectAllByKeyboard(page); + await triggerComponentToolbarAction(page, 'addGroup'); + + await createShapeElement(page, [0, 0], [100, 100], Shape.Diamond); + await selectAllByKeyboard(page); + await assertEdgelessSelectedModelRect(page, [0, 0, 500, 400]); + + // arrange + await triggerComponentToolbarAction(page, 'autoArrange'); + await waitNextFrame(page, 200); + await assertEdgelessSelectedModelRect(page, [0, 0, 420, 300]); + }); + + test('arrange frame', async ({ page }) => { + await commonSetup(page); + await createShapeElement(page, [200, 300], [300, 400], Shape.Square); + await createShapeElement(page, [400, 100], [500, 200], Shape.Triangle); + await selectAllByKeyboard(page); + await createFrameElement(page, [150, 50], [550, 450]); + + await createShapeElement(page, [0, 0], [100, 100], Shape.Diamond); + + await page.mouse.click(0, 0); + await page.mouse.move(75, 395); + await page.mouse.down(); + await page.mouse.move(900, 900); + await page.mouse.up(); + await assertEdgelessSelectedModelRect(page, [0, 0, 550, 450]); + + // arrange + await triggerComponentToolbarAction(page, 'autoArrange'); + await waitNextFrame(page, 200); + await assertEdgelessSelectedModelRect(page, [0, 0, 520, 400]); + }); + + // TODO mindmap size different on CI + test('arrange mindmap', async ({ page }) => { + await commonSetup(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Diamond); + await page.keyboard.press('m'); + await clickView(page, [500, 200]); + + await selectAllByKeyboard(page); + const box1 = await getSelectedRect(page); + expect(box1.width).toBeGreaterThan(700); + expect(box1.height).toBeGreaterThan(300); + + // arrange + await triggerComponentToolbarAction(page, 'autoArrange'); + await waitNextFrame(page, 200); + const box2 = await getSelectedRect(page); + expect(box2.width).toBeLessThan(550); + expect(box2.height).toBeLessThan(210); + }); + + test('arrange shape, note, connector, brush and edgeless text', async ({ + page, + }) => { + await commonSetup(page); + // shape + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await createShapeElement(page, [150, 150], [300, 300], Shape.Ellipse); + //note + await createNote(page, [200, 100], 'Hello World'); + // connector + await createConnectorElement(page, [200, -200], [400, -100]); + // brush + const start = { x: 400, y: 400 }; + const end = { x: 480, y: 480 }; + await addBasicBrushElement(page, start, end); + // edgeless text + const point = await toViewCoord(page, [-100, -100]); + await setEdgelessTool(page, 'default'); + await page.mouse.dblclick(point[0], point[1], { + delay: 100, + }); + await waitNextFrame(page); + await type(page, 'edgeless text'); + + await page.mouse.click(0, 0); + await selectAllByKeyboard(page); + + await assertEdgelessSelectedModelRect(page, [-125, -200, 793, 500]); + // arrange + await triggerComponentToolbarAction(page, 'autoArrange'); + await waitNextFrame(page, 200); + await assertEdgelessSelectedModelRect(page, [-125, -125, 668, 270]); + }); +}); + +test.describe('auto resize align', () => { + test('resize and arrange shapes', async ({ page }) => { + await commonSetup(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Diamond); + await createShapeElement(page, [100, -100], [300, 100], Shape.Ellipse); + await createShapeElement(page, [200, 300], [300, 400], Shape.Square); + await createShapeElement(page, [400, 100], [500, 200], Shape.Triangle); + await createShapeElement( + page, + [0, 200], + [100, 300], + Shape['Rounded rectangle'] + ); + + await page.mouse.click(0, 0); + await selectAllByKeyboard(page); + + await assertEdgelessSelectedModelRect(page, [0, -100, 500, 500]); + // arrange + await triggerComponentToolbarAction(page, 'autoResize'); + await waitNextFrame(page, 200); + await assertEdgelessSelectedModelRect(page, [0, 0, 860, 420]); + }); + + test('resize and arrange rotated shapes', async ({ page }) => { + await commonSetup(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Ellipse); + await createShapeElement(page, [100, 100], [200, 200], Shape.Square); + + const point = await toViewCoord(page, [100, 100]); + await page.mouse.click(point[0] + 50, point[1] + 50); + await page.mouse.move(point[0] - 5, point[1] - 5); + await page.mouse.down(); + await page.mouse.move(point[0] - 5, point[1] + 45); + await page.mouse.up(); + + await selectAllByKeyboard(page); + await assertEdgelessSelectedModelRect(page, [0, 0, 220, 220]); + + // arrange + await triggerComponentToolbarAction(page, 'autoResize'); + await waitNextFrame(page, 200); + await assertEdgelessSelectedModelRect(page, [0, 0, 420, 200]); + }); + + test('resize and arrange connected shapes', async ({ page }) => { + await commonSetup(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await createShapeElement(page, [100, 100], [200, 200], Shape.Ellipse); + await createConnectorElement(page, [50, 100], [150, 100]); + + await selectAllByKeyboard(page); + await assertEdgelessSelectedModelRect(page, [0, 0, 200, 200]); + + // arrange + await triggerComponentToolbarAction(page, 'autoResize'); + await waitNextFrame(page, 200); + await assertEdgelessSelectedModelRect(page, [0, -16, 420, 232]); + }); + + test('resize and arrange connector', async ({ page }) => { + await commonSetup(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await createConnectorElement(page, [200, 200], [300, 200]); + + await selectAllByKeyboard(page); + await assertEdgelessSelectedModelRect(page, [0, 0, 300, 200]); + + // arrange + await triggerComponentToolbarAction(page, 'autoResize'); + await waitNextFrame(page, 200); + await assertEdgelessSelectedModelRect(page, [0, 0, 320, 200]); + }); + + test('resize and arrange edgeless text', async ({ page }) => { + await commonSetup(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + + const point = await toViewCoord(page, [200, -100]); + await setEdgelessTool(page, 'default'); + await page.mouse.dblclick(point[0], point[1], { + delay: 100, + }); + await waitNextFrame(page); + await type(page, 'a'); + await page.mouse.click(0, 0); + + await selectAllByKeyboard(page); + await assertEdgelessSelectedModelRect(page, [0, -125, 225, 225]); + + // arrange + await triggerComponentToolbarAction(page, 'autoResize'); + await waitNextFrame(page, 200); + await assertEdgelessSelectedModelRect(page, [0, 0, 604.6, 200]); + }); + + test('resize and arrange note', async ({ page }) => { + await commonSetup(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await createNote(page, [200, 200], 'Hello World'); + await page.mouse.click(0, 0); + + await selectAllByKeyboard(page); + await assertEdgelessSelectedModelRect(page, [0, 0, 668, 252]); + + // arrange + await triggerComponentToolbarAction(page, 'autoResize'); + await waitNextFrame(page, 200); + await assertEdgelessSelectedModelRect(page, [0, 0, 1302.5, 200]); + }); + + test('resize and arrange group', async ({ page }) => { + await commonSetup(page); + await createShapeElement(page, [200, 300], [300, 400], Shape.Square); + await createShapeElement(page, [400, 100], [500, 200], Shape.Triangle); + await selectAllByKeyboard(page); + await triggerComponentToolbarAction(page, 'addGroup'); + + await createShapeElement(page, [0, 0], [100, 100], Shape.Diamond); + await selectAllByKeyboard(page); + await assertEdgelessSelectedModelRect(page, [0, 0, 500, 400]); + + // arrange + await triggerComponentToolbarAction(page, 'autoResize'); + await waitNextFrame(page, 200); + await assertEdgelessSelectedModelRect(page, [0, 0, 420, 200]); + }); + + test('resize and arrange frame', async ({ page }) => { + await commonSetup(page); + await createShapeElement(page, [200, 300], [300, 400], Shape.Square); + await createShapeElement(page, [400, 100], [500, 200], Shape.Triangle); + await selectAllByKeyboard(page); + await createFrameElement(page, [150, 50], [550, 450]); + + await createShapeElement(page, [0, 0], [100, 100], Shape.Diamond); + + await page.mouse.click(0, 0); + await page.mouse.move(75, 395); + await page.mouse.down(); + await page.mouse.move(900, 900); + await page.mouse.up(); + await assertEdgelessSelectedModelRect(page, [0, 0, 550, 450]); + + // arrange + await triggerComponentToolbarAction(page, 'autoResize'); + await waitNextFrame(page, 200); + await assertEdgelessSelectedModelRect(page, [0, 0, 420, 200]); + }); + + // TODO mindmap size different on CI + test('resize and arrange mindmap', async ({ page }) => { + await commonSetup(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Diamond); + await page.keyboard.press('m'); + await clickView(page, [500, 200]); + + await selectAllByKeyboard(page); + const box1 = await getSelectedRect(page); + expect(box1.width).toBeGreaterThan(700); + expect(box1.height).toBeGreaterThan(300); + + // arrange + await triggerComponentToolbarAction(page, 'autoResize'); + await waitNextFrame(page, 200); + const box2 = await getSelectedRect(page); + expect(box2.width).toBeLessThan(650); + expect(box2.height).toBeLessThan(210); + }); + + test('resize and arrange shape, note, connector, brush and text', async ({ + page, + }) => { + await commonSetup(page); + // shape + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await createShapeElement(page, [150, 150], [300, 300], Shape.Ellipse); + //note + await createNote(page, [200, 100], 'Hello World'); + // connector + await createConnectorElement(page, [200, -200], [400, -100]); + // brush + const start = { x: 400, y: 400 }; + const end = { x: 480, y: 480 }; + await addBasicBrushElement(page, start, end); + // edgeless text + const point = await toViewCoord(page, [-100, -100]); + await setEdgelessTool(page, 'default'); + await page.mouse.dblclick(point[0], point[1], { + delay: 100, + }); + await waitNextFrame(page); + await type(page, 'edgeless text'); + + await page.mouse.click(0, 0); + await selectAllByKeyboard(page); + + await assertEdgelessSelectedModelRect(page, [-125, -200, 793, 500]); + // arrange + await triggerComponentToolbarAction(page, 'autoResize'); + await waitNextFrame(page, 200); + await assertEdgelessSelectedModelRect(page, [0, 0, 1421.5, 420]); + }); +}); diff --git a/blocksuite/tests-legacy/edgeless/auto-complete.spec.ts b/blocksuite/tests-legacy/edgeless/auto-complete.spec.ts new file mode 100644 index 0000000000000..253cc60478a3f --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/auto-complete.spec.ts @@ -0,0 +1,244 @@ +import { DEFAULT_NOTE_BACKGROUND_COLOR } from '@blocksuite/affine-model'; +import { expect, type Page } from '@playwright/test'; + +import { clickView, moveView } from '../utils/actions/click.js'; +import { dragBetweenCoords } from '../utils/actions/drag.js'; +import { + addNote, + changeEdgelessNoteBackground, + changeShapeFillColor, + changeShapeStrokeColor, + createShapeElement, + deleteAll, + dragBetweenViewCoords, + edgelessCommonSetup, + getEdgelessSelectedRectModel, + Shape, + switchEditorMode, + toViewCoord, + triggerComponentToolbarAction, +} from '../utils/actions/edgeless.js'; +import { + enterPlaygroundRoom, + initEmptyEdgelessState, + waitForInlineEditorStateUpdated, + waitNextFrame, +} from '../utils/actions/misc.js'; +import { + assertConnectorStrokeColor, + assertEdgelessCanvasText, + assertEdgelessNoteBackground, + assertExists, + assertRichTexts, + assertSelectedBound, +} from '../utils/asserts.js'; +import { test } from '../utils/playwright.js'; + +function getAutoCompletePanelButton(page: Page, type: string) { + return page + .locator('.auto-complete-panel-container') + .locator('edgeless-tool-icon-button') + .filter({ hasText: `${type}` }); +} + +test.describe('auto-complete', () => { + test.describe('click on auto-complete button', () => { + test('click on right auto-complete button', async ({ page }) => { + await edgelessCommonSetup(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await assertSelectedBound(page, [0, 0, 100, 100]); + await clickView(page, [120, 50]); + await assertSelectedBound(page, [200, 0, 100, 100]); + }); + test('click on bottom auto-complete button', async ({ page }) => { + await edgelessCommonSetup(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await assertSelectedBound(page, [0, 0, 100, 100]); + await clickView(page, [50, 120]); + await assertSelectedBound(page, [0, 200, 100, 100]); + }); + test('click on left auto-complete button', async ({ page }) => { + await edgelessCommonSetup(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await assertSelectedBound(page, [0, 0, 100, 100]); + await clickView(page, [-20, 50]); + await assertSelectedBound(page, [-200, 0, 100, 100]); + }); + test('click on top auto-complete button', async ({ page }) => { + await edgelessCommonSetup(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await assertSelectedBound(page, [0, 0, 100, 100]); + await clickView(page, [50, -20]); + await assertSelectedBound(page, [0, -200, 100, 100]); + }); + + test('click on note auto-complete button', async ({ page }) => { + await edgelessCommonSetup(page); + await addNote(page, 'note', 100, 100); + await page.mouse.click(600, 50); + await page.mouse.click(300, 50); + await page.mouse.click(150, 120); + const rect = await getEdgelessSelectedRectModel(page); + await moveView(page, [rect[0] + rect[2] + 30, rect[1] + rect[3] / 2]); + await clickView(page, [rect[0] + rect[2] + 30, rect[1] + rect[3] / 2]); + const newRect = await getEdgelessSelectedRectModel(page); + expect(rect[0]).not.toEqual(newRect[0]); + expect(rect[1]).toEqual(newRect[1]); + expect(rect[2]).toEqual(newRect[2]); + expect(rect[3]).toEqual(newRect[3]); + }); + }); + + test.describe('drag on auto-complete button', () => { + test('drag on right auto-complete button to add shape', async ({ + page, + }) => { + await edgelessCommonSetup(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await assertSelectedBound(page, [0, 0, 100, 100]); + await dragBetweenViewCoords(page, [120, 50], [200, 0]); + + const ellipseButton = getAutoCompletePanelButton(page, 'ellipse'); + await expect(ellipseButton).toBeVisible(); + await ellipseButton.click(); + + await assertSelectedBound(page, [200, -50, 100, 100]); + }); + + test('drag on right auto-complete button to add canvas text', async ({ + page, + }) => { + await enterPlaygroundRoom(page, { + flags: { + enable_edgeless_text: false, + }, + }); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + await deleteAll(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await assertSelectedBound(page, [0, 0, 100, 100]); + await dragBetweenViewCoords(page, [120, 50], [200, 0]); + + const canvasTextButton = getAutoCompletePanelButton(page, 'text'); + await expect(canvasTextButton).toBeVisible(); + await canvasTextButton.click(); + + await waitForInlineEditorStateUpdated(page); + await waitNextFrame(page); + await page.keyboard.type('hello'); + await assertEdgelessCanvasText(page, 'hello'); + }); + + test('drag on right auto-complete button to add note', async ({ page }) => { + await edgelessCommonSetup(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await assertSelectedBound(page, [0, 0, 100, 100]); + await triggerComponentToolbarAction(page, 'changeShapeStrokeColor'); + const lineColor = '--affine-palette-line-red'; + await changeShapeStrokeColor(page, lineColor); + await triggerComponentToolbarAction(page, 'changeShapeFillColor'); + const color = '--affine-palette-shape-green'; + await changeShapeFillColor(page, color); + await dragBetweenViewCoords(page, [120, 50], [200, 0]); + + const noteButton = getAutoCompletePanelButton(page, 'note'); + await expect(noteButton).toBeVisible(); + await noteButton.click(); + await waitNextFrame(page); + + const edgelessNote = page.locator('affine-edgeless-note'); + + expect(await edgelessNote.count()).toBe(1); + const [x, y] = await toViewCoord(page, [240, 20]); + await page.mouse.click(x, y); + await page.keyboard.type('hello'); + await waitNextFrame(page); + await assertRichTexts(page, ['hello']); + + const noteId = await page.evaluate(() => { + const note = document.body.querySelector('affine-edgeless-note'); + return note?.getAttribute('data-block-id'); + }); + assertExists(noteId); + await assertEdgelessNoteBackground( + page, + noteId, + DEFAULT_NOTE_BACKGROUND_COLOR + ); + + const rect = await edgelessNote.boundingBox(); + assertExists(rect); + + // blur note block + await page.mouse.click(rect.x + rect.width / 2, rect.y + rect.height * 3); + await waitNextFrame(page); + + // select connector + await dragBetweenViewCoords(page, [140, 50], [160, 0]); + await waitNextFrame(page); + await assertConnectorStrokeColor(page, lineColor); + + // select note block + await page.mouse.click(rect.x + rect.width / 2, rect.y + rect.height / 2); + await waitNextFrame(page); + + await triggerComponentToolbarAction(page, 'changeNoteColor'); + const noteColor = '--affine-note-background-red'; + await changeEdgelessNoteBackground(page, noteColor); + + // move to arrow icon + await page.mouse.move( + rect.x + rect.width + 20, + rect.y + rect.height / 2, + { steps: 5 } + ); + await waitNextFrame(page); + + // drag arrow + await dragBetweenCoords( + page, + { + x: rect.x + rect.width + 20, + y: rect.y + rect.height / 2, + }, + { + x: rect.x + rect.width + 20 + 50, + y: rect.y + rect.height / 2 + 50, + } + ); + + // `Add a same object` button has the same type. + const noteButton2 = getAutoCompletePanelButton(page, 'note').nth(0); + await expect(noteButton2).toBeVisible(); + await noteButton2.click(); + await waitNextFrame(page); + + const noteId2 = await page.evaluate(() => { + const note = document.body.querySelectorAll('affine-edgeless-note')[1]; + return note?.getAttribute('data-block-id'); + }); + assertExists(noteId2); + await assertEdgelessNoteBackground(page, noteId, noteColor); + + expect(await edgelessNote.count()).toBe(2); + }); + + test('drag on right auto-complete button to add frame', async ({ + page, + }) => { + await edgelessCommonSetup(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await assertSelectedBound(page, [0, 0, 100, 100]); + await dragBetweenViewCoords(page, [120, 50], [200, 0]); + + expect(await page.locator('.affine-frame-container').count()).toBe(0); + + const frameButton = getAutoCompletePanelButton(page, 'frame'); + await expect(frameButton).toBeVisible(); + await frameButton.click(); + + expect(await page.locator('.affine-frame-container').count()).toBe(1); + }); + }); +}); diff --git a/blocksuite/tests-legacy/edgeless/auto-connect.spec.ts b/blocksuite/tests-legacy/edgeless/auto-connect.spec.ts new file mode 100644 index 0000000000000..23187c196b858 --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/auto-connect.spec.ts @@ -0,0 +1,180 @@ +import { NoteDisplayMode } from '@blocksuite/affine-model'; +import { assertExists } from '@blocksuite/global/utils'; +import { expect, type Page } from '@playwright/test'; + +import { + addNote, + changeNoteDisplayModeWithId, + dragBetweenViewCoords, + edgelessCommonSetup, + getNoteBoundBoxInEdgeless, + getSelectedBound, + selectNoteInEdgeless, + zoomResetByKeyboard, +} from '../utils/actions/edgeless.js'; +import { assertSelectedBound } from '../utils/asserts.js'; +import { test } from '../utils/playwright.js'; + +test.describe('auto-connect', () => { + async function init(page: Page) { + await edgelessCommonSetup(page); + } + test('navigator', async ({ page }) => { + await init(page); + const id1 = await addNote(page, 'page1', 200, 300); + const id2 = await addNote(page, 'page2', 300, 500); + const id3 = await addNote(page, 'page3', 400, 700); + + await page.mouse.click(200, 50); + // Notes added in edgeless mode only visible in edgeless mode + // To use index label navigator, we need to change display mode to PageAndEdgeless + await changeNoteDisplayModeWithId( + page, + id1, + NoteDisplayMode.DocAndEdgeless + ); + await changeNoteDisplayModeWithId( + page, + id2, + NoteDisplayMode.DocAndEdgeless + ); + await changeNoteDisplayModeWithId( + page, + id3, + NoteDisplayMode.DocAndEdgeless + ); + + await selectNoteInEdgeless(page, id1); + const bound = await getSelectedBound(page, 0); + await page.locator('.page-visible-index-label').nth(0).click(); + await assertSelectedBound(page, bound); + + await page.locator('.edgeless-auto-connect-next-button').click(); + bound[0] += 100; + bound[1] += 200; + await assertSelectedBound(page, bound); + + await page.locator('.edgeless-auto-connect-next-button').click(); + bound[0] += 100; + bound[1] += 200; + await assertSelectedBound(page, bound); + }); + + test('should display index label when select note', async ({ page }) => { + await init(page); + const id1 = await addNote(page, 'page1', 200, 300); + const id2 = await addNote(page, 'page2', 300, 500); + + await page.mouse.click(200, 50); + + await changeNoteDisplayModeWithId( + page, + id1, + NoteDisplayMode.DocAndEdgeless + ); + + await selectNoteInEdgeless(page, id2); + const edgelessOnlyIndexLabel = page.locator('.edgeless-only-index-label'); + await expect(edgelessOnlyIndexLabel).toBeVisible(); + await expect(edgelessOnlyIndexLabel).toHaveCount(1); + + await selectNoteInEdgeless(page, id1); + const pageVisibleIndexLabel = page.locator('.page-visible-index-label'); + await expect(pageVisibleIndexLabel).toBeVisible(); + await expect(pageVisibleIndexLabel).toHaveCount(1); + }); + + test('should hide index label when dragging note', async ({ page }) => { + await init(page); + const id1 = await addNote(page, 'page1', 200, 300); + + await page.mouse.click(200, 50); + + await changeNoteDisplayModeWithId( + page, + id1, + NoteDisplayMode.DocAndEdgeless + ); + + const pageVisibleIndexLabel = page.locator('.page-visible-index-label'); + await expect(pageVisibleIndexLabel).toBeVisible(); + await expect(pageVisibleIndexLabel).toHaveCount(1); + + const bound = await getNoteBoundBoxInEdgeless(page, id1); + await page.mouse.move( + bound.x + bound.width / 2, + bound.y + bound.height / 2 + ); + await page.mouse.down(); + await page.mouse.move( + bound.x + bound.width * 2, + bound.y + bound.height * 2 + ); + + await expect(pageVisibleIndexLabel).not.toBeVisible(); + + await page.mouse.up(); + await expect(pageVisibleIndexLabel).toBeVisible(); + }); + + test('should update index label position after dragging', async ({ + page, + }) => { + await init(page); + await zoomResetByKeyboard(page); + + const id1 = await addNote(page, 'page1', 200, 300); + const id2 = await addNote(page, 'page2', 300, 500); + + await page.mouse.click(200, 50); + + await changeNoteDisplayModeWithId( + page, + id1, + NoteDisplayMode.DocAndEdgeless + ); + + await selectNoteInEdgeless(page, id2); + const edgelessOnlyIndexLabel = page.locator('.edgeless-only-index-label'); + await expect(edgelessOnlyIndexLabel).toBeVisible(); + + // check initial index label position + const noteBound = await getNoteBoundBoxInEdgeless(page, id2); + const edgelessOnlyIndexLabelBound = + await edgelessOnlyIndexLabel.boundingBox(); + assertExists(edgelessOnlyIndexLabelBound); + const border = 1; + const offset = 16; + expect(edgelessOnlyIndexLabelBound.x).toBeCloseTo( + noteBound.x + + noteBound.width / 2 - + edgelessOnlyIndexLabelBound.width / 2 + + border + ); + expect(edgelessOnlyIndexLabelBound.y).toBeCloseTo( + noteBound.y + noteBound.height + offset + ); + + // move note + await dragBetweenViewCoords( + page, + [noteBound.x + noteBound.width / 2, noteBound.y + noteBound.height / 2], + [noteBound.x + noteBound.width, noteBound.y + noteBound.height] + ); + + // check new index label position + const newNoteBound = await getNoteBoundBoxInEdgeless(page, id2); + const newEdgelessOnlyIndexLabelBound = + await edgelessOnlyIndexLabel.boundingBox(); + assertExists(newEdgelessOnlyIndexLabelBound); + expect(newEdgelessOnlyIndexLabelBound.x).toBeCloseTo( + newNoteBound.x + + newNoteBound.width / 2 - + newEdgelessOnlyIndexLabelBound.width / 2 + + border + ); + expect(newEdgelessOnlyIndexLabelBound.y).toBeCloseTo( + newNoteBound.y + newNoteBound.height + offset + ); + }); +}); diff --git a/blocksuite/tests-legacy/edgeless/basic.spec.ts b/blocksuite/tests-legacy/edgeless/basic.spec.ts new file mode 100644 index 0000000000000..392a6cb059a9b --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/basic.spec.ts @@ -0,0 +1,358 @@ +import { + DEFAULT_NOTE_HEIGHT, + DEFAULT_NOTE_WIDTH, +} from '@blocksuite/affine-model'; +import { assertExists } from '@blocksuite/global/utils'; +import { expect } from '@playwright/test'; + +import { + createShapeElement, + decreaseZoomLevel, + deleteAll, + edgelessCommonSetup, + increaseZoomLevel, + locatorEdgelessComponentToolButton, + multiTouchDown, + multiTouchMove, + multiTouchUp, + optionMouseDrag, + Shape, + shiftClickView, + switchEditorMode, + toggleEditorReadonly, + ZOOM_BAR_RESPONSIVE_SCREEN_WIDTH, + zoomByMouseWheel, + zoomResetByKeyboard, +} from '../utils/actions/edgeless.js'; +import { + addBasicBrushElement, + addBasicRectShapeElement, + captureHistory, + clickView, + enterPlaygroundRoom, + focusRichText, + initEmptyEdgelessState, + redoByClick, + type, + undoByClick, + waitNextFrame, +} from '../utils/actions/index.js'; +import { + assertEdgelessNonSelectedRect, + assertEdgelessSelectedModelRect, + assertEdgelessSelectedRect, + assertNoteXYWH, + assertRichTextInlineRange, + assertRichTexts, + assertSelectedBound, + assertZoomLevel, +} from '../utils/asserts.js'; +import { test } from '../utils/playwright.js'; + +const CENTER_X = 450; +const CENTER_Y = 450; + +test('switch to edgeless mode', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await focusRichText(page); + await type(page, 'hello'); + await assertRichTexts(page, ['hello']); + await assertRichTextInlineRange(page, 0, 5, 0); + + await switchEditorMode(page); + const locator = page.locator('affine-edgeless-root gfx-viewport'); + await expect(locator).toHaveCount(1); + await assertRichTexts(page, ['hello']); + await waitNextFrame(page); + + // FIXME: got very flaky result on cursor keeping + // await assertNativeSelectionRangeCount(page, 1); +}); + +test('can zoom viewport', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + + await switchEditorMode(page); + await zoomResetByKeyboard(page); + + await assertNoteXYWH(page, [0, 0, DEFAULT_NOTE_WIDTH, DEFAULT_NOTE_HEIGHT]); + + await page.mouse.click(CENTER_X, CENTER_Y); + const original = [0, 0, DEFAULT_NOTE_WIDTH, DEFAULT_NOTE_HEIGHT]; + await assertEdgelessSelectedModelRect(page, original); + await assertZoomLevel(page, 100); + + await decreaseZoomLevel(page); + await assertZoomLevel(page, 75); + await decreaseZoomLevel(page); + await assertZoomLevel(page, 50); + + const zoomed = [0, 0, original[2] * 0.5, original[3] * 0.5]; + await assertEdgelessSelectedModelRect(page, zoomed); + + await increaseZoomLevel(page); + await assertZoomLevel(page, 75); + await increaseZoomLevel(page); + await assertZoomLevel(page, 100); + await assertEdgelessSelectedModelRect(page, original); +}); + +test('zoom by mouse', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + + await switchEditorMode(page); + await zoomResetByKeyboard(page); + await assertZoomLevel(page, 100); + + await assertNoteXYWH(page, [0, 0, DEFAULT_NOTE_WIDTH, DEFAULT_NOTE_HEIGHT]); + + await page.mouse.click(CENTER_X, CENTER_Y); + const original = [0, 0, DEFAULT_NOTE_WIDTH, DEFAULT_NOTE_HEIGHT]; + await assertEdgelessSelectedModelRect(page, original); + + await zoomByMouseWheel(page, 0, 125); + await assertZoomLevel(page, 90); + + const zoomed = [0, 0, original[2] * 0.9, original[3] * 0.9]; + await assertEdgelessSelectedModelRect(page, zoomed); +}); + +test('zoom by pinch', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + + await switchEditorMode(page); + await zoomResetByKeyboard(page); + await assertZoomLevel(page, 100); + + await assertNoteXYWH(page, [0, 0, DEFAULT_NOTE_WIDTH, DEFAULT_NOTE_HEIGHT]); + + await page.mouse.click(CENTER_X, CENTER_Y); + const original = [0, 0, DEFAULT_NOTE_WIDTH, DEFAULT_NOTE_HEIGHT]; + await assertEdgelessSelectedModelRect(page, original); + + const from = [ + { x: CENTER_X - 100, y: CENTER_Y }, + { x: CENTER_X + 100, y: CENTER_Y }, + ]; + const to = [ + { x: CENTER_X - 50, y: CENTER_Y - 35 }, + { x: CENTER_X + 50, y: CENTER_Y + 35 }, + ]; + await multiTouchDown(page, from); + await multiTouchMove(page, from, to); + await multiTouchUp(page, to); + + await assertZoomLevel(page, 50); + const zoomed = [0, 0, 0.5 * DEFAULT_NOTE_WIDTH, 46]; + await assertEdgelessSelectedModelRect(page, zoomed); +}); + +test('zoom by pinch when edgeless is readonly', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + + await switchEditorMode(page); + await zoomResetByKeyboard(page); + await assertZoomLevel(page, 100); + + await toggleEditorReadonly(page); + + const from = [ + { x: CENTER_X - 100, y: CENTER_Y }, + { x: CENTER_X + 100, y: CENTER_Y }, + ]; + const to = [ + { x: CENTER_X - 50, y: CENTER_Y - 35 }, + { x: CENTER_X + 50, y: CENTER_Y + 35 }, + ]; + await multiTouchDown(page, from); + await multiTouchMove(page, from, to); + await multiTouchUp(page, to); + + await toggleEditorReadonly(page); + await assertZoomLevel(page, 50); +}); + +test('move by pan', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + + await switchEditorMode(page); + await zoomResetByKeyboard(page); + await assertZoomLevel(page, 100); + + await assertNoteXYWH(page, [0, 0, DEFAULT_NOTE_WIDTH, DEFAULT_NOTE_HEIGHT]); + + await page.mouse.click(CENTER_X, CENTER_Y); + const original = [0, 0, DEFAULT_NOTE_WIDTH, DEFAULT_NOTE_HEIGHT]; + await assertEdgelessSelectedModelRect(page, original); + + const from = [ + { x: CENTER_X - 100, y: CENTER_Y }, + { x: CENTER_X + 100, y: CENTER_Y }, + ]; + const to = [ + { x: CENTER_X - 50, y: CENTER_Y + 50 }, + { x: CENTER_X + 150, y: CENTER_Y + 50 }, + ]; + + await multiTouchDown(page, from); + await multiTouchMove(page, from, to); + await multiTouchUp(page, to); + + const moved = [0, 0, DEFAULT_NOTE_WIDTH, DEFAULT_NOTE_HEIGHT]; + await assertEdgelessSelectedModelRect(page, moved); +}); + +test('option/alt mouse drag duplicate a new element', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + await zoomResetByKeyboard(page); + await deleteAll(page); + + const start = [0, 0]; + const end = [100, 100]; + await createShapeElement(page, start, end, Shape.Square); + await optionMouseDrag(page, [50, 50], [150, 50]); + await assertSelectedBound(page, [100, 0, 100, 100]); + + await captureHistory(page); + await undoByClick(page); + await assertSelectedBound(page, [0, 0, 100, 100]); + + await redoByClick(page); + await assertSelectedBound(page, [100, 0, 100, 100]); +}); + +test('should cancel select when the selected point is outside the current selected element', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + await zoomResetByKeyboard(page); + + const firstStart = { x: 100, y: 100 }; + const firstEnd = { x: 200, y: 200 }; + await addBasicRectShapeElement(page, firstStart, firstEnd); + + const secondStart = { x: 300, y: 300 }; + const secondEnd = { x: 400, y: 400 }; + await addBasicRectShapeElement(page, secondStart, secondEnd); + + // select the first rect + await page.mouse.click(110, 150); + await assertEdgelessSelectedRect(page, [100, 100, 100, 100]); + + // click outside the selected rect + await page.mouse.click(200, 200); + await assertEdgelessNonSelectedRect(page); +}); + +test('the tooltip of more button should be hidden when the action menu is shown', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + + const start = { x: 100, y: 100 }; + const end = { x: 200, y: 200 }; + await addBasicBrushElement(page, start, end); + + await page.mouse.click(start.x + 5, start.y + 5); + await assertEdgelessSelectedRect(page, [98, 98, 104, 104]); + + const moreButton = locatorEdgelessComponentToolButton(page, 'more'); + await expect(moreButton).toBeVisible(); + + const moreButtonBox = await moreButton.boundingBox(); + const tooltip = page.locator('.affine-tooltip'); + + assertExists(moreButtonBox); + + // need to wait for previous tooltip to be hidden + await page.waitForTimeout(100); + await page.mouse.move(moreButtonBox.x + 10, moreButtonBox.y + 10); + await expect(tooltip).toBeVisible(); + + await page.mouse.click(moreButtonBox.x + 10, moreButtonBox.y + 10); + await expect(tooltip).toBeHidden(); + + await page.mouse.click(moreButtonBox.x + 10, moreButtonBox.y + 10); + await expect(tooltip).toBeVisible(); +}); + +test('shift click multi select and de-select', async ({ page }) => { + await edgelessCommonSetup(page); + const start = [0, 0]; + const end = [100, 100]; + await createShapeElement(page, start, end, Shape.Square); + start[0] = 100; + end[0] = 200; + await createShapeElement(page, start, end, Shape.Square); + + await clickView(page, [50, 0]); + await assertEdgelessSelectedModelRect(page, [0, 0, 100, 100]); + + await shiftClickView(page, [150, 50]); + await assertEdgelessSelectedModelRect(page, [0, 0, 200, 100]); + + // we will try to write text on a shape element when we dbclick it + + await waitNextFrame(page, 500); + await shiftClickView(page, [150, 50]); + await assertEdgelessSelectedModelRect(page, [0, 0, 100, 100]); +}); + +test('Before and after switching to Edgeless, the previous zoom ratio and position when Edgeless was opened should be remembered', async ({ + page, +}) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/toeverything/blocksuite/issues/2479', + }); + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + await zoomResetByKeyboard(page); + await assertZoomLevel(page, 100); + await increaseZoomLevel(page); + await assertZoomLevel(page, 125); + await switchEditorMode(page); + await switchEditorMode(page); + await assertZoomLevel(page, 125); +}); + +test('should close zoom bar when click blank area', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + + const screenWidth = page.viewportSize()?.width; + assertExists(screenWidth); + if (screenWidth > ZOOM_BAR_RESPONSIVE_SCREEN_WIDTH) { + await page.setViewportSize({ + width: 1000, + height: 1000, + }); + } + + await zoomResetByKeyboard(page); + await assertZoomLevel(page, 100); + await increaseZoomLevel(page); + await assertZoomLevel(page, 125); + + const verticalZoomBar = '.edgeless-zoom-toolbar-container.vertical'; + const zoomBar = page.locator(verticalZoomBar); + await expect(zoomBar).toBeVisible(); + + // Click Blank Area + await page.mouse.click(10, 100); + await expect(zoomBar).toBeHidden(); +}); diff --git a/blocksuite/tests-legacy/edgeless/brush.spec.ts b/blocksuite/tests-legacy/edgeless/brush.spec.ts new file mode 100644 index 0000000000000..893dc2a1ee5c0 --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/brush.spec.ts @@ -0,0 +1,193 @@ +import { expect } from '@playwright/test'; + +import { + assertEdgelessTool, + deleteAll, + pickColorAtPoints, + selectBrushColor, + selectBrushSize, + setEdgelessTool, + switchEditorMode, + updateExistedBrushElementSize, + zoomResetByKeyboard, +} from '../utils/actions/edgeless.js'; +import { + addBasicBrushElement, + click, + dragBetweenCoords, + enterPlaygroundRoom, + initEmptyEdgelessState, + resizeElementByHandle, +} from '../utils/actions/index.js'; +import { + assertEdgelessColorSameWithHexColor, + assertEdgelessSelectedRect, + assertSameColor, +} from '../utils/asserts.js'; +import { test } from '../utils/playwright.js'; + +test('change editor mode when brush color palette opening', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + await setEdgelessTool(page, 'brush'); + + const brushMenu = page.locator('edgeless-brush-menu'); + await expect(brushMenu).toBeVisible(); + + await switchEditorMode(page); + await expect(brushMenu).toBeHidden(); +}); + +test('add brush element', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + + const start = { x: 100, y: 100 }; + const end = { x: 200, y: 200 }; + await addBasicBrushElement(page, start, end, false); + + await assertEdgelessTool(page, 'brush'); +}); + +test('resize brush element', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + + const start = { x: 100, y: 100 }; + const end = { x: 200, y: 200 }; + await addBasicBrushElement(page, start, end); + + await page.mouse.click(start.x + 5, start.y + 5); + await assertEdgelessSelectedRect(page, [98, 98, 104, 104]); + + await page.mouse.click(start.x + 5, start.y + 5); + const delta = { x: 20, y: 40 }; + await resizeElementByHandle(page, delta, 'top-left', 10); + + await page.mouse.click(start.x + 25, start.y + 45); + await assertEdgelessSelectedRect(page, [118, 138, 84, 64]); +}); + +test('add brush element with color', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + + await setEdgelessTool(page, 'brush'); + const color = '--affine-palette-line-blue'; + await selectBrushColor(page, color); + + const start = { x: 100, y: 100 }; + const end = { x: 200, y: 200 }; + await dragBetweenCoords(page, start, end, { steps: 100 }); + + const [pickedColor] = await pickColorAtPoints(page, [[110, 110]]); + + await assertEdgelessColorSameWithHexColor(page, color, pickedColor); +}); + +test('keep same color when mouse mode switched back to brush', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + await deleteAll(page); + + await setEdgelessTool(page, 'brush'); + const color = '--affine-palette-line-blue'; + await selectBrushColor(page, color); + const start = { x: 200, y: 200 }; + const end = { x: 300, y: 300 }; + await dragBetweenCoords(page, start, end, { steps: 100 }); + + await setEdgelessTool(page, 'default'); + await click(page, { x: 50, y: 50 }); + + await setEdgelessTool(page, 'brush'); + const origin = { x: 100, y: 100 }; + await dragBetweenCoords(page, origin, start, { steps: 100 }); + const [pickedColor] = await pickColorAtPoints(page, [[110, 110]]); + await assertEdgelessColorSameWithHexColor(page, color, pickedColor); +}); + +test('add brush element with different size', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + + await setEdgelessTool(page, 'brush'); + await selectBrushSize(page, 'ten'); + const color = '--affine-palette-line-blue'; + await selectBrushColor(page, color); + + const start = { x: 100, y: 100 }; + const end = { x: 200, y: 100 }; + await dragBetweenCoords(page, start, end, { steps: 100 }); + + const [topEdge, bottomEdge, nearTopEdge, nearBottomEdge] = + await pickColorAtPoints(page, [ + // Select two points on the top and bottom border of the line, + // their color should be the same as the specified color + [110, 95], + [110, 104], + // Select two points close to the upper and lower boundaries of the line, + // their color should be different from the specified color + [110, 94], + [110, 105], + ]); + + await assertEdgelessColorSameWithHexColor(page, color, topEdge); + await assertEdgelessColorSameWithHexColor(page, color, bottomEdge); + assertSameColor(nearTopEdge, '#4f90ff'); + assertSameColor(nearBottomEdge, '#4f90ff'); +}); + +test('change brush element size by component-toolbar', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + await zoomResetByKeyboard(page); + + const start = { x: 100, y: 100 }; + const end = { x: 200, y: 200 }; + await addBasicBrushElement(page, start, end); + + // wait for menu hide animation + await page.waitForTimeout(500); + + // change to line width 12 + await page.mouse.click(110, 110); + await updateExistedBrushElementSize(page, 6); + await assertEdgelessSelectedRect(page, [94, 94, 112, 112]); + + // change to line width 10 + await page.mouse.click(110, 110); + await updateExistedBrushElementSize(page, 5); + await assertEdgelessSelectedRect(page, [95, 95, 110, 110]); + + // change to line width 8 + await page.mouse.click(110, 110); + await updateExistedBrushElementSize(page, 4); + await assertEdgelessSelectedRect(page, [96, 96, 108, 108]); + + // change to line width 6 + await page.mouse.click(110, 110); + await updateExistedBrushElementSize(page, 3); + await assertEdgelessSelectedRect(page, [97, 97, 106, 106]); + + // change to line width 4 + await page.mouse.click(110, 110); + await updateExistedBrushElementSize(page, 2); + await assertEdgelessSelectedRect(page, [98, 98, 104, 104]); + + // change to line width 2 + await page.mouse.click(110, 110); + await updateExistedBrushElementSize(page, 1); + await assertEdgelessSelectedRect(page, [99, 99, 102, 102]); +}); diff --git a/blocksuite/tests-legacy/edgeless/clipboard.spec.ts b/blocksuite/tests-legacy/edgeless/clipboard.spec.ts new file mode 100644 index 0000000000000..52a6a7f3db0d3 --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/clipboard.spec.ts @@ -0,0 +1,235 @@ +import { expect } from '@playwright/test'; + +import { + createNote, + createShapeElement, + decreaseZoomLevel, + deleteAll, + getAllSortedIds, + Shape, + switchEditorMode, + toViewCoord, + triggerComponentToolbarAction, +} from '../utils/actions/edgeless.js'; +import { + copyByKeyboard, + cutByKeyboard, + edgelessCommonSetup as commonSetup, + enterPlaygroundRoom, + expectConsoleMessage, + focusTitle, + getCurrentEditorDocId, + initEmptyEdgelessState, + mockParseDocUrlService, + pasteByKeyboard, + pasteContent, + selectAllByKeyboard, + type, + waitNextFrame, +} from '../utils/actions/index.js'; +import { assertRichImage } from '../utils/asserts.js'; +import { test } from '../utils/playwright.js'; + +test.describe('mime', () => { + test('should paste svg in text/plain mime', async ({ page }) => { + expectConsoleMessage(page, 'Error: Image sourceId is missing!', 'warning'); + await commonSetup(page); + const content = { + 'text/plain': ` + + + + `, + }; + + await pasteContent(page, content); + + // wait for paste + await page.waitForTimeout(200); + await assertRichImage(page, 1); + }); + + test('should not paste bad svg', async ({ page }) => { + expectConsoleMessage(page, 'BlockSuiteError: val does not exist', 'error'); + expectConsoleMessage(page, 'Error: Image sourceId is missing!', 'warning'); + + await commonSetup(page); + const contents = [ + { + 'text/plain': ` + + + `, + }, + + { + 'text/plain': ` + + + + `, + }, + ]; + for (const content of contents) { + await pasteContent(page, content); + } + + await assertRichImage(page, 0); + }); +}); + +test.describe('frame clipboard', () => { + test('copy and paste frame with shape elements inside', async ({ page }) => { + await commonSetup(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await createNote(page, [100, -100]); + await page.mouse.click(10, 50); + + await selectAllByKeyboard(page); + await triggerComponentToolbarAction(page, 'addFrame'); + const originIds = await getAllSortedIds(page); + expect(originIds.length).toBe(3); + + await copyByKeyboard(page); + const move = await toViewCoord(page, [250, 250]); + await page.mouse.move(move[0], move[1]); + await page.mouse.click(move[0], move[1]); + await pasteByKeyboard(page, true); + await waitNextFrame(page, 500); + const sortedIds = await getAllSortedIds(page); + expect(sortedIds.length).toBe(6); + }); + + test('copy and paste frame with group elements inside', async ({ page }) => { + await commonSetup(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await createNote(page, [100, -100]); + await page.mouse.click(10, 50); + await selectAllByKeyboard(page); + await triggerComponentToolbarAction(page, 'addGroup'); + + await createShapeElement(page, [200, 0], [300, 100], Shape.Square); + await triggerComponentToolbarAction(page, 'createFrameOnMoreOption'); + const originIds = await getAllSortedIds(page); + expect(originIds.length).toBe(5); + + await selectAllByKeyboard(page); + await copyByKeyboard(page); + const move = await toViewCoord(page, [250, 250]); + await page.mouse.move(move[0], move[1]); + await page.mouse.click(move[0], move[1]); + await pasteByKeyboard(page, true); + await waitNextFrame(page, 500); + const sortedIds = await getAllSortedIds(page); + expect(sortedIds.length).toBe(10); + }); + + test('copy and paste frame with frame inside', async ({ page }) => { + await commonSetup(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await createNote(page, [100, -100]); + await page.mouse.click(10, 50); + await selectAllByKeyboard(page); + await triggerComponentToolbarAction(page, 'addFrame'); + + await decreaseZoomLevel(page); + await createShapeElement(page, [700, 0], [800, 100], Shape.Square); + await selectAllByKeyboard(page); + await triggerComponentToolbarAction(page, 'addFrame'); + + const originIds = await getAllSortedIds(page); + expect(originIds.length).toBe(5); + + await copyByKeyboard(page); + const move = await toViewCoord(page, [250, 250]); + await page.mouse.move(move[0], move[1]); + await page.mouse.click(move[0], move[1]); + await pasteByKeyboard(page, true); + await waitNextFrame(page, 500); + const sortedIds = await getAllSortedIds(page); + expect(sortedIds.length).toBe(10); + }); + + test('cut frame with shape elements inside', async ({ page }) => { + await commonSetup(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await createNote(page, [100, -100]); + await page.mouse.click(10, 50); + + await selectAllByKeyboard(page); + await triggerComponentToolbarAction(page, 'addFrame'); + const originIds = await getAllSortedIds(page); + expect(originIds.length).toBe(3); + + await cutByKeyboard(page); + const move = await toViewCoord(page, [250, 250]); + await page.mouse.move(move[0], move[1]); + await page.mouse.click(move[0], move[1]); + await pasteByKeyboard(page, true); + await waitNextFrame(page, 500); + const sortedIds = await getAllSortedIds(page); + expect(sortedIds.length).toBe(3); + }); +}); + +test.describe('pasting URLs', () => { + test('pasting github pr url', async ({ page }) => { + await commonSetup(page); + await waitNextFrame(page); + await pasteContent(page, { + 'text/plain': 'https://github.com/toeverything/blocksuite/pull/7217', + }); + + await expect( + page.locator('affine-embed-edgeless-github-block') + ).toBeVisible(); + }); + + test('pasting internal link', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await waitNextFrame(page); + await focusTitle(page); + const docId = await getCurrentEditorDocId(page); + + await type(page, 'doc title'); + + await switchEditorMode(page); + await deleteAll(page); + + await mockParseDocUrlService(page, { + 'http://workspace/doc-id': docId, + }); + + await pasteContent(page, { + 'text/plain': 'http://workspace/doc-id', + }); + + await expect( + page.locator('affine-embed-edgeless-linked-doc-block') + ).toBeVisible(); + + await expect( + page.locator('.affine-embed-linked-doc-content-title') + ).toHaveText('doc title'); + }); + + test('pasting external link', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await waitNextFrame(page); + await focusTitle(page); + + await type(page, 'doc title'); + + await switchEditorMode(page); + await deleteAll(page); + await waitNextFrame(page); + + await pasteContent(page, { + 'text/plain': 'https://affine.pro', + }); + + await expect(page.locator('bookmark-card')).toBeVisible(); + }); +}); diff --git a/blocksuite/tests-legacy/edgeless/color-picker.spec.ts b/blocksuite/tests-legacy/edgeless/color-picker.spec.ts new file mode 100644 index 0000000000000..8e137be135c2f --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/color-picker.spec.ts @@ -0,0 +1,368 @@ +import { parseStringToRgba } from '@blocks/root-block/edgeless/components/color-picker/utils.js'; +import { expect, type Locator, type Page } from '@playwright/test'; +import { dragBetweenCoords } from 'utils/actions/drag.js'; +import { + addBasicShapeElement, + Shape, + switchEditorMode, + triggerComponentToolbarAction, +} from 'utils/actions/edgeless.js'; +import { + enterPlaygroundRoom, + initEmptyEdgelessState, +} from 'utils/actions/misc.js'; + +import { test } from '../utils/playwright.js'; + +async function setupWithColorPickerFunction(page: Page) { + await enterPlaygroundRoom(page, { flags: { enable_color_picker: true } }); + await initEmptyEdgelessState(page); + await switchEditorMode(page); +} + +function getColorPickerButtonWithClass(page: Page, classes: string) { + return page.locator(`edgeless-color-picker-button.${classes}`); +} + +function getCurrentColorUnitButton(locator: Locator) { + return locator.locator('edgeless-color-button').locator('.color-unit'); +} + +function getCurrentColor(locator: Locator) { + return locator.evaluate(ele => + getComputedStyle(ele).getPropertyValue('background-color') + ); +} + +function getCustomButton(locator: Locator) { + return locator.locator('edgeless-color-custom-button'); +} + +function getColorPickerPanel(locator: Locator) { + return locator.locator('edgeless-color-picker'); +} + +function getPaletteControl(locator: Locator) { + return locator.locator('.color-palette'); +} + +function getHueControl(locator: Locator) { + return locator.locator('.color-slider-wrapper.hue .color-slider'); +} + +function getAlphaControl(locator: Locator) { + return locator.locator('.color-slider-wrapper.alpha .color-slider'); +} + +function getHexInput(locator: Locator) { + return locator.locator('label.color input'); +} + +function getAlphaInput(locator: Locator) { + return locator.locator('label.alpha input'); +} + +// Basic functions +test.describe('basic functions', () => { + test('custom color button should be displayed', async ({ page }) => { + await setupWithColorPickerFunction(page); + + const start0 = { x: 100, y: 100 }; + const end0 = { x: 150, y: 200 }; + await addBasicShapeElement(page, start0, end0, Shape.Square); + + const fillColorButton = getColorPickerButtonWithClass(page, 'fill-color'); + await expect(fillColorButton).toBeVisible(); + + await triggerComponentToolbarAction(page, 'changeShapeFillColor'); + + const customButton = getCustomButton(fillColorButton); + await expect(customButton).toBeVisible(); + }); + + test('should open color-picker panel when clicking on custom color button', async ({ + page, + }) => { + await setupWithColorPickerFunction(page); + + const start0 = { x: 100, y: 100 }; + const end0 = { x: 150, y: 200 }; + await addBasicShapeElement(page, start0, end0, Shape.Square); + + await triggerComponentToolbarAction(page, 'changeShapeFillColor'); + + const fillColorButton = getColorPickerButtonWithClass(page, 'fill-color'); + const customButton = getCustomButton(fillColorButton); + + await customButton.click(); + + const colorPickerPanel = getColorPickerPanel(fillColorButton); + + await expect(colorPickerPanel).toBeVisible(); + }); + + test('should close color-picker panel when clicking on outside', async ({ + page, + }) => { + await setupWithColorPickerFunction(page); + + const start0 = { x: 100, y: 100 }; + const end0 = { x: 150, y: 200 }; + await addBasicShapeElement(page, start0, end0, Shape.Square); + + await triggerComponentToolbarAction(page, 'changeShapeFillColor'); + + const fillColorButton = getColorPickerButtonWithClass(page, 'fill-color'); + const currentColorUnit = getCurrentColorUnitButton(fillColorButton); + + const value = await getCurrentColor(currentColorUnit); + await expect(currentColorUnit).toHaveCSS('background-color', value); + + const customButton = getCustomButton(fillColorButton); + + await customButton.click(); + + const colorPickerPanel = getColorPickerPanel(fillColorButton); + + await expect(colorPickerPanel).toBeVisible(); + + await colorPickerPanel.click({ position: { x: 0, y: 0 } }); + await expect(colorPickerPanel).toBeVisible(); + + await page.mouse.click(0, 0); + + await expect(colorPickerPanel).toBeHidden(); + }); + + test('should return to the palette panel when re-clicking the color button', async ({ + page, + }) => { + await setupWithColorPickerFunction(page); + + const start0 = { x: 100, y: 100 }; + const end0 = { x: 150, y: 200 }; + await addBasicShapeElement(page, start0, end0, Shape.Square); + + await triggerComponentToolbarAction(page, 'changeShapeFillColor'); + + const fillColorButton = getColorPickerButtonWithClass(page, 'fill-color'); + const customButton = getCustomButton(fillColorButton); + const colorPickerPanel = getColorPickerPanel(fillColorButton); + + await customButton.click(); + + await expect(colorPickerPanel).toBeVisible(); + + await page.mouse.click(0, 0); + + await expect(colorPickerPanel).toBeHidden(); + + await dragBetweenCoords(page, { x: 125, y: 75 }, { x: 175, y: 225 }); + + await fillColorButton.click(); + + await expect(customButton).toBeVisible(); + await expect(colorPickerPanel).toBeHidden(); + }); + + test('should pick a color when clicking on the palette canvas', async ({ + page, + }) => { + await setupWithColorPickerFunction(page); + + const start0 = { x: 100, y: 100 }; + const end0 = { x: 150, y: 200 }; + await addBasicShapeElement(page, start0, end0, Shape.Square); + + await triggerComponentToolbarAction(page, 'changeShapeFillColor'); + + const fillColorButton = getColorPickerButtonWithClass(page, 'fill-color'); + const customButton = getCustomButton(fillColorButton); + const colorPickerPanel = getColorPickerPanel(fillColorButton); + + await customButton.click(); + + const paletteControl = getPaletteControl(colorPickerPanel); + const hexInput = getHexInput(colorPickerPanel); + + const value = await hexInput.inputValue(); + + await paletteControl.click(); + + const newValue = await hexInput.inputValue(); + + expect(value).not.toEqual(newValue); + }); + + test('should pick a color when clicking on the hue control', async ({ + page, + }) => { + await setupWithColorPickerFunction(page); + + const start0 = { x: 100, y: 100 }; + const end0 = { x: 150, y: 200 }; + await addBasicShapeElement(page, start0, end0, Shape.Square); + + await triggerComponentToolbarAction(page, 'changeShapeFillColor'); + + const fillColorButton = getColorPickerButtonWithClass(page, 'fill-color'); + const customButton = getCustomButton(fillColorButton); + const colorPickerPanel = getColorPickerPanel(fillColorButton); + + await customButton.click(); + + const hueControl = getHueControl(colorPickerPanel); + const hexInput = getHexInput(colorPickerPanel); + + const value = await hexInput.inputValue(); + + await hueControl.click(); + + const newValue = await hexInput.inputValue(); + + expect(value).not.toEqual(newValue); + }); + + test('should update color when changing the hex input', async ({ page }) => { + await setupWithColorPickerFunction(page); + + const start0 = { x: 100, y: 100 }; + const end0 = { x: 150, y: 200 }; + await addBasicShapeElement(page, start0, end0, Shape.Square); + + await triggerComponentToolbarAction(page, 'changeShapeFillColor'); + + const fillColorButton = getColorPickerButtonWithClass(page, 'fill-color'); + const customButton = getCustomButton(fillColorButton); + const colorPickerPanel = getColorPickerPanel(fillColorButton); + + await customButton.click(); + + const hexInput = getHexInput(colorPickerPanel); + + await hexInput.fill('fff'); + await page.keyboard.press('Enter'); + await expect(hexInput).toHaveValue('ffffff'); + + await hexInput.fill('000000'); + await page.keyboard.press('Enter'); + await expect(hexInput).toHaveValue('000000'); + + await hexInput.fill('fff$'); + await page.keyboard.press('Enter'); + await expect(hexInput).toHaveValue('ffffff'); + + await hexInput.fill('#f0f'); + await page.keyboard.press('Enter'); + await expect(hexInput).toHaveValue('ff00ff'); + }); + + test('should adjust alpha when clicking on the alpha control', async ({ + page, + }) => { + await setupWithColorPickerFunction(page); + + const start0 = { x: 100, y: 100 }; + const end0 = { x: 150, y: 200 }; + await addBasicShapeElement(page, start0, end0, Shape.Square); + + await triggerComponentToolbarAction(page, 'changeShapeFillColor'); + + const fillColorButton = getColorPickerButtonWithClass(page, 'fill-color'); + const customButton = getCustomButton(fillColorButton); + const colorPickerPanel = getColorPickerPanel(fillColorButton); + + await customButton.click(); + + const alphaControl = getAlphaControl(colorPickerPanel); + const alphaInput = getAlphaInput(colorPickerPanel); + + const value = await alphaInput.inputValue(); + + await alphaControl.click(); + + const newValue = await alphaInput.inputValue(); + + expect(value).not.toEqual(newValue); + }); + + test('should adjust alpha when changing the alpha input', async ({ + page, + }) => { + await setupWithColorPickerFunction(page); + + const start0 = { x: 100, y: 100 }; + const end0 = { x: 150, y: 200 }; + await addBasicShapeElement(page, start0, end0, Shape.Square); + + await triggerComponentToolbarAction(page, 'changeShapeFillColor'); + + const fillColorButton = getColorPickerButtonWithClass(page, 'fill-color'); + const customButton = getCustomButton(fillColorButton); + const colorPickerPanel = getColorPickerPanel(fillColorButton); + + await customButton.click(); + + const alphaInput = getAlphaInput(colorPickerPanel); + + await alphaInput.fill('101'); + await expect(alphaInput).toHaveValue('100'); + + await alphaInput.fill('-1'); + await expect(alphaInput).toHaveValue('1'); + + await alphaInput.pressSequentially('--1'); + await expect(alphaInput).toHaveValue('1'); + + await alphaInput.pressSequentially('++1'); + await expect(alphaInput).toHaveValue('1'); + + await alphaInput.pressSequentially('-+1'); + await expect(alphaInput).toHaveValue('1'); + + await alphaInput.pressSequentially('+-1'); + await expect(alphaInput).toHaveValue('1'); + + await alphaInput.fill('23'); + await expect(alphaInput).toHaveValue('23'); + }); + + test('the computed style should be parsed correctly', async ({ page }) => { + await setupWithColorPickerFunction(page); + + const start0 = { x: 100, y: 100 }; + const end0 = { x: 150, y: 200 }; + await addBasicShapeElement(page, start0, end0, Shape.Square); + + await triggerComponentToolbarAction(page, 'changeShapeFillColor'); + + const fillColorButton = getColorPickerButtonWithClass(page, 'fill-color'); + const currentColorUnit = getCurrentColorUnitButton(fillColorButton); + + const value = await getCurrentColor(currentColorUnit); + let rgba = parseStringToRgba(value); + + expect(rgba.a).toEqual(1); + + rgba = parseStringToRgba('rgb(25.5,0,0)'); + expect(rgba.r).toBeCloseTo(0.1); + + rgba = parseStringToRgba('rgba(233,233,233, .5)'); + expect(rgba.a).toEqual(0.5); + + rgba = parseStringToRgba('transparent'); + expect(rgba).toEqual({ r: 1, g: 1, b: 1, a: 0 }); + + rgba = parseStringToRgba('--blocksuite-transparent'); + expect(rgba).toEqual({ r: 1, g: 1, b: 1, a: 0 }); + + rgba = parseStringToRgba('--affine-palette-transparent'); + expect(rgba).toEqual({ r: 1, g: 1, b: 1, a: 0 }); + + rgba = parseStringToRgba('#ff0'); + expect(rgba).toEqual({ r: 1, g: 1, b: 0, a: 1 }); + + rgba = parseStringToRgba('#ff09'); + expect(rgba).toEqual({ r: 1, g: 1, b: 0, a: 0.6 }); + }); +}); diff --git a/blocksuite/tests-legacy/edgeless/connector/clipboard.spec.ts b/blocksuite/tests-legacy/edgeless/connector/clipboard.spec.ts new file mode 100644 index 0000000000000..eb30a6b13397b --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/connector/clipboard.spec.ts @@ -0,0 +1,142 @@ +import { expect } from '@playwright/test'; + +import { + copyByKeyboard, + createConnectorElement, + createNote, + createShapeElement, + edgelessCommonSetup as commonSetup, + getAllSortedIds, + getTypeById, + pasteByKeyboard, + selectAllByKeyboard, + Shape, + toViewCoord, + triggerComponentToolbarAction, + waitNextFrame, +} from '../../utils/actions/index.js'; +import { assertConnectorPath } from '../../utils/asserts.js'; +import { test } from '../../utils/playwright.js'; + +test.describe('connector clipboard', () => { + test('copy and paste connector whose both sides connect nothing', async ({ + page, + }) => { + await commonSetup(page); + await createConnectorElement(page, [0, 0], [200, 100]); + await waitNextFrame(page); + await copyByKeyboard(page); + const move = await toViewCoord(page, [100, -50]); + await page.mouse.click(move[0], move[1]); + await pasteByKeyboard(page, false); + await waitNextFrame(page); + await assertConnectorPath( + page, + [ + [0, -100], + [100, -100], + [100, 0], + [200, 0], + ], + 1 + ); + }); + + test('copy and paste connector whose both sides connect elements', async ({ + page, + }) => { + await commonSetup(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await createShapeElement(page, [200, 0], [300, 100], Shape.Square); + await createConnectorElement(page, [60, 50], [240, 50]); + + await selectAllByKeyboard(page); + await copyByKeyboard(page); + const move = await toViewCoord(page, [150, -50]); + await page.mouse.click(move[0], move[1]); + await pasteByKeyboard(page, false); + await waitNextFrame(page); + await assertConnectorPath( + page, + [ + [100, -50], + [200, -50], + ], + 1 + ); + }); + + test('copy and paste connector whose both sides connect elements, but only paste connector', async ({ + page, + }) => { + await commonSetup(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await createShapeElement(page, [200, 0], [300, 100], Shape.Square); + await createConnectorElement(page, [70, 50], [230, 50]); + + await copyByKeyboard(page); + const move = await toViewCoord(page, [150, -50]); + await page.mouse.move(move[0], move[1]); + await pasteByKeyboard(page, false); + await waitNextFrame(page); + await assertConnectorPath( + page, + [ + [100, -50], + [200, -50], + ], + 1 + ); + }); + + test('copy and paste connector whose one side connects elements', async ({ + page, + }) => { + await commonSetup(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await createConnectorElement(page, [55, 50], [200, 50]); + + await selectAllByKeyboard(page); + await copyByKeyboard(page); + const move = await toViewCoord(page, [100, -50]); + await page.mouse.click(move[0], move[1]); + await pasteByKeyboard(page, false); + await assertConnectorPath( + page, + [ + [100, -50], + [200, -50], + ], + 1 + ); + }); + + test('original relative index should keep same when copy and paste group with note and shape', async ({ + page, + }) => { + await commonSetup(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await createNote(page, [100, 50]); + await page.mouse.click(10, 50); + await selectAllByKeyboard(page); + await triggerComponentToolbarAction(page, 'addGroup'); + await copyByKeyboard(page); + const move = await toViewCoord(page, [250, 250]); + await page.mouse.move(move[0], move[1]); + await page.mouse.click(move[0], move[1]); + await pasteByKeyboard(page, true); + await waitNextFrame(page, 500); + + const sortedIds = await getAllSortedIds(page); + expect(sortedIds.length).toBe(6); + expect(await getTypeById(page, sortedIds[0])).toBe( + await getTypeById(page, sortedIds[3]) + ); + expect(await getTypeById(page, sortedIds[1])).toBe( + await getTypeById(page, sortedIds[4]) + ); + expect(await getTypeById(page, sortedIds[2])).toBe( + await getTypeById(page, sortedIds[5]) + ); + }); +}); diff --git a/blocksuite/tests-legacy/edgeless/connector/connector.spec.ts b/blocksuite/tests-legacy/edgeless/connector/connector.spec.ts new file mode 100644 index 0000000000000..049daee8fbcb1 --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/connector/connector.spec.ts @@ -0,0 +1,320 @@ +import { expect } from '@playwright/test'; + +import { + addBasicConnectorElement, + changeConnectorStrokeColor, + changeConnectorStrokeStyle, + changeConnectorStrokeWidth, + createConnectorElement, + createShapeElement, + dragBetweenViewCoords, + edgelessCommonSetup as commonSetup, + pickColorAtPoints, + rotateElementByHandle, + Shape, + toModelCoord, + toViewCoord, + triggerComponentToolbarAction, +} from '../../utils/actions/edgeless.js'; +import { pressBackspace, waitNextFrame } from '../../utils/actions/index.js'; +import { + assertConnectorPath, + assertEdgelessNonSelectedRect, + assertEdgelessSelectedRect, + assertExists, +} from '../../utils/asserts.js'; +import { test } from '../../utils/playwright.js'; + +test('path #1, the upper line is parallel with the lower line of antoher, and anchor from top to bottom of another', async ({ + page, +}) => { + await commonSetup(page); + + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await createShapeElement(page, [200, -100], [300, 0], Shape.Square); + await createConnectorElement(page, [50, 0], [250, 0]); + + await waitNextFrame(page); + await assertConnectorPath(page, [ + [50, 0], + [50, -20], + [150, -20], + [150, 20], + [250, 20], + [250, 0], + ]); +}); + +test('path #2, the top-right point is overlapped with the bottom-left point of another, and anchor from top to bottom of another', async ({ + page, +}) => { + await commonSetup(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await createShapeElement(page, [100, -100], [200, 0], Shape.Square); + await createConnectorElement(page, [50, 0], [150, 0]); + + await assertConnectorPath(page, [ + [50, 0], + [50, -120], + [220, -120], + [220, 20], + [150, 20], + [150, 0], + ]); +}); + +test('path #3, the two shape are parallel in x axis, the anchor from the right to right', async ({ + page, +}) => { + await commonSetup(page); + + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await createShapeElement(page, [200, 0], [300, 100], Shape.Square); + await createConnectorElement(page, [100, 50], [300, 50]); + await assertConnectorPath(page, [ + [100, 50], + [150, 50], + [150, 120], + [320, 120], + [320, 50], + [300, 50], + ]); +}); + +test('when element is removed, connector should be deleted too', async ({ + page, +}) => { + await commonSetup(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await createConnectorElement(page, [100, 50], [200, 0]); + + //select + await dragBetweenViewCoords(page, [10, -10], [20, 20]); + await pressBackspace(page); + await dragBetweenViewCoords(page, [100, 50], [0, 50]); + await assertEdgelessNonSelectedRect(page); +}); + +test('connector connects triangle shape', async ({ page }) => { + await commonSetup(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Triangle); + await createConnectorElement(page, [75, 50], [100, 50]); + + await assertConnectorPath(page, [ + [75, 50], + [100, 50], + ]); +}); + +test('connector connects diamond shape', async ({ page }) => { + await commonSetup(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Diamond); + await createConnectorElement(page, [100, 50], [200, 50]); + + await assertConnectorPath(page, [ + [100, 50], + [200, 50], + ]); +}); + +test('connector connects rotated Square shape', async ({ page }) => { + await commonSetup(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await createConnectorElement(page, [50, 0], [50, -100]); + await dragBetweenViewCoords(page, [-10, 50], [60, 60]); + await rotateElementByHandle(page, 30, 'top-left'); + await assertConnectorPath(page, [ + [75, 6.7], + [75, -46.65], + [50, -46.65], + [50, -100], + ]); + await rotateElementByHandle(page, 30, 'top-left'); + await assertConnectorPath(page, [ + [93.3, 25], + [138.3, 25], + [138.3, -38.3], + [50, -38.3], + [50, -100], + ]); +}); + +test('change connector line width', async ({ page }) => { + await commonSetup(page); + + const start = { x: 100, y: 200 }; + const end = { x: 300, y: 300 }; + await addBasicConnectorElement(page, start, end); + + await page.mouse.click(start.x + 5, start.y); + await triggerComponentToolbarAction(page, 'changeConnectorStrokeColor'); + await changeConnectorStrokeColor(page, '--affine-palette-line-teal'); + + await triggerComponentToolbarAction(page, 'changeConnectorStrokeStyles'); + await changeConnectorStrokeWidth(page, 5); + + await waitNextFrame(page); + + await triggerComponentToolbarAction(page, 'changeConnectorStrokeStyles'); + + const pickedColor = await pickColorAtPoints(page, [ + [start.x + 5, start.y], + [start.x + 10, start.y], + ]); + expect(pickedColor[0]).toBe(pickedColor[1]); +}); + +test('change connector stroke style', async ({ page }) => { + await commonSetup(page); + + const start = { x: 100, y: 200 }; + const end = { x: 300, y: 300 }; + await addBasicConnectorElement(page, start, end); + + await page.mouse.click(start.x + 5, start.y); + await triggerComponentToolbarAction(page, 'changeConnectorStrokeColor'); + await changeConnectorStrokeColor(page, '--affine-palette-line-teal'); + + await triggerComponentToolbarAction(page, 'changeConnectorStrokeStyles'); + await changeConnectorStrokeStyle(page, 'dash'); + await waitNextFrame(page); + + await triggerComponentToolbarAction(page, 'changeConnectorStrokeStyles'); + + const pickedColor = await pickColorAtPoints(page, [[start.x + 20, start.y]]); + expect(pickedColor[0]).toBe('#000000'); +}); + +test.describe('quick connect', () => { + test('should create a connector when clicking on button', async ({ + page, + }) => { + await commonSetup(page); + + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + const [x, y] = await toViewCoord(page, [50, 50]); + await page.mouse.click(x, y); + + const quickConnectBtn = page.getByRole('button', { + name: 'Draw connector', + }); + + await expect(quickConnectBtn).toBeVisible(); + await quickConnectBtn.click(); + await expect(quickConnectBtn).toBeHidden(); + + await assertConnectorPath(page, [ + [100, 50], + [x, y], + ]); + }); + + test('should be uncreated if the target is not found after clicking', async ({ + page, + }) => { + await commonSetup(page); + + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + const [x, y] = await toViewCoord(page, [50, 50]); + await page.mouse.click(x, y); + + const quickConnectBtn = page.getByRole('button', { + name: 'Draw connector', + }); + + const bounds = await quickConnectBtn.boundingBox(); + assertExists(bounds); + + await quickConnectBtn.click(); + + await page.mouse.click(bounds.x, bounds.y); + await assertEdgelessSelectedRect(page, [x - 50, y - 50, 100, 100]); + }); + + test('should be uncreated if the target is not found after pressing ESC', async ({ + page, + }) => { + await commonSetup(page); + + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + + // select shape + const [x, y] = await toViewCoord(page, [50, 50]); + await page.mouse.click(x, y); + + // click button + await triggerComponentToolbarAction(page, 'quickConnect'); + + await page.keyboard.press('Escape'); + + await assertEdgelessNonSelectedRect(page); + }); + + test('should be connected if the target is found', async ({ page }) => { + await commonSetup(page); + + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await createShapeElement(page, [200, 0], [300, 100], Shape.Square); + + // select shape + const [x, y] = await toViewCoord(page, [50, 50]); + await page.mouse.click(x, y); + + // click button + await triggerComponentToolbarAction(page, 'quickConnect'); + + // click target + const [tx, ty] = await toViewCoord(page, [200, 50]); + await page.mouse.click(tx, ty); + + await assertConnectorPath(page, [ + [100, 50], + [200, 50], + ]); + }); + + test('should follow the mouse to automatically select the starting point', async ({ + page, + }) => { + await commonSetup(page); + + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + const shapeBounds = await toViewCoord(page, [0, 0]); + + // select shape + const [x, y] = await toViewCoord(page, [50, 50]); + await page.mouse.click(x, y); + + // click button + const quickConnectBtn = page.getByRole('button', { + name: 'Draw connector', + }); + const bounds = await quickConnectBtn.boundingBox(); + assertExists(bounds); + await quickConnectBtn.click(); + + // at right + let point: [number, number] = [bounds.x, bounds.y]; + let endpoint = await toModelCoord(page, point); + await assertConnectorPath(page, [[100, 50], endpoint]); + + // at top + point = [shapeBounds[0] + 50, shapeBounds[1] - 50]; + endpoint = await toModelCoord(page, point); + await page.mouse.move(...point); + await waitNextFrame(page); + await assertConnectorPath(page, [[50, 0], endpoint]); + + // at left + point = [shapeBounds[0] - 50, shapeBounds[1] + 50]; + endpoint = await toModelCoord(page, point); + await page.mouse.move(...point); + await assertConnectorPath(page, [[0, 50], endpoint]); + + // at bottom + point = [shapeBounds[0] + 50, shapeBounds[1] + 100 + 50]; + endpoint = await toModelCoord(page, point); + await page.mouse.move(...point); + await assertConnectorPath(page, [[50, 100], endpoint]); + }); +}); diff --git a/blocksuite/tests-legacy/edgeless/connector/elbow.spec.ts b/blocksuite/tests-legacy/edgeless/connector/elbow.spec.ts new file mode 100644 index 0000000000000..0cde357a324a8 --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/connector/elbow.spec.ts @@ -0,0 +1,206 @@ +import { + assertEdgelessConnectorToolMode, + ConnectorMode, + createConnectorElement, + createShapeElement, + deleteAllConnectors, + dragBetweenViewCoords, + edgelessCommonSetup as commonSetup, + redoByClick, + setEdgelessTool, + Shape, + undoByClick, +} from '../../utils/actions/index.js'; +import { assertConnectorPath } from '../../utils/asserts.js'; +import { test } from '../../utils/playwright.js'; + +test('elbow connector without node and width greater than height', async ({ + page, +}) => { + await commonSetup(page); + await setEdgelessTool(page, 'connector'); + await assertEdgelessConnectorToolMode(page, ConnectorMode.Curve); + await dragBetweenViewCoords(page, [0, 0], [200, 100]); + await assertConnectorPath(page, [ + [0, 0], + [100, 0], + [100, 100], + [200, 100], + ]); +}); + +test('elbow connector without node and width less than height', async ({ + page, +}) => { + await commonSetup(page); + await createConnectorElement(page, [0, 0], [100, 200]); + await assertConnectorPath(page, [ + [0, 0], + [0, 100], + [100, 100], + [100, 200], + ]); +}); + +test('elbow connector one side attached element another side free', async ({ + page, +}) => { + await commonSetup(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await createConnectorElement(page, [51, 50], [200, 0]); + + await assertConnectorPath(page, [ + [100, 50], + [150, 50], + [150, 0], + [200, 0], + ]); + + await deleteAllConnectors(page); + await createConnectorElement(page, [50, 50], [125, 0]); + + await assertConnectorPath(page, [ + [50, 0], + [125, 50], + [125, 0], + ]); +}); + +test('elbow connector both side attatched element', async ({ page }) => { + await commonSetup(page); + + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await createShapeElement(page, [200, 0], [300, 100], Shape.Square); + await createConnectorElement(page, [50, 50], [249, 50]); + + await assertConnectorPath(page, [ + [50, 0], + [200, 50], + ]); + + // Could drag directly + // because the default shape type change to general style with filled color + await dragBetweenViewCoords(page, [250, 50], [250, 0]); + await assertConnectorPath(page, [ + [50, 0], + [150, 50], + [150, 0], + [200, 0], + ]); + + await dragBetweenViewCoords(page, [250, 0], [150, -50]); + await assertConnectorPath(page, [ + [50, 0], + [50, -50], + [100, -50], + ]); + + await dragBetweenViewCoords(page, [150, -50], [150, -150]); + await assertConnectorPath(page, [ + [50, 0], + [50, -50], + [150, -50], + [150, -100], + ]); + + await undoByClick(page); + await assertConnectorPath(page, [ + [50, 0], + [50, -50], + [100, -50], + ]); + await undoByClick(page); + await assertConnectorPath(page, [ + [50, 0], + [150, 50], + [150, 0], + [200, 0], + ]); + await undoByClick(page); + await assertConnectorPath(page, [ + [50, 0], + [200, 50], + ]); + await redoByClick(page); + await assertConnectorPath(page, [ + [50, 0], + [150, 50], + [150, 0], + [200, 0], + ]); + await redoByClick(page); + await assertConnectorPath(page, [ + [50, 0], + [50, -50], + [100, -50], + ]); + await redoByClick(page); + await assertConnectorPath(page, [ + [50, 0], + [50, -50], + [150, -50], + [150, -100], + ]); +}); + +test('elbow connector both side attached element with one attach element and other is fixed', async ({ + page, +}) => { + await commonSetup(page); + + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await createShapeElement(page, [200, 0], [300, 100], Shape.Square); + await createConnectorElement(page, [50, 0], [250, 50]); + + await assertConnectorPath(page, [ + [50, 0], + [50, -20], + [150, -20], + [150, 50], + [200, 50], + ]); + + // select + await dragBetweenViewCoords(page, [255, -10], [255, 55]); + await dragBetweenViewCoords(page, [250, 50], [250, 0]); + + await assertConnectorPath(page, [ + [50, 0], + [50, -20], + [150, -20], + [150, 0], + [200, 0], + ]); + + await dragBetweenViewCoords(page, [250, 0], [250, -20]); + await assertConnectorPath(page, [ + [50, 0], + [50, -20], + [200, -20], + ]); + + await dragBetweenViewCoords(page, [250, -20], [150, -150]); + await assertConnectorPath(page, [ + [50, 0], + [50, -50], + [150, -50], + [150, -100], + ]); +}); + +test('elbow connector both side attached element with all fixed', async ({ + page, +}) => { + await commonSetup(page); + + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await createShapeElement(page, [200, 0], [300, 100], Shape.Square); + await createConnectorElement(page, [50, 0], [300, 50]); + await assertConnectorPath(page, [ + [50, 0], + [50, -20], + [320, -20], + [320, 50], + [300, 50], + ]); +}); diff --git a/blocksuite/tests-legacy/edgeless/connector/group.spec.ts b/blocksuite/tests-legacy/edgeless/connector/group.spec.ts new file mode 100644 index 0000000000000..28ab2b931bc84 --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/connector/group.spec.ts @@ -0,0 +1,107 @@ +import type { Page } from '@playwright/test'; + +import { + clickView, + createShapeElement, + dragBetweenViewCoords, + edgelessCommonSetup as commonSetup, + moveView, + selectAllByKeyboard, + Shape, + triggerComponentToolbarAction, + waitNextFrame, +} from '../../utils/actions/index.js'; +import { assertConnectorPath } from '../../utils/asserts.js'; +import { test } from '../../utils/playwright.js'; + +test.describe('groups connections', () => { + async function groupsSetup(page: Page) { + await commonSetup(page); + + // group 1 + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await createShapeElement(page, [100, 100], [200, 200], Shape.Square); + await selectAllByKeyboard(page); + await triggerComponentToolbarAction(page, 'addGroup'); + + // group 2 + await createShapeElement(page, [500, 0], [600, 100], Shape.Square); + await createShapeElement(page, [600, 100], [700, 200], Shape.Square); + await dragBetweenViewCoords(page, [550, -50], [650, 250]); + await triggerComponentToolbarAction(page, 'addGroup'); + + await waitNextFrame(page); + } + + test('should connect to other groups', async ({ page }) => { + await groupsSetup(page); + + // click button + await triggerComponentToolbarAction(page, 'quickConnect'); + + // move to group 1 + await moveView(page, [200, 50]); + await waitNextFrame(page); + + await assertConnectorPath(page, [ + [500, 100], + [200, 50], + ]); + }); + + test('should connect to elements within other groups', async ({ page }) => { + await groupsSetup(page); + + // click button + await triggerComponentToolbarAction(page, 'quickConnect'); + + // move to group 1 + await moveView(page, [200, 100]); + await waitNextFrame(page); + + await assertConnectorPath(page, [ + [500, 100], + [200, 100], + ]); + + // move to elements within group 1 + await moveView(page, [190, 150]); + await waitNextFrame(page); + + await assertConnectorPath(page, [ + [500, 100], + [200, 150], + ]); + }); + + test('elements within groups should connect to other groups', async ({ + page, + }) => { + await groupsSetup(page); + + // click elements within group 1 + await clickView(page, [40, 40]); + await clickView(page, [60, 60]); + + // click button + await triggerComponentToolbarAction(page, 'quickConnect'); + + // move to elements within group 2 + await moveView(page, [610, 50]); + await waitNextFrame(page); + + await assertConnectorPath(page, [ + [100, 50], + [600, 50], + ]); + + // move to group 2 + await moveView(page, [600, 100]); + await waitNextFrame(page); + + await assertConnectorPath(page, [ + [100, 50], + [600, 100], + ]); + }); +}); diff --git a/blocksuite/tests-legacy/edgeless/connector/label.spec.ts b/blocksuite/tests-legacy/edgeless/connector/label.spec.ts new file mode 100644 index 0000000000000..dd6a33f31b800 --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/connector/label.spec.ts @@ -0,0 +1,334 @@ +import { assertExists } from '@blocksuite/global/utils'; +import { expect, type Page } from '@playwright/test'; + +import { + addBasicConnectorElement, + createConnectorElement, + createShapeElement, + dragBetweenViewCoords, + edgelessCommonSetup as commonSetup, + locatorComponentToolbar, + setEdgelessTool, + Shape, + SHORT_KEY, + toViewCoord, + triggerComponentToolbarAction, + type, + waitNextFrame, +} from '../../utils/actions/index.js'; +import { + assertConnectorPath, + assertEdgelessCanvasText, + assertPointAlmostEqual, +} from '../../utils/asserts.js'; +import { test } from '../../utils/playwright.js'; + +test.describe('connector label with straight shape', () => { + async function getEditorCenter(page: Page) { + const bounds = await page + .locator('edgeless-connector-label-editor rich-text') + .boundingBox(); + assertExists(bounds); + const cx = bounds.x + bounds.width / 2; + const cy = bounds.y + bounds.height / 2; + return [cx, cy]; + } + + function calcOffsetDistance(s: number[], e: number[], p: number[]) { + const p1 = Math.hypot(s[1] - p[1], s[0] - p[0]); + const f1 = Math.hypot(s[1] - e[1], s[0] - e[0]); + return p1 / f1; + } + + test('should insert in the middle of the path when clicking on the button', async ({ + page, + }) => { + await commonSetup(page); + const start = { x: 100, y: 200 }; + const end = { x: 300, y: 300 }; + await addBasicConnectorElement(page, start, end); + await page.mouse.click(105, 200); + + await triggerComponentToolbarAction(page, 'addText'); + await type(page, ' a '); + await assertEdgelessCanvasText(page, ' a '); + + await page.mouse.click(0, 0); + await waitNextFrame(page); + await page.mouse.click(105, 200); + + const addTextBtn = locatorComponentToolbar(page).getByRole('button', { + name: 'Add text', + }); + await expect(addTextBtn).toBeHidden(); + + await page.mouse.dblclick(200, 250); + await assertEdgelessCanvasText(page, 'a'); + + await page.keyboard.press('Backspace'); + await assertEdgelessCanvasText(page, ''); + + await page.mouse.click(0, 0); + await waitNextFrame(page); + await page.mouse.click(200, 250); + + await expect(addTextBtn).toBeVisible(); + }); + + test('should insert at the place when double clicking on the path', async ({ + page, + }) => { + await commonSetup(page); + await setEdgelessTool(page, 'connector'); + + await page.mouse.move(0, 0); + + const menu = page.locator('edgeless-connector-menu'); + await expect(menu).toBeVisible(); + + const straightBtn = menu.locator('edgeless-tool-icon-button', { + hasText: 'Straight', + }); + await expect(straightBtn).toBeVisible(); + await straightBtn.click(); + + const start = { x: 250, y: 250 }; + const end = { x: 500, y: 250 }; + await addBasicConnectorElement(page, start, end); + + await page.mouse.dblclick(300, 250); + await type(page, 'a'); + await assertEdgelessCanvasText(page, 'a'); + + await page.mouse.click(0, 0); + await waitNextFrame(page); + + await page.mouse.dblclick(300, 250); + await waitNextFrame(page); + + await page.keyboard.press('ArrowRight'); + await type(page, 'b'); + await assertEdgelessCanvasText(page, 'ab'); + + await page.mouse.click(0, 0); + await waitNextFrame(page); + + await page.mouse.dblclick(300, 250); + await waitNextFrame(page); + + await type(page, 'c'); + await assertEdgelessCanvasText(page, 'c'); + await waitNextFrame(page); + + const [cx, cy] = await getEditorCenter(page); + assertPointAlmostEqual([cx, cy], [300, 250]); + expect((cx - 250) / (500 - 250)).toBeCloseTo(50 / 250); + }); + + test('should move alone the path', async ({ page }) => { + await commonSetup(page); + + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await createShapeElement(page, [200, 0], [300, 100], Shape.Square); + await createConnectorElement(page, [100, 50], [200, 50]); + + await dragBetweenViewCoords(page, [140, 40], [160, 60]); + await triggerComponentToolbarAction(page, 'changeConnectorShape'); + const straightBtn = locatorComponentToolbar(page).getByRole('button', { + name: 'Straight', + }); + await straightBtn.click(); + + await assertConnectorPath(page, [ + [100, 50], + [200, 50], + ]); + + const [x, y] = await toViewCoord(page, [150, 50]); + await page.mouse.dblclick(x, y); + await type(page, 'label'); + await assertEdgelessCanvasText(page, 'label'); + await waitNextFrame(page); + + let [cx, cy] = await getEditorCenter(page); + assertPointAlmostEqual([cx, cy], [x, y]); + + await page.mouse.click(0, 0); + await waitNextFrame(page); + + await dragBetweenViewCoords(page, [150, 50], [130, 30]); + + await page.mouse.click(0, 0); + await waitNextFrame(page); + + await page.mouse.dblclick(x - 20, y); + await waitNextFrame(page); + + [cx, cy] = await getEditorCenter(page); + assertPointAlmostEqual([cx, cy], [x - 20, y]); + + await page.mouse.click(0, 0); + await waitNextFrame(page); + + await dragBetweenViewCoords(page, [130, 50], [170, 70]); + + await page.mouse.click(0, 0); + await waitNextFrame(page); + + await page.mouse.dblclick(x + 20, y); + await waitNextFrame(page); + + [cx, cy] = await getEditorCenter(page); + assertPointAlmostEqual([cx, cy], [x + 20, y]); + }); + + test('should only move within constraints', async ({ page }) => { + await commonSetup(page); + + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await createShapeElement(page, [200, 0], [300, 100], Shape.Square); + await createConnectorElement(page, [100, 50], [200, 50]); + + await assertConnectorPath(page, [ + [100, 50], + [200, 50], + ]); + + const [x, y] = await toViewCoord(page, [150, 50]); + await page.mouse.dblclick(x, y); + await type(page, 'label'); + await assertEdgelessCanvasText(page, 'label'); + await waitNextFrame(page); + + await page.mouse.click(0, 0); + await waitNextFrame(page); + + await dragBetweenViewCoords(page, [150, 50], [300, 110]); + + await page.mouse.click(0, 0); + await waitNextFrame(page); + + await page.mouse.dblclick(x + 55, y); + await waitNextFrame(page); + + let [cx, cy] = await getEditorCenter(page); + assertPointAlmostEqual([cx, cy], [x + 50, y]); + + await page.mouse.click(0, 0); + await waitNextFrame(page); + + await dragBetweenViewCoords(page, [200, 50], [0, 50]); + + await page.mouse.click(0, 0); + await waitNextFrame(page); + + await page.mouse.dblclick(x - 55, y); + await waitNextFrame(page); + + [cx, cy] = await getEditorCenter(page); + assertPointAlmostEqual([cx, cy], [x - 50, y]); + }); + + test('should automatically adjust position via offset distance', async ({ + page, + }) => { + await commonSetup(page); + + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await createShapeElement(page, [200, 0], [300, 100], Shape.Square); + await createConnectorElement(page, [100, 50], [200, 50]); + + await dragBetweenViewCoords(page, [140, 40], [160, 60]); + await triggerComponentToolbarAction(page, 'changeConnectorShape'); + const straightBtn = locatorComponentToolbar(page).getByRole('button', { + name: 'Straight', + }); + await straightBtn.click(); + + const point = [170, 50]; + const offsetDistance = calcOffsetDistance([100, 50], [200, 50], point); + let [x, y] = await toViewCoord(page, point); + await page.mouse.dblclick(x, y); + await type(page, 'label'); + + await page.mouse.click(0, 0); + await waitNextFrame(page); + + await page.mouse.dblclick(x, y); + await waitNextFrame(page); + + let [cx, cy] = await getEditorCenter(page); + assertPointAlmostEqual([cx, cy], [x, y]); + + await page.mouse.click(0, 0); + await waitNextFrame(page); + + await page.mouse.click(50, 50); + await waitNextFrame(page); + await dragBetweenViewCoords(page, [50, 50], [-50, 50]); + await waitNextFrame(page); + + await page.mouse.click(0, 0); + await waitNextFrame(page); + + await page.mouse.click(250, 50); + await waitNextFrame(page); + await dragBetweenViewCoords(page, [250, 50], [350, 50]); + await waitNextFrame(page); + + const start = [0, 50]; + const end = [300, 50]; + const mx = start[0] + offsetDistance * (end[0] - start[0]); + const my = start[1] + offsetDistance * (end[1] - start[1]); + [x, y] = await toViewCoord(page, [mx, my]); + + await page.mouse.dblclick(x, y); + await waitNextFrame(page); + + [cx, cy] = await getEditorCenter(page); + assertPointAlmostEqual([cx, cy], [x, y]); + }); + + test('should enter the label editing state when pressing `Enter`', async ({ + page, + }) => { + await commonSetup(page); + const start = { x: 100, y: 200 }; + const end = { x: 300, y: 300 }; + await addBasicConnectorElement(page, start, end); + await page.mouse.click(105, 200); + + await page.keyboard.press('Enter'); + await type(page, ' a '); + await assertEdgelessCanvasText(page, ' a '); + }); + + test('should exit the label editing state when pressing `Mod-Enter` or `Escape`', async ({ + page, + }) => { + await commonSetup(page); + const start = { x: 100, y: 200 }; + const end = { x: 300, y: 300 }; + await addBasicConnectorElement(page, start, end); + await page.mouse.click(105, 200); + + await page.keyboard.press('Enter'); + await waitNextFrame(page); + await type(page, ' a '); + await assertEdgelessCanvasText(page, ' a '); + + await page.keyboard.press(`${SHORT_KEY}+Enter`); + + await page.keyboard.press('Enter'); + await waitNextFrame(page); + await type(page, 'b'); + await assertEdgelessCanvasText(page, 'b'); + + await page.keyboard.press('Escape'); + + await page.keyboard.press('Enter'); + await waitNextFrame(page); + await type(page, 'c'); + await assertEdgelessCanvasText(page, 'c'); + }); +}); diff --git a/blocksuite/tests-legacy/edgeless/edgeless-text.spec.ts b/blocksuite/tests-legacy/edgeless/edgeless-text.spec.ts new file mode 100644 index 0000000000000..85f4aa164f019 --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/edgeless-text.spec.ts @@ -0,0 +1,596 @@ +import type { EdgelessTextBlockComponent } from '@blocks/edgeless-text-block/edgeless-text-block.js'; +import { Bound } from '@blocksuite/global/utils'; +import { expect, type Page } from '@playwright/test'; + +import { + autoFit, + captureHistory, + cutByKeyboard, + dragBetweenIndices, + enterPlaygroundRoom, + getEdgelessSelectedRect, + getPageSnapshot, + initEmptyEdgelessState, + pasteByKeyboard, + pressArrowLeft, + pressArrowRight, + pressArrowUp, + pressBackspace, + pressEnter, + pressEscape, + selectAllByKeyboard, + setEdgelessTool, + switchEditorMode, + toViewCoord, + type, + undoByKeyboard, + waitNextFrame, +} from '../utils/actions/index.js'; +import { + assertBlockChildrenIds, + assertBlockFlavour, + assertBlockTextContent, + assertRichTextInlineDeltas, + assertRichTextInlineRange, +} from '../utils/asserts.js'; +import { test } from '../utils/playwright.js'; +import { getFormatBar } from '../utils/query.js'; + +async function assertEdgelessTextModelRect( + page: Page, + id: string, + bound: Bound +) { + const realXYWH = await page.evaluate(id => { + const block = window.host.view.getBlock(id) as EdgelessTextBlockComponent; + return block?.model.xywh; + }, id); + const realBound = Bound.deserialize(realXYWH); + expect(realBound.x).toBeCloseTo(bound.x, 0); + expect(realBound.y).toBeCloseTo(bound.y, 0); + expect(realBound.w).toBeCloseTo(bound.w, 0); + expect(realBound.h).toBeCloseTo(bound.h, 0); +} + +test.describe('edgeless text block', () => { + test.beforeEach(async ({ page }) => { + await enterPlaygroundRoom(page, { + flags: { + enable_edgeless_text: true, + }, + }); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + }); + + test('add text block in default mode', async ({ page }) => { + await setEdgelessTool(page, 'default'); + await page.mouse.dblclick(130, 140, { + delay: 100, + }); + await waitNextFrame(page); + + // https://github.com/toeverything/blocksuite/pull/8574 + await pressBackspace(page); + + await type(page, 'aaa'); + await pressEnter(page); + await type(page, 'bbb'); + await pressEnter(page); + await type(page, 'ccc'); + + await assertBlockFlavour(page, 4, 'affine:edgeless-text'); + await assertBlockFlavour(page, 5, 'affine:paragraph'); + await assertBlockFlavour(page, 6, 'affine:paragraph'); + await assertBlockFlavour(page, 7, 'affine:paragraph'); + await assertBlockChildrenIds(page, '4', ['5', '6', '7']); + await assertBlockTextContent(page, 5, 'aaa'); + await assertBlockTextContent(page, 6, 'bbb'); + await assertBlockTextContent(page, 7, 'ccc'); + + await dragBetweenIndices(page, [1, 1], [3, 2]); + await captureHistory(page); + await pressBackspace(page); + await assertBlockChildrenIds(page, '4', ['5']); + await assertBlockTextContent(page, 5, 'ac'); + + await undoByKeyboard(page); + await assertBlockChildrenIds(page, '4', ['5', '6', '7']); + await assertBlockTextContent(page, 5, 'aaa'); + await assertBlockTextContent(page, 6, 'bbb'); + await assertBlockTextContent(page, 7, 'ccc'); + + const { boldBtn } = getFormatBar(page); + await boldBtn.click(); + await assertRichTextInlineDeltas( + page, + [ + { + insert: 'a', + }, + { + insert: 'aa', + attributes: { + bold: true, + }, + }, + ], + 1 + ); + await assertRichTextInlineDeltas( + page, + [ + { + insert: 'bbb', + attributes: { + bold: true, + }, + }, + ], + 2 + ); + await assertRichTextInlineDeltas( + page, + [ + { + insert: 'cc', + attributes: { + bold: true, + }, + }, + { + insert: 'c', + }, + ], + 3 + ); + + await pressArrowRight(page); + await assertRichTextInlineRange(page, 3, 2); + await pressArrowUp(page); + await assertRichTextInlineRange(page, 2, 2); + }); + + test('edgeless text width auto-adjusting', async ({ page }) => { + await setEdgelessTool(page, 'default'); + const point = await toViewCoord(page, [0, 0]); + await page.mouse.dblclick(point[0], point[1], { + delay: 100, + }); + await waitNextFrame(page); + await assertEdgelessTextModelRect(page, '4', new Bound(-25, -25, 50, 26)); + + await type(page, 'aaaaaaaaaa'); + await waitNextFrame(page, 1000); + // just width changed + await assertEdgelessTextModelRect(page, '4', new Bound(-25, -25, 83, 26)); + + await type(page, '\nbbb'); + // width not changed, height changed + await assertEdgelessTextModelRect(page, '4', new Bound(-25, -25, 83, 50)); + await type(page, '\ncccccccccccccccc'); + + // width and height changed + await assertEdgelessTextModelRect(page, '4', new Bound(-25, -25, 131, 74)); + + // blur, max width set to true + await page.mouse.click(point[0] - 50, point[1], { + delay: 100, + }); + await page.mouse.dblclick(point[0], point[1], { + delay: 100, + }); + await type(page, 'dddddddddddddddddddd'); + // width not changed, height changed + await assertEdgelessTextModelRect(page, '4', new Bound(-25, -25, 131, 98)); + }); + + test('edgeless text width fixed when drag moving', async ({ page }) => { + // https://github.com/toeverything/blocksuite/pull/7486 + + await setEdgelessTool(page, 'default'); + await page.mouse.dblclick(130, 140, { + delay: 100, + }); + await waitNextFrame(page); + await type(page, 'aaaaaa bbbb '); + await pressEscape(page); + await waitNextFrame(page); + await page.mouse.click(130, 140); + await page.mouse.down(); + await page.mouse.move(800, 800, { + steps: 15, + }); + + const rect = await page.evaluate(() => { + const container = document.querySelector( + '.edgeless-text-block-container' + )!; + return container.getBoundingClientRect(); + }); + const model = await page.evaluate(() => { + const block = window.host.view.getBlock( + '4' + ) as EdgelessTextBlockComponent; + return block.model; + }); + const bound = Bound.deserialize(model.xywh); + expect(rect.width).toBeCloseTo(bound.w); + expect(rect.height).toBeCloseTo(bound.h); + }); + + test('When creating edgeless text, if the input is empty, it will be automatically deleted', async ({ + page, + }) => { + await setEdgelessTool(page, 'default'); + await page.mouse.dblclick(130, 140, { + delay: 100, + }); + await waitNextFrame(page); + let block = page.locator('affine-edgeless-text[data-block-id="4"]'); + expect(await block.isVisible()).toBe(true); + await page.mouse.click(0, 0); + expect(await block.isVisible()).toBe(false); + + block = page.locator('affine-edgeless-text[data-block-id="6"]'); + expect(await block.isVisible()).not.toBe(true); + await page.mouse.dblclick(130, 140, { + delay: 100, + }); + expect(await block.isVisible()).toBe(true); + await type(page, '\na'); + expect(await block.isVisible()).toBe(true); + await page.mouse.click(0, 0); + expect(await block.isVisible()).not.toBe(false); + }); + + test('edgeless text should maintain selection when deleting across multiple lines', async ({ + page, + }) => { + // https://github.com/toeverything/blocksuite/pull/7443 + + await setEdgelessTool(page, 'default'); + await page.mouse.dblclick(130, 140, { + delay: 100, + }); + await waitNextFrame(page); + await type(page, 'aaaa\nbbbb'); + await assertBlockTextContent(page, 5, 'aaaa'); + await assertBlockTextContent(page, 6, 'bbbb'); + + await pressArrowLeft(page); + await page.keyboard.down('Shift'); + await pressArrowLeft(page, 3); + await pressArrowUp(page); + await pressArrowRight(page); + await page.keyboard.up('Shift'); + await pressBackspace(page); + await assertBlockTextContent(page, 5, 'ab'); + await type(page, 'sss\n'); + await assertBlockTextContent(page, 5, 'asss'); + await assertBlockTextContent(page, 7, 'b'); + }); + + test('edgeless text should not blur after pressing backspace', async ({ + page, + }) => { + // https://github.com/toeverything/blocksuite/pull/7555 + + await setEdgelessTool(page, 'default'); + await page.mouse.dblclick(130, 140, { + delay: 100, + }); + await waitNextFrame(page); + await type(page, 'a'); + await assertBlockTextContent(page, 5, 'a'); + await pressBackspace(page); + await type(page, 'b'); + await assertBlockTextContent(page, 5, 'b'); + }); + + // FIXME(@flrande): This test fails randomly on CI + test.fixme('edgeless text max width', async ({ page }) => { + await setEdgelessTool(page, 'default'); + const point = await toViewCoord(page, [0, 0]); + await page.mouse.dblclick(point[0], point[1], { + delay: 100, + }); + await waitNextFrame(page); + await assertEdgelessTextModelRect(page, '4', new Bound(-25, -25, 50, 56)); + + await type(page, 'aaaaaa'); + await waitNextFrame(page); + await assertEdgelessTextModelRect(page, '4', new Bound(-25, -25, 71, 56)); + await type(page, 'bbb'); + await waitNextFrame(page, 200); + // height not changed + await assertEdgelessTextModelRect(page, '4', new Bound(-25, -25, 98, 56)); + + // blur + await page.mouse.click(0, 0); + // select text element + await page.mouse.click(point[0] + 10, point[1] + 10); + + let selectedRect = await getEdgelessSelectedRect(page); + + // move cursor to the right edge and drag it to resize the width of text + + // from left to right + await page.mouse.move( + selectedRect.x + selectedRect.width, + selectedRect.y + selectedRect.height / 2 + ); + await page.mouse.down(); + await page.mouse.move( + selectedRect.x + selectedRect.width + 30, + selectedRect.y + selectedRect.height / 2, + { + steps: 10, + } + ); + await page.mouse.up(); + await assertEdgelessTextModelRect(page, '4', new Bound(-25, -25, 128, 56)); + selectedRect = await getEdgelessSelectedRect(page); + let textRect = await page + .locator('affine-edgeless-text[data-block-id="4"]') + .boundingBox(); + expect(selectedRect).not.toBeNull(); + expect(selectedRect.width).toBeCloseTo(textRect!.width); + expect(selectedRect.height).toBeCloseTo(textRect!.height); + expect(selectedRect.x).toBeCloseTo(textRect!.x); + expect(selectedRect.y).toBeCloseTo(textRect!.y); + + // from right to left + await page.mouse.move( + selectedRect.x + selectedRect.width, + selectedRect.y + selectedRect.height / 2 + ); + await page.mouse.down(); + await page.mouse.move( + selectedRect.x + selectedRect.width - 45, + selectedRect.y + selectedRect.height / 2, + { + steps: 10, + } + ); + await page.mouse.up(); + // height changed + await assertEdgelessTextModelRect(page, '4', new Bound(-25, -25, 83, 80)); + selectedRect = await getEdgelessSelectedRect(page); + textRect = await page + .locator('affine-edgeless-text[data-block-id="4"]') + .boundingBox(); + expect(selectedRect).not.toBeNull(); + expect(selectedRect.width).toBeCloseTo(textRect!.width); + expect(selectedRect.height).toBeCloseTo(textRect!.height); + expect(selectedRect.x).toBeCloseTo(textRect!.x); + expect(selectedRect.y).toBeCloseTo(textRect!.y); + }); + + test('min width limit for embed block', async ({ page }, testInfo) => { + await setEdgelessTool(page, 'default'); + const point = await toViewCoord(page, [0, 0]); + await page.mouse.dblclick(point[0], point[1], { + delay: 100, + }); + await waitNextFrame(page); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_init.json` + ); + + await type(page, '@'); + await pressEnter(page); + await waitNextFrame(page, 200); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_add_linked_doc.json` + ); + + await page.locator('affine-reference').hover(); + await page.getByLabel('Switch view').click(); + await page.getByTestId('link-to-card').click(); + await autoFit(page); + + await waitNextFrame(page); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_link_to_card.json` + ); + + // blur + await page.mouse.click(0, 0); + // select text element + await page.mouse.click(point[0] + 10, point[1] + 10); + await waitNextFrame(page, 200); + const selectedRect0 = await getEdgelessSelectedRect(page); + + // from right to left + await page.mouse.move( + selectedRect0.x + selectedRect0.width, + selectedRect0.y + selectedRect0.height / 2 + ); + await page.mouse.down(); + await page.mouse.move( + selectedRect0.x, + selectedRect0.y + selectedRect0.height / 2, + { + steps: 10, + } + ); + await page.mouse.up(); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_link_to_card_min_width.json` + ); + + const selectedRect1 = await getEdgelessSelectedRect(page); + // from left to right + await page.mouse.move( + selectedRect1.x + selectedRect1.width, + selectedRect1.y + selectedRect1.height / 2 + ); + await page.mouse.down(); + await page.mouse.move( + selectedRect0.x + selectedRect0.width + 45, + selectedRect1.y + selectedRect1.height / 2, + { + steps: 10, + } + ); + await page.mouse.up(); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_drag.json` + ); + }); + + test('cut edgeless text', async ({ page }) => { + await setEdgelessTool(page, 'default'); + await page.mouse.dblclick(130, 140, { + delay: 100, + }); + await waitNextFrame(page); + await type(page, 'aaaa\nbbbb\ncccc'); + + const edgelessText = page.locator('affine-edgeless-text'); + const paragraph = page.locator('affine-edgeless-text affine-paragraph'); + + expect(await edgelessText.count()).toBe(1); + expect(await paragraph.count()).toBe(3); + + await page.mouse.click(50, 50, { + delay: 100, + }); + await waitNextFrame(page); + await page.mouse.click(130, 140, { + delay: 100, + }); + await cutByKeyboard(page); + expect(await edgelessText.count()).toBe(0); + expect(await paragraph.count()).toBe(0); + + await pasteByKeyboard(page); + expect(await edgelessText.count()).toBe(1); + expect(await paragraph.count()).toBe(3); + }); + + test('latex in edgeless text', async ({ page }) => { + await setEdgelessTool(page, 'default'); + await page.mouse.dblclick(130, 140, { + delay: 100, + }); + await waitNextFrame(page); + await type(page, '$$bbb$$ '); + await assertRichTextInlineDeltas( + page, + [ + { + insert: ' ', + attributes: { + latex: 'bbb', + }, + }, + ], + 1 + ); + + await page.locator('affine-latex-node').click(); + await waitNextFrame(page); + await type(page, 'ccc'); + await assertRichTextInlineDeltas( + page, + [ + { + insert: ' ', + attributes: { + latex: 'bbbccc', + }, + }, + ], + 1 + ); + + await page.locator('.latex-editor-hint').click(); + await type(page, 'sss'); + await assertRichTextInlineDeltas( + page, + [ + { + insert: ' ', + attributes: { + latex: 'bbbccc', + }, + }, + ], + 1 + ); + await page.locator('latex-editor-unit').click(); + await selectAllByKeyboard(page); + await type(page, 'sss'); + await assertRichTextInlineDeltas( + page, + [ + { + insert: ' ', + attributes: { + latex: 'sss', + }, + }, + ], + 1 + ); + }); +}); + +test('press backspace at the start of first line when edgeless text exist', async ({ + page, +}, testInfo) => { + await enterPlaygroundRoom(page, { + flags: { + enable_edgeless_text: true, + }, + }); + await page.evaluate(() => { + const { doc } = window; + const rootId = doc.addBlock('affine:page', { + title: new doc.Text(), + }); + doc.addBlock('affine:surface', {}, rootId); + doc.addBlock('affine:note', {}, rootId); + + // do not add paragraph block + + doc.resetHistory(); + }); + await switchEditorMode(page); + + await setEdgelessTool(page, 'default'); + const point = await toViewCoord(page, [0, 0]); + await page.mouse.dblclick(point[0], point[1], { + delay: 100, + }); + await waitNextFrame(page); + await type(page, 'aaa'); + + await waitNextFrame(page); + await switchEditorMode(page); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_note_empty.json` + ); + + await page.locator('.affine-page-root-block-container').click(); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_note_not_empty.json` + ); + + await type(page, 'bbb'); + await pressArrowLeft(page, 3); + await pressBackspace(page); + await waitNextFrame(page); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_finial.json` + ); +}); diff --git a/blocksuite/tests-legacy/edgeless/element-toolbar.spec.ts b/blocksuite/tests-legacy/edgeless/element-toolbar.spec.ts new file mode 100644 index 0000000000000..777e7cd6a6053 --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/element-toolbar.spec.ts @@ -0,0 +1,88 @@ +import { expect } from '@playwright/test'; + +import { + addBasicRectShapeElement, + locatorComponentToolbar, + resizeElementByHandle, + selectNoteInEdgeless, + switchEditorMode, + zoomResetByKeyboard, +} from '../utils/actions/edgeless.js'; +import { + enterPlaygroundRoom, + initEmptyEdgelessState, +} from '../utils/actions/misc.js'; +import { test } from '../utils/playwright.js'; + +test('toolbar should appear when select note', async ({ page }) => { + await enterPlaygroundRoom(page); + const { noteId } = await initEmptyEdgelessState(page); + await switchEditorMode(page); + + await selectNoteInEdgeless(page, noteId); + + const toolbar = locatorComponentToolbar(page); + await expect(toolbar).toBeVisible(); +}); + +test('tooltip should be hidden after clicking on button', async ({ page }) => { + await enterPlaygroundRoom(page); + const { noteId } = await initEmptyEdgelessState(page); + await switchEditorMode(page); + + await selectNoteInEdgeless(page, noteId); + + const toolbar = locatorComponentToolbar(page); + const modeBtn = toolbar.getByRole('button', { name: 'Mode' }); + + await modeBtn.hover(); + await expect(page.locator('.blocksuite-portal')).toBeVisible(); + + await modeBtn.click(); + await expect(page.locator('.blocksuite-portal')).toBeHidden(); + await expect(page.locator('note-display-mode-panel')).toBeVisible(); + + await modeBtn.click(); + await expect(page.locator('.blocksuite-portal')).toBeVisible(); + await expect(page.locator('note-display-mode-panel')).toBeHidden(); + + await modeBtn.click(); + await expect(page.locator('.blocksuite-portal')).toBeHidden(); + await expect(page.locator('note-display-mode-panel')).toBeVisible(); + + const colorBtn = toolbar.getByRole('button', { + name: 'Background', + }); + + await colorBtn.hover(); + await expect(page.locator('.blocksuite-portal')).toBeVisible(); + + await colorBtn.click(); + await expect(page.locator('.blocksuite-portal')).toBeHidden(); + await expect(page.locator('note-display-mode-panel')).toBeHidden(); + await expect(page.locator('edgeless-color-panel')).toBeVisible(); +}); + +test('should be hidden when resizing element', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + await zoomResetByKeyboard(page); + + await addBasicRectShapeElement(page, { x: 210, y: 110 }, { x: 310, y: 210 }); + await page.mouse.click(220, 120); + + const toolbar = locatorComponentToolbar(page); + await expect(toolbar).toBeVisible(); + + await resizeElementByHandle(page, { x: 400, y: 300 }, 'top-left', 30); + + await page.mouse.move(450, 300); + await expect(toolbar).toBeEmpty(); + + await page.mouse.move(320, 220); + await expect(toolbar).toBeEmpty(); + + await page.mouse.up(); + await expect(toolbar).toBeVisible(); +}); diff --git a/blocksuite/tests-legacy/edgeless/eraser.spec.ts b/blocksuite/tests-legacy/edgeless/eraser.spec.ts new file mode 100644 index 0000000000000..cba83e8f8a23f --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/eraser.spec.ts @@ -0,0 +1,49 @@ +import { click } from '../utils/actions/click.js'; +import { dragBetweenCoords } from '../utils/actions/drag.js'; +import { + addBasicRectShapeElement, + deleteAll, + getNoteBoundBoxInEdgeless, + setEdgelessTool, + switchEditorMode, +} from '../utils/actions/edgeless.js'; +import { + enterPlaygroundRoom, + initEmptyEdgelessState, +} from '../utils/actions/misc.js'; +import { + assertBlockCount, + assertEdgelessNonSelectedRect, +} from '../utils/asserts.js'; +import { test } from '../utils/playwright.js'; + +test('erase shape', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + + await deleteAll(page); + + await addBasicRectShapeElement(page, { x: 0, y: 0 }, { x: 100, y: 100 }); + await setEdgelessTool(page, 'eraser'); + + await dragBetweenCoords(page, { x: 50, y: 150 }, { x: 50, y: 50 }); + await click(page, { x: 50, y: 50 }); + await assertEdgelessNonSelectedRect(page); +}); + +test('erase note', async ({ page }) => { + await enterPlaygroundRoom(page); + const { noteId } = await initEmptyEdgelessState(page); + await switchEditorMode(page); + await assertBlockCount(page, 'edgeless-note', 1); + + await setEdgelessTool(page, 'eraser'); + const box = await getNoteBoundBoxInEdgeless(page, noteId); + await dragBetweenCoords( + page, + { x: 0, y: 0 }, + { x: box.x + 10, y: box.y + 10 } + ); + await assertBlockCount(page, 'edgeless-note', 0); +}); diff --git a/blocksuite/tests-legacy/edgeless/frame/clipboard.spec.ts b/blocksuite/tests-legacy/edgeless/frame/clipboard.spec.ts new file mode 100644 index 0000000000000..929668ae0a37c --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/frame/clipboard.spec.ts @@ -0,0 +1,157 @@ +import type { Page } from '@playwright/test'; +import { clickView, moveView } from 'utils/actions/click.js'; +import { + autoFit, + createFrame as _createFrame, + createShapeElement, + deleteAll, + dragBetweenViewCoords, + edgelessCommonSetup, + getAllSortedIds, + getFirstContainerId, + getIds, + Shape, + shiftClickView, + triggerComponentToolbarAction, + zoomResetByKeyboard, +} from 'utils/actions/edgeless.js'; +import { + copyByKeyboard, + pasteByKeyboard, + pressBackspace, + pressEscape, +} from 'utils/actions/keyboard.js'; +import { assertContainerOfElements } from 'utils/asserts.js'; + +import { test } from '../../utils/playwright.js'; + +const createFrame = async ( + page: Page, + coord1: [number, number], + coord2: [number, number] +) => { + await _createFrame(page, coord1, coord2); + await autoFit(page); +}; + +test.beforeEach(async ({ page }) => { + await edgelessCommonSetup(page); + await zoomResetByKeyboard(page); +}); + +test.describe('frame copy and paste', () => { + test('copy of frame should keep relationship of child elements', async ({ + page, + }) => { + await createFrame(page, [50, 50], [450, 450]); + await createShapeElement(page, [200, 200], [300, 300], Shape.Square); + + const frameTitle = page.locator('affine-frame-title'); + + await pressEscape(page); + await frameTitle.click(); + await copyByKeyboard(page); + await deleteAll(page); + await moveView(page, [500, 500]); // center copy + await pasteByKeyboard(page); + + const frameId = await getFirstContainerId(page); + const shapeId = (await getAllSortedIds(page)).filter(id => id !== frameId); + await assertContainerOfElements(page, shapeId, frameId); + }); + + test('copy of frame by alt/option dragging should keep relationship of child elements', async ({ + page, + }) => { + await createFrame(page, [50, 50], [450, 450]); + await createShapeElement(page, [200, 200], [300, 300], Shape.Square); + await createShapeElement(page, [250, 250], [350, 350], Shape.Square); + await createShapeElement(page, [300, 300], [400, 400], Shape.Square); + await pressEscape(page); + + const frameTitles = page.locator('affine-frame-title'); + + await shiftClickView(page, [260, 260]); + await shiftClickView(page, [310, 310]); + await triggerComponentToolbarAction(page, 'addGroup'); + await pressEscape(page); + + await frameTitles.nth(0).click(); + await page.keyboard.down('Alt'); + await dragBetweenViewCoords(page, [60, 60], [460, 460]); + await page.keyboard.up('Alt'); + await pressEscape(page); + + await frameTitles.nth(0).click({ modifiers: ['Shift'] }); + await shiftClickView(page, [250, 250]); + await shiftClickView(page, [350, 350]); + await pressBackspace(page); // remove original elements + + const frameId = await getFirstContainerId(page); + const groupId = await getFirstContainerId(page, [frameId]); + const shapeIds = (await getIds(page)).filter( + id => ![frameId, groupId].includes(id) + ); + + await assertContainerOfElements(page, [groupId], frameId); + await assertContainerOfElements(page, [shapeIds[0]], frameId); + await assertContainerOfElements(page, [shapeIds[1]], groupId); + await assertContainerOfElements(page, [shapeIds[2]], groupId); + }); + + test('duplicate element in frame', async ({ page }) => { + await createFrame(page, [50, 50], [450, 450]); + await createShapeElement(page, [100, 100], [200, 200], Shape.Square); + await pressEscape(page); + + const frameTitles = page.locator('affine-frame-title'); + + await frameTitles.nth(0).click(); + await page.locator('edgeless-more-button').click(); + await page.locator('editor-menu-action', { hasText: 'Duplicate' }).click(); + await pressEscape(page); + + await frameTitles.nth(0).click(); + await shiftClickView(page, [150, 150]); + await pressBackspace(page); // remove original elements + + const frameId = await getFirstContainerId(page); + const shapeIds = (await getIds(page)).filter(id => id !== frameId); + await assertContainerOfElements(page, shapeIds, frameId); + }); + + test('copy of element by alt/option dragging in frame should belong to frame', async ({ + page, + }) => { + await createFrame(page, [50, 50], [450, 450]); + await createShapeElement(page, [100, 100], [200, 200], Shape.Square); + await pressEscape(page); + + await clickView(page, [150, 150]); + await page.keyboard.down('Alt'); + await dragBetweenViewCoords(page, [150, 150], [250, 250]); + await page.keyboard.up('Alt'); + + const frameId = await getFirstContainerId(page); + const shapeIds = (await getIds(page)).filter(id => id !== frameId); + await assertContainerOfElements(page, shapeIds, frameId); + }); + + test('copy of element by alt/option dragging out of frame should not belong to frame', async ({ + page, + }) => { + await createFrame(page, [50, 50], [450, 450]); + await createShapeElement(page, [100, 100], [200, 200], Shape.Square); + await pressEscape(page); + + await clickView(page, [150, 150]); + await page.keyboard.down('Alt'); + await dragBetweenViewCoords(page, [150, 150], [550, 550]); + await page.keyboard.up('Alt'); + + const frameId = await getFirstContainerId(page); + const shapeIds = (await getIds(page)).filter(id => id !== frameId); + await assertContainerOfElements(page, [shapeIds[0]], frameId); + await assertContainerOfElements(page, [shapeIds[1]], null); + }); +}); diff --git a/blocksuite/tests-legacy/edgeless/frame/frame-mindmap.spec.ts b/blocksuite/tests-legacy/edgeless/frame/frame-mindmap.spec.ts new file mode 100644 index 0000000000000..9c726685e6aa5 --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/frame/frame-mindmap.spec.ts @@ -0,0 +1,224 @@ +import type { Page } from '@playwright/test'; +import { clickView } from 'utils/actions/click.js'; +import { + createFrame, + dragBetweenViewCoords as _dragBetweenViewCoords, + edgelessCommonSetup, + getFirstContainerId, + getSelectedBound, + toViewCoord, + triggerComponentToolbarAction, + zoomResetByKeyboard, +} from 'utils/actions/edgeless.js'; +import { pressEscape } from 'utils/actions/keyboard.js'; +import { waitNextFrame } from 'utils/actions/misc.js'; +import { assertContainerOfElements } from 'utils/asserts.js'; + +import { test } from '../../utils/playwright.js'; + +const dragBetweenViewCoords = async ( + page: Page, + start: number[], + end: number[] +) => { + // dragging slowly may drop frame if mindmap is existed, so for test we drag quickly + await _dragBetweenViewCoords(page, start, end, { steps: 2 }); + await waitNextFrame(page); +}; + +test.beforeEach(async ({ page }) => { + await edgelessCommonSetup(page); + await zoomResetByKeyboard(page); +}); + +test('drag root node of mindmap into frame partially, then drag root node of mindmap out.', async ({ + page, +}) => { + await createFrame(page, [50, 50], [550, 550]); + await pressEscape(page); + const frameId = await getFirstContainerId(page); + + await triggerComponentToolbarAction(page, 'addMindmap'); + const mindmapId = await getFirstContainerId(page, [frameId]); + + // drag in + { + const mindmapBound = await getSelectedBound(page); + await clickView(page, [ + mindmapBound[0] + 10, + mindmapBound[1] + 0.5 * mindmapBound[3], + ]); + await dragBetweenViewCoords( + page, + [mindmapBound[0] + 10, mindmapBound[1] + 0.5 * mindmapBound[3]], + [100, 100] + ); + } + + await assertContainerOfElements(page, [mindmapId], frameId); + + // drag out + { + const mindmapBound = await getSelectedBound(page); + await clickView(page, [ + mindmapBound[0] + 10, + mindmapBound[1] + 0.5 * mindmapBound[3], + ]); + await dragBetweenViewCoords( + page, + [mindmapBound[0] + 10, mindmapBound[1] + 0.5 * mindmapBound[3]], + [-100, -100] + ); + } + + await assertContainerOfElements(page, [mindmapId], null); +}); + +test('drag root node of mindmap into frame fully, then drag root node of mindmap out.', async ({ + page, +}) => { + await createFrame(page, [50, 50], [550, 550]); + const frameId = await getFirstContainerId(page); + await pressEscape(page); + + await triggerComponentToolbarAction(page, 'addMindmap'); + const mindmapId = await getFirstContainerId(page, [frameId]); + + // drag in + { + const mindmapBound = await getSelectedBound(page); + + await clickView(page, [ + mindmapBound[0] + 10, + mindmapBound[1] + 0.5 * mindmapBound[3], + ]); + await dragBetweenViewCoords( + page, + [mindmapBound[0] + 10, mindmapBound[1] + 0.5 * mindmapBound[3]], + [100, 200] + ); + } + + await assertContainerOfElements(page, [mindmapId], frameId); + + // drag out + { + const mindmapBound = await getSelectedBound(page); + await clickView(page, [ + mindmapBound[0] + 10, + mindmapBound[1] + 0.5 * mindmapBound[3], + ]); + await dragBetweenViewCoords( + page, + [mindmapBound[0] + 10, mindmapBound[1] + 0.5 * mindmapBound[3]], + [-100, -100] + ); + } + + await assertContainerOfElements(page, [mindmapId], null); +}); + +test('drag whole mindmap into frame, then drag root node of mindmap out.', async ({ + page, +}) => { + await createFrame(page, [50, 50], [550, 550]); + const frameId = await getFirstContainerId(page); + await pressEscape(page); + + await triggerComponentToolbarAction(page, 'addMindmap'); + const mindmapId = await getFirstContainerId(page, [frameId]); + + // drag in + { + const mindmapBound = await getSelectedBound(page); + const rootNodePos = [ + mindmapBound[0] + 10, + mindmapBound[1] + 0.5 * mindmapBound[3], + ]; + await dragBetweenViewCoords(page, rootNodePos, [ + rootNodePos[0] - 20, + rootNodePos[1] + 200, + ]); + } + + await assertContainerOfElements(page, [mindmapId], frameId); + + // drag out + { + const mindmapBound = await getSelectedBound(page); + const rootNodePos = [ + mindmapBound[0] + 10, + mindmapBound[1] + 0.5 * mindmapBound[3], + ]; + + await dragBetweenViewCoords(page, rootNodePos, [-100, -100]); + } + + await assertContainerOfElements(page, [mindmapId], null); +}); + +test('add mindmap into frame, then drag root node of mindmap out.', async ({ + page, +}) => { + await createFrame(page, [50, 50], [550, 550]); + const frameId = await getFirstContainerId(page); + await pressEscape(page); + + const button = page.locator('edgeless-mindmap-tool-button'); + await button.click(); + await toViewCoord(page, [100, 200]); + await clickView(page, [100, 200]); + const mindmapId = await getFirstContainerId(page, [frameId]); + + await assertContainerOfElements(page, [mindmapId], frameId); + + // drag out + { + const mindmapBound = await getSelectedBound(page); + pressEscape(page); + await clickView(page, [ + mindmapBound[0] + 10, + mindmapBound[1] + 0.5 * mindmapBound[3], + ]); + await dragBetweenViewCoords( + page, + [mindmapBound[0] + 10, mindmapBound[1] + 0.5 * mindmapBound[3]], + [-20, -20] + ); + } + + await assertContainerOfElements(page, [mindmapId], null); +}); + +test('add mindmap out of frame and add new node in frame then drag frame', async ({ + page, +}) => { + await createFrame(page, [500, 50], [1000, 550]); + await pressEscape(page); + + const button = page.locator('edgeless-mindmap-tool-button'); + await button.click(); + await toViewCoord(page, [20, 200]); + await clickView(page, [20, 200]); + await waitNextFrame(page, 100); + const mindmapId = await getFirstContainerId(page); + + // add new node + { + const mindmapBound = await getSelectedBound(page); + await pressEscape(page); + await waitNextFrame(page, 500); + await clickView(page, [ + mindmapBound[2] - 50, + mindmapBound[1] + 0.5 * mindmapBound[3], + ]); + await waitNextFrame(page, 500); + await clickView(page, [ + mindmapBound[2] + 10, + mindmapBound[1] + 0.5 * mindmapBound[3], + ]); + await pressEscape(page, 2); + } + + await assertContainerOfElements(page, [mindmapId], null); +}); diff --git a/blocksuite/tests-legacy/edgeless/frame/frame-title.spec.ts b/blocksuite/tests-legacy/edgeless/frame/frame-title.spec.ts new file mode 100644 index 0000000000000..35b6f2e5823b8 --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/frame/frame-title.spec.ts @@ -0,0 +1,158 @@ +import { expect, type Page } from '@playwright/test'; +import { + addNote, + autoFit, + createFrame as _createFrame, + dragBetweenViewCoords, + edgelessCommonSetup, + getFrameTitle, + zoomOutByKeyboard, + zoomResetByKeyboard, +} from 'utils/actions/edgeless.js'; +import { + pressBackspace, + pressEnter, + pressEscape, + type, +} from 'utils/actions/keyboard.js'; +import { waitNextFrame } from 'utils/actions/misc.js'; + +import { test } from '../../utils/playwright.js'; + +const createFrame = async ( + page: Page, + coord1: [number, number], + coord2: [number, number] +) => { + const frame = await _createFrame(page, coord1, coord2); + await autoFit(page); + return frame; +}; + +test.beforeEach(async ({ page }) => { + await edgelessCommonSetup(page); + await zoomResetByKeyboard(page); +}); + +const enterFrameTitleEditor = async (page: Page) => { + const frameTitle = page.locator('affine-frame-title'); + await frameTitle.dblclick(); + + const frameTitleEditor = page.locator('edgeless-frame-title-editor'); + await frameTitleEditor.waitFor({ + state: 'attached', + }); + return frameTitleEditor; +}; + +test.describe('frame title rendering', () => { + test('frame title should be displayed', async ({ page }) => { + const frame = await createFrame(page, [50, 50], [150, 150]); + + const frameTitle = getFrameTitle(page, frame); + await expect(frameTitle).toBeVisible(); + await expect(frameTitle).toHaveText('Frame 1'); + }); + + test('frame title should be rendered on the top', async ({ page }) => { + const frame = await createFrame(page, [50, 50], [150, 150]); + + const frameTitle = getFrameTitle(page, frame); + await expect(frameTitle).toBeVisible(); + + const frameTitleBounding = await frameTitle.boundingBox(); + expect(frameTitleBounding).not.toBeNull(); + if (!frameTitleBounding) return; + + const frameTitleCenter = [ + frameTitleBounding.x + frameTitleBounding.width / 2, + frameTitleBounding.y + frameTitleBounding.height / 2, + ]; + + await addNote(page, '', frameTitleCenter[0], frameTitleCenter[1]); + await pressEscape(page, 3); + await waitNextFrame(page, 500); + + try { + // if the frame title is rendered on the top, it should be clickable + await frameTitle.click(); + } catch { + expect(true, 'frame title should be rendered on the top').toBeFalsy(); + } + }); + + test('should not display frame title component when title is empty', async ({ + page, + }) => { + const frame = await createFrame(page, [50, 50], [150, 150]); + await enterFrameTitleEditor(page); + + await pressBackspace(page); + await pressEnter(page); + const frameTitle = getFrameTitle(page, frame); + await expect(frameTitle).toBeHidden(); + }); +}); + +test.describe('frame title editing', () => { + test('edit frame title by db-click title', async ({ page }) => { + const frame = await createFrame(page, [50, 50], [150, 150]); + const frameTitle = getFrameTitle(page, frame); + + await enterFrameTitleEditor(page); + + await type(page, 'ABC'); + await pressEnter(page); + await expect(frameTitle).toHaveText('ABC'); + }); + + test('frame title can be edited repeatedly', async ({ page }) => { + const frame = await createFrame(page, [50, 50], [150, 150]); + const frameTitle = getFrameTitle(page, frame); + + await enterFrameTitleEditor(page); + await type(page, 'ABC'); + await pressEnter(page); + + await enterFrameTitleEditor(page); + await type(page, 'DEF'); + await pressEnter(page); + await expect(frameTitle).toHaveText('DEF'); + }); + + test('edit frame after zoom', async ({ page }) => { + const frame = await createFrame(page, [50, 50], [150, 150]); + const frameTitle = getFrameTitle(page, frame); + + await zoomOutByKeyboard(page); + await enterFrameTitleEditor(page); + await type(page, 'ABC'); + await pressEnter(page); + await expect(frameTitle).toHaveText('ABC'); + }); + + test('edit frame title after drag', async ({ page }) => { + const frame = await createFrame(page, [50, 50], [150, 150]); + const frameTitle = getFrameTitle(page, frame); + await dragBetweenViewCoords(page, [50 + 10, 50 + 10], [50 + 20, 50 + 20]); + + await enterFrameTitleEditor(page); + await type(page, 'ABC'); + await pressEnter(page); + await expect(frameTitle).toHaveText('ABC'); + }); + + test('blur unmount frame editor', async ({ page }) => { + await createFrame(page, [50, 50], [150, 150]); + const frameTitleEditor = await enterFrameTitleEditor(page); + await page.mouse.click(10, 10); + await expect(frameTitleEditor).toHaveCount(0); + }); + + test('enter unmount frame editor', async ({ page }) => { + await createFrame(page, [50, 50], [150, 150]); + const frameTitleEditor = await enterFrameTitleEditor(page); + await pressEnter(page); + await expect(frameTitleEditor).toHaveCount(0); + }); +}); diff --git a/blocksuite/tests-legacy/edgeless/frame/frame.spec.ts b/blocksuite/tests-legacy/edgeless/frame/frame.spec.ts new file mode 100644 index 0000000000000..088a15bb9b79f --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/frame/frame.spec.ts @@ -0,0 +1,414 @@ +import { + DEFAULT_NOTE_HEIGHT, + DEFAULT_NOTE_WIDTH, +} from '@blocksuite/affine-model'; +import { Bound } from '@blocksuite/global/utils'; +import { expect, type Page } from '@playwright/test'; + +import { clickView } from '../../utils/actions/click.js'; +import { + addNote, + autoFit, + createFrame as _createFrame, + createShapeElement, + dragBetweenViewCoords, + edgelessCommonSetup, + getFirstContainerId, + getIds, + getSelectedBound, + getSelectedIds, + pickColorAtPoints, + setEdgelessTool, + Shape, + shiftClickView, + toViewCoord, + triggerComponentToolbarAction, + zoomResetByKeyboard, +} from '../../utils/actions/edgeless.js'; +import { + pressBackspace, + pressEscape, + SHORT_KEY, +} from '../../utils/actions/keyboard.js'; +import { + assertCanvasElementsCount, + assertContainerChildCount, + assertEdgelessElementBound, + assertSelectedBound, +} from '../../utils/asserts.js'; +import { test } from '../../utils/playwright.js'; + +const createFrame = async ( + page: Page, + coord1: [number, number], + coord2: [number, number] +) => { + const frameId = await _createFrame(page, coord1, coord2); + await autoFit(page); + await pressEscape(page); + return frameId; +}; + +test.beforeEach(async ({ page }) => { + await edgelessCommonSetup(page); + await zoomResetByKeyboard(page); +}); + +test.describe('add a frame', () => { + const createThreeShapesAndSelectTowShape = async (page: Page) => { + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await createShapeElement(page, [100, 0], [200, 100], Shape.Square); + await createShapeElement(page, [200, 0], [300, 100], Shape.Square); + + await clickView(page, [50, 50]); + await shiftClickView(page, [150, 50]); + }; + + test('multi select and add frame by shortcut F', async ({ page }) => { + await createThreeShapesAndSelectTowShape(page); + await page.keyboard.press('f'); + + await expect(page.locator('affine-frame')).toHaveCount(1); + await assertSelectedBound(page, [-40, -40, 280, 180]); + + const frameId = await getFirstContainerId(page); + await assertContainerChildCount(page, frameId, 2); + }); + + test('multi select and add frame by component toolbar', async ({ page }) => { + await createThreeShapesAndSelectTowShape(page); + await triggerComponentToolbarAction(page, 'addFrame'); + + await expect(page.locator('affine-frame')).toHaveCount(1); + await assertSelectedBound(page, [-40, -40, 280, 180]); + + const frameId = await getFirstContainerId(page); + await assertContainerChildCount(page, frameId, 2); + }); + + test('multi select and add frame by more option create frame', async ({ + page, + }) => { + await createThreeShapesAndSelectTowShape(page); + await triggerComponentToolbarAction(page, 'createFrameOnMoreOption'); + + await expect(page.locator('affine-frame')).toHaveCount(1); + await assertSelectedBound(page, [-40, -40, 280, 180]); + + const frameId = await getFirstContainerId(page); + await assertContainerChildCount(page, frameId, 2); + }); + + test('multi select add frame by edgeless toolbar', async ({ page }) => { + await createThreeShapesAndSelectTowShape(page); + await autoFit(page); + await setEdgelessTool(page, 'frame'); + const frameMenu = page.locator('edgeless-frame-menu'); + await expect(frameMenu).toBeVisible(); + const button = page.locator('.frame-add-button[data-name="1:1"]'); + await button.click(); + await assertSelectedBound(page, [-450, -550, 1200, 1200]); + + // the third should be inner frame because + const frameId = await getFirstContainerId(page); + await assertContainerChildCount(page, frameId, 3); + }); + + test('add frame by dragging with shortcut F', async ({ page }) => { + await createThreeShapesAndSelectTowShape(page); + await pressEscape(page); // unselect + + await page.keyboard.press('f'); + await dragBetweenViewCoords(page, [-10, -10], [210, 110]); + + await expect(page.locator('affine-frame')).toHaveCount(1); + await assertSelectedBound(page, [-10, -10, 220, 120]); + + const frameId = await getFirstContainerId(page); + await assertContainerChildCount(page, frameId, 2); + }); + + test('add inner frame', async ({ page }) => { + await createFrame(page, [50, 50], [450, 450]); + await createShapeElement(page, [200, 200], [300, 300], Shape.Square); + await pressEscape(page); + + await shiftClickView(page, [250, 250]); + await page.keyboard.press('f'); + const innerFrameBound = await getSelectedBound(page); + expect( + new Bound(50, 50, 400, 400).contains(Bound.fromXYWH(innerFrameBound)) + ).toBeTruthy(); + }); +}); + +test.describe('add element to frame and then move frame', () => { + test.describe('add single element', () => { + test('element should be moved since it is created in frame', async ({ + page, + }) => { + const frameId = await createFrame(page, [50, 50], [550, 550]); + const shapeId = await createShapeElement( + page, + [100, 100], + [200, 200], + Shape.Square + ); + + const noteCoord = await toViewCoord(page, [200, 200]); + const noteId = await addNote(page, '', noteCoord[0], noteCoord[1]); + + const frameTitle = page.locator('affine-frame-title'); + + await pressEscape(page); + + await frameTitle.click(); + await dragBetweenViewCoords(page, [60, 60], [110, 110]); + + await assertEdgelessElementBound(page, shapeId, [150, 150, 100, 100]); + await assertEdgelessElementBound(page, frameId, [100, 100, 500, 500]); + await assertEdgelessElementBound(page, noteId, [ + 220, + 210, + DEFAULT_NOTE_WIDTH, + DEFAULT_NOTE_HEIGHT, + ]); + }); + + test('element should be not moved since it is created not in frame', async ({ + page, + }) => { + const frameId = await createFrame(page, [50, 50], [550, 550]); + const shapeId = await createShapeElement( + page, + [600, 600], + [500, 500], + Shape.Square + ); + await pressEscape(page); + + const frameTitle = page.locator('affine-frame-title'); + + await frameTitle.click(); + await dragBetweenViewCoords(page, [60, 60], [110, 110]); + + await assertEdgelessElementBound(page, shapeId, [500, 500, 100, 100]); + await assertEdgelessElementBound(page, frameId, [100, 100, 500, 500]); + }); + }); + + test.describe('add group', () => { + // Group + // |<150px>| + // ┌────┐ ─ + // │ ┌─┼──┐ 150 px + // └──┼─┘ │ | + // └────┘ ─ + + test('group should be moved since it is fully contained in frame', async ({ + page, + }) => { + const [frameId, ...shapeIds] = [ + await createFrame(page, [50, 50], [550, 550]), + await createShapeElement(page, [100, 100], [200, 200], Shape.Square), + await createShapeElement(page, [150, 150], [250, 250], Shape.Square), + ]; + await pressEscape(page); + + const frameTitle = page.locator('affine-frame-title'); + + await shiftClickView(page, [110, 110]); + await shiftClickView(page, [160, 160]); + await page.keyboard.press(`${SHORT_KEY}+g`); + const groupId = (await getSelectedIds(page))[0]; + await pressEscape(page); + + await frameTitle.click(); + await dragBetweenViewCoords(page, [60, 60], [110, 110]); + + await assertEdgelessElementBound(page, shapeIds[0], [150, 150, 100, 100]); + await assertEdgelessElementBound(page, shapeIds[1], [200, 200, 100, 100]); + await assertEdgelessElementBound(page, groupId, [150, 150, 150, 150]); + await assertEdgelessElementBound(page, frameId, [100, 100, 500, 500]); + }); + + test('group should be moved since its center is in frame', async ({ + page, + }) => { + const [frameId, ...shapeIds] = [ + await createFrame(page, [50, 50], [550, 550]), + await createShapeElement(page, [450, 450], [550, 550], Shape.Square), + await createShapeElement(page, [500, 500], [600, 600], Shape.Square), + ]; + await pressEscape(page); + + const frameTitle = page.locator('affine-frame-title'); + + await shiftClickView(page, [460, 460]); + await shiftClickView(page, [510, 510]); + await page.keyboard.press(`${SHORT_KEY}+g`); + const groupId = (await getSelectedIds(page))[0]; + await pressEscape(page); + + await frameTitle.click(); + await dragBetweenViewCoords(page, [60, 60], [110, 110]); + + await assertEdgelessElementBound(page, shapeIds[0], [500, 500, 100, 100]); + await assertEdgelessElementBound(page, shapeIds[1], [550, 550, 100, 100]); + await assertEdgelessElementBound(page, groupId, [500, 500, 150, 150]); + await assertEdgelessElementBound(page, frameId, [100, 100, 500, 500]); + }); + }); + + test.describe('add inner frame', () => { + test('the inner frame and its children should be moved since it is fully contained in frame', async ({ + page, + }) => { + const [frameId, innerId, shapeId] = [ + await createFrame(page, [50, 50], [550, 550]), + await createFrame(page, [100, 100], [300, 300]), + await createShapeElement(page, [150, 150], [250, 250], Shape.Square), + ]; + await pressEscape(page); + + const frameTitles = page.locator('affine-frame-title'); + + await frameTitles.nth(0).click(); + await dragBetweenViewCoords(page, [60, 60], [110, 110]); + + await assertEdgelessElementBound(page, shapeId, [200, 200, 100, 100]); + await assertEdgelessElementBound(page, innerId, [150, 150, 200, 200]); + await assertEdgelessElementBound(page, frameId, [100, 100, 500, 500]); + }); + + test('the inner frame and its children should be moved since its center is in frame', async ({ + page, + }) => { + const [frameId, innerId, shapeId] = [ + await createFrame(page, [50, 50], [550, 550]), + await createFrame(page, [400, 400], [600, 600]), + await createShapeElement(page, [550, 550], [600, 600], Shape.Square), + ]; + await pressEscape(page); + + const frameTitles = page.locator('affine-frame-title'); + + await frameTitles.nth(0).click(); + await dragBetweenViewCoords(page, [60, 60], [110, 110]); + + await assertEdgelessElementBound(page, shapeId, [600, 600, 50, 50]); + await assertEdgelessElementBound(page, innerId, [450, 450, 200, 200]); + await assertEdgelessElementBound(page, frameId, [100, 100, 500, 500]); + }); + + test('the inner frame and its children should also be moved even though its center is not in frame', async ({ + page, + }) => { + const [frameId, innerId, shapeId] = [ + await createFrame(page, [50, 50], [550, 550]), + await createFrame(page, [500, 500], [600, 600]), + await createShapeElement(page, [550, 550], [600, 600], Shape.Square), + ]; + + const frameTitles = page.locator('affine-frame-title'); + + await frameTitles.nth(0).click(); + await dragBetweenViewCoords(page, [60, 60], [110, 110]); + + await assertEdgelessElementBound(page, shapeId, [600, 600, 50, 50]); + await assertEdgelessElementBound(page, innerId, [550, 550, 100, 100]); + await assertEdgelessElementBound(page, frameId, [100, 100, 500, 500]); + }); + }); +}); + +test.describe('resize frame then move ', () => { + test('resize frame to warp shape', async ({ page }) => { + const [frameId, shapeId] = [ + await createFrame(page, [50, 50], [150, 150]), + await createShapeElement(page, [200, 200], [300, 300], Shape.Square), + ]; + await pressEscape(page); + + const frameTitle = page.locator('affine-frame-title'); + + await frameTitle.click(); + await dragBetweenViewCoords(page, [150, 150], [450, 450]); + + await dragBetweenViewCoords(page, [60, 60], [110, 110]); + + await assertEdgelessElementBound(page, shapeId, [250, 250, 100, 100]); + await assertEdgelessElementBound(page, frameId, [100, 100, 400, 400]); + }); + + test('resize frame to unwrap shape', async ({ page }) => { + const [frameId, shapeId] = [ + await createFrame(page, [50, 50], [450, 450]), + await createShapeElement(page, [200, 200], [300, 300], Shape.Square), + ]; + await pressEscape(page); + + const frameTitle = page.locator('affine-frame-title'); + + await frameTitle.click(); + await dragBetweenViewCoords(page, [450, 450], [150, 150]); + + await dragBetweenViewCoords(page, [60, 60], [110, 110]); + + await assertEdgelessElementBound(page, shapeId, [200, 200, 100, 100]); + await assertEdgelessElementBound(page, frameId, [100, 100, 100, 100]); + }); +}); + +test('delete frame should also delete its children', async ({ page }) => { + await createFrame(page, [50, 50], [450, 450]); + await createShapeElement(page, [200, 200], [300, 300], Shape.Square); + await pressEscape(page); + + const frameTitle = page.locator('affine-frame-title'); + + await frameTitle.click(); + await pressBackspace(page); + await expect(page.locator('affine-frame')).toHaveCount(0); + + await assertCanvasElementsCount(page, 0); +}); + +test('delete frame by click ungroup should not delete its children', async ({ + page, +}) => { + await createFrame(page, [50, 50], [450, 450]); + const shapeId = await createShapeElement( + page, + [200, 200], + [300, 300], + Shape.Square + ); + await pressEscape(page); + + const frameTitle = page.locator('affine-frame-title'); + await frameTitle.click(); + const elementToolbar = page.locator('edgeless-element-toolbar-widget'); + const ungroupButton = elementToolbar.getByLabel('Ungroup'); + await ungroupButton.click(); + + await assertCanvasElementsCount(page, 1); + expect(await getIds(page)).toEqual([shapeId]); +}); + +test('outline should keep updated during a new frame created by frame-tool dragging', async ({ + page, +}) => { + await page.keyboard.press('f'); + + const start = await toViewCoord(page, [0, 0]); + const end = await toViewCoord(page, [100, 100]); + await page.mouse.move(start[0], start[1]); + await page.mouse.down(); + await page.mouse.move(end[0], end[1], { steps: 10 }); + await page.waitForTimeout(50); + + expect( + await pickColorAtPoints(page, [start, [end[0] - 1, end[1] - 1]]) + ).toEqual(['#1e96eb', '#1e96eb']); +}); diff --git a/blocksuite/tests-legacy/edgeless/frame/layer.spec.ts b/blocksuite/tests-legacy/edgeless/frame/layer.spec.ts new file mode 100644 index 0000000000000..025af14ae2d65 --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/frame/layer.spec.ts @@ -0,0 +1,64 @@ +import { expect } from '@playwright/test'; +import { + createFrame, + createNote, + createShapeElement, + edgelessCommonSetup, + getAllSortedIds, + getEdgelessSelectedRectModel, + Shape, + zoomResetByKeyboard, +} from 'utils/actions/edgeless.js'; +import { pressEscape, selectAllByKeyboard } from 'utils/actions/keyboard.js'; + +import { test } from '../../utils/playwright.js'; + +test.beforeEach(async ({ page }) => { + await edgelessCommonSetup(page); + await zoomResetByKeyboard(page); +}); + +test.describe('layer logic of frame block', () => { + test('a new frame should be on the bottom layer', async ({ page }) => { + const shapeId = await createShapeElement( + page, + [100, 100], + [200, 200], + Shape.Square + ); + const noteId = await createNote(page, [200, 200]); + await pressEscape(page, 3); + + await selectAllByKeyboard(page); + const [x, y, w, h] = await getEdgelessSelectedRectModel(page); + await pressEscape(page); + const frameAId = await createFrame( + page, + [x - 10, y - 10], + [x + w + 10, y + h + 10] + ); + + let sortedIds = await getAllSortedIds(page); + expect( + sortedIds[0], + 'a new frame created by frame-tool should be on the bottom layer' + ).toBe(frameAId); + expect(sortedIds[1]).toBe(shapeId); + expect(sortedIds[2]).toBe(noteId); + + await selectAllByKeyboard(page); + await page.keyboard.press('f'); + + sortedIds = await getAllSortedIds(page); + const frameBId = sortedIds.find( + id => ![frameAId, noteId, shapeId].includes(id) + ); + expect( + sortedIds[0], + 'a new frame created by short-cut should also be on the bottom layer' + ).toBe(frameBId); + expect(sortedIds[1]).toBe(frameAId); + expect(sortedIds[2]).toBe(shapeId); + expect(sortedIds[3]).toBe(noteId); + }); +}); diff --git a/blocksuite/tests-legacy/edgeless/frame/selection.spec.ts b/blocksuite/tests-legacy/edgeless/frame/selection.spec.ts new file mode 100644 index 0000000000000..626d0acbd7ec6 --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/frame/selection.spec.ts @@ -0,0 +1,158 @@ +import { expect, type Page } from '@playwright/test'; +import { click, clickView, dblclickView } from 'utils/actions/click.js'; +import { + addNote, + autoFit, + createFrame as _createFrame, + createShapeElement, + dragBetweenViewCoords, + edgelessCommonSetup, + getFrameTitle, + getSelectedBoundCount, + getSelectedIds, + Shape, + toViewCoord, + zoomResetByKeyboard, +} from 'utils/actions/edgeless.js'; +import { + pressBackspace, + pressEnter, + pressEscape, + selectAllByKeyboard, + type, +} from 'utils/actions/keyboard.js'; +import { waitNextFrame } from 'utils/actions/misc.js'; +import { + assertEdgelessCanvasText, + assertRichTexts, + assertSelectedBound, +} from 'utils/asserts.js'; + +import { test } from '../../utils/playwright.js'; + +const createFrame = async ( + page: Page, + coord1: [number, number], + coord2: [number, number] +) => { + const frame = await _createFrame(page, coord1, coord2); + await autoFit(page); + return frame; +}; + +test.beforeEach(async ({ page }) => { + await edgelessCommonSetup(page); + await zoomResetByKeyboard(page); +}); + +test.describe('frame selection', () => { + test('frame can not be selected by click blank area of frame if it has title', async ({ + page, + }) => { + await createFrame(page, [50, 50], [150, 150]); + await pressEscape(page); + expect(await getSelectedBoundCount(page)).toBe(0); + + await clickView(page, [100, 100]); + expect(await getSelectedBoundCount(page)).toBe(0); + }); + + test('frame can selected by click blank area of frame if it has not title', async ({ + page, + }) => { + await createFrame(page, [50, 50], [150, 150]); + await pressEscape(page); + expect(await getSelectedBoundCount(page)).toBe(0); + + await page.locator('affine-frame-title').dblclick(); + await pressBackspace(page); + await pressEnter(page); + + await clickView(page, [100, 100]); + expect(await getSelectedBoundCount(page)).toBe(1); + }); + + test('frame can be selected by click frame title', async ({ page }) => { + const frame = await createFrame(page, [50, 50], [150, 150]); + await pressEscape(page); + expect(await getSelectedBoundCount(page)).toBe(0); + + const frameTitle = getFrameTitle(page, frame); + await frameTitle.click(); + + expect(await getSelectedBoundCount(page)).toBe(1); + await assertSelectedBound(page, [50, 50, 100, 100]); + }); + + test('frame can be selected by click frame title when a note overlap on it', async ({ + page, + }) => { + const frame = await createFrame(page, [50, 50], [150, 150]); + await pressEscape(page); + + const frameTitle = getFrameTitle(page, frame); + const frameTitleBox = await frameTitle.boundingBox(); + expect(frameTitleBox).not.toBeNull(); + if (frameTitleBox === null) return; + + const frameTitleCenter = { + x: frameTitleBox.x + frameTitleBox.width / 2, + y: frameTitleBox.y + frameTitleBox.height / 2, + }; + + await addNote(page, '', frameTitleCenter.x - 10, frameTitleCenter.y); + await pressEscape(page, 3); + await waitNextFrame(page, 500); + expect(await getSelectedBoundCount(page)).toBe(0); + + await click(page, frameTitleCenter); + expect(await getSelectedBoundCount(page)).toBe(1); + const selectedIds = await getSelectedIds(page); + expect(selectedIds.length).toBe(1); + expect(selectedIds[0]).toBe(frame); + }); + + test('shape inside frame can be selected and edited', async ({ page }) => { + await createFrame(page, [50, 50], [150, 150]); + await createShapeElement(page, [100, 100], [200, 200], Shape.Square); + await pressEscape(page); + + await clickView(page, [150, 150]); + expect(await getSelectedBoundCount(page)).toBe(1); + await assertSelectedBound(page, [100, 100, 100, 100]); + + await dblclickView(page, [150, 150]); + await type(page, 'hello'); + await assertEdgelessCanvasText(page, 'hello'); + }); + + test('dom inside frame can be selected and edited', async ({ page }) => { + await createFrame(page, [50, 50], [150, 150]); + const noteCoord = await toViewCoord(page, [100, 100]); + await addNote(page, '', noteCoord[0], noteCoord[1]); + await page.mouse.click(noteCoord[0] - 80, noteCoord[1]); + + await dblclickView(page, [150, 150]); + await type(page, 'hello'); + await assertRichTexts(page, ['hello']); + }); + + test('element in frame should not be selected when frame is selected by drag or Cmd/Ctrl + A', async ({ + page, + }) => { + await createFrame(page, [50, 50], [200, 200]); + await createShapeElement(page, [100, 100], [150, 150], Shape.Square); + await pressEscape(page); + + await dragBetweenViewCoords(page, [0, 0], [250, 250]); + expect(await getSelectedBoundCount(page)).toBe(1); + await assertSelectedBound(page, [50, 50, 150, 150]); + + await pressEscape(page); + expect(await getSelectedBoundCount(page)).toBe(0); + + await selectAllByKeyboard(page); + expect(await getSelectedBoundCount(page)).toBe(1); + await assertSelectedBound(page, [50, 50, 150, 150]); + }); +}); diff --git a/blocksuite/tests-legacy/edgeless/group/clipboard.spec.ts b/blocksuite/tests-legacy/edgeless/group/clipboard.spec.ts new file mode 100644 index 0000000000000..0f8a4c1f10349 --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/group/clipboard.spec.ts @@ -0,0 +1,152 @@ +import { expect } from '@playwright/test'; + +import { + copyByKeyboard, + createConnectorElement, + createNote, + createShapeElement, + decreaseZoomLevel, + edgelessCommonSetup as commonSetup, + edgelessCommonSetup, + getAllSortedIds, + getFirstContainerId, + pasteByKeyboard, + selectAllByKeyboard, + Shape, + toViewCoord, + triggerComponentToolbarAction, + waitNextFrame, +} from '../../utils/actions/index.js'; +import { + assertContainerChildCount, + assertContainerIds, +} from '../../utils/asserts.js'; +import { test } from '../../utils/playwright.js'; + +test.describe('clipboard', () => { + test('copy and paste group', async ({ page }) => { + await edgelessCommonSetup(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await createShapeElement(page, [100, 0], [200, 100], Shape.Square); + await selectAllByKeyboard(page); + await triggerComponentToolbarAction(page, 'addGroup'); + const originGroupId = await getFirstContainerId(page); + + await copyByKeyboard(page); + await waitNextFrame(page, 100); + const move = await toViewCoord(page, [100, -50]); + await page.mouse.click(move[0], move[1]); + await waitNextFrame(page, 1000); + await pasteByKeyboard(page, false); + const copyedGroupId = await getFirstContainerId(page, [originGroupId]); + + await assertContainerIds(page, { + [originGroupId]: 2, + [copyedGroupId]: 2, + null: 2, + }); + await assertContainerChildCount(page, originGroupId, 2); + await assertContainerChildCount(page, copyedGroupId, 2); + }); + + test('copy and paste group with connector', async ({ page }) => { + await edgelessCommonSetup(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await createShapeElement(page, [100, 0], [200, 100], Shape.Square); + await createConnectorElement(page, [100, 50], [200, 50]); + await selectAllByKeyboard(page); + await triggerComponentToolbarAction(page, 'addGroup'); + const originGroupId = await getFirstContainerId(page); + + await copyByKeyboard(page); + await waitNextFrame(page, 100); + const move = await toViewCoord(page, [100, -50]); + await page.mouse.click(move[0], move[1]); + await waitNextFrame(page, 1000); + await pasteByKeyboard(page, false); + const copyedGroupId = await getFirstContainerId(page, [originGroupId]); + + await assertContainerIds(page, { + [originGroupId]: 3, + [copyedGroupId]: 3, + null: 2, + }); + await assertContainerChildCount(page, originGroupId, 3); + await assertContainerChildCount(page, copyedGroupId, 3); + }); +}); + +test.describe('group clipboard', () => { + test('copy and paste group with shape and note inside', async ({ page }) => { + await commonSetup(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await createNote(page, [100, -100]); + await page.mouse.click(10, 50); + + await selectAllByKeyboard(page); + await triggerComponentToolbarAction(page, 'addGroup'); + + const originIds = await getAllSortedIds(page); + expect(originIds.length).toBe(3); + + await copyByKeyboard(page); + const move = await toViewCoord(page, [250, 250]); + await page.mouse.move(move[0], move[1]); + await page.mouse.click(move[0], move[1]); + await pasteByKeyboard(page, true); + await waitNextFrame(page, 500); + const sortedIds = await getAllSortedIds(page); + expect(sortedIds.length).toBe(6); + }); + + test('copy and paste group with group inside', async ({ page }) => { + await commonSetup(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await createShapeElement(page, [200, 0], [300, 100], Shape.Square); + await selectAllByKeyboard(page); + await triggerComponentToolbarAction(page, 'addGroup'); + + await createNote(page, [100, -100]); + await page.mouse.click(10, 50); + await selectAllByKeyboard(page); + await triggerComponentToolbarAction(page, 'createGroupOnMoreOption'); + + const originIds = await getAllSortedIds(page); + expect(originIds.length).toBe(5); + + await copyByKeyboard(page); + const move = await toViewCoord(page, [250, 250]); + await page.mouse.move(move[0], move[1]); + await page.mouse.click(move[0], move[1]); + await pasteByKeyboard(page, true); + await waitNextFrame(page, 500); + const sortedIds = await getAllSortedIds(page); + expect(sortedIds.length).toBe(10); + }); + + test('copy and paste group with frame inside', async ({ page }) => { + await commonSetup(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await createNote(page, [100, -100]); + await page.mouse.click(10, 50); + await selectAllByKeyboard(page); + await triggerComponentToolbarAction(page, 'addFrame'); + + await decreaseZoomLevel(page); + await createShapeElement(page, [700, 0], [800, 100], Shape.Square); + await selectAllByKeyboard(page); + await triggerComponentToolbarAction(page, 'addGroup'); + + const originIds = await getAllSortedIds(page); + expect(originIds.length).toBe(5); + + await copyByKeyboard(page); + const move = await toViewCoord(page, [250, 250]); + await page.mouse.move(move[0], move[1]); + await page.mouse.click(move[0], move[1]); + await pasteByKeyboard(page, true); + await waitNextFrame(page, 500); + const sortedIds = await getAllSortedIds(page); + expect(sortedIds.length).toBe(10); + }); +}); diff --git a/blocksuite/tests-legacy/edgeless/group/group-and-ungroup.spec.ts b/blocksuite/tests-legacy/edgeless/group/group-and-ungroup.spec.ts new file mode 100644 index 0000000000000..cd18fe9229fa9 --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/group/group-and-ungroup.spec.ts @@ -0,0 +1,123 @@ +import type { Page } from '@playwright/test'; + +import { + captureHistory, + clickView, + createShapeElement, + edgelessCommonSetup, + getFirstContainerId, + getIds, + redoByKeyboard, + selectAllByKeyboard, + Shape, + shiftClickView, + toIdCountMap, + triggerComponentToolbarAction, + undoByKeyboard, +} from '../../utils/actions/index.js'; +import { + assertContainerChildCount, + assertContainerChildIds, + assertContainerIds, + assertSelectedBound, +} from '../../utils/asserts.js'; +import { test } from '../../utils/playwright.js'; + +let initShapes: string[] = []; +async function init(page: Page) { + await edgelessCommonSetup(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await createShapeElement(page, [100, 0], [200, 100], Shape.Square); + initShapes = await getIds(page); +} + +test.describe('group and ungroup in group', () => { + let outterGroupId: string; + let newAddedShape: string; + + test.beforeEach(async ({ page }) => { + await init(page); + await createShapeElement(page, [200, 0], [300, 100], Shape.Square); + newAddedShape = (await getIds(page)).filter( + id => !initShapes.includes(id) + )[0]; + await selectAllByKeyboard(page); + await triggerComponentToolbarAction(page, 'addGroup'); + outterGroupId = await getFirstContainerId(page); + }); + + test('group in group', async ({ page }) => { + await clickView(page, [50, 50]); + await shiftClickView(page, [150, 50]); + await captureHistory(page); + await triggerComponentToolbarAction(page, 'addGroup'); + const groupId = await getFirstContainerId(page, [outterGroupId]); + await assertSelectedBound(page, [0, 0, 200, 100]); + await assertContainerIds(page, { + [groupId]: 2, + [outterGroupId]: 2, + null: 1, + }); + await assertContainerChildCount(page, groupId, 2); + await assertContainerChildCount(page, outterGroupId, 2); + + // undo the creation + await undoByKeyboard(page); + await assertContainerIds(page, { + [outterGroupId]: 3, + null: 1, + }); + await assertContainerChildCount(page, outterGroupId, 3); + + // redo the creation + await redoByKeyboard(page); + await assertContainerIds(page, { + [groupId]: 2, + [outterGroupId]: 2, + null: 1, + }); + await assertContainerChildCount(page, groupId, 2); + await assertContainerChildCount(page, outterGroupId, 2); + }); + + test('ungroup in group', async ({ page }) => { + await clickView(page, [50, 50]); + await shiftClickView(page, [150, 50]); + await triggerComponentToolbarAction(page, 'addGroup'); + await captureHistory(page); + const groupId = await getFirstContainerId(page, [outterGroupId]); + await triggerComponentToolbarAction(page, 'ungroup'); + await assertContainerIds(page, { [outterGroupId]: 3, null: 1 }); + await assertContainerChildIds( + page, + toIdCountMap(await getIds(page, true)), + outterGroupId + ); + + // undo, group should in group again + await undoByKeyboard(page); + await assertContainerIds(page, { + [outterGroupId]: 2, + [groupId]: 2, + null: 1, + }); + await assertContainerChildIds(page, toIdCountMap(initShapes), groupId); + await assertContainerChildIds( + page, + { + [groupId]: 1, + [newAddedShape]: 1, + }, + outterGroupId + ); + + // redo, group should be ungroup again + await redoByKeyboard(page); + await assertContainerIds(page, { [outterGroupId]: 3, null: 1 }); + await assertContainerChildIds( + page, + toIdCountMap(await getIds(page, true)), + outterGroupId + ); + }); +}); diff --git a/blocksuite/tests-legacy/edgeless/group/group.spec.ts b/blocksuite/tests-legacy/edgeless/group/group.spec.ts new file mode 100644 index 0000000000000..94e4477191daf --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/group/group.spec.ts @@ -0,0 +1,260 @@ +import { expect, type Page } from '@playwright/test'; + +import { clickView } from '../../utils/actions/click.js'; +import { + createShapeElement, + dragBetweenViewCoords, + edgelessCommonSetup, + getFirstContainerId, + Shape, + shiftClickView, + triggerComponentToolbarAction, +} from '../../utils/actions/edgeless.js'; +import { + pressBackspace, + redoByKeyboard, + selectAllByKeyboard, + SHORT_KEY, + undoByKeyboard, +} from '../../utils/actions/keyboard.js'; +import { captureHistory } from '../../utils/actions/misc.js'; +import { + assertCanvasElementsCount, + assertContainerChildCount, + assertContainerIds, + assertEdgelessNonSelectedRect, + assertSelectedBound, +} from '../../utils/asserts.js'; +import { test } from '../../utils/playwright.js'; + +export const GROUP_ROOT_ID = 'GROUP_ROOT'; + +test.describe('group', () => { + async function init(page: Page) { + await edgelessCommonSetup(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await createShapeElement(page, [100, 0], [200, 100], Shape.Square); + } + + test.describe('group create', () => { + test.beforeEach(async ({ page }) => { + await init(page); + }); + + test('create group button not show when single select', async ({ + page, + }) => { + await clickView(page, [50, 50]); + await expect( + page.locator('edgeless-element-toolbar-widget') + ).toBeVisible(); + await expect(page.locator('edgeless-add-group-button')).not.toBeVisible(); + }); + + test('create button show up when multi select', async ({ page }) => { + await selectAllByKeyboard(page); + await expect(page.locator('edgeless-add-group-button')).toBeVisible(); + }); + + test('create group by component toolbar', async ({ page }) => { + await selectAllByKeyboard(page); + await triggerComponentToolbarAction(page, 'addGroup'); + await assertSelectedBound(page, [0, 0, 200, 100]); + }); + + test('create group by shortcut mod + G', async ({ page }) => { + await selectAllByKeyboard(page); + await page.keyboard.press(`${SHORT_KEY}+g`); + await assertSelectedBound(page, [0, 0, 200, 100]); + }); + + test('create group and undo, redo', async ({ page }) => { + await selectAllByKeyboard(page); + await captureHistory(page); + await page.keyboard.press(`${SHORT_KEY}+g`); + await assertSelectedBound(page, [0, 0, 200, 100]); + await undoByKeyboard(page); + await assertSelectedBound(page, [0, 0, 100, 100]); + await redoByKeyboard(page); + await assertSelectedBound(page, [0, 0, 200, 100]); + }); + }); + + test.describe('ungroup', () => { + test.beforeEach(async ({ page }) => { + await init(page); + }); + + test('ungroup by component toolbar', async ({ page }) => { + await selectAllByKeyboard(page); + await triggerComponentToolbarAction(page, 'addGroup'); + await assertSelectedBound(page, [0, 0, 200, 100]); + await triggerComponentToolbarAction(page, 'ungroup'); + await assertEdgelessNonSelectedRect(page); + }); + + test('ungroup by shortcut mod + shift + G', async ({ page }) => { + await selectAllByKeyboard(page); + await triggerComponentToolbarAction(page, 'addGroup'); + await assertSelectedBound(page, [0, 0, 200, 100]); + await page.keyboard.press(`${SHORT_KEY}+Shift+g`); + await assertEdgelessNonSelectedRect(page); + }); + + test('ungroup and undo, redo', async ({ page }) => { + await selectAllByKeyboard(page); + await triggerComponentToolbarAction(page, 'addGroup'); + await assertSelectedBound(page, [0, 0, 200, 100]); + await captureHistory(page); + await page.keyboard.press(`${SHORT_KEY}+Shift+g`); + await assertEdgelessNonSelectedRect(page); + await undoByKeyboard(page); + await assertSelectedBound(page, [0, 0, 200, 100]); + await redoByKeyboard(page); + await assertEdgelessNonSelectedRect(page); + }); + }); + + test.describe('drag group', () => { + test.beforeEach(async ({ page }) => { + await init(page); + }); + + test('drag group to move', async ({ page }) => { + await selectAllByKeyboard(page); + await triggerComponentToolbarAction(page, 'addGroup'); + await dragBetweenViewCoords(page, [100, 50], [110, 50]); + await assertSelectedBound(page, [10, 0, 200, 100]); + }); + }); + + test.describe('select', () => { + test.beforeEach(async ({ page }) => { + await init(page); + await selectAllByKeyboard(page); + await triggerComponentToolbarAction(page, 'addGroup'); + }); + + test('select group by click', async ({ page }) => { + await clickView(page, [300, -100]); + await assertEdgelessNonSelectedRect(page); + await clickView(page, [50, 50]); + await assertSelectedBound(page, [0, 0, 200, 100]); + }); + + test('select sub-element by first select group', async ({ page }) => { + await clickView(page, [50, 50]); + await assertSelectedBound(page, [0, 0, 100, 100]); + }); + + test('select element when enter gorup', async ({ page }) => { + await clickView(page, [50, 50]); + await assertSelectedBound(page, [0, 0, 100, 100]); + await clickView(page, [150, 50]); + await assertSelectedBound(page, [100, 0, 100, 100]); + }); + }); + + test.describe('delete', () => { + test.beforeEach(async ({ page }) => { + await init(page); + }); + + test('delete root group', async ({ page }) => { + await selectAllByKeyboard(page); + await triggerComponentToolbarAction(page, 'addGroup'); + const groupId = await getFirstContainerId(page); + await captureHistory(page); + await pressBackspace(page); + await assertCanvasElementsCount(page, 0); + + // undo the delete + await undoByKeyboard(page); + await assertCanvasElementsCount(page, 3); + await assertContainerIds(page, { + [groupId]: 2, + null: 1, + }); + await assertContainerChildCount(page, groupId, 2); + + // redo the delete + await redoByKeyboard(page); + await assertCanvasElementsCount(page, 0); + }); + + test('delete sub-element in group', async ({ page }) => { + await selectAllByKeyboard(page); + await triggerComponentToolbarAction(page, 'addGroup'); + const groupId = await getFirstContainerId(page); + await captureHistory(page); + await clickView(page, [50, 50]); + await pressBackspace(page); + + await assertCanvasElementsCount(page, 2); + await assertContainerIds(page, { + [groupId]: 1, + null: 1, + }); + await assertContainerChildCount(page, groupId, 1); + + // undo the delete + await undoByKeyboard(page); + await assertCanvasElementsCount(page, 3); + await assertContainerIds(page, { + [groupId]: 2, + null: 1, + }); + await assertContainerChildCount(page, groupId, 2); + + // redo the delete + await redoByKeyboard(page); + await assertCanvasElementsCount(page, 2); + await assertContainerIds(page, { + [groupId]: 1, + null: 1, + }); + await assertContainerChildCount(page, groupId, 1); + }); + + test('delete group in group', async ({ page }) => { + await createShapeElement(page, [200, 0], [300, 100], Shape.Square); + await selectAllByKeyboard(page); + await triggerComponentToolbarAction(page, 'addGroup'); + const firstGroup = await getFirstContainerId(page); + await clickView(page, [50, 50]); + await shiftClickView(page, [150, 50]); + await triggerComponentToolbarAction(page, 'addGroup'); + const secondGroup = await getFirstContainerId(page, [firstGroup]); + await captureHistory(page); + + // delete group in group + await pressBackspace(page); + await assertCanvasElementsCount(page, 2); + await assertContainerIds(page, { + [firstGroup]: 1, + null: 1, + }); + await assertContainerChildCount(page, firstGroup, 1); + + // undo the delete + await undoByKeyboard(page); + await assertCanvasElementsCount(page, 5); + await assertContainerIds(page, { + [firstGroup]: 2, + [secondGroup]: 2, + null: 1, + }); + await assertContainerChildCount(page, firstGroup, 2); + await assertContainerChildCount(page, secondGroup, 2); + + // redo the delete + await redoByKeyboard(page); + await assertCanvasElementsCount(page, 2); + await assertContainerIds(page, { + [firstGroup]: 1, + null: 1, + }); + await assertContainerChildCount(page, firstGroup, 1); + }); + }); +}); diff --git a/blocksuite/tests-legacy/edgeless/group/release.spec.ts b/blocksuite/tests-legacy/edgeless/group/release.spec.ts new file mode 100644 index 0000000000000..7de0e84c8eb7a --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/group/release.spec.ts @@ -0,0 +1,116 @@ +import type { Page } from '@playwright/test'; + +import { + captureHistory, + clickView, + createShapeElement, + edgelessCommonSetup, + getFirstContainerId, + redoByKeyboard, + selectAllByKeyboard, + Shape, + shiftClickView, + triggerComponentToolbarAction, + undoByKeyboard, +} from '../../utils/actions/index.js'; +import { + assertContainerChildCount, + assertContainerIds, + assertSelectedBound, +} from '../../utils/asserts.js'; +import { test } from '../../utils/playwright.js'; + +async function init(page: Page) { + await edgelessCommonSetup(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await createShapeElement(page, [100, 0], [200, 100], Shape.Square); +} + +test.describe('release from group', () => { + let outterGroupId: string; + + test.beforeEach(async ({ page }) => { + await init(page); + await createShapeElement(page, [200, 0], [300, 100], Shape.Square); + await selectAllByKeyboard(page); + await triggerComponentToolbarAction(page, 'addGroup'); + + outterGroupId = await getFirstContainerId(page); + }); + + test('release element from group', async ({ page }) => { + await clickView(page, [50, 50]); + await captureHistory(page); + await triggerComponentToolbarAction(page, 'releaseFromGroup'); + await assertContainerIds(page, { + [outterGroupId]: 2, + null: 2, + }); + await assertContainerChildCount(page, outterGroupId, 2); + await assertSelectedBound(page, [0, 0, 100, 100]); + + // undo the release + await undoByKeyboard(page); + await assertContainerIds(page, { + [outterGroupId]: 3, + null: 1, + }); + await assertContainerChildCount(page, outterGroupId, 3); + await assertSelectedBound(page, [0, 0, 100, 100]); + + // redo the release + await redoByKeyboard(page); + await assertContainerIds(page, { + [outterGroupId]: 2, + null: 2, + }); + await assertContainerChildCount(page, outterGroupId, 2); + await assertSelectedBound(page, [0, 0, 100, 100]); + }); + + test('release group from group', async ({ page }) => { + await clickView(page, [50, 50]); + await shiftClickView(page, [150, 50]); + await triggerComponentToolbarAction(page, 'addGroup'); + await captureHistory(page); + const groupId = await getFirstContainerId(page, [outterGroupId]); + + await assertContainerIds(page, { + [groupId]: 2, + [outterGroupId]: 2, + null: 1, + }); + await assertContainerChildCount(page, groupId, 2); + await assertContainerChildCount(page, outterGroupId, 2); + + // release group from group + await triggerComponentToolbarAction(page, 'releaseFromGroup'); + await assertContainerIds(page, { + [groupId]: 2, + [outterGroupId]: 1, + null: 2, + }); + await assertContainerChildCount(page, outterGroupId, 1); + await assertContainerChildCount(page, groupId, 2); + + // undo the release + await undoByKeyboard(page); + await assertContainerIds(page, { + [groupId]: 2, + [outterGroupId]: 2, + null: 1, + }); + await assertContainerChildCount(page, groupId, 2); + await assertContainerChildCount(page, outterGroupId, 2); + + // redo the release + await redoByKeyboard(page); + await assertContainerIds(page, { + [groupId]: 2, + [outterGroupId]: 1, + null: 2, + }); + await assertContainerChildCount(page, outterGroupId, 1); + await assertContainerChildCount(page, groupId, 2); + }); +}); diff --git a/blocksuite/tests-legacy/edgeless/group/title.spec.ts b/blocksuite/tests-legacy/edgeless/group/title.spec.ts new file mode 100644 index 0000000000000..cac0d334ce9ac --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/group/title.spec.ts @@ -0,0 +1,72 @@ +import { expect, type Page } from '@playwright/test'; + +import { + createShapeElement, + dblclickView, + edgelessCommonSetup, + getSelectedBound, + pressEnter, + selectAllByKeyboard, + Shape, + triggerComponentToolbarAction, + type, +} from '../../utils/actions/index.js'; +import { assertEdgelessCanvasText } from '../../utils/asserts.js'; +import { test } from '../../utils/playwright.js'; + +async function init(page: Page) { + await edgelessCommonSetup(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await createShapeElement(page, [100, 0], [200, 100], Shape.Square); +} + +test.describe('group title', () => { + test.beforeEach(async ({ page }) => { + await init(page); + await selectAllByKeyboard(page); + await triggerComponentToolbarAction(page, 'addGroup'); + }); + + test('edit group title by component toolbar', async ({ page }) => { + expect(await page.locator('edgeless-group-title-editor').count()).toBe(0); + + await triggerComponentToolbarAction(page, 'renameGroup'); + await page.locator('edgeless-group-title-editor').waitFor({ + state: 'attached', + }); + }); + + test('edit group title by dbclick', async ({ page }) => { + expect(await page.locator('edgeless-group-title-editor').count()).toBe(0); + + const bound = await getSelectedBound(page); + await dblclickView(page, [bound[0] + 10, bound[1] - 10]); + await page.locator('edgeless-group-title-editor').waitFor({ + state: 'attached', + }); + await type(page, 'ABC'); + await assertEdgelessCanvasText(page, 'ABC'); + }); + + test('blur unmount group editor', async ({ page }) => { + const bound = await getSelectedBound(page); + await dblclickView(page, [bound[0] + 10, bound[1] - 10]); + + await page.locator('edgeless-group-title-editor').waitFor({ + state: 'attached', + }); + await page.mouse.click(10, 10); + expect(await page.locator('edgeless-group-title-editor').count()).toBe(0); + }); + + test('enter unmount group editor', async ({ page }) => { + const bound = await getSelectedBound(page); + await dblclickView(page, [bound[0] + 10, bound[1] - 10]); + + await page.locator('edgeless-group-title-editor').waitFor({ + state: 'attached', + }); + await pressEnter(page); + expect(await page.locator('edgeless-group-title-editor').count()).toBe(0); + }); +}); diff --git a/blocksuite/tests-legacy/edgeless/lasso.spec.ts b/blocksuite/tests-legacy/edgeless/lasso.spec.ts new file mode 100644 index 0000000000000..d0a74967357d4 --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/lasso.spec.ts @@ -0,0 +1,262 @@ +import { sleep } from '@blocksuite/global/utils'; +import { expect } from '@playwright/test'; + +import { + addBasicRectShapeElement, + assertEdgelessTool, + edgelessCommonSetup as commonSetup, + setEdgelessTool, +} from '../utils/actions/edgeless.js'; +import { + dragBetweenCoords, + selectAllByKeyboard, +} from '../utils/actions/index.js'; +import { + assertEdgelessNonSelectedRect, + assertEdgelessSelectedRect, +} from '../utils/asserts.js'; +import { test } from '../utils/playwright.js'; + +test.skip('lasso tool should deselect when dragging in an empty area', async ({ + page, +}) => { + await commonSetup(page); + + const start = { x: 100, y: 100 }; + const end = { x: 200, y: 200 }; + await addBasicRectShapeElement(page, start, end); + await assertEdgelessSelectedRect(page, [100, 100, 100, 100]); + + await setEdgelessTool(page, 'lasso'); + await assertEdgelessTool(page, 'lasso'); + + await dragBetweenCoords(page, { x: 10, y: 10 }, { x: 15, y: 15 }); + + await assertEdgelessNonSelectedRect(page); +}); + +test.skip('freehand lasso basic test', async ({ page }) => { + await commonSetup(page); + + await addBasicRectShapeElement(page, { x: 100, y: 100 }, { x: 200, y: 200 }); + await addBasicRectShapeElement(page, { x: 300, y: 300 }, { x: 400, y: 400 }); + + await page.mouse.click(10, 10); // deselect + + await setEdgelessTool(page, 'lasso'); + await assertEdgelessTool(page, 'lasso'); + + await assertEdgelessNonSelectedRect(page); + + // simulate a basic lasso selection to select both the rects + const points: [number, number][] = [ + [500, 100], + [500, 500], + [90, 500], + ]; + await page.mouse.move(90, 90); + await page.mouse.down(); + for (const point of points) await page.mouse.move(...point); + await page.mouse.up(); + + await assertEdgelessSelectedRect(page, [100, 100, 200, 200]); +}); + +test.skip('freehand lasso add to selection', async ({ page }) => { + await commonSetup(page); + + await addBasicRectShapeElement(page, { x: 100, y: 100 }, { x: 200, y: 200 }); + await addBasicRectShapeElement(page, { x: 300, y: 300 }, { x: 400, y: 400 }); + + await page.mouse.click(10, 10); // deselect + + await setEdgelessTool(page, 'lasso'); + await assertEdgelessTool(page, 'lasso'); + await assertEdgelessNonSelectedRect(page); + + // some random selection covering the rectangle + let points: [number, number][] = [ + [250, 90], + [250, 300], + [10, 300], + ]; + await page.mouse.move(90, 90); + await page.mouse.down(); + for (const point of points) await page.mouse.move(...point); + await page.mouse.up(); + + await assertEdgelessSelectedRect(page, [100, 100, 100, 100]); + + points = [ + [400, 250], + [400, 450], + [250, 450], + ]; + + await page.keyboard.down('Shift'); // addition selection + await page.mouse.move(250, 250); + await page.mouse.down(); + for (const point of points) await page.mouse.move(...point); + await page.mouse.up(); + + await assertEdgelessSelectedRect(page, [100, 100, 200, 200]); +}); + +test.skip('freehand lasso subtract from selection', async ({ page }) => { + await commonSetup(page); + + await addBasicRectShapeElement(page, { x: 100, y: 100 }, { x: 200, y: 200 }); + await addBasicRectShapeElement(page, { x: 300, y: 300 }, { x: 400, y: 400 }); + await setEdgelessTool(page, 'default'); + + await selectAllByKeyboard(page); + + await setEdgelessTool(page, 'lasso'); + + const points: [number, number][] = [ + [410, 290], + [410, 410], + [290, 410], + ]; + + await page.keyboard.down('Alt'); + + await page.mouse.move(290, 290); + await page.mouse.down(); + for (const point of points) await page.mouse.move(...point); + await page.mouse.up(); + + await assertEdgelessSelectedRect(page, [100, 100, 100, 100]); // only the first rectangle should be selected +}); + +test.skip('polygonal lasso basic test', async ({ page }) => { + await commonSetup(page); + await addBasicRectShapeElement(page, { x: 100, y: 100 }, { x: 200, y: 200 }); + await addBasicRectShapeElement(page, { x: 300, y: 300 }, { x: 400, y: 400 }); + await page.mouse.click(10, 10); // deselect + + await assertEdgelessNonSelectedRect(page); + + await setEdgelessTool(page, 'lasso'); + await setEdgelessTool(page, 'lasso'); // switch to polygonal lasso + await sleep(100); + + const points: [number, number][] = [ + [90, 90], + [500, 90], + [500, 500], + [90, 500], + [90, 90], + ]; + + for (const point of points) { + await page.mouse.click(...point); + } + + await assertEdgelessSelectedRect(page, [100, 100, 200, 200]); +}); + +test.skip('polygonal lasso add to selection by holding Shift Key', async ({ + page, +}) => { + await commonSetup(page); + + await addBasicRectShapeElement(page, { x: 100, y: 100 }, { x: 200, y: 200 }); + await addBasicRectShapeElement(page, { x: 300, y: 300 }, { x: 400, y: 400 }); + + await page.mouse.click(10, 10); // deselect + await assertEdgelessNonSelectedRect(page); + + await setEdgelessTool(page, 'lasso'); + await setEdgelessTool(page, 'lasso'); + await sleep(100); + + let points: [number, number][] = [ + [90, 90], + [150, 90], + [150, 150], + [90, 150], + [90, 90], + ]; + + // select the first rectangle + for (const point of points) await page.mouse.click(...point); + + points = [ + [290, 290], + [350, 290], + [350, 350], + [290, 350], + [290, 290], + ]; + + await page.keyboard.down('Shift'); // add to selection + // selects the second rectangle + for (const point of points) await page.mouse.click(...point); + + // by the end both of the rects should be selected + await assertEdgelessSelectedRect(page, [100, 100, 200, 200]); +}); + +test.skip('polygonal lasso subtract from selection by holding Alt', async ({ + page, +}) => { + await commonSetup(page); + + await addBasicRectShapeElement(page, { x: 100, y: 100 }, { x: 200, y: 200 }); + await addBasicRectShapeElement(page, { x: 300, y: 300 }, { x: 400, y: 400 }); + + await selectAllByKeyboard(page); + + const points: [number, number][] = [ + [290, 290], + [350, 290], + [350, 350], + [290, 350], + [290, 290], + ]; + + // switch to polygonal lasso tool + await setEdgelessTool(page, 'lasso'); + await setEdgelessTool(page, 'lasso'); + await sleep(100); + + await page.keyboard.down('Alt'); // subtract from selection + for (const point of points) await page.mouse.click(...point); + + // By the end the second rectangle must be deselected leaving the first rect selection + await assertEdgelessSelectedRect(page, [100, 100, 100, 100]); +}); + +test.skip('polygonal lasso should complete selection when clicking the last point', async ({ + page, +}) => { + await commonSetup(page); + + // switch to polygonal lasso + await setEdgelessTool(page, 'lasso'); + await setEdgelessTool(page, 'lasso'); + await sleep(100); + + const lassoPoints: [number, number][] = [ + [100, 100], + [200, 200], + [250, 150], + [100, 100], + ]; + + for (const point of lassoPoints) await page.mouse.click(...point); + + const isSelecting = await page.evaluate(() => { + const edgeless = document.querySelector('affine-edgeless-root'); + if (!edgeless) throw new Error('Missing edgless root block'); + + const curController = edgeless.gfx.tool.currentTool$.peek(); + if (curController?.toolName !== 'lasso') + throw new Error('expected lasso tool controller'); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return (curController as any)['_isSelecting']; + }); + + expect(isSelecting).toBe(false); +}); diff --git a/blocksuite/tests-legacy/edgeless/linked-doc.spec.ts b/blocksuite/tests-legacy/edgeless/linked-doc.spec.ts new file mode 100644 index 0000000000000..5215ff760bae3 --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/linked-doc.spec.ts @@ -0,0 +1,354 @@ +import { assertNotExists } from '@blocksuite/global/utils'; +import { expect } from '@playwright/test'; + +import { + activeNoteInEdgeless, + createConnectorElement, + createNote, + createShapeElement, + edgelessCommonSetup, + getConnectorPath, + locatorComponentToolbarMoreButton, + selectNoteInEdgeless, + Shape, + triggerComponentToolbarAction, +} from '../utils/actions/edgeless.js'; +import { + addBasicBrushElement, + pressEnter, + selectAllByKeyboard, + type, + waitNextFrame, +} from '../utils/actions/index.js'; +import { assertConnectorPath, assertExists } from '../utils/asserts.js'; +import { test } from '../utils/playwright.js'; + +test.describe('note to linked doc', () => { + test('select a note and turn it into a linked doc', async ({ page }) => { + await edgelessCommonSetup(page); + const noteId = await createNote(page, [100, 0], ''); + await activeNoteInEdgeless(page, noteId); + await waitNextFrame(page, 200); + await type(page, 'Hello'); + await pressEnter(page); + await type(page, 'World'); + + await page.mouse.click(10, 50); + await selectNoteInEdgeless(page, noteId); + await triggerComponentToolbarAction(page, 'turnIntoLinkedDoc'); + + await waitNextFrame(page, 200); + const embedSyncedBlock = page.locator('affine-embed-synced-doc-block'); + assertExists(embedSyncedBlock); + + await triggerComponentToolbarAction(page, 'openLinkedDoc'); + await waitNextFrame(page, 200); + const noteBlock = page.locator('affine-edgeless-note'); + assertExists(noteBlock); + const noteContent = await noteBlock.innerText(); + expect(noteContent).toBe('Hello\nWorld'); + }); + + test('turn note into a linked doc, connector keeps', async ({ page }) => { + await edgelessCommonSetup(page); + const noteId = await createNote(page, [100, 0]); + await createShapeElement(page, [100, 100], [100, 100], Shape.Square); + await createConnectorElement(page, [100, 150], [100, 10]); + const connectorPath = await getConnectorPath(page); + + await page.mouse.click(10, 50); + await selectNoteInEdgeless(page, noteId); + await triggerComponentToolbarAction(page, 'turnIntoLinkedDoc'); + + await waitNextFrame(page, 200); + const embedSyncedBlock = page.locator('affine-embed-synced-doc-block'); + assertExists(embedSyncedBlock); + + await assertConnectorPath(page, [connectorPath[0], connectorPath[1]], 0); + }); + + // TODO FIX ME + test.skip('embed-synced-doc card can not turn into linked doc', async ({ + page, + }) => { + await edgelessCommonSetup(page); + const noteId = await createNote(page, [100, 0]); + await activeNoteInEdgeless(page, noteId); + await waitNextFrame(page, 200); + await type(page, 'Hello World'); + + await page.mouse.click(10, 50); + await selectNoteInEdgeless(page, noteId); + await triggerComponentToolbarAction(page, 'turnIntoLinkedDoc'); + + const moreButton = locatorComponentToolbarMoreButton(page); + await moreButton.click(); + const turnButton = page.locator('.turn-into-linked-doc'); + assertNotExists(turnButton); + }); + + // TODO FIX ME + test.skip('embed-linked-doc card can not turn into linked doc', async ({ + page, + }) => { + await edgelessCommonSetup(page); + const noteId = await createNote(page, [100, 0]); + await activeNoteInEdgeless(page, noteId); + await waitNextFrame(page, 200); + await type(page, 'Hello World'); + + await page.mouse.click(10, 50); + await selectNoteInEdgeless(page, noteId); + await triggerComponentToolbarAction(page, 'turnIntoLinkedDoc'); + + await triggerComponentToolbarAction(page, 'toCardView'); + const moreButton = locatorComponentToolbarMoreButton(page); + await moreButton.click(); + const turnButton = page.locator('.turn-into-linked-doc'); + assertNotExists(turnButton); + }); +}); + +test.describe('single edgeless element to linked doc', () => { + test('select a shape, turn into a linked doc', async ({ page }) => { + await edgelessCommonSetup(page); + await createShapeElement(page, [100, 100], [100, 100], Shape.Square); + + await triggerComponentToolbarAction(page, 'createLinkedDoc'); + await waitNextFrame(page, 200); + const linkedSyncedBlock = page.locator('affine-linked-synced-doc-block'); + assertExists(linkedSyncedBlock); + + await triggerComponentToolbarAction(page, 'openLinkedDoc'); + await waitNextFrame(page, 200); + + const shapes = await page.evaluate(() => { + const container = document.querySelector('affine-edgeless-root'); + return container!.service + .getElementsByType('shape') + .map(s => ({ type: s.type, xywh: s.xywh })); + }); + expect(shapes.length).toBe(1); + expect(shapes[0]).toEqual({ type: 'shape', xywh: '[100,100,100,100]' }); + }); + + test('select a connector, turn into a linked doc', async ({ page }) => { + await edgelessCommonSetup(page); + await createConnectorElement(page, [100, 150], [100, 10]); + const connectorPath = await getConnectorPath(page); + + await triggerComponentToolbarAction(page, 'createLinkedDoc'); + await waitNextFrame(page, 200); + const linkedSyncedBlock = page.locator('affine-linked-synced-doc-block'); + assertExists(linkedSyncedBlock); + + await triggerComponentToolbarAction(page, 'openLinkedDoc'); + await waitNextFrame(page, 200); + await assertConnectorPath(page, [connectorPath[0], connectorPath[1]], 0); + }); + + test('select a brush, turn into a linked doc', async ({ page }) => { + await edgelessCommonSetup(page); + const start = { x: 400, y: 400 }; + const end = { x: 500, y: 500 }; + await addBasicBrushElement(page, start, end); + await page.mouse.click(start.x + 5, start.y + 5); + + await triggerComponentToolbarAction(page, 'createLinkedDoc'); + await waitNextFrame(page, 200); + const linkedSyncedBlock = page.locator('affine-linked-synced-doc-block'); + assertExists(linkedSyncedBlock); + + await triggerComponentToolbarAction(page, 'openLinkedDoc'); + await waitNextFrame(page, 200); + const brushes = await page.evaluate(() => { + const container = document.querySelector('affine-edgeless-root'); + return container!.service + .getElementsByType('brush') + .map(s => ({ type: s.type, xywh: s.xywh })); + }); + expect(brushes.length).toBe(1); + }); + + test('select a group, turn into a linked doc', async ({ page }) => { + await edgelessCommonSetup(page); + await createNote(page, [100, 0]); + await createShapeElement(page, [100, 100], [100, 100], Shape.Square); + await createConnectorElement(page, [100, 150], [100, 10]); + const start = { x: 400, y: 400 }; + const end = { x: 500, y: 500 }; + await addBasicBrushElement(page, start, end); + + await selectAllByKeyboard(page); + await triggerComponentToolbarAction(page, 'addGroup'); + + await triggerComponentToolbarAction(page, 'createLinkedDoc'); + await waitNextFrame(page, 200); + const linkedSyncedBlock = page.locator('affine-linked-synced-doc-block'); + assertExists(linkedSyncedBlock); + + await triggerComponentToolbarAction(page, 'openLinkedDoc'); + await waitNextFrame(page, 200); + const groups = await page.evaluate(() => { + const container = document.querySelector('affine-edgeless-root'); + return container!.service.getElementsByType('group').map(s => ({ + type: s.type, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + children: s.childElements.map((c: any) => c.type || c.flavour), + })); + }); + expect(groups.length).toBe(1); + expect(groups[0].children).toContain('affine:note'); + expect(groups[0].children).toContain('shape'); + expect(groups[0].children).toContain('connector'); + expect(groups[0].children).toContain('brush'); + }); + + test('select a frame, turn into a linked doc', async ({ page }) => { + await edgelessCommonSetup(page); + await createNote(page, [100, 0]); + await createShapeElement(page, [100, 100], [100, 100], Shape.Square); + await createConnectorElement(page, [100, 150], [100, 10]); + + await selectAllByKeyboard(page); + await triggerComponentToolbarAction(page, 'addGroup'); + + const start = { x: 400, y: 400 }; + const end = { x: 500, y: 500 }; + await addBasicBrushElement(page, start, end); + await selectAllByKeyboard(page); + await triggerComponentToolbarAction(page, 'addFrame'); + + await triggerComponentToolbarAction(page, 'createLinkedDoc'); + await waitNextFrame(page, 200); + const linkedSyncedBlock = page.locator('affine-linked-synced-doc-block'); + assertExists(linkedSyncedBlock); + + await triggerComponentToolbarAction(page, 'openLinkedDoc'); + await waitNextFrame(page, 200); + const nodes = await page.evaluate(() => { + const container = document.querySelector('affine-edgeless-root'); + const elements = container!.service.elements.map(s => s.type); + const blocks = container!.service.blocks.map(b => b.flavour); + + blocks.sort(); + elements.sort(); + + return { blocks, elements }; + }); + + expect(nodes).toEqual({ + blocks: ['affine:note', 'affine:frame'].sort(), + elements: ['group', 'shape', 'connector', 'brush'].sort(), + }); + }); +}); + +test.describe('multiple edgeless elements to linked doc', () => { + test('multi-select note, frame, shape, connector, brush and group, turn it into a linked doc', async ({ + page, + }) => { + await edgelessCommonSetup(page); + await createNote(page, [100, 0], 'Hello World'); + await page.mouse.click(10, 50); + + await createShapeElement(page, [100, 100], [200, 200], Shape.Square); + await selectAllByKeyboard(page); + await triggerComponentToolbarAction(page, 'addGroup'); + + await createShapeElement(page, [200, 200], [300, 300], Shape.Square); + await createConnectorElement(page, [250, 300], [100, 70]); + await selectAllByKeyboard(page); + await triggerComponentToolbarAction(page, 'addFrame'); + + const start = { x: 400, y: 400 }; + const end = { x: 500, y: 500 }; + await addBasicBrushElement(page, start, end); + + await selectAllByKeyboard(page); + await triggerComponentToolbarAction(page, 'createLinkedDoc'); + await waitNextFrame(page, 200); + const linkedSyncedBlock = page.locator('affine-linked-synced-doc-block'); + assertExists(linkedSyncedBlock); + + await triggerComponentToolbarAction(page, 'openLinkedDoc'); + await waitNextFrame(page, 200); + const nodes = await page.evaluate(() => { + const container = document.querySelector('affine-edgeless-root'); + const elements = container!.service.elements.map(s => s.type); + const blocks = container!.service.blocks.map(b => b.flavour); + + blocks.sort(); + elements.sort(); + + return { blocks, elements }; + }); + expect(nodes).toEqual({ + blocks: ['affine:frame', 'affine:note'].sort(), + elements: ['shape', 'shape', 'group', 'connector', 'brush'].sort(), + }); + }); + + test('multi-select with embed doc card inside, turn it into a linked doc', async ({ + page, + }) => { + await edgelessCommonSetup(page); + const noteId = await createNote(page, [100, 0], 'Hello World'); + await page.mouse.click(10, 50); + await selectNoteInEdgeless(page, noteId); + await triggerComponentToolbarAction(page, 'turnIntoLinkedDoc'); + + await createShapeElement(page, [100, 100], [100, 100], Shape.Square); + await createConnectorElement(page, [100, 150], [100, 10]); + + await selectAllByKeyboard(page); + await triggerComponentToolbarAction(page, 'createLinkedDoc'); + await waitNextFrame(page, 200); + const linkedSyncedBlock = page.locator('affine-linked-synced-doc-block'); + assertExists(linkedSyncedBlock); + + await triggerComponentToolbarAction(page, 'openLinkedDoc'); + await waitNextFrame(page, 200); + const nodes = await page.evaluate(() => { + const container = document.querySelector('affine-edgeless-root'); + const elements = container!.service.elements.map(s => s.type); + const blocks = container!.service.blocks.map(b => b.flavour); + return { blocks, elements }; + }); + + expect(nodes.blocks).toHaveLength(1); + expect(nodes.blocks).toContain('affine:embed-synced-doc'); + + expect(nodes.elements).toHaveLength(2); + expect(nodes.elements).toContain('shape'); + expect(nodes.elements).toContain('connector'); + }); + + test('multi-select with mindmap, turn it into a linked doc', async ({ + page, + }) => { + await edgelessCommonSetup(page); + await triggerComponentToolbarAction(page, 'addMindmap'); + + await selectAllByKeyboard(page); + await triggerComponentToolbarAction(page, 'createLinkedDoc'); + await waitNextFrame(page, 200); + const linkedSyncedBlock = page.locator('affine-linked-synced-doc-block'); + assertExists(linkedSyncedBlock); + + await triggerComponentToolbarAction(page, 'openLinkedDoc'); + await waitNextFrame(page, 200); + const nodes = await page.evaluate(() => { + const container = document.querySelector('affine-edgeless-root'); + const elements = container!.service.elements.map(s => s.type); + const blocks = container!.service.blocks.map(b => b.flavour); + return { blocks, elements }; + }); + + expect(nodes.blocks).toHaveLength(0); + + expect(nodes.elements).toHaveLength(5); + expect(nodes.elements).toContain('mindmap'); + expect(nodes.elements.filter(el => el === 'shape')).toHaveLength(4); + }); +}); diff --git a/blocksuite/tests-legacy/edgeless/lock.spec.ts b/blocksuite/tests-legacy/edgeless/lock.spec.ts new file mode 100644 index 0000000000000..b5e5863aa74f8 --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/lock.spec.ts @@ -0,0 +1,559 @@ +import { expect, type Page } from '@playwright/test'; +import { clickView, dblclickView, moveView } from 'utils/actions/click.js'; +import { + createBrushElement, + createConnectorElement, + createEdgelessText, + createFrame, + createMindmap, + createNote as _createNote, + createShapeElement, + deleteAll, + dragBetweenViewCoords, + edgelessCommonSetup, + getContainerChildIds, + getSelectedBound, + getSelectedIds, + getTypeById, + setEdgelessTool, +} from 'utils/actions/edgeless.js'; +import { + copyByKeyboard, + pasteByKeyboard, + pressArrowDown, + pressBackspace, + pressEscape, + pressForwardDelete, + pressTab, + selectAllByKeyboard, + SHORT_KEY, + type, + undoByKeyboard, +} from 'utils/actions/keyboard.js'; +import { waitNextFrame } from 'utils/actions/misc.js'; +import { + assertCanvasElementsCount, + assertEdgelessElementBound, + assertEdgelessSelectedModelRect, + assertRichTexts, +} from 'utils/asserts.js'; + +import { test } from '../utils/playwright.js'; + +test.describe('lock', () => { + const getButtons = (page: Page) => { + const elementToolbar = page.locator('edgeless-element-toolbar-widget'); + return { + lock: elementToolbar.locator('edgeless-lock-button[data-locked="false"]'), + unlock: elementToolbar.locator( + 'edgeless-lock-button[data-locked="true"]' + ), + }; + }; + + async function createNote(page: Page, coord1: number[], content?: string) { + await _createNote(page, coord1, content); + await pressEscape(page, 3); + } + + test('edgeless element can be locked and unlocked', async ({ page }) => { + await edgelessCommonSetup(page); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const wrapTest = async any>( + elementCreateFn: F, + ...args: Parameters + ) => { + await elementCreateFn(...args); + await waitNextFrame(page); + await pressEscape(page); + await selectAllByKeyboard(page); + + const ids = await getSelectedIds(page); + expect(ids).toHaveLength(1); + const type = await getTypeById(page, ids[0]); + const message = `element(${type}) should be able to be (un)locked`; + + const { lock, unlock } = getButtons(page); + + await expect(lock, message).toBeVisible(); + await expect(unlock, message).toBeHidden(); + + await lock.click(); + await expect(lock, message).toBeHidden(); + await expect(unlock, message).toBeVisible(); + + await unlock.click(); + await expect(lock, message).toBeVisible(); + await expect(unlock, message).toBeHidden(); + await deleteAll(page); + await waitNextFrame(page); + }; + + await wrapTest(createBrushElement, page, [100, 100], [150, 150]); + await wrapTest(createConnectorElement, page, [100, 100], [150, 150]); + await wrapTest(createShapeElement, page, [100, 100], [150, 150]); + await wrapTest(createEdgelessText, page, [100, 100]); + await wrapTest(createMindmap, page, [100, 100]); + await wrapTest(createFrame, page, [100, 100], [150, 150]); + await wrapTest(createNote, page, [100, 100]); + + await wrapTest(async () => { + await createShapeElement(page, [100, 100], [150, 150]); + await createShapeElement(page, [150, 150], [200, 200]); + await selectAllByKeyboard(page); + await page.keyboard.press(`${SHORT_KEY}+g`); + }); + }); + + test('locked element should be selectable by clicking or short-cut', async ({ + page, + }) => { + await edgelessCommonSetup(page); + await createShapeElement(page, [100, 100], [150, 150]); + await selectAllByKeyboard(page); + + await getButtons(page).lock.click(); + expect(await getSelectedIds(page)).toHaveLength(1); + await pressEscape(page); + expect(await getSelectedIds(page)).toHaveLength(0); + await selectAllByKeyboard(page); + expect(await getSelectedIds(page)).toHaveLength(1); + + await pressEscape(page); + await clickView(page, [125, 125]); + expect(await getSelectedIds(page)).toHaveLength(1); + }); + + test('locked element should not be selectable by dragging default tool or lasso tool. unlocking will recover', async ({ + page, + }) => { + await edgelessCommonSetup(page); + await createShapeElement(page, [100, 100], [150, 150]); + await selectAllByKeyboard(page); + + const { lock, unlock } = getButtons(page); + + await lock.click(); + await pressEscape(page); + await dragBetweenViewCoords(page, [90, 90], [160, 160]); + expect(await getSelectedIds(page)).toHaveLength(0); + + await clickView(page, [125, 125]); + await unlock.click(); + await dragBetweenViewCoords(page, [90, 90], [160, 160]); + expect(await getSelectedIds(page)).toHaveLength(1); + }); + + test('descendant of locked element should not be selectable. unlocking will recover', async ({ + page, + }) => { + await edgelessCommonSetup(page); + const shapeId = await createShapeElement(page, [100, 100], [150, 150]); + await createShapeElement(page, [150, 150], [200, 200]); + await selectAllByKeyboard(page); + await page.keyboard.press(`${SHORT_KEY}+g`); + const groupId = (await getSelectedIds(page))[0]; + + const { lock, unlock } = getButtons(page); + + await lock.click(); + await pressEscape(page); + await clickView(page, [125, 125]); + expect(await getSelectedIds(page)).toEqual([groupId]); + await clickView(page, [125, 125]); + expect(await getSelectedIds(page)).toEqual([groupId]); + + await unlock.click(); + await clickView(page, [125, 125]); + expect(await getSelectedIds(page)).toEqual([shapeId]); + await pressEscape(page); + + const frameId = await createFrame(page, [50, 50], [250, 250]); + await selectAllByKeyboard(page); + await lock.click(); + await pressEscape(page); + await clickView(page, [125, 125]); + expect(await getSelectedIds(page)).toEqual([frameId]); + await unlock.click(); + await clickView(page, [125, 125]); + expect(await getSelectedIds(page)).toEqual([shapeId]); + }); + + test('the selected rect of locked element should contain descendant. unlocking will recover', async ({ + page, + }) => { + await edgelessCommonSetup(page); + // frame + await createFrame(page, [0, 0], [100, 100]); + await createShapeElement(page, [100, 100], [150, 150]); + await dragBetweenViewCoords(page, [125, 125], [95, 95]); // add shape to frame, and partial area out of frame + await selectAllByKeyboard(page); + + await assertEdgelessSelectedModelRect(page, [0, 0, 100, 100]); // only frame outline + + const { lock, unlock } = getButtons(page); + + await lock.click(); + await assertEdgelessSelectedModelRect(page, [0, 0, 120, 120]); // frame outline and shape + await pressEscape(page); + await clickView(page, [100, 100]); + await assertEdgelessSelectedModelRect(page, [0, 0, 120, 120]); + + await unlock.click(); + await assertEdgelessSelectedModelRect(page, [0, 0, 100, 100]); + await pressEscape(page); + await clickView(page, [100, 100]); + await assertEdgelessSelectedModelRect(page, [70, 70, 50, 50]); + + await deleteAll(page); + + // mindmap + await createMindmap(page, [100, 100]); + const bound = await getSelectedBound(page); + const rootNodePos: [number, number] = [ + bound[0] + 10, + bound[1] + 0.5 * bound[3], + ]; + + await clickView(page, rootNodePos); + const rootNodeBound = await getSelectedBound(page); + + await lock.click(); + await assertEdgelessSelectedModelRect(page, bound); + await clickView(page, rootNodePos); + await assertEdgelessSelectedModelRect(page, bound); + + await unlock.click(); + await assertEdgelessSelectedModelRect(page, bound); + await clickView(page, rootNodePos); + await assertEdgelessSelectedModelRect(page, rootNodeBound); + }); + + test('locked element should be copyable, and the copy is unlocked', async ({ + page, + }) => { + await edgelessCommonSetup(page); + await createShapeElement(page, [100, 100], [150, 150]); + await selectAllByKeyboard(page); + + await getButtons(page).lock.click(); + await pressEscape(page); + await clickView(page, [125, 125]); + await copyByKeyboard(page); + await moveView(page, [200, 200]); + await pasteByKeyboard(page); + await clickView(page, [200, 200]); + await expect(getButtons(page).lock).toBeVisible(); + }); + + test('locked element and descendant should not be draggable and moved by arrow key. unlocking will recover', async ({ + page, + }) => { + await edgelessCommonSetup(page); + const frame = await createFrame(page, [50, 50], [250, 250]); + const shape1 = await createShapeElement(page, [100, 100], [150, 150]); + const shape2 = await createShapeElement(page, [150, 150], [200, 200]); + await selectAllByKeyboard(page); + + await getButtons(page).lock.click(); + await pressEscape(page); + + await dragBetweenViewCoords(page, [100, 100], [150, 150]); + await assertEdgelessElementBound(page, frame, [50, 50, 200, 200]); + await assertEdgelessElementBound(page, shape1, [100, 100, 50, 50]); + await assertEdgelessElementBound(page, shape2, [150, 150, 50, 50]); + + await pressArrowDown(page, 3); + await assertEdgelessElementBound(page, frame, [50, 50, 200, 200]); + await assertEdgelessElementBound(page, shape1, [100, 100, 50, 50]); + await assertEdgelessElementBound(page, shape2, [150, 150, 50, 50]); + + await getButtons(page).unlock.click(); + await dragBetweenViewCoords(page, [100, 100], [150, 150]); + await assertEdgelessElementBound(page, frame, [100, 100, 200, 200]); + await assertEdgelessElementBound(page, shape1, [150, 150, 50, 50]); + await assertEdgelessElementBound(page, shape2, [200, 200, 50, 50]); + + await pressArrowDown(page, 3); + await assertEdgelessElementBound(page, frame, [100, 103, 200, 200]); + await assertEdgelessElementBound(page, shape1, [150, 153, 50, 50]); + await assertEdgelessElementBound(page, shape2, [200, 203, 50, 50]); + }); + + test('locked element should be moved if parent is moved', async ({ + page, + }) => { + await edgelessCommonSetup(page); + const frame = await createFrame(page, [50, 50], [250, 250]); + const shape = await createShapeElement(page, [100, 100], [150, 150]); + await clickView(page, [125, 125]); + await getButtons(page).lock.click(); + + await selectAllByKeyboard(page); + await dragBetweenViewCoords(page, [100, 100], [150, 150]); + + assertEdgelessElementBound(page, frame, [100, 100, 200, 200]); + assertEdgelessElementBound(page, shape, [150, 150, 50, 50]); + }); + + test('locked element should not be scalable and rotatable. unlocking will recover', async ({ + page, + }) => { + await edgelessCommonSetup(page); + await createShapeElement(page, [100, 100], [150, 150]); + await selectAllByKeyboard(page); + + const rect = page.locator('edgeless-selected-rect'); + const { lock, unlock } = getButtons(page); + await expect(rect.locator('.resize')).toHaveCount(8); + await expect(rect.locator('.rotate')).toHaveCount(4); + + await lock.click(); + await expect(rect.locator('.resize')).toHaveCount(0); + await expect(rect.locator('.rotate')).toHaveCount(0); + + await unlock.click(); + await expect(rect.locator('.resize')).toHaveCount(8); + await expect(rect.locator('.rotate')).toHaveCount(4); + }); + + test('locked element should not be editable. unlocking will recover', async ({ + page, + }) => { + await edgelessCommonSetup(page); + const { lock, unlock } = getButtons(page); + + // Shape + { + await createShapeElement(page, [100, 100], [150, 150]); + await selectAllByKeyboard(page); + await lock.click(); + await dblclickView(page, [125, 125]); + await expect(page.locator('edgeless-shape-text-editor')).toHaveCount(0); + await unlock.click(); + await dblclickView(page, [125, 125]); + await expect(page.locator('edgeless-shape-text-editor')).toHaveCount(1); + await deleteAll(page); + } + + // Connector + { + await createConnectorElement(page, [100, 100], [150, 150]); + await selectAllByKeyboard(page); + await lock.click(); + await dblclickView(page, [125, 125]); + await expect(page.locator('edgeless-connector-label-editor')).toHaveCount( + 0 + ); + await unlock.click(); + await dblclickView(page, [125, 125]); + await expect(page.locator('edgeless-connector-label-editor')).toHaveCount( + 1 + ); + await deleteAll(page); + } + + // Mindmap + { + await createMindmap(page, [100, 100]); + const bound = await getSelectedBound(page); + const rootPos: [number, number] = [ + bound[0] + 10, + bound[1] + 0.5 * bound[3], + ]; + await lock.click(); + await dblclickView(page, rootPos); + await expect(page.locator('edgeless-shape-text-editor')).toHaveCount(0); + await unlock.click(); + await dblclickView(page, rootPos); + await expect(page.locator('edgeless-shape-text-editor')).toHaveCount(1); + await deleteAll(page); + } + + // Edgeless Text + { + await createEdgelessText(page, [100, 100], 'text'); + await selectAllByKeyboard(page); + await lock.click(); + const text = page.locator('affine-edgeless-text'); + await text.dblclick(); + await type(page, '111'); + await expect(text).toHaveText('text'); + await unlock.click(); + await text.dblclick(); + await type(page, '111'); + await expect(text).toHaveText('111'); + await deleteAll(page); + } + + // Note + { + await createNote(page, [100, 100], 'note'); + await selectAllByKeyboard(page); + await lock.click(); + const note = page.locator('affine-edgeless-note'); + await note.dblclick(); + await page.keyboard.press('End'); + await type(page, '111'); + await assertRichTexts(page, ['note']); + await unlock.click(); + await note.dblclick(); + await page.keyboard.press('End'); + await type(page, '111'); + await assertRichTexts(page, ['note111']); + await pressEscape(page, 3); + await deleteAll(page); + } + }); + + test('locked element should not be deletable. unlocking will recover', async ({ + page, + }) => { + await edgelessCommonSetup(page); + await createShapeElement(page, [100, 100], [150, 150]); + await selectAllByKeyboard(page); + const { lock, unlock } = getButtons(page); + + await lock.click(); + await clickView(page, [125, 125]); + await pressBackspace(page); + await assertCanvasElementsCount(page, 1); + await page.keyboard.press('Delete'); + await assertCanvasElementsCount(page, 1); + await pressForwardDelete(page); + await assertCanvasElementsCount(page, 1); + await setEdgelessTool(page, 'eraser'); + await dragBetweenViewCoords(page, [90, 90], [160, 160], { steps: 2 }); + await assertCanvasElementsCount(page, 1); + await setEdgelessTool(page, 'default'); + + await selectAllByKeyboard(page); + await unlock.click(); + await page.evaluate(() => { + window.doc.captureSync(); + }); + await pressBackspace(page); + await assertCanvasElementsCount(page, 0); + await undoByKeyboard(page); + await assertCanvasElementsCount(page, 1); + await page.keyboard.press('Delete'); + await assertCanvasElementsCount(page, 0); + await undoByKeyboard(page); + await assertCanvasElementsCount(page, 1); + await pressForwardDelete(page); + await assertCanvasElementsCount(page, 0); + await undoByKeyboard(page); + await assertCanvasElementsCount(page, 1); + await setEdgelessTool(page, 'eraser'); + await dragBetweenViewCoords(page, [90, 90], [160, 160], { steps: 2 }); + await assertCanvasElementsCount(page, 0); + }); + + test('locked frame should not add new child element. unlocking will recover', async ({ + page, + }) => { + await edgelessCommonSetup(page); + const frame = await createFrame(page, [50, 50], [250, 250]); + await selectAllByKeyboard(page); + const frameTitle = page.locator('affine-frame-title'); + const { lock, unlock } = getButtons(page); + + await lock.click(); + const shape = await createShapeElement(page, [100, 100], [150, 150]); + + expect(await getContainerChildIds(page, frame)).toHaveLength(0); + + await frameTitle.click(); + await unlock.click(); + expect(await getContainerChildIds(page, frame)).toHaveLength(0); + await clickView(page, [125, 125]); + await dragBetweenViewCoords(page, [125, 125], [130, 130]); // move shape into frame + expect(await getContainerChildIds(page, frame)).toEqual([shape]); + }); + + test('locked mindmap can not create new node by pressing Tab. unlocking will recover', async ({ + page, + }) => { + await edgelessCommonSetup(page); + await createMindmap(page, [100, 100]); + await selectAllByKeyboard(page); + const bound = await getSelectedBound(page); + const rootPos: [number, number] = [ + bound[0] + 10, + bound[1] + 0.5 * bound[3], + ]; + const nodeEditor = page.locator('edgeless-shape-text-editor'); + const { lock, unlock } = getButtons(page); + await lock.click(); + await clickView(page, rootPos); + await pressTab(page); + await expect(nodeEditor).toHaveCount(0); + await unlock.click(); + await clickView(page, rootPos); + await pressTab(page); + await expect(nodeEditor).toHaveCount(1); + await expect(nodeEditor).toHaveText('New node'); + }); + + test('endpoint of locked connector should not be changeable. unlocking will recover', async ({ + page, + }) => { + await edgelessCommonSetup(page); + await createConnectorElement(page, [100, 100], [150, 150]); + const handles = page.locator('edgeless-connector-handle'); + await expect(handles).toHaveCount(1); + const { lock, unlock } = getButtons(page); + await lock.click(); + await expect(handles).toHaveCount(0); + await unlock.click(); + await expect(handles).toHaveCount(1); + }); + + test('locking multiple elements will create locked group. unlocking a group will release elements', async ({ + page, + }) => { + await edgelessCommonSetup(page); + const shape1 = await createShapeElement(page, [100, 100], [150, 150]); + const shape2 = await createShapeElement(page, [150, 150], [200, 200]); + await selectAllByKeyboard(page); + const { lock, unlock } = getButtons(page); + await lock.click(); + const group = (await getSelectedIds(page))[0]; + expect(group).not.toBeUndefined(); + expect(await getTypeById(page, group)).toBe('group'); + + await unlock.click(); + expect(await getSelectedIds(page)).toEqual([shape1, shape2]); + }); + + test('locking a group should not create a new group', async ({ page }) => { + await edgelessCommonSetup(page); + await createShapeElement(page, [100, 100], [150, 150]); + await createShapeElement(page, [150, 150], [200, 200]); + await selectAllByKeyboard(page); + await page.keyboard.press(`${SHORT_KEY}+g`); + const group = (await getSelectedIds(page))[0]; + await getButtons(page).lock.click(); + expect(await getSelectedIds(page)).toEqual([group]); + }); + + test('unlocking an element should not unlock its locked descendant', async ({ + page, + }) => { + await edgelessCommonSetup(page); + await createFrame(page, [50, 50], [250, 250]); + await createShapeElement(page, [150, 150], [200, 200]); + + const { lock, unlock } = getButtons(page); + + await clickView(page, [175, 175]); + await lock.click(); + await page.locator('affine-frame-title').click(); + await lock.click(); + await unlock.click(); + await clickView(page, [175, 175]); + await expect(lock).toBeHidden(); + await expect(unlock).toBeVisible(); + }); +}); diff --git a/blocksuite/tests-legacy/edgeless/mindmap.spec.ts b/blocksuite/tests-legacy/edgeless/mindmap.spec.ts new file mode 100644 index 0000000000000..7b1a10c4a41a0 --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/mindmap.spec.ts @@ -0,0 +1,126 @@ +import type { MindmapElementModel } from '@blocksuite/affine-model'; +import { expect } from '@playwright/test'; +import { clickView } from 'utils/actions/click.js'; +import { dragBetweenCoords } from 'utils/actions/drag.js'; +import { + addBasicRectShapeElement, + autoFit, + edgelessCommonSetup, + getSelectedBound, + getSelectedBoundCount, + zoomResetByKeyboard, +} from 'utils/actions/edgeless.js'; +import { + pressBackspace, + selectAllByKeyboard, + undoByKeyboard, +} from 'utils/actions/keyboard.js'; +import { waitNextFrame } from 'utils/actions/misc.js'; +import { + assertEdgelessSelectedRect, + assertSelectedBound, +} from 'utils/asserts.js'; + +import { test } from '../utils/playwright.js'; + +test('elements should be selectable after open mindmap menu', async ({ + page, +}) => { + await edgelessCommonSetup(page); + + const start = { x: 100, y: 100 }; + const end = { x: 200, y: 200 }; + await addBasicRectShapeElement(page, start, end); + + await page.locator('.basket-wrapper').click({ position: { x: 0, y: 0 } }); + await expect(page.locator('edgeless-mindmap-menu')).toBeVisible(); + + await page.mouse.click(start.x + 5, start.y + 5); + await assertEdgelessSelectedRect(page, [100, 100, 100, 100]); +}); + +test('undo deletion of mindmap should restore the deleted element', async ({ + page, +}) => { + await edgelessCommonSetup(page); + await zoomResetByKeyboard(page); + + await page.keyboard.press('m'); + await clickView(page, [0, 0]); + await autoFit(page); + + await selectAllByKeyboard(page); + const mindmapBound = await getSelectedBound(page); + + await pressBackspace(page); + + await selectAllByKeyboard(page); + expect(await getSelectedBoundCount(page)).toBe(0); + + await undoByKeyboard(page); + + await selectAllByKeyboard(page); + await assertSelectedBound(page, mindmapBound); +}); + +test('drag mind map node to reorder the node', async ({ page }) => { + await edgelessCommonSetup(page); + await zoomResetByKeyboard(page); + + await page.keyboard.press('m'); + await clickView(page, [0, 0]); + await autoFit(page); + + const { mindmapId, nodeId, nodeRect } = await page.evaluate(() => { + const edgelessBlock = document.querySelector('affine-edgeless-root'); + if (!edgelessBlock) { + throw new Error('edgeless block not found'); + } + const mindmap = edgelessBlock.gfx.gfxElements.filter( + el => 'type' in el && el.type === 'mindmap' + )[0] as MindmapElementModel; + const node = mindmap.tree.children[0].element; + const rect = edgelessBlock.gfx.viewport.toViewBound(node.elementBound); + + edgelessBlock.gfx.selection.set({ elements: [node.id] }); + + return { + mindmapId: mindmap.id, + nodeId: node.id, + nodeRect: { + x: rect.x, + y: rect.y, + w: rect.w, + h: rect.h, + }, + }; + }); + + await waitNextFrame(page, 100); + + await dragBetweenCoords( + page, + { x: nodeRect.x + nodeRect.w / 2, y: nodeRect.y + nodeRect.h / 2 }, + { x: nodeRect.x + nodeRect.w / 2, y: nodeRect.y + nodeRect.h / 2 + 120 }, + { + steps: 50, + } + ); + + const secondNodeId = await page.evaluate( + ({ mindmapId }) => { + const edgelessBlock = document.querySelector('affine-edgeless-root'); + if (!edgelessBlock) { + throw new Error('edgeless block not found'); + } + const mindmap = edgelessBlock.gfx.getElementById( + mindmapId + ) as MindmapElementModel; + + return mindmap.tree.children[1].id; + }, + { mindmapId, nodeId } + ); + + expect(secondNodeId).toEqual(nodeId); +}); diff --git a/blocksuite/tests-legacy/edgeless/note/drag-handle.spec.ts b/blocksuite/tests-legacy/edgeless/note/drag-handle.spec.ts new file mode 100644 index 0000000000000..b83955c303632 --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/note/drag-handle.spec.ts @@ -0,0 +1,155 @@ +import { expect } from '@playwright/test'; + +import { + addNote, + dragHandleFromBlockToBlockBottomById, + enterPlaygroundRoom, + focusRichText, + initEmptyEdgelessState, + initThreeParagraphs, + setEdgelessTool, + switchEditorMode, + type, + waitNextFrame, +} from '../../utils/actions/index.js'; +import { assertRectExist, assertRichTexts } from '../../utils/asserts.js'; +import { test } from '../../utils/playwright.js'; + +const CENTER_X = 450; +const CENTER_Y = 450; + +test('drag handle should be shown when a note is activated in default mode or hidden in other modes', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await focusRichText(page); + await type(page, 'hello'); + await assertRichTexts(page, ['hello']); + + await switchEditorMode(page); + const noteBox = await page.locator('affine-edgeless-note').boundingBox(); + if (!noteBox) { + throw new Error('Missing edgeless affine-note'); + } + + const [x, y] = [noteBox.x + 26, noteBox.y + noteBox.height / 2]; + + await page.mouse.move(x, y); + await expect(page.locator('.affine-drag-handle-container')).toBeHidden(); + await page.mouse.dblclick(x, y); + await waitNextFrame(page); + await page.mouse.move(x, y); + + await expect(page.locator('.affine-drag-handle-container')).toBeVisible(); + + await page.mouse.move(0, 0); + await setEdgelessTool(page, 'shape'); + await page.mouse.move(x, y); + await expect(page.locator('.affine-drag-handle-container')).toBeHidden(); + + await page.mouse.move(0, 0); + await setEdgelessTool(page, 'default'); + await page.mouse.move(x, y); + await expect(page.locator('.affine-drag-handle-container')).toBeVisible(); +}); + +test('drag handle can drag note into another note', async ({ page }) => { + await enterPlaygroundRoom(page); + const { noteId } = await initEmptyEdgelessState(page); + await focusRichText(page); + await type(page, 'hello'); + await assertRichTexts(page, ['hello']); + + await switchEditorMode(page); + const noteRect = await page + .locator(`[data-block-id="${noteId}"]`) + .boundingBox(); + assertRectExist(noteRect); + + const secondNoteId = await addNote(page, 'hello world', 100, 100); + await waitNextFrame(page); + const secondNoteRect = await page + .locator(`[data-block-id="${secondNoteId}"]`) + .boundingBox(); + assertRectExist(secondNoteRect); + + { + const [x, y] = [ + noteRect.x + noteRect.width / 2, + noteRect.y + noteRect.height / 2, + ]; + await page.mouse.click(noteRect.x, noteRect.y + noteRect.height + 100); + await page.mouse.move(x, y); + await page.mouse.click(x, y); + + const handlerRect = await page + .locator('.affine-drag-handle-container') + .boundingBox(); + assertRectExist(handlerRect); + + await page.mouse.move( + handlerRect.x + handlerRect.width / 2, + handlerRect.y + handlerRect.height / 2 + ); + await page.mouse.down(); + + const [targetX, targetY] = [ + secondNoteRect.x + 10, + secondNoteRect.y + secondNoteRect.height / 2, + ]; + await page.mouse.move(targetX, targetY); + await page.mouse.up(); + + await waitNextFrame(page); + } +}); + +test('drag handle should work inside one note', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await initThreeParagraphs(page); + + await switchEditorMode(page); + + await page.mouse.dblclick(CENTER_X, CENTER_Y); + await dragHandleFromBlockToBlockBottomById(page, '3', '5'); + await waitNextFrame(page); + await expect(page.locator('affine-drag-handle-container')).toBeHidden(); + await assertRichTexts(page, ['456', '789', '123']); +}); + +test.fixme( + 'drag handle should work across multiple notes', + async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + await switchEditorMode(page); + + await setEdgelessTool(page, 'note'); + + await page.mouse.click(200, 200); + await focusRichText(page, 3); + await waitNextFrame(page); + + // block id 7 + await type(page, '000'); + + await page.mouse.dblclick(CENTER_X, CENTER_Y - 20); + await dragHandleFromBlockToBlockBottomById(page, '3', '7'); + await expect(page.locator('.affine-drag-handle-container')).toBeHidden(); + await waitNextFrame(page); + await assertRichTexts(page, ['456', '789', '000', '123']); + + // await page.mouse.dblclick(305, 305); + await dragHandleFromBlockToBlockBottomById(page, '3', '4'); + await waitNextFrame(page); + await expect(page.locator('.affine-drag-handle-container')).toBeHidden(); + await assertRichTexts(page, ['456', '123', '789', '000']); + + await expect(page.locator('selected > *')).toHaveCount(0); + } +); diff --git a/blocksuite/tests-legacy/edgeless/note/mode.spec.ts b/blocksuite/tests-legacy/edgeless/note/mode.spec.ts new file mode 100644 index 0000000000000..b3d0cec10a740 --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/note/mode.spec.ts @@ -0,0 +1,80 @@ +import { NoteDisplayMode } from '@blocksuite/affine-model'; + +import { + addNote, + changeNoteDisplayModeWithId, + enterPlaygroundRoom, + initEmptyEdgelessState, + switchEditorMode, + zoomResetByKeyboard, +} from '../../utils/actions/index.js'; +import { assertBlockCount } from '../../utils/asserts.js'; +import { test } from '../../utils/playwright.js'; + +test('Note added on doc mode should display on both modes by default', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + + // there should be 1 note in doc page + await assertBlockCount(page, 'note', 1); + + await switchEditorMode(page); + // there should be 1 note in edgeless page as well + await assertBlockCount(page, 'edgeless-note', 1); +}); + +test('Note added on edgeless mode should display on edgeless only by default', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + + await switchEditorMode(page); + await zoomResetByKeyboard(page); + await addNote(page, 'note2', 100, 100); + + // assert add note success, there should be 2 notes in edgeless page + await assertBlockCount(page, 'edgeless-note', 2); + + await switchEditorMode(page); + // switch to doc mode, the note added on edgeless mode should not render on doc mode + // there should be only 1 note in doc page + await assertBlockCount(page, 'note', 1); +}); + +test('Note can be changed to display on doc and edgeless mode', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + + await switchEditorMode(page); + await zoomResetByKeyboard(page); + const noteId = await addNote(page, 'note2', 100, 200); + await page.mouse.click(200, 150); + // assert add note success, there should be 2 notes in edgeless page + await assertBlockCount(page, 'edgeless-note', 2); + + // switch to doc mode + await switchEditorMode(page); + // there should be 1 notes in doc page + await assertBlockCount(page, 'note', 1); + + // switch back to edgeless mode + await switchEditorMode(page); + // change note display mode to doc only + await changeNoteDisplayModeWithId( + page, + noteId, + NoteDisplayMode.DocAndEdgeless + ); + // there should still be 2 notes in edgeless page + await assertBlockCount(page, 'edgeless-note', 2); + + // switch to doc mode + await switchEditorMode(page); + // change successfully, there should be 2 notes in doc page + await assertBlockCount(page, 'note', 2); +}); diff --git a/blocksuite/tests-legacy/edgeless/note/note.spec.ts b/blocksuite/tests-legacy/edgeless/note/note.spec.ts new file mode 100644 index 0000000000000..fb5e422e93230 --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/note/note.spec.ts @@ -0,0 +1,531 @@ +import { + DEFAULT_NOTE_HEIGHT, + DEFAULT_NOTE_WIDTH, + NoteDisplayMode, +} from '@blocksuite/affine-model'; +import { expect } from '@playwright/test'; + +import { + activeNoteInEdgeless, + addNote, + assertEdgelessTool, + changeEdgelessNoteBackground, + changeNoteDisplayMode, + locatorComponentToolbar, + locatorEdgelessZoomToolButton, + selectNoteInEdgeless, + setEdgelessTool, + switchEditorMode, + triggerComponentToolbarAction, + zoomOutByKeyboard, + zoomResetByKeyboard, +} from '../../utils/actions/edgeless.js'; +import { + click, + clickBlockById, + dragBetweenCoords, + dragBetweenIndices, + enterPlaygroundRoom, + focusRichText, + focusRichTextEnd, + initEmptyEdgelessState, + initThreeParagraphs, + pressArrowDown, + pressArrowUp, + pressBackspace, + pressEnter, + pressTab, + type, + undoByKeyboard, + waitForInlineEditorStateUpdated, + waitNextFrame, +} from '../../utils/actions/index.js'; +import { + assertBlockChildrenIds, + assertBlockCount, + assertEdgelessNonSelectedRect, + assertEdgelessNoteBackground, + assertEdgelessSelectedRect, + assertExists, + assertNoteSequence, + assertNoteXYWH, + assertRichTextInlineRange, + assertRichTexts, + assertTextSelection, +} from '../../utils/asserts.js'; +import { test } from '../../utils/playwright.js'; + +const CENTER_X = 450; +const CENTER_Y = 450; + +test('can drag selected non-active note', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await focusRichText(page); + await type(page, 'hello'); + await assertRichTexts(page, ['hello']); + + await switchEditorMode(page); + await zoomResetByKeyboard(page); + await assertNoteXYWH(page, [0, 0, DEFAULT_NOTE_WIDTH, DEFAULT_NOTE_HEIGHT]); + + // selected, non-active + await page.mouse.click(CENTER_X, CENTER_Y); + await dragBetweenCoords( + page, + { x: CENTER_X, y: CENTER_Y }, + { x: CENTER_X, y: CENTER_Y + 100 } + ); + await assertNoteXYWH(page, [0, 100, DEFAULT_NOTE_WIDTH, DEFAULT_NOTE_HEIGHT]); + + await undoByKeyboard(page); + await waitNextFrame(page); + await assertNoteXYWH(page, [0, 0, DEFAULT_NOTE_WIDTH, DEFAULT_NOTE_HEIGHT]); +}); + +test('add Note', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + + await switchEditorMode(page); + await zoomResetByKeyboard(page); + await addNote(page, 'hello', 300, 300); + + await assertEdgelessTool(page, 'default'); + await assertRichTexts(page, ['', 'hello']); + await page.mouse.click(300, 200); + await page.mouse.click(350, 320); + await assertEdgelessSelectedRect(page, [ + 270, + 260, + DEFAULT_NOTE_WIDTH, + DEFAULT_NOTE_HEIGHT, + ]); +}); + +test('add empty Note', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + + await switchEditorMode(page); + await zoomResetByKeyboard(page); + await setEdgelessTool(page, 'note'); + // add note at 300,300 + await page.mouse.click(300, 300); + await waitForInlineEditorStateUpdated(page); + // should wait for inline editor update and resizeObserver callback + await waitNextFrame(page); + + // assert add note success + await assertBlockCount(page, 'edgeless-note', 2); + + // click out of note + await page.mouse.click(250, 200); + + // assert empty note is note removed + await page.mouse.move(320, 320); + await assertBlockCount(page, 'edgeless-note', 2); +}); + +test('always keep at least 1 note block', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + + await switchEditorMode(page); + await setEdgelessTool(page, 'default'); + + // clicking in default mode will try to remove empty note block + await page.mouse.click(0, 0); + + const notes = await page.locator('affine-edgeless-note').all(); + expect(notes.length).toEqual(1); +}); + +test('edgeless arrow up/down', async ({ page }) => { + await enterPlaygroundRoom(page); + const { paragraphId, noteId } = await initEmptyEdgelessState(page); + await switchEditorMode(page); + await activeNoteInEdgeless(page, noteId); + await waitNextFrame(page, 400); + + await type(page, 'aaaaa'); + await pressEnter(page); + await type(page, 'aaaaa'); + await pressEnter(page); + await type(page, 'aaa'); + + await waitForInlineEditorStateUpdated(page); + // 0 for page, 1 for surface, 2 for note, 3 for paragraph + expect(paragraphId).toBe('3'); + await clickBlockById(page, paragraphId); + await assertRichTextInlineRange(page, 0, 5, 0); + + await pressArrowDown(page); + await waitNextFrame(page); + await assertRichTextInlineRange(page, 1, 5, 0); + + await pressArrowUp(page); + await waitNextFrame(page); + await assertRichTextInlineRange(page, 0, 5, 0); + + await pressArrowUp(page); + await waitNextFrame(page); + await assertRichTextInlineRange(page, 0, 0, 0); +}); + +test('dragging un-selected note', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await focusRichText(page); + await type(page, 'hello'); + await assertRichTexts(page, ['hello']); + + await switchEditorMode(page); + + const noteBox = await page.locator('affine-edgeless-note').boundingBox(); + if (!noteBox) { + throw new Error('Missing edgeless affine-note'); + } + await page.mouse.click(noteBox.x + 5, noteBox.y + 5); + await assertEdgelessSelectedRect(page, [ + noteBox.x, + noteBox.y, + noteBox.width, + noteBox.height, + ]); + + await dragBetweenCoords( + page, + { x: noteBox.x + 10, y: noteBox.y + 15 }, + { x: noteBox.x + 10, y: noteBox.y + 35 }, + { steps: 10 } + ); + + await assertEdgelessSelectedRect(page, [ + noteBox.x, + noteBox.y + 20, + noteBox.width, + noteBox.height, + ]); +}); + +test('format quick bar should show up when double-clicking on text', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await initThreeParagraphs(page); + await switchEditorMode(page); + + await page.mouse.dblclick(CENTER_X, CENTER_Y); + await waitNextFrame(page); + + await page + .locator('rich-text') + .nth(1) + .dblclick({ + position: { x: 10, y: 10 }, + delay: 20, + }); + await page.waitForTimeout(200); + const formatBar = page.locator('.affine-format-bar-widget'); + await expect(formatBar).toBeVisible(); +}); + +test('when editing text in edgeless, should hide component toolbar', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + const { noteId } = await initEmptyEdgelessState(page); + await initThreeParagraphs(page); + await switchEditorMode(page); + + await selectNoteInEdgeless(page, noteId); + + const toolbar = locatorComponentToolbar(page); + await expect(toolbar).toBeVisible(); + + await page.mouse.click(0, 0); + await activeNoteInEdgeless(page, noteId); + await expect(toolbar).toBeHidden(); +}); + +test('duplicate note should work correctly', async ({ page }) => { + await enterPlaygroundRoom(page); + const { noteId } = await initEmptyEdgelessState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + await switchEditorMode(page); + + await selectNoteInEdgeless(page, noteId); + + await triggerComponentToolbarAction(page, 'duplicate'); + await waitNextFrame(page, 200); // wait viewport fit animation + const moreActionsContainer = page.locator('.more-actions-container'); + await expect(moreActionsContainer).toBeHidden(); + + const noteLocator = page.locator('affine-edgeless-note'); + await expect(noteLocator).toHaveCount(2); + const [firstNote, secondNote] = await noteLocator.all(); + + // content should be same + expect(await firstNote.innerText()).toEqual(await secondNote.innerText()); + + // size should be same + const firstNoteBox = await firstNote.boundingBox(); + const secondNoteBox = await secondNote.boundingBox(); + expect(firstNoteBox!.width).toBeCloseTo(secondNoteBox!.width); + expect(firstNoteBox!.height).toBeCloseTo(secondNoteBox!.height); +}); + +test('double click toolbar zoom button, should not add text', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + + const zoomOutButton = await locatorEdgelessZoomToolButton( + page, + 'zoomOut', + false + ); + await zoomOutButton.dblclick(); + await assertEdgelessNonSelectedRect(page); +}); + +test('change note color', async ({ page }) => { + await enterPlaygroundRoom(page); + const { noteId } = await initEmptyEdgelessState(page); + await initThreeParagraphs(page); + await switchEditorMode(page); + + await assertEdgelessNoteBackground( + page, + noteId, + '--affine-note-background-white' + ); + + await selectNoteInEdgeless(page, noteId); + await triggerComponentToolbarAction(page, 'changeNoteColor'); + const color = '--affine-note-background-green'; + await changeEdgelessNoteBackground(page, color); + await assertEdgelessNoteBackground(page, noteId, color); +}); + +test('cursor for active and inactive state', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await focusRichText(page); + await type(page, 'hello'); + await pressEnter(page); + await pressEnter(page); + await assertRichTexts(page, ['hello', '', '']); + + await switchEditorMode(page); + + await assertTextSelection(page); + await page.mouse.click(CENTER_X, CENTER_Y); + await waitNextFrame(page); + await assertTextSelection(page); + await page.mouse.dblclick(CENTER_X, CENTER_Y); + await waitNextFrame(page); + await assertTextSelection(page, { + blockId: '3', + index: 5, + length: 0, + }); +}); + +test('when no visible note block, clicking in page mode will auto add a new note block', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + + await assertBlockCount(page, 'edgeless-note', 1); + // select note + await selectNoteInEdgeless(page, '2'); + await assertNoteSequence(page, '1'); + await assertBlockCount(page, 'edgeless-note', 1); + // hide note + await triggerComponentToolbarAction(page, 'changeNoteDisplayMode'); + await waitNextFrame(page); + await changeNoteDisplayMode(page, NoteDisplayMode.EdgelessOnly); + + await switchEditorMode(page); + let note = await page.evaluate(() => { + return document.querySelector('affine-note'); + }); + expect(note).toBeNull(); + await click(page, { x: 200, y: 280 }); + + note = await page.evaluate(() => { + return document.querySelector('affine-note'); + }); + expect(note).not.toBeNull(); +}); + +test.fixme( + 'Click at empty note should add a paragraph block', + async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await focusRichText(page); + await type(page, '123'); + await assertRichTexts(page, ['123']); + + await switchEditorMode(page); + + // Drag paragraph out of note block + const paragraphBlock = await page + .locator(`[data-block-id="3"]`) + .boundingBox(); + assertExists(paragraphBlock); + await page.mouse.dblclick(paragraphBlock.x, paragraphBlock.y); + await waitNextFrame(page); + await page.mouse.move( + paragraphBlock.x + paragraphBlock.width / 2, + paragraphBlock.y + paragraphBlock.height / 2 + ); + await waitNextFrame(page); + const handle = await page + .locator('.affine-drag-handle-container') + .boundingBox(); + assertExists(handle); + await page.mouse.move( + handle.x + handle.width / 2, + handle.y + handle.height / 2, + { steps: 10 } + ); + await page.mouse.down(); + await page.mouse.move(100, 200, { steps: 30 }); + await page.mouse.up(); + + // There should be two note blocks and one paragraph block + await assertRichTexts(page, ['123']); + await assertBlockCount(page, 'edgeless-note', 2); + await assertBlockCount(page, 'paragraph', 1); + + // Click at empty note block to add a paragraph block + const emptyNote = await page.locator(`[data-block-id="2"]`).boundingBox(); + assertExists(emptyNote); + await page.mouse.click( + emptyNote.x + emptyNote.width / 2, + emptyNote.y + emptyNote.height / 2 + ); + await waitNextFrame(page, 300); + await type(page, '456'); + await waitNextFrame(page, 400); + + await page.mouse.click(100, 100); + await waitNextFrame(page, 400); + await assertBlockCount(page, 'paragraph', 2); + } +); + +test('Should focus at closest text block when note collapse', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + + // Make sure there is no rich text content + await switchEditorMode(page); + await zoomResetByKeyboard(page); + await assertRichTexts(page, ['']); + + // Select the note + await zoomOutByKeyboard(page); + const notePortalBox = await page + .locator('affine-edgeless-note') + .boundingBox(); + assertExists(notePortalBox); + await page.mouse.click(notePortalBox.x + 10, notePortalBox.y + 10); + await waitNextFrame(page, 200); + const selectedRect = page + .locator('edgeless-selected-rect') + .locator('.affine-edgeless-selected-rect'); + await expect(selectedRect).toBeVisible(); + + // Collapse the note + const selectedBox = await selectedRect.boundingBox(); + assertExists(selectedBox); + await page.mouse.move( + selectedBox.x + selectedBox.width / 2, + selectedBox.y + selectedBox.height + ); + await page.mouse.down(); + await page.mouse.move( + selectedBox.x + selectedBox.width / 2, + selectedBox.y + selectedBox.height + 200, + { steps: 10 } + ); + await page.mouse.up(); + await expect(selectedRect).toBeVisible(); + + // Click at the bottom of note to focus at the closest text block + await page.mouse.click( + selectedBox.x + selectedBox.width / 2, + selectedBox.y + selectedBox.height - 20 + ); + await waitNextFrame(page, 200); + + // Should be enter edit mode and there are no selected rect + await expect(selectedRect).toBeHidden(); + + // Focus at the closest text block and make sure can type + await type(page, 'hello'); + await waitNextFrame(page, 200); + await assertRichTexts(page, ['hello']); +}); + +test('delete first block in edgeless note', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + await zoomResetByKeyboard(page); + await assertNoteXYWH(page, [0, 0, DEFAULT_NOTE_WIDTH, DEFAULT_NOTE_HEIGHT]); + await page.mouse.dblclick(CENTER_X, CENTER_Y); + + // first block without children, nothing should happen + await assertRichTexts(page, ['']); + await assertBlockChildrenIds(page, '3', []); + await pressBackspace(page); + + await type(page, 'aaa'); + await pressEnter(page); + await type(page, 'bbb'); + await pressTab(page); + await assertRichTexts(page, ['aaa', 'bbb']); + await assertBlockChildrenIds(page, '3', ['4']); + + // first block with children, need to bring children to parent + await focusRichTextEnd(page); + await pressBackspace(page, 3); + await assertRichTexts(page, ['', 'bbb']); + await pressBackspace(page); + await assertRichTexts(page, ['bbb']); + await assertBlockChildrenIds(page, '4', []); +}); + +test('select text cross blocks in edgeless note', async ({ page }) => { + await enterPlaygroundRoom(page); + const { noteId } = await initEmptyEdgelessState(page); + await switchEditorMode(page); + await activeNoteInEdgeless(page, noteId); + await waitNextFrame(page, 400); + + await type(page, 'aaa'); + await pressEnter(page); + await type(page, 'bbb'); + await pressEnter(page); + await type(page, 'ccc'); + await assertRichTexts(page, ['aaa', 'bbb', 'ccc']); + + await dragBetweenIndices(page, [0, 1], [2, 2]); + await pressBackspace(page); + await assertRichTexts(page, ['ac']); +}); diff --git a/blocksuite/tests-legacy/edgeless/note/resize.spec.ts b/blocksuite/tests-legacy/edgeless/note/resize.spec.ts new file mode 100644 index 0000000000000..2226e6a1f1dd0 --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/note/resize.spec.ts @@ -0,0 +1,254 @@ +import { NOTE_MIN_HEIGHT, NOTE_MIN_WIDTH } from '@blocksuite/affine-model'; +import { expect } from '@playwright/test'; + +import { + activeNoteInEdgeless, + dragBetweenCoords, + enterPlaygroundRoom, + getNoteRect, + initEmptyEdgelessState, + redoByClick, + selectNoteInEdgeless, + setEdgelessTool, + switchEditorMode, + triggerComponentToolbarAction, + type, + undoByClick, + waitForInlineEditorStateUpdated, + waitNextFrame, + zoomResetByKeyboard, +} from '../../utils/actions/index.js'; +import { + assertBlockCount, + assertEdgelessSelectedRect, + assertNoteRectEqual, + assertRectEqual, + assertRichTexts, +} from '../../utils/asserts.js'; +import { test } from '../../utils/playwright.js'; + +test('resize note in edgeless mode', async ({ page }) => { + await enterPlaygroundRoom(page); + const { noteId } = await initEmptyEdgelessState(page); + await switchEditorMode(page); + await zoomResetByKeyboard(page); + await activeNoteInEdgeless(page, noteId); + await waitNextFrame(page, 400); + await type(page, 'hello'); + await assertRichTexts(page, ['hello']); + + // unselect note + await page.mouse.click(50, 50); + + expect(noteId).toBe('2'); // 0 for page, 1 for surface + await selectNoteInEdgeless(page, noteId); + + const initRect = await getNoteRect(page, noteId); + const leftHandle = page.locator('.handle[aria-label="left"] .resize'); + const box = await leftHandle.boundingBox(); + if (box === null) throw new Error(); + + await dragBetweenCoords( + page, + { x: box.x + 5, y: box.y + 5 }, + { x: box.x - 95, y: box.y + 5 } + ); + const draggedRect = await getNoteRect(page, noteId); + assertRectEqual(draggedRect, { + x: initRect.x - 100, + y: initRect.y, + w: initRect.w + 100, + h: initRect.h, + }); + + await switchEditorMode(page); + await switchEditorMode(page); + const newRect = await getNoteRect(page, noteId); + assertRectEqual(newRect, draggedRect); +}); + +test('resize note then collapse note', async ({ page }) => { + await enterPlaygroundRoom(page); + const { noteId } = await initEmptyEdgelessState(page); + await switchEditorMode(page); + await zoomResetByKeyboard(page); + await activeNoteInEdgeless(page, noteId); + await waitNextFrame(page, 400); + await type(page, 'hello'); + await assertRichTexts(page, ['hello']); + + // unselect note + await page.mouse.click(50, 50); + + expect(noteId).toBe('2'); // 0 for page, 1 for surface + await selectNoteInEdgeless(page, noteId); + + const initRect = await getNoteRect(page, noteId); + const leftHandle = page.locator('.handle[aria-label="left"] .resize'); + let box = await leftHandle.boundingBox(); + if (box === null) throw new Error(); + + await dragBetweenCoords( + page, + { x: box.x + 50, y: box.y + box.height }, + { x: box.x + 50, y: box.y + box.height + 100 } + ); + let noteRect = await getNoteRect(page, noteId); + await expect(page.locator('.edgeless-note-collapse-button')).toBeVisible(); + assertRectEqual(noteRect, { + x: initRect.x, + y: initRect.y, + w: initRect.w, + h: initRect.h + 100, + }); + + await page.locator('.edgeless-note-collapse-button')!.click(); + let domRect = await page.locator('affine-edgeless-note').boundingBox(); + expect(domRect!.height).toBeCloseTo(NOTE_MIN_HEIGHT); + + await page.locator('.edgeless-note-collapse-button')!.click(); + domRect = await page.locator('affine-edgeless-note').boundingBox(); + expect(domRect!.height).toBeCloseTo(initRect.h + 100); + + await selectNoteInEdgeless(page, noteId); + box = await leftHandle.boundingBox(); + if (box === null) throw new Error(); + await dragBetweenCoords( + page, + { x: box.x + 50, y: box.y + box.height }, + { x: box.x + 50, y: box.y + box.height - 150 } + ); + noteRect = await getNoteRect(page, noteId); + await expect( + page.locator('.edgeless-note-collapse-button') + ).not.toBeVisible(); + assertRectEqual(noteRect, { + x: initRect.x, + y: initRect.y, + w: initRect.w, + h: NOTE_MIN_HEIGHT, + }); + + await switchEditorMode(page); + await switchEditorMode(page); + const newRect = await getNoteRect(page, noteId); + assertRectEqual(newRect, noteRect); +}); + +test('resize note then auto size and custom size', async ({ page }) => { + await enterPlaygroundRoom(page); + const { noteId } = await initEmptyEdgelessState(page); + await switchEditorMode(page); + await zoomResetByKeyboard(page); + await activeNoteInEdgeless(page, noteId); + await waitNextFrame(page, 400); + await type(page, 'hello'); + await assertRichTexts(page, ['hello']); + // unselect note + await page.mouse.click(50, 50); + await selectNoteInEdgeless(page, noteId); + + const initRect = await getNoteRect(page, noteId); + const bottomRightResize = page.locator( + '.handle[aria-label="bottom-right"] .resize' + ); + const box = await bottomRightResize.boundingBox(); + if (box === null) throw new Error(); + + await dragBetweenCoords( + page, + { x: box.x + 5, y: box.y + 5 }, + { x: box.x + 5, y: box.y + 105 } + ); + + const draggedRect = await getNoteRect(page, noteId); + assertRectEqual(draggedRect, { + x: initRect.x, + y: initRect.y, + w: initRect.w, + h: initRect.h + 100, + }); + + await triggerComponentToolbarAction(page, 'autoSize'); + await waitNextFrame(page, 200); + const autoSizeRect = await getNoteRect(page, noteId); + assertRectEqual(autoSizeRect, initRect); + + await triggerComponentToolbarAction(page, 'autoSize'); + await waitNextFrame(page, 200); + await assertNoteRectEqual(page, noteId, draggedRect); + + await undoByClick(page); + await page.mouse.click(50, 50); + await waitNextFrame(page, 200); + await assertNoteRectEqual(page, noteId, initRect); + + await redoByClick(page); + await waitNextFrame(page, 200); + await assertNoteRectEqual(page, noteId, draggedRect); +}); + +test('drag to add customized size note', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + + await switchEditorMode(page); + await zoomResetByKeyboard(page); + await setEdgelessTool(page, 'note'); + // add note at 300,300 + await page.mouse.move(300, 300); + await page.mouse.down(); + await page.mouse.move(900, 600, { steps: 10 }); + await page.mouse.up(); + // should wait for inline editor update and resizeObserver callback + await waitForInlineEditorStateUpdated(page); + + // assert add note success + await assertBlockCount(page, 'edgeless-note', 2); + + // click out of note + await page.mouse.click(250, 200); + // click on note to select it + await page.mouse.click(600, 500); + // assert selected note + // note add on edgeless mode will have a offsetX of 30 and offsetY of 40 + await assertEdgelessSelectedRect(page, [270, 260, 600, 300]); +}); + +test('drag to add customized size note: should clamp to min width and min height', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + + await switchEditorMode(page); + await zoomResetByKeyboard(page); + await setEdgelessTool(page, 'note'); + + // add note at 300,300 + await page.mouse.move(300, 300); + await page.mouse.down(); + await page.mouse.move(400, 360, { steps: 10 }); + await page.mouse.up(); + await waitNextFrame(page); + + await waitNextFrame(page); + + // should wait for inline editor update and resizeObserver callback + await waitForInlineEditorStateUpdated(page); + // assert add note success + await assertBlockCount(page, 'edgeless-note', 2); + + // click out of note + await page.mouse.click(250, 200); + // click on note to select it + await page.mouse.click(320, 300); + // assert selected note + // note add on edgeless mode will have a offsetX of 30 and offsetY of 40 + await assertEdgelessSelectedRect(page, [ + 270, + 260, + NOTE_MIN_WIDTH, + NOTE_MIN_HEIGHT, + ]); +}); diff --git a/blocksuite/tests-legacy/edgeless/note/scale.spec.ts b/blocksuite/tests-legacy/edgeless/note/scale.spec.ts new file mode 100644 index 0000000000000..97c65157f9909 --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/note/scale.spec.ts @@ -0,0 +1,146 @@ +import { expect, type Page } from '@playwright/test'; +import { + addNote, + locatorScalePanelButton, + selectNoteInEdgeless, + switchEditorMode, + triggerComponentToolbarAction, + zoomResetByKeyboard, +} from 'utils/actions/edgeless.js'; +import { + copyByKeyboard, + pasteByKeyboard, + selectAllByKeyboard, +} from 'utils/actions/keyboard.js'; +import { + enterPlaygroundRoom, + initEmptyEdgelessState, + waitNextFrame, +} from 'utils/actions/misc.js'; +import { assertRectExist } from 'utils/asserts.js'; +import { test } from 'utils/playwright.js'; + +async function setupAndAddNote(page: Page) { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + await zoomResetByKeyboard(page); + const noteId = await addNote(page, 'hello world', 100, 200); + await page.mouse.click(0, 0); + return noteId; +} + +async function openScalePanel(page: Page, noteId: string) { + await selectNoteInEdgeless(page, noteId); + await triggerComponentToolbarAction(page, 'changeNoteScale'); + await waitNextFrame(page); + const scalePanel = page.locator('edgeless-scale-panel'); + await expect(scalePanel).toBeVisible(); + return scalePanel; +} + +async function checkNoteScale( + page: Page, + noteId: string, + expectedScale: number, + expectedType: 'equal' | 'greater' | 'less' = 'equal' +) { + const edgelessNote = page.locator( + `affine-edgeless-note[data-block-id="${noteId}"]` + ); + const noteContainer = edgelessNote.locator('.edgeless-note-container'); + const style = await noteContainer.getAttribute('style'); + + if (!style) { + throw new Error('Style attribute not found'); + } + + const scaleMatch = style.match(/transform:\s*scale\(([\d.]+)\)/); + if (!scaleMatch) { + throw new Error('Scale transform not found in style'); + } + + const actualScale = parseFloat(scaleMatch[1]); + + switch (expectedType) { + case 'equal': + expect(actualScale).toBeCloseTo(expectedScale, 2); + break; + case 'greater': + expect(actualScale).toBeGreaterThan(expectedScale); + break; + case 'less': + expect(actualScale).toBeLessThan(expectedScale); + } +} + +test.describe('note scale', () => { + test('Note scale can be changed by scale panel button', async ({ page }) => { + const noteId = await setupAndAddNote(page); + await openScalePanel(page, noteId); + + const scale150 = locatorScalePanelButton(page, 50); + await scale150.click(); + + await checkNoteScale(page, noteId, 0.5); + }); + + test('Note scale can be changed by scale panel input', async ({ page }) => { + const noteId = await setupAndAddNote(page); + const scalePanel = await openScalePanel(page, noteId); + + const scaleInput = scalePanel.locator('.scale-input'); + await scaleInput.click(); + await page.keyboard.type('50'); + await page.keyboard.press('Enter'); + + await checkNoteScale(page, noteId, 0.5); + }); + + test('Note scale input support copy paste', async ({ page }) => { + const noteId = await setupAndAddNote(page); + const scalePanel = await openScalePanel(page, noteId); + + const scaleInput = scalePanel.locator('.scale-input'); + await scaleInput.click(); + await page.keyboard.type('50'); + await selectAllByKeyboard(page); + await copyByKeyboard(page); + await page.mouse.click(0, 0); + + await selectNoteInEdgeless(page, noteId); + await triggerComponentToolbarAction(page, 'changeNoteScale'); + await waitNextFrame(page); + + await scaleInput.click(); + await pasteByKeyboard(page); + await page.keyboard.press('Enter'); + + await checkNoteScale(page, noteId, 0.5); + }); + + test('Note scale can be changed by shift drag', async ({ page }) => { + const noteId = await setupAndAddNote(page); + await selectNoteInEdgeless(page, noteId); + + const edgelessNote = page.locator( + `affine-edgeless-note[data-block-id="${noteId}"]` + ); + const noteRect = await edgelessNote.boundingBox(); + assertRectExist(noteRect); + await page.mouse.move( + noteRect.x + noteRect.width, + noteRect.y + noteRect.height + ); + await page.keyboard.down('Shift'); + await page.mouse.down(); + await page.mouse.move( + noteRect.x + noteRect.width * 2, + noteRect.y + noteRect.height * 2 + ); + await page.mouse.up(); + + // expect style scale to be greater than 1 + await checkNoteScale(page, noteId, 1, 'greater'); + }); +}); diff --git a/blocksuite/tests-legacy/edgeless/note/slicer.spec.ts b/blocksuite/tests-legacy/edgeless/note/slicer.spec.ts new file mode 100644 index 0000000000000..4b40031d9c8d5 --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/note/slicer.spec.ts @@ -0,0 +1,156 @@ +import { expect } from '@playwright/test'; + +import { + enterPlaygroundRoom, + initEmptyEdgelessState, + initSixParagraphs, + initThreeParagraphs, + selectNoteInEdgeless, + switchEditorMode, + triggerComponentToolbarAction, +} from '../../utils/actions/index.js'; +import { assertRectExist, assertRichTexts } from '../../utils/asserts.js'; +import { test } from '../../utils/playwright.js'; + +test.describe('note slicer', () => { + test('could enable and disenable note slicer', async ({ page }) => { + await enterPlaygroundRoom(page); + const { noteId } = await initEmptyEdgelessState(page); + await initSixParagraphs(page); + + await switchEditorMode(page); + await selectNoteInEdgeless(page, noteId); + // note slicer button should not be visible when note slicer setting is disenabled + await expect(page.locator('.note-slicer-button')).toBeHidden(); + await expect(page.locator('.note-slicer-dividing-line')).toHaveCount(0); + + await triggerComponentToolbarAction(page, 'changeNoteSlicerSetting'); + // note slicer button should be visible when note slicer setting is enabled + await expect(page.locator('.note-slicer-button')).toBeVisible(); + await expect(page.locator('.note-slicer-dividing-line')).toHaveCount(5); + }); + + test('note slicer will add new note', async ({ page }) => { + await enterPlaygroundRoom(page); + const { noteId } = await initEmptyEdgelessState(page); + await initSixParagraphs(page); + + await switchEditorMode(page); + await expect(page.locator('affine-edgeless-note')).toHaveCount(1); + + await selectNoteInEdgeless(page, noteId); + await triggerComponentToolbarAction(page, 'changeNoteSlicerSetting'); + await expect(page.locator('.note-slicer-button')).toBeVisible(); + + await page.locator('.note-slicer-button').click(); + + await expect(page.locator('affine-edgeless-note')).toHaveCount(2); + }); + + test('note slicer button should appears at right position', async ({ + page, + }) => { + await enterPlaygroundRoom(page); + const { noteId } = await initEmptyEdgelessState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + await switchEditorMode(page); + await selectNoteInEdgeless(page, noteId); + await triggerComponentToolbarAction(page, 'changeNoteSlicerSetting'); + + const blocks = await page + .locator(`[data-block-id="${noteId}"] [data-block-id]`) + .all(); + expect(blocks.length).toBe(3); + + const firstBlockRect = await blocks[0].boundingBox(); + assertRectExist(firstBlockRect); + const secondBlockRect = await blocks[1].boundingBox(); + assertRectExist(secondBlockRect); + await page.mouse.move( + secondBlockRect.x + 1, + secondBlockRect.y + secondBlockRect.height / 2 + ); + + let slicerButtonRect = await page + .locator('.note-slicer-button') + .boundingBox(); + assertRectExist(slicerButtonRect); + + let buttonRectMiddle = slicerButtonRect.y + slicerButtonRect.height / 2; + + expect(buttonRectMiddle).toBeGreaterThan( + firstBlockRect.y + firstBlockRect.height + ); + expect(buttonRectMiddle).toBeGreaterThan(secondBlockRect.y); + + const thirdBlockRect = await blocks[2].boundingBox(); + assertRectExist(thirdBlockRect); + await page.mouse.move( + thirdBlockRect.x + 1, + thirdBlockRect.y + thirdBlockRect.height / 2 + ); + + slicerButtonRect = await page.locator('.note-slicer-button').boundingBox(); + assertRectExist(slicerButtonRect); + + buttonRectMiddle = slicerButtonRect.y + slicerButtonRect.height / 2; + expect(buttonRectMiddle).toBeGreaterThan( + secondBlockRect.y + secondBlockRect.height + ); + expect(buttonRectMiddle).toBeLessThan(thirdBlockRect.y); + }); + + test('note slicer button should appears at right position when editor is not located at left top corner', async ({ + page, + }) => { + await enterPlaygroundRoom(page); + const { noteId } = await initEmptyEdgelessState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + await switchEditorMode(page); + await selectNoteInEdgeless(page, noteId); + + await page.evaluate(() => { + const el = document.createElement('div'); + const app = document.querySelector('#app') as HTMLElement; + + el.style.height = '100px'; + el.style.background = 'red'; + + app!.style.paddingLeft = '80px'; + + document.body.insertBefore(el, app); + }); + + const blocks = await page + .locator(`[data-block-id="${noteId}"] [data-block-id]`) + .all(); + expect(blocks.length).toBe(3); + + const firstBlockRect = await blocks[0].boundingBox(); + assertRectExist(firstBlockRect); + const secondBlockRect = await blocks[1].boundingBox(); + assertRectExist(secondBlockRect); + + await triggerComponentToolbarAction(page, 'changeNoteSlicerSetting'); + await page.mouse.move( + secondBlockRect.x + 1, + secondBlockRect.y + secondBlockRect.height / 2 + ); + + const slicerButtonRect = await page + .locator('.note-slicer-button') + .boundingBox(); + assertRectExist(slicerButtonRect); + + const buttonRectMiddle = slicerButtonRect.y + slicerButtonRect.height / 2; + + expect(buttonRectMiddle).toBeGreaterThan( + firstBlockRect.y + firstBlockRect.height + ); + expect(buttonRectMiddle).toBeGreaterThan(secondBlockRect.y); + }); +}); diff --git a/blocksuite/tests-legacy/edgeless/note/undo-redo.spec.ts b/blocksuite/tests-legacy/edgeless/note/undo-redo.spec.ts new file mode 100644 index 0000000000000..48c5e5129a260 --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/note/undo-redo.spec.ts @@ -0,0 +1,140 @@ +import { expect } from '@playwright/test'; + +import { + activeNoteInEdgeless, + click, + copyByKeyboard, + countBlock, + dragBetweenCoords, + enterPlaygroundRoom, + fillLine, + focusRichText, + getNoteRect, + initEmptyEdgelessState, + initSixParagraphs, + pasteByKeyboard, + redoByClick, + redoByKeyboard, + selectNoteInEdgeless, + switchEditorMode, + triggerComponentToolbarAction, + type, + undoByClick, + undoByKeyboard, + waitNextFrame, + zoomResetByKeyboard, +} from '../../utils/actions/index.js'; +import { assertRectEqual } from '../../utils/asserts.js'; +import { test } from '../../utils/playwright.js'; + +test('undo/redo should work correctly after clipping', async ({ page }) => { + await enterPlaygroundRoom(page); + const { noteId } = await initEmptyEdgelessState(page); + await initSixParagraphs(page); + + await switchEditorMode(page); + await expect(page.locator('affine-edgeless-note')).toHaveCount(1); + + await selectNoteInEdgeless(page, noteId); + await triggerComponentToolbarAction(page, 'changeNoteSlicerSetting'); + + const button = page.locator('.note-slicer-button'); + await button.click(); + await expect(page.locator('affine-edgeless-note')).toHaveCount(2); + + await undoByKeyboard(page); + await waitNextFrame(page); + await expect(page.locator('affine-edgeless-note')).toHaveCount(1); + await redoByKeyboard(page); + await waitNextFrame(page); + await expect(page.locator('affine-edgeless-note')).toHaveCount(2); +}); + +test('undo/redo should work correctly after resizing', async ({ page }) => { + await enterPlaygroundRoom(page); + const { noteId } = await initEmptyEdgelessState(page); + await switchEditorMode(page); + await zoomResetByKeyboard(page); + await activeNoteInEdgeless(page, noteId); + await waitNextFrame(page, 400); + // current implementation may be a little inefficient + await fillLine(page, true); + await page.mouse.click(0, 0); + await waitNextFrame(page, 400); + await selectNoteInEdgeless(page, noteId); + + const initRect = await getNoteRect(page, noteId); + const rightHandle = page.locator('.handle[aria-label="right"] .resize'); + const box = await rightHandle.boundingBox(); + if (box === null) throw new Error(); + + await dragBetweenCoords( + page, + { x: box.x + 5, y: box.y + 5 }, + { x: box.x + 105, y: box.y + 5 } + ); + const draggedRect = await getNoteRect(page, noteId); + assertRectEqual(draggedRect, { + x: initRect.x, + y: initRect.y, + w: initRect.w + 100, + h: draggedRect.h, // not assert `h` here + }); + expect(draggedRect.h).toBe(initRect.h); + + await undoByKeyboard(page); + await waitNextFrame(page); + const undoRect = await getNoteRect(page, noteId); + assertRectEqual(undoRect, initRect); + + await redoByKeyboard(page); + await waitNextFrame(page); + const redoRect = await getNoteRect(page, noteId); + assertRectEqual(redoRect, draggedRect); +}); + +test('continuous undo and redo (note block add operation) should work', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await focusRichText(page); + await type(page, 'hello'); + await switchEditorMode(page); + await click(page, { x: 260, y: 450 }); + await copyByKeyboard(page); + + let count = await countBlock(page, 'affine-edgeless-note'); + expect(count).toBe(1); + + await page.mouse.move(100, 100); + await pasteByKeyboard(page, false); + await waitNextFrame(page, 1000); + + await page.mouse.move(200, 200); + await pasteByKeyboard(page, false); + await waitNextFrame(page, 1000); + + await page.mouse.move(300, 300); + await pasteByKeyboard(page, false); + await waitNextFrame(page, 1000); + + count = await countBlock(page, 'affine-edgeless-note'); + expect(count).toBe(4); + + await undoByClick(page); + count = await countBlock(page, 'affine-edgeless-note'); + expect(count).toBe(3); + + await undoByClick(page); + count = await countBlock(page, 'affine-edgeless-note'); + expect(count).toBe(2); + + await redoByClick(page); + count = await countBlock(page, 'affine-edgeless-note'); + expect(count).toBe(3); + + await redoByClick(page); + count = await countBlock(page, 'affine-edgeless-note'); + expect(count).toBe(4); +}); diff --git a/blocksuite/tests-legacy/edgeless/pan.spec.ts b/blocksuite/tests-legacy/edgeless/pan.spec.ts new file mode 100644 index 0000000000000..1b8cf2b465de2 --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/pan.spec.ts @@ -0,0 +1,263 @@ +import { expect, type Locator, type Page } from '@playwright/test'; + +import { + activeNoteInEdgeless, + addNote, + assertEdgelessTool, + locatorEdgelessToolButton, + multiTouchDown, + multiTouchMove, + multiTouchUp, + setEdgelessTool, + switchEditorMode, +} from '../utils/actions/edgeless.js'; +import { + addBasicRectShapeElement, + dragBetweenCoords, + enterPlaygroundRoom, + initEmptyEdgelessState, + toggleEditorReadonly, + type, + waitForInlineEditorStateUpdated, + waitNextFrame, +} from '../utils/actions/index.js'; +import { + assertEdgelessSelectedRect, + assertNotHasClass, + assertRichTexts, +} from '../utils/asserts.js'; +import { test } from '../utils/playwright.js'; + +test('pan tool basic', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + + const start = { x: 100, y: 100 }; + const end = { x: 200, y: 200 }; + await addBasicRectShapeElement(page, start, end); + + await setEdgelessTool(page, 'pan'); + await dragBetweenCoords( + page, + { + x: start.x + 5, + y: start.y + 5, + }, + { + x: start.x + 25, + y: start.y + 25, + } + ); + await setEdgelessTool(page, 'default'); + + await page.mouse.click(start.x + 25, start.y + 25); + await assertEdgelessSelectedRect(page, [120, 120, 100, 100]); +}); + +test('pan tool shortcut', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + + const start = { x: 100, y: 100 }; + const end = { x: 200, y: 200 }; + await addBasicRectShapeElement(page, start, end); + + await page.mouse.click(start.x + 5, start.y + 5); + await assertEdgelessSelectedRect(page, [100, 100, 100, 100]); + + await page.keyboard.down('Space'); + await assertEdgelessTool(page, 'pan'); + + await dragBetweenCoords( + page, + { + x: start.x + 5, + y: start.y + 5, + }, + { + x: start.x + 25, + y: start.y + 25, + } + ); + + await page.keyboard.up('Space'); + await assertEdgelessSelectedRect(page, [120, 120, 100, 100]); +}); + +// FIXME(@doouding): Failed on CI +test.skip('pan tool with middle button', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + + const start = { x: 100, y: 100 }; + const end = { x: 200, y: 200 }; + await addBasicRectShapeElement(page, start, end); + + await page.mouse.click(start.x + 5, start.y + 5); + await assertEdgelessSelectedRect(page, [100, 100, 100, 100]); + + await dragBetweenCoords( + page, + { + x: 400, + y: 400, + }, + { + x: 420, + y: 420, + }, + { + button: 'middle', + } + ); + + await assertEdgelessTool(page, 'default'); + await assertEdgelessSelectedRect(page, [120, 120, 100, 100]); +}); + +test('pan tool shortcut should revert to the previous tool on keyup', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + await page.mouse.click(100, 100); + + await setEdgelessTool(page, 'brush'); + { + await page.keyboard.down('Space'); + await assertEdgelessTool(page, 'pan'); + + await page.keyboard.up('Space'); + await assertEdgelessTool(page, 'brush'); + } +}); + +test('pan tool shortcut does not affect other tools while using the tool', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + + // Test if while drawing shortcut does not switch to pan tool + await setEdgelessTool(page, 'brush'); + await dragBetweenCoords( + page, + { x: 100, y: 110 }, + { x: 200, y: 300 }, + { + click: true, + beforeMouseUp: async () => { + await page.keyboard.down('Space'); + await assertEdgelessTool(page, 'brush'); + }, + } + ); + + await setEdgelessTool(page, 'eraser'); + await dragBetweenCoords( + page, + { x: 100, y: 110 }, + { x: 200, y: 300 }, + { + click: true, + beforeMouseUp: async () => { + await page.keyboard.down('Space'); + await assertEdgelessTool(page, 'eraser'); + }, + } + ); + // Maybe add other tools too +}); + +test('pan tool shortcut when user is editing', async ({ page }) => { + await enterPlaygroundRoom(page); + const ids = await initEmptyEdgelessState(page); + await switchEditorMode(page); + await setEdgelessTool(page, 'default'); + + await activeNoteInEdgeless(page, ids.noteId); + await waitForInlineEditorStateUpdated(page); + + await type(page, 'hello'); + await assertRichTexts(page, ['hello']); + + await page.keyboard.down('Space'); + const defaultButton = await locatorEdgelessToolButton(page, 'pan', false); + await assertNotHasClass(defaultButton, 'pan'); + await waitNextFrame(page); +}); + +test.describe('pan tool in readonly mode', () => { + async function setupReadonlyEdgeless(page: Page) { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + + const noteId = await addNote(page, 'hello world', 100, 200); + await page.mouse.click(50, 100); + + const edgelessNote = page.locator( + `affine-edgeless-note[data-block-id="${noteId}"]` + ); + const originalBoundingBox = await edgelessNote.boundingBox(); + expect(originalBoundingBox).not.toBeNull(); + const { x: originalX, y: originalY } = originalBoundingBox!; + + // Toggle readonly mode + await toggleEditorReadonly(page); + await page.waitForTimeout(100); + + return { edgelessNote, originalX, originalY }; + } + + async function assertPanned( + edgelessNote: Locator, + originalX: number, + originalY: number + ) { + const newBoundingBox = await edgelessNote.boundingBox(); + expect(newBoundingBox).not.toBeNull(); + const { x: newX, y: newY } = newBoundingBox!; + + expect(newX).toBeGreaterThan(originalX); + expect(newY).toBeGreaterThan(originalY); + } + + test('can be used by keyboard', async ({ page }) => { + const { edgelessNote, originalX, originalY } = + await setupReadonlyEdgeless(page); + + await page.keyboard.down('Space'); + await assertEdgelessTool(page, 'pan'); + + // Pan the viewport + await dragBetweenCoords(page, { x: 300, y: 300 }, { x: 400, y: 400 }); + + await assertPanned(edgelessNote, originalX, originalY); + }); + + test('can be used by multi-touch', async ({ page }) => { + const { edgelessNote, originalX, originalY } = + await setupReadonlyEdgeless(page); + + // Pan the viewport using multi-touch + const from = [ + { x: 300, y: 300 }, + { x: 400, y: 300 }, + ]; + const to = [ + { x: 350, y: 350 }, + { x: 450, y: 350 }, + ]; + await multiTouchDown(page, from); + await multiTouchMove(page, from, to); + await multiTouchUp(page, to); + + await assertPanned(edgelessNote, originalX, originalY); + }); +}); diff --git a/blocksuite/tests-legacy/edgeless/paste-block.spec.ts b/blocksuite/tests-legacy/edgeless/paste-block.spec.ts new file mode 100644 index 0000000000000..280ba6954032d --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/paste-block.spec.ts @@ -0,0 +1,127 @@ +import { expect, type Page } from '@playwright/test'; +import { + click, + copyByKeyboard, + enterPlaygroundRoom, + focusRichText, + getAllEdgelessNoteIds, + getAllEdgelessTextIds, + getNoteBoundBoxInEdgeless, + initEmptyEdgelessState, + pasteByKeyboard, + pasteTestImage, + pressEnter, + pressEnterWithShortkey, + pressEscape, + selectAllByKeyboard, + setEdgelessTool, + switchEditorMode, + type, +} from 'utils/actions/index.js'; + +import { test } from '../utils/playwright.js'; + +test.describe('pasting blocks', () => { + const initContent = async (page: Page) => { + // Text + await type(page, 'hello'); + await pressEnter(page); + // Image + await pasteTestImage(page); + await pressEnter(page); + // Text + await type(page, 'world'); + await pressEnter(page); + // code + await type(page, '``` '); + await type(page, 'code'); + await pressEnterWithShortkey(page); + }; + test('pasting a note block', async ({ page }) => { + await enterPlaygroundRoom(page); + const { noteId } = await initEmptyEdgelessState(page); + await focusRichText(page); + await initContent(page); + await switchEditorMode(page); + const box = await getNoteBoundBoxInEdgeless(page, noteId); + await click(page, { + x: box.x + 10, + y: box.y + 10, + }); + await copyByKeyboard(page); + await pasteByKeyboard(page); + // not equal to noteId + const noteIds = await getAllEdgelessNoteIds(page); + expect(noteIds.length).toBe(2); + expect(noteIds[0]).toBe(noteId); + const newNoteId = noteIds[1]; + const newNote = page.locator( + `affine-edgeless-note[data-block-id="${newNoteId}"]` + ); + await expect(newNote).toBeVisible(); + const blocks = newNote.locator('[data-block-id]'); + await expect(blocks.nth(0)).toContainText('hello'); + await expect(blocks.nth(1).locator('.resizable-img')).toBeVisible(); + await expect(blocks.nth(2)).toContainText('world'); + await expect(blocks.nth(3)).toContainText('code'); + }); + test('pasting a edgeless block', async ({ page }) => { + await enterPlaygroundRoom(page, { + flags: { + enable_edgeless_text: true, + }, + }); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + await setEdgelessTool(page, 'default'); + await page.mouse.dblclick(130, 140, { + delay: 100, + }); + await initContent(page); + await pressEscape(page, 3); + await page.mouse.click(130, 140); + await copyByKeyboard(page); + await pasteByKeyboard(page); + const textIds = await getAllEdgelessTextIds(page); + expect(textIds.length).toBe(2); + const newTextId = textIds[1]; + const newText = page.locator( + `affine-edgeless-text[data-block-id="${newTextId}"]` + ); + await expect(newText).toBeVisible(); + const blocks = newText.locator('[data-block-id]'); + await expect(blocks.nth(0)).toContainText('hello'); + await expect(blocks.nth(1).locator('.resizable-img')).toBeVisible(); + await expect(blocks.nth(2)).toContainText('world'); + await expect(blocks.nth(3)).toContainText('code'); + }); + + test('pasting a note block from doc mode', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await focusRichText(page); + await type(page, 'hello world'); + + await selectAllByKeyboard(page); + await copyByKeyboard(page); + + await switchEditorMode(page); + await click(page, { + x: 100, + y: 100, + }); + await pasteByKeyboard(page); + + // not equal to noteId + const noteIds = await getAllEdgelessNoteIds(page); + expect(noteIds.length).toBe(2); + + const newNoteId = noteIds[1]; + const newNote = page.locator( + `affine-edgeless-note[data-block-id="${newNoteId}"]` + ); + await expect(newNote).toBeVisible(); + const blocks = newNote.locator('[data-block-id]'); + await expect(blocks.nth(0)).toContainText('hello world'); + }); +}); diff --git a/blocksuite/tests-legacy/edgeless/presentation.spec.ts b/blocksuite/tests-legacy/edgeless/presentation.spec.ts new file mode 100644 index 0000000000000..0531f724b4bf0 --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/presentation.spec.ts @@ -0,0 +1,249 @@ +import { expect } from '@playwright/test'; +import { + assertEdgelessTool, + createFrame, + createNote, + createShapeElement, + dragBetweenViewCoords, + edgelessCommonSetup, + enterPresentationMode, + locatorPresentationToolbarButton, + setEdgelessTool, + Shape, + toggleFramePanel, +} from 'utils/actions/edgeless.js'; +import { + copyByKeyboard, + pasteByKeyboard, + pressEscape, + selectAllBlocksByKeyboard, +} from 'utils/actions/keyboard.js'; +import { waitNextFrame } from 'utils/actions/misc.js'; + +import { test } from '../utils/playwright.js'; + +test.describe('presentation', () => { + test('should render note when enter presentation mode', async ({ page }) => { + await edgelessCommonSetup(page); + await createShapeElement(page, [100, 100], [200, 200], Shape.Square); + await createNote(page, [300, 100], 'hello'); + + // Frame shape + await setEdgelessTool(page, 'frame'); + await dragBetweenViewCoords(page, [80, 80], [220, 220]); + await waitNextFrame(page, 100); + + // Frame note + await setEdgelessTool(page, 'frame'); + await dragBetweenViewCoords(page, [240, 0], [800, 200]); + + expect(await page.locator('affine-frame').count()).toBe(2); + + await enterPresentationMode(page); + await waitNextFrame(page, 100); + + const nextButton = locatorPresentationToolbarButton(page, 'next'); + await nextButton.click(); + const edgelessNote = page.locator('affine-edgeless-note'); + await expect(edgelessNote).toBeVisible(); + + const prevButton = locatorPresentationToolbarButton(page, 'previous'); + await prevButton.click(); + await expect(edgelessNote).toBeHidden(); + + await waitNextFrame(page, 300); + await nextButton.click(); + await expect(edgelessNote).toBeVisible(); + }); + + test('should exit presentation mode when press escape', async ({ page }) => { + await edgelessCommonSetup(page); + await createNote(page, [300, 100], 'hello'); + + // Frame note + await setEdgelessTool(page, 'frame'); + await dragBetweenViewCoords(page, [240, 0], [800, 200]); + + expect(await page.locator('affine-frame').count()).toBe(1); + + await enterPresentationMode(page); + await waitNextFrame(page, 300); + + await assertEdgelessTool(page, 'frameNavigator'); + const navigatorBlackBackground = page.locator( + '.edgeless-navigator-black-background' + ); + await expect(navigatorBlackBackground).toBeVisible(); + + await pressEscape(page); + await waitNextFrame(page, 100); + + await assertEdgelessTool(page, 'default'); + await expect(navigatorBlackBackground).toBeHidden(); + }); + + test('should be able to adjust order of presentation in toolbar', async ({ + page, + }) => { + await edgelessCommonSetup(page); + + await createFrame(page, [100, 100], [100, 200]); + await createFrame(page, [200, 100], [300, 200]); + await createFrame(page, [300, 100], [400, 200]); + await createFrame(page, [400, 100], [500, 200]); + + await enterPresentationMode(page); + + await page.locator('.edgeless-frame-order-button').click(); + const frameItems = page.locator( + 'edgeless-frame-order-menu .item.draggable' + ); + const dragIndicators = page.locator( + 'edgeless-frame-order-menu .drag-indicator' + ); + + await expect(frameItems).toHaveCount(4); + await expect(frameItems.nth(0)).toHaveText('Frame 1'); + await expect(frameItems.nth(1)).toHaveText('Frame 2'); + await expect(frameItems.nth(2)).toHaveText('Frame 3'); + await expect(frameItems.nth(3)).toHaveText('Frame 4'); + + // 1 2 3 4 + await frameItems.nth(2).dragTo(dragIndicators.nth(0)); + // 3 1 2 4 + await frameItems.nth(3).dragTo(dragIndicators.nth(2)); + // 3 1 4 2 + await frameItems.nth(1).dragTo(dragIndicators.nth(3)); + // 3 4 1 2 + + await expect(frameItems).toHaveCount(4); + await expect(frameItems.nth(0)).toHaveText('Frame 3'); + await expect(frameItems.nth(1)).toHaveText('Frame 4'); + await expect(frameItems.nth(2)).toHaveText('Frame 1'); + await expect(frameItems.nth(3)).toHaveText('Frame 2'); + + const currentFrame = page.locator('.edgeless-frame-navigator-title'); + const nextButton = locatorPresentationToolbarButton(page, 'next'); + + await expect(currentFrame).toHaveText('Frame 3'); + await nextButton.click(); + await expect(currentFrame).toHaveText('Frame 4'); + await nextButton.click(); + await expect(currentFrame).toHaveText('Frame 1'); + await nextButton.click(); + await expect(currentFrame).toHaveText('Frame 2'); + }); + + test('should be able to adjust order of presentation in frame panel', async ({ + page, + }) => { + await edgelessCommonSetup(page); + + await createFrame(page, [100, 100], [100, 200]); + await createFrame(page, [200, 100], [300, 200]); + await createFrame(page, [300, 100], [400, 200]); + await createFrame(page, [400, 100], [500, 200]); + + // await enterPresentationMode(page); + + await toggleFramePanel(page); + + // await page.locator('.edgeless-frame-order-button').click(); + const frameCards = page.locator('affine-frame-card .frame-card-body'); + const frameTitles = page.locator('affine-frame-card-title .card-title'); + + await expect(frameTitles).toHaveCount(4); + await expect(frameTitles.nth(0)).toHaveText('Frame 1'); + await expect(frameTitles.nth(1)).toHaveText('Frame 2'); + await expect(frameTitles.nth(2)).toHaveText('Frame 3'); + await expect(frameTitles.nth(3)).toHaveText('Frame 4'); + + const drag = async (from: number, to: number) => { + const startBBox = await frameCards.nth(from).boundingBox(); + expect(startBBox).not.toBeNull(); + if (startBBox === null) return; + + const endBBox = await frameTitles.nth(to).boundingBox(); + expect(endBBox).not.toBeNull(); + if (endBBox === null) return; + + await page.mouse.move( + startBBox.x + startBBox.width / 2, + startBBox.y + startBBox.height / 2 + ); + await page.mouse.down(); + await page.mouse.move(endBBox.x + endBBox.width / 2, endBBox.y, { + steps: 2, + }); + await page.mouse.up(); + }; + + // 1 2 3 4 + await drag(2, 0); + // 3 1 2 4 + await drag(3, 2); + // 3 1 4 2 + await drag(1, 3); + // 3 4 1 2 + + await expect(frameTitles).toHaveCount(4); + await expect(frameTitles.nth(0)).toHaveText('Frame 3'); + await expect(frameTitles.nth(1)).toHaveText('Frame 4'); + await expect(frameTitles.nth(2)).toHaveText('Frame 1'); + await expect(frameTitles.nth(3)).toHaveText('Frame 2'); + + await enterPresentationMode(page); + await page.locator('.edgeless-frame-order-button').click(); + const frameItems = page.locator( + 'edgeless-frame-order-menu .item.draggable' + ); + + await expect(frameItems).toHaveCount(4); + await expect(frameItems.nth(0)).toHaveText('Frame 3'); + await expect(frameItems.nth(1)).toHaveText('Frame 4'); + await expect(frameItems.nth(2)).toHaveText('Frame 1'); + await expect(frameItems.nth(3)).toHaveText('Frame 2'); + + const currentFrame = page.locator('.edgeless-frame-navigator-title'); + const nextButton = locatorPresentationToolbarButton(page, 'next'); + + await expect(currentFrame).toHaveText('Frame 3'); + await nextButton.click(); + await expect(currentFrame).toHaveText('Frame 4'); + await nextButton.click(); + await expect(currentFrame).toHaveText('Frame 1'); + await nextButton.click(); + await expect(currentFrame).toHaveText('Frame 2'); + }); + + test('duplicate frames should keep the presentation orders', async ({ + page, + }) => { + await edgelessCommonSetup(page); + + await createFrame(page, [100, 100], [100, 200]); + await createFrame(page, [200, 100], [300, 200]); + await createFrame(page, [300, 100], [400, 200]); + await createFrame(page, [400, 100], [500, 200]); + + await selectAllBlocksByKeyboard(page); + await copyByKeyboard(page); + await pasteByKeyboard(page); + + await enterPresentationMode(page); + await page.locator('.edgeless-frame-order-button').click(); + const frameItems = page.locator( + 'edgeless-frame-order-menu .item.draggable' + ); + + await expect(frameItems).toHaveCount(8); + await expect(frameItems.nth(0)).toHaveText('Frame 1'); + await expect(frameItems.nth(1)).toHaveText('Frame 2'); + await expect(frameItems.nth(2)).toHaveText('Frame 3'); + await expect(frameItems.nth(3)).toHaveText('Frame 4'); + await expect(frameItems.nth(4)).toHaveText('Frame 1'); + await expect(frameItems.nth(5)).toHaveText('Frame 2'); + await expect(frameItems.nth(6)).toHaveText('Frame 3'); + await expect(frameItems.nth(7)).toHaveText('Frame 4'); + }); +}); diff --git a/blocksuite/tests-legacy/edgeless/reordering.spec.ts b/blocksuite/tests-legacy/edgeless/reordering.spec.ts new file mode 100644 index 0000000000000..999f80cf54f9a --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/reordering.spec.ts @@ -0,0 +1,448 @@ +import { + DEFAULT_NOTE_HEIGHT, + DEFAULT_NOTE_WIDTH, +} from '@blocksuite/affine-model'; +import { expect, type Page } from '@playwright/test'; + +import { + createShapeElement, + edgelessCommonSetup, + getFirstContainerId, + getSelectedBound, + getSortedIds, + initThreeOverlapFilledShapes, + initThreeOverlapNotes, + Shape, + shiftClickView, + switchEditorMode, + triggerComponentToolbarAction, + zoomResetByKeyboard, +} from '../utils/actions/edgeless.js'; +import { + captureHistory, + clickView, + enterPlaygroundRoom, + initEmptyEdgelessState, + redoByKeyboard, + undoByKeyboard, + waitNextFrame, +} from '../utils/actions/index.js'; +import { + assertEdgelessSelectedRect, + assertSelectedBound, +} from '../utils/asserts.js'; +import { test } from '../utils/playwright.js'; + +test.describe('reordering', () => { + test.describe('group index', () => { + let sortedIds: string[]; + + async function init(page: Page) { + await edgelessCommonSetup(page); + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await createShapeElement(page, [100, 0], [200, 100], Shape.Square); + await createShapeElement(page, [200, 0], [300, 100], Shape.Square); + await createShapeElement(page, [300, 0], [400, 100], Shape.Square); + sortedIds = await getSortedIds(page); + } + + test('group', async ({ page }) => { + await init(page); + await clickView(page, [50, 50]); + await shiftClickView(page, [150, 50]); + await triggerComponentToolbarAction(page, 'addGroup'); + const groupId = await getFirstContainerId(page); + const currentSortedIds = await getSortedIds(page); + + expect(currentSortedIds).toEqual([ + ...sortedIds.slice(2), + groupId, + ...sortedIds.slice(0, 2), + ]); + }); + + test('release from group', async ({ page }) => { + await init(page); + await clickView(page, [50, 50]); + await shiftClickView(page, [150, 50]); + await triggerComponentToolbarAction(page, 'addGroup'); + const groupId = await getFirstContainerId(page); + await clickView(page, [50, 50]); + await triggerComponentToolbarAction(page, 'releaseFromGroup'); + const currentSortedIds = await getSortedIds(page); + const releasedShapeId = sortedIds[0]; + + expect(currentSortedIds).toEqual([ + ...sortedIds.slice(2), + groupId, + sortedIds[1], + releasedShapeId, + ]); + }); + + test('ungroup', async ({ page }) => { + await init(page); + await clickView(page, [50, 50]); + await shiftClickView(page, [150, 50]); + await triggerComponentToolbarAction(page, 'addGroup'); + await triggerComponentToolbarAction(page, 'ungroup'); + const currentSortedIds = await getSortedIds(page); + const ungroupedIds = [sortedIds[0], sortedIds[1]]; + + expect(currentSortedIds).toEqual([ + ...sortedIds.filter(id => !ungroupedIds.includes(id)), + ...ungroupedIds, + ]); + }); + }); + + test.describe('reordering shapes', () => { + async function init(page: Page) { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + await initThreeOverlapFilledShapes(page); + await page.mouse.click(0, 0); + } + + test('bring to front', async ({ page }) => { + await init(page); + + // should be rect2 + await page.mouse.click(180, 180); + await assertEdgelessSelectedRect(page, [160, 160, 100, 100]); + + // click outside to clear selection + await page.mouse.click(50, 50); + + // should be rect1 + await page.mouse.click(150, 150); + await assertEdgelessSelectedRect(page, [130, 130, 100, 100]); + + // should be rect0 + await page.mouse.click(110, 130); + await assertEdgelessSelectedRect(page, [100, 100, 100, 100]); + + // bring rect0 to front + await triggerComponentToolbarAction(page, 'bringToFront'); + + // click outside to clear selection + await page.mouse.click(50, 50); + + // should be rect0 + await page.mouse.click(180, 180); + await assertEdgelessSelectedRect(page, [100, 100, 100, 100]); + }); + + test('bring forward', async ({ page }) => { + await init(page); + + // should be rect0 + await page.mouse.click(120, 120); + await assertEdgelessSelectedRect(page, [100, 100, 100, 100]); + + // bring rect0 forward + await triggerComponentToolbarAction(page, 'bringForward'); + + // click outside to clear selection + await page.mouse.click(50, 50); + + // should be rect0 + await page.mouse.click(150, 150); + await assertEdgelessSelectedRect(page, [100, 100, 100, 100]); + }); + + test('send backward', async ({ page }) => { + await init(page); + + // should be rect2 + await page.mouse.click(180, 180); + await assertEdgelessSelectedRect(page, [160, 160, 100, 100]); + + // bring rect2 backward + await triggerComponentToolbarAction(page, 'sendBackward'); + + // click outside to clear selection + await page.mouse.click(50, 50); + + // should be rect1 + await page.mouse.click(180, 180); + await assertEdgelessSelectedRect(page, [130, 130, 100, 100]); + }); + + test('send to back', async ({ page }) => { + await init(page); + + // should be rect2 + await page.mouse.click(180, 180); + await assertEdgelessSelectedRect(page, [160, 160, 100, 100]); + + // bring rect2 to back + await triggerComponentToolbarAction(page, 'sendToBack'); + + // click outside to clear selection + await page.mouse.click(50, 50); + + // should be rect1 + await page.mouse.click(180, 180); + await assertEdgelessSelectedRect(page, [130, 130, 100, 100]); + + // send rect1 to back + await triggerComponentToolbarAction(page, 'sendToBack'); + + // click outside to clear selection + await page.mouse.click(50, 50); + + // should be rect0 + await page.mouse.click(180, 180); + await assertEdgelessSelectedRect(page, [100, 100, 100, 100]); + }); + + test('undo and redo', async ({ page }) => { + await init(page); + + // should be rect2 + await page.mouse.click(180, 180); + await assertEdgelessSelectedRect(page, [160, 160, 100, 100]); + + // send rect2 to back + await triggerComponentToolbarAction(page, 'sendToBack'); + + // click outside to clear selection + await page.mouse.click(50, 50); + + // should be rect1 + await page.mouse.click(180, 180); + await assertEdgelessSelectedRect(page, [130, 130, 100, 100]); + + // undo + await undoByKeyboard(page); + + // clear selection + await page.mouse.click(50, 50); + + // should be rect2 + await page.mouse.click(180, 180); + await assertEdgelessSelectedRect(page, [160, 160, 100, 100]); + + // redo + await redoByKeyboard(page); + + // clear selection + await page.mouse.click(50, 50); + + // should be rect2 + await page.mouse.click(180, 180); + await assertEdgelessSelectedRect(page, [130, 130, 100, 100]); + }); + }); + + test.describe('reordering notes', () => { + async function init(page: Page) { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + await zoomResetByKeyboard(page); + await initThreeOverlapNotes(page); + await waitNextFrame(page); + await page.mouse.click(0, 0); + } + + test('bring to front', async ({ page }) => { + await edgelessCommonSetup(page); + await zoomResetByKeyboard(page); + await initThreeOverlapNotes(page, 130, 190); + await waitNextFrame(page); + // click outside to clear selection + await page.mouse.click(50, 100); + // should be note2 + await page.mouse.click(180, 200); + const bound = await getSelectedBound(page); + + await assertSelectedBound(page, bound); + + await clickView(page, [bound[0] - 15, bound[1] + 10]); + bound[0] -= 30; + await assertSelectedBound(page, bound); + + await clickView(page, [bound[0] - 15, bound[1] + 10]); + bound[0] -= 30; + await assertSelectedBound(page, bound); + + // bring note0 to front + await triggerComponentToolbarAction(page, 'bringToFront'); + // clear + await page.mouse.click(100, 50); + // should be note0 + await clickView(page, [bound[0] + 40, bound[1] + 10]); + await assertSelectedBound(page, bound); + }); + + test('bring forward', async ({ page }) => { + await init(page); + + // click outside to clear selection + await page.mouse.click(50, 50); + + // should be note0 + await page.mouse.click(120, 140); + await assertEdgelessSelectedRect(page, [ + 100, + 100, + DEFAULT_NOTE_WIDTH, + DEFAULT_NOTE_HEIGHT, + ]); + + // bring note0 forward + await triggerComponentToolbarAction(page, 'bringForward'); + + // click outside to clear selection + await page.mouse.click(50, 50); + + // should be rect0 + await page.mouse.click(150, 140); + await assertEdgelessSelectedRect(page, [ + 100, + 100, + DEFAULT_NOTE_WIDTH, + DEFAULT_NOTE_HEIGHT, + ]); + }); + + test('send backward', async ({ page }) => { + await init(page); + + // click outside to clear selection + await page.mouse.click(50, 50); + + // should be note2 + await page.mouse.click(180, 140); + await assertEdgelessSelectedRect(page, [ + 160, + 100, + DEFAULT_NOTE_WIDTH, + DEFAULT_NOTE_HEIGHT, + ]); + + // bring note2 backward + await triggerComponentToolbarAction(page, 'sendBackward'); + + // click outside to clear selection + await page.mouse.click(50, 50); + + // should be note1 + await page.mouse.click(180, 140); + await assertEdgelessSelectedRect(page, [ + 130, + 100, + DEFAULT_NOTE_WIDTH, + DEFAULT_NOTE_HEIGHT, + ]); + }); + + test('send to back', async ({ page }) => { + await init(page); + + // click outside to clear selection + await page.mouse.click(50, 50); + + // should be note2 + await page.mouse.click(180, 140); + await assertEdgelessSelectedRect(page, [ + 160, + 100, + DEFAULT_NOTE_WIDTH, + DEFAULT_NOTE_HEIGHT, + ]); + + // bring note2 to back + await triggerComponentToolbarAction(page, 'sendToBack'); + + // click outside to clear selection + await page.mouse.click(50, 50); + + // should be note1 + await page.mouse.click(180, 140); + await assertEdgelessSelectedRect(page, [ + 130, + 100, + DEFAULT_NOTE_WIDTH, + DEFAULT_NOTE_HEIGHT, + ]); + + // send note1 to back + await triggerComponentToolbarAction(page, 'sendToBack'); + + // click outside to clear selection + await page.mouse.click(50, 50); + + // should be note0 + await page.mouse.click(180, 140); + await assertEdgelessSelectedRect(page, [ + 100, + 100, + DEFAULT_NOTE_WIDTH, + DEFAULT_NOTE_HEIGHT, + ]); + }); + + test('undo and redo', async ({ page }) => { + await init(page); + + // click outside to clear selection + await page.mouse.click(50, 50); + + // should be note2 + await page.mouse.click(180, 140); + await assertEdgelessSelectedRect(page, [ + 160, + 100, + DEFAULT_NOTE_WIDTH, + DEFAULT_NOTE_HEIGHT, + ]); + + await captureHistory(page); + + // bring note2 to back + await triggerComponentToolbarAction(page, 'sendToBack'); + + // click outside to clear selection + await page.mouse.click(50, 50); + + // should be note1 + await page.mouse.click(180, 140); + await assertEdgelessSelectedRect(page, [ + 130, + 100, + DEFAULT_NOTE_WIDTH, + DEFAULT_NOTE_HEIGHT, + ]); + + // undo + await undoByKeyboard(page); + // clear selection + await page.mouse.click(50, 50); + // should be note2 + await page.mouse.click(180, 140); + await assertEdgelessSelectedRect(page, [ + 160, + 100, + DEFAULT_NOTE_WIDTH, + DEFAULT_NOTE_HEIGHT, + ]); + + // redo + await redoByKeyboard(page); + // clear selection + await page.mouse.click(50, 50); + // should be note1 + await page.mouse.click(180, 140); + await assertEdgelessSelectedRect(page, [ + 130, + 100, + DEFAULT_NOTE_WIDTH, + DEFAULT_NOTE_HEIGHT, + ]); + }); + }); +}); diff --git a/blocksuite/tests-legacy/edgeless/resizing.spec.ts b/blocksuite/tests-legacy/edgeless/resizing.spec.ts new file mode 100644 index 0000000000000..46fd70e5d33f9 --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/resizing.spec.ts @@ -0,0 +1,199 @@ +import { + switchEditorMode, + zoomResetByKeyboard, +} from '../utils/actions/edgeless.js'; +import { + addBasicBrushElement, + addBasicRectShapeElement, + dragBetweenCoords, + enterPlaygroundRoom, + initEmptyEdgelessState, + resizeElementByHandle, +} from '../utils/actions/index.js'; +import { + assertEdgelessSelectedReactCursor, + assertEdgelessSelectedRect, +} from '../utils/asserts.js'; +import { test } from '../utils/playwright.js'; + +test.describe('resizing shapes and aspect ratio will be maintained', () => { + test('positive adjustment', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + await zoomResetByKeyboard(page); + + await addBasicBrushElement(page, { x: 100, y: 100 }, { x: 200, y: 200 }); + await page.mouse.click(110, 110); + await assertEdgelessSelectedRect(page, [98, 98, 104, 104]); + + await addBasicRectShapeElement( + page, + { x: 210, y: 110 }, + { x: 310, y: 210 } + ); + await page.mouse.click(220, 120); + await assertEdgelessSelectedRect(page, [210, 110, 100, 100]); + + await dragBetweenCoords(page, { x: 120, y: 90 }, { x: 220, y: 130 }); + await assertEdgelessSelectedRect(page, [98, 98, 212, 112]); + + await resizeElementByHandle(page, { x: 50, y: 50 }); + await assertEdgelessSelectedRect(page, [148, 124.19, 162, 85.81]); + + await page.mouse.move(160, 160); + await assertEdgelessSelectedRect(page, [148, 124.19, 162, 85.81]); + + await page.mouse.move(260, 160); + await assertEdgelessSelectedRect(page, [148, 124.19, 162, 85.81]); + }); + + test('negative adjustment', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + await zoomResetByKeyboard(page); + + await addBasicBrushElement(page, { x: 100, y: 100 }, { x: 200, y: 200 }); + await page.mouse.click(110, 110); + await assertEdgelessSelectedRect(page, [98, 98, 104, 104]); + + await addBasicRectShapeElement( + page, + { x: 210, y: 110 }, + { x: 310, y: 210 } + ); + await page.mouse.click(220, 120); + await assertEdgelessSelectedRect(page, [210, 110, 100, 100]); + + await dragBetweenCoords(page, { x: 120, y: 90 }, { x: 220, y: 130 }); + await assertEdgelessSelectedRect(page, [98, 98, 212, 112]); + + await resizeElementByHandle(page, { x: 400, y: 300 }, 'top-left', 30); + await assertEdgelessSelectedRect(page, [310, 210, 356, 188]); + + await page.mouse.move(450, 300); + await assertEdgelessSelectedRect(page, [310, 210, 356, 188]); + + await page.mouse.move(320, 220); + await assertEdgelessSelectedRect(page, [310, 210, 356, 188]); + }); +}); + +test.describe('cursor style', () => { + test('editor is aligned at the start of viewport', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + await zoomResetByKeyboard(page); + + await addBasicRectShapeElement( + page, + { x: 200, y: 200 }, + { x: 300, y: 300 } + ); + await page.mouse.click(250, 250); + await assertEdgelessSelectedRect(page, [200, 200, 100, 100]); + + await assertEdgelessSelectedReactCursor(page, { + mode: 'resize', + handle: 'top', + cursor: 'ns-resize', + }); + await assertEdgelessSelectedReactCursor(page, { + mode: 'resize', + handle: 'right', + cursor: 'ew-resize', + }); + await assertEdgelessSelectedReactCursor(page, { + mode: 'resize', + handle: 'bottom', + cursor: 'ns-resize', + }); + await assertEdgelessSelectedReactCursor(page, { + mode: 'resize', + handle: 'left', + cursor: 'ew-resize', + }); + await assertEdgelessSelectedReactCursor(page, { + mode: 'resize', + handle: 'top-left', + cursor: 'nwse-resize', + }); + await assertEdgelessSelectedReactCursor(page, { + mode: 'resize', + handle: 'top-right', + cursor: 'nesw-resize', + }); + await assertEdgelessSelectedReactCursor(page, { + mode: 'resize', + handle: 'bottom-left', + cursor: 'nesw-resize', + }); + await assertEdgelessSelectedReactCursor(page, { + mode: 'resize', + handle: 'bottom-right', + cursor: 'nwse-resize', + }); + }); + + test('editor is not aligned at the start of viewport', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + await zoomResetByKeyboard(page); + + await page.addStyleTag({ + content: 'body { padding: 100px 150px; }', + }); + + await addBasicRectShapeElement( + page, + { x: 200, y: 200 }, + { x: 300, y: 300 } + ); + await page.mouse.click(250, 250); + await assertEdgelessSelectedRect(page, [200, 200, 100, 100]); + + await assertEdgelessSelectedReactCursor(page, { + mode: 'resize', + handle: 'top', + cursor: 'ns-resize', + }); + await assertEdgelessSelectedReactCursor(page, { + mode: 'resize', + handle: 'right', + cursor: 'ew-resize', + }); + await assertEdgelessSelectedReactCursor(page, { + mode: 'resize', + handle: 'bottom', + cursor: 'ns-resize', + }); + await assertEdgelessSelectedReactCursor(page, { + mode: 'resize', + handle: 'left', + cursor: 'ew-resize', + }); + await assertEdgelessSelectedReactCursor(page, { + mode: 'resize', + handle: 'top-left', + cursor: 'nwse-resize', + }); + await assertEdgelessSelectedReactCursor(page, { + mode: 'resize', + handle: 'top-right', + cursor: 'nesw-resize', + }); + await assertEdgelessSelectedReactCursor(page, { + mode: 'resize', + handle: 'bottom-left', + cursor: 'nesw-resize', + }); + await assertEdgelessSelectedReactCursor(page, { + mode: 'resize', + handle: 'bottom-right', + cursor: 'nwse-resize', + }); + }); +}); diff --git a/blocksuite/tests-legacy/edgeless/rotation.spec.ts b/blocksuite/tests-legacy/edgeless/rotation.spec.ts new file mode 100644 index 0000000000000..49bab473fc52b --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/rotation.spec.ts @@ -0,0 +1,227 @@ +import { + addBasicRectShapeElement, + dragBetweenCoords, + enterPlaygroundRoom, + initEmptyEdgelessState, + resizeElementByHandle, + rotateElementByHandle, + switchEditorMode, +} from '../utils/actions/index.js'; +import { + assertEdgelessSelectedReactCursor, + assertEdgelessSelectedRect, + assertEdgelessSelectedRectRotation, +} from '../utils/asserts.js'; +import { test } from '../utils/playwright.js'; + +test.describe('rotation', () => { + test('angle adjustment by four corners', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + + await addBasicRectShapeElement( + page, + { x: 100, y: 100 }, + { x: 200, y: 200 } + ); + + await rotateElementByHandle(page, 45, 'top-left'); + await assertEdgelessSelectedRectRotation(page, 45); + + await rotateElementByHandle(page, 45, 'top-right'); + await assertEdgelessSelectedRectRotation(page, 90); + + await rotateElementByHandle(page, 45, 'bottom-right'); + await assertEdgelessSelectedRectRotation(page, 135); + + await rotateElementByHandle(page, 45, 'bottom-left'); + await assertEdgelessSelectedRectRotation(page, 180); + }); + + test('angle snap', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + + await addBasicRectShapeElement( + page, + { x: 100, y: 100 }, + { x: 200, y: 200 } + ); + + await page.keyboard.down('Shift'); + + await rotateElementByHandle(page, 5); + await assertEdgelessSelectedRectRotation(page, 0); + + await rotateElementByHandle(page, 10); + await assertEdgelessSelectedRectRotation(page, 15); + + await rotateElementByHandle(page, 10); + await assertEdgelessSelectedRectRotation(page, 30); + + await rotateElementByHandle(page, 10); + await assertEdgelessSelectedRectRotation(page, 45); + + await rotateElementByHandle(page, 5); + await assertEdgelessSelectedRectRotation(page, 45); + + await page.keyboard.up('Shift'); + }); + + test('single shape', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + + await addBasicRectShapeElement( + page, + { x: 100, y: 100 }, + { x: 200, y: 200 } + ); + await assertEdgelessSelectedRect(page, [100, 100, 100, 100]); + + await rotateElementByHandle(page, 45, 'top-right'); + await assertEdgelessSelectedRectRotation(page, 45); + }); + + test('multiple shapes', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + + await addBasicRectShapeElement( + page, + { x: 100, y: 100 }, + { x: 200, y: 200 } + ); + await addBasicRectShapeElement( + page, + { x: 200, y: 100 }, + { x: 300, y: 200 } + ); + + await dragBetweenCoords(page, { x: 90, y: 90 }, { x: 310, y: 110 }); + await assertEdgelessSelectedRect(page, [100, 100, 200, 100]); + + await rotateElementByHandle(page, 90, 'bottom-right'); + await assertEdgelessSelectedRectRotation(page, 0); + await assertEdgelessSelectedRect(page, [150, 50, 100, 200]); + }); + + test('combination with resizing', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + + await addBasicRectShapeElement( + page, + { x: 100, y: 100 }, + { x: 200, y: 200 } + ); + await rotateElementByHandle(page, 90, 'bottom-left'); + await assertEdgelessSelectedRectRotation(page, 90); + + await resizeElementByHandle(page, { x: 10, y: -10 }, 'bottom-right'); + await assertEdgelessSelectedRect(page, [110, 100, 90, 90]); + + await rotateElementByHandle(page, -90, 'bottom-right'); + await assertEdgelessSelectedRectRotation(page, 0); + + await resizeElementByHandle(page, { x: 10, y: 10 }, 'bottom-right'); + await assertEdgelessSelectedRect(page, [110, 100, 100, 100]); + }); + + test('combination with resizing for multiple shapes', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + + await addBasicRectShapeElement( + page, + { x: 100, y: 100 }, + { x: 200, y: 200 } + ); + await addBasicRectShapeElement( + page, + { x: 200, y: 100 }, + { x: 300, y: 200 } + ); + + await dragBetweenCoords(page, { x: 90, y: 90 }, { x: 310, y: 110 }); + await assertEdgelessSelectedRect(page, [100, 100, 200, 100]); + + await rotateElementByHandle(page, 90, 'bottom-left'); + await assertEdgelessSelectedRectRotation(page, 0); + await assertEdgelessSelectedRect(page, [150, 50, 100, 200]); + + await resizeElementByHandle(page, { x: -10, y: -20 }, 'bottom-right'); + await assertEdgelessSelectedRect(page, [150, 50, 90, 180]); + + await rotateElementByHandle(page, -90, 'bottom-right'); + await assertEdgelessSelectedRectRotation(page, 0); + await assertEdgelessSelectedRect(page, [105, 95, 180, 90]); + + await resizeElementByHandle(page, { x: 20, y: 10 }, 'bottom-right'); + await assertEdgelessSelectedRect(page, [105, 95, 200, 100]); + }); +}); + +test.describe('cursor style', () => { + test('update resize cursor direction after rotating', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + + await addBasicRectShapeElement( + page, + { x: 100, y: 100 }, + { x: 200, y: 200 } + ); + + await rotateElementByHandle(page, 45, 'top-left'); + await assertEdgelessSelectedRectRotation(page, 45); + + await assertEdgelessSelectedReactCursor(page, { + mode: 'resize', + handle: 'top', + cursor: 'nesw-resize', + }); + await assertEdgelessSelectedReactCursor(page, { + mode: 'resize', + handle: 'right', + cursor: 'nwse-resize', + }); + await assertEdgelessSelectedReactCursor(page, { + mode: 'resize', + handle: 'bottom', + cursor: 'nesw-resize', + }); + await assertEdgelessSelectedReactCursor(page, { + mode: 'resize', + handle: 'left', + cursor: 'nwse-resize', + }); + await assertEdgelessSelectedReactCursor(page, { + mode: 'resize', + handle: 'top-right', + cursor: 'ew-resize', + }); + await assertEdgelessSelectedReactCursor(page, { + mode: 'resize', + handle: 'top-left', + cursor: 'ns-resize', + }); + await assertEdgelessSelectedReactCursor(page, { + mode: 'resize', + handle: 'bottom-right', + cursor: 'ns-resize', + }); + await assertEdgelessSelectedReactCursor(page, { + mode: 'resize', + handle: 'bottom-left', + cursor: 'ew-resize', + }); + }); +}); diff --git a/blocksuite/tests-legacy/edgeless/selection/connector.spec.ts b/blocksuite/tests-legacy/edgeless/selection/connector.spec.ts new file mode 100644 index 0000000000000..f0f7a351e0572 --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/selection/connector.spec.ts @@ -0,0 +1,94 @@ +import { expect } from '@playwright/test'; + +import * as actions from '../../utils/actions/edgeless.js'; +import { + addBasicConnectorElement, + createConnectorElement, + createShapeElement, + dragBetweenCoords, + enterPlaygroundRoom, + initEmptyEdgelessState, + Shape, + switchEditorMode, + toModelCoord, + waitNextFrame, +} from '../../utils/actions/index.js'; +import { test } from '../../utils/playwright.js'; + +test.describe('select multiple connectors', () => { + test('should show single selection rect', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + await actions.zoomResetByKeyboard(page); + + await addBasicConnectorElement( + page, + { x: 100, y: 200 }, + { x: 300, y: 200 } + ); + await addBasicConnectorElement( + page, + { x: 100, y: 230 }, + { x: 300, y: 230 } + ); + await addBasicConnectorElement( + page, + { x: 100, y: 260 }, + { x: 300, y: 260 } + ); + + await dragBetweenCoords(page, { x: 50, y: 50 }, { x: 400, y: 290 }); + await waitNextFrame(page); + + expect( + await page + .locator('.affine-edgeless-selected-rect') + .locator('.element-handle') + .count() + ).toBe(0); + }); + + test('should disable resize when a connector is already connected', async ({ + page, + }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + await actions.zoomResetByKeyboard(page); + + const start = await toModelCoord(page, [100, 0]); + const end = await toModelCoord(page, [200, 100]); + await createShapeElement(page, start, end, Shape.Diamond); + const c1 = await toModelCoord(page, [200, 50]); + const c2 = await toModelCoord(page, [450, 50]); + await createConnectorElement(page, c1, c2); + + await addBasicConnectorElement( + page, + { x: 250, y: 200 }, + { x: 450, y: 200 } + ); + await addBasicConnectorElement( + page, + { x: 250, y: 230 }, + { x: 450, y: 230 } + ); + await addBasicConnectorElement( + page, + { x: 250, y: 260 }, + { x: 450, y: 260 } + ); + + await dragBetweenCoords(page, { x: 500, y: 20 }, { x: 400, y: 290 }); + await waitNextFrame(page); + + const selectedRectLocalor = page.locator('.affine-edgeless-selected-rect'); + expect(await selectedRectLocalor.locator('.element-handle').count()).toBe( + 0 + ); + expect( + await selectedRectLocalor.locator('.handle').locator('.resize').count() + ).toBe(0); + }); +}); diff --git a/blocksuite/tests-legacy/edgeless/selection/keyboard.spec.ts b/blocksuite/tests-legacy/edgeless/selection/keyboard.spec.ts new file mode 100644 index 0000000000000..a7690fa1f419b --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/selection/keyboard.spec.ts @@ -0,0 +1,265 @@ +import { NoteDisplayMode } from '@blocksuite/affine-model'; +import { expect } from '@playwright/test'; + +import * as actions from '../../utils/actions/edgeless.js'; +import { + addNote, + changeNoteDisplayModeWithId, + setEdgelessTool, + zoomResetByKeyboard, +} from '../../utils/actions/edgeless.js'; +import { + addBasicBrushElement, + addBasicRectShapeElement, + dragBetweenCoords, + enterPlaygroundRoom, + initEmptyEdgelessState, + selectAllByKeyboard, + switchEditorMode, +} from '../../utils/actions/index.js'; +import { + assertEdgelessDraggingArea, + assertEdgelessNonSelectedRect, + assertEdgelessSelectedElementHandleCount, + assertEdgelessSelectedRect, + assertVisibleBlockCount, +} from '../../utils/asserts.js'; +import { test } from '../../utils/playwright.js'; + +test.describe('translation should constrain to cur axis when dragged with shift key', () => { + test('constrain-x', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + + await switchEditorMode(page); + + await addBasicRectShapeElement( + page, + { x: 100, y: 100 }, + { x: 200, y: 200 } + ); + + await page.mouse.move(110, 110); + await page.mouse.down(); + + await assertEdgelessSelectedRect(page, [100, 100, 100, 100]); + await page.keyboard.down('Shift'); + await page.mouse.move(110, 200); // constrain to y + await page.mouse.move(300, 200); // constrain to x + await assertEdgelessSelectedRect(page, [290, 100, 100, 100]); // y should remain same as constrained to x + }); + + test('constrain-y', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + + await switchEditorMode(page); + + await addBasicRectShapeElement( + page, + { x: 100, y: 100 }, + { x: 200, y: 200 } + ); + + await page.mouse.move(110, 110); + await page.mouse.down(); + + await assertEdgelessSelectedRect(page, [100, 100, 100, 100]); + await page.keyboard.down('Shift'); + await page.mouse.move(200, 110); // constrain to x + await page.mouse.move(200, 300); // constrain to y + await assertEdgelessSelectedRect(page, [100, 290, 100, 100]); // x should remain same as constrained to y + }); +}); + +test('select multiple shapes and press "Escape" to cancel selection', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + + await switchEditorMode(page); + await actions.zoomResetByKeyboard(page); + + await addBasicBrushElement(page, { x: 100, y: 100 }, { x: 200, y: 200 }); + await page.mouse.click(110, 110); + await assertEdgelessSelectedRect(page, [98, 98, 104, 104]); + + await addBasicRectShapeElement(page, { x: 210, y: 110 }, { x: 310, y: 210 }); + await page.mouse.click(220, 120); + await assertEdgelessSelectedRect(page, [210, 110, 100, 100]); + + // Select both shapes + await dragBetweenCoords(page, { x: 90, y: 90 }, { x: 320, y: 220 }); + + // assert all shapes are selected + await assertEdgelessSelectedRect(page, [98, 98, 212, 112]); + + // Press "Escape" to cancel the selection + await page.keyboard.press('Escape'); + + await assertEdgelessNonSelectedRect(page); +}); + +test('should move selection drag area when holding spaceBar', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + await actions.zoomResetByKeyboard(page); + await setEdgelessTool(page, 'default'); + + // Click to start the initial dragging area + await page.mouse.click(100, 100); + + const initialX = 100, + initialY = 100; + const finalX = 300, + finalY = 300; + + await dragBetweenCoords( + page, + { x: initialX, y: initialY }, + { x: finalX, y: finalY }, + { + beforeMouseUp: async () => { + await page.keyboard.down('Space'); + + const dx = 100, + dy = 100; + await page.mouse.move(finalX + dx, finalY + dy); + await assertEdgelessDraggingArea(page, [ + initialX + dx, + initialY + dy, + // width and height should be same + finalX - initialX, + finalY - initialY, + ]); + + await page.keyboard.up('Space'); + }, + } + ); +}); + +test('selection drag-area start should be same when space is pressed again', async ({ + page, +}) => { + //? This test is to check whether there is any flicker or jump when using the space again in the same selection + + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + await actions.zoomResetByKeyboard(page); + + // Make the selection out side the rect and move the selection to the rect + await dragBetweenCoords( + page, + // Make the selection not selecting the rect + { x: 100, y: 100 }, + { x: 200, y: 200 }, + { + beforeMouseUp: async () => { + await page.keyboard.down('Space'); + // Move the selection over to the rect + await page.mouse.move(300, 300); + + let draggingArea = page.locator('.affine-edgeless-dragging-area'); + const firstBound = await draggingArea.boundingBox(); + + await page.keyboard.up('Space'); + + await page.mouse.move(400, 400); + await page.keyboard.down('Space'); + + await page.mouse.move(410, 410); + await page.mouse.move(400, 400); + + draggingArea = page.locator('.affine-edgeless-dragging-area'); + const newBound = await draggingArea.boundingBox(); + + expect(firstBound).not.toBe(null); + expect(newBound).not.toBe(null); + + const { x: fx, y: fy } = firstBound!; + const { x: nx, y: ny } = newBound!; + + expect([fx, fy]).toStrictEqual([nx, ny]); + }, + } + ); +}); + +test('should be able to update selection dragging area after releasing space', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + await actions.zoomResetByKeyboard(page); + await setEdgelessTool(page, 'default'); + + // Click to start the initial dragging area + await page.mouse.click(100, 100); + + const initialX = 100, + initialY = 100; + const finalX = 300, + finalY = 300; + + await dragBetweenCoords( + page, + { x: initialX, y: initialY }, + { x: finalX, y: finalY }, + { + beforeMouseUp: async () => { + await page.keyboard.down('Space'); + + const dx = 100, + dy = 100; + + // Move the mouse to simulate dragging with spaceBar held + await page.mouse.move(finalX + dx, finalY + dy); + + await page.keyboard.up('Space'); + // scale after moving + const dSx = 100; + const dSy = 100; + + await page.mouse.move(finalX + dx + dSx, finalY + dy + dSy); + + await assertEdgelessDraggingArea(page, [ + initialX + dx, + initialY + dy, + // In the second scale it should scale by dS(.) + finalX - initialX + dSx, + finalY - initialY + dSy, + ]); + }, + } + ); +}); + +test('cmd+a should not select doc only note', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + + await switchEditorMode(page); + await zoomResetByKeyboard(page); + const note2 = await addNote(page, 'note2', 100, 200); + await addNote(page, 'note3', 200, 300); + await page.mouse.click(200, 500); + // assert add note success, there should be 2 notes in edgeless page + await assertVisibleBlockCount(page, 'edgeless-note', 3); + + // change note display mode to doc only + await changeNoteDisplayModeWithId(page, note2, NoteDisplayMode.DocOnly); + // there should still be 2 notes in edgeless page + await assertVisibleBlockCount(page, 'edgeless-note', 2); + + // cmd+a should not select doc only note + await selectAllByKeyboard(page); + // there should be only 2 notes in selection + await assertEdgelessSelectedElementHandleCount(page, 2); +}); diff --git a/blocksuite/tests-legacy/edgeless/selection/selection.spec.ts b/blocksuite/tests-legacy/edgeless/selection/selection.spec.ts new file mode 100644 index 0000000000000..754838e5f458e --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/selection/selection.spec.ts @@ -0,0 +1,466 @@ +import { expect } from '@playwright/test'; + +import * as actions from '../../utils/actions/edgeless.js'; +import { + getNoteBoundBoxInEdgeless, + setEdgelessTool, + switchEditorMode, +} from '../../utils/actions/edgeless.js'; +import { + addBasicBrushElement, + addBasicRectShapeElement, + click, + clickInCenter, + dragBetweenCoords, + enterPlaygroundRoom, + getBoundingRect, + initEmptyEdgelessState, + initThreeParagraphs, + pressEnter, + waitNextFrame, +} from '../../utils/actions/index.js'; +import { + assertBlockCount, + assertEdgelessRemoteSelectedModelRect, + assertEdgelessRemoteSelectedRect, + assertEdgelessSelectedModelRect, + assertEdgelessSelectedRect, + assertSelectionInNote, +} from '../../utils/asserts.js'; +import { test } from '../../utils/playwright.js'; + +test('should update rect of selection when resizing viewport', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await actions.switchEditorMode(page); + await actions.zoomResetByKeyboard(page); + + await addBasicRectShapeElement(page, { x: 100, y: 100 }, { x: 200, y: 200 }); + await dragBetweenCoords(page, { x: 120, y: 90 }, { x: 220, y: 130 }); + + const selectedRectClass = '.affine-edgeless-selected-rect'; + + await actions.zoomResetByKeyboard(page); + + await assertEdgelessSelectedRect(page, [100, 100, 100, 100]); + + await actions.decreaseZoomLevel(page); + await waitNextFrame(page); + await actions.decreaseZoomLevel(page); + await waitNextFrame(page); + const selectedRectInZoom = await getBoundingRect(page, selectedRectClass); + await assertEdgelessSelectedRect(page, [ + selectedRectInZoom.x, + selectedRectInZoom.y, + 50, + 50, + ]); + + await actions.switchEditorEmbedMode(page); + await waitNextFrame(page); + const selectedRectInEmbed = await getBoundingRect(page, selectedRectClass); + await assertEdgelessSelectedRect(page, [ + selectedRectInEmbed.x, + selectedRectInEmbed.y, + 50, + 50, + ]); + + await actions.switchEditorEmbedMode(page); + await actions.increaseZoomLevel(page); + await waitNextFrame(page); + await actions.increaseZoomLevel(page); + await waitNextFrame(page); + await assertEdgelessSelectedRect(page, [100, 100, 100, 100]); +}); + +test('should update react of remote selection when resizing viewport', async ({ + context, + page: pageA, +}) => { + const room = await enterPlaygroundRoom(pageA); + await initEmptyEdgelessState(pageA); + await actions.switchEditorMode(pageA); + await actions.zoomResetByKeyboard(pageA); + + const pageB = await context.newPage(); + await enterPlaygroundRoom(pageB, { + room, + noInit: true, + }); + await actions.switchEditorMode(pageB); + await actions.zoomResetByKeyboard(pageB); + + await actions.createShapeElement( + pageA, + [0, 0], + [100, 100], + actions.Shape.Square + ); + const point = await actions.toViewCoord(pageA, [50, 50]); + await click(pageA, { x: point[0], y: point[1] }); + await click(pageB, { x: point[0], y: point[1] }); + + await assertEdgelessSelectedModelRect(pageB, [0, 0, 100, 100]); + await assertEdgelessRemoteSelectedModelRect(pageB, [0, 0, 100, 100]); + + // to 50% + await actions.decreaseZoomLevel(pageB); + await waitNextFrame(pageB); + await actions.decreaseZoomLevel(pageB); + await waitNextFrame(pageB); + + const selectedRectInZoom = await getBoundingRect( + pageB, + '.affine-edgeless-selected-rect' + ); + await assertEdgelessRemoteSelectedRect(pageB, [ + selectedRectInZoom.x, + selectedRectInZoom.y, + 50, + 50, + ]); +}); + +test('select multiple shapes and translate', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + + await switchEditorMode(page); + await actions.zoomResetByKeyboard(page); + + await addBasicBrushElement(page, { x: 100, y: 100 }, { x: 200, y: 200 }); + await page.mouse.click(110, 110); + await assertEdgelessSelectedRect(page, [98, 98, 104, 104]); + + await addBasicRectShapeElement(page, { x: 210, y: 110 }, { x: 310, y: 210 }); + await page.mouse.click(220, 120); + await assertEdgelessSelectedRect(page, [210, 110, 100, 100]); + + await dragBetweenCoords(page, { x: 120, y: 90 }, { x: 220, y: 130 }); + await assertEdgelessSelectedRect(page, [98, 98, 212, 112]); + + await dragBetweenCoords(page, { x: 120, y: 120 }, { x: 150, y: 150 }); + await assertEdgelessSelectedRect(page, [125, 128, 212, 112]); + + await page.mouse.click(160, 160); + await assertEdgelessSelectedRect(page, [125, 128, 104, 104]); + + await page.mouse.click(250, 150); + await assertEdgelessSelectedRect(page, [237, 140, 100, 100]); +}); + +test('selection box of shape element sync on fast dragging', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + await actions.zoomResetByKeyboard(page); + + await setEdgelessTool(page, 'shape'); + await dragBetweenCoords(page, { x: 100, y: 100 }, { x: 200, y: 200 }); + await setEdgelessTool(page, 'default'); + await dragBetweenCoords( + page, + { x: 110, y: 110 }, + { x: 660, y: 460 }, + { click: true } + ); + + await assertEdgelessSelectedRect(page, [650, 446, 100, 100]); +}); + +test('when the selection is always a note, it should remain in an active state', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + const ids = await initEmptyEdgelessState(page); + await initThreeParagraphs(page); + + await switchEditorMode(page); + await actions.zoomResetByKeyboard(page); + const bound = await getNoteBoundBoxInEdgeless(page, ids.noteId); + + await setEdgelessTool(page, 'note'); + const newNoteX = bound.x; + const newNoteY = bound.y + bound.height + 100; + // add text + await page.mouse.click(newNoteX, newNoteY); + await waitNextFrame(page); + await page.keyboard.type('hello'); + await pressEnter(page); + // should wait for inline editor update and resizeObserver callback + await waitNextFrame(page); + // assert add text success + await assertBlockCount(page, 'edgeless-note', 2); + + await clickInCenter(page, bound); + await clickInCenter(page, bound); + await waitNextFrame(page); + await assertSelectionInNote(page, ids.noteId, 'affine-edgeless-note'); +}); + +test('should auto panning when selection rectangle reaches viewport edges', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + + await switchEditorMode(page); + await actions.zoomResetByKeyboard(page); + + await addBasicRectShapeElement(page, { x: 200, y: 100 }, { x: 300, y: 200 }); + await page.mouse.click(210, 110); + await assertEdgelessSelectedRect(page, [200, 100, 100, 100]); + + const selectedRectClass = '.affine-edgeless-selected-rect'; + + // Panning to the left + await setEdgelessTool(page, 'pan'); + await dragBetweenCoords( + page, + { + x: 600, + y: 200, + }, + { + x: 200, + y: 200, + } + ); + await setEdgelessTool(page, 'default'); + await page.mouse.click(210, 110); + let selectedRect = page.locator(selectedRectClass); + await page.waitForTimeout(300); + await expect(selectedRect).toBeHidden(); + // Click to start selection and hold the mouse to trigger auto panning to the left + await page.mouse.move(210, 110); + await page.mouse.down(); + await page.mouse.move(0, 210, { steps: 20 }); + await page.waitForTimeout(500); + await page.mouse.up(); + + // Expect to select the shape element + selectedRect = page.locator(selectedRectClass); + await page.waitForTimeout(300); + await expect(selectedRect).toBeVisible(); + + // Panning to the top + await page.mouse.click(400, 600); + await setEdgelessTool(page, 'pan'); + await dragBetweenCoords( + page, + { + x: 400, + y: 600, + }, + { + x: 400, + y: 100, + } + ); + await setEdgelessTool(page, 'default'); + await page.mouse.click(600, 100); + selectedRect = page.locator(selectedRectClass); + await page.waitForTimeout(300); + await expect(selectedRect).toBeHidden(); + // Click to start selection and hold the mouse to trigger auto panning to the top + await page.mouse.move(600, 100); + await page.mouse.down(); + await page.mouse.move(400, 0, { steps: 20 }); + await page.waitForTimeout(500); + await page.mouse.up(); + + // Expect to select the empty note + selectedRect = page.locator(selectedRectClass); + await page.waitForTimeout(300); + await expect(selectedRect).toBeVisible(); + + // Panning to the right + await page.mouse.click(100, 600); + await setEdgelessTool(page, 'pan'); + await dragBetweenCoords( + page, + { + x: 20, + y: 600, + }, + { + x: 1000, + y: 600, + } + ); + await setEdgelessTool(page, 'default'); + await page.mouse.click(800, 600); + selectedRect = page.locator(selectedRectClass); + await page.waitForTimeout(100); + await expect(selectedRect).toBeHidden(); + // Click to start selection and hold the mouse to trigger auto panning to the right + await dragBetweenCoords( + page, + { + x: 800, + y: 600, + }, + { + x: 1000, + y: 200, + }, + { + beforeMouseUp: async () => { + await page.waitForTimeout(600); + }, + } + ); + + // Expect to select the empty note + selectedRect = page.locator(selectedRectClass); + await page.waitForTimeout(300); + await expect(selectedRect).toBeVisible(); + + // Panning to the bottom + await page.mouse.click(400, 100); + await setEdgelessTool(page, 'pan'); + await dragBetweenCoords( + page, + { + x: 400, + y: 100, + }, + { + x: 400, + y: 850, + }, + { + click: true, + } + ); + await setEdgelessTool(page, 'default'); + await waitNextFrame(page, 500); + await page.mouse.click(400, 400); + selectedRect = page.locator(selectedRectClass); + await page.waitForTimeout(100); + await expect(selectedRect).toBeHidden(); + + // Click to start selection and hold the mouse to trigger auto panning to the right + await dragBetweenCoords( + page, + { + x: 800, + y: 300, + }, + { + x: 820, + y: 1150, + }, + { + click: true, + beforeMouseUp: async () => { + await page.waitForTimeout(500); + }, + } + ); + + // Expect to select the empty note + selectedRect = page.locator(selectedRectClass); + await page.waitForTimeout(300); + await expect(selectedRect).toBeVisible(); +}); + +test('should also update dragging area when viewport changes', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + + await switchEditorMode(page); + await actions.zoomResetByKeyboard(page); + + // Panning to the top + await page.mouse.click(400, 600); + await setEdgelessTool(page, 'pan'); + await dragBetweenCoords( + page, + { + x: 400, + y: 600, + }, + { + x: 400, + y: 100, + } + ); + await setEdgelessTool(page, 'default'); + await page.mouse.click(200, 300); + + const selectedRectClass = '.affine-edgeless-selected-rect'; + let selectedRect = page.locator(selectedRectClass); + await expect(selectedRect).toBeHidden(); + // set up initial dragging area + await page.mouse.move(200, 300); + await page.mouse.down(); + await page.mouse.move(600, 200, { steps: 20 }); + await page.waitForTimeout(300); + + // wheel the viewport to the top + await page.mouse.wheel(0, -300); + await page.waitForTimeout(300); + await page.mouse.up(); + + // Expect to select the empty note + selectedRect = page.locator(selectedRectClass); + await page.waitForTimeout(300); + await expect(selectedRect).toBeVisible(); + await page.waitForTimeout(300); +}); + +test('should select shapes while moving selection', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + await actions.zoomResetByKeyboard(page); + + await addBasicRectShapeElement(page, { x: 100, y: 100 }, { x: 200, y: 200 }); + + // Make the selection out side the rect and move the selection to the rect + await dragBetweenCoords( + page, + // Make the selection not selecting the rect + { x: 70, y: 70 }, + { x: 90, y: 90 }, + { + beforeMouseUp: async () => { + await page.keyboard.down('Space'); + // Move the selection over to the rect + await page.mouse.move(120, 120); + await page.keyboard.up('Space'); + }, + } + ); + + await assertEdgelessSelectedRect(page, [100, 100, 100, 100]); + + await addBasicBrushElement(page, { x: 210, y: 100 }, { x: 310, y: 300 }); + await page.mouse.click(211, 101); + + // Make a wide selection and move it to select both of the shapes + await dragBetweenCoords( + page, + // Make the selection above the spaces + { x: 70, y: 70 }, + { x: 400, y: 90 }, + { + beforeMouseUp: async () => { + await page.keyboard.down('Space'); + // Move the selection over both of the shapes + await page.mouse.move(400, 120); + await page.keyboard.up('Space'); + }, + } + ); + + await assertEdgelessSelectedRect(page, [100, 98, 212, 204]); +}); diff --git a/blocksuite/tests-legacy/edgeless/shape.spec.ts b/blocksuite/tests-legacy/edgeless/shape.spec.ts new file mode 100644 index 0000000000000..3af1f2bd5ba80 --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/shape.spec.ts @@ -0,0 +1,727 @@ +import { expect, type Page } from '@playwright/test'; + +import { + assertEdgelessTool, + changeShapeFillColor, + changeShapeFillColorToTransparent, + changeShapeStrokeColor, + changeShapeStrokeStyle, + changeShapeStrokeWidth, + changeShapeStyle, + clickComponentToolbarMoreMenuButton, + getEdgelessSelectedRect, + locatorComponentToolbar, + locatorEdgelessToolButton, + locatorShapeStrokeStyleButton, + openComponentToolbarMoreMenu, + pickColorAtPoints, + resizeElementByHandle, + setEdgelessTool, + switchEditorMode, + triggerComponentToolbarAction, + zoomResetByKeyboard, +} from '../utils/actions/edgeless.js'; +import { + addBasicBrushElement, + addBasicRectShapeElement, + copyByKeyboard, + dragBetweenCoords, + enterPlaygroundRoom, + focusRichText, + initEmptyEdgelessState, + pasteByKeyboard, + pressEscape, + type, + waitNextFrame, +} from '../utils/actions/index.js'; +import { + assertEdgelessCanvasText, + assertEdgelessColorSameWithHexColor, + assertEdgelessNonSelectedRect, + assertEdgelessSelectedRect, + assertExists, + assertRichTexts, +} from '../utils/asserts.js'; +import { test } from '../utils/playwright.js'; + +test.describe('add shape', () => { + test('without holding shift key', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + + const start0 = { x: 100, y: 100 }; + const end0 = { x: 150, y: 200 }; + await addBasicRectShapeElement(page, start0, end0); + + await assertEdgelessTool(page, 'default'); + await assertEdgelessSelectedRect(page, [100, 100, 50, 100]); + + const start1 = { x: 100, y: 100 }; + const end1 = { x: 200, y: 150 }; + await addBasicRectShapeElement(page, start1, end1); + + await assertEdgelessTool(page, 'default'); + await assertEdgelessSelectedRect(page, [100, 100, 100, 50]); + }); + + test('with holding shift key', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + + await page.keyboard.down('Shift'); + + const start0 = { x: 100, y: 100 }; + const end0 = { x: 150, y: 200 }; + await addBasicRectShapeElement(page, start0, end0); + + await page.keyboard.up('Shift'); + + await assertEdgelessTool(page, 'default'); + await assertEdgelessSelectedRect(page, [100, 100, 100, 100]); + + await page.keyboard.down('Shift'); + + const start1 = { x: 100, y: 100 }; + const end1 = { x: 200, y: 150 }; + await addBasicRectShapeElement(page, start1, end1); + + await assertEdgelessTool(page, 'default'); + await assertEdgelessSelectedRect(page, [100, 100, 100, 100]); + }); + test('with holding space bar', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + + const start0 = { x: 100, y: 100 }; + const end0 = { x: 200, y: 200 }; + await setEdgelessTool(page, 'shape'); + await dragBetweenCoords(page, start0, end0, { + steps: 50, + beforeMouseUp: async () => { + // move the shape + await page.keyboard.down('Space'); + await page.mouse.move(300, 300); + await page.keyboard.up('Space'); + + await page.mouse.move(500, 600); + }, + }); + + await assertEdgelessSelectedRect(page, [200, 200, 300, 400]); + }); + + test('with holding space bar + shift', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + + const start0 = { x: 100, y: 100 }; + const end0 = { x: 200, y: 200 }; + await setEdgelessTool(page, 'shape'); + await page.keyboard.down('Shift'); + await dragBetweenCoords(page, start0, end0, { + steps: 50, + beforeMouseUp: async () => { + // move the shape + await page.keyboard.down('Space'); + await page.mouse.move(300, 300); + await page.keyboard.up('Space'); + + await page.mouse.move(500, 600); + }, + }); + + await assertEdgelessSelectedRect(page, [200, 200, 400, 400]); + }); +}); + +test('delete shape by component-toolbar', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + + const start = { x: 100, y: 100 }; + const end = { x: 200, y: 200 }; + await addBasicBrushElement(page, start, end); + + await page.mouse.click(110, 110); + await openComponentToolbarMoreMenu(page); + await clickComponentToolbarMoreMenuButton(page, 'delete'); + await assertEdgelessNonSelectedRect(page); +}); + +//FIXME: need a way to test hand-drawn-like style +test.skip('change shape fill color', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + + const rect = { + start: { x: 100, y: 100 }, + end: { x: 200, y: 200 }, + }; + await addBasicRectShapeElement(page, rect.start, rect.end); + + await page.mouse.click(rect.start.x + 5, rect.start.y + 5); + await triggerComponentToolbarAction(page, 'changeShapeFillColor'); + const color = '--affine-palette-shape-teal'; + await changeShapeFillColor(page, color); + await page.waitForTimeout(50); + const [picked] = await pickColorAtPoints(page, [ + [rect.start.x + 20, rect.start.y + 20], + ]); + + await assertEdgelessColorSameWithHexColor(page, color, picked); +}); + +test('change shape stroke color', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + + const rect = { + start: { x: 100, y: 100 }, + end: { x: 200, y: 200 }, + }; + await addBasicRectShapeElement(page, rect.start, rect.end); + + await page.mouse.click(rect.start.x + 5, rect.start.y + 5); + await triggerComponentToolbarAction(page, 'changeShapeStrokeColor'); + const color = '--affine-palette-line-teal'; + await changeShapeStrokeColor(page, color); + await page.waitForTimeout(50); + const [picked] = await pickColorAtPoints(page, [ + [rect.start.x + 1, rect.start.y + 1], + ]); + + await assertEdgelessColorSameWithHexColor(page, color, picked); +}); + +test('the tooltip of shape tool button should be hidden when the shape menu is shown', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + + const shapeTool = await locatorEdgelessToolButton(page, 'shape'); + const shapeToolBox = await shapeTool.boundingBox(); + const tooltip = page.locator('.affine-tooltip'); + + assertExists(shapeToolBox); + + await page.mouse.move(shapeToolBox.x + 2, shapeToolBox.y + 2); + await expect(tooltip).toBeVisible(); + + await page.mouse.click(shapeToolBox.x + 2, shapeToolBox.y + 2); + await expect(tooltip).toBeHidden(); + + await page.mouse.click(shapeToolBox.x + 2, shapeToolBox.y + 2); + await expect(tooltip).toBeVisible(); +}); + +test('delete shape block by keyboard', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + + await switchEditorMode(page); + await setEdgelessTool(page, 'shape'); + await dragBetweenCoords(page, { x: 100, y: 100 }, { x: 200, y: 200 }); + + await setEdgelessTool(page, 'default'); + const startPoint = await page.evaluate(() => { + const hitbox = document.querySelector('[data-block-id="3"]'); + if (!hitbox) { + throw new Error('hitbox is null'); + } + const rect = hitbox.getBoundingClientRect(); + if (rect == null) { + throw new Error('rect is null'); + } + return { + x: rect.x, + y: rect.y, + }; + }); + await page.mouse.click(startPoint.x + 2, startPoint.y + 2); + await waitNextFrame(page); + await page.keyboard.press('Backspace'); + const exist = await page.evaluate(() => { + return document.querySelector('[data-block-id="3"]') != null; + }); + expect(exist).toBe(false); +}); + +test('edgeless toolbar shape menu shows up and close normally', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + + const toolbarLocator = page.locator('.edgeless-toolbar-container'); + await expect(toolbarLocator).toBeVisible(); + + const shapeTool = await locatorEdgelessToolButton(page, 'shape'); + const shapeToolBox = await shapeTool.boundingBox(); + + assertExists(shapeToolBox); + + await page.mouse.click(shapeToolBox.x + 2, shapeToolBox.y + 2); + + const shapeMenu = page.locator('edgeless-shape-menu'); + await expect(shapeMenu).toBeVisible(); + await page.waitForTimeout(500); + + await page.mouse.click(shapeToolBox.x + 2, shapeToolBox.y + 2); + await page.waitForTimeout(500); + await expect(shapeMenu).toBeHidden(); +}); + +test('hovering on shape should not have effect on underlying block', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await focusRichText(page); + + await type(page, 'hello'); + await assertRichTexts(page, ['hello']); + + await switchEditorMode(page); + + const block = page.locator('affine-edgeless-note'); + const blockBox = await block.boundingBox(); + if (blockBox === null) throw new Error('Unexpected box value: box is null'); + + const { x, y } = blockBox; + + await setEdgelessTool(page, 'shape'); + await dragBetweenCoords(page, { x, y }, { x: x + 100, y: y + 100 }); + await setEdgelessTool(page, 'default'); + + await page.mouse.click(x + 10, y + 10); + await assertEdgelessSelectedRect(page, [x, y, 100, 100]); +}); + +test('shape element should not move when the selected state is inactive', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + + await setEdgelessTool(page, 'shape'); + await dragBetweenCoords(page, { x: 100, y: 100 }, { x: 200, y: 200 }); + await setEdgelessTool(page, 'default'); + await dragBetweenCoords( + page, + { x: 50, y: 50 }, + { x: 110, y: 110 }, + { steps: 2 } + ); + + await assertEdgelessSelectedRect(page, [100, 100, 100, 100]); +}); + +test('change shape stroke width', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + + const start = { x: 100, y: 150 }; + const end = { x: 200, y: 250 }; + await addBasicRectShapeElement(page, start, end); + + await page.mouse.click(start.x + 5, start.y + 5); + await triggerComponentToolbarAction(page, 'changeShapeStrokeColor'); + await changeShapeStrokeColor(page, '--affine-palette-line-teal'); + + await triggerComponentToolbarAction(page, 'changeShapeStrokeStyles'); + await changeShapeStrokeWidth(page); + await page.mouse.click(start.x + 5, start.y + 5); + await assertEdgelessSelectedRect(page, [100, 150, 100, 100]); + + await waitNextFrame(page); + + await triggerComponentToolbarAction(page, 'changeShapeStrokeStyles'); +}); + +test('change shape stroke style', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + + const start = { x: 100, y: 150 }; + const end = { x: 200, y: 250 }; + await addBasicRectShapeElement(page, start, end); + + await page.mouse.click(start.x + 5, start.y + 5); + await triggerComponentToolbarAction(page, 'changeShapeStrokeColor'); + await changeShapeStrokeColor(page, '--affine-palette-line-teal'); + + await triggerComponentToolbarAction(page, 'changeShapeStrokeStyles'); + await changeShapeStrokeStyle(page, 'dash'); + await waitNextFrame(page); + + await triggerComponentToolbarAction(page, 'changeShapeStrokeStyles'); + const activeButton = locatorShapeStrokeStyleButton(page, 'dash'); + const className = await activeButton.evaluate(ele => ele.className); + expect(className.includes(' active')).toBeTruthy(); + + const pickedColor = await pickColorAtPoints(page, [[start.x + 20, start.y]]); + expect(pickedColor[0]).toBe('#000000'); +}); + +test('click to add shape', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + await zoomResetByKeyboard(page); + + await setEdgelessTool(page, 'shape'); + await waitNextFrame(page, 500); + + await page.mouse.move(400, 400); + await page.mouse.move(200, 200); + await page.mouse.click(200, 200, { button: 'left', delay: 300 }); + + await assertEdgelessTool(page, 'default'); + await assertEdgelessSelectedRect(page, [200, 200, 100, 100]); +}); + +test('dbclick to add text in shape', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + await zoomResetByKeyboard(page); + + await setEdgelessTool(page, 'shape'); + await waitNextFrame(page, 500); + + await page.mouse.click(200, 150); + await waitNextFrame(page); + await page.mouse.dblclick(250, 200); + await waitNextFrame(page); + + await type(page, 'hello'); + await assertEdgelessCanvasText(page, 'hello'); + await assertEdgelessTool(page, 'default'); + + // test select, copy, paste + const select = async () => { + await page.mouse.move(245, 205); + await page.mouse.down(); + + await page.mouse.move(245, 205); + await page.mouse.down(); + await page.mouse.move(262, 205, { + steps: 10, + }); + await page.mouse.up(); + }; + await select(); + // h|ell|o + await waitNextFrame(page); + await copyByKeyboard(page); + await waitNextFrame(page); + + // FIXME(@Flrande): this is a workaround, we should keep selection + await select(); + + await waitNextFrame(page); + await type(page, 'ddd', 50); + await waitNextFrame(page); + await assertEdgelessCanvasText(page, 'hdddo'); + + await pasteByKeyboard(page); + await assertEdgelessCanvasText(page, 'hdddello'); +}); + +test('should show selected rect after exiting editing by pressing Escape', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + await zoomResetByKeyboard(page); + + await setEdgelessTool(page, 'shape'); + await waitNextFrame(page, 500); + + await dragBetweenCoords(page, { x: 100, y: 100 }, { x: 200, y: 200 }); + + await waitNextFrame(page); + await page.mouse.dblclick(150, 150); + await waitNextFrame(page); + + await type(page, 'hello'); + await assertEdgelessCanvasText(page, 'hello'); + + await pressEscape(page); + await assertEdgelessSelectedRect(page, [100, 100, 100, 100]); +}); + +test('auto wrap text in shape', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + await zoomResetByKeyboard(page); + + await setEdgelessTool(page, 'shape'); + await waitNextFrame(page, 500); + + await page.mouse.click(200, 150); + await waitNextFrame(page); + await page.mouse.dblclick(250, 200); + await waitNextFrame(page); + + await type(page, 'aaaa\nbbbb\n'); + await assertEdgelessCanvasText(page, 'aaaa\nbbbb\n'); + await assertEdgelessTool(page, 'default'); + + // blur to finish typing + await page.mouse.click(150, 150); + // select shape + await page.mouse.click(200, 150); + // the height of shape should be increased because of \n + let selectedRect = await getEdgelessSelectedRect(page); + let lastWidth = selectedRect.width; + let lastHeight = selectedRect.height; + + await page.mouse.dblclick(250, 200); + await waitNextFrame(page); + // type long text + await type(page, '\ncccccccc'); + await assertEdgelessCanvasText(page, 'aaaa\nbbbb\ncccccccc'); + + // blur to finish typing + await page.mouse.click(150, 150); + // select shape + await page.mouse.click(200, 150); + // the height of shape should be increased because of long text + // cccccccc -- wrap --> cccccc\ncc + selectedRect = await getEdgelessSelectedRect(page); + expect(selectedRect.width).toBe(lastWidth); + expect(selectedRect.height).toBeGreaterThan(lastHeight); + lastWidth = selectedRect.width; + lastHeight = selectedRect.height; + + // try to decrease height + await resizeElementByHandle(page, { x: 0, y: -50 }, 'bottom-right'); + // you can't decrease height because of min height to fit text + selectedRect = await getEdgelessSelectedRect(page); + expect(selectedRect.width).toBe(lastWidth); + expect(selectedRect.height).toBeGreaterThanOrEqual(lastHeight); + lastWidth = selectedRect.width; + lastHeight = selectedRect.height; + + // increase width to make text not wrap + await resizeElementByHandle(page, { x: 50, y: -10 }, 'bottom-right'); + // the height of shape should be decreased because of long text not wrap + selectedRect = await getEdgelessSelectedRect(page); + expect(selectedRect.width).toBeGreaterThan(lastWidth); + expect(selectedRect.height).toBeLessThan(lastHeight); + + // try to decrease width + await resizeElementByHandle(page, { x: -140, y: 0 }, 'bottom-right'); + // you can't decrease width after text can't wrap (each line just has 1 char) + await assertEdgelessSelectedRect(page, [200, 150, 52, 404]); +}); + +test('change shape style', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + + const start = { x: 100, y: 150 }; + const end = { x: 200, y: 250 }; + await addBasicRectShapeElement(page, start, end); + + await page.mouse.click(start.x + 5, start.y + 5); + await triggerComponentToolbarAction(page, 'changeShapeStyle'); + await changeShapeStyle(page, 'general'); + await waitNextFrame(page); + + await page.mouse.click(start.x + 5, start.y + 5); + await triggerComponentToolbarAction(page, 'changeShapeStrokeColor'); + const color = '--affine-palette-line-teal'; + await changeShapeStrokeColor(page, color); + await page.waitForTimeout(50); + const [picked] = await pickColorAtPoints(page, [[start.x + 1, start.y + 1]]); + + await assertEdgelessColorSameWithHexColor(page, color, picked); +}); + +test('shape adds text by button', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + await zoomResetByKeyboard(page); + + await setEdgelessTool(page, 'shape'); + await waitNextFrame(page, 500); + + await page.mouse.click(200, 150); + await waitNextFrame(page); + + await triggerComponentToolbarAction(page, 'addText'); + await type(page, 'hello'); + await assertEdgelessCanvasText(page, 'hello'); +}); + +test('should reset shape text when text is empty', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + await zoomResetByKeyboard(page); + + await setEdgelessTool(page, 'shape'); + await waitNextFrame(page, 500); + + await page.mouse.click(200, 150); + await waitNextFrame(page); + + await triggerComponentToolbarAction(page, 'addText'); + await type(page, ' a '); + await assertEdgelessCanvasText(page, ' a '); + + await page.mouse.click(0, 0); + await waitNextFrame(page); + await page.mouse.click(200, 150); + + const addTextBtn = locatorComponentToolbar(page).getByRole('button', { + name: 'Add text', + }); + await expect(addTextBtn).toBeHidden(); + + await page.mouse.dblclick(250, 200); + await assertEdgelessCanvasText(page, 'a'); + + await page.keyboard.press('Backspace'); + await assertEdgelessCanvasText(page, ''); + + await page.mouse.click(0, 0); + await waitNextFrame(page); + await page.mouse.click(200, 150); + + await expect(addTextBtn).toBeVisible(); +}); + +test.describe('shape hit test', () => { + async function addTransparentRect( + page: Page, + start: { x: number; y: number }, + end: { x: number; y: number } + ) { + const rect = { + start, + end, + }; + await addBasicRectShapeElement(page, rect.start, rect.end); + + await page.mouse.click(rect.start.x + 5, rect.start.y + 5); + await triggerComponentToolbarAction(page, 'changeShapeFillColor'); + await changeShapeFillColorToTransparent(page); + await page.waitForTimeout(50); + } + + test.beforeEach(async ({ page }) => { + await enterPlaygroundRoom(page, { + flags: { + enable_edgeless_text: false, + }, + }); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + }); + + const rect = { + start: { x: 100, y: 100 }, + end: { x: 200, y: 200 }, + }; + + test('can select hollow shape by clicking center area', async ({ page }) => { + await addTransparentRect(page, rect.start, rect.end); + await page.mouse.click(rect.start.x - 20, rect.start.y - 20); + await assertEdgelessNonSelectedRect(page); + + await page.mouse.click(rect.start.x + 50, rect.start.y + 50); + await assertEdgelessSelectedRect(page, [100, 100, 100, 100]); + }); + + test('double click can add text in shape hollow area', async ({ page }) => { + await addTransparentRect(page, rect.start, rect.end); + await page.mouse.click(rect.start.x - 20, rect.start.y - 20); + await assertEdgelessNonSelectedRect(page); + + await assertEdgelessTool(page, 'default'); + await page.mouse.dblclick(rect.start.x + 20, rect.start.y + 20); + await waitNextFrame(page); + + await type(page, 'hello'); + await assertEdgelessCanvasText(page, 'hello'); + }); + + // FIXME(@flrande): This is broken by recent changes + // In Playwright, we can't add text in shape hollow area + test.fixme( + 'using text tool to add text in shape hollow area', + async ({ page }) => { + await addTransparentRect(page, rect.start, rect.end); + await page.mouse.click(rect.start.x - 20, rect.start.y - 20); + await assertEdgelessNonSelectedRect(page); + + await assertEdgelessTool(page, 'default'); + await setEdgelessTool(page, 'text'); + await page.mouse.click(rect.start.x + 50, rect.start.y + 50); + await waitNextFrame(page); + + await type(page, 'hello'); + await assertEdgelessCanvasText(page, 'hello'); + } + ); + + test('should enter edit mode when double-clicking a text area in a shape with a transparent background', async ({ + page, + }) => { + await addTransparentRect(page, rect.start, rect.end); + await page.mouse.click(rect.start.x - 20, rect.start.y - 20); + await assertEdgelessNonSelectedRect(page); + + await assertEdgelessTool(page, 'default'); + await page.mouse.dblclick(rect.start.x + 50, rect.start.y + 50); + await waitNextFrame(page); + await type(page, 'hello'); + + await pressEscape(page); + await waitNextFrame(page); + + const textAlignBtn = locatorComponentToolbar(page).getByRole('button', { + name: 'Alignment', + }); + await textAlignBtn.click(); + + await page + .locator('edgeless-align-panel') + .getByRole('button', { name: 'Left' }) + .click(); + + // creates an edgeless-text + await page.mouse.dblclick(rect.start.x + 80, rect.start.y + 20); + await waitNextFrame(page); + await page.locator('edgeless-text-editor').isVisible(); + + await pressEscape(page); + await waitNextFrame(page); + + // enters edit mode + await page.mouse.dblclick(rect.start.x + 20, rect.start.y + 50); + await page.locator('edgeless-shape-text-editor').isVisible(); + await type(page, ' world'); + await assertEdgelessCanvasText(page, 'hello world'); + }); +}); diff --git a/blocksuite/tests-legacy/edgeless/shortcut.spec.ts b/blocksuite/tests-legacy/edgeless/shortcut.spec.ts new file mode 100644 index 0000000000000..f77e4c9513b02 --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/shortcut.spec.ts @@ -0,0 +1,324 @@ +import { expect } from '@playwright/test'; + +import { + addBasicRectShapeElement, + assertEdgelessShapeType, + createShapeElement, + edgelessCommonSetup, + getEdgelessSelectedRect, + getZoomLevel, + locatorEdgelessToolButton, + setEdgelessTool, + type ShapeName, + switchEditorMode, + zoomFitByKeyboard, + zoomInByKeyboard, + zoomOutByKeyboard, + zoomResetByKeyboard, +} from '../utils/actions/edgeless.js'; +import { + clickView, + enterPlaygroundRoom, + focusRichText, + initEmptyEdgelessState, + pressBackspace, + pressEscape, + pressForwardDelete, + selectAllByKeyboard, + selectNoteInEdgeless, + type, + waitNextFrame, +} from '../utils/actions/index.js'; +import { + assertBlockCount, + assertEdgelessNonSelectedRect, + assertEdgelessSelectedModelRect, + assertEdgelessSelectedRect, +} from '../utils/asserts.js'; +import { test } from '../utils/playwright.js'; + +test('shortcut', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + await page.mouse.click(100, 100); + + // text is removed temporarily + // await page.keyboard.press('t'); + // const textButton = await locatorEdgelessToolButton(page, 'text'); + // await expect(textButton).toHaveAttribute('active', ''); + + await page.keyboard.press('s'); + const shapeButton = await locatorEdgelessToolButton(page, 'shape'); + await expect(shapeButton).toHaveAttribute('active', ''); + + await page.keyboard.press('p'); + const penButton = await locatorEdgelessToolButton(page, 'brush'); + await expect(penButton).toHaveAttribute('active', ''); + + await page.keyboard.press('h'); + const panButton = await locatorEdgelessToolButton(page, 'pan'); + await expect(panButton).toHaveAttribute('active', ''); + + await page.keyboard.press('c'); + const connectorButton = await locatorEdgelessToolButton(page, 'connector'); + await expect(connectorButton).toHaveAttribute('active', ''); + + // await page.keyboard.press('l'); + // const lassoButton = await locatorEdgelessToolButton(page, 'lasso'); + // await expect(lassoButton).toHaveAttribute('active', ''); +}); + +test.skip('toggle lasso tool modes', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + await page.mouse.click(100, 100); + + const lassoButton = await locatorEdgelessToolButton(page, 'lasso', false); + + const isLassoMode = async (type: 'freehand' | 'polygonal') => { + const classes = (await lassoButton.getAttribute('class'))?.split(' ') ?? []; + return classes.includes(type); + }; + + await page.keyboard.press('Shift+l'); + expect(await isLassoMode('freehand')).toBe(true); + + await page.keyboard.press('Shift+l'); + expect(await isLassoMode('polygonal')).toBe(true); + + await page.keyboard.press('Shift+l'); + expect(await isLassoMode('freehand')).toBe(true); +}); + +test('toggle shapes shortcut', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + await page.mouse.click(100, 100); + await setEdgelessTool(page, 'shape'); + + const shapesInOrder = [ + 'ellipse', + 'diamond', + 'triangle', + 'roundedRect', + 'rect', + 'ellipse', + 'diamond', + 'triangle', + 'roundedRect', + ] as ShapeName[]; + for (const shape of shapesInOrder) { + await page.keyboard.press('Shift+s'); + await assertEdgelessShapeType(page, shape); + } +}); + +test('should not switch shapes in editing', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + + await setEdgelessTool(page, 'shape'); + await waitNextFrame(page); + await assertEdgelessShapeType(page, 'rect'); + + await page.mouse.click(200, 150); + await waitNextFrame(page); + await page.mouse.dblclick(250, 200); + await waitNextFrame(page); + + await type(page, 'hello'); + await page.keyboard.press('Shift+s'); + await page.keyboard.press('Escape'); + await waitNextFrame(page); + await setEdgelessTool(page, 'shape'); + await assertEdgelessShapeType(page, 'rect'); + + await setEdgelessTool(page, 'default'); + await page.mouse.dblclick(250, 200); + await waitNextFrame(page); + await page.keyboard.press('Shift+S'); + await page.keyboard.press('Escape'); + await waitNextFrame(page); + await setEdgelessTool(page, 'shape'); + await assertEdgelessShapeType(page, 'rect'); +}); + +test('pressing the ESC key will return to the default state', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + + const start = { x: 100, y: 100 }; + const end = { x: 200, y: 200 }; + await addBasicRectShapeElement(page, start, end); + + await page.mouse.click(start.x + 5, start.y + 5); + await assertEdgelessSelectedRect(page, [100, 100, 100, 100]); + + await pressEscape(page); + await assertEdgelessNonSelectedRect(page); +}); + +test.describe('zooming', () => { + test('zoom fit to screen', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + await zoomResetByKeyboard(page); + + const start = { x: 0, y: 0 }; + const end = { x: 900, y: 200 }; + await addBasicRectShapeElement(page, start, end); + await zoomFitByKeyboard(page); + + const zoom = await getZoomLevel(page); + expect(zoom).not.toBe(100); + }); + test('zoom out', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + + await clickView(page, [0, 0]); + await zoomResetByKeyboard(page); + + await zoomOutByKeyboard(page); + + let zoom = await getZoomLevel(page); + expect(zoom).toBe(75); + + await zoomOutByKeyboard(page); + + zoom = await getZoomLevel(page); + expect(zoom).toBe(50); + }); + test('zoom reset', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + + await clickView(page, [0, 0]); + await zoomResetByKeyboard(page); + let zoom = await getZoomLevel(page); + expect(zoom).toBe(100); + + await zoomOutByKeyboard(page); + + zoom = await getZoomLevel(page); + expect(zoom).toBe(75); + + await zoomResetByKeyboard(page); + + zoom = await getZoomLevel(page); + expect(zoom).toBe(100); + }); + test('zoom in', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + + await clickView(page, [0, 0]); + await zoomResetByKeyboard(page); + + await zoomInByKeyboard(page); + + let zoom = await getZoomLevel(page); + expect(zoom).toBe(125); + + await zoomInByKeyboard(page); + + zoom = await getZoomLevel(page); + expect(zoom).toBe(150); + }); +}); + +test('cmd + A should select all elements by default', async ({ page }) => { + await edgelessCommonSetup(page); + await createShapeElement(page, [0, 0], [100, 100]); + await createShapeElement(page, [100, 0], [200, 100]); + await selectAllByKeyboard(page); + await assertEdgelessSelectedModelRect(page, [0, 0, 200, 100]); +}); + +test('cmd + A should not fire inside active note', async ({ page }) => { + await enterPlaygroundRoom(page); + const { noteId } = await initEmptyEdgelessState(page); + await focusRichText(page); + await type(page, 'hello'); + await switchEditorMode(page); + + await selectNoteInEdgeless(page, noteId); + // second click become active + await selectNoteInEdgeless(page, noteId); + await selectAllByKeyboard(page); + + // should not have selected rect + let error = null; + try { + await getEdgelessSelectedRect(page); + } catch (e) { + error = e; + } + expect(error).not.toBeNull(); +}); + +test.describe('delete', () => { + test('do not delete element when active', async ({ page }) => { + await enterPlaygroundRoom(page); + const { noteId } = await initEmptyEdgelessState(page); + await focusRichText(page); + await type(page, 'hello'); + await switchEditorMode(page); + await selectNoteInEdgeless(page, noteId); + const box1 = await getEdgelessSelectedRect(page); + await page.mouse.click(box1.x + 10, box1.y + 10); + await pressBackspace(page); + await assertBlockCount(page, 'edgeless-note', 1); + await pressForwardDelete(page); + await assertBlockCount(page, 'edgeless-note', 1); + }); +}); + +test.describe('Arrow Keys should move selection', () => { + test('with shift increment by 10px', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + + await addBasicRectShapeElement( + page, + { x: 100, y: 100 }, + { x: 200, y: 200 } + ); + + await page.keyboard.down('Shift'); + + for (let i = 0; i < 10; i++) await page.keyboard.press('ArrowLeft'); + for (let i = 0; i < 10; i++) await page.keyboard.press('ArrowDown'); + + await assertEdgelessSelectedRect(page, [0, 200, 100, 100]); + }); + + test('without shift increment by 1px', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + + await addBasicRectShapeElement( + page, + { x: 100, y: 100 }, + { x: 200, y: 200 } + ); + + for (let i = 0; i < 10; i++) await page.keyboard.press('ArrowRight'); + for (let i = 0; i < 10; i++) await page.keyboard.press('ArrowUp'); + + await assertEdgelessSelectedRect(page, [110, 90, 100, 100]); + }); +}); diff --git a/blocksuite/tests-legacy/edgeless/snap.spec.ts b/blocksuite/tests-legacy/edgeless/snap.spec.ts new file mode 100644 index 0000000000000..666e54f76ec76 --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/snap.spec.ts @@ -0,0 +1,45 @@ +import { undoByClick } from '../utils/actions/click.js'; +import { + createShapeElement, + dragBetweenViewCoords, + edgelessCommonSetup, + Shape, +} from '../utils/actions/edgeless.js'; +import { waitNextFrame } from '../utils/actions/misc.js'; +import { assertSelectedBound } from '../utils/asserts.js'; +import { test } from '../utils/playwright.js'; + +test.describe('snap', () => { + test('snap', async ({ page }) => { + await edgelessCommonSetup(page); + + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await createShapeElement(page, [300, 0], [300 + 100, 100], Shape.Square); + + await assertSelectedBound(page, [300, 0, 100, 100]); + + await dragBetweenViewCoords(page, [300 + 5, 50], [300 + 5, 50 + 5]); + await assertSelectedBound(page, [300, 5, 100, 100]); + + await undoByClick(page); + await dragBetweenViewCoords(page, [300 + 5, 50], [300 + 5, 50 + 3]); + await assertSelectedBound(page, [300, 0, 100, 100]); + }); + + test('snapDistribute', async ({ page }) => { + await edgelessCommonSetup(page); + + await createShapeElement(page, [0, 0], [100, 100], Shape.Square); + await createShapeElement(page, [300, 0], [300 + 100, 100], Shape.Square); + await createShapeElement(page, [144, 0], [144 + 100, 100], Shape.Square); + + await assertSelectedBound(page, [144, 0, 100, 100]); + await dragBetweenViewCoords( + page, + [144 + 100 - 9, 100 - 9], + [144 + 100 - 9 + 3, 100 - 9] + ); + await assertSelectedBound(page, [150, 0, 100, 100]); + await waitNextFrame(page); + }); +}); diff --git a/blocksuite/tests-legacy/edgeless/text.spec.ts b/blocksuite/tests-legacy/edgeless/text.spec.ts new file mode 100644 index 0000000000000..a0197659aded5 --- /dev/null +++ b/blocksuite/tests-legacy/edgeless/text.spec.ts @@ -0,0 +1,316 @@ +import { expect, type Page } from '@playwright/test'; +import { getLinkedDocPopover } from 'utils/actions/linked-doc.js'; + +import { + assertEdgelessTool, + enterPlaygroundRoom, + getEdgelessSelectedRect, + initEmptyEdgelessState, + pressArrowLeft, + pressEnter, + setEdgelessTool, + SHORT_KEY, + switchEditorMode, + type, + waitForInlineEditorStateUpdated, + waitNextFrame, + zoomResetByKeyboard, +} from '../utils/actions/index.js'; +import { assertEdgelessCanvasText } from '../utils/asserts.js'; +import { test } from '../utils/playwright.js'; + +async function assertTextFont(page: Page, font: string) { + const fontButton = page.getByRole('button', { + name: /^Font$/, + }); + const fontPanel = page.locator('edgeless-font-family-panel'); + const isFontPanelShow = await fontPanel.isVisible(); + if (!isFontPanelShow) { + if (!(await fontButton.isVisible())) + throw new Error('edgeless change text toolbar is not visible'); + + await fontButton.click(); + } + + const button = fontPanel.locator(`[data-font="${font}"]`); + await expect(button.locator('.active-mode-color[active]')).toBeVisible(); +} + +test.describe('edgeless canvas text', () => { + test.beforeEach(async ({ page }) => { + await enterPlaygroundRoom(page, { + flags: { + enable_edgeless_text: false, + }, + }); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + }); + + test('add text element in default mode', async ({ page }) => { + await setEdgelessTool(page, 'default'); + await page.mouse.dblclick(130, 140); + await waitForInlineEditorStateUpdated(page); + await waitNextFrame(page); + + await type(page, 'hello'); + await assertEdgelessCanvasText(page, 'hello'); + await assertEdgelessTool(page, 'default'); + + await page.mouse.click(120, 140); + + expect(await page.locator('edgeless-text-editor').count()).toBe(0); + + await page.mouse.dblclick(145, 155); + await waitNextFrame(page); + await page.locator('edgeless-text-editor').waitFor({ + state: 'attached', + }); + await type(page, 'hello'); + await assertEdgelessCanvasText(page, 'hhelloello'); + + await pressArrowLeft(page, 5); + await type(page, 'ddd\n'); + await assertEdgelessCanvasText(page, 'hddd\nhelloello'); + }); + + test('should not trigger linked doc popover in canvas text', async ({ + page, + }) => { + await setEdgelessTool(page, 'default'); + await page.mouse.dblclick(130, 140); + await waitForInlineEditorStateUpdated(page); + await waitNextFrame(page); + + await type(page, '@'); + const { linkedDocPopover } = getLinkedDocPopover(page); + await expect(linkedDocPopover).not.toBeVisible(); + await pressEnter(page); + await assertEdgelessCanvasText(page, '@\n'); + }); + + // it's also a little flaky + test('add text element in text mode', async ({ page }) => { + await page.mouse.dblclick(130, 140); + await waitNextFrame(page); + + await type(page, 'hello'); + await assertEdgelessCanvasText(page, 'hello'); + await assertEdgelessTool(page, 'default'); + + await page.mouse.click(120, 140); + + expect(await page.locator('edgeless-text-editor').count()).toBe(0); + + await page.mouse.dblclick(145, 145); + + await page.locator('edgeless-text-editor').waitFor({ + state: 'attached', + }); + await type(page, 'hello'); + await page.waitForTimeout(100); + await assertEdgelessCanvasText(page, 'hhelloello'); + + await page.mouse.click(145, 155); + await type(page, 'ddd\n'); + await assertEdgelessCanvasText(page, 'hddd\nhelloello'); + }); + + test('copy and paste', async ({ page }) => { + await setEdgelessTool(page, 'default'); + await page.mouse.dblclick(130, 140); + await waitNextFrame(page); + + await type(page, 'hello'); + await assertEdgelessCanvasText(page, 'hello'); + await assertEdgelessTool(page, 'default'); + + await page.mouse.move(145, 155); + await page.mouse.down(); + await page.mouse.move(170, 155, { + steps: 10, + }); + await page.mouse.up(); + // h|ell|o + await waitNextFrame(page, 200); + await page.keyboard.press(`${SHORT_KEY}+c`); + + await waitNextFrame(page, 200); + await type(page, 'ddd', 100); + await waitNextFrame(page, 200); + await assertEdgelessCanvasText(page, 'hdddo'); + + await page.keyboard.press(`${SHORT_KEY}+v`); + await assertEdgelessCanvasText(page, 'hdddello'); + }); + + test('normalize text element rect after change its font', async ({ + page, + }) => { + await page.mouse.dblclick(200, 200); + await waitNextFrame(page); + + await type(page, 'aaa\nbbbbbbbb\n\ncc'); + await assertEdgelessCanvasText(page, 'aaa\nbbbbbbbb\n\ncc'); + await assertEdgelessTool(page, 'default'); + await page.mouse.click(10, 100); + + await page.mouse.click(220, 210); + await waitNextFrame(page); + let { width: lastWidth, height: lastHeight } = + await getEdgelessSelectedRect(page); + const fontButton = page.getByRole('button', { name: /^Font$/ }); + await fontButton.click(); + + // Default is Inter + await assertTextFont(page, 'Inter'); + const kalamTextFont = page.getByText('Kalam'); + await kalamTextFont.click(); + await waitNextFrame(page); + let selectedRect = await getEdgelessSelectedRect(page); + expect(selectedRect.width).not.toEqual(lastWidth); + expect(selectedRect.height).not.toEqual(lastHeight); + + lastWidth = selectedRect.width; + lastHeight = selectedRect.height; + await fontButton.click(); + await assertTextFont(page, 'Kalam'); + const InterTextFont = page.getByText('Inter'); + await InterTextFont.click(); + await waitNextFrame(page); + selectedRect = await getEdgelessSelectedRect(page); + expect(selectedRect.width).not.toEqual(lastWidth); + expect(selectedRect.height).not.toEqual(lastHeight); + }); + + test('auto wrap text by dragging left and right edge', async ({ page }) => { + await zoomResetByKeyboard(page); + await setEdgelessTool(page, 'default'); + await page.mouse.dblclick(130, 140); + await waitForInlineEditorStateUpdated(page); + await waitNextFrame(page); + + await type(page, 'hellohello'); + await assertEdgelessCanvasText(page, 'hellohello'); + await assertEdgelessTool(page, 'default'); + + // quit edit mode + await page.mouse.click(120, 140); + + // select text element + await page.mouse.click(150, 140); + await waitNextFrame(page); + + // should exit selected rect and record last width and height, then compare them + let selectedRect = await getEdgelessSelectedRect(page); + let lastWidth = selectedRect.width; + let lastHeight = selectedRect.height; + + // move cursor to the right edge and drag it to resize the width of text element + await page.mouse.move(130 + lastWidth, 160); + await page.mouse.down(); + await page.mouse.move(130 + lastWidth / 2, 160, { + steps: 10, + }); + await page.mouse.up(); + + // the text should be wrapped, so check the width and height of text element + selectedRect = await getEdgelessSelectedRect(page); + expect(selectedRect.width).toBeLessThan(lastWidth); + expect(selectedRect.height).toBeGreaterThan(lastHeight); + + await page.mouse.dblclick(140, 160); + await waitForInlineEditorStateUpdated(page); + await waitNextFrame(page); + await assertEdgelessCanvasText(page, 'hellohello'); + + // quit edit mode + await page.mouse.click(120, 140); + + // select text element + await page.mouse.click(150, 140); + await waitNextFrame(page); + + // check selected rect and record the last width and height + selectedRect = await getEdgelessSelectedRect(page); + lastWidth = selectedRect.width; + lastHeight = selectedRect.height; + // move cursor to the left edge and drag it to resize the width of text element + await page.mouse.move(130, 160); + await page.mouse.down(); + await page.mouse.move(60, 160, { + steps: 10, + }); + await page.mouse.up(); + + // the text should be unwrapped, check the width and height of text element + selectedRect = await getEdgelessSelectedRect(page); + expect(selectedRect.width).toBeGreaterThan(lastWidth); + expect(selectedRect.height).toBeLessThan(lastHeight); + + await page.mouse.dblclick(100, 160); + await waitForInlineEditorStateUpdated(page); + await waitNextFrame(page); + await assertEdgelessCanvasText(page, 'hellohello'); + }); + + test('text element should have maxWidth after adjusting width by dragging left or right edge', async ({ + page, + }) => { + await zoomResetByKeyboard(page); + await setEdgelessTool(page, 'default'); + await page.mouse.dblclick(130, 140); + await waitForInlineEditorStateUpdated(page); + await waitNextFrame(page); + + await type(page, 'hellohello'); + await assertEdgelessCanvasText(page, 'hellohello'); + await assertEdgelessTool(page, 'default'); + + // quit edit mode + await page.mouse.click(120, 140); + + // select text element + await page.mouse.click(150, 140); + await waitNextFrame(page); + + let selectedRect = await getEdgelessSelectedRect(page); + let lastWidth = selectedRect.width; + let lastHeight = selectedRect.height; + + // move cursor to the right edge and drag it to resize the width of text element + await page.mouse.move(130 + lastWidth, 160); + await page.mouse.down(); + await page.mouse.move(130 + lastWidth / 2, 160, { + steps: 10, + }); + await page.mouse.up(); + + // the text should be wrapped, so check the width and height of text element + selectedRect = await getEdgelessSelectedRect(page); + expect(selectedRect.width).toBeLessThan(lastWidth); + expect(selectedRect.height).toBeGreaterThan(lastHeight); + lastWidth = selectedRect.width; + lastHeight = selectedRect.height; + + // enter edit mode + await waitNextFrame(page); + await page.mouse.dblclick(140, 180); + await waitForInlineEditorStateUpdated(page); + await waitNextFrame(page); + await type(page, 'hello'); + await assertEdgelessCanvasText(page, 'hellohellohello'); + + // quit edit mode + await page.mouse.click(120, 140); + + // select text element + await page.mouse.click(150, 140); + await waitNextFrame(page); + + // after input, the width of the text element should be the same as before, but the height should be changed + selectedRect = await getEdgelessSelectedRect(page); + expect(selectedRect.width).toBeCloseTo(Math.round(lastWidth)); + expect(selectedRect.height).toBeGreaterThan(lastHeight); + }); +}); diff --git a/blocksuite/tests-legacy/embed-synced-doc.spec.ts b/blocksuite/tests-legacy/embed-synced-doc.spec.ts new file mode 100644 index 0000000000000..9fd6b8f1a674e --- /dev/null +++ b/blocksuite/tests-legacy/embed-synced-doc.spec.ts @@ -0,0 +1,268 @@ +import type { DatabaseBlockModel } from '@blocksuite/affine-model'; +import { assertExists } from '@blocksuite/global/utils'; +import { expect, type Page } from '@playwright/test'; +import { switchEditorMode } from 'utils/actions/edgeless.js'; +import { getLinkedDocPopover } from 'utils/actions/linked-doc.js'; +import { + enterPlaygroundRoom, + focusRichText, + initEmptyEdgelessState, + initEmptyParagraphState, + waitNextFrame, +} from 'utils/actions/misc.js'; + +import { test } from './utils/playwright.js'; + +test.describe('Embed synced doc', () => { + test.beforeEach(async ({ page }) => { + await enterPlaygroundRoom(page); + }); + + async function createAndConvertToEmbedSyncedDoc(page: Page) { + const { createLinkedDoc } = getLinkedDocPopover(page); + const linkedDoc = await createLinkedDoc('page1'); + const lickedDocBox = await linkedDoc.boundingBox(); + assertExists(lickedDocBox); + await page.mouse.move( + lickedDocBox.x + lickedDocBox.width / 2, + lickedDocBox.y + lickedDocBox.height / 2 + ); + + await waitNextFrame(page, 200); + const referencePopup = page.locator('.affine-reference-popover-container'); + await expect(referencePopup).toBeVisible(); + + const switchButton = page.getByRole('button', { name: 'Switch view' }); + await switchButton.click(); + + const embedSyncedDocBtn = page.getByRole('button', { name: 'Embed view' }); + await expect(embedSyncedDocBtn).toBeVisible(); + + await embedSyncedDocBtn.click(); + await waitNextFrame(page, 200); + + const embedSyncedBlock = page.locator('affine-embed-synced-doc-block'); + expect(await embedSyncedBlock.count()).toBe(1); + } + + test('can change linked doc to embed synced doc', async ({ page }) => { + await initEmptyParagraphState(page); + await focusRichText(page); + + await createAndConvertToEmbedSyncedDoc(page); + }); + + test('can change embed synced doc to card view', async ({ page }) => { + await initEmptyParagraphState(page); + await focusRichText(page); + + await createAndConvertToEmbedSyncedDoc(page); + + const syncedDoc = page.locator(`affine-embed-synced-doc-block`); + const syncedDocBox = await syncedDoc.boundingBox(); + assertExists(syncedDocBox); + await page.mouse.click( + syncedDocBox.x + syncedDocBox.width / 2, + syncedDocBox.y + syncedDocBox.height / 2 + ); + + await waitNextFrame(page, 200); + const toolbar = page.locator('.embed-card-toolbar'); + await expect(toolbar).toBeVisible(); + + const switchBtn = toolbar.getByRole('button', { name: 'Switch view' }); + await expect(switchBtn).toBeVisible(); + + await switchBtn.click(); + await waitNextFrame(page, 200); + + const cardBtn = toolbar.getByRole('button', { name: 'Card view' }); + await cardBtn.click(); + await waitNextFrame(page, 200); + + const embedSyncedBlock = page.locator('affine-embed-linked-doc-block'); + expect(await embedSyncedBlock.count()).toBe(1); + }); + + test.fixme( + 'drag embed synced doc to whiteboard should fit in height', + async ({ page }) => { + await initEmptyEdgelessState(page); + await focusRichText(page); + + await createAndConvertToEmbedSyncedDoc(page); + + // Focus on the embed synced doc + const embedSyncedBlock = page.locator('affine-embed-synced-doc-block'); + let embedSyncedBox = await embedSyncedBlock.boundingBox(); + assertExists(embedSyncedBox); + await page.mouse.click( + embedSyncedBox.x + embedSyncedBox.width / 2, + embedSyncedBox.y + embedSyncedBox.height / 2 + ); + + // Switch to edgeless mode + await switchEditorMode(page); + await waitNextFrame(page, 200); + + // Double click on note to enter edit status + const noteBlock = page.locator('affine-edgeless-note'); + const noteBlockBox = await noteBlock.boundingBox(); + assertExists(noteBlockBox); + await page.mouse.dblclick(noteBlockBox.x + 10, noteBlockBox.y + 10); + await waitNextFrame(page, 200); + + // Drag the embed synced doc to whiteboard + embedSyncedBox = await embedSyncedBlock.boundingBox(); + assertExists(embedSyncedBox); + const height = embedSyncedBox.height; + await page.mouse.move(embedSyncedBox.x - 10, embedSyncedBox.y - 100); + await page.mouse.move(embedSyncedBox.x - 10, embedSyncedBox.y + 10); + await waitNextFrame(page); + await page.mouse.down(); + await page.mouse.move(100, 200, { steps: 30 }); + await page.mouse.up(); + + // Check the height of the embed synced doc portal, it should be the same as the embed synced doc in note + const EmbedSyncedDocBlock = page.locator( + 'affine-embed-edgeless-synced-doc-block' + ); + const EmbedSyncedDocBlockBox = await EmbedSyncedDocBlock.boundingBox(); + const border = 1; + assertExists(EmbedSyncedDocBlockBox); + expect(EmbedSyncedDocBlockBox.height).toBeCloseTo(height + 2 * border, 1); + } + ); + + test('nested embed synced doc should be rendered as card when depth >=1', async ({ + page, + }) => { + await page.evaluate(() => { + const { doc, collection } = window; + const rootId = doc.addBlock('affine:page', { + title: new doc.Text(), + }); + + const noteId = doc.addBlock('affine:note', {}, rootId); + doc.addBlock('affine:paragraph', {}, noteId); + + const doc2 = collection.createDoc({ id: 'doc2' }); + doc2.load(); + const rootId2 = doc2.addBlock('affine:page', { + title: new doc.Text('Doc 2'), + }); + + const noteId2 = doc2.addBlock('affine:note', {}, rootId2); + doc2.addBlock( + 'affine:paragraph', + { + text: new doc.Text('Hello from Doc 2'), + }, + noteId2 + ); + + const doc3 = collection.createDoc({ id: 'doc3' }); + doc3.load(); + const rootId3 = doc3.addBlock('affine:page', { + title: new doc.Text('Doc 3'), + }); + + const noteId3 = doc3.addBlock('affine:note', {}, rootId3); + doc3.addBlock( + 'affine:paragraph', + { + text: new doc.Text('Hello from Doc 3'), + }, + noteId3 + ); + + doc2.addBlock( + 'affine:embed-synced-doc', + { + pageId: 'doc3', + }, + noteId2 + ); + doc.addBlock( + 'affine:embed-synced-doc', + { + pageId: 'doc2', + }, + noteId + ); + }); + expect(await page.locator('affine-embed-synced-doc-block').count()).toBe(2); + expect(await page.locator('affine-paragraph').count()).toBe(2); + expect(await page.locator('affine-embed-synced-doc-card').count()).toBe(1); + expect(await page.locator('editor-host').count()).toBe(2); + }); + + test.describe('synced doc should be readonly', () => { + test('synced doc should be readonly', async ({ page }) => { + await initEmptyParagraphState(page); + await focusRichText(page); + await createAndConvertToEmbedSyncedDoc(page); + const locator = page.locator('affine-embed-synced-doc-block'); + await locator.click(); + + const toolbar = page.locator('editor-toolbar'); + const openMenu = toolbar.getByRole('button', { name: 'Open' }); + await openMenu.click(); + + const button = toolbar.getByRole('button', { name: 'Open this doc' }); + await button.click(); + + await page.evaluate(async () => { + const { collection } = window; + const getDocCollection = () => { + for (const [id, doc] of collection.docs.entries()) { + if (id === 'doc:home') { + continue; + } + return doc; + } + return null; + }; + + const doc2Collection = getDocCollection(); + const doc2 = doc2Collection!.getDoc(); + const [noteBlock] = doc2!.getBlocksByFlavour('affine:note'); + const noteId = noteBlock.id; + + const databaseId = doc2.addBlock( + 'affine:database', + { + title: new doc2.Text('Database 1'), + }, + noteId + ); + const model = doc2.getBlockById(databaseId) as DatabaseBlockModel; + await new Promise(resolve => setTimeout(resolve, 100)); + const databaseBlock = document.querySelector('affine-database'); + const databaseService = databaseBlock?.service; + if (databaseService) { + databaseService.databaseViewInitEmpty( + model, + databaseService.viewPresets.tableViewMeta.type + ); + databaseService.applyColumnUpdate(model); + } + }); + + // go back to previous doc + await page.evaluate(() => { + const { collection, editor } = window; + editor.doc = collection.getDoc('doc:home')!; + }); + + const databaseFirstCell = page.locator( + '.affine-database-column-header.database-row' + ); + await databaseFirstCell.click({ force: true }); + const indicatorCount = await page + .locator('affine-drag-indicator') + .count(); + expect(indicatorCount).toBe(1); + }); + }); +}); diff --git a/blocksuite/tests-legacy/fixtures/smile.png b/blocksuite/tests-legacy/fixtures/smile.png new file mode 100644 index 0000000000000000000000000000000000000000..1a51fe72041492c5e0e9429c62770b473b0f6c67 GIT binary patch literal 96 zcmZ?wbhEHb { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await dragBetweenIndices(page, [0, 0], [2, 3]); + const { formatBar } = getFormatBar(page); + await expect(formatBar).toBeVisible(); + + const box = await formatBar.boundingBox(); + if (!box) { + throw new Error("formatBar doesn't exist"); + } + const rect = await getSelectionRect(page); + assertAlmostEqual(box.x - rect.left, -98, 5); + assertAlmostEqual(box.y - rect.bottom, 10, 5); + + // Click the edge of the format quick bar + await page.mouse.click(box.x + 4, box.y + box.height / 2); + // Even not any button is clicked, the format quick bar should't be hidden + await expect(formatBar).toBeVisible(); + + const noteEl = page.locator('affine-note'); + const { x, y } = await getBoundingBox(noteEl); + await page.mouse.click(x + 100, y + 20); + await expect(formatBar).not.toBeVisible(); +}); + +test('should format quick bar show when clicking drag handle', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + + const locator = page.locator('affine-paragraph').first(); + await locator.hover(); + const dragHandle = page.locator('.affine-drag-handle-grabber'); + const dragHandleRect = await dragHandle.boundingBox(); + assertExists(dragHandleRect); + await dragHandle.click(); + + const { formatBar } = getFormatBar(page); + await expect(formatBar).toBeVisible(); + + const box = await formatBar.boundingBox(); + if (!box) { + throw new Error("formatBar doesn't exist"); + } + assertAlmostEqual(box.x, 251, 5); + assertAlmostEqual(box.y - dragHandleRect.y, -55.5, 5); +}); + +test('should format quick bar show when select text by keyboard', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'hello world'); + await withPressKey(page, 'Shift', async () => { + let i = 10; + while (i--) { + await page.keyboard.press('ArrowLeft'); + await waitNextFrame(page); + } + }); + + const { formatBar } = getFormatBar(page); + await expect(formatBar).toBeVisible(); + + const formatBarBox = await formatBar.boundingBox(); + if (!formatBarBox) { + throw new Error("formatBar doesn't exist"); + } + let selectionRect = await getSelectionRect(page); + assertAlmostEqual(formatBarBox.x - selectionRect.x, -107, 3); + assertAlmostEqual( + formatBarBox.y + formatBarBox.height - selectionRect.top, + -10, + 3 + ); + + await page.keyboard.press('ArrowLeft'); + await expect(formatBar).not.toBeVisible(); + + await withPressKey(page, 'Shift', async () => { + let i = 10; + while (i--) { + await page.keyboard.press('ArrowRight'); + await waitNextFrame(page); + } + }); + + await expect(formatBar).toBeVisible(); + + const rightBox = await formatBar.boundingBox(); + if (!rightBox) { + throw new Error("formatBar doesn't exist"); + } + // The x position of the format quick bar depends on the font size + // so there are slight differences in different environments + selectionRect = await getSelectionRect(page); + assertAlmostEqual(formatBarBox.x - selectionRect.x, -107, 3); + assertAlmostEqual( + formatBarBox.y + formatBarBox.height - selectionRect.top, + -10, + 3 + ); +}); + +test('should format quick bar can only display one at a time', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await dragBetweenIndices(page, [0, 3], [0, 0]); + const { formatBar } = getFormatBar(page); + await expect(formatBar).toBeVisible(); + + await dragBetweenIndices(page, [2, 0], [2, 3]); + await expect(formatBar).toHaveCount(1); +}); + +test('should format quick bar hide when type text', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await dragBetweenIndices(page, [0, 0], [2, 3]); + const { formatBar } = getFormatBar(page); + await expect(formatBar).toBeVisible(); + await type(page, '1'); + await expect(formatBar).not.toBeVisible(); +}); + +test('should format quick bar be able to format text', async ({ + page, +}, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + // drag only the `456` paragraph + await dragBetweenIndices(page, [1, 0], [1, 3]); + + const { boldBtn, italicBtn, underlineBtn, strikeBtn, codeBtn } = + getFormatBar(page); + + await expect(boldBtn).not.toHaveAttribute('active', ''); + await expect(italicBtn).not.toHaveAttribute('active', ''); + await expect(underlineBtn).not.toHaveAttribute('active', ''); + await expect(strikeBtn).not.toHaveAttribute('active', ''); + await expect(codeBtn).not.toHaveAttribute('active', ''); + + await boldBtn.click(); + await italicBtn.click(); + await underlineBtn.click(); + await strikeBtn.click(); + await codeBtn.click(); + + // The button should be active after click + await expect(boldBtn).toHaveAttribute('active', ''); + await expect(italicBtn).toHaveAttribute('active', ''); + await expect(underlineBtn).toHaveAttribute('active', ''); + await expect(strikeBtn).toHaveAttribute('active', ''); + await expect(codeBtn).toHaveAttribute('active', ''); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_init.json` + ); + + await boldBtn.click(); + await underlineBtn.click(); + await codeBtn.click(); + + await waitNextFrame(page); + + // The bold button should be inactive after click again + await expect(boldBtn).not.toHaveAttribute('active', ''); + await expect(italicBtn).toHaveAttribute('active', ''); + await expect(underlineBtn).not.toHaveAttribute('active', ''); + await expect(strikeBtn).toHaveAttribute('active', ''); + await expect(codeBtn).not.toHaveAttribute('active', ''); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_finial.json` + ); +}); + +test('should format quick bar be able to change background color', async ({ + page, +}, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + // select `456` paragraph by dragging + await dragBetweenIndices(page, [1, 0], [1, 3]); + + const { highlight } = getFormatBar(page); + + await highlight.highlightBtn.hover(); + await expect(highlight.redForegroundBtn).toBeVisible(); + await expect(highlight.highlightBtn).toHaveAttribute( + 'data-last-used', + 'unset' + ); + await highlight.redForegroundBtn.click(); + await expect(highlight.highlightBtn).toHaveAttribute( + 'data-last-used', + 'var(--affine-text-highlight-foreground-red)' + ); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_init.json` + ); + + // select `123` paragraph by ctrl + a + await focusRichText(page); + await selectAllByKeyboard(page); + // use last used color + await highlight.highlightBtn.click(); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_select_all.json` + ); + + await expect(highlight.defaultColorBtn).toBeVisible(); + await highlight.defaultColorBtn.click(); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_default_color.json` + ); +}); + +test('should format quick bar be able to format text when select multiple line', async ({ + page, +}, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await dragBetweenIndices(page, [0, 0], [2, 3]); + + const { boldBtn } = getFormatBar(page); + await expect(boldBtn).not.toHaveAttribute('active', ''); + await boldBtn.click(); + + // The bold button should be active after click + await expect(boldBtn).toHaveAttribute('active', ''); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_init.json` + ); + + await boldBtn.click(); + await expect(boldBtn).not.toHaveAttribute('active', ''); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_finial.json` + ); +}); + +test('should format quick bar be able to link text', async ({ + page, +}, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + // drag only the `456` paragraph + await dragBetweenIndices(page, [1, 0], [1, 3]); + + const { linkBtn } = getFormatBar(page); + await expect(linkBtn).not.toHaveAttribute('active', ''); + await linkBtn.click(); + + const linkPopoverInput = page.locator('.affine-link-popover-input'); + await expect(linkPopoverInput).toBeVisible(); + + await type(page, 'https://www.example.com'); + await pressEnter(page); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_init.json` + ); + + // FIXME: remove this + await focusRichText(page); + await setSelection(page, 3, 0, 3, 3); + // The link button should be active after click + await expect(linkBtn).toHaveAttribute('active', ''); + await linkBtn.click(); + await waitNextFrame(page); + await expect(linkBtn).not.toHaveAttribute('active', ''); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_finial.json` + ); +}); + +test('should format quick bar be able to change to heading paragraph type', async ({ + page, +}, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + // drag only the `456` paragraph + await dragBetweenIndices(page, [0, 0], [0, 3]); + + const { openParagraphMenu, h1Btn, bulletedBtn } = getFormatBar(page); + await openParagraphMenu(); + + await expect(h1Btn).toBeVisible(); + await h1Btn.click(); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_init.json` + ); + + await bulletedBtn.click(); + await openParagraphMenu(); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_bulleted.json` + ); + + const { textBtn } = getFormatBar(page); + await textBtn.click(); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_finial.json` + ); + await page.waitForTimeout(10); + // The paragraph button should prevent selection after click + await assertRichTextInlineRange(page, 0, 0, 3); +}); + +test('should format quick bar show when double click text', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + const editorHost = getEditorHostLocator(page); + const richText = editorHost.locator('rich-text').nth(0); + await richText.dblclick({ + position: { x: 10, y: 10 }, + }); + const { formatBar } = getFormatBar(page); + await expect(formatBar).toBeVisible(); +}); + +test('should format quick bar not show at readonly mode', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await switchReadonly(page); + + await dragBetweenIndices(page, [0, 0], [2, 3]); + const { formatBar } = getFormatBar(page); + await expect(formatBar).not.toBeVisible(); + + const editorHost = getEditorHostLocator(page); + const richText = editorHost.locator('rich-text').nth(0); + await richText.dblclick({ + position: { x: 10, y: 10 }, + }); + await expect(formatBar).not.toBeVisible(); +}); + +test('should format bar follow scroll', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + + for (let i = 0; i < 30; i++) { + await pressEnter(page); + } + + await scrollToTop(page); + + await dragBetweenIndices(page, [0, 0], [2, 3]); + const { formatBar, boldBtn } = getFormatBar(page); + await assertLocatorVisible(page, formatBar); + + await scrollToBottom(page); + + await assertLocatorVisible(page, formatBar, false); + + // should format bar follow scroll after click bold button + await scrollToTop(page); + await assertLocatorVisible(page, formatBar); + await boldBtn.click(); + await scrollToBottom(page); + await assertLocatorVisible(page, formatBar, false); + + // should format bar follow scroll after transform text type + await scrollToTop(page); + await assertLocatorVisible(page, formatBar); + await updateBlockType(page, 'affine:list', 'bulleted'); + await scrollToBottom(page); + await assertLocatorVisible(page, formatBar, false); +}); + +test('should format quick bar position correct at the start of second line', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await page.evaluate(() => { + const { doc } = window; + const rootId = doc.addBlock('affine:page', { + title: new doc.Text(), + }); + const note = doc.addBlock('affine:note', {}, rootId); + const text = new doc.Text('a'.repeat(100)); + const paragraphId = doc.addBlock('affine:paragraph', { text }, note); + return paragraphId; + }); + // await focusRichText(page); + const editorHost = getEditorHostLocator(page); + const locator = editorHost.locator('.inline-editor').nth(0); + const textBox = await locator.boundingBox(); + if (!textBox) { + throw new Error("Can't get bounding box"); + } + // Drag to the start of the second line + await dragBetweenCoords( + page, + { x: textBox.x + textBox.width - 1, y: textBox.y + textBox.height - 1 }, + { x: textBox.x, y: textBox.y + textBox.height - 1 } + ); + + const { formatBar } = getFormatBar(page); + await expect(formatBar).toBeVisible(); + await waitNextFrame(page); + + const formatBox = await formatBar.boundingBox(); + if (!formatBox) { + throw new Error("formatBar doesn't exist"); + } + const selectionRect = await getSelectionRect(page); + assertAlmostEqual(formatBox.x - selectionRect.x, -99, 5); + assertAlmostEqual(formatBox.y + formatBox.height - selectionRect.top, 68, 5); +}); + +test('should format quick bar action status updated while undo', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'helloworld'); + await captureHistory(page); + await dragBetweenIndices(page, [0, 1], [0, 6]); + + const { formatBar, boldBtn } = getFormatBar(page); + await expect(formatBar).toBeVisible(); + await expect(boldBtn).toBeVisible(); + + await expect(boldBtn).not.toHaveAttribute('active', ''); + await boldBtn.click(); + await expect(boldBtn).toHaveAttribute('active', ''); + + await undoByKeyboard(page); + await expect(formatBar).toBeVisible(); + await expect(boldBtn).not.toHaveAttribute('active', ''); +}); + +test('should format quick bar work in single block selection', async ({ + page, +}, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + + await dragBetweenIndices( + page, + [1, 0], + [1, 3], + { x: -26 - 24, y: -10 }, + { x: 0, y: 0 } + ); + const blockSelections = page + .locator('affine-block-selection') + .locator('visible=true'); + await expect(blockSelections).toHaveCount(1); + + const { formatBar } = getFormatBar(page); + await expect(formatBar).toBeVisible(); + + const formatRect = await formatBar.boundingBox(); + const selectionRect = await blockSelections.boundingBox(); + assertExists(formatRect); + assertExists(selectionRect); + assertAlmostEqual(formatRect.x - selectionRect.x, 147.5, 10); + assertAlmostEqual(formatRect.y - selectionRect.y, 33, 10); + + const boldBtn = formatBar.getByTestId('bold'); + await boldBtn.click(); + const italicBtn = formatBar.getByTestId('italic'); + await italicBtn.click(); + const underlineBtn = formatBar.getByTestId('underline'); + await underlineBtn.click(); + //FIXME: trt to cancel italic + // Cancel italic + // await italicBtn.click(); + + await expect(blockSelections).toHaveCount(1); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}.json` + ); + + const noteEl = page.locator('affine-note'); + const { x, y, width, height } = await getBoundingBox(noteEl); + await page.mouse.click(x + width / 2, y + height / 2); + await expect(formatBar).not.toBeVisible(); +}); + +test('should format quick bar work in multiple block selection', async ({ + page, +}, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + + await dragBetweenIndices( + page, + [2, 3], + [0, 0], + { x: 20, y: 20 }, + { x: 0, y: 0 } + ); + const blockSelections = page + .locator('affine-block-selection') + .locator('visible=true'); + await expect(blockSelections).toHaveCount(3); + + const formatBarController = getFormatBar(page); + await expect(formatBarController.formatBar).toBeVisible(); + + const box = await formatBarController.formatBar.boundingBox(); + if (!box) { + throw new Error("formatBar doesn't exist"); + } + const rect = await blockSelections.first().boundingBox(); + assertExists(rect); + assertAlmostEqual(box.x - rect.x, 147.5, 10); + assertAlmostEqual(box.y - rect.y, 99, 10); + + await formatBarController.boldBtn.click(); + await formatBarController.italicBtn.click(); + await formatBarController.underlineBtn.click(); + // Cancel italic + await formatBarController.italicBtn.click(); + + await expect(blockSelections).toHaveCount(3); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}.json` + ); + + const noteEl = page.locator('affine-note'); + const { x, y, width, height } = await getBoundingBox(noteEl); + await page.mouse.click(x + width / 2, y + height / 2); + await expect(formatBarController.formatBar).not.toBeVisible(); +}); + +test('should format quick bar with block selection works when update block type', async ({ + page, +}, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + + await dragBetweenIndices( + page, + [2, 3], + [0, 0], + { x: 20, y: 20 }, + { x: 0, y: 0 } + ); + const blockSelections = page + .locator('affine-block-selection') + .locator('visible=true'); + await expect(blockSelections).toHaveCount(3); + + const formatBarController = getFormatBar(page); + await expect(formatBarController.formatBar).toBeVisible(); + + await formatBarController.openParagraphMenu(); + await formatBarController.bulletedBtn.click(); + await expect(blockSelections).toHaveCount(3); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_init.json` + ); + + await expect(formatBarController.formatBar).toBeVisible(); + await formatBarController.h1Btn.click(); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_final.json` + ); + await expect(formatBarController.formatBar).toBeVisible(); + await expect(blockSelections).toHaveCount(3); + + const noteEl = page.locator('affine-note'); + const { x, y, width, height } = await getBoundingBox(noteEl); + await page.mouse.click(x + width / 2, y + height / 2); + await expect(formatBarController.formatBar).not.toBeVisible(); +}); + +test('should format quick bar show after convert to code block', async ({ + page, +}, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + const formatBarController = getFormatBar(page); + await dragBetweenIndices( + page, + [2, 3], + [0, 0], + { x: 20, y: 20 }, + { x: 0, y: 0 } + ); + await expect(formatBarController.formatBar).toBeVisible(); + await expect(formatBarController.formatBar).toBeInViewport(); + + await formatBarController.openParagraphMenu(); + await formatBarController.codeBlockBtn.click(); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}.json` + ); +}); + +test('buttons in format quick bar should have correct active styles', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + + // `45` + await setInlineRangeInInlineEditor( + page, + { + index: 0, + length: 2, + }, + 2 + ); + const { codeBtn } = getFormatBar(page); + await codeBtn.click(); + await expect(codeBtn).toHaveAttribute('active', ''); + + // `456` + await setInlineRangeInInlineEditor( + page, + { + index: 0, + length: 3, + }, + 2 + ); + await expect(codeBtn).not.toHaveAttribute('active', ''); +}); + +test('should format bar style active correctly', async ({ page }) => { + await enterPlaygroundRoom(page); + await page.evaluate(() => { + const { doc } = window; + const rootId = doc.addBlock('affine:page', { + title: new doc.Text(), + }); + const note = doc.addBlock('affine:note', {}, rootId); + const delta = [ + { insert: '1', attributes: { bold: true, italic: true } }, + { insert: '2', attributes: { bold: true, underline: true } }, + { insert: '3', attributes: { bold: true, code: true } }, + ]; + const text = new doc.Text(delta as DeltaInsert[]); + doc.addBlock('affine:paragraph', { text }, note); + }); + + const { boldBtn, codeBtn, underlineBtn } = getFormatBar(page); + await dragBetweenIndices(page, [0, 0], [0, 3]); + await expect(boldBtn).toHaveAttribute('active', ''); + await expect(underlineBtn).not.toHaveAttribute('active', ''); + await expect(codeBtn).not.toHaveAttribute('active', ''); + + await underlineBtn.click(); + await expect(underlineBtn).toHaveAttribute('active', ''); + await expect(boldBtn).toHaveAttribute('active', ''); + await expect(codeBtn).not.toHaveAttribute('active', ''); +}); + +test('should format quick bar show when double click button', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await dragBetweenIndices(page, [0, 0], [2, 3]); + const { formatBar, boldBtn } = getFormatBar(page); + await expect(formatBar).toBeVisible(); + await boldBtn.dblclick({ + delay: 100, + }); + await expect(formatBar).toBeVisible(); +}); + +test('should the database action icon show correctly', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + + const databaseAction = page.getByTestId('convert-to-database'); + + await focusRichText(page); + await dragBetweenIndices( + page, + [2, 3], + [0, 0], + { x: 20, y: 20 }, + { x: 0, y: 0 } + ); + await expect(databaseAction).toBeVisible(); + + await focusRichText(page, 2); + await pressEnter(page); + await updateBlockType(page, 'affine:code'); + const codeBlock = page.locator('affine-code'); + const codeBox = await codeBlock.boundingBox(); + if (!codeBox) throw new Error('Missing code block box'); + + await page.keyboard.type('hello world'); + const position = { + startX: codeBox.x, + startY: codeBox.y + codeBox.height / 2, + endX: codeBox.x + codeBox.width, + endY: codeBox.y + codeBox.height / 2, + }; + await page.mouse.click(position.endX + 150, position.endY + 150); + await dragBetweenCoords( + page, + { x: position.startX + 10, y: position.startY - 10 }, + { x: position.endX, y: position.endY }, + { steps: 20 } + ); + await expect(databaseAction).not.toBeVisible(); +}); + +test('should convert to database work', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + + await dragBetweenIndices( + page, + [2, 3], + [0, 0], + { x: 20, y: 20 }, + { x: 0, y: 0 } + ); + const databaseAction = page.getByTestId('convert-to-database'); + await databaseAction.click(); + const database = page.locator('affine-database'); + await expect(database).toBeVisible(); + const rows = page.locator('.affine-database-block-row'); + expect(await rows.count()).toBe(3); +}); + +test('should show format-quick-bar and select all text of the block when triple clicking on text', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'hello world'); + + const editorHost = getEditorHostLocator(page); + const locator = editorHost.locator('.inline-editor').nth(0); + const textBox = await locator.boundingBox(); + if (!textBox) { + throw new Error("Can't get bounding box"); + } + + await page.mouse.dblclick(textBox.x + 10, textBox.y + textBox.height / 2); + + const { formatBar } = getFormatBar(page); + await expect(formatBar).toBeVisible(); + + await assertRichTextInlineRange(page, 0, 0, 5); + + const noteEl = page.locator('affine-note'); + const { x, y, width, height } = await getBoundingBox(noteEl); + await page.mouse.click(x + width / 2, y + height / 2); + + await expect(formatBar).toBeHidden(); + + await page.mouse.move(textBox.x + 10, textBox.y + textBox.height / 2); + + const options = { + clickCount: 1, + }; + await page.mouse.down(options); + await page.mouse.up(options); + + options.clickCount++; + await page.mouse.down(options); + await page.mouse.up(options); + + options.clickCount++; + await page.mouse.down(options); + await page.mouse.up(options); + + await assertRichTextInlineRange(page, 0, 0, 'hello world'.length); +}); + +test('should update the format quick bar state when there is a change in keyboard selection', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await page.evaluate(() => { + const { doc } = window; + const rootId = doc.addBlock('affine:page', { + title: new doc.Text(), + }); + const note = doc.addBlock('affine:note', {}, rootId); + const delta = [ + { insert: '1', attributes: { bold: true } }, + { insert: '2', attributes: { bold: true } }, + { insert: '3', attributes: { bold: false } }, + ]; + const text = new doc.Text(delta as DeltaInsert[]); + doc.addBlock('affine:paragraph', { text }, note); + }); + await focusTitle(page); + await pressArrowDown(page); + + const formatBar = getFormatBar(page); + await withPressKey(page, 'Shift', async () => { + await page.keyboard.press('ArrowRight'); + await page.keyboard.press('ArrowRight'); + await expect(formatBar.boldBtn).toHaveAttribute('active', ''); + await page.keyboard.press('ArrowRight'); + await expect(formatBar.boldBtn).not.toHaveAttribute('active', ''); + }); +}); + +test('format quick bar should not break cursor jumping', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await dragBetweenIndices(page, [1, 3], [1, 2]); + + const { formatBar } = getFormatBar(page); + await expect(formatBar).toBeVisible(); + + await pressArrowUp(page); + await type(page, '0'); + await assertRichTexts(page, ['1203', '456', '789']); + + await dragBetweenIndices(page, [1, 3], [1, 2]); + await pressArrowDown(page); + await type(page, '0'); + await assertRichTexts(page, ['1203', '456', '7809']); +}); + +test('selecting image should not show format bar', async ({ page }) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/toeverything/blocksuite/issues/4535', + }); + await enterPlaygroundRoom(page); + await initImageState(page); + await assertRichImage(page, 1); + await activeEmbed(page); + await waitNextFrame(page); + const { formatBar } = getFormatBar(page); + await expect(formatBar).not.toBeVisible(); +}); + +test('create linked doc from block selection with format bar', async ({ + page, +}, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + + await focusRichText(page, 1); + await pressTab(page); + await assertRichTexts(page, ['123', '456', '789']); + await assertBlockChildrenIds(page, '1', ['2', '4']); + await assertBlockChildrenIds(page, '2', ['3']); + + await selectAllBlocksByKeyboard(page); + await waitNextFrame(page, 200); + + const blockSelections = page + .locator('affine-block-selection') + .locator('visible=true'); + await expect(blockSelections).toHaveCount(2); + + const { createLinkedDocBtn } = getFormatBar(page); + expect(await createLinkedDocBtn.isVisible()).toBe(true); + await createLinkedDocBtn.click(); + + const linkedDocBlock = page.locator('affine-embed-linked-doc-block'); + await expect(linkedDocBlock).toHaveCount(1); + + const linkedDocBox = await linkedDocBlock.boundingBox(); + assertExists(linkedDocBox); + await page.mouse.dblclick( + linkedDocBox.x + linkedDocBox.width / 2, + linkedDocBox.y + linkedDocBox.height / 2 + ); + await waitNextFrame(page, 200); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}.json` + ); +}); + +test.describe('more menu button', () => { + test('should be able to perform the copy action', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + // drag only the `456` paragraph + await dragBetweenIndices(page, [1, 0], [1, 3]); + + const { openMoreMenu, copyBtn } = getFormatBar(page); + await openMoreMenu(); + await expect(copyBtn).toBeVisible(); + await assertRichTextInlineRange(page, 1, 0, 3); + await copyBtn.click(); + await assertRichTextInlineRange(page, 1, 0, 3); + + await focusRichText(page, 1); + await pasteByKeyboard(page); + await waitNextFrame(page); + + await assertRichTexts(page, ['123', '456456', '789']); + }); + + test('should be able to perform the duplicate action', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + + await focusRichText(page, 1); + await pressEscape(page); + + const { openMoreMenu, duplicateBtn } = getFormatBar(page); + await openMoreMenu(); + await expect(duplicateBtn).toBeVisible(); + await duplicateBtn.click(); + + await waitNextFrame(page); + + await assertRichTexts(page, ['123', '456', '456', '789']); + }); + + test('should be able to perform the delete action', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + + await focusRichText(page, 1); + await pressEscape(page); + + const { openMoreMenu, deleteBtn } = getFormatBar(page); + await openMoreMenu(); + await expect(deleteBtn).toBeVisible(); + await deleteBtn.click(); + + await waitNextFrame(page); + + await assertRichTexts(page, ['123', '789']); + }); +}); diff --git a/blocksuite/tests-legacy/fragments/frame-panel.spec.ts b/blocksuite/tests-legacy/fragments/frame-panel.spec.ts new file mode 100644 index 0000000000000..451e2e8275d49 --- /dev/null +++ b/blocksuite/tests-legacy/fragments/frame-panel.spec.ts @@ -0,0 +1,356 @@ +import { expect, type Locator, type Page } from '@playwright/test'; +import { dragBetweenCoords } from 'utils/actions/drag.js'; +import { + assertEdgelessNonSelectedRect, + assertEdgelessSelectedRect, + assertZoomLevel, +} from 'utils/asserts.js'; + +import { + addBasicShapeElement, + addNote, + createNote, + createShapeElement, + dragBetweenViewCoords, + edgelessCommonSetup, + enterPresentationMode, + getZoomLevel, + setEdgelessTool, + Shape, + switchEditorMode, + toggleFramePanel, +} from '../utils/actions/edgeless.js'; +import { waitNextFrame } from '../utils/actions/index.js'; +import { test } from '../utils/playwright.js'; + +async function dragFrameCard( + page: Page, + fromCard: Locator, + toCard: Locator, + direction: 'up' | 'down' = 'down' +) { + const fromRect = await fromCard.boundingBox(); + const toRect = await toCard.boundingBox(); + // drag to the center of the toCard + const center = { x: toRect!.width / 2, y: toRect!.height / 2 }; + const offset = direction === 'up' ? { x: 0, y: -20 } : { x: 0, y: 20 }; + await page.mouse.move(fromRect!.x + center.x, fromRect!.y + center.y); + await page.mouse.down(); + await page.mouse.move( + toRect!.x + center.x + offset.x, + toRect!.y + center.y + offset.y, + { steps: 10 } + ); + await page.mouse.up(); +} + +test.describe('frame panel', () => { + test('should display empty placeholder when no frames', async ({ page }) => { + await edgelessCommonSetup(page); + await toggleFramePanel(page); + const frameCards = page.locator('affine-frame-card'); + expect(await frameCards.count()).toBe(0); + + const placeholder = page.locator('.no-frame-placeholder'); + expect(await placeholder.isVisible()).toBeTruthy(); + }); + + test('should display frame cards when there are frames', async ({ page }) => { + await edgelessCommonSetup(page); + await toggleFramePanel(page); + + await addBasicShapeElement( + page, + { x: 300, y: 300 }, + { x: 350, y: 350 }, + Shape.Square + ); + + await addNote(page, 'hello', 150, 500); + + await page.mouse.click(0, 0); + + await setEdgelessTool(page, 'frame'); + await dragBetweenCoords(page, { x: 250, y: 250 }, { x: 360, y: 360 }); + + await setEdgelessTool(page, 'frame'); + await dragBetweenCoords(page, { x: 100, y: 440 }, { x: 600, y: 600 }); + + const frames = page.locator('affine-frame'); + expect(await frames.count()).toBe(2); + const frameCards = page.locator('affine-frame-card'); + expect(await frameCards.count()).toBe(2); + }); + + test('should render edgeless note correctly in frame preview', async ({ + page, + }) => { + await edgelessCommonSetup(page); + await toggleFramePanel(page); + + await addNote(page, 'hello', 150, 500); + + await page.mouse.click(0, 0); + + await setEdgelessTool(page, 'frame'); + await dragBetweenCoords(page, { x: 100, y: 440 }, { x: 600, y: 600 }); + await waitNextFrame(page, 100); + + const frames = page.locator('affine-frame'); + expect(await frames.count()).toBe(1); + const frameCards = page.locator('affine-frame-card'); + expect(await frameCards.count()).toBe(1); + const edgelessNote = page.locator('affine-frame-card affine-edgeless-note'); + expect(await edgelessNote.count()).toBe(1); + }); + + test('should update panel when frames change', async ({ page }) => { + await edgelessCommonSetup(page); + await toggleFramePanel(page); + const frameCards = page.locator('affine-frame-card'); + expect(await frameCards.count()).toBe(0); + + await addNote(page, 'hello', 150, 500); + + await page.mouse.click(0, 0); + + await setEdgelessTool(page, 'frame'); + await dragBetweenCoords(page, { x: 100, y: 440 }, { x: 600, y: 600 }); + + await setEdgelessTool(page, 'frame'); + await dragBetweenCoords(page, { x: 50, y: 300 }, { x: 120, y: 400 }); + await waitNextFrame(page); + + const frames = page.locator('affine-frame'); + expect(await frames.count()).toBe(2); + expect(await frameCards.count()).toBe(2); + + await page.mouse.click(50, 300); + await page.keyboard.press('Delete'); + await waitNextFrame(page); + + expect(await frames.count()).toBe(1); + expect(await frameCards.count()).toBe(1); + }); + + test.describe('frame panel behavior after mode switch', () => { + async function setupFrameTest(page: Page) { + await edgelessCommonSetup(page); + await toggleFramePanel(page); + + await addNote(page, 'hello', 150, 500); + await page.mouse.click(0, 0); + await waitNextFrame(page, 100); + + await setEdgelessTool(page, 'frame'); + await dragBetweenCoords( + page, + { x: 100, y: 440 }, + { x: 640, y: 600 }, + { steps: 10 } + ); + await waitNextFrame(page, 100); + + const edgelessNote = page.locator( + 'affine-frame-card affine-edgeless-note' + ); + expect(await edgelessNote.count()).toBe(1); + + return edgelessNote; + } + + test('should render edgeless note correctly after mode switch', async ({ + page, + }) => { + const edgelessNote = await setupFrameTest(page); + + const initialNoteRect = await edgelessNote.boundingBox(); + expect(initialNoteRect).not.toBeNull(); + + const { + width: noteWidth, + height: noteHeight, + x: noteX, + y: noteY, + } = initialNoteRect!; + + const checkNoteRect = async () => { + expect(await edgelessNote.count()).toBe(1); + + const newNoteRect = await edgelessNote.boundingBox(); + expect(newNoteRect).not.toBeNull(); + + expect(newNoteRect!.width).toBe(noteWidth); + expect(newNoteRect!.height).toBe(noteHeight); + expect(newNoteRect!.x).toBe(noteX); + expect(newNoteRect!.y).toBe(noteY); + }; + + await switchEditorMode(page); + await checkNoteRect(); + + await switchEditorMode(page); + await checkNoteRect(); + }); + + test('should update frame preview when note is moved', async ({ page }) => { + const edgelessNote = await setupFrameTest(page); + + const initialNoteRect = await edgelessNote.boundingBox(); + expect(initialNoteRect).not.toBeNull(); + + await switchEditorMode(page); + await switchEditorMode(page); + + async function moveNoteAndCheck( + start: { x: number; y: number }, + end: { x: number; y: number }, + comparison: 'greaterThan' | 'lessThan' + ) { + await page.mouse.move(start.x, start.y); + await page.mouse.down(); + await page.mouse.move(end.x, end.y); + await page.mouse.up(); + await waitNextFrame(page); + + const newNoteRect = await edgelessNote.boundingBox(); + expect(newNoteRect).not.toBeNull(); + + if (comparison === 'greaterThan') { + expect(newNoteRect!.x).toBeGreaterThan(initialNoteRect!.x); + expect(newNoteRect!.y).toBeGreaterThan(initialNoteRect!.y); + } else { + expect(newNoteRect!.x).toBeLessThan(initialNoteRect!.x); + expect(newNoteRect!.y).toBeLessThan(initialNoteRect!.y); + } + } + + // Move the note to the right + await moveNoteAndCheck( + { x: 150, y: 500 }, + { x: 200, y: 550 }, + 'greaterThan' + ); + + // Move the note back to the left + await moveNoteAndCheck( + { x: 200, y: 550 }, + { x: 100, y: 450 }, + 'lessThan' + ); + + // Move the note diagonally + await moveNoteAndCheck( + { x: 100, y: 450 }, + { x: 250, y: 600 }, + 'greaterThan' + ); + }); + }); + + test.describe('select and de-select frame', () => { + async function setupFrameTest(page: Page) { + await edgelessCommonSetup(page); + await toggleFramePanel(page); + + await addNote(page, 'hello', 150, 500); + + await page.mouse.click(0, 0); + + await setEdgelessTool(page, 'frame'); + await dragBetweenCoords(page, { x: 100, y: 440 }, { x: 640, y: 600 }); + await waitNextFrame(page); + + const frames = page.locator('affine-frame'); + const frameCards = page.locator('affine-frame-card'); + expect(await frames.count()).toBe(1); + expect(await frameCards.count()).toBe(1); + + return { frames, frameCards }; + } + + test('by click on frame card', async ({ page }) => { + const { frameCards } = await setupFrameTest(page); + + // click on the first frame card + await frameCards.nth(0).click(); + await assertEdgelessSelectedRect(page, [100, 440, 540, 160]); + + await frameCards.nth(0).click(); + await assertEdgelessNonSelectedRect(page); + }); + + test('by click on blank area', async ({ page }) => { + const { frameCards } = await setupFrameTest(page); + + // click on the first frame card + await frameCards.nth(0).click(); + await assertEdgelessSelectedRect(page, [100, 440, 540, 160]); + + const framePanel = page.locator('.frame-panel-container'); + const panelRect = await framePanel.boundingBox(); + expect(panelRect).not.toBeNull(); + const { x, y, width, height } = panelRect!; + await page.mouse.click(x + width / 2, y + height / 2); + await assertEdgelessNonSelectedRect(page); + }); + }); + + test('should fit the viewport to the frame when double click frame card', async ({ + page, + }) => { + await edgelessCommonSetup(page); + await toggleFramePanel(page); + + await assertZoomLevel(page, 100); + + await addNote(page, 'hello', 150, 500); + await page.mouse.click(0, 0); + + await setEdgelessTool(page, 'frame'); + await dragBetweenCoords(page, { x: 100, y: 440 }, { x: 600, y: 600 }); + await waitNextFrame(page); + + const frameCards = page.locator('affine-frame-card'); + await frameCards.nth(0).dblclick(); + + const zoomLevel = await getZoomLevel(page); + expect(zoomLevel).toBeGreaterThan(100); + }); + + test('should reorder frames when drag and drop frame card', async ({ + page, + }) => { + await edgelessCommonSetup(page); + await createShapeElement(page, [100, 100], [200, 200], Shape.Square); + await createNote(page, [300, 100], 'hello'); + + // Frame shape + await setEdgelessTool(page, 'frame'); + await dragBetweenViewCoords(page, [80, 80], [220, 220]); + await waitNextFrame(page, 100); + + // Frame note + await setEdgelessTool(page, 'frame'); + await dragBetweenViewCoords(page, [240, 0], [800, 200]); + + expect(await page.locator('affine-frame').count()).toBe(2); + + await toggleFramePanel(page); + + const frameCards = page.locator('affine-frame-card'); + expect(await frameCards.count()).toBe(2); + + // Drag the first frame card to the second + await dragFrameCard(page, frameCards.nth(0), frameCards.nth(1)); + + await enterPresentationMode(page); + await waitNextFrame(page, 100); + + // Check if frame contains note now is the first + const edgelessNote = page.locator( + 'affine-edgeless-root affine-edgeless-note' + ); + await expect(edgelessNote).toBeVisible(); + }); +}); diff --git a/blocksuite/tests-legacy/fragments/outline/outline-panel.spec.ts b/blocksuite/tests-legacy/fragments/outline/outline-panel.spec.ts new file mode 100644 index 0000000000000..2c4e9f669aa86 --- /dev/null +++ b/blocksuite/tests-legacy/fragments/outline/outline-panel.spec.ts @@ -0,0 +1,361 @@ +import { NoteDisplayMode } from '@blocksuite/affine-model'; +import { expect, type Locator, type Page } from '@playwright/test'; +import { + addNote, + changeNoteDisplayModeWithId, + switchEditorMode, + triggerComponentToolbarAction, + zoomResetByKeyboard, +} from 'utils/actions/edgeless.js'; +import { pressBackspace, pressEnter, type } from 'utils/actions/keyboard.js'; +import { + enterPlaygroundRoom, + focusRichTextEnd, + focusTitle, + getEditorHostLocator, + initEmptyEdgelessState, + initEmptyParagraphState, + waitNextFrame, +} from 'utils/actions/misc.js'; +import { assertRichTexts } from 'utils/asserts.js'; + +import { test } from '../../utils/playwright.js'; +import { + createHeadingsWithGap, + getVerticalCenterFromLocator, +} from './utils.js'; + +test.describe('toc-panel', () => { + async function toggleTocPanel(page: Page) { + await page.click('sl-button:text("Test Operations")'); + await page.click('sl-menu-item:text("Toggle Outline Panel")'); + await waitNextFrame(page); + const panel = page.locator('affine-outline-panel'); + await expect(panel).toBeVisible(); + + return panel; + } + + function getHeading(panel: Locator, level: number) { + return panel.locator(`affine-outline-panel-body .h${level} > span`); + } + + function getTitle(panel: Locator) { + return panel.locator(`affine-outline-panel-body .title`); + } + + async function toggleNoteSorting(page: Page) { + const enableSortingButton = page.locator( + '.outline-panel-header-container .note-sorting-button' + ); + await enableSortingButton.click(); + } + + async function dragNoteCard(page: Page, fromCard: Locator, toCard: Locator) { + const fromRect = await fromCard.boundingBox(); + const toRect = await toCard.boundingBox(); + + await page.mouse.move(fromRect!.x + 10, fromRect!.y + 10); + await page.mouse.down(); + await page.mouse.move(toRect!.x + 5, toRect!.y + 5, { steps: 10 }); + await page.mouse.up(); + } + + test('should display placeholder when no headings', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + const panel = await toggleTocPanel(page); + + const noHeadingPlaceholder = panel.locator('.note-placeholder'); + + await focusTitle(page); + await type(page, 'Title'); + await focusRichTextEnd(page); + await type(page, 'Hello World'); + + await expect(noHeadingPlaceholder).toBeVisible(); + }); + + test('should not display empty when there are only empty headings', async ({ + page, + }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + const panel = await toggleTocPanel(page); + + await focusTitle(page); + await type(page, 'Title'); + await focusRichTextEnd(page); + + // heading 1 to 6 + for (let i = 1; i <= 6; i++) { + await type(page, `${'#'.repeat(i)} `); + await pressEnter(page); + await expect(getHeading(panel, i)).toBeHidden(); + } + + // Title also should be hidden + await expect(getTitle(panel)).toBeHidden(); + }); + + test('should display title and headings when there are non-empty headings in editor', async ({ + page, + }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + const panel = await toggleTocPanel(page); + + await focusRichTextEnd(page); + + // heading 1 to 6 + for (let i = 1; i <= 6; i++) { + await type(page, `${'#'.repeat(i)} `); + await type(page, `Heading ${i}`); + await pressEnter(page); + + const heading = getHeading(panel, i); + await expect(heading).toBeVisible(); + await expect(heading).toContainText(`Heading ${i}`); + } + + const title = getTitle(panel); + await expect(title).toBeHidden(); + await focusTitle(page); + await type(page, 'Title'); + await expect(title).toHaveText('Title'); + + // heading 1 to 6 + for (let i = 1; i <= 6; i++) { + const heading = getHeading(panel, i); + await expect(heading).toBeVisible(); + await expect(heading).toContainText(`Heading ${i}`); + } + }); + + test('should update headings', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + const panel = await toggleTocPanel(page); + + await focusRichTextEnd(page); + + const h1 = getHeading(panel, 1); + + await type(page, '# Heading 1'); + await expect(h1).toContainText('Heading 1'); + + await pressBackspace(page, 'Heading 1'.length); + await expect(h1).toBeHidden(); + await type(page, 'Hello World'); + await expect(h1).toContainText('Hello World'); + + const title = getTitle(panel); + + await focusTitle(page); + await type(page, 'Title'); + await expect(title).toContainText('Title'); + + await pressBackspace(page, 2); + await expect(title).toContainText('Tit'); + }); + + test('should add padding to sub-headings', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + const panel = await toggleTocPanel(page); + + await focusRichTextEnd(page); + + await type(page, '# Heading 1'); + await pressEnter(page); + + await type(page, '## Heading 2'); + await pressEnter(page); + + const h1 = getHeading(panel, 1); + const h2 = getHeading(panel, 2); + + const h1Rect = await h1.boundingBox(); + const h2Rect = await h2.boundingBox(); + + expect(h1Rect).not.toBeNull(); + expect(h2Rect).not.toBeNull(); + + expect(h1Rect!.x).toBeLessThan(h2Rect!.x); + }); + + test('should highlight heading when scroll to area before viewport center', async ({ + page, + }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + const editor = getEditorHostLocator(page); + const panel = await toggleTocPanel(page); + + await focusRichTextEnd(page); + const headings = await createHeadingsWithGap(page); + await editor.locator('.inline-editor').first().scrollIntoViewIfNeeded(); + + const viewportCenter = await getVerticalCenterFromLocator( + page.locator('body') + ); + + const activeHeadingContainer = panel.locator( + 'affine-outline-panel-body .active' + ); + + for (let i = 0; i < headings.length; i++) { + const lastHeadingCenter = await getVerticalCenterFromLocator(headings[i]); + await page.mouse.wheel(0, lastHeadingCenter - viewportCenter + 50); + await waitNextFrame(page); + await expect(activeHeadingContainer).toContainText(`Heading ${i + 1}`); + } + }); + + test('should scroll to heading and highlight heading when click item in outline panel', async ({ + page, + }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + const panel = await toggleTocPanel(page); + + await focusRichTextEnd(page); + const headings = await createHeadingsWithGap(page); + const activeHeadingContainer = panel.locator( + 'affine-outline-panel-body .active' + ); + + const headingsInPanel = Array.from({ length: 6 }, (_, i) => + getHeading(panel, i + 1) + ); + + await headingsInPanel[2].click(); + await expect(headings[2]).toBeVisible(); + await expect(activeHeadingContainer).toContainText('Heading 3'); + }); + + test('should scroll to title when click title in outline panel', async ({ + page, + }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + const panel = await toggleTocPanel(page); + + await focusTitle(page); + await type(page, 'Title'); + + await focusRichTextEnd(page); + await createHeadingsWithGap(page); + + const title = page.locator('doc-title'); + const titleInPanel = getTitle(panel); + + await expect(title).not.toBeInViewport(); + await titleInPanel.click(); + await waitNextFrame(page, 50); + await expect(title).toBeVisible(); + }); + + test('should update notes when change note display mode from note toolbar', async ({ + page, + }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + await zoomResetByKeyboard(page); + const noteId = await addNote(page, 'hello', 300, 300); + await page.mouse.click(100, 100); + + await toggleTocPanel(page); + await toggleNoteSorting(page); + const docVisibleCard = page.locator( + '.card-container[data-invisible="false"]' + ); + const docInvisibleCard = page.locator( + '.card-container[data-invisible="true"]' + ); + + await expect(docVisibleCard).toHaveCount(1); + await expect(docInvisibleCard).toHaveCount(1); + + await changeNoteDisplayModeWithId( + page, + noteId, + NoteDisplayMode.DocAndEdgeless + ); + + await expect(docVisibleCard).toHaveCount(2); + await expect(docInvisibleCard).toHaveCount(0); + }); + + test('should reorder notes when drag and drop note in outline panel', async ({ + page, + }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + await zoomResetByKeyboard(page); + const note1 = await addNote(page, 'hello', 300, 300); + const note2 = await addNote(page, 'world', 300, 500); + await page.mouse.click(100, 100); + + await changeNoteDisplayModeWithId( + page, + note1, + NoteDisplayMode.DocAndEdgeless + ); + await changeNoteDisplayModeWithId( + page, + note2, + NoteDisplayMode.DocAndEdgeless + ); + + await toggleTocPanel(page); + await toggleNoteSorting(page); + const docVisibleCard = page.locator( + '.card-container[data-invisible="false"]' + ); + + await expect(docVisibleCard).toHaveCount(3); + await assertRichTexts(page, ['', 'hello', 'world']); + + const noteCard3 = docVisibleCard.nth(2); + const noteCard1 = docVisibleCard.nth(0); + + await dragNoteCard(page, noteCard3, noteCard1); + + await waitNextFrame(page); + await assertRichTexts(page, ['world', '', 'hello']); + }); + + test('should update notes after slicing note', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + await zoomResetByKeyboard(page); + const note1 = await addNote(page, 'hello', 100, 300); + await pressEnter(page); + await type(page, 'world'); + await page.mouse.click(100, 100); + + await changeNoteDisplayModeWithId( + page, + note1, + NoteDisplayMode.DocAndEdgeless + ); + + await toggleTocPanel(page); + await toggleNoteSorting(page); + const docVisibleCard = page.locator( + '.card-container[data-invisible="false"]' + ); + + await expect(docVisibleCard).toHaveCount(2); + + await triggerComponentToolbarAction(page, 'changeNoteSlicerSetting'); + await expect(page.locator('.note-slicer-button')).toBeVisible(); + await page.locator('.note-slicer-button').click(); + + await expect(docVisibleCard).toHaveCount(3); + }); +}); diff --git a/blocksuite/tests-legacy/fragments/outline/toc-viewer.spec.ts b/blocksuite/tests-legacy/fragments/outline/toc-viewer.spec.ts new file mode 100644 index 0000000000000..6818613b79821 --- /dev/null +++ b/blocksuite/tests-legacy/fragments/outline/toc-viewer.spec.ts @@ -0,0 +1,208 @@ +import { noop } from '@blocksuite/global/utils'; +import type { OutlineViewer } from '@blocksuite/presets'; +import { expect, type Page } from '@playwright/test'; +import { addNote, switchEditorMode } from 'utils/actions/edgeless.js'; +import { pressEnter, type } from 'utils/actions/keyboard.js'; +import { + enterPlaygroundRoom, + focusRichTextEnd, + focusTitle, + getEditorLocator, + initEmptyEdgelessState, + initEmptyParagraphState, + waitNextFrame, +} from 'utils/actions/misc.js'; + +import { test } from '../../utils/playwright.js'; +import { + createHeadingsWithGap, + getVerticalCenterFromLocator, +} from './utils.js'; + +test.describe('toc-viewer', () => { + async function toggleTocViewer(page: Page) { + await page.click('sl-button:text("Test Operations")'); + await page.click('sl-menu-item:text("Enable Outline Viewer")'); + await waitNextFrame(page); + const viewer = page.locator('affine-outline-viewer'); + return viewer; + } + + function getIndicators(page: Page) { + return page.locator('affine-outline-viewer .outline-viewer-indicator'); + } + + test('should display highlight indicators when non-empty headings exists', async ({ + page, + }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await toggleTocViewer(page); + + await focusRichTextEnd(page); + + const indicators = getIndicators(page); + + // heading 1 to 6 + for (let i = 1; i <= 6; i++) { + await type(page, `${'#'.repeat(i)} `); + await type(page, `Heading ${i}`); + await pressEnter(page); + + await expect(indicators.nth(i - 1)).toBeVisible(); + } + }); + + test('should be hidden when only empty headings exists', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await toggleTocViewer(page); + + await focusTitle(page); + await type(page, 'Title'); + await focusRichTextEnd(page); + + const indicators = getIndicators(page); + + // heading 1 to 6 + for (let i = 1; i <= 6; i++) { + await type(page, `${'#'.repeat(i)} `); + await pressEnter(page); + await expect(indicators).toHaveCount(0); + } + }); + + test('should display outline content when hovering over indicators', async ({ + page, + }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await toggleTocViewer(page); + + await focusRichTextEnd(page); + + await type(page, '# Heading 1'); + await pressEnter(page); + + const indicator = getIndicators(page).first(); + await indicator.hover({ force: true }); + + const items = page.locator('.outline-viewer-item'); + await expect(items).toHaveCount(2); + await expect(items.nth(0)).toContainText(['Table of Contents']); + await expect(items.nth(1)).toContainText(['Heading 1']); + }); + + test('should highlight indicator when scrolling', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await toggleTocViewer(page); + await focusRichTextEnd(page); + + const editor = getEditorLocator(page); + const indicators = getIndicators(page); + const headings = await createHeadingsWithGap(page); + await editor.locator('.inline-editor').first().scrollIntoViewIfNeeded(); + + const viewportCenter = await getVerticalCenterFromLocator( + page.locator('body') + ); + for (let i = 0; i < headings.length; i++) { + const lastHeadingCenter = await getVerticalCenterFromLocator(headings[i]); + await page.mouse.wheel(0, lastHeadingCenter - viewportCenter + 50); + await expect(indicators.nth(i)).toHaveClass(/active/); + } + }); + + test('should highlight indicator when click item in outline panel', async ({ + page, + }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + const viewer = await toggleTocViewer(page); + + await focusRichTextEnd(page); + const headings = await createHeadingsWithGap(page); + + const indicators = getIndicators(page); + await indicators.first().hover({ force: true }); + + const headingsInPanel = Array.from({ length: 6 }, (_, i) => + viewer.locator(`.h${i + 1} > span`) + ); + + await headingsInPanel[2].click(); + await expect(headings[2]).toBeVisible(); + await expect(indicators.nth(2)).toHaveClass(/active/); + }); + + test('should hide in edgeless mode', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await toggleTocViewer(page); + + const indicators = getIndicators(page); + + await focusRichTextEnd(page); + await type(page, '# Heading 1'); + await pressEnter(page); + + await expect(indicators).toHaveCount(1); + + await switchEditorMode(page); + + await expect(indicators).toHaveCount(0); + }); + + test('should hide edgeless-only note headings', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + const viewer = await toggleTocViewer(page); + + await focusRichTextEnd(page); + + await type(page, '# Heading 1'); + await pressEnter(page); + + await type(page, '## Heading 2'); + await pressEnter(page); + + await switchEditorMode(page); + + await addNote(page, '# Edgeless', 300, 300); + + await switchEditorMode(page); + + const indicators = getIndicators(page); + await expect(indicators).toHaveCount(2); + + await indicators.first().hover({ force: true }); + + await expect(viewer).toBeVisible(); + const hiddenTitle = viewer.locator('.hidden-title'); + await expect(hiddenTitle).toBeHidden(); + }); + + test('outline panel toggle button', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + const viewer = await toggleTocViewer(page); + + await focusRichTextEnd(page); + await createHeadingsWithGap(page); + + const toggleButton = viewer.locator( + '[data-testid="toggle-outline-panel-button"]' + ); + await expect(toggleButton).toHaveCount(0); + await viewer.evaluate((el: OutlineViewer) => { + el.toggleOutlinePanel = () => { + noop(); + }; + }); + + await waitNextFrame(page); + await expect(toggleButton).toHaveCount(1); + await expect(toggleButton).toBeVisible(); + }); +}); diff --git a/blocksuite/tests-legacy/fragments/outline/utils.ts b/blocksuite/tests-legacy/fragments/outline/utils.ts new file mode 100644 index 0000000000000..2e686201646da --- /dev/null +++ b/blocksuite/tests-legacy/fragments/outline/utils.ts @@ -0,0 +1,27 @@ +import { expect, type Locator, type Page } from '@playwright/test'; +import { pressEnter, type } from 'utils/actions/keyboard.js'; +import { getEditorHostLocator } from 'utils/actions/misc.js'; + +export async function getVerticalCenterFromLocator(locator: Locator) { + const rect = await locator.boundingBox(); + return rect!.y + rect!.height / 2; +} + +export async function createHeadingsWithGap(page: Page) { + // heading 1 to 6 + const editor = getEditorHostLocator(page); + + const headings: Locator[] = []; + await pressEnter(page, 10); + for (let i = 1; i <= 6; i++) { + await type(page, `${'#'.repeat(i)} `); + await type(page, `Heading ${i}`); + const heading = editor.locator(`.h${i}`); + await expect(heading).toBeVisible(); + headings.push(heading); + await pressEnter(page, 10); + } + await pressEnter(page, 10); + + return headings; +} diff --git a/blocksuite/tests-legacy/hotkey/bracket.spec.ts b/blocksuite/tests-legacy/hotkey/bracket.spec.ts new file mode 100644 index 0000000000000..1455e02fabe28 --- /dev/null +++ b/blocksuite/tests-legacy/hotkey/bracket.spec.ts @@ -0,0 +1,93 @@ +import { expect } from '@playwright/test'; + +import { + dragBetweenIndices, + enterPlaygroundRoom, + focusRichText, + getPageSnapshot, + initEmptyCodeBlockState, + initEmptyParagraphState, + initThreeParagraphs, + resetHistory, + type, + undoByClick, +} from '../utils/actions/index.js'; +import { assertRichTexts } from '../utils/asserts.js'; +import { test } from '../utils/playwright.js'; + +test('should bracket complete works', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, '([{'); + // type without selection should not trigger bracket complete + await assertRichTexts(page, ['([{']); + + await dragBetweenIndices(page, [0, 1], [0, 2]); + await type(page, '('); + await assertRichTexts(page, ['(([){']); + + await type(page, ')'); + // Should not trigger bracket complete when type right bracket + await assertRichTexts(page, ['(()){']); +}); + +test('bracket complete should not work when selecting mutiple lines', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + + // 1(23 45)6 789 + await dragBetweenIndices(page, [0, 1], [1, 2]); + await type(page, '('); + await assertRichTexts(page, ['1(6', '789']); +}); + +test('should bracket complete with backtick works', async ({ + page, +}, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'hello world'); + + await dragBetweenIndices(page, [0, 2], [0, 5]); + await resetHistory(page); + await type(page, '`'); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}.json` + ); + + await undoByClick(page); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_undo.json` + ); +}); + +test('auto delete bracket right', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyCodeBlockState(page); + await focusRichText(page); + await type(page, '('); + await assertRichTexts(page, ['()']); + await type(page, '('); + await assertRichTexts(page, ['(())']); + await page.keyboard.press('Backspace'); + await assertRichTexts(page, ['()']); + await page.keyboard.press('Backspace'); + await assertRichTexts(page, ['']); +}); + +test('skip redundant right bracket', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyCodeBlockState(page); + await focusRichText(page); + await type(page, '('); + await assertRichTexts(page, ['()']); + await type(page, ')'); + await assertRichTexts(page, ['()']); + await type(page, ')'); + await assertRichTexts(page, ['())']); +}); diff --git a/blocksuite/tests-legacy/hotkey/hotkey.spec.ts b/blocksuite/tests-legacy/hotkey/hotkey.spec.ts new file mode 100644 index 0000000000000..581ca2bb7ea1e --- /dev/null +++ b/blocksuite/tests-legacy/hotkey/hotkey.spec.ts @@ -0,0 +1,474 @@ +import { expect } from '@playwright/test'; + +import { + dragBetweenIndices, + enterPlaygroundRoom, + focusRichText, + getPageSnapshot, + initEmptyParagraphState, + initThreeParagraphs, + inlineCode, + MODIFIER_KEY, + pressArrowDown, + pressArrowLeft, + pressArrowRight, + pressArrowUp, + pressEnter, + pressForwardDelete, + pressShiftTab, + pressTab, + readClipboardText, + redoByClick, + redoByKeyboard, + resetHistory, + setInlineRangeInSelectedRichText, + SHIFT_KEY, + SHORT_KEY, + strikethrough, + type, + undoByClick, + undoByKeyboard, + updateBlockType, + waitNextFrame, +} from '../utils/actions/index.js'; +import { + assertBlockChildrenIds, + assertRichTextInlineRange, + assertRichTextModelType, + assertRichTexts, + assertTextFormat, +} from '../utils/asserts.js'; +import { test } from '../utils/playwright.js'; + +test('rich-text hotkey scope on single press', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'hello'); + await pressEnter(page); + await type(page, 'world'); + await assertRichTexts(page, ['hello', 'world']); + + await dragBetweenIndices(page, [0, 0], [1, 5]); + await page.keyboard.press('Backspace'); + await assertRichTexts(page, ['']); +}); + +test('single line rich-text inline code hotkey', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'hello'); + await dragBetweenIndices(page, [0, 0], [0, 5]); + await inlineCode(page); + await assertTextFormat(page, 0, 5, { code: true }); + + // undo + await undoByKeyboard(page); + await assertTextFormat(page, 0, 5, {}); + // redo + await redoByKeyboard(page); + await waitNextFrame(page); + await assertTextFormat(page, 0, 5, { code: true }); + + // the format should be removed after trigger the hotkey again + await inlineCode(page); + await assertTextFormat(page, 0, 5, {}); +}); + +test('type character jump out code node', async ({ page }, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'Hello'); + await setInlineRangeInSelectedRichText(page, 0, 5); + await inlineCode(page); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_1.json` + ); + await focusRichText(page); + await page.keyboard.press(`${SHORT_KEY}+ArrowRight`); + await type(page, 'block suite'); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_2.json` + ); +}); + +test('single line rich-text strikethrough hotkey', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'hello'); + await dragBetweenIndices(page, [0, 0], [0, 5]); + await strikethrough(page); + await assertTextFormat(page, 0, 5, { strike: true }); + + await undoByClick(page); + await assertTextFormat(page, 0, 5, {}); + + await redoByClick(page); + await assertTextFormat(page, 0, 5, { strike: true }); + + await waitNextFrame(page); + // the format should be removed after trigger the hotkey again + await strikethrough(page); + await assertTextFormat(page, 0, 5, {}); +}); + +test('use formatted cursor with hotkey', async ({ page }, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'aaa'); + // format italic + await page.keyboard.press(`${SHORT_KEY}+i`, { delay: 50 }); + await type(page, 'bbb'); + // format bold + await page.keyboard.press(`${SHORT_KEY}+b`, { delay: 50 }); + await type(page, 'ccc'); + // unformat italic + await page.keyboard.press(`${SHORT_KEY}+i`, { delay: 50 }); + await type(page, 'ddd'); + // unformat bold + await page.keyboard.press(`${SHORT_KEY}+b`, { delay: 50 }); + await type(page, 'eee'); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_init.json` + ); + + // format bold + await page.keyboard.press(`${SHORT_KEY}+b`, { delay: 50 }); + await type(page, 'fff'); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_bold.json` + ); + + await pressArrowLeft(page); + await pressArrowRight(page); + await type(page, 'ggg'); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_bold_ggg.json` + ); + + await setInlineRangeInSelectedRichText(page, 3, 0); + await waitNextFrame(page); + await type(page, 'hhh'); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_bold_hhh.json` + ); +}); + +test('use formatted cursor with hotkey at empty line', async ({ + page, +}, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + // format bold + await page.keyboard.press(`${SHORT_KEY}+b`, { delay: 50 }); + await type(page, 'aaa'); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_bold.json` + ); +}); + +test('should single line format hotkey work', async ({ page }, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'hello'); + await dragBetweenIndices(page, [0, 1], [0, 4]); + + // bold + await page.keyboard.press(`${SHORT_KEY}+b`, { delay: 50 }); + // italic + await page.keyboard.press(`${SHORT_KEY}+i`, { delay: 50 }); + // underline + await page.keyboard.press(`${SHORT_KEY}+u`, { delay: 50 }); + // strikethrough + await page.keyboard.press(`${SHORT_KEY}+Shift+s`, { delay: 50 }); + + await waitNextFrame(page); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_init.json` + ); + + // bold + await page.keyboard.press(`${SHORT_KEY}+b`, { delay: 50 }); + // italic + await page.keyboard.press(`${SHORT_KEY}+i`, { delay: 50 }); + // underline + await page.keyboard.press(`${SHORT_KEY}+u`, { delay: 50 }); + // strikethrough + await page.keyboard.press(`${SHORT_KEY}+Shift+s`, { delay: 50 }); + + await waitNextFrame(page); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_finial.json` + ); +}); + +test('should hotkey work in paragraph', async ({ page }, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + + await focusRichText(page, 0); + await type(page, 'hello'); + + // XXX wait for group to be updated + await page.waitForTimeout(10); + await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+1`); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_init.json` + ); + await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+6`); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_press_6.json` + ); + await page.waitForTimeout(50); + await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+8`); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_press_8.json` + ); + await page.waitForTimeout(50); + await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+9`); + await waitNextFrame(page, 200); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_press_9.json` + ); + await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+0`); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_press_0.json` + ); + await page.waitForTimeout(50); + await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+d`); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_press_d.json` + ); +}); + +test('format list to h1', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + + await focusRichText(page, 0); + await updateBlockType(page, 'affine:list', 'bulleted'); + await type(page, 'aa'); + await focusRichText(page, 0); + await updateBlockType(page, 'affine:paragraph', 'h1'); + await assertRichTextModelType(page, 'h1'); + await undoByClick(page); + await assertRichTextModelType(page, 'bulleted'); + await redoByClick(page); + await assertRichTextModelType(page, 'h1'); +}); + +test('should cut work single line', async ({ page }, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'hello'); + await resetHistory(page); + await dragBetweenIndices(page, [0, 1], [0, 4]); + // cut + await page.keyboard.press(`${SHORT_KEY}+x`); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_init.json` + ); + await undoByKeyboard(page); + const text = await readClipboardText(page); + expect(text).toBe('ell'); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_undo.json` + ); +}); + +test('should ctrl+enter create new block', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + + await focusRichText(page); + await type(page, '123'); + await pressArrowLeft(page, 2); + await pressEnter(page); + await waitNextFrame(page); + await assertRichTexts(page, ['1', '23']); + await page.keyboard.press(`${SHORT_KEY}+Enter`); + await assertRichTexts(page, ['1', '23', '']); +}); + +test('should left/right key navigator works', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await focusRichText(page, 0); + await assertRichTextInlineRange(page, 0, 3); + await page.keyboard.press(`${SHORT_KEY}+ArrowLeft`, { delay: 50 }); + await assertRichTextInlineRange(page, 0, 0); + await pressArrowLeft(page); + await assertRichTextInlineRange(page, 0, 0); + await page.keyboard.press(`${SHORT_KEY}+ArrowRight`, { delay: 50 }); + await assertRichTextInlineRange(page, 0, 3); + await pressArrowRight(page); + await assertRichTextInlineRange(page, 1, 0); + await pressArrowLeft(page); + await assertRichTextInlineRange(page, 0, 3); + await pressArrowRight(page, 4); + await assertRichTextInlineRange(page, 1, 3); + await pressArrowRight(page); + await assertRichTextInlineRange(page, 2, 0); + await pressArrowLeft(page); + await assertRichTextInlineRange(page, 1, 3); +}); + +test('should up/down key navigator works', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await focusRichText(page, 0); + await assertRichTextInlineRange(page, 0, 3); + await pressArrowDown(page); + await assertRichTextInlineRange(page, 1, 3); + await pressArrowDown(page); + await assertRichTextInlineRange(page, 2, 3); + await page.keyboard.press(`${SHORT_KEY}+ArrowLeft`, { delay: 50 }); + await assertRichTextInlineRange(page, 2, 0); + await pressArrowUp(page); + await assertRichTextInlineRange(page, 1, 0); + await pressArrowRight(page); + await pressArrowUp(page); + await assertRichTextInlineRange(page, 0, 1); + await pressArrowDown(page); + await assertRichTextInlineRange(page, 1, 1); +}); + +test('should support ctrl/cmd+shift+l convert to linked doc', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + + await dragBetweenIndices( + page, + [2, 3], + [0, 0], + { x: 20, y: 20 }, + { x: 0, y: 0 } + ); + + await waitNextFrame(page); + await page.keyboard.press(`${SHORT_KEY}+${SHIFT_KEY}+l`); + + const linkedDocCard = page.locator('affine-embed-linked-doc-block'); + await expect(linkedDocCard).toBeVisible(); + + const title = page.locator('.affine-embed-linked-doc-content-title-text'); + expect(await title.innerText()).toBe('Untitled'); + + const noteContent = page.locator('.affine-embed-linked-doc-content-note'); + expect(await noteContent.innerText()).toBe('123'); +}); + +test('should forwardDelete works when delete single character', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page, 0); + await type(page, 'hello'); + await pressArrowLeft(page, 5); + await pressForwardDelete(page); + await assertRichTexts(page, ['ello']); +}); + +test.describe('keyboard operation to move block up or down', () => { + test('common paragraph', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'hello'); + await pressEnter(page); + await type(page, 'world'); + await pressEnter(page); + await type(page, 'foo'); + await pressEnter(page); + await type(page, 'bar'); + await assertRichTexts(page, ['hello', 'world', 'foo', 'bar']); + await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+ArrowUp`); + await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+ArrowUp`); + await assertRichTexts(page, ['hello', 'bar', 'world', 'foo']); + await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+ArrowDown`); + await assertRichTexts(page, ['hello', 'world', 'bar', 'foo']); + }); + + test('with indent', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'hello'); + await pressEnter(page); + await pressTab(page); + await waitNextFrame(page); + await type(page, 'world'); + await pressEnter(page); + await pressShiftTab(page); + await waitNextFrame(page); + await type(page, 'foo'); + await assertRichTexts(page, ['hello', 'world', 'foo']); + await assertBlockChildrenIds(page, '2', ['3']); + await pressArrowUp(page, 2); + await waitNextFrame(page); + await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+ArrowDown`); + await waitNextFrame(page); + await assertRichTexts(page, ['foo', 'hello', 'world']); + await assertBlockChildrenIds(page, '1', ['4', '2']); + await assertBlockChildrenIds(page, '2', ['3']); + }); + + test('keep cursor', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'hello'); + await pressEnter(page); + await type(page, 'world'); + await pressEnter(page); + await type(page, 'foo'); + await assertRichTexts(page, ['hello', 'world', 'foo']); + await assertRichTextInlineRange(page, 2, 3); + await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+ArrowUp`); + await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+ArrowUp`); + await assertRichTextInlineRange(page, 0, 3); + await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+ArrowDown`); + await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+ArrowDown`); + await assertRichTextInlineRange(page, 2, 3); + }); +}); + +test('Enter key should as expected after setting heading by shortkey', async ({ + page, +}, testInfo) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/toeverything/blocksuite/issues/4987', + }); + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'hello'); + await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+1`); + await pressEnter(page); + await type(page, 'world'); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}.json` + ); +}); diff --git a/blocksuite/tests-legacy/hotkey/multiline.spec.ts b/blocksuite/tests-legacy/hotkey/multiline.spec.ts new file mode 100644 index 0000000000000..1a0d1f1b9a5fe --- /dev/null +++ b/blocksuite/tests-legacy/hotkey/multiline.spec.ts @@ -0,0 +1,176 @@ +import { expect } from '@playwright/test'; + +import { + dragBetweenIndices, + enterPlaygroundRoom, + focusRichText, + getPageSnapshot, + initEmptyParagraphState, + initThreeParagraphs, + inlineCode, + pressArrowLeft, + pressArrowUp, + pressEnter, + pressForwardDelete, + pressShiftEnter, + readClipboardText, + redoByClick, + resetHistory, + setInlineRangeInSelectedRichText, + SHORT_KEY, + type, + undoByClick, + undoByKeyboard, + waitNextFrame, +} from '../utils/actions/index.js'; +import { + assertBlockSelections, + assertRichTextInlineRange, + assertRichTexts, +} from '../utils/asserts.js'; +import { test } from '../utils/playwright.js'; + +test('should multiple line format hotkey work', async ({ page }, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + // 0 1 2 + // 1|23 456 78|9 + await dragBetweenIndices(page, [0, 1], [2, 2]); + + // bold + await page.keyboard.press(`${SHORT_KEY}+b`); + // italic + await page.keyboard.press(`${SHORT_KEY}+i`); + // underline + await page.keyboard.press(`${SHORT_KEY}+u`); + // strikethrough + await page.keyboard.press(`${SHORT_KEY}+Shift+S`); + + await waitNextFrame(page); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_init.json` + ); + + // bold + await page.keyboard.press(`${SHORT_KEY}+b`, { delay: 50 }); + // italic + await page.keyboard.press(`${SHORT_KEY}+i`, { delay: 50 }); + // underline + await page.keyboard.press(`${SHORT_KEY}+u`, { delay: 50 }); + // strikethrough + await page.keyboard.press(`${SHORT_KEY}+Shift+s`, { delay: 50 }); + + await waitNextFrame(page); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_finial.json` + ); +}); + +test('multi line rich-text inline code hotkey', async ({ page }, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + // 0 1 2 + // 1|23 456 78|9 + await dragBetweenIndices(page, [0, 1], [2, 2]); + await inlineCode(page); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_init.json` + ); + + await undoByClick(page); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_undo.json` + ); + + await redoByClick(page); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_redo.json` + ); +}); + +test('should cut work multiple line', async ({ page }, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await resetHistory(page); + // 0 1 2 + // 1|23 456 78|9 + await dragBetweenIndices(page, [0, 1], [2, 2]); + // cut + await page.keyboard.press(`${SHORT_KEY}+x`); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_init.json` + ); + await undoByKeyboard(page); + const text = await readClipboardText(page); + expect(text).toBe(`23 456 78`); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_undo.json` + ); +}); + +test('arrow up and down behavior on multiline text blocks when previous is non-text', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + await pressEnter(page); + await pressArrowUp(page); + await type(page, '--- '); + await pressEnter(page); + + await focusRichText(page); + await type(page, '124'); + await pressShiftEnter(page); + await type(page, '1234'); + + await pressArrowUp(page); + await waitNextFrame(page, 100); + await assertRichTextInlineRange(page, 0, 3); + + await pressArrowUp(page); + await assertBlockSelections(page, ['4']); +}); + +test('should forwardDelete works when delete multi characters', async ({ + page, +}) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/toeverything/blocksuite/issues/3122', + }); + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page, 0); + await type(page, 'hello'); + await pressArrowLeft(page, 5); + await setInlineRangeInSelectedRichText(page, 1, 3); + await pressForwardDelete(page); + await assertRichTexts(page, ['ho']); +}); + +test('should drag multiple block and input text works', async ({ page }) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/toeverything/blocksuite/issues/2982', + }); + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await dragBetweenIndices(page, [0, 1], [2, 1]); + await type(page, 'ab'); + await assertRichTexts(page, ['1ab89']); + await undoByKeyboard(page); + await assertRichTexts(page, ['123', '456', '789']); +}); diff --git a/blocksuite/tests-legacy/hotkey/title.spec.ts b/blocksuite/tests-legacy/hotkey/title.spec.ts new file mode 100644 index 0000000000000..4958a107c4b22 --- /dev/null +++ b/blocksuite/tests-legacy/hotkey/title.spec.ts @@ -0,0 +1,43 @@ +import { + cutByKeyboard, + dragOverTitle, + enterPlaygroundRoom, + focusRichText, + focusTitle, + initEmptyParagraphState, + pasteByKeyboard, + pressEnter, + type, +} from '../utils/actions/index.js'; +import { assertRichTexts, assertTitle } from '../utils/asserts.js'; +import { test } from '../utils/playwright.js'; + +test('should cut in title works', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + + await focusTitle(page); + await type(page, 'hello'); + await assertTitle(page, 'hello'); + + await dragOverTitle(page); + await cutByKeyboard(page); + await assertTitle(page, ''); + + await focusRichText(page); + await pasteByKeyboard(page); + await assertRichTexts(page, ['hello']); +}); + +test('enter in title should move cursor in new paragraph block', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusTitle(page); + await type(page, 'hello'); + await assertTitle(page, 'hello'); + await pressEnter(page); + await type(page, 'world'); + await assertRichTexts(page, ['world', '']); +}); diff --git a/blocksuite/tests-legacy/image/image.spec.ts b/blocksuite/tests-legacy/image/image.spec.ts new file mode 100644 index 0000000000000..de51aaac51aea --- /dev/null +++ b/blocksuite/tests-legacy/image/image.spec.ts @@ -0,0 +1,154 @@ +import '../utils/declare-test-window.js'; + +import type { Page } from '@playwright/test'; +import { expect } from '@playwright/test'; + +import { + activeEmbed, + copyByKeyboard, + dragEmbedResizeByTopLeft, + dragEmbedResizeByTopRight, + enterPlaygroundRoom, + initImageState, + moveToImage, + pasteByKeyboard, + pressArrowLeft, + pressEnter, + redoByClick, + redoByKeyboard, + type, + undoByKeyboard, + waitNextFrame, +} from '../utils/actions/index.js'; +import { + assertImageOption, + assertImageSize, + assertRichDragButton, + assertRichImage, + assertRichTextInlineRange, + assertRichTexts, +} from '../utils/asserts.js'; +import { test } from '../utils/playwright.js'; + +async function focusCaption(page: Page) { + await page.click( + '.affine-image-toolbar-container .image-toolbar-button.caption' + ); +} + +test('can drag resize image by left menu', async ({ page }) => { + await enterPlaygroundRoom(page); + await initImageState(page); + await assertRichImage(page, 1); + + await activeEmbed(page); + await assertRichDragButton(page); + await assertImageSize(page, { width: 752, height: 564 }); + + await dragEmbedResizeByTopLeft(page); + await waitNextFrame(page); + await assertImageSize(page, { width: 358, height: 268 }); + + await undoByKeyboard(page); + await waitNextFrame(page); + await assertImageSize(page, { width: 752, height: 564 }); + + await redoByKeyboard(page); + await waitNextFrame(page); + await assertImageSize(page, { width: 358, height: 268 }); +}); + +test('can drag resize image by right menu', async ({ page }) => { + await enterPlaygroundRoom(page); + await initImageState(page); + await assertRichImage(page, 1); + + await activeEmbed(page); + await assertRichDragButton(page); + await assertImageSize(page, { width: 752, height: 564 }); + + await dragEmbedResizeByTopRight(page); + await assertImageSize(page, { width: 338, height: 253 }); + + await undoByKeyboard(page); + await assertImageSize(page, { width: 752, height: 564 }); + + await redoByKeyboard(page); + await assertImageSize(page, { width: 338, height: 253 }); +}); + +test('can click and delete image', async ({ page }) => { + await enterPlaygroundRoom(page); + await initImageState(page); + await assertRichImage(page, 1); + + await activeEmbed(page); + await page.keyboard.press('Backspace'); + await assertRichImage(page, 0); + + await undoByKeyboard(page); + await assertRichImage(page, 1); + + await redoByClick(page); + await assertRichImage(page, 0); +}); + +test('can click and copy image', async ({ page }) => { + await enterPlaygroundRoom(page); + await initImageState(page); + await assertRichImage(page, 1); + + await activeEmbed(page); + await copyByKeyboard(page); + await pressEnter(page); + await waitNextFrame(page); + + await pasteByKeyboard(page); + await waitNextFrame(page, 200); + await assertRichImage(page, 2); +}); + +test('enter shortcut on focusing embed block and its caption', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initImageState(page); + await assertRichImage(page, 1); + + await moveToImage(page); + await assertImageOption(page); + + const caption = page.locator('affine-image block-caption-editor textarea'); + await focusCaption(page); + await type(page, '123'); + + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/toeverything/blocksuite/issues/2495', + }); + + // blur + await page.mouse.click(0, 500); + await caption.click({ position: { x: 0, y: 0 } }); + await type(page, 'abc'); + await expect(caption).toHaveValue('abc123'); +}); + +test('should support the enter key of image caption', async ({ page }) => { + await enterPlaygroundRoom(page); + await initImageState(page); + await assertRichImage(page, 1); + + await moveToImage(page); + await assertImageOption(page); + + const caption = page.locator('affine-image block-caption-editor textarea'); + await focusCaption(page); + await type(page, 'abc123'); + await pressArrowLeft(page, 3); + await pressEnter(page); + await expect(caption).toHaveValue('abc'); + + await assertRichTexts(page, ['123']); + await assertRichTextInlineRange(page, 0, 0, 0); +}); diff --git a/blocksuite/tests-legacy/image/keymap.spec.ts b/blocksuite/tests-legacy/image/keymap.spec.ts new file mode 100644 index 0000000000000..19af869e154f7 --- /dev/null +++ b/blocksuite/tests-legacy/image/keymap.spec.ts @@ -0,0 +1,79 @@ +import { expect } from '@playwright/test'; + +import { + activeEmbed, + enterPlaygroundRoom, + initImageState, + pressArrowDown, + pressArrowUp, + pressBackspace, + pressEnter, + type, +} from '../utils/actions/index.js'; +import { + assertBlockCount, + assertBlockSelections, + assertRichImage, + assertRichTextInlineRange, + assertRichTexts, +} from '../utils/asserts.js'; +import { test } from '../utils/playwright.js'; + +test.beforeEach(async ({ page }) => { + await enterPlaygroundRoom(page); + await initImageState(page, true); + await assertRichImage(page, 1); +}); + +test('press enter will create new block when click and select image', async ({ + page, +}) => { + await activeEmbed(page); + await pressEnter(page); + await type(page, 'aa'); + await assertRichTexts(page, ['', 'aa']); +}); + +test('press backspace after image block can select image block', async ({ + page, +}) => { + await activeEmbed(page); + await pressEnter(page); + await assertRichTextInlineRange(page, 1, 0); + await assertBlockCount(page, 'paragraph', 2); + await pressBackspace(page); + await assertBlockSelections(page, ['3']); + await assertBlockCount(page, 'paragraph', 1); +}); + +test('press enter when image is selected should move next paragraph and should placeholder', async ({ + page, +}) => { + await activeEmbed(page); + await pressEnter(page); + + const placeholder = page.locator('.affine-paragraph-placeholder.visible'); + await expect(placeholder).toBeVisible(); +}); + +test('press arrow up when image is selected should move to previous paragraph', async ({ + page, +}) => { + await activeEmbed(page); + await pressArrowUp(page); + await assertRichTextInlineRange(page, 0, 0); + await type(page, 'aa'); + await assertRichTexts(page, ['aa']); +}); + +test('press arrow down when image is selected should move to previous paragraph', async ({ + page, +}) => { + await activeEmbed(page); + await pressEnter(page); + await type(page, 'aa'); + await activeEmbed(page); + await pressArrowDown(page); + await type(page, 'bb'); + await assertRichTexts(page, ['', 'bbaa']); +}); diff --git a/blocksuite/tests-legacy/image/load.spec.ts b/blocksuite/tests-legacy/image/load.spec.ts new file mode 100644 index 0000000000000..b67ad44599a16 --- /dev/null +++ b/blocksuite/tests-legacy/image/load.spec.ts @@ -0,0 +1,168 @@ +import { readFile } from 'node:fs/promises'; +import { fileURLToPath } from 'node:url'; + +import type { Page } from '@playwright/test'; +import { expect } from '@playwright/test'; + +import { + enterPlaygroundRoom, + expectConsoleMessage, +} from '../utils/actions/index.js'; +import { test } from '../utils/playwright.js'; + +const mockImageId = '_e2e_test_image_id_'; + +async function initMockImage(page: Page) { + await page.evaluate(() => { + const { doc } = window; + doc.captureSync(); + const rootId = doc.addBlock('affine:page'); + const noteId = doc.addBlock('affine:note', {}, rootId); + doc.addBlock( + 'affine:image', + { + sourceId: '_e2e_test_image_id_', + width: 200, + height: 180, + }, + noteId + ); + doc.captureSync(); + }); +} + +test('image loading but failed', async ({ page }) => { + expectConsoleMessage( + page, + 'Error: Failed to fetch blob _e2e_test_image_id_', + 'warning' + ); + expectConsoleMessage( + page, + 'Failed to load resource: the server responded with a status of 404 (Not Found)' + ); + expectConsoleMessage( + page, + 'Error: Image blob is missing!, retrying', + 'warning' + ); + + const room = await enterPlaygroundRoom(page, { blobSource: ['mock'] }); + const timeout = 2000; + + // block image data request, force wait 100ms for loading test, + // always return 404 + await page.route( + `**/api/collection/${room}/blob/${mockImageId}`, + async route => { + await page.waitForTimeout(timeout); + // broken image + return route.fulfill({ + status: 404, + }); + } + ); + + await initMockImage(page); + + const loadingContent = await page + .locator( + '.affine-image-fallback-card .affine-image-fallback-card-title-text' + ) + .innerText(); + expect(loadingContent).toBe('Loading image...'); + + await page.waitForTimeout(3 * timeout); + + await expect( + page.locator( + '.affine-image-fallback-card .affine-image-fallback-card-title-text' + ) + ).toContainText('Image loading failed.'); +}); + +test('image loading but success', async ({ page }) => { + expectConsoleMessage( + page, + 'Error: Failed to fetch blob _e2e_test_image_id_', + 'warning' + ); + expectConsoleMessage( + page, + 'Failed to load resource: the server responded with a status of 404 (Not Found)' + ); + expectConsoleMessage( + page, + 'Error: Image blob is missing!, retrying', + 'warning' + ); + + const room = await enterPlaygroundRoom(page, { blobSource: ['mock'] }); + const imageBuffer = await readFile( + fileURLToPath(new URL('../fixtures/smile.png', import.meta.url)) + ); + + const timeout = 2000; + let count = 0; + + // block image data request, force wait 100ms for loading test, + // always return 404 + await page.route( + `**/api/collection/${room}/blob/${mockImageId}`, + async route => { + await page.waitForTimeout(timeout); + count++; + if (count === 3) { + return route.fulfill({ + status: 200, + body: imageBuffer, + }); + } + // broken image + return route.fulfill({ + status: 404, + }); + } + ); + + await initMockImage(page); + + const loadingContent = await page + .locator( + '.affine-image-fallback-card .affine-image-fallback-card-title-text' + ) + .innerText(); + expect(loadingContent).toBe('Loading image...'); + + await page.waitForTimeout(3 * timeout); + + const img = page.locator('.affine-image-container img'); + await expect(img).toBeVisible(); + const src = await img.getAttribute('src'); + expect(src).toBeDefined(); +}); + +test('image loaded successfully', async ({ page }) => { + const room = await enterPlaygroundRoom(page, { blobSource: ['mock'] }); + const imageBuffer = await readFile( + fileURLToPath(new URL('../fixtures/smile.png', import.meta.url)) + ); + await page.route( + `**/api/collection/${room}/blob/${mockImageId}`, + async route => { + return route.fulfill({ + status: 200, + body: imageBuffer, + }); + } + ); + + await initMockImage(page); + + await page.waitForTimeout(1000); + + const img = page.locator('.affine-image-container img'); + await expect(img).toBeVisible(); + const src = await img.getAttribute('src'); + expect(src).toBeDefined(); +}); diff --git a/blocksuite/tests-legacy/image/menu.spec.ts b/blocksuite/tests-legacy/image/menu.spec.ts new file mode 100644 index 0000000000000..e62cf9632ccb3 --- /dev/null +++ b/blocksuite/tests-legacy/image/menu.spec.ts @@ -0,0 +1,90 @@ +import { expect } from '@playwright/test'; + +import { + activeEmbed, + dragBetweenCoords, + enterPlaygroundRoom, + initImageState, + insertThreeLevelLists, + pressEnter, + scrollToTop, +} from '../utils/actions/index.js'; +import { assertRichImage } from '../utils/asserts.js'; +import { test } from '../utils/playwright.js'; + +// FIXME(@fundon): This behavior is not meeting the design spec +test.skip('popup menu should follow position of image when scrolling', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initImageState(page); + await activeEmbed(page); + await pressEnter(page); + await insertThreeLevelLists(page, 0); + await pressEnter(page); + await insertThreeLevelLists(page, 3); + await pressEnter(page); + await insertThreeLevelLists(page, 6); + await pressEnter(page); + await insertThreeLevelLists(page, 9); + await pressEnter(page); + await insertThreeLevelLists(page, 12); + + await scrollToTop(page); + + const rect = await page.locator('.affine-image-container img').boundingBox(); + if (!rect) throw new Error('image not found'); + + await page.mouse.move(rect.x + rect.width / 2, rect.y + rect.height / 2); + + await page.waitForTimeout(150); + + const menu = page.locator('.affine-image-toolbar-container'); + + await expect(menu).toBeVisible(); + + await page.evaluate( + ([rect]) => { + const viewport = document.querySelector('.affine-page-viewport'); + if (!viewport) { + throw new Error(); + } + // const distance = viewport.scrollHeight - viewport.clientHeight; + viewport.scrollTo(0, (rect.height + rect.y) / 2); + }, + [rect] + ); + + await page.waitForTimeout(150); + const image = page.locator('.affine-image-container img'); + const imageRect = await image.boundingBox(); + const menuRect = await menu.boundingBox(); + if (!imageRect) throw new Error('image not found'); + if (!menuRect) throw new Error('menu not found'); + expect(imageRect.y).toBeCloseTo((rect.y - rect.height) / 2, 172); + expect(menuRect.y).toBeCloseTo(65, -0.325); +}); + +test('select image should not show format bar', async ({ page }) => { + await enterPlaygroundRoom(page); + await initImageState(page); + await assertRichImage(page, 1); + + const image = page.locator('affine-image'); + const rect = await image.boundingBox(); + if (!rect) { + throw new Error('image not found'); + } + await dragBetweenCoords( + page, + { x: rect.x - 20, y: rect.y + 20 }, + { x: rect.x + 20, y: rect.y + 40 } + ); + const rects = page.locator('affine-block-selection').locator('visible=true'); + await expect(rects).toHaveCount(1); + const formatQuickBar = page.locator(`.format-quick-bar`); + await expect(formatQuickBar).not.toBeVisible(); + await page.mouse.wheel(0, rect.y + rect.height); + await expect(formatQuickBar).not.toBeVisible(); + await page.mouse.click(0, 0); +}); diff --git a/blocksuite/tests-legacy/inline/inline-editor.spec.ts b/blocksuite/tests-legacy/inline/inline-editor.spec.ts new file mode 100644 index 0000000000000..a1eaa110729d5 --- /dev/null +++ b/blocksuite/tests-legacy/inline/inline-editor.spec.ts @@ -0,0 +1,1013 @@ +import { + assertSelection, + enterInlineEditorPlayground, + focusInlineRichText, + getDeltaFromInlineRichText, + getInlineRangeIndexRect, + getInlineRichTextLine, + press, + setInlineRichTextRange, + type, +} from '@inline/__tests__/utils.js'; +import { ZERO_WIDTH_SPACE } from '@inline/consts.js'; +import type { InlineEditor } from '@inline/index.js'; +import { expect, test } from '@playwright/test'; + +test('basic input', async ({ page }) => { + await enterInlineEditorPlayground(page); + await focusInlineRichText(page); + + const editorA = page.locator('[data-v-root="true"]').nth(0); + const editorB = page.locator('[data-v-root="true"]').nth(1); + + const editorAUndo = page.getByText('undo').nth(0); + const editorARedo = page.getByText('redo').nth(0); + + expect(await editorA.innerText()).toBe(ZERO_WIDTH_SPACE); + expect(await editorB.innerText()).toBe(ZERO_WIDTH_SPACE); + + await page.waitForTimeout(100); + + await type(page, 'abcd😃efg👨‍👨‍👧‍👦hj'); + + expect(await editorA.innerText()).toBe('abcd😃efg👨‍👨‍👧‍👦hj'); + expect(await editorB.innerText()).toBe('abcd😃efg👨‍👨‍👧‍👦hj'); + + await editorAUndo.click(); + + expect(await editorA.innerText()).toBe(ZERO_WIDTH_SPACE); + expect(await editorB.innerText()).toBe(ZERO_WIDTH_SPACE); + + await editorARedo.click(); + + expect(await editorA.innerText()).toBe('abcd😃efg👨‍👨‍👧‍👦hj'); + expect(await editorB.innerText()).toBe('abcd😃efg👨‍👨‍👧‍👦hj'); + + await focusInlineRichText(page); + await press(page, 'Backspace'); + await press(page, 'Backspace'); + await press(page, 'Backspace'); + + expect(await editorA.innerText()).toBe('abcd😃efg'); + expect(await editorB.innerText()).toBe('abcd😃efg'); + + await editorAUndo.click(); + + expect(await editorA.innerText()).toBe('abcd😃efg👨‍👨‍👧‍👦hj'); + expect(await editorB.innerText()).toBe('abcd😃efg👨‍👨‍👧‍👦hj'); + + await editorARedo.click(); + + expect(await editorA.innerText()).toBe('abcd😃efg'); + expect(await editorB.innerText()).toBe('abcd😃efg'); + + await focusInlineRichText(page); + await press(page, 'ArrowLeft'); + await press(page, 'ArrowLeft'); + await press(page, 'ArrowLeft'); + await press(page, 'ArrowLeft'); + await press(page, 'ArrowLeft'); + await press(page, 'Delete'); + await press(page, 'Delete'); + + await type(page, '🥰👨‍👨‍👧‍👦'); + expect(await editorA.innerText()).toBe('abc🥰👨‍👨‍👧‍👦efg'); + expect(await editorB.innerText()).toBe('abc🥰👨‍👨‍👧‍👦efg'); + + await setInlineRichTextRange(page, { + index: 3, + length: 16, + }); + await page.waitForTimeout(100); + await press(page, 'Delete'); + + expect(await editorA.innerText()).toBe('abc'); + expect(await editorA.innerText()).toBe('abc'); + + await editorAUndo.click(); + + expect(await editorA.innerText()).toBe('abcd😃efg'); + expect(await editorB.innerText()).toBe('abcd😃efg'); + + await editorARedo.click(); + + expect(await editorA.innerText()).toBe('abc'); + expect(await editorB.innerText()).toBe('abc'); + + await focusInlineRichText(page); + await page.waitForTimeout(100); + await press(page, 'Enter'); + await press(page, 'Enter'); + await type(page, 'bbb'); + + await page.waitForTimeout(100); + + expect(await editorA.innerText()).toBe('abc\n' + ZERO_WIDTH_SPACE + '\nbbb'); + expect(await editorB.innerText()).toBe('abc\n' + ZERO_WIDTH_SPACE + '\nbbb'); + + await editorAUndo.click(); + + expect(await editorA.innerText()).toBe('abc'); + expect(await editorB.innerText()).toBe('abc'); + + await editorARedo.click(); + + expect(await editorA.innerText()).toBe('abc\n' + ZERO_WIDTH_SPACE + '\nbbb'); + expect(await editorB.innerText()).toBe('abc\n' + ZERO_WIDTH_SPACE + '\nbbb'); + + await focusInlineRichText(page); + await page.waitForTimeout(100); + await press(page, 'Backspace'); + await press(page, 'Backspace'); + await press(page, 'Backspace'); + await press(page, 'Backspace'); + await press(page, 'Backspace'); + + expect(await editorA.innerText()).toBe('abc'); + expect(await editorB.innerText()).toBe('abc'); + + await editorAUndo.click(); + + expect(await editorA.innerText()).toBe('abc\n' + ZERO_WIDTH_SPACE + '\nbbb'); + expect(await editorB.innerText()).toBe('abc\n' + ZERO_WIDTH_SPACE + '\nbbb'); + + await editorARedo.click(); + + expect(await editorA.innerText()).toBe('abc'); + + await focusInlineRichText(page); + await page.waitForTimeout(100); + await press(page, 'ArrowLeft'); + await press(page, 'ArrowLeft'); + await type(page, 'bb'); + await press(page, 'ArrowRight'); + await press(page, 'ArrowRight'); + await type(page, 'dd'); + + expect(await editorA.innerText()).toBe('abbbcdd'); + expect(await editorB.innerText()).toBe('abbbcdd'); + + await editorAUndo.click(); + + expect(await editorA.innerText()).toBe('abc'); + + await editorARedo.click(); + + expect(await editorA.innerText()).toBe('abbbcdd'); + expect(await editorB.innerText()).toBe('abbbcdd'); + + await focusInlineRichText(page); + await page.waitForTimeout(100); + await press(page, 'ArrowLeft'); + await press(page, 'ArrowLeft'); + await press(page, 'Enter'); + await press(page, 'Enter'); + + expect(await editorA.innerText()).toBe('abbbc\n' + ZERO_WIDTH_SPACE + '\ndd'); + expect(await editorB.innerText()).toBe('abbbc\n' + ZERO_WIDTH_SPACE + '\ndd'); + + await editorAUndo.click(); + + expect(await editorA.innerText()).toBe('abbbcdd'); + expect(await editorB.innerText()).toBe('abbbcdd'); + + await editorARedo.click(); + + expect(await editorA.innerText()).toBe('abbbc\n' + ZERO_WIDTH_SPACE + '\ndd'); + expect(await editorB.innerText()).toBe('abbbc\n' + ZERO_WIDTH_SPACE + '\ndd'); +}); + +test('chinese input', async ({ page }) => { + await enterInlineEditorPlayground(page); + await focusInlineRichText(page); + + const editorA = page.locator('[data-v-root="true"]').nth(0); + const editorB = page.locator('[data-v-root="true"]').nth(1); + + expect(await editorA.innerText()).toBe(ZERO_WIDTH_SPACE); + expect(await editorB.innerText()).toBe(ZERO_WIDTH_SPACE); + + await page.waitForTimeout(100); + const client = await page.context().newCDPSession(page); + await client.send('Input.imeSetComposition', { + selectionStart: 0, + selectionEnd: 0, + text: 'n', + }); + await client.send('Input.imeSetComposition', { + selectionStart: 0, + selectionEnd: 1, + text: 'ni', + }); + await client.send('Input.insertText', { + text: '你', + }); + expect(await editorA.innerText()).toBe('你'); + expect(await editorB.innerText()).toBe('你'); +}); + +test('type many times in one moment', async ({ page }) => { + await enterInlineEditorPlayground(page); + await focusInlineRichText(page); + await page.waitForTimeout(100); + await Promise.all( + 'aaaaaaaaaaaaaaaaaaaa'.split('').map(s => page.keyboard.type(s)) + ); + const preOffset = await page.evaluate(() => { + return getSelection()?.getRangeAt(0).endOffset; + }); + await page.keyboard.press('ArrowRight'); + await page.keyboard.press('ArrowRight'); + await page.keyboard.press('ArrowRight'); + const offset = await page.evaluate(() => { + return getSelection()?.getRangeAt(0).endOffset; + }); + expect(preOffset).toBe(offset); +}); + +test('readonly mode', async ({ page }) => { + await enterInlineEditorPlayground(page); + await focusInlineRichText(page); + + const editorA = page.locator('[data-v-root="true"]').nth(0); + const editorB = page.locator('[data-v-root="true"]').nth(1); + + expect(await editorA.innerText()).toBe(ZERO_WIDTH_SPACE); + expect(await editorB.innerText()).toBe(ZERO_WIDTH_SPACE); + + await page.waitForTimeout(100); + + await type(page, 'abcdefg'); + + expect(await editorA.innerText()).toBe('abcdefg'); + expect(await editorB.innerText()).toBe('abcdefg'); + + await page.evaluate(() => { + const richTextA = document + .querySelector('test-page') + ?.querySelector('test-rich-text'); + + if (!richTextA) { + throw new Error('Cannot find editor'); + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (richTextA as any).inlineEditor.setReadonly(true); + }); + + await type(page, 'aaaa'); + + expect(await editorA.innerText()).toBe('abcdefg'); + expect(await editorB.innerText()).toBe('abcdefg'); +}); + +test('basic styles', async ({ page }) => { + await enterInlineEditorPlayground(page); + await focusInlineRichText(page); + + const editorA = page.locator('[data-v-root="true"]').nth(0); + const editorB = page.locator('[data-v-root="true"]').nth(1); + + const editorABold = page.getByText('bold').nth(0); + const editorAItalic = page.getByText('italic').nth(0); + const editorAUnderline = page.getByText('underline').nth(0); + const editorAStrike = page.getByText('strike').nth(0); + const editorACode = page.getByText('code').nth(0); + + const editorAUndo = page.getByText('undo').nth(0); + const editorARedo = page.getByText('redo').nth(0); + + expect(await editorA.innerText()).toBe(ZERO_WIDTH_SPACE); + expect(await editorB.innerText()).toBe(ZERO_WIDTH_SPACE); + + await page.waitForTimeout(100); + + await type(page, 'abcdefg'); + + expect(await editorA.innerText()).toBe('abcdefg'); + expect(await editorB.innerText()).toBe('abcdefg'); + + let delta = await getDeltaFromInlineRichText(page); + expect(delta).toEqual([ + { + insert: 'abcdefg', + }, + ]); + + await setInlineRichTextRange(page, { index: 2, length: 3 }); + + await editorABold.click(); + await page.waitForTimeout(100); + delta = await getDeltaFromInlineRichText(page); + expect(delta).toEqual([ + { + insert: 'ab', + }, + { + insert: 'cde', + attributes: { + bold: true, + }, + }, + { + insert: 'fg', + }, + ]); + + await editorAItalic.click(); + await page.waitForTimeout(100); + delta = await getDeltaFromInlineRichText(page); + expect(delta).toEqual([ + { + insert: 'ab', + }, + { + insert: 'cde', + attributes: { + bold: true, + italic: true, + }, + }, + { + insert: 'fg', + }, + ]); + + await editorAUnderline.click(); + await page.waitForTimeout(100); + delta = await getDeltaFromInlineRichText(page); + expect(delta).toEqual([ + { + insert: 'ab', + }, + { + insert: 'cde', + attributes: { + bold: true, + italic: true, + underline: true, + }, + }, + { + insert: 'fg', + }, + ]); + + await editorAStrike.click(); + await page.waitForTimeout(100); + delta = await getDeltaFromInlineRichText(page); + expect(delta).toEqual([ + { + insert: 'ab', + }, + { + insert: 'cde', + attributes: { + bold: true, + italic: true, + underline: true, + strike: true, + }, + }, + { + insert: 'fg', + }, + ]); + + await editorACode.click(); + await page.waitForTimeout(100); + delta = await getDeltaFromInlineRichText(page); + expect(delta).toEqual([ + { + insert: 'ab', + }, + { + insert: 'cde', + attributes: { + bold: true, + italic: true, + underline: true, + strike: true, + code: true, + }, + }, + { + insert: 'fg', + }, + ]); + + await editorAUndo.click({ + clickCount: 5, + }); + await page.waitForTimeout(100); + delta = await getDeltaFromInlineRichText(page); + expect(delta).toEqual([ + { + insert: 'abcdefg', + }, + ]); + + await editorARedo.click({ + clickCount: 5, + }); + await page.waitForTimeout(100); + delta = await getDeltaFromInlineRichText(page); + expect(delta).toEqual([ + { + insert: 'ab', + }, + { + insert: 'cde', + attributes: { + bold: true, + italic: true, + underline: true, + strike: true, + code: true, + }, + }, + { + insert: 'fg', + }, + ]); + + await editorABold.click(); + await page.waitForTimeout(100); + delta = await getDeltaFromInlineRichText(page); + expect(delta).toEqual([ + { + insert: 'ab', + }, + { + insert: 'cde', + attributes: { + italic: true, + underline: true, + strike: true, + code: true, + }, + }, + { + insert: 'fg', + }, + ]); + + await editorAItalic.click(); + await page.waitForTimeout(100); + delta = await getDeltaFromInlineRichText(page); + expect(delta).toEqual([ + { + insert: 'ab', + }, + { + insert: 'cde', + attributes: { + underline: true, + strike: true, + code: true, + }, + }, + { + insert: 'fg', + }, + ]); + + await editorAUnderline.click(); + await page.waitForTimeout(100); + delta = await getDeltaFromInlineRichText(page); + expect(delta).toEqual([ + { + insert: 'ab', + }, + { + insert: 'cde', + attributes: { + strike: true, + code: true, + }, + }, + { + insert: 'fg', + }, + ]); + + await editorAStrike.click(); + await page.waitForTimeout(100); + delta = await getDeltaFromInlineRichText(page); + expect(delta).toEqual([ + { + insert: 'ab', + }, + { + insert: 'cde', + attributes: { + code: true, + }, + }, + { + insert: 'fg', + }, + ]); + + await editorACode.click(); + await page.waitForTimeout(100); + delta = await getDeltaFromInlineRichText(page); + expect(delta).toEqual([ + { + insert: 'abcdefg', + }, + ]); +}); + +test('overlapping styles', async ({ page }) => { + await enterInlineEditorPlayground(page); + await focusInlineRichText(page); + + const editorA = page.locator('[data-v-root="true"]').nth(0); + const editorB = page.locator('[data-v-root="true"]').nth(1); + + const editorABold = page.getByText('bold').nth(0); + const editorAItalic = page.getByText('italic').nth(0); + + const editorAUndo = page.getByText('undo').nth(0); + const editorARedo = page.getByText('redo').nth(0); + + expect(await editorA.innerText()).toBe(ZERO_WIDTH_SPACE); + expect(await editorB.innerText()).toBe(ZERO_WIDTH_SPACE); + + await page.waitForTimeout(100); + + await type(page, 'abcdefghijk'); + + expect(await editorA.innerText()).toBe('abcdefghijk'); + expect(await editorB.innerText()).toBe('abcdefghijk'); + + let delta = await getDeltaFromInlineRichText(page); + expect(delta).toEqual([ + { + insert: 'abcdefghijk', + }, + ]); + + await setInlineRichTextRange(page, { index: 1, length: 3 }); + await editorABold.click(); + + delta = await getDeltaFromInlineRichText(page); + expect(delta).toEqual([ + { + insert: 'a', + }, + { + insert: 'bcd', + attributes: { + bold: true, + }, + }, + { + insert: 'efghijk', + }, + ]); + + await setInlineRichTextRange(page, { index: 7, length: 3 }); + await editorABold.click(); + + delta = await getDeltaFromInlineRichText(page); + expect(delta).toEqual([ + { + insert: 'a', + }, + { + insert: 'bcd', + attributes: { + bold: true, + }, + }, + { + insert: 'efg', + }, + { + insert: 'hij', + attributes: { + bold: true, + }, + }, + { + insert: 'k', + }, + ]); + + await setInlineRichTextRange(page, { index: 3, length: 5 }); + await editorAItalic.click(); + + delta = await getDeltaFromInlineRichText(page); + expect(delta).toEqual([ + { + insert: 'a', + }, + { + insert: 'bc', + attributes: { + bold: true, + }, + }, + { + insert: 'd', + attributes: { + bold: true, + italic: true, + }, + }, + { + insert: 'efg', + attributes: { + italic: true, + }, + }, + { + insert: 'h', + attributes: { + bold: true, + italic: true, + }, + }, + { + insert: 'ij', + attributes: { + bold: true, + }, + }, + { + insert: 'k', + }, + ]); + + await editorAUndo.click({ + clickCount: 3, + }); + delta = await getDeltaFromInlineRichText(page); + expect(delta).toEqual([ + { + insert: 'abcdefghijk', + }, + ]); + + await editorARedo.click({ + clickCount: 3, + }); + delta = await getDeltaFromInlineRichText(page); + expect(delta).toEqual([ + { + insert: 'a', + }, + { + insert: 'bc', + attributes: { + bold: true, + }, + }, + { + insert: 'd', + attributes: { + bold: true, + italic: true, + }, + }, + { + insert: 'efg', + attributes: { + italic: true, + }, + }, + { + insert: 'h', + attributes: { + bold: true, + italic: true, + }, + }, + { + insert: 'ij', + attributes: { + bold: true, + }, + }, + { + insert: 'k', + }, + ]); +}); + +test('input continuous spaces', async ({ page }) => { + await enterInlineEditorPlayground(page); + await focusInlineRichText(page); + + const editorA = page.locator('[data-v-root="true"]').nth(0); + const editorB = page.locator('[data-v-root="true"]').nth(1); + + expect(await editorA.innerText()).toBe(ZERO_WIDTH_SPACE); + expect(await editorB.innerText()).toBe(ZERO_WIDTH_SPACE); + + await page.waitForTimeout(100); + + await type(page, 'abc def'); + + expect(await editorA.innerText()).toBe('abc def'); + expect(await editorB.innerText()).toBe('abc def'); + + await focusInlineRichText(page); + await page.waitForTimeout(100); + await press(page, 'ArrowLeft'); + await press(page, 'ArrowLeft'); + await press(page, 'ArrowLeft'); + await press(page, 'ArrowLeft'); + + await press(page, 'Enter'); + + expect(await editorA.innerText()).toBe('abc \n' + ' def'); + expect(await editorB.innerText()).toBe('abc \n' + ' def'); +}); + +test('select from the start of line using shift+arrow', async ({ page }) => { + await enterInlineEditorPlayground(page); + await focusInlineRichText(page); + + const editorA = page.locator('[data-v-root="true"]').nth(0); + const editorB = page.locator('[data-v-root="true"]').nth(1); + + expect(await editorA.innerText()).toBe(ZERO_WIDTH_SPACE); + expect(await editorB.innerText()).toBe(ZERO_WIDTH_SPACE); + + await page.waitForTimeout(100); + + await type(page, 'abc'); + await press(page, 'Enter'); + await type(page, 'def'); + await press(page, 'Enter'); + await type(page, 'ghi'); + + expect(await editorA.innerText()).toBe('abc\ndef\nghi'); + expect(await editorB.innerText()).toBe('abc\ndef\nghi'); + + /** + * abc + * def + * |ghi + */ + await press(page, 'ArrowLeft'); + await press(page, 'ArrowLeft'); + await press(page, 'ArrowLeft'); + await assertSelection(page, 0, 8); + + /** + * |abc + * def + * |ghi + */ + await page.keyboard.down('Shift'); + await press(page, 'ArrowUp'); + await press(page, 'ArrowUp'); + await assertSelection(page, 0, 0, 8); + + /** + * a|bc + * def + * |ghi + */ + await press(page, 'ArrowRight'); + await assertSelection(page, 0, 1, 7); + await press(page, 'Backspace'); + await page.waitForTimeout(100); + + expect(await editorA.innerText()).toBe('aghi'); + expect(await editorB.innerText()).toBe('aghi'); +}); + +test('getLine', async ({ page }) => { + await enterInlineEditorPlayground(page); + await focusInlineRichText(page); + + const editorA = page.locator('[data-v-root="true"]').nth(0); + const editorB = page.locator('[data-v-root="true"]').nth(1); + + expect(await editorA.innerText()).toBe(ZERO_WIDTH_SPACE); + expect(await editorB.innerText()).toBe(ZERO_WIDTH_SPACE); + + await page.waitForTimeout(100); + + await type(page, 'abc\ndef\nghi'); + + expect(await editorA.innerText()).toBe('abc\ndef\nghi'); + expect(await editorB.innerText()).toBe('abc\ndef\nghi'); + + const [line1, offset1] = await getInlineRichTextLine(page, 0); + const [line2, offset2] = await getInlineRichTextLine(page, 1); + const [line3, offset3] = await getInlineRichTextLine(page, 4); + const [line4, offset4] = await getInlineRichTextLine(page, 5); + const [line5, offset5] = await getInlineRichTextLine(page, 8); + const [line6, offset6] = await getInlineRichTextLine(page, 11); + + expect(line1).toEqual('abc'); + expect(offset1).toEqual(0); + expect(line2).toEqual('abc'); + expect(offset2).toEqual(1); + expect(line3).toEqual('def'); + expect(offset3).toEqual(0); + expect(line4).toEqual('def'); + expect(offset4).toEqual(1); + expect(line5).toEqual('ghi'); + expect(offset5).toEqual(0); + expect(line6).toEqual('ghi'); + expect(offset6).toEqual(3); +}); + +test('yText should not contain \r', async ({ page }) => { + await enterInlineEditorPlayground(page); + await focusInlineRichText(page); + + await page.waitForTimeout(100); + const message = await page.evaluate(() => { + const richText = document + .querySelector('test-page') + ?.querySelector('test-rich-text'); + + if (!richText) { + throw new Error('Cannot find test-rich-text'); + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const editor = (richText as any).inlineEditor as InlineEditor; + + try { + editor.insertText({ index: 0, length: 0 }, 'abc\r'); + } catch (e) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return (e as any).message; + } + }); + + expect(message).toBe( + 'yText must not contain "\\r" because it will break the range synchronization' + ); +}); + +test('embed', async ({ page }) => { + await enterInlineEditorPlayground(page); + await focusInlineRichText(page); + + const editorA = page.locator('[data-v-root="true"]').nth(0); + const editorAEmbed = page.getByText('embed').nth(0); + + expect(await editorA.innerText()).toBe(ZERO_WIDTH_SPACE); + + await page.waitForTimeout(100); + + await type(page, 'abcde'); + + expect(await editorA.innerText()).toBe('abcde'); + + await press(page, 'ArrowLeft'); + await page.waitForTimeout(100); + await page.keyboard.down('Shift'); + await press(page, 'ArrowLeft'); + await press(page, 'ArrowLeft'); + await press(page, 'ArrowLeft'); + await page.keyboard.up('Shift'); + await page.waitForTimeout(100); + await assertSelection(page, 0, 1, 3); + + await editorAEmbed.click(); + const embedCount = await page.locator('[data-v-embed="true"]').count(); + expect(embedCount).toBe(3); + + // try to update cursor position using arrow keys + await assertSelection(page, 0, 1, 3); + await press(page, 'ArrowLeft'); + await assertSelection(page, 0, 1, 0); + await press(page, 'ArrowLeft'); + await assertSelection(page, 0, 0, 0); + await press(page, 'ArrowRight'); + await assertSelection(page, 0, 1, 0); + await press(page, 'ArrowRight'); + await assertSelection(page, 0, 1, 1); + await press(page, 'ArrowRight'); + await assertSelection(page, 0, 2, 0); + await press(page, 'ArrowRight'); + await assertSelection(page, 0, 2, 1); + await press(page, 'ArrowRight'); + await assertSelection(page, 0, 3, 0); + await press(page, 'ArrowRight'); + await assertSelection(page, 0, 3, 1); + await press(page, 'ArrowRight'); + await assertSelection(page, 0, 4, 0); + await press(page, 'ArrowRight'); + await assertSelection(page, 0, 5, 0); + await press(page, 'ArrowLeft'); + await assertSelection(page, 0, 4, 0); + await press(page, 'ArrowLeft'); + await assertSelection(page, 0, 3, 1); + + // try to update cursor position and select embed element by clicking embed element + let rect = await getInlineRangeIndexRect(page, [0, 1]); + await page.mouse.click(rect.x + 3, rect.y); + await assertSelection(page, 0, 1, 1); + + rect = await getInlineRangeIndexRect(page, [0, 2]); + await page.mouse.click(rect.x + 3, rect.y); + await assertSelection(page, 0, 2, 1); + + rect = await getInlineRangeIndexRect(page, [0, 3]); + await page.mouse.click(rect.x + 3, rect.y); + await assertSelection(page, 0, 3, 1); +}); + +test('delete embed when pressing backspace after embed', async ({ page }) => { + await enterInlineEditorPlayground(page); + await focusInlineRichText(page); + + const editorA = page.locator('[data-v-root="true"]').nth(0); + const editorAEmbed = page.getByText('embed').nth(0); + expect(await editorA.innerText()).toBe(ZERO_WIDTH_SPACE); + await page.waitForTimeout(100); + await type(page, 'ab'); + expect(await editorA.innerText()).toBe('ab'); + + await page.keyboard.down('Shift'); + await press(page, 'ArrowLeft'); + await page.keyboard.up('Shift'); + await page.waitForTimeout(100); + await assertSelection(page, 0, 1, 1); + await editorAEmbed.click(); + + let delta = await getDeltaFromInlineRichText(page); + expect(delta).toEqual([ + { + insert: 'a', + }, + { + insert: 'b', + attributes: { + embed: true, + }, + }, + ]); + + const rect = await getInlineRangeIndexRect(page, [0, 2]); + // use click to select right side of the embed instead of use arrow key + await page.mouse.click(rect.x + 3, rect.y); + await assertSelection(page, 0, 2, 0); + await press(page, 'Backspace'); + + delta = await getDeltaFromInlineRichText(page); + expect(delta).toEqual([ + { + insert: 'a', + }, + ]); +}); + +test('markdown shortcut using keyboard util', async ({ page }) => { + await enterInlineEditorPlayground(page); + await focusInlineRichText(page); + + await page.waitForTimeout(100); + + await type(page, 'aaa**bbb** ccc'); + + const delta = await getDeltaFromInlineRichText(page); + expect(delta).toEqual([ + { + insert: 'aaa', + }, + { + insert: 'bbb', + attributes: { + bold: true, + }, + }, + { + insert: 'ccc', + }, + ]); +}); + +test('triple click to select line', async ({ page }) => { + await enterInlineEditorPlayground(page); + await focusInlineRichText(page); + + const editorA = page.locator('[data-v-root="true"]').nth(0); + + expect(await editorA.innerText()).toBe(ZERO_WIDTH_SPACE); + await page.waitForTimeout(100); + await type(page, 'abc\nabc abc abc\nabc'); + + expect(await editorA.innerText()).toBe('abc\nabc abc abc\nabc'); + + const rect = await getInlineRangeIndexRect(page, [0, 10]); + await page.mouse.click(rect.x, rect.y, { + clickCount: 3, + }); + await assertSelection(page, 0, 4, 11); + + await press(page, 'Backspace'); + expect(await editorA.innerText()).toBe('abc\n' + ZERO_WIDTH_SPACE + '\nabc'); +}); diff --git a/blocksuite/tests-legacy/latex/block.spec.ts b/blocksuite/tests-legacy/latex/block.spec.ts new file mode 100644 index 0000000000000..c2b782f475576 --- /dev/null +++ b/blocksuite/tests-legacy/latex/block.spec.ts @@ -0,0 +1,62 @@ +import { expect } from '@playwright/test'; + +import { + enterPlaygroundRoom, + focusRichText, + getPageSnapshot, + initEmptyParagraphState, + type, +} from '../utils/actions/index.js'; +import { test } from '../utils/playwright.js'; + +test('add latex block using slash menu', async ({ page }, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_init.json` + ); + + await type(page, '/eq\naaa'); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_finial.json` + ); +}); + +test('add latex block using markdown shortcut with space', async ({ + page, +}, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_init.json` + ); + + await type(page, '$$$$ aaa'); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_finial.json` + ); +}); + +test('add latex block using markdown shortcut with enter', async ({ + page, +}, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_init.json` + ); + + await type(page, '$$$$\naaa'); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_finial.json` + ); +}); diff --git a/blocksuite/tests-legacy/latex/inline.spec.ts b/blocksuite/tests-legacy/latex/inline.spec.ts new file mode 100644 index 0000000000000..5fe461a3f72d5 --- /dev/null +++ b/blocksuite/tests-legacy/latex/inline.spec.ts @@ -0,0 +1,337 @@ +import { ZERO_WIDTH_SPACE } from '@inline/consts.js'; +import { expect } from '@playwright/test'; +import { + assertRichTextInlineDeltas, + assertRichTextInlineRange, +} from 'utils/asserts.js'; + +import { + cutByKeyboard, + pasteByKeyboard, + pressArrowLeft, + pressArrowRight, + pressArrowUp, + pressBackspace, + pressBackspaceWithShortKey, + pressEnter, + pressShiftEnter, + redoByKeyboard, + selectAllByKeyboard, + type, + undoByKeyboard, +} from '../utils/actions/keyboard.js'; +import { + enterPlaygroundRoom, + focusRichText, + initEmptyParagraphState, +} from '../utils/actions/misc.js'; +import { test } from '../utils/playwright.js'; + +test('add inline latex at the start of line', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + const latexEditorLine = page.locator('latex-editor-menu v-line div'); + const latexElement = page.locator( + 'affine-paragraph rich-text affine-latex-node' + ); + + expect(await latexEditorLine.isVisible()).not.toBeTruthy(); + expect(await latexElement.isVisible()).not.toBeTruthy(); + await type(page, '$$ '); + expect(await latexEditorLine.isVisible()).toBeTruthy(); + expect(await latexElement.isVisible()).toBeTruthy(); + expect(await latexElement.locator('.placeholder').innerText()).toBe( + 'Equation' + ); + await type(page, 'E=mc^2'); + expect(await latexEditorLine.innerText()).toBe('E=mc^2'); + expect(await latexElement.locator('.katex').innerHTML()).toBe( + 'E=mc2E=mc^2' + ); + + await pressEnter(page); + expect(await latexEditorLine.isVisible()).not.toBeTruthy(); + expect(await latexElement.locator('.katex').innerHTML()).toBe( + 'E=mc2E=mc^2' + ); +}); + +test('add inline latex in the middle of text', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + const latexEditorLine = page.locator('latex-editor-menu v-line div'); + const latexElement = page.locator( + 'affine-paragraph rich-text affine-latex-node' + ); + + expect(await latexEditorLine.isVisible()).not.toBeTruthy(); + expect(await latexElement.isVisible()).not.toBeTruthy(); + await type(page, 'aaaa'); + await pressArrowLeft(page, 2); + await type(page, '$$ '); + expect(await latexEditorLine.isVisible()).toBeTruthy(); + expect(await latexElement.isVisible()).toBeTruthy(); + expect(await latexElement.locator('.placeholder').innerText()).toBe( + 'Equation' + ); + await type(page, 'E=mc^2'); + expect(await latexEditorLine.innerText()).toBe('E=mc^2'); + expect(await latexElement.locator('.katex').innerHTML()).toBe( + 'E=mc2E=mc^2' + ); + + await pressEnter(page); + expect(await latexEditorLine.isVisible()).not.toBeTruthy(); + expect(await latexElement.locator('.katex').innerHTML()).toBe( + 'E=mc2E=mc^2' + ); +}); + +test('update inline latex by clicking the node', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + const latexEditorLine = page.locator('latex-editor-menu v-line div'); + const latexElement = page.locator( + 'affine-paragraph rich-text affine-latex-node' + ); + + expect(await latexEditorLine.isVisible()).not.toBeTruthy(); + await type(page, '$$ '); + expect(await latexEditorLine.isVisible()).toBeTruthy(); + await type(page, 'E=mc^2'); + await pressEnter(page); + expect(await latexEditorLine.isVisible()).not.toBeTruthy(); + await latexElement.click(); + expect(await latexEditorLine.isVisible()).toBeTruthy(); + await pressBackspace(page, 6); + await type(page, String.raw`\def\arraystretch{1.5}`); + await pressShiftEnter(page); + await type(page, String.raw`\begin{array}{c:c:c}`); + await pressShiftEnter(page); + await type(page, String.raw`a & b & c \\ \\ hline`); + await pressShiftEnter(page); + await type(page, String.raw`d & e & f \\`); + await pressShiftEnter(page); + await type(page, String.raw`\hdashline`); + await pressShiftEnter(page); + await type(page, String.raw`g & h & i`); + await pressShiftEnter(page); + await type(page, String.raw`\end{array}`); + expect(await latexElement.locator('.katex').innerHTML()).toBe( + 'abchlinedefghi\\def\\arraystretch{1.5}\n\\begin{array}{c:c:c}\na & b & c \\\\ \\\\ hline\nd & e & f \\\\\n\\hdashline\ng & h & i\n\\end{array}' + ); + + // click outside to hide the editor + await page.click('affine-editor-container'); + expect(await latexEditorLine.isVisible()).not.toBeTruthy(); +}); + +test('latex editor', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + const latexEditorLine = page.locator('latex-editor-menu v-line div'); + const latexElement = page.locator( + 'affine-paragraph rich-text affine-latex-node' + ); + + expect(await latexEditorLine.isVisible()).not.toBeTruthy(); + await type(page, '$$ '); + expect(await latexEditorLine.isVisible()).toBeTruthy(); + // test cursor movement works as expected + // https://github.com/toeverything/blocksuite/pull/8368 + await type(page, 'ababababababababababababababababababababababababab'); + expect(await latexEditorLine.innerText()).toBe( + 'ababababababababababababababababababababababababab' + ); + // click outside to hide the editor + expect(await latexEditorLine.isVisible()).toBeTruthy(); + await page.mouse.click(130, 130); + expect(await latexEditorLine.isVisible()).not.toBeTruthy(); + await latexElement.click(); + expect(await latexEditorLine.isVisible()).toBeTruthy(); + expect(await latexEditorLine.innerText()).toBe( + 'ababababababababababababababababababababababababab' + ); + + await pressBackspaceWithShortKey(page, 2); + expect(await latexEditorLine.innerText()).toBe(ZERO_WIDTH_SPACE); + await undoByKeyboard(page); + expect(await latexEditorLine.innerText()).toBe( + 'ababababababababababababababababababababababababab' + ); + await redoByKeyboard(page); + expect(await latexEditorLine.innerText()).toBe(ZERO_WIDTH_SPACE); + await undoByKeyboard(page); + expect(await latexEditorLine.innerText()).toBe( + 'ababababababababababababababababababababababababab' + ); + + // undo-redo + await pressArrowLeft(page, 5); + await page.keyboard.down('Shift'); + await pressArrowUp(page); + await pressArrowRight(page); + await page.keyboard.up('Shift'); + /** + * abababababababababab|ababab + * abababababababababa|babab + */ + await cutByKeyboard(page); + expect(await latexEditorLine.innerText()).toBe('ababababababababababbabab'); + /** + * abababababababababab|babab + */ + await pressArrowRight(page, 2); + /** + * ababababababababababba|bab + */ + await pasteByKeyboard(page); + expect(await latexEditorLine.innerText()).toBe( + 'ababababababababababbaabababababababababababababab' + ); + + await selectAllByKeyboard(page); + await pressBackspace(page); + expect(await latexEditorLine.innerText()).toBe(ZERO_WIDTH_SPACE); + + // highlight + await type( + page, + String.raw`a+\left(\vcenter{\hbox{$\frac{\frac a b}c$}}\right)` + ); + expect( + (await latexEditorLine.locator('latex-editor-unit').innerHTML()).replace( + /lit\$\d+\$/g, + 'lit$test$' + ) + ).toBe( + '\x3C!---->\x3C!--?lit$test$-->\x3C!---->\x3C!---->\x3C!--?lit$test$-->a+\x3C!---->\x3C!---->\x3C!---->\x3C!--?lit$test$-->\\left\x3C!---->\x3C!---->\x3C!---->\x3C!--?lit$test$-->(\x3C!---->\x3C!---->\x3C!---->\x3C!--?lit$test$-->\\vcenter\x3C!---->\x3C!---->\x3C!---->\x3C!--?lit$test$-->{\x3C!---->\x3C!---->\x3C!---->\x3C!--?lit$test$-->\\hbox\x3C!---->\x3C!---->\x3C!---->\x3C!--?lit$test$-->{\x3C!---->\x3C!---->\x3C!---->\x3C!--?lit$test$-->$\x3C!---->\x3C!---->\x3C!---->\x3C!--?lit$test$-->\\frac{\\frac a b}c\x3C!---->\x3C!---->\x3C!---->\x3C!--?lit$test$-->$\x3C!---->\x3C!---->\x3C!---->\x3C!--?lit$test$-->}}\x3C!---->\x3C!---->\x3C!---->\x3C!--?lit$test$-->\\right\x3C!---->\x3C!---->\x3C!---->\x3C!--?lit$test$-->)\x3C!---->' + ); +}); + +test('add inline latex using slash menu', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + const latexEditorLine = page.locator('latex-editor-menu v-line div'); + const latexElement = page.locator( + 'affine-paragraph rich-text affine-latex-node' + ); + + expect(await latexEditorLine.isVisible()).not.toBeTruthy(); + expect(await latexElement.isVisible()).not.toBeTruthy(); + await type(page, '/ieq\n'); + expect(await latexEditorLine.isVisible()).toBeTruthy(); + expect(await latexElement.isVisible()).toBeTruthy(); + expect(await latexElement.locator('.placeholder').innerText()).toBe( + 'Equation' + ); + await type(page, 'E=mc^2'); + expect(await latexEditorLine.innerText()).toBe('E=mc^2'); + expect(await latexElement.locator('.katex').innerHTML()).toBe( + 'E=mc2E=mc^2' + ); + + await pressEnter(page); + expect(await latexEditorLine.isVisible()).not.toBeTruthy(); + expect(await latexElement.locator('.katex').innerHTML()).toBe( + 'E=mc2E=mc^2' + ); +}); + +test('add inline latex using markdown shortcut', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + // toggle by space or enter + await type(page, 'aa$$bb$$ cc$$dd$$\n'); + await assertRichTextInlineDeltas(page, [ + { + insert: 'aa', + }, + { + insert: ' ', + attributes: { + latex: 'bb', + }, + }, + { + insert: 'cc', + }, + { + insert: ' ', + attributes: { + latex: 'dd', + }, + }, + ]); + + await pressArrowUp(page); + await pressArrowRight(page, 3); + await pressBackspace(page); + await assertRichTextInlineDeltas(page, [ + { + insert: 'aacc', + }, + { + insert: ' ', + attributes: { + latex: 'dd', + }, + }, + ]); +}); + +test('undo-redo when add inline latex using markdown shortcut', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + await type(page, 'aa$$bb$$ '); + await assertRichTextInlineDeltas(page, [ + { + insert: 'aa', + }, + { + insert: ' ', + attributes: { + latex: 'bb', + }, + }, + ]); + await assertRichTextInlineRange(page, 0, 3, 0); + + await undoByKeyboard(page); + await assertRichTextInlineDeltas(page, [ + { + insert: 'aa$$bb$$ ', + }, + ]); + await assertRichTextInlineRange(page, 0, 9, 0); + + await redoByKeyboard(page); + await assertRichTextInlineDeltas(page, [ + { + insert: 'aa', + }, + { + insert: ' ', + attributes: { + latex: 'bb', + }, + }, + ]); + await assertRichTextInlineRange(page, 0, 3, 0); +}); diff --git a/blocksuite/tests-legacy/link.spec.ts b/blocksuite/tests-legacy/link.spec.ts new file mode 100644 index 0000000000000..568a3eb8aa7c1 --- /dev/null +++ b/blocksuite/tests-legacy/link.spec.ts @@ -0,0 +1,543 @@ +import type { Page } from '@playwright/test'; +import { expect } from '@playwright/test'; + +import { + cutByKeyboard, + dragBetweenIndices, + enterPlaygroundRoom, + focusRichText, + focusRichTextEnd, + getPageSnapshot, + initEmptyParagraphState, + pasteByKeyboard, + pressEnter, + pressShiftEnter, + pressTab, + selectAllByKeyboard, + setSelection, + SHORT_KEY, + switchReadonly, + type, + waitNextFrame, +} from './utils/actions/index.js'; +import { + assertKeyboardWorkInInput, + assertStoreMatchJSX, +} from './utils/asserts.js'; +import { test } from './utils/playwright.js'; + +const pressCreateLinkShortCut = async (page: Page) => { + await page.keyboard.press(`${SHORT_KEY}+k`); +}; + +test('basic link', async ({ page }, testInfo) => { + const linkText = 'linkText'; + const link = 'http://example.com'; + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, linkText); + + // Create link + await dragBetweenIndices(page, [0, 0], [0, 8]); + await pressCreateLinkShortCut(page); + await page.mouse.move(0, 0); + + const createLinkPopoverLocator = page.locator('.affine-link-popover.create'); + await expect(createLinkPopoverLocator).toBeVisible(); + const linkPopoverInput = page.locator('.affine-link-popover-input'); + await expect(linkPopoverInput).toBeVisible(); + await type(page, link); + await pressEnter(page); + await expect(createLinkPopoverLocator).not.toBeVisible(); + + const linkLocator = page.locator('affine-link a'); + await expect(linkLocator).toHaveAttribute('href', link); + + // clear text selection + await page.keyboard.press('ArrowLeft'); + + const viewLinkPopoverLocator = page.locator('.affine-link-popover.view'); + // Hover link + await expect(viewLinkPopoverLocator).not.toBeVisible(); + await linkLocator.hover(); + // wait for popover delay open + await page.waitForTimeout(200); + await expect(viewLinkPopoverLocator).toBeVisible(); + + // Edit link + const text2 = 'link2'; + const link2 = 'https://github.com'; + const editLinkBtn = viewLinkPopoverLocator.getByTestId('edit'); + await editLinkBtn.click(); + + const editLinkPopoverLocator = page.locator('.affine-link-edit-popover'); + await expect(editLinkPopoverLocator).toBeVisible(); + // workaround to make tab key work as expected + await editLinkPopoverLocator.click({ + position: { x: 5, y: 5 }, + }); + await page.keyboard.press('Tab'); + await type(page, text2); + await page.keyboard.press('Tab'); + await type(page, link2); + await page.keyboard.press('Tab'); + await pressEnter(page); + const link2Locator = page.locator('affine-link a'); + + await expect(link2Locator).toHaveAttribute('href', link2); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}.json` + ); +}); + +test('add link when dragging from empty line', async ({ page }) => { + const linkText = 'linkText\n\n'; + const link = 'http://example.com'; + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, linkText); + + // Create link + await dragBetweenIndices(page, [2, 0], [0, 0], { + x: 1, + y: 2, + }); + await pressCreateLinkShortCut(page); + await page.mouse.move(0, 0); + + const createLinkPopoverLocator = page.locator('.affine-link-popover.create'); + await expect(createLinkPopoverLocator).toBeVisible(); + const linkPopoverInput = page.locator('.affine-link-popover-input'); + await expect(linkPopoverInput).toBeVisible(); + await type(page, link); + await pressEnter(page); + await expect(createLinkPopoverLocator).not.toBeVisible(); + + const linkLocator = page.locator('affine-link a'); + await expect(linkLocator).toHaveAttribute('href', link); +}); + +async function createLinkBlock(page: Page, str: string, link: string) { + const id = await page.evaluate( + ([str, link]) => { + const { doc } = window; + const rootId = doc.addBlock('affine:page', { + title: new doc.Text('title'), + }); + const noteId = doc.addBlock('affine:note', {}, rootId); + + const text = new doc.Text([ + { insert: 'Hello' }, + { insert: str, attributes: { link } }, + ]); + const id = doc.addBlock( + 'affine:paragraph', + { type: 'text', text: text }, + noteId + ); + return id; + }, + [str, link] + ); + return id; +} + +test('type character in link should not jump out link node', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + const id = await createLinkBlock(page, 'link text', 'http://example.com'); + await focusRichText(page, 0); + await page.keyboard.press('ArrowLeft'); + await type(page, 'IN_LINK'); + await assertStoreMatchJSX( + page, + ` + + + + + } + prop:type="text" +/>`, + id + ); +}); + +test('type character after link should not extend the link attributes', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + const id = await createLinkBlock(page, 'link text', 'http://example.com'); + await focusRichText(page, 0); + await type(page, 'AFTER_LINK'); + await assertStoreMatchJSX( + page, + ` + + + + + + } + prop:type="text" +/>`, + id + ); +}); + +test('readonly mode should not trigger link popup', async ({ page }) => { + await enterPlaygroundRoom(page); + const linkText = 'linkText'; + await createLinkBlock(page, 'linkText', 'http://example.com'); + await focusRichText(page, 0); + const linkLocator = page.locator(`text="${linkText}"`); + + // Hover link + const linkPopoverLocator = page.locator('.affine-link-popover'); + await linkLocator.hover(); + await expect(linkPopoverLocator).toBeVisible(); + await switchReadonly(page); + + await page.mouse.move(0, 0); + // XXX Wait for readonly delay + await page.waitForTimeout(300); + + await linkLocator.hover(); + await expect(linkPopoverLocator).not.toBeVisible(); + + // --- + // press hotkey should not trigger create link popup + + await dragBetweenIndices(page, [0, 0], [0, 3]); + await pressCreateLinkShortCut(page); + + await expect(linkPopoverLocator).not.toBeVisible(); + const linkPopoverInput = page.locator('.affine-link-popover-input'); + await expect(linkPopoverInput).not.toBeVisible(); +}); + +test('should mock selection not stored', async ({ page }) => { + const linkText = 'linkText'; + const link = 'http://example.com'; + await enterPlaygroundRoom(page); + const { paragraphId } = await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, linkText); + + // Create link + await dragBetweenIndices(page, [0, 0], [0, 8]); + await pressCreateLinkShortCut(page); + + const mockSelectNode = page.locator('.mock-selection'); + await expect(mockSelectNode).toHaveCount(1); + await expect(mockSelectNode).toBeVisible(); + + // the mock select node should not be stored in the Y doc + await assertStoreMatchJSX( + page, + ` +`, + paragraphId + ); + + await type(page, link); + await pressEnter(page); + + // the mock select node should be removed after link created + await expect(mockSelectNode).not.toBeVisible(); + await expect(mockSelectNode).toHaveCount(0); +}); + +test('should keyboard work in link popover', async ({ page }) => { + await enterPlaygroundRoom(page); + const linkText = 'linkText'; + await createLinkBlock(page, linkText, 'http://example.com'); + + await dragBetweenIndices(page, [0, 0], [0, 8]); + await pressCreateLinkShortCut(page); + const linkPopoverInput = page.locator('.affine-link-popover-input'); + await assertKeyboardWorkInInput(page, linkPopoverInput); + await page.mouse.click(500, 500); + + const linkLocator = page.locator(`text="${linkText}"`); + const linkPopover = page.locator('.affine-link-popover'); + await linkLocator.hover(); + await waitNextFrame(page, 200); + await expect(linkLocator).toBeVisible(); + // Hover link + await linkLocator.hover(); + // wait for popover delay open + await page.waitForTimeout(200); + await expect(linkPopover).toBeVisible(); + const editLinkBtn = linkPopover.getByTestId('edit'); + await editLinkBtn.click(); + + const editLinkPopover = page.locator('.affine-link-edit-popover'); + await expect(editLinkPopover).toBeVisible(); + + const editTextInput = editLinkPopover.locator( + '.affine-edit-area.text .affine-edit-input' + ); + await assertKeyboardWorkInInput(page, editTextInput); + const editLinkInput = editLinkPopover.locator( + '.affine-edit-area.link .affine-edit-input' + ); + await assertKeyboardWorkInInput(page, editLinkInput); +}); + +test('link bar should not be appear when the range is collapsed', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'aaa'); + + await pressCreateLinkShortCut(page); + const linkPopoverLocator = page.locator('.affine-link-popover'); + await expect(linkPopoverLocator).not.toBeVisible(); + + await dragBetweenIndices(page, [0, 0], [0, 3]); + await pressCreateLinkShortCut(page); + await expect(linkPopoverLocator).toBeVisible(); + + await focusRichText(page); // click to cancel the link popover + await focusRichTextEnd(page); + await pressShiftEnter(page); + await waitNextFrame(page); + await type(page, 'bbb'); + await dragBetweenIndices(page, [0, 1], [0, 5]); + await pressCreateLinkShortCut(page); + await expect(linkPopoverLocator).toBeVisible(); + + await focusRichTextEnd(page); + await pressEnter(page); + // create auto line-break in span element + await type(page, 'd'.repeat(67)); + await page.mouse.click(1, 1); + await waitNextFrame(page); + await dragBetweenIndices(page, [1, 1], [1, 66]); + await pressCreateLinkShortCut(page); + await expect(linkPopoverLocator).toBeVisible(); +}); + +test('create link with paste', async ({ page }) => { + await enterPlaygroundRoom(page); + const { paragraphId } = await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'aaa'); + + await dragBetweenIndices(page, [0, 0], [0, 3]); + await pressCreateLinkShortCut(page); + + const createLinkPopoverLocator = page.locator('.affine-link-popover.create'); + const confirmBtn = createLinkPopoverLocator.locator('.affine-confirm-button'); + + await expect(createLinkPopoverLocator).toBeVisible(); + await expect(confirmBtn).toHaveAttribute('disabled'); + + await type(page, 'affine.pro'); + await expect(confirmBtn).not.toHaveAttribute('disabled'); + await selectAllByKeyboard(page); + await cutByKeyboard(page); + + // press enter should not trigger confirm + await pressEnter(page); + await expect(createLinkPopoverLocator).toBeVisible(); + await expect(confirmBtn).toHaveAttribute('disabled'); + + await pasteByKeyboard(page, false); + await expect(confirmBtn).not.toHaveAttribute('disabled'); + await pressEnter(page); + await expect(createLinkPopoverLocator).not.toBeVisible(); + await assertStoreMatchJSX( + page, + ` + + + + } + prop:type="text" +/>`, + paragraphId + ); +}); + +test('convert link to card', async ({ page }, testInfo) => { + const linkText = 'alinkTexta'; + const link = 'http://example.com'; + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'aaa'); + await pressEnter(page); + await type(page, linkText); + + // Create link + await setSelection(page, 3, 1, 3, 9); + await pressCreateLinkShortCut(page); + await waitNextFrame(page); + const linkPopoverLocator = page.locator('.affine-link-popover'); + await expect(linkPopoverLocator).toBeVisible(); + const linkPopoverInput = page.locator('.affine-link-popover-input'); + await expect(linkPopoverInput).toBeVisible(); + await type(page, link); + await pressEnter(page); + await expect(linkPopoverLocator).not.toBeVisible(); + await focusRichText(page, 1); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}.json` + ); + + const linkLocator = page.locator('affine-link a'); + + await linkLocator.hover(); + await waitNextFrame(page); + await expect(linkPopoverLocator).toBeVisible(); + + await page.getByRole('button', { name: 'Switch view' }).click(); + const linkToCardBtn = page.getByTestId('link-to-card'); + const linkToEmbedBtn = page.getByTestId('link-to-embed'); + await expect(linkToCardBtn).toBeVisible(); + await expect(linkToEmbedBtn).not.toBeVisible(); + + await page.mouse.move(0, 0); + await waitNextFrame(page); + await expect(linkPopoverLocator).not.toBeVisible(); + await focusRichText(page, 1); + await pressTab(page); + + await linkLocator.hover(); + await waitNextFrame(page); + await expect(linkPopoverLocator).toBeVisible(); + await page.getByRole('button', { name: 'Switch view' }).click(); + await expect(linkToCardBtn).toBeVisible(); + await expect(linkToEmbedBtn).not.toBeVisible(); +}); + +//TODO: wait for embed block completed +test.skip('convert link to embed', async ({ page }) => { + const linkText = 'alinkTexta'; + const link = 'https://www.youtube.com/watch?v=U6s2pdxebSo'; + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'aaa'); + await pressEnter(page); + await type(page, linkText); + + // Create link + await setSelection(page, 3, 1, 3, 9); + await pressCreateLinkShortCut(page); + await waitNextFrame(page); + const linkPopoverLocator = page.locator('.affine-link-popover'); + await expect(linkPopoverLocator).toBeVisible(); + const linkPopoverInput = page.locator('.affine-link-popover-input'); + await expect(linkPopoverInput).toBeVisible(); + await type(page, link); + await pressEnter(page); + await expect(linkPopoverLocator).not.toBeVisible(); + await focusRichText(page); + + await assertStoreMatchJSX( + page, + ` + + + + + + + + + } + prop:type="text" + /> + +` + ); + + const linkToCardBtn = page.getByTestId('link-to-card'); + const linkToEmbedBtn = page.getByTestId('link-to-embed'); + const linkLocator = page.locator('affine-link a'); + + await linkLocator.hover(); + await waitNextFrame(page); + await expect(linkPopoverLocator).toBeVisible(); + await expect(linkToCardBtn).toBeVisible(); + await expect(linkToEmbedBtn).toBeVisible(); + + await page.mouse.move(0, 0); + await waitNextFrame(page); + await expect(linkPopoverLocator).not.toBeVisible(); + await focusRichText(page, 1); + await pressTab(page); + + await linkLocator.hover(); + await waitNextFrame(page); + await expect(linkPopoverLocator).toBeVisible(); + await expect(linkToCardBtn).not.toBeVisible(); + await expect(linkToEmbedBtn).not.toBeVisible(); +}); diff --git a/blocksuite/tests-legacy/linked-page.spec.ts b/blocksuite/tests-legacy/linked-page.spec.ts new file mode 100644 index 0000000000000..bcfa9ca1e4214 --- /dev/null +++ b/blocksuite/tests-legacy/linked-page.spec.ts @@ -0,0 +1,1220 @@ +import { expect, type Page } from '@playwright/test'; +import { switchEditorMode } from 'utils/actions/edgeless.js'; +import { getLinkedDocPopover } from 'utils/actions/linked-doc.js'; + +import { + addNewPage, + getDebugMenu, + switchToPage, +} from './utils/actions/click.js'; +import { dragBetweenIndices, dragBlockToPoint } from './utils/actions/drag.js'; +import { + copyByKeyboard, + cutByKeyboard, + pasteByKeyboard, + pressArrowLeft, + pressArrowRight, + pressBackspace, + pressEnter, + redoByKeyboard, + selectAllByKeyboard, + SHORT_KEY, + type, + undoByKeyboard, +} from './utils/actions/keyboard.js'; +import { + captureHistory, + enterPlaygroundRoom, + focusRichText, + focusTitle, + getPageSnapshot, + initEmptyEdgelessState, + initEmptyParagraphState, + setInlineRangeInSelectedRichText, + waitNextFrame, +} from './utils/actions/misc.js'; +import { + assertExists, + assertParentBlockFlavour, + assertRichTexts, + assertStoreMatchJSX, + assertTitle, +} from './utils/asserts.js'; +import { test } from './utils/playwright.js'; + +async function createAndConvertToEmbedLinkedDoc(page: Page) { + const { createLinkedDoc } = getLinkedDocPopover(page); + const linkedDoc = await createLinkedDoc('page1'); + const lickedDocBox = await linkedDoc.boundingBox(); + assertExists(lickedDocBox); + await page.mouse.move( + lickedDocBox.x + lickedDocBox.width / 2, + lickedDocBox.y + lickedDocBox.height / 2 + ); + + await waitNextFrame(page, 200); + const referencePopup = page.locator('.affine-reference-popover-container'); + await expect(referencePopup).toBeVisible(); + + const switchButton = page.getByRole('button', { name: 'Switch view' }); + await switchButton.click(); + + const embedLinkedDocBtn = page.getByRole('button', { name: 'Card view' }); + await expect(embedLinkedDocBtn).toBeVisible(); + await embedLinkedDocBtn.click(); + await waitNextFrame(page, 200); +} + +test.describe('multiple page', () => { + test('should create and switch page work', async ({ page }, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusTitle(page); + await type(page, 'title0'); + await focusRichText(page); + await type(page, 'page0'); + await assertRichTexts(page, ['page0']); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_init.json` + ); + + const { id } = await addNewPage(page); + await switchToPage(page, id); + await focusTitle(page); + await type(page, 'title1'); + await focusRichText(page); + await type(page, 'page1'); + await assertRichTexts(page, ['page1']); + + await switchToPage(page); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_final.json` + ); + }); +}); + +test.describe('reference node', () => { + test('linked doc popover can show and hide correctly', async ({ page }) => { + await enterPlaygroundRoom(page); + const { paragraphId } = await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, '[['); + + // `[[` should be converted to `@` + await assertStoreMatchJSX( + page, + ` +`, + paragraphId + ); + const { linkedDocPopover } = getLinkedDocPopover(page); + await expect(linkedDocPopover).toBeVisible(); + await pressArrowRight(page); + await expect(linkedDocPopover).toBeHidden(); + await type(page, '@'); + await expect(linkedDocPopover).toBeVisible(); + await assertRichTexts(page, ['@@']); + await pressBackspace(page); + await expect(linkedDocPopover).toBeHidden(); + }); + + test('linked doc popover should not show when the current content is @xx and pressing backspace', async ({ + page, + }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, '@'); + await page.keyboard.press('Escape'); + await type(page, 'a'); + + const { linkedDocPopover } = getLinkedDocPopover(page); + await expect(linkedDocPopover).toBeHidden(); + + await pressBackspace(page); + await expect(linkedDocPopover).toBeHidden(); + }); + + test('should reference node attributes correctly', async ({ page }) => { + await enterPlaygroundRoom(page); + const { paragraphId } = await initEmptyParagraphState(page); + const { id } = await addNewPage(page); + await focusRichText(page); + await type(page, '[['); + await pressEnter(page); + + await assertStoreMatchJSX( + page, + ` + + + + } + prop:type="text" +/>`, + paragraphId + ); + + await pressBackspace(page); + await assertStoreMatchJSX( + page, + ` +`, + paragraphId + ); + }); + + test('should reference node can be selected', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await addNewPage(page); + await focusRichText(page); + + await type(page, '1'); + await type(page, '[['); + await pressEnter(page); + + await assertRichTexts(page, ['1 ']); + await type(page, '2'); + await assertRichTexts(page, ['1 2']); + await page.keyboard.press('ArrowLeft'); + await type(page, '3'); + await assertRichTexts(page, ['1 32']); + await page.keyboard.press('ArrowLeft'); + await waitNextFrame(page); + // select the reference node + await page.keyboard.press('ArrowLeft'); + + // delete the reference node and insert text + await type(page, '4'); + await assertRichTexts(page, ['1432']); + }); + + test('text inserted in the between of reference nodes should not be extend attributes', async ({ + page, + }) => { + await enterPlaygroundRoom(page); + const { paragraphId } = await initEmptyParagraphState(page); + const { id } = await addNewPage(page); + await focusRichText(page); + + await type(page, '1'); + await type(page, '@'); + await pressEnter(page); + await type(page, '@'); + await pressEnter(page); + + await assertRichTexts(page, ['1 ']); + await type(page, '2'); + await assertRichTexts(page, ['1 2']); + await page.keyboard.press('ArrowLeft'); + await waitNextFrame(page); + await page.keyboard.press('ArrowLeft'); + await waitNextFrame(page); + await page.keyboard.press('ArrowLeft'); + await type(page, '3'); + await assertRichTexts(page, ['1 3 2']); + + const snapshot = ` + + + + + + + + } + prop:type="text" +/>`; + await assertStoreMatchJSX(page, snapshot, paragraphId); + }); + + test('text can be inserted as expected when reference node is in the start or end of line', async ({ + page, + }) => { + await enterPlaygroundRoom(page); + const { paragraphId } = await initEmptyParagraphState(page); + const { id } = await addNewPage(page); + await focusRichText(page); + + await type(page, '@'); + await pressEnter(page); + await type(page, '@'); + await pressEnter(page); + + await assertRichTexts(page, [' ']); + await type(page, '2'); + await assertRichTexts(page, [' 2']); + await page.keyboard.press('ArrowLeft'); + await waitNextFrame(page); + await page.keyboard.press('ArrowLeft'); + await waitNextFrame(page); + await page.keyboard.press('ArrowLeft'); + await type(page, '3'); + await assertRichTexts(page, [' 3 2']); + await page.keyboard.press('ArrowLeft'); + await waitNextFrame(page); + await page.keyboard.press('ArrowLeft'); + await waitNextFrame(page); + await page.keyboard.press('ArrowLeft'); + await type(page, '1'); + await assertRichTexts(page, ['1 3 2']); + + const snapshot = ` + + + + + + + + } + prop:type="text" +/>`; + await assertStoreMatchJSX(page, snapshot, paragraphId); + }); + + test('should the cursor move correctly around reference node', async ({ + page, + }) => { + await enterPlaygroundRoom(page); + const { paragraphId } = await initEmptyParagraphState(page); + const { id } = await addNewPage(page); + await focusRichText(page); + + await type(page, '1'); + await type(page, '[['); + await pressEnter(page); + + await assertRichTexts(page, ['1 ']); + await type(page, '2'); + await assertRichTexts(page, ['1 2']); + await page.keyboard.press('ArrowLeft'); + await type(page, '3'); + await assertRichTexts(page, ['1 32']); + await page.keyboard.press('ArrowLeft'); + await waitNextFrame(page); + await page.keyboard.press('ArrowLeft'); + await waitNextFrame(page); + await page.keyboard.press('ArrowLeft'); + + await type(page, '4'); + await assertRichTexts(page, ['14 32']); + + const snapshot = ` + + + + + + } + prop:type="text" +/>`; + await assertStoreMatchJSX(page, snapshot, paragraphId); + + await page.keyboard.press('ArrowRight'); + await captureHistory(page); + await pressBackspace(page); + await assertStoreMatchJSX( + page, + ` +`, + paragraphId + ); + await undoByKeyboard(page); + await assertStoreMatchJSX(page, snapshot, paragraphId); + await redoByKeyboard(page); + await assertStoreMatchJSX( + page, + ` +`, + paragraphId + ); + }); + + test('should create reference node works', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + const defaultPageId = 'doc:home'; + const { id: newId } = await addNewPage(page); + await switchToPage(page, newId); + await focusTitle(page); + await type(page, 'title'); + await switchToPage(page, defaultPageId); + + await focusRichText(page); + await type(page, '@'); + const { + linkedDocPopover, + refNode, + assertExistRefText: assertReferenceText, + } = getLinkedDocPopover(page); + await expect(linkedDocPopover).toBeVisible(); + await pressEnter(page); + await expect(linkedDocPopover).toBeHidden(); + await assertRichTexts(page, [' ']); + await expect(refNode).toBeVisible(); + await expect(refNode).toHaveCount(1); + await assertReferenceText('title'); + + await switchToPage(page, newId); + await focusTitle(page); + await pressBackspace(page); + await type(page, '1'); + await switchToPage(page, defaultPageId); + await assertReferenceText('titl1'); + }); + + test('can create linked page and jump', async ({ page }, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusTitle(page); + await type(page, 'page0'); + + await focusRichText(page); + const { createLinkedDoc, findRefNode } = getLinkedDocPopover(page); + const linkedNode = await createLinkedDoc('page1'); + await linkedNode.click(); + + await assertTitle(page, 'page1'); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_init.json` + ); + await focusRichText(page); + await type(page, '@page0'); + await pressEnter(page); + const refNode = await findRefNode('page0'); + await refNode.click(); + await assertTitle(page, 'page0'); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_final.json` + ); + }); + + test('should not merge consecutive identical reference nodes for rendering', async ({ + page, + }) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/toeverything/blocksuite/issues/2136', + }); + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, '[['); + await pressEnter(page); + await type(page, '[['); + await pressEnter(page); + + const { refNode } = getLinkedDocPopover(page); + await assertRichTexts(page, [' ']); + await expect(refNode).toHaveCount(2); + }); +}); + +test.describe('linked page popover', () => { + test('should show linked page popover show and hide', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + const { linkedDocPopover } = getLinkedDocPopover(page); + + await type(page, '[['); + await expect(linkedDocPopover).toBeVisible(); + await pressBackspace(page); + await expect(linkedDocPopover).toBeHidden(); + + await type(page, '@'); + await expect(linkedDocPopover).toBeVisible(); + await page.keyboard.press('Escape'); + await expect(linkedDocPopover).toBeHidden(); + + await type(page, '@'); + await expect(linkedDocPopover).toBeVisible(); + await page.keyboard.press('ArrowRight'); + await expect(linkedDocPopover).toBeHidden(); + + await type(page, '@'); + await expect(linkedDocPopover).toBeVisible(); + await copyByKeyboard(page); + await expect(linkedDocPopover).toBeHidden(); + }); + + test('should fuzzy search works', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + const { + linkedDocPopover, + pageBtn, + assertExistRefText, + assertActivePageIdx, + } = getLinkedDocPopover(page); + + await focusTitle(page); + await type(page, 'page0'); + + const page1 = await addNewPage(page); + await switchToPage(page, page1.id); + await focusTitle(page); + await type(page, 'page1'); + + const page2 = await addNewPage(page); + await switchToPage(page, page2.id); + await focusTitle(page); + await type(page, 'page2'); + + await switchToPage(page); + await focusRichText(page); + await type(page, '@'); + await expect(linkedDocPopover).toBeVisible(); + await expect(pageBtn).toHaveCount(4); + + await assertActivePageIdx(0); + await page.keyboard.press('ArrowDown'); + await assertActivePageIdx(1); + + await page.keyboard.press('ArrowUp'); + await assertActivePageIdx(0); + await page.keyboard.press('Tab'); + await assertActivePageIdx(1); + await page.keyboard.press('Shift+Tab'); + await assertActivePageIdx(0); + + await expect(pageBtn).toHaveText([ + 'page1', + 'page2', + 'Create "Untitled" doc', + 'Import', + ]); + // page2 + // ^ ^ + await type(page, 'a2'); + await expect(pageBtn).toHaveCount(3); + await expect(pageBtn).toHaveText(['page2', 'Create "a2" doc', 'Import']); + await pressEnter(page); + await expect(linkedDocPopover).toBeHidden(); + await assertExistRefText('page2'); + }); + + test('should paste query works', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + + await (async () => { + for (let index = 0; index < 3; index++) { + const newPage = await addNewPage(page); + await switchToPage(page, newPage.id); + await focusTitle(page); + await type(page, 'page' + index); + } + })(); + + await switchToPage(page); + await getDebugMenu(page).pagesBtn.click(); + await focusRichText(page); + await type(page, 'e2'); + await setInlineRangeInSelectedRichText(page, 0, 2); + await cutByKeyboard(page); + + const { pageBtn, linkedDocPopover } = getLinkedDocPopover(page); + await type(page, '@'); + await expect(linkedDocPopover).toBeVisible(); + await expect(pageBtn).toHaveText([ + 'page0', + 'page1', + 'page2', + 'Create "Untitled" doc', + 'Import', + ]); + + await page.keyboard.press(`${SHORT_KEY}+v`); + await expect(linkedDocPopover).toBeVisible(); + await expect(pageBtn).toHaveText(['page2', 'Create "e2" doc', 'Import']); + }); + + test('should multiple paste query not works', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + + await (async () => { + for (let index = 0; index < 3; index++) { + const newPage = await addNewPage(page); + await switchToPage(page, newPage.id); + await focusTitle(page); + await type(page, 'page' + index); + } + })(); + + await switchToPage(page); + await getDebugMenu(page).pagesBtn.click(); + await focusRichText(page); + await type(page, 'pa'); + await pressEnter(page); + await type(page, 'ge'); + await pressEnter(page); + await type(page, '2'); + + await selectAllByKeyboard(page); + await waitNextFrame(page, 200); + await selectAllByKeyboard(page); + await waitNextFrame(page, 200); + await selectAllByKeyboard(page); + await waitNextFrame(page, 200); + await cutByKeyboard(page); + const note = page.locator('affine-note'); + await note.click({ force: true, position: { x: 100, y: 100 } }); + await waitNextFrame(page, 200); + + const { pageBtn, linkedDocPopover } = getLinkedDocPopover(page); + await type(page, '@'); + await expect(linkedDocPopover).toBeVisible(); + await expect(pageBtn).toHaveText([ + 'page0', + 'page1', + 'page2', + 'Create "Untitled" doc', + 'Import', + ]); + + await page.keyboard.press(`${SHORT_KEY}+v`); + await expect(linkedDocPopover).not.toBeVisible(); + }); + + test('should more docs works', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + + await (async () => { + for (let index = 0; index < 10; index++) { + const newPage = await addNewPage(page); + await switchToPage(page, newPage.id); + await focusTitle(page); + await type(page, 'page' + index); + } + })(); + + await switchToPage(page); + await getDebugMenu(page).pagesBtn.click(); + await focusRichText(page); + await type(page, '@'); + + const { pageBtn, linkedDocPopover } = getLinkedDocPopover(page); + await expect(linkedDocPopover).toBeVisible(); + await expect(pageBtn).toHaveText([ + ...Array.from({ length: 6 }, (_, index) => `page${index}`), + '4 more docs', + 'Create "Untitled" doc', + 'Import', + ]); + + const moreNode = page.locator(`icon-button[data-id="Link to Doc More"]`); + await moreNode.click(); + await expect(pageBtn).toHaveText([ + ...Array.from({ length: 10 }, (_, index) => `page${index}`), + 'Create "Untitled" doc', + 'Import', + ]); + }); +}); + +test.describe('linked page with clipboard', () => { + test('paste linked page should paste as linked page', async ({ + page, + }, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + const { createLinkedDoc } = getLinkedDocPopover(page); + + await createLinkedDoc('page1'); + + await selectAllByKeyboard(page); + await copyByKeyboard(page); + await focusRichText(page); + await pasteByKeyboard(page); + const json = await getPageSnapshot(page, true); + expect(json).toMatchSnapshot(`${testInfo.title}.json`); + }); + + test('duplicated linked page should paste as linked page', async ({ + page, + }, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + const { createLinkedDoc } = getLinkedDocPopover(page); + + await createLinkedDoc('page0'); + + await type(page, '/duplicate'); + await pressEnter(page); + const json = await getPageSnapshot(page, true); + expect(json).toMatchSnapshot(`${testInfo.title}.json`); + }); +}); + +test('should [[Selected text]] converted to linked page', async ({ page }) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/toeverything/blocksuite/issues/2730', + }); + await enterPlaygroundRoom(page); + const { paragraphId } = await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, '1234'); + + await dragBetweenIndices(page, [0, 1], [0, 2]); + await type(page, '['); + await assertRichTexts(page, ['1[2]34']); + await type(page, '['); + await assertStoreMatchJSX( + page, + ` + + + + + + } + prop:type="text" +/>`, + paragraphId + ); + await switchToPage(page, '3'); + await assertTitle(page, '2'); +}); + +test('add reference node before the other reference node', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'aaa'); + + const firstRefNode = page.locator('affine-reference').nth(0); + + await type(page, '@bbb'); + await pressEnter(page); + + expect(await firstRefNode.textContent()).toEqual( + expect.stringContaining('bbb') + ); + expect(await firstRefNode.textContent()).not.toEqual( + expect.stringContaining('ccc') + ); + + await pressArrowLeft(page, 3); + await type(page, '@ccc'); + await pressEnter(page); + + expect(await firstRefNode.textContent()).not.toEqual( + expect.stringContaining('bbb') + ); + expect(await firstRefNode.textContent()).toEqual( + expect.stringContaining('ccc') + ); +}); + +test('linked doc can be dragged from note to surface top level block', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await focusRichText(page); + await createAndConvertToEmbedLinkedDoc(page); + + await switchEditorMode(page); + await page.mouse.dblclick(450, 450); + + await dragBlockToPoint(page, '9', { x: 200, y: 200 }); + + await waitNextFrame(page); + await assertParentBlockFlavour(page, '9', 'affine:surface'); +}); + +// Aliases +test.describe('Customize linked doc title and description', () => { + // Inline View + test('should set a custom title for inline link', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusTitle(page); + await type(page, 'title0'); + await focusRichText(page); + await type(page, 'page0'); + await assertRichTexts(page, ['page0']); + + const { id } = await addNewPage(page); + await switchToPage(page, id); + await focusTitle(page); + await type(page, 'title1'); + await focusRichText(page); + await type(page, 'page1'); + await assertRichTexts(page, ['page1']); + + await getDebugMenu(page).pagesBtn.click(); + + await focusRichText(page); + await type(page, '@title0'); + await pressEnter(page); + + const { findRefNode } = getLinkedDocPopover(page); + const page0 = await findRefNode('title0'); + await page0.hover(); + + await waitNextFrame(page, 200); + const referencePopup = page.locator('.affine-reference-popover-container'); + await expect(referencePopup).toBeVisible(); + + const editButton = referencePopup.getByRole('button', { name: 'Edit' }); + await editButton.click(); + + await waitNextFrame(page, 200); + const popup = page.locator('.alias-form-popup'); + await expect(popup).toBeVisible(); + + const input = popup.locator('input'); + await expect(input).toBeFocused(); + + // title alias + await type(page, 'page0-title0'); + await pressEnter(page); + + await page.mouse.click(0, 0); + + await focusRichText(page); + await waitNextFrame(page, 200); + + const page0Alias = await findRefNode('page0-title0'); + await page0Alias.hover(); + + await waitNextFrame(page, 200); + await expect(referencePopup).toBeVisible(); + + // original title button + const docTitle = referencePopup.getByRole('button', { name: 'Doc title' }); + await expect(docTitle).toHaveText('title0', { useInnerText: true }); + + // reedit + await editButton.click(); + + await waitNextFrame(page, 200); + + // reset + await popup.getByRole('button', { name: 'Reset' }).click(); + + await waitNextFrame(page, 200); + + const resetedPage0 = await findRefNode('title0'); + await resetedPage0.hover(); + + await waitNextFrame(page, 200); + await expect(referencePopup).toBeVisible(); + await expect(docTitle).not.toBeVisible(); + }); + + // Card View + test('should set a custom title and description for card link', async ({ + page, + }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusTitle(page); + await type(page, 'title0'); + + const { id } = await addNewPage(page); + await switchToPage(page, id); + await focusTitle(page); + await type(page, 'title1'); + + await getDebugMenu(page).pagesBtn.click(); + + await focusRichText(page); + await type(page, '@title0'); + await pressEnter(page); + + const { findRefNode } = getLinkedDocPopover(page); + const page0 = await findRefNode('title0'); + await page0.hover(); + + await waitNextFrame(page, 200); + const referencePopup = page.locator('.affine-reference-popover-container'); + + let editButton = referencePopup.getByRole('button', { name: 'Edit' }); + await editButton.click(); + + // title alias + await type(page, 'page0-title0'); + await pressEnter(page); + + await focusRichText(page); + await waitNextFrame(page, 200); + + const page0Alias = await findRefNode('page0-title0'); + await page0Alias.hover(); + + await waitNextFrame(page, 200); + const switchButton = referencePopup.getByRole('button', { + name: 'Switch view', + }); + await switchButton.click(); + + // switches to card view + const toCardButton = referencePopup.getByRole('button', { + name: 'Card view', + }); + await toCardButton.click(); + + await waitNextFrame(page, 200); + const linkedDocBlock = page.locator('affine-embed-linked-doc-block'); + await expect(linkedDocBlock).toBeVisible(); + + const linkedDocBlockTitle = linkedDocBlock.locator( + '.affine-embed-linked-doc-content-title-text' + ); + await expect(linkedDocBlockTitle).toHaveText('page0-title0'); + + await linkedDocBlock.click(); + + await waitNextFrame(page, 200); + const cardToolbar = page.locator('affine-embed-card-toolbar'); + const docTitleButton = cardToolbar.getByRole('button', { + name: 'Doc title', + }); + await expect(docTitleButton).toBeVisible(); + + editButton = cardToolbar.getByRole('button', { name: 'Edit' }); + await editButton.click(); + + await waitNextFrame(page, 200); + const editModal = page.locator('embed-card-edit-modal'); + const resetButton = editModal.getByRole('button', { name: 'Reset' }); + const saveButton = editModal.getByRole('button', { name: 'Save' }); + + // clears aliases + await resetButton.click(); + + await waitNextFrame(page, 200); + await expect(linkedDocBlockTitle).toHaveText('title0'); + + await linkedDocBlock.click(); + + await waitNextFrame(page, 200); + await editButton.click(); + + await waitNextFrame(page, 200); + + // title alias + await type(page, 'page0-title0'); + await page.keyboard.press('Tab'); + // description alias + await type(page, 'This is a new description'); + + // saves aliases + await saveButton.click(); + + await waitNextFrame(page, 200); + await expect(linkedDocBlockTitle).toHaveText('page0-title0'); + await expect( + page.locator('.affine-embed-linked-doc-content-note.alias') + ).toHaveText('This is a new description'); + }); + + // Embed View + test('should automatically switch to card view and set a custom title and description', async ({ + page, + }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusTitle(page); + await type(page, 'title0'); + + const { id } = await addNewPage(page); + await switchToPage(page, id); + await focusTitle(page); + await type(page, 'title1'); + + await getDebugMenu(page).pagesBtn.click(); + + await focusRichText(page); + await type(page, '@title0'); + await pressEnter(page); + + const { findRefNode } = getLinkedDocPopover(page); + const page0 = await findRefNode('title0'); + await page0.hover(); + + await waitNextFrame(page, 200); + const referencePopup = page.locator('.affine-reference-popover-container'); + + let editButton = referencePopup.getByRole('button', { name: 'Edit' }); + await editButton.click(); + + // title alias + await type(page, 'page0-title0'); + await pressEnter(page); + + await focusRichText(page); + await waitNextFrame(page, 200); + + const page0Alias = await findRefNode('page0-title0'); + await page0Alias.hover(); + + await waitNextFrame(page, 200); + let switchButton = referencePopup.getByRole('button', { + name: 'Switch view', + }); + await switchButton.click(); + + // switches to card view + const toCardButton = referencePopup.getByRole('button', { + name: 'Card view', + }); + await toCardButton.click(); + + await waitNextFrame(page, 200); + const linkedDocBlock = page.locator('affine-embed-linked-doc-block'); + + await linkedDocBlock.click(); + + await waitNextFrame(page, 200); + const cardToolbar = page.locator('affine-embed-card-toolbar'); + switchButton = cardToolbar.getByRole('button', { name: 'Switch view' }); + await switchButton.click(); + + await waitNextFrame(page, 200); + + // switches to embed view + const toEmbedButton = cardToolbar.getByRole('button', { + name: 'Embed view', + }); + await toEmbedButton.click(); + + await waitNextFrame(page, 200); + const syncedDocBlock = page.locator('affine-embed-synced-doc-block'); + + await syncedDocBlock.click(); + + await waitNextFrame(page, 200); + const syncedDocBlockTitle = syncedDocBlock.locator( + '.affine-embed-synced-doc-title' + ); + await expect(syncedDocBlockTitle).toHaveText('title0'); + + await syncedDocBlock.click(); + + await waitNextFrame(page, 200); + editButton = cardToolbar.getByRole('button', { name: 'Edit' }); + await editButton.click(); + + await waitNextFrame(page, 200); + const editModal = page.locator('embed-card-edit-modal'); + const cancelButton = editModal.getByRole('button', { name: 'Cancel' }); + const saveButton = editModal.getByRole('button', { name: 'Save' }); + + // closes edit-model + await cancelButton.click(); + + await waitNextFrame(page, 200); + await expect(editModal).not.toBeVisible(); + + await syncedDocBlock.click(); + + await waitNextFrame(page, 200); + await editButton.click(); + + await waitNextFrame(page, 200); + + // title alias + await type(page, 'page0-title0'); + await page.keyboard.press('Tab'); + // description alias + await type(page, 'This is a new description'); + + // saves aliases + await saveButton.click(); + + await waitNextFrame(page, 200); + + // automatically switch to card view + await expect(syncedDocBlock).not.toBeVisible(); + + await expect(linkedDocBlock).toBeVisible(); + const linkedDocBlockTitle = linkedDocBlock.locator( + '.affine-embed-linked-doc-content-title-text' + ); + await expect(linkedDocBlockTitle).toHaveText('page0-title0'); + await expect( + linkedDocBlock.locator('.affine-embed-linked-doc-content-note.alias') + ).toHaveText('This is a new description'); + }); + + // Embed View + test.fixme( + 'should automatically switch to card view and set a custom title and description on edgeless', + async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await focusRichText(page); + await createAndConvertToEmbedLinkedDoc(page); + + await switchEditorMode(page); + await page.mouse.dblclick(450, 450); + + await dragBlockToPoint(page, '9', { x: 200, y: 200 }); + + await waitNextFrame(page); + + const toolbar = page.locator('editor-toolbar'); + await toolbar.getByRole('button', { name: 'Switch view' }).click(); + await toolbar.getByRole('button', { name: 'Embed view' }).click(); + + await waitNextFrame(page); + + await toolbar.getByRole('button', { name: 'Edit' }).click(); + + await waitNextFrame(page); + const editModal = page.locator('embed-card-edit-modal'); + const saveButton = editModal.getByRole('button', { name: 'Save' }); + + // title alias + await type(page, 'page0-title0'); + await page.keyboard.press('Tab'); + // description alias + await type(page, 'This is a new description'); + + // saves aliases + await saveButton.click(); + + await waitNextFrame(page); + + const syncedDocBlock = page.locator( + 'affine-embed-edgeless-synced-doc-block' + ); + + await expect(syncedDocBlock).toBeHidden(); + + const linkedDocBlock = page.locator( + 'affine-embed-edgeless-linked-doc-block' + ); + + await expect(linkedDocBlock).toBeVisible(); + + const linkedDocBlockTitle = linkedDocBlock.locator( + '.affine-embed-linked-doc-content-title-text' + ); + await expect(linkedDocBlockTitle).toHaveText('page0-title0'); + await expect( + linkedDocBlock.locator('.affine-embed-linked-doc-content-note.alias') + ).toHaveText('This is a new description'); + } + ); +}); diff --git a/blocksuite/tests-legacy/list.spec.ts b/blocksuite/tests-legacy/list.spec.ts new file mode 100644 index 0000000000000..3a8c3e85ea66e --- /dev/null +++ b/blocksuite/tests-legacy/list.spec.ts @@ -0,0 +1,842 @@ +import { expect, type Locator } from '@playwright/test'; +import { getFormatBar } from 'utils/query.js'; + +import { + dragBetweenIndices, + enterPlaygroundRoom, + enterPlaygroundWithList, + focusRichText, + getPageSnapshot, + initEmptyEdgelessState, + initEmptyParagraphState, + initThreeLists, + pressArrowLeft, + pressArrowUp, + pressBackspace, + pressBackspaceWithShortKey, + pressEnter, + pressShiftEnter, + pressShiftTab, + pressSpace, + pressTab, + redoByClick, + switchEditorMode, + switchReadonly, + type, + undoByClick, + undoByKeyboard, + updateBlockType, + waitNextFrame, +} from './utils/actions/index.js'; +import { + assertBlockChildrenFlavours, + assertBlockChildrenIds, + assertBlockCount, + assertBlockType, + assertListPrefix, + assertRichTextInlineRange, + assertRichTexts, + assertTextContent, +} from './utils/asserts.js'; +import { test } from './utils/playwright.js'; + +async function isToggleIconVisible(toggleIcon: Locator) { + const connected = await toggleIcon.isVisible(); + if (!connected) return false; + const element = await toggleIcon.elementHandle(); + if (!element) return false; + const opacity = await element.evaluate(node => { + // https://stackoverflow.com/questions/11365296/how-do-i-get-the-opacity-of-an-element-using-javascript + return window.getComputedStyle(node).getPropertyValue('opacity'); + }); + if (!opacity || typeof opacity !== 'string') { + throw new Error('opacity is not a string'); + } + const isVisible = opacity !== '0'; + return isVisible; +} + +test('add new bulleted list', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + + await focusRichText(page, 0); + await updateBlockType(page, 'affine:list', 'bulleted'); + await focusRichText(page, 0); + await type(page, 'aa'); + await pressEnter(page); + await type(page, 'aa'); + await pressEnter(page); + + await assertRichTexts(page, ['aa', 'aa', '']); + await assertBlockCount(page, 'list', 3); +}); + +test('add new todo list', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + + await focusRichText(page, 0); + await updateBlockType(page, 'affine:list', 'todo'); + await focusRichText(page, 0); + + await type(page, 'aa'); + await assertRichTexts(page, ['aa']); + + const checkBox = page.locator('.affine-list-block__prefix'); + await expect(page.locator('.affine-list--checked')).toHaveCount(0); + await checkBox.click(); + await expect(page.locator('.affine-list--checked')).toHaveCount(1); + await checkBox.click(); + await expect(page.locator('.affine-list--checked')).toHaveCount(0); +}); + +test('add new toggle list', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + + await focusRichText(page, 0); + await updateBlockType(page, 'affine:list', 'toggle'); + await focusRichText(page, 0); + await type(page, 'top'); + await pressTab(page); + await pressEnter(page); + await type(page, 'kid 1'); + await pressEnter(page); + + await assertRichTexts(page, ['top', 'kid 1', '']); + await assertBlockCount(page, 'list', 3); +}); + +test('convert nested paragraph to list', async ({ page }, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + await type(page, 'aaa\nbbb'); + await pressTab(page); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_init.json` + ); + + await dragBetweenIndices(page, [0, 1], [1, 2]); + const { openParagraphMenu, bulletedBtn } = getFormatBar(page); + await openParagraphMenu(); + await bulletedBtn.click(); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_final.json` + ); +}); + +test('convert to numbered list block', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + + await focusRichText(page, 0); // created 0, 1, 2 + await updateBlockType(page, 'affine:list', 'bulleted'); // replaced 2 to 3 + await waitNextFrame(page); + await updateBlockType(page, 'affine:list', 'numbered'); + await focusRichText(page, 0); + + const listSelector = '.affine-list-rich-text-wrapper'; + const bulletIconSelector = `${listSelector} > div`; + await assertTextContent(page, bulletIconSelector, /1\./); + + await undoByClick(page); + // const numberIconSelector = `${listSelector} > svg`; + // await expect(page.locator(numberIconSelector)).toHaveCount(1); + + await redoByClick(page); + await focusRichText(page, 0); + await type(page, 'aa'); + await pressEnter(page); // created 4 + await assertBlockType(page, '4', 'numbered'); + + await type(page, 'aa'); + await pressEnter(page); // created 5 + await assertBlockType(page, '5', 'numbered'); + + await page.keyboard.press('Tab'); + await assertBlockType(page, '5', 'numbered'); +}); + +test('indent list block', async ({ page }) => { + await enterPlaygroundWithList(page); // 0(1(2,3,4)) + + await focusRichText(page, 1); + await type(page, 'hello'); + await assertRichTexts(page, ['', 'hello', '']); + + await page.keyboard.press('Tab'); // 0(1(2(3)4)) + await assertRichTexts(page, ['', 'hello', '']); + await assertBlockChildrenIds(page, '1', ['2', '4']); + await assertBlockChildrenIds(page, '2', ['3']); + + await undoByKeyboard(page); // 0(1(2,3,4)) + await assertBlockChildrenIds(page, '1', ['2', '3', '4']); +}); + +test('unindent list block', async ({ page }) => { + await enterPlaygroundWithList(page); // 0(1(2,3,4)) + + await focusRichText(page, 1); + await page.keyboard.press('Tab', { delay: 50 }); // 0(1(2(3)4)) + + await assertBlockChildrenIds(page, '1', ['2', '4']); + await assertBlockChildrenIds(page, '2', ['3']); + + await pressShiftTab(page); // 0(1(2,3,4)) + await assertBlockChildrenIds(page, '1', ['2', '3', '4']); + + await pressShiftTab(page); + await assertBlockChildrenIds(page, '1', ['2', '3', '4']); +}); + +test('remove all indent for a list block', async ({ page }) => { + await enterPlaygroundWithList(page); // 0(1(2,3,4)) + + await focusRichText(page, 1); + await page.keyboard.press('Tab', { delay: 50 }); // 0(1(2(3)4)) + await focusRichText(page, 2); + await page.keyboard.press('Tab', { delay: 50 }); + await page.keyboard.press('Tab', { delay: 50 }); // 0(1(2(3(4)))) + await assertBlockChildrenIds(page, '3', ['4']); + await pressBackspaceWithShortKey(page); // 0(1(2(3)4)) + await assertBlockChildrenIds(page, '1', ['2', '4']); + await assertBlockChildrenIds(page, '2', ['3']); +}); + +test('insert new list block by enter', async ({ page }) => { + await enterPlaygroundWithList(page); + await assertRichTexts(page, ['', '', '']); + + await focusRichText(page, 1); + await type(page, 'hello'); + await assertRichTexts(page, ['', 'hello', '']); + + await pressEnter(page); + await type(page, 'world'); + await assertRichTexts(page, ['', 'hello', 'world', '']); + await assertBlockChildrenFlavours(page, '1', [ + 'affine:list', + 'affine:list', + 'affine:list', + 'affine:list', + ]); +}); + +test('delete at start of list block', async ({ page }) => { + await enterPlaygroundWithList(page); + await focusRichText(page, 1); + await page.keyboard.press('Backspace'); + await assertBlockChildrenFlavours(page, '1', [ + 'affine:list', + 'affine:paragraph', + 'affine:list', + ]); + await waitNextFrame(page, 200); + await assertRichTextInlineRange(page, 1, 0, 0); + + await undoByClick(page); + await assertBlockChildrenFlavours(page, '1', [ + 'affine:list', + 'affine:list', + 'affine:list', + ]); + await waitNextFrame(page); + //FIXME: it just failed in playwright + // await assertSelection(page, 1, 0, 0); +}); + +test('nested list blocks', async ({ page }, testInfo) => { + await enterPlaygroundWithList(page); + + await focusRichText(page, 0); + await type(page, '123'); + + await focusRichText(page, 1); + await pressTab(page); + await type(page, '456'); + + await focusRichText(page, 2); + await pressTab(page); + await pressTab(page); + await type(page, '789'); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_init.json` + ); + + await focusRichText(page, 1); + await pressShiftTab(page); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_finial.json` + ); +}); + +test('update numbered list block prefix', async ({ page }) => { + await enterPlaygroundWithList(page, ['', '', ''], 'numbered'); // 0(1(2,3,4)) + + await focusRichText(page, 1); + await type(page, 'lunatic'); + await assertRichTexts(page, ['', 'lunatic', '']); + await assertListPrefix(page, ['1', '2', '3']); + + await page.keyboard.press('Tab'); + await assertListPrefix(page, ['1', 'a', '2']); + + await page.keyboard.press('Shift+Tab'); + await assertListPrefix(page, ['1', '2', '3']); + + await waitNextFrame(page, 200); + await page.keyboard.press('Enter'); + await assertListPrefix(page, ['1', '2', '3', '4']); + + await waitNextFrame(page, 200); + await type(page, 'concorde'); + await assertRichTexts(page, ['', 'lunatic', 'concorde', '']); + + await page.keyboard.press('Tab'); + await assertListPrefix(page, ['1', '2', 'a', '3']); +}); + +test('basic indent and unindent', async ({ page }, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + await type(page, 'text1'); + await pressEnter(page); + await type(page, 'text2'); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_init.json` + ); + + await page.keyboard.press('Tab'); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_after_tab.json` + ); + + await page.waitForTimeout(100); + await pressShiftTab(page); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_after_shift_tab.json` + ); +}); + +test('should indent todo block preserve todo status', async ({ + page, +}, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'text1'); + await pressEnter(page); + + await type(page, '[x]'); + await pressSpace(page); + + await type(page, 'todo item'); + await pressTab(page); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_init.json` + ); + await pressShiftTab(page); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_final.json` + ); +}); + +test('enter list block with empty text', async ({ page }, testInfo) => { + await enterPlaygroundWithList(page); // 0(1(2,3,4)) + + /** + * - + * - + * - + */ + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_init.json` + ); + + await focusRichText(page, 1); + await pressTab(page); + await focusRichText(page, 2); + await pressTab(page); + + /** + * - + * - + * -| + */ + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_1.json` + ); + + await pressEnter(page); + + /** + * - + * - + * -| + */ + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_2.json` + ); + + await pressEnter(page); + + /** + * - + * - + * | + */ + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_3.json` + ); + + await undoByClick(page); + await undoByClick(page); + + /** + * - + * - + * -| + */ + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_1.json` + ); + + /** + * - + * -| + * - + */ + await focusRichText(page, 1); + await waitNextFrame(page); + await pressEnter(page); + await waitNextFrame(page); + + /** + * - + * -| + * - + */ + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_4.json` + ); + + await undoByClick(page); + + /** + * - + * - + * -| + */ + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_1.json` + ); + + /** + * -| + * - + * - + */ + await focusRichText(page, 0); + await waitNextFrame(page); + await pressEnter(page); + await waitNextFrame(page); + + /** + * | + * - + * - + */ + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_5.json` + ); + + await undoByClick(page); + + /** + * - + * - + * -| + */ + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_1.json` + ); +}); + +test('enter list block with non-empty text', async ({ page }) => { + await enterPlaygroundWithList(page); // 0(1(2,3,4)) + + await focusRichText(page, 0); + await type(page, 'aa'); + await focusRichText(page, 1); + await type(page, 'bb'); + await pressTab(page); + await focusRichText(page, 2); + await type(page, 'cc'); + await pressTab(page); + await assertBlockChildrenIds(page, '2', ['3', '4']); // 0(1(2,(3,4))) + + await focusRichText(page, 1); + await pressEnter(page); + await assertBlockChildrenIds(page, '2', ['3', '5', '4']); + await undoByClick(page); + await assertBlockChildrenIds(page, '2', ['3', '4']); // 0(1(2,(3,4))) + + await focusRichText(page, 0); + await pressEnter(page); + await assertBlockChildrenIds(page, '2', ['6', '3', '4']); // 0(1(2,(6,3,4))) + await waitNextFrame(page); + await undoByClick(page); + await assertBlockChildrenIds(page, '2', ['3', '4']); // 0(1(2,(3,4))) +}); + +test.describe('indent correctly when deleting list item', () => { + test('delete the child item in the middle position', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page, 0); + + await type(page, '- a'); + await pressEnter(page); + await pressTab(page); + await type(page, 'b'); + await pressEnter(page); + await type(page, 'c'); + await pressEnter(page); + await type(page, 'd'); + await pressArrowUp(page); + await pressArrowLeft(page); + await pressBackspace(page); + await pressBackspace(page); + + await assertBlockChildrenIds(page, '3', ['4', '6']); + await assertRichTexts(page, ['a', 'bc', 'd']); + await assertRichTextInlineRange(page, 1, 1); + }); + + test('merge two lists', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page, 0); + + await type(page, '- a'); + await pressEnter(page); + await pressTab(page); + await type(page, 'b'); + await pressEnter(page); + await pressTab(page); + await type(page, 'c'); + await pressEnter(page); + await pressBackspace(page, 3); + await assertRichTexts(page, ['a', 'b', 'c', '']); + + await waitNextFrame(page); + await pressEnter(page); + await type(page, '- d'); + await pressEnter(page); + await pressTab(page); + await type(page, 'e'); + await pressEnter(page); + await pressTab(page); + await type(page, 'f'); + await pressArrowUp(page, 3); + await pressBackspace(page, 2); + + await waitNextFrame(page, 200); + await assertRichTexts(page, ['a', 'b', '', 'd', 'e', 'f']); + await assertBlockChildrenIds(page, '1', ['3', '9']); + await assertBlockChildrenIds(page, '3', ['4']); + await assertBlockChildrenIds(page, '4', ['5']); + await assertBlockChildrenIds(page, '10', ['11']); + }); +}); + +test('delete list item with nested children items', async ({ page }) => { + await enterPlaygroundWithList(page); + + await focusRichText(page, 0); + await type(page, '1'); + + await focusRichText(page, 1); + await pressTab(page); + await type(page, '2'); + + await focusRichText(page, 2); + await pressTab(page); + await pressTab(page); + await type(page, '3'); + + await pressEnter(page); + await type(page, '4'); + + await focusRichText(page, 1); + await pressArrowLeft(page); + // 1 + // |2 + // 3 + // 4 + + await pressBackspace(page); + await waitNextFrame(page); + // 1 + // |2 (transformed to paragraph) + // 3 + // 4 + + await pressBackspace(page); + await waitNextFrame(page); + // 1 + // |2 + // 3 + // 4 + + await pressBackspace(page); + await waitNextFrame(page); + // 1|2 + // 3 + // 4 + + await assertRichTextInlineRange(page, 0, 1); + await assertRichTexts(page, ['12', '3', '4']); + await assertBlockChildrenIds(page, '1', ['2', '4', '5']); +}); + +test('add number prefix to a todo item should not forcefully change it into numbered list, vice versa', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page, 0); + await type(page, '1. numberList'); + await assertListPrefix(page, ['1']); + await focusRichText(page, 0, { clickPosition: { x: 0, y: 0 } }); + await type(page, '[] '); + await assertListPrefix(page, ['1']); + await pressBackspace(page, 14); + await type(page, '[] todoList'); + await assertListPrefix(page, ['']); + await focusRichText(page, 0, { clickPosition: { x: 0, y: 0 } }); + await type(page, '1. '); + await assertListPrefix(page, ['']); +}); + +test('should not convert to a list when pressing space at the second line', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'aaa'); + await pressShiftEnter(page); + await type(page, '-'); + await pressSpace(page); + await type(page, 'bbb'); + await assertRichTexts(page, ['aaa\n- bbb']); +}); + +test.describe('toggle list', () => { + test('click toggle icon should collapsed list', async ({ + page, + }, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeLists(page); + const toggleIcon = page.locator('.toggle-icon'); + const prefixes = page.locator('.affine-list-block__prefix'); + const listChildren = page + .locator('[data-block-id="4"] .affine-block-children-container') + .nth(0); + const parentPrefix = prefixes.nth(1); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_init.json` + ); + + await parentPrefix.hover(); + await waitNextFrame(page); + expect(await isToggleIconVisible(toggleIcon)).toBe(true); + + await expect(listChildren).toBeVisible(); + await toggleIcon.click(); + await expect(listChildren).not.toBeVisible(); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_toggle.json` + ); + + // Collapsed toggle icon should be show always + await page.mouse.move(0, 0); + expect(await isToggleIconVisible(toggleIcon)).toBe(true); + + await expect(listChildren).not.toBeVisible(); + await toggleIcon.click(); + await expect(listChildren).toBeVisible(); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_init.json` + ); + + await page.mouse.move(0, 0); + await waitNextFrame(page, 200); + expect(await isToggleIconVisible(toggleIcon)).toBe(false); + }); + + test('indent item should expand toggle', async ({ page }, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeLists(page); + await focusRichText(page, 2); + await pressEnter(page); + await pressEnter(page); + await type(page, '012'); + + const toggleIcon = page.locator('.toggle-icon'); + const listChildren = page + .locator('[data-block-id="4"] .affine-block-children-container') + .nth(0); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_init.json` + ); + + await expect(listChildren).toBeVisible(); + await toggleIcon.click(); + await expect(listChildren).not.toBeVisible(); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_toggle.json` + ); + + await focusRichText(page, 3); + await pressTab(page); + await waitNextFrame(page, 200); + await expect(listChildren).not.toBeVisible(); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_finial.json` + ); + }); + + test('toggle icon should be show when hover', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeLists(page); + const toggleIcon = page.locator('.toggle-icon'); + + const prefixes = page.locator('.affine-list-block__prefix'); + const parentPrefix = prefixes.nth(1); + + expect(await isToggleIconVisible(toggleIcon)).toBe(false); + await parentPrefix.hover(); + await waitNextFrame(page, 200); + expect(await isToggleIconVisible(toggleIcon)).toBe(true); + + await page.mouse.move(0, 0); + await waitNextFrame(page, 300); + expect(await isToggleIconVisible(toggleIcon)).toBe(false); + }); +}); + +test.describe('readonly', () => { + test('can expand toggle in readonly mode', async ({ page }, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeLists(page); + const toggleIcon = page.locator('.toggle-icon'); + const prefixes = page.locator('.affine-list-block__prefix'); + const listChildren = page + .locator('[data-block-id="4"] .affine-block-children-container') + .nth(0); + const parentPrefix = prefixes.nth(1); + + await parentPrefix.hover(); + await waitNextFrame(page, 200); + expect(await isToggleIconVisible(toggleIcon)).toBe(true); + + await expect(listChildren).toBeVisible(); + await toggleIcon.click(); + await expect(listChildren).not.toBeVisible(); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_before_readonly.json` + ); + + await waitNextFrame(page, 200); + await switchReadonly(page); + + await waitNextFrame(page, 200); + expect(await isToggleIconVisible(toggleIcon)).toBe(true); + + await expect(listChildren).not.toBeVisible(); + await toggleIcon.click(); + await expect(listChildren).toBeVisible(); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_before_readonly.json` + ); + + await toggleIcon.click(); + await expect(listChildren).not.toBeVisible(); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_before_readonly.json` + ); + }); + + test('can not modify todo list in readonly mode', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + const checkBox = page.locator('.affine-list-block__prefix'); + + { + await type(page, '[] todo'); + await switchReadonly(page); + await expect(page.locator('.affine-list--checked')).toHaveCount(0); + await checkBox.click(); + await expect(page.locator('.affine-list--checked')).toHaveCount(0); + } + + { + await switchReadonly(page, false); + await checkBox.click(); + await switchReadonly(page); + await expect(page.locator('.affine-list--checked')).toHaveCount(1); + await checkBox.click(); + await expect(page.locator('.affine-list--checked')).toHaveCount(1); + } + }); + + test('should render collapsed list correctly', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + // await switchEditorMode(page); + await initThreeLists(page); + + const toggleIcon = page.locator('.toggle-icon'); + const listChildren = page + .locator('[data-block-id="5"] .affine-block-children-container') + .nth(0); + + await expect(listChildren).toBeVisible(); + await toggleIcon.click(); + await expect(listChildren).not.toBeVisible(); + + await switchReadonly(page); + // trick for render a readonly doc from scratch + await switchEditorMode(page); + await switchEditorMode(page); + + await expect(listChildren).not.toBeVisible(); + }); +}); diff --git a/blocksuite/tests-legacy/markdown.spec.ts b/blocksuite/tests-legacy/markdown.spec.ts new file mode 100644 index 0000000000000..19989a5f29bb5 --- /dev/null +++ b/blocksuite/tests-legacy/markdown.spec.ts @@ -0,0 +1,535 @@ +import { + enterPlaygroundRoom, + focusRichText, + getCursorBlockIdAndHeight, + initEmptyParagraphState, + pressArrowLeft, + pressBackspace, + pressEnter, + pressSpace, + redoByKeyboard, + resetHistory, + type, + undoByClick, + undoByKeyboard, + waitNextFrame, +} from './utils/actions/index.js'; +import { + assertBlockType, + assertRichTextInlineDeltas, + assertRichTextInlineRange, + assertRichTexts, + assertText, +} from './utils/asserts.js'; +import { test } from './utils/playwright.js'; + +test('markdown shortcut', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await resetHistory(page); + + let id: string | null = null; + + await waitNextFrame(page); + await type(page, '[] '); + await waitNextFrame(page); + [id] = await getCursorBlockIdAndHeight(page); + await assertBlockType(page, id, 'todo'); + await undoByClick(page); + await assertText(page, '[] '); + await undoByClick(page); + //FIXME: it just failed in playwright + await focusRichText(page); + await assertRichTexts(page, ['']); + + await waitNextFrame(page); + await type(page, '[ ] '); + await waitNextFrame(page); + [id] = await getCursorBlockIdAndHeight(page); + await assertBlockType(page, id, 'todo'); + await undoByClick(page); + await assertText(page, '[ ] '); + await undoByClick(page); + //FIXME: it just failed in playwright + await focusRichText(page); + await assertRichTexts(page, ['']); + + await waitNextFrame(page); + await type(page, '[x] '); + await waitNextFrame(page); + [id] = await getCursorBlockIdAndHeight(page); + await assertBlockType(page, id, 'todo'); + await undoByClick(page); + await assertText(page, '[x] '); + await undoByClick(page); + await assertRichTexts(page, ['']); + + await waitNextFrame(page); + await type(page, '* '); + await waitNextFrame(page); + [id] = await getCursorBlockIdAndHeight(page); + await assertBlockType(page, id, 'bulleted'); + await undoByClick(page); + await assertText(page, '* '); + await undoByClick(page); + await assertRichTexts(page, ['']); + + await waitNextFrame(page); + await type(page, '- '); + await waitNextFrame(page); + [id] = await getCursorBlockIdAndHeight(page); + await assertBlockType(page, id, 'bulleted'); + await undoByClick(page); + await assertText(page, '- '); + await undoByClick(page); + await assertRichTexts(page, ['']); + + await waitNextFrame(page); + await type(page, '1. '); + await waitNextFrame(page); + [id] = await getCursorBlockIdAndHeight(page); + await assertBlockType(page, id, 'numbered'); + await undoByClick(page); + await assertText(page, '1. '); + await undoByClick(page); + await assertRichTexts(page, ['']); + + await waitNextFrame(page); + await type(page, '20. '); + await waitNextFrame(page); + [id] = await getCursorBlockIdAndHeight(page); + await assertBlockType(page, id, 'numbered'); + await undoByClick(page); + await assertText(page, '20. '); + await undoByClick(page); + await assertRichTexts(page, ['']); + + await waitNextFrame(page); + await type(page, '# '); + await waitNextFrame(page); + [id] = await getCursorBlockIdAndHeight(page); + await assertBlockType(page, id, 'h1'); + await undoByClick(page); + await assertText(page, '# '); + await undoByClick(page); + await assertRichTexts(page, ['']); + + await waitNextFrame(page); + await type(page, '## '); + await waitNextFrame(page); + [id] = await getCursorBlockIdAndHeight(page); + await assertBlockType(page, id, 'h2'); + await undoByClick(page); + await assertText(page, '## '); + await undoByClick(page); + await assertRichTexts(page, ['']); + + await waitNextFrame(page); + await type(page, '### '); + await waitNextFrame(page); + [id] = await getCursorBlockIdAndHeight(page); + await assertBlockType(page, id, 'h3'); + await undoByClick(page); + await assertText(page, '### '); + await undoByClick(page); + await assertRichTexts(page, ['']); + + await waitNextFrame(page); + await type(page, '#### '); + await waitNextFrame(page); + [id] = await getCursorBlockIdAndHeight(page); + await assertBlockType(page, id, 'h4'); + await undoByClick(page); + await assertText(page, '#### '); + await undoByClick(page); + await assertRichTexts(page, ['']); + + await waitNextFrame(page); + await type(page, '##### '); + await waitNextFrame(page); + [id] = await getCursorBlockIdAndHeight(page); + await assertBlockType(page, id, 'h5'); + await undoByClick(page); + await assertText(page, '##### '); + await undoByClick(page); + await assertRichTexts(page, ['']); + + await waitNextFrame(page); + await type(page, '###### '); + await waitNextFrame(page); + [id] = await getCursorBlockIdAndHeight(page); + await assertBlockType(page, id, 'h6'); + await undoByClick(page); + await assertText(page, '###### '); + await undoByClick(page); + await assertRichTexts(page, ['']); + + await waitNextFrame(page); + await type(page, '> '); + await waitNextFrame(page); + [id] = await getCursorBlockIdAndHeight(page); + await assertBlockType(page, id, 'quote'); + await undoByClick(page); + await assertText(page, '> '); + await undoByClick(page); + await assertRichTexts(page, ['']); + + await waitNextFrame(page); + await type(page, '--- '); + await undoByClick(page); + await assertRichTexts(page, ['--- ']); + await undoByClick(page); + await assertRichTexts(page, ['']); + await waitNextFrame(page); + await type(page, '*** '); + await undoByClick(page); + await assertRichTexts(page, ['*** ']); + await undoByClick(page); + await assertRichTexts(page, ['']); +}); + +test.describe('markdown inline-text', () => { + test.beforeEach(async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await resetHistory(page); + }); + + test('bolditalic', async ({ page }) => { + await type(page, 'aa***bb*** '); + await assertRichTextInlineDeltas(page, [ + { + insert: 'aa', + }, + { + insert: 'bb', + attributes: { + bold: true, + italic: true, + }, + }, + ]); + await undoByKeyboard(page); + await assertRichTextInlineRange(page, 0, 11); + await assertRichTextInlineDeltas(page, [ + { + insert: 'aa***bb*** ', + }, + ]); + await redoByKeyboard(page); + await type(page, 'cc'); + await assertRichTextInlineDeltas(page, [ + { + insert: 'aa', + }, + { + insert: 'bbcc', + attributes: { + bold: true, + italic: true, + }, + }, + ]); + await undoByKeyboard(page); + await undoByKeyboard(page); + await undoByKeyboard(page); + await assertRichTexts(page, ['']); + + await waitNextFrame(page); + await type(page, '***test *** '); + await assertRichTexts(page, ['***test *** ']); + await undoByKeyboard(page); + await assertRichTexts(page, ['']); + + // *** + space will be converted to divider, so needn't test this case here + // await waitNextFrame(page); + // await type(page, '*** test*** '); + // await assertRichTexts(page, ['*** test*** ']); + // await undoByKeyboard(page); + // await assertRichTexts(page, ['']); + }); + + test('bold', async ({ page }) => { + await type(page, 'aa**bb** '); + await assertRichTextInlineDeltas(page, [ + { + insert: 'aa', + }, + { + insert: 'bb', + attributes: { + bold: true, + }, + }, + ]); + await undoByKeyboard(page); + await assertRichTextInlineRange(page, 0, 9); + await assertRichTextInlineDeltas(page, [ + { + insert: 'aa**bb** ', + }, + ]); + await redoByKeyboard(page); + await type(page, 'cc'); + await assertRichTextInlineDeltas(page, [ + { + insert: 'aa', + }, + { + insert: 'bbcc', + attributes: { + bold: true, + }, + }, + ]); + await undoByKeyboard(page); + await undoByKeyboard(page); + await undoByKeyboard(page); + await assertRichTexts(page, ['']); + + await waitNextFrame(page); + await type(page, '**test ** '); + await assertRichTexts(page, ['**test ** ']); + await undoByKeyboard(page); + await assertRichTexts(page, ['']); + + await waitNextFrame(page); + await type(page, '** test** '); + await assertRichTexts(page, ['** test** ']); + await undoByKeyboard(page); + await assertRichTexts(page, ['']); + }); + + test('italic', async ({ page }) => { + await type(page, 'aa*bb* '); + await assertRichTextInlineDeltas(page, [ + { + insert: 'aa', + }, + { + insert: 'bb', + attributes: { + italic: true, + }, + }, + ]); + await undoByKeyboard(page); + await assertRichTextInlineRange(page, 0, 7); + await assertRichTextInlineDeltas(page, [ + { + insert: 'aa*bb* ', + }, + ]); + await redoByKeyboard(page); + await type(page, 'cc'); + await assertRichTextInlineDeltas(page, [ + { + insert: 'aa', + }, + { + insert: 'bbcc', + attributes: { + italic: true, + }, + }, + ]); + await undoByKeyboard(page); + await undoByKeyboard(page); + await undoByKeyboard(page); + await assertRichTexts(page, ['']); + + await waitNextFrame(page); + await type(page, '*test * '); + await assertRichTexts(page, ['*test * ']); + await undoByKeyboard(page); + await assertRichTexts(page, ['']); + + // * + space will be converted to bulleted list, so needn't test this case here + // await waitNextFrame(page); + // await type(page, '* test* '); + // await assertRichTexts(page, ['* test* ']); + // await undoByKeyboard(page); + // await assertRichTexts(page, ['']); + }); + + test('strike', async ({ page }) => { + await type(page, 'aa~~bb~~ '); + await assertRichTextInlineDeltas(page, [ + { + insert: 'aa', + }, + { + insert: 'bb', + attributes: { + strike: true, + }, + }, + ]); + await undoByKeyboard(page); + await assertRichTextInlineRange(page, 0, 9); + await assertRichTextInlineDeltas(page, [ + { + insert: 'aa~~bb~~ ', + }, + ]); + await redoByKeyboard(page); + await type(page, 'cc'); + await assertRichTextInlineDeltas(page, [ + { + insert: 'aa', + }, + { + insert: 'bbcc', + attributes: { + strike: true, + }, + }, + ]); + await undoByKeyboard(page); + await undoByKeyboard(page); + await undoByKeyboard(page); + await assertRichTexts(page, ['']); + + await waitNextFrame(page); + await type(page, '~~test ~~ '); + await assertRichTexts(page, ['~~test ~~ ']); + await undoByKeyboard(page); + await assertRichTexts(page, ['']); + + await waitNextFrame(page); + await type(page, '~~ test~~ '); + await assertRichTexts(page, ['~~ test~~ ']); + await undoByKeyboard(page); + await assertRichTexts(page, ['']); + }); + + test('underline', async ({ page }) => { + await type(page, 'aa~bb~ '); + await assertRichTextInlineDeltas(page, [ + { + insert: 'aa', + }, + { + insert: 'bb', + attributes: { + underline: true, + }, + }, + ]); + await undoByKeyboard(page); + await assertRichTextInlineRange(page, 0, 7); + await assertRichTextInlineDeltas(page, [ + { + insert: 'aa~bb~ ', + }, + ]); + await redoByKeyboard(page); + await type(page, 'cc'); + await assertRichTextInlineDeltas(page, [ + { + insert: 'aa', + }, + { + insert: 'bbcc', + attributes: { + underline: true, + }, + }, + ]); + await undoByKeyboard(page); + await undoByKeyboard(page); + await undoByKeyboard(page); + await assertRichTexts(page, ['']); + + await waitNextFrame(page); + await type(page, '~test ~ '); + await assertRichTexts(page, ['~test ~ ']); + await undoByKeyboard(page); + await assertRichTexts(page, ['']); + + await waitNextFrame(page); + await type(page, '~ test~ '); + await assertRichTexts(page, ['~ test~ ']); + await undoByKeyboard(page); + await assertRichTexts(page, ['']); + }); + + test('code', async ({ page }) => { + await type(page, 'aa`bb` '); + await assertRichTextInlineDeltas(page, [ + { + insert: 'aa', + }, + { + insert: 'bb', + attributes: { + code: true, + }, + }, + ]); + await undoByKeyboard(page); + await assertRichTextInlineRange(page, 0, 7); + await assertRichTextInlineDeltas(page, [ + { + insert: 'aa`bb` ', + }, + ]); + await redoByKeyboard(page); + await type(page, 'cc'); + await assertRichTextInlineDeltas(page, [ + { + insert: 'aa', + }, + { + insert: 'bb', + attributes: { + code: true, + }, + }, + { + insert: 'cc', + }, + ]); + await undoByKeyboard(page); + await undoByKeyboard(page); + await undoByKeyboard(page); + await assertRichTexts(page, ['']); + + await waitNextFrame(page); + await type(page, '`test ` '); + await assertRichTexts(page, ['`test ` ']); + await undoByKeyboard(page); + await assertRichTexts(page, ['']); + + await waitNextFrame(page); + await type(page, '` test` '); + await assertRichTexts(page, ['` test` ']); + await undoByKeyboard(page); + await assertRichTexts(page, ['']); + }); +}); + +test('inline code should work when pressing Enter followed by Backspace twice', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + await type(page, '`test`'); + await pressSpace(page); + await waitNextFrame(page); + await pressArrowLeft(page); + await waitNextFrame(page); + await pressEnter(page); + await waitNextFrame(page); + await pressBackspace(page); + await waitNextFrame(page); + await pressEnter(page); + await waitNextFrame(page); + await pressBackspace(page); + + await assertRichTexts(page, ['test']); +}); diff --git a/blocksuite/tests-legacy/multiple-editors/edgeless.spec.ts b/blocksuite/tests-legacy/multiple-editors/edgeless.spec.ts new file mode 100644 index 0000000000000..fa3d88fb02b46 --- /dev/null +++ b/blocksuite/tests-legacy/multiple-editors/edgeless.spec.ts @@ -0,0 +1,43 @@ +import { expect } from '@playwright/test'; + +import { + switchMultipleEditorsMode, + toggleMultipleEditors, +} from '../utils/actions/edgeless.js'; +import { + enterPlaygroundRoom, + initEmptyEdgelessState, + initThreeParagraphs, + waitNextFrame, +} from '../utils/actions/misc.js'; +import { test } from '../utils/playwright.js'; + +test('the shift pressing status should effect all editors', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await initThreeParagraphs(page); + await toggleMultipleEditors(page); + await switchMultipleEditorsMode(page); + + await waitNextFrame(page, 5000); + + const getShiftPressedStatus = async () => { + return page.evaluate(() => { + const edgelessBlocks = document.querySelectorAll('affine-edgeless-root'); + + return Array.from(edgelessBlocks).map(edgelessRoot => { + return edgelessRoot.gfx.keyboard.shiftKey$.peek(); + }); + }); + }; + + await page.keyboard.down('Shift'); + const pressed = await getShiftPressedStatus(); + expect(pressed).toEqual([true, true]); + + await page.keyboard.up('Shift'); + const released = await getShiftPressedStatus(); + expect(released).toEqual([false, false]); +}); diff --git a/blocksuite/tests-legacy/multiple-editors/selection.spec.ts b/blocksuite/tests-legacy/multiple-editors/selection.spec.ts new file mode 100644 index 0000000000000..7b42798ab880e --- /dev/null +++ b/blocksuite/tests-legacy/multiple-editors/selection.spec.ts @@ -0,0 +1,33 @@ +import { expect } from '@playwright/test'; + +import { dragBetweenCoords } from '../utils/actions/drag.js'; +import { toggleMultipleEditors } from '../utils/actions/edgeless.js'; +import { + enterPlaygroundRoom, + initEmptyParagraphState, + initThreeParagraphs, +} from '../utils/actions/misc.js'; +import { getRichTextBoundingBox } from '../utils/actions/selection.js'; +import { test } from '../utils/playwright.js'; + +test('should only show one format bar when multiple editors are toggled', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await toggleMultipleEditors(page); + + // Select some text + const box123 = await getRichTextBoundingBox(page, '2'); + const above123 = { x: box123.left + 10, y: box123.top + 2 }; + + const box789 = await getRichTextBoundingBox(page, '4'); + const bottomRight789 = { x: box789.right - 10, y: box789.bottom - 2 }; + + await dragBetweenCoords(page, above123, bottomRight789, { steps: 10 }); + + // should only show one format bar + const formatBar = page.locator('.affine-format-bar-widget'); + await expect(formatBar).toHaveCount(1); +}); diff --git a/blocksuite/tests-legacy/package.json b/blocksuite/tests-legacy/package.json new file mode 100644 index 0000000000000..be6c998777920 --- /dev/null +++ b/blocksuite/tests-legacy/package.json @@ -0,0 +1,20 @@ +{ + "name": "@blocksuite/legacy-e2e", + "private": true, + "type": "module", + "main": "index.js", + "scripts": { + "test": "yarn playwright test" + }, + "dependencies": { + "@blocksuite/affine-model": "workspace:*", + "@blocksuite/block-std": "workspace:*", + "@blocksuite/global": "workspace:*", + "@blocksuite/presets": "workspace:*", + "@playwright/test": "=1.49.1" + }, + "repository": { + "type": "git", + "url": "https://github.com/toeverything/blocksuite.git" + } +} diff --git a/blocksuite/tests-legacy/paragraph.spec.ts b/blocksuite/tests-legacy/paragraph.spec.ts new file mode 100644 index 0000000000000..9c8a373170e65 --- /dev/null +++ b/blocksuite/tests-legacy/paragraph.spec.ts @@ -0,0 +1,1334 @@ +import type { DeltaInsert } from '@inline/types.js'; +import { expect } from '@playwright/test'; + +import { + captureHistory, + dragBetweenIndices, + dragOverTitle, + enterPlaygroundRoom, + focusRichText, + focusTitle, + getIndexCoordinate, + getPageSnapshot, + initEmptyEdgelessState, + initEmptyParagraphState, + initThreeDividers, + initThreeParagraphs, + pressArrowDown, + pressArrowLeft, + pressArrowRight, + pressArrowUp, + pressBackspace, + pressBackspaceWithShortKey, + pressEnter, + pressForwardDelete, + pressShiftEnter, + pressShiftTab, + pressSpace, + pressTab, + redoByClick, + redoByKeyboard, + resetHistory, + setSelection, + SHORT_KEY, + switchReadonly, + type, + undoByClick, + undoByKeyboard, + updateBlockType, + waitDefaultPageLoaded, + waitNextFrame, +} from './utils/actions/index.js'; +import { + assertBlockChildrenFlavours, + assertBlockChildrenIds, + assertBlockCount, + assertBlockSelections, + assertBlockType, + assertClassName, + assertDivider, + assertDocTitleFocus, + assertRichTextInlineRange, + assertRichTexts, + assertStoreMatchJSX, + assertTitle, +} from './utils/asserts.js'; +import { test } from './utils/playwright.js'; + +test('init paragraph by page title enter at last', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await waitDefaultPageLoaded(page); + await focusTitle(page); + await type(page, 'hello'); + await pressEnter(page); + await type(page, 'world'); + + await assertTitle(page, 'hello'); + await assertRichTexts(page, ['world', '']); + + //#region Fixes: https://github.com/toeverything/blocksuite/issues/1007 + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/toeverything/blocksuite/issues/1007', + }); + await page.keyboard.press('ArrowLeft'); + await focusTitle(page); + await pressEnter(page); + await assertRichTexts(page, ['', 'world', '']); + //#endregion +}); + +test('init paragraph by page title enter in middle', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await waitDefaultPageLoaded(page); + await focusTitle(page); + await type(page, 'hello'); + await page.keyboard.press('ArrowLeft'); + await page.keyboard.press('ArrowLeft'); + await page.keyboard.press('ArrowLeft'); + await pressEnter(page); + + await assertTitle(page, 'he'); + await assertRichTexts(page, ['llo', '']); +}); + +test('drag over paragraph title', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await waitDefaultPageLoaded(page); + await focusTitle(page); + await type(page, 'hello'); + await assertTitle(page, 'hello'); + await resetHistory(page); + + await dragOverTitle(page); + await page.keyboard.press('Backspace', { delay: 100 }); + await assertTitle(page, ''); + + await undoByKeyboard(page); + await assertTitle(page, 'hello'); +}); + +test('backspace and arrow on title', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await waitDefaultPageLoaded(page); + await focusTitle(page); + await type(page, 'hello'); + await assertTitle(page, 'hello'); + await resetHistory(page); + + await pressBackspace(page); + await assertTitle(page, 'hell'); + + await pressArrowLeft(page, 2); + await captureHistory(page); + await pressBackspace(page); + await assertTitle(page, 'hll'); + + await pressArrowDown(page); + await assertRichTextInlineRange(page, 0, 0, 0); + + await undoByKeyboard(page); + await assertTitle(page, 'hell'); + + await redoByClick(page); + await assertTitle(page, 'hll'); +}); + +for (const { initState, desc } of [ + { + initState: initEmptyParagraphState, + desc: 'without surface', + }, + { + initState: initEmptyEdgelessState, + desc: 'with surface', + }, +]) { + test(`backspace on line start of the first block (${desc})`, async ({ + page, + }) => { + await enterPlaygroundRoom(page); + await initState(page); + await waitDefaultPageLoaded(page); + await focusTitle(page); + await type(page, 'hello'); + await assertTitle(page, 'hello'); + await resetHistory(page); + + await focusRichText(page, 0); + await type(page, 'abc'); + await page.keyboard.press('ArrowLeft'); + await page.keyboard.press('ArrowLeft'); + await page.keyboard.press('ArrowLeft'); + await page.waitForTimeout(300); + await assertRichTextInlineRange(page, 0, 0, 0); + + await pressBackspace(page); + await assertTitle(page, 'helloabc'); + + await pressEnter(page); + await assertTitle(page, 'hello'); + await assertRichTexts(page, ['abc', '']); + + await pressBackspace(page); + await assertTitle(page, 'helloabc'); + await assertRichTexts(page, ['']); + await undoByClick(page); + await assertTitle(page, 'hello'); + await assertRichTexts(page, ['abc', '']); + + await redoByClick(page); + await assertTitle(page, 'helloabc'); + await assertRichTexts(page, ['']); + }); + + test(`backspace on line start of the first empty block (${desc})`, async ({ + page, + }) => { + await enterPlaygroundRoom(page); + await initState(page); + await focusTitle(page); + + await pressArrowDown(page); + await pressBackspace(page); + await assertBlockCount(page, 'paragraph', 1); + + await pressArrowDown(page); + await assertRichTextInlineRange(page, 0, 0, 0); + }); +} + +test('append new paragraph block by enter', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'hello'); + await assertRichTextInlineRange(page, 0, 5, 0); + + await pressEnter(page); + await assertRichTexts(page, ['hello', '']); + await assertRichTextInlineRange(page, 1, 0, 0); + + await undoByKeyboard(page); + await waitNextFrame(page); + await assertRichTexts(page, ['hello']); + await assertRichTextInlineRange(page, 0, 5, 0); + + await redoByKeyboard(page); + await waitNextFrame(page); + await assertRichTexts(page, ['hello', '']); + await assertRichTextInlineRange(page, 1, 0, 0); +}); + +test('insert new paragraph block by enter', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await pressEnter(page); + await pressEnter(page); + await assertRichTexts(page, ['', '', '']); + + await focusRichText(page, 1); + await type(page, 'hello'); + await assertRichTexts(page, ['', 'hello', '']); + + await pressEnter(page); + await type(page, 'world'); + await assertRichTexts(page, ['', 'hello', 'world', '']); + await assertBlockChildrenFlavours(page, '1', [ + 'affine:paragraph', + 'affine:paragraph', + 'affine:paragraph', + 'affine:paragraph', + ]); +}); + +test('split paragraph block by enter', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + await type(page, 'hello'); + await assertRichTexts(page, ['hello']); + + await pressArrowLeft(page, 3); + await page.waitForTimeout(300); + await assertRichTextInlineRange(page, 0, 2, 0); + + await pressEnter(page); + await assertRichTexts(page, ['he', 'llo']); + await assertBlockChildrenFlavours(page, '1', [ + 'affine:paragraph', + 'affine:paragraph', + ]); + await assertRichTextInlineRange(page, 1, 0, 0); + + await undoByKeyboard(page); + await assertRichTexts(page, ['hello']); + + await redoByKeyboard(page); + await assertRichTexts(page, ['he', 'llo']); +}); + +test('split paragraph block with selected text by enter', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + await type(page, 'hello'); + await assertRichTexts(page, ['hello']); + // Avoid Yjs history manager merge two operations + await captureHistory(page); + + // select 'll' + await page.keyboard.press('ArrowLeft'); + await page.keyboard.down('Shift'); + await page.waitForTimeout(100); + await page.keyboard.press('ArrowLeft'); + await page.keyboard.press('ArrowLeft'); + await page.waitForTimeout(300); + await page.keyboard.up('Shift'); + await assertRichTextInlineRange(page, 0, 2, 2); + + await pressEnter(page); + await assertRichTexts(page, ['he', 'o']); + await assertBlockChildrenFlavours(page, '1', [ + 'affine:paragraph', + 'affine:paragraph', + ]); + await assertRichTextInlineRange(page, 1, 0, 0); + + await undoByKeyboard(page); + await assertRichTexts(page, ['hello']); + + await redoByKeyboard(page); + await assertRichTexts(page, ['he', 'o']); +}); + +test('add multi line by soft enter', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + await type(page, 'hello'); + await assertRichTexts(page, ['hello']); + + await pressArrowLeft(page, 3); + await assertRichTextInlineRange(page, 0, 2, 0); + // Avoid Yjs history manager merge two operations + await captureHistory(page); + + await pressShiftEnter(page); + await assertRichTexts(page, ['he\nllo']); + await assertRichTextInlineRange(page, 0, 3, 0); + + await undoByKeyboard(page); + await assertRichTexts(page, ['hello']); + + await redoByKeyboard(page); + await assertRichTexts(page, ['he\nllo']); +}); + +test('indent and unindent existing paragraph block', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'hello'); + + await pressEnter(page); + await focusRichText(page, 1); + await type(page, 'world'); + await assertRichTexts(page, ['hello', 'world']); + + // indent + await page.keyboard.press('Tab'); + await assertRichTexts(page, ['hello', 'world']); + await assertBlockChildrenIds(page, '1', ['2']); + await assertBlockChildrenIds(page, '2', ['3']); + + // unindent + await pressShiftTab(page); + await assertRichTexts(page, ['hello', 'world']); + await assertBlockChildrenIds(page, '1', ['2', '3']); + + await undoByKeyboard(page); + await assertBlockChildrenIds(page, '1', ['2']); + + await redoByKeyboard(page); + await assertBlockChildrenIds(page, '1', ['2', '3']); +}); + +test('remove all indent for a paragraph block', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'hello'); + await pressEnter(page); + await pressTab(page); + await type(page, 'world'); + await pressEnter(page); + await pressTab(page); + await type(page, 'foo'); + await assertBlockChildrenIds(page, '3', ['4']); + await assertRichTexts(page, ['hello', 'world', 'foo']); + await pressBackspaceWithShortKey(page); + await assertRichTexts(page, ['hello', 'world', '']); + await pressBackspaceWithShortKey(page); + await assertBlockChildrenIds(page, '1', ['2', '4']); + await assertBlockChildrenIds(page, '2', ['3']); +}); + +test('update paragraph with children to head type', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'aaa'); + + await pressEnter(page); + await focusRichText(page, 1); + await type(page, 'bbb'); + await pressEnter(page); + await focusRichText(page, 2); + await type(page, 'ccc'); + await assertRichTexts(page, ['aaa', 'bbb', 'ccc']); + + // aaa + // bbc + // ccc + await focusRichText(page, 1); + await page.keyboard.press('Tab'); + await focusRichText(page, 2); + await page.keyboard.press('Tab'); + await assertRichTexts(page, ['aaa', 'bbb', 'ccc']); + await assertBlockChildrenIds(page, '1', ['2']); + await assertBlockChildrenIds(page, '2', ['3', '4']); + + await focusRichText(page); + await pressArrowLeft(page, 3); + + await type(page, '# '); + + await assertRichTexts(page, ['aaa', 'bbb', 'ccc']); + await assertBlockChildrenIds(page, '2', ['3', '4']); + + await undoByKeyboard(page); + await assertRichTexts(page, ['# aaa', 'bbb', 'ccc']); + await assertBlockChildrenIds(page, '1', ['2']); + await assertBlockChildrenIds(page, '2', ['3', '4']); + + await redoByKeyboard(page); + await assertRichTexts(page, ['aaa', 'bbb', 'ccc']); + await assertBlockChildrenIds(page, '2', ['3', '4']); +}); + +test('should indent and unindent works with children', async ({ + page, +}, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await pressEnter(page); + await type(page, '012'); + await pressEnter(page); + await type(page, '345'); + // 123 + // 456 + // 789 + // 012 + // 345 + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_init.json` + ); + //-- test indent --// + + // Focus 789 + await focusRichText(page, 2); + await pressTab(page); + // 123 + // 456 + // 789| + // 012 + // 345 + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_indent.json` + ); + + // Focus 012 + await focusRichText(page, 3); + await pressTab(page); + // 123 + // 456 + // 789 + // 012| + // 345 + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_indent_2.json` + ); + + // Focus 456 + await focusRichText(page, 1); + await pressTab(page); + // 123 + // 456| + // 789 + // 012 + // 345 + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_indent_3.json` + ); + + // Focus 345 + await focusRichText(page, 4); + await pressTab(page, 3); + // 123 + // 456 + // 789 + // 012 + // 345| + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_indent_4.json` + ); + //-- test unindent --// + + // Focus 456 + await focusRichText(page, 1); + await pressShiftTab(page); + // 123 + // 456| + // 789 + // 012 + // 345 + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_unindent_1.json` + ); + + // Focus 012 + await focusRichText(page, 3); + await pressShiftTab(page); + // 123 + // 456 + // 789 + // 012| + // 345 + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_unindent_2.json` + ); + + // Focus 789 + await focusRichText(page, 2); + await pressShiftTab(page); + // 123 + // 456 + // 789| + // 012 + // 345 + + // Focus 345 + await focusRichText(page, 4); + await pressShiftTab(page); + // 123 + // 456 + // 789 + // 012 + // 345| + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_unindent_3.json` + ); +}); + +test('paragraph with child block should work at enter', async ({ + page, +}, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, '123'); + await pressEnter(page); + await type(page, '456'); + + await focusRichText(page, 1); + await page.keyboard.press('Tab'); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_init.json` + ); + await focusRichText(page, 0); + await pressEnter(page); + await type(page, '789'); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_final.json` + ); +}); + +test('should delete paragraph block child can hold cursor in correct position', async ({ + page, +}, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, '123'); + await pressEnter(page); + await pressTab(page); + await waitNextFrame(page); + await type(page, '4'); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_init.json` + ); + + await pressBackspace(page, 2); + await waitNextFrame(page); + await type(page, 'now'); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_final.json` + ); +}); + +test('switch between paragraph types', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'hello'); + + const selector = '.affine-paragraph-rich-text-wrapper'; + + await updateBlockType(page, 'affine:paragraph', 'h1'); + await assertClassName(page, selector, /h1/); + + await updateBlockType(page, 'affine:paragraph', 'h2'); + await assertClassName(page, selector, /h2/); + + await updateBlockType(page, 'affine:paragraph', 'h3'); + await assertClassName(page, selector, /h3/); + + await undoByClick(page); + await assertClassName(page, selector, /h2/); + + await undoByClick(page); + await assertClassName(page, selector, /h1/); +}); + +test('delete at start of paragraph block', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'hello'); + + await pressEnter(page); + await type(page, 'a'); + + await updateBlockType(page, 'affine:paragraph', 'h1'); + await focusRichText(page, 1); + await assertBlockType(page, '2', 'text'); + await assertBlockType(page, '3', 'h1'); + + await pressBackspace(page); + await pressBackspace(page); + await assertBlockType(page, '3', 'text'); + await assertBlockChildrenIds(page, '1', ['2', '3']); + + await page.keyboard.press('Backspace'); + await assertBlockChildrenIds(page, '1', ['2']); + + await undoByClick(page); + await assertBlockChildrenIds(page, '1', ['2', '3']); +}); + +test('delete at start of paragraph immediately following list', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'hello'); + + await pressEnter(page); + await type(page, 'a'); + + await captureHistory(page); + + await assertRichTexts(page, ['hello', 'a']); + await assertBlockChildrenIds(page, '1', ['2', '3']); + await assertBlockType(page, '3', 'text'); + + // text -> bulleted + await focusRichText(page, 1); + await updateBlockType(page, 'affine:list', 'bulleted'); + await assertBlockType(page, '2', 'text'); + await assertBlockType(page, '4', 'bulleted'); + await pressBackspace(page, 2); + await waitNextFrame(page); + await assertBlockType(page, '5', 'text'); + await assertBlockChildrenIds(page, '1', ['2', '5']); + await pressBackspace(page); + await assertBlockChildrenIds(page, '1', ['2']); + + // reset + await undoByClick(page); + await undoByClick(page); + await assertRichTexts(page, ['hello', 'a']); + await assertBlockChildrenIds(page, '1', ['2', '3']); + await assertBlockType(page, '3', 'text'); + + // text -> numbered + await focusRichText(page, 1); + await updateBlockType(page, 'affine:list', 'numbered'); + await assertBlockType(page, '2', 'text'); + await assertBlockType(page, '6', 'numbered'); + await pressBackspace(page, 2); + await waitNextFrame(page); + await assertBlockType(page, '7', 'text'); + await assertBlockChildrenIds(page, '1', ['2', '7']); + await pressBackspace(page); + await assertBlockChildrenIds(page, '1', ['2']); + + // reset + await undoByClick(page); + await undoByClick(page); + await assertRichTexts(page, ['hello', 'a']); + await assertBlockChildrenIds(page, '1', ['2', '3']); + await assertBlockType(page, '3', 'text'); + + // text -> todo + await focusRichText(page, 1); + await updateBlockType(page, 'affine:list', 'todo'); + await assertBlockType(page, '2', 'text'); + await assertBlockType(page, '8', 'todo'); + await pressBackspace(page, 2); + await waitNextFrame(page); + await assertBlockType(page, '9', 'text'); + await assertBlockChildrenIds(page, '1', ['2', '9']); + await pressBackspace(page); + await assertBlockChildrenIds(page, '1', ['2']); +}); + +test('delete at start of paragraph with content', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, '123'); + + await pressEnter(page); + await type(page, '456'); + await assertRichTexts(page, ['123', '456']); + + await captureHistory(page); + + await pressArrowLeft(page, 3); + await assertRichTextInlineRange(page, 1, 0, 0); + + await pressBackspace(page); + await assertRichTexts(page, ['123456']); + + await undoByClick(page); + await assertRichTexts(page, ['123', '456']); +}); + +test('get focus from page title enter', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusTitle(page); + await type(page, 'hello'); + await assertRichTexts(page, ['']); + + await pressEnter(page); + await type(page, 'world'); + await assertRichTexts(page, ['world', '']); +}); + +test('handling keyup when cursor located in first paragraph', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusTitle(page); + await type(page, 'hello'); + await assertRichTexts(page, ['']); + + await pressEnter(page); + await type(page, 'world'); + await assertRichTexts(page, ['world', '']); + await pressArrowUp(page); + await waitNextFrame(page); + await pressArrowUp(page); + await assertDocTitleFocus(page); +}); + +test('after deleting a text row, cursor should jump to the end of previous list row', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'hello'); + await assertRichTextInlineRange(page, 0, 5, 0); + + await pressEnter(page); + await type(page, 'w'); + await assertRichTexts(page, ['hello', 'w']); + await assertRichTextInlineRange(page, 1, 1, 0); + await pressArrowUp(page); + await pressArrowDown(page); + + await pressArrowLeft(page); + await pressBackspace(page); + await assertRichTextInlineRange(page, 0, 5, 0); +}); + +test('press tab in paragraph children', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await waitDefaultPageLoaded(page); + await focusTitle(page); + await pressEnter(page); + await type(page, '1'); + await pressEnter(page); + await pressTab(page); + await type(page, '2'); + await pressEnter(page); + await pressTab(page); + await type(page, '3'); + await page.keyboard.press('ArrowUp', { delay: 50 }); + await page.keyboard.press('ArrowLeft', { delay: 50 }); + await type(page, '- '); + await assertRichTexts(page, ['1', '2', '3', '']); +}); + +test('press left in first paragraph start should not change cursor position', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, '1'); + + await pressArrowLeft(page, 2); + await type(page, 'l'); + await assertRichTexts(page, ['l1']); + await assertTitle(page, ''); +}); + +test('press arrow down should move caret to the start of line', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await page.evaluate(() => { + const { doc } = window; + const rootId = doc.addBlock('affine:page', { + title: new doc.Text(), + }); + const note = doc.addBlock('affine:note', {}, rootId); + doc.addBlock( + 'affine:paragraph', + { + text: new doc.Text('0'.repeat(100)), + }, + note + ); + doc.addBlock( + 'affine:paragraph', + { + text: new doc.Text('1'), + }, + note + ); + }); + + // Focus the empty child paragraph + await focusRichText(page, 1); + await pressArrowLeft(page); + await pressArrowUp(page); + await pressArrowDown(page); + await type(page, '2'); + await assertRichTexts(page, ['0'.repeat(100), '21']); +}); + +test('press arrow up in the second line should move caret to the first line', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await page.evaluate(() => { + const { doc } = window; + const rootId = doc.addBlock('affine:page', { + title: new doc.Text(), + }); + const note = doc.addBlock('affine:note', {}, rootId); + const delta = Array.from({ length: 150 }, (_, i) => { + return i % 2 === 0 + ? { insert: 'i', attributes: { italic: true } } + : { insert: 'b', attributes: { bold: true } }; + }) as DeltaInsert[]; + const text = new doc.Text(delta); + doc.addBlock('affine:paragraph', { text }, note); + doc.addBlock('affine:paragraph', {}, note); + }); + + // Focus the empty paragraph + await focusRichText(page, 1); + await assertRichTexts(page, ['ib'.repeat(75), '']); + await pressArrowUp(page, 2); + await type(page, '0'); + await assertTitle(page, ''); + await assertRichTexts(page, ['0' + 'ib'.repeat(75), '']); + await pressArrowUp(page, 2); + + // At title + await type(page, '1'); + await assertTitle(page, '1'); + await assertRichTexts(page, ['0' + 'ib'.repeat(75), '']); + + // At the first line of the first paragraph + await pressArrowDown(page); + // At the second paragraph + await pressArrowDown(page, 3); + await pressArrowRight(page); + await type(page, '2'); + + await assertRichTexts(page, ['0' + 'ib'.repeat(75), '2']); + + // Go to the start of the second paragraph + await pressArrowLeft(page); + await pressArrowUp(page); + await pressArrowDown(page); + // Should be inserted at the start of the second paragraph + await type(page, '3'); + await assertRichTexts(page, ['0' + 'ib'.repeat(75), '32']); +}); + +test('press arrow down in indent line should not move caret to the start of line', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await page.evaluate(() => { + const { doc } = window; + const rootId = doc.addBlock('affine:page', { + title: new doc.Text(), + }); + const note = doc.addBlock('affine:note', {}, rootId); + const p1 = doc.addBlock('affine:paragraph', {}, note); + const p2 = doc.addBlock('affine:paragraph', {}, p1); + doc.addBlock('affine:paragraph', {}, p2); + doc.addBlock( + 'affine:paragraph', + { + text: new doc.Text('0'), + }, + note + ); + }); + + // Focus the empty child paragraph + await focusRichText(page, 2); + await pressArrowDown(page, 2); + await pressArrowRight(page); + await waitNextFrame(page); + // Now the caret should be at the end of the last paragraph + await type(page, '1'); + await assertRichTexts(page, ['', '', '', '01']); + + await focusRichText(page, 2); + await waitNextFrame(page); + // Insert a new long text to wrap the line + await page.keyboard.insertText('0'.repeat(100)); + await waitNextFrame(page); + + await focusRichText(page, 1); + // Through long text + await pressArrowDown(page, 3); + await pressArrowRight(page); + await type(page, '2'); + await assertRichTexts(page, ['', '', '0'.repeat(100), '012']); +}); + +test('should placeholder works', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + const placeholder = page.locator('.affine-paragraph-placeholder.visible'); + await expect(placeholder).toBeVisible(); + await expect(placeholder).toHaveCount(1); + await expect(placeholder).toContainText("Type '/' for commands"); + + await type(page, '1'); + await expect(placeholder).not.toBeVisible(); + await pressBackspace(page); + + await expect(placeholder).toBeVisible(); + await updateBlockType(page, 'affine:paragraph', 'h1'); + + await expect(placeholder).toBeVisible(); + await expect(placeholder).toHaveText('Heading 1'); + await updateBlockType(page, 'affine:paragraph', 'text'); + await focusRichText(page, 0); + await expect(placeholder).toBeVisible(); + await expect(placeholder).toContainText("Type '/' for commands"); + + await pressEnter(page); + await expect(placeholder).toHaveCount(1); +}); + +test('should placeholder not show when multiple blocks are selected', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await pressEnter(page); + await assertRichTexts(page, ['', '']); + const coord = await getIndexCoordinate(page, [0, 0]); + // blur + await page.mouse.click(0, 0); + await page.mouse.move(coord.x - 26 - 24, coord.y - 10, { steps: 20 }); + await page.mouse.down(); + // ← + await page.mouse.move(coord.x + 20, coord.y + 50, { steps: 20 }); + await page.mouse.up(); + const placeholder = page.locator('.affine-paragraph-placeholder.visible'); + await expect(placeholder).toBeHidden(); +}); + +test.describe('press ArrowDown when cursor is at the last line of a block', () => { + test.beforeEach(async ({ page }) => { + await enterPlaygroundRoom(page); + await page.evaluate(() => { + const { doc } = window; + const rootId = doc.addBlock('affine:page', { + title: new doc.Text(), + }); + const note = doc.addBlock('affine:note', {}, rootId); + doc.addBlock( + 'affine:paragraph', + { + text: new doc.Text('This is the 2nd last block.'), + }, + note + ); + doc.addBlock( + 'affine:paragraph', + { + text: new doc.Text('This is the last block.'), + }, + note + ); + }); + }); + + test('move cursor to next block if this block is _not_ the last block in the page', async ({ + page, + }) => { + // Click at the top-left corner of the 2nd last block to place the cursor at its start + await focusRichText(page, 0, { clickPosition: { x: 0, y: 0 } }); + // Cursor should have been moved to the start of the last block. + await pressArrowDown(page); + await type(page, "I'm here. "); + await assertRichTexts(page, [ + 'This is the 2nd last block.', + "I'm here. This is the last block.", + ]); + }); + test('move cursor to the end of line if the block is the last block in the page', async ({ + page, + }) => { + // Click at the top-left corner of the last block to place the cursor at its start + await focusRichText(page, 1, { clickPosition: { x: 0, y: 0 } }); + // Cursor should have been moved to the end of the only line. + await pressArrowDown(page, 2); + await pressArrowRight(page); + await type(page, " I'm here."); + await assertRichTexts(page, [ + 'This is the 2nd last block.', + "This is the last block. I'm here.", + ]); + }); +}); + +test('delete empty text paragraph block should keep children blocks when following custom blocks', async ({ + page, +}, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, '--- '); + await assertDivider(page, 1); + + // Click blank area to add a paragraph block after divider + await page.mouse.click(100, 200); + await page.waitForTimeout(200); + + // Add to paragraph blocks + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + // Indent the second paragraph block + await focusRichText(page, 2); + await pressTab(page); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_init.json` + ); + + // Delete the parent paragraph block + await focusRichText(page, 1); + await pressBackspace(page, 4); + + await assertRichTexts(page, ['123', '789']); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_final.json` + ); +}); + +test('delete first paragraph with children should keep children blocks', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, '123'); + await pressEnter(page); + await pressTab(page); + await waitNextFrame(page); + await type(page, '456'); + await setSelection(page, 2, 1, 2, 1); + await pressBackspace(page, 2); + await waitNextFrame(page); + await assertTitle(page, '23'); + await assertRichTexts(page, ['456']); +}); + +test('paragraph indent and delete in line start', async ({ + page, +}, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'abc'); + await pressEnter(page); + await pressTab(page); + await type(page, 'efg'); + await pressEnter(page); + await pressTab(page); + await type(page, 'hij'); + await pressEnter(page); + await pressShiftTab(page); + await type(page, 'klm'); + await pressEnter(page); + await type(page, 'nop'); + await setSelection(page, 3, 1, 3, 1); + // abc + // e|fg + // hij + // klm + // nop + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_init.json` + ); + + await pressBackspace(page, 2); + await setSelection(page, 5, 1, 5, 1); + // abcfg + // hij + // k|lm + // nop + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_after_press_backspace.json` + ); + + await pressBackspace(page, 2); + await setSelection(page, 6, 1, 6, 1); + // abcfg + // hijlm + // n|op + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_after_press_backspace_2.json` + ); + + await pressBackspace(page, 2); + // abcfg + // hijlm + // |op + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_after_press_backspace_3.json` + ); +}); + +test('delete at the start of paragraph (multiple notes)', async ({ page }) => { + await enterPlaygroundRoom(page); + await page.evaluate(() => { + const { doc } = window; + + const rootId = doc.addBlock('affine:page', { + title: new doc.Text(), + }); + doc.addBlock('affine:surface', {}, rootId); + + ['123', '456'].forEach(text => { + const noteId = doc.addBlock('affine:note', {}, rootId); + doc.addBlock( + 'affine:paragraph', + { + text: new doc.Text(text), + }, + noteId + ); + }); + + doc.resetHistory(); + }); + + await assertBlockCount(page, 'note', 2); + + await assertRichTexts(page, ['123', '456']); + await focusRichText(page, 1); + await pressArrowLeft(page, 3); + await pressBackspace(page); + await assertRichTexts(page, ['123456']); +}); + +test('arrow up/down navigation within and across paragraphs containing different types of text', async ({ + page, +}) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/toeverything/blocksuite/issues/5155', + }); + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + await type(page, 'a'.repeat(20)); + await assertRichTextInlineRange(page, 0, 20, 0); + await type(page, '*'); + await type(page, 'i'.repeat(5)); + await type(page, '*'); + await pressSpace(page); + await assertRichTextInlineRange(page, 0, 25, 0); + await type(page, 'a'.repeat(100)); + await assertRichTextInlineRange(page, 0, 125, 0); + await pressEnter(page); + + await type(page, 'a'.repeat(100)); + await assertRichTextInlineRange(page, 1, 100, 0); + await type(page, '*'); + await type(page, 'i'.repeat(5)); + await type(page, '*'); + await pressSpace(page); + await assertRichTextInlineRange(page, 1, 105, 0); + await type(page, 'a'.repeat(20)); + await assertRichTextInlineRange(page, 1, 125, 0); + + await pressArrowUp(page); + await assertRichTextInlineRange(page, 1, 32, 0); + await pressArrowUp(page); + await assertRichTextInlineRange(page, 0, 125, 0); + await pressArrowUp(page); + await assertRichTextInlineRange(page, 0, 35, 0); + await pressArrowUp(page); + await assertRichTextInlineRange(page, 0, 0, 0); + await pressArrowDown(page); + await assertRichTextInlineRange(page, 0, 125, 0); + await pressArrowDown(page); + await assertRichTextInlineRange(page, 1, 32, 0); + await pressArrowDown(page); + await assertRichTextInlineRange(page, 1, 125, 0); +}); + +test('select divider using delete keyboard from prev/next paragraph', async ({ + page, +}) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/toeverything/blocksuite/issues/4547', + }); + + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + + await initThreeDividers(page); + await assertDivider(page, 3); + await assertRichTexts(page, ['123', '123']); + + await focusRichText(page, 0); + await pressForwardDelete(page); + await assertBlockSelections(page, ['4']); + await assertDivider(page, 3); + + await focusRichText(page, 1); + await pressArrowLeft(page, 3); + await pressBackspace(page); + await assertBlockSelections(page, ['6']); + await assertDivider(page, 3); + + await assertRichTexts(page, ['123', '123']); +}); + +test.describe('readonly mode', () => { + test('should placeholder not show at readonly mode', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + await pressEnter(page); + await updateBlockType(page, 'affine:paragraph', 'h1'); + + const placeholder = page.locator('.affine-paragraph-placeholder.visible'); + + await switchReadonly(page); + await focusRichText(page, 0); + await expect(placeholder).toBeHidden(); + + await focusRichText(page, 1); + await expect(placeholder).toBeHidden(); + }); + + test('should readonly mode not be able to modify text', async ({ page }) => { + await enterPlaygroundRoom(page); + const { paragraphId } = await initEmptyParagraphState(page); + + await focusRichText(page); + await type(page, 'hello'); + await switchReadonly(page); + + await pressBackspace(page, 5); + await type(page, 'world'); + await dragBetweenIndices(page, [0, 1], [0, 3]); + await page.keyboard.press(`${SHORT_KEY}+b`); + await assertStoreMatchJSX( + page, + ` +`, + paragraphId + ); + + await undoByKeyboard(page); + await assertStoreMatchJSX( + page, + ` +`, + paragraphId + ); + }); +}); diff --git a/blocksuite/tests-legacy/playwright.config.ts b/blocksuite/tests-legacy/playwright.config.ts new file mode 100644 index 0000000000000..15fe0b095dfd9 --- /dev/null +++ b/blocksuite/tests-legacy/playwright.config.ts @@ -0,0 +1,46 @@ +import process from 'node:process'; + +import type { PlaywrightWorkerOptions } from '@playwright/test'; +import { defineConfig } from '@playwright/test'; + +export default defineConfig({ + testDir: '.', + timeout: process.env.CI ? 40000 : 999999, + fullyParallel: true, + snapshotDir: 'snapshots', + snapshotPathTemplate: 'snapshots/{testFilePath}/{arg}{ext}', + webServer: { + command: process.env.CI + ? 'yarn workspace @blocksuite/playground run preview' + : 'yarn workspace @blocksuite/playground run dev', + port: process.env.CI ? 4173 : 5173, + reuseExistingServer: !process.env.CI, + env: { + COVERAGE: process.env.COVERAGE ?? '', + }, + }, + use: { + browserName: + (process.env.BROWSER as PlaywrightWorkerOptions['browserName']) ?? + 'chromium', + viewport: { width: 960, height: 900 }, + // Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer + // You can open traces locally(`npx playwright show-trace trace.zip`) + // or in your browser on [Playwright Trace Viewer](https://trace.playwright.dev/). + trace: 'on-first-retry', + // Record video only when retrying a test for the first time. + video: 'on-first-retry', + // Timeout for each action + actionTimeout: 5_000, + permissions: + process.env.BROWSER && process.env.BROWSER !== 'chromium' + ? [] + : ['clipboard-read', 'clipboard-write'], + }, + workers: '80%', + retries: process.env.CI ? 3 : 0, + // 'github' for GitHub Actions CI to generate annotations, plus a concise 'dot' + // default 'list' when running locally + // See https://playwright.dev/docs/test-reporters#github-actions-annotations + reporter: process.env.CI ? 'github' : 'list', +}); diff --git a/blocksuite/tests-legacy/quote.spec.ts b/blocksuite/tests-legacy/quote.spec.ts new file mode 100644 index 0000000000000..32add78bad150 --- /dev/null +++ b/blocksuite/tests-legacy/quote.spec.ts @@ -0,0 +1,102 @@ +import { + enterPlaygroundRoom, + focusRichText, + initEmptyParagraphState, + pressArrowDown, + pressArrowRight, + pressArrowUp, + pressEnter, + type, +} from './utils/actions/index.js'; +import { + assertRichTextInlineRange, + assertTextContain, +} from './utils/asserts.js'; +import { test } from './utils/playwright.js'; + +test('prohibit creating divider within quote', async ({ page }) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/toeverything/blocksuite/issues/995', + }); + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, '>'); + await page.keyboard.press('Space', { delay: 50 }); + await focusRichText(page); + await type(page, '123'); + await pressEnter(page); + await type(page, '---'); + await page.keyboard.press('Space', { delay: 50 }); + await assertTextContain(page, '---'); +}); + +test('quote arrow up/down', async ({ page }) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/toeverything/blocksuite/issues/2834', + }); + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + await type(page, 'aaaaa'); + await pressEnter(page); + await type(page, 'aaaaa'); + await pressEnter(page); + await type(page, 'aaa'); + await pressEnter(page); + await type(page, '> aaaaaaaaa'); + await pressEnter(page); + await type(page, 'aaa'); + await pressEnter(page); + await type(page, 'aaaaaaaaa'); + await pressEnter(page); + await pressEnter(page); + await type(page, 'aaaaa'); + await pressEnter(page); + await type(page, 'aaaaa'); + await pressEnter(page); + await type(page, 'aaa'); + + await assertRichTextInlineRange(page, 6, 3, 0); + await pressArrowUp(page); + await assertRichTextInlineRange(page, 5, 3, 0); + await pressArrowUp(page); + await assertRichTextInlineRange(page, 4, 3, 0); + await pressArrowUp(page); + await assertRichTextInlineRange(page, 3, 15, 0); + await pressArrowRight(page, 8); + await assertRichTextInlineRange(page, 3, 23, 0); + await pressArrowUp(page); + await assertRichTextInlineRange(page, 3, 13, 0); + await pressArrowUp(page); + await assertRichTextInlineRange(page, 3, 9, 0); + await pressArrowUp(page); + await assertRichTextInlineRange(page, 2, 3, 0); + await pressArrowUp(page); + await assertRichTextInlineRange(page, 1, 5, 0); + await pressArrowUp(page); + await assertRichTextInlineRange(page, 0, 5, 0); + await pressArrowUp(page); + await assertRichTextInlineRange(page, 0, 0, 0); + await pressArrowRight(page, 4); + await assertRichTextInlineRange(page, 0, 4, 0); + await pressArrowDown(page); + await assertRichTextInlineRange(page, 1, 4, 0); + await pressArrowDown(page); + await assertRichTextInlineRange(page, 2, 3, 0); + await pressArrowDown(page); + await assertRichTextInlineRange(page, 3, 2, 0); + await pressArrowRight(page, 8); + await assertRichTextInlineRange(page, 3, 10, 0); + await pressArrowDown(page); + await assertRichTextInlineRange(page, 3, 14, 0); + await pressArrowDown(page); + await assertRichTextInlineRange(page, 4, 2, 0); + await pressArrowDown(page); + await assertRichTextInlineRange(page, 5, 2, 0); + await pressArrowDown(page); + await assertRichTextInlineRange(page, 6, 2, 0); +}); diff --git a/blocksuite/tests-legacy/selection/block.spec.ts b/blocksuite/tests-legacy/selection/block.spec.ts new file mode 100644 index 0000000000000..5f2531f23305f --- /dev/null +++ b/blocksuite/tests-legacy/selection/block.spec.ts @@ -0,0 +1,1425 @@ +import type { Page } from '@playwright/test'; +import { expect } from '@playwright/test'; + +import { + activeEmbed, + clickBlockDragHandle, + copyByKeyboard, + dragBetweenCoords, + dragBetweenIndices, + dragEmbedResizeByTopLeft, + enterPlaygroundRoom, + focusRichText, + getIndexCoordinate, + getPageSnapshot, + getRichTextBoundingBox, + initEmptyParagraphState, + initImageState, + initMultipleNoteWithParagraphState, + initParagraphsByCount, + initThreeLists, + initThreeParagraphs, + pasteByKeyboard, + pressBackspace, + pressEnter, + pressEscape, + pressForwardDelete, + pressShiftTab, + pressSpace, + pressTab, + redoByClick, + redoByKeyboard, + resetHistory, + selectAllByKeyboard, + shamefullyBlurActiveElement, + type, + undoByClick, + undoByKeyboard, + waitNextFrame, +} from '../utils/actions/index.js'; +import { + assertAlmostEqual, + assertBlockCount, + assertRichTexts, +} from '../utils/asserts.js'; +import { test } from '../utils/playwright.js'; + +test('block level range delete', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + await resetHistory(page); + + const box123 = await getRichTextBoundingBox(page, '2'); + const above123 = { x: box123.left, y: box123.top - 10 }; + + const box789 = await getRichTextBoundingBox(page, '4'); + const below789 = { x: box789.right - 10, y: box789.bottom + 10 }; + + await dragBetweenCoords(page, below789, above123); + await pressBackspace(page); + await assertBlockCount(page, 'paragraph', 1); + await assertRichTexts(page, ['']); + + await waitNextFrame(page); + await undoByClick(page); + await assertRichTexts(page, ['123', '456', '789']); + + await redoByClick(page); + await assertRichTexts(page, ['']); +}); + +test('block level range delete by forwardDelete', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + await resetHistory(page); + + const box123 = await getRichTextBoundingBox(page, '2'); + const above123 = { x: box123.left, y: box123.top - 10 }; + + const box789 = await getRichTextBoundingBox(page, '4'); + const below789 = { x: box789.right - 10, y: box789.bottom + 10 }; + + await dragBetweenCoords(page, below789, above123); + await pressForwardDelete(page); + await waitNextFrame(page); + await assertBlockCount(page, 'paragraph', 1); + await assertRichTexts(page, ['']); + + await waitNextFrame(page); + await undoByClick(page); + await assertRichTexts(page, ['123', '456', '789']); + + await redoByClick(page); + await assertRichTexts(page, ['']); +}); + +// XXX: Doesn't simulate full user operation due to backspace cursor issue in Playwright. +test('select all and delete', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + await selectAllByKeyboard(page); + await selectAllByKeyboard(page); + await selectAllByKeyboard(page); + await shamefullyBlurActiveElement(page); + await pressBackspace(page); + await focusRichText(page, 0); + await type(page, 'abc'); + await assertRichTexts(page, ['abc']); +}); + +test('select all and delete by forwardDelete', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + await selectAllByKeyboard(page); + await selectAllByKeyboard(page); + await selectAllByKeyboard(page); + await shamefullyBlurActiveElement(page); + await pressForwardDelete(page); + await focusRichText(page, 0); + await type(page, 'abc'); + await assertRichTexts(page, ['abc']); +}); + +test('select all should work for multiple notes in doc mode', async ({ + page, +}) => { + const n = 4; + await enterPlaygroundRoom(page); + await initMultipleNoteWithParagraphState(page, undefined, n); + + await focusRichText(page, 0); + await type(page, '123'); + await focusRichText(page, 1); + await type(page, '456'); + await selectAllByKeyboard(page); + await selectAllByKeyboard(page); + await selectAllByKeyboard(page); + const rects = page.locator('affine-block-selection').locator('visible=true'); + await expect(rects).toHaveCount(n); +}); + +async function clickListIcon(page: Page, i = 0) { + const locator = page.locator('.affine-list-block__prefix').nth(i); + await locator.click({ force: true }); +} + +test('click the list icon can select and copy', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeLists(page); + await assertRichTexts(page, ['123', '456', '789']); + await clickListIcon(page, 0); + // copy 123 + await copyByKeyboard(page); + + await focusRichText(page, 2); + await pasteByKeyboard(page); + await assertRichTexts(page, ['123', '456', '789123']); + + // copy 789123 + await clickListIcon(page, 2); + await copyByKeyboard(page); + + await focusRichText(page, 0); + await pasteByKeyboard(page); + await assertRichTexts(page, ['123789123', '456', '789123']); +}); + +test('click the list icon can select and delete', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeLists(page); + await assertRichTexts(page, ['123', '456', '789']); + + await clickListIcon(page, 0); + await waitNextFrame(page); + await pressBackspace(page); + await assertRichTexts(page, ['', '456', '789']); + await clickListIcon(page, 0); + await waitNextFrame(page); + await pressBackspace(page); + await assertRichTexts(page, ['', '']); +}); + +test('click the list icon can select and delete by forwardDelete', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeLists(page); + await assertRichTexts(page, ['123', '456', '789']); + + await clickListIcon(page, 0); + await waitNextFrame(page); + await pressForwardDelete(page); + await assertRichTexts(page, ['', '456', '789']); + await clickListIcon(page, 0); + await waitNextFrame(page); + await pressForwardDelete(page); + await assertRichTexts(page, ['', '']); +}); + +test('selection on heavy page', async ({ page }) => { + await page + .locator('body') + .evaluate(element => (element.style.padding = '50px')); + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + for (let i = 0; i < 5; i++) { + await type(page, `Line ${i + 1}`); + await pressEnter(page); + } + const [first, last] = await page.evaluate(() => { + const first = document.querySelector('[data-block-id="2"]'); + if (!first) { + throw new Error(); + } + + const last = document.querySelector('[data-block-id="6"]'); + if (!last) { + throw new Error(); + } + return [first.getBoundingClientRect(), last.getBoundingClientRect()]; + }); + await dragBetweenCoords( + page, + { + x: first.x - 1, + y: first.y - 1, + }, + { + x: last.x + 1, + y: last.y + 1, + }, + { + beforeMouseUp: async () => { + const rect = await page + .locator('.affine-page-dragging-area') + .evaluate(element => element.getBoundingClientRect()); + assertAlmostEqual(rect.x, first.x - 1, 1); + assertAlmostEqual(rect.y, first.y - 1, 1); + assertAlmostEqual(rect.right, last.x + 1, 1); + assertAlmostEqual(rect.bottom, last.y + 1, 1); + }, + } + ); + const rects = page.locator('affine-block-selection').locator('visible=true'); + await expect(rects).toHaveCount(5); +}); + +test('should indent multi-selection block', async ({ page }, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + const coord = await getIndexCoordinate(page, [1, 2]); + + // blur + await page.mouse.click(0, 0); + await page.mouse.move(coord.x - 26 - 24, coord.y - 10, { steps: 20 }); + await page.mouse.down(); + // ← + await page.mouse.move(coord.x + 20, coord.y + 50, { steps: 20 }); + await page.mouse.up(); + + await page.keyboard.press('Tab'); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}.json` + ); +}); + +test('should unindent multi-selection block', async ({ page }, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + let coord = await getIndexCoordinate(page, [1, 2]); + + // blur + await page.mouse.click(0, 0); + await page.mouse.move(coord.x - 26 - 24, coord.y - 10, { steps: 20 }); + await page.mouse.down(); + // ← + await page.mouse.move(coord.x + 20, coord.y + 50, { steps: 20 }); + await page.mouse.up(); + + await page.keyboard.press('Tab'); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_init.json` + ); + + coord = await getIndexCoordinate(page, [1, 2]); + + // blur + await page.mouse.click(0, 0); + await page.mouse.move(coord.x - 26 - 50, coord.y, { steps: 20 }); + await page.mouse.down(); + // ← + await page.mouse.move(coord.x + 20, coord.y + 30, { steps: 20 }); + await page.mouse.up(); + + await pressShiftTab(page); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_final.json` + ); +}); + +// ↑ +test('should keep selection state when scrolling backward', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + + await assertRichTexts(page, ['123', '456', '789']); + + for (let i = 0; i < 6; i++) { + await pressEnter(page); + } + + await type(page, '987'); + await pressEnter(page); + await type(page, '654'); + await pressEnter(page); + await type(page, '321'); + + const data = Array.from({ length: 5 }, () => ''); + data.unshift('123', '456', '789'); + data.push('987', '654', '321'); + await assertRichTexts(page, data); + + const [, container, distance] = await page.evaluate(() => { + const viewport = document.querySelector('.affine-page-viewport'); + if (!viewport) { + throw new Error(); + } + const distance = viewport.scrollHeight - viewport.clientHeight; + viewport.scrollTo(0, distance); + + const container = viewport.querySelector( + '.affine-block-children-container' + ); + if (!container) { + throw new Error(); + } + return [ + viewport.getBoundingClientRect(), + container.getBoundingClientRect(), + distance, + ] as const; + }); + + await page.mouse.move(0, 0); + await dragBetweenCoords( + page, + { + x: container.right + 1, + y: container.bottom, + }, + { + x: container.right - 1, + y: 1, + }, + { + // dont release mouse + beforeMouseUp: async () => { + const count = distance / (10 * 0.25); + await page.waitForTimeout((1000 / 60) * count); + }, + } + ); + + const scrollTop = await page.evaluate(() => { + const viewport = document.querySelector('.affine-page-viewport'); + if (!viewport) { + throw new Error(); + } + return viewport.scrollTop; + }); + + const rects = page.locator('affine-block-selection').locator('visible=true'); + await expect(rects).toHaveCount(3 + 5 + 3); + expect(scrollTop).toBe(0); +}); + +// ↓ +test('should keep selection state when scrolling forward', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + for (let i = 0; i < 6; i++) { + await pressEnter(page); + } + + await type(page, '987'); + await pressEnter(page); + await type(page, '654'); + await pressEnter(page); + await type(page, '321'); + + const data = Array.from({ length: 5 }, () => ''); + data.unshift('123', '456', '789'); + data.push('987', '654', '321'); + await assertRichTexts(page, data); + + const [viewport, container, distance] = await page.evaluate(() => { + const viewport = document.querySelector('.affine-page-viewport'); + if (!viewport) { + throw new Error(); + } + const distance = viewport.scrollHeight - viewport.clientHeight; + const container = viewport.querySelector( + '.affine-block-children-container' + ); + if (!container) { + throw new Error(); + } + return [ + viewport.getBoundingClientRect(), + container.getBoundingClientRect(), + distance, + ] as const; + }); + + await page.mouse.move(0, 0); + + await dragBetweenCoords( + page, + { + x: container.right + 1, + y: container.top + 1, + }, + { + x: container.right - 1, + y: viewport.height - 1, + }, + { + // dont release mouse + beforeMouseUp: async () => { + const count = distance / (10 * 0.25); + await page.waitForTimeout((1000 / 60) * count); + }, + } + ); + + const scrollTop = await page.evaluate(() => { + const viewport = document.querySelector('.affine-page-viewport'); + if (!viewport) { + throw new Error(); + } + return viewport.scrollTop; + }); + const rects = page.locator('affine-block-selection').locator('visible=true'); + await expect(rects).toHaveCount(3 + 5 + 3); + // See https://jestjs.io/docs/expect#tobeclosetonumber-numdigits + // Math.abs(scrollTop - distance) < Math.pow(10, -1 * -0.01)/2 = 0.511646496140377 + expect(scrollTop).toBeCloseTo(distance, -0.01); +}); + +// ↑ +test('should keep selection state when scrolling backward with the scroll wheel', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + for (let i = 0; i < 6; i++) { + await pressEnter(page); + } + + await type(page, '987'); + await pressEnter(page); + await type(page, '654'); + await pressEnter(page); + await type(page, '321'); + + const data = Array.from({ length: 5 }, () => ''); + data.unshift('123', '456', '789'); + data.push('987', '654', '321'); + await assertRichTexts(page, data); + + const [last, distance] = await page.evaluate(() => { + const viewport = document.querySelector('.affine-page-viewport'); + if (!viewport) { + throw new Error(); + } + const distance = viewport.scrollHeight - viewport.clientHeight; + viewport.scrollTo(0, distance); + const container = viewport.querySelector( + 'affine-note .affine-block-children-container' + ); + if (!container) { + throw new Error(); + } + const last = container.lastElementChild; + if (!last) { + throw new Error(); + } + return [last.getBoundingClientRect(), distance] as const; + }); + await page.waitForTimeout(250); + + await page.mouse.move(0, 0); + + await dragBetweenCoords( + page, + { + x: last.right + 1, + y: last.top + 1, + }, + { + x: last.right - 1, + y: last.top - 1, + }, + { + // dont release mouse + beforeMouseUp: async () => { + await page.mouse.wheel(0, -distance * 2); + await page.waitForTimeout(250); + }, + } + ); + + // get count with scroll wheel + const rects = page.locator('affine-block-selection').locator('visible=true'); + const count0 = await rects.count(); + const scrollTop0 = await page.evaluate(() => { + const viewport = document.querySelector('.affine-page-viewport'); + if (!viewport) { + throw new Error(); + } + return viewport.scrollTop; + }); + + await page.mouse.move(0, 0); + + await page.evaluate(() => { + const viewport = document.querySelector('.affine-page-viewport'); + if (!viewport) { + throw new Error(); + } + const distance = viewport.scrollHeight - viewport.clientHeight; + viewport.scrollTo(0, distance); + }); + await page.waitForTimeout(250); + + await dragBetweenCoords( + page, + { + x: last.right + 1, + y: last.top + 1, + }, + { + x: last.right - 1, + y: last.top - 1 - distance, + } + ); + + // get count with moving mouse + const count1 = await rects.count(); + const scrollTop1 = await page.evaluate(() => { + const viewport = document.querySelector('.affine-page-viewport'); + if (!viewport) { + throw new Error(); + } + return viewport.scrollTop; + }); + + expect(count0).toBe(count1); + expect(scrollTop0).toBe(0); + expect(scrollTop1).toBeCloseTo(distance, -0.5); +}); + +// ↓ +test('should keep selection state when scrolling forward with the scroll wheel', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + for (let i = 0; i < 6; i++) { + await pressEnter(page); + } + + await type(page, '987'); + await pressEnter(page); + await type(page, '654'); + await pressEnter(page); + await type(page, '321'); + + const data = Array.from({ length: 5 }, () => ''); + data.unshift('123', '456', '789'); + data.push('987', '654', '321'); + await assertRichTexts(page, data); + + const [first, distance] = await page.evaluate(() => { + const viewport = document.querySelector('.affine-page-viewport'); + if (!viewport) { + throw new Error(); + } + const distance = viewport.scrollHeight - viewport.clientHeight; + const container = viewport.querySelector( + 'affine-note .affine-block-children-container' + ); + if (!container) { + throw new Error(); + } + const first = container.firstElementChild; + if (!first) { + throw new Error(); + } + return [first.getBoundingClientRect(), distance] as const; + }); + + await page.mouse.move(0, 0); + + await dragBetweenCoords( + page, + { + x: first.left - 10, + y: first.top - 10, + }, + { + x: first.left + 1, + y: first.top + 1, + }, + { + // don't release mouse + beforeMouseUp: async () => { + await page.mouse.wheel(0, distance * 2); + await page.waitForTimeout(250); + }, + } + ); + + // get count with scroll wheel + const rects = page.locator('affine-block-selection').locator('visible=true'); + const count0 = await rects.count(); + const scrollTop0 = await page.evaluate(() => { + const viewport = document.querySelector('.affine-page-viewport'); + if (!viewport) { + throw new Error(); + } + return viewport.scrollTop; + }); + + await page.mouse.move(0, 0); + + await page.evaluate(() => { + const viewport = document.querySelector('.affine-page-viewport'); + if (!viewport) { + throw new Error(); + } + viewport.scrollTo(0, 0); + }); + await page.waitForTimeout(250); + + await dragBetweenCoords( + page, + { + x: first.left - 10, + y: first.top - 10, + }, + { + x: first.left + 1, + y: first.top + 1 + distance, + } + ); + + // get count with moving mouse + const count1 = await rects.count(); + const scrollTop1 = await page.evaluate(() => { + const viewport = document.querySelector('.affine-page-viewport'); + if (!viewport) { + throw new Error(); + } + return viewport.scrollTop; + }); + + expect(count0).toBe(count1); + expect(scrollTop0).toBeCloseTo(distance, -0.8); + expect(scrollTop1).toBe(0); +}); + +test('should not clear selected rects when clicking on scrollbar', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + for (let i = 0; i < 6; i++) { + await pressEnter(page); + } + + await type(page, '987'); + await pressEnter(page); + await type(page, '654'); + await pressEnter(page); + await type(page, '321'); + + const [viewport, first, distance] = await page.evaluate(() => { + const viewport = document.querySelector('.affine-page-viewport'); + if (!viewport) { + throw new Error(); + } + const distance = viewport.scrollHeight - viewport.clientHeight; + viewport.scrollTo(0, distance / 2); + const container = viewport.querySelector( + 'affine-note .affine-block-children-container' + ); + if (!container) { + throw new Error(); + } + const first = container.firstElementChild; + if (!first) { + throw new Error(); + } + return [ + viewport.getBoundingClientRect(), + first.getBoundingClientRect(), + distance, + ] as const; + }); + + await page.mouse.move(0, 0); + + await dragBetweenCoords( + page, + { + x: first.left - 10, + y: first.top - 10, + }, + { + x: first.right + 10, + y: first.bottom + distance / 2, + } + ); + + const rects = page.locator('affine-block-selection').locator('visible=true'); + const count0 = await rects.count(); + const scrollTop0 = await page.evaluate(() => { + const viewport = document.querySelector('.affine-page-viewport'); + if (!viewport) { + throw new Error(); + } + return viewport.scrollTop; + }); + + await page.mouse.click(viewport.right, distance / 2); + + const count1 = await rects.count(); + const scrollTop1 = await page.evaluate(() => { + const viewport = document.querySelector('.affine-page-viewport'); + if (!viewport) { + throw new Error(); + } + return viewport.scrollTop; + }); + + expect(count0).toBeGreaterThan(0); + expect(scrollTop0).toBeCloseTo(distance / 2, -0.01); + expect(count0).toBe(count1); + expect(scrollTop0).toBeCloseTo(scrollTop1, -0.01); +}); + +test('should not clear selected rects when scrolling the wheel', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + for (let i = 0; i < 6; i++) { + await pressEnter(page); + } + + await type(page, '987'); + await pressEnter(page); + await type(page, '654'); + await pressEnter(page); + await type(page, '321'); + + const [viewport, first, distance] = await page.evaluate(() => { + const viewport = document.querySelector('.affine-page-viewport'); + if (!viewport) { + throw new Error(); + } + const distance = viewport.scrollHeight - viewport.clientHeight; + viewport.scrollTo(0, distance / 2); + const container = viewport.querySelector( + 'affine-note .affine-block-children-container' + ); + if (!container) { + throw new Error(); + } + const first = container.firstElementChild; + if (!first) { + throw new Error(); + } + return [ + viewport.getBoundingClientRect(), + first.getBoundingClientRect(), + distance, + ] as const; + }); + + await page.mouse.move(0, 0); + + await dragBetweenCoords( + page, + { + x: first.left - 10, + y: first.top - 10, + }, + { + x: first.right + 10, + y: first.bottom + distance / 2, + } + ); + + const rects = page.locator('affine-block-selection').locator('visible=true'); + const count0 = await rects.count(); + + await page.mouse.wheel(viewport.right, -distance / 4); + await waitNextFrame(page); + + const count1 = await rects.count(); + + expect(count0).toBeGreaterThan(0); + expect(count0).toBe(count1); + + await page.mouse.wheel(viewport.right, distance / 4); + await waitNextFrame(page); + + const count2 = await page.evaluate(() => { + const viewport = document.querySelector('.affine-page-viewport'); + if (!viewport) { + throw new Error(); + } + const rects = viewport.querySelectorAll('affine-block-selection'); + const visibleRects = Array.from(rects).filter(rect => { + const display = window.getComputedStyle(rect).display; + return display !== 'none'; + }); + return visibleRects.length; + }); + + expect(count0).toBe(count2); +}); + +test('should refresh selected rects when resizing the window/viewport', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + for (let i = 0; i < 6; i++) { + await pressEnter(page); + } + + await type(page, '987'); + await pressEnter(page); + await type(page, '654'); + await pressEnter(page); + await type(page, '321'); + + const [viewport, first, distance] = await page.evaluate(() => { + const viewport = document.querySelector('.affine-page-viewport'); + if (!viewport) { + throw new Error(); + } + const distance = viewport.scrollHeight - viewport.clientHeight; + viewport.scrollTo(0, distance / 2); + const container = viewport.querySelector( + 'affine-note .affine-block-children-container' + ); + if (!container) { + throw new Error(); + } + const first = container.firstElementChild; + if (!first) { + throw new Error(); + } + return [ + viewport.getBoundingClientRect(), + first.getBoundingClientRect(), + distance, + ] as const; + }); + + await page.mouse.move(0, 0); + + await dragBetweenCoords( + page, + { + x: first.left - 1, + y: first.top - 1, + }, + { + x: first.left + 1, + y: first.top + distance / 2, + } + ); + + const rects = page.locator('affine-block-selection').locator('visible=true'); + const count0 = await rects.count(); + const scrollTop0 = await page.evaluate(() => { + const viewport = document.querySelector('.affine-page-viewport'); + if (!viewport) { + throw new Error(); + } + return viewport.scrollTop; + }); + + await page.mouse.click(viewport.right, first.top + distance / 2); + + const size = page.viewportSize(); + + if (!size) { + throw new Error(); + } + + await page.setViewportSize({ + width: size.width - 100, + height: size.height - 100, + }); + await page.waitForTimeout(250); + + const count1 = await rects.count(); + const scrollTop1 = await page.evaluate(() => { + const viewport = document.querySelector('.affine-page-viewport'); + if (!viewport) { + throw new Error(); + } + return viewport.scrollTop; + }); + + expect(count0).toBe(count1); + expect(scrollTop0).toBeCloseTo(scrollTop1, -0.01); +}); + +test('should clear block selection before native selection', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + // `123` + const first = await page.evaluate(() => { + const first = document.querySelector('[data-block-id="2"]'); + if (!first) { + throw new Error(); + } + return first.getBoundingClientRect(); + }); + + await dragBetweenCoords( + page, + { + x: first.left - 10, + y: first.top - 10, + }, + { + x: first.left + 1, + y: first.top + 1, + } + ); + + const rects = page.locator('affine-block-selection').locator('visible=true'); + const count0 = await rects.count(); + + await dragBetweenIndices( + page, + [1, 3], + [1, 0], + { x: 0, y: 0 }, + { x: 0, y: 0 } + ); + + const count1 = await rects.count(); + const textCount = await page.evaluate(() => { + return window.getSelection()?.rangeCount || 0; + }); + + expect(count0).toBe(1); + expect(count1).toBe(0); + expect(textCount).toBe(1); +}); + +test('should not be misaligned when the editor container has padding or margin', async ({ + page, +}) => { + await page.locator('body').evaluate(element => { + element.style.margin = '50px'; + element.style.padding = '50px'; + }); + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + // `123`, `789` + const [first, last] = await page.evaluate(() => { + const viewport = document.querySelector('.affine-page-viewport'); + if (!viewport) { + throw new Error(); + } + const container = viewport.querySelector( + 'affine-note .affine-block-children-container' + ); + if (!container) { + throw new Error(); + } + const first = container.firstElementChild; + if (!first) { + throw new Error(); + } + const last = container.lastElementChild; + if (!last) { + throw new Error(); + } + return [first.getBoundingClientRect(), last.getBoundingClientRect()]; + }); + + await dragBetweenCoords( + page, + { + x: first.left - 10, + y: first.top - 10, + }, + { + x: last.left + 1, + y: last.top + 1, + } + ); + + const rects = page.locator('affine-block-selection').locator('visible=true'); + await expect(rects).toHaveCount(3); +}); + +test('undo should clear block selection', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + await type(page, 'hello'); + await pressEnter(page); + await type(page, 'world'); + await pressEnter(page); + + const rect = await getRichTextBoundingBox(page, '2'); + await dragBetweenCoords( + page, + { x: rect.x - 5, y: rect.y - 5 }, + { x: rect.x + 5, y: rect.y + rect.height } + ); + + await redoByKeyboard(page); + const selectedBlocks = page + .locator('affine-block-selection') + .locator('visible=true'); + await expect(selectedBlocks).toHaveCount(1); + + await undoByKeyboard(page); + await expect(selectedBlocks).toHaveCount(0); +}); + +test('should not draw rect for sub selected blocks when entering tab key', async ({ + page, +}, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + const coord = await getIndexCoordinate(page, [1, 3]); + + // blur + await page.mouse.click(20, 20); + + await dragBetweenCoords( + page, + { x: coord.x - 60, y: coord.y + 10 }, + { x: coord.x + 100, y: coord.y + 30 }, + { steps: 50 } + ); + + await pressTab(page); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}.json` + ); +}); + +test('should blur rich-text first on starting block selection', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + await expect(page.locator('*:focus')).toHaveCount(1); + + const coord = await getIndexCoordinate(page, [1, 2]); + await dragBetweenCoords( + page, + { x: coord.x - 30, y: coord.y - 10 }, + { x: coord.x + 20, y: coord.y + 50 } + ); + + await expect(page.locator('*:focus')).toHaveCount(0); +}); + +test('should not show option menu of image on block selection', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initImageState(page); + await activeEmbed(page); + + await expect(page.locator('.affine-image-toolbar-container')).toHaveCount(1); + + await pressEnter(page); + + const imageRect = await page.locator('affine-image').boundingBox(); + if (!imageRect) { + throw new Error(); + } + + await dragBetweenCoords( + page, + { + x: imageRect.x + imageRect.width + 60, + y: imageRect.y + imageRect.height / 2 + 10, + }, + { + x: imageRect.x - 100, + y: imageRect.y + imageRect.height / 2, + } + ); + + await page.waitForTimeout(50); + + await expect(page.locator('.affine-image-toolbar-container')).toHaveCount(0); + await expect( + page.locator('affine-block-selection').locator('visible=true') + ).toHaveCount(1); +}); + +test('click bottom of page and if the last is embed block, editor should insert a new editable block', async ({ + page, +}, testInfo) => { + await enterPlaygroundRoom(page); + await initImageState(page); + await activeEmbed(page); + await dragEmbedResizeByTopLeft(page); + + const hostRect = await page.evaluate(() => { + const host = document.querySelector('editor-host'); + if (!host) { + throw new Error("Can't find doc viewport"); + } + return host.getBoundingClientRect(); + }); + + await page.mouse.click(hostRect.x + hostRect.width / 2, hostRect.bottom - 10); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}.json` + ); +}); + +test('should select blocks when pressing escape', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + await focusRichText(page, 2); + await page.keyboard.press('Escape'); + await expect( + page.locator('affine-block-selection').locator('visible=true') + ).toHaveCount(1); + await page.keyboard.press('Escape'); + + const cords = await getIndexCoordinate(page, [1, 2]); + await page.mouse.move(cords.x + 10, cords.y + 10, { steps: 20 }); + await page.mouse.down(); + await page.mouse.move(cords.x + 20, cords.y + 30, { steps: 20 }); + await page.mouse.up(); + + await page.keyboard.press('Escape'); + await expect( + page.locator('affine-block-selection').locator('visible=true') + ).toHaveCount(1); +}); + +test('should un-select blocks when pressing escape', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + await focusRichText(page, 2); + await pressEscape(page); + await expect( + page.locator('affine-block-selection').locator('visible=true') + ).toHaveCount(1); + + await pressEscape(page); + await expect( + page.locator('affine-block-selection').locator('visible=true') + ).toHaveCount(0); + + await focusRichText(page, 2); + await pressEnter(page); + await type(page, '-'); + await pressSpace(page); + await clickListIcon(page, 0); + await expect( + page.locator('affine-block-selection').locator('visible=true') + ).toHaveCount(1); + + await pressEscape(page); + await expect( + page.locator('affine-block-selection').locator('visible=true') + ).toHaveCount(0); +}); + +test('verify cursor position after changing block type', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'hello'); + const anchorOffset = await page.evaluate(() => { + return window.getSelection()?.anchorOffset || 0; + }); + expect(anchorOffset).toBe(5); + + await type(page, '/'); + const slashMenu = page.locator(`.slash-menu`); + await expect(slashMenu).toBeVisible(); + + const todayBlock = page.getByTestId('Heading 1'); + await todayBlock.click(); + await expect(slashMenu).toBeHidden(); + + await type(page, 'w'); + const anchorOffset2 = await page.evaluate(() => { + return window.getSelection()?.anchorOffset || 0; + }); + expect(anchorOffset2).toBe(6); +}); + +// https://github.com/toeverything/blocksuite/issues/3613 +test('should scroll page properly by wheel after inserting a new block and selecting it', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + + await test.step('Insert enough blocks to make page scrollable', async () => { + await focusRichText(page); + + for (let i = 0; i < 10; i++) { + await type(page, String(i)); + await pressEnter(page); + await pressEnter(page); + } + }); + + await type(page, 'new block'); + + const lastBlockId = await page.evaluate(() => { + const viewport = document.querySelector('.affine-page-viewport')!; + const container = viewport.querySelector( + 'affine-note .affine-block-children-container' + ); + const last = container!.lastElementChild as HTMLElement; + if (!last) { + throw new Error(); + } + return last.dataset.blockId!; + }); + + // click drag handle to select block + await clickBlockDragHandle(page, lastBlockId); + + async function getViewportScrollTop() { + return page.evaluate(() => { + const viewport = document.querySelector('.affine-page-viewport'); + if (!viewport) { + throw new Error(); + } + return viewport.scrollTop; + }); + } + await page.mouse.move(0, 0); + // scroll to top by wheel + await page.mouse.wheel(0, -(await getViewportScrollTop()) * 2); + await page.waitForTimeout(250); + expect(await getViewportScrollTop()).toBe(0); + + // scroll to end by wheel + const distanceToEnd = await page.evaluate(() => { + const viewport = document.querySelector('.affine-page-viewport')!; + return viewport.scrollHeight - viewport.clientHeight; + }); + await page.mouse.wheel(0, distanceToEnd * 2); + await page.waitForTimeout(250); + expect(await getViewportScrollTop()).toBe(distanceToEnd); +}); + +test('should not select parent block when dragging area only intersects with child', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + const coord = await getIndexCoordinate(page, [1, 2]); + + // blur + await page.mouse.click(0, 0); + await page.mouse.move(coord.x - 26 - 24, coord.y - 10, { steps: 20 }); + await page.mouse.down(); + // ← + await page.mouse.move(coord.x + 20, coord.y + 50, { steps: 20 }); + await page.mouse.up(); + + let rects = page.locator('affine-block-selection').locator('visible=true'); + await expect(rects).toHaveCount(2); + + // indent children blocks + await pressTab(page); + + const secondCoord = await getIndexCoordinate(page, [1, 2]); + await page.mouse.click(0, 0); + await page.mouse.move(secondCoord.x - 100, secondCoord.y - 10, { + steps: 20, + }); + await page.mouse.down(); + // ← + await page.mouse.move(secondCoord.x + 100, secondCoord.y + 10, { steps: 20 }); + await page.mouse.up(); + + rects = page.locator('affine-block-selection').locator('visible=true'); + await expect(rects).toHaveCount(1); +}); + +test('scroll should update dragging area and select blocks when dragging', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initParagraphsByCount(page, 20); + + await page.mouse.click(0, 0); + // eslint-disable-next-line sonarjs/no-identical-functions + async function getViewportScrollTop() { + return page.evaluate(() => { + const viewport = document.querySelector('.affine-page-viewport'); + if (!viewport) { + throw new Error(); + } + return viewport.scrollTop; + }); + } + + await page.mouse.wheel(0, -(await getViewportScrollTop()) * 2); + await page.waitForTimeout(250); + expect(await getViewportScrollTop()).toBe(0); + + const coord = await getIndexCoordinate(page, [1, 1]); + + await page.mouse.move(coord.x - 26 - 24, coord.y - 30, { steps: 40 }); + await waitNextFrame(page, 300); + await page.mouse.down(); + await waitNextFrame(page, 300); + await page.mouse.move(coord.x + 100, coord.y + 10, { steps: 40 }); + await waitNextFrame(page, 300); + + let rects = page.locator('affine-block-selection').locator('visible=true'); + await expect(rects).toHaveCount(2); + + // scroll to end by wheel + const distanceToEnd = await page.evaluate(() => { + const viewport = document.querySelector('.affine-page-viewport')!; + return viewport.scrollHeight - viewport.clientHeight; + }); + await page.mouse.wheel(0, distanceToEnd * 2); + await page.waitForTimeout(250); + expect(await getViewportScrollTop()).toBe(distanceToEnd); + + await page.mouse.up(); + + rects = page.locator('affine-block-selection').locator('visible=true'); + await expect(rects).toHaveCount(3); +}); diff --git a/blocksuite/tests-legacy/selection/native.spec.ts b/blocksuite/tests-legacy/selection/native.spec.ts new file mode 100644 index 0000000000000..bd2a8163fa490 --- /dev/null +++ b/blocksuite/tests-legacy/selection/native.spec.ts @@ -0,0 +1,1788 @@ +import { expect } from '@playwright/test'; + +import { + activeEmbed, + activeNoteInEdgeless, + addNoteByClick, + click, + copyByKeyboard, + dragBetweenCoords, + dragBetweenIndices, + enterPlaygroundRoom, + fillLine, + focusRichText, + focusTitle, + getCursorBlockIdAndHeight, + getEditorHostLocator, + getIndexCoordinate, + getInlineSelectionIndex, + getInlineSelectionText, + getPageSnapshot, + getRichTextBoundingBox, + getSelectedText, + getSelectedTextByInlineEditor, + initEmptyEdgelessState, + initEmptyParagraphState, + initImageState, + initThreeLists, + initThreeParagraphs, + pasteByKeyboard, + pressArrowDown, + pressArrowLeft, + pressArrowRight, + pressArrowUp, + pressBackspace, + pressEnter, + pressEscape, + pressForwardDelete, + pressShiftEnter, + pressShiftTab, + pressTab, + redoByKeyboard, + resetHistory, + scrollToTop, + selectAllByKeyboard, + setInlineRangeInInlineEditor, + setSelection, + SHORT_KEY, + switchEditorMode, + type, + undoByKeyboard, + waitNextFrame, +} from '../utils/actions/index.js'; +import { + assertBlockCount, + assertBlockSelections, + assertClipItems, + assertDivider, + assertExists, + assertNativeSelectionRangeCount, + assertRichTextInlineRange, + assertRichTexts, + assertTextSelection, + assertTitle, +} from '../utils/asserts.js'; +import { test } from '../utils/playwright.js'; + +test('native range delete', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + await dragBetweenIndices(page, [0, 0], [2, 3]); + await pressBackspace(page); + await assertBlockCount(page, 'paragraph', 1); + await assertRichTexts(page, ['']); + + await waitNextFrame(page); + await undoByKeyboard(page); + await assertRichTexts(page, ['123', '456', '789']); + await redoByKeyboard(page); + await assertRichTexts(page, ['']); +}); + +test('native range delete with indent', async ({ page }, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + + await focusRichText(page); + await type(page, '123'); + await pressEnter(page); + await type(page, '456'); + await pressEnter(page); + await type(page, '789'); + await pressEnter(page); + await type(page, 'abc'); + await pressEnter(page); + await type(page, 'def'); + await pressEnter(page); + await type(page, 'ghi'); + await resetHistory(page); + + await focusRichText(page, 1); + await pressTab(page); + await focusRichText(page, 2); + await pressTab(page, 2); + await focusRichText(page, 4); + await pressTab(page); + await focusRichText(page, 5); + await pressTab(page, 2); + + // 123 + // 456 + // 789 + // abc + // def + // ghi + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_init.json` + ); + + await dragBetweenIndices(page, [0, 2], [4, 1]); + + // 12|3 + // 456 + // 789 + // abc + // d|ef + // ghi + + await pressBackspace(page); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_after_backspace.json` + ); + + await waitNextFrame(page); + await undoByKeyboard(page); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_after_undo.json` + ); + + await redoByKeyboard(page); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_after_redo.json` + ); +}); + +test('native range delete by forwardDelete', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + const box123 = await getRichTextBoundingBox(page, '2'); + const inside123 = { x: box123.left - 1, y: box123.top + 1 }; + + const box789 = await getRichTextBoundingBox(page, '4'); + const inside789 = { x: box789.right - 1, y: box789.bottom - 1 }; + + // from top to bottom + await dragBetweenCoords(page, inside123, inside789, { steps: 50 }); + await pressForwardDelete(page); + await assertBlockCount(page, 'paragraph', 1); + await assertRichTexts(page, ['']); + + await waitNextFrame(page); + await undoByKeyboard(page); + await assertRichTexts(page, ['123', '456', '789']); + + await redoByKeyboard(page); + await assertRichTexts(page, ['']); +}); + +test('native range input', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + const box123 = await getRichTextBoundingBox(page, '2'); + const inside123 = { x: box123.left - 1, y: box123.top + 1 }; + + const box789 = await getRichTextBoundingBox(page, '4'); + const inside789 = { x: box789.right - 1, y: box789.bottom - 1 }; + + // from top to bottom + await dragBetweenCoords(page, inside123, inside789, { steps: 50 }); + await pressForwardDelete(page); + await page.keyboard.press('a'); + await assertBlockCount(page, 'paragraph', 1); + await assertRichTexts(page, ['a']); +}); + +test('native range selection backwards', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + const box123 = await getRichTextBoundingBox(page, '2'); + const above123 = { x: box123.left, y: box123.top - 2 }; + + const box789 = await getRichTextBoundingBox(page, '4'); + const bottomRight789 = { x: box789.right, y: box789.bottom }; + + // from bottom to top + await dragBetweenCoords(page, bottomRight789, above123, { steps: 10 }); + await pressBackspace(page); + await assertBlockCount(page, 'paragraph', 1); + await assertRichTexts(page, ['']); + + await waitNextFrame(page); + await undoByKeyboard(page); + // FIXME + // await assertRichTexts(page, ['123', '456', '789']); + + await redoByKeyboard(page); + await assertRichTexts(page, ['']); +}); + +test('native range selection backwards by forwardDelete', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + const box123 = await getRichTextBoundingBox(page, '2'); + const above123 = { x: box123.left, y: box123.top - 2 }; + + const box789 = await getRichTextBoundingBox(page, '4'); + const bottomRight789 = { x: box789.right, y: box789.bottom }; + + // from bottom to top + await dragBetweenCoords(page, bottomRight789, above123, { steps: 10 }); + await pressForwardDelete(page); + await assertBlockCount(page, 'paragraph', 1); + await assertRichTexts(page, ['']); + + await waitNextFrame(page); + await undoByKeyboard(page); + await assertRichTexts(page, ['123', '456', '789']); + + await redoByKeyboard(page); + await assertRichTexts(page, ['']); +}); + +test('cursor move up and down', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'arrow down test 1'); + await pressEnter(page); + await type(page, 'arrow down test 2'); + + await pressArrowUp(page); + const textOne = await getInlineSelectionText(page); + expect(textOne).toBe('arrow down test 1'); + + await pressArrowDown(page); + const textTwo = await getInlineSelectionText(page); + expect(textTwo).toBe('arrow down test 2'); +}); + +test('cursor move to up and down with children block', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'arrow down test 1'); + await pressEnter(page); + await type(page, 'arrow down test 2'); + await page.keyboard.press('Tab'); + for (let i = 0; i <= 17; i++) { + await page.keyboard.press('ArrowRight'); + } + await pressEnter(page); + await type(page, 'arrow down test 3'); + await pressShiftTab(page); + for (let i = 0; i < 2; i++) { + await page.keyboard.press('ArrowRight'); + } + await page.keyboard.press('ArrowUp'); + const indexOne = await getInlineSelectionIndex(page); + const textOne = await getInlineSelectionText(page); + expect(textOne).toBe('arrow down test 2'); + expect(indexOne).toBe(13); + for (let i = 0; i < 3; i++) { + await page.keyboard.press('ArrowLeft'); + } + await page.keyboard.press('ArrowUp'); + const indexTwo = await getInlineSelectionIndex(page); + const textTwo = await getInlineSelectionText(page); + expect(textTwo).toBe('arrow down test 1'); + expect(indexTwo).toBeGreaterThanOrEqual(12); + expect(indexTwo).toBeLessThanOrEqual(17); + await page.keyboard.press('ArrowDown'); + const textThree = await getInlineSelectionText(page); + expect(textThree).toBe('arrow down test 2'); +}); + +test('cursor move left and right', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'arrow down test 1'); + await pressEnter(page); + await type(page, 'arrow down test 2'); + const index1 = await getInlineSelectionIndex(page); + expect(index1).toBe(17); + await pressArrowLeft(page, 17); + const index2 = await getInlineSelectionIndex(page); + expect(index2).toBe(0); + await pressArrowLeft(page); + const index3 = await getInlineSelectionIndex(page); + expect(index3).toBe(17); +}); + +test('cursor move up at edge of the second line', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await pressEnter(page); + const [id, height] = await getCursorBlockIdAndHeight(page); + if (id && height) { + await fillLine(page, true); + await pressArrowLeft(page); + const [currentId] = await getCursorBlockIdAndHeight(page); + expect(currentId).toBe(id); + } +}); + +test('cursor move down at edge of the last line', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await pressEnter(page); + const [id] = await getCursorBlockIdAndHeight(page); + await page.keyboard.press('ArrowUp'); + const [, height] = await getCursorBlockIdAndHeight(page); + if (id && height) { + await fillLine(page, true); + await pressArrowLeft(page); + await pressArrowDown(page); + const [currentId] = await getCursorBlockIdAndHeight(page); + expect(currentId).toBe(id); + } +}); + +test('cursor move up and down through note', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await addNoteByClick(page); + await focusRichText(page, 0); + let currentId: string | null; + const [id] = await getCursorBlockIdAndHeight(page); + await pressArrowDown(page); + currentId = (await getCursorBlockIdAndHeight(page))[0]; + expect(id).not.toBe(currentId); + await pressArrowUp(page); + currentId = (await getCursorBlockIdAndHeight(page))[0]; + expect(id).toBe(currentId); +}); + +test('double click choose words', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'hello block suite'); + await assertRichTexts(page, ['hello block suite']); + + const hello = await getRichTextBoundingBox(page, '2'); + const helloPosition = { x: hello.x + 2, y: hello.y + 8 }; + + await page.mouse.dblclick(helloPosition.x, helloPosition.y); + const text = await page.evaluate(() => { + let text = ''; + const selection = window.getSelection(); + if (selection) { + text = selection.toString(); + } + return text; + }); + expect(text).toBe('hello'); +}); + +test('select all text with dragging and delete', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + await dragBetweenIndices(page, [0, 0], [2, 3], undefined, undefined, { + steps: 20, + }); + await pressBackspace(page); + await type(page, 'abc'); + const textOne = await getInlineSelectionText(page); + expect(textOne).toBe('abc'); +}); + +test('select all text with dragging and delete by forwardDelete', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + await dragBetweenIndices(page, [0, 0], [2, 3], undefined, undefined, { + steps: 20, + }); + await pressForwardDelete(page); + await type(page, 'abc'); + const textOne = await getInlineSelectionText(page); + expect(textOne).toBe('abc'); +}); + +test('select all text with keyboard delete', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + await focusRichText(page); + await selectAllByKeyboard(page); + await pressBackspace(page); + const text1 = await getInlineSelectionText(page); + expect(text1).toBe(''); + await type(page, 'abc'); + const text2 = await getInlineSelectionText(page); + expect(text2).toBe('abc'); + + await selectAllByKeyboard(page); + await selectAllByKeyboard(page); + await pressBackspace(page); + await assertRichTexts(page, ['', '456', '789']); + + await type(page, 'abc'); + await selectAllByKeyboard(page); + await selectAllByKeyboard(page); + await selectAllByKeyboard(page); + await pressBackspace(page); + await assertRichTexts(page, ['']); +}); + +test('select text leaving a few words in the last line and delete', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + await dragBetweenIndices(page, [0, 0], [2, 1], undefined, undefined, { + steps: 20, + }); + await page.keyboard.press('Backspace'); + await waitNextFrame(page); + await type(page, 'abc'); + const textOne = await getInlineSelectionText(page); + expect(textOne).toBe('abc89'); +}); + +test('select text leaving a few words in the last line and delete by forwardDelete', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + await dragBetweenIndices(page, [0, 0], [2, 1], undefined, undefined, { + steps: 20, + }); + await pressForwardDelete(page); + await waitNextFrame(page); + await type(page, 'abc'); + const textOne = await getInlineSelectionText(page); + expect(textOne).toBe('abc89'); +}); + +test('select text in the same line with dragging leftward and move outside the affine-note', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + const noteLeft = await page.evaluate(() => { + const note = document.querySelector('affine-note'); + if (!note) { + throw new Error(); + } + return note.getBoundingClientRect().left; + }); + + // `456` + const blockRect = await page.evaluate(() => { + const block = document.querySelector('[data-block-id="3"]'); + if (!block) { + throw new Error(); + } + return block.getBoundingClientRect(); + }); + + await dragBetweenIndices( + page, + [1, 3], + [1, 0], + { x: 0, y: 0 }, + { x: 0, y: 0 }, + { + steps: 20, + async beforeMouseUp() { + await page.mouse.move( + noteLeft - 1, + blockRect.top + blockRect.height / 2 + ); + }, + } + ); + await pressBackspace(page); + await type(page, 'abc'); + await assertRichTexts(page, ['123', 'abc', '789']); +}); + +test('select text in the same line with dragging leftward and move outside the affine-note by forwardDelete', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + const noteLeft = await page.evaluate(() => { + const note = document.querySelector('affine-note'); + if (!note) { + throw new Error(); + } + return note.getBoundingClientRect().left; + }); + + // `456` + const blockRect = await page.evaluate(() => { + const block = document.querySelector('[data-block-id="3"]'); + if (!block) { + throw new Error(); + } + return block.getBoundingClientRect(); + }); + + await dragBetweenIndices( + page, + [1, 3], + [1, 0], + { x: 0, y: 0 }, + { x: 0, y: 0 }, + { + steps: 20, + async beforeMouseUp() { + await page.mouse.move( + noteLeft - 1, + blockRect.top + blockRect.height / 2 + ); + }, + } + ); + await pressForwardDelete(page); + await type(page, 'abc'); + await assertRichTexts(page, ['123', 'abc', '789']); +}); + +test('select text in the same line with dragging rightward and move outside the affine-note', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + const noteRight = await page.evaluate(() => { + const note = document.querySelector('affine-note'); + if (!note) { + throw new Error(); + } + return note.getBoundingClientRect().right; + }); + + // `456` + const blockRect = await page.evaluate(() => { + const block = document.querySelector('[data-block-id="3"]'); + if (!block) { + throw new Error(); + } + return block.getBoundingClientRect(); + }); + + await dragBetweenIndices( + page, + [1, 0], + [1, 3], + { x: 0, y: 0 }, + { x: 0, y: 0 }, + { + steps: 20, + async beforeMouseUp() { + await page.mouse.move( + noteRight + 1, + blockRect.top + blockRect.height / 2 + ); + }, + } + ); + await pressBackspace(page); + await type(page, 'abc'); + const textOne = await getInlineSelectionText(page); + expect(textOne).toBe('abc'); +}); + +test('select text in the same line with dragging rightward and move outside the affine-note by forwardDelete', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + const noteRight = await page.evaluate(() => { + const note = document.querySelector('affine-note'); + if (!note) { + throw new Error(); + } + return note.getBoundingClientRect().right; + }); + + // `456` + const blockRect = await page.evaluate(() => { + const block = document.querySelector('[data-block-id="3"]'); + if (!block) { + throw new Error(); + } + return block.getBoundingClientRect(); + }); + + await dragBetweenIndices( + page, + [1, 0], + [1, 3], + { x: 0, y: 0 }, + { x: 0, y: 0 }, + { + steps: 20, + async beforeMouseUp() { + await page.mouse.move( + noteRight + 1, + blockRect.top + blockRect.height / 2 + ); + }, + } + ); + await pressForwardDelete(page); + await type(page, 'abc'); + const textOne = await getInlineSelectionText(page); + expect(textOne).toBe('abc'); +}); + +test('select text in the same line with dragging rightward and press enter create block', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + // blur the editor + await page.mouse.click(20, 20); + + const box123 = await getRichTextBoundingBox(page, '2'); + const above123 = { x: box123.left + 100, y: box123.top }; + + const box789 = await getRichTextBoundingBox(page, '4'); + const below789 = { x: box789.right + 30, y: box789.bottom + 50 }; + + await dragBetweenCoords(page, below789, above123, { steps: 50 }); + await page.waitForTimeout(300); + + await pressEnter(page); + await pressEnter(page); + await type(page, 'abc'); + await assertRichTexts(page, ['123', '456', '789', 'abc']); +}); + +test('drag to select tagged text, and copy', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + + await focusRichText(page); + await page.keyboard.insertText('123456789'); + await assertRichTexts(page, ['123456789']); + + await dragBetweenIndices(page, [0, 1], [0, 3], undefined, undefined, { + steps: 20, + }); + await page.keyboard.press(`${SHORT_KEY}+B`); + await dragBetweenIndices(page, [0, 0], [0, 5], undefined, undefined, { + steps: 20, + }); + await page.keyboard.press(`${SHORT_KEY}+C`); + const textOne = await getSelectedTextByInlineEditor(page); + expect(textOne).toBe('12345'); +}); + +test('drag to select tagged text, and input character', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + + await focusRichText(page); + await page.keyboard.insertText('123456789'); + await assertRichTexts(page, ['123456789']); + + await dragBetweenIndices(page, [0, 1], [0, 3], undefined, undefined, { + steps: 20, + }); + await page.keyboard.press(`${SHORT_KEY}+B`); + await dragBetweenIndices(page, [0, 0], [0, 5], undefined, undefined, { + steps: 20, + }); + await type(page, '1'); + const textOne = await getInlineSelectionText(page); + expect(textOne).toBe('16789'); +}); + +test('Change title when first content is divider', async ({ page }) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/toeverything/blocksuite/issues/1004', + }); + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, '--- '); + await assertDivider(page, 1); + await focusTitle(page); + await type(page, 'title'); + await assertTitle(page, 'title'); +}); + +test('ArrowUp and ArrowDown to select divider and copy', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, '--- '); + await assertDivider(page, 1); + await pressEscape(page); + await pressArrowUp(page); + await copyByKeyboard(page); + await pressArrowDown(page); + await pressEnter(page); + await pasteByKeyboard(page); + await assertDivider(page, 2); +}); + +test('Delete the blank line between two dividers', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, '--- '); + await assertDivider(page, 1); + + await waitNextFrame(page); + await pressEnter(page); + await type(page, '--- '); + await assertDivider(page, 2); + await assertRichTexts(page, ['', '']); + + await pressArrowUp(page); + await assertBlockSelections(page, ['5']); + await pressArrowUp(page); + await assertBlockSelections(page, []); + await assertRichTextInlineRange(page, 0, 0); + await pressBackspace(page); + await assertRichTexts(page, ['']); + await assertBlockSelections(page, ['3']); + await assertDivider(page, 2); +}); + +test('Delete the second divider between two dividers by forwardDelete', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, '--- '); + await assertDivider(page, 1); + + await pressEnter(page); + await type(page, '--- '); + await assertDivider(page, 2); + await pressEscape(page); + await pressArrowUp(page); + await pressForwardDelete(page); + await assertDivider(page, 1); + await assertRichTexts(page, ['', '', '']); +}); + +test('should delete line with content after divider not lose content', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, '--- '); + await type(page, '123'); + await assertDivider(page, 1); + // Jump to line start + await page.keyboard.press(`${SHORT_KEY}+ArrowLeft`, { delay: 50 }); + await waitNextFrame(page); + await pressBackspace(page, 2); + await assertDivider(page, 0); + await assertRichTexts(page, ['', '123']); +}); + +test('should forwardDelete divider works properly', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, '123'); + await pressEnter(page); + await type(page, '--- '); + await assertDivider(page, 1); + // Jump to first line start + await pressEscape(page); + await pressArrowUp(page); + await page.keyboard.press(`${SHORT_KEY}+ArrowRight`, { delay: 50 }); + await pressForwardDelete(page); + await assertDivider(page, 0); + await assertRichTexts(page, ['123', '', '']); +}); + +test('the cursor should move to closest editor block when clicking outside container', async ({ + page, +}) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/toeverything/blocksuite/pull/570', + }); + // This test only works in playwright or touch device! + test.fail(); + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + const text2 = page.locator('[data-block-id="3"] .inline-editor'); + const rect = await text2.boundingBox(); + assertExists(rect); + + // The behavior of mouse click is similar to touch in mobile device + // await page.mouse.click(rect.x - 50, rect.y + 5); + await page.mouse.move(rect.x - 50, rect.y + 5); + await page.mouse.down(); + await page.mouse.up(); + + await pressArrowLeft(page, 4); + await pressBackspace(page); + await waitNextFrame(page); + await assertRichTexts(page, ['123456', '789']); + + await undoByKeyboard(page); + await waitNextFrame(page); + + // await page.mouse.click(rect.x + rect.width + 50, rect.y + 5); + await page.mouse.move(rect.x + rect.width + 50, rect.y + 5); + await page.mouse.down(); + await page.mouse.up(); + await waitNextFrame(page); + + await pressArrowLeft(page); + await pressBackspace(page); + await waitNextFrame(page); + await assertRichTexts(page, ['123', '46', '789']); +}); + +test('should not crash when mouse over the left side of the list block prefix', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeLists(page); + await assertRichTexts(page, ['123', '456', '789']); + await dragBetweenIndices(page, [1, 2], [1, 0]); + await copyByKeyboard(page); + assertClipItems(page, 'text/plain', '45'); + + // `456` + const prefixIconRect = await page.evaluate(() => { + const block = document.querySelector('[data-block-id="4"]'); + if (!block) { + throw new Error(); + } + const prefixIcon = block.querySelector('.affine-list-block__prefix '); + if (!prefixIcon) { + throw new Error(); + } + return prefixIcon.getBoundingClientRect(); + }); + + await dragBetweenIndices( + page, + [1, 2], + [1, 0], + { x: 0, y: 0 }, + { x: 0, y: 0 }, + { + beforeMouseUp: async () => { + await page.mouse.move(prefixIconRect.left - 1, prefixIconRect.top); + }, + } + ); + + await copyByKeyboard(page); + assertClipItems(page, 'text/plain', '45'); +}); + +test('should set the last block to end the range after when leaving the affine-note', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + await dragBetweenIndices(page, [0, 2], [2, 1]); + await copyByKeyboard(page); + assertClipItems(page, 'text/plain', '34567'); + // blur + await page.mouse.click(0, 0); + + await dragBetweenIndices( + page, + [0, 2], + [2, 1], + { x: 0, y: 0 }, + { x: 0, y: 30 } // drag below the bottom of the last block + ); + await copyByKeyboard(page); + assertClipItems(page, 'text/plain', '3456789'); +}); + +test('should set the first block to start the range before when leaving the affine-note-block-container', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + await dragBetweenIndices(page, [2, 1], [0, 2]); + await copyByKeyboard(page); + assertClipItems(page, 'text/plain', '34567'); + // blur + await page.mouse.click(0, 0); + + await dragBetweenIndices( + page, + [2, 1], + [0, 2], + { x: 0, y: 0 }, + { x: 0, y: -30 } // drag above the top of the first block + ); + await copyByKeyboard(page); + assertClipItems(page, 'text/plain', '1234567'); +}); + +test('should select texts on cross-note dragging', async ({ page }) => { + await enterPlaygroundRoom(page); + const { rootId } = await initEmptyParagraphState(page); + await initThreeParagraphs(page); + + await initEmptyParagraphState(page, rootId); + + // focus last block in first note + await setInlineRangeInInlineEditor( + page, + { + index: 3, + length: 0, + }, + 3 + ); + // goto next note + await pressArrowDown(page); + await waitNextFrame(page); + await type(page, 'ABC'); + + await assertRichTexts(page, ['123', '456', '789', 'ABC']); + + // blur + await page.mouse.click(0, 0); + + await dragBetweenIndices( + page, + [0, 2], + [3, 1], + { x: 0, y: 0 }, + { x: 0, y: 30 } // drag below the bottom of the last block + ); + + await copyByKeyboard(page); + assertClipItems(page, 'text/plain', '3456789ABC'); +}); + +test('should select full text of the first block when leaving the affine-note-block-container in edgeless mode', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + const ids = await initEmptyEdgelessState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + await switchEditorMode(page); + await activeNoteInEdgeless(page, ids.noteId); + await dragBetweenIndices(page, [2, 1], [0, 2], undefined, undefined, { + click: true, + }); + await copyByKeyboard(page); + assertClipItems(page, 'text/plain', '34567'); + + const containerRect = await page.evaluate(() => { + const container = document.querySelector('.affine-note-block-container'); + if (!container) { + throw new Error(); + } + return container.getBoundingClientRect(); + }); + + await dragBetweenIndices( + page, + [2, 1], + [0, 2], + { x: 0, y: 0 }, + { x: 0, y: 0 }, // drag above the top of the first block + { + beforeMouseUp: async () => { + await page.mouse.move(containerRect.left, containerRect.top - 30); + }, + } + ); +}); + +test('should add a new line when clicking the bottom of the last non-text block', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + await pressEnter(page); + await waitNextFrame(page); + + // code block + await type(page, '```'); + await pressEnter(page); + + const locator = page.locator('affine-code'); + await expect(locator).toBeVisible(); + + await type(page, 'ABC'); + await waitNextFrame(page); + await assertRichTexts(page, ['123', '456', '789', 'ABC']); +}); + +test('should select texts on dragging around the page', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + const coord = await getIndexCoordinate(page, [1, 2]); + + // blur + await page.mouse.click(0, 0); + await page.mouse.move(coord.x, coord.y); + await page.mouse.down(); + // 123 + // 45|6 + // 789| + await page.mouse.move(coord.x + 26, coord.y + 90, { steps: 20 }); + await page.mouse.up(); + await page.keyboard.press('Backspace'); + await waitNextFrame(page); + await assertRichTexts(page, ['123', '45']); + + await waitNextFrame(page); + await undoByKeyboard(page); + + // blur + await page.mouse.click(0, 0); + await page.mouse.move(coord.x, coord.y); + await page.mouse.down(); + await page.mouse.move(coord.x + 26, coord.y + 90, { steps: 20 }); + await page.mouse.up(); + await page.keyboard.press('Backspace'); + await waitNextFrame(page); + await assertRichTexts(page, ['123', '45']); +}); + +test('indent native multi-selection block', async ({ page }, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await pressEnter(page); + await type(page, '012'); + await assertRichTexts(page, ['123', '456', '789', '012']); + + const from = { + blockId: '3', + index: 1, + length: 2, + }; + const to = { + blockId: '5', + index: 0, + length: 1, + }; + + await setSelection(page, 3, 1, 5, 1); + await assertTextSelection(page, from, to); + await waitNextFrame(page); + await pressTab(page); + // should restore selection + await assertTextSelection(page, from, to); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_after_tab.json` + ); + + await setSelection(page, 3, 1, 5, 1); + await assertTextSelection(page, from, to); + await waitNextFrame(page); + await pressShiftTab(page); + // should restore selection + await assertTextSelection(page, from, to); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_after_shift_tab.json` + ); +}); + +test('should clear native selection before block selection', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + await dragBetweenIndices( + page, + [1, 3], + [1, 0], + { x: 0, y: 0 }, + { x: 0, y: 0 }, + { steps: 20 } + ); + + const text0 = await getInlineSelectionText(page); + + // `123` + const first = await page.evaluate(() => { + const first = document.querySelector('[data-block-id="2"]'); + if (!first) { + throw new Error(); + } + return first.getBoundingClientRect(); + }); + + await dragBetweenCoords( + page, + { + x: first.right + 10, + y: first.top + 1, + }, + { + x: first.right - 10, + y: first.top + 2, + } + ); + + await waitNextFrame(page); + const textCount = await page.evaluate(() => { + return window.getSelection()?.rangeCount || 0; + }); + + expect(text0).toBe('456'); + expect(textCount).toBe(0); + const rects = page.locator('affine-block-selection').locator('visible=true'); + await expect(rects).toHaveCount(1); +}); + +// ↑ +test('should keep native range selection when scrolling backward with the scroll wheel', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + for (let i = 0; i < 10; i++) { + await pressEnter(page); + } + + await type(page, '987'); + await pressEnter(page); + await type(page, '654'); + await pressEnter(page); + await type(page, '321'); + + const data = Array.from({ length: 9 }, () => ''); + data.unshift('123', '456', '789'); + data.push('987', '654', '321'); + await assertRichTexts(page, data); + + const blockHeight = await page.evaluate(() => { + const viewport = document.querySelector('.affine-page-viewport'); + if (!viewport) { + throw new Error(); + } + const distance = viewport.scrollHeight - viewport.clientHeight; + viewport.scrollTo(0, distance); + const container = viewport.querySelector( + 'affine-note .affine-block-children-container' + ); + if (!container) { + throw new Error(); + } + const first = container.firstElementChild; + if (!first) { + throw new Error(); + } + const second = first.nextElementSibling; + if (!second) { + throw new Error(); + } + return ( + second.getBoundingClientRect().top - first.getBoundingClientRect().top + ); + }); + await page.waitForTimeout(250); + + await page.mouse.move(0, 0); + + await dragBetweenIndices( + page, + [14, 3], + [14, 0], + { x: 0, y: 0 }, + { x: 0, y: 0 }, + { + // dont release mouse + beforeMouseUp: async () => { + await page.mouse.wheel(0, -blockHeight * 4); + await page.waitForTimeout(250); + }, + } + ); + + await copyByKeyboard(page); + assertClipItems(page, 'text/plain', '987654321'); +}); + +// ↓ +test('should keep native range selection when scrolling forward with the scroll wheel', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + for (let i = 0; i < 10; i++) { + await pressEnter(page); + } + + await type(page, '987'); + await pressEnter(page); + await type(page, '654'); + await pressEnter(page); + await type(page, '321'); + + const data = Array.from({ length: 9 }, () => ''); + data.unshift('123', '456', '789'); + data.push('987', '654', '321'); + await assertRichTexts(page, data); + + const blockHeight = await page.evaluate(() => { + const viewport = document.querySelector('.affine-page-viewport'); + if (!viewport) { + throw new Error(); + } + const container = viewport.querySelector( + 'affine-note .affine-block-children-container' + ); + if (!container) { + throw new Error(); + } + const first = container.firstElementChild; + if (!first) { + throw new Error(); + } + const second = first.nextElementSibling; + if (!second) { + throw new Error(); + } + return ( + second.getBoundingClientRect().top - first.getBoundingClientRect().top + ); + }); + await page.waitForTimeout(250); + + await page.evaluate(() => { + document.querySelector('.affine-page-viewport')?.scrollTo(0, 0); + }); + await page.mouse.move(0, 0); + + await dragBetweenIndices( + page, + [0, 0], + [0, 3], + { x: 0, y: 0 }, + { x: 0, y: 0 }, + { + // dont release mouse + beforeMouseUp: async () => { + await page.mouse.wheel(0, blockHeight * 3); + await page.waitForTimeout(250); + }, + } + ); + + await copyByKeyboard(page); + assertClipItems(page, 'text/plain', '123456789'); +}); + +test('should not show option menu of image on native selection', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initImageState(page); + await activeEmbed(page); + + await expect(page.locator('.affine-image-toolbar-container')).toHaveCount(1); + + await pressEscape(page); + await pressEnter(page); + await type(page, '123'); + + await page.mouse.click(0, 0); + + await dragBetweenIndices( + page, + [0, 1], + [0, 0], + { x: 0, y: 0 }, + { x: -40, y: 0 } + ); + + await waitNextFrame(page); + + await copyByKeyboard(page); + assertClipItems(page, 'text/plain', '123'); + + await page.mouse.click(0, 0); + + await dragBetweenIndices( + page, + [0, 1], + [0, 0], + { x: 0, y: 0 }, + { x: -40, y: -100 } + ); + + await waitNextFrame(page); + + await copyByKeyboard(page); + assertClipItems(page, 'text/plain', '123'); + + await expect(page.locator('.affine-image-toolbar-container')).toHaveCount(0); +}); + +test('should select with shift-click', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + await focusRichText(page); + + await page.click('[data-block-id="4"] [data-v-text]', { + modifiers: ['Shift'], + }); + expect(await getSelectedText(page)).toContain('4567'); +}); + +test('should collapse to end when press arrow-right on multi-line selection', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + await dragBetweenIndices(page, [0, 0], [1, 2]); + expect(await getSelectedText(page)).toBe('12345'); + await pressArrowRight(page); + await pressBackspace(page); + await assertRichTexts(page, ['123', '46', '789']); +}); + +test('should collapse to start when press arrow-left on multi-line selection', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + await dragBetweenIndices(page, [0, 1], [1, 2]); + expect(await getSelectedText(page)).toBe('2345'); + await pressArrowLeft(page); + await pressBackspace(page); + await assertRichTexts(page, ['23', '456', '789']); +}); + +test('should select when clicking on blank area in edgeless mode', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + const ids = await initEmptyEdgelessState(page); + await initThreeParagraphs(page); + await assertRichTexts(page, ['123', '456', '789']); + + await switchEditorMode(page); + await activeNoteInEdgeless(page, ids.noteId); + + const r1 = await page.locator('[data-block-id="3"]').boundingBox(); + const r2 = await page.locator('[data-block-id="4"]').boundingBox(); + const r3 = await page.locator('[data-block-id="5"]').boundingBox(); + if (!r1 || !r2 || !r3) { + throw new Error(); + } + + await click(page, { x: r3.x + 40, y: r3.y + 5 }); + await waitNextFrame(page); + + expect(await getInlineSelectionText(page)).toBe('789'); +}); + +test('press ArrowLeft in the start of first paragraph should not focus on title', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + + await focusRichText(page, 0); + await type(page, '123'); + await pressArrowLeft(page, 5); + + await type(page, 'title'); + await assertTitle(page, ''); +}); + +test('should not scroll page when mouse is click down', async ({ page }) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/toeverything/blocksuite/issues/5034', + }); + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + for (let i = 0; i < 10; i++) { + await pressEnter(page); + } + for (let i = 0; i < 20; i++) { + await type(page, String(i)); + await pressShiftEnter(page); + } + await assertRichTexts(page, [ + ...' '.repeat(9).split(' '), // 10 empty paragraph + '0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n', + ]); + + await scrollToTop(page); + await focusRichText(page, 0); + + const editorHost = getEditorHostLocator(page); + const longText = editorHost.locator('rich-text').nth(10); + const rect = await longText.boundingBox(); + if (!rect) throw new Error(); + await page.mouse.move(rect.x + rect.width / 2, rect.y + rect.height / 2); + await assertRichTextInlineRange(page, 0, 0); + + await page.mouse.down(); + await assertRichTextInlineRange(page, 10, 22); + // simulate user click down and wait for 500ms + await waitNextFrame(page, 500); + await page.mouse.up(); + await assertRichTextInlineRange(page, 10, 22); +}); + +test('scroll vertically when inputting long text in a block', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + for (let i = 0; i < 40; i++) { + await type(page, String(i)); + await pressShiftEnter(page); + } + + const viewportScrollTop = await page.evaluate(() => { + const viewport = document.querySelector('.affine-page-viewport'); + if (!viewport) { + throw new Error('viewport not found'); + } + return viewport.scrollTop; + }); + + expect(viewportScrollTop).toBeGreaterThan(100); +}); + +test('scroll vertically when adding multiple blocks', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + for (let i = 0; i < 40; i++) { + await type(page, String(i)); + await pressEnter(page); + } + + const viewportScrollTop = await page.evaluate(() => { + const viewport = document.querySelector('.affine-page-viewport'); + if (!viewport) { + throw new Error('viewport not found'); + } + return viewport.scrollTop; + }); + + expect(viewportScrollTop).toBeGreaterThan(400); +}); + +test('click to select divided', async ({ page }) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/toeverything/blocksuite/issues/4547', + }); + + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + + await focusRichText(page); + await type(page, '--- '); + await assertDivider(page, 1); + + await page.click('affine-divider'); + const selectedBlocks = page + .locator('affine-block-selection') + .locator('visible=true'); + await expect(selectedBlocks).toHaveCount(1); + + await pressForwardDelete(page); + await assertDivider(page, 0); +}); + +test('auto-scroll when creating a new paragraph-block by pressing enter', async ({ + page, +}) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/toeverything/blocksuite/issues/4547', + }); + + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + + await focusRichText(page); + + const getScrollTop = async () => { + return page.evaluate(() => { + const viewport = document.querySelector('.affine-page-viewport'); + if (!viewport) { + throw new Error(); + } + return viewport.scrollTop; + }); + }; + + await pressEnter(page, 30); + const oldScrollTop = await getScrollTop(); + + await pressEnter(page, 30); + const newScrollTop = await getScrollTop(); + + expect(newScrollTop).toBeGreaterThan(oldScrollTop); +}); + +test('Use arrow up and down to select two types of block', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, '123'); + await pressEnter(page); + await type(page, '--- --- '); + await type(page, '123'); + await pressEnter(page); + await type(page, '--- 123'); + // 123 + // --- + // --- + // 123 + // --- + // 123 + + await assertDivider(page, 3); + await assertRichTexts(page, ['123', '123', '123']); + + // from bottom to top + await assertNativeSelectionRangeCount(page, 1); + await assertRichTextInlineRange(page, 2, 3); + await pressArrowUp(page); + await assertNativeSelectionRangeCount(page, 0); + await assertBlockSelections(page, ['7']); + await pressArrowUp(page); + await assertNativeSelectionRangeCount(page, 1); + await assertRichTextInlineRange(page, 1, 3); + await pressArrowUp(page); + await assertNativeSelectionRangeCount(page, 0); + await assertBlockSelections(page, ['5']); + await pressArrowUp(page); + await assertNativeSelectionRangeCount(page, 0); + await assertBlockSelections(page, ['4']); + await pressArrowUp(page); + await assertNativeSelectionRangeCount(page, 1); + await assertRichTextInlineRange(page, 0, 3); + + // from top to bottom + await pressArrowDown(page); + await assertNativeSelectionRangeCount(page, 0); + await assertBlockSelections(page, ['4']); + await pressArrowDown(page); + await assertNativeSelectionRangeCount(page, 0); + await assertBlockSelections(page, ['5']); + await pressArrowDown(page); + await assertNativeSelectionRangeCount(page, 1); + await assertRichTextInlineRange(page, 1, 0); + await pressArrowDown(page); + await assertNativeSelectionRangeCount(page, 0); + await assertBlockSelections(page, ['7']); + await pressArrowDown(page); + await assertNativeSelectionRangeCount(page, 1); + await assertRichTextInlineRange(page, 2, 0); +}); + +test.describe('should scroll text to view when drag to select at top or bottom edge', () => { + test('from top to bottom', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + for (let i = 0; i < 50; i++) { + await type(page, `${i}`); + await pressEnter(page); + } + + const startCoord = await getIndexCoordinate(page, [49, 2]); + const endCoord = await getIndexCoordinate(page, [0, 0]); + + // simulate actual drag to select from bottom to top + await page.mouse.move(startCoord.x, startCoord.y); + await page.mouse.down(); + await page.mouse.move(endCoord.x, 0); // move to top edge + await page.waitForTimeout(5000); + await page.mouse.up(); + + const firstParagraph = page.locator('[data-block-id="2"]'); + await expect(firstParagraph).toBeInViewport(); + }); + + // playwright doesn't auto scroll when drag selection to bottom edge + test.skip('from bottom to top', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + for (let i = 0; i < 50; i++) { + await type(page, `${i}`); + await pressEnter(page); + } + + const firstParagraph = page.locator('[data-block-id="2"]'); + await firstParagraph.scrollIntoViewIfNeeded(); + + const startCoord = await getIndexCoordinate(page, [0, 0]); + const endCoord = await getIndexCoordinate(page, [49, 2]); + + const viewportHeight = await page.evaluate( + () => document.documentElement.clientHeight + ); + + // simulate actual drag to select from top to bottom + await page.mouse.move(startCoord.x, startCoord.y); + await page.mouse.down(); + await page.mouse.move(endCoord.x, viewportHeight - 10); // move to bottom edge + await page.waitForTimeout(5000); + await page.mouse.up(); + + const lastParagraph = page.locator('[data-block-id="51"]'); + await expect(lastParagraph).toBeInViewport(); + }); +}); + +test('abnormal cursor jumping', async ({ page }) => { + // https://github.com/toeverything/blocksuite/pull/8552 + + await enterPlaygroundRoom(page); + await initImageState(page); + + await pressEnter(page); + await page.locator('affine-image block-zero-width .block-zero-width').click(); + await pressArrowUp(page); + await pressTab(page); + await pressArrowDown(page); + await pressTab(page); + await pressEnter(page, 12); + + const image = page.locator('affine-image'); + const rect = await image.boundingBox(); + // make sure the image is out of view + expect(rect?.y).toBeLessThan(0); + + await setSelection(page, 4, 0, 4, 0); + await type(page, 'aaaaaaaaaaaaaa'); + await page.locator('[data-block-id="4"]').dblclick({ + position: { + x: 50, + y: 5, + }, + }); + const newRect = await image.boundingBox(); + expect(rect).toEqual(newRect); +}); + +test('unexpected scroll when clicking padding area', async ({ page }) => { + // https://github.com/toeverything/blocksuite/pull/8678 + + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + await pressEnter(page, 30); + await pressArrowUp(page, 5); + await type(page, '1. aaa\nbbb'); + await pressTab(page); + + const list = page.locator('[data-block-id="34"]'); + const listRect = await list.boundingBox(); + assertExists(listRect); + await page.mouse.click(listRect.x - 30, listRect.y + 5); + const newListRect = await list.boundingBox(); + // not scroll + expect(listRect).toEqual(newListRect); + + await pressArrowUp(page, 4); + await type(page, '/table\n'); + const database = page.locator('affine-database'); + const databaseRect = await database.boundingBox(); + assertExists(databaseRect); + await page.mouse.click( + databaseRect.x + databaseRect.width + 10, + databaseRect.y + 10 + ); + const newDatabaseRect = await database.boundingBox(); + // not scroll + expect(databaseRect).toEqual(newDatabaseRect); +}); diff --git a/blocksuite/tests-legacy/slash-menu.spec.ts b/blocksuite/tests-legacy/slash-menu.spec.ts new file mode 100644 index 0000000000000..ec3569d27a79f --- /dev/null +++ b/blocksuite/tests-legacy/slash-menu.spec.ts @@ -0,0 +1,990 @@ +import { expect } from '@playwright/test'; + +import { addNote, switchEditorMode } from './utils/actions/edgeless.js'; +import { + pressArrowDown, + pressArrowLeft, + pressArrowRight, + pressArrowUp, + pressBackspace, + pressEnter, + pressEscape, + pressShiftEnter, + pressShiftTab, + pressTab, + redoByKeyboard, + SHORT_KEY, + type, + undoByKeyboard, +} from './utils/actions/keyboard.js'; +import { + captureHistory, + enterPlaygroundRoom, + focusRichText, + getInlineSelectionText, + getPageSnapshot, + getSelectionRect, + initEmptyEdgelessState, + initEmptyParagraphState, + insertThreeLevelLists, + waitNextFrame, +} from './utils/actions/misc.js'; +import { + assertAlmostEqual, + assertBlockCount, + assertExists, + assertRichTexts, + assertStoreMatchJSX, +} from './utils/asserts.js'; +import { test } from './utils/playwright.js'; + +test.describe('slash menu should show and hide correctly', () => { + test.beforeEach(async ({ page }) => { + await enterPlaygroundRoom(page); + }); + + test("slash menu should show when user input '/'", async ({ page }) => { + await initEmptyParagraphState(page); + const slashMenu = page.locator(`.slash-menu`); + await focusRichText(page); + await type(page, '/'); + await expect(slashMenu).toBeVisible(); + }); + + // Playwright dose not support IME + // https://github.com/microsoft/playwright/issues/5777 + test.skip("slash menu should show when user input '、'", async ({ page }) => { + await initEmptyParagraphState(page); + const slashMenu = page.locator(`.slash-menu`); + await focusRichText(page); + await type(page, '、'); + + await expect(slashMenu).toBeVisible(); + }); + + test('slash menu should hide after click away', async ({ page }) => { + const id = await initEmptyParagraphState(page); + const paragraphId = id.paragraphId; + const slashMenu = page.locator(`.slash-menu`); + await focusRichText(page); + await type(page, '/'); + await expect(slashMenu).toBeVisible(); + // Click outside should close slash menu + await page.mouse.click(0, 50); + await expect(slashMenu).toBeHidden(); + await assertStoreMatchJSX( + page, + ` +`, + paragraphId + ); + }); + + test('slash menu should hide after input whitespace', async ({ page }) => { + await initEmptyParagraphState(page); + const slashMenu = page.locator(`.slash-menu`); + await focusRichText(page); + await type(page, '/'); + await expect(slashMenu).toBeVisible(); + await type(page, ' '); + await expect(slashMenu).toBeHidden(); + await assertRichTexts(page, ['/ ']); + await pressBackspace(page); + await expect(slashMenu).toBeVisible(); + + await type(page, 'head'); + await expect(slashMenu).toBeVisible(); + await type(page, ' '); + await expect(slashMenu).toBeHidden(); + await pressBackspace(page); + await expect(slashMenu).toBeVisible(); + }); + + test('delete the slash symbol should close the slash menu', async ({ + page, + }) => { + const id = await initEmptyParagraphState(page); + const paragraphId = id.paragraphId; + const slashMenu = page.locator(`.slash-menu`); + await focusRichText(page); + await type(page, '/'); + await expect(slashMenu).toBeVisible(); + + await pressBackspace(page); + await expect(slashMenu).toBeHidden(); + await assertStoreMatchJSX( + page, + ` +`, + paragraphId + ); + }); + + test('typing something that does not match should close the slash menu', async ({ + page, + }) => { + await initEmptyParagraphState(page); + const slashMenu = page.locator(`.slash-menu`); + await focusRichText(page); + await type(page, '/'); + await expect(slashMenu).toBeVisible(); + + await type(page, '_'); + await expect(slashMenu).toBeHidden(); + await assertRichTexts(page, ['/_']); + + // And pressing backspace immediately should reappear the slash menu + await pressBackspace(page); + await expect(slashMenu).toBeVisible(); + + await type(page, '__'); + await pressBackspace(page); + await expect(slashMenu).toBeHidden(); + }); + + test('pressing the slash key again should close the old slash menu and open new one', async ({ + page, + }) => { + await initEmptyParagraphState(page); + const slashMenu = page.locator(`.slash-menu`); + await focusRichText(page); + await type(page, '/'); + await expect(slashMenu).toBeVisible(); + + await type(page, '/'); + await expect(slashMenu).toBeVisible(); + await expect(slashMenu).toHaveCount(1); + await assertRichTexts(page, ['//']); + }); + + test('should position slash menu correctly', async ({ page }) => { + await initEmptyParagraphState(page); + const slashMenu = page.locator(`.slash-menu`); + await focusRichText(page); + await type(page, '/'); + await expect(slashMenu).toBeVisible(); + + const box = await slashMenu.boundingBox(); + if (!box) { + throw new Error("slashMenu doesn't exist"); + } + const rect = await getSelectionRect(page); + const { x, y } = box; + assertAlmostEqual(x - rect.x, 0, 10); + assertAlmostEqual(y - rect.bottom, 5, 10); + }); + + test('should move up down with arrow key', async ({ page }) => { + await initEmptyParagraphState(page); + const slashMenu = page.locator(`.slash-menu`); + await focusRichText(page); + await type(page, '/'); + await expect(slashMenu).toBeVisible(); + + const slashItems = slashMenu.locator('icon-button'); + + await pressArrowDown(page); + await expect(slashMenu).toBeVisible(); + await expect(slashItems.nth(1)).toHaveAttribute('hover', 'true'); + await expect(slashItems.nth(1).locator('.text')).toHaveText(['Heading 1']); + await assertRichTexts(page, ['/']); + + await pressArrowUp(page); + await expect(slashMenu).toBeVisible(); + await expect(slashItems.first()).toHaveAttribute('hover', 'true'); + await expect(slashItems.first().locator('.text')).toHaveText(['Text']); + await assertRichTexts(page, ['/']); + + await pressArrowUp(page); + await expect(slashMenu).toBeVisible(); + await expect(slashItems.last()).toHaveAttribute('hover', 'true'); + await expect(slashItems.last().locator('.text')).toHaveText(['Delete']); + await assertRichTexts(page, ['/']); + + await pressArrowDown(page); + await expect(slashMenu).toBeVisible(); + await expect(slashItems.first()).toHaveAttribute('hover', 'true'); + await expect(slashItems.first().locator('.text')).toHaveText(['Text']); + await assertRichTexts(page, ['/']); + }); + + test('slash menu hover state', async ({ page }) => { + await initEmptyParagraphState(page); + const slashMenu = page.locator(`.slash-menu`); + await focusRichText(page); + await type(page, '/'); + await expect(slashMenu).toBeVisible(); + + const slashItems = slashMenu.locator('icon-button'); + + await pressArrowDown(page); + await expect(slashItems.nth(1)).toHaveAttribute('hover', 'true'); + + await pressArrowUp(page); + await expect(slashItems.nth(1)).toHaveAttribute('hover', 'false'); + await expect(slashItems.nth(0)).toHaveAttribute('hover', 'true'); + + await pressArrowDown(page); + await pressArrowDown(page); + await expect(slashItems.nth(2)).toHaveAttribute('hover', 'true'); + await expect(slashItems.nth(1)).toHaveAttribute('hover', 'false'); + await expect(slashItems.nth(0)).toHaveAttribute('hover', 'false'); + + await slashItems.nth(0).hover(); + await expect(slashItems.nth(0)).toHaveAttribute('hover', 'true'); + await expect(slashItems.nth(2)).toHaveAttribute('hover', 'false'); + await expect(slashItems.nth(1)).toHaveAttribute('hover', 'false'); + }); + + test('should open tooltip when hover on item', async ({ page }) => { + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, '/'); + const slashMenu = page.locator(`.slash-menu`); + await expect(slashMenu).toBeVisible(); + + const slashItems = slashMenu.locator('icon-button'); + const tooltip = page.locator('.affine-tooltip'); + + await slashItems.nth(0).hover(); + await expect(tooltip).toBeVisible(); + await expect(tooltip.locator('.tooltip-caption')).toHaveText(['Text']); + await page.mouse.move(0, 0); + await expect(tooltip).toBeHidden(); + + await slashItems.nth(1).hover(); + await expect(tooltip).toBeVisible(); + await expect(tooltip.locator('.tooltip-caption')).toHaveText([ + 'Heading #1', + ]); + await page.mouse.move(0, 0); + await expect(tooltip).toBeHidden(); + + await expect(slashItems.nth(4).locator('.text')).toHaveText([ + 'Other Headings', + ]); + await slashItems.nth(4).hover(); + await expect(tooltip).toBeHidden(); + }); + + test('press tab should move up and down', async ({ page }) => { + await initEmptyParagraphState(page); + const slashMenu = page.locator(`.slash-menu`); + await focusRichText(page); + await type(page, '/'); + await expect(slashMenu).toBeVisible(); + + const slashItems = slashMenu.locator('icon-button'); + + await pressTab(page); + await expect(slashMenu).toBeVisible(); + await expect(slashItems.nth(1)).toHaveAttribute('hover', 'true'); + await expect(slashItems.nth(1).locator('.text')).toHaveText(['Heading 1']); + await assertRichTexts(page, ['/']); + + await pressShiftTab(page); + await expect(slashMenu).toBeVisible(); + await expect(slashItems.first()).toHaveAttribute('hover', 'true'); + await expect(slashItems.first().locator('.text')).toHaveText(['Text']); + await assertRichTexts(page, ['/']); + + await pressShiftTab(page); + await expect(slashMenu).toBeVisible(); + await expect(slashItems.last()).toHaveAttribute('hover', 'true'); + await expect(slashItems.last().locator('.text')).toHaveText(['Delete']); + await assertRichTexts(page, ['/']); + + await pressTab(page); + await expect(slashMenu).toBeVisible(); + await expect(slashItems.first()).toHaveAttribute('hover', 'true'); + await expect(slashItems.first().locator('.text')).toHaveText(['Text']); + await assertRichTexts(page, ['/']); + }); + + test('should move up down with ctrl/cmd+n and ctrl/cmd+p', async ({ + page, + }) => { + await initEmptyParagraphState(page); + const slashMenu = page.locator(`.slash-menu`); + await focusRichText(page); + await type(page, '/'); + await expect(slashMenu).toBeVisible(); + + const slashItems = slashMenu.locator('icon-button'); + + await page.keyboard.press(`${SHORT_KEY}+n`); + await expect(slashMenu).toBeVisible(); + await expect(slashItems.nth(1)).toHaveAttribute('hover', 'true'); + await expect(slashItems.nth(1).locator('.text')).toHaveText(['Heading 1']); + await assertRichTexts(page, ['/']); + + await page.keyboard.press(`${SHORT_KEY}+p`); + await expect(slashMenu).toBeVisible(); + await expect(slashItems.first()).toHaveAttribute('hover', 'true'); + await expect(slashItems.first().locator('.text')).toHaveText(['Text']); + await assertRichTexts(page, ['/']); + + await page.keyboard.press(`${SHORT_KEY}+p`); + await expect(slashMenu).toBeVisible(); + await expect(slashItems.last()).toHaveAttribute('hover', 'true'); + await expect(slashItems.last().locator('.text')).toHaveText(['Delete']); + await assertRichTexts(page, ['/']); + + await page.keyboard.press(`${SHORT_KEY}+n`); + await expect(slashMenu).toBeVisible(); + await expect(slashItems.first()).toHaveAttribute('hover', 'true'); + await expect(slashItems.first().locator('.text')).toHaveText(['Text']); + await assertRichTexts(page, ['/']); + }); + + test('should open sub menu when hover on SubMenuItem', async ({ page }) => { + await initEmptyParagraphState(page); + await focusRichText(page); + + await type(page, '/'); + const slashMenu = page.locator('.slash-menu[data-testid=sub-menu-0]'); + await expect(slashMenu).toBeVisible(); + + const slashItems = slashMenu.locator('icon-button'); + + const subMenu = page.locator('.slash-menu[data-testid=sub-menu-1]'); + + let rect = await slashItems.nth(4).boundingBox(); + assertExists(rect); + await page.mouse.move(rect.x + 10, rect.y + 10); + await expect(slashMenu).toBeVisible(); + await expect(slashItems.nth(4)).toHaveAttribute('hover', 'true'); + await expect(slashItems.nth(4).locator('.text')).toHaveText([ + 'Other Headings', + ]); + await expect(subMenu).toBeVisible(); + + rect = await slashItems.nth(3).boundingBox(); + assertExists(rect); + await page.mouse.move(rect.x + 10, rect.y + 10); + await expect(slashMenu).toBeVisible(); + await expect(slashItems.nth(3)).toHaveAttribute('hover', 'true'); + await expect(slashItems.nth(3).locator('.text')).toHaveText(['Heading 3']); + await expect(subMenu).toBeHidden(); + }); + + test('should open and close menu when using left right arrow, Enter, Esc keys', async ({ + page, + }) => { + await initEmptyParagraphState(page); + await focusRichText(page); + + const slashMenu = page.locator('.slash-menu[data-testid=sub-menu-0]'); + + await type(page, '/'); + await expect(slashMenu).toBeVisible(); + await pressEscape(page); + await expect(slashMenu).toBeHidden(); + + await type(page, '/'); + await expect(slashMenu).toBeVisible(); + await pressArrowLeft(page); + await expect(slashMenu).toBeHidden(); + + // Test sub menu case + const slashItems = slashMenu.locator('icon-button'); + + await type(page, '/'); + await pressArrowDown(page, 4); + await expect(slashItems.nth(4)).toHaveAttribute('hover', 'true'); + await expect(slashItems.nth(4).locator('.text')).toHaveText([ + 'Other Headings', + ]); + + const subMenu = page.locator('.slash-menu[data-testid=sub-menu-1]'); + + await pressArrowRight(page); + await expect(slashMenu).toBeVisible(); + await expect(subMenu).toBeVisible(); + + await pressArrowLeft(page); + await expect(slashMenu).toBeVisible(); + await expect(subMenu).toBeHidden(); + + await pressEnter(page); + await expect(slashMenu).toBeVisible(); + await expect(subMenu).toBeVisible(); + + await pressEscape(page); + await expect(slashMenu).toBeVisible(); + await expect(subMenu).toBeHidden(); + }); + + test('show close current all submenu when typing', async ({ page }) => { + await initEmptyParagraphState(page); + await focusRichText(page); + + const slashMenu = page.locator('.slash-menu[data-testid=sub-menu-0]'); + const subMenu = page.locator('.slash-menu[data-testid=sub-menu-1]'); + const slashItems = slashMenu.locator('icon-button'); + + await type(page, '/'); + await expect(slashMenu).toBeVisible(); + await pressArrowDown(page, 4); + await expect(slashItems.nth(4)).toHaveAttribute('hover', 'true'); + await expect(slashItems.nth(4).locator('.text')).toHaveText([ + 'Other Headings', + ]); + await pressEnter(page); + await expect(subMenu).toBeVisible(); + + await type(page, 'h'); + await expect(subMenu).toBeHidden(); + }); + + test('should allow only pressing modifier key', async ({ page }) => { + await initEmptyParagraphState(page); + await focusRichText(page); + + const slashMenu = page.locator(`.slash-menu`); + + await type(page, '/'); + await expect(slashMenu).toBeVisible(); + + await page.keyboard.press(SHORT_KEY); + await expect(slashMenu).toBeVisible(); + + await page.keyboard.press('Shift'); + await expect(slashMenu).toBeVisible(); + }); + + test('should allow other hotkey to passthrough', async ({ page }) => { + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'hello'); + await pressEnter(page); + await type(page, 'world'); + + const slashMenu = page.locator(`.slash-menu`); + + await type(page, '/'); + await expect(slashMenu).toBeVisible(); + + await page.keyboard.press(`${SHORT_KEY}+a`); + await expect(slashMenu).toBeHidden(); + await assertRichTexts(page, ['hello', 'world/']); + + const selected = await getInlineSelectionText(page); + expect(selected).toBe('world/'); + }); + + test('can input search input after click menu', async ({ page }) => { + await initEmptyParagraphState(page); + const slashMenu = page.locator(`.slash-menu`); + await focusRichText(page); + await type(page, '/'); + await expect(slashMenu).toBeVisible(); + + const box = await slashMenu.boundingBox(); + if (!box) { + throw new Error("slashMenu doesn't exist"); + } + const { x, y } = box; + await page.mouse.click(x + 10, y + 10); + await expect(slashMenu).toBeVisible(); + await type(page, 'a'); + await assertRichTexts(page, ['/a']); + }); +}); + +test.describe('slash menu should not be shown in ignored blocks', () => { + test('code block', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + await type(page, '```'); + await pressEnter(page); + await type(page, '/'); + await expect(page.locator('.slash-menu')).toBeHidden(); + }); +}); + +test('should slash menu works with fast type', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + await type(page, 'a/text', 0); + const slashMenu = page.locator(`.slash-menu`); + await expect(slashMenu).toBeVisible(); +}); + +test('should clean slash string after soft enter', async ({ page }) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/toeverything/blocksuite/issues/1126', + }); + await enterPlaygroundRoom(page); + const { paragraphId } = await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, 'hello'); + await pressShiftEnter(page); + await waitNextFrame(page); + await type(page, '/copy'); + await pressEnter(page); + + await assertStoreMatchJSX( + page, + ` + `, + paragraphId + ); +}); + +test.describe('slash search', () => { + test('should slash menu search and keyboard works', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + const slashMenu = page.locator(`.slash-menu`); + const slashItems = slashMenu.locator('icon-button'); + + await type(page, '/'); + await expect(slashMenu).toBeVisible(); + + // search should active the first item + await type(page, 'co'); + await expect(slashItems).toHaveCount(3); + await expect(slashItems.nth(0).locator('.text')).toHaveText(['Copy']); + await expect(slashItems.nth(1).locator('.text')).toHaveText(['Code Block']); + await expect(slashItems.nth(0)).toHaveAttribute('hover', 'true'); + + await type(page, 'p'); + await expect(slashItems).toHaveCount(1); + await expect(slashItems.nth(0).locator('.text')).toHaveText(['Copy']); + + // assert backspace works + await pressBackspace(page); + await expect(slashItems).toHaveCount(3); + await expect(slashItems.nth(0).locator('.text')).toHaveText(['Copy']); + await expect(slashItems.nth(1).locator('.text')).toHaveText(['Code Block']); + await expect(slashItems.nth(0)).toHaveAttribute('hover', 'true'); + }); + + test('slash menu supports fuzzy search', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + const slashMenu = page.locator(`.slash-menu`); + const slashItems = slashMenu.locator('icon-button'); + + await type(page, '/'); + await expect(slashMenu).toBeVisible(); + + await type(page, 'c'); + await expect(slashItems).toHaveCount(8); + await expect(slashItems.nth(0).locator('.text')).toHaveText(['Copy']); + await expect(slashItems.nth(1).locator('.text')).toHaveText(['Italic']); + await expect(slashItems.nth(2).locator('.text')).toHaveText(['New Doc']); + await expect(slashItems.nth(3).locator('.text')).toHaveText(['Duplicate']); + await expect(slashItems.nth(4).locator('.text')).toHaveText(['Code Block']); + await expect(slashItems.nth(5).locator('.text')).toHaveText(['Linked Doc']); + await expect(slashItems.nth(6).locator('.text')).toHaveText(['Attachment']); + await type(page, 'b'); + await expect(slashItems.nth(0).locator('.text')).toHaveText(['Code Block']); + }); + + test('slash menu supports alias search', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + await type(page, '/'); + const slashMenu = page.locator(`.slash-menu`); + await expect(slashMenu).toBeVisible(); + + const slashItems = slashMenu.locator('icon-button'); + await type(page, 'database'); + await expect(slashItems).toHaveCount(2); + await expect(slashItems.nth(0).locator('.text')).toHaveText(['Table View']); + await expect(slashItems.nth(1).locator('.text')).toHaveText([ + 'Kanban View', + ]); + await type(page, 'v'); + await expect(slashItems).toHaveCount(0); + }); +}); + +test('should focus on code blocks created by the slash menu', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + await type(page, '000'); + + await type(page, '/code'); + const slashMenu = page.locator(`.slash-menu`); + await expect(slashMenu).toBeVisible(); + + const codeBlock = page.getByTestId('Code Block'); + await codeBlock.click(); + await expect(slashMenu).toBeHidden(); + + await focusRichText(page); // FIXME: flaky selection asserter + await type(page, '111'); + await assertRichTexts(page, ['000111']); +}); + +// Selection is not yet available in edgeless +test('slash menu should work in edgeless mode', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + + await switchEditorMode(page); + + await addNote(page, '/', 30, 40); + await assertRichTexts(page, ['', '/']); + + const slashMenu = page.locator(`.slash-menu`); + await expect(slashMenu).toBeVisible(); +}); + +test.describe('slash menu with date & time', () => { + test("should insert Today's time string", async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + await type(page, '/'); + const slashMenu = page.locator(`.slash-menu`); + await expect(slashMenu).toBeVisible(); + + const todayBlock = page.getByTestId('Today'); + await todayBlock.click(); + await expect(slashMenu).toBeHidden(); + + const date = new Date(); + const strTime = date.toISOString().split('T')[0]; + + await assertRichTexts(page, [strTime]); + }); + + test("should create Tomorrow's time string", async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + await type(page, '/'); + const slashMenu = page.locator(`.slash-menu`); + await expect(slashMenu).toBeVisible(); + + const todayBlock = page.getByTestId('Tomorrow'); + await todayBlock.click(); + await expect(slashMenu).toBeHidden(); + + const date = new Date(); + date.setDate(date.getDate() + 1); + const strTime = date.toISOString().split('T')[0]; + + await assertRichTexts(page, [strTime]); + }); + + test("should insert Yesterday's time string", async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + await type(page, '/'); + const slashMenu = page.locator(`.slash-menu`); + await expect(slashMenu).toBeVisible(); + + const todayBlock = page.getByTestId('Yesterday'); + await todayBlock.click(); + await expect(slashMenu).toBeHidden(); + + const date = new Date(); + date.setDate(date.getDate() - 1); + const strTime = date.toISOString().split('T')[0]; + + await assertRichTexts(page, [strTime]); + }); +}); + +test.describe('slash menu with style', () => { + test('should style text line works', async ({ page }) => { + await enterPlaygroundRoom(page); + const { paragraphId } = await initEmptyParagraphState(page); + await focusRichText(page); + + await type(page, 'hello/'); + const slashMenu = page.locator(`.slash-menu`); + await expect(slashMenu).toBeVisible(); + const bold = page.getByTestId('Bold'); + await bold.click(); + await assertStoreMatchJSX( + page, + ` + + + + } + prop:type="text" +/>`, + paragraphId + ); + }); + + test('should style empty line works', async ({ page }) => { + await enterPlaygroundRoom(page); + const { paragraphId } = await initEmptyParagraphState(page); + await focusRichText(page); + + await type(page, '/'); + const slashMenu = page.locator(`.slash-menu`); + await expect(slashMenu).toBeVisible(); + const bold = page.getByTestId('Bold'); + await bold.click(); + await page.waitForTimeout(50); + await type(page, 'hello'); + await assertStoreMatchJSX( + page, + ` + + + + } + prop:type="text" +/>`, + paragraphId + ); + }); +}); + +test('should insert database', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusRichText(page); + + await assertBlockCount(page, 'paragraph', 1); + await type(page, '/'); + const tableBlock = page.getByTestId('Table View'); + await tableBlock.click(); + await assertBlockCount(page, 'paragraph', 0); + await assertBlockCount(page, 'database', 1); + + const database = page.locator('affine-database'); + await expect(database).toBeVisible(); + const tagColumn = page.locator('.affine-database-column').nth(1); + expect(await tagColumn.innerText()).toBe('Status'); + const defaultRows = page.locator('.affine-database-block-row'); + expect(await defaultRows.count()).toBe(4); +}); + +test.describe('slash menu with customize menu', () => { + test('can remove specified menus', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await page.evaluate(async () => { + // https://github.com/lit/lit/blob/84df6ef8c73fffec92384891b4b031d7efc01a64/packages/lit-html/src/static.ts#L93 + const fakeLiteral = (strings: TemplateStringsArray) => + ({ + ['_$litStatic$']: strings[0], + r: Symbol.for(''), + }) as const; + + const editor = document.querySelector('affine-editor-container'); + if (!editor) throw new Error("Can't find affine-editor-container"); + + const SlashMenuWidget = window.$blocksuite.blocks.AffineSlashMenuWidget; + class CustomSlashMenu extends SlashMenuWidget { + override config = { + ...SlashMenuWidget.DEFAULT_CONFIG, + items: [ + { groupName: 'custom-group' }, + ...SlashMenuWidget.DEFAULT_CONFIG.items + .filter(item => 'action' in item) + .slice(0, 5), + ], + }; + } + // Fix `Illegal constructor` error + // see https://stackoverflow.com/questions/41521812/illegal-constructor-with-ecmascript-6 + customElements.define('affine-custom-slash-menu', CustomSlashMenu); + + const pageSpecs = window.$blocksuite.blocks.PageEditorBlockSpecs; + editor.pageSpecs = [ + ...pageSpecs, + { + setup: di => { + di.override( + window.$blocksuite.identifiers.WidgetViewMapIdentifier( + 'affine:page' + ), + // @ts-ignore + () => ({ + 'affine-slash-menu-widget': fakeLiteral`affine-custom-slash-menu`, + }) + ); + }, + }, + ]; + await editor.updateComplete; + }); + + await focusRichText(page); + + const slashMenu = page.locator(`.slash-menu`); + const slashItems = slashMenu.locator('icon-button'); + + await type(page, '/'); + await expect(slashMenu).toBeVisible(); + await expect(slashItems).toHaveCount(5); + }); + + test('can add some menus', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await page.evaluate(async () => { + // https://github.com/lit/lit/blob/84df6ef8c73fffec92384891b4b031d7efc01a64/packages/lit-html/src/static.ts#L93 + // eslint-disable-next-line sonarjs/no-identical-functions + const fakeLiteral = (strings: TemplateStringsArray) => + ({ + ['_$litStatic$']: strings[0], + r: Symbol.for(''), + }) as const; + + const editor = document.querySelector('affine-editor-container'); + if (!editor) throw new Error("Can't find affine-editor-container"); + const SlashMenuWidget = window.$blocksuite.blocks.AffineSlashMenuWidget; + + class CustomSlashMenu extends SlashMenuWidget { + config = { + ...SlashMenuWidget.DEFAULT_CONFIG, + items: [ + { groupName: 'Custom Menu' }, + { + name: 'Custom Menu Item', + // eslint-disable-next-line @typescript-eslint/no-explicit-any + icon: '' as any, + action: () => { + // do nothing + }, + }, + { + name: 'Custom Menu Item', + // eslint-disable-next-line @typescript-eslint/no-explicit-any + icon: '' as any, + action: () => { + // do nothing + }, + showWhen: () => false, + }, + ], + }; + } + // Fix `Illegal constructor` error + // see https://stackoverflow.com/questions/41521812/illegal-constructor-with-ecmascript-6 + customElements.define('affine-custom-slash-menu', CustomSlashMenu); + + const pageSpecs = window.$blocksuite.blocks.PageEditorBlockSpecs; + editor.pageSpecs = [ + ...pageSpecs, + { + setup: di => + di.override( + window.$blocksuite.identifiers.WidgetViewMapIdentifier( + 'affine:page' + ), + // @ts-ignore + () => ({ + 'affine-slash-menu-widget': fakeLiteral`affine-custom-slash-menu`, + }) + ), + }, + ]; + await editor.updateComplete; + }); + + await focusRichText(page); + + const slashMenu = page.locator(`.slash-menu`); + const slashItems = slashMenu.locator('icon-button'); + + await type(page, '/'); + await expect(slashMenu).toBeVisible(); + await expect(slashItems).toHaveCount(1); + }); +}); + +test('move block up and down by slash menu', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + const slashMenu = page.locator(`.slash-menu`); + + await focusRichText(page); + await type(page, 'hello'); + await pressEnter(page); + await type(page, 'world'); + await assertRichTexts(page, ['hello', 'world']); + await type(page, '/'); + await expect(slashMenu).toBeVisible(); + + const moveUp = page.getByTestId('Move Up'); + await moveUp.click(); + await assertRichTexts(page, ['world', 'hello']); + await type(page, '/'); + await expect(slashMenu).toBeVisible(); + + const moveDown = page.getByTestId('Move Down'); + await moveDown.click(); + await assertRichTexts(page, ['hello', 'world']); +}); + +test('delete block by slash menu should remove children', async ({ + page, +}, testInfo) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await insertThreeLevelLists(page); + const slashMenu = page.locator(`.slash-menu`); + const slashItems = slashMenu.locator('icon-button'); + + await captureHistory(page); + await focusRichText(page, 1); + await waitNextFrame(page); + await type(page, '/'); + + await expect(slashMenu).toBeVisible(); + await type(page, 'remove'); + await expect(slashItems).toHaveCount(1); + await pressEnter(page); + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}.json` + ); + + await undoByKeyboard(page); + await assertRichTexts(page, ['123', '456', '789']); + await redoByKeyboard(page); + await assertRichTexts(page, ['123']); +}); diff --git a/blocksuite/tests-legacy/snapshots/basic.spec.ts/automatic-identify-url-text-final.json b/blocksuite/tests-legacy/snapshots/basic.spec.ts/automatic-identify-url-text-final.json new file mode 100644 index 0000000000000..10d8e45e23e25 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/basic.spec.ts/automatic-identify-url-text-final.json @@ -0,0 +1,66 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "abc " + }, + { + "insert": "https://google.com", + "attributes": { + "link": "https://google.com" + } + }, + { + "insert": " " + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/basic.spec.ts/basic-test-default.json b/blocksuite/tests-legacy/snapshots/basic.spec.ts/basic-test-default.json new file mode 100644 index 0000000000000..d902db61ddad6 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/basic.spec.ts/basic-test-default.json @@ -0,0 +1,57 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "hello" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/bookmark.spec.ts/copy-bookmark-url-by-copy-button-final.json b/blocksuite/tests-legacy/snapshots/bookmark.spec.ts/copy-bookmark-url-by-copy-button-final.json new file mode 100644 index 0000000000000..b5c3f610836b1 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/bookmark.spec.ts/copy-bookmark-url-by-copy-button-final.json @@ -0,0 +1,80 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:bookmark", + "version": 1, + "props": { + "style": "horizontal", + "url": "http://localhost", + "caption": null, + "description": null, + "icon": null, + "image": null, + "title": null, + "index": "a0", + "xywh": "[0,0,0,0]", + "lockedBySelf": false, + "rotate": 0 + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "http://localhost", + "attributes": { + "link": "http://localhost" + } + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/bookmark.spec.ts/copy-url-to-create-bookmark-in-edgeless-mode-final.json b/blocksuite/tests-legacy/snapshots/bookmark.spec.ts/copy-url-to-create-bookmark-in-edgeless-mode-final.json new file mode 100644 index 0000000000000..972fa40850092 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/bookmark.spec.ts/copy-url-to-create-bookmark-in-edgeless-mode-final.json @@ -0,0 +1,87 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:surface", + "version": 5, + "props": { + "elements": {} + }, + "children": [] + }, + { + "type": "block", + "id": "2", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,234]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "http://localhost" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:bookmark", + "version": 1, + "props": { + "style": "horizontal", + "url": "http://localhost", + "caption": null, + "description": null, + "icon": null, + "image": null, + "title": null, + "index": "a0", + "xywh": "[0,0,0,0]", + "lockedBySelf": false, + "rotate": 0 + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/bookmark.spec.ts/copy-url-to-create-bookmark-in-page-mode-final.json b/blocksuite/tests-legacy/snapshots/bookmark.spec.ts/copy-url-to-create-bookmark-in-page-mode-final.json new file mode 100644 index 0000000000000..b90751b9e75af --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/bookmark.spec.ts/copy-url-to-create-bookmark-in-page-mode-final.json @@ -0,0 +1,77 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "http://localhost" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:bookmark", + "version": 1, + "props": { + "style": "horizontal", + "url": "http://localhost", + "caption": null, + "description": null, + "icon": null, + "image": null, + "title": null, + "index": "a0", + "xywh": "[0,0,0,0]", + "lockedBySelf": false, + "rotate": 0 + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/bookmark.spec.ts/covert-bookmark-block-to-link-text-final.json b/blocksuite/tests-legacy/snapshots/bookmark.spec.ts/covert-bookmark-block-to-link-text-final.json new file mode 100644 index 0000000000000..0d09533b20a88 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/bookmark.spec.ts/covert-bookmark-block-to-link-text-final.json @@ -0,0 +1,60 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "http://localhost", + "attributes": { + "link": "http://localhost" + } + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/bookmark.spec.ts/create-bookmark-by-slash-menu-final.json b/blocksuite/tests-legacy/snapshots/bookmark.spec.ts/create-bookmark-by-slash-menu-final.json new file mode 100644 index 0000000000000..cba48a995627f --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/bookmark.spec.ts/create-bookmark-by-slash-menu-final.json @@ -0,0 +1,58 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:bookmark", + "version": 1, + "props": { + "style": "horizontal", + "url": "http://localhost", + "caption": null, + "description": null, + "icon": null, + "image": null, + "title": null, + "index": "a0", + "xywh": "[0,0,0,0]", + "lockedBySelf": false, + "rotate": 0 + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/bookmark.spec.ts/embed-figma.json b/blocksuite/tests-legacy/snapshots/bookmark.spec.ts/embed-figma.json new file mode 100644 index 0000000000000..a1cf2bc7e3462 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/bookmark.spec.ts/embed-figma.json @@ -0,0 +1,56 @@ +{ + "type": "block", + "id": "*", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "*", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "*", + "flavour": "affine:embed-figma", + "version": 1, + "props": { + "index": "a0", + "xywh": "[0,0,0,0]", + "lockedBySelf": false, + "rotate": 0, + "style": "figma", + "url": "https://www.figma.com/design/JuXs6uOAICwf4I4tps0xKZ123", + "caption": null, + "title": "Figma", + "description": "https://www.figma.com/design/JuXs6uOAICwf4I4tps0xKZ123" + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/bookmark.spec.ts/embed-youtube.json b/blocksuite/tests-legacy/snapshots/bookmark.spec.ts/embed-youtube.json new file mode 100644 index 0000000000000..fa4138915dd8c --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/bookmark.spec.ts/embed-youtube.json @@ -0,0 +1,61 @@ +{ + "type": "block", + "id": "*", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "*", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "*", + "flavour": "affine:embed-youtube", + "version": 1, + "props": { + "index": "a0", + "xywh": "[0,0,0,0]", + "lockedBySelf": false, + "rotate": 0, + "style": "video", + "url": "https://www.youtube.com/watch?v=fakeid", + "caption": null, + "image": null, + "title": null, + "description": null, + "creator": null, + "creatorUrl": null, + "creatorImage": null, + "videoId": "fakeid" + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/bookmark.spec.ts/horizontal-figma.json b/blocksuite/tests-legacy/snapshots/bookmark.spec.ts/horizontal-figma.json new file mode 100644 index 0000000000000..9a3c75eb53015 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/bookmark.spec.ts/horizontal-figma.json @@ -0,0 +1,58 @@ +{ + "type": "block", + "id": "*", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "*", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "*", + "flavour": "affine:bookmark", + "version": 1, + "props": { + "style": "horizontal", + "url": "https://www.figma.com/design/JuXs6uOAICwf4I4tps0xKZ123", + "caption": null, + "description": null, + "icon": null, + "image": null, + "title": null, + "index": "a0", + "xywh": "[0,0,0,0]", + "lockedBySelf": false, + "rotate": 0 + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/bookmark.spec.ts/horizontal-youtube.json b/blocksuite/tests-legacy/snapshots/bookmark.spec.ts/horizontal-youtube.json new file mode 100644 index 0000000000000..f58738152c7b4 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/bookmark.spec.ts/horizontal-youtube.json @@ -0,0 +1,58 @@ +{ + "type": "block", + "id": "*", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "*", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "*", + "flavour": "affine:bookmark", + "version": 1, + "props": { + "style": "horizontal", + "url": "https://www.youtube.com/watch?v=fakeid", + "caption": null, + "description": null, + "icon": null, + "image": null, + "title": null, + "index": "a0", + "xywh": "[0,0,0,0]", + "lockedBySelf": false, + "rotate": 0 + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/bookmark.spec.ts/support-dragging-bookmark-block-directly-after-add-paragraph.json b/blocksuite/tests-legacy/snapshots/bookmark.spec.ts/support-dragging-bookmark-block-directly-after-add-paragraph.json new file mode 100644 index 0000000000000..a89a9ca431909 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/bookmark.spec.ts/support-dragging-bookmark-block-directly-after-add-paragraph.json @@ -0,0 +1,113 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:bookmark", + "version": 1, + "props": { + "style": "horizontal", + "url": "http://localhost", + "caption": null, + "description": null, + "icon": null, + "image": null, + "title": null, + "index": "a0", + "xywh": "[0,0,0,0]", + "rotate": 0 + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "111" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "5", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "222" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "6", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "333" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/bookmark.spec.ts/support-dragging-bookmark-block-directly-after-drag.json b/blocksuite/tests-legacy/snapshots/bookmark.spec.ts/support-dragging-bookmark-block-directly-after-drag.json new file mode 100644 index 0000000000000..c9b458d4395a9 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/bookmark.spec.ts/support-dragging-bookmark-block-directly-after-drag.json @@ -0,0 +1,113 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "111" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "5", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "222" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:bookmark", + "version": 1, + "props": { + "style": "horizontal", + "url": "http://localhost", + "caption": null, + "description": null, + "icon": null, + "image": null, + "title": null, + "index": "a0", + "xywh": "[0,0,0,0]", + "rotate": 0 + }, + "children": [] + }, + { + "type": "block", + "id": "6", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "333" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/bookmark.spec.ts/support-dragging-bookmark-block-directly-init.json b/blocksuite/tests-legacy/snapshots/bookmark.spec.ts/support-dragging-bookmark-block-directly-init.json new file mode 100644 index 0000000000000..d2090527c2176 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/bookmark.spec.ts/support-dragging-bookmark-block-directly-init.json @@ -0,0 +1,56 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:bookmark", + "version": 1, + "props": { + "style": "horizontal", + "url": "http://localhost", + "caption": null, + "description": null, + "icon": null, + "image": null, + "title": null, + "index": "a0", + "xywh": "[0,0,0,0]", + "rotate": 0 + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/clipboard/clipboard.spec.ts/auto-identify-url-final.json b/blocksuite/tests-legacy/snapshots/clipboard/clipboard.spec.ts/auto-identify-url-final.json new file mode 100644 index 0000000000000..c9e9f94e83414 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/clipboard/clipboard.spec.ts/auto-identify-url-final.json @@ -0,0 +1,63 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "test " + }, + { + "insert": "https://www.google.com", + "attributes": { + "link": "https://www.google.com" + } + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/clipboard/clipboard.spec.ts/clipboard-copy-nested-items-clipboard.html b/blocksuite/tests-legacy/snapshots/clipboard/clipboard.spec.ts/clipboard-copy-nested-items-clipboard.html new file mode 100644 index 0000000000000..132277ee2df88 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/clipboard/clipboard.spec.ts/clipboard-copy-nested-items-clipboard.html @@ -0,0 +1,9 @@ +
    +

    bc

    +
    +
    +

    d

    +
    +
    +
    +
    \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/clipboard/clipboard.spec.ts/clipboard-copy-nested-items-clipboard.json b/blocksuite/tests-legacy/snapshots/clipboard/clipboard.spec.ts/clipboard-copy-nested-items-clipboard.json new file mode 100644 index 0000000000000..ebac99dbab456 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/clipboard/clipboard.spec.ts/clipboard-copy-nested-items-clipboard.json @@ -0,0 +1,49 @@ +[ + { + "type": "block", + "id": "", + "flavour": "affine:note", + "props": {}, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "bc" + } + ] + }, + "collapsed": false + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "d" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] + } +] \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/clipboard/clipboard.spec.ts/clipboard-copy-nested-items-clipboard.md b/blocksuite/tests-legacy/snapshots/clipboard/clipboard.spec.ts/clipboard-copy-nested-items-clipboard.md new file mode 100644 index 0000000000000..6833e8ae1cbd5 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/clipboard/clipboard.spec.ts/clipboard-copy-nested-items-clipboard.md @@ -0,0 +1,2 @@ +bc +d \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/clipboard/clipboard.spec.ts/clipboard-copy-nested-items-clipboard2.html b/blocksuite/tests-legacy/snapshots/clipboard/clipboard.spec.ts/clipboard-copy-nested-items-clipboard2.html new file mode 100644 index 0000000000000..007c36cf41c15 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/clipboard/clipboard.spec.ts/clipboard-copy-nested-items-clipboard2.html @@ -0,0 +1,8 @@ +
    +

    hi

    +
    +
    +
    +

    j

    +
    +
    \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/clipboard/clipboard.spec.ts/clipboard-copy-nested-items-clipboard2.json b/blocksuite/tests-legacy/snapshots/clipboard/clipboard.spec.ts/clipboard-copy-nested-items-clipboard2.json new file mode 100644 index 0000000000000..76860336d5b61 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/clipboard/clipboard.spec.ts/clipboard-copy-nested-items-clipboard2.json @@ -0,0 +1,48 @@ +[ + { + "type": "block", + "id": "", + "flavour": "affine:note", + "props": {}, + "children": [ + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "hi" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "5", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "j" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } +] \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/clipboard/clipboard.spec.ts/clipboard-copy-nested-items-clipboard2.md b/blocksuite/tests-legacy/snapshots/clipboard/clipboard.spec.ts/clipboard-copy-nested-items-clipboard2.md new file mode 100644 index 0000000000000..02fa1f0285acf --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/clipboard/clipboard.spec.ts/clipboard-copy-nested-items-clipboard2.md @@ -0,0 +1,2 @@ +hi +j \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/clipboard/list.spec.ts/copy-a-nested-list-by-clicking-button-the-clipboard-data-should-be-complete-clipboard.html b/blocksuite/tests-legacy/snapshots/clipboard/list.spec.ts/copy-a-nested-list-by-clicking-button-the-clipboard-data-should-be-complete-clipboard.html new file mode 100644 index 0000000000000..b74b581cefa8d --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/clipboard/list.spec.ts/copy-a-nested-list-by-clicking-button-the-clipboard-data-should-be-complete-clipboard.html @@ -0,0 +1,11 @@ +
      +
    • aaa +
        +
      • bbb +
          +
        • ccc
        • +
        +
      • +
      +
    • +
    \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/clipboard/list.spec.ts/copy-a-nested-list-by-clicking-button-the-clipboard-data-should-be-complete-clipboard.json b/blocksuite/tests-legacy/snapshots/clipboard/list.spec.ts/copy-a-nested-list-by-clicking-button-the-clipboard-data-should-be-complete-clipboard.json new file mode 100644 index 0000000000000..43b78e52be343 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/clipboard/list.spec.ts/copy-a-nested-list-by-clicking-button-the-clipboard-data-should-be-complete-clipboard.json @@ -0,0 +1,75 @@ +[ + { + "type": "block", + "id": "", + "flavour": "affine:note", + "props": {}, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "aaa" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [ + { + "type": "block", + "id": "4", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "bbb" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [ + { + "type": "block", + "id": "5", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "ccc" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + } + ] + } + ] + } + ] + } +] \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/clipboard/list.spec.ts/copy-a-nested-list-by-clicking-button-the-clipboard-data-should-be-complete-clipboard.md b/blocksuite/tests-legacy/snapshots/clipboard/list.spec.ts/copy-a-nested-list-by-clicking-button-the-clipboard-data-should-be-complete-clipboard.md new file mode 100644 index 0000000000000..8cc58960380eb --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/clipboard/list.spec.ts/copy-a-nested-list-by-clicking-button-the-clipboard-data-should-be-complete-clipboard.md @@ -0,0 +1,3 @@ +aaa +bbb +ccc \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/clipboard/list.spec.ts/cut-will-delete-all-content-and-copy-will-reappear-content-after-cut.json b/blocksuite/tests-legacy/snapshots/clipboard/list.spec.ts/cut-will-delete-all-content-and-copy-will-reappear-content-after-cut.json new file mode 100644 index 0000000000000..b4999d04e2bd6 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/clipboard/list.spec.ts/cut-will-delete-all-content-and-copy-will-reappear-content-after-cut.json @@ -0,0 +1,55 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/clipboard/list.spec.ts/cut-will-delete-all-content-and-copy-will-reappear-content-after-paste.json b/blocksuite/tests-legacy/snapshots/clipboard/list.spec.ts/cut-will-delete-all-content-and-copy-will-reappear-content-after-paste.json new file mode 100644 index 0000000000000..5adfa07699158 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/clipboard/list.spec.ts/cut-will-delete-all-content-and-copy-will-reappear-content-after-paste.json @@ -0,0 +1,123 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "7", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "1" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [ + { + "type": "block", + "id": "8", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "2" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + }, + { + "type": "block", + "id": "9", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "3" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + } + ] + }, + { + "type": "block", + "id": "10", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "4" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/clipboard/list.spec.ts/should-keep-paragraph-block-s-type-when-pasting-at-the-start-of-empty-paragraph-block-except-type-text-after-paste-1.json b/blocksuite/tests-legacy/snapshots/clipboard/list.spec.ts/should-keep-paragraph-block-s-type-when-pasting-at-the-start-of-empty-paragraph-block-except-type-text-after-paste-1.json new file mode 100644 index 0000000000000..e6f186417e971 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/clipboard/list.spec.ts/should-keep-paragraph-block-s-type-when-pasting-at-the-start-of-empty-paragraph-block-except-type-text-after-paste-1.json @@ -0,0 +1,57 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "quote", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/clipboard/list.spec.ts/should-keep-paragraph-block-s-type-when-pasting-at-the-start-of-empty-paragraph-block-except-type-text-after-paste-2.json b/blocksuite/tests-legacy/snapshots/clipboard/list.spec.ts/should-keep-paragraph-block-s-type-when-pasting-at-the-start-of-empty-paragraph-block-except-type-text-after-paste-2.json new file mode 100644 index 0000000000000..3f8d89f149f21 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/clipboard/list.spec.ts/should-keep-paragraph-block-s-type-when-pasting-at-the-start-of-empty-paragraph-block-except-type-text-after-paste-2.json @@ -0,0 +1,76 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "quote", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/code/copy-paste.spec.ts/code-block-has-content-click-code-block-copy-menu-copy-whole-code-block-pasted.json b/blocksuite/tests-legacy/snapshots/code/copy-paste.spec.ts/code-block-has-content-click-code-block-copy-menu-copy-whole-code-block-pasted.json new file mode 100644 index 0000000000000..2140c127eeb28 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/code/copy-paste.spec.ts/code-block-has-content-click-code-block-copy-menu-copy-whole-code-block-pasted.json @@ -0,0 +1,78 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:code", + "version": 1, + "props": { + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "use" + } + ] + }, + "language": "javascript", + "wrap": false, + "caption": "" + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:code", + "version": 1, + "props": { + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "use" + } + ] + }, + "language": "javascript", + "wrap": false, + "caption": "" + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/code/copy-paste.spec.ts/code-block-is-empty-click-code-block-copy-menu-copy-the-empty-code-block-pasted.json b/blocksuite/tests-legacy/snapshots/code/copy-paste.spec.ts/code-block-is-empty-click-code-block-copy-menu-copy-the-empty-code-block-pasted.json new file mode 100644 index 0000000000000..0ce43f6161599 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/code/copy-paste.spec.ts/code-block-is-empty-click-code-block-copy-menu-copy-the-empty-code-block-pasted.json @@ -0,0 +1,70 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:code", + "version": 1, + "props": { + "text": { + "$blocksuite:internal:text$": true, + "delta": [] + }, + "language": "javascript", + "wrap": false, + "caption": "" + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:code", + "version": 1, + "props": { + "text": { + "$blocksuite:internal:text$": true, + "delta": [] + }, + "language": "javascript", + "wrap": false, + "caption": "" + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/code/crud.spec.ts/delete-code-block-in-more-menu-final.json b/blocksuite/tests-legacy/snapshots/code/crud.spec.ts/delete-code-block-in-more-menu-final.json new file mode 100644 index 0000000000000..5db1d16b72d2a --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/code/crud.spec.ts/delete-code-block-in-more-menu-final.json @@ -0,0 +1,37 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/code/crud.spec.ts/duplicate-code-block-final.json b/blocksuite/tests-legacy/snapshots/code/crud.spec.ts/duplicate-code-block-final.json new file mode 100644 index 0000000000000..6a79a3a76e98e --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/code/crud.spec.ts/duplicate-code-block-final.json @@ -0,0 +1,78 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:code", + "version": 1, + "props": { + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "let a: u8 = 7" + } + ] + }, + "language": "rust", + "wrap": true, + "caption": "BlockSuite" + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:code", + "version": 1, + "props": { + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "let a: u8 = 7" + } + ] + }, + "language": "rust", + "wrap": true, + "caption": "BlockSuite" + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/code/crud.spec.ts/format-text-in-code-block-format.json b/blocksuite/tests-legacy/snapshots/code/crud.spec.ts/format-text-in-code-block-format.json new file mode 100644 index 0000000000000..41b96fa369f8d --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/code/crud.spec.ts/format-text-in-code-block-format.json @@ -0,0 +1,85 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:code", + "version": 1, + "props": { + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "c" + }, + { + "insert": "o", + "attributes": { + "bold": true + } + }, + { + "insert": "ns" + }, + { + "insert": "t a", + "attributes": { + "bold": true + } + }, + { + "insert": "a" + }, + { + "insert": "a = 1000", + "attributes": { + "bold": true + } + }, + { + "insert": ";" + } + ] + }, + "language": "typescript", + "wrap": false, + "caption": "" + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/code/crud.spec.ts/format-text-in-code-block-init.json b/blocksuite/tests-legacy/snapshots/code/crud.spec.ts/format-text-in-code-block-init.json new file mode 100644 index 0000000000000..63f501b5eafd9 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/code/crud.spec.ts/format-text-in-code-block-init.json @@ -0,0 +1,58 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:code", + "version": 1, + "props": { + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "const aaa = 1000;" + } + ] + }, + "language": "typescript", + "wrap": false, + "caption": "" + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/code/crud.spec.ts/format-text-in-code-block-link.json b/blocksuite/tests-legacy/snapshots/code/crud.spec.ts/format-text-in-code-block-link.json new file mode 100644 index 0000000000000..e2a658224e5e7 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/code/crud.spec.ts/format-text-in-code-block-link.json @@ -0,0 +1,96 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:code", + "version": 1, + "props": { + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "c" + }, + { + "insert": "o", + "attributes": { + "bold": true + } + }, + { + "insert": "ns" + }, + { + "insert": "t a", + "attributes": { + "link": "https://www.baidu.com", + "bold": true + } + }, + { + "insert": "a", + "attributes": { + "link": "https://www.baidu.com" + } + }, + { + "insert": "a ", + "attributes": { + "link": "https://www.baidu.com", + "bold": true + } + }, + { + "insert": "= 1000", + "attributes": { + "bold": true + } + }, + { + "insert": ";" + } + ] + }, + "language": "typescript", + "wrap": false, + "caption": "" + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/code/crud.spec.ts/use-markdown-syntax-can-create-code-block-init.json b/blocksuite/tests-legacy/snapshots/code/crud.spec.ts/use-markdown-syntax-can-create-code-block-init.json new file mode 100644 index 0000000000000..5c6d65e23cd0d --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/code/crud.spec.ts/use-markdown-syntax-can-create-code-block-init.json @@ -0,0 +1,97 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "aaa" + } + ] + }, + "collapsed": false + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "bbb" + } + ] + }, + "collapsed": false + }, + "children": [ + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "ccc" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/code/crud.spec.ts/use-markdown-syntax-can-create-code-block-markdown-syntax.json b/blocksuite/tests-legacy/snapshots/code/crud.spec.ts/use-markdown-syntax-can-create-code-block-markdown-syntax.json new file mode 100644 index 0000000000000..de7599d6d0817 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/code/crud.spec.ts/use-markdown-syntax-can-create-code-block-markdown-syntax.json @@ -0,0 +1,112 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "5", + "flavour": "affine:code", + "version": 1, + "props": { + "text": { + "$blocksuite:internal:text$": true, + "delta": [] + }, + "language": null, + "wrap": false, + "caption": "" + }, + "children": [] + }, + { + "type": "block", + "id": "6", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "aaa" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "bbb" + } + ] + }, + "collapsed": false + }, + "children": [ + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "ccc" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/drag.spec.ts/move-to-the-last-block-of-each-level-in-multi-level-nesting-drag-3-4.json b/blocksuite/tests-legacy/snapshots/drag.spec.ts/move-to-the-last-block-of-each-level-in-multi-level-nesting-drag-3-4.json new file mode 100644 index 0000000000000..f05e973912a2e --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/drag.spec.ts/move-to-the-last-block-of-each-level-in-multi-level-nesting-drag-3-4.json @@ -0,0 +1,187 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "5", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "C" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [ + { + "type": "block", + "id": "6", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "D" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + }, + { + "type": "block", + "id": "7", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "E" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [ + { + "type": "block", + "id": "8", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "F" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + }, + { + "type": "block", + "id": "9", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "G" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + } + ] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "B" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + } + ] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "A" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/drag.spec.ts/move-to-the-last-block-of-each-level-in-multi-level-nesting-drag-3-9.json b/blocksuite/tests-legacy/snapshots/drag.spec.ts/move-to-the-last-block-of-each-level-in-multi-level-nesting-drag-3-9.json new file mode 100644 index 0000000000000..8aa721ad61799 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/drag.spec.ts/move-to-the-last-block-of-each-level-in-multi-level-nesting-drag-3-9.json @@ -0,0 +1,187 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "4", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "B" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + }, + { + "type": "block", + "id": "5", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "C" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [ + { + "type": "block", + "id": "6", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "D" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + }, + { + "type": "block", + "id": "7", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "E" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [ + { + "type": "block", + "id": "8", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "F" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + }, + { + "type": "block", + "id": "9", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "G" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "A" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + } + ] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/drag.spec.ts/move-to-the-last-block-of-each-level-in-multi-level-nesting-drag-4-3.json b/blocksuite/tests-legacy/snapshots/drag.spec.ts/move-to-the-last-block-of-each-level-in-multi-level-nesting-drag-4-3.json new file mode 100644 index 0000000000000..822a308be925b --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/drag.spec.ts/move-to-the-last-block-of-each-level-in-multi-level-nesting-drag-4-3.json @@ -0,0 +1,187 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "5", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "C" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [ + { + "type": "block", + "id": "6", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "D" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + }, + { + "type": "block", + "id": "7", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "E" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [ + { + "type": "block", + "id": "8", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "F" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + }, + { + "type": "block", + "id": "9", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "G" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "A" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + } + ] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "B" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/drag.spec.ts/move-to-the-last-block-of-each-level-in-multi-level-nesting-init.json b/blocksuite/tests-legacy/snapshots/drag.spec.ts/move-to-the-last-block-of-each-level-in-multi-level-nesting-init.json new file mode 100644 index 0000000000000..51622ddeb6d78 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/drag.spec.ts/move-to-the-last-block-of-each-level-in-multi-level-nesting-init.json @@ -0,0 +1,187 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "A" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "B" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + }, + { + "type": "block", + "id": "5", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "C" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [ + { + "type": "block", + "id": "6", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "D" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + }, + { + "type": "block", + "id": "7", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "E" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [ + { + "type": "block", + "id": "8", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "F" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + }, + { + "type": "block", + "id": "9", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "G" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + } + ] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/drag.spec.ts/should-be-able-to-drag-drop-multiple-blocks-to-nested-block-finial.json b/blocksuite/tests-legacy/snapshots/drag.spec.ts/should-be-able-to-drag-drop-multiple-blocks-to-nested-block-finial.json new file mode 100644 index 0000000000000..faedc471497ba --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/drag.spec.ts/should-be-able-to-drag-drop-multiple-blocks-to-nested-block-finial.json @@ -0,0 +1,186 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "5", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "C" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [ + { + "type": "block", + "id": "6", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "D" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + }, + { + "type": "block", + "id": "7", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "E" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [ + { + "type": "block", + "id": "8", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "F" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "A" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "B" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + }, + { + "type": "block", + "id": "9", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "G" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + } + ] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/drag.spec.ts/should-be-able-to-drag-drop-multiple-blocks-to-nested-block-init.json b/blocksuite/tests-legacy/snapshots/drag.spec.ts/should-be-able-to-drag-drop-multiple-blocks-to-nested-block-init.json new file mode 100644 index 0000000000000..e04d86f006d36 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/drag.spec.ts/should-be-able-to-drag-drop-multiple-blocks-to-nested-block-init.json @@ -0,0 +1,186 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "A" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "B" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + }, + { + "type": "block", + "id": "5", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "C" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [ + { + "type": "block", + "id": "6", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "D" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + }, + { + "type": "block", + "id": "7", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "E" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [ + { + "type": "block", + "id": "8", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "F" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + }, + { + "type": "block", + "id": "9", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "G" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + } + ] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/edgeless/edgeless-text.spec.ts/min-width-limit-for-embed-block-add-linked-doc.json b/blocksuite/tests-legacy/snapshots/edgeless/edgeless-text.spec.ts/min-width-limit-for-embed-block-add-linked-doc.json new file mode 100644 index 0000000000000..136df9a3faa74 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/edgeless/edgeless-text.spec.ts/min-width-limit-for-embed-block-add-linked-doc.json @@ -0,0 +1,110 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:surface", + "version": 5, + "props": { + "elements": {} + }, + "children": [ + { + "type": "block", + "id": "4", + "flavour": "affine:edgeless-text", + "version": 1, + "props": { + "xywh": "[-25,-25,88.75,50]", + "index": "a1", + "lockedBySelf": false, + "color": "--affine-palette-line-blue", + "fontFamily": "blocksuite:surface:Inter", + "fontStyle": "normal", + "fontWeight": "400", + "textAlign": "left", + "scale": 1, + "rotate": 0, + "hasMaxWidth": false + }, + "children": [ + { + "type": "block", + "id": "5", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": " ", + "attributes": { + "reference": { + "type": "LinkedPage", + "pageId": "6" + } + } + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] + }, + { + "type": "block", + "id": "2", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/edgeless/edgeless-text.spec.ts/min-width-limit-for-embed-block-drag.json b/blocksuite/tests-legacy/snapshots/edgeless/edgeless-text.spec.ts/min-width-limit-for-embed-block-drag.json new file mode 100644 index 0000000000000..26b0b16c696ed --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/edgeless/edgeless-text.spec.ts/min-width-limit-for-embed-block-drag.json @@ -0,0 +1,101 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:surface", + "version": 5, + "props": { + "elements": {} + }, + "children": [ + { + "type": "block", + "id": "4", + "flavour": "affine:edgeless-text", + "version": 1, + "props": { + "xywh": "[-25,-25,497,154]", + "index": "a1", + "lockedBySelf": false, + "color": "--affine-palette-line-blue", + "fontFamily": "blocksuite:surface:Inter", + "fontStyle": "normal", + "fontWeight": "400", + "textAlign": "left", + "scale": 1, + "rotate": 0, + "hasMaxWidth": true + }, + "children": [ + { + "type": "block", + "id": "11", + "flavour": "affine:embed-linked-doc", + "version": 1, + "props": { + "index": "a0", + "xywh": "[0,0,0,0]", + "lockedBySelf": false, + "rotate": 0, + "pageId": "6", + "style": "horizontal", + "caption": null + }, + "children": [] + } + ] + } + ] + }, + { + "type": "block", + "id": "2", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/edgeless/edgeless-text.spec.ts/min-width-limit-for-embed-block-init.json b/blocksuite/tests-legacy/snapshots/edgeless/edgeless-text.spec.ts/min-width-limit-for-embed-block-init.json new file mode 100644 index 0000000000000..38bceda452dea --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/edgeless/edgeless-text.spec.ts/min-width-limit-for-embed-block-init.json @@ -0,0 +1,100 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:surface", + "version": 5, + "props": { + "elements": {} + }, + "children": [ + { + "type": "block", + "id": "4", + "flavour": "affine:edgeless-text", + "version": 1, + "props": { + "xywh": "[-25,-25,50,26]", + "index": "a1", + "lockedBySelf": false, + "color": "--affine-palette-line-blue", + "fontFamily": "blocksuite:surface:Inter", + "fontStyle": "normal", + "fontWeight": "400", + "textAlign": "left", + "scale": 1, + "rotate": 0, + "hasMaxWidth": false + }, + "children": [ + { + "type": "block", + "id": "5", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] + }, + { + "type": "block", + "id": "2", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/edgeless/edgeless-text.spec.ts/min-width-limit-for-embed-block-link-to-card-min-width.json b/blocksuite/tests-legacy/snapshots/edgeless/edgeless-text.spec.ts/min-width-limit-for-embed-block-link-to-card-min-width.json new file mode 100644 index 0000000000000..ad9c08c538bc1 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/edgeless/edgeless-text.spec.ts/min-width-limit-for-embed-block-link-to-card-min-width.json @@ -0,0 +1,101 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:surface", + "version": 5, + "props": { + "elements": {} + }, + "children": [ + { + "type": "block", + "id": "4", + "flavour": "affine:edgeless-text", + "version": 1, + "props": { + "xywh": "[-25,-25,452,154]", + "index": "a1", + "lockedBySelf": false, + "color": "--affine-palette-line-blue", + "fontFamily": "blocksuite:surface:Inter", + "fontStyle": "normal", + "fontWeight": "400", + "textAlign": "left", + "scale": 1, + "rotate": 0, + "hasMaxWidth": true + }, + "children": [ + { + "type": "block", + "id": "11", + "flavour": "affine:embed-linked-doc", + "version": 1, + "props": { + "index": "a0", + "xywh": "[0,0,0,0]", + "lockedBySelf": false, + "rotate": 0, + "pageId": "6", + "style": "horizontal", + "caption": null + }, + "children": [] + } + ] + } + ] + }, + { + "type": "block", + "id": "2", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/edgeless/edgeless-text.spec.ts/min-width-limit-for-embed-block-link-to-card.json b/blocksuite/tests-legacy/snapshots/edgeless/edgeless-text.spec.ts/min-width-limit-for-embed-block-link-to-card.json new file mode 100644 index 0000000000000..2915f2d3ea10c --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/edgeless/edgeless-text.spec.ts/min-width-limit-for-embed-block-link-to-card.json @@ -0,0 +1,101 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:surface", + "version": 5, + "props": { + "elements": {} + }, + "children": [ + { + "type": "block", + "id": "4", + "flavour": "affine:edgeless-text", + "version": 1, + "props": { + "xywh": "[-25,-25,452,154]", + "index": "a1", + "lockedBySelf": false, + "color": "--affine-palette-line-blue", + "fontFamily": "blocksuite:surface:Inter", + "fontStyle": "normal", + "fontWeight": "400", + "textAlign": "left", + "scale": 1, + "rotate": 0, + "hasMaxWidth": false + }, + "children": [ + { + "type": "block", + "id": "11", + "flavour": "affine:embed-linked-doc", + "version": 1, + "props": { + "index": "a0", + "xywh": "[0,0,0,0]", + "lockedBySelf": false, + "rotate": 0, + "pageId": "6", + "style": "horizontal", + "caption": null + }, + "children": [] + } + ] + } + ] + }, + { + "type": "block", + "id": "2", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/edgeless/edgeless-text.spec.ts/press-backspace-at-the-start-of-first-line-when-edgeless-text-exist-finial.json b/blocksuite/tests-legacy/snapshots/edgeless/edgeless-text.spec.ts/press-backspace-at-the-start-of-first-line-when-edgeless-text-exist-finial.json new file mode 100644 index 0000000000000..8696508b7b647 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/edgeless/edgeless-text.spec.ts/press-backspace-at-the-start-of-first-line-when-edgeless-text-exist-finial.json @@ -0,0 +1,108 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "bbb" + } + ] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:surface", + "version": 5, + "props": { + "elements": {} + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:edgeless-text", + "version": 1, + "props": { + "xywh": "[-25,-25,50,26]", + "index": "a1", + "lockedBySelf": false, + "color": "--affine-palette-line-blue", + "fontFamily": "blocksuite:surface:Inter", + "fontStyle": "normal", + "fontWeight": "400", + "textAlign": "left", + "scale": 1, + "rotate": 0, + "hasMaxWidth": true + }, + "children": [ + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "aaa" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] + }, + { + "type": "block", + "id": "2", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,48]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "5", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/edgeless/edgeless-text.spec.ts/press-backspace-at-the-start-of-first-line-when-edgeless-text-exist-note-empty.json b/blocksuite/tests-legacy/snapshots/edgeless/edgeless-text.spec.ts/press-backspace-at-the-start-of-first-line-when-edgeless-text-exist-note-empty.json new file mode 100644 index 0000000000000..b42fa3f133c7b --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/edgeless/edgeless-text.spec.ts/press-backspace-at-the-start-of-first-line-when-edgeless-text-exist-note-empty.json @@ -0,0 +1,88 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:surface", + "version": 5, + "props": { + "elements": {} + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:edgeless-text", + "version": 1, + "props": { + "xywh": "[-25,-25,50,26]", + "index": "a1", + "lockedBySelf": false, + "color": "--affine-palette-line-blue", + "fontFamily": "blocksuite:surface:Inter", + "fontStyle": "normal", + "fontWeight": "400", + "textAlign": "left", + "scale": 1, + "rotate": 0, + "hasMaxWidth": true + }, + "children": [ + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "aaa" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] + }, + { + "type": "block", + "id": "2", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,48]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/edgeless/edgeless-text.spec.ts/press-backspace-at-the-start-of-first-line-when-edgeless-text-exist-note-not-empty.json b/blocksuite/tests-legacy/snapshots/edgeless/edgeless-text.spec.ts/press-backspace-at-the-start-of-first-line-when-edgeless-text-exist-note-not-empty.json new file mode 100644 index 0000000000000..f79cfb43bb51f --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/edgeless/edgeless-text.spec.ts/press-backspace-at-the-start-of-first-line-when-edgeless-text-exist-note-not-empty.json @@ -0,0 +1,104 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:surface", + "version": 5, + "props": { + "elements": {} + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:edgeless-text", + "version": 1, + "props": { + "xywh": "[-25,-25,50,26]", + "index": "a1", + "lockedBySelf": false, + "color": "--affine-palette-line-blue", + "fontFamily": "blocksuite:surface:Inter", + "fontStyle": "normal", + "fontWeight": "400", + "textAlign": "left", + "scale": 1, + "rotate": 0, + "hasMaxWidth": true + }, + "children": [ + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "aaa" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] + }, + { + "type": "block", + "id": "2", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,48]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "5", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/create-linked-doc-from-block-selection-with-format-bar.json b/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/create-linked-doc-from-block-selection-with-format-bar.json new file mode 100644 index 0000000000000..f1f55038c6557 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/create-linked-doc-from-block-selection-with-format-bar.json @@ -0,0 +1,54 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "11", + "flavour": "affine:embed-linked-doc", + "version": 1, + "props": { + "index": "a0", + "xywh": "[0,0,0,0]", + "lockedBySelf": false, + "rotate": 0, + "pageId": "5", + "style": "horizontal", + "caption": null + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-change-background-color-default-color.json b/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-change-background-color-default-color.json new file mode 100644 index 0000000000000..35d5c4f9fc1cf --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-change-background-color-default-color.json @@ -0,0 +1,98 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456", + "attributes": { + "color": "var(--affine-text-highlight-foreground-red)" + } + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-change-background-color-init.json b/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-change-background-color-init.json new file mode 100644 index 0000000000000..35d5c4f9fc1cf --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-change-background-color-init.json @@ -0,0 +1,98 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456", + "attributes": { + "color": "var(--affine-text-highlight-foreground-red)" + } + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-change-background-color-select-all.json b/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-change-background-color-select-all.json new file mode 100644 index 0000000000000..821396ae94742 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-change-background-color-select-all.json @@ -0,0 +1,101 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123", + "attributes": { + "color": "var(--affine-text-highlight-foreground-red)" + } + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456", + "attributes": { + "color": "var(--affine-text-highlight-foreground-red)" + } + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-change-to-heading-paragraph-type-bulleted.json b/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-change-to-heading-paragraph-type-bulleted.json new file mode 100644 index 0000000000000..3310a6113fe82 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-change-to-heading-paragraph-type-bulleted.json @@ -0,0 +1,97 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "5", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-change-to-heading-paragraph-type-finial.json b/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-change-to-heading-paragraph-type-finial.json new file mode 100644 index 0000000000000..f7b8f9535280b --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-change-to-heading-paragraph-type-finial.json @@ -0,0 +1,95 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "6", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-change-to-heading-paragraph-type-init.json b/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-change-to-heading-paragraph-type-init.json new file mode 100644 index 0000000000000..b8e6d44b34fc5 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-change-to-heading-paragraph-type-init.json @@ -0,0 +1,95 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "h1", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-format-text-finial.json b/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-format-text-finial.json new file mode 100644 index 0000000000000..1ccc87a0d2d8a --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-format-text-finial.json @@ -0,0 +1,99 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456", + "attributes": { + "strike": true, + "italic": true + } + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-format-text-init.json b/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-format-text-init.json new file mode 100644 index 0000000000000..ba6bf3c7bd7e4 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-format-text-init.json @@ -0,0 +1,102 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456", + "attributes": { + "code": true, + "strike": true, + "underline": true, + "italic": true, + "bold": true + } + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-format-text-when-select-multiple-line-finial.json b/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-format-text-when-select-multiple-line-finial.json new file mode 100644 index 0000000000000..aea12cd56eeb7 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-format-text-when-select-multiple-line-finial.json @@ -0,0 +1,95 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-format-text-when-select-multiple-line-init.json b/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-format-text-when-select-multiple-line-init.json new file mode 100644 index 0000000000000..359a657870028 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-format-text-when-select-multiple-line-init.json @@ -0,0 +1,104 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123", + "attributes": { + "bold": true + } + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456", + "attributes": { + "bold": true + } + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789", + "attributes": { + "bold": true + } + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-link-text-finial.json b/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-link-text-finial.json new file mode 100644 index 0000000000000..aea12cd56eeb7 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-link-text-finial.json @@ -0,0 +1,95 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-link-text-init.json b/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-link-text-init.json new file mode 100644 index 0000000000000..425e623f2c8fd --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-be-able-to-link-text-init.json @@ -0,0 +1,98 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456", + "attributes": { + "link": "https://www.example.com" + } + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-show-after-convert-to-code-block.json b/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-show-after-convert-to-code-block.json new file mode 100644 index 0000000000000..37b954d36c422 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-show-after-convert-to-code-block.json @@ -0,0 +1,58 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "5", + "flavour": "affine:code", + "version": 1, + "props": { + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123\n456\n789" + } + ] + }, + "language": null, + "wrap": false, + "caption": "" + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-with-block-selection-works-when-update-block-type-final.json b/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-with-block-selection-works-when-update-block-type-final.json new file mode 100644 index 0000000000000..e851e80ea9b07 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-with-block-selection-works-when-update-block-type-final.json @@ -0,0 +1,95 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "8", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "h1", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "9", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "h1", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "10", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "h1", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-with-block-selection-works-when-update-block-type-init.json b/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-with-block-selection-works-when-update-block-type-init.json new file mode 100644 index 0000000000000..48f1782b68acd --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-with-block-selection-works-when-update-block-type-init.json @@ -0,0 +1,101 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "5", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + }, + { + "type": "block", + "id": "6", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + }, + { + "type": "block", + "id": "7", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-work-in-multiple-block-selection.json b/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-work-in-multiple-block-selection.json new file mode 100644 index 0000000000000..f76f2a7b7982b --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-work-in-multiple-block-selection.json @@ -0,0 +1,107 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123", + "attributes": { + "underline": true, + "bold": true + } + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456", + "attributes": { + "underline": true, + "bold": true + } + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789", + "attributes": { + "underline": true, + "bold": true + } + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-work-in-single-block-selection.json b/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-work-in-single-block-selection.json new file mode 100644 index 0000000000000..4df164a74ee1b --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/format-bar.spec.ts/should-format-quick-bar-work-in-single-block-selection.json @@ -0,0 +1,100 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456", + "attributes": { + "underline": true, + "italic": true, + "bold": true + } + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/Enter-key-should-as-expected-after-setting-heading-by-shortkey.json b/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/Enter-key-should-as-expected-after-setting-heading-by-shortkey.json new file mode 100644 index 0000000000000..749ef405126ed --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/Enter-key-should-as-expected-after-setting-heading-by-shortkey.json @@ -0,0 +1,75 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "h1", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "hello" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "world" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/multi-line-rich-text-inline-code-hotkey-init.json b/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/multi-line-rich-text-inline-code-hotkey-init.json new file mode 100644 index 0000000000000..4a195bffe1d42 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/multi-line-rich-text-inline-code-hotkey-init.json @@ -0,0 +1,109 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "1" + }, + { + "insert": "23", + "attributes": { + "code": true + } + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456", + "attributes": { + "code": true + } + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "78", + "attributes": { + "code": true + } + }, + { + "insert": "9" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/multi-line-rich-text-inline-code-hotkey-redo.json b/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/multi-line-rich-text-inline-code-hotkey-redo.json new file mode 100644 index 0000000000000..4a195bffe1d42 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/multi-line-rich-text-inline-code-hotkey-redo.json @@ -0,0 +1,109 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "1" + }, + { + "insert": "23", + "attributes": { + "code": true + } + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456", + "attributes": { + "code": true + } + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "78", + "attributes": { + "code": true + } + }, + { + "insert": "9" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/multi-line-rich-text-inline-code-hotkey-undo.json b/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/multi-line-rich-text-inline-code-hotkey-undo.json new file mode 100644 index 0000000000000..4beb61dab0774 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/multi-line-rich-text-inline-code-hotkey-undo.json @@ -0,0 +1,94 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-cut-work-multiple-line-init.json b/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-cut-work-multiple-line-init.json new file mode 100644 index 0000000000000..dda4f654e2703 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-cut-work-multiple-line-init.json @@ -0,0 +1,56 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "19" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-cut-work-multiple-line-undo.json b/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-cut-work-multiple-line-undo.json new file mode 100644 index 0000000000000..4beb61dab0774 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-cut-work-multiple-line-undo.json @@ -0,0 +1,94 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-cut-work-single-line-init.json b/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-cut-work-single-line-init.json new file mode 100644 index 0000000000000..05e911d31610a --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-cut-work-single-line-init.json @@ -0,0 +1,56 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "ho" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-cut-work-single-line-undo.json b/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-cut-work-single-line-undo.json new file mode 100644 index 0000000000000..e284ca678aa42 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-cut-work-single-line-undo.json @@ -0,0 +1,56 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "hello" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-hotkey-work-in-paragraph-init.json b/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-hotkey-work-in-paragraph-init.json new file mode 100644 index 0000000000000..54aa2945a06c2 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-hotkey-work-in-paragraph-init.json @@ -0,0 +1,56 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "h1", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "hello" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-hotkey-work-in-paragraph-press-0.json b/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-hotkey-work-in-paragraph-press-0.json new file mode 100644 index 0000000000000..2c33f911b5c88 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-hotkey-work-in-paragraph-press-0.json @@ -0,0 +1,56 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "hello" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-hotkey-work-in-paragraph-press-6.json b/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-hotkey-work-in-paragraph-press-6.json new file mode 100644 index 0000000000000..9c632abfd1561 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-hotkey-work-in-paragraph-press-6.json @@ -0,0 +1,56 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "h6", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "hello" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-hotkey-work-in-paragraph-press-8.json b/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-hotkey-work-in-paragraph-press-8.json new file mode 100644 index 0000000000000..5c5730a0edcf1 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-hotkey-work-in-paragraph-press-8.json @@ -0,0 +1,58 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "hello" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-hotkey-work-in-paragraph-press-9.json b/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-hotkey-work-in-paragraph-press-9.json new file mode 100644 index 0000000000000..4493d992ed166 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-hotkey-work-in-paragraph-press-9.json @@ -0,0 +1,58 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "numbered", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "hello" + } + ] + }, + "checked": false, + "collapsed": false, + "order": 1 + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-hotkey-work-in-paragraph-press-d.json b/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-hotkey-work-in-paragraph-press-d.json new file mode 100644 index 0000000000000..3c9888aedc3fc --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-hotkey-work-in-paragraph-press-d.json @@ -0,0 +1,79 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "hello" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "5", + "flavour": "affine:divider", + "version": 1, + "props": {}, + "children": [] + }, + { + "type": "block", + "id": "6", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-multiple-line-format-hotkey-work-finial.json b/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-multiple-line-format-hotkey-work-finial.json new file mode 100644 index 0000000000000..4beb61dab0774 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-multiple-line-format-hotkey-work-finial.json @@ -0,0 +1,94 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-multiple-line-format-hotkey-work-init.json b/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-multiple-line-format-hotkey-work-init.json new file mode 100644 index 0000000000000..c7bbeae253c58 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-multiple-line-format-hotkey-work-init.json @@ -0,0 +1,118 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "1" + }, + { + "insert": "23", + "attributes": { + "strike": true, + "underline": true, + "italic": true, + "bold": true + } + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456", + "attributes": { + "strike": true, + "underline": true, + "italic": true, + "bold": true + } + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "78", + "attributes": { + "strike": true, + "underline": true, + "italic": true, + "bold": true + } + }, + { + "insert": "9" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-single-line-format-hotkey-work-finial.json b/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-single-line-format-hotkey-work-finial.json new file mode 100644 index 0000000000000..e284ca678aa42 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-single-line-format-hotkey-work-finial.json @@ -0,0 +1,56 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "hello" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-single-line-format-hotkey-work-init.json b/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-single-line-format-hotkey-work-init.json new file mode 100644 index 0000000000000..e387c5f0c9740 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/should-single-line-format-hotkey-work-init.json @@ -0,0 +1,68 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "h" + }, + { + "insert": "ell", + "attributes": { + "strike": true, + "underline": true, + "italic": true, + "bold": true + } + }, + { + "insert": "o" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/use-formatted-cursor-with-hotkey-bold-ggg.json b/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/use-formatted-cursor-with-hotkey-bold-ggg.json new file mode 100644 index 0000000000000..8542cbc43b761 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/use-formatted-cursor-with-hotkey-bold-ggg.json @@ -0,0 +1,84 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "aaa" + }, + { + "insert": "bbb", + "attributes": { + "italic": true + } + }, + { + "insert": "ccc", + "attributes": { + "italic": true, + "bold": true + } + }, + { + "insert": "ddd", + "attributes": { + "bold": true + } + }, + { + "insert": "eee" + }, + { + "insert": "fffggg", + "attributes": { + "bold": true + } + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/use-formatted-cursor-with-hotkey-bold-hhh.json b/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/use-formatted-cursor-with-hotkey-bold-hhh.json new file mode 100644 index 0000000000000..2a2f09ac41eb7 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/use-formatted-cursor-with-hotkey-bold-hhh.json @@ -0,0 +1,84 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "aaahhh" + }, + { + "insert": "bbb", + "attributes": { + "italic": true + } + }, + { + "insert": "ccc", + "attributes": { + "italic": true, + "bold": true + } + }, + { + "insert": "ddd", + "attributes": { + "bold": true + } + }, + { + "insert": "eee" + }, + { + "insert": "fffggg", + "attributes": { + "bold": true + } + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/use-formatted-cursor-with-hotkey-bold.json b/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/use-formatted-cursor-with-hotkey-bold.json new file mode 100644 index 0000000000000..46f310342b7bc --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/use-formatted-cursor-with-hotkey-bold.json @@ -0,0 +1,84 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "aaa" + }, + { + "insert": "bbb", + "attributes": { + "italic": true + } + }, + { + "insert": "ccc", + "attributes": { + "italic": true, + "bold": true + } + }, + { + "insert": "ddd", + "attributes": { + "bold": true + } + }, + { + "insert": "eee" + }, + { + "insert": "fff", + "attributes": { + "bold": true + } + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/use-formatted-cursor-with-hotkey-init.json b/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/use-formatted-cursor-with-hotkey-init.json new file mode 100644 index 0000000000000..74969002ab4b7 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey.spec.ts/use-formatted-cursor-with-hotkey-init.json @@ -0,0 +1,78 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "aaa" + }, + { + "insert": "bbb", + "attributes": { + "italic": true + } + }, + { + "insert": "ccc", + "attributes": { + "italic": true, + "bold": true + } + }, + { + "insert": "ddd", + "attributes": { + "bold": true + } + }, + { + "insert": "eee" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey/bracket.spec.ts/should-bracket-complete-with-backtick-works-undo.json b/blocksuite/tests-legacy/snapshots/hotkey/bracket.spec.ts/should-bracket-complete-with-backtick-works-undo.json new file mode 100644 index 0000000000000..f92e305be2f55 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey/bracket.spec.ts/should-bracket-complete-with-backtick-works-undo.json @@ -0,0 +1,57 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "hello world" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey/bracket.spec.ts/should-bracket-complete-with-backtick-works.json b/blocksuite/tests-legacy/snapshots/hotkey/bracket.spec.ts/should-bracket-complete-with-backtick-works.json new file mode 100644 index 0000000000000..b5eaddaf55c83 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey/bracket.spec.ts/should-bracket-complete-with-backtick-works.json @@ -0,0 +1,66 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "he" + }, + { + "insert": "llo", + "attributes": { + "code": true + } + }, + { + "insert": " world" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/Enter-key-should-as-expected-after-setting-heading-by-shortkey.json b/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/Enter-key-should-as-expected-after-setting-heading-by-shortkey.json new file mode 100644 index 0000000000000..563a1e7594358 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/Enter-key-should-as-expected-after-setting-heading-by-shortkey.json @@ -0,0 +1,76 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "h1", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "hello" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "world" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/should-cut-work-single-line-init.json b/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/should-cut-work-single-line-init.json new file mode 100644 index 0000000000000..75e9f991214e7 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/should-cut-work-single-line-init.json @@ -0,0 +1,57 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "ho" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/should-cut-work-single-line-undo.json b/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/should-cut-work-single-line-undo.json new file mode 100644 index 0000000000000..d902db61ddad6 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/should-cut-work-single-line-undo.json @@ -0,0 +1,57 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "hello" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/should-hotkey-work-in-paragraph-init.json b/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/should-hotkey-work-in-paragraph-init.json new file mode 100644 index 0000000000000..a7c1e7d4fa741 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/should-hotkey-work-in-paragraph-init.json @@ -0,0 +1,57 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "h1", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "hello" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/should-hotkey-work-in-paragraph-press-0.json b/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/should-hotkey-work-in-paragraph-press-0.json new file mode 100644 index 0000000000000..140fdef87eabb --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/should-hotkey-work-in-paragraph-press-0.json @@ -0,0 +1,57 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "hello" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/should-hotkey-work-in-paragraph-press-6.json b/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/should-hotkey-work-in-paragraph-press-6.json new file mode 100644 index 0000000000000..022ec800e4fdb --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/should-hotkey-work-in-paragraph-press-6.json @@ -0,0 +1,57 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "h6", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "hello" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/should-hotkey-work-in-paragraph-press-8.json b/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/should-hotkey-work-in-paragraph-press-8.json new file mode 100644 index 0000000000000..d0e2cf4630396 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/should-hotkey-work-in-paragraph-press-8.json @@ -0,0 +1,59 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "hello" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/should-hotkey-work-in-paragraph-press-9.json b/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/should-hotkey-work-in-paragraph-press-9.json new file mode 100644 index 0000000000000..51c03e53476bc --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/should-hotkey-work-in-paragraph-press-9.json @@ -0,0 +1,59 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "numbered", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "hello" + } + ] + }, + "checked": false, + "collapsed": false, + "order": 1 + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/should-hotkey-work-in-paragraph-press-d.json b/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/should-hotkey-work-in-paragraph-press-d.json new file mode 100644 index 0000000000000..1b737ee3633ed --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/should-hotkey-work-in-paragraph-press-d.json @@ -0,0 +1,80 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "hello" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "5", + "flavour": "affine:divider", + "version": 1, + "props": {}, + "children": [] + }, + { + "type": "block", + "id": "6", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/should-single-line-format-hotkey-work-finial.json b/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/should-single-line-format-hotkey-work-finial.json new file mode 100644 index 0000000000000..d902db61ddad6 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/should-single-line-format-hotkey-work-finial.json @@ -0,0 +1,57 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "hello" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/should-single-line-format-hotkey-work-init.json b/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/should-single-line-format-hotkey-work-init.json new file mode 100644 index 0000000000000..55cbe3dea6ef2 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/should-single-line-format-hotkey-work-init.json @@ -0,0 +1,69 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "h" + }, + { + "insert": "ell", + "attributes": { + "strike": true, + "underline": true, + "italic": true, + "bold": true + } + }, + { + "insert": "o" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/type-character-jump-out-code-node-1.json b/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/type-character-jump-out-code-node-1.json new file mode 100644 index 0000000000000..dc495e4bff729 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/type-character-jump-out-code-node-1.json @@ -0,0 +1,60 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "Hello", + "attributes": { + "code": true + } + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/type-character-jump-out-code-node-2.json b/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/type-character-jump-out-code-node-2.json new file mode 100644 index 0000000000000..dffb438146b45 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/type-character-jump-out-code-node-2.json @@ -0,0 +1,63 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "Hello", + "attributes": { + "code": true + } + }, + { + "insert": "block suite" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/use-formatted-cursor-with-hotkey-at-empty-line-bold.json b/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/use-formatted-cursor-with-hotkey-at-empty-line-bold.json new file mode 100644 index 0000000000000..511d260c0a6c8 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/use-formatted-cursor-with-hotkey-at-empty-line-bold.json @@ -0,0 +1,60 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "aaa", + "attributes": { + "bold": true + } + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/use-formatted-cursor-with-hotkey-bold-ggg.json b/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/use-formatted-cursor-with-hotkey-bold-ggg.json new file mode 100644 index 0000000000000..dff5aa5b70242 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/use-formatted-cursor-with-hotkey-bold-ggg.json @@ -0,0 +1,85 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "aaa" + }, + { + "insert": "bbb", + "attributes": { + "italic": true + } + }, + { + "insert": "ccc", + "attributes": { + "italic": true, + "bold": true + } + }, + { + "insert": "ddd", + "attributes": { + "bold": true + } + }, + { + "insert": "eee" + }, + { + "insert": "fffggg", + "attributes": { + "bold": true + } + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/use-formatted-cursor-with-hotkey-bold-hhh.json b/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/use-formatted-cursor-with-hotkey-bold-hhh.json new file mode 100644 index 0000000000000..ea0483de0947e --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/use-formatted-cursor-with-hotkey-bold-hhh.json @@ -0,0 +1,85 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "aaahhh" + }, + { + "insert": "bbb", + "attributes": { + "italic": true + } + }, + { + "insert": "ccc", + "attributes": { + "italic": true, + "bold": true + } + }, + { + "insert": "ddd", + "attributes": { + "bold": true + } + }, + { + "insert": "eee" + }, + { + "insert": "fffggg", + "attributes": { + "bold": true + } + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/use-formatted-cursor-with-hotkey-bold.json b/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/use-formatted-cursor-with-hotkey-bold.json new file mode 100644 index 0000000000000..9c38d0ce4bafa --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/use-formatted-cursor-with-hotkey-bold.json @@ -0,0 +1,85 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "aaa" + }, + { + "insert": "bbb", + "attributes": { + "italic": true + } + }, + { + "insert": "ccc", + "attributes": { + "italic": true, + "bold": true + } + }, + { + "insert": "ddd", + "attributes": { + "bold": true + } + }, + { + "insert": "eee" + }, + { + "insert": "fff", + "attributes": { + "bold": true + } + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/use-formatted-cursor-with-hotkey-init.json b/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/use-formatted-cursor-with-hotkey-init.json new file mode 100644 index 0000000000000..5e58ca68db293 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey/hotkey.spec.ts/use-formatted-cursor-with-hotkey-init.json @@ -0,0 +1,79 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "aaa" + }, + { + "insert": "bbb", + "attributes": { + "italic": true + } + }, + { + "insert": "ccc", + "attributes": { + "italic": true, + "bold": true + } + }, + { + "insert": "ddd", + "attributes": { + "bold": true + } + }, + { + "insert": "eee" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey/multiline.spec.ts/multi-line-rich-text-inline-code-hotkey-init.json b/blocksuite/tests-legacy/snapshots/hotkey/multiline.spec.ts/multi-line-rich-text-inline-code-hotkey-init.json new file mode 100644 index 0000000000000..2174dc3229027 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey/multiline.spec.ts/multi-line-rich-text-inline-code-hotkey-init.json @@ -0,0 +1,110 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "1" + }, + { + "insert": "23", + "attributes": { + "code": true + } + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456", + "attributes": { + "code": true + } + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "78", + "attributes": { + "code": true + } + }, + { + "insert": "9" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey/multiline.spec.ts/multi-line-rich-text-inline-code-hotkey-redo.json b/blocksuite/tests-legacy/snapshots/hotkey/multiline.spec.ts/multi-line-rich-text-inline-code-hotkey-redo.json new file mode 100644 index 0000000000000..2174dc3229027 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey/multiline.spec.ts/multi-line-rich-text-inline-code-hotkey-redo.json @@ -0,0 +1,110 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "1" + }, + { + "insert": "23", + "attributes": { + "code": true + } + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456", + "attributes": { + "code": true + } + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "78", + "attributes": { + "code": true + } + }, + { + "insert": "9" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey/multiline.spec.ts/multi-line-rich-text-inline-code-hotkey-undo.json b/blocksuite/tests-legacy/snapshots/hotkey/multiline.spec.ts/multi-line-rich-text-inline-code-hotkey-undo.json new file mode 100644 index 0000000000000..aea12cd56eeb7 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey/multiline.spec.ts/multi-line-rich-text-inline-code-hotkey-undo.json @@ -0,0 +1,95 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey/multiline.spec.ts/should-cut-work-multiple-line-init.json b/blocksuite/tests-legacy/snapshots/hotkey/multiline.spec.ts/should-cut-work-multiple-line-init.json new file mode 100644 index 0000000000000..77f254be3688c --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey/multiline.spec.ts/should-cut-work-multiple-line-init.json @@ -0,0 +1,57 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "19" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey/multiline.spec.ts/should-cut-work-multiple-line-undo.json b/blocksuite/tests-legacy/snapshots/hotkey/multiline.spec.ts/should-cut-work-multiple-line-undo.json new file mode 100644 index 0000000000000..aea12cd56eeb7 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey/multiline.spec.ts/should-cut-work-multiple-line-undo.json @@ -0,0 +1,95 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey/multiline.spec.ts/should-multiple-line-format-hotkey-work-finial.json b/blocksuite/tests-legacy/snapshots/hotkey/multiline.spec.ts/should-multiple-line-format-hotkey-work-finial.json new file mode 100644 index 0000000000000..aea12cd56eeb7 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey/multiline.spec.ts/should-multiple-line-format-hotkey-work-finial.json @@ -0,0 +1,95 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/hotkey/multiline.spec.ts/should-multiple-line-format-hotkey-work-init.json b/blocksuite/tests-legacy/snapshots/hotkey/multiline.spec.ts/should-multiple-line-format-hotkey-work-init.json new file mode 100644 index 0000000000000..63cd6eff50854 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/hotkey/multiline.spec.ts/should-multiple-line-format-hotkey-work-init.json @@ -0,0 +1,119 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "1" + }, + { + "insert": "23", + "attributes": { + "strike": true, + "underline": true, + "italic": true, + "bold": true + } + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456", + "attributes": { + "strike": true, + "underline": true, + "italic": true, + "bold": true + } + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "78", + "attributes": { + "strike": true, + "underline": true, + "italic": true, + "bold": true + } + }, + { + "insert": "9" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/latex/block.spec.ts/add-latex-block-using-markdown-shortcut-with-enter-finial.json b/blocksuite/tests-legacy/snapshots/latex/block.spec.ts/add-latex-block-using-markdown-shortcut-with-enter-finial.json new file mode 100644 index 0000000000000..ff3339caeabb1 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/latex/block.spec.ts/add-latex-block-using-markdown-shortcut-with-enter-finial.json @@ -0,0 +1,68 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:latex", + "version": 1, + "props": { + "xywh": "[0,0,16,16]", + "index": "a0", + "lockedBySelf": false, + "scale": 1, + "rotate": 0, + "latex": "aaa" + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/latex/block.spec.ts/add-latex-block-using-markdown-shortcut-with-enter-init.json b/blocksuite/tests-legacy/snapshots/latex/block.spec.ts/add-latex-block-using-markdown-shortcut-with-enter-init.json new file mode 100644 index 0000000000000..5893152dc88eb --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/latex/block.spec.ts/add-latex-block-using-markdown-shortcut-with-enter-init.json @@ -0,0 +1,53 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/latex/block.spec.ts/add-latex-block-using-markdown-shortcut-with-space-finial.json b/blocksuite/tests-legacy/snapshots/latex/block.spec.ts/add-latex-block-using-markdown-shortcut-with-space-finial.json new file mode 100644 index 0000000000000..ff3339caeabb1 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/latex/block.spec.ts/add-latex-block-using-markdown-shortcut-with-space-finial.json @@ -0,0 +1,68 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:latex", + "version": 1, + "props": { + "xywh": "[0,0,16,16]", + "index": "a0", + "lockedBySelf": false, + "scale": 1, + "rotate": 0, + "latex": "aaa" + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/latex/block.spec.ts/add-latex-block-using-markdown-shortcut-with-space-init.json b/blocksuite/tests-legacy/snapshots/latex/block.spec.ts/add-latex-block-using-markdown-shortcut-with-space-init.json new file mode 100644 index 0000000000000..5893152dc88eb --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/latex/block.spec.ts/add-latex-block-using-markdown-shortcut-with-space-init.json @@ -0,0 +1,53 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/latex/block.spec.ts/add-latex-block-using-slash-menu-finial.json b/blocksuite/tests-legacy/snapshots/latex/block.spec.ts/add-latex-block-using-slash-menu-finial.json new file mode 100644 index 0000000000000..74e9e8fdc814c --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/latex/block.spec.ts/add-latex-block-using-slash-menu-finial.json @@ -0,0 +1,53 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:latex", + "version": 1, + "props": { + "xywh": "[0,0,16,16]", + "index": "a0", + "lockedBySelf": false, + "scale": 1, + "rotate": 0, + "latex": "aaa" + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/latex/block.spec.ts/add-latex-block-using-slash-menu-init.json b/blocksuite/tests-legacy/snapshots/latex/block.spec.ts/add-latex-block-using-slash-menu-init.json new file mode 100644 index 0000000000000..5893152dc88eb --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/latex/block.spec.ts/add-latex-block-using-slash-menu-init.json @@ -0,0 +1,53 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/link.spec.ts/basic-link.json b/blocksuite/tests-legacy/snapshots/link.spec.ts/basic-link.json new file mode 100644 index 0000000000000..d81b55acec365 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/link.spec.ts/basic-link.json @@ -0,0 +1,60 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "link2", + "attributes": { + "link": "https://github.com" + } + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/link.spec.ts/convert-link-to-card.json b/blocksuite/tests-legacy/snapshots/link.spec.ts/convert-link-to-card.json new file mode 100644 index 0000000000000..dc269e572796c --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/link.spec.ts/convert-link-to-card.json @@ -0,0 +1,85 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "aaa" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "a" + }, + { + "insert": "linkText", + "attributes": { + "link": "http://example.com" + } + }, + { + "insert": "a" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/linked-page.spec.ts/can-create-linked-page-and-jump-final.json b/blocksuite/tests-legacy/snapshots/linked-page.spec.ts/can-create-linked-page-and-jump-final.json new file mode 100644 index 0000000000000..34fee791fbef7 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/linked-page.spec.ts/can-create-linked-page-and-jump-final.json @@ -0,0 +1,67 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "page0" + } + ] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": " ", + "attributes": { + "reference": { + "type": "LinkedPage", + "pageId": "3" + } + } + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/linked-page.spec.ts/can-create-linked-page-and-jump-init.json b/blocksuite/tests-legacy/snapshots/linked-page.spec.ts/can-create-linked-page-and-jump-init.json new file mode 100644 index 0000000000000..34fee791fbef7 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/linked-page.spec.ts/can-create-linked-page-and-jump-init.json @@ -0,0 +1,67 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "page0" + } + ] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": " ", + "attributes": { + "reference": { + "type": "LinkedPage", + "pageId": "3" + } + } + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/linked-page.spec.ts/duplicated-linked-page-should-paste-as-linked-page.json b/blocksuite/tests-legacy/snapshots/linked-page.spec.ts/duplicated-linked-page-should-paste-as-linked-page.json new file mode 100644 index 0000000000000..26669e0fa1601 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/linked-page.spec.ts/duplicated-linked-page-should-paste-as-linked-page.json @@ -0,0 +1,88 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "8", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": " ", + "attributes": { + "reference": { + "type": "LinkedPage", + "pageId": "3" + } + } + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": " ", + "attributes": { + "reference": { + "type": "LinkedPage", + "pageId": "3" + } + } + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/linked-page.spec.ts/paste-linked-page-should-paste-as-linked-page.json b/blocksuite/tests-legacy/snapshots/linked-page.spec.ts/paste-linked-page-should-paste-as-linked-page.json new file mode 100644 index 0000000000000..9d6b2f6f5f4f5 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/linked-page.spec.ts/paste-linked-page-should-paste-as-linked-page.json @@ -0,0 +1,63 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": " ", + "attributes": { + "reference": { + "type": "LinkedPage", + "pageId": "3" + } + } + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/linked-page.spec.ts/should-create-and-switch-page-work-final.json b/blocksuite/tests-legacy/snapshots/linked-page.spec.ts/should-create-and-switch-page-work-final.json new file mode 100644 index 0000000000000..d1f40cdb5d598 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/linked-page.spec.ts/should-create-and-switch-page-work-final.json @@ -0,0 +1,61 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "title0" + } + ] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "page0" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/linked-page.spec.ts/should-create-and-switch-page-work-init.json b/blocksuite/tests-legacy/snapshots/linked-page.spec.ts/should-create-and-switch-page-work-init.json new file mode 100644 index 0000000000000..d1f40cdb5d598 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/linked-page.spec.ts/should-create-and-switch-page-work-init.json @@ -0,0 +1,61 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "title0" + } + ] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "page0" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/list.spec.ts/basic-indent-and-unindent-after-shift-tab.json b/blocksuite/tests-legacy/snapshots/list.spec.ts/basic-indent-and-unindent-after-shift-tab.json new file mode 100644 index 0000000000000..d49a8a8d19904 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/list.spec.ts/basic-indent-and-unindent-after-shift-tab.json @@ -0,0 +1,76 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "text1" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "text2" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/list.spec.ts/basic-indent-and-unindent-after-tab.json b/blocksuite/tests-legacy/snapshots/list.spec.ts/basic-indent-and-unindent-after-tab.json new file mode 100644 index 0000000000000..d9da2bbc00c76 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/list.spec.ts/basic-indent-and-unindent-after-tab.json @@ -0,0 +1,77 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "text1" + } + ] + }, + "collapsed": false + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "text2" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/list.spec.ts/basic-indent-and-unindent-init.json b/blocksuite/tests-legacy/snapshots/list.spec.ts/basic-indent-and-unindent-init.json new file mode 100644 index 0000000000000..d49a8a8d19904 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/list.spec.ts/basic-indent-and-unindent-init.json @@ -0,0 +1,76 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "text1" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "text2" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/list.spec.ts/can-expand-toggle-in-readonly-mode-before-readonly.json b/blocksuite/tests-legacy/snapshots/list.spec.ts/can-expand-toggle-in-readonly-mode-before-readonly.json new file mode 100644 index 0000000000000..efa91ab5ddd92 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/list.spec.ts/can-expand-toggle-in-readonly-mode-before-readonly.json @@ -0,0 +1,102 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456" + } + ] + }, + "checked": false, + "collapsed": true, + "order": null + }, + "children": [ + { + "type": "block", + "id": "5", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/list.spec.ts/click-toggle-icon-should-collapsed-list-init.json b/blocksuite/tests-legacy/snapshots/list.spec.ts/click-toggle-icon-should-collapsed-list-init.json new file mode 100644 index 0000000000000..5af9598dbc839 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/list.spec.ts/click-toggle-icon-should-collapsed-list-init.json @@ -0,0 +1,102 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [ + { + "type": "block", + "id": "5", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/list.spec.ts/click-toggle-icon-should-collapsed-list-toggle.json b/blocksuite/tests-legacy/snapshots/list.spec.ts/click-toggle-icon-should-collapsed-list-toggle.json new file mode 100644 index 0000000000000..efa91ab5ddd92 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/list.spec.ts/click-toggle-icon-should-collapsed-list-toggle.json @@ -0,0 +1,102 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456" + } + ] + }, + "checked": false, + "collapsed": true, + "order": null + }, + "children": [ + { + "type": "block", + "id": "5", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/list.spec.ts/convert-nested-paragraph-to-list-final.json b/blocksuite/tests-legacy/snapshots/list.spec.ts/convert-nested-paragraph-to-list-final.json new file mode 100644 index 0000000000000..f50c6cb9de5cb --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/list.spec.ts/convert-nested-paragraph-to-list-final.json @@ -0,0 +1,81 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "4", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "aaa" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [ + { + "type": "block", + "id": "5", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "bbb" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/list.spec.ts/convert-nested-paragraph-to-list-init.json b/blocksuite/tests-legacy/snapshots/list.spec.ts/convert-nested-paragraph-to-list-init.json new file mode 100644 index 0000000000000..e1635548a5e61 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/list.spec.ts/convert-nested-paragraph-to-list-init.json @@ -0,0 +1,77 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "aaa" + } + ] + }, + "collapsed": false + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "bbb" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/list.spec.ts/enter-list-block-with-empty-text-1.json b/blocksuite/tests-legacy/snapshots/list.spec.ts/enter-list-block-with-empty-text-1.json new file mode 100644 index 0000000000000..8eb1d2a5d8903 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/list.spec.ts/enter-list-block-with-empty-text-1.json @@ -0,0 +1,90 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/list.spec.ts/enter-list-block-with-empty-text-2.json b/blocksuite/tests-legacy/snapshots/list.spec.ts/enter-list-block-with-empty-text-2.json new file mode 100644 index 0000000000000..fbcb1adb5d328 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/list.spec.ts/enter-list-block-with-empty-text-2.json @@ -0,0 +1,90 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + } + ] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/list.spec.ts/enter-list-block-with-empty-text-3.json b/blocksuite/tests-legacy/snapshots/list.spec.ts/enter-list-block-with-empty-text-3.json new file mode 100644 index 0000000000000..915995696c1f6 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/list.spec.ts/enter-list-block-with-empty-text-3.json @@ -0,0 +1,88 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + } + ] + }, + { + "type": "block", + "id": "5", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/list.spec.ts/enter-list-block-with-empty-text-4.json b/blocksuite/tests-legacy/snapshots/list.spec.ts/enter-list-block-with-empty-text-4.json new file mode 100644 index 0000000000000..eb5985cb32e09 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/list.spec.ts/enter-list-block-with-empty-text-4.json @@ -0,0 +1,90 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [ + { + "type": "block", + "id": "4", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/list.spec.ts/enter-list-block-with-empty-text-5.json b/blocksuite/tests-legacy/snapshots/list.spec.ts/enter-list-block-with-empty-text-5.json new file mode 100644 index 0000000000000..14784e2f8507f --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/list.spec.ts/enter-list-block-with-empty-text-5.json @@ -0,0 +1,88 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "6", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [] + }, + "collapsed": false + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/list.spec.ts/enter-list-block-with-empty-text-init.json b/blocksuite/tests-legacy/snapshots/list.spec.ts/enter-list-block-with-empty-text-init.json new file mode 100644 index 0000000000000..bd93841987cb3 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/list.spec.ts/enter-list-block-with-empty-text-init.json @@ -0,0 +1,89 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/list.spec.ts/indent-item-should-expand-toggle-finial.json b/blocksuite/tests-legacy/snapshots/list.spec.ts/indent-item-should-expand-toggle-finial.json new file mode 100644 index 0000000000000..e9082f36e0c4e --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/list.spec.ts/indent-item-should-expand-toggle-finial.json @@ -0,0 +1,123 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456" + } + ] + }, + "checked": false, + "collapsed": true, + "order": null + }, + "children": [ + { + "type": "block", + "id": "5", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + }, + { + "type": "block", + "id": "6", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "012" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/list.spec.ts/indent-item-should-expand-toggle-init.json b/blocksuite/tests-legacy/snapshots/list.spec.ts/indent-item-should-expand-toggle-init.json new file mode 100644 index 0000000000000..ea46f779f8a0e --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/list.spec.ts/indent-item-should-expand-toggle-init.json @@ -0,0 +1,123 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [ + { + "type": "block", + "id": "5", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + } + ] + }, + { + "type": "block", + "id": "6", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "012" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/list.spec.ts/indent-item-should-expand-toggle-toggle.json b/blocksuite/tests-legacy/snapshots/list.spec.ts/indent-item-should-expand-toggle-toggle.json new file mode 100644 index 0000000000000..ce1357e1463d0 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/list.spec.ts/indent-item-should-expand-toggle-toggle.json @@ -0,0 +1,123 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456" + } + ] + }, + "checked": false, + "collapsed": true, + "order": null + }, + "children": [ + { + "type": "block", + "id": "5", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + } + ] + }, + { + "type": "block", + "id": "6", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "012" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/list.spec.ts/nested-list-blocks-finial.json b/blocksuite/tests-legacy/snapshots/list.spec.ts/nested-list-blocks-finial.json new file mode 100644 index 0000000000000..5f33886e45df2 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/list.spec.ts/nested-list-blocks-finial.json @@ -0,0 +1,102 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [ + { + "type": "block", + "id": "4", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/list.spec.ts/nested-list-blocks-init.json b/blocksuite/tests-legacy/snapshots/list.spec.ts/nested-list-blocks-init.json new file mode 100644 index 0000000000000..7618c3e7ff3c0 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/list.spec.ts/nested-list-blocks-init.json @@ -0,0 +1,103 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [ + { + "type": "block", + "id": "4", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + } + ] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/list.spec.ts/should-indent-todo-block-preserve-todo-status-final.json b/blocksuite/tests-legacy/snapshots/list.spec.ts/should-indent-todo-block-preserve-todo-status-final.json new file mode 100644 index 0000000000000..b142b1320c3e5 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/list.spec.ts/should-indent-todo-block-preserve-todo-status-final.json @@ -0,0 +1,78 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "text1" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "todo", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "todo item" + } + ] + }, + "checked": true, + "collapsed": false, + "order": null + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/list.spec.ts/should-indent-todo-block-preserve-todo-status-init.json b/blocksuite/tests-legacy/snapshots/list.spec.ts/should-indent-todo-block-preserve-todo-status-init.json new file mode 100644 index 0000000000000..618e5372f49af --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/list.spec.ts/should-indent-todo-block-preserve-todo-status-init.json @@ -0,0 +1,79 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "text1" + } + ] + }, + "collapsed": false + }, + "children": [ + { + "type": "block", + "id": "4", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "todo", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "todo item" + } + ] + }, + "checked": true, + "collapsed": false, + "order": null + }, + "children": [] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/delete-empty-text-paragraph-block-should-keep-children-blocks-when-following-custom-blocks-final.json b/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/delete-empty-text-paragraph-block-should-keep-children-blocks-when-following-custom-blocks-final.json new file mode 100644 index 0000000000000..628adfe41ea0f --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/delete-empty-text-paragraph-block-should-keep-children-blocks-when-following-custom-blocks-final.json @@ -0,0 +1,84 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:divider", + "version": 1, + "props": {}, + "children": [] + }, + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "5", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/delete-empty-text-paragraph-block-should-keep-children-blocks-when-following-custom-blocks-init.json b/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/delete-empty-text-paragraph-block-should-keep-children-blocks-when-following-custom-blocks-init.json new file mode 100644 index 0000000000000..0487e165d1264 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/delete-empty-text-paragraph-block-should-keep-children-blocks-when-following-custom-blocks-init.json @@ -0,0 +1,104 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:divider", + "version": 1, + "props": {}, + "children": [] + }, + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456" + } + ] + }, + "collapsed": false + }, + "children": [ + { + "type": "block", + "id": "5", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/paragraph-indent-and-delete-in-line-start-after-press-backspace-2.json b/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/paragraph-indent-and-delete-in-line-start-after-press-backspace-2.json new file mode 100644 index 0000000000000..434bad82d698e --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/paragraph-indent-and-delete-in-line-start-after-press-backspace-2.json @@ -0,0 +1,96 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "abcfg" + } + ] + }, + "collapsed": false + }, + "children": [ + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "hijlm" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "6", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "nop" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/paragraph-indent-and-delete-in-line-start-after-press-backspace-3.json b/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/paragraph-indent-and-delete-in-line-start-after-press-backspace-3.json new file mode 100644 index 0000000000000..62c225f682a87 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/paragraph-indent-and-delete-in-line-start-after-press-backspace-3.json @@ -0,0 +1,96 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "abcfg" + } + ] + }, + "collapsed": false + }, + "children": [ + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "hijlm" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + }, + { + "type": "block", + "id": "6", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "op" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/paragraph-indent-and-delete-in-line-start-after-press-backspace.json b/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/paragraph-indent-and-delete-in-line-start-after-press-backspace.json new file mode 100644 index 0000000000000..934cea41964a2 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/paragraph-indent-and-delete-in-line-start-after-press-backspace.json @@ -0,0 +1,115 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "abcfg" + } + ] + }, + "collapsed": false + }, + "children": [ + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "hij" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "5", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "klm" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "6", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "nop" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/paragraph-indent-and-delete-in-line-start-init.json b/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/paragraph-indent-and-delete-in-line-start-init.json new file mode 100644 index 0000000000000..84bc634380ad4 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/paragraph-indent-and-delete-in-line-start-init.json @@ -0,0 +1,135 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "abc" + } + ] + }, + "collapsed": false + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "efg" + } + ] + }, + "collapsed": false + }, + "children": [ + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "hij" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + }, + { + "type": "block", + "id": "5", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "klm" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "6", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "nop" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/paragraph-with-child-block-should-work-at-enter-final.json b/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/paragraph-with-child-block-should-work-at-enter-final.json new file mode 100644 index 0000000000000..9395797d11107 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/paragraph-with-child-block-should-work-at-enter-final.json @@ -0,0 +1,96 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "collapsed": false + }, + "children": [ + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/paragraph-with-child-block-should-work-at-enter-init.json b/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/paragraph-with-child-block-should-work-at-enter-init.json new file mode 100644 index 0000000000000..06a75e586d5db --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/paragraph-with-child-block-should-work-at-enter-init.json @@ -0,0 +1,77 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "collapsed": false + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/should-delete-paragraph-block-child-can-hold-cursor-in-correct-position-final.json b/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/should-delete-paragraph-block-child-can-hold-cursor-in-correct-position-final.json new file mode 100644 index 0000000000000..948323f6a742f --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/should-delete-paragraph-block-child-can-hold-cursor-in-correct-position-final.json @@ -0,0 +1,76 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "now" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/should-delete-paragraph-block-child-can-hold-cursor-in-correct-position-init.json b/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/should-delete-paragraph-block-child-can-hold-cursor-in-correct-position-init.json new file mode 100644 index 0000000000000..91aeefe9e9831 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/should-delete-paragraph-block-child-can-hold-cursor-in-correct-position-init.json @@ -0,0 +1,77 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "collapsed": false + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "4" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/should-indent-and-unindent-works-with-children-indent-2.json b/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/should-indent-and-unindent-works-with-children-indent-2.json new file mode 100644 index 0000000000000..6041eb9fae4f9 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/should-indent-and-unindent-works-with-children-indent-2.json @@ -0,0 +1,134 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456" + } + ] + }, + "collapsed": false + }, + "children": [ + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "5", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "012" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + }, + { + "type": "block", + "id": "6", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "345" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/should-indent-and-unindent-works-with-children-indent-3.json b/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/should-indent-and-unindent-works-with-children-indent-3.json new file mode 100644 index 0000000000000..b1d0cd2303c4a --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/should-indent-and-unindent-works-with-children-indent-3.json @@ -0,0 +1,135 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "collapsed": false + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456" + } + ] + }, + "collapsed": false + }, + "children": [ + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "5", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "012" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] + }, + { + "type": "block", + "id": "6", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "345" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/should-indent-and-unindent-works-with-children-indent-4.json b/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/should-indent-and-unindent-works-with-children-indent-4.json new file mode 100644 index 0000000000000..25e234e5713f9 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/should-indent-and-unindent-works-with-children-indent-4.json @@ -0,0 +1,136 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "collapsed": false + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456" + } + ] + }, + "collapsed": false + }, + "children": [ + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "5", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "012" + } + ] + }, + "collapsed": false + }, + "children": [ + { + "type": "block", + "id": "6", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "345" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/should-indent-and-unindent-works-with-children-indent.json b/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/should-indent-and-unindent-works-with-children-indent.json new file mode 100644 index 0000000000000..4a65ef72dfe6f --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/should-indent-and-unindent-works-with-children-indent.json @@ -0,0 +1,134 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456" + } + ] + }, + "collapsed": false + }, + "children": [ + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + }, + { + "type": "block", + "id": "5", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "012" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "6", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "345" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/should-indent-and-unindent-works-with-children-init.json b/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/should-indent-and-unindent-works-with-children-init.json new file mode 100644 index 0000000000000..1ea7265dc5540 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/should-indent-and-unindent-works-with-children-init.json @@ -0,0 +1,133 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "5", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "012" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "6", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "345" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/should-indent-and-unindent-works-with-children-unindent-1.json b/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/should-indent-and-unindent-works-with-children-unindent-1.json new file mode 100644 index 0000000000000..5dcfb3dfa854b --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/should-indent-and-unindent-works-with-children-unindent-1.json @@ -0,0 +1,135 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456" + } + ] + }, + "collapsed": false + }, + "children": [ + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "5", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "012" + } + ] + }, + "collapsed": false + }, + "children": [ + { + "type": "block", + "id": "6", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "345" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/should-indent-and-unindent-works-with-children-unindent-2.json b/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/should-indent-and-unindent-works-with-children-unindent-2.json new file mode 100644 index 0000000000000..f493fae0542c3 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/should-indent-and-unindent-works-with-children-unindent-2.json @@ -0,0 +1,135 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456" + } + ] + }, + "collapsed": false + }, + "children": [ + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + }, + { + "type": "block", + "id": "5", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "012" + } + ] + }, + "collapsed": false + }, + "children": [ + { + "type": "block", + "id": "6", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "345" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/should-indent-and-unindent-works-with-children-unindent-3.json b/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/should-indent-and-unindent-works-with-children-unindent-3.json new file mode 100644 index 0000000000000..1ea7265dc5540 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/paragraph.spec.ts/should-indent-and-unindent-works-with-children-unindent-3.json @@ -0,0 +1,133 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "5", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "012" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "6", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "345" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/selection/block.spec.ts/click-bottom-of-page-and-if-the-last-is-embed-block-editor-should-insert-a-new-editable-block.json b/blocksuite/tests-legacy/snapshots/selection/block.spec.ts/click-bottom-of-page-and-if-the-last-is-embed-block-editor-should-insert-a-new-editable-block.json new file mode 100644 index 0000000000000..4ea1706b5faba --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/selection/block.spec.ts/click-bottom-of-page-and-if-the-last-is-embed-block-editor-should-insert-a-new-editable-block.json @@ -0,0 +1,71 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:image", + "version": 1, + "props": { + "caption": "", + "sourceId": "ejImogf-Tb7AuKY-v94uz1zuOJbClqK-tWBxVr_ksGA=", + "width": 358, + "height": 268.5, + "index": "a0", + "xywh": "[0,0,0,0]", + "lockedBySelf": false, + "rotate": 0, + "size": -1 + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/selection/block.spec.ts/should-indent-multi-selection-block.json b/blocksuite/tests-legacy/snapshots/selection/block.spec.ts/should-indent-multi-selection-block.json new file mode 100644 index 0000000000000..f5b1715e1c0ed --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/selection/block.spec.ts/should-indent-multi-selection-block.json @@ -0,0 +1,96 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "collapsed": false + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/selection/block.spec.ts/should-not-draw-rect-for-sub-selected-blocks-when-entering-tab-key.json b/blocksuite/tests-legacy/snapshots/selection/block.spec.ts/should-not-draw-rect-for-sub-selected-blocks-when-entering-tab-key.json new file mode 100644 index 0000000000000..f5b1715e1c0ed --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/selection/block.spec.ts/should-not-draw-rect-for-sub-selected-blocks-when-entering-tab-key.json @@ -0,0 +1,96 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "collapsed": false + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/selection/block.spec.ts/should-unindent-multi-selection-block-final.json b/blocksuite/tests-legacy/snapshots/selection/block.spec.ts/should-unindent-multi-selection-block-final.json new file mode 100644 index 0000000000000..aea12cd56eeb7 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/selection/block.spec.ts/should-unindent-multi-selection-block-final.json @@ -0,0 +1,95 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/selection/block.spec.ts/should-unindent-multi-selection-block-init.json b/blocksuite/tests-legacy/snapshots/selection/block.spec.ts/should-unindent-multi-selection-block-init.json new file mode 100644 index 0000000000000..f5b1715e1c0ed --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/selection/block.spec.ts/should-unindent-multi-selection-block-init.json @@ -0,0 +1,96 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "collapsed": false + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/selection/native.spec.ts/indent-native-multi-selection-block-after-shift-tab.json b/blocksuite/tests-legacy/snapshots/selection/native.spec.ts/indent-native-multi-selection-block-after-shift-tab.json new file mode 100644 index 0000000000000..d210121ee41f0 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/selection/native.spec.ts/indent-native-multi-selection-block-after-shift-tab.json @@ -0,0 +1,114 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "5", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "012" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/selection/native.spec.ts/indent-native-multi-selection-block-after-tab.json b/blocksuite/tests-legacy/snapshots/selection/native.spec.ts/indent-native-multi-selection-block-after-tab.json new file mode 100644 index 0000000000000..2a88fad730784 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/selection/native.spec.ts/indent-native-multi-selection-block-after-tab.json @@ -0,0 +1,115 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "collapsed": false + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "5", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "012" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/selection/native.spec.ts/native-range-delete-with-indent-after-backspace.json b/blocksuite/tests-legacy/snapshots/selection/native.spec.ts/native-range-delete-with-indent-after-backspace.json new file mode 100644 index 0000000000000..294f40489cbeb --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/selection/native.spec.ts/native-range-delete-with-indent-after-backspace.json @@ -0,0 +1,76 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "12ef" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "7", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "ghi" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/selection/native.spec.ts/native-range-delete-with-indent-after-redo.json b/blocksuite/tests-legacy/snapshots/selection/native.spec.ts/native-range-delete-with-indent-after-redo.json new file mode 100644 index 0000000000000..294f40489cbeb --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/selection/native.spec.ts/native-range-delete-with-indent-after-redo.json @@ -0,0 +1,76 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "12ef" + } + ] + }, + "collapsed": false + }, + "children": [] + }, + { + "type": "block", + "id": "7", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "ghi" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/selection/native.spec.ts/native-range-delete-with-indent-after-undo.json b/blocksuite/tests-legacy/snapshots/selection/native.spec.ts/native-range-delete-with-indent-after-undo.json new file mode 100644 index 0000000000000..4a3b0c356986f --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/selection/native.spec.ts/native-range-delete-with-indent-after-undo.json @@ -0,0 +1,156 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "collapsed": false + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456" + } + ] + }, + "collapsed": false + }, + "children": [ + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] + }, + { + "type": "block", + "id": "5", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "abc" + } + ] + }, + "collapsed": false + }, + "children": [ + { + "type": "block", + "id": "6", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "def" + } + ] + }, + "collapsed": false + }, + "children": [ + { + "type": "block", + "id": "7", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "ghi" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/selection/native.spec.ts/native-range-delete-with-indent-init.json b/blocksuite/tests-legacy/snapshots/selection/native.spec.ts/native-range-delete-with-indent-init.json new file mode 100644 index 0000000000000..4a3b0c356986f --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/selection/native.spec.ts/native-range-delete-with-indent-init.json @@ -0,0 +1,156 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "2", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "collapsed": false + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "456" + } + ] + }, + "collapsed": false + }, + "children": [ + { + "type": "block", + "id": "4", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "789" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] + }, + { + "type": "block", + "id": "5", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "abc" + } + ] + }, + "collapsed": false + }, + "children": [ + { + "type": "block", + "id": "6", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "def" + } + ] + }, + "collapsed": false + }, + "children": [ + { + "type": "block", + "id": "7", + "flavour": "affine:paragraph", + "version": 1, + "props": { + "type": "text", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "ghi" + } + ] + }, + "collapsed": false + }, + "children": [] + } + ] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/snapshots/slash-menu.spec.ts/delete-block-by-slash-menu-should-remove-children.json b/blocksuite/tests-legacy/snapshots/slash-menu.spec.ts/delete-block-by-slash-menu-should-remove-children.json new file mode 100644 index 0000000000000..561a6677532a8 --- /dev/null +++ b/blocksuite/tests-legacy/snapshots/slash-menu.spec.ts/delete-block-by-slash-menu-should-remove-children.json @@ -0,0 +1,59 @@ +{ + "type": "block", + "id": "0", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "1", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,498,92]", + "background": "--affine-note-background-white", + "index": "a0", + "lockedBySelf": false, + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "3", + "flavour": "affine:list", + "version": 1, + "props": { + "type": "bulleted", + "text": { + "$blocksuite:internal:text$": true, + "delta": [ + { + "insert": "123" + } + ] + }, + "checked": false, + "collapsed": false, + "order": null + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/blocksuite/tests-legacy/tsconfig.json b/blocksuite/tests-legacy/tsconfig.json new file mode 100644 index 0000000000000..8e57bdabb7f5b --- /dev/null +++ b/blocksuite/tests-legacy/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "baseUrl": ".", + "noEmit": true, + "composite": false, + "paths": { + "@blocks/*": ["../blocks/src/*"], + "@inline/*": ["../framework/inline/src/*"], + "@store/*": ["../framework/store/src/*"], + "@playground/*": ["../playground/*"] + } + }, + "include": ["**.spec.ts", "**.test.ts", "**/**.ts"] +} diff --git a/blocksuite/tests-legacy/utils/actions/block.ts b/blocksuite/tests-legacy/utils/actions/block.ts new file mode 100644 index 0000000000000..fe11bde35b79e --- /dev/null +++ b/blocksuite/tests-legacy/utils/actions/block.ts @@ -0,0 +1,25 @@ +import type { Page } from '@playwright/test'; + +import { waitNextFrame } from './misc.js'; + +export async function updateBlockType( + page: Page, + flavour: BlockSuite.Flavour, + type?: string +) { + await page.evaluate( + ([flavour, type]) => { + window.host.std.command + .chain() + .updateBlockType({ + flavour, + props: { + type, + }, + }) + .run(); + }, + [flavour, type] as [BlockSuite.Flavour, string?] + ); + await waitNextFrame(page, 400); +} diff --git a/blocksuite/tests-legacy/utils/actions/click.ts b/blocksuite/tests-legacy/utils/actions/click.ts new file mode 100644 index 0000000000000..dc813e80f83c1 --- /dev/null +++ b/blocksuite/tests-legacy/utils/actions/click.ts @@ -0,0 +1,130 @@ +import type { IPoint } from '@blocksuite/global/utils'; +import type { Page } from '@playwright/test'; + +import { toViewCoord } from './edgeless.js'; +import { waitNextFrame } from './misc.js'; + +export function getDebugMenu(page: Page) { + const debugMenu = page.locator('starter-debug-menu'); + return { + debugMenu, + undoBtn: debugMenu.locator('sl-tooltip[content="Undo"]'), + redoBtn: debugMenu.locator('sl-tooltip[content="Redo"]'), + + blockTypeButton: debugMenu.getByRole('button', { name: 'Block Type' }), + testOperationsButton: debugMenu.getByRole('button', { + name: 'Test Operations', + }), + + pagesBtn: debugMenu.getByTestId('docs-button'), + }; +} + +export async function moveView(page: Page, point: [number, number]) { + const [x, y] = await toViewCoord(page, point); + await page.mouse.move(x, y); +} + +export async function click(page: Page, point: IPoint) { + await page.mouse.click(point.x, point.y); +} + +export async function clickView(page: Page, point: [number, number]) { + const [x, y] = await toViewCoord(page, point); + await page.mouse.click(x, y); +} + +export async function dblclickView(page: Page, point: [number, number]) { + const [x, y] = await toViewCoord(page, point); + await page.mouse.dblclick(x, y); +} + +export async function undoByClick(page: Page) { + await getDebugMenu(page).undoBtn.click(); +} + +export async function redoByClick(page: Page) { + await getDebugMenu(page).redoBtn.click(); +} + +export async function clickBlockById(page: Page, id: string) { + await page.click(`[data-block-id="${id}"]`); +} + +export async function doubleClickBlockById(page: Page, id: string) { + await page.dblclick(`[data-block-id="${id}"]`); +} + +export async function disconnectByClick(page: Page) { + await clickTestOperationsMenuItem(page, 'Disconnect'); +} + +export async function connectByClick(page: Page) { + await clickTestOperationsMenuItem(page, 'Connect'); +} + +export async function addNoteByClick(page: Page) { + await clickTestOperationsMenuItem(page, 'Add Note'); +} + +export async function addNewPage(page: Page) { + const { pagesBtn } = getDebugMenu(page); + if (!(await page.locator('docs-panel').isVisible())) { + await pagesBtn.click(); + } + await page.locator('.new-doc-button').click(); + const docMetas = await page.evaluate(() => { + const { collection } = window; + return collection.meta.docMetas; + }); + if (!docMetas.length) throw new Error('Add new doc failed'); + return docMetas[docMetas.length - 1]; +} + +export async function switchToPage(page: Page, docId?: string) { + await page.evaluate(docId => { + const { collection, editor } = window; + + if (!docId) { + const docMetas = collection.meta.docMetas; + if (!docMetas.length) return; + docId = docMetas[0].id; + } + + const doc = collection.getDoc(docId); + if (!doc) return; + editor.doc = doc; + }, docId); +} + +export async function clickTestOperationsMenuItem(page: Page, name: string) { + const menuButton = getDebugMenu(page).testOperationsButton; + await menuButton.click(); + await waitNextFrame(page); // wait for animation ended + + const menuItem = page.getByRole('menuitem', { name }); + await menuItem.click(); + await menuItem.waitFor({ state: 'hidden' }); // wait for animation ended +} + +export async function switchReadonly(page: Page, value = true) { + await page.evaluate(_value => { + const defaultPage = document.querySelector( + 'affine-page-root' + ) as HTMLElement & { + doc: { + awarenessStore: { setFlag: (key: string, value: unknown) => void }; + }; + }; + const doc = defaultPage.doc; + doc.awarenessStore.setFlag('readonly', { 'doc:home': _value }); + }, value); +} + +export async function activeEmbed(page: Page) { + await page.click('.resizable-img'); +} + +export async function toggleDarkMode(page: Page) { + await page.click('sl-tooltip[content="Toggle Dark Mode"] sl-button'); +} diff --git a/blocksuite/tests-legacy/utils/actions/drag.ts b/blocksuite/tests-legacy/utils/actions/drag.ts new file mode 100644 index 0000000000000..c824f4f544704 --- /dev/null +++ b/blocksuite/tests-legacy/utils/actions/drag.ts @@ -0,0 +1,271 @@ +import type { Page } from '@playwright/test'; +import { assertImageOption } from 'utils/asserts.js'; + +import { getIndexCoordinate, waitNextFrame } from './misc.js'; + +export async function dragBetweenCoords( + page: Page, + from: { x: number; y: number }, + to: { x: number; y: number }, + options?: { + beforeMouseUp?: () => Promise; + steps?: number; + click?: boolean; + button?: 'left' | 'right' | 'middle'; + } +) { + const steps = options?.steps ?? 20; + const button: 'left' | 'right' | 'middle' = options?.button ?? 'left'; + + const { x: x1, y: y1 } = from; + const { x: x2, y: y2 } = to; + options?.click && (await page.mouse.click(x1, y1)); + await page.mouse.move(x1, y1); + await page.mouse.down({ button }); + await page.mouse.move(x2, y2, { steps }); + await options?.beforeMouseUp?.(); + await page.mouse.up({ button }); +} + +export async function dragBetweenIndices( + page: Page, + [startRichTextIndex, startVIndex]: [number, number], + [endRichTextIndex, endVIndex]: [number, number], + startCoordOffSet: { x: number; y: number } = { x: 0, y: 0 }, + endCoordOffSet: { x: number; y: number } = { x: 0, y: 0 }, + options?: { + beforeMouseUp?: () => Promise; + steps?: number; + click?: boolean; + } +) { + const finalOptions = { + steps: 50, + ...options, + }; + const startCoord = await getIndexCoordinate( + page, + [startRichTextIndex, startVIndex], + startCoordOffSet + ); + const endCoord = await getIndexCoordinate( + page, + [endRichTextIndex, endVIndex], + endCoordOffSet + ); + + await dragBetweenCoords(page, startCoord, endCoord, finalOptions); +} + +export async function dragOverTitle(page: Page) { + const { from, to } = await page.evaluate(() => { + const titleInput = document.querySelector( + 'doc-title rich-text' + ) as HTMLTextAreaElement; + const titleBound = titleInput.getBoundingClientRect(); + + return { + from: { x: titleBound.left + 1, y: titleBound.top + 1 }, + to: { x: titleBound.right - 1, y: titleBound.bottom - 1 }, + }; + }); + await dragBetweenCoords(page, from, to, { + steps: 5, + }); +} + +export async function dragEmbedResizeByTopRight(page: Page) { + const { from, to } = await page.evaluate(() => { + const bottomRightButton = document.querySelector( + '.top-right' + ) as HTMLInputElement; + const bottomRightButtonBound = bottomRightButton.getBoundingClientRect(); + const y = bottomRightButtonBound.top; + return { + from: { x: bottomRightButtonBound.left + 5, y: y + 5 }, + to: { x: bottomRightButtonBound.left + 5 - 200, y }, + }; + }); + await dragBetweenCoords(page, from, to, { + steps: 10, + }); +} + +export async function dragEmbedResizeByTopLeft(page: Page) { + const { from, to } = await page.evaluate(() => { + const bottomRightButton = document.querySelector( + '.top-left' + ) as HTMLInputElement; + const bottomRightButtonBound = bottomRightButton.getBoundingClientRect(); + const y = bottomRightButtonBound.top; + return { + from: { x: bottomRightButtonBound.left + 5, y: y + 5 }, + to: { x: bottomRightButtonBound.left + 5 + 200, y }, + }; + }); + await dragBetweenCoords(page, from, to, { + steps: 10, + }); +} + +export async function dragHandleFromBlockToBlockBottomById( + page: Page, + sourceId: string, + targetId: string, + bottom = true, + offset?: number, + beforeMouseUp?: () => Promise +) { + const sourceBlock = await page + .locator(`[data-block-id="${sourceId}"]`) + .boundingBox(); + const targetBlock = await page + .locator(`[data-block-id="${targetId}"]`) + .boundingBox(); + if (!sourceBlock || !targetBlock) { + throw new Error(); + } + await page.mouse.move( + sourceBlock.x + sourceBlock.width / 2, + sourceBlock.y + sourceBlock.height / 2 + ); + await waitNextFrame(page); + const dragHandleContainer = page.locator('.affine-drag-handle-container'); + await dragHandleContainer.hover(); + const handle = await dragHandleContainer.boundingBox(); + if (!handle) { + throw new Error(); + } + await page.mouse.move( + handle.x + handle.width / 2, + handle.y + handle.height / 2, + { steps: 10 } + ); + await page.mouse.down(); + await page.mouse.move( + targetBlock.x, + targetBlock.y + (bottom ? targetBlock.height - 1 : 1), + { + steps: 50, + } + ); + + if (offset) { + await page.mouse.move( + targetBlock.x + offset, + targetBlock.y + (bottom ? targetBlock.height - 1 : 1), + { + steps: 50, + } + ); + } + + if (beforeMouseUp) { + await beforeMouseUp(); + } + + await page.mouse.up(); +} + +export async function dragBlockToPoint( + page: Page, + sourceId: string, + point: { x: number; y: number } +) { + const sourceBlock = await page + .locator(`[data-block-id="${sourceId}"]`) + .boundingBox(); + if (!sourceBlock) { + throw new Error(); + } + await page.mouse.move( + sourceBlock.x + sourceBlock.width / 2, + sourceBlock.y + sourceBlock.height / 2 + ); + const handle = await page + .locator('.affine-drag-handle-container') + .boundingBox(); + if (!handle) { + throw new Error(); + } + await page.mouse.move( + handle.x + handle.width / 2, + handle.y + handle.height / 2 + ); + await page.mouse.down(); + await page.mouse.move(point.x, point.y, { + steps: 50, + }); + + await page.mouse.up(); +} + +export async function moveToImage(page: Page) { + const { x, y } = await page.evaluate(() => { + const bottomRightButton = document.querySelector('img') as HTMLElement; + const imageClient = bottomRightButton.getBoundingClientRect(); + const y = imageClient.top; + return { + x: imageClient.left + 30, + y: y + 30, + }; + }); + await page.mouse.move(x, y); +} + +export async function popImageMoreMenu(page: Page) { + await moveToImage(page); + await assertImageOption(page); + const moreButton = page.locator('.image-toolbar-button.more'); + await moreButton.click(); + const menu = page.locator('.image-more-popup-menu'); + + const turnIntoCardButton = page.locator('editor-menu-action', { + hasText: 'Turn into card view', + }); + + const copyButton = page.locator('editor-menu-action', { + hasText: 'Copy', + }); + + const duplicateButton = page.locator('editor-menu-action', { + hasText: 'Duplicate', + }); + + const deleteButton = page.locator('editor-menu-action', { + hasText: 'Delete', + }); + + return { + menu, + copyButton, + turnIntoCardButton, + duplicateButton, + deleteButton, + }; +} + +export async function clickBlockDragHandle(page: Page, blockId: string) { + const blockBox = await page + .locator(`[data-block-id="${blockId}"]`) + .boundingBox(); + + if (!blockBox) { + throw new Error(); + } + await page.mouse.move( + blockBox.x + blockBox.width / 2, + blockBox.y + blockBox.height / 2 + ); + + const handleBox = await page + .locator('.affine-drag-handle-container') + .boundingBox(); + if (!handleBox) { + throw new Error(); + } + await page.mouse.click( + handleBox.x + handleBox.width / 2, + handleBox.y + handleBox.height / 2 + ); +} diff --git a/blocksuite/tests-legacy/utils/actions/edgeless.ts b/blocksuite/tests-legacy/utils/actions/edgeless.ts new file mode 100644 index 0000000000000..633baf4286245 --- /dev/null +++ b/blocksuite/tests-legacy/utils/actions/edgeless.ts @@ -0,0 +1,1918 @@ +import '../declare-test-window.js'; + +import type { NoteBlockModel, NoteDisplayMode } from '@blocks/index.js'; +import type { IPoint, IVec } from '@blocksuite/global/utils'; +import { assertExists, sleep } from '@blocksuite/global/utils'; +import type { Locator, Page } from '@playwright/test'; +import { expect } from '@playwright/test'; + +import type { Bound } from '../asserts.js'; +import { clickView } from './click.js'; +import { dragBetweenCoords } from './drag.js'; +import { + pressBackspace, + pressEnter, + pressEscape, + selectAllByKeyboard, + SHIFT_KEY, + SHORT_KEY, + type, +} from './keyboard.js'; +import { + enterPlaygroundRoom, + getEditorLocator, + initEmptyEdgelessState, + resetHistory, + waitNextFrame, +} from './misc.js'; + +const rotWith = (A: number[], C: number[], r = 0): number[] => { + if (r === 0) return A; + + const s = Math.sin(r); + const c = Math.cos(r); + + const px = A[0] - C[0]; + const py = A[1] - C[1]; + + const nx = px * c - py * s; + const ny = px * s + py * c; + + return [nx + C[0], ny + C[1]]; +}; + +const AWAIT_TIMEOUT = 500; +export const ZOOM_BAR_RESPONSIVE_SCREEN_WIDTH = 1200; +export type Point = { x: number; y: number }; +export enum Shape { + Diamond = 'Diamond', + Ellipse = 'Ellipse', + 'Rounded rectangle' = 'Rounded rectangle', + Square = 'Square', + Triangle = 'Triangle', +} + +export enum LassoMode { + FreeHand = 'freehand', + Polygonal = 'polygonal', +} + +export enum ConnectorMode { + Straight, + Orthogonal, + Curve, +} + +export async function getNoteRect(page: Page, noteId: string) { + const xywh: string | null = await page.evaluate( + ([noteId]) => { + const doc = window.collection.getDoc('doc:home'); + const block = doc?.getBlockById(noteId); + if (block?.flavour === 'affine:note') { + return (block as NoteBlockModel).xywh; + } else { + return null; + } + }, + [noteId] as const + ); + expect(xywh).not.toBeNull(); + const [x, y, w, h] = JSON.parse(xywh as string); + return { x, y, w, h }; +} + +export async function getNoteProps(page: Page, noteId: string) { + const props = await page.evaluate( + ([id]) => { + const doc = window.collection.getDoc('doc:home'); + const block = doc?.getBlockById(id); + if (block?.flavour === 'affine:note') { + return (block as NoteBlockModel).keys.reduce( + (pre, key) => { + pre[key] = block[key as keyof typeof block] as string; + return pre; + }, + {} as Record + ); + } else { + return null; + } + }, + [noteId] as const + ); + return props; +} + +export async function extendFormatBar(page: Page) { + await page.click('sl-button:text("Test Operations")'); + await page.click('sl-menu-item:text("Extend Format Bar")'); + await waitNextFrame(page); +} + +export async function toggleFramePanel(page: Page) { + await page.click('sl-button:text("Test Operations")'); + await page.click('sl-menu-item:text("Toggle Frame Panel")'); + await waitNextFrame(page); +} + +export async function toggleMultipleEditors(page: Page) { + await page.click('sl-button:text("Test Operations")'); + await page.click('sl-menu-item:text("Toggle Multiple Editors")'); + await waitNextFrame(page); +} + +export async function switchEditorMode(page: Page) { + await page.click('sl-tooltip[content="Switch Editor"]'); + // FIXME: listen to editor loaded event + await waitNextFrame(page); +} + +export async function switchMultipleEditorsMode(page: Page) { + await page.evaluate(() => { + const containers = document.querySelectorAll('affine-editor-container'); + const mode = containers[0].mode === 'edgeless' ? 'page' : 'edgeless'; + + containers.forEach(container => { + container.mode = mode; + }); + }); +} + +export async function switchEditorEmbedMode(page: Page) { + await page.click('sl-button:text("Test Operations")'); + await page.click('sl-menu-item:text("Switch Offset Mode")'); +} + +export async function enterPresentationMode(page: Page) { + await page.click('sl-tooltip[content="Enter presentation mode"]'); + await waitNextFrame(page); +} + +export async function toggleEditorReadonly(page: Page) { + await page.click('sl-button:text("Test Operations")'); + await page.click('sl-menu-item:text("Toggle Readonly")'); + await waitNextFrame(page); +} + +type EdgelessTool = + | 'default' + | 'pan' + | 'note' + | 'shape' + | 'brush' + | 'eraser' + | 'text' + | 'connector' + | 'frame' + | 'frameNavigator' + | 'lasso'; +type ZoomToolType = 'zoomIn' | 'zoomOut' | 'fitToScreen'; +type ComponentToolType = 'shape' | 'thin' | 'thick' | 'brush' | 'more'; + +type PresentationToolType = 'previous' | 'next'; + +const locatorEdgelessToolButtonSenior = async ( + page: Page, + selector: string +): Promise => { + const target = page.locator(selector); + const visible = await target.isVisible(); + if (visible) return target; + // try to click next page + const nextButton = page.locator( + '.senior-nav-button-wrapper.next > icon-button' + ); + const nextExists = await nextButton.count(); + const isDisabled = + (await nextButton.getAttribute('data-test-disabled')) === 'true'; + if (!nextExists || isDisabled) return target; + await nextButton.click(); + await page.waitForTimeout(200); + return locatorEdgelessToolButtonSenior(page, selector); +}; + +export async function locatorEdgelessToolButton( + page: Page, + type: EdgelessTool, + innerContainer = true +) { + const selector = { + default: '.edgeless-default-button', + pan: '.edgeless-default-button', + shape: '.edgeless-shape-button', + brush: '.edgeless-brush-button', + eraser: '.edgeless-eraser-button', + text: '.edgeless-mindmap-button', + connector: '.edgeless-connector-button', + note: '.edgeless-note-button', + frame: '.edgeless-frame-button', + frameNavigator: '.edgeless-frame-navigator-button', + lasso: '.edgeless-lasso-button', + }[type]; + + let buttonType; + switch (type) { + case 'brush': + case 'text': + case 'eraser': + case 'shape': + case 'note': + buttonType = 'edgeless-toolbar-button'; + break; + default: + buttonType = 'edgeless-tool-icon-button'; + } + // TODO: quickTool locator is different + const button = await locatorEdgelessToolButtonSenior( + page, + `edgeless-toolbar-widget ${buttonType}${selector}` + ); + + return innerContainer ? button.locator('.icon-container') : button; +} + +export async function toggleZoomBarWhenSmallScreenWidth(page: Page) { + const toggleZoomBarButton = page.locator( + '.toggle-button edgeless-tool-icon-button' + ); + const isClosed = (await toggleZoomBarButton.count()) === 1; + if (isClosed) { + await toggleZoomBarButton.click(); + await page.waitForTimeout(200); + } +} + +export async function locatorEdgelessZoomToolButton( + page: Page, + type: ZoomToolType, + innerContainer = true +) { + const text = { + zoomIn: 'Zoom in', + zoomOut: 'Zoom out', + fitToScreen: 'Fit to screen', + }[type]; + + const screenWidth = page.viewportSize()?.width; + assertExists(screenWidth); + let zoomBarClass = 'horizontal'; + if (screenWidth < ZOOM_BAR_RESPONSIVE_SCREEN_WIDTH) { + await toggleZoomBarWhenSmallScreenWidth(page); + zoomBarClass = 'vertical'; + } + + const button = page + .locator( + `.edgeless-zoom-toolbar-container.${zoomBarClass} edgeless-tool-icon-button` + ) + .filter({ + hasText: text, + }); + + return innerContainer ? button.locator('.icon-container') : button; +} + +export function locatorEdgelessComponentToolButton( + page: Page, + type: ComponentToolType, + innerContainer = true +) { + const text = { + shape: 'Shape', + brush: 'Color', + thin: 'Thin', + thick: 'Thick', + more: 'More', + }[type]; + const button = page + .locator('edgeless-element-toolbar-widget editor-icon-button') + .filter({ + hasText: text, + }); + + return innerContainer ? button.locator('.icon-container') : button; +} + +export function locatorPresentationToolbarButton( + page: Page, + type: PresentationToolType +) { + const text = { + previous: 'Previous', + next: 'Next', + }[type]; + const button = page + .locator('presentation-toolbar edgeless-tool-icon-button') + .filter({ + hasText: text, + }); + + return button; +} + +export async function setEdgelessTool( + page: Page, + mode: EdgelessTool, + shape = Shape.Square +) { + switch (mode) { + // text tool is removed, use shortcut to trigger + case 'text': + await page.keyboard.press('t', { delay: 100 }); + break; + case 'default': { + const button = await locatorEdgelessToolButton(page, 'default', false); + const classes = (await button.getAttribute('class'))?.split(' '); + if (!classes?.includes('default')) { + await button.click(); + await sleep(100); + } + break; + } + case 'pan': { + const button = await locatorEdgelessToolButton(page, 'default', false); + const classes = (await button.getAttribute('class'))?.split(' '); + if (classes?.includes('default')) { + await button.click(); + await sleep(100); + } else if (classes?.includes('pan')) { + await button.click(); // change to default + await sleep(100); + await button.click(); // change to pan + await sleep(100); + } + break; + } + case 'lasso': + case 'note': + case 'brush': + case 'eraser': + case 'frame': + case 'connector': { + const button = await locatorEdgelessToolButton(page, mode, false); + await button.click(); + break; + } + case 'shape': { + const shapeToolButton = await locatorEdgelessToolButton( + page, + 'shape', + false + ); + // Avoid clicking on the shape-element (will trigger dragging mode) + await shapeToolButton.click({ position: { x: 5, y: 5 } }); + + const squareShapeButton = page + .locator('edgeless-slide-menu edgeless-tool-icon-button') + .filter({ hasText: shape }); + await squareShapeButton.click(); + break; + } + } +} +export type ShapeName = + | 'rect' + | 'ellipse' + | 'diamond' + | 'triangle' + | 'roundedRect'; + +export async function assertEdgelessShapeType(page: Page, type: ShapeName) { + const curType = await page.evaluate(() => { + const container = document.querySelector('affine-edgeless-root'); + if (!container) { + throw new Error('Missing edgeless page'); + } + const tool = container.gfx.tool.currentToolOption$.peek(); + if (tool.type !== 'shape') throw new Error('Expected shape tool'); + + return tool.shapeName; + }); + + expect(type).toEqual(curType); +} + +export async function assertEdgelessTool(page: Page, mode: EdgelessTool) { + const type = await page.evaluate(() => { + const container = document.querySelector('affine-edgeless-root'); + if (!container) { + throw new Error('Missing edgeless page'); + } + return container.gfx.tool.currentToolOption$.peek().type; + }); + expect(type).toEqual(mode); +} + +export async function assertEdgelessConnectorToolMode( + page: Page, + mode: ConnectorMode +) { + const tool = await page.evaluate(() => { + const container = document.querySelector('affine-edgeless-root'); + if (!container) { + throw new Error('Missing edgeless page'); + } + return container.gfx.tool.currentToolOption$.peek(); + }); + if (tool.type !== 'connector') { + throw new Error('Expected connector tool'); + } + expect(tool.mode).toEqual(mode); +} + +export async function assertEdgelessLassoToolMode(page: Page, mode: LassoMode) { + const tool = await page.evaluate(() => { + const container = document.querySelector('affine-edgeless-root'); + if (!container) { + throw new Error('Missing edgeless page'); + } + return container.gfx.tool.currentToolOption$.peek(); + }); + if (tool.type !== 'lasso') { + throw new Error('Expected lasso tool'); + } + expect(tool.mode).toEqual(mode === LassoMode.FreeHand ? 0 : 1); +} + +export async function getEdgelessBlockChild(page: Page) { + const block = page.locator('affine-edgeless-note'); + const blockBox = await block.boundingBox(); + if (blockBox === null) throw new Error('Missing edgeless block child rect'); + return blockBox; +} + +export async function getEdgelessSelectedRect(page: Page) { + const selectedBox = await page.evaluate(() => { + const selected = document + .querySelector('edgeless-selected-rect') + ?.shadowRoot?.querySelector('.affine-edgeless-selected-rect'); + if (!selected) { + throw new Error('Missing edgeless selected rect'); + } + return selected.getBoundingClientRect(); + }); + return selectedBox; +} + +export async function getEdgelessSelectedRectModel(page: Page): Promise { + return page.evaluate(() => { + const container = document.querySelector('affine-edgeless-root'); + if (!container) throw new Error('container not found'); + const bound = container.service.selection.selectedBound; + return [bound.x, bound.y, bound.w, bound.h]; + }); +} + +export async function decreaseZoomLevel(page: Page) { + const btn = await locatorEdgelessZoomToolButton(page, 'zoomOut', false); + await btn.click(); + await sleep(AWAIT_TIMEOUT); +} + +export async function increaseZoomLevel(page: Page) { + const btn = await locatorEdgelessZoomToolButton(page, 'zoomIn', false); + await btn.click(); + await sleep(AWAIT_TIMEOUT); +} + +export async function autoFit(page: Page) { + const btn = await locatorEdgelessZoomToolButton(page, 'fitToScreen', false); + await btn.click(); + await sleep(AWAIT_TIMEOUT); +} + +export async function addBasicBrushElement( + page: Page, + start: Point, + end: Point, + auto = true +) { + await setEdgelessTool(page, 'brush'); + await dragBetweenCoords(page, start, end, { steps: 100 }); + auto && (await setEdgelessTool(page, 'default')); +} + +export async function addBasicRectShapeElement( + page: Page, + start: Point, + end: Point +) { + await setEdgelessTool(page, 'shape'); + await dragBetweenCoords(page, start, end, { steps: 50 }); +} + +export async function addBasicShapeElement( + page: Page, + start: Point, + end: Point, + shape: Shape +) { + await setEdgelessTool(page, 'shape', shape); + await dragBetweenCoords(page, start, end, { steps: 50 }); + return (await getSelectedIds(page))[0]; +} + +export async function addBasicConnectorElement( + page: Page, + start: Point, + end: Point +) { + await setEdgelessTool(page, 'connector'); + await dragBetweenCoords(page, start, end, { steps: 100 }); +} + +export async function addBasicFrameElement( + page: Page, + start: Point, + end: Point +) { + await setEdgelessTool(page, 'frame'); + await dragBetweenCoords(page, start, end, { steps: 50 }); +} + +export async function addBasicEdgelessText( + page: Page, + text: string, + x: number, + y: number +) { + await setEdgelessTool(page, 'text'); + await page.mouse.click(x, y); + await page.locator('affine-edgeless-text').waitFor({ state: 'visible' }); + await waitNextFrame(page, 100); + await type(page, text, 20); + await pressEscape(page, 2); + await setEdgelessTool(page, 'default'); +} + +export async function addNote(page: Page, text: string, x: number, y: number) { + await setEdgelessTool(page, 'note'); + await page.mouse.click(x, y); + await waitNextFrame(page); + + const paragraphs = text.split('\n'); + let i = 0; + for (const paragraph of paragraphs) { + ++i; + await type(page, paragraph, 20); + + if (i < paragraphs.length) { + await pressEnter(page); + } + } + + const { id } = await page.evaluate(() => { + const container = document.querySelector('affine-edgeless-root'); + if (!container) throw new Error('container not found'); + + return { + id: container.service.selection.selectedIds[0], + }; + }); + + return id; +} + +export async function exitEditing(page: Page) { + await page.evaluate(() => { + const container = document.querySelector('affine-edgeless-root'); + if (!container) throw new Error('container not found'); + + container.service.selection.set({ + elements: [], + editing: false, + }); + }); +} + +export async function resizeElementByHandle( + page: Page, + delta: Point, + corner: + | 'top-left' + | 'top-right' + | 'bottom-right' + | 'bottom-left' = 'top-left', + steps = 1 +) { + const handle = page.locator(`.handle[aria-label="${corner}"] .resize`); + const box = await handle.boundingBox(); + if (box === null) throw new Error(); + const offset = 5; + await dragBetweenCoords( + page, + { x: box.x + offset, y: box.y + offset }, + { x: box.x + delta.x + offset, y: box.y + delta.y + offset }, + { + steps, + } + ); +} + +export async function rotateElementByHandle( + page: Page, + deg = 0, + corner: + | 'top-left' + | 'top-right' + | 'bottom-right' + | 'bottom-left' = 'top-left', + steps = 1 +) { + const rect = await page + .locator('.affine-edgeless-selected-rect') + .boundingBox(); + if (rect === null) throw new Error(); + const box = await page + .locator(`.handle[aria-label="${corner}"] .rotate`) + .boundingBox(); + if (box === null) throw new Error(); + + const cx = rect.x + rect.width / 2; + const cy = rect.y + rect.height / 2; + const x = box.x + box.width / 2; + const y = box.y + box.height / 2; + + const t = rotWith([x, y], [cx, cy], (deg * Math.PI) / 180); + + await dragBetweenCoords( + page, + { x, y }, + { x: t[0], y: t[1] }, + { + steps, + } + ); +} + +export async function selectBrushColor(page: Page, color: string) { + const colorButton = page.locator( + `edgeless-brush-menu .color-unit[aria-label="${color.toLowerCase()}"]` + ); + await colorButton.click(); +} + +export async function selectBrushSize(page: Page, size: string) { + const sizeIndexMap: Record = { + two: 1, + four: 2, + six: 3, + eight: 4, + ten: 5, + twelve: 6, + }; + const sizeButton = page.locator( + `edgeless-brush-menu .line-width-panel .line-width-button:nth-child(${sizeIndexMap[size]})` + ); + await sizeButton.click(); +} + +export async function pickColorAtPoints(page: Page, points: number[][]) { + const pickedColors: `#${string}`[] = await page.evaluate(points => { + const node = document.querySelector( + '.affine-edgeless-surface-block-container canvas' + ) as HTMLCanvasElement; + const w = node.width; + const h = node.height; + const ctx = node?.getContext('2d'); + if (!ctx) throw new Error('Cannot get canvas context'); + const pixelData = ctx.getImageData(0, 0, w, h).data; + + const colors = points.map(([x, y]) => { + const startPosition = (y * w + x) * 4; + return ('#' + + ( + (1 << 24) + + (pixelData[startPosition] << 16) + + (pixelData[startPosition + 1] << 8) + + pixelData[startPosition + 2] + ) + .toString(16) + .slice(1)) as `#${string}`; + }); + return colors; + }, points); + return pickedColors; +} + +export async function getNoteBoundBoxInEdgeless(page: Page, noteId: string) { + const editor = getEditorLocator(page); + const note = editor.locator( + `affine-edgeless-note[data-block-id="${noteId}"]` + ); + const bound = await note.boundingBox(); + if (!bound) { + throw new Error(`Missing note: ${noteId}`); + } + return bound; +} + +export async function getAllNoteIds(page: Page) { + return page.evaluate(() => { + return Array.from(document.querySelectorAll('affine-note')).map( + note => note.model.id + ); + }); +} + +export async function getAllEdgelessNoteIds(page: Page) { + return page.evaluate(() => { + return Array.from(document.querySelectorAll('affine-edgeless-note')).map( + note => note.model.id + ); + }); +} + +export async function getAllEdgelessTextIds(page: Page) { + return page.evaluate(() => { + return Array.from(document.querySelectorAll('affine-edgeless-text')).map( + text => text.model.id + ); + }); +} + +export async function countBlock(page: Page, flavour: string) { + return page.evaluate( + ([flavour]) => { + return Array.from(document.querySelectorAll(flavour)).length; + }, + [flavour] + ); +} + +export async function activeNoteInEdgeless(page: Page, noteId: string) { + const bound = await getNoteBoundBoxInEdgeless(page, noteId); + await page.mouse.dblclick( + bound.x + bound.width / 2, + bound.y + bound.height / 2 + ); +} + +export async function selectNoteInEdgeless(page: Page, noteId: string) { + const bound = await getNoteBoundBoxInEdgeless(page, noteId); + await page.mouse.click(bound.x + bound.width / 2, bound.y + bound.height / 2); +} + +export function locatorNoteDisplayModeButton( + page: Page, + mode: NoteDisplayMode +) { + return page + .locator('edgeless-change-note-button') + .locator('note-display-mode-panel') + .locator(`.item.${mode}`); +} + +export function locatorScalePanelButton(page: Page, scale: number) { + return page.locator('edgeless-scale-panel').locator(`.scale-${scale}`); +} + +export async function changeNoteDisplayMode(page: Page, mode: NoteDisplayMode) { + const button = locatorNoteDisplayModeButton(page, mode); + await button.click(); +} + +export async function changeNoteDisplayModeWithId( + page: Page, + noteId: string, + mode: NoteDisplayMode +) { + await selectNoteInEdgeless(page, noteId); + await triggerComponentToolbarAction(page, 'changeNoteDisplayMode'); + await waitNextFrame(page); + await changeNoteDisplayMode(page, mode); +} + +export async function updateExistedBrushElementSize( + page: Page, + nthSizeButton: 1 | 2 | 3 | 4 | 5 | 6 +) { + // get the nth brush size button + const btn = page.locator( + `.line-width-panel > div:nth-child(${nthSizeButton})` + ); + + await btn.click(); +} + +export async function openComponentToolbarMoreMenu(page: Page) { + const btn = page.locator( + 'edgeless-element-toolbar-widget edgeless-more-button editor-menu-button' + ); + + await btn.click(); +} + +export async function clickComponentToolbarMoreMenuButton( + page: Page, + button: 'delete' +) { + const text = { + delete: 'Delete', + }[button]; + + const btn = locatorComponentToolbarMoreButton(page) + .locator('editor-menu-action') + .filter({ hasText: text }); + + await btn.click(); +} + +// stepX/Y may not equal to wheel event delta. +// Chromium reports deltaX/deltaY scaled by host device scale factor. +// https://bugs.chromium.org/p/chromium/issues/detail?id=1324819 +export async function zoomByMouseWheel( + page: Page, + stepX: number, + stepY: number +) { + await page.keyboard.down(SHORT_KEY); + await page.mouse.wheel(stepX, stepY); + await page.keyboard.up(SHORT_KEY); +} + +// touch screen is not supported by Playwright now +// use pointer event mock instead +// https://github.com/microsoft/playwright/issues/2903 +export async function multiTouchDown(page: Page, points: Point[]) { + await page.evaluate(points => { + const target = document.querySelector('affine-edgeless-root'); + if (!target) { + throw new Error('Missing edgeless page'); + } + points.forEach((point, index) => { + const clientX = point.x; + const clientY = point.y; + + target.dispatchEvent( + new PointerEvent('pointerdown', { + clientX, + clientY, + bubbles: true, + pointerType: 'touch', + pointerId: index, + isPrimary: index === 0, + }) + ); + }); + }, points); +} + +export async function multiTouchMove( + page: Page, + from: Point[], + to: Point[], + step = 5 +) { + await page.evaluate( + async ({ from, to, step }) => { + const target = document.querySelector('affine-edgeless-root'); + if (!target) { + throw new Error('Missing edgeless page'); + } + + if (from.length !== to.length) { + throw new Error('from and to should have the same length'); + } + + if (step !== 0) { + for (const [i] of Array.from({ length: step }).entries()) { + from.forEach((point, index) => { + const clientX = + point.x + ((to[index].x - point.x) / step) * (i + 1); + const clientY = + point.y + ((to[index].y - point.y) / step) * (i + 1); + + target.dispatchEvent( + new PointerEvent('pointermove', { + clientX, + clientY, + bubbles: true, + pointerType: 'touch', + pointerId: index, + isPrimary: index === 0, + }) + ); + }); + await new Promise(resolve => setTimeout(resolve, 16)); + } + } + }, + { from, to, step } + ); +} + +export async function multiTouchUp(page: Page, points: Point[]) { + await page.evaluate(points => { + const target = document.querySelector('affine-edgeless-root'); + if (!target) { + throw new Error('Missing edgeless page'); + } + points.forEach((point, index) => { + const clientX = point.x; + const clientY = point.y; + + target.dispatchEvent( + new PointerEvent('pointerup', { + clientX, + clientY, + bubbles: true, + pointerType: 'touch', + pointerId: index, + isPrimary: index === 0, + }) + ); + }); + }, points); +} + +export async function zoomFitByKeyboard(page: Page) { + await page.keyboard.press(`${SHORT_KEY}+1`, { delay: 100 }); + await waitNextFrame(page, 300); +} + +export async function zoomOutByKeyboard(page: Page) { + await page.keyboard.press(`${SHORT_KEY}+-`, { delay: 100 }); + await waitNextFrame(page, 300); +} + +export async function zoomResetByKeyboard(page: Page) { + await page.keyboard.press(`${SHORT_KEY}+0`, { delay: 50 }); + // Wait for animation + await waitNextFrame(page, 300); +} + +export async function zoomInByKeyboard(page: Page) { + await page.keyboard.press(`${SHORT_KEY}+=`, { delay: 50 }); + await waitNextFrame(page, 300); +} + +export async function getZoomLevel(page: Page) { + const screenWidth = page.viewportSize()?.width; + assertExists(screenWidth); + let zoomBarClass = 'horizontal'; + if (screenWidth < ZOOM_BAR_RESPONSIVE_SCREEN_WIDTH) { + await toggleZoomBarWhenSmallScreenWidth(page); + zoomBarClass = 'vertical'; + } + const span = page.locator( + `.edgeless-zoom-toolbar-container.${zoomBarClass} .zoom-percent` + ); + await waitNextFrame(page); + const text = await span.textContent(); + if (!text) { + throw new Error('Missing .zoom-percent'); + } + return Number(text.replace('%', '')); +} + +export async function optionMouseDrag( + page: Page, + start: number[], + end: number[] +) { + start = await toViewCoord(page, start); + end = await toViewCoord(page, end); + await page.keyboard.down('Alt'); + await dragBetweenCoords( + page, + { x: start[0], y: start[1] }, + { x: end[0], y: end[1] }, + { steps: 30 } + ); + await page.keyboard.up('Alt'); +} + +export async function shiftClick(page: Page, point: IPoint) { + await page.keyboard.down(SHIFT_KEY); + await page.mouse.click(point.x, point.y); + await page.keyboard.up(SHIFT_KEY); +} + +export async function shiftClickView(page: Page, point: [number, number]) { + await page.keyboard.down(SHIFT_KEY); + await clickView(page, point); + await page.keyboard.up(SHIFT_KEY); +} + +export async function deleteAll(page: Page) { + await clickView(page, [0, 0]); + await selectAllByKeyboard(page); + await pressBackspace(page); +} + +export async function deleteAllConnectors(page: Page) { + return page.evaluate(() => { + const container = document.querySelector('affine-edgeless-root'); + if (!container) throw new Error('container not found'); + container.service.getElementsByType('connector').forEach(c => { + container.service.removeElement(c.id); + }); + }); +} + +export function locatorComponentToolbar(page: Page) { + return page.locator('edgeless-element-toolbar-widget'); +} + +export function locatorComponentToolbarMoreButton(page: Page) { + const moreButton = locatorComponentToolbar(page).locator( + 'edgeless-more-button' + ); + return moreButton; +} +type Action = + | 'bringToFront' + | 'bringForward' + | 'sendBackward' + | 'sendToBack' + | 'copyAsPng' + | 'changeNoteColor' + | 'changeShapeStyle' + | 'changeShapeFillColor' + | 'changeShapeStrokeColor' + | 'changeShapeStrokeStyles' + | 'changeConnectorStrokeColor' + | 'changeConnectorStrokeStyles' + | 'changeConnectorShape' + | 'addFrame' + | 'addGroup' + | 'addMindmap' + | 'createGroupOnMoreOption' + | 'ungroup' + | 'releaseFromGroup' + | 'createFrameOnMoreOption' + | 'duplicate' + | 'renameGroup' + | 'autoSize' + | 'changeNoteDisplayMode' + | 'changeNoteSlicerSetting' + | 'changeNoteScale' + | 'addText' + | 'quickConnect' + | 'turnIntoLinkedDoc' + | 'createLinkedDoc' + | 'openLinkedDoc' + | 'toCardView' + | 'toEmbedView' + | 'autoArrange' + | 'autoResize'; + +export async function triggerComponentToolbarAction( + page: Page, + action: Action +) { + switch (action) { + case 'bringToFront': { + const moreButton = locatorComponentToolbarMoreButton(page); + await moreButton.click(); + + const actionButton = moreButton + .locator('.more-actions-container editor-menu-action') + .filter({ + hasText: 'Bring to Front', + }); + await actionButton.click(); + break; + } + case 'bringForward': { + const moreButton = locatorComponentToolbarMoreButton(page); + await moreButton.click(); + + const actionButton = moreButton + .locator('.more-actions-container editor-menu-action') + .filter({ + hasText: 'Bring Forward', + }); + await actionButton.click(); + break; + } + case 'sendBackward': { + const moreButton = locatorComponentToolbarMoreButton(page); + await moreButton.click(); + + const actionButton = moreButton + .locator('.more-actions-container editor-menu-action') + .filter({ + hasText: 'Send Backward', + }); + await actionButton.click(); + break; + } + case 'sendToBack': { + const moreButton = locatorComponentToolbarMoreButton(page); + await moreButton.click(); + + const actionButton = moreButton + .locator('.more-actions-container editor-menu-action') + .filter({ + hasText: 'Send to Back', + }); + await actionButton.click(); + break; + } + case 'copyAsPng': { + const moreButton = locatorComponentToolbarMoreButton(page); + await moreButton.click(); + + const actionButton = moreButton + .locator('.more-actions-container editor-menu-action') + .filter({ + hasText: 'Copy as PNG', + }); + await actionButton.click(); + break; + } + case 'createFrameOnMoreOption': { + const moreButton = locatorComponentToolbarMoreButton(page); + await moreButton.click(); + + const actionButton = moreButton + .locator('.more-actions-container editor-menu-action') + .filter({ + hasText: 'Frame Section', + }); + await actionButton.click(); + break; + } + case 'duplicate': { + const moreButton = locatorComponentToolbarMoreButton(page); + await moreButton.click(); + + const actionButton = moreButton + .locator('.more-actions-container editor-menu-action') + .filter({ + hasText: 'Duplicate', + }); + await actionButton.click(); + break; + } + case 'changeShapeFillColor': { + const button = locatorComponentToolbar(page) + .locator('edgeless-change-shape-button') + .getByRole('button', { name: 'Fill color' }); + await button.click(); + break; + } + case 'changeShapeStrokeStyles': + case 'changeShapeStrokeColor': { + const button = locatorComponentToolbar(page) + .locator('edgeless-change-shape-button') + .getByRole('button', { name: 'Border style' }); + await button.click(); + break; + } + case 'changeShapeStyle': { + const button = locatorComponentToolbar(page) + .locator('edgeless-change-shape-button') + .getByRole('button', { name: /^Style$/ }); + await button.click(); + break; + } + case 'changeConnectorStrokeColor': { + const button = page + .locator('edgeless-change-connector-button') + .getByRole('button', { name: 'Stroke style' }); + await button.click(); + break; + } + case 'changeConnectorStrokeStyles': { + const button = locatorComponentToolbar(page) + .locator('edgeless-change-connector-button') + .getByRole('button', { name: 'Stroke style' }); + await button.click(); + break; + } + case 'changeConnectorShape': { + const button = locatorComponentToolbar(page) + .locator('edgeless-change-connector-button') + .getByRole('button', { name: 'Shape' }); + await button.click(); + break; + } + case 'addFrame': { + const button = locatorComponentToolbar(page).locator( + 'edgeless-add-frame-button' + ); + await button.click(); + break; + } + case 'addGroup': { + const button = locatorComponentToolbar(page).locator( + 'edgeless-add-group-button' + ); + await button.click(); + break; + } + case 'addMindmap': { + const button = page.locator('edgeless-mindmap-tool-button'); + await button.click(); + await page.mouse.move(400, 400); + await page.mouse.click(400, 400); + break; + } + case 'createGroupOnMoreOption': { + const moreButton = locatorComponentToolbarMoreButton(page); + await moreButton.click(); + + const actionButton = moreButton + .locator('.more-actions-container editor-menu-action') + .filter({ + hasText: 'Group Section', + }); + await actionButton.click(); + break; + } + case 'ungroup': { + const button = locatorComponentToolbar(page) + .locator('edgeless-change-group-button') + .getByRole('button', { name: 'Ungroup' }); + await button.click(); + break; + } + case 'renameGroup': { + const button = locatorComponentToolbar(page) + .locator('edgeless-change-group-button') + .getByRole('button', { name: 'Rename' }); + await button.click(); + break; + } + case 'releaseFromGroup': { + const button = locatorComponentToolbar(page).locator( + 'edgeless-release-from-group-button' + ); + await button.click(); + break; + } + case 'changeNoteColor': { + const button = locatorComponentToolbar(page) + .locator('edgeless-change-note-button') + .getByRole('button', { name: 'Background' }); + await button.click(); + break; + } + case 'changeNoteDisplayMode': { + const button = locatorComponentToolbar(page) + .locator('edgeless-change-note-button') + .getByRole('button', { name: 'Mode' }); + await button.click(); + break; + } + case 'changeNoteSlicerSetting': { + const button = locatorComponentToolbar(page) + .locator('edgeless-change-note-button') + .getByRole('button', { name: 'Slicer' }); + await button.click(); + break; + } + case 'changeNoteScale': { + const button = locatorComponentToolbar(page) + .locator('edgeless-change-note-button') + .getByRole('button', { name: 'Scale' }); + await button.click(); + break; + } + case 'autoSize': { + const button = locatorComponentToolbar(page) + .locator('edgeless-change-note-button') + .getByRole('button', { name: 'Size' }); + await button.click(); + break; + } + case 'addText': { + const button = locatorComponentToolbar(page).getByRole('button', { + name: 'Add text', + }); + await button.click(); + break; + } + case 'quickConnect': { + const button = locatorComponentToolbar(page).getByRole('button', { + name: 'Draw connector', + }); + await button.click(); + break; + } + case 'turnIntoLinkedDoc': { + const moreButton = locatorComponentToolbarMoreButton(page); + await moreButton.click(); + + const actionButton = moreButton + .locator('.more-actions-container editor-menu-action') + .filter({ + hasText: 'Turn into linked doc', + }); + await actionButton.click(); + break; + } + case 'createLinkedDoc': { + const moreButton = locatorComponentToolbarMoreButton(page); + await moreButton.click(); + + const actionButton = moreButton + .locator('.more-actions-container editor-menu-action') + .filter({ + hasText: 'Create linked doc', + }); + await actionButton.click(); + break; + } + case 'openLinkedDoc': { + const openButton = locatorComponentToolbar(page).getByRole('button', { + name: 'Open', + }); + await openButton.click(); + + const button = locatorComponentToolbar(page).getByRole('button', { + name: 'Open this doc', + }); + await button.click(); + break; + } + case 'toCardView': { + const button = locatorComponentToolbar(page) + .locator('edgeless-tool-icon-button') + .filter({ + hasText: 'Card view', + }); + await button.click(); + break; + } + case 'toEmbedView': { + const button = locatorComponentToolbar(page) + .locator('edgeless-tool-icon-button') + .filter({ + hasText: 'Embed view', + }); + await button.click(); + break; + } + case 'autoArrange': { + const button = locatorComponentToolbar(page).locator( + 'edgeless-align-button' + ); + await button.click(); + const arrange = button.locator('editor-icon-button').filter({ + hasText: 'Auto arrange', + }); + await arrange.click(); + break; + } + case 'autoResize': { + const button = locatorComponentToolbar(page).locator( + 'edgeless-align-button' + ); + await button.click(); + const resize = button.locator('editor-icon-button').filter({ + hasText: 'Resize & Align', + }); + await resize.click(); + break; + } + } +} + +export async function changeEdgelessNoteBackground(page: Page, color: string) { + const colorButton = page + .locator('edgeless-change-note-button') + .locator(`.color-unit[aria-label="${color}"]`); + await colorButton.click(); +} + +export async function changeShapeFillColor(page: Page, color: string) { + const colorButton = page + .locator('edgeless-change-shape-button') + .locator('edgeless-color-picker-button.fill-color') + .locator('edgeless-color-panel') + .locator(`.color-unit[aria-label="${color}"]`); + await colorButton.click({ force: true }); +} + +export async function changeShapeFillColorToTransparent(page: Page) { + const colorButton = page + .locator('edgeless-change-shape-button') + .locator('edgeless-color-picker-button.fill-color') + .locator('edgeless-color-panel') + .locator('edgeless-color-custom-button'); + await colorButton.click({ force: true }); + + const input = page.locator('edgeless-color-picker').locator('label.alpha'); + await input.focus(); + await input.press('ArrowRight'); + await input.press('ArrowRight'); + await input.press('ArrowRight'); + await input.press('Backspace'); + await input.press('Backspace'); + await input.press('Backspace'); +} + +export async function changeShapeStrokeColor(page: Page, color: string) { + const colorButton = page + .locator('edgeless-change-shape-button') + .locator('edgeless-color-picker-button.border-style') + .locator(`.color-unit[aria-label="${color}"]`); + await colorButton.click(); +} + +export async function resizeConnectorByStartCapitalHandler( + page: Page, + delta: { x: number; y: number }, + steps = 1 +) { + const handler = page.locator( + '.affine-edgeless-selected-rect .line-controller.line-start' + ); + const box = await handler.boundingBox(); + if (box === null) throw new Error(); + const offset = 5; + await dragBetweenCoords( + page, + { x: box.x + offset, y: box.y + offset }, + { x: box.x + delta.x + offset, y: box.y + delta.y + offset }, + { + steps, + } + ); +} + +export function getEdgelessLineWidthPanel(page: Page) { + return page + .locator('edgeless-change-shape-button') + .locator('edgeless-line-width-panel') + .locator('.line-width-panel'); +} +export async function changeShapeStrokeWidth(page: Page) { + const lineWidthPanel = getEdgelessLineWidthPanel(page); + const lineWidthPanelRect = await lineWidthPanel.boundingBox(); + assertExists(lineWidthPanelRect); + // click line width panel by position + const x = lineWidthPanelRect.x + 40; + const y = lineWidthPanelRect.y + 10; + await page.mouse.click(x, y); +} + +export function locatorShapeStrokeStyleButton( + page: Page, + mode: 'solid' | 'dash' | 'none' +) { + return page + .locator('edgeless-change-shape-button') + .locator(`.line-style-button.mode-${mode}`); +} + +export async function changeShapeStrokeStyle( + page: Page, + mode: 'solid' | 'dash' | 'none' +) { + const button = locatorShapeStrokeStyleButton(page, mode); + await button.click(); +} + +export function locatorShapeStyleButton( + page: Page, + style: 'general' | 'scribbled' +) { + return page + .locator('edgeless-change-shape-button') + .locator('edgeless-shape-style-panel') + .getByRole('button', { name: style }); +} + +export async function changeShapeStyle( + page: Page, + style: 'general' | 'scribbled' +) { + const button = locatorShapeStyleButton(page, style); + await button.click(); +} + +export async function changeConnectorStrokeColor(page: Page, color: string) { + const colorButton = page + .locator('edgeless-change-connector-button') + .locator('edgeless-color-panel') + .getByLabel(color); + await colorButton.click(); +} + +export function locatorConnectorStrokeWidthButton( + page: Page, + buttonPosition: number +) { + return page + .locator('edgeless-change-connector-button') + .locator(`edgeless-line-width-panel`) + .locator(`.line-width-button:nth-child(${buttonPosition})`); +} +export async function changeConnectorStrokeWidth( + page: Page, + buttonPosition: number +) { + const button = locatorConnectorStrokeWidthButton(page, buttonPosition); + await button.click(); +} + +export function locatorConnectorStrokeStyleButton( + page: Page, + mode: 'solid' | 'dash' | 'none' +) { + return page + .locator('edgeless-change-connector-button') + .locator(`.line-style-button.mode-${mode}`); +} +export async function changeConnectorStrokeStyle( + page: Page, + mode: 'solid' | 'dash' | 'none' +) { + const button = locatorConnectorStrokeStyleButton(page, mode); + await button.click(); +} + +export async function initThreeOverlapFilledShapes(page: Page) { + const rect0 = { + start: { x: 100, y: 100 }, + end: { x: 200, y: 200 }, + }; + await addBasicRectShapeElement(page, rect0.start, rect0.end); + await page.mouse.click(rect0.start.x + 5, rect0.start.y + 5); + await triggerComponentToolbarAction(page, 'changeShapeFillColor'); + await changeShapeFillColor(page, '--affine-palette-shape-teal'); + + const rect1 = { + start: { x: 130, y: 130 }, + end: { x: 230, y: 230 }, + }; + await addBasicRectShapeElement(page, rect1.start, rect1.end); + await page.mouse.click(rect1.start.x + 5, rect1.start.y + 5); + await triggerComponentToolbarAction(page, 'changeShapeFillColor'); + await changeShapeFillColor(page, '--affine-palette-shape-black'); + + const rect2 = { + start: { x: 160, y: 160 }, + end: { x: 260, y: 260 }, + }; + await addBasicRectShapeElement(page, rect2.start, rect2.end); + await page.mouse.click(rect2.start.x + 5, rect2.start.y + 5); + await triggerComponentToolbarAction(page, 'changeShapeFillColor'); + await changeShapeFillColor(page, '--affine-palette-shape-white'); +} + +export async function initThreeOverlapNotes(page: Page, x = 130, y = 140) { + await addNote(page, 'abc', x, y); + await addNote(page, 'efg', x + 30, y); + await addNote(page, 'hij', x + 60, y); +} + +export async function initThreeNotes(page: Page) { + await addNote(page, 'abc', 30 + 100, 40 + 100); + await addNote(page, 'efg', 30 + 130, 40 + 200); + await addNote(page, 'hij', 30 + 160, 40 + 300); +} + +export async function toViewCoord(page: Page, point: number[]) { + return page.evaluate(point => { + const container = document.querySelector('affine-edgeless-root'); + if (!container) throw new Error('container not found'); + return container.service.viewport.toViewCoord(point[0], point[1]); + }, point); +} + +export async function dragBetweenViewCoords( + page: Page, + start: number[], + end: number[], + options?: Parameters[3] +) { + const [startX, startY] = await toViewCoord(page, start); + const [endX, endY] = await toViewCoord(page, end); + await dragBetweenCoords( + page, + { x: startX, y: startY }, + { x: endX, y: endY }, + options + ); +} + +export async function toModelCoord(page: Page, point: number[]) { + return page.evaluate(point => { + const container = document.querySelector('affine-edgeless-root'); + if (!container) throw new Error('container not found'); + return container.service.viewport.toModelCoord(point[0], point[1]); + }, point); +} + +export async function getConnectorSourceConnection(page: Page) { + return page.evaluate(() => { + const container = document.querySelector('affine-edgeless-root'); + if (!container) throw new Error('container not found'); + return container.service.getElementsByType('connector')[0].source; + }); +} + +export async function getConnectorPath(page: Page, index = 0): Promise { + return page.evaluate( + ([index]) => { + const container = document.querySelector('affine-edgeless-root'); + if (!container) throw new Error('container not found'); + const connectors = container.service.getElementsByType('connector'); + return connectors[index].absolutePath; + }, + [index] + ); +} + +export async function getEdgelessElementBound( + page: Page, + elementId: string +): Promise<[number, number, number, number]> { + return page.evaluate( + ([elementId]) => { + const container = document.querySelector('affine-edgeless-root'); + if (!container) throw new Error('container not found'); + const element = container.service.getElementById(elementId); + if (!element) throw new Error(`element not found: ${elementId}`); + return JSON.parse(element.xywh); + }, + [elementId] + ); +} + +export async function getSelectedIds(page: Page) { + return page.evaluate(() => { + const container = document.querySelector('affine-edgeless-root'); + if (!container) throw new Error('container not found'); + return container.service.selection.selectedElements.map(e => e.id); + }); +} + +export async function getSelectedBoundCount(page: Page) { + return page.evaluate(() => { + const container = document.querySelector('affine-edgeless-root'); + if (!container) throw new Error('container not found'); + return container.service.selection.selectedElements.length; + }); +} + +export async function getSelectedBound( + page: Page, + index = 0 +): Promise<[number, number, number, number]> { + return page.evaluate( + ([index]) => { + const container = document.querySelector('affine-edgeless-root'); + if (!container) throw new Error('container not found'); + const selected = container.service.selection.selectedElements[index]; + return JSON.parse(selected.xywh); + }, + [index] + ); +} + +export async function getContainerOfElements(page: Page, ids: string[]) { + return page.evaluate( + ([ids]) => { + const container = document.querySelector('affine-edgeless-root'); + if (!container) throw new Error('container not found'); + + return ids.map(id => container.service.surface.getGroup(id)?.id ?? null); + }, + [ids] + ); +} + +export async function getContainerIds(page: Page) { + return page.evaluate(() => { + const container = document.querySelector('affine-edgeless-root'); + if (!container) throw new Error('container not found'); + return container.service.elements.map(el => el.group?.id ?? 'null'); + }); +} + +export async function getContainerChildIds(page: Page, id: string) { + return page.evaluate( + ([id]) => { + const container = document.querySelector('affine-edgeless-root'); + if (!container) throw new Error('container not found'); + const gfxModel = container.service.getElementById(id); + + return gfxModel && container.service.surface.isGroup(gfxModel) + ? gfxModel.childIds + : []; + }, + [id] + ); +} + +export async function getCanvasElementsCount(page: Page) { + return page.evaluate(() => { + const container = document.querySelector('affine-edgeless-root'); + if (!container) throw new Error('container not found'); + return container.service.elements.length; + }); +} + +export async function getSortedIds(page: Page) { + return page.evaluate(() => { + const container = document.querySelector('affine-edgeless-root'); + if (!container) throw new Error('container not found'); + return container.service.layer.canvasElements.map(e => e.id); + }); +} + +export async function getAllSortedIds(page: Page) { + return page.evaluate(() => { + const container = document.querySelector('affine-edgeless-root'); + if (!container) throw new Error('container not found'); + return container.service.edgelessElements.map(e => e.id); + }); +} + +export async function getTypeById(page: Page, id: string) { + return page.evaluate( + ([id]) => { + const container = document.querySelector('affine-edgeless-root'); + if (!container) throw new Error('container not found'); + const element = container.service.getElementById(id)!; + return 'flavour' in element ? element.flavour : element.type; + }, + [id] + ); +} + +export async function getIds(page: Page, filterGroup = false) { + return page.evaluate( + ([filterGroup]) => { + const container = document.querySelector('affine-edgeless-root'); + if (!container) throw new Error('container not found'); + return container.service.elements + .filter(el => !filterGroup || el.type !== 'group') + .map(e => e.id); + }, + [filterGroup] + ); +} + +export async function getFirstContainerId(page: Page, exclude: string[] = []) { + return page.evaluate( + ([exclude]) => { + const container = document.querySelector('affine-edgeless-root'); + if (!container) throw new Error('container not found'); + return ( + container.service.edgelessElements.find( + e => container.service.surface.isGroup(e) && !exclude.includes(e.id) + )?.id ?? '' + ); + }, + [exclude] + ); +} + +export async function getIndexes(page: Page) { + return page.evaluate(() => { + const container = document.querySelector('affine-edgeless-root'); + if (!container) throw new Error('container not found'); + return container.service.elements.map(e => e.index); + }); +} + +export async function getSortedIdsInViewport(page: Page) { + return page.evaluate(() => { + const container = document.querySelector('affine-edgeless-root'); + if (!container) throw new Error('container not found'); + const { service } = container; + return service.gfx.grid + .search(service.viewport.viewportBounds, { + filter: ['canvas'], + }) + .map(e => e.id); + }); +} + +export async function edgelessCommonSetup(page: Page) { + await enterPlaygroundRoom(page); + await initEmptyEdgelessState(page); + await switchEditorMode(page); + await deleteAll(page); + await resetHistory(page); +} + +export async function createFrame( + page: Page, + coord1: [number, number], + coord2: [number, number] +) { + await page.keyboard.press('f'); + await dragBetweenViewCoords(page, coord1, coord2); + const id = (await getSelectedIds(page))[0]; + await page.keyboard.press('Escape'); + return id; +} + +export async function createShapeElement( + page: Page, + coord1: number[], + coord2: number[], + shape = Shape.Square +) { + const start = await toViewCoord(page, coord1); + const end = await toViewCoord(page, coord2); + const shapeId = await addBasicShapeElement( + page, + { x: start[0], y: start[1] }, + { x: end[0], y: end[1] }, + shape + ); + return shapeId; +} + +export async function createConnectorElement( + page: Page, + coord1: number[], + coord2: number[] +) { + const start = await toViewCoord(page, coord1); + const end = await toViewCoord(page, coord2); + await addBasicConnectorElement( + page, + { x: start[0], y: start[1] }, + { x: end[0], y: end[1] } + ); +} + +export async function createFrameElement( + page: Page, + coord1: number[], + coord2: number[] +) { + const start = await toViewCoord(page, coord1); + const end = await toViewCoord(page, coord2); + await addBasicFrameElement( + page, + { x: start[0], y: start[1] }, + { x: end[0], y: end[1] } + ); +} + +export async function createBrushElement( + page: Page, + coord1: number[], + coord2: number[], + auto = true +) { + const start = await toViewCoord(page, coord1); + const end = await toViewCoord(page, coord2); + await addBasicBrushElement( + page, + { x: start[0], y: start[1] }, + { x: end[0], y: end[1] }, + auto + ); +} + +export async function createEdgelessText( + page: Page, + coord: number[], + text = 'text' +) { + const position = await toViewCoord(page, coord); + await addBasicEdgelessText(page, text, position[0], position[1]); +} + +export async function createMindmap(page: Page, coord: number[]) { + const position = await toViewCoord(page, coord); + await page.keyboard.press('m'); + await page.mouse.click(position[0], position[1]); +} + +export async function createNote( + page: Page, + coord1: number[], + content?: string +) { + const start = await toViewCoord(page, coord1); + return addNote(page, content || 'note', start[0], start[1]); +} + +export async function hoverOnNote(page: Page, id: string, offset = [0, 0]) { + const blockRect = await page.locator(`[data-block-id="${id}"]`).boundingBox(); + + assertExists(blockRect); + + await page.mouse.move( + blockRect.x + blockRect.width / 2 + offset[0], + blockRect.y + blockRect.height / 2 + offset[1] + ); +} + +export function toIdCountMap(ids: string[]) { + return ids.reduce( + (pre, cur) => { + pre[cur] = (pre[cur] ?? 0) + 1; + return pre; + }, + {} as Record + ); +} + +export function getFrameTitle(page: Page, frame: string) { + return page.locator(`affine-frame-title[data-id="${frame}"]`); +} diff --git a/blocksuite/tests-legacy/utils/actions/index.ts b/blocksuite/tests-legacy/utils/actions/index.ts new file mode 100644 index 0000000000000..0f20f0a3b6a31 --- /dev/null +++ b/blocksuite/tests-legacy/utils/actions/index.ts @@ -0,0 +1,7 @@ +export * from './block.js'; +export * from './click.js'; +export * from './drag.js'; +export * from './edgeless.js'; +export * from './keyboard.js'; +export * from './misc.js'; +export * from './selection.js'; diff --git a/blocksuite/tests-legacy/utils/actions/keyboard.ts b/blocksuite/tests-legacy/utils/actions/keyboard.ts new file mode 100644 index 0000000000000..3ea10d879be49 --- /dev/null +++ b/blocksuite/tests-legacy/utils/actions/keyboard.ts @@ -0,0 +1,241 @@ +import type { Page } from '@playwright/test'; + +const IS_MAC = process.platform === 'darwin'; +// const IS_WINDOWS = process.platform === 'win32'; +// const IS_LINUX = !IS_MAC && !IS_WINDOWS; + +/** + * The key will be 'Meta' on Macs and 'Control' on other platforms + * @example + * ```ts + * await page.keyboard.press(`${SHORT_KEY}+a`); + * ``` + */ +export const SHORT_KEY = IS_MAC ? 'Meta' : 'Control'; +/** + * The key will be 'Alt' on Macs and 'Shift' on other platforms + * @example + * ```ts + * await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+1`); + * ``` + */ +export const MODIFIER_KEY = IS_MAC ? 'Alt' : 'Shift'; + +export const SHIFT_KEY = 'Shift'; + +export async function type(page: Page, content: string, delay = 20) { + await page.keyboard.type(content, { delay }); +} + +export async function withPressKey( + page: Page, + key: string, + fn: () => Promise +) { + await page.keyboard.down(key); + await fn(); + await page.keyboard.up(key); +} + +export async function defaultTool(page: Page) { + await page.keyboard.press('v', { delay: 20 }); +} + +export async function pressBackspace(page: Page, count = 1) { + for (let i = 0; i < count; i++) { + await page.keyboard.press('Backspace', { delay: 20 }); + } +} + +export async function pressSpace(page: Page) { + await page.keyboard.press('Space', { delay: 20 }); +} + +export async function pressArrowLeft(page: Page, count = 1) { + for (let i = 0; i < count; i++) { + await page.keyboard.press('ArrowLeft', { delay: 20 }); + } +} +export async function pressArrowRight(page: Page, count = 1) { + for (let i = 0; i < count; i++) { + await page.keyboard.press('ArrowRight', { delay: 20 }); + } +} + +export async function pressArrowDown(page: Page, count = 1) { + for (let i = 0; i < count; i++) { + await page.keyboard.press('ArrowDown', { delay: 20 }); + } +} + +export async function pressArrowUp(page: Page, count = 1) { + for (let i = 0; i < count; i++) { + await page.keyboard.press('ArrowUp', { delay: 20 }); + } +} + +export async function pressArrowDownWithShiftKey(page: Page, count = 1) { + for (let i = 0; i < count; i++) { + await page.keyboard.press(`${SHIFT_KEY}+ArrowDown`, { delay: 20 }); + } +} + +export async function pressArrowUpWithShiftKey(page: Page, count = 1) { + for (let i = 0; i < count; i++) { + await page.keyboard.press(`${SHIFT_KEY}+ArrowUp`, { delay: 20 }); + } +} + +export async function pressEnter(page: Page, count = 1) { + // avoid flaky test by simulate real user input + for (let i = 0; i < count; i++) { + await page.keyboard.press('Enter', { delay: 30 }); + } +} + +export async function pressEnterWithShortkey(page: Page) { + await page.keyboard.press(`${SHORT_KEY}+Enter`, { delay: 20 }); +} + +export async function pressEscape(page: Page, count = 1) { + for (let i = 0; i < count; i++) { + await page.keyboard.press('Escape', { delay: 20 }); + } +} + +export async function undoByKeyboard(page: Page) { + await page.keyboard.press(`${SHORT_KEY}+z`, { delay: 20 }); +} + +export async function formatType(page: Page) { + await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+1`, { + delay: 20, + }); +} + +export async function redoByKeyboard(page: Page) { + await page.keyboard.press(`${SHORT_KEY}+Shift+Z`, { delay: 20 }); +} + +export async function selectAllByKeyboard(page: Page) { + await page.keyboard.press(`${SHORT_KEY}+a`, { + delay: 20, + }); +} + +export async function selectAllBlocksByKeyboard(page: Page) { + for (let i = 0; i < 3; i++) { + await selectAllByKeyboard(page); + } +} + +export async function pressTab(page: Page, count = 1) { + for (let i = 0; i < count; i++) { + await page.keyboard.press('Tab', { delay: 20 }); + } +} + +export async function pressShiftTab(page: Page) { + await page.keyboard.press('Shift+Tab', { delay: 20 }); +} + +export async function pressBackspaceWithShortKey(page: Page, count = 1) { + for (let i = 0; i < count; i++) { + await page.keyboard.press(`${SHORT_KEY}+Backspace`, { delay: 20 }); + } +} + +export async function pressShiftEnter(page: Page) { + await page.keyboard.press('Shift+Enter', { delay: 20 }); +} + +export async function inlineCode(page: Page) { + await page.keyboard.press(`${SHORT_KEY}+e`, { delay: 20 }); +} + +export async function strikethrough(page: Page) { + await page.keyboard.press(`${SHORT_KEY}+Shift+s`, { delay: 20 }); +} + +export async function copyByKeyboard(page: Page) { + await page.keyboard.press(`${SHORT_KEY}+c`, { delay: 20 }); +} + +export async function cutByKeyboard(page: Page) { + await page.keyboard.press(`${SHORT_KEY}+x`, { delay: 20 }); +} + +/** + * Notice: this method will try to click closest editor by default + */ +export async function pasteByKeyboard(page: Page, forceFocus = true) { + if (forceFocus) { + const isEditorActive = await page.evaluate(() => + document.activeElement?.closest('affine-editor-container') + ); + if (!isEditorActive) { + await page.click('affine-editor-container'); + } + } + + await page.keyboard.press(`${SHORT_KEY}+v`, { delay: 20 }); +} + +export async function createCodeBlock(page: Page) { + await page.keyboard.press(`${SHORT_KEY}+Alt+c`); +} + +export async function getCursorBlockIdAndHeight( + page: Page +): Promise<[string | null, number | null]> { + return page.evaluate(() => { + const selection = window.getSelection() as Selection; + + const range = selection.getRangeAt(0); + const startContainer = + range.startContainer instanceof Text + ? (range.startContainer.parentElement as HTMLElement) + : (range.startContainer as HTMLElement); + + const startComponent = startContainer.closest(`[data-block-id]`); + const { height } = (startComponent as HTMLElement).getBoundingClientRect(); + const id = (startComponent as HTMLElement).dataset.blockId!; + return [id, height]; + }); +} + +/** + * fill a line by keep triggering key input + * @param page + * @param toNext if true, fill until soft wrap + */ +export async function fillLine(page: Page, toNext = false) { + const [id, height] = await getCursorBlockIdAndHeight(page); + if (id && height) { + let nextHeight; + // type until current block height is changed, means has new line + do { + await page.keyboard.type('a', { delay: 20 }); + [, nextHeight] = await getCursorBlockIdAndHeight(page); + } while (nextHeight === height); + if (!toNext) { + await page.keyboard.press('Backspace'); + } + } +} + +export async function pressForwardDelete(page: Page) { + if (IS_MAC) { + await page.keyboard.press('Control+d', { delay: 20 }); + } else { + await page.keyboard.press('Delete', { delay: 20 }); + } +} + +export async function pressForwardDeleteWord(page: Page) { + if (IS_MAC) { + await page.keyboard.press('Alt+Delete', { delay: 20 }); + } else { + await page.keyboard.press('Control+Delete', { delay: 20 }); + } +} diff --git a/blocksuite/tests-legacy/utils/actions/linked-doc.ts b/blocksuite/tests-legacy/utils/actions/linked-doc.ts new file mode 100644 index 0000000000000..04ab36c2c9343 --- /dev/null +++ b/blocksuite/tests-legacy/utils/actions/linked-doc.ts @@ -0,0 +1,70 @@ +import { expect, type Page } from '@playwright/test'; + +import { pressEnter, type } from './keyboard.js'; + +export function getLinkedDocPopover(page: Page) { + const REFERENCE_NODE = ' ' as const; + const refNode = page.locator('affine-reference'); + const linkedDocPopover = page.locator('.linked-doc-popover'); + const pageBtn = linkedDocPopover.locator('.group > icon-button'); + + const findRefNode = async (title: string) => { + const refNode = page.locator(`affine-reference`, { + has: page.locator(`.affine-reference-title[data-title="${title}"]`), + }); + await expect(refNode).toBeVisible(); + return refNode; + }; + const assertExistRefText = async (text: string) => { + await expect(refNode).toBeVisible(); + const refTitleNode = refNode.locator('.affine-reference-title'); + // Since the text is in the pseudo element + // we need to use `toHaveAttribute` to assert it. + // And it's not a good strict way to assert the text. + await expect(refTitleNode).toHaveAttribute('data-title', text); + }; + + const createDoc = async ( + pageType: 'LinkedPage' | 'Subpage', + pageName?: string + ) => { + await type(page, '@'); + await expect(linkedDocPopover).toBeVisible(); + if (pageName) { + await type(page, pageName); + } else { + pageName = 'Untitled'; + } + + await page.keyboard.press('ArrowUp'); + if (pageType === 'LinkedPage') { + await page.keyboard.press('ArrowUp'); + } + await pressEnter(page); + return findRefNode(pageName); + }; + + const assertActivePageIdx = async (idx: number) => { + if (idx !== 0) { + await expect(pageBtn.nth(0)).toHaveAttribute('hover', 'false'); + } + await expect(pageBtn.nth(idx)).toHaveAttribute('hover', 'true'); + }; + + return { + REFERENCE_NODE, + linkedDocPopover, + refNode, + pageBtn, + + findRefNode, + assertExistRefText, + createLinkedDoc: async (pageName?: string) => + createDoc('LinkedPage', pageName), + /** + * @deprecated + */ + createSubpage: async (pageName?: string) => createDoc('Subpage', pageName), + assertActivePageIdx, + }; +} diff --git a/blocksuite/tests-legacy/utils/actions/misc.ts b/blocksuite/tests-legacy/utils/actions/misc.ts new file mode 100644 index 0000000000000..b71f6d7f198ca --- /dev/null +++ b/blocksuite/tests-legacy/utils/actions/misc.ts @@ -0,0 +1,1464 @@ +import '../declare-test-window.js'; + +import type { DatabaseBlockModel, ListType, RichText } from '@blocks/index.js'; +import type { EditorHost, ExtensionType } from '@blocksuite/block-std'; +import type { BlockSuiteFlags } from '@blocksuite/global/types'; +import { assertExists } from '@blocksuite/global/utils'; +import type { AffineEditorContainer } from '@blocksuite/presets'; +import type { InlineRange, InlineRootElement } from '@inline/index.js'; +import type { CustomFramePanel } from '@playground/apps/_common/components/custom-frame-panel.js'; +import type { CustomOutlinePanel } from '@playground/apps/_common/components/custom-outline-panel.js'; +import type { CustomOutlineViewer } from '@playground/apps/_common/components/custom-outline-viewer.js'; +import type { DocsPanel } from '@playground/apps/_common/components/docs-panel.js'; +import type { StarterDebugMenu } from '@playground/apps/_common/components/starter-debug-menu.js'; +import type { ConsoleMessage, Locator, Page } from '@playwright/test'; +import { expect } from '@playwright/test'; +import type { BlockModel } from '@store/schema/index.js'; +import { uuidv4 } from '@store/utils/id-generator.js'; +import lz from 'lz-string'; + +import { currentEditorIndex, multiEditor } from '../multiple-editor.js'; +import { + pressArrowRight, + pressEnter, + pressEscape, + pressSpace, + pressTab, + selectAllBlocksByKeyboard, + SHORT_KEY, + type, +} from './keyboard.js'; + +declare global { + interface WindowEventMap { + 'blocksuite:doc-ready': CustomEvent; + } +} + +export const defaultPlaygroundURL = new URL( + `http://localhost:${process.env.CI ? 4173 : 5173}/starter/` +); + +const NEXT_FRAME_TIMEOUT = 50; +const DEFAULT_PLAYGROUND = defaultPlaygroundURL.toString(); +const RICH_TEXT_SELECTOR = '.inline-editor'; + +function generateRandomRoomId() { + return `playwright-${uuidv4()}`; +} + +export const getSelectionRect = async (page: Page): Promise => { + const rect = await page.evaluate(() => { + return getSelection()?.getRangeAt(0).getBoundingClientRect(); + }); + assertExists(rect); + return rect; +}; + +/** + * @example + * ```ts + * await initEmptyEditor(page, { enable_some_flag: true }); + * ``` + */ +async function initEmptyEditor({ + page, + flags = {}, + noInit = false, + multiEditor = false, +}: { + page: Page; + flags?: Partial; + noInit?: boolean; + multiEditor?: boolean; +}) { + await page.evaluate( + ([flags, noInit, multiEditor]) => { + const { collection } = window; + + async function waitForMountPageEditor( + doc: ReturnType + ) { + if (!doc.ready) doc.load(); + + if (!doc.root) { + 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 + const appRoot = document.querySelector('#app'); + if (!appRoot) { + throw new Error('Cannot find app root element(#app).'); + } + const createEditor = () => { + const editor = document.createElement('affine-editor-container'); + editor.doc = doc; + editor.autofocus = true; + const defaultExtensions: ExtensionType[] = [ + ...window.$blocksuite.defaultExtensions(), + { + setup: di => { + di.addImpl(window.$blocksuite.identifiers.ParseDocUrlService, { + parseDocUrl() { + return undefined; + }, + }); + }, + }, + { + setup: di => { + di.override( + window.$blocksuite.identifiers.DocModeProvider, + window.$blocksuite.mockServices.mockDocModeService( + () => editor.mode, + mode => editor.switchEditor(mode) + ) + ); + }, + }, + ]; + editor.pageSpecs = [...editor.pageSpecs, ...defaultExtensions]; + editor.edgelessSpecs = [ + ...editor.edgelessSpecs, + ...defaultExtensions, + ]; + + editor.std + .get(window.$blocksuite.identifiers.RefNodeSlotsProvider) + .docLinkClicked.on(({ pageId: docId }) => { + const newDoc = collection.getDoc(docId); + if (!newDoc) { + throw new Error(`Failed to jump to page ${docId}`); + } + editor.doc = newDoc; + }); + appRoot.append(editor); + return editor; + }; + + const editor = createEditor(); + if (multiEditor) createEditor(); + + editor.updateComplete + .then(() => { + const debugMenu: StarterDebugMenu = + document.createElement('starter-debug-menu'); + const docsPanel: DocsPanel = document.createElement('docs-panel'); + const framePanel: CustomFramePanel = + document.createElement('custom-frame-panel'); + const outlinePanel: CustomOutlinePanel = document.createElement( + 'custom-outline-panel' + ); + const outlineViewer: CustomOutlineViewer = document.createElement( + 'custom-outline-viewer' + ); + docsPanel.editor = editor; + framePanel.editor = editor; + outlinePanel.editor = editor; + outlineViewer.editor = editor; + debugMenu.collection = collection; + debugMenu.editor = editor; + debugMenu.docsPanel = docsPanel; + debugMenu.framePanel = framePanel; + debugMenu.outlineViewer = outlineViewer; + debugMenu.outlinePanel = outlinePanel; + const leftSidePanel = document.createElement('left-side-panel'); + debugMenu.leftSidePanel = leftSidePanel; + document.body.append(debugMenu); + document.body.append(leftSidePanel); + document.body.append(framePanel); + document.body.append(outlinePanel); + document.body.append(outlineViewer); + + window.debugMenu = debugMenu; + window.editor = editor; + window.doc = doc; + Object.defineProperty(globalThis, 'host', { + get() { + return document.querySelector('editor-host'); + }, + }); + Object.defineProperty(globalThis, 'std', { + get() { + return document.querySelector('editor-host')?.std; + }, + }); + window.dispatchEvent( + new CustomEvent('blocksuite:doc-ready', { detail: doc.id }) + ); + }) + .catch(console.error); + } + + if (noInit) { + const firstDoc = collection.docs.values().next().value?.getDoc() as + | ReturnType + | undefined; + if (firstDoc) { + window.doc = firstDoc; + waitForMountPageEditor(firstDoc).catch; + } else { + collection.slots.docAdded.on(docId => { + const doc = collection.getDoc(docId); + if (!doc) { + throw new Error(`Failed to get doc ${docId}`); + } + window.doc = doc; + waitForMountPageEditor(doc).catch(console.error); + }); + } + } else { + collection.meta.initialize(); + const doc = collection.createDoc({ id: 'doc:home' }); + window.doc = doc; + waitForMountPageEditor(doc).catch(console.error); + } + }, + [flags, noInit, multiEditor] as const + ); + await waitNextFrame(page); +} + +export const getEditorLocator = (page: Page) => { + return page.locator('affine-editor-container').nth(currentEditorIndex); +}; + +export const getEditorHostLocator = (page: Page) => { + return page.locator('editor-host').nth(currentEditorIndex); +}; + +type TaggedConsoleMessage = ConsoleMessage & { __ignore?: boolean }; +function ignoredLog(message: ConsoleMessage) { + (message as TaggedConsoleMessage).__ignore = true; +} +function isIgnoredLog( + message: ConsoleMessage +): message is TaggedConsoleMessage { + return '__ignore' in message && !!message.__ignore; +} + +/** + * Expect console message to be called in the test. + * + * This function **should** be called before the `enterPlaygroundRoom` function! + * + * ```ts + * expectConsoleMessage(page, 'Failed to load resource'); // Default type is 'error' + * expectConsoleMessage(page, '[vite] connected.', 'warning'); // Specify type + * ``` + */ +export function expectConsoleMessage( + page: Page, + logPrefixOrRegex: string | RegExp, + type: + | 'log' + | 'debug' + | 'info' + | 'error' + | 'warning' + | 'dir' + | 'dirxml' + | 'table' + | 'trace' + | 'clear' + | 'startGroup' + | 'startGroupCollapsed' + | 'endGroup' + | 'assert' + | 'profile' + | 'profileEnd' + | 'count' + | 'timeEnd' = 'error' +) { + page.on('console', (message: ConsoleMessage) => { + const sameType = message.type() === type; + const textMatch = + logPrefixOrRegex instanceof RegExp + ? logPrefixOrRegex.test(message.text()) + : message.text().startsWith(logPrefixOrRegex); + if (sameType && textMatch) { + ignoredLog(message); + } + }); +} + +export type PlaygroundRoomOptions = { + flags?: Partial; + room?: string; + blobSource?: ('idb' | 'mock')[]; + noInit?: boolean; +}; +export async function enterPlaygroundRoom( + page: Page, + ops?: PlaygroundRoomOptions +) { + const url = new URL(DEFAULT_PLAYGROUND); + let room = ops?.room; + const blobSource = ops?.blobSource; + if (!room) { + room = generateRandomRoomId(); + } + url.searchParams.set('room', room); + url.searchParams.set('blobSource', blobSource?.join(',') || 'idb'); + await page.goto(url.toString()); + + // See https://github.com/microsoft/playwright/issues/5546 + page.on('console', message => { + if ( + [ + '', + // React devtools: + '%cDownload the React DevTools for a better development experience: https://reactjs.org/link/react-devtools font-weight:bold', + // Vite: + '[vite] connected.', + '[vite] connecting...', + // Figma embed: + 'Fullscreen: Using 4GB WASM heap', + // Lit: + 'Lit is in dev mode. Not recommended for production! See https://lit.dev/msg/dev-mode for more information.', + // Figma embed: + 'Running frontend commit', + ].includes(message.text()) + ) { + return; + } + const ignore = isIgnoredLog(message) || !process.env.CI; + if (!ignore) { + expect + .soft('Unexpected console message: ' + message.text()) + .toBe( + 'Please remove the "console.log" or declare `expectConsoleMessage` before `enterPlaygroundRoom`. It is advised not to output logs in a production environment.' + ); + } + console.log(`Console ${message.type()}: ${message.text()}`); + }); + + // Log all uncaught errors + page.on('pageerror', exception => { + throw new Error(`Uncaught exception: "${exception}"\n${exception.stack}`); + }); + + await initEmptyEditor({ + page, + flags: ops?.flags, + noInit: ops?.noInit, + multiEditor, + }); + + const locator = page.locator('affine-editor-container'); + await locator.isVisible(); + await page.evaluate(async () => { + const dom = document.querySelector( + 'affine-editor-container' + ); + if (dom) { + await dom.updateComplete; + } + }); + + await page.evaluate(() => { + if (typeof window.$blocksuite !== 'object') { + throw new Error('window.$blocksuite is not object'); + } + }, []); + return room; +} + +export async function waitDefaultPageLoaded(page: Page) { + await page.waitForSelector('affine-page-root[data-block-id="0"]'); +} + +export async function waitEmbedLoaded(page: Page) { + await page.waitForSelector('.resizable-img'); +} + +export async function waitNextFrame( + page: Page, + frameTimeout = NEXT_FRAME_TIMEOUT +) { + await page.waitForTimeout(frameTimeout); +} + +export async function clearLog(page: Page) { + await page.evaluate(() => console.clear()); +} + +export async function captureHistory(page: Page) { + await page.evaluate(() => { + window.doc.captureSync(); + }); +} + +export async function resetHistory(page: Page) { + await page.evaluate(() => { + const space = window.doc; + space.resetHistory(); + }); +} + +// XXX: This doesn't add surface yet, the page state should not be switched to edgeless. +export async function enterPlaygroundWithList( + page: Page, + contents: string[] = ['', '', ''], + type: ListType = 'bulleted' +) { + const room = generateRandomRoomId(); + await page.goto(`${DEFAULT_PLAYGROUND}?room=${room}`); + await initEmptyEditor({ page }); + + await page.evaluate( + ({ contents, type }: { contents: string[]; type: ListType }) => { + const { doc } = window; + const rootId = doc.addBlock('affine:page', { + title: new doc.Text(), + }); + const noteId = doc.addBlock('affine:note', {}, rootId); + // eslint-disable-next-line @typescript-eslint/prefer-for-of + for (let i = 0; i < contents.length; i++) { + doc.addBlock( + 'affine:list', + contents.length > 0 + ? { text: new doc.Text(contents[i]), type } + : { type }, + noteId + ); + } + }, + { contents, type } + ); + await waitNextFrame(page); +} + +// XXX: This doesn't add surface yet, the doc state should not be switched to edgeless. +export async function initEmptyParagraphState(page: Page, rootId?: string) { + const ids = await page.evaluate(rootId => { + const { doc } = window; + doc.captureSync(); + if (!rootId) { + rootId = doc.addBlock('affine:page', { + title: new doc.Text(), + }); + } + + const noteId = doc.addBlock('affine:note', {}, rootId); + const paragraphId = doc.addBlock('affine:paragraph', {}, noteId); + // doc.addBlock('affine:surface', {}, rootId); + doc.captureSync(); + + return { rootId, noteId, paragraphId }; + }, rootId); + return ids; +} + +export async function initMultipleNoteWithParagraphState( + page: Page, + rootId?: string, + count = 2 +) { + const ids = await page.evaluate( + ({ rootId, count }) => { + const { doc } = window; + doc.captureSync(); + if (!rootId) { + rootId = doc.addBlock('affine:page', { + title: new doc.Text(), + }); + } + + const ids = Array.from({ length: count }) + .fill(0) + .map(() => { + const noteId = doc.addBlock('affine:note', {}, rootId); + const paragraphId = doc.addBlock('affine:paragraph', {}, noteId); + return { noteId, paragraphId }; + }); + + // doc.addBlock('affine:surface', {}, rootId); + doc.captureSync(); + + return { rootId, ids }; + }, + { rootId, count } + ); + return ids; +} + +export async function initEmptyEdgelessState(page: Page) { + const ids = await page.evaluate(() => { + const { doc } = window; + const rootId = doc.addBlock('affine:page', { + title: new doc.Text(), + }); + doc.addBlock('affine:surface', {}, rootId); + const noteId = doc.addBlock('affine:note', {}, rootId); + const paragraphId = doc.addBlock('affine:paragraph', {}, noteId); + + doc.resetHistory(); + + return { rootId, noteId, paragraphId }; + }); + return ids; +} + +export async function initEmptyDatabaseState(page: Page, rootId?: string) { + const ids = await page.evaluate(async rootId => { + const { doc } = window; + doc.captureSync(); + if (!rootId) { + rootId = doc.addBlock('affine:page', { + title: new doc.Text(), + }); + } + const noteId = doc.addBlock('affine:note', {}, rootId); + const databaseId = doc.addBlock( + 'affine:database', + { + title: new doc.Text('Database 1'), + }, + noteId + ); + const model = doc.getBlockById(databaseId) as DatabaseBlockModel; + await new Promise(resolve => setTimeout(resolve, 100)); + const databaseBlock = document.querySelector('affine-database'); + const databaseService = databaseBlock?.service; + if (databaseService) { + databaseService.databaseViewInitEmpty( + model, + databaseService.viewPresets.tableViewMeta.type + ); + databaseService.applyColumnUpdate(model); + } + + doc.captureSync(); + return { rootId, noteId, databaseId }; + }, rootId); + return ids; +} + +export async function initKanbanViewState( + page: Page, + config: { + rows: string[]; + columns: { type: string; value?: unknown[] }[]; + }, + rootId?: string +) { + const ids = await page.evaluate( + async ({ rootId, config }) => { + const { doc } = window; + + doc.captureSync(); + if (!rootId) { + rootId = doc.addBlock('affine:page', { + title: new doc.Text(), + }); + } + const noteId = doc.addBlock('affine:note', {}, rootId); + const databaseId = doc.addBlock( + 'affine:database', + { + title: new doc.Text('Database 1'), + }, + noteId + ); + const model = doc.getBlockById(databaseId) as DatabaseBlockModel; + await new Promise(resolve => setTimeout(resolve, 100)); + const databaseBlock = document.querySelector('affine-database'); + const databaseService = databaseBlock?.service; + if (databaseService) { + const rowIds = config.rows.map(rowText => { + const rowId = doc.addBlock( + 'affine:paragraph', + { type: 'text', text: new doc.Text(rowText) }, + databaseId + ); + return rowId; + }); + config.columns.forEach(column => { + const columnId = databaseService.addColumn(model, 'end', { + data: {}, + name: column.type, + type: column.type, + }); + rowIds.forEach((rowId, index) => { + const value = column.value?.[index]; + if (value !== undefined) { + databaseService.updateCell(model, rowId, { + columnId, + value: + column.type === 'rich-text' + ? new doc.Text(value as string) + : value, + }); + } + }); + }); + databaseService.databaseViewInitEmpty( + model, + databaseService.viewPresets.kanbanViewMeta.type + ); + databaseService.applyColumnUpdate(model); + } + doc.captureSync(); + return { rootId, noteId, databaseId }; + }, + { rootId, config } + ); + return ids; +} + +export async function initEmptyDatabaseWithParagraphState( + page: Page, + rootId?: string +) { + const ids = await page.evaluate(async rootId => { + const { doc } = window; + doc.captureSync(); + if (!rootId) { + rootId = doc.addBlock('affine:page', { + title: new doc.Text(), + }); + } + const noteId = doc.addBlock('affine:note', {}, rootId); + const databaseId = doc.addBlock( + 'affine:database', + { + title: new doc.Text('Database 1'), + }, + noteId + ); + const model = doc.getBlockById(databaseId) as DatabaseBlockModel; + await new Promise(resolve => setTimeout(resolve, 100)); + const databaseBlock = document.querySelector('affine-database'); + const databaseService = databaseBlock?.service; + if (databaseService) { + databaseService.databaseViewInitEmpty( + model, + databaseService.viewPresets.tableViewMeta.type + ); + databaseService.applyColumnUpdate(model); + } + doc.addBlock('affine:paragraph', {}, noteId); + + doc.captureSync(); + return { rootId, noteId, databaseId }; + }, rootId); + return ids; +} + +export async function initDatabaseRow(page: Page) { + const editorHost = getEditorHostLocator(page); + const addRow = editorHost.locator('.data-view-table-group-add-row'); + await addRow.click(); +} + +export async function initDatabaseRowWithData(page: Page, data: string) { + await initDatabaseRow(page); + await waitNextFrame(page, 50); + await type(page, data); +} +export const getAddRow = (page: Page): Locator => { + return page.locator('.data-view-table-group-add-row'); +}; +export async function initDatabaseDynamicRowWithData( + page: Page, + data: string, + addRow = false, + index = 0 +) { + const editorHost = getEditorHostLocator(page); + if (addRow) { + await initDatabaseRow(page); + await waitNextFrame(page, 100); + await pressEscape(page); + } + const lastRow = editorHost.locator('.affine-database-block-row').last(); + const cell = lastRow.locator('.database-cell').nth(index + 1); + await cell.click(); + await waitNextFrame(page); + await pressEnter(page); + await waitNextFrame(page); + await type(page, data); + await pressEnter(page); +} + +export async function focusDatabaseTitle(page: Page) { + const dbTitle = page.locator('[data-block-is-database-title="true"]'); + await dbTitle.click(); + + await page.evaluate(() => { + const dbTitle = document.querySelector( + 'affine-database-title textarea' + ) as HTMLTextAreaElement | null; + if (!dbTitle) { + throw new Error('Cannot find database title'); + } + + dbTitle.focus(); + }); + await selectAllBlocksByKeyboard(page); + await pressArrowRight(page); + await waitNextFrame(page); +} + +export async function assertDatabaseColumnOrder(page: Page, order: string[]) { + const columns = await page + .locator('affine-database-column-header') + .locator('affine-database-header-column') + .all(); + expect(await Promise.all(columns.slice(1).map(v => v.innerText()))).toEqual( + order + ); +} + +export async function initEmptyCodeBlockState( + page: Page, + codeBlockProps = {} as { language?: string } +) { + const ids = await page.evaluate(codeBlockProps => { + const { doc } = window; + doc.captureSync(); + const rootId = doc.addBlock('affine:page'); + const noteId = doc.addBlock('affine:note', {}, rootId); + const codeBlockId = doc.addBlock('affine:code', codeBlockProps, noteId); + doc.captureSync(); + + return { rootId, noteId, codeBlockId }; + }, codeBlockProps); + await page.waitForSelector(`[data-block-id="${ids.codeBlockId}"] rich-text`); + return ids; +} + +type FocusRichTextOptions = { + clickPosition?: { x: number; y: number }; +}; + +export async function focusRichText( + page: Page, + i = 0, + options?: FocusRichTextOptions +) { + await page.mouse.move(0, 0); + const editor = getEditorHostLocator(page); + const locator = editor.locator(RICH_TEXT_SELECTOR).nth(i); + // need to set `force` to true when clicking on `affine-selected-blocks` + await locator.click({ force: true, position: options?.clickPosition }); +} + +export async function focusRichTextEnd(page: Page, i = 0) { + await page.evaluate( + ([i, currentEditorIndex]) => { + const editorHost = + document.querySelectorAll('editor-host')[currentEditorIndex]; + const richTexts = Array.from(editorHost.querySelectorAll('rich-text')); + + richTexts[i].inlineEditor?.focusEnd(); + }, + [i, currentEditorIndex] + ); + await waitNextFrame(page); +} + +export async function initThreeParagraphs(page: Page) { + await focusRichText(page); + await type(page, '123'); + await pressEnter(page); + await type(page, '456'); + await pressEnter(page); + await type(page, '789'); + await resetHistory(page); +} + +export async function initSixParagraphs(page: Page) { + await focusRichText(page); + await type(page, '1'); + await pressEnter(page); + await type(page, '2'); + await pressEnter(page); + await type(page, '3'); + await pressEnter(page); + await type(page, '4'); + await pressEnter(page); + await type(page, '5'); + await pressEnter(page); + await type(page, '6'); + await resetHistory(page); +} + +export async function initThreeLists(page: Page) { + await focusRichText(page); + await type(page, '-'); + await pressSpace(page); + await type(page, '123'); + await pressEnter(page); + await type(page, '456'); + await pressEnter(page); + await pressTab(page); + await type(page, '789'); +} + +export async function insertThreeLevelLists(page: Page, i = 0) { + await focusRichText(page, i); + await type(page, '-'); + await pressSpace(page); + await type(page, '123'); + await pressEnter(page); + await pressTab(page); + await type(page, '456'); + await pressEnter(page); + await pressTab(page); + await type(page, '789'); +} + +export async function initThreeDividers(page: Page) { + await focusRichText(page); + await type(page, '123'); + await pressEnter(page); + await type(page, '---'); + await pressSpace(page); + await type(page, '---'); + await pressSpace(page); + await type(page, '---'); + await pressSpace(page); + await type(page, '123'); +} + +export async function initParagraphsByCount(page: Page, count: number) { + await focusRichText(page); + for (let i = 0; i < count; i++) { + await type(page, `paragraph ${i}`); + await pressEnter(page); + } + await resetHistory(page); +} + +export async function getInlineSelectionIndex(page: Page) { + return page.evaluate(() => { + const selection = window.getSelection() as Selection; + + const range = selection.getRangeAt(0); + const component = range.startContainer.parentElement?.closest('rich-text'); + const index = component?.inlineEditor?.getInlineRange()?.index; + return index !== undefined ? index : -1; + }); +} + +export async function getInlineSelectionText(page: Page) { + return page.evaluate(() => { + const selection = window.getSelection() as Selection; + const range = selection.getRangeAt(0); + const component = range.startContainer.parentElement?.closest('rich-text'); + return component?.inlineEditor?.yText.toString() ?? ''; + }); +} + +export async function getSelectedTextByInlineEditor(page: Page) { + return page.evaluate(() => { + const selection = window.getSelection() as Selection; + const range = selection.getRangeAt(0); + const component = range.startContainer.parentElement?.closest('rich-text'); + + const inlineRange = component?.inlineEditor?.getInlineRange(); + if (!inlineRange) return ''; + + const { index, length } = inlineRange; + return ( + component?.inlineEditor?.yText.toString().slice(index, index + length) || + '' + ); + }); +} + +export async function getSelectedText(page: Page) { + return page.evaluate(() => { + let content = ''; + const selection = window.getSelection() as Selection; + + if (selection.rangeCount === 0) return content; + + const range = selection.getRangeAt(0); + const components = + range.commonAncestorContainer.parentElement?.querySelectorAll( + 'rich-text' + ) || []; + + components.forEach(component => { + const inlineRange = component.inlineEditor?.getInlineRange(); + if (!inlineRange) return; + const { index, length } = inlineRange; + content += + component?.inlineEditor?.yText + .toString() + .slice(index, index + length) || ''; + }); + + return content; + }); +} + +export async function setInlineRangeInSelectedRichText( + page: Page, + index: number, + length: number +) { + await page.evaluate( + ({ index, length }) => { + const selection = window.getSelection() as Selection; + + const range = selection.getRangeAt(0); + const component = + range.startContainer.parentElement?.closest('rich-text'); + component?.inlineEditor?.setInlineRange({ + index, + length, + }); + }, + { index, length } + ); + await waitNextFrame(page); +} + +export async function setInlineRangeInInlineEditor( + page: Page, + inlineRange: InlineRange, + i = 0 +) { + await page.evaluate( + ({ i, inlineRange }) => { + const inlineEditor = document.querySelectorAll( + '[data-v-root="true"]' + )[i]?.inlineEditor; + if (!inlineEditor) { + throw new Error('Cannot find inline editor'); + } + inlineEditor.setInlineRange(inlineRange); + }, + { i, inlineRange } + ); + await waitNextFrame(page); +} + +export async function pasteContent( + page: Page, + clipData: Record +) { + await page.evaluate( + ({ clipData }) => { + const e = new ClipboardEvent('paste', { + clipboardData: new DataTransfer(), + }); + Object.defineProperty(e, 'target', { + writable: false, + value: document, + }); + Object.keys(clipData).forEach(key => { + e.clipboardData?.setData(key, clipData[key] as string); + }); + document.dispatchEvent(e); + }, + { clipData } + ); + await waitNextFrame(page); +} + +export async function pasteTestImage(page: Page) { + await page.evaluate(async () => { + const imageBlob = await fetch(`${location.origin}/test-card-1.png`).then( + response => response.blob() + ); + + const imageFile = new File([imageBlob], 'test-card-1.png', { + type: 'image/png', + }); + + const e = new ClipboardEvent('paste', { + clipboardData: new DataTransfer(), + }); + + Object.defineProperty(e, 'target', { + writable: false, + value: document, + }); + + e.clipboardData?.items.add(imageFile); + document.dispatchEvent(e); + }); + await waitNextFrame(page); +} + +export async function getClipboardHTML(page: Page) { + const dataInClipboard = await page.evaluate(async () => { + function format(node: HTMLElement, level: number) { + const indentBefore = ' '.repeat(level++); + const indentAfter = ' '.repeat(level >= 2 ? level - 2 : 0); + let textNode; + + // eslint-disable-next-line @typescript-eslint/prefer-for-of + for (let i = 0; i < node.children.length; i++) { + textNode = document.createTextNode('\n' + indentBefore); + node.insertBefore(textNode, node.children[i]); + + format(node.children[i] as HTMLElement, level); + + if (node.lastElementChild == node.children[i]) { + textNode = document.createTextNode('\n' + indentAfter); + node.append(textNode); + } + } + + return node; + } + const clipItems = await navigator.clipboard.read(); + const item = clipItems.find(item => item.types.includes('text/html')); + const data = await item?.getType('text/html'); + const text = await data?.text(); + const html = new DOMParser().parseFromString(text ?? '', 'text/html'); + const container = html.querySelector( + '[data-blocksuite-snapshot]' + ); + if (!container) { + return ''; + } + return format(container, 0).innerHTML.trim(); + }); + + return dataInClipboard; +} + +export async function getClipboardText(page: Page) { + const dataInClipboard = await page.evaluate(async () => { + const clipItems = await navigator.clipboard.read(); + const item = clipItems.find(item => item.types.includes('text/plain')); + const data = await item?.getType('text/plain'); + const text = await data?.text(); + return text ?? ''; + }); + return dataInClipboard; +} + +export async function getClipboardCustomData(page: Page, type: string) { + const dataInClipboard = await page.evaluate(async () => { + const clipItems = await navigator.clipboard.read(); + const item = clipItems.find(item => item.types.includes('text/html')); + const data = await item?.getType('text/html'); + const text = await data?.text(); + const html = new DOMParser().parseFromString(text ?? '', 'text/html'); + const container = html.querySelector( + '[data-blocksuite-snapshot]' + ); + return container?.dataset.blocksuiteSnapshot ?? ''; + }); + + const decompressed = lz.decompressFromEncodedURIComponent(dataInClipboard); + let json: Record | null = null; + try { + json = JSON.parse(decompressed); + } catch { + throw new Error(`Invalid snapshot in clipboard: ${dataInClipboard}`); + } + + return json?.[type]; +} + +export async function getClipboardSnapshot(page: Page) { + const dataInClipboard = await getClipboardCustomData( + page, + 'BLOCKSUITE/SNAPSHOT' + ); + assertExists(dataInClipboard); + const json = JSON.parse(dataInClipboard as string); + return json; +} + +export async function getPageSnapshot(page: Page, toJSON?: boolean) { + const json = await page.evaluate(() => { + const { job, doc } = window; + const snapshot = job.docToSnapshot(doc); + if (!snapshot) { + throw new Error('Failed to get snapshot'); + } + return snapshot.blocks; + }); + if (toJSON) { + return JSON.stringify(json, null, 2); + } + return json; +} + +export async function setSelection( + page: Page, + anchorBlockId: number, + anchorOffset: number, + focusBlockId: number, + focusOffset: number +) { + await page.evaluate( + ({ + anchorBlockId, + anchorOffset, + focusBlockId, + focusOffset, + currentEditorIndex, + }) => { + const editorHost = + document.querySelectorAll('editor-host')[currentEditorIndex]; + const anchorRichText = editorHost.querySelector( + `[data-block-id="${anchorBlockId}"] rich-text` + )!; + const anchorRichTextRange = anchorRichText.inlineEditor!.toDomRange({ + index: anchorOffset, + length: 0, + })!; + const focusRichText = editorHost.querySelector( + `[data-block-id="${focusBlockId}"] rich-text` + )!; + const focusRichTextRange = focusRichText.inlineEditor!.toDomRange({ + index: focusOffset, + length: 0, + })!; + + const sl = getSelection(); + if (!sl) throw new Error('Cannot get selection'); + const range = document.createRange(); + range.setStart( + anchorRichTextRange.startContainer, + anchorRichTextRange.startOffset + ); + range.setEnd( + focusRichTextRange.startContainer, + focusRichTextRange.startOffset + ); + sl.removeAllRanges(); + sl.addRange(range); + }, + { + anchorBlockId, + anchorOffset, + focusBlockId, + focusOffset, + currentEditorIndex, + } + ); +} + +export async function readClipboardText( + page: Page, + type: 'input' | 'textarea' = 'input' +) { + const id = 'clipboard-test'; + const selector = `#${id}`; + await page.evaluate( + ({ type, id }) => { + const input = document.createElement(type); + input.setAttribute('id', id); + document.body.append(input); + }, + { type, id } + ); + const input = page.locator(selector); + await input.focus(); + await page.keyboard.press(`${SHORT_KEY}+v`); + const text = await input.inputValue(); + await page.evaluate( + ({ selector }) => { + const input = document.querySelector(selector); + input?.remove(); + }, + { selector } + ); + return text; +} + +export const getCenterPositionByLocator: ( + page: Page, + locator: Locator +) => Promise<{ x: number; y: number }> = async ( + _page: Page, + locator: Locator +) => { + const box = await locator.boundingBox(); + if (!box) { + throw new Error("Failed to getCenterPosition! Can't get bounding box"); + } + return { + x: box.x + box.width / 2, + y: box.y + box.height / 2, + }; +}; + +/** + * @deprecated Use `page.locator(selector).boundingBox()` instead + */ +export const getBoundingClientRect: ( + page: Page, + selector: string +) => Promise = async (page: Page, selector: string) => { + return page.evaluate((selector: string) => { + return document.querySelector(selector)?.getBoundingClientRect() as DOMRect; + }, selector); +}; + +export async function getBoundingBox(locator: Locator) { + const box = await locator.boundingBox(); + if (!box) throw new Error('Missing column box'); + return box; +} + +export async function getBlockModel( + page: Page, + blockId: string +) { + const result: BlockModel | null | undefined = await page.evaluate(blockId => { + return window.doc?.getBlock(blockId)?.model; + }, blockId); + expect(result).not.toBeNull(); + return result as Model; +} + +export async function getIndexCoordinate( + page: Page, + [richTextIndex, vIndex]: [number, number], + coordOffSet: { x: number; y: number } = { x: 0, y: 0 } +) { + const coord = await page.evaluate( + ({ richTextIndex, vIndex, coordOffSet, currentEditorIndex }) => { + const editorHost = + document.querySelectorAll('editor-host')[currentEditorIndex]; + const richText = editorHost.querySelectorAll('rich-text')[ + richTextIndex + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ] as any; + const domRange = richText.inlineEditor.toDomRange({ + index: vIndex, + length: 0, + }); + const pointBound = domRange.getBoundingClientRect(); + return { + x: pointBound.left + coordOffSet.x, + y: pointBound.top + pointBound.height / 2 + coordOffSet.y, + }; + }, + { + richTextIndex, + vIndex, + coordOffSet, + currentEditorIndex, + } + ); + return coord; +} + +export function inlineEditorInnerTextToString(innerText: string): string { + return innerText.replace('\u200B', '').trim(); +} + +export async function focusTitle(page: Page) { + await page.locator('doc-title rich-text').click(); + await page.evaluate(i => { + const docTitle = document.querySelectorAll('doc-title')[i]; + if (!docTitle) { + throw new Error('Doc title component not found'); + } + const docTitleRichText = docTitle.querySelector('rich-text'); + if (!docTitleRichText) { + throw new Error('Doc title rich text component not found'); + } + if (!docTitleRichText.inlineEditor) { + throw new Error('Doc title inline editor not found'); + } + docTitleRichText.inlineEditor.focusEnd(); + }, currentEditorIndex); + await waitNextFrame(page, 200); +} + +/** + * XXX: this is a workaround for the bug in Playwright + */ +export async function shamefullyBlurActiveElement(page: Page) { + await page.evaluate(() => { + if ( + !document.activeElement || + !(document.activeElement instanceof HTMLElement) + ) { + throw new Error("document.activeElement doesn't exist"); + } + document.activeElement.blur(); + }); +} + +/** + * FIXME: + * Sometimes inline editor state is not updated in time. Bad case like below: + * + * ``` + * await focusRichText(page); + * await type(page, 'hello'); + * await assertRichTexts(page, ['hello']); + * ``` + * + * output(failed or flaky): + * + * ``` + * - Expected - 1 + * + Received + 1 + * Array [ + * - "hello", + * + "ello", + * ] + * ``` + * + */ +export async function waitForInlineEditorStateUpdated(page: Page) { + return page.evaluate(async () => { + const selection = window.getSelection() as Selection; + + if (selection.rangeCount === 0) return; + + const range = selection.getRangeAt(0); + const component = range.startContainer.parentElement?.closest('rich-text'); + await component?.inlineEditor?.waitForUpdate(); + }); +} + +export async function initImageState(page: Page, prependParagraph = false) { + await page.evaluate(async prepend => { + const { doc } = window; + const rootId = doc.addBlock('affine:page', { + title: new doc.Text(), + }); + const noteId = doc.addBlock('affine:note', {}, rootId); + + await new Promise(res => setTimeout(res, 200)); + + const pageRoot = document.querySelector('affine-page-root'); + if (!pageRoot) throw new Error('Cannot find doc page'); + const imageBlob = await fetch(`${location.origin}/test-card-1.png`).then( + response => response.blob() + ); + const storage = pageRoot.doc.blobSync; + const sourceId = await storage.set(imageBlob); + if (prepend) { + doc.addBlock('affine:paragraph', {}, noteId); + } + const imageId = doc.addBlock( + 'affine:image', + { + sourceId, + }, + noteId + ); + + doc.resetHistory(); + + return { rootId, noteId, imageId }; + }, prependParagraph); + + // due to pasting img calls fetch, so we need timeout for downloading finished. + await page.waitForTimeout(500); +} + +export async function getCurrentEditorDocId(page: Page) { + return page.evaluate(index => { + const editor = document.querySelectorAll('affine-editor-container')[index]; + if (!editor) throw new Error("Can't find affine-editor-container"); + const docId = editor.doc.id; + return docId; + }, currentEditorIndex); +} + +export async function getCurrentHTMLTheme(page: Page) { + const root = page.locator('html'); + // eslint-disable-next-line unicorn/prefer-dom-node-dataset + return root.getAttribute('data-theme'); +} + +export async function getCurrentEditorTheme(page: Page) { + const mode = await page + .locator('affine-editor-container') + .first() + .evaluate(() => + window + .getComputedStyle(document.documentElement) + .getPropertyValue('--affine-theme-mode') + .trim() + ); + return mode; +} + +export async function getCurrentThemeCSSPropertyValue( + page: Page, + property: string +) { + const value = await page + .locator('affine-editor-container') + .evaluate( + (_, property) => + window + .getComputedStyle(document.documentElement) + .getPropertyValue(property) + .trim(), + property + ); + return value; +} + +export async function scrollToTop(page: Page) { + await page.mouse.wheel(0, -1000); + + await page.waitForFunction(() => { + const scrollContainer = document.querySelector('.affine-page-viewport'); + if (!scrollContainer) { + throw new Error("Can't find scroll container"); + } + return scrollContainer.scrollTop < 10; + }); +} + +export async function scrollToBottom(page: Page) { + // await page.mouse.wheel(0, 1000); + + await page + .locator('.affine-page-viewport') + .evaluate(node => + node.scrollTo({ left: 0, top: 1000, behavior: 'smooth' }) + ); + // TODO switch to `scrollend` + // See https://developer.chrome.com/en/blog/scrollend-a-new-javascript-event/ + await page.waitForFunction(() => { + const scrollContainer = document.querySelector('.affine-page-viewport'); + if (!scrollContainer) { + throw new Error("Can't find scroll container"); + } + + return ( + // Wait for scrolled to the bottom + // Refer to https://stackoverflow.com/questions/3898130/check-if-a-user-has-scrolled-to-the-bottom-not-just-the-window-but-any-element + Math.abs( + scrollContainer.scrollHeight - + scrollContainer.scrollTop - + scrollContainer.clientHeight + ) < 10 + ); + }); +} + +export async function mockParseDocUrlService( + page: Page, + mapping: Record +) { + await page.evaluate(mapping => { + const parseDocUrlService = window.host.std.get( + window.$blocksuite.identifiers.ParseDocUrlService + ); + parseDocUrlService.parseDocUrl = (url: string) => { + const docId = mapping[url]; + if (docId) { + return { docId }; + } + return; + }; + }, mapping); +} diff --git a/blocksuite/tests-legacy/utils/actions/selection.ts b/blocksuite/tests-legacy/utils/actions/selection.ts new file mode 100644 index 0000000000000..7a45259103aab --- /dev/null +++ b/blocksuite/tests-legacy/utils/actions/selection.ts @@ -0,0 +1,45 @@ +import type { Page } from '@playwright/test'; + +export async function getRichTextBoundingBox( + page: Page, + blockId: string +): Promise { + return page.evaluate(id => { + const paragraph = document.querySelector( + `[data-block-id="${id}"] .inline-editor` + ); + const bbox = paragraph?.getBoundingClientRect() as DOMRect; + return bbox; + }, blockId); +} + +interface Rect { + x: number; + y: number; + width: number; + height: number; +} + +export async function clickInEdge(page: Page, rect: Rect) { + const edgeX = rect.x + rect.width / 2; + const edgeY = rect.y + rect.height - 5; + await page.mouse.click(edgeX, edgeY); +} + +export async function clickInCenter(page: Page, rect: Rect) { + const centerX = rect.x + rect.width / 2; + const centerY = rect.y + rect.height / 2; + await page.mouse.click(centerX, centerY); +} + +export async function getBoundingRect( + page: Page, + selector: string +): Promise { + const div = page.locator(selector); + const boundingRect = await div.boundingBox(); + if (!boundingRect) { + throw new Error(`Missing ${selector}`); + } + return boundingRect; +} diff --git a/blocksuite/tests-legacy/utils/asserts.ts b/blocksuite/tests-legacy/utils/asserts.ts new file mode 100644 index 0000000000000..225694c6977de --- /dev/null +++ b/blocksuite/tests-legacy/utils/asserts.ts @@ -0,0 +1,1344 @@ +import './declare-test-window.js'; + +import type { + AffineInlineEditor, + NoteBlockModel, + RichText, + RootBlockModel, +} from '@blocks/index.js'; +import { + DEFAULT_NOTE_HEIGHT, + DEFAULT_NOTE_WIDTH, +} from '@blocksuite/affine-model'; +import type { BlockComponent, EditorHost } from '@blocksuite/block-std'; +import { BLOCK_ID_ATTR } from '@blocksuite/block-std'; +import { assertExists } from '@blocksuite/global/utils'; +import type { InlineRootElement } from '@inline/inline-editor.js'; +import { expect, type Locator, type Page } from '@playwright/test'; +import { COLLECTION_VERSION, PAGE_VERSION } from '@store/consts.js'; +import type { BlockModel } from '@store/index.js'; +import type { JSXElement } from '@store/utils/jsx.js'; +import { + format as prettyFormat, + plugins as prettyFormatPlugins, +} from 'pretty-format'; + +import { + getCanvasElementsCount, + getConnectorPath, + getContainerChildIds, + getContainerIds, + getContainerOfElements, + getEdgelessElementBound, + getNoteRect, + getSelectedBound, + getSortedIdsInViewport, + getZoomLevel, + toIdCountMap, + toModelCoord, +} from './actions/edgeless.js'; +import { + pressArrowLeft, + pressArrowRight, + pressBackspace, + redoByKeyboard, + SHORT_KEY, + type, + undoByKeyboard, +} from './actions/keyboard.js'; +import { + captureHistory, + getClipboardCustomData, + getCurrentEditorDocId, + getCurrentThemeCSSPropertyValue, + getEditorLocator, + inlineEditorInnerTextToString, +} from './actions/misc.js'; +import { getStringFromRichText } from './inline-editor.js'; +import { currentEditorIndex } from './multiple-editor.js'; + +export { assertExists }; + +export const defaultStore = { + meta: { + pages: [ + { + id: 'doc:home', + title: '', + tags: [], + }, + ], + blockVersions: { + 'affine:paragraph': 1, + 'affine:page': 2, + 'affine:database': 3, + 'affine:data-view': 1, + 'affine:list': 1, + 'affine:note': 1, + 'affine:divider': 1, + 'affine:embed-youtube': 1, + 'affine:embed-figma': 1, + 'affine:embed-github': 1, + 'affine:embed-loom': 1, + 'affine:embed-html': 1, + 'affine:embed-linked-doc': 1, + 'affine:embed-synced-doc': 1, + 'affine:image': 1, + 'affine:latex': 1, + 'affine:frame': 1, + 'affine:code': 1, + 'affine:surface': 5, + 'affine:bookmark': 1, + 'affine:attachment': 1, + 'affine:surface-ref': 1, + 'affine:edgeless-text': 1, + }, + workspaceVersion: COLLECTION_VERSION, + pageVersion: PAGE_VERSION, + }, + spaces: { + 'doc:home': { + blocks: { + '0': { + 'prop:title': '', + 'sys:id': '0', + 'sys:flavour': 'affine:page', + 'sys:children': ['1'], + 'sys:version': 2, + }, + '1': { + 'sys:flavour': 'affine:note', + 'sys:id': '1', + 'sys:children': ['2'], + 'sys:version': 1, + 'prop:xywh': `[0,0,${DEFAULT_NOTE_WIDTH}, ${DEFAULT_NOTE_HEIGHT}]`, + 'prop:background': '--affine-note-background-white', + 'prop:index': 'a0', + 'prop:hidden': false, + 'prop:displayMode': 'both', + 'prop:edgeless': { + style: { + borderRadius: 8, + borderSize: 4, + borderStyle: 'none', + shadowType: '--affine-note-shadow-box', + }, + }, + }, + '2': { + 'sys:flavour': 'affine:paragraph', + 'sys:id': '2', + 'sys:children': [], + 'sys:version': 1, + 'prop:text': 'hello', + 'prop:type': 'text', + }, + }, + }, + }, +}; + +export type Bound = [x: number, y: number, w: number, h: number]; + +export async function assertEmpty(page: Page) { + await assertRichTexts(page, ['']); +} + +export async function assertTitle(page: Page, text: string) { + const editor = getEditorLocator(page); + const inlineEditor = editor.locator('.doc-title-container').first(); + const vText = inlineEditorInnerTextToString(await inlineEditor.innerText()); + expect(vText).toBe(text); +} + +export async function assertInlineEditorDeltas( + page: Page, + deltas: unknown[], + i = 0 +) { + const actual = await page.evaluate(i => { + const inlineRoot = document.querySelectorAll( + '[data-v-root="true"]' + )[i]; + return inlineRoot.inlineEditor.yTextDeltas; + }, i); + expect(actual).toEqual(deltas); +} + +export async function assertRichTextInlineDeltas( + page: Page, + deltas: unknown[], + i = 0 +) { + const actual = await page.evaluate( + ([i, currentEditorIndex]) => { + const editorHost = + document.querySelectorAll('editor-host')[currentEditorIndex]; + const inlineRoot = editorHost.querySelectorAll( + 'rich-text [data-v-root="true"]' + )[i]; + return inlineRoot.inlineEditor.yTextDeltas; + }, + [i, currentEditorIndex] + ); + expect(actual).toEqual(deltas); +} + +export async function assertText(page: Page, text: string, i = 0) { + const actual = await getStringFromRichText(page, i); + expect(actual).toBe(text); +} + +export async function assertTextContain(page: Page, text: string, i = 0) { + const actual = await getStringFromRichText(page, i); + expect(actual).toContain(text); +} + +export async function assertRichTexts(page: Page, texts: string[]) { + const actualTexts = await page.evaluate(currentEditorIndex => { + const editorHost = + document.querySelectorAll('editor-host')[currentEditorIndex]; + const richTexts = Array.from( + editorHost?.querySelectorAll('rich-text') ?? [] + ); + return richTexts.map(richText => { + const editor = richText.inlineEditor as AffineInlineEditor; + return editor.yText.toString(); + }); + }, currentEditorIndex); + expect(actualTexts).toEqual(texts); +} + +export async function assertEdgelessCanvasText(page: Page, text: string) { + const actualTexts = await page.evaluate(() => { + const editor = document.querySelector( + [ + 'edgeless-text-editor', + 'edgeless-shape-text-editor', + 'edgeless-frame-title-editor', + 'edgeless-group-title-editor', + 'edgeless-connector-label-editor', + ].join(',') + ); + if (!editor) { + throw new Error('editor not found'); + } + // @ts-ignore + const inlineEditor = editor.inlineEditor; + return inlineEditor?.yText.toString(); + }); + expect(actualTexts).toEqual(text); +} + +export async function assertRichImage(page: Page, count: number) { + const editor = getEditorLocator(page); + await expect(editor.locator('.resizable-img')).toHaveCount(count); +} + +export async function assertDivider(page: Page, count: number) { + await expect(page.locator('affine-divider')).toHaveCount(count); +} + +export async function assertRichDragButton(page: Page) { + await expect(page.locator('.resize')).toHaveCount(4); +} + +export async function assertImageSize( + page: Page, + size: { width: number; height: number } +) { + const actual = await page.locator('.resizable-img').boundingBox(); + expect(size).toEqual({ + width: Math.floor(actual?.width ?? NaN), + height: Math.floor(actual?.height ?? NaN), + }); +} + +export async function assertImageOption(page: Page) { + // const actual = await page.locator('.embed-editing-state').count(); + // expect(actual).toEqual(1); + const locator = page.locator('.affine-image-toolbar-container'); + await expect(locator).toBeVisible(); +} + +export async function assertDocTitleFocus(page: Page) { + const locator = page.locator('doc-title .inline-editor').nth(0); + await expect(locator).toBeFocused(); +} + +export async function assertListPrefix( + page: Page, + predict: (string | RegExp)[], + range?: [number, number] +) { + const prefixs = page.locator('.affine-list-block__prefix'); + + let start = 0; + let end = await prefixs.count(); + if (range) { + [start, end] = range; + } + + for (let i = start; i < end; i++) { + const prefix = await prefixs.nth(i).innerText(); + expect(prefix).toContain(predict[i]); + } +} + +export async function assertBlockCount( + page: Page, + flavour: string, + count: number +) { + await expect(page.locator(`affine-${flavour}`)).toHaveCount(count); +} +export async function assertRowCount(page: Page, count: number) { + await expect(page.locator('.affine-database-block-row')).toHaveCount(count); +} + +export async function assertVisibleBlockCount( + page: Page, + flavour: string, + count: number +) { + // not only count, but also check if all the blocks are visible + const locator = page.locator(`affine-${flavour}`); + let visibleCount = 0; + for (let i = 0; i < count; i++) { + if (await locator.nth(i).isVisible()) { + visibleCount++; + } + } + expect(visibleCount).toEqual(count); +} + +export async function assertRichTextInlineRange( + page: Page, + richTextIndex: number, + rangeIndex: number, + rangeLength = 0 +) { + const actual = await page.evaluate( + ([richTextIndex, currentEditorIndex]) => { + const editorHost = + document.querySelectorAll('editor-host')[currentEditorIndex]; + const richText = editorHost?.querySelectorAll('rich-text')[richTextIndex]; + const inlineEditor = richText.inlineEditor; + return inlineEditor?.getInlineRange(); + }, + [richTextIndex, currentEditorIndex] + ); + expect(actual).toEqual({ index: rangeIndex, length: rangeLength }); +} + +export async function assertNativeSelectionRangeCount( + page: Page, + count: number +) { + const actual = await page.evaluate(() => { + const selection = window.getSelection(); + return selection?.rangeCount; + }); + expect(actual).toEqual(count); +} + +export async function assertNoteXYWH( + page: Page, + expected: [number, number, number, number] +) { + const actual = await page.evaluate(() => { + const rootModel = window.doc.root as RootBlockModel; + const note = rootModel.children.find( + x => x.flavour === 'affine:note' + ) as NoteBlockModel; + return JSON.parse(note.xywh) as number[]; + }); + expect(actual[0]).toBeCloseTo(expected[0]); + expect(actual[1]).toBeCloseTo(expected[1]); + expect(actual[2]).toBeCloseTo(expected[2]); + expect(actual[3]).toBeCloseTo(expected[3]); +} + +export async function assertTextFormat( + page: Page, + richTextIndex: number, + index: number, + resultObj: unknown +) { + const actual = await page.evaluate( + ({ richTextIndex, index, currentEditorIndex }) => { + const editorHost = + document.querySelectorAll('editor-host')[currentEditorIndex]; + const richText = editorHost.querySelectorAll('rich-text')[richTextIndex]; + const inlineEditor = richText.inlineEditor; + if (!inlineEditor) { + throw new Error('Inline editor is undefined'); + } + + const result = inlineEditor.getFormat({ + index, + length: 0, + }); + return result; + }, + { richTextIndex, index, currentEditorIndex } + ); + expect(actual).toEqual(resultObj); +} + +export async function assertRichTextModelType( + page: Page, + type: string, + index = 0 +) { + const actual = await page.evaluate( + ({ index, BLOCK_ID_ATTR, currentEditorIndex }) => { + const editorHost = + document.querySelectorAll('editor-host')[currentEditorIndex]; + const richText = editorHost.querySelectorAll('rich-text')[index]; + const block = richText.closest(`[${BLOCK_ID_ATTR}]`); + + if (!block) { + throw new Error('block component is undefined'); + } + return (block.model as BlockModel<{ type: string }>).type; + }, + { index, BLOCK_ID_ATTR, currentEditorIndex } + ); + expect(actual).toEqual(type); +} + +export async function assertTextFormats(page: Page, resultObj: unknown[]) { + const actual = await page.evaluate(index => { + const editorHost = document.querySelectorAll('editor-host')[index]; + const elements = editorHost.querySelectorAll('rich-text'); + return Array.from(elements).map(el => { + const inlineEditor = el.inlineEditor; + if (!inlineEditor) { + throw new Error('Inline editor is undefined'); + } + + const result = inlineEditor.getFormat({ + index: 0, + length: inlineEditor.yText.length, + }); + return result; + }); + }, currentEditorIndex); + expect(actual).toEqual(resultObj); +} + +export async function assertStore( + page: Page, + expected: Record +) { + const actual = await page.evaluate(() => { + const json = window.collection.doc.toJSON(); + delete json.meta.pages[0].createDate; + return json; + }); + expect(actual).toEqual(expected); +} + +export async function assertBlockChildrenIds( + page: Page, + blockId: string, + ids: string[] +) { + const actual = await page.evaluate( + ({ blockId }) => { + const element = document.querySelector(`[data-block-id="${blockId}"]`); + // @ts-ignore + const model = element.model as BlockModel; + return model.children.map(child => child.id); + }, + { blockId } + ); + expect(actual).toEqual(ids); +} + +export async function assertBlockChildrenFlavours( + page: Page, + blockId: string, + flavours: string[] +) { + const actual = await page.evaluate( + ({ blockId }) => { + const element = document.querySelector(`[data-block-id="${blockId}"]`); + // @ts-ignore + const model = element.model as BlockModel; + return model.children.map(child => child.flavour); + }, + { blockId } + ); + expect(actual).toEqual(flavours); +} + +export async function assertParentBlockId( + page: Page, + blockId: string, + parentId: string +) { + const actual = await page.evaluate( + ({ blockId }) => { + const model = window.doc?.getBlock(blockId)?.model; + if (!model) { + throw new Error(`Block with id ${blockId} not found`); + } + return model.doc.getParent(model)?.id; + }, + { blockId } + ); + expect(actual).toEqual(parentId); +} + +export async function assertParentBlockFlavour( + page: Page, + blockId: string, + flavour: string +) { + const actual = await page.evaluate( + ({ blockId }) => { + const model = window.doc?.getBlock(blockId)?.model; + if (!model) { + throw new Error(`Block with id ${blockId} not found`); + } + return model.doc.getParent(model)?.flavour; + }, + { blockId } + ); + expect(actual).toEqual(flavour); +} + +export async function assertClassName( + page: Page, + selector: string, + className: RegExp +) { + const locator = page.locator(selector); + await expect(locator).toHaveClass(className); +} + +export async function assertTextContent( + page: Page, + selector: string, + text: RegExp +) { + const locator = page.locator(selector); + await expect(locator).toHaveText(text); +} + +export async function assertBlockType( + page: Page, + id: string | number | null, + type: string +) { + const actual = await page.evaluate( + ({ id }) => { + const element = document.querySelector( + `[data-block-id="${id}"]` + ); + + if (!element) { + throw new Error(`Element with id ${id} not found`); + } + + const model = element.model; + // @ts-ignore + return model.type; + }, + { id } + ); + expect(actual).toBe(type); +} + +export async function assertBlockFlavour( + page: Page, + id: string | number, + flavour: BlockSuite.Flavour +) { + const actual = await page.evaluate( + ({ id }) => { + const element = document.querySelector( + `[data-block-id="${id}"]` + ); + + if (!element) { + throw new Error(`Element with id ${id} not found`); + } + + const model = element.model; + return model.flavour; + }, + { id } + ); + expect(actual).toBe(flavour); +} + +export async function assertBlockTextContent( + page: Page, + id: string | number, + str: string +) { + const actual = await page.evaluate( + ({ id }) => { + const element = document.querySelector( + `[data-block-id="${id}"]` + ); + + if (!element) { + throw new Error(`Element with id ${id} not found`); + } + + const model = element.model; + return model.text?.toString() ?? ''; + }, + { id } + ); + expect(actual).toBe(str); +} + +export async function assertBlockProps( + page: Page, + id: string, + props: Record +) { + const actual = await page.evaluate( + ([id, props]) => { + const element = document.querySelector(`[data-block-id="${id}"]`); + // @ts-ignore + const model = element.model as BlockModel; + return Object.fromEntries( + // @ts-ignore + Object.keys(props).map(key => [key, (model[key] as unknown).toString()]) + ); + }, + [id, props] as const + ); + expect(actual).toEqual(props); +} + +export async function assertBlockTypes(page: Page, blockTypes: string[]) { + const actual = await page.evaluate(index => { + const editor = document.querySelectorAll('affine-editor-container')[index]; + const elements = editor?.querySelectorAll('[data-block-id]'); + return ( + Array.from(elements) + .slice(2) + // @ts-ignore + .map(el => el.model.type) + ); + }, currentEditorIndex); + expect(actual).toEqual(blockTypes); +} + +/** + * @example + * ```ts + * await assertMatchMarkdown( + * page, + * `title + * text1 + * text2` + * ); + * ``` + * @deprecated experimental, use {@link assertStoreMatchJSX} instead + */ +export async function assertMatchMarkdown(page: Page, text: string) { + const jsonDoc = (await page.evaluate(() => + window.collection.doc.toJSON() + )) as Record>; + const titleNode = jsonDoc['doc:home']['0'] as Record; + + const markdownVisitor = (node: Record): string => { + // TODO use schema + if (node['sys:flavour'] === 'affine:page') { + return (node['prop:title'] as Text).toString() ?? ''; + } + if (!('prop:type' in node)) { + return '[? unknown node]'; + } + if (node['prop:type'] === 'text') { + return node['prop:text'] as string; + } + if (node['prop:type'] === 'bulleted') { + return `- ${node['prop:text']}`; + } + // TODO please fix this + return `[? ${node['prop:type']} node]`; + }; + + const INDENT_SIZE = 2; + const visitNodes = ( + node: Record, + visitor: (node: Record) => string + ): string[] => { + if (!('sys:children' in node) || !Array.isArray(node['sys:children'])) { + throw new Error("Failed to visit nodes: 'sys:children' is not an array"); + // return visitor(node); + } + + const children = node['sys:children'].map(id => jsonDoc['doc:home'][id]); + return [ + visitor(node), + ...children.flatMap(child => + visitNodes(child as Record, visitor).map(line => { + if (node['sys:flavour'] === 'affine:page') { + // Ad hoc way to remove the title indent + return line; + } + + return ' '.repeat(INDENT_SIZE) + line; + }) + ), + ]; + }; + const visitRet = visitNodes(titleNode, markdownVisitor); + const actual = visitRet.join('\n'); + + expect(actual).toEqual(text); +} + +export async function assertStoreMatchJSX( + page: Page, + snapshot: string, + blockId?: string +) { + const docId = await getCurrentEditorDocId(page); + const element = (await page.evaluate( + ([blockId, docId]) => window.collection.exportJSX(blockId, docId), + [blockId, docId] + )) as JSXElement; + + // Fix symbol can not be serialized, we need to set $$typeof manually + // If the function passed to the page.evaluate(pageFunction[, arg]) returns a non-Serializable value, + // then page.evaluate(pageFunction[, arg]) resolves to undefined. + // See https://playwright.dev/docs/api/class-page#page-evaluate + const testSymbol = Symbol.for('react.test.json'); + const markSymbol = (node: JSXElement) => { + node.$$typeof = testSymbol; + if (!node.children) { + return; + } + const propText = node.props['prop:text']; + if (propText && typeof propText === 'object') { + markSymbol(propText); + } + node.children.forEach(child => { + if (!(typeof child === 'object')) { + return; + } + markSymbol(child); + }); + }; + + markSymbol(element); + + // See https://github.com/facebook/jest/blob/main/packages/pretty-format + const formattedJSX = prettyFormat(element, { + plugins: [prettyFormatPlugins.ReactTestComponent], + printFunctionName: false, + }); + expect(formattedJSX, formattedJSX).toEqual(snapshot.trimStart()); +} + +type MimeType = 'text/plain' | 'blocksuite/x-c+w' | 'text/html'; + +export function assertClipItems(_page: Page, _key: MimeType, _value: unknown) { + // FIXME: use original clipboard API + // const clipItems = await page.evaluate(() => { + // return document + // .getElementsByTagName('affine-editor-container')[0] + // .clipboard['_copy']['_getClipItems'](); + // }); + // const actual = clipItems.find(item => item.mimeType === key)?.data; + // expect(actual).toEqual(value); + return true; +} + +export function assertAlmostEqual( + actual: number, + expected: number, + precision = 0.001 +) { + expect( + Math.abs(actual - expected), + `expected: ${expected}, but actual: ${actual}` + ).toBeLessThan(precision); +} + +export function assertPointAlmostEqual( + actual: number[], + expected: number[], + precision = 0.001 +) { + assertAlmostEqual(actual[0], expected[0], precision); + assertAlmostEqual(actual[1], expected[1], precision); +} + +/** + * Assert the locator is visible in the viewport. + * It will check the bounding box of the locator is within the viewport. + * + * See also https://playwright.dev/docs/actionability#visible + */ +export async function assertLocatorVisible( + page: Page, + locator: Locator, + visible = true +) { + const bodyRect = await page.locator('body').boundingBox(); + const rect = await locator.boundingBox(); + expect(rect).toBeTruthy(); + expect(bodyRect).toBeTruthy(); + if (!rect || !bodyRect) { + throw new Error('Unreachable'); + } + if (visible) { + // Assert the locator is **fully** visible + await expect(locator).toBeVisible(); + expect(rect.x).toBeGreaterThanOrEqual(0); + expect(rect.y).toBeGreaterThanOrEqual(0); + expect(rect.x + rect.width).toBeLessThanOrEqual( + bodyRect.x + bodyRect.width + ); + expect(rect.y + rect.height).toBeLessThanOrEqual( + bodyRect.x + bodyRect.height + ); + } else { + // Assert the locator is **fully** invisible + const locatorIsVisible = await locator.isVisible(); + if (!locatorIsVisible) { + // If the locator is invisible, we don't need to check the bounding box + return; + } + const isInVisible = + rect.x > bodyRect.x + bodyRect.width || + rect.y > bodyRect.y + bodyRect.height || + rect.x + rect.width < bodyRect.x || + rect.y + rect.height < bodyRect.y; + expect(isInVisible).toBe(true); + } +} + +/** + * Assert basic keyboard operation works in input + * + * NOTICE: + * - it will clear the input value. + * - it will pollute undo/redo history. + */ +export async function assertKeyboardWorkInInput(page: Page, locator: Locator) { + await expect(locator).toBeVisible(); + await locator.focus(); + // Clear input before test + await locator.clear(); + // type/backspace + await type(page, '12/34'); + await expect(locator).toHaveValue('12/34'); + await captureHistory(page); + await pressBackspace(page); + await expect(locator).toHaveValue('12/3'); + + // undo/redo + await undoByKeyboard(page); + await expect(locator).toHaveValue('12/34'); + await redoByKeyboard(page); + await expect(locator).toHaveValue('12/3'); + + // keyboard + await pressArrowLeft(page, 2); + await pressArrowRight(page, 1); + await pressBackspace(page); + await expect(locator).toHaveValue('123'); + await pressBackspace(page); + await expect(locator).toHaveValue('13'); + + // copy/cut/paste + await page.keyboard.press(`${SHORT_KEY}+a`, { delay: 50 }); + await page.keyboard.press(`${SHORT_KEY}+c`, { delay: 50 }); + await pressBackspace(page); + await expect(locator).toHaveValue(''); + await page.keyboard.press(`${SHORT_KEY}+v`, { delay: 50 }); + await expect(locator).toHaveValue('13'); + await page.keyboard.press(`${SHORT_KEY}+a`, { delay: 50 }); + await page.keyboard.press(`${SHORT_KEY}+x`, { delay: 50 }); + await expect(locator).toHaveValue(''); +} + +export function assertSameColor(c1?: `#${string}`, c2?: `#${string}`) { + expect(c1?.toLowerCase()).toEqual(c2?.toLowerCase()); +} + +type Rect = { x: number; y: number; w: number; h: number }; + +export async function assertNoteRectEqual( + page: Page, + noteId: string, + expected: Rect +) { + const rect = await getNoteRect(page, noteId); + assertRectEqual(rect, expected); +} + +export function assertRectEqual(a: Rect, b: Rect) { + expect(a.x).toBeCloseTo(b.x, 0); + expect(a.y).toBeCloseTo(b.y, 0); + expect(a.w).toBeCloseTo(b.w, 0); + expect(a.h).toBeCloseTo(b.h, 0); +} + +export function assertDOMRectEqual(a: DOMRect, b: DOMRect) { + expect(a.x).toBeCloseTo(b.x, 0); + expect(a.y).toBeCloseTo(b.y, 0); + expect(a.width).toBeCloseTo(b.width, 0); + expect(a.height).toBeCloseTo(b.height, 0); +} + +export async function assertEdgelessDraggingArea(page: Page, xywh: number[]) { + const [x, y, w, h] = xywh; + const editor = getEditorLocator(page); + const draggingArea = editor + .locator('edgeless-dragging-area-rect') + .locator('.affine-edgeless-dragging-area'); + + const box = await draggingArea.boundingBox(); + if (!box) throw new Error('Missing edgeless dragging area'); + + expect(box.x).toBeCloseTo(x, 0); + expect(box.y).toBeCloseTo(y, 0); + expect(box.width).toBeCloseTo(w, 0); + expect(box.height).toBeCloseTo(h, 0); +} + +export async function getSelectedRect(page: Page) { + const editor = getEditorLocator(page); + const selectedRect = editor + .locator('edgeless-selected-rect') + .locator('.affine-edgeless-selected-rect'); + // FIXME: remove this timeout + await page.waitForTimeout(50); + const box = await selectedRect.boundingBox(); + if (!box) throw new Error('Missing edgeless selected rect'); + return box; +} + +// Better to use xxSelectedModelRect +export async function assertEdgelessSelectedRect(page: Page, xywh: number[]) { + const [x, y, w, h] = xywh; + const box = await getSelectedRect(page); + + expect(box.x).toBeCloseTo(x, 0); + expect(box.y).toBeCloseTo(y, 0); + expect(box.width).toBeCloseTo(w, 0); + expect(box.height).toBeCloseTo(h, 0); +} + +export async function assertEdgelessSelectedModelRect( + page: Page, + xywh: number[] +) { + const [x, y, w, h] = xywh; + const box = await getSelectedRect(page); + const [mX, mY] = await toModelCoord(page, [box.x, box.y]); + + expect(mX).toBeCloseTo(x, 0); + expect(mY).toBeCloseTo(y, 0); + expect(box.width).toBeCloseTo(w, 0); + expect(box.height).toBeCloseTo(h, 0); +} + +export async function assertEdgelessSelectedElementHandleCount( + page: Page, + count: number +) { + const editor = getEditorLocator(page); + const handles = editor.locator('.element-handle'); + await expect(handles).toHaveCount(count); +} + +// Better to use xxSelectedModelRect +export async function assertEdgelessRemoteSelectedRect( + page: Page, + xywh: number[], + index = 0 +) { + const [x, y, w, h] = xywh; + const editor = getEditorLocator(page); + const remoteSelectedRect = editor + .locator('affine-edgeless-remote-selection-widget') + .locator('.remote-rect') + .nth(index); + + const box = await remoteSelectedRect.boundingBox(); + if (!box) throw new Error('Missing edgeless remote selected rect'); + + expect(box.x).toBeCloseTo(x, 0); + expect(box.y).toBeCloseTo(y, 0); + expect(box.width).toBeCloseTo(w, 0); + expect(box.height).toBeCloseTo(h, 0); +} + +export async function assertEdgelessRemoteSelectedModelRect( + page: Page, + xywh: number[], + index = 0 +) { + const [x, y, w, h] = xywh; + const editor = getEditorLocator(page); + const remoteSelectedRect = editor + .locator('affine-edgeless-remote-selection-widget') + .locator('.remote-rect') + .nth(index); + + const box = await remoteSelectedRect.boundingBox(); + if (!box) throw new Error('Missing edgeless remote selected rect'); + + const [mX, mY] = await toModelCoord(page, [box.x, box.y]); + expect(mX).toBeCloseTo(x, 0); + expect(mY).toBeCloseTo(y, 0); + expect(box.width).toBeCloseTo(w, 0); + expect(box.height).toBeCloseTo(h, 0); +} + +export async function assertEdgelessSelectedRectRotation(page: Page, deg = 0) { + const editor = getEditorLocator(page); + const selectedRect = editor + .locator('edgeless-selected-rect') + .locator('.affine-edgeless-selected-rect'); + + const transform = await selectedRect.evaluate(el => el.style.transform); + const r = new RegExp(`rotate\\(${deg}deg\\)`); + expect(transform).toMatch(r); +} + +export async function assertEdgelessSelectedReactCursor( + page: Page, + expected: ( + | { + mode: 'resize'; + handle: + | 'top' + | 'right' + | 'bottom' + | 'left' + | 'top-left' + | 'top-right' + | 'bottom-right' + | 'bottom-left'; + } + | { + mode: 'rotate'; + handle: 'top-left' | 'top-right' | 'bottom-right' | 'bottom-left'; + } + ) & { + cursor: string; + } +) { + const editor = getEditorLocator(page); + const selectedRect = editor + .locator('edgeless-selected-rect') + .locator('.affine-edgeless-selected-rect'); + + const handle = selectedRect + .getByLabel(expected.handle, { exact: true }) + .locator(`.${expected.mode}`); + + await handle.hover(); + await expect(handle).toHaveCSS('cursor', expected.cursor); +} + +export async function assertEdgelessNonSelectedRect(page: Page) { + const rect = page.locator('edgeless-selected-rect'); + await expect(rect).toBeHidden(); +} + +export async function assertSelectionInNote( + page: Page, + noteId: string, + blockNote: string = 'affine-note' +) { + const closestNoteId = await page.evaluate(blockNote => { + const selection = window.getSelection(); + const note = selection?.anchorNode?.parentElement?.closest(blockNote); + return note?.getAttribute('data-block-id'); + }, blockNote); + expect(closestNoteId).toEqual(noteId); +} + +export async function assertEdgelessNoteBackground( + page: Page, + noteId: string, + color: string +) { + const editor = getEditorLocator(page); + const backgroundColor = await editor + .locator(`affine-edgeless-note[data-block-id="${noteId}"]`) + .evaluate(ele => { + const noteWrapper = + ele?.querySelector('.note-background'); + if (!noteWrapper) { + throw new Error(`Could not find note: ${noteId}`); + } + return noteWrapper.style.backgroundColor; + }); + + expect(backgroundColor).toEqual(`var(${color})`); +} + +function toHex(color: string) { + let r, g, b; + + if (color.startsWith('#')) { + color = color.substr(1); + if (color.length === 3) { + color = color.replace(/./g, '$&$&'); + } + [r, g, b] = color.match(/.{2}/g)?.map(hex => parseInt(hex, 16)) ?? []; + } else if (color.startsWith('rgba')) { + [r, g, b] = color.match(/\d+/g)?.map(Number) ?? []; + } else if (color.startsWith('rgb')) { + [r, g, b] = color.match(/\d+/g)?.map(Number) ?? []; + } else { + throw new Error('Invalid color format'); + } + + if (r === undefined || g === undefined || b === undefined) { + throw new Error('Invalid color format'); + } + + const hex = ((r << 16) | (g << 8) | b).toString(16); + return '#' + '0'.repeat(6 - hex.length) + hex; +} + +export async function assertEdgelessColorSameWithHexColor( + page: Page, + edgelessColor: string, + hexColor: `#${string}` +) { + const themeColor = await getCurrentThemeCSSPropertyValue(page, edgelessColor); + expect(themeColor).toBeTruthy(); + const edgelessHexColor = toHex(themeColor); + + assertSameColor(hexColor, edgelessHexColor as `#${string}`); +} + +export async function assertZoomLevel(page: Page, zoom: number) { + const z = await getZoomLevel(page); + expect(z).toBe(Math.ceil(zoom)); +} + +export async function assertConnectorPath( + page: Page, + path: number[][], + index = 0 +) { + const actualPath = await getConnectorPath(page, index); + actualPath.every((p, i) => assertPointAlmostEqual(p, path[i], 0.1)); +} + +export function assertRectExist( + rect: { x: number; y: number; width: number; height: number } | null +): asserts rect is { x: number; y: number; width: number; height: number } { + expect(rect).not.toBe(null); +} + +export async function assertEdgelessElementBound( + page: Page, + elementId: string, + bound: Bound +) { + const actual = await getEdgelessElementBound(page, elementId); + assertBound(actual, bound); +} + +export async function assertSelectedBound( + page: Page, + expected: Bound, + index = 0 +) { + const bound = await getSelectedBound(page, index); + assertBound(bound, expected); +} + +/** + * asserts all groups and they children count at the same time + * @param page + * @param expected the expected group id and the count of of its children + */ +export async function assertContainerIds( + page: Page, + expected: Record +) { + const ids = await getContainerIds(page); + const result = toIdCountMap(ids); + + expect(result).toEqual(expected); +} + +export async function assertSortedIds(page: Page, expected: string[]) { + const ids = await getSortedIdsInViewport(page); + expect(ids).toEqual(expected); +} + +export async function assertContainerChildIds( + page: Page, + expected: Record, + id: string +) { + const ids = await getContainerChildIds(page, id); + const result = toIdCountMap(ids); + + expect(result).toEqual(expected); +} + +export async function assertContainerOfElements( + page: Page, + elements: string[], + containerId: string | null +) { + const elementContainers = await getContainerOfElements(page, elements); + + elementContainers.forEach(elementContainer => { + expect(elementContainer).toEqual(containerId); + }); +} + +/** + * Assert the given container has the expected children count. + * And the children's container id should equal to the given container id. + * @param page + * @param containerId + * @param childrenCount + */ +export async function assertContainerChildCount( + page: Page, + containerId: string, + childrenCount: number +) { + const ids = await getContainerChildIds(page, containerId); + + await assertContainerOfElements(page, ids, containerId); + expect(new Set(ids).size).toBe(childrenCount); +} + +export async function assertCanvasElementsCount(page: Page, expected: number) { + const number = await getCanvasElementsCount(page); + expect(number).toEqual(expected); +} +export function assertBound(received: Bound, expected: Bound) { + expect(received[0]).toBeCloseTo(expected[0], 0); + expect(received[1]).toBeCloseTo(expected[1], 0); + expect(received[2]).toBeCloseTo(expected[2], 0); + expect(received[3]).toBeCloseTo(expected[3], 0); +} + +export async function assertClipboardItem( + page: Page, + data: unknown, + type: string +) { + type Args = [type: string]; + const dataInClipboard = await page.evaluate( + async ([type]: Args) => { + const clipItems = await navigator.clipboard.read(); + const item = clipItems.find(item => item.types.includes(type)); + const data = await item?.getType(type); + return data?.text(); + }, + [type] as Args + ); + + expect(dataInClipboard).toBe(data); +} + +export async function assertClipboardCustomData( + page: Page, + type: string, + data: unknown +) { + const dataInClipboard = await getClipboardCustomData(page, type); + expect(dataInClipboard).toBe(data); +} + +export function assertClipData( + clipItems: { mimeType: string; data: unknown }[], + expectClipItems: { mimeType: string; data: unknown }[], + type: string +) { + expect(clipItems.find(item => item.mimeType === type)?.data).toBe( + expectClipItems.find(item => item.mimeType === type)?.data + ); +} + +export async function assertHasClass(locator: Locator, className: string) { + expect( + (await locator.getAttribute('class'))?.split(' ').includes(className) + ).toEqual(true); +} + +export async function assertNotHasClass(locator: Locator, className: string) { + expect( + (await locator.getAttribute('class'))?.split(' ').includes(className) + ).toEqual(false); +} + +export async function assertNoteSequence(page: Page, expected: string) { + const actual = await page.locator('.page-visible-index-label').innerText(); + expect(expected).toBe(actual); +} + +export async function assertBlockSelections(page: Page, paths: string[]) { + const selections = await page.evaluate(() => { + const host = document.querySelector('editor-host'); + if (!host) { + throw new Error('editor-host host not found'); + } + return host.selection.filter('block'); + }); + const actualPaths = selections.map(selection => selection.blockId); + expect(actualPaths).toEqual(paths); +} + +export async function assertTextSelection( + page: Page, + from?: { + blockId: string; + index: number; + length: number; + }, + to?: { + blockId: string; + index: number; + length: number; + } +) { + const selection = await page.evaluate(() => { + const host = document.querySelector('editor-host'); + if (!host) { + throw new Error('editor-host host not found'); + } + return host.selection.find('text'); + }); + + if (!from && !to) { + expect(selection).toBeUndefined(); + return; + } + + if (from) { + expect(selection?.from).toEqual(from); + } + if (to) { + expect(selection?.to).toEqual(to); + } +} + +export async function assertConnectorStrokeColor(page: Page, color: string) { + const colorButton = page + .locator('edgeless-change-connector-button') + .locator('edgeless-color-panel') + .locator(`.color-unit[aria-label="${color}"]`); + + expect(await colorButton.count()).toBe(1); +} diff --git a/blocksuite/tests-legacy/utils/declare-test-window.ts b/blocksuite/tests-legacy/utils/declare-test-window.ts new file mode 100644 index 0000000000000..3f57d33d3d787 --- /dev/null +++ b/blocksuite/tests-legacy/utils/declare-test-window.ts @@ -0,0 +1,48 @@ +import type { RefNodeSlotsProvider, TestUtils } from '@blocks/index.js'; +import type { + EditorHost, + ExtensionType, + WidgetViewMapIdentifier, +} from '@blocksuite/block-std'; +import type { AffineEditorContainer } from '@blocksuite/presets'; +import type { StarterDebugMenu } from '@playground/apps/_common/components/starter-debug-menu.js'; +import type { BlockModel, Doc, DocCollection, Job } from '@store/index.js'; + +declare global { + interface Window { + /** Available on playground window + * the following instance are initialized in `packages/playground/apps/starter/main.ts` + */ + $blocksuite: { + store: typeof import('../../packages/framework/store/src/index.js'); + blocks: typeof import('../../packages/blocks/src/index.js'); + global: { + utils: typeof import('../../packages/framework/global/src/utils.js'); + }; + editor: typeof import('../../packages/presets/src/index.js'); + identifiers: { + WidgetViewMapIdentifier: typeof WidgetViewMapIdentifier; + QuickSearchProvider: typeof import('../../packages/affine/shared/src/services/quick-search-service.js').QuickSearchProvider; + DocModeProvider: typeof import('../../packages/affine/shared/src/services/doc-mode-service.js').DocModeProvider; + ThemeProvider: typeof import('../../packages/affine/shared/src/services/theme-service.js').ThemeProvider; + RefNodeSlotsProvider: typeof RefNodeSlotsProvider; + ParseDocUrlService: typeof import('../../packages/affine/shared/src/services/parse-url-service.js').ParseDocUrlProvider; + }; + defaultExtensions: () => ExtensionType[]; + extensions: { + WidgetViewMapExtension: typeof import('../../packages/framework/block-std/src/extension/widget-view-map.js').WidgetViewMapExtension; + }; + mockServices: { + mockDocModeService: typeof import('../../packages/playground/apps/_common/mock-services.js').mockDocModeService; + }; + }; + collection: DocCollection; + blockSchema: Record; + doc: Doc; + debugMenu: StarterDebugMenu; + editor: AffineEditorContainer; + host: EditorHost; + testUtils: TestUtils; + job: Job; + } +} diff --git a/blocksuite/tests-legacy/utils/ignore.ts b/blocksuite/tests-legacy/utils/ignore.ts new file mode 100644 index 0000000000000..a0859d8b7581d --- /dev/null +++ b/blocksuite/tests-legacy/utils/ignore.ts @@ -0,0 +1,28 @@ +import type { BlockSnapshot } from '@store/index.js'; + +export function ignoreFields(target: unknown, keys: string[]): unknown { + if (Array.isArray(target)) { + return target.map((item: unknown) => ignoreFields(item, keys)); + } else if (typeof target === 'object' && target !== null) { + return Object.keys(target).reduce( + (acc: Record, key: string) => { + if (keys.includes(key)) { + acc[key] = '*'; + } else { + acc[key] = ignoreFields( + (target as Record)[key], + keys + ); + } + return acc; + }, + {} + ); + } + return target; +} + +export function ignoreSnapshotId(snapshot: BlockSnapshot) { + const ignored = ignoreFields(snapshot, ['id']); + return JSON.stringify(ignored, null, 2); +} diff --git a/blocksuite/tests-legacy/utils/inline-editor.ts b/blocksuite/tests-legacy/utils/inline-editor.ts new file mode 100644 index 0000000000000..0558b256fb14a --- /dev/null +++ b/blocksuite/tests-legacy/utils/inline-editor.ts @@ -0,0 +1,26 @@ +import type { Page } from '@playwright/test'; + +import { currentEditorIndex } from './multiple-editor.js'; + +export async function getStringFromRichText( + page: Page, + index = 0 +): Promise { + await page.waitForTimeout(50); + return page.evaluate( + ([index, currentEditorIndex]) => { + const editorHost = + document.querySelectorAll('editor-host')[currentEditorIndex]; + const richTexts = editorHost.querySelectorAll('rich-text'); + + if (!richTexts) { + throw new Error('Cannot find rich-text'); + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const editor = (richTexts[index] as any).inlineEditor; + return editor.yText.toString(); + }, + [index, currentEditorIndex] + ); +} diff --git a/blocksuite/tests-legacy/utils/multiple-editor.ts b/blocksuite/tests-legacy/utils/multiple-editor.ts new file mode 100644 index 0000000000000..90d3b0a32520c --- /dev/null +++ b/blocksuite/tests-legacy/utils/multiple-editor.ts @@ -0,0 +1,15 @@ +import process from 'node:process'; + +const editorIndex = { + 0: 0, + 1: 1, +}[process.env.MULTIPLE_EDITOR_INDEX ?? '']; +export const scope = + editorIndex == null + ? undefined + : editorIndex === 0 + ? 'FIRST | ' + : 'SECOND | '; +export const multiEditor = scope != null; + +export const currentEditorIndex = editorIndex ?? 0; diff --git a/blocksuite/tests-legacy/utils/playwright.ts b/blocksuite/tests-legacy/utils/playwright.ts new file mode 100644 index 0000000000000..bd7f2f4cf4454 --- /dev/null +++ b/blocksuite/tests-legacy/utils/playwright.ts @@ -0,0 +1,110 @@ +import crypto from 'node:crypto'; +import fs from 'node:fs'; +import path from 'node:path'; +import process from 'node:process'; + +import { expect, type Page, test as baseTest } from '@playwright/test'; + +import { + enterPlaygroundRoom, + initEmptyParagraphState, +} from './actions/misc.js'; +import { currentEditorIndex, scope } from './multiple-editor.js'; + +const istanbulTempDir = process.env.ISTANBUL_TEMP_DIR + ? path.resolve(process.env.ISTANBUL_TEMP_DIR) + : path.join(process.cwd(), '.nyc_output'); + +function generateUUID() { + return crypto.randomUUID(); +} + +const enableCoverage = !!process.env.CI || !!process.env.COVERAGE; +export const scoped = (stringsArray: TemplateStringsArray) => { + return `${scope ?? ''}${stringsArray.join()}`; +}; +export const test = baseTest.extend<{}>({ + context: async ({ context }, use) => { + if (enableCoverage) { + await context.addInitScript(() => + window.addEventListener('beforeunload', () => + // @ts-expect-error + window.collectIstanbulCoverage(JSON.stringify(window.__coverage__)) + ) + ); + + await fs.promises.mkdir(istanbulTempDir, { recursive: true }); + await context.exposeFunction( + 'collectIstanbulCoverage', + (coverageJSON?: string) => { + if (coverageJSON) + fs.writeFileSync( + path.join( + istanbulTempDir, + `playwright_coverage_${generateUUID()}.json` + ), + coverageJSON + ); + } + ); + } + await use(context); + if (enableCoverage) { + for (const page of context.pages()) { + await page.evaluate(() => + // @ts-expect-error + window.collectIstanbulCoverage(JSON.stringify(window.__coverage__)) + ); + } + } + }, +}); +if (scope) { + test.beforeEach(async ({ browser }, testInfo) => { + if (scope && !testInfo.title.startsWith(scope)) { + testInfo.fn = () => { + testInfo.skip(); + }; + testInfo.skip(); + await browser.close(); + } + }); + + let page: Page; + + test.beforeAll(async ({ browser }) => { + page = await browser.newPage(); + }); + + // eslint-disable-next-line no-empty-pattern + test.afterAll(async ({}, testInfo) => { + if (!scope || !testInfo.title.startsWith(scope)) { + return; + } + const focusInSecondEditor = await page.evaluate( + ([currentEditorIndex]) => { + const editor = document.querySelectorAll('affine-editor-container')[ + currentEditorIndex + ]; + const selection = getSelection(); + if (!selection || selection.rangeCount === 0) { + return true; + } + // once the range exists, it must be in the corresponding editor + return editor.contains(selection.getRangeAt(0).startContainer); + }, + [currentEditorIndex] + ); + expect(focusInSecondEditor).toBe(true); + }); + + test('ensure enable two editor', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + const count = await page.evaluate(() => { + return document.querySelectorAll('affine-editor-container').length; + }); + + expect(count).toBe(2); + }); +} diff --git a/blocksuite/tests-legacy/utils/query.ts b/blocksuite/tests-legacy/utils/query.ts new file mode 100644 index 0000000000000..d8c40d15af3ef --- /dev/null +++ b/blocksuite/tests-legacy/utils/query.ts @@ -0,0 +1,125 @@ +import { expect, type Page } from '@playwright/test'; + +import { waitNextFrame } from './actions/misc.js'; +import { assertAlmostEqual } from './asserts.js'; + +export function getFormatBar(page: Page) { + const formatBar = page.locator('.affine-format-bar-widget'); + const boldBtn = formatBar.getByTestId('bold'); + const italicBtn = formatBar.getByTestId('italic'); + const underlineBtn = formatBar.getByTestId('underline'); + const strikeBtn = formatBar.getByTestId('strike'); + const codeBtn = formatBar.getByTestId('code'); + const linkBtn = formatBar.getByTestId('link'); + // highlight + const highlightBtn = formatBar.locator('.highlight-icon'); + const redForegroundBtn = formatBar.getByTestId( + 'var(--affine-text-highlight-foreground-red)' + ); + const createLinkedDocBtn = formatBar.getByTestId('convert-to-linked-doc'); + const defaultColorBtn = formatBar.getByTestId('unset'); + const highlight = { + highlightBtn, + redForegroundBtn, + defaultColorBtn, + }; + + const paragraphBtn = formatBar.locator(`.paragraph-button`); + const openParagraphMenu = async () => { + await expect(formatBar).toBeVisible(); + await paragraphBtn.hover(); + }; + + const textBtn = formatBar.getByTestId('affine:paragraph/text'); + const h1Btn = formatBar.getByTestId('affine:paragraph/h1'); + const bulletedBtn = formatBar.getByTestId('affine:list/bulleted'); + const codeBlockBtn = formatBar.getByTestId('affine:code/'); + + const moreBtn = formatBar.getByRole('button', { name: 'More' }); + const copyBtn = formatBar.getByRole('button', { name: 'Copy' }); + const duplicateBtn = formatBar.getByRole('button', { name: 'Duplicate' }); + const deleteBtn = formatBar.getByRole('button', { name: 'Delete' }); + const openMoreMenu = async () => { + await expect(formatBar).toBeVisible(); + await moreBtn.click(); + }; + + const assertBoundingBox = async (x: number, y: number) => { + const boundingBox = await formatBar.boundingBox(); + if (!boundingBox) { + throw new Error("formatBar doesn't exist"); + } + assertAlmostEqual(boundingBox.x, x, 6); + assertAlmostEqual(boundingBox.y, y, 6); + }; + + return { + formatBar, + boldBtn, + italicBtn, + underlineBtn, + strikeBtn, + codeBtn, + linkBtn, + highlight, + createLinkedDocBtn, + + openParagraphMenu, + textBtn, + h1Btn, + bulletedBtn, + codeBlockBtn, + + moreBtn, + openMoreMenu, + copyBtn, + duplicateBtn, + deleteBtn, + + assertBoundingBox, + }; +} + +export function getEmbedCardToolbar(page: Page) { + const embedCardToolbar = page.locator('.embed-card-toolbar'); + function createButtonLocator(name: string) { + return embedCardToolbar.getByRole('button', { name }); + } + const copyButton = createButtonLocator('copy'); + const editButton = createButtonLocator('edit'); + const cardStyleButton = createButtonLocator('card style'); + const captionButton = createButtonLocator('caption'); + const moreButton = createButtonLocator('more'); + + const cardStyleHorizontalButton = embedCardToolbar.getByRole('button', { + name: 'Large horizontal style', + }); + const cardStyleListButton = embedCardToolbar.getByRole('button', { + name: 'Small horizontal style', + }); + + const openCardStyleMenu = async () => { + await expect(embedCardToolbar).toBeVisible(); + await cardStyleButton.click(); + await waitNextFrame(page); + }; + + const openMoreMenu = async () => { + await expect(embedCardToolbar).toBeVisible(); + await moreButton.click(); + await waitNextFrame(page); + }; + + return { + embedCardToolbar, + copyButton, + editButton, + cardStyleButton, + captionButton, + moreButton, + openCardStyleMenu, + openMoreMenu, + cardStyleHorizontalButton, + cardStyleListButton, + }; +} diff --git a/blocksuite/tests-legacy/worker.spec.ts b/blocksuite/tests-legacy/worker.spec.ts new file mode 100644 index 0000000000000..813034cae36e7 --- /dev/null +++ b/blocksuite/tests-legacy/worker.spec.ts @@ -0,0 +1,33 @@ +import { expect } from '@playwright/test'; + +import { + enterPlaygroundRoom, + initEmptyParagraphState, +} from './utils/actions/index.js'; +import { test } from './utils/playwright.js'; + +declare global { + interface Window { + testWorker: Worker; + } +} + +test.skip('should the worker in the playground work fine.', async ({ + page, +}) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + + const ok = await page.evaluate(async () => { + return new Promise(resolve => { + window.testWorker.postMessage('ping'); + window.testWorker.addEventListener('message', event => { + if (event.data === 'pong') { + resolve(true); + } + }); + }); + }); + + expect(ok).toBeTruthy(); +}); diff --git a/blocksuite/tests-legacy/zero-width.spec.ts b/blocksuite/tests-legacy/zero-width.spec.ts new file mode 100644 index 0000000000000..d386969d5859d --- /dev/null +++ b/blocksuite/tests-legacy/zero-width.spec.ts @@ -0,0 +1,145 @@ +import './utils/declare-test-window.js'; + +import { + enterPlaygroundRoom, + initEmptyCodeBlockState, +} from './utils/actions/index.js'; +import { assertBlockChildrenIds, assertBlockFlavour } from './utils/asserts.js'; +import { scoped, test } from './utils/playwright.js'; + +const bookMarkUrl = 'http://localhost'; +const embedUrl = 'https://github.com/toeverything/blocksuite/pull/7217'; + +test.beforeEach(async ({ page }) => { + await page.route( + 'https://affine-worker.toeverything.workers.dev/api/worker/link-preview', + async route => { + await route.fulfill({ + json: {}, + }); + } + ); +}); + +test( + scoped`create a paragraph-block while clicking zero-width of code-block which is the last block of page`, + async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyCodeBlockState(page); + const codeComponent = page.locator('affine-code'); + const rect = await codeComponent.boundingBox(); + if (!rect) { + throw new Error('code-block not found'); + } + // click the zero width + await page.mouse.click(rect.x + 20, rect.y + rect.height + 8); + await assertBlockFlavour(page, '3', 'affine:paragraph'); + } +); + +test( + scoped`don't create a paragraph-block while clicking zero-width of code-block before a paragraph-block`, + async ({ page }) => { + await enterPlaygroundRoom(page); + await page.evaluate(() => { + const { doc } = window; + const rootId = doc.addBlock('affine:page', { + title: new doc.Text(), + }); + const note = doc.addBlock('affine:note', {}, rootId); + doc.addBlock('affine:code', {}, note); + doc.addBlock('affine:paragraph', {}, note); + }); + const codeComponent = page.locator('affine-code'); + const codeComponentrect = await codeComponent.boundingBox(); + if (!codeComponentrect) { + throw new Error('code-block not found'); + } + await page.mouse.click( + codeComponentrect.x + 20, + codeComponentrect.y + codeComponentrect.height + 8 + ); + await assertBlockChildrenIds(page, '1', ['2', '3']); + } +); + +test( + scoped`create a paragraph-block while clicking between two non-paragraph-block`, + async ({ page }) => { + await enterPlaygroundRoom(page); + await page.evaluate( + async ({ bookMarkUrl, embedUrl }) => { + const { doc } = window; + const rootId = doc.addBlock('affine:page', { + title: new doc.Text(), + }); + const note = doc.addBlock('affine:note', {}, rootId); + doc.addBlock('affine:code', {}, note); + doc.addBlock('affine:divider', {}, note); + doc.addBlock('affine:bookmark', { url: bookMarkUrl }, note); + await new Promise(res => setTimeout(res, 200)); + const pageRoot = document.querySelector('affine-page-root'); + if (!pageRoot) throw new Error('Cannot find doc page'); + const imageBlob = await fetch( + `${location.origin}/test-card-1.png` + ).then(response => response.blob()); + const storage = doc.blobSync; + const sourceId = await storage.set(imageBlob); + doc.addBlock('affine:image', { sourceId }, note); + doc.addBlock('affine:embed-github', { url: embedUrl }, note); + }, + { bookMarkUrl, embedUrl } + ); + const codeComponent = page.locator('affine-code'); + const codeComponentrect = await codeComponent.boundingBox(); + if (!codeComponentrect) { + throw new Error('code-block not found'); + } + await page.mouse.click( + codeComponentrect.x + 20, + codeComponentrect.y + codeComponentrect.height + 8 + ); + await assertBlockFlavour(page, '7', 'affine:paragraph'); + + const dividerComponent = page.locator('affine-divider'); + const dividerComponentRect = await dividerComponent.boundingBox(); + if (!dividerComponentRect) { + throw new Error('divider-block not found'); + } + await page.mouse.click( + dividerComponentRect.x + 20, + dividerComponentRect.y + dividerComponentRect.height + 8 + ); + await assertBlockFlavour(page, '8', 'affine:paragraph'); + + const bookmarkComponent = page.locator('affine-bookmark'); + const bookmarkComponentRect = await bookmarkComponent.boundingBox(); + if (!bookmarkComponentRect) { + throw new Error('bookmark-block not found'); + } + await page.mouse.click( + bookmarkComponentRect.x + 20, + bookmarkComponentRect.y + bookmarkComponentRect.height + 8 + ); + await assertBlockFlavour(page, '9', 'affine:paragraph'); + + await page.evaluate(() => { + const viewport = document.querySelector('.affine-page-viewport'); + if (!viewport) { + throw new Error(); + } + viewport.scrollTo(0, 600); + }); + + const imageComponent = page.locator('affine-image'); + const imageComponentRect = await imageComponent.boundingBox(); + if (!imageComponentRect) { + throw new Error('image-block not found'); + } + await page.mouse.click( + imageComponentRect.x + 20, + imageComponentRect.y + imageComponentRect.height + 8 + ); + await assertBlockFlavour(page, '10', 'affine:paragraph'); + } +); diff --git a/oxlint.json b/oxlint.json index e5fe6d31d522a..4b02f12885d80 100644 --- a/oxlint.json +++ b/oxlint.json @@ -194,6 +194,15 @@ "typescript/no-non-null-assertion": "off", "unicorn/prefer-array-some": "off" } + }, + { + "files": ["blocksuite/tests-legacy/**/*.ts"], + "rules": { + "typescript/ban-ts-comment": "off", + "unicorn/prefer-dom-node-dataset": "off", + "typescript/consistent-type-imports": "off", + "no-cycle": "off" + } } ] } diff --git a/package.json b/package.json index 69128e0b88bda..bb0a001e30e59 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ "@types/node": "^20.17.10", "@typescript-eslint/parser": "^8.18.0", "@vanilla-extract/vite-plugin": "^4.0.18", + "@vitest/browser": "2.1.8", "@vitest/coverage-istanbul": "2.1.8", "@vitest/ui": "2.1.8", "cross-env": "^7.0.3", diff --git a/scripts/vitest-global.js b/scripts/vitest-global.js new file mode 100644 index 0000000000000..370cddd32ad17 --- /dev/null +++ b/scripts/vitest-global.js @@ -0,0 +1,3 @@ +export const setup = () => { + process.env.TZ = 'Asia/Singapore'; +}; diff --git a/vitest.workspace.ts b/vitest.workspace.ts index 89cb8a2833816..87ef83e7695d8 100644 --- a/vitest.workspace.ts +++ b/vitest.workspace.ts @@ -1 +1,5 @@ -export default ['.', './packages/frontend/apps/electron']; +export default [ + '.', + './packages/frontend/apps/electron', + './blocksuite/**/*/vitest.config.ts', +]; diff --git a/yarn.lock b/yarn.lock index 427e86030ff79..1df85624e949a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -635,6 +635,7 @@ __metadata: "@types/node": "npm:^20.17.10" "@typescript-eslint/parser": "npm:^8.18.0" "@vanilla-extract/vite-plugin": "npm:^4.0.18" + "@vitest/browser": "npm:2.1.8" "@vitest/coverage-istanbul": "npm:2.1.8" "@vitest/ui": "npm:2.1.8" cross-env: "npm:^7.0.3" @@ -3413,6 +3414,7 @@ __metadata: "@lit/context": "npm:^1.1.2" "@preact/signals-core": "npm:^1.8.0" "@types/hast": "npm:^3.0.4" + dompurify: "npm:^3.1.6" fractional-indexing: "npm:^3.2.0" lib0: "npm:^0.2.97" lit: "npm:^3.2.0" @@ -3547,6 +3549,18 @@ __metadata: languageName: unknown linkType: soft +"@blocksuite/legacy-e2e@workspace:blocksuite/tests-legacy": + version: 0.0.0-use.local + resolution: "@blocksuite/legacy-e2e@workspace:blocksuite/tests-legacy" + dependencies: + "@blocksuite/affine-model": "workspace:*" + "@blocksuite/block-std": "workspace:*" + "@blocksuite/global": "workspace:*" + "@blocksuite/presets": "workspace:*" + "@playwright/test": "npm:=1.49.1" + languageName: unknown + linkType: soft + "@blocksuite/playground@workspace:blocksuite/playground": version: 0.0.0-use.local resolution: "@blocksuite/playground@workspace:blocksuite/playground" @@ -3575,6 +3589,8 @@ __metadata: lz-string: "npm:^1.5.0" magic-string: "npm:^0.30.11" tweakpane: "npm:^4.0.4" + vite: "npm:^6.0.3" + vite-plugin-istanbul: "npm:^6.0.2" vite-plugin-wasm: "npm:^3.3.0" vite-plugin-web-components-hmr: "npm:^0.1.3" y-indexeddb: "npm:^9.0.12" @@ -6914,6 +6930,19 @@ __metadata: languageName: node linkType: hard +"@istanbuljs/load-nyc-config@npm:^1.1.0": + version: 1.1.0 + resolution: "@istanbuljs/load-nyc-config@npm:1.1.0" + dependencies: + camelcase: "npm:^5.3.1" + find-up: "npm:^4.1.0" + get-package-type: "npm:^0.1.0" + js-yaml: "npm:^3.13.1" + resolve-from: "npm:^5.0.0" + checksum: 10/b000a5acd8d4fe6e34e25c399c8bdbb5d3a202b4e10416e17bfc25e12bab90bb56d33db6089ae30569b52686f4b35ff28ef26e88e21e69821d2b85884bd055b8 + languageName: node + linkType: hard + "@istanbuljs/schema@npm:^0.1.2, @istanbuljs/schema@npm:^0.1.3": version: 0.1.3 resolution: "@istanbuljs/schema@npm:0.1.3" @@ -13696,7 +13725,7 @@ __metadata: languageName: node linkType: hard -"@testing-library/user-event@npm:14.5.2": +"@testing-library/user-event@npm:14.5.2, @testing-library/user-event@npm:^14.5.2": version: 14.5.2 resolution: "@testing-library/user-event@npm:14.5.2" peerDependencies: @@ -15199,6 +15228,34 @@ __metadata: languageName: node linkType: hard +"@vitest/browser@npm:2.1.8": + version: 2.1.8 + resolution: "@vitest/browser@npm:2.1.8" + dependencies: + "@testing-library/dom": "npm:^10.4.0" + "@testing-library/user-event": "npm:^14.5.2" + "@vitest/mocker": "npm:2.1.8" + "@vitest/utils": "npm:2.1.8" + magic-string: "npm:^0.30.12" + msw: "npm:^2.6.4" + sirv: "npm:^3.0.0" + tinyrainbow: "npm:^1.2.0" + ws: "npm:^8.18.0" + peerDependencies: + playwright: "*" + vitest: 2.1.8 + webdriverio: "*" + peerDependenciesMeta: + playwright: + optional: true + safaridriver: + optional: true + webdriverio: + optional: true + checksum: 10/6063e02222440347bbc23b2c54e259078aa83a29869337b9ffd642be5a4321ac3ddf3c0bbe4eac5237eb0bb8b9fa17d21d2c31299376de407716e3c7dd3b704c + languageName: node + linkType: hard + "@vitest/coverage-istanbul@npm:2.1.8": version: 2.1.8 resolution: "@vitest/coverage-istanbul@npm:2.1.8" @@ -17217,7 +17274,7 @@ __metadata: languageName: node linkType: hard -"camelcase@npm:^5.0.0": +"camelcase@npm:^5.0.0, camelcase@npm:^5.3.1": version: 5.3.1 resolution: "camelcase@npm:5.3.1" checksum: 10/e6effce26b9404e3c0f301498184f243811c30dfe6d0b9051863bd8e4034d09c8c2923794f280d6827e5aa055f6c434115ff97864a16a963366fb35fd673024b @@ -21875,6 +21932,13 @@ __metadata: languageName: node linkType: hard +"get-package-type@npm:^0.1.0": + version: 0.1.0 + resolution: "get-package-type@npm:0.1.0" + checksum: 10/bba0811116d11e56d702682ddef7c73ba3481f114590e705fc549f4d868972263896af313c57a25c076e3c0d567e11d919a64ba1b30c879be985fc9d44f96148 + languageName: node + linkType: hard + "get-source@npm:^2.0.12": version: 2.0.12 resolution: "get-source@npm:2.0.12" @@ -23892,7 +23956,7 @@ __metadata: languageName: node linkType: hard -"istanbul-lib-instrument@npm:^6.0.3": +"istanbul-lib-instrument@npm:^6.0.2, istanbul-lib-instrument@npm:^6.0.3": version: 6.0.3 resolution: "istanbul-lib-instrument@npm:6.0.3" dependencies: @@ -24092,7 +24156,7 @@ __metadata: languageName: node linkType: hard -"js-yaml@npm:^3.14.1": +"js-yaml@npm:^3.13.1, js-yaml@npm:^3.14.1": version: 3.14.1 resolution: "js-yaml@npm:3.14.1" dependencies: @@ -26402,9 +26466,9 @@ __metadata: languageName: node linkType: hard -"msw@npm:^2.6.8": - version: 2.6.9 - resolution: "msw@npm:2.6.9" +"msw@npm:^2.6.4, msw@npm:^2.6.8": + version: 2.7.0 + resolution: "msw@npm:2.7.0" dependencies: "@bundled-es-modules/cookie": "npm:^2.0.1" "@bundled-es-modules/statuses": "npm:^1.0.1" @@ -26415,12 +26479,12 @@ __metadata: "@open-draft/until": "npm:^2.1.0" "@types/cookie": "npm:^0.6.0" "@types/statuses": "npm:^2.0.4" - chalk: "npm:^4.1.2" graphql: "npm:^16.8.1" headers-polyfill: "npm:^4.0.2" is-node-process: "npm:^1.2.0" outvariant: "npm:^1.4.3" path-to-regexp: "npm:^6.3.0" + picocolors: "npm:^1.1.1" strict-event-emitter: "npm:^0.5.1" type-fest: "npm:^4.26.1" yargs: "npm:^17.7.2" @@ -26431,7 +26495,7 @@ __metadata: optional: true bin: msw: cli/index.js - checksum: 10/20a74a5e49b780567aa3430c0de9f27830208f931d6109087a24566fcb3e68f058ff51b022891bfe6fe0f320afad22b4039593722b928aa9c4ab5b05c2746c4a + checksum: 10/165ccf37d90da0d5271fdb8e01f89f48f7a60fb810038ff73d18c0e5e5ddfdb1266002d19cde61b9ae689ef37c39499b10d9d07e0d16662a31630ce9adce1d77 languageName: node linkType: hard @@ -31111,7 +31175,7 @@ __metadata: languageName: node linkType: hard -"source-map@npm:^0.7.3": +"source-map@npm:^0.7.3, source-map@npm:^0.7.4": version: 0.7.4 resolution: "source-map@npm:0.7.4" checksum: 10/a0f7c9b797eda93139842fd28648e868a9a03ea0ad0d9fa6602a0c1f17b7fb6a7dcca00c144476cccaeaae5042e99a285723b1a201e844ad67221bf5d428f1dc @@ -32066,6 +32130,17 @@ __metadata: languageName: node linkType: hard +"test-exclude@npm:^6.0.0": + version: 6.0.0 + resolution: "test-exclude@npm:6.0.0" + dependencies: + "@istanbuljs/schema": "npm:^0.1.2" + glob: "npm:^7.1.4" + minimatch: "npm:^3.0.4" + checksum: 10/8fccb2cb6c8fcb6bb4115394feb833f8b6cf4b9503ec2485c2c90febf435cac62abe882a0c5c51a37b9bbe70640cdd05acf5f45e486ac4583389f4b0855f69e5 + languageName: node + linkType: hard + "test-exclude@npm:^7.0.1": version: 7.0.1 resolution: "test-exclude@npm:7.0.1" @@ -33478,6 +33553,22 @@ __metadata: languageName: node linkType: hard +"vite-plugin-istanbul@npm:^6.0.2": + version: 6.0.2 + resolution: "vite-plugin-istanbul@npm:6.0.2" + dependencies: + "@istanbuljs/load-nyc-config": "npm:^1.1.0" + espree: "npm:^10.0.1" + istanbul-lib-instrument: "npm:^6.0.2" + picocolors: "npm:^1.0.0" + source-map: "npm:^0.7.4" + test-exclude: "npm:^6.0.0" + peerDependencies: + vite: ">=4 <=6" + checksum: 10/fddd8367fa02159a047179c9c576c309f9a14bb970116e35fe543d40e3acc615c1671387b9de2394a5bc75d2ab0cfb7b7ebb9e29ec4ddff5411fa7bfee663e42 + languageName: node + linkType: hard + "vite-plugin-wasm@npm:^3.3.0": version: 3.4.1 resolution: "vite-plugin-wasm@npm:3.4.1" From 3a82da0e5b2b7da0ac192061daa432648d84fde1 Mon Sep 17 00:00:00 2001 From: Saul-Mirone Date: Fri, 20 Dec 2024 16:48:10 +0000 Subject: [PATCH 05/17] chore: fix eslint in blocksuite (#9232) --- .../components/fullscreen-toolbar.ts | 2 +- .../embed-linked-doc-block.ts | 6 +- .../embed-synced-doc-block.ts | 4 +- .../affine/block-list/src/list-block.ts | 2 +- .../block-paragraph/src/paragraph-block.ts | 6 +- .../src/managers/connector-manager.ts | 2 +- .../src/renderer/canvas-renderer.ts | 4 +- .../affine/block-surface/src/surface-block.ts | 2 +- .../affine/block-surface/src/surface-model.ts | 2 +- .../affine/block-surface/src/utils/a-star.ts | 19 +-- .../affine/block-surface/src/utils/graph.ts | 12 +- .../block-surface/src/utils/priority-queue.ts | 2 +- .../block-surface/src/utils/rough/canvas.ts | 6 +- .../src/utils/rough/fillers/dashed-filler.ts | 2 +- .../src/utils/rough/fillers/dot-filler.ts | 2 +- .../src/utils/rough/fillers/hachure-filler.ts | 2 +- .../utils/rough/fillers/zigzag-line-filler.ts | 2 +- .../src/utils/rough/generator.ts | 2 +- .../block-surface/src/utils/rough/svg.ts | 4 +- .../affine/block-surface/src/view/mindmap.ts | 2 +- .../components/src/context-menu/input.ts | 14 +-- .../src/context-menu/menu-renderer.ts | 4 +- .../components/src/context-menu/menu.ts | 4 +- .../components/src/date-picker/date-picker.ts | 4 +- .../components/src/date-picker/utils.ts | 2 +- .../affine/components/src/peek/controller.ts | 6 +- .../presets/nodes/link-node/affine-link.ts | 4 +- .../nodes/link-node/link-popup/link-popup.ts | 18 +-- .../reference-node/reference-alias-popup.ts | 4 +- .../nodes/reference-node/reference-node.ts | 4 +- .../nodes/reference-node/reference-popup.ts | 10 +- .../components/src/rich-text/rich-text.ts | 14 ++- .../affine/components/src/toolbar/tooltip.ts | 2 +- .../src/virtual-keyboard/controller.ts | 4 +- .../core/component/tags/multi-tag-select.ts | 12 +- .../affine/data-view/src/core/data-view.ts | 6 +- .../affine/data-view/src/core/detail/field.ts | 2 +- .../data-view/src/core/detail/selection.ts | 2 +- .../core/group-by/renderer/number-group.ts | 2 +- .../core/group-by/renderer/select-group.ts | 2 +- .../core/group-by/renderer/string-group.ts | 2 +- .../data-view/src/core/group-by/trait.ts | 6 +- .../data-view/src/core/logical/data-type.ts | 4 +- .../data-view/src/core/logical/matcher.ts | 4 +- .../data-view/src/core/logical/type-system.ts | 2 +- .../affine/data-view/src/core/sort/manager.ts | 2 +- .../src/core/utils/wc-dnd/dnd-context.ts | 14 +-- .../src/core/utils/wc-dnd/sensors/mouse.ts | 4 +- .../src/core/utils/wc-dnd/utils/listeners.ts | 2 +- .../src/core/view-manager/single-view.ts | 4 +- .../property-presets/date/cell-renderer.ts | 4 +- .../multi-select/cell-renderer.ts | 2 +- .../property-presets/number/cell-renderer.ts | 6 +- .../property-presets/select/cell-renderer.ts | 2 +- .../property-presets/text/cell-renderer.ts | 4 +- .../src/view-presets/kanban/mobile/card.ts | 4 +- .../src/view-presets/kanban/mobile/cell.ts | 2 +- .../src/view-presets/kanban/mobile/group.ts | 6 +- .../src/view-presets/kanban/pc/card.ts | 6 +- .../src/view-presets/kanban/pc/cell.ts | 2 +- .../kanban/pc/controller/clipboard.ts | 4 +- .../view-presets/kanban/pc/controller/drag.ts | 2 +- .../kanban/pc/controller/hotkeys.ts | 2 +- .../kanban/pc/controller/selection.ts | 2 +- .../src/view-presets/kanban/pc/group.ts | 6 +- .../src/view-presets/kanban/pc/header.ts | 2 +- .../src/view-presets/kanban/pc/kanban-view.ts | 2 +- .../src/view-presets/table/mobile/cell.ts | 2 +- .../table/mobile/column-header.ts | 2 +- .../src/view-presets/table/mobile/group.ts | 8 +- .../src/view-presets/table/mobile/header.ts | 2 +- .../view-presets/table/mobile/table-view.ts | 2 +- .../src/view-presets/table/pc/cell.ts | 2 +- .../table/pc/controller/clipboard.ts | 6 +- .../view-presets/table/pc/controller/drag.ts | 2 +- .../table/pc/controller/hotkeys.ts | 2 +- .../table/pc/controller/selection.ts | 2 +- .../src/view-presets/table/pc/group.ts | 8 +- .../table/pc/header/column-header.ts | 2 +- .../table/pc/header/database-header-column.ts | 14 +-- .../table/pc/header/number-format-bar.ts | 4 +- .../src/view-presets/table/pc/row/row.ts | 2 +- .../view-presets/table/table-view-manager.ts | 12 +- .../filter/condition-view.ts | 4 +- .../filter/group-panel-view.ts | 8 +- .../quick-setting-bar/filter/list-view.ts | 6 +- .../filter/root-panel-view.ts | 4 +- .../tools/presets/search/search.ts | 10 +- .../tools/presets/table-add-row/add-row.ts | 2 +- .../model/src/elements/mindmap/mindmap.ts | 2 +- .../model/src/elements/mindmap/style.ts | 12 +- .../shared/src/services/edit-props-store.ts | 4 +- .../src/services/embed-option-service.ts | 2 +- .../shared/src/services/theme-service.ts | 4 +- .../shared/src/utils/spec/spec-provider.ts | 2 +- .../src/scroll-anchoring.ts | 6 +- .../src/_common/adapters/html-adapter/html.ts | 6 +- .../src/_common/adapters/markdown/markdown.ts | 4 +- .../blocks/src/_common/adapters/mix-text.ts | 2 +- .../adapters/notion-html/notion-html.ts | 2 +- .../components/ai-item/ai-item-list.ts | 6 +- .../components/ai-item/ai-sub-item-list.ts | 2 +- .../modal/embed-card-create-modal.ts | 6 +- .../embed-card/modal/embed-card-edit-modal.ts | 12 +- .../_common/components/file-drop-manager.ts | 8 +- .../src/_common/components/smooth-corner.ts | 2 +- .../_common/configs/quick-action/config.ts | 22 ++-- .../_common/export-manager/export-manager.ts | 4 +- .../blocks/src/_common/transformers/utils.ts | 2 +- .../attachment-block/attachment-service.ts | 2 +- .../blocks/src/attachment-block/embed.ts | 2 +- .../blocks/src/data-view-block/data-source.ts | 11 +- .../src/data-view-block/data-view-block.ts | 4 +- .../database-block/components/title/index.ts | 10 +- .../src/database-block/database-block.ts | 6 +- .../properties/link/cell-renderer.ts | 10 +- .../properties/rich-text/cell-renderer.ts | 6 +- .../database-block/properties/title/text.ts | 6 +- .../edgeless-text-block.ts | 2 +- .../blocks/src/image-block/image-service.ts | 2 +- .../blocks/src/note-block/note-service.ts | 28 ++--- .../blocks/src/root-block/clipboard/index.ts | 2 +- .../root-block/clipboard/middlewares/paste.ts | 16 +-- .../edgeless/clipboard/clipboard.ts | 10 +- .../auto-complete/edgeless-auto-complete.ts | 2 +- .../components/auto-complete/utils.ts | 6 +- .../components/color-picker/button.ts | 2 +- .../components/color-picker/color-picker.ts | 4 +- .../components/frame/frame-preview.ts | 9 +- .../components/panel/line-width-panel.ts | 15 ++- .../edgeless/components/panel/scale-panel.ts | 2 +- .../edgeless/components/panel/size-panel.ts | 2 +- .../rects/edgeless-selected-rect.ts | 26 ++-- .../components/resize/resize-manager.ts | 8 +- .../text/edgeless-connector-label-editor.ts | 2 +- .../components/text/edgeless-text-editor.ts | 2 +- .../components/toolbar/brush/brush-menu.ts | 2 +- .../toolbar/brush/brush-tool-button.ts | 2 +- .../toolbar/connector/connector-menu.ts | 2 +- .../connector/connector-tool-button.ts | 2 +- .../components/toolbar/edgeless-toolbar.ts | 8 +- .../toolbar/lasso/lasso-tool-button.ts | 2 +- .../toolbar/mindmap/mindmap-menu.ts | 2 +- .../toolbar/mindmap/mindmap-tool-button.ts | 2 +- .../toolbar/note/note-senior-button.ts | 4 +- .../toolbar/note/note-tool-button.ts | 2 +- .../present/navigator-setting-button.ts | 2 +- .../components/toolbar/shape/shape-menu.ts | 8 +- .../toolbar/shape/shape-tool-button.ts | 4 +- .../toolbar/shape/shape-tool-element.ts | 18 +-- .../toolbar/template/overlay-scrollbar.ts | 2 +- .../edgeless/edgeless-root-block.ts | 33 ++--- .../edgeless/edgeless-root-preview-block.ts | 23 ++-- .../edgeless/edgeless-root-service.ts | 2 +- .../src/root-block/edgeless/frame-manager.ts | 4 +- .../edgeless/gfx-tool/brush-tool.ts | 2 +- .../mind-map-ext/mind-map-ext.ts | 2 +- .../edgeless/gfx-tool/default-tool.ts | 16 +-- .../edgeless/gfx-tool/eraser-tool.ts | 6 +- .../edgeless/gfx-tool/lasso-tool.ts | 4 +- .../root-block/keyboard/keyboard-manager.ts | 24 ++-- .../remote-color-manager/color-picker.ts | 2 +- .../blocks/src/root-block/root-service.ts | 2 +- .../root-block/widgets/ai-panel/ai-panel.ts | 16 +-- .../ai-panel/components/state/error.ts | 2 +- .../ai-panel/components/state/input.ts | 6 +- .../code-toolbar/components/lang-button.ts | 2 +- .../root-block/widgets/code-toolbar/index.ts | 2 +- .../doc-remote-selection.ts | 6 +- .../root-block/widgets/drag-handle/config.ts | 2 +- .../widgets/drag-handle/drag-handle.ts | 26 ++-- .../drag-handle/helpers/preview-helper.ts | 4 +- .../drag-handle/helpers/rect-helper.ts | 2 +- .../watchers/drag-event-watcher.ts | 26 ++-- .../drag-handle/watchers/edgeless-watcher.ts | 12 +- .../watchers/handle-event-watcher.ts | 8 +- .../watchers/keyboard-event-watcher.ts | 2 +- .../watchers/legacy-drag-event-watcher.ts | 18 +-- .../watchers/pointer-event-watcher.ts | 115 +++++++++--------- .../edgeless-auto-connect.ts | 2 +- .../edgeless-copilot-panel/toolbar-entry.ts | 2 +- .../edgeless-remote-selection/index.ts | 10 +- .../element-toolbar/add-frame-button.ts | 2 +- .../element-toolbar/add-group-button.ts | 2 +- .../change-attachment-button.ts | 6 +- .../element-toolbar/change-brush-button.ts | 4 +- .../change-embed-card-button.ts | 26 ++-- .../element-toolbar/change-image-button.ts | 4 +- .../element-toolbar/change-mindmap-button.ts | 4 +- .../element-toolbar/change-note-button.ts | 4 +- .../element-toolbar/change-text-menu.ts | 14 ++- .../widgets/element-toolbar/index.ts | 6 +- .../element-toolbar/more-menu/context.ts | 8 +- .../embed-card-toolbar/embed-card-toolbar.ts | 14 +-- .../root-block/widgets/format-bar/config.ts | 46 +++---- .../root-block/widgets/image-toolbar/index.ts | 2 +- .../widgets/keyboard-toolbar/index.ts | 2 +- .../keyboard-toolbar/keyboard-toolbar.ts | 2 +- .../linked-doc/import-doc/import-doc.ts | 8 +- .../root-block/widgets/linked-doc/index.ts | 2 +- .../widgets/linked-doc/linked-doc-popover.ts | 6 +- .../linked-doc/mobile-linked-doc-menu.ts | 4 +- .../page-dragging-area/page-dragging-area.ts | 2 +- .../src/root-block/widgets/pie-menu/index.ts | 4 +- .../src/root-block/widgets/pie-menu/menu.ts | 6 +- .../src/root-block/widgets/pie-menu/node.ts | 6 +- .../widgets/pie-menu/pie-manager.ts | 2 +- .../root-block/widgets/slash-menu/config.ts | 14 ++- .../root-block/widgets/slash-menu/index.ts | 10 +- .../widgets/slash-menu/slash-menu-popover.ts | 26 ++-- .../surface-ref-toolbar.ts | 2 +- .../surface-ref-block/surface-ref-block.ts | 5 +- .../block-std/src/clipboard/index.ts | 6 +- .../block-std/src/command/manager.ts | 6 +- .../block-std/src/event/control/clipboard.ts | 8 +- .../block-std/src/event/control/keyboard.ts | 8 +- .../block-std/src/event/control/pointer.ts | 44 +++---- .../block-std/src/event/control/range.ts | 12 +- .../block-std/src/event/dispatcher.ts | 8 +- .../framework/block-std/src/gfx/controller.ts | 2 +- .../framework/block-std/src/gfx/grid.ts | 14 ++- .../framework/block-std/src/gfx/keyboard.ts | 2 +- .../framework/block-std/src/gfx/layer.ts | 4 +- .../src/gfx/model/surface/element-model.ts | 2 +- .../gfx/model/surface/local-element-model.ts | 2 +- .../block-std/src/gfx/tool/tool-controller.ts | 8 +- .../block-std/src/gfx/view/view-manager.ts | 6 +- .../framework/block-std/src/gfx/view/view.ts | 5 +- .../block-std/src/gfx/viewport-element.ts | 6 +- .../block-std/src/range/range-binding.ts | 12 +- .../block-std/src/selection/manager.ts | 14 ++- .../src/view/element/block-component.ts | 2 +- .../src/view/element/gfx-block-component.ts | 8 +- .../block-std/src/view/element/lit-host.ts | 2 +- .../block-std/src/view/view-store.ts | 2 +- .../framework/inline/src/inline-editor.ts | 3 +- .../framework/inline/src/services/event.ts | 16 +-- .../framework/inline/src/services/render.ts | 5 +- .../framework/store/src/adapter/base.ts | 4 +- .../framework/store/src/adapter/context.ts | 2 +- .../framework/store/src/reactive/proxy.ts | 4 +- .../framework/store/src/reactive/text.ts | 4 +- blocksuite/framework/store/src/schema/base.ts | 8 +- .../store/src/store/doc/block-collection.ts | 18 +-- .../store/src/store/doc/block/index.ts | 2 +- .../framework/store/src/store/doc/doc.ts | 2 +- blocksuite/framework/store/src/store/meta.ts | 2 +- .../framework/store/src/yjs/awareness.ts | 4 +- blocksuite/framework/store/src/yjs/doc.ts | 2 +- .../framework/sync/src/doc/impl/broadcast.ts | 2 +- .../components/attachment-viewer-panel.ts | 8 +- .../_common/components/collab-debug-menu.ts | 8 +- .../_common/components/starter-debug-menu.ts | 4 +- .../apps/_common/sync/websocket/awareness.ts | 7 +- .../apps/_common/sync/websocket/doc.ts | 2 +- .../scripts/hmr-plugin/fine-tune.ts | 2 +- .../presets/src/editors/editor-container.ts | 16 +-- .../src/fragments/comment/comment-input.ts | 4 +- .../doc-meta-tags/backlink-popover.ts | 4 +- .../src/fragments/doc-title/doc-title.ts | 4 +- .../frame-panel/body/frame-panel-body.ts | 8 +- .../card/frame-card-title-editor.ts | 2 +- .../frame-panel/card/frame-card-title.ts | 6 +- .../frame-panel/header/frame-panel-header.ts | 6 +- .../frame-panel/header/frames-setting-menu.ts | 6 +- .../outline/body/outline-panel-body.ts | 2 +- .../src/fragments/outline/outline-panel.ts | 6 +- .../src/fragments/outline/outline-viewer.ts | 4 +- eslint.config.mjs | 21 +++- 269 files changed, 941 insertions(+), 848 deletions(-) diff --git a/blocksuite/affine/block-embed/src/embed-html-block/components/fullscreen-toolbar.ts b/blocksuite/affine/block-embed/src/embed-html-block/components/fullscreen-toolbar.ts index 5a64af26d50e4..7eda881411344 100644 --- a/blocksuite/affine/block-embed/src/embed-html-block/components/fullscreen-toolbar.ts +++ b/blocksuite/affine/block-embed/src/embed-html-block/components/fullscreen-toolbar.ts @@ -68,7 +68,7 @@ export class EmbedHtmlFullscreenToolbar extends LitElement { } `; - private _popSettings = () => { + private readonly _popSettings = () => { this._popperVisible = true; popMenu(popupTargetFromElement(this._fullScreenToolbarContainer), { options: { diff --git a/blocksuite/affine/block-embed/src/embed-linked-doc-block/embed-linked-doc-block.ts b/blocksuite/affine/block-embed/src/embed-linked-doc-block/embed-linked-doc-block.ts index 83fcf51cc24f4..4c830ff00d175 100644 --- a/blocksuite/affine/block-embed/src/embed-linked-doc-block/embed-linked-doc-block.ts +++ b/blocksuite/affine/block-embed/src/embed-linked-doc-block/embed-linked-doc-block.ts @@ -48,7 +48,7 @@ import { getEmbedLinkedDocIcons } from './utils.js'; export class EmbedLinkedDocBlockComponent extends EmbedBlockComponent { static override styles = styles; - private _load = async () => { + private readonly _load = async () => { const { loading = true, isError = false, @@ -103,7 +103,7 @@ export class EmbedLinkedDocBlockComponent extends EmbedBlockComponent { + private readonly _selectBlock = () => { const selectionManager = this.host.selection; const blockSelection = selectionManager.create('block', { blockId: this.blockId, @@ -111,7 +111,7 @@ export class EmbedLinkedDocBlockComponent extends EmbedBlockComponent { + private readonly _setDocUpdatedAt = () => { const meta = this.doc.collection.meta.getDocMeta(this.model.pageId); if (meta) { const date = meta.updatedDate || meta.createDate; diff --git a/blocksuite/affine/block-embed/src/embed-synced-doc-block/embed-synced-doc-block.ts b/blocksuite/affine/block-embed/src/embed-synced-doc-block/embed-synced-doc-block.ts index cef87d17082f8..5335445076057 100644 --- a/blocksuite/affine/block-embed/src/embed-synced-doc-block/embed-synced-doc-block.ts +++ b/blocksuite/affine/block-embed/src/embed-synced-doc-block/embed-synced-doc-block.ts @@ -55,7 +55,7 @@ export class EmbedSyncedDocBlockComponent extends EmbedBlockComponent { + private readonly _initEdgelessFitEffect = () => { const fitToContent = () => { if (this.isPageMode) return; @@ -99,7 +99,7 @@ export class EmbedSyncedDocBlockComponent extends EmbedBlockComponent {}); }; - private _pageFilter: Query = { + private readonly _pageFilter: Query = { mode: 'loose', match: [ { diff --git a/blocksuite/affine/block-list/src/list-block.ts b/blocksuite/affine/block-list/src/list-block.ts index 880d101777669..22a076468bdfb 100644 --- a/blocksuite/affine/block-list/src/list-block.ts +++ b/blocksuite/affine/block-list/src/list-block.ts @@ -36,7 +36,7 @@ export class ListBlockComponent extends CaptionedBlockComponent< private _inlineRangeProvider: InlineRangeProvider | null = null; - private _onClickIcon = (e: MouseEvent) => { + private readonly _onClickIcon = (e: MouseEvent) => { e.stopPropagation(); if (this.model.type === 'toggle') { diff --git a/blocksuite/affine/block-paragraph/src/paragraph-block.ts b/blocksuite/affine/block-paragraph/src/paragraph-block.ts index b6151681de92d..169ca49b03ad4 100644 --- a/blocksuite/affine/block-paragraph/src/paragraph-block.ts +++ b/blocksuite/affine/block-paragraph/src/paragraph-block.ts @@ -34,13 +34,13 @@ export class ParagraphBlockComponent extends CaptionedBlockComponent< > { static override styles = paragraphBlockStyles; - private _composing = signal(false); + private readonly _composing = signal(false); - private _displayPlaceholder = signal(false); + private readonly _displayPlaceholder = signal(false); private _inlineRangeProvider: InlineRangeProvider | null = null; - private _isInDatabase = () => { + private readonly _isInDatabase = () => { let parent = this.parentElement; while (parent && parent !== document.body) { if (parent.tagName.toLowerCase() === 'affine-database') { diff --git a/blocksuite/affine/block-surface/src/managers/connector-manager.ts b/blocksuite/affine/block-surface/src/managers/connector-manager.ts index 7cbfc3c0174f0..f9e4ddbaac0b5 100644 --- a/blocksuite/affine/block-surface/src/managers/connector-manager.ts +++ b/blocksuite/affine/block-surface/src/managers/connector-manager.ts @@ -1149,7 +1149,7 @@ export class PathGenerator { export class ConnectorPathGenerator extends PathGenerator { constructor( - private options: { + private readonly options: { getElementById: (id: string) => GfxModel | null; } ) { diff --git a/blocksuite/affine/block-surface/src/renderer/canvas-renderer.ts b/blocksuite/affine/block-surface/src/renderer/canvas-renderer.ts index 3c1e242e070d4..68578c1cf6361 100644 --- a/blocksuite/affine/block-surface/src/renderer/canvas-renderer.ts +++ b/blocksuite/affine/block-surface/src/renderer/canvas-renderer.ts @@ -42,9 +42,9 @@ type RendererOptions = { export class CanvasRenderer { private _container!: HTMLElement; - private _disposables = new DisposableGroup(); + private readonly _disposables = new DisposableGroup(); - private _overlays = new Set(); + private readonly _overlays = new Set(); private _refreshRafId: number | null = null; diff --git a/blocksuite/affine/block-surface/src/surface-block.ts b/blocksuite/affine/block-surface/src/surface-block.ts index f52975088a3fc..6b8558c4a2bee 100644 --- a/blocksuite/affine/block-surface/src/surface-block.ts +++ b/blocksuite/affine/block-surface/src/surface-block.ts @@ -100,7 +100,7 @@ export class SurfaceBlockComponent extends BlockComponent< private _cachedViewport = new Bound(); - private _initThemeObserver = () => { + private readonly _initThemeObserver = () => { const theme = this.std.get(ThemeProvider); this.disposables.add(theme.theme$.subscribe(() => this.requestUpdate())); }; diff --git a/blocksuite/affine/block-surface/src/surface-model.ts b/blocksuite/affine/block-surface/src/surface-model.ts index 30e99abff68d5..586999132490e 100644 --- a/blocksuite/affine/block-surface/src/surface-model.ts +++ b/blocksuite/affine/block-surface/src/surface-model.ts @@ -34,7 +34,7 @@ export const SurfaceBlockSchema = defineBlockSchema({ export type SurfaceMiddleware = (surface: SurfaceBlockModel) => () => void; export class SurfaceBlockModel extends BaseSurfaceModel { - private _disposables: DisposableGroup = new DisposableGroup(); + private readonly _disposables: DisposableGroup = new DisposableGroup(); override _init() { this._extendElement(elementsCtorMap); diff --git a/blocksuite/affine/block-surface/src/utils/a-star.ts b/blocksuite/affine/block-surface/src/utils/a-star.ts index bee00280d04c8..ce56976a3f9f9 100644 --- a/blocksuite/affine/block-surface/src/utils/a-star.ts +++ b/blocksuite/affine/block-surface/src/utils/a-star.ts @@ -33,24 +33,27 @@ function pointAlmostEqual(a: IVec3, b: IVec3): boolean { } export class AStarRunner { - private _cameFrom = new Map(); + private readonly _cameFrom = new Map< + IVec3, + { from: IVec3[]; indexs: number[] } + >(); private _complete = false; - private _costSoFar = new Map(); + private readonly _costSoFar = new Map(); private _current: IVec3 | null = null; - private _diagonalCount = new Map(); + private readonly _diagonalCount = new Map(); private _frontier!: PriorityQueue< IVec3, [diagonalCount: number, pointPriority: number, distCost: number] >; - private _graph: Graph; + private readonly _graph: Graph; - private _pointPriority = new Map(); + private readonly _pointPriority = new Map(); get path() { const result: IVec3[] = []; @@ -72,9 +75,9 @@ export class AStarRunner { constructor( points: IVec3[], - private _sp: IVec3, - private _ep: IVec3, - private _originalSp: IVec3, + private readonly _sp: IVec3, + private readonly _ep: IVec3, + private readonly _originalSp: IVec3, private _originalEp: IVec3, blocks: Bound[] = [], expandBlocks: Bound[] = [] diff --git a/blocksuite/affine/block-surface/src/utils/graph.ts b/blocksuite/affine/block-surface/src/utils/graph.ts index 1440f7879d489..3d57875960b37 100644 --- a/blocksuite/affine/block-surface/src/utils/graph.ts +++ b/blocksuite/affine/block-surface/src/utils/graph.ts @@ -27,15 +27,15 @@ function arrayAlmostEqual(point: IVec | IVec3, point2: IVec | IVec3) { } export class Graph { - private _xMap = new Map(); + private readonly _xMap = new Map(); - private _yMap = new Map(); + private readonly _yMap = new Map(); constructor( - private points: V[], - private blocks: Bound[] = [], - private expandedBlocks: Bound[] = [], - private excludedPoints: V[] = [] + private readonly points: V[], + private readonly blocks: Bound[] = [], + private readonly expandedBlocks: Bound[] = [], + private readonly excludedPoints: V[] = [] ) { const xMap = this._xMap; const yMap = this._yMap; diff --git a/blocksuite/affine/block-surface/src/utils/priority-queue.ts b/blocksuite/affine/block-surface/src/utils/priority-queue.ts index 842e709fdb78e..a5df2b5c034b2 100644 --- a/blocksuite/affine/block-surface/src/utils/priority-queue.ts +++ b/blocksuite/affine/block-surface/src/utils/priority-queue.ts @@ -6,7 +6,7 @@ type PriorityQueueNode = { export class PriorityQueue { heap: PriorityQueueNode[] = []; - constructor(private _compare: (a: K, b: K) => number) {} + constructor(private readonly _compare: (a: K, b: K) => number) {} bubbleDown(): void { let index = 0; diff --git a/blocksuite/affine/block-surface/src/utils/rough/canvas.ts b/blocksuite/affine/block-surface/src/utils/rough/canvas.ts index 0a23cb33a53b0..bc990a36bbfbc 100644 --- a/blocksuite/affine/block-surface/src/utils/rough/canvas.ts +++ b/blocksuite/affine/block-surface/src/utils/rough/canvas.ts @@ -9,11 +9,11 @@ import { RoughGenerator } from './generator.js'; import type { Point } from './geometry.js'; export class RoughCanvas { - private canvas: HTMLCanvasElement; + private readonly canvas: HTMLCanvasElement; - private ctx: CanvasRenderingContext2D; + private readonly ctx: CanvasRenderingContext2D; - private gen: RoughGenerator; + private readonly gen: RoughGenerator; get generator(): RoughGenerator { return this.gen; diff --git a/blocksuite/affine/block-surface/src/utils/rough/fillers/dashed-filler.ts b/blocksuite/affine/block-surface/src/utils/rough/fillers/dashed-filler.ts index 3b698a7cacd64..20c113ced2f15 100644 --- a/blocksuite/affine/block-surface/src/utils/rough/fillers/dashed-filler.ts +++ b/blocksuite/affine/block-surface/src/utils/rough/fillers/dashed-filler.ts @@ -5,7 +5,7 @@ import type { PatternFiller, RenderHelper } from './filler-interface.js'; import { polygonHachureLines } from './scan-line-hachure.js'; export class DashedFiller implements PatternFiller { - private helper: RenderHelper; + private readonly helper: RenderHelper; constructor(helper: RenderHelper) { this.helper = helper; diff --git a/blocksuite/affine/block-surface/src/utils/rough/fillers/dot-filler.ts b/blocksuite/affine/block-surface/src/utils/rough/fillers/dot-filler.ts index b208f350f635d..bb08033499632 100644 --- a/blocksuite/affine/block-surface/src/utils/rough/fillers/dot-filler.ts +++ b/blocksuite/affine/block-surface/src/utils/rough/fillers/dot-filler.ts @@ -5,7 +5,7 @@ import type { PatternFiller, RenderHelper } from './filler-interface.js'; import { polygonHachureLines } from './scan-line-hachure.js'; export class DotFiller implements PatternFiller { - private helper: RenderHelper; + private readonly helper: RenderHelper; constructor(helper: RenderHelper) { this.helper = helper; diff --git a/blocksuite/affine/block-surface/src/utils/rough/fillers/hachure-filler.ts b/blocksuite/affine/block-surface/src/utils/rough/fillers/hachure-filler.ts index a9cacdd336646..b4492b6db4dd0 100644 --- a/blocksuite/affine/block-surface/src/utils/rough/fillers/hachure-filler.ts +++ b/blocksuite/affine/block-surface/src/utils/rough/fillers/hachure-filler.ts @@ -4,7 +4,7 @@ import type { PatternFiller, RenderHelper } from './filler-interface.js'; import { polygonHachureLines } from './scan-line-hachure.js'; export class HachureFiller implements PatternFiller { - private helper: RenderHelper; + private readonly helper: RenderHelper; constructor(helper: RenderHelper) { this.helper = helper; diff --git a/blocksuite/affine/block-surface/src/utils/rough/fillers/zigzag-line-filler.ts b/blocksuite/affine/block-surface/src/utils/rough/fillers/zigzag-line-filler.ts index 3b9c847d8b59e..797578a856f18 100644 --- a/blocksuite/affine/block-surface/src/utils/rough/fillers/zigzag-line-filler.ts +++ b/blocksuite/affine/block-surface/src/utils/rough/fillers/zigzag-line-filler.ts @@ -5,7 +5,7 @@ import type { PatternFiller, RenderHelper } from './filler-interface.js'; import { polygonHachureLines } from './scan-line-hachure.js'; export class ZigZagLineFiller implements PatternFiller { - private helper: RenderHelper; + private readonly helper: RenderHelper; constructor(helper: RenderHelper) { this.helper = helper; diff --git a/blocksuite/affine/block-surface/src/utils/rough/generator.ts b/blocksuite/affine/block-surface/src/utils/rough/generator.ts index d123b701b04cc..e7e1da2a7ca92 100644 --- a/blocksuite/affine/block-surface/src/utils/rough/generator.ts +++ b/blocksuite/affine/block-surface/src/utils/rough/generator.ts @@ -28,7 +28,7 @@ import { const NOS = 'none'; export class RoughGenerator { - private config: Config; + private readonly config: Config; defaultOptions: ResolvedOptions = { maxRandomnessOffset: 2, diff --git a/blocksuite/affine/block-surface/src/utils/rough/svg.ts b/blocksuite/affine/block-surface/src/utils/rough/svg.ts index 3c754fca47045..47412708569d1 100644 --- a/blocksuite/affine/block-surface/src/utils/rough/svg.ts +++ b/blocksuite/affine/block-surface/src/utils/rough/svg.ts @@ -10,9 +10,9 @@ import { RoughGenerator } from './generator.js'; import type { Point } from './geometry.js'; export class RoughSVG { - private gen: RoughGenerator; + private readonly gen: RoughGenerator; - private svg: SVGSVGElement; + private readonly svg: SVGSVGElement; get generator(): RoughGenerator { return this.gen; diff --git a/blocksuite/affine/block-surface/src/view/mindmap.ts b/blocksuite/affine/block-surface/src/view/mindmap.ts index f38194f8f8daf..be6cff91c5ac2 100644 --- a/blocksuite/affine/block-surface/src/view/mindmap.ts +++ b/blocksuite/affine/block-surface/src/view/mindmap.ts @@ -15,7 +15,7 @@ import { handleLayout } from '../utils/mindmap/utils.js'; export class MindMapView extends GfxElementModelView { static override type = 'mindmap'; - private _collapseButtons = new Map(); + private readonly _collapseButtons = new Map(); private _hoveredState = new Map< string, diff --git a/blocksuite/affine/components/src/context-menu/input.ts b/blocksuite/affine/components/src/context-menu/input.ts index dfe47cb56f5bd..d60af8500e980 100644 --- a/blocksuite/affine/components/src/context-menu/input.ts +++ b/blocksuite/affine/components/src/context-menu/input.ts @@ -45,17 +45,17 @@ export class MenuInput extends MenuFocusable { } `; - private onCompositionEnd = () => { + private readonly onCompositionEnd = () => { this.data.onChange?.(this.inputRef.value); }; - private onInput = (e: InputEvent) => { + private readonly onInput = (e: InputEvent) => { e.stopPropagation(); if (e.isComposing) return; this.data.onChange?.(this.inputRef.value); }; - private onKeydown = (e: KeyboardEvent) => { + private readonly onKeydown = (e: KeyboardEvent) => { e.stopPropagation(); if (e.isComposing) return; if (e.key === 'Escape') { @@ -71,7 +71,7 @@ export class MenuInput extends MenuFocusable { } }; - private stopPropagation = (e: Event) => { + private readonly stopPropagation = (e: Event) => { e.stopPropagation(); }; @@ -140,17 +140,17 @@ export class MobileMenuInput extends MenuFocusable { } `; - private onCompositionEnd = () => { + private readonly onCompositionEnd = () => { this.data.onChange?.(this.inputRef.value); }; - private onInput = (e: InputEvent) => { + private readonly onInput = (e: InputEvent) => { e.stopPropagation(); if (e.isComposing) return; this.data.onChange?.(this.inputRef.value); }; - private stopPropagation = (e: Event) => { + private readonly stopPropagation = (e: Event) => { e.stopPropagation(); }; diff --git a/blocksuite/affine/components/src/context-menu/menu-renderer.ts b/blocksuite/affine/components/src/context-menu/menu-renderer.ts index 1677e258a3603..848f168429c27 100644 --- a/blocksuite/affine/components/src/context-menu/menu-renderer.ts +++ b/blocksuite/affine/components/src/context-menu/menu-renderer.ts @@ -83,13 +83,13 @@ export class MenuComponent } `; - private _clickContainer = (e: MouseEvent) => { + private readonly _clickContainer = (e: MouseEvent) => { e.stopPropagation(); this.focusInput(); this.menu.closeSubMenu(); }; - private searchRef = createRef(); + private readonly searchRef = createRef(); override firstUpdated() { const input = this.searchRef.value; diff --git a/blocksuite/affine/components/src/context-menu/menu.ts b/blocksuite/affine/components/src/context-menu/menu.ts index e968ce16010f9..3011f4c0b49cb 100644 --- a/blocksuite/affine/components/src/context-menu/menu.ts +++ b/blocksuite/affine/components/src/context-menu/menu.ts @@ -57,9 +57,9 @@ export function onMenuOpen(listener: MenuOpenListener) { export class Menu { private _cleanupFns: Array<() => void> = []; - private _currentFocused$ = signal(); + private readonly _currentFocused$ = signal(); - private _subMenu$ = signal(); + private readonly _subMenu$ = signal(); closed = false; diff --git a/blocksuite/affine/components/src/date-picker/date-picker.ts b/blocksuite/affine/components/src/date-picker/date-picker.ts index ef10c8c5d8b35..6810ceb8bb34b 100644 --- a/blocksuite/affine/components/src/date-picker/date-picker.ts +++ b/blocksuite/affine/components/src/date-picker/date-picker.ts @@ -54,9 +54,9 @@ export class DatePicker extends WithDisposable(LitElement) { /** current active month */ private _cursor = new Date(); - private _maxYear = 2099; + private readonly _maxYear = 2099; - private _minYear = 1970; + private readonly _minYear = 1970; get _cardStyle() { return { diff --git a/blocksuite/affine/components/src/date-picker/utils.ts b/blocksuite/affine/components/src/date-picker/utils.ts index e8a7e0182cff0..3616e380680d6 100644 --- a/blocksuite/affine/components/src/date-picker/utils.ts +++ b/blocksuite/affine/components/src/date-picker/utils.ts @@ -68,6 +68,6 @@ export function getMonthMatrix(maybeDate: MaybeDate) { } export function clamp(num1: number, num2: number, value: number) { - const [min, max] = [num1, num2].sort(); + const [min, max] = [num1, num2].sort((a, b) => a - b); return Math.min(Math.max(value, min), max); } diff --git a/blocksuite/affine/components/src/peek/controller.ts b/blocksuite/affine/components/src/peek/controller.ts index b47f6bda76054..53fffb50ef3fa 100644 --- a/blocksuite/affine/components/src/peek/controller.ts +++ b/blocksuite/affine/components/src/peek/controller.ts @@ -4,7 +4,7 @@ import { PeekViewProvider } from './service.js'; import type { PeekableClass, PeekViewService } from './type.js'; export class PeekableController { - private _getPeekViewService = (): PeekViewService | null => { + private readonly _getPeekViewService = (): PeekViewService | null => { return this.target.std.getOptional(PeekViewProvider); }; @@ -25,7 +25,7 @@ export class PeekableController { } constructor( - private target: T, - private enable?: (e: T) => boolean + private readonly target: T, + private readonly enable?: (e: T) => boolean ) {} } diff --git a/blocksuite/affine/components/src/rich-text/inline/presets/nodes/link-node/affine-link.ts b/blocksuite/affine/components/src/rich-text/inline/presets/nodes/link-node/affine-link.ts index 69674cd18a753..d8f74b89009f2 100644 --- a/blocksuite/affine/components/src/rich-text/inline/presets/nodes/link-node/affine-link.ts +++ b/blocksuite/affine/components/src/rich-text/inline/presets/nodes/link-node/affine-link.ts @@ -30,7 +30,7 @@ export class AffineLink extends ShadowlessElement { private _identified: boolean = false; // see https://github.com/toeverything/AFFiNE/issues/1540 - private _onMouseUp = () => { + private readonly _onMouseUp = () => { const anchorElement = this.querySelector('a'); if (!anchorElement || !anchorElement.isContentEditable) return; anchorElement.contentEditable = 'false'; @@ -58,7 +58,7 @@ export class AffineLink extends ShadowlessElement { refNodeSlotsProvider.docLinkClicked.emit(referenceInfo); }; - private _whenHover = new HoverController( + private readonly _whenHover = new HoverController( this, ({ abortController }) => { if (this.block?.doc.readonly) { diff --git a/blocksuite/affine/components/src/rich-text/inline/presets/nodes/link-node/link-popup/link-popup.ts b/blocksuite/affine/components/src/rich-text/inline/presets/nodes/link-node/link-popup/link-popup.ts index f4e1445f91b11..b3b1bd4616cac 100644 --- a/blocksuite/affine/components/src/rich-text/inline/presets/nodes/link-node/link-popup/link-popup.ts +++ b/blocksuite/affine/components/src/rich-text/inline/presets/nodes/link-node/link-popup/link-popup.ts @@ -49,7 +49,7 @@ export class LinkPopup extends WithDisposable(LitElement) { private _bodyOverflowStyle = ''; - private _createTemplate = () => { + private readonly _createTemplate = () => { this.updateComplete .then(() => { this.linkInput?.focus(); @@ -74,14 +74,14 @@ export class LinkPopup extends WithDisposable(LitElement) { `; }; - private _delete = () => { + private readonly _delete = () => { if (this.inlineEditor.isValidInlineRange(this.targetInlineRange)) { this.inlineEditor.deleteText(this.targetInlineRange); } this.abortController.abort(); }; - private _edit = () => { + private readonly _edit = () => { if (!this.host) return; this.type = 'edit'; @@ -89,7 +89,7 @@ export class LinkPopup extends WithDisposable(LitElement) { track(this.host.std, 'OpenedAliasPopup', { control: 'edit' }); }; - private _editTemplate = () => { + private readonly _editTemplate = () => { this.updateComplete .then(() => { if ( @@ -139,7 +139,7 @@ export class LinkPopup extends WithDisposable(LitElement) { private _embedOptions: EmbedOptions | null = null; - private _openLink = () => { + private readonly _openLink = () => { if (this.openLink) { this.openLink(); return; @@ -154,7 +154,7 @@ export class LinkPopup extends WithDisposable(LitElement) { this.abortController.abort(); }; - private _removeLink = () => { + private readonly _removeLink = () => { if (this.inlineEditor.isValidInlineRange(this.targetInlineRange)) { this.inlineEditor.formatText(this.targetInlineRange, { link: null, @@ -163,7 +163,7 @@ export class LinkPopup extends WithDisposable(LitElement) { this.abortController.abort(); }; - private _toggleViewSelector = (e: Event) => { + private readonly _toggleViewSelector = (e: Event) => { if (!this.host) return; const opened = (e as CustomEvent).detail; @@ -172,7 +172,7 @@ export class LinkPopup extends WithDisposable(LitElement) { track(this.host.std, 'OpenedViewSelector', { control: 'switch view' }); }; - private _trackViewSelected = (type: string) => { + private readonly _trackViewSelected = (type: string) => { if (!this.host) return; track(this.host.std, 'SelectedView', { @@ -181,7 +181,7 @@ export class LinkPopup extends WithDisposable(LitElement) { }); }; - private _viewTemplate = () => { + private readonly _viewTemplate = () => { if (!this.currentLink) return; this._embedOptions = diff --git a/blocksuite/affine/components/src/rich-text/inline/presets/nodes/reference-node/reference-alias-popup.ts b/blocksuite/affine/components/src/rich-text/inline/presets/nodes/reference-node/reference-alias-popup.ts index 1abdb3f3f2026..8a50f49327be6 100644 --- a/blocksuite/affine/components/src/rich-text/inline/presets/nodes/reference-node/reference-alias-popup.ts +++ b/blocksuite/affine/components/src/rich-text/inline/presets/nodes/reference-node/reference-alias-popup.ts @@ -89,7 +89,7 @@ export class ReferenceAliasPopup extends SignalWatcher( } `; - private _onSave = () => { + private readonly _onSave = () => { const title = this.title$.value.trim(); if (!title) { this.remove(); @@ -103,7 +103,7 @@ export class ReferenceAliasPopup extends SignalWatcher( this.remove(); }; - private _updateTitle = (e: InputEvent) => { + private readonly _updateTitle = (e: InputEvent) => { const target = e.target as HTMLInputElement; const value = target.value; this.title$.value = value; diff --git a/blocksuite/affine/components/src/rich-text/inline/presets/nodes/reference-node/reference-node.ts b/blocksuite/affine/components/src/rich-text/inline/presets/nodes/reference-node/reference-node.ts index 6879b51c2c6a0..10cf460568267 100644 --- a/blocksuite/affine/components/src/rich-text/inline/presets/nodes/reference-node/reference-node.ts +++ b/blocksuite/affine/components/src/rich-text/inline/presets/nodes/reference-node/reference-node.ts @@ -68,7 +68,7 @@ export class AffineReference extends WithDisposable(ShadowlessElement) { } `; - private _updateRefMeta = (doc: Doc) => { + private readonly _updateRefMeta = (doc: Doc) => { const refAttribute = this.delta.attributes?.reference; if (!refAttribute) { return; @@ -88,7 +88,7 @@ export class AffineReference extends WithDisposable(ShadowlessElement) { @state() accessor refMeta: DocMeta | undefined = undefined; - private _whenHover: HoverController = new HoverController( + private readonly _whenHover: HoverController = new HoverController( this, ({ abortController }) => { if ( diff --git a/blocksuite/affine/components/src/rich-text/inline/presets/nodes/reference-node/reference-popup.ts b/blocksuite/affine/components/src/rich-text/inline/presets/nodes/reference-node/reference-popup.ts index d85675de9f80a..cc55aa43b2dda 100644 --- a/blocksuite/affine/components/src/rich-text/inline/presets/nodes/reference-node/reference-popup.ts +++ b/blocksuite/affine/components/src/rich-text/inline/presets/nodes/reference-node/reference-popup.ts @@ -50,7 +50,7 @@ import { styles } from './styles.js'; export class ReferencePopup extends WithDisposable(LitElement) { static override styles = styles; - private _copyLink = () => { + private readonly _copyLink = () => { const url = this.std .getOptional(GenerateDocUrlProvider) ?.generateDocUrl(this.referenceInfo.pageId, this.referenceInfo.params); @@ -65,13 +65,13 @@ export class ReferencePopup extends WithDisposable(LitElement) { track(this.std, 'CopiedLink', { control: 'copy link' }); }; - private _openDoc = () => { + private readonly _openDoc = () => { this.std .getOptional(RefNodeSlotsProvider) ?.docLinkClicked.emit(this.referenceInfo); }; - private _openEditPopup = (e: MouseEvent) => { + private readonly _openEditPopup = (e: MouseEvent) => { e.stopPropagation(); if (document.body.querySelector('reference-alias-popup')) { @@ -102,14 +102,14 @@ export class ReferencePopup extends WithDisposable(LitElement) { track(std, 'OpenedAliasPopup', { control: 'edit' }); }; - private _toggleViewSelector = (e: Event) => { + private readonly _toggleViewSelector = (e: Event) => { const opened = (e as CustomEvent).detail; if (!opened) return; track(this.std, 'OpenedViewSelector', { control: 'switch view' }); }; - private _trackViewSelected = (type: string) => { + private readonly _trackViewSelected = (type: string) => { track(this.std, 'SelectedView', { control: 'select view', type: `${type} view`, diff --git a/blocksuite/affine/components/src/rich-text/rich-text.ts b/blocksuite/affine/components/src/rich-text/rich-text.ts index 4427ceaaf2d4c..bed5e2c136fa2 100644 --- a/blocksuite/affine/components/src/rich-text/rich-text.ts +++ b/blocksuite/affine/components/src/rich-text/rich-text.ts @@ -60,7 +60,7 @@ export class RichText extends WithDisposable(ShadowlessElement) { private _inlineEditor: AffineInlineEditor | null = null; - private _onCopy = (e: ClipboardEvent) => { + private readonly _onCopy = (e: ClipboardEvent) => { const inlineEditor = this.inlineEditor; if (!inlineEditor) return; @@ -77,7 +77,7 @@ export class RichText extends WithDisposable(ShadowlessElement) { e.stopPropagation(); }; - private _onCut = (e: ClipboardEvent) => { + private readonly _onCut = (e: ClipboardEvent) => { const inlineEditor = this.inlineEditor; if (!inlineEditor) return; @@ -99,7 +99,7 @@ export class RichText extends WithDisposable(ShadowlessElement) { e.stopPropagation(); }; - private _onPaste = (e: ClipboardEvent) => { + private readonly _onPaste = (e: ClipboardEvent) => { const inlineEditor = this.inlineEditor; if (!inlineEditor) return; @@ -121,14 +121,18 @@ export class RichText extends WithDisposable(ShadowlessElement) { e.stopPropagation(); }; - private _onStackItemAdded = (event: { stackItem: RichTextStackItem }) => { + private readonly _onStackItemAdded = (event: { + stackItem: RichTextStackItem; + }) => { const inlineRange = this.inlineEditor?.getInlineRange(); if (inlineRange) { event.stackItem.meta.set('richtext-v-range', inlineRange); } }; - private _onStackItemPopped = (event: { stackItem: RichTextStackItem }) => { + private readonly _onStackItemPopped = (event: { + stackItem: RichTextStackItem; + }) => { const inlineRange = event.stackItem.meta.get('richtext-v-range'); if (inlineRange && this.inlineEditor?.isValidInlineRange(inlineRange)) { this.inlineEditor?.setInlineRange(inlineRange); diff --git a/blocksuite/affine/components/src/toolbar/tooltip.ts b/blocksuite/affine/components/src/toolbar/tooltip.ts index 9cc12e15d234c..6eb2c7c1166c6 100644 --- a/blocksuite/affine/components/src/toolbar/tooltip.ts +++ b/blocksuite/affine/components/src/toolbar/tooltip.ts @@ -125,7 +125,7 @@ export class Tooltip extends LitElement { private _hoverController!: HoverController; - private _setUpHoverController = () => { + private readonly _setUpHoverController = () => { this._hoverController = new HoverController( this, () => { diff --git a/blocksuite/affine/components/src/virtual-keyboard/controller.ts b/blocksuite/affine/components/src/virtual-keyboard/controller.ts index b9bf58a6318a7..7e99c209e5e3a 100644 --- a/blocksuite/affine/components/src/virtual-keyboard/controller.ts +++ b/blocksuite/affine/components/src/virtual-keyboard/controller.ts @@ -13,13 +13,13 @@ export type VirtualKeyboardControllerConfig = { }; export class VirtualKeyboardController implements ReactiveController { - private _disposables = new DisposableGroup(); + private readonly _disposables = new DisposableGroup(); private readonly _keyboardHeight$ = signal(0); private readonly _keyboardOpened$ = signal(false); - private _storeInitialInputElementAttributes = () => { + private readonly _storeInitialInputElementAttributes = () => { const { inputElement } = this.config; if (navigator.virtualKeyboard) { const { overlaysContent } = navigator.virtualKeyboard; diff --git a/blocksuite/affine/data-view/src/core/component/tags/multi-tag-select.ts b/blocksuite/affine/data-view/src/core/component/tags/multi-tag-select.ts index d38dc1f358a7a..9c12fbef21743 100644 --- a/blocksuite/affine/data-view/src/core/component/tags/multi-tag-select.ts +++ b/blocksuite/affine/data-view/src/core/component/tags/multi-tag-select.ts @@ -150,7 +150,7 @@ class TagManager { return this.ops.value; } - constructor(private ops: TagManagerOptions) {} + constructor(private readonly ops: TagManagerOptions) {} deleteTag(id: string) { this.ops.onChange(this.value.value.filter(item => item !== id)); @@ -180,7 +180,7 @@ export class MultiTagSelect extends SignalWatcher( ) { static override styles = styles; - private _clickItemOption = (e: MouseEvent, id: string) => { + private readonly _clickItemOption = (e: MouseEvent, id: string) => { e.stopPropagation(); const option = this.options.value.find(v => v.id === id); if (!option) { @@ -235,11 +235,11 @@ export class MultiTagSelect extends SignalWatcher( }); }; - private _onInput = (event: KeyboardEvent) => { + private readonly _onInput = (event: KeyboardEvent) => { this.tagManager.text.value = (event.target as HTMLInputElement).value; }; - private _onInputKeydown = (event: KeyboardEvent) => { + private readonly _onInputKeydown = (event: KeyboardEvent) => { event.stopPropagation(); const inputValue = this.text.value.trim(); if (event.key === 'Backspace' && inputValue === '') { @@ -257,9 +257,9 @@ export class MultiTagSelect extends SignalWatcher( } }; - private tagManager = new TagManager(this); + private readonly tagManager = new TagManager(this); - private selectedTag$ = computed(() => { + private readonly selectedTag$ = computed(() => { return this.tagManager.filteredOptions$.value[this.selectedIndex]; }); diff --git a/blocksuite/affine/data-view/src/core/data-view.ts b/blocksuite/affine/data-view/src/core/data-view.ts index 8cc56bc00c8bf..ad8e938189747 100644 --- a/blocksuite/affine/data-view/src/core/data-view.ts +++ b/blocksuite/affine/data-view/src/core/data-view.ts @@ -63,14 +63,14 @@ export class DataViewRenderer extends SignalWatcher( } `; - private _view = createRef<{ + private readonly _view = createRef<{ expose: DataViewInstance; }>(); @property({ attribute: false }) accessor config!: DataViewRendererConfig; - private currentViewId$ = computed(() => { + private readonly currentViewId$ = computed(() => { return this.config.dataSource.viewManager.currentViewId$.value; }); @@ -218,7 +218,7 @@ declare global { } export class DataView { - private _ref = createRef(); + private readonly _ref = createRef(); get expose() { return this._ref.value?.view?.expose; diff --git a/blocksuite/affine/data-view/src/core/detail/field.ts b/blocksuite/affine/data-view/src/core/detail/field.ts index 2531a1bf12861..effe05fb70a88 100644 --- a/blocksuite/affine/data-view/src/core/detail/field.ts +++ b/blocksuite/affine/data-view/src/core/detail/field.ts @@ -109,7 +109,7 @@ export class RecordField extends SignalWatcher( } `; - private _cell = createRef(); + private readonly _cell = createRef(); _click = (e: MouseEvent) => { e.stopPropagation(); diff --git a/blocksuite/affine/data-view/src/core/detail/selection.ts b/blocksuite/affine/data-view/src/core/detail/selection.ts index aa300050a06b8..a0ab39daf25b5 100644 --- a/blocksuite/affine/data-view/src/core/detail/selection.ts +++ b/blocksuite/affine/data-view/src/core/detail/selection.ts @@ -49,7 +49,7 @@ export class DetailSelection { } } - constructor(private viewEle: RecordDetail) {} + constructor(private readonly viewEle: RecordDetail) {} blur(selection: DetailViewSelection) { const container = this.getFocusCellContainer(selection); diff --git a/blocksuite/affine/data-view/src/core/group-by/renderer/number-group.ts b/blocksuite/affine/data-view/src/core/group-by/renderer/number-group.ts index da3917f1e76c2..be9f840eb7090 100644 --- a/blocksuite/affine/data-view/src/core/group-by/renderer/number-group.ts +++ b/blocksuite/affine/data-view/src/core/group-by/renderer/number-group.ts @@ -21,7 +21,7 @@ export class NumberGroupView extends BaseGroup, number> { } `; - private _click = () => { + private readonly _click = () => { if (this.readonly) { return; } diff --git a/blocksuite/affine/data-view/src/core/group-by/renderer/select-group.ts b/blocksuite/affine/data-view/src/core/group-by/renderer/select-group.ts index 2d6b45589b12b..ad36f6f483034 100644 --- a/blocksuite/affine/data-view/src/core/group-by/renderer/select-group.ts +++ b/blocksuite/affine/data-view/src/core/group-by/renderer/select-group.ts @@ -42,7 +42,7 @@ export class SelectGroupView extends BaseGroup< } `; - private _click = (e: MouseEvent) => { + private readonly _click = (e: MouseEvent) => { if (this.readonly) { return; } diff --git a/blocksuite/affine/data-view/src/core/group-by/renderer/string-group.ts b/blocksuite/affine/data-view/src/core/group-by/renderer/string-group.ts index 8d1f6df0c68bc..cb39355c3f4c8 100644 --- a/blocksuite/affine/data-view/src/core/group-by/renderer/string-group.ts +++ b/blocksuite/affine/data-view/src/core/group-by/renderer/string-group.ts @@ -21,7 +21,7 @@ export class StringGroupView extends BaseGroup, string> { } `; - private _click = () => { + private readonly _click = () => { if (this.readonly) { return; } diff --git a/blocksuite/affine/data-view/src/core/group-by/trait.ts b/blocksuite/affine/data-view/src/core/group-by/trait.ts index 25c785558d773..699b8f009583b 100644 --- a/blocksuite/affine/data-view/src/core/group-by/trait.ts +++ b/blocksuite/affine/data-view/src/core/group-by/trait.ts @@ -103,7 +103,7 @@ export class GroupTrait { return groupMap; }); - private _groupsDataList$ = computed(() => { + private readonly _groupsDataList$ = computed(() => { const groupMap = this.groupDataMap$.value; if (!groupMap) { return; @@ -143,9 +143,9 @@ export class GroupTrait { } constructor( - private groupBy$: ReadonlySignal, + private readonly groupBy$: ReadonlySignal, public view: SingleView, - private ops: { + private readonly ops: { groupBySet: (groupBy: GroupBy | undefined) => void; sortGroup: (keys: string[]) => string[]; sortRow: (groupKey: string, rowIds: string[]) => string[]; diff --git a/blocksuite/affine/data-view/src/core/logical/data-type.ts b/blocksuite/affine/data-view/src/core/logical/data-type.ts index 91c2ddabe2402..feeaaa96b9121 100644 --- a/blocksuite/affine/data-view/src/core/logical/data-type.ts +++ b/blocksuite/affine/data-view/src/core/logical/data-type.ts @@ -50,9 +50,9 @@ export class DataType< > implements TypeDefinition { constructor( - private name: Name, + private readonly name: Name, _dataSchema: DataSchema, - private valueSchema: ValueSchema + private readonly valueSchema: ValueSchema ) {} instance(literal?: Zod.TypeOf) { diff --git a/blocksuite/affine/data-view/src/core/logical/matcher.ts b/blocksuite/affine/data-view/src/core/logical/matcher.ts index 1ca41326a7292..5c1db06ba8e2f 100644 --- a/blocksuite/affine/data-view/src/core/logical/matcher.ts +++ b/blocksuite/affine/data-view/src/core/logical/matcher.ts @@ -14,8 +14,8 @@ export class MatcherCreator { export class Matcher { constructor( - private list: MatcherData[], - private _match: (type: Type, target: TypeInstance) => boolean = ( + private readonly list: MatcherData[], + private readonly _match: (type: Type, target: TypeInstance) => boolean = ( type, target ) => typeSystem.unify(target, type) diff --git a/blocksuite/affine/data-view/src/core/logical/type-system.ts b/blocksuite/affine/data-view/src/core/logical/type-system.ts index 0222727d6186e..acf71d34f3576 100644 --- a/blocksuite/affine/data-view/src/core/logical/type-system.ts +++ b/blocksuite/affine/data-view/src/core/logical/type-system.ts @@ -32,7 +32,7 @@ const getMap2 = ( }; export class TypeSystem { - private _unify: Unify = ( + private readonly _unify: Unify = ( ctx: TypeVarContext, left: TypeInstance | undefined, right: TypeInstance | undefined diff --git a/blocksuite/affine/data-view/src/core/sort/manager.ts b/blocksuite/affine/data-view/src/core/sort/manager.ts index 976be11ce5055..a89147855d31d 100644 --- a/blocksuite/affine/data-view/src/core/sort/manager.ts +++ b/blocksuite/affine/data-view/src/core/sort/manager.ts @@ -34,7 +34,7 @@ export class SortManager { constructor( readonly sort$: ReadonlySignal, readonly view: SingleView, - private ops: { + private readonly ops: { setSortList: (sortList: Sort) => void; } ) {} diff --git a/blocksuite/affine/data-view/src/core/utils/wc-dnd/dnd-context.ts b/blocksuite/affine/data-view/src/core/utils/wc-dnd/dnd-context.ts index 0d761c801d3b1..80598a9a09e52 100644 --- a/blocksuite/affine/data-view/src/core/utils/wc-dnd/dnd-context.ts +++ b/blocksuite/affine/data-view/src/core/utils/wc-dnd/dnd-context.ts @@ -56,20 +56,20 @@ const defaultCoordinates: Coordinates = { }; export class DndContext { - private dragMove = (coordinates: Coordinates) => { + private readonly dragMove = (coordinates: Coordinates) => { this.activationCoordinates$.value = coordinates; this.autoScroll(); }; - private droppableNodes$ = signal(new Map()); + private readonly droppableNodes$ = signal(new Map()); - private initialCoordinates$ = signal(); + private readonly initialCoordinates$ = signal(); - private initScrollOffset$ = signal(defaultCoordinates); + private readonly initScrollOffset$ = signal(defaultCoordinates); - private session$ = signal(); + private readonly session$ = signal(); - private startSession = ( + private readonly startSession = ( id: UniqueIdentifier, activeNode: HTMLElement, sessionCreator: DndSessionCreator @@ -96,7 +96,7 @@ export class DndContext { activationCoordinates$ = signal(); - private translate$ = computed(() => { + private readonly translate$ = computed(() => { const init = this.initialCoordinates$.value; const current = this.activationCoordinates$.value; if (!init || !current) { diff --git a/blocksuite/affine/data-view/src/core/utils/wc-dnd/sensors/mouse.ts b/blocksuite/affine/data-view/src/core/utils/wc-dnd/sensors/mouse.ts index c34837de79d52..5057feca7cba7 100644 --- a/blocksuite/affine/data-view/src/core/utils/wc-dnd/sensors/mouse.ts +++ b/blocksuite/affine/data-view/src/core/utils/wc-dnd/sensors/mouse.ts @@ -192,8 +192,8 @@ export class MouseSession implements DndSession { constructor( event: Event, - private sessionProps: DndSessionProps, - private props: MouseSensorProps + private readonly sessionProps: DndSessionProps, + private readonly props: MouseSensorProps ) { this.initialCoordinates = getEventCoordinates(event) ?? defaultCoordinates; this.attach(); diff --git a/blocksuite/affine/data-view/src/core/utils/wc-dnd/utils/listeners.ts b/blocksuite/affine/data-view/src/core/utils/wc-dnd/utils/listeners.ts index 4550e615ae1aa..2ead5cf79089a 100644 --- a/blocksuite/affine/data-view/src/core/utils/wc-dnd/utils/listeners.ts +++ b/blocksuite/affine/data-view/src/core/utils/wc-dnd/utils/listeners.ts @@ -1,5 +1,5 @@ export class Listeners { - private listeners: [ + private readonly listeners: [ string, EventListenerOrEventListenerObject | null, AddEventListenerOptions | boolean | undefined, diff --git a/blocksuite/affine/data-view/src/core/view-manager/single-view.ts b/blocksuite/affine/data-view/src/core/view-manager/single-view.ts index 65f7630f67eea..84520c178fa35 100644 --- a/blocksuite/affine/data-view/src/core/view-manager/single-view.ts +++ b/blocksuite/affine/data-view/src/core/view-manager/single-view.ts @@ -139,9 +139,9 @@ export abstract class SingleViewBase< ViewData extends DataViewDataType = DataViewDataType, > implements SingleView { - private searchString = signal(''); + private readonly searchString = signal(''); - private traitMap = new Map(); + private readonly traitMap = new Map(); data$ = computed(() => { return this.dataSource.viewDataGet(this.id) as ViewData | undefined; diff --git a/blocksuite/affine/data-view/src/property-presets/date/cell-renderer.ts b/blocksuite/affine/data-view/src/property-presets/date/cell-renderer.ts index 3df9503b9187e..e4a131b9b6149 100644 --- a/blocksuite/affine/data-view/src/property-presets/date/cell-renderer.ts +++ b/blocksuite/affine/data-view/src/property-presets/date/cell-renderer.ts @@ -66,7 +66,7 @@ export class DateCellEditing extends BaseCellRenderer { private _prevPortalAbortController: AbortController | null = null; - private openDatePicker = () => { + private readonly openDatePicker = () => { if ( this._prevPortalAbortController && !this._prevPortalAbortController.signal.aborted @@ -168,7 +168,7 @@ height: 46px; } }; - private updateValue = () => { + private readonly updateValue = () => { const tempValue = this.tempValue$.value; const currentValue = this.value; diff --git a/blocksuite/affine/data-view/src/property-presets/multi-select/cell-renderer.ts b/blocksuite/affine/data-view/src/property-presets/multi-select/cell-renderer.ts index f46a266745fde..5e67b252e8f3c 100644 --- a/blocksuite/affine/data-view/src/property-presets/multi-select/cell-renderer.ts +++ b/blocksuite/affine/data-view/src/property-presets/multi-select/cell-renderer.ts @@ -28,7 +28,7 @@ export class MultiSelectCellEditing extends BaseCellRenderer< string[], SelectPropertyData > { - private popTagSelect = () => { + private readonly popTagSelect = () => { const value = signal(this._value); this._disposables.add({ dispose: popTagSelect( diff --git a/blocksuite/affine/data-view/src/property-presets/number/cell-renderer.ts b/blocksuite/affine/data-view/src/property-presets/number/cell-renderer.ts index 5d753bd620e96..dd21a2005261b 100644 --- a/blocksuite/affine/data-view/src/property-presets/number/cell-renderer.ts +++ b/blocksuite/affine/data-view/src/property-presets/number/cell-renderer.ts @@ -95,7 +95,7 @@ export class NumberCellEditing extends BaseCellRenderer< } `; - private _getFormattedString = (value: number) => { + private readonly _getFormattedString = (value: number) => { const enableNewFormatting = this.view.featureFlags$.value.enable_number_formatting; const decimals = this.property.data$.value.decimal ?? 0; @@ -106,7 +106,7 @@ export class NumberCellEditing extends BaseCellRenderer< : value.toString(); }; - private _keydown = (e: KeyboardEvent) => { + private readonly _keydown = (e: KeyboardEvent) => { const ctrlKey = IS_MAC ? e.metaKey : e.ctrlKey; if (e.key.toLowerCase() === 'z' && ctrlKey) { @@ -121,7 +121,7 @@ export class NumberCellEditing extends BaseCellRenderer< } }; - private _setValue = (str: string = this._inputEle.value) => { + private readonly _setValue = (str: string = this._inputEle.value) => { if (!str) { this.onChange(undefined); return; diff --git a/blocksuite/affine/data-view/src/property-presets/select/cell-renderer.ts b/blocksuite/affine/data-view/src/property-presets/select/cell-renderer.ts index ae3347ee3926a..bfdf9ba89afa5 100644 --- a/blocksuite/affine/data-view/src/property-presets/select/cell-renderer.ts +++ b/blocksuite/affine/data-view/src/property-presets/select/cell-renderer.ts @@ -28,7 +28,7 @@ export class SelectCellEditing extends BaseCellRenderer< string, SelectPropertyData > { - private popTagSelect = () => { + private readonly popTagSelect = () => { const value = signal(this._value); this._disposables.add({ dispose: popTagSelect( diff --git a/blocksuite/affine/data-view/src/property-presets/text/cell-renderer.ts b/blocksuite/affine/data-view/src/property-presets/text/cell-renderer.ts index 7102f7a53e08e..40149a999f6f2 100644 --- a/blocksuite/affine/data-view/src/property-presets/text/cell-renderer.ts +++ b/blocksuite/affine/data-view/src/property-presets/text/cell-renderer.ts @@ -70,7 +70,7 @@ export class TextCellEditing extends BaseCellRenderer { } `; - private _keydown = (e: KeyboardEvent) => { + private readonly _keydown = (e: KeyboardEvent) => { if (e.key === 'Enter' && !e.isComposing) { this._setValue(); setTimeout(() => { @@ -79,7 +79,7 @@ export class TextCellEditing extends BaseCellRenderer { } }; - private _setValue = (str: string = this._inputEle.value) => { + private readonly _setValue = (str: string = this._inputEle.value) => { this._inputEle.value = `${this.value ?? ''}`; this.onChange(str); }; diff --git a/blocksuite/affine/data-view/src/view-presets/kanban/mobile/card.ts b/blocksuite/affine/data-view/src/view-presets/kanban/mobile/card.ts index 66380a02a1414..8c1f7f5b61423 100644 --- a/blocksuite/affine/data-view/src/view-presets/kanban/mobile/card.ts +++ b/blocksuite/affine/data-view/src/view-presets/kanban/mobile/card.ts @@ -91,7 +91,7 @@ export class MobileKanbanCard extends SignalWatcher( ) { static override styles = styles; - private clickCenterPeek = (e: MouseEvent) => { + private readonly clickCenterPeek = (e: MouseEvent) => { e.stopPropagation(); this.dataViewEle.openDetailPanel({ view: this.view, @@ -99,7 +99,7 @@ export class MobileKanbanCard extends SignalWatcher( }); }; - private clickMore = (e: MouseEvent) => { + private readonly clickMore = (e: MouseEvent) => { e.stopPropagation(); popCardMenu( popupTargetFromElement(e.currentTarget as HTMLElement), diff --git a/blocksuite/affine/data-view/src/view-presets/kanban/mobile/cell.ts b/blocksuite/affine/data-view/src/view-presets/kanban/mobile/cell.ts index e30e48446cc93..70723432524ae 100644 --- a/blocksuite/affine/data-view/src/view-presets/kanban/mobile/cell.ts +++ b/blocksuite/affine/data-view/src/view-presets/kanban/mobile/cell.ts @@ -51,7 +51,7 @@ export class MobileKanbanCell extends SignalWatcher( ) { static override styles = styles; - private _cell = createRef(); + private readonly _cell = createRef(); isEditing$ = computed(() => { const selection = this.kanban?.props.selection$.value; diff --git a/blocksuite/affine/data-view/src/view-presets/kanban/mobile/group.ts b/blocksuite/affine/data-view/src/view-presets/kanban/mobile/group.ts index b01a99c3f170f..52dd078648c0f 100644 --- a/blocksuite/affine/data-view/src/view-presets/kanban/mobile/group.ts +++ b/blocksuite/affine/data-view/src/view-presets/kanban/mobile/group.ts @@ -60,15 +60,15 @@ export class MobileKanbanGroup extends SignalWatcher( ) { static override styles = styles; - private clickAddCard = () => { + private readonly clickAddCard = () => { this.view.addCard('end', this.group.key); }; - private clickAddCardInStart = () => { + private readonly clickAddCardInStart = () => { this.view.addCard('start', this.group.key); }; - private clickGroupOptions = (e: MouseEvent) => { + private readonly clickGroupOptions = (e: MouseEvent) => { const ele = e.currentTarget as HTMLElement; popFilterableSimpleMenu(popupTargetFromElement(ele), [ menu.group({ diff --git a/blocksuite/affine/data-view/src/view-presets/kanban/pc/card.ts b/blocksuite/affine/data-view/src/view-presets/kanban/pc/card.ts index 0009d590e8984..bcfcb0b2cc6ff 100644 --- a/blocksuite/affine/data-view/src/view-presets/kanban/pc/card.ts +++ b/blocksuite/affine/data-view/src/view-presets/kanban/pc/card.ts @@ -125,7 +125,7 @@ export class KanbanCard extends SignalWatcher( ) { static override styles = styles; - private clickEdit = (e: MouseEvent) => { + private readonly clickEdit = (e: MouseEvent) => { e.stopPropagation(); const selection = this.getSelection(); if (selection) { @@ -133,7 +133,7 @@ export class KanbanCard extends SignalWatcher( } }; - private clickMore = (e: MouseEvent) => { + private readonly clickMore = (e: MouseEvent) => { e.stopPropagation(); const selection = this.getSelection(); const ele = e.currentTarget as HTMLElement; @@ -156,7 +156,7 @@ export class KanbanCard extends SignalWatcher( } }; - private contextMenu = (e: MouseEvent) => { + private readonly contextMenu = (e: MouseEvent) => { e.stopPropagation(); e.preventDefault(); const selection = this.getSelection(); diff --git a/blocksuite/affine/data-view/src/view-presets/kanban/pc/cell.ts b/blocksuite/affine/data-view/src/view-presets/kanban/pc/cell.ts index 1c10e99c828ba..8deef53dae4eb 100644 --- a/blocksuite/affine/data-view/src/view-presets/kanban/pc/cell.ts +++ b/blocksuite/affine/data-view/src/view-presets/kanban/pc/cell.ts @@ -59,7 +59,7 @@ export class KanbanCell extends SignalWatcher( ) { static override styles = styles; - private _cell = createRef(); + private readonly _cell = createRef(); selectCurrentCell = (editing: boolean) => { const selectionView = this.closest( diff --git a/blocksuite/affine/data-view/src/view-presets/kanban/pc/controller/clipboard.ts b/blocksuite/affine/data-view/src/view-presets/kanban/pc/controller/clipboard.ts index 8fe68fc462602..d45206ea98e20 100644 --- a/blocksuite/affine/data-view/src/view-presets/kanban/pc/controller/clipboard.ts +++ b/blocksuite/affine/data-view/src/view-presets/kanban/pc/controller/clipboard.ts @@ -5,7 +5,7 @@ import type { KanbanViewSelectionWithType } from '../../types.js'; import type { DataViewKanban } from '../kanban-view.js'; export class KanbanClipboardController implements ReactiveController { - private _onCopy = ( + private readonly _onCopy = ( _context: UIEventStateContext, _kanbanSelection: KanbanViewSelectionWithType ) => { @@ -13,7 +13,7 @@ export class KanbanClipboardController implements ReactiveController { return true; }; - private _onPaste = (_context: UIEventStateContext) => { + private readonly _onPaste = (_context: UIEventStateContext) => { // todo return true; }; diff --git a/blocksuite/affine/data-view/src/view-presets/kanban/pc/controller/drag.ts b/blocksuite/affine/data-view/src/view-presets/kanban/pc/controller/drag.ts index e585112433b40..74c216a134311 100644 --- a/blocksuite/affine/data-view/src/view-presets/kanban/pc/controller/drag.ts +++ b/blocksuite/affine/data-view/src/view-presets/kanban/pc/controller/drag.ts @@ -140,7 +140,7 @@ export class KanbanDragController implements ReactiveController { return scrollContainer; } - constructor(private host: DataViewKanban) { + constructor(private readonly host: DataViewKanban) { this.host.addController(this); } diff --git a/blocksuite/affine/data-view/src/view-presets/kanban/pc/controller/hotkeys.ts b/blocksuite/affine/data-view/src/view-presets/kanban/pc/controller/hotkeys.ts index 1c74cf59fe2cd..57b38508f26c1 100644 --- a/blocksuite/affine/data-view/src/view-presets/kanban/pc/controller/hotkeys.ts +++ b/blocksuite/affine/data-view/src/view-presets/kanban/pc/controller/hotkeys.ts @@ -7,7 +7,7 @@ export class KanbanHotkeysController implements ReactiveController { return !!this.host.selectionController.selection; } - constructor(private host: DataViewKanban) { + constructor(private readonly host: DataViewKanban) { this.host.addController(this); } diff --git a/blocksuite/affine/data-view/src/view-presets/kanban/pc/controller/selection.ts b/blocksuite/affine/data-view/src/view-presets/kanban/pc/controller/selection.ts index 1bf0df1f0c8da..a2cc30058dce6 100644 --- a/blocksuite/affine/data-view/src/view-presets/kanban/pc/controller/selection.ts +++ b/blocksuite/affine/data-view/src/view-presets/kanban/pc/controller/selection.ts @@ -79,7 +79,7 @@ export class KanbanSelectionController implements ReactiveController { return this.host.props.view; } - constructor(private host: DataViewKanban) { + constructor(private readonly host: DataViewKanban) { this.host.addController(this); } diff --git a/blocksuite/affine/data-view/src/view-presets/kanban/pc/group.ts b/blocksuite/affine/data-view/src/view-presets/kanban/pc/group.ts index 7544f32fd0b5e..be27e1114c3f3 100644 --- a/blocksuite/affine/data-view/src/view-presets/kanban/pc/group.ts +++ b/blocksuite/affine/data-view/src/view-presets/kanban/pc/group.ts @@ -96,7 +96,7 @@ export class KanbanGroup extends SignalWatcher( ) { static override styles = styles; - private clickAddCard = () => { + private readonly clickAddCard = () => { const id = this.view.addCard('end', this.group.key); requestAnimationFrame(() => { const kanban = this.closest('affine-data-view-kanban'); @@ -114,7 +114,7 @@ export class KanbanGroup extends SignalWatcher( }); }; - private clickAddCardInStart = () => { + private readonly clickAddCardInStart = () => { const id = this.view.addCard('start', this.group.key); requestAnimationFrame(() => { const kanban = this.closest('affine-data-view-kanban'); @@ -132,7 +132,7 @@ export class KanbanGroup extends SignalWatcher( }); }; - private clickGroupOptions = (e: MouseEvent) => { + private readonly clickGroupOptions = (e: MouseEvent) => { const ele = e.currentTarget as HTMLElement; popFilterableSimpleMenu(popupTargetFromElement(ele), [ menu.action({ diff --git a/blocksuite/affine/data-view/src/view-presets/kanban/pc/header.ts b/blocksuite/affine/data-view/src/view-presets/kanban/pc/header.ts index d4378246e4652..ebf21f4ad3a88 100644 --- a/blocksuite/affine/data-view/src/view-presets/kanban/pc/header.ts +++ b/blocksuite/affine/data-view/src/view-presets/kanban/pc/header.ts @@ -35,7 +35,7 @@ export class KanbanHeader extends SignalWatcher( ) { static override styles = styles; - private clickGroup = (e: MouseEvent) => { + private readonly clickGroup = (e: MouseEvent) => { const groupTrait = this.view.traitGet(groupTraitKey); if (!groupTrait) { return; diff --git a/blocksuite/affine/data-view/src/view-presets/kanban/pc/kanban-view.ts b/blocksuite/affine/data-view/src/view-presets/kanban/pc/kanban-view.ts index 83a55978b2e7f..5c55c8fd0d78c 100644 --- a/blocksuite/affine/data-view/src/view-presets/kanban/pc/kanban-view.ts +++ b/blocksuite/affine/data-view/src/view-presets/kanban/pc/kanban-view.ts @@ -100,7 +100,7 @@ export class DataViewKanban extends DataViewBase< > { static override styles = styles; - private dragController = new KanbanDragController(this); + private readonly dragController = new KanbanDragController(this); clipboardController = new KanbanClipboardController(this); diff --git a/blocksuite/affine/data-view/src/view-presets/table/mobile/cell.ts b/blocksuite/affine/data-view/src/view-presets/table/mobile/cell.ts index 41d0c436d333c..a55f9f66197cd 100644 --- a/blocksuite/affine/data-view/src/view-presets/table/mobile/cell.ts +++ b/blocksuite/affine/data-view/src/view-presets/table/mobile/cell.ts @@ -36,7 +36,7 @@ export class MobileTableCell extends SignalWatcher( } `; - private _cell = createRef(); + private readonly _cell = createRef(); @property({ attribute: false }) accessor column!: TableColumn; diff --git a/blocksuite/affine/data-view/src/view-presets/table/mobile/column-header.ts b/blocksuite/affine/data-view/src/view-presets/table/mobile/column-header.ts index 0a8cfed8c765b..ed3a1f3cf0bfd 100644 --- a/blocksuite/affine/data-view/src/view-presets/table/mobile/column-header.ts +++ b/blocksuite/affine/data-view/src/view-presets/table/mobile/column-header.ts @@ -53,7 +53,7 @@ export class MobileTableColumnHeader extends SignalWatcher( } `; - private _clickColumn = () => { + private readonly _clickColumn = () => { if (this.tableViewManager.readonly$.value) { return; } diff --git a/blocksuite/affine/data-view/src/view-presets/table/mobile/group.ts b/blocksuite/affine/data-view/src/view-presets/table/mobile/group.ts index cff0ad8f4da4f..0911f6fbabfbe 100644 --- a/blocksuite/affine/data-view/src/view-presets/table/mobile/group.ts +++ b/blocksuite/affine/data-view/src/view-presets/table/mobile/group.ts @@ -50,7 +50,7 @@ export class MobileTableGroup extends SignalWatcher( ) { static override styles = styles; - private clickAddRow = () => { + private readonly clickAddRow = () => { this.view.rowAdd('end', this.group?.key); requestAnimationFrame(() => { const selectionController = this.viewEle.selectionController; @@ -68,7 +68,7 @@ export class MobileTableGroup extends SignalWatcher( }); }; - private clickAddRowInStart = () => { + private readonly clickAddRowInStart = () => { this.view.rowAdd('start', this.group?.key); requestAnimationFrame(() => { const selectionController = this.viewEle.selectionController; @@ -86,7 +86,7 @@ export class MobileTableGroup extends SignalWatcher( }); }; - private clickGroupOptions = (e: MouseEvent) => { + private readonly clickGroupOptions = (e: MouseEvent) => { const group = this.group; if (!group) { return; @@ -111,7 +111,7 @@ export class MobileTableGroup extends SignalWatcher( ]); }; - private renderGroupHeader = () => { + private readonly renderGroupHeader = () => { if (!this.group) { return null; } diff --git a/blocksuite/affine/data-view/src/view-presets/table/mobile/header.ts b/blocksuite/affine/data-view/src/view-presets/table/mobile/header.ts index 8e0fc2eaba861..f453d7a7b6693 100644 --- a/blocksuite/affine/data-view/src/view-presets/table/mobile/header.ts +++ b/blocksuite/affine/data-view/src/view-presets/table/mobile/header.ts @@ -23,7 +23,7 @@ export class MobileTableHeader extends SignalWatcher( } `; - private _onAddColumn = () => { + private readonly _onAddColumn = () => { if (this.readonly) return; this.tableViewManager.propertyAdd('end'); this.editLastColumnTitle(); diff --git a/blocksuite/affine/data-view/src/view-presets/table/mobile/table-view.ts b/blocksuite/affine/data-view/src/view-presets/table/mobile/table-view.ts index 65f546dbcec01..ab8c9abf2b980 100644 --- a/blocksuite/affine/data-view/src/view-presets/table/mobile/table-view.ts +++ b/blocksuite/affine/data-view/src/view-presets/table/mobile/table-view.ts @@ -45,7 +45,7 @@ export class MobileDataViewTable extends DataViewBase< } `; - private _addRow = ( + private readonly _addRow = ( tableViewManager: TableSingleView, position: InsertToPosition | number ) => { diff --git a/blocksuite/affine/data-view/src/view-presets/table/pc/cell.ts b/blocksuite/affine/data-view/src/view-presets/table/pc/cell.ts index 7f199616e82cd..7508728f8d1a2 100644 --- a/blocksuite/affine/data-view/src/view-presets/table/pc/cell.ts +++ b/blocksuite/affine/data-view/src/view-presets/table/pc/cell.ts @@ -43,7 +43,7 @@ export class DatabaseCellContainer extends SignalWatcher( } `; - private _cell = createRef(); + private readonly _cell = createRef(); @property({ attribute: false }) accessor column!: TableColumn; diff --git a/blocksuite/affine/data-view/src/view-presets/table/pc/controller/clipboard.ts b/blocksuite/affine/data-view/src/view-presets/table/pc/controller/clipboard.ts index 688a430e1372a..6151fc8fa34ee 100644 --- a/blocksuite/affine/data-view/src/view-presets/table/pc/controller/clipboard.ts +++ b/blocksuite/affine/data-view/src/view-presets/table/pc/controller/clipboard.ts @@ -16,7 +16,7 @@ type JsonAreaData = string[][]; const TEXT = 'text/plain'; export class TableClipboardController implements ReactiveController { - private _onCopy = ( + private readonly _onCopy = ( tableSelection: TableViewSelectionWithType, isCut = false ) => { @@ -72,11 +72,11 @@ export class TableClipboardController implements ReactiveController { return true; }; - private _onCut = (tableSelection: TableViewSelectionWithType) => { + private readonly _onCut = (tableSelection: TableViewSelectionWithType) => { this._onCopy(tableSelection, true); }; - private _onPaste = async (_context: UIEventStateContext) => { + private readonly _onPaste = async (_context: UIEventStateContext) => { const event = _context.get('clipboardState').raw; event.stopPropagation(); const view = this.host; diff --git a/blocksuite/affine/data-view/src/view-presets/table/pc/controller/drag.ts b/blocksuite/affine/data-view/src/view-presets/table/pc/controller/drag.ts index 3d6d2b09faf2d..fb91a1c56c1db 100644 --- a/blocksuite/affine/data-view/src/view-presets/table/pc/controller/drag.ts +++ b/blocksuite/affine/data-view/src/view-presets/table/pc/controller/drag.ts @@ -128,7 +128,7 @@ export class TableDragController implements ReactiveController { return position; }; - constructor(private host: DataViewTable) { + constructor(private readonly host: DataViewTable) { this.host.addController(this); } diff --git a/blocksuite/affine/data-view/src/view-presets/table/pc/controller/hotkeys.ts b/blocksuite/affine/data-view/src/view-presets/table/pc/controller/hotkeys.ts index a0bb910a15cd0..293147e1a4bcd 100644 --- a/blocksuite/affine/data-view/src/view-presets/table/pc/controller/hotkeys.ts +++ b/blocksuite/affine/data-view/src/view-presets/table/pc/controller/hotkeys.ts @@ -10,7 +10,7 @@ export class TableHotkeysController implements ReactiveController { return this.host.selectionController; } - constructor(private host: DataViewTable) { + constructor(private readonly host: DataViewTable) { this.host.addController(this); } diff --git a/blocksuite/affine/data-view/src/view-presets/table/pc/controller/selection.ts b/blocksuite/affine/data-view/src/view-presets/table/pc/controller/selection.ts index 61733ed79fb18..e541dfb3a0eae 100644 --- a/blocksuite/affine/data-view/src/view-presets/table/pc/controller/selection.ts +++ b/blocksuite/affine/data-view/src/view-presets/table/pc/controller/selection.ts @@ -29,7 +29,7 @@ import { export class TableSelectionController implements ReactiveController { private _tableViewSelection?: TableViewSelectionWithType; - private getFocusCellContainer = () => { + private readonly getFocusCellContainer = () => { if ( !this._tableViewSelection || this._tableViewSelection.selectionType !== 'area' diff --git a/blocksuite/affine/data-view/src/view-presets/table/pc/group.ts b/blocksuite/affine/data-view/src/view-presets/table/pc/group.ts index 0474c20e87eba..c427c2c55205f 100644 --- a/blocksuite/affine/data-view/src/view-presets/table/pc/group.ts +++ b/blocksuite/affine/data-view/src/view-presets/table/pc/group.ts @@ -67,7 +67,7 @@ export class TableGroup extends SignalWatcher( ) { static override styles = styles; - private clickAddRow = () => { + private readonly clickAddRow = () => { this.view.rowAdd('end', this.group?.key); requestAnimationFrame(() => { const selectionController = this.viewEle.selectionController; @@ -85,7 +85,7 @@ export class TableGroup extends SignalWatcher( }); }; - private clickAddRowInStart = () => { + private readonly clickAddRowInStart = () => { this.view.rowAdd('start', this.group?.key); requestAnimationFrame(() => { const selectionController = this.viewEle.selectionController; @@ -103,7 +103,7 @@ export class TableGroup extends SignalWatcher( }); }; - private clickGroupOptions = (e: MouseEvent) => { + private readonly clickGroupOptions = (e: MouseEvent) => { const group = this.group; if (!group) { return; @@ -128,7 +128,7 @@ export class TableGroup extends SignalWatcher( ]); }; - private renderGroupHeader = () => { + private readonly renderGroupHeader = () => { if (!this.group) { return null; } diff --git a/blocksuite/affine/data-view/src/view-presets/table/pc/header/column-header.ts b/blocksuite/affine/data-view/src/view-presets/table/pc/header/column-header.ts index 1a81a50d4b752..e446d524c72f6 100644 --- a/blocksuite/affine/data-view/src/view-presets/table/pc/header/column-header.ts +++ b/blocksuite/affine/data-view/src/view-presets/table/pc/header/column-header.ts @@ -19,7 +19,7 @@ export class DatabaseColumnHeader extends SignalWatcher( ) { static override styles = styles; - private _onAddColumn = (e: MouseEvent) => { + private readonly _onAddColumn = (e: MouseEvent) => { if (this.readonly) return; this.tableViewManager.propertyAdd('end'); const ele = e.currentTarget as HTMLElement; diff --git a/blocksuite/affine/data-view/src/view-presets/table/pc/header/database-header-column.ts b/blocksuite/affine/data-view/src/view-presets/table/pc/header/database-header-column.ts index bac236a2b3356..b518e13e5b74d 100644 --- a/blocksuite/affine/data-view/src/view-presets/table/pc/header/database-header-column.ts +++ b/blocksuite/affine/data-view/src/view-presets/table/pc/header/database-header-column.ts @@ -63,14 +63,14 @@ export class DatabaseHeaderColumn extends SignalWatcher( } `; - private _clickColumn = () => { + private readonly _clickColumn = () => { if (this.tableViewManager.readonly$.value) { return; } this.popMenu(); }; - private _clickTypeIcon = (event: MouseEvent) => { + private readonly _clickTypeIcon = (event: MouseEvent) => { if (this.tableViewManager.readonly$.value) { return; } @@ -96,7 +96,7 @@ export class DatabaseHeaderColumn extends SignalWatcher( }); }; - private _contextMenu = (e: MouseEvent) => { + private readonly _contextMenu = (e: MouseEvent) => { if (this.tableViewManager.readonly$.value) { return; } @@ -104,7 +104,7 @@ export class DatabaseHeaderColumn extends SignalWatcher( this.popMenu(e.currentTarget as HTMLElement); }; - private _enterWidthDragBar = () => { + private readonly _enterWidthDragBar = () => { if (this.tableViewManager.readonly$.value) { return; } @@ -115,13 +115,13 @@ export class DatabaseHeaderColumn extends SignalWatcher( this.drawWidthDragBar(); }; - private _leaveWidthDragBar = () => { + private readonly _leaveWidthDragBar = () => { cancelAnimationFrame(this.drawWidthDragBarTask); this.drawWidthDragBarTask = 0; getVerticalIndicator().remove(); }; - private drawWidthDragBar = () => { + private readonly drawWidthDragBar = () => { const rect = getTableGroupRect(this); if (!rect) { return; @@ -136,7 +136,7 @@ export class DatabaseHeaderColumn extends SignalWatcher( private drawWidthDragBarTask = 0; - private widthDragBar = createRef(); + private readonly widthDragBar = createRef(); editTitle = () => { this._clickColumn(); diff --git a/blocksuite/affine/data-view/src/view-presets/table/pc/header/number-format-bar.ts b/blocksuite/affine/data-view/src/view-presets/table/pc/header/number-format-bar.ts index fde8520079d2e..16c83f54d3ff2 100644 --- a/blocksuite/affine/data-view/src/view-presets/table/pc/header/number-format-bar.ts +++ b/blocksuite/affine/data-view/src/view-presets/table/pc/header/number-format-bar.ts @@ -87,14 +87,14 @@ export class DatabaseNumberFormatBar extends WithDisposable(LitElement) { } `; - private _decrementDecimalPlaces = () => { + private readonly _decrementDecimalPlaces = () => { this.column.dataUpdate(data => ({ decimal: Math.max(((data.decimal as number) ?? 0) - 1, 0), })); this.requestUpdate(); }; - private _incrementDecimalPlaces = () => { + private readonly _incrementDecimalPlaces = () => { this.column.dataUpdate(data => ({ decimal: Math.min(((data.decimal as number) ?? 0) + 1, 8), })); diff --git a/blocksuite/affine/data-view/src/view-presets/table/pc/row/row.ts b/blocksuite/affine/data-view/src/view-presets/table/pc/row/row.ts index 0bf5465bf6bae..dc2f635b8dfd8 100644 --- a/blocksuite/affine/data-view/src/view-presets/table/pc/row/row.ts +++ b/blocksuite/affine/data-view/src/view-presets/table/pc/row/row.ts @@ -114,7 +114,7 @@ export class TableRow extends SignalWatcher(WithDisposable(ShadowlessElement)) { } `; - private _clickDragHandler = () => { + private readonly _clickDragHandler = () => { if (this.view.readonly$.value) { return; } diff --git a/blocksuite/affine/data-view/src/view-presets/table/table-view-manager.ts b/blocksuite/affine/data-view/src/view-presets/table/table-view-manager.ts index 6b34fdb89dbab..1c2f7da0ee671 100644 --- a/blocksuite/affine/data-view/src/view-presets/table/table-view-manager.ts +++ b/blocksuite/affine/data-view/src/view-presets/table/table-view-manager.ts @@ -39,7 +39,7 @@ export class TableSingleView extends SingleViewBase { return result; }); - private computedColumns$ = computed(() => { + private readonly computedColumns$ = computed(() => { return this.propertiesWithoutFilter$.value.map(id => { const column = this.propertyGet(id); return { @@ -51,19 +51,19 @@ export class TableSingleView extends SingleViewBase { }); }); - private filter$ = computed(() => { + private readonly filter$ = computed(() => { return this.data$.value?.filter ?? emptyFilterGroup; }); - private groupBy$ = computed(() => { + private readonly groupBy$ = computed(() => { return this.data$.value?.groupBy; }); - private sortList$ = computed(() => { + private readonly sortList$ = computed(() => { return this.data$.value?.sort; }); - private sortManager = this.traitSet( + private readonly sortManager = this.traitSet( sortTraitKey, new SortManager(this.sortList$, this, { setSortList: sortList => { @@ -385,7 +385,7 @@ export class TableColumn extends PropertyBase { } constructor( - private tableView: TableSingleView, + private readonly tableView: TableSingleView, columnId: string ) { super(tableView as SingleView, columnId); diff --git a/blocksuite/affine/data-view/src/widget-presets/quick-setting-bar/filter/condition-view.ts b/blocksuite/affine/data-view/src/widget-presets/quick-setting-bar/filter/condition-view.ts index d835c78d8ab72..ccc9717a4190d 100644 --- a/blocksuite/affine/data-view/src/widget-presets/quick-setting-bar/filter/condition-view.ts +++ b/blocksuite/affine/data-view/src/widget-presets/quick-setting-bar/filter/condition-view.ts @@ -82,13 +82,13 @@ export class FilterConditionView extends SignalWatcher(ShadowlessElement) { } `; - private onClickButton = (evt: Event) => { + private readonly onClickButton = (evt: Event) => { this.popConditionEdit( popupTargetFromElement(evt.currentTarget as HTMLElement) ); }; - private popConditionEdit = (target: PopupTarget) => { + private readonly popConditionEdit = (target: PopupTarget) => { const type = this.leftVar$.value?.type; if (!type) { return; diff --git a/blocksuite/affine/data-view/src/widget-presets/quick-setting-bar/filter/group-panel-view.ts b/blocksuite/affine/data-view/src/widget-presets/quick-setting-bar/filter/group-panel-view.ts index 2b7312126d114..db4bda2b344c8 100644 --- a/blocksuite/affine/data-view/src/widget-presets/quick-setting-bar/filter/group-panel-view.ts +++ b/blocksuite/affine/data-view/src/widget-presets/quick-setting-bar/filter/group-panel-view.ts @@ -184,7 +184,7 @@ export class FilterGroupView extends SignalWatcher(ShadowlessElement) { } `; - private _addNew = (e: MouseEvent) => { + private readonly _addNew = (e: MouseEvent) => { if (this.isMaxDepth) { this.onChange({ ...this.filterGroup.value, @@ -202,7 +202,7 @@ export class FilterGroupView extends SignalWatcher(ShadowlessElement) { }); }; - private _selectOp = (event: MouseEvent) => { + private readonly _selectOp = (event: MouseEvent) => { popFilterableSimpleMenu( popupTargetFromElement(event.currentTarget as HTMLElement), [ @@ -228,7 +228,7 @@ export class FilterGroupView extends SignalWatcher(ShadowlessElement) { ); }; - private _setFilter = (index: number, filter: Filter) => { + private readonly _setFilter = (index: number, filter: Filter) => { this.onChange({ ...this.filterGroup.value, conditions: this.filterGroup.value.conditions.map((v, i) => @@ -237,7 +237,7 @@ export class FilterGroupView extends SignalWatcher(ShadowlessElement) { }); }; - private opMap = { + private readonly opMap = { and: 'And', or: 'Or', }; diff --git a/blocksuite/affine/data-view/src/widget-presets/quick-setting-bar/filter/list-view.ts b/blocksuite/affine/data-view/src/widget-presets/quick-setting-bar/filter/list-view.ts index 412fba0a0b9c6..97741b0a2ce25 100644 --- a/blocksuite/affine/data-view/src/widget-presets/quick-setting-bar/filter/list-view.ts +++ b/blocksuite/affine/data-view/src/widget-presets/quick-setting-bar/filter/list-view.ts @@ -77,7 +77,7 @@ export class FilterBar extends SignalWatcher(ShadowlessElement) { } `; - private _setFilter = (index: number, filter: Filter) => { + private readonly _setFilter = (index: number, filter: Filter) => { this.onChange({ ...this.filterGroup.value, conditions: this.filterGroup.value.conditions.map((v, i) => @@ -86,7 +86,7 @@ export class FilterBar extends SignalWatcher(ShadowlessElement) { }); }; - private addFilter = (e: MouseEvent) => { + private readonly addFilter = (e: MouseEvent) => { const element = popupTargetFromElement(e.target as HTMLElement); popCreateFilter(element, { vars: this.vars, @@ -103,7 +103,7 @@ export class FilterBar extends SignalWatcher(ShadowlessElement) { }); }; - private expandGroup = (position: PopupTarget, i: number) => { + private readonly expandGroup = (position: PopupTarget, i: number) => { if (this.filterGroup.value.conditions[i]?.type !== 'group') { return; } diff --git a/blocksuite/affine/data-view/src/widget-presets/quick-setting-bar/filter/root-panel-view.ts b/blocksuite/affine/data-view/src/widget-presets/quick-setting-bar/filter/root-panel-view.ts index 5e89c10b73b2f..083f80e96cab2 100644 --- a/blocksuite/affine/data-view/src/widget-presets/quick-setting-bar/filter/root-panel-view.ts +++ b/blocksuite/affine/data-view/src/widget-presets/quick-setting-bar/filter/root-panel-view.ts @@ -158,7 +158,7 @@ export class FilterRootView extends SignalWatcher(ShadowlessElement) { } `; - private _setFilter = (index: number, filter: Filter) => { + private readonly _setFilter = (index: number, filter: Filter) => { this.onChange({ ...this.filterGroup.value, conditions: this.filterGroup.value.conditions.map((v, i) => @@ -167,7 +167,7 @@ export class FilterRootView extends SignalWatcher(ShadowlessElement) { }); }; - private expandGroup = (position: PopupTarget, i: number) => { + private readonly expandGroup = (position: PopupTarget, i: number) => { if (this.filterGroup.value.conditions[i]?.type !== 'group') { return; } diff --git a/blocksuite/affine/data-view/src/widget-presets/tools/presets/search/search.ts b/blocksuite/affine/data-view/src/widget-presets/tools/presets/search/search.ts index e7b2d8991a3ab..ed751f1abd7aa 100644 --- a/blocksuite/affine/data-view/src/widget-presets/tools/presets/search/search.ts +++ b/blocksuite/affine/data-view/src/widget-presets/tools/presets/search/search.ts @@ -93,7 +93,7 @@ export class DataViewHeaderToolsSearch extends WidgetBase< > { static override styles = styles; - private _clearSearch = () => { + private readonly _clearSearch = () => { this._searchInput.value = ''; this.view.setSearch(''); this.preventBlur = true; @@ -102,25 +102,25 @@ export class DataViewHeaderToolsSearch extends WidgetBase< }); }; - private _clickSearch = (e: MouseEvent) => { + private readonly _clickSearch = (e: MouseEvent) => { e.stopPropagation(); this.showSearch = true; }; - private _onSearch = (event: InputEvent) => { + private readonly _onSearch = (event: InputEvent) => { const el = event.target as HTMLInputElement; const inputValue = el.value.trim(); this.view.setSearch(inputValue); }; - private _onSearchBlur = () => { + private readonly _onSearchBlur = () => { if (this._searchInput.value || this.preventBlur) { return; } this.showSearch = false; }; - private _onSearchKeydown = (event: KeyboardEvent) => { + private readonly _onSearchKeydown = (event: KeyboardEvent) => { if (event.key === 'Escape') { if (this._searchInput.value) { this._searchInput.value = ''; diff --git a/blocksuite/affine/data-view/src/widget-presets/tools/presets/table-add-row/add-row.ts b/blocksuite/affine/data-view/src/widget-presets/tools/presets/table-add-row/add-row.ts index 159ad672525a7..01577e0c5c572 100644 --- a/blocksuite/affine/data-view/src/widget-presets/tools/presets/table-add-row/add-row.ts +++ b/blocksuite/affine/data-view/src/widget-presets/tools/presets/table-add-row/add-row.ts @@ -19,7 +19,7 @@ const styles = css` export class DataViewHeaderToolsAddRow extends WidgetBase { static override styles = styles; - private _onAddNewRecord = () => { + private readonly _onAddNewRecord = () => { if (this.readonly) return; this.viewMethods.addRow?.('start'); }; diff --git a/blocksuite/affine/model/src/elements/mindmap/mindmap.ts b/blocksuite/affine/model/src/elements/mindmap/mindmap.ts index 8316b0abf5f76..9be393e3c511e 100644 --- a/blocksuite/affine/model/src/elements/mindmap/mindmap.ts +++ b/blocksuite/affine/model/src/elements/mindmap/mindmap.ts @@ -151,7 +151,7 @@ export class MindmapElementModel extends GfxGroupLikeElementModel(); + private readonly _stashedNode = new Set(); private _tree!: MindmapRoot; diff --git a/blocksuite/affine/model/src/elements/mindmap/style.ts b/blocksuite/affine/model/src/elements/mindmap/style.ts index 3c72c03c8ad23..08e88aed41d25 100644 --- a/blocksuite/affine/model/src/elements/mindmap/style.ts +++ b/blocksuite/affine/model/src/elements/mindmap/style.ts @@ -77,7 +77,7 @@ export abstract class MindmapStyleGetter { } export class StyleOne extends MindmapStyleGetter { - private _colorOrders = [ + private readonly _colorOrders = [ LineColor.Purple, LineColor.Magenta, LineColor.Orange, @@ -188,7 +188,7 @@ export class StyleOne extends MindmapStyleGetter { export const styleOne = new StyleOne(); export class StyleTwo extends MindmapStyleGetter { - private _colorOrders = [ + private readonly _colorOrders = [ ShapeFillColor.Blue, '#7ae2d5', ShapeFillColor.Yellow, @@ -298,7 +298,11 @@ export class StyleTwo extends MindmapStyleGetter { export const styleTwo = new StyleTwo(); export class StyleThree extends MindmapStyleGetter { - private _strokeColor = [LineColor.Yellow, LineColor.Green, LineColor.Teal]; + private readonly _strokeColor = [ + LineColor.Yellow, + LineColor.Green, + LineColor.Teal, + ]; readonly root = { radius: 10, @@ -402,7 +406,7 @@ export class StyleThree extends MindmapStyleGetter { export const styleThree = new StyleThree(); export class StyleFour extends MindmapStyleGetter { - private _colors = [ + private readonly _colors = [ ShapeFillColor.Purple, ShapeFillColor.Magenta, ShapeFillColor.Orange, diff --git a/blocksuite/affine/shared/src/services/edit-props-store.ts b/blocksuite/affine/shared/src/services/edit-props-store.ts index d5353723408f1..9c083adbac160 100644 --- a/blocksuite/affine/shared/src/services/edit-props-store.ts +++ b/blocksuite/affine/shared/src/services/edit-props-store.ts @@ -78,9 +78,9 @@ function customizer(_target: unknown, source: unknown) { export class EditPropsStore extends LifeCycleWatcher { static override key = 'EditPropsStore'; - private _disposables = new DisposableGroup(); + private readonly _disposables = new DisposableGroup(); - private innerProps$: Signal> = signal({}); + private readonly innerProps$: Signal> = signal({}); lastProps$: Signal; diff --git a/blocksuite/affine/shared/src/services/embed-option-service.ts b/blocksuite/affine/shared/src/services/embed-option-service.ts index 356a5659c2eb0..3762a99e21f43 100644 --- a/blocksuite/affine/shared/src/services/embed-option-service.ts +++ b/blocksuite/affine/shared/src/services/embed-option-service.ts @@ -23,7 +23,7 @@ export class EmbedOptionService extends Extension implements EmbedOptionProvider { - private _embedBlockRegistry = new Set(); + private readonly _embedBlockRegistry = new Set(); getEmbedBlockOptions = (url: string): EmbedOptions | null => { const entries = this._embedBlockRegistry.entries(); diff --git a/blocksuite/affine/shared/src/services/theme-service.ts b/blocksuite/affine/shared/src/services/theme-service.ts index 734ea3164bb02..5c789f74c4b9f 100644 --- a/blocksuite/affine/shared/src/services/theme-service.ts +++ b/blocksuite/affine/shared/src/services/theme-service.ts @@ -61,7 +61,7 @@ export class ThemeService extends Extension { return isInsideEdgelessEditor(this.std.host) ? this.edgeless$ : this.app$; } - constructor(private std: BlockStdScope) { + constructor(private readonly std: BlockStdScope) { super(); const extension = this.std.getOptional(ThemeExtensionIdentifier); this.app$ = extension?.getAppTheme?.() || getThemeObserver().theme$; @@ -172,7 +172,7 @@ export class ThemeService extends Extension { } export class ThemeObserver { - private observer: MutationObserver; + private readonly observer: MutationObserver; theme$ = signal(ColorScheme.Light); diff --git a/blocksuite/affine/shared/src/utils/spec/spec-provider.ts b/blocksuite/affine/shared/src/utils/spec/spec-provider.ts index ac635db1d1f30..d81b4363b6377 100644 --- a/blocksuite/affine/shared/src/utils/spec/spec-provider.ts +++ b/blocksuite/affine/shared/src/utils/spec/spec-provider.ts @@ -6,7 +6,7 @@ import { SpecBuilder } from './spec-builder.js'; export class SpecProvider { static instance: SpecProvider; - private specMap = new Map(); + private readonly specMap = new Map(); private constructor() {} diff --git a/blocksuite/affine/widget-scroll-anchoring/src/scroll-anchoring.ts b/blocksuite/affine/widget-scroll-anchoring/src/scroll-anchoring.ts index 432576be3ace0..e96632eadb197 100644 --- a/blocksuite/affine/widget-scroll-anchoring/src/scroll-anchoring.ts +++ b/blocksuite/affine/widget-scroll-anchoring/src/scroll-anchoring.ts @@ -54,9 +54,11 @@ export class AffineScrollAnchoringWidget extends WidgetComponent { #listened = false; - #requestUpdateFn = () => this.requestUpdate(); + readonly #requestUpdateFn = () => this.requestUpdate(); - #resizeObserver: ResizeObserver = new ResizeObserver(this.#requestUpdateFn); + readonly #resizeObserver: ResizeObserver = new ResizeObserver( + this.#requestUpdateFn + ); anchor$ = signal(null); diff --git a/blocksuite/blocks/src/_common/adapters/html-adapter/html.ts b/blocksuite/blocks/src/_common/adapters/html-adapter/html.ts index 373e5b2e00374..6c77f8054d200 100644 --- a/blocksuite/blocks/src/_common/adapters/html-adapter/html.ts +++ b/blocksuite/blocks/src/_common/adapters/html-adapter/html.ts @@ -51,11 +51,11 @@ type HtmlToSliceSnapshotPayload = { }; export class HtmlAdapter extends BaseAdapter { - private _astToHtml = (ast: Root) => { + private readonly _astToHtml = (ast: Root) => { return unified().use(rehypeStringify).stringify(ast); }; - private _traverseHtml = async ( + private readonly _traverseHtml = async ( html: HtmlAST, snapshot: BlockSnapshot, assets?: AssetsManager @@ -108,7 +108,7 @@ export class HtmlAdapter extends BaseAdapter { return walker.walk(html, snapshot); }; - private _traverseSnapshot = async ( + private readonly _traverseSnapshot = async ( snapshot: BlockSnapshot, html: HtmlAST, assets?: AssetsManager diff --git a/blocksuite/blocks/src/_common/adapters/markdown/markdown.ts b/blocksuite/blocks/src/_common/adapters/markdown/markdown.ts index 1a0acba7bcdf0..36805f19f08b1 100644 --- a/blocksuite/blocks/src/_common/adapters/markdown/markdown.ts +++ b/blocksuite/blocks/src/_common/adapters/markdown/markdown.ts @@ -50,7 +50,7 @@ type MarkdownToSliceSnapshotPayload = { }; export class MarkdownAdapter extends BaseAdapter { - private _traverseMarkdown = ( + private readonly _traverseMarkdown = ( markdown: MarkdownAST, snapshot: BlockSnapshot, assets?: AssetsManager @@ -105,7 +105,7 @@ export class MarkdownAdapter extends BaseAdapter { return walker.walk(markdown, snapshot); }; - private _traverseSnapshot = async ( + private readonly _traverseSnapshot = async ( snapshot: BlockSnapshot, markdown: MarkdownAST, assets?: AssetsManager diff --git a/blocksuite/blocks/src/_common/adapters/mix-text.ts b/blocksuite/blocks/src/_common/adapters/mix-text.ts index a1aa791acfa31..f082fa742940b 100644 --- a/blocksuite/blocks/src/_common/adapters/mix-text.ts +++ b/blocksuite/blocks/src/_common/adapters/mix-text.ts @@ -38,7 +38,7 @@ type MixTextToSliceSnapshotPayload = { }; export class MixTextAdapter extends BaseAdapter { - private _markdownAdapter: MarkdownAdapter; + private readonly _markdownAdapter: MarkdownAdapter; constructor(job: Job) { super(job); diff --git a/blocksuite/blocks/src/_common/adapters/notion-html/notion-html.ts b/blocksuite/blocks/src/_common/adapters/notion-html/notion-html.ts index 6f34641b3ef9a..885da785cc619 100644 --- a/blocksuite/blocks/src/_common/adapters/notion-html/notion-html.ts +++ b/blocksuite/blocks/src/_common/adapters/notion-html/notion-html.ts @@ -54,7 +54,7 @@ type NotionHtmlToDocSnapshotPayload = { type NotionHtmlToBlockSnapshotPayload = NotionHtmlToDocSnapshotPayload; export class NotionHtmlAdapter extends BaseAdapter { - private _traverseNotionHtml = async ( + private readonly _traverseNotionHtml = async ( html: HtmlAST, snapshot: BlockSnapshot, assets?: AssetsManager, diff --git a/blocksuite/blocks/src/_common/components/ai-item/ai-item-list.ts b/blocksuite/blocks/src/_common/components/ai-item/ai-item-list.ts index 75c3e717fe156..e76a82454da61 100644 --- a/blocksuite/blocks/src/_common/components/ai-item/ai-item-list.ts +++ b/blocksuite/blocks/src/_common/components/ai-item/ai-item-list.ts @@ -48,7 +48,7 @@ export class AIItemList extends WithDisposable(LitElement) { private _activeSubMenuItem: AIItemConfig | null = null; - private _closeSubMenu = () => { + private readonly _closeSubMenu = () => { if (this._abortController) { this._abortController.abort(); this._abortController = null; @@ -56,11 +56,11 @@ export class AIItemList extends WithDisposable(LitElement) { this._activeSubMenuItem = null; }; - private _itemClassName = (item: AIItemConfig) => { + private readonly _itemClassName = (item: AIItemConfig) => { return 'ai-item-' + item.name.split(' ').join('-').toLocaleLowerCase(); }; - private _openSubMenu = (item: AIItemConfig) => { + private readonly _openSubMenu = (item: AIItemConfig) => { if (!item.subItem || item.subItem.length === 0) { this._closeSubMenu(); return; diff --git a/blocksuite/blocks/src/_common/components/ai-item/ai-sub-item-list.ts b/blocksuite/blocks/src/_common/components/ai-item/ai-sub-item-list.ts index fc62784dcb172..4be353ac18174 100644 --- a/blocksuite/blocks/src/_common/components/ai-item/ai-sub-item-list.ts +++ b/blocksuite/blocks/src/_common/components/ai-item/ai-sub-item-list.ts @@ -45,7 +45,7 @@ export class AISubItemList extends WithDisposable(LitElement) { ${menuItemStyles} `; - private _handleClick = (subItem: AISubItemConfig) => { + private readonly _handleClick = (subItem: AISubItemConfig) => { this.onClick?.(); if (subItem.handler) { // TODO: add parameters to ai handler diff --git a/blocksuite/blocks/src/_common/components/embed-card/modal/embed-card-create-modal.ts b/blocksuite/blocks/src/_common/components/embed-card/modal/embed-card-create-modal.ts index 54da070ca63d1..014ad07abdf58 100644 --- a/blocksuite/blocks/src/_common/components/embed-card/modal/embed-card-create-modal.ts +++ b/blocksuite/blocks/src/_common/components/embed-card/modal/embed-card-create-modal.ts @@ -22,11 +22,11 @@ import { embedCardModalStyles } from './styles.js'; export class EmbedCardCreateModal extends WithDisposable(ShadowlessElement) { static override styles = embedCardModalStyles; - private _onCancel = () => { + private readonly _onCancel = () => { this.remove(); }; - private _onConfirm = () => { + private readonly _onConfirm = () => { const url = this.input.value; if (!isValidUrl(url)) { @@ -91,7 +91,7 @@ export class EmbedCardCreateModal extends WithDisposable(ShadowlessElement) { this.remove(); }; - private _onDocumentKeydown = (e: KeyboardEvent) => { + private readonly _onDocumentKeydown = (e: KeyboardEvent) => { e.stopPropagation(); if (e.key === 'Enter' && !e.isComposing) { this._onConfirm(); diff --git a/blocksuite/blocks/src/_common/components/embed-card/modal/embed-card-edit-modal.ts b/blocksuite/blocks/src/_common/components/embed-card/modal/embed-card-edit-modal.ts index c4639d0ba4ebd..2bdc565ed3c50 100644 --- a/blocksuite/blocks/src/_common/components/embed-card/modal/embed-card-edit-modal.ts +++ b/blocksuite/blocks/src/_common/components/embed-card/modal/embed-card-edit-modal.ts @@ -144,11 +144,11 @@ export class EmbedCardEditModal extends SignalWatcher( private _blockComponent: BlockComponent | null = null; - private _hide = () => { + private readonly _hide = () => { this.remove(); }; - private _onKeydown = (e: KeyboardEvent) => { + private readonly _onKeydown = (e: KeyboardEvent) => { e.stopPropagation(); if (e.key === 'Enter' && !(e.isComposing || e.shiftKey)) { this._onSave(); @@ -159,7 +159,7 @@ export class EmbedCardEditModal extends SignalWatcher( } }; - private _onReset = () => { + private readonly _onReset = () => { const blockComponent = this._blockComponent; if (!blockComponent) { @@ -186,7 +186,7 @@ export class EmbedCardEditModal extends SignalWatcher( this.remove(); }; - private _onSave = () => { + private readonly _onSave = () => { const blockComponent = this._blockComponent; if (!blockComponent) { @@ -224,12 +224,12 @@ export class EmbedCardEditModal extends SignalWatcher( this.remove(); }; - private _updateDescription = (e: InputEvent) => { + private readonly _updateDescription = (e: InputEvent) => { const target = e.target as HTMLTextAreaElement; this.description$.value = target.value; }; - private _updateTitle = (e: InputEvent) => { + private readonly _updateTitle = (e: InputEvent) => { const target = e.target as HTMLInputElement; this.title$.value = target.value; }; diff --git a/blocksuite/blocks/src/_common/components/file-drop-manager.ts b/blocksuite/blocks/src/_common/components/file-drop-manager.ts index 1acb9b24e997e..07d515c8310fd 100644 --- a/blocksuite/blocks/src/_common/components/file-drop-manager.ts +++ b/blocksuite/blocks/src/_common/components/file-drop-manager.ts @@ -31,13 +31,13 @@ export type FileDropOptions = { export class FileDropManager { private static _dropResult: DropResult | null = null; - private _blockService: BlockService; + private readonly _blockService: BlockService; - private _fileDropOptions: FileDropOptions; + private readonly _fileDropOptions: FileDropOptions; - private _indicator!: DragIndicator; + private readonly _indicator!: DragIndicator; - private _onDrop = (event: DragEvent) => { + private readonly _onDrop = (event: DragEvent) => { this._indicator.rect = null; const { onDrop } = this._fileDropOptions; diff --git a/blocksuite/blocks/src/_common/components/smooth-corner.ts b/blocksuite/blocks/src/_common/components/smooth-corner.ts index 803237d47101e..71ffde7bdb79f 100644 --- a/blocksuite/blocks/src/_common/components/smooth-corner.ts +++ b/blocksuite/blocks/src/_common/components/smooth-corner.ts @@ -64,7 +64,7 @@ export class SmoothCorner extends LitElement { } `; - private _resizeObserver: ResizeObserver | null = null; + private readonly _resizeObserver: ResizeObserver | null = null; get _path() { return getFigmaSquircleSvgPath({ diff --git a/blocksuite/blocks/src/_common/configs/quick-action/config.ts b/blocksuite/blocks/src/_common/configs/quick-action/config.ts index 1c9f408071edc..dacc63414361a 100644 --- a/blocksuite/blocks/src/_common/configs/quick-action/config.ts +++ b/blocksuite/blocks/src/_common/configs/quick-action/config.ts @@ -137,16 +137,18 @@ export const quickActionConfig: QuickActionConfig[] = [ const doc = host.doc; const autofill = getTitleFromSelectedModels(selectedModels); - void promptDocTitle(host, autofill).then(title => { - if (title === null) return; - convertSelectedBlocksToLinkedDoc( - host.std, - doc, - draftedModels, - title - ).catch(console.error); - notifyDocCreated(host, doc); - }); + promptDocTitle(host, autofill) + .then(title => { + if (title === null) return; + convertSelectedBlocksToLinkedDoc( + host.std, + doc, + draftedModels, + title + ).catch(console.error); + notifyDocCreated(host, doc); + }) + .catch(console.error); }, }, ]; diff --git a/blocksuite/blocks/src/_common/export-manager/export-manager.ts b/blocksuite/blocks/src/_common/export-manager/export-manager.ts index b5b2bfabd618d..a22f4f522dfeb 100644 --- a/blocksuite/blocks/src/_common/export-manager/export-manager.ts +++ b/blocksuite/blocks/src/_common/export-manager/export-manager.ts @@ -44,11 +44,11 @@ export type ExportOptions = { imageProxyEndpoint: string; }; export class ExportManager { - private _exportOptions: ExportOptions = { + private readonly _exportOptions: ExportOptions = { imageProxyEndpoint: DEFAULT_IMAGE_PROXY_ENDPOINT, }; - private _replaceRichTextWithSvgElement = (element: HTMLElement) => { + private readonly _replaceRichTextWithSvgElement = (element: HTMLElement) => { const richList = Array.from(element.querySelectorAll('.inline-editor')); richList.forEach(rich => { const svgEle = this._elementToSvgElement( diff --git a/blocksuite/blocks/src/_common/transformers/utils.ts b/blocksuite/blocks/src/_common/transformers/utils.ts index cf3256156398c..1c8cd19b5bc01 100644 --- a/blocksuite/blocks/src/_common/transformers/utils.ts +++ b/blocksuite/blocks/src/_common/transformers/utils.ts @@ -8,7 +8,7 @@ export class Zip { private finalized = false; - private zip = new fflate.Zip((err, chunk, final) => { + private readonly zip = new fflate.Zip((err, chunk, final) => { if (!err) { const temp = new Uint8Array(this.compressed.length + chunk.length); temp.set(this.compressed); diff --git a/blocksuite/blocks/src/attachment-block/attachment-service.ts b/blocksuite/blocks/src/attachment-block/attachment-service.ts index 3e01be68fd18f..d4d7ec14ef7a2 100644 --- a/blocksuite/blocks/src/attachment-block/attachment-service.ts +++ b/blocksuite/blocks/src/attachment-block/attachment-service.ts @@ -26,7 +26,7 @@ import { addSiblingAttachmentBlocks } from './utils.js'; export class AttachmentBlockService extends BlockService { static override readonly flavour = AttachmentBlockSchema.model.flavour; - private _fileDropOptions: FileDropOptions = { + private readonly _fileDropOptions: FileDropOptions = { flavour: this.flavour, onDrop: async ({ files, targetModel, place, point }) => { if (!files.length) return false; diff --git a/blocksuite/blocks/src/attachment-block/embed.ts b/blocksuite/blocks/src/attachment-block/embed.ts index 3eb19541ac1af..2f5d492e16f68 100644 --- a/blocksuite/blocks/src/attachment-block/embed.ts +++ b/blocksuite/blocks/src/attachment-block/embed.ts @@ -67,7 +67,7 @@ export class AttachmentEmbedService extends Extension { return this.configs.values(); } - constructor(private configs: Map) { + constructor(private readonly configs: Map) { super(); } diff --git a/blocksuite/blocks/src/data-view-block/data-source.ts b/blocksuite/blocks/src/data-view-block/data-source.ts index 1a6ee03831147..cd337f5d3de70 100644 --- a/blocksuite/blocks/src/data-view-block/data-source.ts +++ b/blocksuite/blocks/src/data-view-block/data-source.ts @@ -24,9 +24,12 @@ export type BlockQueryDataSourceConfig = { // @ts-expect-error FIXME: ts error export class BlockQueryDataSource extends DataSourceBase { - private columnMetaMap = new Map>(); + private readonly columnMetaMap = new Map< + string, + PropertyMetaConfig + >(); - private meta: BlockMeta; + private readonly meta: BlockMeta; blockMap = new Map(); @@ -60,8 +63,8 @@ export class BlockQueryDataSource extends DataSourceBase { } constructor( - private host: EditorHost, - private block: DataViewBlockModel, + private readonly host: EditorHost, + private readonly block: DataViewBlockModel, config: BlockQueryDataSourceConfig ) { super(); diff --git a/blocksuite/blocks/src/data-view-block/data-view-block.ts b/blocksuite/blocks/src/data-view-block/data-view-block.ts index 3135d5347eb3a..6c14a174cc2dc 100644 --- a/blocksuite/blocks/src/data-view-block/data-view-block.ts +++ b/blocksuite/blocks/src/data-view-block/data-view-block.ts @@ -92,7 +92,7 @@ export class DataViewBlockComponent extends CaptionedBlockComponent { + private readonly _clickDatabaseOps = (e: MouseEvent) => { popMenu(popupTargetFromElement(e.currentTarget as HTMLElement), { options: { items: [ @@ -136,7 +136,7 @@ export class DataViewBlockComponent extends CaptionedBlockComponent { return { diff --git a/blocksuite/blocks/src/database-block/components/title/index.ts b/blocksuite/blocks/src/database-block/components/title/index.ts index 67eeb70a907ef..b71bbb9b94a9d 100644 --- a/blocksuite/blocks/src/database-block/components/title/index.ts +++ b/blocksuite/blocks/src/database-block/components/title/index.ts @@ -70,29 +70,29 @@ export class DatabaseTitle extends WithDisposable(ShadowlessElement) { } `; - private compositionEnd = () => { + private readonly compositionEnd = () => { this.titleText.replace(0, this.titleText.length, this.input.value); }; - private onBlur = () => { + private readonly onBlur = () => { this.isFocus = false; }; - private onFocus = () => { + private readonly onFocus = () => { this.isFocus = true; if (this.database?.viewSelection$?.value) { this.database?.setSelection(undefined); } }; - private onInput = (e: InputEvent) => { + private readonly onInput = (e: InputEvent) => { this.text = this.input.value; if (!e.isComposing) { this.titleText.replace(0, this.titleText.length, this.input.value); } }; - private onKeyDown = (event: KeyboardEvent) => { + private readonly onKeyDown = (event: KeyboardEvent) => { event.stopPropagation(); if (event.key === 'Enter' && !event.isComposing) { event.preventDefault(); diff --git a/blocksuite/blocks/src/database-block/database-block.ts b/blocksuite/blocks/src/database-block/database-block.ts index b649c16880ff2..cd1fe8d5a13f1 100644 --- a/blocksuite/blocks/src/database-block/database-block.ts +++ b/blocksuite/blocks/src/database-block/database-block.ts @@ -106,7 +106,7 @@ export class DatabaseBlockComponent extends CaptionedBlockComponent< } `; - private _clickDatabaseOps = (e: MouseEvent) => { + private readonly _clickDatabaseOps = (e: MouseEvent) => { const options = this.optionsConfig.configure(this.model, { items: [ menu.input({ @@ -156,9 +156,9 @@ export class DatabaseBlockComponent extends CaptionedBlockComponent< private _dataSource?: DatabaseBlockDataSource; - private dataView = new DataView(); + private readonly dataView = new DataView(); - private renderTitle = (dataViewMethod: DataViewInstance) => { + private readonly renderTitle = (dataViewMethod: DataViewInstance) => { const addRow = () => dataViewMethod.addRow?.('start'); return html` { } `; - private _onClick = (event: Event) => { + private readonly _onClick = (event: Event) => { event.stopPropagation(); const value = this.value ?? ''; @@ -102,7 +102,7 @@ export class LinkCell extends BaseCellRenderer { } }; - private _onEdit = (e: Event) => { + private readonly _onEdit = (e: Event) => { e.stopPropagation(); this.selectCurrentCell(true); }; @@ -199,13 +199,13 @@ export class LinkCellEditing extends BaseCellRenderer { } `; - private _focusEnd = () => { + private readonly _focusEnd = () => { const end = this._container.value.length; this._container.focus(); this._container.setSelectionRange(end, end); }; - private _onKeydown = (e: KeyboardEvent) => { + private readonly _onKeydown = (e: KeyboardEvent) => { if (e.key === 'Enter' && !e.isComposing) { this._setValue(); setTimeout(() => { @@ -214,7 +214,7 @@ export class LinkCellEditing extends BaseCellRenderer { } }; - private _setValue = (value: string = this._container.value) => { + private readonly _setValue = (value: string = this._container.value) => { let url = value; if (isValidUrl(value)) { url = normalizeUrl(value); diff --git a/blocksuite/blocks/src/database-block/properties/rich-text/cell-renderer.ts b/blocksuite/blocks/src/database-block/properties/rich-text/cell-renderer.ts index be5809fbe5472..eac5cbff52360 100644 --- a/blocksuite/blocks/src/database-block/properties/rich-text/cell-renderer.ts +++ b/blocksuite/blocks/src/database-block/properties/rich-text/cell-renderer.ts @@ -205,7 +205,7 @@ export class RichTextCellEditing extends BaseCellRenderer { } `; - private _handleKeyDown = (event: KeyboardEvent) => { + private readonly _handleKeyDown = (event: KeyboardEvent) => { if (event.key !== 'Escape') { if (event.key === 'Tab') { event.preventDefault(); @@ -274,12 +274,12 @@ export class RichTextCellEditing extends BaseCellRenderer { } }; - private _initYText = (text?: string) => { + private readonly _initYText = (text?: string) => { const yText = new Text(text); this.onChange(yText); }; - private _onSoftEnter = () => { + private readonly _onSoftEnter = () => { if (this.value && this.inlineEditor) { const inlineRange = this.inlineEditor.getInlineRange(); assertExists(inlineRange); diff --git a/blocksuite/blocks/src/database-block/properties/title/text.ts b/blocksuite/blocks/src/database-block/properties/title/text.ts index 00345d32fbf52..2e74c82cca677 100644 --- a/blocksuite/blocks/src/database-block/properties/title/text.ts +++ b/blocksuite/blocks/src/database-block/properties/title/text.ts @@ -205,7 +205,7 @@ export class HeaderAreaTextCell extends BaseTextCell { } export class HeaderAreaTextCellEditing extends BaseTextCell { - private _onCopy = (e: ClipboardEvent) => { + private readonly _onCopy = (e: ClipboardEvent) => { const inlineEditor = this.inlineEditor; assertExists(inlineEditor); @@ -222,7 +222,7 @@ export class HeaderAreaTextCellEditing extends BaseTextCell { e.stopPropagation(); }; - private _onCut = (e: ClipboardEvent) => { + private readonly _onCut = (e: ClipboardEvent) => { const inlineEditor = this.inlineEditor; assertExists(inlineEditor); @@ -244,7 +244,7 @@ export class HeaderAreaTextCellEditing extends BaseTextCell { e.stopPropagation(); }; - private _onPaste = (e: ClipboardEvent) => { + private readonly _onPaste = (e: ClipboardEvent) => { const inlineEditor = this.inlineEditor; const inlineRange = inlineEditor?.getInlineRange(); if (!inlineRange) return; diff --git a/blocksuite/blocks/src/edgeless-text-block/edgeless-text-block.ts b/blocksuite/blocks/src/edgeless-text-block/edgeless-text-block.ts index ed041542eb1f6..a6c82d4624041 100644 --- a/blocksuite/blocks/src/edgeless-text-block/edgeless-text-block.ts +++ b/blocksuite/blocks/src/edgeless-text-block/edgeless-text-block.ts @@ -31,7 +31,7 @@ export class EdgelessTextBlockComponent extends GfxBlockComponent { + private readonly _resizeObserver = new ResizeObserver(() => { if (this.doc.readonly) { return; } diff --git a/blocksuite/blocks/src/image-block/image-service.ts b/blocksuite/blocks/src/image-block/image-service.ts index 90a91e952a825..f4c8f5d67c388 100644 --- a/blocksuite/blocks/src/image-block/image-service.ts +++ b/blocksuite/blocks/src/image-block/image-service.ts @@ -28,7 +28,7 @@ export class ImageBlockService extends BlockService { static setImageProxyURL = setImageProxyMiddlewareURL; - private _fileDropOptions: FileDropOptions = { + private readonly _fileDropOptions: FileDropOptions = { flavour: this.flavour, onDrop: async ({ files, targetModel, place, point }) => { const imageFiles = files.filter(file => file.type.startsWith('image/')); diff --git a/blocksuite/blocks/src/note-block/note-service.ts b/blocksuite/blocks/src/note-block/note-service.ts index 90651284a538e..a18dfcc9f0cdc 100644 --- a/blocksuite/blocks/src/note-block/note-service.ts +++ b/blocksuite/blocks/src/note-block/note-service.ts @@ -22,7 +22,7 @@ export class NoteBlockService extends BlockService { private _anchorSel: BlockSelection | null = null; - private _bindMoveBlockHotKey = () => { + private readonly _bindMoveBlockHotKey = () => { return moveBlockConfigs.reduce( (acc, config) => { const keys = config.hotkey.reduce( @@ -46,7 +46,7 @@ export class NoteBlockService extends BlockService { ); }; - private _bindQuickActionHotKey = () => { + private readonly _bindQuickActionHotKey = () => { return quickActionConfig .filter(config => config.hotkey) .reduce( @@ -65,7 +65,7 @@ export class NoteBlockService extends BlockService { ); }; - private _bindTextConversionHotKey = () => { + private readonly _bindTextConversionHotKey = () => { return textConversionConfigs .filter(item => item.hotkey) .reduce( @@ -131,7 +131,7 @@ export class NoteBlockService extends BlockService { private _focusBlock: BlockComponent | null = null; - private _getClosestNoteByBlockId = (blockId: string) => { + private readonly _getClosestNoteByBlockId = (blockId: string) => { const doc = this._std.doc; let parent = doc.getBlock(blockId)?.model ?? null; while (parent) { @@ -143,7 +143,7 @@ export class NoteBlockService extends BlockService { return null; }; - private _onArrowDown = (ctx: UIEventStateContext) => { + private readonly _onArrowDown = (ctx: UIEventStateContext) => { const event = ctx.get('defaultState').event; const [result] = this._std.command @@ -240,7 +240,7 @@ export class NoteBlockService extends BlockService { return result; }; - private _onArrowUp = (ctx: UIEventStateContext) => { + private readonly _onArrowUp = (ctx: UIEventStateContext) => { const event = ctx.get('defaultState').event; const [result] = this._std.command @@ -336,7 +336,7 @@ export class NoteBlockService extends BlockService { return result; }; - private _onBlockShiftDown = (cmd: BlockSuite.CommandChain) => { + private readonly _onBlockShiftDown = (cmd: BlockSuite.CommandChain) => { return cmd .getBlockSelections() .inline<'currentSelectionPath' | 'anchorBlock'>((ctx, next) => { @@ -376,7 +376,7 @@ export class NoteBlockService extends BlockService { .selectBlocksBetween({ tail: true }); }; - private _onBlockShiftUp = (cmd: BlockSuite.CommandChain) => { + private readonly _onBlockShiftUp = (cmd: BlockSuite.CommandChain) => { return cmd .getBlockSelections() .inline<'currentSelectionPath' | 'anchorBlock'>((ctx, next) => { @@ -414,7 +414,7 @@ export class NoteBlockService extends BlockService { .selectBlocksBetween({ tail: false }); }; - private _onEnter = (ctx: UIEventStateContext) => { + private readonly _onEnter = (ctx: UIEventStateContext) => { const event = ctx.get('defaultState').event; const [result] = this._std.command .chain() @@ -461,7 +461,7 @@ export class NoteBlockService extends BlockService { return result; }; - private _onEsc = () => { + private readonly _onEsc = () => { const [result] = this._std.command .chain() .getBlockSelections() @@ -482,7 +482,7 @@ export class NoteBlockService extends BlockService { return result; }; - private _onSelectAll: UIEventHandler = ctx => { + private readonly _onSelectAll: UIEventHandler = ctx => { const selection = this._std.selection; const block = selection.find('block'); if (!block) { @@ -506,7 +506,7 @@ export class NoteBlockService extends BlockService { }); }; - private _onShiftArrowDown = () => { + private readonly _onShiftArrowDown = () => { const [result] = this._std.command .chain() .try(cmd => [ @@ -518,7 +518,7 @@ export class NoteBlockService extends BlockService { return result; }; - private _onShiftArrowUp = () => { + private readonly _onShiftArrowUp = () => { const [result] = this._std.command .chain() .try(cmd => [ @@ -530,7 +530,7 @@ export class NoteBlockService extends BlockService { return result; }; - private _reset = () => { + private readonly _reset = () => { this._anchorSel = null; this._focusBlock = null; }; diff --git a/blocksuite/blocks/src/root-block/clipboard/index.ts b/blocksuite/blocks/src/root-block/clipboard/index.ts index 1b502bc5e7438..75b7d651d83c2 100644 --- a/blocksuite/blocks/src/root-block/clipboard/index.ts +++ b/blocksuite/blocks/src/root-block/clipboard/index.ts @@ -18,7 +18,7 @@ import { ClipboardAdapter } from './adapter.js'; import { copyMiddleware, pasteMiddleware } from './middlewares/index.js'; export class PageClipboard { - private _copySelected = (onCopy?: () => void) => { + private readonly _copySelected = (onCopy?: () => void) => { return this._std.command .chain() .with({ onCopy }) diff --git a/blocksuite/blocks/src/root-block/clipboard/middlewares/paste.ts b/blocksuite/blocks/src/root-block/clipboard/middlewares/paste.ts index 6160bc1eebedd..ba91e9930beec 100644 --- a/blocksuite/blocks/src/root-block/clipboard/middlewares/paste.ts +++ b/blocksuite/blocks/src/root-block/clipboard/middlewares/paste.ts @@ -57,7 +57,7 @@ const findLast = (snapshot: SliceSnapshot): BlockSnapshot | null => { }; class PointState { - private _blockFromPath = (path: string) => { + private readonly _blockFromPath = (path: string) => { const block = this.std.view.getBlock(path); assertExists(block); return block; @@ -88,7 +88,7 @@ class PointState { } class PasteTr { - private _getDeltas = () => { + private readonly _getDeltas = () => { const firstTextSnapshot = this._textFromSnapshot(this.firstSnapshot!); const lastTextSnapshot = this._textFromSnapshot(this.lastSnapshot!); const fromDelta = this.pointState.text.sliceToDelta( @@ -111,7 +111,7 @@ class PasteTr { }; }; - private _mergeCode = () => { + private readonly _mergeCode = () => { const deltas: DeltaOperation[] = [{ retain: this.pointState.point.index }]; this.snapshot.content.forEach((blockSnapshot, i) => { if (blockSnapshot.props.text) { @@ -126,7 +126,7 @@ class PasteTr { this.snapshot.content = []; }; - private _mergeMultiple = () => { + private readonly _mergeMultiple = () => { this._updateFlavour(); const { lastTextSnapshot, toDelta, firstDelta, lastDelta } = @@ -152,7 +152,7 @@ class PasteTr { lastTextSnapshot.delta = [...lastDelta, ...toDelta]; }; - private _mergeSingle = () => { + private readonly _mergeSingle = () => { this._updateFlavour(); const { firstDelta } = this._getDeltas(); const { index, length } = this.pointState.point; @@ -172,14 +172,14 @@ class PasteTr { this._updateSnapshot(); }; - private _textFromSnapshot = (snapshot: BlockSnapshot) => { + private readonly _textFromSnapshot = (snapshot: BlockSnapshot) => { return (snapshot.props.text ?? { delta: [] }) as Record< 'delta', DeltaOperation[] >; }; - private _updateSnapshot = () => { + private readonly _updateSnapshot = () => { if (this.snapshot.content.length === 0) { this.firstSnapshot = this.lastSnapshot = undefined; return; @@ -192,7 +192,7 @@ class PasteTr { private readonly firstSnapshotIsPlainText: boolean; - private lastIndex: number; + private readonly lastIndex: number; private lastSnapshot?: BlockSnapshot; diff --git a/blocksuite/blocks/src/root-block/edgeless/clipboard/clipboard.ts b/blocksuite/blocks/src/root-block/edgeless/clipboard/clipboard.ts index 506f29c456238..7e491364a1ad7 100644 --- a/blocksuite/blocks/src/root-block/edgeless/clipboard/clipboard.ts +++ b/blocksuite/blocks/src/root-block/edgeless/clipboard/clipboard.ts @@ -123,9 +123,9 @@ interface BlockConfig { } export class EdgelessClipboardController extends PageClipboard { - private _blockConfigs: BlockConfig[] = []; + private readonly _blockConfigs: BlockConfig[] = []; - private _initEdgelessClipboard = () => { + private readonly _initEdgelessClipboard = () => { this.host.handleEvent( 'copy', ctx => { @@ -156,7 +156,7 @@ export class EdgelessClipboardController extends PageClipboard { ); }; - private _onCopy = async ( + private readonly _onCopy = async ( _context: UIEventStateContext, surfaceSelection: SurfaceSelection[] ) => { @@ -184,7 +184,7 @@ export class EdgelessClipboardController extends PageClipboard { }); }; - private _onCut = async (_context: UIEventStateContext) => { + private readonly _onCut = async (_context: UIEventStateContext) => { const { surfaceSelections, selectedElements } = this.selectionManager; if (selectedElements.length === 0) return; @@ -214,7 +214,7 @@ export class EdgelessClipboardController extends PageClipboard { }); }; - private _onPaste = async (_context: UIEventStateContext) => { + private readonly _onPaste = async (_context: UIEventStateContext) => { if ( document.activeElement instanceof HTMLInputElement || document.activeElement instanceof HTMLTextAreaElement diff --git a/blocksuite/blocks/src/root-block/edgeless/components/auto-complete/edgeless-auto-complete.ts b/blocksuite/blocks/src/root-block/edgeless/components/auto-complete/edgeless-auto-complete.ts index 90ffe81c8470e..14acc7b094d13 100644 --- a/blocksuite/blocks/src/root-block/edgeless/components/auto-complete/edgeless-auto-complete.ts +++ b/blocksuite/blocks/src/root-block/edgeless/components/auto-complete/edgeless-auto-complete.ts @@ -162,7 +162,7 @@ export class EdgelessAutoComplete extends WithDisposable(LitElement) { private _autoCompleteOverlay!: AutoCompleteOverlay; - private _onPointerDown = (e: PointerEvent, type: Direction) => { + private readonly _onPointerDown = (e: PointerEvent, type: Direction) => { const { service } = this.edgeless; const viewportRect = service.viewport.boundingClientRect; const start = service.viewport.toModelCoord( diff --git a/blocksuite/blocks/src/root-block/edgeless/components/auto-complete/utils.ts b/blocksuite/blocks/src/root-block/edgeless/components/auto-complete/utils.ts index 190f57f24a7ad..0b15186381f73 100644 --- a/blocksuite/blocks/src/root-block/edgeless/components/auto-complete/utils.ts +++ b/blocksuite/blocks/src/root-block/edgeless/components/auto-complete/utils.ts @@ -80,7 +80,7 @@ export class AutoCompleteTextOverlay extends AutoCompleteTargetOverlay { } export class AutoCompleteNoteOverlay extends AutoCompleteTargetOverlay { - private _background: string; + private readonly _background: string; constructor(gfx: GfxController, xywh: XYWH, background: string) { super(gfx, xywh); @@ -110,7 +110,7 @@ export class AutoCompleteNoteOverlay extends AutoCompleteTargetOverlay { } export class AutoCompleteFrameOverlay extends AutoCompleteTargetOverlay { - private _strokeColor; + private readonly _strokeColor; constructor(gfx: GfxController, xywh: XYWH, strokeColor: string) { super(gfx, xywh); @@ -151,7 +151,7 @@ export class AutoCompleteFrameOverlay extends AutoCompleteTargetOverlay { } export class AutoCompleteShapeOverlay extends Overlay { - private _shape: Shape; + private readonly _shape: Shape; constructor( gfx: GfxController, diff --git a/blocksuite/blocks/src/root-block/edgeless/components/color-picker/button.ts b/blocksuite/blocks/src/root-block/edgeless/components/color-picker/button.ts index cb243155cc874..dad904d91bc2c 100644 --- a/blocksuite/blocks/src/root-block/edgeless/components/color-picker/button.ts +++ b/blocksuite/blocks/src/root-block/edgeless/components/color-picker/button.ts @@ -17,7 +17,7 @@ import { keepColor, preprocessColor } from './utils.js'; type Type = 'normal' | 'custom'; export class EdgelessColorPickerButton extends WithDisposable(LitElement) { - #select = (e: ColorEvent) => { + readonly #select = (e: ColorEvent) => { this.#pick({ palette: e.detail }); }; diff --git a/blocksuite/blocks/src/root-block/edgeless/components/color-picker/color-picker.ts b/blocksuite/blocks/src/root-block/edgeless/components/color-picker/color-picker.ts index 1620790d068dd..9e92317922495 100644 --- a/blocksuite/blocks/src/root-block/edgeless/components/color-picker/color-picker.ts +++ b/blocksuite/blocks/src/root-block/edgeless/components/color-picker/color-picker.ts @@ -49,7 +49,7 @@ export class EdgelessColorPicker extends SignalWatcher( #alphaRect = new DOMRect(); - #editAlpha = (e: InputEvent) => { + readonly #editAlpha = (e: InputEvent) => { const target = e.target as HTMLInputElement; const orignalValue = target.value; let value = orignalValue.trim().replace(/[^0-9]/, ''); @@ -71,7 +71,7 @@ export class EdgelessColorPicker extends SignalWatcher( this.#pick(); }; - #editHex = (e: KeyboardEvent) => { + readonly #editHex = (e: KeyboardEvent) => { e.stopPropagation(); const target = e.target as HTMLInputElement; diff --git a/blocksuite/blocks/src/root-block/edgeless/components/frame/frame-preview.ts b/blocksuite/blocks/src/root-block/edgeless/components/frame/frame-preview.ts index e583a650744d5..374a75dbe0783 100644 --- a/blocksuite/blocks/src/root-block/edgeless/components/frame/frame-preview.ts +++ b/blocksuite/blocks/src/root-block/edgeless/components/frame/frame-preview.ts @@ -61,12 +61,12 @@ const styles = css` export class FramePreview extends WithDisposable(ShadowlessElement) { static override styles = styles; - private _clearFrameDisposables = () => { + private readonly _clearFrameDisposables = () => { this._frameDisposables?.dispose(); this._frameDisposables = null; }; - private _docFilter: Query = { + private readonly _docFilter: Query = { mode: 'loose', match: [ { @@ -80,9 +80,10 @@ export class FramePreview extends WithDisposable(ShadowlessElement) { private _previewDoc: Doc | null = null; - private _previewSpec = SpecProvider.getInstance().getSpec('edgeless:preview'); + private readonly _previewSpec = + SpecProvider.getInstance().getSpec('edgeless:preview'); - private _updateFrameViewportWH = () => { + private readonly _updateFrameViewportWH = () => { const [, , w, h] = deserializeXYWH(this.frame.xywh); let scale = 1; diff --git a/blocksuite/blocks/src/root-block/edgeless/components/panel/line-width-panel.ts b/blocksuite/blocks/src/root-block/edgeless/components/panel/line-width-panel.ts index b6b4a47737cc5..c5b7ce815a897 100644 --- a/blocksuite/blocks/src/root-block/edgeless/components/panel/line-width-panel.ts +++ b/blocksuite/blocks/src/root-block/edgeless/components/panel/line-width-panel.ts @@ -107,7 +107,10 @@ export class EdgelessLineWidthPanel extends WithDisposable(LitElement) { private _dragConfig: DragConfig | null = null; - private _getDragHandlePosition = (e: PointerEvent, config: DragConfig) => { + private readonly _getDragHandlePosition = ( + e: PointerEvent, + config: DragConfig + ) => { const x = e.clientX; const { boundLeft, bottomLineWidth, stepWidth, containerWidth } = config; @@ -128,7 +131,7 @@ export class EdgelessLineWidthPanel extends WithDisposable(LitElement) { return dragHandlerPosition; }; - private _onPointerDown = (e: PointerEvent) => { + private readonly _onPointerDown = (e: PointerEvent) => { e.preventDefault(); if (this.disable) return; const { left, width } = this._lineWidthPanel.getBoundingClientRect(); @@ -142,7 +145,7 @@ export class EdgelessLineWidthPanel extends WithDisposable(LitElement) { this._onPointerMove(e); }; - private _onPointerMove = (e: PointerEvent) => { + private readonly _onPointerMove = (e: PointerEvent) => { e.preventDefault(); if (!this._dragConfig) return; const dragHandlerPosition = this._getDragHandlePosition( @@ -154,7 +157,7 @@ export class EdgelessLineWidthPanel extends WithDisposable(LitElement) { this._updateIconsColor(); }; - private _onPointerOut = (e: PointerEvent) => { + private readonly _onPointerOut = (e: PointerEvent) => { // If the pointer is out of the line width panel // Stop dragging and update the selected size by nearest size. e.preventDefault(); @@ -167,7 +170,7 @@ export class EdgelessLineWidthPanel extends WithDisposable(LitElement) { this._dragConfig = null; }; - private _onPointerUp = (e: PointerEvent) => { + private readonly _onPointerUp = (e: PointerEvent) => { e.preventDefault(); if (!this._dragConfig) return; const dragHandlerPosition = this._getDragHandlePosition( @@ -178,7 +181,7 @@ export class EdgelessLineWidthPanel extends WithDisposable(LitElement) { this._dragConfig = null; }; - private _updateIconsColor = () => { + private readonly _updateIconsColor = () => { if (!this._dragHandle.offsetParent) { requestConnectedFrame(() => this._updateIconsColor(), this); return; diff --git a/blocksuite/blocks/src/root-block/edgeless/components/panel/scale-panel.ts b/blocksuite/blocks/src/root-block/edgeless/components/panel/scale-panel.ts index 8cedd56957aab..ac7105bee2f6b 100644 --- a/blocksuite/blocks/src/root-block/edgeless/components/panel/scale-panel.ts +++ b/blocksuite/blocks/src/root-block/edgeless/components/panel/scale-panel.ts @@ -45,7 +45,7 @@ export class EdgelessScalePanel extends LitElement { } `; - private _onKeydown = (e: KeyboardEvent) => { + private readonly _onKeydown = (e: KeyboardEvent) => { e.stopPropagation(); if (e.key === 'Enter' && !e.isComposing) { diff --git a/blocksuite/blocks/src/root-block/edgeless/components/panel/size-panel.ts b/blocksuite/blocks/src/root-block/edgeless/components/panel/size-panel.ts index d2dd0becaf01e..277098508ce83 100644 --- a/blocksuite/blocks/src/root-block/edgeless/components/panel/size-panel.ts +++ b/blocksuite/blocks/src/root-block/edgeless/components/panel/size-panel.ts @@ -54,7 +54,7 @@ export class EdgelessSizePanel extends LitElement { } `; - private _onKeydown = (e: KeyboardEvent) => { + private readonly _onKeydown = (e: KeyboardEvent) => { e.stopPropagation(); if (e.key === 'Enter' && !e.isComposing) { diff --git a/blocksuite/blocks/src/root-block/edgeless/components/rects/edgeless-selected-rect.ts b/blocksuite/blocks/src/root-block/edgeless/components/rects/edgeless-selected-rect.ts index 1c87fded91308..0decab312a7f3 100644 --- a/blocksuite/blocks/src/root-block/edgeless/components/rects/edgeless-selected-rect.ts +++ b/blocksuite/blocks/src/root-block/edgeless/components/rects/edgeless-selected-rect.ts @@ -451,7 +451,7 @@ export class EdgelessSelectedRectWidget extends WidgetComponent< private _dragEndCallback: (() => void)[] = []; - private _initSelectedSlot = () => { + private readonly _initSelectedSlot = () => { this._propDisposables.forEach(disposable => disposable.dispose()); this._propDisposables = []; @@ -466,7 +466,7 @@ export class EdgelessSelectedRectWidget extends WidgetComponent< }); }; - private _onDragEnd = () => { + private readonly _onDragEnd = () => { this.slots.dragEnd.emit(); this.doc.transact(() => { @@ -488,7 +488,7 @@ export class EdgelessSelectedRectWidget extends WidgetComponent< this.frameOverlay.clear(); }; - private _onDragMove = ( + private readonly _onDragMove = ( newBounds: Map< string, { @@ -561,7 +561,7 @@ export class EdgelessSelectedRectWidget extends WidgetComponent< }); }; - private _onDragRotate = (center: IPoint, delta: number) => { + private readonly _onDragRotate = (center: IPoint, delta: number) => { this.slots.dragRotate.emit(); const { selection } = this; @@ -606,7 +606,7 @@ export class EdgelessSelectedRectWidget extends WidgetComponent< this._updateMode(); }; - private _onDragStart = () => { + private readonly _onDragStart = () => { this.slots.dragStart.emit(); const rotation = this._resizeManager.rotation; @@ -651,9 +651,9 @@ export class EdgelessSelectedRectWidget extends WidgetComponent< private _propDisposables: Disposable[] = []; - private _resizeManager: HandleResizeManager; + private readonly _resizeManager: HandleResizeManager; - private _updateCursor = ( + private readonly _updateCursor = ( dragging: boolean, options?: { type: 'resize' | 'rotate'; @@ -711,7 +711,7 @@ export class EdgelessSelectedRectWidget extends WidgetComponent< this.gfx.cursor$.value = cursor; }; - private _updateMode = () => { + private readonly _updateMode = () => { if (this._cursorRotate) { this._mode = 'rotate'; return; @@ -738,7 +738,7 @@ export class EdgelessSelectedRectWidget extends WidgetComponent< } }; - private _updateOnElementChange = ( + private readonly _updateOnElementChange = ( element: string | { id: string }, fromRemote: boolean = false ) => { @@ -754,7 +754,7 @@ export class EdgelessSelectedRectWidget extends WidgetComponent< } }; - private _updateOnSelectionChange = () => { + private readonly _updateOnSelectionChange = () => { this._initSelectedSlot(); this._updateSelectedRect(); this._updateResizeManagerState(true); @@ -763,7 +763,7 @@ export class EdgelessSelectedRectWidget extends WidgetComponent< this._updateMode(); }; - private _updateOnViewportChange = () => { + private readonly _updateOnViewportChange = () => { if (this.selection.empty) { return; } @@ -775,7 +775,7 @@ export class EdgelessSelectedRectWidget extends WidgetComponent< /** * @param refresh indicate whether to completely refresh the state of resize manager, otherwise only update the position */ - private _updateResizeManagerState = (refresh: boolean) => { + private readonly _updateResizeManagerState = (refresh: boolean) => { const { _resizeManager, _selectedRect, @@ -813,7 +813,7 @@ export class EdgelessSelectedRectWidget extends WidgetComponent< borderStyle: 'solid', }; - private _updateSelectedRect = requestThrottledConnectedFrame(() => { + private readonly _updateSelectedRect = requestThrottledConnectedFrame(() => { const { zoom, selection, gfx } = this; const elements = selection.selectedElements; diff --git a/blocksuite/blocks/src/root-block/edgeless/components/resize/resize-manager.ts b/blocksuite/blocks/src/root-block/edgeless/components/resize/resize-manager.ts index 9c0890541a733..2f2ab2b8e688e 100644 --- a/blocksuite/blocks/src/root-block/edgeless/components/resize/resize-manager.ts +++ b/blocksuite/blocks/src/root-block/edgeless/components/resize/resize-manager.ts @@ -64,13 +64,13 @@ export class HandleResizeManager { private _locked = false; - private _onDragEnd: DragEndHandler; + private readonly _onDragEnd: DragEndHandler; - private _onDragStart: DragStartHandler; + private readonly _onDragStart: DragStartHandler; - private _onResizeMove: ResizeMoveHandler; + private readonly _onResizeMove: ResizeMoveHandler; - private _onRotateMove: RotateMoveHandler; + private readonly _onRotateMove: RotateMoveHandler; private _origin: { x: number; y: number } = { x: 0, y: 0 }; diff --git a/blocksuite/blocks/src/root-block/edgeless/components/text/edgeless-connector-label-editor.ts b/blocksuite/blocks/src/root-block/edgeless/components/text/edgeless-connector-label-editor.ts index 8a44ca273e080..3d5af296ae5f9 100644 --- a/blocksuite/blocks/src/root-block/edgeless/components/text/edgeless-connector-label-editor.ts +++ b/blocksuite/blocks/src/root-block/edgeless/components/text/edgeless-connector-label-editor.ts @@ -67,7 +67,7 @@ export class EdgelessConnectorLabelEditor extends WithDisposable( private _resizeObserver: ResizeObserver | null = null; - private _updateLabelRect = () => { + private readonly _updateLabelRect = () => { const { connector, edgeless } = this; if (!connector || !edgeless) return; diff --git a/blocksuite/blocks/src/root-block/edgeless/components/text/edgeless-text-editor.ts b/blocksuite/blocks/src/root-block/edgeless/components/text/edgeless-text-editor.ts index d2cf079cb4c50..942ea21555d86 100644 --- a/blocksuite/blocks/src/root-block/edgeless/components/text/edgeless-text-editor.ts +++ b/blocksuite/blocks/src/root-block/edgeless/components/text/edgeless-text-editor.ts @@ -69,7 +69,7 @@ export class EdgelessTextEditor extends WithDisposable(ShadowlessElement) { private _keeping = false; - private _updateRect = () => { + private readonly _updateRect = () => { const edgeless = this.edgeless; const element = this.element; diff --git a/blocksuite/blocks/src/root-block/edgeless/components/toolbar/brush/brush-menu.ts b/blocksuite/blocks/src/root-block/edgeless/components/toolbar/brush/brush-menu.ts index ef9745e3122f4..b96401ec3e108 100644 --- a/blocksuite/blocks/src/root-block/edgeless/components/toolbar/brush/brush-menu.ts +++ b/blocksuite/blocks/src/root-block/edgeless/components/toolbar/brush/brush-menu.ts @@ -36,7 +36,7 @@ export class EdgelessBrushMenu extends EdgelessToolbarToolMixin( } `; - private _props$ = computed(() => { + private readonly _props$ = computed(() => { const { color, lineWidth } = this.edgeless.std.get(EditPropsStore).lastProps$.value.brush; return { diff --git a/blocksuite/blocks/src/root-block/edgeless/components/toolbar/brush/brush-tool-button.ts b/blocksuite/blocks/src/root-block/edgeless/components/toolbar/brush/brush-tool-button.ts index 84624a51353c1..79d76eeffa736 100644 --- a/blocksuite/blocks/src/root-block/edgeless/components/toolbar/brush/brush-tool-button.ts +++ b/blocksuite/blocks/src/root-block/edgeless/components/toolbar/brush/brush-tool-button.ts @@ -43,7 +43,7 @@ export class EdgelessBrushToolButton extends EdgelessToolbarToolMixin( } `; - private _color$ = computed(() => { + private readonly _color$ = computed(() => { const theme = this.edgeless.std.get(ThemeProvider).theme$.value; return this.edgeless.std .get(ThemeProvider) diff --git a/blocksuite/blocks/src/root-block/edgeless/components/toolbar/connector/connector-menu.ts b/blocksuite/blocks/src/root-block/edgeless/components/toolbar/connector/connector-menu.ts index 8cc0d321e35eb..1abaf2e9adcbf 100644 --- a/blocksuite/blocks/src/root-block/edgeless/components/toolbar/connector/connector-menu.ts +++ b/blocksuite/blocks/src/root-block/edgeless/components/toolbar/connector/connector-menu.ts @@ -97,7 +97,7 @@ export class EdgelessConnectorMenu extends EdgelessToolbarToolMixin( } `; - private _props$ = computed(() => { + private readonly _props$ = computed(() => { const { mode, stroke, strokeWidth } = this.edgeless.std.get(EditPropsStore).lastProps$.value.connector; return { mode, stroke, strokeWidth }; diff --git a/blocksuite/blocks/src/root-block/edgeless/components/toolbar/connector/connector-tool-button.ts b/blocksuite/blocks/src/root-block/edgeless/components/toolbar/connector/connector-tool-button.ts index a21496878388f..7a626db5e72f8 100644 --- a/blocksuite/blocks/src/root-block/edgeless/components/toolbar/connector/connector-tool-button.ts +++ b/blocksuite/blocks/src/root-block/edgeless/components/toolbar/connector/connector-tool-button.ts @@ -39,7 +39,7 @@ export class EdgelessConnectorToolButton extends QuickToolMixin( } `; - private _mode$ = computed(() => { + private readonly _mode$ = computed(() => { return this.edgeless.std.get(EditPropsStore).lastProps$.value.connector .mode; }); diff --git a/blocksuite/blocks/src/root-block/edgeless/components/toolbar/edgeless-toolbar.ts b/blocksuite/blocks/src/root-block/edgeless/components/toolbar/edgeless-toolbar.ts index 6a6dd13ba745a..cba68fd9574ad 100644 --- a/blocksuite/blocks/src/root-block/edgeless/components/toolbar/edgeless-toolbar.ts +++ b/blocksuite/blocks/src/root-block/edgeless/components/toolbar/edgeless-toolbar.ts @@ -229,7 +229,7 @@ export class EdgelessToolbarWidget extends WidgetComponent< @state() accessor containerWidth = 1920; - private _onContainerResize = debounce(({ w }: { w: number }) => { + private readonly _onContainerResize = debounce(({ w }: { w: number }) => { if (!this.isConnected) return; this.slots.resize.emit({ w, h: TOOLBAR_HEIGHT }); @@ -262,17 +262,17 @@ export class EdgelessToolbarWidget extends WidgetComponent< private _resizeObserver: ResizeObserver | null = null; - private _slotsProvider = new ContextProvider(this, { + private readonly _slotsProvider = new ContextProvider(this, { context: edgelessToolbarSlotsContext, initialValue: { resize: new Slot() } satisfies EdgelessToolbarSlots, }); - private _themeProvider = new ContextProvider(this, { + private readonly _themeProvider = new ContextProvider(this, { context: edgelessToolbarThemeContext, initialValue: ColorScheme.Light, }); - private _toolbarProvider = new ContextProvider(this, { + private readonly _toolbarProvider = new ContextProvider(this, { context: edgelessToolbarContext, initialValue: this, }); diff --git a/blocksuite/blocks/src/root-block/edgeless/components/toolbar/lasso/lasso-tool-button.ts b/blocksuite/blocks/src/root-block/edgeless/components/toolbar/lasso/lasso-tool-button.ts index caf7a397fb838..9c266ede2aded 100644 --- a/blocksuite/blocks/src/root-block/edgeless/components/toolbar/lasso/lasso-tool-button.ts +++ b/blocksuite/blocks/src/root-block/edgeless/components/toolbar/lasso/lasso-tool-button.ts @@ -33,7 +33,7 @@ export class EdgelessLassoToolButton extends QuickToolMixin( } `; - private _changeTool = () => { + private readonly _changeTool = () => { const tool = this.edgelessTool; if (tool.type !== 'lasso') { this.setEdgelessTool({ type: 'lasso', mode: this.curMode }); diff --git a/blocksuite/blocks/src/root-block/edgeless/components/toolbar/mindmap/mindmap-menu.ts b/blocksuite/blocks/src/root-block/edgeless/components/toolbar/mindmap/mindmap-menu.ts index 1de0a532fc473..a63c4cd5e69f5 100644 --- a/blocksuite/blocks/src/root-block/edgeless/components/toolbar/mindmap/mindmap-menu.ts +++ b/blocksuite/blocks/src/root-block/edgeless/components/toolbar/mindmap/mindmap-menu.ts @@ -95,7 +95,7 @@ export class EdgelessMindmapMenu extends EdgelessToolbarToolMixin( } `; - private _style$ = computed(() => { + private readonly _style$ = computed(() => { const { style } = this.edgeless.std.get(EditPropsStore).lastProps$.value.mindmap; return style; diff --git a/blocksuite/blocks/src/root-block/edgeless/components/toolbar/mindmap/mindmap-tool-button.ts b/blocksuite/blocks/src/root-block/edgeless/components/toolbar/mindmap/mindmap-tool-button.ts index dd060e2efad6c..12284ff2fb6b5 100644 --- a/blocksuite/blocks/src/root-block/edgeless/components/toolbar/mindmap/mindmap-tool-button.ts +++ b/blocksuite/blocks/src/root-block/edgeless/components/toolbar/mindmap/mindmap-tool-button.ts @@ -124,7 +124,7 @@ export class EdgelessMindmapToolButton extends EdgelessToolbarToolMixin( } `; - private _style$ = computed(() => { + private readonly _style$ = computed(() => { const { style } = this.edgeless.std.get(EditPropsStore).lastProps$.value.mindmap; return style; diff --git a/blocksuite/blocks/src/root-block/edgeless/components/toolbar/note/note-senior-button.ts b/blocksuite/blocks/src/root-block/edgeless/components/toolbar/note/note-senior-button.ts index 98356b4dd465a..3fa0ca888f4ed 100644 --- a/blocksuite/blocks/src/root-block/edgeless/components/toolbar/note/note-senior-button.ts +++ b/blocksuite/blocks/src/root-block/edgeless/components/toolbar/note/note-senior-button.ts @@ -125,7 +125,7 @@ export class EdgelessNoteSeniorButton extends EdgelessToolbarToolMixin( } `; - private _noteBg$ = computed(() => { + private readonly _noteBg$ = computed(() => { return this.edgeless.std .get(ThemeProvider) .generateColorProperty( @@ -134,7 +134,7 @@ export class EdgelessNoteSeniorButton extends EdgelessToolbarToolMixin( ); }); - private _states = ['childFlavour', 'childType', 'tip'] as const; + private readonly _states = ['childFlavour', 'childType', 'tip'] as const; override enableActiveBackground = true; diff --git a/blocksuite/blocks/src/root-block/edgeless/components/toolbar/note/note-tool-button.ts b/blocksuite/blocks/src/root-block/edgeless/components/toolbar/note/note-tool-button.ts index a1cc1fe75caa7..bd6a5966a8b1b 100644 --- a/blocksuite/blocks/src/root-block/edgeless/components/toolbar/note/note-tool-button.ts +++ b/blocksuite/blocks/src/root-block/edgeless/components/toolbar/note/note-tool-button.ts @@ -27,7 +27,7 @@ export class EdgelessNoteToolButton extends QuickToolMixin(LitElement) { private _noteMenu: MenuPopper | null = null; - private _states = ['childFlavour', 'childType', 'tip'] as const; + private readonly _states = ['childFlavour', 'childType', 'tip'] as const; override type: GfxToolsFullOptionValue['type'] = 'affine:note'; diff --git a/blocksuite/blocks/src/root-block/edgeless/components/toolbar/present/navigator-setting-button.ts b/blocksuite/blocks/src/root-block/edgeless/components/toolbar/present/navigator-setting-button.ts index 2cb5365ddef64..cadb90c64f407 100644 --- a/blocksuite/blocks/src/root-block/edgeless/components/toolbar/present/navigator-setting-button.ts +++ b/blocksuite/blocks/src/root-block/edgeless/components/toolbar/present/navigator-setting-button.ts @@ -71,7 +71,7 @@ export class EdgelessNavigatorSettingButton extends WithDisposable(LitElement) { typeof createButtonPopper > | null = null; - private _onBlackBackgroundChange = (checked: boolean) => { + private readonly _onBlackBackgroundChange = (checked: boolean) => { this.blackBackground = checked; this.edgeless.slots.navigatorSettingUpdated.emit({ blackBackground: this.blackBackground, diff --git a/blocksuite/blocks/src/root-block/edgeless/components/toolbar/shape/shape-menu.ts b/blocksuite/blocks/src/root-block/edgeless/components/toolbar/shape/shape-menu.ts index f4a74136f1026..24e8d934374a5 100644 --- a/blocksuite/blocks/src/root-block/edgeless/components/toolbar/shape/shape-menu.ts +++ b/blocksuite/blocks/src/root-block/edgeless/components/toolbar/shape/shape-menu.ts @@ -59,12 +59,12 @@ export class EdgelessShapeMenu extends SignalWatcher( } `; - private _shapeName$: Signal = signal(ShapeType.Rect); + private readonly _shapeName$: Signal = signal(ShapeType.Rect); @property({ attribute: false }) accessor edgeless!: EdgelessRootBlockComponent; - private _props$ = computed(() => { + private readonly _props$ = computed(() => { const shapeName: ShapeName = this._shapeName$.value; const { shapeStyle, fillColor, strokeColor, radius } = this.edgeless.std.get(EditPropsStore).lastProps$.value[ @@ -79,7 +79,7 @@ export class EdgelessShapeMenu extends SignalWatcher( }; }); - private _setFillColor = (fillColor: ShapeFillColor) => { + private readonly _setFillColor = (fillColor: ShapeFillColor) => { const filled = !isTransparent(fillColor); let strokeColor = fillColor.replace( SHAPE_COLOR_PREFIX, @@ -101,7 +101,7 @@ export class EdgelessShapeMenu extends SignalWatcher( this.onChange(shapeName); }; - private _setShapeStyle = (shapeStyle: ShapeStyle) => { + private readonly _setShapeStyle = (shapeStyle: ShapeStyle) => { const { shapeName } = this._props$.value; this.edgeless.std .get(EditPropsStore) diff --git a/blocksuite/blocks/src/root-block/edgeless/components/toolbar/shape/shape-tool-button.ts b/blocksuite/blocks/src/root-block/edgeless/components/toolbar/shape/shape-tool-button.ts index be79d5e3e66c7..fa425a0cc8c99 100644 --- a/blocksuite/blocks/src/root-block/edgeless/components/toolbar/shape/shape-tool-button.ts +++ b/blocksuite/blocks/src/root-block/edgeless/components/toolbar/shape/shape-tool-button.ts @@ -23,14 +23,14 @@ export class EdgelessShapeToolButton extends EdgelessToolbarToolMixin( } `; - private _handleShapeClick = (shape: DraggableShape) => { + private readonly _handleShapeClick = (shape: DraggableShape) => { this.setEdgelessTool(this.type, { shapeName: shape.name, }); if (!this.popper) this._toggleMenu(); }; - private _handleWrapperClick = () => { + private readonly _handleWrapperClick = () => { if (this.tryDisposePopper()) return; this.setEdgelessTool(this.type, { diff --git a/blocksuite/blocks/src/root-block/edgeless/components/toolbar/shape/shape-tool-element.ts b/blocksuite/blocks/src/root-block/edgeless/components/toolbar/shape/shape-tool-element.ts index 0b7d0719e9cfe..81e577de9fe46 100644 --- a/blocksuite/blocks/src/root-block/edgeless/components/toolbar/shape/shape-tool-element.ts +++ b/blocksuite/blocks/src/root-block/edgeless/components/toolbar/shape/shape-tool-element.ts @@ -67,7 +67,7 @@ export class EdgelessShapeToolElement extends WithDisposable(LitElement) { } `; - private _addShape = (coord: Coord, padding: Coord) => { + private readonly _addShape = (coord: Coord, padding: Coord) => { const width = 100; const height = 100; const { x: edgelessX, y: edgelessY } = @@ -85,7 +85,7 @@ export class EdgelessShapeToolElement extends WithDisposable(LitElement) { }); }; - private _onDragEnd = async (coord: Coord) => { + private readonly _onDragEnd = async (coord: Coord) => { if (this._startCoord.x === coord.x && this._startCoord.y === coord.y) { this.handleClick(); this._dragging = false; @@ -118,7 +118,7 @@ export class EdgelessShapeToolElement extends WithDisposable(LitElement) { } }; - private _onDragMove = (coord: Coord) => { + private readonly _onDragMove = (coord: Coord) => { if (!this._dragging) { return; } @@ -153,7 +153,7 @@ export class EdgelessShapeToolElement extends WithDisposable(LitElement) { this._isOutside = isOut; }; - private _onDragStart = (coord: Coord) => { + private readonly _onDragStart = (coord: Coord) => { this._startCoord = { x: coord.x, y: coord.y }; if (this.order !== 1) { return; @@ -162,20 +162,20 @@ export class EdgelessShapeToolElement extends WithDisposable(LitElement) { this._shapeElement.classList.add('dragging'); }; - private _onMouseMove = (event: MouseEvent) => { + private readonly _onMouseMove = (event: MouseEvent) => { if (!this._dragging) { return; } this._onDragMove({ x: event.clientX, y: event.clientY }); }; - private _onMouseUp = (event: MouseEvent) => { + private readonly _onMouseUp = (event: MouseEvent) => { this._onDragEnd({ x: event.clientX, y: event.clientY }).catch( console.error ); }; - private _onTouchEnd = (event: TouchEvent) => { + private readonly _onTouchEnd = (event: TouchEvent) => { if (!event.changedTouches.length) return; this._onDragEnd({ @@ -185,7 +185,7 @@ export class EdgelessShapeToolElement extends WithDisposable(LitElement) { }).catch(console.error); }; - private _touchMove = (event: TouchEvent) => { + private readonly _touchMove = (event: TouchEvent) => { if (!this._dragging) { return; } @@ -195,7 +195,7 @@ export class EdgelessShapeToolElement extends WithDisposable(LitElement) { }); }; - private _transformMap: TransformMap = { + private readonly _transformMap: TransformMap = { z1: { x: 0, y: 5, scale: 1.1, origin: '50% 100%' }, z2: { x: -15, y: 0, scale: 0.75, origin: '20% 20%' }, z3: { x: 15, y: 0, scale: 0.75, origin: '80% 20%' }, diff --git a/blocksuite/blocks/src/root-block/edgeless/components/toolbar/template/overlay-scrollbar.ts b/blocksuite/blocks/src/root-block/edgeless/components/toolbar/template/overlay-scrollbar.ts index 0009cdf73f2ec..2b9b0fa35650c 100644 --- a/blocksuite/blocks/src/root-block/edgeless/components/toolbar/template/overlay-scrollbar.ts +++ b/blocksuite/blocks/src/root-block/edgeless/components/toolbar/template/overlay-scrollbar.ts @@ -50,7 +50,7 @@ export class OverlayScrollbar extends LitElement { } `; - private _disposable = new DisposableGroup(); + private readonly _disposable = new DisposableGroup(); private _handleVisible = false; diff --git a/blocksuite/blocks/src/root-block/edgeless/edgeless-root-block.ts b/blocksuite/blocks/src/root-block/edgeless/edgeless-root-block.ts index c7698d4fa5c7c..39df706ecfc74 100644 --- a/blocksuite/blocks/src/root-block/edgeless/edgeless-root-block.ts +++ b/blocksuite/blocks/src/root-block/edgeless/edgeless-root-block.ts @@ -98,21 +98,24 @@ export class EdgelessRootBlockComponent extends BlockComponent< } `; - private _refreshLayerViewport = requestThrottledConnectedFrame(() => { - const { zoom, translateX, translateY } = this.gfx.viewport; - const { gap } = getBackgroundGrid(zoom, true); - - if (this.backgroundElm) { - this.backgroundElm.style.setProperty( - 'background-position', - `${translateX}px ${translateY}px` - ); - this.backgroundElm.style.setProperty( - 'background-size', - `${gap}px ${gap}px` - ); - } - }, this); + private readonly _refreshLayerViewport = requestThrottledConnectedFrame( + () => { + const { zoom, translateX, translateY } = this.gfx.viewport; + const { gap } = getBackgroundGrid(zoom, true); + + if (this.backgroundElm) { + this.backgroundElm.style.setProperty( + 'background-position', + `${translateX}px ${translateY}px` + ); + this.backgroundElm.style.setProperty( + 'background-size', + `${gap}px ${gap}px` + ); + } + }, + this + ); private _resizeObserver: ResizeObserver | null = null; diff --git a/blocksuite/blocks/src/root-block/edgeless/edgeless-root-preview-block.ts b/blocksuite/blocks/src/root-block/edgeless/edgeless-root-preview-block.ts index a57acf510f2e4..475429c4f10f9 100644 --- a/blocksuite/blocks/src/root-block/edgeless/edgeless-root-preview-block.ts +++ b/blocksuite/blocks/src/root-block/edgeless/edgeless-root-preview-block.ts @@ -64,16 +64,19 @@ export class EdgelessRootPreviewBlockComponent extends BlockComponent< @query('.edgeless-background') accessor background!: HTMLDivElement; - private _refreshLayerViewport = requestThrottledConnectedFrame(() => { - const { zoom, translateX, translateY } = this.service.viewport; - const { gap } = getBackgroundGrid(zoom, true); - - this.background.style.setProperty( - 'background-position', - `${translateX}px ${translateY}px` - ); - this.background.style.setProperty('background-size', `${gap}px ${gap}px`); - }, this); + private readonly _refreshLayerViewport = requestThrottledConnectedFrame( + () => { + const { zoom, translateX, translateY } = this.service.viewport; + const { gap } = getBackgroundGrid(zoom, true); + + this.background.style.setProperty( + 'background-position', + `${translateX}px ${translateY}px` + ); + this.background.style.setProperty('background-size', `${gap}px ${gap}px`); + }, + this + ); private _resizeObserver: ResizeObserver | null = null; diff --git a/blocksuite/blocks/src/root-block/edgeless/edgeless-root-service.ts b/blocksuite/blocks/src/root-block/edgeless/edgeless-root-service.ts index d7c2ae115dbc4..32b37fff9dcc9 100644 --- a/blocksuite/blocks/src/root-block/edgeless/edgeless-root-service.ts +++ b/blocksuite/blocks/src/root-block/edgeless/edgeless-root-service.ts @@ -56,7 +56,7 @@ import { export class EdgelessRootService extends RootService implements SurfaceContext { static override readonly flavour = RootBlockSchema.model.flavour; - private _surface: SurfaceBlockModel; + private readonly _surface: SurfaceBlockModel; elementRenderers: Record = elementRenderers; diff --git a/blocksuite/blocks/src/root-block/edgeless/frame-manager.ts b/blocksuite/blocks/src/root-block/edgeless/frame-manager.ts index e0183a694fae7..1c671fd5469f6 100644 --- a/blocksuite/blocks/src/root-block/edgeless/frame-manager.ts +++ b/blocksuite/blocks/src/root-block/edgeless/frame-manager.ts @@ -37,7 +37,7 @@ export class FrameOverlay extends Overlay { private _innerElements = new Set(); - private _prevXYWH: SerializedXYWH | null = null; + private readonly _prevXYWH: SerializedXYWH | null = null; private get _frameManager() { return this.gfx.std.get( @@ -132,7 +132,7 @@ export class FrameOverlay extends Overlay { export class EdgelessFrameManager extends GfxExtension { static override key = 'frame-manager'; - private _disposable = new DisposableGroup(); + private readonly _disposable = new DisposableGroup(); /** * Get all sorted frames by presentation orderer, diff --git a/blocksuite/blocks/src/root-block/edgeless/gfx-tool/brush-tool.ts b/blocksuite/blocks/src/root-block/edgeless/gfx-tool/brush-tool.ts index 83345dc2f4dbd..67390bd508531 100644 --- a/blocksuite/blocks/src/root-block/edgeless/gfx-tool/brush-tool.ts +++ b/blocksuite/blocks/src/root-block/edgeless/gfx-tool/brush-tool.ts @@ -19,7 +19,7 @@ export class BrushTool extends BaseTool { private _lastPopLength = 0; - private _pressureSupportedPointerIds = new Set(); + private readonly _pressureSupportedPointerIds = new Set(); private _straightLineType: 'horizontal' | 'vertical' | null = null; diff --git a/blocksuite/blocks/src/root-block/edgeless/gfx-tool/default-tool-ext/mind-map-ext/mind-map-ext.ts b/blocksuite/blocks/src/root-block/edgeless/gfx-tool/default-tool-ext/mind-map-ext/mind-map-ext.ts index 6ad04ebb68059..50e3fade1b1eb 100644 --- a/blocksuite/blocks/src/root-block/edgeless/gfx-tool/default-tool-ext/mind-map-ext/mind-map-ext.ts +++ b/blocksuite/blocks/src/root-block/edgeless/gfx-tool/default-tool-ext/mind-map-ext/mind-map-ext.ts @@ -36,7 +36,7 @@ type DragMindMapCtx = { }; export class MindMapExt extends DefaultToolExt { - private _responseAreaUpdated = new Set(); + private readonly _responseAreaUpdated = new Set(); override supportedDragTypes: DefaultModeDragType[] = [ DefaultModeDragType.ContentMoving, diff --git a/blocksuite/blocks/src/root-block/edgeless/gfx-tool/default-tool.ts b/blocksuite/blocks/src/root-block/edgeless/gfx-tool/default-tool.ts index 35408569d74fa..9e14cd128a740 100644 --- a/blocksuite/blocks/src/root-block/edgeless/gfx-tool/default-tool.ts +++ b/blocksuite/blocks/src/root-block/edgeless/gfx-tool/default-tool.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ import { ConnectorUtils, OverlayIdentifier, @@ -79,14 +78,14 @@ export class DefaultTool extends BaseTool { private _autoPanTimer: number | null = null; - private _clearDisposable = () => { + private readonly _clearDisposable = () => { if (this._disposables) { this._disposables.dispose(); this._disposables = null; } }; - private _clearSelectingState = () => { + private readonly _clearSelectingState = () => { this._stopAutoPanning(); this._clearDisposable(); @@ -110,13 +109,13 @@ export class DefaultTool extends BaseTool { private _lock = false; - private _panViewport = (delta: IVec) => { + private readonly _panViewport = (delta: IVec) => { this._accumulateDelta[0] += delta[0]; this._accumulateDelta[1] += delta[1]; this.gfx.viewport.applyDeltaCenter(delta[0], delta[1]); }; - private _pendingUpdates = new Map< + private readonly _pendingUpdates = new Map< GfxBlockModel | GfxPrimitiveElementModel, Partial >(); @@ -139,7 +138,7 @@ export class DefaultTool extends BaseTool { endY: number; } = null; - private _startAutoPanning = (delta: IVec) => { + private readonly _startAutoPanning = (delta: IVec) => { this._panViewport(delta); this._updateSelectingState(delta); this._stopAutoPanning(); @@ -150,7 +149,7 @@ export class DefaultTool extends BaseTool { }, 30); }; - private _stopAutoPanning = () => { + private readonly _stopAutoPanning = () => { if (this._autoPanTimer) { clearTimeout(this._autoPanTimer); this._autoPanTimer = null; @@ -159,7 +158,7 @@ export class DefaultTool extends BaseTool { private _toBeMoved: GfxModel[] = []; - private _updateSelectingState = (delta: IVec = [0, 0]) => { + private readonly _updateSelectingState = (delta: IVec = [0, 0]) => { const { gfx } = this; if (gfx.keyboard.spaceKey$.peek() && this._selectionRectTransition) { @@ -943,6 +942,7 @@ export class DefaultTool extends BaseTool { } } + // eslint-disable-next-line @typescript-eslint/no-misused-promises override async dragStart(e: PointerEventState) { if (this.edgelessSelectionManager.editing) return; // Determine the drag type based on the current state and event diff --git a/blocksuite/blocks/src/root-block/edgeless/gfx-tool/eraser-tool.ts b/blocksuite/blocks/src/root-block/edgeless/gfx-tool/eraser-tool.ts index 82edd249b78f0..c752c7f465d65 100644 --- a/blocksuite/blocks/src/root-block/edgeless/gfx-tool/eraser-tool.ts +++ b/blocksuite/blocks/src/root-block/edgeless/gfx-tool/eraser-tool.ts @@ -30,9 +30,9 @@ export class EraserTool extends BaseTool { private _eraserPoints: IVec[] = []; - private _eraseTargets = new Set(); + private readonly _eraseTargets = new Set(); - private _loop = () => { + private readonly _loop = () => { const now = Date.now(); const elapsed = now - this._timestamp; @@ -62,7 +62,7 @@ export class EraserTool extends BaseTool { this._timer = requestAnimationFrame(this._loop); }; - private _overlay = new EraserOverlay(this.gfx); + private readonly _overlay = new EraserOverlay(this.gfx); private _prevEraserPoint: IVec = [0, 0]; diff --git a/blocksuite/blocks/src/root-block/edgeless/gfx-tool/lasso-tool.ts b/blocksuite/blocks/src/root-block/edgeless/gfx-tool/lasso-tool.ts index 084b3048939c1..99ff427a0d62d 100644 --- a/blocksuite/blocks/src/root-block/edgeless/gfx-tool/lasso-tool.ts +++ b/blocksuite/blocks/src/root-block/edgeless/gfx-tool/lasso-tool.ts @@ -68,7 +68,7 @@ export class LassoTool extends BaseTool { private _lastPoint: IVec = [0, 0]; - private _loop = () => { + private readonly _loop = () => { const path = this.activatedOption.mode === LassoMode.FreeHand ? CommonUtils.getSvgPathFromStroke(this._lassoPoints) @@ -79,7 +79,7 @@ export class LassoTool extends BaseTool { this._raf = requestAnimationFrame(this._loop); }; - private _overlay = new LassoOverlay(this.gfx); + private readonly _overlay = new LassoOverlay(this.gfx); private _raf = 0; diff --git a/blocksuite/blocks/src/root-block/keyboard/keyboard-manager.ts b/blocksuite/blocks/src/root-block/keyboard/keyboard-manager.ts index 04e4fcb84a9d0..849370d0bf436 100644 --- a/blocksuite/blocks/src/root-block/keyboard/keyboard-manager.ts +++ b/blocksuite/blocks/src/root-block/keyboard/keyboard-manager.ts @@ -11,7 +11,7 @@ import { } from '../../_common/utils/render-linked-doc.js'; export class PageKeyboardManager { - private _handleDelete = () => { + private readonly _handleDelete = () => { const blockSelections = this._currentSelection.filter(sel => sel.is('block') ); @@ -117,16 +117,18 @@ export class PageKeyboardManager { const doc = rootComponent.host.doc; const autofill = getTitleFromSelectedModels(selectedModels); - void promptDocTitle(rootComponent.host, autofill).then(title => { - if (title === null) return; - convertSelectedBlocksToLinkedDoc( - this.rootComponent.std, - doc, - draftedModels, - title - ).catch(console.error); - notifyDocCreated(rootComponent.host, doc); - }); + promptDocTitle(rootComponent.host, autofill) + .then(title => { + if (title === null) return; + convertSelectedBlocksToLinkedDoc( + this.rootComponent.std, + doc, + draftedModels, + title + ).catch(console.error); + notifyDocCreated(rootComponent.host, doc); + }) + .catch(console.error); } private _deleteBlocksBySelection(selections: BlockSelection[]) { diff --git a/blocksuite/blocks/src/root-block/remote-color-manager/color-picker.ts b/blocksuite/blocks/src/root-block/remote-color-manager/color-picker.ts index e20e8f7307f24..f1165596f1ec9 100644 --- a/blocksuite/blocks/src/root-block/remote-color-manager/color-picker.ts +++ b/blocksuite/blocks/src/root-block/remote-color-manager/color-picker.ts @@ -1,7 +1,7 @@ class RandomPicker { private _copyArray: T[]; - private _originalArray: T[]; + private readonly _originalArray: T[]; constructor(array: T[]) { this._originalArray = [...array]; diff --git a/blocksuite/blocks/src/root-block/root-service.ts b/blocksuite/blocks/src/root-block/root-service.ts index df6bc81f025ac..fe51a1f38841b 100644 --- a/blocksuite/blocks/src/root-block/root-service.ts +++ b/blocksuite/blocks/src/root-block/root-service.ts @@ -16,7 +16,7 @@ import type { RootBlockComponent } from './types.js'; export abstract class RootService extends BlockService { static override readonly flavour = RootBlockSchema.model.flavour; - private _fileDropOptions: FileDropOptions = { + private readonly _fileDropOptions: FileDropOptions = { flavour: this.flavour, }; diff --git a/blocksuite/blocks/src/root-block/widgets/ai-panel/ai-panel.ts b/blocksuite/blocks/src/root-block/widgets/ai-panel/ai-panel.ts index 0fe64e061d7af..ff992cf2d6323 100644 --- a/blocksuite/blocks/src/root-block/widgets/ai-panel/ai-panel.ts +++ b/blocksuite/blocks/src/root-block/widgets/ai-panel/ai-panel.ts @@ -84,18 +84,18 @@ export class AffineAIPanelWidget extends WidgetComponent { private _answer: string | null = null; - private _cancelCallback = () => { + private readonly _cancelCallback = () => { this.focus(); }; - private _clearDiscardModal = () => { + private readonly _clearDiscardModal = () => { if (this._discardModalAbort) { this._discardModalAbort.abort(); this._discardModalAbort = null; } }; - private _clickOutside = () => { + private readonly _clickOutside = () => { switch (this.state) { case 'hidden': return; @@ -112,21 +112,21 @@ export class AffineAIPanelWidget extends WidgetComponent { } }; - private _discardCallback = () => { + private readonly _discardCallback = () => { this.hide(); this.config?.discardCallback?.(); }; private _discardModalAbort: AbortController | null = null; - private _inputFinish = (text: string) => { + private readonly _inputFinish = (text: string) => { this._inputText = text; this.generate(); }; private _inputText: string | null = null; - private _onDocumentClick = (e: MouseEvent) => { + private readonly _onDocumentClick = (e: MouseEvent) => { if ( this.state !== 'hidden' && e.target !== this && @@ -139,7 +139,7 @@ export class AffineAIPanelWidget extends WidgetComponent { return false; }; - private _onKeyDown = (event: KeyboardEvent) => { + private readonly _onKeyDown = (event: KeyboardEvent) => { event.stopPropagation(); const { state } = this; if (state !== 'generating' && state !== 'input') { @@ -157,7 +157,7 @@ export class AffineAIPanelWidget extends WidgetComponent { } }; - private _resetAbortController = () => { + private readonly _resetAbortController = () => { if (this.state === 'generating') { this._abortController.abort(); } diff --git a/blocksuite/blocks/src/root-block/widgets/ai-panel/components/state/error.ts b/blocksuite/blocks/src/root-block/widgets/ai-panel/components/state/error.ts index 7dbd1e3556127..c073b5cb1467e 100644 --- a/blocksuite/blocks/src/root-block/widgets/ai-panel/components/state/error.ts +++ b/blocksuite/blocks/src/root-block/widgets/ai-panel/components/state/error.ts @@ -127,7 +127,7 @@ export class AIPanelError extends WithDisposable(LitElement) { } `; - private _getResponseGroup = () => { + private readonly _getResponseGroup = () => { let responseGroup: AIItemGroupConfig[] = []; const errorType = this.config.error?.type; if (errorType && errorType !== AIErrorType.GeneralNetworkError) { diff --git a/blocksuite/blocks/src/root-block/widgets/ai-panel/components/state/input.ts b/blocksuite/blocks/src/root-block/widgets/ai-panel/components/state/input.ts index 0e57647890e9b..e9c6dbcba6bb5 100644 --- a/blocksuite/blocks/src/root-block/widgets/ai-panel/components/state/input.ts +++ b/blocksuite/blocks/src/root-block/widgets/ai-panel/components/state/input.ts @@ -84,7 +84,7 @@ export class AIPanelInput extends WithDisposable(LitElement) { } `; - private _onInput = () => { + private readonly _onInput = () => { this.textarea.style.height = 'auto'; this.textarea.style.height = this.textarea.scrollHeight + 'px'; @@ -99,14 +99,14 @@ export class AIPanelInput extends WithDisposable(LitElement) { } }; - private _onKeyDown = (e: KeyboardEvent) => { + private readonly _onKeyDown = (e: KeyboardEvent) => { if (e.key === 'Enter' && !e.shiftKey && !e.isComposing) { e.preventDefault(); this._sendToAI(); } }; - private _sendToAI = () => { + private readonly _sendToAI = () => { const value = this.textarea.value.trim(); if (value.length === 0) return; diff --git a/blocksuite/blocks/src/root-block/widgets/code-toolbar/components/lang-button.ts b/blocksuite/blocks/src/root-block/widgets/code-toolbar/components/lang-button.ts index 147c1f4c8c9c0..f4e41ecf507a2 100644 --- a/blocksuite/blocks/src/root-block/widgets/code-toolbar/components/lang-button.ts +++ b/blocksuite/blocks/src/root-block/widgets/code-toolbar/components/lang-button.ts @@ -47,7 +47,7 @@ export class LanguageListButton extends WithDisposable( private _abortController?: AbortController; - private _clickLangBtn = () => { + private readonly _clickLangBtn = () => { if (this.blockComponent.doc.readonly) return; if (this._abortController) { // Close the language list if it's already opened. diff --git a/blocksuite/blocks/src/root-block/widgets/code-toolbar/index.ts b/blocksuite/blocks/src/root-block/widgets/code-toolbar/index.ts index eda4c8b359d8e..20d74152da4a7 100644 --- a/blocksuite/blocks/src/root-block/widgets/code-toolbar/index.ts +++ b/blocksuite/blocks/src/root-block/widgets/code-toolbar/index.ts @@ -24,7 +24,7 @@ export class AffineCodeToolbarWidget extends WidgetComponent< private _isActivated = false; - private _setHoverController = () => { + private readonly _setHoverController = () => { this._hoverController = null; this._hoverController = new HoverController( this, diff --git a/blocksuite/blocks/src/root-block/widgets/doc-remote-selection/doc-remote-selection.ts b/blocksuite/blocks/src/root-block/widgets/doc-remote-selection/doc-remote-selection.ts index fd57a054aa989..d1fdd79187bc2 100644 --- a/blocksuite/blocks/src/root-block/widgets/doc-remote-selection/doc-remote-selection.ts +++ b/blocksuite/blocks/src/root-block/widgets/doc-remote-selection/doc-remote-selection.ts @@ -34,11 +34,11 @@ export class AffineDocRemoteSelectionWidget extends WidgetComponent { } `; - private _abortController = new AbortController(); + private readonly _abortController = new AbortController(); private _remoteColorManager: RemoteColorManager | null = null; - private _remoteSelections = computed(() => { + private readonly _remoteSelections = computed(() => { const status = this.doc.awarenessStore.getStates(); return [...this.std.selection.remoteSelections.entries()].map( ([id, selections]) => { @@ -51,7 +51,7 @@ export class AffineDocRemoteSelectionWidget extends WidgetComponent { ); }); - private _resizeObserver: ResizeObserver = new ResizeObserver(() => { + private readonly _resizeObserver: ResizeObserver = new ResizeObserver(() => { this.requestUpdate(); }); diff --git a/blocksuite/blocks/src/root-block/widgets/drag-handle/config.ts b/blocksuite/blocks/src/root-block/widgets/drag-handle/config.ts index 745b6ecb8d142..f711948df0e19 100644 --- a/blocksuite/blocks/src/root-block/widgets/drag-handle/config.ts +++ b/blocksuite/blocks/src/root-block/widgets/drag-handle/config.ts @@ -31,7 +31,7 @@ export type DropResult = { }; export class DragHandleOptionsRunner { - private optionMap = new Map(); + private readonly optionMap = new Map(); get options(): DragHandleOption[] { return Array.from(this.optionMap.keys()); diff --git a/blocksuite/blocks/src/root-block/widgets/drag-handle/drag-handle.ts b/blocksuite/blocks/src/root-block/widgets/drag-handle/drag-handle.ts index b2d23cd67c812..84f117d3d8136 100644 --- a/blocksuite/blocks/src/root-block/widgets/drag-handle/drag-handle.ts +++ b/blocksuite/blocks/src/root-block/widgets/drag-handle/drag-handle.ts @@ -58,16 +58,18 @@ export class AffineDragHandleWidget extends WidgetComponent { private _anchorModelDisposables: DisposableGroup | null = null; - private _dragEventWatcher = new DragEventWatcher(this); + private readonly _dragEventWatcher = new DragEventWatcher(this); - private _getBlockView = (blockId: string) => { + private readonly _getBlockView = (blockId: string) => { return this.host.view.getBlock(blockId); }; /** * When dragging, should update indicator position and target drop block id */ - private _getDropResult = (state: DndEventState): DropResult | null => { + private readonly _getDropResult = ( + state: DndEventState + ): DropResult | null => { const point = new Point(state.raw.x, state.raw.y); const closestBlock = getClosestBlockByPoint( this.host, @@ -137,22 +139,22 @@ export class AffineDragHandleWidget extends WidgetComponent { return dropIndicator; }; - private _handleEventWatcher = new HandleEventWatcher(this); + private readonly _handleEventWatcher = new HandleEventWatcher(this); - private _keyboardEventWatcher = new KeyboardEventWatcher(this); + private readonly _keyboardEventWatcher = new KeyboardEventWatcher(this); - private _legacyDragEventWatcher = new LegacyDragEventWatcher(this); + private readonly _legacyDragEventWatcher = new LegacyDragEventWatcher(this); - private _pageWatcher = new PageWatcher(this); + private readonly _pageWatcher = new PageWatcher(this); - private _removeDropIndicator = () => { + private readonly _removeDropIndicator = () => { if (this.dropIndicator) { this.dropIndicator.remove(); this.dropIndicator = null; } }; - private _reset = () => { + private readonly _reset = () => { this.draggingElements = []; this.dropBlockId = ''; this.dropType = null; @@ -173,17 +175,17 @@ export class AffineDragHandleWidget extends WidgetComponent { this._resetCursor(); }; - private _resetCursor = () => { + private readonly _resetCursor = () => { document.documentElement.classList.remove('affine-drag-preview-grabbing'); }; - private _resetDropResult = () => { + private readonly _resetDropResult = () => { this.dropBlockId = ''; this.dropType = null; if (this.dropIndicator) this.dropIndicator.rect = null; }; - private _updateDropResult = (dropResult: DropResult | null) => { + private readonly _updateDropResult = (dropResult: DropResult | null) => { if (!this.dropIndicator) return; this.dropBlockId = dropResult?.dropBlockId ?? ''; this.dropType = dropResult?.dropType ?? null; diff --git a/blocksuite/blocks/src/root-block/widgets/drag-handle/helpers/preview-helper.ts b/blocksuite/blocks/src/root-block/widgets/drag-handle/helpers/preview-helper.ts index bc90963c54fcc..4e82df2a8e641 100644 --- a/blocksuite/blocks/src/root-block/widgets/drag-handle/helpers/preview-helper.ts +++ b/blocksuite/blocks/src/root-block/widgets/drag-handle/helpers/preview-helper.ts @@ -11,7 +11,7 @@ import { DragPreview } from '../components/drag-preview.js'; import type { AffineDragHandleWidget } from '../drag-handle.js'; export class PreviewHelper { - private _calculatePreviewOffset = ( + private readonly _calculatePreviewOffset = ( blocks: BlockComponent[], state: DndEventState ) => { @@ -20,7 +20,7 @@ export class PreviewHelper { return previewOffset; }; - private _calculateQuery = (selectedIds: string[]): Query => { + private readonly _calculateQuery = (selectedIds: string[]): Query => { const ids: Array<{ id: string; viewType: BlockViewType }> = selectedIds.map( id => ({ id, diff --git a/blocksuite/blocks/src/root-block/widgets/drag-handle/helpers/rect-helper.ts b/blocksuite/blocks/src/root-block/widgets/drag-handle/helpers/rect-helper.ts index 0db43c3939382..6562ecf97262e 100644 --- a/blocksuite/blocks/src/root-block/widgets/drag-handle/helpers/rect-helper.ts +++ b/blocksuite/blocks/src/root-block/widgets/drag-handle/helpers/rect-helper.ts @@ -15,7 +15,7 @@ import { } from '../utils.js'; export class RectHelper { - private _getHoveredBlocks = (): BlockComponent[] => { + private readonly _getHoveredBlocks = (): BlockComponent[] => { if (!this.widget.isHoverDragHandleVisible || !this.widget.anchorBlockId) return []; diff --git a/blocksuite/blocks/src/root-block/widgets/drag-handle/watchers/drag-event-watcher.ts b/blocksuite/blocks/src/root-block/widgets/drag-handle/watchers/drag-event-watcher.ts index d82b55d232bc7..f35a1eb1ee0b5 100644 --- a/blocksuite/blocks/src/root-block/widgets/drag-handle/watchers/drag-event-watcher.ts +++ b/blocksuite/blocks/src/root-block/widgets/drag-handle/watchers/drag-event-watcher.ts @@ -44,7 +44,7 @@ import { surfaceRefToEmbed } from '../middleware/surface-ref-to-embed.js'; import { containBlock, includeTextSelection } from '../utils.js'; export class DragEventWatcher { - private _computeEdgelessBound = ( + private readonly _computeEdgelessBound = ( x: number, y: number, width: number, @@ -70,19 +70,19 @@ export class DragEventWatcher { ); }; - private _createDropIndicator = () => { + private readonly _createDropIndicator = () => { if (!this.widget.dropIndicator) { this.widget.dropIndicator = new DropIndicator(); this.widget.rootComponent.append(this.widget.dropIndicator); } }; - private _dragEndHandler: UIEventHandler = () => { + private readonly _dragEndHandler: UIEventHandler = () => { this.widget.clearRaf(); this.widget.hide(true); }; - private _dragMoveHandler: UIEventHandler = ctx => { + private readonly _dragMoveHandler: UIEventHandler = ctx => { if ( this.widget.isHoverDragHandleVisible || this.widget.isTopLevelDragHandleVisible @@ -104,7 +104,7 @@ export class DragEventWatcher { /** * When start dragging, should set dragging elements and create drag preview */ - private _dragStartHandler: UIEventHandler = ctx => { + private readonly _dragStartHandler: UIEventHandler = ctx => { const state = ctx.get('dndState'); // If not click left button to start dragging, should do nothing const { button } = state.raw; @@ -115,14 +115,14 @@ export class DragEventWatcher { return this._onDragStart(state); }; - private _dropHandler = (context: UIEventStateContext) => { + private readonly _dropHandler = (context: UIEventStateContext) => { this._onDrop(context); this._std.selection.setGroup('gfx', []); this.widget.clearRaf(); this.widget.hide(true); }; - private _onDragMove = (state: DndEventState) => { + private readonly _onDragMove = (state: DndEventState) => { this.widget.clearRaf(); this.widget.rafID = requestAnimationFrame(() => { @@ -132,7 +132,7 @@ export class DragEventWatcher { return true; }; - private _onDragStart = (state: DndEventState) => { + private readonly _onDragStart = (state: DndEventState) => { // Get current hover block element by path const hoverBlock = this.widget.anchorBlockComponent.peek(); if (!hoverBlock) return false; @@ -234,7 +234,7 @@ export class DragEventWatcher { return true; }; - private _onDrop = (context: UIEventStateContext) => { + private readonly _onDrop = (context: UIEventStateContext) => { const state = context.get('dndState'); const event = state.raw; @@ -280,7 +280,7 @@ export class DragEventWatcher { this._deserializeData(state, parent.id, index).catch(console.error); }; - private _onDropNoteOnNote = ( + private readonly _onDropNoteOnNote = ( snapshot: SliceSnapshot, parent?: string, index?: number @@ -305,7 +305,7 @@ export class DragEventWatcher { .catch(console.error); }; - private _onDropOnEdgelessCanvas = (context: UIEventStateContext) => { + private readonly _onDropOnEdgelessCanvas = (context: UIEventStateContext) => { const state = context.get('dndState'); // If drop a note, should do nothing const snapshot = this._deserializeSnapshot(state); @@ -405,7 +405,7 @@ export class DragEventWatcher { this._deserializeData(state, newNoteId).catch(console.error); }; - private _startDragging = ( + private readonly _startDragging = ( blocks: BlockComponent[], state: DndEventState, dragPreviewEl?: HTMLElement, @@ -435,7 +435,7 @@ export class DragEventWatcher { this._serializeData(slice, state); }; - private _trackLinkedDocCreated = (id: string) => { + private readonly _trackLinkedDocCreated = (id: string) => { const isNewBlock = !this._std.doc.hasBlock(id); if (!isNewBlock) { return; diff --git a/blocksuite/blocks/src/root-block/widgets/drag-handle/watchers/edgeless-watcher.ts b/blocksuite/blocks/src/root-block/widgets/drag-handle/watchers/edgeless-watcher.ts index fd4f84aa95ab7..efdb1d37a0236 100644 --- a/blocksuite/blocks/src/root-block/widgets/drag-handle/watchers/edgeless-watcher.ts +++ b/blocksuite/blocks/src/root-block/widgets/drag-handle/watchers/edgeless-watcher.ts @@ -24,7 +24,9 @@ import { import type { AffineDragHandleWidget } from '../drag-handle.js'; export class EdgelessWatcher { - private _handleEdgelessToolUpdated = (newTool: GfxToolsFullOptionValue) => { + private readonly _handleEdgelessToolUpdated = ( + newTool: GfxToolsFullOptionValue + ) => { if (newTool.type === 'default') { this.checkTopLevelBlockSelection(); } else { @@ -32,7 +34,7 @@ export class EdgelessWatcher { } }; - private _handleEdgelessViewPortUpdated = ({ + private readonly _handleEdgelessViewPortUpdated = ({ zoom, center, }: { @@ -60,7 +62,7 @@ export class EdgelessWatcher { } }; - private _showDragHandleOnTopLevelBlocks = async () => { + private readonly _showDragHandleOnTopLevelBlocks = async () => { if (this.widget.mode === 'page') return; const { edgelessRoot } = this; await edgelessRoot.surface.updateComplete; @@ -99,13 +101,13 @@ export class EdgelessWatcher { this.widget.isTopLevelDragHandleVisible = true; }; - private _updateDragHoverRectTopLevelBlock = () => { + private readonly _updateDragHoverRectTopLevelBlock = () => { if (!this.widget.dragHoverRect) return; this.widget.dragHoverRect = this.hoverAreaRectTopLevelBlock; }; - private _updateDragPreviewOnViewportUpdate = () => { + private readonly _updateDragPreviewOnViewportUpdate = () => { if (this.widget.dragPreview && this.widget.lastDragPointerState) { this.updateDragPreviewPosition(this.widget.lastDragPointerState); } diff --git a/blocksuite/blocks/src/root-block/widgets/drag-handle/watchers/handle-event-watcher.ts b/blocksuite/blocks/src/root-block/widgets/drag-handle/watchers/handle-event-watcher.ts index 6ed86e01cb97c..d0bb58dee2e95 100644 --- a/blocksuite/blocks/src/root-block/widgets/drag-handle/watchers/handle-event-watcher.ts +++ b/blocksuite/blocks/src/root-block/widgets/drag-handle/watchers/handle-event-watcher.ts @@ -6,14 +6,14 @@ import { import type { AffineDragHandleWidget } from '../drag-handle.js'; export class HandleEventWatcher { - private _onDragHandlePointerDown = () => { + private readonly _onDragHandlePointerDown = () => { if (!this.widget.isHoverDragHandleVisible || !this.widget.anchorBlockId) return; this.widget.dragHoverRect = this.widget.draggingAreaRect.value; }; - private _onDragHandlePointerEnter = () => { + private readonly _onDragHandlePointerEnter = () => { const container = this.widget.dragHandleContainer; const grabber = this.widget.dragHandleGrabber; if (!container || !grabber) return; @@ -42,7 +42,7 @@ export class HandleEventWatcher { } }; - private _onDragHandlePointerLeave = () => { + private readonly _onDragHandlePointerLeave = () => { this.widget.isDragHandleHovered = false; this.widget.dragHoverRect = null; @@ -53,7 +53,7 @@ export class HandleEventWatcher { this.widget.pointerEventWatcher.showDragHandleOnHoverBlock(); }; - private _onDragHandlePointerUp = () => { + private readonly _onDragHandlePointerUp = () => { if (!this.widget.isHoverDragHandleVisible) return; this.widget.dragHoverRect = null; }; diff --git a/blocksuite/blocks/src/root-block/widgets/drag-handle/watchers/keyboard-event-watcher.ts b/blocksuite/blocks/src/root-block/widgets/drag-handle/watchers/keyboard-event-watcher.ts index 13845aab7fa14..2507fd91f8e1d 100644 --- a/blocksuite/blocks/src/root-block/widgets/drag-handle/watchers/keyboard-event-watcher.ts +++ b/blocksuite/blocks/src/root-block/widgets/drag-handle/watchers/keyboard-event-watcher.ts @@ -3,7 +3,7 @@ import type { UIEventHandler } from '@blocksuite/block-std'; import type { AffineDragHandleWidget } from '../drag-handle.js'; export class KeyboardEventWatcher { - private _keyboardHandler: UIEventHandler = ctx => { + private readonly _keyboardHandler: UIEventHandler = ctx => { if (!this.widget.dragging || !this.widget.dragPreview) { return; } diff --git a/blocksuite/blocks/src/root-block/widgets/drag-handle/watchers/legacy-drag-event-watcher.ts b/blocksuite/blocks/src/root-block/widgets/drag-handle/watchers/legacy-drag-event-watcher.ts index 2fba127016f32..97aa5f3673eb3 100644 --- a/blocksuite/blocks/src/root-block/widgets/drag-handle/watchers/legacy-drag-event-watcher.ts +++ b/blocksuite/blocks/src/root-block/widgets/drag-handle/watchers/legacy-drag-event-watcher.ts @@ -30,11 +30,11 @@ import { } from '../utils.js'; export class LegacyDragEventWatcher { - private _changeCursorToGrabbing = () => { + private readonly _changeCursorToGrabbing = () => { document.documentElement.classList.add('affine-drag-preview-grabbing'); }; - private _createDropIndicator = () => { + private readonly _createDropIndicator = () => { if (!this.widget.dropIndicator) { this.widget.dropIndicator = new DropIndicator(); this.widget.rootComponent.append(this.widget.dropIndicator); @@ -44,7 +44,7 @@ export class LegacyDragEventWatcher { /** * When drag end, should move blocks to drop position */ - private _dragEndHandler: UIEventHandler = ctx => { + private readonly _dragEndHandler: UIEventHandler = ctx => { this.widget.clearRaf(); if (!this.widget.dragging || !this.widget.dragPreview) return false; if (this.widget.draggingElements.length === 0 || this.widget.doc.readonly) { @@ -96,7 +96,7 @@ export class LegacyDragEventWatcher { * Update indicator position * Update drop block id */ - private _dragMoveHandler: UIEventHandler = ctx => { + private readonly _dragMoveHandler: UIEventHandler = ctx => { if ( this.widget.isHoverDragHandleVisible || this.widget.isTopLevelDragHandleVisible @@ -130,7 +130,7 @@ export class LegacyDragEventWatcher { /** * When start dragging, should set dragging elements and create drag preview */ - private _dragStartHandler: UIEventHandler = ctx => { + private readonly _dragStartHandler: UIEventHandler = ctx => { const state = ctx.get('pointerState'); // If not click left button to start dragging, should do nothing const { button } = state.raw; @@ -156,7 +156,7 @@ export class LegacyDragEventWatcher { return this._onDragStart(state); }; - private _onDragEnd = (state: PointerEventState) => { + private readonly _onDragEnd = (state: PointerEventState) => { const targetBlockId = this.widget.dropBlockId; const dropType = this.widget.dropType; const draggingElements = this.widget.draggingElements; @@ -306,7 +306,7 @@ export class LegacyDragEventWatcher { return true; }; - private _onDragMove = (state: PointerEventState) => { + private readonly _onDragMove = (state: PointerEventState) => { this.widget.clearRaf(); this.widget.rafID = requestAnimationFrame(() => { @@ -318,7 +318,7 @@ export class LegacyDragEventWatcher { return true; }; - private _onDragStart = (state: PointerEventState) => { + private readonly _onDragStart = (state: PointerEventState) => { // Get current hover block element by path const hoverBlock = this.widget.anchorBlockComponent.peek(); if (!hoverBlock) return false; @@ -432,7 +432,7 @@ export class LegacyDragEventWatcher { return true; }; - private _startDragging = ( + private readonly _startDragging = ( blocks: BlockComponent[], state: PointerEventState, dragPreviewEl?: HTMLElement, diff --git a/blocksuite/blocks/src/root-block/widgets/drag-handle/watchers/pointer-event-watcher.ts b/blocksuite/blocks/src/root-block/widgets/drag-handle/watchers/pointer-event-watcher.ts index 584fa3493130f..3c7b6363076ca 100644 --- a/blocksuite/blocks/src/root-block/widgets/drag-handle/watchers/pointer-event-watcher.ts +++ b/blocksuite/blocks/src/root-block/widgets/drag-handle/watchers/pointer-event-watcher.ts @@ -30,7 +30,7 @@ import { } from '../utils.js'; export class PointerEventWatcher { - private _canEditing = (noteBlock: BlockComponent) => { + private readonly _canEditing = (noteBlock: BlockComponent) => { if (noteBlock.doc.id !== this.widget.doc.id) return false; if (this.widget.mode === 'page') return true; @@ -50,7 +50,7 @@ export class PointerEventWatcher { * Should select the block and show slash menu if current block is not selected * Should clear selection if current block is the first selected block */ - private _clickHandler: UIEventHandler = ctx => { + private readonly _clickHandler: UIEventHandler = ctx => { if (!this.widget.isHoverDragHandleVisible) return; const state = ctx.get('pointerState'); @@ -90,7 +90,7 @@ export class PointerEventWatcher { }; // Need to consider block padding and scale - private _getTopWithBlockComponent = (block: BlockComponent) => { + private readonly _getTopWithBlockComponent = (block: BlockComponent) => { const computedStyle = getComputedStyle(block); const { top } = block.getBoundingClientRect(); const paddingTop = @@ -102,7 +102,7 @@ export class PointerEventWatcher { ); }; - private _containerStyle = computed(() => { + private readonly _containerStyle = computed(() => { const draggingAreaRect = this.widget.draggingAreaRect.value; if (!draggingAreaRect) return null; @@ -135,7 +135,7 @@ export class PointerEventWatcher { }; }); - private _grabberStyle = computed(() => { + private readonly _grabberStyle = computed(() => { const scaleInNote = this.widget.scaleInNote.value; return { width: `${DRAG_HANDLE_GRABBER_WIDTH * scaleInNote}px`, @@ -151,7 +151,7 @@ export class PointerEventWatcher { * When pointer move on block, should show drag handle * And update hover block id and path */ - private _pointerMoveOnBlock = (state: PointerEventState) => { + private readonly _pointerMoveOnBlock = (state: PointerEventState) => { if (this.widget.isTopLevelDragHandleVisible) return; const point = new Point(state.raw.x, state.raw.y); @@ -190,7 +190,7 @@ export class PointerEventWatcher { } }; - private _pointerOutHandler: UIEventHandler = ctx => { + private readonly _pointerOutHandler: UIEventHandler = ctx => { const state = ctx.get('pointerState'); state.raw.preventDefault(); @@ -214,57 +214,60 @@ export class PointerEventWatcher { } }; - private _throttledPointerMoveHandler = throttle(ctx => { - if ( - this.widget.doc.readonly || - this.widget.dragging || - !this.widget.isConnected - ) { - this.widget.hide(); - return; - } - if (this.widget.isTopLevelDragHandleVisible) return; - - const state = ctx.get('pointerState'); - const { target } = state.raw; - const element = captureEventTarget(target); - // When pointer not on block or on dragging, should do nothing - if (!element) return; - - // When pointer on drag handle, should do nothing - if (element.closest('.affine-drag-handle-container')) return; - - // When pointer out of note block hover area or inside database, should hide drag handle - const point = new Point(state.raw.x, state.raw.y); - - const closestNoteBlock = getClosestNoteBlock( - this.widget.host, - this.widget.rootComponent, - point - ) as NoteBlockComponent | null; - - this.widget.noteScale.value = - this.widget.mode === 'page' - ? 1 - : (closestNoteBlock?.model.edgeless.scale ?? 1); - - if ( - closestNoteBlock && - this._canEditing(closestNoteBlock) && - !isOutOfNoteBlock( + private readonly _throttledPointerMoveHandler = throttle( + ctx => { + if ( + this.widget.doc.readonly || + this.widget.dragging || + !this.widget.isConnected + ) { + this.widget.hide(); + return; + } + if (this.widget.isTopLevelDragHandleVisible) return; + + const state = ctx.get('pointerState'); + const { target } = state.raw; + const element = captureEventTarget(target); + // When pointer not on block or on dragging, should do nothing + if (!element) return; + + // When pointer on drag handle, should do nothing + if (element.closest('.affine-drag-handle-container')) return; + + // When pointer out of note block hover area or inside database, should hide drag handle + const point = new Point(state.raw.x, state.raw.y); + + const closestNoteBlock = getClosestNoteBlock( this.widget.host, - closestNoteBlock, - point, - this.widget.scaleInNote.peek() - ) - ) { - this._pointerMoveOnBlock(state); - return true; - } + this.widget.rootComponent, + point + ) as NoteBlockComponent | null; + + this.widget.noteScale.value = + this.widget.mode === 'page' + ? 1 + : (closestNoteBlock?.model.edgeless.scale ?? 1); + + if ( + closestNoteBlock && + this._canEditing(closestNoteBlock) && + !isOutOfNoteBlock( + this.widget.host, + closestNoteBlock, + point, + this.widget.scaleInNote.peek() + ) + ) { + this._pointerMoveOnBlock(state); + return true; + } - this.widget.hide(); - return false; - }, 1000 / 60); + this.widget.hide(); + return false; + }, + 1000 / 60 + ); // Multiple blocks: drag handle should show on the vertical middle of all blocks showDragHandleOnHoverBlock = () => { diff --git a/blocksuite/blocks/src/root-block/widgets/edgeless-auto-connect/edgeless-auto-connect.ts b/blocksuite/blocks/src/root-block/widgets/edgeless-auto-connect/edgeless-auto-connect.ts index 984aa7834f4bb..23a666430a6c9 100644 --- a/blocksuite/blocks/src/root-block/widgets/edgeless-auto-connect/edgeless-auto-connect.ts +++ b/blocksuite/blocks/src/root-block/widgets/edgeless-auto-connect/edgeless-auto-connect.ts @@ -172,7 +172,7 @@ export class EdgelessAutoConnectWidget extends WidgetComponent< } `; - private _updateLabels = () => { + private readonly _updateLabels = () => { const service = this.service; if (!service.doc.root) return; diff --git a/blocksuite/blocks/src/root-block/widgets/edgeless-copilot-panel/toolbar-entry.ts b/blocksuite/blocks/src/root-block/widgets/edgeless-copilot-panel/toolbar-entry.ts index bdf447ca43792..e5fc90cfdaf0a 100644 --- a/blocksuite/blocks/src/root-block/widgets/edgeless-copilot-panel/toolbar-entry.ts +++ b/blocksuite/blocks/src/root-block/widgets/edgeless-copilot-panel/toolbar-entry.ts @@ -21,7 +21,7 @@ export class EdgelessCopilotToolbarEntry extends WithDisposable(LitElement) { } `; - private _onClick = () => { + private readonly _onClick = () => { this.onClick?.(); this._showCopilotPanel(); }; diff --git a/blocksuite/blocks/src/root-block/widgets/edgeless-remote-selection/index.ts b/blocksuite/blocks/src/root-block/widgets/edgeless-remote-selection/index.ts index c690045275db7..1232667bf11a2 100644 --- a/blocksuite/blocks/src/root-block/widgets/edgeless-remote-selection/index.ts +++ b/blocksuite/blocks/src/root-block/widgets/edgeless-remote-selection/index.ts @@ -81,14 +81,16 @@ export class EdgelessRemoteSelectionWidget extends WidgetComponent< private _remoteColorManager: RemoteColorManager | null = null; - private _updateOnElementChange = (element: string | { id: string }) => { + private readonly _updateOnElementChange = ( + element: string | { id: string } + ) => { const id = typeof element === 'string' ? element : element.id; if (this.isConnected && this.selection.hasRemote(id)) this._updateRemoteRects(); }; - private _updateRemoteCursor = () => { + private readonly _updateRemoteCursor = () => { const remoteCursors: EdgelessRemoteSelectionWidget['_remoteCursors'] = new Map(); const status = this.doc.awarenessStore.getStates(); @@ -106,7 +108,7 @@ export class EdgelessRemoteSelectionWidget extends WidgetComponent< this._remoteCursors = remoteCursors; }; - private _updateRemoteRects = () => { + private readonly _updateRemoteRects = () => { const { selection, block } = this; const remoteSelectionsMap = selection.remoteSurfaceSelectionsMap; const remoteRects: EdgelessRemoteSelectionWidget['_remoteRects'] = @@ -148,7 +150,7 @@ export class EdgelessRemoteSelectionWidget extends WidgetComponent< this._remoteRects = remoteRects; }; - private _updateTransform = requestThrottledConnectedFrame(() => { + private readonly _updateTransform = requestThrottledConnectedFrame(() => { const { translateX, translateY, zoom } = this.edgeless.service.viewport; this.style.setProperty('--v-zoom', `${zoom}`); diff --git a/blocksuite/blocks/src/root-block/widgets/element-toolbar/add-frame-button.ts b/blocksuite/blocks/src/root-block/widgets/element-toolbar/add-frame-button.ts index bab88940de7e5..e0f583c2d73a8 100644 --- a/blocksuite/blocks/src/root-block/widgets/element-toolbar/add-frame-button.ts +++ b/blocksuite/blocks/src/root-block/widgets/element-toolbar/add-frame-button.ts @@ -14,7 +14,7 @@ export class EdgelessAddFrameButton extends WithDisposable(LitElement) { } `; - private _createFrame = () => { + private readonly _createFrame = () => { const frame = this.edgeless.service.frame.createFrameOnSelected(); if (!frame) return; this.edgeless.std diff --git a/blocksuite/blocks/src/root-block/widgets/element-toolbar/add-group-button.ts b/blocksuite/blocks/src/root-block/widgets/element-toolbar/add-group-button.ts index 8a89ed07e753e..61fb223481868 100644 --- a/blocksuite/blocks/src/root-block/widgets/element-toolbar/add-group-button.ts +++ b/blocksuite/blocks/src/root-block/widgets/element-toolbar/add-group-button.ts @@ -16,7 +16,7 @@ export class EdgelessAddGroupButton extends WithDisposable(LitElement) { } `; - private _createGroup = () => { + private readonly _createGroup = () => { this.edgeless.service.createGroupFromSelected(); }; diff --git a/blocksuite/blocks/src/root-block/widgets/element-toolbar/change-attachment-button.ts b/blocksuite/blocks/src/root-block/widgets/element-toolbar/change-attachment-button.ts index 2268b1351cc4b..d835d3e836e33 100644 --- a/blocksuite/blocks/src/root-block/widgets/element-toolbar/change-attachment-button.ts +++ b/blocksuite/blocks/src/root-block/widgets/element-toolbar/change-attachment-button.ts @@ -23,11 +23,11 @@ import { attachmentViewToggleMenu } from '../../../attachment-block/index.js'; import type { EdgelessRootBlockComponent } from '../../edgeless/edgeless-root-block.js'; export class EdgelessChangeAttachmentButton extends WithDisposable(LitElement) { - private _download = () => { + private readonly _download = () => { this._block?.download(); }; - private _setCardStyle = (style: EmbedCardStyle) => { + private readonly _setCardStyle = (style: EmbedCardStyle) => { const bounds = Bound.deserialize(this.model.xywh); bounds.w = EMBED_CARD_WIDTH[style]; bounds.h = EMBED_CARD_HEIGHT[style]; @@ -35,7 +35,7 @@ export class EdgelessChangeAttachmentButton extends WithDisposable(LitElement) { this.model.doc.updateBlock(this.model, { style, xywh }); }; - private _showCaption = () => { + private readonly _showCaption = () => { this._block?.captionEditor?.show(); }; diff --git a/blocksuite/blocks/src/root-block/widgets/element-toolbar/change-brush-button.ts b/blocksuite/blocks/src/root-block/widgets/element-toolbar/change-brush-button.ts index a50b6f146e460..c4db36174e781 100644 --- a/blocksuite/blocks/src/root-block/widgets/element-toolbar/change-brush-button.ts +++ b/blocksuite/blocks/src/root-block/widgets/element-toolbar/change-brush-button.ts @@ -44,12 +44,12 @@ function notEqual(key: K, value: BrushProps[K]) { } export class EdgelessChangeBrushButton extends WithDisposable(LitElement) { - private _setBrushColor = ({ detail: color }: ColorEvent) => { + private readonly _setBrushColor = ({ detail: color }: ColorEvent) => { this._setBrushProp('color', color); this._selectedColor = color; }; - private _setLineWidth = ({ detail: lineWidth }: LineWidthEvent) => { + private readonly _setLineWidth = ({ detail: lineWidth }: LineWidthEvent) => { this._setBrushProp('lineWidth', lineWidth); this._selectedSize = lineWidth; }; diff --git a/blocksuite/blocks/src/root-block/widgets/element-toolbar/change-embed-card-button.ts b/blocksuite/blocks/src/root-block/widgets/element-toolbar/change-embed-card-button.ts index c67a849f48611..dd6287d6943c0 100644 --- a/blocksuite/blocks/src/root-block/widgets/element-toolbar/change-embed-card-button.ts +++ b/blocksuite/blocks/src/root-block/widgets/element-toolbar/change-embed-card-button.ts @@ -112,7 +112,7 @@ export class EdgelessChangeEmbedCardButton extends WithDisposable(LitElement) { } `; - private _convertToCardView = () => { + private readonly _convertToCardView = () => { if (this._isCardView) { return; } @@ -162,7 +162,7 @@ export class EdgelessChangeEmbedCardButton extends WithDisposable(LitElement) { this._doc.deleteBlock(this.model); }; - private _convertToEmbedView = () => { + private readonly _convertToEmbedView = () => { if (this._isEmbedView) { return; } @@ -217,7 +217,7 @@ export class EdgelessChangeEmbedCardButton extends WithDisposable(LitElement) { this._doc.deleteBlock(this.model); }; - private _copyUrl = () => { + private readonly _copyUrl = () => { let url!: ReturnType; if ('url' in this.model) { @@ -241,7 +241,7 @@ export class EdgelessChangeEmbedCardButton extends WithDisposable(LitElement) { private _embedOptions: EmbedOptions | null = null; - private _getScale = () => { + private readonly _getScale = () => { if ('scale' in this.model) { return this.model.scale ?? 1; } else if (isEmbedHtmlBlock(this.model)) { @@ -252,11 +252,11 @@ export class EdgelessChangeEmbedCardButton extends WithDisposable(LitElement) { return bound.h / EMBED_CARD_HEIGHT[this.model.style]; }; - private _open = () => { + private readonly _open = () => { this._blockComponent?.open(); }; - private _openEditPopup = (e: MouseEvent) => { + private readonly _openEditPopup = (e: MouseEvent) => { e.stopPropagation(); if (isEmbedHtmlBlock(this.model)) return; @@ -277,12 +277,12 @@ export class EdgelessChangeEmbedCardButton extends WithDisposable(LitElement) { }); }; - private _peek = () => { + private readonly _peek = () => { if (!this._blockComponent) return; peek(this._blockComponent); }; - private _setCardStyle = (style: EmbedCardStyle) => { + private readonly _setCardStyle = (style: EmbedCardStyle) => { const bounds = Bound.deserialize(this.model.xywh); bounds.w = EMBED_CARD_WIDTH[style]; bounds.h = EMBED_CARD_HEIGHT[style]; @@ -295,7 +295,7 @@ export class EdgelessChangeEmbedCardButton extends WithDisposable(LitElement) { }); }; - private _setEmbedScale = (scale: number) => { + private readonly _setEmbedScale = (scale: number) => { if (isEmbedHtmlBlock(this.model)) return; const bound = Bound.deserialize(this.model.xywh); @@ -320,7 +320,7 @@ export class EdgelessChangeEmbedCardButton extends WithDisposable(LitElement) { }); }; - private _toggleCardScaleSelector = (e: Event) => { + private readonly _toggleCardScaleSelector = (e: Event) => { const opened = (e as CustomEvent).detail; if (!opened) return; @@ -329,7 +329,7 @@ export class EdgelessChangeEmbedCardButton extends WithDisposable(LitElement) { }); }; - private _toggleCardStyleSelector = (e: Event) => { + private readonly _toggleCardStyleSelector = (e: Event) => { const opened = (e as CustomEvent).detail; if (!opened) return; @@ -338,7 +338,7 @@ export class EdgelessChangeEmbedCardButton extends WithDisposable(LitElement) { }); }; - private _toggleViewSelector = (e: Event) => { + private readonly _toggleViewSelector = (e: Event) => { const opened = (e as CustomEvent).detail; if (!opened) return; @@ -347,7 +347,7 @@ export class EdgelessChangeEmbedCardButton extends WithDisposable(LitElement) { }); }; - private _trackViewSelected = (type: string) => { + private readonly _trackViewSelected = (type: string) => { track(this.std, this.model, this._viewType, 'SelectedView', { control: 'select view', type: `${type} view`, diff --git a/blocksuite/blocks/src/root-block/widgets/element-toolbar/change-image-button.ts b/blocksuite/blocks/src/root-block/widgets/element-toolbar/change-image-button.ts index 45f685cd7cc95..aa0ac93c7a344 100644 --- a/blocksuite/blocks/src/root-block/widgets/element-toolbar/change-image-button.ts +++ b/blocksuite/blocks/src/root-block/widgets/element-toolbar/change-image-button.ts @@ -9,12 +9,12 @@ import { downloadImageBlob } from '../../../image-block/utils.js'; import type { EdgelessRootBlockComponent } from '../../edgeless/edgeless-root-block.js'; export class EdgelessChangeImageButton extends WithDisposable(LitElement) { - private _download = () => { + private readonly _download = () => { if (!this._blockComponent) return; downloadImageBlob(this._blockComponent).catch(console.error); }; - private _showCaption = () => { + private readonly _showCaption = () => { this._blockComponent?.captionEditor?.show(); }; diff --git a/blocksuite/blocks/src/root-block/widgets/element-toolbar/change-mindmap-button.ts b/blocksuite/blocks/src/root-block/widgets/element-toolbar/change-mindmap-button.ts index 40f20e94ea753..c7d114125beca 100644 --- a/blocksuite/blocks/src/root-block/widgets/element-toolbar/change-mindmap-button.ts +++ b/blocksuite/blocks/src/root-block/widgets/element-toolbar/change-mindmap-button.ts @@ -155,7 +155,7 @@ export class EdgelessChangeMindmapLayoutPanel extends LitElement { } export class EdgelessChangeMindmapButton extends WithDisposable(LitElement) { - private _updateLayoutType = (layoutType: LayoutType) => { + private readonly _updateLayoutType = (layoutType: LayoutType) => { this.edgeless.std.get(EditPropsStore).recordLastProps('mindmap', { layoutType, }); @@ -166,7 +166,7 @@ export class EdgelessChangeMindmapButton extends WithDisposable(LitElement) { this.layoutType = layoutType; }; - private _updateStyle = (style: MindmapStyle) => { + private readonly _updateStyle = (style: MindmapStyle) => { this.edgeless.std.get(EditPropsStore).recordLastProps('mindmap', { style }); this._mindmaps.forEach(element => (element.style = style)); }; diff --git a/blocksuite/blocks/src/root-block/widgets/element-toolbar/change-note-button.ts b/blocksuite/blocks/src/root-block/widgets/element-toolbar/change-note-button.ts index ab5a56793300f..23aa1e8b64e71 100644 --- a/blocksuite/blocks/src/root-block/widgets/element-toolbar/change-note-button.ts +++ b/blocksuite/blocks/src/root-block/widgets/element-toolbar/change-note-button.ts @@ -78,7 +78,7 @@ function getMostCommonBackground( } export class EdgelessChangeNoteButton extends WithDisposable(LitElement) { - private _setBorderRadius = (borderRadius: number) => { + private readonly _setBorderRadius = (borderRadius: number) => { this.notes.forEach(note => { const props = { edgeless: { @@ -92,7 +92,7 @@ export class EdgelessChangeNoteButton extends WithDisposable(LitElement) { }); }; - private _setNoteScale = (scale: number) => { + private readonly _setNoteScale = (scale: number) => { this.notes.forEach(note => { this.doc.updateBlock(note, () => { const bound = Bound.deserialize(note.xywh); diff --git a/blocksuite/blocks/src/root-block/widgets/element-toolbar/change-text-menu.ts b/blocksuite/blocks/src/root-block/widgets/element-toolbar/change-text-menu.ts index f7a6c1479bc53..e207b5b35edbd 100644 --- a/blocksuite/blocks/src/root-block/widgets/element-toolbar/change-text-menu.ts +++ b/blocksuite/blocks/src/root-block/widgets/element-toolbar/change-text-menu.ts @@ -176,7 +176,7 @@ export class EdgelessChangeTextMenu extends WithDisposable(LitElement) { } `; - private _setFontFamily = (fontFamily: FontFamily) => { + private readonly _setFontFamily = (fontFamily: FontFamily) => { const currentFontWeight = getMostCommonFontWeight(this.elements); const fontWeight = TextUtils.isFontWeightSupported( fontFamily, @@ -199,7 +199,7 @@ export class EdgelessChangeTextMenu extends WithDisposable(LitElement) { }); }; - private _setFontSize = (fontSize: number) => { + private readonly _setFontSize = (fontSize: number) => { const props = { fontSize }; this.elements.forEach(element => { this.service.updateElement(element.id, buildProps(element, props)); @@ -207,7 +207,7 @@ export class EdgelessChangeTextMenu extends WithDisposable(LitElement) { }); }; - private _setFontWeightAndStyle = ( + private readonly _setFontWeightAndStyle = ( fontWeight: FontWeight, fontStyle: FontStyle ) => { @@ -218,21 +218,23 @@ export class EdgelessChangeTextMenu extends WithDisposable(LitElement) { }); }; - private _setTextAlign = (textAlign: TextAlign) => { + private readonly _setTextAlign = (textAlign: TextAlign) => { const props = { textAlign }; this.elements.forEach(element => { this.service.updateElement(element.id, buildProps(element, props)); }); }; - private _setTextColor = ({ detail: color }: ColorEvent) => { + private readonly _setTextColor = ({ detail: color }: ColorEvent) => { const props = { color }; this.elements.forEach(element => { this.service.updateElement(element.id, buildProps(element, props)); }); }; - private _updateElementBound = (element: BlockSuite.EdgelessTextModelType) => { + private readonly _updateElementBound = ( + element: BlockSuite.EdgelessTextModelType + ) => { const elementType = this.elementType; if (elementType === 'text' && element instanceof TextElementModel) { // the change of font family will change the bound of the text diff --git a/blocksuite/blocks/src/root-block/widgets/element-toolbar/index.ts b/blocksuite/blocks/src/root-block/widgets/element-toolbar/index.ts index 810a14654a395..e41a0381a030e 100644 --- a/blocksuite/blocks/src/root-block/widgets/element-toolbar/index.ts +++ b/blocksuite/blocks/src/root-block/widgets/element-toolbar/index.ts @@ -112,7 +112,7 @@ export class EdgelessElementToolbarWidget extends WidgetComponent< } `; - private _quickConnect = ({ x, y }: MouseEvent) => { + private readonly _quickConnect = ({ x, y }: MouseEvent) => { const element = this.selection.selectedElements[0]; const point = this.edgeless.service.viewport.toViewCoordFromClientCoord([ x, @@ -127,7 +127,9 @@ export class EdgelessElementToolbarWidget extends WidgetComponent< ctc.quickConnect(point, element); }; - private _updateOnSelectedChange = (element: string | { id: string }) => { + private readonly _updateOnSelectedChange = ( + element: string | { id: string } + ) => { const id = typeof element === 'string' ? element : element.id; if (this.isConnected && !this._dragging && this.selection.has(id)) { diff --git a/blocksuite/blocks/src/root-block/widgets/element-toolbar/more-menu/context.ts b/blocksuite/blocks/src/root-block/widgets/element-toolbar/more-menu/context.ts index d1a3b1eeaacdc..f2e57561237eb 100644 --- a/blocksuite/blocks/src/root-block/widgets/element-toolbar/more-menu/context.ts +++ b/blocksuite/blocks/src/root-block/widgets/element-toolbar/more-menu/context.ts @@ -20,13 +20,13 @@ import { } from '../../../edgeless/utils/query.js'; export class ElementToolbarMoreMenuContext extends MenuContext { - #empty = true; + readonly #empty: boolean; - #includedFrame = false; + readonly #includedFrame: boolean; - #multiple = false; + readonly #multiple: boolean; - #single = false; + readonly #single: boolean; edgeless!: EdgelessRootBlockComponent; diff --git a/blocksuite/blocks/src/root-block/widgets/embed-card-toolbar/embed-card-toolbar.ts b/blocksuite/blocks/src/root-block/widgets/embed-card-toolbar/embed-card-toolbar.ts index 8cc1ee0aad149..1e3a3becbef80 100644 --- a/blocksuite/blocks/src/root-block/widgets/embed-card-toolbar/embed-card-toolbar.ts +++ b/blocksuite/blocks/src/root-block/widgets/embed-card-toolbar/embed-card-toolbar.ts @@ -82,7 +82,7 @@ export class EmbedCardToolbar extends WidgetComponent< private _abortController = new AbortController(); - private _copyUrl = () => { + private readonly _copyUrl = () => { const model = this.focusModel; if (!model) return; @@ -109,7 +109,7 @@ export class EmbedCardToolbar extends WidgetComponent< private _embedOptions: EmbedOptions | null = null; - private _openEditPopup = (e: MouseEvent) => { + private readonly _openEditPopup = (e: MouseEvent) => { e.stopPropagation(); const model = this.focusModel; @@ -126,12 +126,12 @@ export class EmbedCardToolbar extends WidgetComponent< }); }; - private _resetAbortController = () => { + private readonly _resetAbortController = () => { this._abortController.abort(); this._abortController = new AbortController(); }; - private _showCaption = () => { + private readonly _showCaption = () => { const focusBlock = this.focusBlock; if (!focusBlock) { return; @@ -151,7 +151,7 @@ export class EmbedCardToolbar extends WidgetComponent< }); }; - private _toggleCardStyleSelector = (e: Event) => { + private readonly _toggleCardStyleSelector = (e: Event) => { const opened = (e as CustomEvent).detail; if (!opened) return; @@ -163,7 +163,7 @@ export class EmbedCardToolbar extends WidgetComponent< }); }; - private _toggleViewSelector = (e: Event) => { + private readonly _toggleViewSelector = (e: Event) => { const opened = (e as CustomEvent).detail; if (!opened) return; @@ -175,7 +175,7 @@ export class EmbedCardToolbar extends WidgetComponent< }); }; - private _trackViewSelected = (type: string) => { + private readonly _trackViewSelected = (type: string) => { const model = this.focusModel; if (!model) return; diff --git a/blocksuite/blocks/src/root-block/widgets/format-bar/config.ts b/blocksuite/blocks/src/root-block/widgets/format-bar/config.ts index 42722a3ebf45e..1b115d4058001 100644 --- a/blocksuite/blocks/src/root-block/widgets/format-bar/config.ts +++ b/blocksuite/blocks/src/root-block/widgets/format-bar/config.ts @@ -209,28 +209,30 @@ export function toolbarDefaultConfig(toolbar: AffineFormatBarWidget) { const doc = host.doc; const autofill = getTitleFromSelectedModels(selectedModels); - void promptDocTitle(host, autofill).then(async title => { - if (title === null) return; - await convertSelectedBlocksToLinkedDoc( - host.std, - doc, - draftedModels, - title - ); - notifyDocCreated(host, doc); - host.std.getOptional(TelemetryProvider)?.track('DocCreated', { - control: 'create linked doc', - page: 'doc editor', - module: 'format toolbar', - type: 'embed-linked-doc', - }); - host.std.getOptional(TelemetryProvider)?.track('LinkedDocCreated', { - control: 'create linked doc', - page: 'doc editor', - module: 'format toolbar', - type: 'embed-linked-doc', - }); - }); + promptDocTitle(host, autofill) + .then(async title => { + if (title === null) return; + await convertSelectedBlocksToLinkedDoc( + host.std, + doc, + draftedModels, + title + ); + notifyDocCreated(host, doc); + host.std.getOptional(TelemetryProvider)?.track('DocCreated', { + control: 'create linked doc', + page: 'doc editor', + module: 'format toolbar', + type: 'embed-linked-doc', + }); + host.std.getOptional(TelemetryProvider)?.track('LinkedDocCreated', { + control: 'create linked doc', + page: 'doc editor', + module: 'format toolbar', + type: 'embed-linked-doc', + }); + }) + .catch(console.error); }, showWhen: chain => { const [_, ctx] = chain diff --git a/blocksuite/blocks/src/root-block/widgets/image-toolbar/index.ts b/blocksuite/blocks/src/root-block/widgets/image-toolbar/index.ts index 8cb066fdcce6b..c16a69d45245a 100644 --- a/blocksuite/blocks/src/root-block/widgets/image-toolbar/index.ts +++ b/blocksuite/blocks/src/root-block/widgets/image-toolbar/index.ts @@ -25,7 +25,7 @@ export class AffineImageToolbarWidget extends WidgetComponent< private _isActivated = false; - private _setHoverController = () => { + private readonly _setHoverController = () => { this._hoverController = null; this._hoverController = new HoverController( this, diff --git a/blocksuite/blocks/src/root-block/widgets/keyboard-toolbar/index.ts b/blocksuite/blocks/src/root-block/widgets/keyboard-toolbar/index.ts index 87d083b213779..610bd7422deb7 100644 --- a/blocksuite/blocks/src/root-block/widgets/keyboard-toolbar/index.ts +++ b/blocksuite/blocks/src/root-block/widgets/keyboard-toolbar/index.ts @@ -16,7 +16,7 @@ export class AffineKeyboardToolbarWidget extends WidgetComponent< RootBlockModel, PageRootBlockComponent > { - private _close = (blur: boolean) => { + private readonly _close = (blur: boolean) => { if (blur) { if (document.activeElement === this._docTitle) { this._docTitle?.blur(); diff --git a/blocksuite/blocks/src/root-block/widgets/keyboard-toolbar/keyboard-toolbar.ts b/blocksuite/blocks/src/root-block/widgets/keyboard-toolbar/keyboard-toolbar.ts index 38d3d4e214977..0950954fd99a6 100644 --- a/blocksuite/blocks/src/root-block/widgets/keyboard-toolbar/keyboard-toolbar.ts +++ b/blocksuite/blocks/src/root-block/widgets/keyboard-toolbar/keyboard-toolbar.ts @@ -90,7 +90,7 @@ export class AffineKeyboardToolbar extends SignalWatcher( private readonly _path$ = signal([]); - private scrollCurrentBlockIntoView = () => { + private readonly scrollCurrentBlockIntoView = () => { const { std } = this.rootComponent; std.command .chain() diff --git a/blocksuite/blocks/src/root-block/widgets/linked-doc/import-doc/import-doc.ts b/blocksuite/blocks/src/root-block/widgets/linked-doc/import-doc/import-doc.ts index 864fb12b5412c..accd158c096a2 100644 --- a/blocksuite/blocks/src/root-block/widgets/linked-doc/import-doc/import-doc.ts +++ b/blocksuite/blocks/src/root-block/widgets/linked-doc/import-doc/import-doc.ts @@ -30,10 +30,10 @@ export class ImportDoc extends WithDisposable(LitElement) { static override styles = styles; constructor( - private collection: DocCollection, - private onSuccess?: OnSuccessHandler, - private onFail?: OnFailHandler, - private abortController = new AbortController() + private readonly collection: DocCollection, + private readonly onSuccess?: OnSuccessHandler, + private readonly onFail?: OnFailHandler, + private readonly abortController = new AbortController() ) { super(); diff --git a/blocksuite/blocks/src/root-block/widgets/linked-doc/index.ts b/blocksuite/blocks/src/root-block/widgets/linked-doc/index.ts index 4dda0483074a2..2d0bb508e0aab 100644 --- a/blocksuite/blocks/src/root-block/widgets/linked-doc/index.ts +++ b/blocksuite/blocks/src/root-block/widgets/linked-doc/index.ts @@ -68,7 +68,7 @@ export class AffineLinkedDocWidget extends WidgetComponent< private _inlineEditor: AffineInlineEditor | null = null; - private _observeInputRects = () => { + private readonly _observeInputRects = () => { if (!this._inlineEditor) return; const updateInputRects = () => { diff --git a/blocksuite/blocks/src/root-block/widgets/linked-doc/linked-doc-popover.ts b/blocksuite/blocks/src/root-block/widgets/linked-doc/linked-doc-popover.ts index 4b391844d7b26..f8c9ff9ce2250 100644 --- a/blocksuite/blocks/src/root-block/widgets/linked-doc/linked-doc-popover.ts +++ b/blocksuite/blocks/src/root-block/widgets/linked-doc/linked-doc-popover.ts @@ -32,7 +32,7 @@ export class LinkedDocPopover extends SignalWatcher( ) { static override styles = linkedDocPopoverStyles; - private _abort = () => { + private readonly _abort = () => { // remove popover dom this.context.close(); // clear input query @@ -43,9 +43,9 @@ export class LinkedDocPopover extends SignalWatcher( ); }; - private _expanded = new Map(); + private readonly _expanded = new Map(); - private _updateLinkedDocGroup = async () => { + private readonly _updateLinkedDocGroup = async () => { const query = this._query; if (this._updateLinkedDocGroupAbortController) { this._updateLinkedDocGroupAbortController.abort(); diff --git a/blocksuite/blocks/src/root-block/widgets/linked-doc/mobile-linked-doc-menu.ts b/blocksuite/blocks/src/root-block/widgets/linked-doc/mobile-linked-doc-menu.ts index 7ce603c4e7fd3..34d7e4e055121 100644 --- a/blocksuite/blocks/src/root-block/widgets/linked-doc/mobile-linked-doc-menu.ts +++ b/blocksuite/blocks/src/root-block/widgets/linked-doc/mobile-linked-doc-menu.ts @@ -45,7 +45,7 @@ export class AffineMobileLinkedDocMenu extends SignalWatcher( private readonly _linkedDocGroup$ = signal([]); - private _renderGroup = (group: LinkedMenuGroup) => { + private readonly _renderGroup = (group: LinkedMenuGroup) => { let items = resolveSignal(group.items); const isOverflow = !!group.maxDisplay && items.length > group.maxDisplay; @@ -90,7 +90,7 @@ export class AffineMobileLinkedDocMenu extends SignalWatcher( `; }; - private _scrollInputToTop = () => { + private readonly _scrollInputToTop = () => { const { inlineEditor } = this.context; const { scrollContainer, scrollTopOffset } = this.context.config.mobile; diff --git a/blocksuite/blocks/src/root-block/widgets/page-dragging-area/page-dragging-area.ts b/blocksuite/blocks/src/root-block/widgets/page-dragging-area/page-dragging-area.ts index eed66f81726e2..cbb068ab95d5a 100644 --- a/blocksuite/blocks/src/root-block/widgets/page-dragging-area/page-dragging-area.ts +++ b/blocksuite/blocks/src/root-block/widgets/page-dragging-area/page-dragging-area.ts @@ -60,7 +60,7 @@ export class AffinePageDraggingAreaWidget extends WidgetComponent< private _rafID = 0; - private _updateDraggingArea = ( + private readonly _updateDraggingArea = ( state: PointerEventState, shouldAutoScroll: boolean ) => { diff --git a/blocksuite/blocks/src/root-block/widgets/pie-menu/index.ts b/blocksuite/blocks/src/root-block/widgets/pie-menu/index.ts index 6bee2823d728f..cab17eb6de1ce 100644 --- a/blocksuite/blocks/src/root-block/widgets/pie-menu/index.ts +++ b/blocksuite/blocks/src/root-block/widgets/pie-menu/index.ts @@ -23,13 +23,13 @@ noop(PieNodeChild); export const AFFINE_PIE_MENU_WIDGET = 'affine-pie-menu-widget'; export class AffinePieMenuWidget extends WidgetComponent { - private _handleCursorPos = (ctx: UIEventStateContext) => { + private readonly _handleCursorPos = (ctx: UIEventStateContext) => { const ev = ctx.get('pointerState'); const { x, y } = ev.point; this.mouse = [x, y]; }; - private _handleKeyUp = (ctx: UIEventStateContext) => { + private readonly _handleKeyUp = (ctx: UIEventStateContext) => { if (!this.currentMenu) return; const ev = ctx.get('keyboardState'); const { trigger } = this.currentMenu.schema; diff --git a/blocksuite/blocks/src/root-block/widgets/pie-menu/menu.ts b/blocksuite/blocks/src/root-block/widgets/pie-menu/menu.ts index 1b98182316d7a..d914a5330685c 100644 --- a/blocksuite/blocks/src/root-block/widgets/pie-menu/menu.ts +++ b/blocksuite/blocks/src/root-block/widgets/pie-menu/menu.ts @@ -32,7 +32,7 @@ const { toDegree, toRadian } = CommonUtils; export class PieMenu extends WithDisposable(LitElement) { static override styles = pieMenuStyles; - private _handleKeyDown = (ev: KeyboardEvent) => { + private readonly _handleKeyDown = (ev: KeyboardEvent) => { const { key } = ev; if (key === 'Escape') { return this.abortController.abort(); @@ -49,7 +49,7 @@ export class PieMenu extends WithDisposable(LitElement) { } }; - private _handlePointerMove = (ev: PointerEvent) => { + private readonly _handlePointerMove = (ev: PointerEvent) => { const { clientX, clientY } = ev; const { ACTIVATE_THRESHOLD_MIN } = PieManager.settings; @@ -73,7 +73,7 @@ export class PieMenu extends WithDisposable(LitElement) { private _openSubmenuTimeout?: NodeJS.Timeout; - private selectChildWithIndex = (index: number) => { + private readonly selectChildWithIndex = (index: number) => { const activeNode = this.activeNode; if (!activeNode || isNaN(index)) return; diff --git a/blocksuite/blocks/src/root-block/widgets/pie-menu/node.ts b/blocksuite/blocks/src/root-block/widgets/pie-menu/node.ts index b2c473ff0a9c8..69cd37e4a9a8f 100644 --- a/blocksuite/blocks/src/root-block/widgets/pie-menu/node.ts +++ b/blocksuite/blocks/src/root-block/widgets/pie-menu/node.ts @@ -18,19 +18,19 @@ import { export class PieNode extends WithDisposable(LitElement) { static override styles = pieNodeStyles; - private _handleChildNodeClick = () => { + private readonly _handleChildNodeClick = () => { this.select(); if (isCommandNode(this.model)) this.menu.close(); }; - private _handleGoBack = () => { + private readonly _handleGoBack = () => { // If the node is not active and if it is hovered then we can go back to that node if (this.menu.activeNode !== this) { this.menu.popSelectionChainTo(this); } }; - private _onPointerAngleUpdated = (angle: number | null) => { + private readonly _onPointerAngleUpdated = (angle: number | null) => { this._rotatorAngle = angle; this.menu.activeNode.requestUpdate(); diff --git a/blocksuite/blocks/src/root-block/widgets/pie-menu/pie-manager.ts b/blocksuite/blocks/src/root-block/widgets/pie-menu/pie-manager.ts index dacaecaa270b5..31e4c203ea82e 100644 --- a/blocksuite/blocks/src/root-block/widgets/pie-menu/pie-manager.ts +++ b/blocksuite/blocks/src/root-block/widgets/pie-menu/pie-manager.ts @@ -12,7 +12,7 @@ import type { PieMenuSchema } from './base.js'; export class PieManager { private static registeredSchemas: Record = {}; - private static schemas = new Set(); + private static readonly schemas = new Set(); static settings = { /** diff --git a/blocksuite/blocks/src/root-block/widgets/slash-menu/config.ts b/blocksuite/blocks/src/root-block/widgets/slash-menu/config.ts index 09c3be0fa83b9..2fb9a43e88915 100644 --- a/blocksuite/blocks/src/root-block/widgets/slash-menu/config.ts +++ b/blocksuite/blocks/src/root-block/widgets/slash-menu/config.ts @@ -588,12 +588,14 @@ export const defaultSlashMenuConfig: SlashMenuConfig = { ); const dataViewModel = rootComponent.doc.getBlock(id)!; - Promise.resolve().then(() => { - const dataView = rootComponent.std.view.getBlock( - dataViewModel.id - ) as DataViewBlockComponent | null; - dataView?.dataSource.viewManager.viewAdd('table'); - }); + Promise.resolve() + .then(() => { + const dataView = rootComponent.std.view.getBlock( + dataViewModel.id + ) as DataViewBlockComponent | null; + dataView?.dataSource.viewManager.viewAdd('table'); + }) + .catch(console.error); tryRemoveEmptyLine(model); }, }, diff --git a/blocksuite/blocks/src/root-block/widgets/slash-menu/index.ts b/blocksuite/blocks/src/root-block/widgets/slash-menu/index.ts index 79d201c2685fe..e177f22039171 100644 --- a/blocksuite/blocks/src/root-block/widgets/slash-menu/index.ts +++ b/blocksuite/blocks/src/root-block/widgets/slash-menu/index.ts @@ -108,7 +108,9 @@ export const AFFINE_SLASH_MENU_WIDGET = 'affine-slash-menu-widget'; export class AffineSlashMenuWidget extends WidgetComponent { static DEFAULT_CONFIG = defaultSlashMenuConfig; - private _getInlineEditor = (evt: KeyboardEvent | CompositionEvent) => { + private readonly _getInlineEditor = ( + evt: KeyboardEvent | CompositionEvent + ) => { if (evt.target instanceof HTMLElement) { const editor = ( evt.target.closest('.inline-editor') as { @@ -129,7 +131,7 @@ export class AffineSlashMenuWidget extends WidgetComponent { return getInlineEditorByModel(this.host, model); }; - private _handleInput = ( + private readonly _handleInput = ( inlineEditor: InlineEditor, isCompositionEnd: boolean ) => { @@ -192,7 +194,7 @@ export class AffineSlashMenuWidget extends WidgetComponent { }); }; - private _onCompositionEnd = (ctx: UIEventStateContext) => { + private readonly _onCompositionEnd = (ctx: UIEventStateContext) => { const event = ctx.get('defaultState').event as CompositionEvent; if ( @@ -208,7 +210,7 @@ export class AffineSlashMenuWidget extends WidgetComponent { this._handleInput(inlineEditor, true); }; - private _onKeyDown = (ctx: UIEventStateContext) => { + private readonly _onKeyDown = (ctx: UIEventStateContext) => { const eventState = ctx.get('keyboardState'); const event = eventState.raw; diff --git a/blocksuite/blocks/src/root-block/widgets/slash-menu/slash-menu-popover.ts b/blocksuite/blocks/src/root-block/widgets/slash-menu/slash-menu-popover.ts index a57f61be3838b..c879fff1b6a16 100644 --- a/blocksuite/blocks/src/root-block/widgets/slash-menu/slash-menu-popover.ts +++ b/blocksuite/blocks/src/root-block/widgets/slash-menu/slash-menu-popover.ts @@ -46,7 +46,7 @@ type InnerSlashMenuContext = SlashMenuContext & { export class SlashMenu extends WithDisposable(LitElement) { static override styles = styles; - private _handleClickItem = (item: SlashMenuActionItem) => { + private readonly _handleClickItem = (item: SlashMenuActionItem) => { // Need to remove the search string // We must to do clean the slash string before we do the action // Otherwise, the action may change the model and cause the slash string to be changed @@ -64,7 +64,7 @@ export class SlashMenu extends WithDisposable(LitElement) { .catch(console.error); }; - private _initItemPathMap = () => { + private readonly _initItemPathMap = () => { const traverse = (item: SlashMenuStaticItem, path: number[]) => { this._itemPathMap.set(item, [...path]); if (isSubMenuItem(item)) { @@ -79,13 +79,13 @@ export class SlashMenu extends WithDisposable(LitElement) { private _innerSlashMenuContext!: InnerSlashMenuContext; - private _itemPathMap = new Map(); + private readonly _itemPathMap = new Map(); private _queryState: 'off' | 'on' | 'no_result' = 'off'; - private _startRange = this.inlineEditor.getInlineRange(); + private readonly _startRange = this.inlineEditor.getInlineRange(); - private _updateFilteredItems = () => { + private readonly _updateFilteredItems = () => { const query = this._query; if (query === null) { this.abortController.abort(); @@ -152,8 +152,8 @@ export class SlashMenu extends WithDisposable(LitElement) { } constructor( - private inlineEditor: AffineInlineEditor, - private abortController = new AbortController() + private readonly inlineEditor: AffineInlineEditor, + private readonly abortController = new AbortController() ) { super(); } @@ -301,7 +301,7 @@ export class SlashMenu extends WithDisposable(LitElement) { export class InnerSlashMenu extends WithDisposable(LitElement) { static override styles = styles; - private _closeSubMenu = () => { + private readonly _closeSubMenu = () => { this._subMenuAbortController?.abort(); this._subMenuAbortController = null; this._currentSubMenu = null; @@ -309,7 +309,7 @@ export class InnerSlashMenu extends WithDisposable(LitElement) { private _currentSubMenu: SlashSubMenu | null = null; - private _openSubMenu = (item: SlashSubMenu) => { + private readonly _openSubMenu = (item: SlashSubMenu) => { if (item === this._currentSubMenu) return; const itemElement = this.shadowRoot?.querySelector( @@ -351,7 +351,7 @@ export class InnerSlashMenu extends WithDisposable(LitElement) { subMenuElement.focus(); }; - private _renderActionItem = (item: SlashMenuActionItem) => { + private readonly _renderActionItem = (item: SlashMenuActionItem) => { const { name, icon, description, tooltip, customTemplate } = item; const hover = item === this._activeItem; @@ -387,11 +387,11 @@ export class InnerSlashMenu extends WithDisposable(LitElement) { `; }; - private _renderGroupItem = (item: SlashMenuGroupDivider) => { + private readonly _renderGroupItem = (item: SlashMenuGroupDivider) => { return html`
    ${item.groupName}
    `; }; - private _renderItem = (item: SlashMenuStaticItem) => { + private readonly _renderItem = (item: SlashMenuStaticItem) => { if (isGroupDivider(item)) return this._renderGroupItem(item); else if (isActionItem(item)) return this._renderActionItem(item); else if (isSubMenuItem(item)) return this._renderSubMenuItem(item); @@ -402,7 +402,7 @@ export class InnerSlashMenu extends WithDisposable(LitElement) { } }; - private _renderSubMenuItem = (item: SlashSubMenu) => { + private readonly _renderSubMenuItem = (item: SlashSubMenu) => { const { name, icon, description } = item; const hover = item === this._activeItem; diff --git a/blocksuite/blocks/src/root-block/widgets/surface-ref-toolbar/surface-ref-toolbar.ts b/blocksuite/blocks/src/root-block/widgets/surface-ref-toolbar/surface-ref-toolbar.ts index cce075508adaa..a3615a16da577 100644 --- a/blocksuite/blocks/src/root-block/widgets/surface-ref-toolbar/surface-ref-toolbar.ts +++ b/blocksuite/blocks/src/root-block/widgets/surface-ref-toolbar/surface-ref-toolbar.ts @@ -42,7 +42,7 @@ export class AffineSurfaceRefToolbar extends WidgetComponent< moreGroups: MenuItemGroup[] = cloneGroups(BUILT_IN_GROUPS); - private _hoverController = new HoverController( + private readonly _hoverController = new HoverController( this, ({ abortController }) => { const surfaceRefBlock = this.block; diff --git a/blocksuite/blocks/src/surface-ref-block/surface-ref-block.ts b/blocksuite/blocks/src/surface-ref-block/surface-ref-block.ts index 98d7a21ddc987..8c6b3cfffd8e4 100644 --- a/blocksuite/blocks/src/surface-ref-block/surface-ref-block.ts +++ b/blocksuite/blocks/src/surface-ref-block/surface-ref-block.ts @@ -239,7 +239,8 @@ export class SurfaceRefBlockComponent extends BlockComponent< private _previewDoc: Doc | null = null; - private _previewSpec = SpecProvider.getInstance().getSpec('edgeless:preview'); + private readonly _previewSpec = + SpecProvider.getInstance().getSpec('edgeless:preview'); private _referencedModel: BlockSuite.EdgelessModel | null = null; @@ -459,7 +460,7 @@ export class SurfaceRefBlockComponent extends BlockComponent< class FrameGroupViewWatcher extends LifeCycleWatcher { static override readonly key = 'surface-ref-group-view-watcher'; - private _disposable = new DisposableGroup(); + private readonly _disposable = new DisposableGroup(); override mounted() { const edgelessService = this.std.get(EdgelessRootService); diff --git a/blocksuite/framework/block-std/src/clipboard/index.ts b/blocksuite/framework/block-std/src/clipboard/index.ts index 48aee32dc5404..a1d8295e9b9b1 100644 --- a/blocksuite/framework/block-std/src/clipboard/index.ts +++ b/blocksuite/framework/block-std/src/clipboard/index.ts @@ -60,10 +60,10 @@ export function onlyContainImgElement( export class Clipboard extends LifeCycleWatcher { static override readonly key = 'clipboard'; - private _adapterMap: AdapterMap = new Map(); + private readonly _adapterMap: AdapterMap = new Map(); // Need to be cloned to a map for later use - private _getDataByType = (clipboardData: DataTransfer) => { + private readonly _getDataByType = (clipboardData: DataTransfer) => { const data = new Map(); for (const type of clipboardData.types) { if (type === 'Files') { @@ -105,7 +105,7 @@ export class Clipboard extends LifeCycleWatcher { }; }; - private _getSnapshotByPriority = async ( + private readonly _getSnapshotByPriority = async ( getItem: (type: string) => string | File[], doc: Doc, parent?: string, diff --git a/blocksuite/framework/block-std/src/command/manager.ts b/blocksuite/framework/block-std/src/command/manager.ts index 974bfb22a33c0..a5078d8ea2e4e 100644 --- a/blocksuite/framework/block-std/src/command/manager.ts +++ b/blocksuite/framework/block-std/src/command/manager.ts @@ -128,9 +128,9 @@ import type { export class CommandManager extends LifeCycleWatcher { static override readonly key = 'commandManager'; - private _commands = new Map(); + private readonly _commands = new Map(); - private _createChain = ( + private readonly _createChain = ( methods: Record, _cmds: Command[] ): Chain => { @@ -239,7 +239,7 @@ export class CommandManager extends LifeCycleWatcher { } as Chain; }; - private _getCommandCtx = (): InitCommandCtx => { + private readonly _getCommandCtx = (): InitCommandCtx => { return { std: this.std, }; diff --git a/blocksuite/framework/block-std/src/event/control/clipboard.ts b/blocksuite/framework/block-std/src/event/control/clipboard.ts index 0f746e85f4898..2993a61178f4a 100644 --- a/blocksuite/framework/block-std/src/event/control/clipboard.ts +++ b/blocksuite/framework/block-std/src/event/control/clipboard.ts @@ -4,7 +4,7 @@ import { ClipboardEventState } from '../state/clipboard.js'; import { EventScopeSourceType, EventSourceState } from '../state/source.js'; export class ClipboardControl { - private _copy = (event: ClipboardEvent) => { + private readonly _copy = (event: ClipboardEvent) => { const clipboardEventState = new ClipboardEventState({ event, }); @@ -14,7 +14,7 @@ export class ClipboardControl { ); }; - private _cut = (event: ClipboardEvent) => { + private readonly _cut = (event: ClipboardEvent) => { const clipboardEventState = new ClipboardEventState({ event, }); @@ -24,7 +24,7 @@ export class ClipboardControl { ); }; - private _paste = (event: ClipboardEvent) => { + private readonly _paste = (event: ClipboardEvent) => { const clipboardEventState = new ClipboardEventState({ event, }); @@ -35,7 +35,7 @@ export class ClipboardControl { ); }; - constructor(private _dispatcher: UIEventDispatcher) {} + constructor(private readonly _dispatcher: UIEventDispatcher) {} private _createContext(event: Event, clipboardState: ClipboardEventState) { return UIEventStateContext.from( diff --git a/blocksuite/framework/block-std/src/event/control/keyboard.ts b/blocksuite/framework/block-std/src/event/control/keyboard.ts index e3e70d63cf9c6..38d590e98bf30 100644 --- a/blocksuite/framework/block-std/src/event/control/keyboard.ts +++ b/blocksuite/framework/block-std/src/event/control/keyboard.ts @@ -11,7 +11,7 @@ import { KeyboardEventState } from '../state/index.js'; import { EventScopeSourceType, EventSourceState } from '../state/source.js'; export class KeyboardControl { - private _down = (event: KeyboardEvent) => { + private readonly _down = (event: KeyboardEvent) => { if (!this._shouldTrigger(event)) { return; } @@ -25,7 +25,7 @@ export class KeyboardControl { ); }; - private _shouldTrigger = (event: KeyboardEvent) => { + private readonly _shouldTrigger = (event: KeyboardEvent) => { if (event.isComposing) { return false; } @@ -41,7 +41,7 @@ export class KeyboardControl { return true; }; - private _up = (event: KeyboardEvent) => { + private readonly _up = (event: KeyboardEvent) => { if (!this._shouldTrigger(event)) { return; } @@ -58,7 +58,7 @@ export class KeyboardControl { private composition = false; - constructor(private _dispatcher: UIEventDispatcher) {} + constructor(private readonly _dispatcher: UIEventDispatcher) {} private _createContext(event: Event, keyboardState: KeyboardEventState) { return UIEventStateContext.from( diff --git a/blocksuite/framework/block-std/src/event/control/pointer.ts b/blocksuite/framework/block-std/src/event/control/pointer.ts index a4e4458f9d496..78a98b7179d73 100644 --- a/blocksuite/framework/block-std/src/event/control/pointer.ts +++ b/blocksuite/framework/block-std/src/event/control/pointer.ts @@ -39,7 +39,7 @@ abstract class PointerControllerBase { } class PointerEventForward extends PointerControllerBase { - private _down = (event: PointerEvent) => { + private readonly _down = (event: PointerEvent) => { const { pointerId } = event; const pointerState = new PointerEventState({ @@ -54,9 +54,9 @@ class PointerEventForward extends PointerControllerBase { this._dispatcher.run('pointerDown', createContext(event, pointerState)); }; - private _lastStates = new Map(); + private readonly _lastStates = new Map(); - private _move = (event: PointerEvent) => { + private readonly _move = (event: PointerEvent) => { const { pointerId } = event; const start = this._startStates.get(pointerId) ?? null; @@ -74,9 +74,9 @@ class PointerEventForward extends PointerControllerBase { this._dispatcher.run('pointerMove', createContext(event, state)); }; - private _startStates = new Map(); + private readonly _startStates = new Map(); - private _upOrOut = (up: boolean) => (event: PointerEvent) => { + private readonly _upOrOut = (up: boolean) => (event: PointerEvent) => { const { pointerId } = event; const start = this._startStates.get(pointerId) ?? null; @@ -109,7 +109,7 @@ class PointerEventForward extends PointerControllerBase { } class ClickController extends PointerControllerBase { - private _down = (event: PointerEvent) => { + private readonly _down = (event: PointerEvent) => { // disable for secondary pointer if (event.isPrimary === false) return; @@ -137,7 +137,7 @@ class ClickController extends PointerControllerBase { private _pointerDownCount = 0; - private _up = (event: PointerEvent) => { + private readonly _up = (event: PointerEvent) => { if (!this._downPointerState) return; if (isFarEnough(this._downPointerState.raw, event)) { @@ -177,7 +177,7 @@ class ClickController extends PointerControllerBase { } class DragController extends PointerControllerBase { - private _down = (event: PointerEvent) => { + private readonly _down = (event: PointerEvent) => { if (this._nativeDragging) return; if (!event.isPrimary) { @@ -209,7 +209,7 @@ class DragController extends PointerControllerBase { private _lastPointerState: PointerEventState | null = null; - private _move = (event: PointerEvent) => { + private readonly _move = (event: PointerEvent) => { if ( this._startPointerState === null || this._startPointerState.raw.pointerId !== event.pointerId @@ -243,7 +243,7 @@ class DragController extends PointerControllerBase { } }; - private _nativeDragEnd = (event: DragEvent) => { + private readonly _nativeDragEnd = (event: DragEvent) => { this._nativeDragging = false; const dndEventState = new DndEventState({ event }); this._dispatcher.run( @@ -254,7 +254,7 @@ class DragController extends PointerControllerBase { private _nativeDragging = false; - private _nativeDragMove = (event: DragEvent) => { + private readonly _nativeDragMove = (event: DragEvent) => { const dndEventState = new DndEventState({ event }); this._dispatcher.run( 'nativeDragMove', @@ -262,7 +262,7 @@ class DragController extends PointerControllerBase { ); }; - private _nativeDragStart = (event: DragEvent) => { + private readonly _nativeDragStart = (event: DragEvent) => { this._reset(); this._nativeDragging = true; const dndEventState = new DndEventState({ event }); @@ -272,7 +272,7 @@ class DragController extends PointerControllerBase { ); }; - private _nativeDrop = (event: DragEvent) => { + private readonly _nativeDrop = (event: DragEvent) => { this._reset(); this._nativeDragging = false; const dndEventState = new DndEventState({ event }); @@ -282,7 +282,7 @@ class DragController extends PointerControllerBase { ); }; - private _reset = () => { + private readonly _reset = () => { this._dragging = false; this._startPointerState = null; this._lastPointerState = null; @@ -293,7 +293,7 @@ class DragController extends PointerControllerBase { private _startPointerState: PointerEventState | null = null; - private _up = (event: PointerEvent) => { + private readonly _up = (event: PointerEvent) => { if ( !this._startPointerState || this._startPointerState.raw.pointerId !== event.pointerId @@ -358,7 +358,7 @@ class DragController extends PointerControllerBase { } abstract class DualDragControllerBase extends PointerControllerBase { - private _down = (event: PointerEvent) => { + private readonly _down = (event: PointerEvent) => { // Another pointer down if ( this._startPointerStates.primary !== null && @@ -394,7 +394,7 @@ abstract class DualDragControllerBase extends PointerControllerBase { secondary: null, }; - private _move = (event: PointerEvent) => { + private readonly _move = (event: PointerEvent) => { if ( this._startPointerStates.primary === null || this._startPointerStates.secondary === null @@ -440,7 +440,7 @@ abstract class DualDragControllerBase extends PointerControllerBase { }; }; - private _reset = () => { + private readonly _reset = () => { this._startPointerStates = { primary: null, secondary: null, @@ -459,7 +459,7 @@ abstract class DualDragControllerBase extends PointerControllerBase { secondary: null, }; - private _upOrOut = (event: PointerEvent) => { + private readonly _upOrOut = (event: PointerEvent) => { const { pointerId } = event; if ( pointerId === this._startPointerStates.primary?.raw.pointerId || @@ -542,7 +542,7 @@ class PanController extends DualDragControllerBase { export class PointerControl { private _cachedRect: DOMRect | null = null; - private _getRect = () => { + private readonly _getRect = () => { if (this._cachedRect === null) { this._updateRect(); } @@ -553,9 +553,9 @@ export class PointerControl { // due to potential performance issues private _pollingInterval: number | null = null; - private controllers: PointerControllerBase[]; + private readonly controllers: PointerControllerBase[]; - constructor(private _dispatcher: UIEventDispatcher) { + constructor(private readonly _dispatcher: UIEventDispatcher) { this.controllers = [ new PointerEventForward(_dispatcher, this._getRect), new ClickController(_dispatcher, this._getRect), diff --git a/blocksuite/framework/block-std/src/event/control/range.ts b/blocksuite/framework/block-std/src/event/control/range.ts index c70bcb68782db..51ab67e09972f 100644 --- a/blocksuite/framework/block-std/src/event/control/range.ts +++ b/blocksuite/framework/block-std/src/event/control/range.ts @@ -8,7 +8,7 @@ import type { import { EventScopeSourceType, EventSourceState } from '../state/source.js'; export class RangeControl { - private _buildScope = (eventName: EventName) => { + private readonly _buildScope = (eventName: EventName) => { let scope: EventHandlerRunner[] | undefined; const selection = document.getSelection(); if (selection && selection.rangeCount > 0) { @@ -23,19 +23,19 @@ export class RangeControl { return scope; }; - private _compositionEnd = (event: Event) => { + private readonly _compositionEnd = (event: Event) => { const scope = this._buildScope('compositionEnd'); this._dispatcher.run('compositionEnd', this._createContext(event), scope); }; - private _compositionStart = (event: Event) => { + private readonly _compositionStart = (event: Event) => { const scope = this._buildScope('compositionStart'); this._dispatcher.run('compositionStart', this._createContext(event), scope); }; - private _compositionUpdate = (event: Event) => { + private readonly _compositionUpdate = (event: Event) => { const scope = this._buildScope('compositionUpdate'); this._dispatcher.run( @@ -47,7 +47,7 @@ export class RangeControl { private _prev: Range | null = null; - private _selectionChange = (event: Event) => { + private readonly _selectionChange = (event: Event) => { const selection = document.getSelection(); if (!selection) return; @@ -59,7 +59,7 @@ export class RangeControl { this._dispatcher.run('selectionChange', this._createContext(event), scope); }; - constructor(private _dispatcher: UIEventDispatcher) {} + constructor(private readonly _dispatcher: UIEventDispatcher) {} private _buildEventScopeByNativeRange(name: EventName, range: Range) { const blockIds = this._findBlockComponentPath(range); diff --git a/blocksuite/framework/block-std/src/event/dispatcher.ts b/blocksuite/framework/block-std/src/event/dispatcher.ts index 05502bc409907..5f77628087072 100644 --- a/blocksuite/framework/block-std/src/event/dispatcher.ts +++ b/blocksuite/framework/block-std/src/event/dispatcher.ts @@ -80,17 +80,17 @@ export class UIEventDispatcher extends LifeCycleWatcher { private _active = false; - private _clipboardControl: ClipboardControl; + private readonly _clipboardControl: ClipboardControl; private _handlersMap = Object.fromEntries( eventNames.map((name): [EventName, Array] => [name, []]) ) as Record>; - private _keyboardControl: KeyboardControl; + private readonly _keyboardControl: KeyboardControl; - private _pointerControl: PointerControl; + private readonly _pointerControl: PointerControl; - private _rangeControl: RangeControl; + private readonly _rangeControl: RangeControl; bindHotkey = (...args: Parameters) => this._keyboardControl.bindHotkey(...args); diff --git a/blocksuite/framework/block-std/src/gfx/controller.ts b/blocksuite/framework/block-std/src/gfx/controller.ts index c62ad73c24907..4a4a707fd0239 100644 --- a/blocksuite/framework/block-std/src/gfx/controller.ts +++ b/blocksuite/framework/block-std/src/gfx/controller.ts @@ -35,7 +35,7 @@ import { Viewport } from './viewport.js'; export class GfxController extends LifeCycleWatcher { static override key = gfxControllerKey; - private _disposables: DisposableGroup = new DisposableGroup(); + private readonly _disposables: DisposableGroup = new DisposableGroup(); private _surface: SurfaceBlockModel | null = null; diff --git a/blocksuite/framework/block-std/src/gfx/grid.ts b/blocksuite/framework/block-std/src/gfx/grid.ts index e997cccd84cfc..12df95ab19604 100644 --- a/blocksuite/framework/block-std/src/gfx/grid.ts +++ b/blocksuite/framework/block-std/src/gfx/grid.ts @@ -66,16 +66,22 @@ const typeFilters = { type FilterFunc = (model: GfxModel | GfxLocalElementModel) => boolean; export class GridManager { - private _elementToGrids = new Map< + private readonly _elementToGrids = new Map< GfxModel | GfxLocalElementModel, Set> >(); - private _externalElementToGrids = new Map>>(); + private readonly _externalElementToGrids = new Map< + GfxModel, + Set> + >(); - private _externalGrids = new Map>(); + private readonly _externalGrids = new Map>(); - private _grids = new Map>(); + private readonly _grids = new Map< + string, + Set + >(); get isEmpty() { return this._grids.size === 0; diff --git a/blocksuite/framework/block-std/src/gfx/keyboard.ts b/blocksuite/framework/block-std/src/gfx/keyboard.ts index 0eda0e4196ce6..c6428ae5a0a18 100644 --- a/blocksuite/framework/block-std/src/gfx/keyboard.ts +++ b/blocksuite/framework/block-std/src/gfx/keyboard.ts @@ -4,7 +4,7 @@ import { Signal } from '@preact/signals-core'; import type { BlockStdScope } from '../scope/block-std-scope.js'; export class KeyboardController { - private _disposable = new DisposableGroup(); + private readonly _disposable = new DisposableGroup(); shiftKey$ = new Signal(false); diff --git a/blocksuite/framework/block-std/src/gfx/layer.ts b/blocksuite/framework/block-std/src/gfx/layer.ts index c77a7f8f02b54..b314faa2d0123 100644 --- a/blocksuite/framework/block-std/src/gfx/layer.ts +++ b/blocksuite/framework/block-std/src/gfx/layer.ts @@ -71,7 +71,7 @@ export type Layer = BlockLayer | CanvasLayer; export class LayerManager { static INITIAL_INDEX = 'a0'; - private _disposable = new DisposableGroup(); + private readonly _disposable = new DisposableGroup(); blocks: GfxBlockElementModel[] = []; @@ -100,7 +100,7 @@ export class LayerManager { }; constructor( - private _doc: Doc, + private readonly _doc: Doc, private _surface: SurfaceBlockModel | null, options: { watch: boolean; diff --git a/blocksuite/framework/block-std/src/gfx/model/surface/element-model.ts b/blocksuite/framework/block-std/src/gfx/model/surface/element-model.ts index 1c5a1c5d25969..bd88ca488a7d3 100644 --- a/blocksuite/framework/block-std/src/gfx/model/surface/element-model.ts +++ b/blocksuite/framework/block-std/src/gfx/model/surface/element-model.ts @@ -388,7 +388,7 @@ export abstract class GfxGroupLikeElementModel< { private _childIds: string[] = []; - private _mutex = createMutex(); + private readonly _mutex = createMutex(); abstract children: Y.Map; diff --git a/blocksuite/framework/block-std/src/gfx/model/surface/local-element-model.ts b/blocksuite/framework/block-std/src/gfx/model/surface/local-element-model.ts index 31f7638b2ad64..aa6b7a1bf696f 100644 --- a/blocksuite/framework/block-std/src/gfx/model/surface/local-element-model.ts +++ b/blocksuite/framework/block-std/src/gfx/model/surface/local-element-model.ts @@ -39,7 +39,7 @@ export function prop() { } export abstract class GfxLocalElementModel implements GfxCompatibleInterface { - private _mutex: mutex.mutex = mutex.createMutex(); + private readonly _mutex: mutex.mutex = mutex.createMutex(); protected _local = new Map(); diff --git a/blocksuite/framework/block-std/src/gfx/tool/tool-controller.ts b/blocksuite/framework/block-std/src/gfx/tool/tool-controller.ts index 7d72e95d64be3..0e74036af815a 100644 --- a/blocksuite/framework/block-std/src/gfx/tool/tool-controller.ts +++ b/blocksuite/framework/block-std/src/gfx/tool/tool-controller.ts @@ -83,15 +83,15 @@ export const eventTarget = Symbol('eventTarget'); export class ToolController extends GfxExtension { static override key = 'ToolController'; - private _builtInHookSlot = new Slot(); + private readonly _builtInHookSlot = new Slot(); - private _disposableGroup = new DisposableGroup(); + private readonly _disposableGroup = new DisposableGroup(); - private _toolOption$ = new Signal( + private readonly _toolOption$ = new Signal( {} as GfxToolsFullOptionValue ); - private _tools = new Map(); + private readonly _tools = new Map(); readonly currentToolName$ = new Signal(); diff --git a/blocksuite/framework/block-std/src/gfx/view/view-manager.ts b/blocksuite/framework/block-std/src/gfx/view/view-manager.ts index d3484a3401fff..b7d92bd4ef046 100644 --- a/blocksuite/framework/block-std/src/gfx/view/view-manager.ts +++ b/blocksuite/framework/block-std/src/gfx/view/view-manager.ts @@ -15,11 +15,11 @@ import { export class ViewManager extends GfxExtension { static override key = 'viewManager'; - private _disposable = new DisposableGroup(); + private readonly _disposable = new DisposableGroup(); - private _viewCtorMap = new Map(); + private readonly _viewCtorMap = new Map(); - private _viewMap = new Map(); + private readonly _viewMap = new Map(); constructor(gfx: GfxController) { super(gfx); diff --git a/blocksuite/framework/block-std/src/gfx/view/view.ts b/blocksuite/framework/block-std/src/gfx/view/view.ts index 8dac129765caf..63e89bc1e3b9c 100644 --- a/blocksuite/framework/block-std/src/gfx/view/view.ts +++ b/blocksuite/framework/block-std/src/gfx/view/view.ts @@ -40,7 +40,10 @@ export class GfxElementModelView< { static type: string; - private _handlers = new Map void)[]>(); + private readonly _handlers = new Map< + keyof EventsHandlerMap, + ((evt: any) => void)[] + >(); private _isConnected = true; diff --git a/blocksuite/framework/block-std/src/gfx/viewport-element.ts b/blocksuite/framework/block-std/src/gfx/viewport-element.ts index 438a2abe6be17..ec36a49969f83 100644 --- a/blocksuite/framework/block-std/src/gfx/viewport-element.ts +++ b/blocksuite/framework/block-std/src/gfx/viewport-element.ts @@ -46,7 +46,7 @@ export class GfxViewportElement extends WithDisposable(ShadowlessElement) { } `; - private _hideOutsideBlock = requestThrottledConnectedFrame(() => { + private readonly _hideOutsideBlock = requestThrottledConnectedFrame(() => { if (this.getModelsInViewport && this.host) { const host = this.host; const modelsInViewport = this.getModelsInViewport(); @@ -77,12 +77,12 @@ export class GfxViewportElement extends WithDisposable(ShadowlessElement) { private _lastVisibleModels?: Set; - private _pendingChildrenUpdates: { + private readonly _pendingChildrenUpdates: { id: string; resolve: () => void; }[] = []; - private _refreshViewport = requestThrottledConnectedFrame(() => { + private readonly _refreshViewport = requestThrottledConnectedFrame(() => { this._hideOutsideBlock(); }, this); diff --git a/blocksuite/framework/block-std/src/range/range-binding.ts b/blocksuite/framework/block-std/src/range/range-binding.ts index fd66f9ca492b4..d7c9d704da541 100644 --- a/blocksuite/framework/block-std/src/range/range-binding.ts +++ b/blocksuite/framework/block-std/src/range/range-binding.ts @@ -15,7 +15,7 @@ export class RangeBinding { | ((event: CompositionEvent) => Promise) | null = null; - private _computePath = (modelId: string) => { + private readonly _computePath = (modelId: string) => { const block = this.host.std.doc.getBlock(modelId)?.model; if (!block) return []; @@ -29,7 +29,7 @@ export class RangeBinding { return path; }; - private _onBeforeInput = (event: InputEvent) => { + private readonly _onBeforeInput = (event: InputEvent) => { const selection = this.selectionManager.find('text'); if (!selection) return; @@ -85,7 +85,7 @@ export class RangeBinding { this.selectionManager.setGroup('note', [newSelection]); }; - private _onCompositionEnd = (event: CompositionEvent) => { + private readonly _onCompositionEnd = (event: CompositionEvent) => { if (this._compositionStartCallback) { event.preventDefault(); event.stopPropagation(); @@ -94,7 +94,7 @@ export class RangeBinding { } }; - private _onCompositionStart = () => { + private readonly _onCompositionStart = () => { const selection = this.selectionManager.find('text'); if (!selection) return; @@ -166,7 +166,7 @@ export class RangeBinding { }; }; - private _onNativeSelectionChanged = async () => { + private readonly _onNativeSelectionChanged = async () => { if (this.isComposing) return; if (!this.host) return; // Unstable when switching views, card <-> embed @@ -246,7 +246,7 @@ export class RangeBinding { this.rangeManager?.syncRangeToTextSelection(range, isRangeReversed); }; - private _onStdSelectionChanged = (selections: BaseSelection[]) => { + private readonly _onStdSelectionChanged = (selections: BaseSelection[]) => { const text = selections.find((selection): selection is TextSelection => selection.is('text') diff --git a/blocksuite/framework/block-std/src/selection/manager.ts b/blocksuite/framework/block-std/src/selection/manager.ts index 8678510f02ddd..c499068a2f754 100644 --- a/blocksuite/framework/block-std/src/selection/manager.ts +++ b/blocksuite/framework/block-std/src/selection/manager.ts @@ -18,20 +18,20 @@ export interface SelectionConstructor { export class SelectionManager extends LifeCycleWatcher { static override readonly key = 'selectionManager'; - private _id: string; + private readonly _id: string; - private _itemAdded = (event: { stackItem: StackItem }) => { + private readonly _itemAdded = (event: { stackItem: StackItem }) => { event.stackItem.meta.set('selection-state', this.value); }; - private _itemPopped = (event: { stackItem: StackItem }) => { + private readonly _itemPopped = (event: { stackItem: StackItem }) => { const selection = event.stackItem.meta.get('selection-state'); if (selection) { this.set(selection as BaseSelection[]); } }; - private _jsonToSelection = (json: Record) => { + private readonly _jsonToSelection = (json: Record) => { const ctor = this._selectionConstructors[json.type as string]; if (!ctor) { throw new BlockSuiteError( @@ -42,11 +42,13 @@ export class SelectionManager extends LifeCycleWatcher { return ctor.fromJSON(json); }; - private _remoteSelections = signal>(new Map()); + private readonly _remoteSelections = signal>( + new Map() + ); private _selectionConstructors: Record = {}; - private _selections = signal([]); + private readonly _selections = signal([]); disposables = new DisposableGroup(); diff --git a/blocksuite/framework/block-std/src/view/element/block-component.ts b/blocksuite/framework/block-std/src/view/element/block-component.ts index 1c1c87e3b5984..e085356a868ce 100644 --- a/blocksuite/framework/block-std/src/view/element/block-component.ts +++ b/blocksuite/framework/block-std/src/view/element/block-component.ts @@ -35,7 +35,7 @@ export class BlockComponent< @consume({ context: stdContext }) accessor std!: BlockStdScope; - private _selected = computed(() => { + private readonly _selected = computed(() => { const selection = this.std.selection.value.find(selection => { return selection.blockId === this.model?.id; }); diff --git a/blocksuite/framework/block-std/src/view/element/gfx-block-component.ts b/blocksuite/framework/block-std/src/view/element/gfx-block-component.ts index 031f9696a7322..38b776292fceb 100644 --- a/blocksuite/framework/block-std/src/view/element/gfx-block-component.ts +++ b/blocksuite/framework/block-std/src/view/element/gfx-block-component.ts @@ -108,13 +108,13 @@ export abstract class GfxBlockComponent< const parent = this.parentElement; if (this.hasUpdated || !parent || !('scheduleUpdateChildren' in parent)) { - super.scheduleUpdate(); + return super.scheduleUpdate(); } else { await (parent.scheduleUpdateChildren as (id: string) => Promise)( this.model.id ); - super.scheduleUpdate(); + return super.scheduleUpdate(); } } @@ -207,13 +207,13 @@ export function toGfxBlockComponent< const parent = this.parentElement; if (this.hasUpdated || !parent || !('scheduleUpdateChildren' in parent)) { - super.scheduleUpdate(); + return super.scheduleUpdate(); } else { await (parent.scheduleUpdateChildren as (id: string) => Promise)( this.model.id ); - super.scheduleUpdate(); + return super.scheduleUpdate(); } } diff --git a/blocksuite/framework/block-std/src/view/element/lit-host.ts b/blocksuite/framework/block-std/src/view/element/lit-host.ts index 0cc55bd25b9e1..6f8203ef7b943 100644 --- a/blocksuite/framework/block-std/src/view/element/lit-host.ts +++ b/blocksuite/framework/block-std/src/view/element/lit-host.ts @@ -41,7 +41,7 @@ export class EditorHost extends SignalWatcher( } `; - private _renderModel = (model: BlockModel): TemplateResult => { + private readonly _renderModel = (model: BlockModel): TemplateResult => { const { flavour } = model; const block = this.doc.getBlock(model.id); if (!block || block.blockViewType === BlockViewType.Hidden) { diff --git a/blocksuite/framework/block-std/src/view/view-store.ts b/blocksuite/framework/block-std/src/view/view-store.ts index 7ea9a34b5311a..fccdab75bed83 100644 --- a/blocksuite/framework/block-std/src/view/view-store.ts +++ b/blocksuite/framework/block-std/src/view/view-store.ts @@ -6,7 +6,7 @@ export class ViewStore extends LifeCycleWatcher { private readonly _blockMap = new Map(); - private _fromId = ( + private readonly _fromId = ( blockId: string | undefined | null ): BlockComponent | null => { const id = blockId ?? this.std.doc.root?.id; diff --git a/blocksuite/framework/inline/src/inline-editor.ts b/blocksuite/framework/inline/src/inline-editor.ts index 5cbf3b29f1049..4155d49df6415 100644 --- a/blocksuite/framework/inline/src/inline-editor.ts +++ b/blocksuite/framework/inline/src/inline-editor.ts @@ -140,7 +140,7 @@ export class InlineEditor< return this._rootElement; } - private _inlineRangeProviderOverride = false; + private readonly _inlineRangeProviderOverride: boolean; get inlineRangeProviderOverride() { return this._inlineRangeProviderOverride; } @@ -220,6 +220,7 @@ export class InlineEditor< inlineRangeProvider, vLineRenderer = null, } = ops; + this._inlineRangeProviderOverride = false; this.yText = yText; this.isEmbed = isEmbed; this.vLineRenderer = vLineRenderer; diff --git a/blocksuite/framework/inline/src/services/event.ts b/blocksuite/framework/inline/src/services/event.ts index 966a5e19c77b4..732dca566273c 100644 --- a/blocksuite/framework/inline/src/services/event.ts +++ b/blocksuite/framework/inline/src/services/event.ts @@ -15,7 +15,7 @@ export class EventService { private _isComposing = false; - private _isRangeCompletelyInRoot = (range: Range) => { + private readonly _isRangeCompletelyInRoot = (range: Range) => { if (range.commonAncestorContainer.ownerDocument !== document) return false; const rootElement = this.editor.rootElement; @@ -38,7 +38,7 @@ export class EventService { } }; - private _onBeforeInput = (event: InputEvent) => { + private readonly _onBeforeInput = (event: InputEvent) => { const range = this.editor.rangeService.getNativeRange(); if ( this.editor.isReadonly || @@ -119,7 +119,7 @@ export class EventService { this.editor.slots.inputting.emit(); }; - private _onClick = (event: MouseEvent) => { + private readonly _onClick = (event: MouseEvent) => { // select embed element when click on it if (event.target instanceof Node && isInEmbedElement(event.target)) { const selection = document.getSelection(); @@ -138,7 +138,7 @@ export class EventService { } }; - private _onCompositionEnd = async (event: CompositionEvent) => { + private readonly _onCompositionEnd = async (event: CompositionEvent) => { this._isComposing = false; if (!this.editor.rootElement.isConnected) return; @@ -179,7 +179,7 @@ export class EventService { this.editor.slots.inputting.emit(); }; - private _onCompositionStart = () => { + private readonly _onCompositionStart = () => { this._isComposing = true; // embeds is not editable and it will break IME const embeds = this.editor.rootElement.querySelectorAll( @@ -197,7 +197,7 @@ export class EventService { } }; - private _onCompositionUpdate = () => { + private readonly _onCompositionUpdate = () => { if (!this.editor.rootElement.isConnected) return; const range = this.editor.rangeService.getNativeRange(); @@ -211,7 +211,7 @@ export class EventService { this.editor.slots.inputting.emit(); }; - private _onKeyDown = (event: KeyboardEvent) => { + private readonly _onKeyDown = (event: KeyboardEvent) => { const inlineRange = this.editor.getInlineRange(); if (!inlineRange) return; @@ -270,7 +270,7 @@ export class EventService { } }; - private _onSelectionChange = () => { + private readonly _onSelectionChange = () => { const rootElement = this.editor.rootElement; const previousInlineRange = this.editor.getInlineRange(); if (this._isComposing) { diff --git a/blocksuite/framework/inline/src/services/render.ts b/blocksuite/framework/inline/src/services/render.ts index 5ae4600117aab..60dda4c05188d 100644 --- a/blocksuite/framework/inline/src/services/render.ts +++ b/blocksuite/framework/inline/src/services/render.ts @@ -11,7 +11,10 @@ import type { BaseTextAttributes } from '../utils/base-attributes.js'; import { deltaInsertsToChunks } from '../utils/delta-convert.js'; export class RenderService { - private _onYTextChange = (_: Y.YTextEvent, transaction: Y.Transaction) => { + private readonly _onYTextChange = ( + _: Y.YTextEvent, + transaction: Y.Transaction + ) => { this.editor.slots.textChange.emit(); const yText = this.editor.yText; diff --git a/blocksuite/framework/store/src/adapter/base.ts b/blocksuite/framework/store/src/adapter/base.ts index a84e0f5a2b5ef..9cf8b779902ef 100644 --- a/blocksuite/framework/store/src/adapter/base.ts +++ b/blocksuite/framework/store/src/adapter/base.ts @@ -218,7 +218,7 @@ export class ASTWalker { private _leave: WalkerFn | undefined; - private _visit = async (o: NodeProps) => { + private readonly _visit = async (o: NodeProps) => { if (!o.node) return; this.context._skipChildrenNum = 0; this.context._skip = false; @@ -277,7 +277,7 @@ export class ASTWalker { } }; - private context: ASTWalkerContext; + private readonly context: ASTWalkerContext; setEnter = (fn: WalkerFn) => { this._enter = fn; diff --git a/blocksuite/framework/store/src/adapter/context.ts b/blocksuite/framework/store/src/adapter/context.ts index d351ab202dd51..9a484e7b4af1d 100644 --- a/blocksuite/framework/store/src/adapter/context.ts +++ b/blocksuite/framework/store/src/adapter/context.ts @@ -5,7 +5,7 @@ export class ASTWalkerContext { private _globalContext: Record = Object.create(null); - private _stack: { + private readonly _stack: { node: TNode; prop: Keyof; context: Record; diff --git a/blocksuite/framework/store/src/reactive/proxy.ts b/blocksuite/framework/store/src/reactive/proxy.ts index 26877d77eaea0..3bc9d6c637f4f 100644 --- a/blocksuite/framework/store/src/reactive/proxy.ts +++ b/blocksuite/framework/store/src/reactive/proxy.ts @@ -17,7 +17,7 @@ export class ReactiveYArray extends BaseReactiveYData< unknown[], YArray > { - private _observer = (event: YArrayEvent) => { + private readonly _observer = (event: YArrayEvent) => { this._onObserve(event, () => { let retain = 0; event.changes.delta.forEach(change => { @@ -159,7 +159,7 @@ export class ReactiveYArray extends BaseReactiveYData< } export class ReactiveYMap extends BaseReactiveYData> { - private _observer = (event: YMapEvent) => { + private readonly _observer = (event: YMapEvent) => { this._onObserve(event, () => { event.keysChanged.forEach(key => { const type = event.changes.keys.get(key); diff --git a/blocksuite/framework/store/src/reactive/text.ts b/blocksuite/framework/store/src/reactive/text.ts index 9fb8af5105cc9..758161f34e405 100644 --- a/blocksuite/framework/store/src/reactive/text.ts +++ b/blocksuite/framework/store/src/reactive/text.ts @@ -16,9 +16,9 @@ export type DeltaOperation = { export type OnTextChange = (data: Y.Text) => void; export class Text { - private _deltas$: Signal; + private readonly _deltas$: Signal; - private _length$: Signal; + private readonly _length$: Signal; private _onChange?: OnTextChange; diff --git a/blocksuite/framework/store/src/schema/base.ts b/blocksuite/framework/store/src/schema/base.ts index a22204f9df0e5..6059dc38483d9 100644 --- a/blocksuite/framework/store/src/schema/base.ts +++ b/blocksuite/framework/store/src/schema/base.ts @@ -155,14 +155,14 @@ export class BlockModel< Props extends object = object, PropsSignal extends object = SignaledProps, > extends MagicProps() { - private _children = signal([]); + private readonly _children = signal([]); /** * @deprecated use doc instead */ page!: Doc; - private _childModels = computed(() => { + private readonly _childModels = computed(() => { const value: BlockModel[] = []; this._children.value.forEach(id => { const block = this.page.getBlock$(id); @@ -173,9 +173,9 @@ export class BlockModel< return value; }); - private _onCreated: Disposable; + private readonly _onCreated: Disposable; - private _onDeleted: Disposable; + private readonly _onDeleted: Disposable; childMap = computed(() => this._children.value.reduce((map, id, index) => { diff --git a/blocksuite/framework/store/src/store/doc/block-collection.ts b/blocksuite/framework/store/src/store/doc/block-collection.ts index 8c9f2110422f3..4893c95af2a8c 100644 --- a/blocksuite/framework/store/src/store/doc/block-collection.ts +++ b/blocksuite/framework/store/src/store/doc/block-collection.ts @@ -47,27 +47,27 @@ export class BlockCollection { private readonly _docCRUD: DocCRUD; - private _docMap = { + private readonly _docMap = { undefined: new Map(), true: new Map(), false: new Map(), }; // doc/space container. - private _handleYEvents = (events: Y.YEvent[]) => { + private readonly _handleYEvents = (events: Y.YEvent[]) => { events.forEach(event => this._handleYEvent(event)); }; private _history!: Y.UndoManager; - private _historyObserver = () => { + private readonly _historyObserver = () => { this._updateCanUndoRedoSignals(); this.slots.historyUpdated.emit(); }; private readonly _idGenerator: IdGenerator; - private _initSubDoc = () => { + private readonly _initSubDoc = () => { let subDoc = this.rootDoc.spaces.get(this.id); if (!subDoc) { subDoc = new Y.Doc({ @@ -86,9 +86,13 @@ export class BlockCollection { private _loaded!: boolean; - private _onLoadSlot = new Slot(); + private readonly _onLoadSlot = new Slot(); - private _onSubdocEvent = ({ loaded }: { loaded: Set }): void => { + private readonly _onSubdocEvent = ({ + loaded, + }: { + loaded: Set; + }): void => { const result = Array.from(loaded).find( doc => doc.guid === this._ySpaceDoc.guid ); @@ -105,7 +109,7 @@ export class BlockCollection { private _shouldTransact = true; - private _updateCanUndoRedoSignals = () => { + private readonly _updateCanUndoRedoSignals = () => { const canRedo = this.readonly ? false : this._history.canRedo(); const canUndo = this.readonly ? false : this._history.canUndo(); if (this._canRedo$.peek() !== canRedo) { diff --git a/blocksuite/framework/store/src/store/doc/block/index.ts b/blocksuite/framework/store/src/store/doc/block/index.ts index 82126de1a1afe..a58e36320521f 100644 --- a/blocksuite/framework/store/src/store/doc/block/index.ts +++ b/blocksuite/framework/store/src/store/doc/block/index.ts @@ -7,7 +7,7 @@ import type { BlockOptions, YBlock } from './types.js'; export * from './types.js'; export class Block { - private _syncController: SyncController; + private readonly _syncController: SyncController; blockViewType: BlockViewType = BlockViewType.Display; diff --git a/blocksuite/framework/store/src/store/doc/doc.ts b/blocksuite/framework/store/src/store/doc/doc.ts index 8502b319d6e2b..5232ac37389fc 100644 --- a/blocksuite/framework/store/src/store/doc/doc.ts +++ b/blocksuite/framework/store/src/store/doc/doc.ts @@ -20,7 +20,7 @@ type DocOptions = { }; export class Doc { - private _runQuery = (block: Block) => { + private readonly _runQuery = (block: Block) => { runQuery(this._query, block); }; diff --git a/blocksuite/framework/store/src/store/meta.ts b/blocksuite/framework/store/src/store/meta.ts index 930ceea8f0f5d..2fbb2980e4f7a 100644 --- a/blocksuite/framework/store/src/store/meta.ts +++ b/blocksuite/framework/store/src/store/meta.ts @@ -36,7 +36,7 @@ export type DocCollectionMetaState = { }; export class DocCollectionMeta { - private _handleDocCollectionMetaEvents = ( + private readonly _handleDocCollectionMetaEvents = ( events: Y.YEvent | Y.Text | Y.Map>[] ) => { events.forEach(e => { diff --git a/blocksuite/framework/store/src/yjs/awareness.ts b/blocksuite/framework/store/src/yjs/awareness.ts index ad0268fa15f96..59662e2e43060 100644 --- a/blocksuite/framework/store/src/yjs/awareness.ts +++ b/blocksuite/framework/store/src/yjs/awareness.ts @@ -32,9 +32,9 @@ export interface AwarenessEvent< } export class AwarenessStore { - private _flags: Signal; + private readonly _flags: Signal; - private _onAwarenessChange = (diff: { + private readonly _onAwarenessChange = (diff: { added: number[]; removed: number[]; updated: number[]; diff --git a/blocksuite/framework/store/src/yjs/doc.ts b/blocksuite/framework/store/src/yjs/doc.ts index 66598cece241e..9abb9731b56d2 100644 --- a/blocksuite/framework/store/src/yjs/doc.ts +++ b/blocksuite/framework/store/src/yjs/doc.ts @@ -10,7 +10,7 @@ export type BlockSuiteDocAllowedValue = export type BlockSuiteDocData = Record; export class BlockSuiteDoc extends Y.Doc { - private _spaces: Y.Map = this.getMap('spaces'); + private readonly _spaces: Y.Map = this.getMap('spaces'); get spaces() { return this._spaces; diff --git a/blocksuite/framework/sync/src/doc/impl/broadcast.ts b/blocksuite/framework/sync/src/doc/impl/broadcast.ts index 7e48814102994..96b3246cb007e 100644 --- a/blocksuite/framework/sync/src/doc/impl/broadcast.ts +++ b/blocksuite/framework/sync/src/doc/impl/broadcast.ts @@ -14,7 +14,7 @@ type ChannelMessage = }; export class BroadcastChannelDocSource implements DocSource { - private _onMessage = (event: MessageEvent) => { + private readonly _onMessage = (event: MessageEvent) => { if (event.data.type === 'init') { for (const [docId, data] of this.docMap) { this.channel.postMessage({ diff --git a/blocksuite/playground/apps/_common/components/attachment-viewer-panel.ts b/blocksuite/playground/apps/_common/components/attachment-viewer-panel.ts index d9ea46ac86fb1..30c941ffde295 100644 --- a/blocksuite/playground/apps/_common/components/attachment-viewer-panel.ts +++ b/blocksuite/playground/apps/_common/components/attachment-viewer-panel.ts @@ -112,13 +112,13 @@ export class AttachmentViewerPanel extends SignalWatcher( } `; - #cursor = signal(0); + readonly #cursor = signal(0); - #docInfo = signal(null); + readonly #docInfo = signal(null); - #fileInfo = signal(null); + readonly #fileInfo = signal(null); - #state = signal(State.Connecting); + readonly #state = signal(State.Connecting); #worker: Worker | null = null; diff --git a/blocksuite/playground/apps/_common/components/collab-debug-menu.ts b/blocksuite/playground/apps/_common/components/collab-debug-menu.ts index 5dae352157370..8ca89f09ff314 100644 --- a/blocksuite/playground/apps/_common/components/collab-debug-menu.ts +++ b/blocksuite/playground/apps/_common/components/collab-debug-menu.ts @@ -65,21 +65,21 @@ export class CollabDebugMenu extends SignalWatcher(ShadowlessElement) { } `; - private _darkModeChange = (e: MediaQueryListEvent) => { + private readonly _darkModeChange = (e: MediaQueryListEvent) => { this._setThemeMode(!!e.matches); }; - private _handleDocsPanelClose = () => { + private readonly _handleDocsPanelClose = () => { this.leftSidePanel.toggle(this.docsPanel); }; - private _keydown = (e: KeyboardEvent) => { + private readonly _keydown = (e: KeyboardEvent) => { if (e.key === 'F1') { this._switchEditorMode(); } }; - private _startCollaboration = async () => { + private readonly _startCollaboration = async () => { if (window.wsProvider) { notify('There is already a websocket provider exists', 'neutral').catch( console.error diff --git a/blocksuite/playground/apps/_common/components/starter-debug-menu.ts b/blocksuite/playground/apps/_common/components/starter-debug-menu.ts index 78c220fdf0c6b..b15130d8c2770 100644 --- a/blocksuite/playground/apps/_common/components/starter-debug-menu.ts +++ b/blocksuite/playground/apps/_common/components/starter-debug-menu.ts @@ -188,11 +188,11 @@ export class StarterDebugMenu extends ShadowlessElement { } `; - private _darkModeChange = (e: MediaQueryListEvent) => { + private readonly _darkModeChange = (e: MediaQueryListEvent) => { this._setThemeMode(!!e.matches); }; - private _handleDocsPanelClose = () => { + private readonly _handleDocsPanelClose = () => { this.leftSidePanel.toggle(this.docsPanel); }; diff --git a/blocksuite/playground/apps/_common/sync/websocket/awareness.ts b/blocksuite/playground/apps/_common/sync/websocket/awareness.ts index 1229da661cb24..0f9a631483975 100644 --- a/blocksuite/playground/apps/_common/sync/websocket/awareness.ts +++ b/blocksuite/playground/apps/_common/sync/websocket/awareness.ts @@ -11,7 +11,10 @@ import type { WebSocketMessage } from './types'; type AwarenessChanges = Record<'added' | 'updated' | 'removed', number[]>; export class WebSocketAwarenessSource implements AwarenessSource { - private _onAwareness = (changes: AwarenessChanges, origin: unknown) => { + private readonly _onAwareness = ( + changes: AwarenessChanges, + origin: unknown + ) => { if (origin === 'remote') return; const changedClients = Object.values(changes).reduce((res, cur) => @@ -31,7 +34,7 @@ export class WebSocketAwarenessSource implements AwarenessSource { ); }; - private _onWebSocket = (event: MessageEvent) => { + private readonly _onWebSocket = (event: MessageEvent) => { const data = JSON.parse(event.data) as WebSocketMessage; if (data.channel !== 'awareness') return; diff --git a/blocksuite/playground/apps/_common/sync/websocket/doc.ts b/blocksuite/playground/apps/_common/sync/websocket/doc.ts index a7fdd73f77805..37c1094cafdd4 100644 --- a/blocksuite/playground/apps/_common/sync/websocket/doc.ts +++ b/blocksuite/playground/apps/_common/sync/websocket/doc.ts @@ -5,7 +5,7 @@ import { diffUpdate, encodeStateVectorFromUpdate, mergeUpdates } from 'yjs'; import type { WebSocketMessage } from './types'; export class WebSocketDocSource implements DocSource { - private _onMessage = (event: MessageEvent) => { + private readonly _onMessage = (event: MessageEvent) => { const data = JSON.parse(event.data) as WebSocketMessage; if (data.channel !== 'doc') return; diff --git a/blocksuite/playground/scripts/hmr-plugin/fine-tune.ts b/blocksuite/playground/scripts/hmr-plugin/fine-tune.ts index dfa6bc8599bee..b547c93fde7d9 100644 --- a/blocksuite/playground/scripts/hmr-plugin/fine-tune.ts +++ b/blocksuite/playground/scripts/hmr-plugin/fine-tune.ts @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ +/* eslint-disable import-x/no-extraneous-dependencies */ import path from 'node:path'; import { init, parse } from 'es-module-lexer'; diff --git a/blocksuite/presets/src/editors/editor-container.ts b/blocksuite/presets/src/editors/editor-container.ts index 68cc89a41575d..e3fc77f4fe3a2 100644 --- a/blocksuite/presets/src/editors/editor-container.ts +++ b/blocksuite/presets/src/editors/editor-container.ts @@ -88,28 +88,30 @@ export class AffineEditorContainer } `; - private _doc = signal(); + private readonly _doc = signal(); - private _edgelessSpecs = signal(EdgelessEditorBlockSpecs); + private readonly _edgelessSpecs = signal( + EdgelessEditorBlockSpecs + ); - private _mode = signal('page'); + private readonly _mode = signal('page'); - private _pageSpecs = signal(PageEditorBlockSpecs); + private readonly _pageSpecs = signal(PageEditorBlockSpecs); - private _specs = computed(() => + private readonly _specs = computed(() => this._mode.value === 'page' ? this._pageSpecs.value : this._edgelessSpecs.value ); - private _std = computed(() => { + private readonly _std = computed(() => { return new BlockStdScope({ doc: this.doc, extensions: this._specs.value, }); }); - private _editorTemplate = computed(() => { + private readonly _editorTemplate = computed(() => { return this._std.value.render(); }); diff --git a/blocksuite/presets/src/fragments/comment/comment-input.ts b/blocksuite/presets/src/fragments/comment/comment-input.ts index e6975aaecce9d..65073ee81fc78 100644 --- a/blocksuite/presets/src/fragments/comment/comment-input.ts +++ b/blocksuite/presets/src/fragments/comment/comment-input.ts @@ -41,11 +41,11 @@ export class CommentInput extends WithDisposable(ShadowlessElement) { } `; - private _cancel = () => { + private readonly _cancel = () => { this.remove(); }; - private _submit = (textSelection: TextSelection) => { + private readonly _submit = (textSelection: TextSelection) => { const deltas = this._editor.inlineEditor?.yTextDeltas; if (!deltas) { this.remove(); diff --git a/blocksuite/presets/src/fragments/doc-meta-tags/backlink-popover.ts b/blocksuite/presets/src/fragments/doc-meta-tags/backlink-popover.ts index b79388c7b4e3b..3fdf7885c496f 100644 --- a/blocksuite/presets/src/fragments/doc-meta-tags/backlink-popover.ts +++ b/blocksuite/presets/src/fragments/doc-meta-tags/backlink-popover.ts @@ -79,10 +79,10 @@ export class BacklinkButton extends WithDisposable(LitElement) { ${scrollbarStyle('.backlink-popover .group')} `; - private _backlinks: BacklinkData[]; + private readonly _backlinks: BacklinkData[]; // Handle click outside - private _onClickAway = (e: Event) => { + private readonly _onClickAway = (e: Event) => { if (e.target === this) return; if (!this._showPopover) return; this._showPopover = false; diff --git a/blocksuite/presets/src/fragments/doc-title/doc-title.ts b/blocksuite/presets/src/fragments/doc-title/doc-title.ts index a404403a880fd..88b9aa1f28745 100644 --- a/blocksuite/presets/src/fragments/doc-title/doc-title.ts +++ b/blocksuite/presets/src/fragments/doc-title/doc-title.ts @@ -59,7 +59,7 @@ export class DocTitle extends WithDisposable(ShadowlessElement) { } `; - private _onTitleKeyDown = (event: KeyboardEvent) => { + private readonly _onTitleKeyDown = (event: KeyboardEvent) => { if (event.isComposing || this.doc.readonly) return; const hasContent = !this.doc.isEmpty; @@ -83,7 +83,7 @@ export class DocTitle extends WithDisposable(ShadowlessElement) { } }; - private _updateTitleInMeta = () => { + private readonly _updateTitleInMeta = () => { this.doc.collection.setDocMeta(this.doc.id, { title: this._rootModel.title.toString(), }); diff --git a/blocksuite/presets/src/fragments/frame-panel/body/frame-panel-body.ts b/blocksuite/presets/src/fragments/frame-panel/body/frame-panel-body.ts index cd90e8ec43226..92595a2cd1a34 100644 --- a/blocksuite/presets/src/fragments/frame-panel/body/frame-panel-body.ts +++ b/blocksuite/presets/src/fragments/frame-panel/body/frame-panel-body.ts @@ -91,7 +91,7 @@ export class FramePanelBody extends SignalWatcher( ) { static override styles = styles; - private _clearDocDisposables = () => { + private readonly _clearDocDisposables = () => { this._docDisposables?.dispose(); this._docDisposables = null; }; @@ -99,7 +99,7 @@ export class FramePanelBody extends SignalWatcher( /** * click at blank area to clear selection */ - private _clickBlank = (e: MouseEvent) => { + private readonly _clickBlank = (e: MouseEvent) => { e.stopPropagation(); // check if click at frame-card, if not, set this._selected to empty if ( @@ -126,7 +126,7 @@ export class FramePanelBody extends SignalWatcher( private _lastEdgelessRootId = ''; - private _selectFrame = (e: SelectEvent) => { + private readonly _selectFrame = (e: SelectEvent) => { const { selected, id, multiselect } = e.detail; if (!selected) { @@ -144,7 +144,7 @@ export class FramePanelBody extends SignalWatcher( }); }; - private _updateFrameItems = () => { + private readonly _updateFrameItems = () => { this._frameItems = this.frames.map((frame, idx) => ({ frame, frameIndex: frame.presentationIndex ?? frame.index, diff --git a/blocksuite/presets/src/fragments/frame-panel/card/frame-card-title-editor.ts b/blocksuite/presets/src/fragments/frame-panel/card/frame-card-title-editor.ts index 7974857cb6fa6..4a95ce714488b 100644 --- a/blocksuite/presets/src/fragments/frame-panel/card/frame-card-title-editor.ts +++ b/blocksuite/presets/src/fragments/frame-panel/card/frame-card-title-editor.ts @@ -16,7 +16,7 @@ export const AFFINE_FRAME_TITLE_EDITOR = 'affine-frame-card-title-editor'; export class FrameCardTitleEditor extends WithDisposable(ShadowlessElement) { static override styles = styles; - private _isComposing = false; + private readonly _isComposing = false; get inlineEditor() { return this.richText.inlineEditor; diff --git a/blocksuite/presets/src/fragments/frame-panel/card/frame-card-title.ts b/blocksuite/presets/src/fragments/frame-panel/card/frame-card-title.ts index de93d03f83411..bdf619fd6410a 100644 --- a/blocksuite/presets/src/fragments/frame-panel/card/frame-card-title.ts +++ b/blocksuite/presets/src/fragments/frame-panel/card/frame-card-title.ts @@ -59,12 +59,12 @@ export const AFFINE_FRAME_CARD_TITLE = 'affine-frame-card-title'; export class FrameCardTitle extends WithDisposable(ShadowlessElement) { static override styles = styles; - private _clearTitleDisposables = () => { + private readonly _clearTitleDisposables = () => { this._titleDisposables?.dispose(); this._titleDisposables = null; }; - private _mountTitleEditor = (e: MouseEvent) => { + private readonly _mountTitleEditor = (e: MouseEvent) => { e.stopPropagation(); const titleEditor = new FrameCardTitleEditor(); @@ -78,7 +78,7 @@ export class FrameCardTitle extends WithDisposable(ShadowlessElement) { private _titleDisposables: DisposableGroup | null = null; - private _updateElement = () => { + private readonly _updateElement = () => { this.requestUpdate(); }; diff --git a/blocksuite/presets/src/fragments/frame-panel/header/frame-panel-header.ts b/blocksuite/presets/src/fragments/frame-panel/header/frame-panel-header.ts index a74881b293fa9..43be03979cc0f 100644 --- a/blocksuite/presets/src/fragments/frame-panel/header/frame-panel-header.ts +++ b/blocksuite/presets/src/fragments/frame-panel/header/frame-panel-header.ts @@ -105,14 +105,14 @@ export const AFFINE_FRAME_PANEL_HEADER = 'affine-frame-panel-header'; export class FramePanelHeader extends WithDisposable(LitElement) { static override styles = styles; - private _clearEdgelessDisposables = () => { + private readonly _clearEdgelessDisposables = () => { this._edgelessDisposables?.dispose(); this._edgelessDisposables = null; }; private _edgelessDisposables: DisposableGroup | null = null; - private _enterPresentationMode = () => { + private readonly _enterPresentationMode = () => { if (!this._edgelessRootService) { this.editorHost.std.get(DocModeProvider).setEditorMode('edgeless'); } @@ -131,7 +131,7 @@ export class FramePanelHeader extends WithDisposable(LitElement) { private _navigatorMode: NavigatorMode = 'fit'; - private _setEdgelessDisposables = () => { + private readonly _setEdgelessDisposables = () => { if (!this._edgelessRootService) return; this._clearEdgelessDisposables(); diff --git a/blocksuite/presets/src/fragments/frame-panel/header/frames-setting-menu.ts b/blocksuite/presets/src/fragments/frame-panel/header/frames-setting-menu.ts index ba6d0ff734e91..4482a11120af3 100644 --- a/blocksuite/presets/src/fragments/frame-panel/header/frames-setting-menu.ts +++ b/blocksuite/presets/src/fragments/frame-panel/header/frames-setting-menu.ts @@ -72,14 +72,14 @@ export const AFFINE_FRAMES_SETTING_MENU = 'affine-frames-setting-menu'; export class FramesSettingMenu extends WithDisposable(LitElement) { static override styles = styles; - private _onBlackBackgroundChange = (checked: boolean) => { + private readonly _onBlackBackgroundChange = (checked: boolean) => { this.blackBackground = checked; this._edgelessRootService?.slots.navigatorSettingUpdated.emit({ blackBackground: this.blackBackground, }); }; - private _onFillScreenChange = (checked: boolean) => { + private readonly _onFillScreenChange = (checked: boolean) => { this.fillScreen = checked; this._edgelessRootService?.slots.navigatorSettingUpdated.emit({ fillScreen: this.fillScreen, @@ -87,7 +87,7 @@ export class FramesSettingMenu extends WithDisposable(LitElement) { this._editPropsStore.setStorage('presentFillScreen', this.fillScreen); }; - private _onHideToolBarChange = (checked: boolean) => { + private readonly _onHideToolBarChange = (checked: boolean) => { this.hideToolbar = checked; this._edgelessRootService?.slots.navigatorSettingUpdated.emit({ hideToolbar: this.hideToolbar, diff --git a/blocksuite/presets/src/fragments/outline/body/outline-panel-body.ts b/blocksuite/presets/src/fragments/outline/body/outline-panel-body.ts index d77ba98892942..c04c62bad14a7 100644 --- a/blocksuite/presets/src/fragments/outline/body/outline-panel-body.ts +++ b/blocksuite/presets/src/fragments/outline/body/outline-panel-body.ts @@ -120,7 +120,7 @@ export class OutlinePanelBody extends SignalWatcher( ) { static override styles = styles; - private _activeHeadingId$ = signal(null); + private readonly _activeHeadingId$ = signal(null); private _changedFlag = false; diff --git a/blocksuite/presets/src/fragments/outline/outline-panel.ts b/blocksuite/presets/src/fragments/outline/outline-panel.ts index ce096f2befe87..122ffde3b28fe 100644 --- a/blocksuite/presets/src/fragments/outline/outline-panel.ts +++ b/blocksuite/presets/src/fragments/outline/outline-panel.ts @@ -55,7 +55,7 @@ export const AFFINE_OUTLINE_PANEL = 'affine-outline-panel'; export class OutlinePanel extends SignalWatcher(WithDisposable(LitElement)) { static override styles = styles; - private _setNoticeVisibility = (visibility: boolean) => { + private readonly _setNoticeVisibility = (visibility: boolean) => { this._noticeVisible = visibility; }; @@ -64,12 +64,12 @@ export class OutlinePanel extends SignalWatcher(WithDisposable(LitElement)) { enableSorting: false, }; - private _toggleNotesSorting = () => { + private readonly _toggleNotesSorting = () => { this._enableNotesSorting = !this._enableNotesSorting; this._updateAndSaveSettings({ enableSorting: this._enableNotesSorting }); }; - private _toggleShowPreviewIcon = (on: boolean) => { + private readonly _toggleShowPreviewIcon = (on: boolean) => { this._showPreviewIcon = on; this._updateAndSaveSettings({ showIcons: on }); }; diff --git a/blocksuite/presets/src/fragments/outline/outline-viewer.ts b/blocksuite/presets/src/fragments/outline/outline-viewer.ts index 93296c2a1a1e5..e7aad26c0f168 100644 --- a/blocksuite/presets/src/fragments/outline/outline-viewer.ts +++ b/blocksuite/presets/src/fragments/outline/outline-viewer.ts @@ -140,13 +140,13 @@ export class OutlineViewer extends SignalWatcher(WithDisposable(LitElement)) { } `; - private _activeHeadingId$ = signal(null); + private readonly _activeHeadingId$ = signal(null); private _highlightMaskDisposable = () => {}; private _lockActiveHeadingId = false; - private _scrollPanel = () => { + private readonly _scrollPanel = () => { this._activeItem?.scrollIntoView({ behavior: 'instant', block: 'center', diff --git a/eslint.config.mjs b/eslint.config.mjs index 04b636602900c..78fa2593bc24c 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -216,7 +216,11 @@ export default tseslint.config( }, }, { - files: ['packages/**/*.{ts,tsx}', 'tools/cli/**/*.{ts,tsx}'], + files: [ + 'packages/**/*.{ts,tsx}', + 'tools/cli/**/*.{ts,tsx}', + 'blocksuite/**/*.{ts,tsx}', + ], rules: { '@typescript-eslint/no-floating-promises': [ 'error', @@ -283,5 +287,20 @@ export default tseslint.config( 'import-x/no-extraneous-dependencies': 'off', }, }, + { + files: ['blocksuite/**/*.{ts,tsx}'], + rules: { + 'rxjs/finnish': 'off', + }, + }, + { + files: [ + 'blocksuite/tests-legacy/**/*.{ts,tsx}', + 'blocksuite/**/__tests__/**/*.{ts,tsx}', + ], + rules: { + 'import-x/no-extraneous-dependencies': 'off', + }, + }, eslintConfigPrettier ); From ea6a05632b12d9972295575a99252e6fbaf74c79 Mon Sep 17 00:00:00 2001 From: Saul-Mirone Date: Sat, 21 Dec 2024 03:37:20 +0000 Subject: [PATCH 06/17] ci: shard vitest to speed up unit test (#9235) --- .github/workflows/build-test.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 54835030a3bd3..4fb05573fef45 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -209,6 +209,10 @@ jobs: - build-native env: DISTRIBUTION: web + strategy: + fail-fast: false + matrix: + shard: [1, 2, 3, 4, 5] steps: - uses: actions/checkout@v4 - name: Setup Node.js @@ -225,7 +229,7 @@ jobs: path: ./packages/frontend/native - name: Unit Test - run: yarn test:coverage + run: yarn test:coverage --shard=${{ matrix.shard }}/${{ strategy.job-total }} - name: Upload unit test coverage results uses: codecov/codecov-action@v5 From ec5bbb442f826cb038a60766f133098614d813b0 Mon Sep 17 00:00:00 2001 From: Mirone Date: Mon, 23 Dec 2024 11:05:36 +0800 Subject: [PATCH 07/17] ci: add code owners for blocksuite (#9234) --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e69de29bb2d1d..cdf951bee370d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +/blocksuite/ @toeverything/blocksuite-core From cddef4c2f6844a751c260547e19c2e631423e111 Mon Sep 17 00:00:00 2001 From: EYHN Date: Mon, 23 Dec 2024 03:06:31 +0000 Subject: [PATCH 08/17] fix(admin): fix the admin app (#9233) --- packages/frontend/admin/package.json | 3 +- packages/frontend/admin/src/app.tsx | 57 ++------ .../components/data-table-toolbar.tsx | 2 +- .../components/use-user-management.ts | 6 +- .../src/modules/accounts/use-user-list.ts | 2 +- .../admin/src/modules/ai/use-prompt.ts | 6 +- packages/frontend/admin/src/modules/common.ts | 5 +- .../config/use-server-service-configs.ts | 2 +- .../settings/use-get-server-runtime-config.ts | 2 +- .../use-update-server-runtime-config.ts | 6 +- packages/frontend/admin/src/use-mutation.ts | 94 +++++++++++++ packages/frontend/admin/src/use-query.ts | 131 ++++++++++++++++++ 12 files changed, 253 insertions(+), 63 deletions(-) create mode 100644 packages/frontend/admin/src/use-mutation.ts create mode 100644 packages/frontend/admin/src/use-query.ts diff --git a/packages/frontend/admin/package.json b/packages/frontend/admin/package.json index 9f969a735ef71..c3c97b72cf6c2 100644 --- a/packages/frontend/admin/package.json +++ b/packages/frontend/admin/package.json @@ -66,7 +66,6 @@ "update-shadcn": "shadcn-ui add -p src/components/ui" }, "exports": { - "./utils": "./src/utils.ts", - "./components/ui/*": "./src/components/ui/*.tsx" + "./*": "./src/*" } } diff --git a/packages/frontend/admin/src/app.tsx b/packages/frontend/admin/src/app.tsx index c17f25fe0958e..199452062ed25 100644 --- a/packages/frontend/admin/src/app.tsx +++ b/packages/frontend/admin/src/app.tsx @@ -1,20 +1,5 @@ import { Toaster } from '@affine/admin/components/ui/sonner'; -import { - configureCloudModule, - DefaultServerService, -} from '@affine/core/modules/cloud'; -import { configureLocalStorageStateStorageImpls } from '@affine/core/modules/storage'; -import { configureUrlModule } from '@affine/core/modules/url'; import { wrapCreateBrowserRouter } from '@sentry/react'; -import { - configureGlobalContextModule, - configureGlobalStorageModule, - configureLifecycleModule, - Framework, - FrameworkRoot, - FrameworkScope, - LifecycleService, -} from '@toeverything/infra'; import { useEffect } from 'react'; import { createBrowserRouter as reactRouterCreateBrowserRouter, @@ -124,38 +109,18 @@ export const router = _createBrowserRouter( } ); -const framework = new Framework(); -configureLifecycleModule(framework); -configureLocalStorageStateStorageImpls(framework); -configureGlobalStorageModule(framework); -configureGlobalContextModule(framework); -configureUrlModule(framework); -configureCloudModule(framework); -const frameworkProvider = framework.provider(); - -// setup application lifecycle events, and emit application start event -window.addEventListener('focus', () => { - frameworkProvider.get(LifecycleService).applicationFocus(); -}); -frameworkProvider.get(LifecycleService).applicationStart(); -const serverService = frameworkProvider.get(DefaultServerService); - export const App = () => { return ( - - - - - - - - - - + + + + + + ); }; diff --git a/packages/frontend/admin/src/modules/accounts/components/data-table-toolbar.tsx b/packages/frontend/admin/src/modules/accounts/components/data-table-toolbar.tsx index 0a9fb406f52e9..270bd82e02f3d 100644 --- a/packages/frontend/admin/src/modules/accounts/components/data-table-toolbar.tsx +++ b/packages/frontend/admin/src/modules/accounts/components/data-table-toolbar.tsx @@ -1,6 +1,6 @@ import { Button } from '@affine/admin/components/ui/button'; import { Input } from '@affine/admin/components/ui/input'; -import { useQuery } from '@affine/core/components/hooks/use-query'; +import { useQuery } from '@affine/admin/use-query'; import { getUserByEmailQuery } from '@affine/graphql'; import { PlusIcon } from 'lucide-react'; import type { SetStateAction } from 'react'; diff --git a/packages/frontend/admin/src/modules/accounts/components/use-user-management.ts b/packages/frontend/admin/src/modules/accounts/components/use-user-management.ts index f062ecf4c4331..94ee0bcca5c50 100644 --- a/packages/frontend/admin/src/modules/accounts/components/use-user-management.ts +++ b/packages/frontend/admin/src/modules/accounts/components/use-user-management.ts @@ -1,9 +1,9 @@ -import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; import { useMutateQueryResource, useMutation, -} from '@affine/core/components/hooks/use-mutation'; -import { useQuery } from '@affine/core/components/hooks/use-query'; +} from '@affine/admin/use-mutation'; +import { useQuery } from '@affine/admin/use-query'; +import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; import { createChangePasswordUrlMutation, createUserMutation, diff --git a/packages/frontend/admin/src/modules/accounts/use-user-list.ts b/packages/frontend/admin/src/modules/accounts/use-user-list.ts index 58656010c2199..6b1abeaadfa26 100644 --- a/packages/frontend/admin/src/modules/accounts/use-user-list.ts +++ b/packages/frontend/admin/src/modules/accounts/use-user-list.ts @@ -1,4 +1,4 @@ -import { useQuery } from '@affine/core/components/hooks/use-query'; +import { useQuery } from '@affine/admin/use-query'; import { listUsersQuery } from '@affine/graphql'; import { useState } from 'react'; diff --git a/packages/frontend/admin/src/modules/ai/use-prompt.ts b/packages/frontend/admin/src/modules/ai/use-prompt.ts index cc41bcfa1c6ca..3c84273c6c6a6 100644 --- a/packages/frontend/admin/src/modules/ai/use-prompt.ts +++ b/packages/frontend/admin/src/modules/ai/use-prompt.ts @@ -1,9 +1,9 @@ -import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; import { useMutateQueryResource, useMutation, -} from '@affine/core/components/hooks/use-mutation'; -import { useQuery } from '@affine/core/components/hooks/use-query'; +} from '@affine/admin/use-mutation'; +import { useQuery } from '@affine/admin/use-query'; +import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; import { getPromptsQuery, updatePromptMutation } from '@affine/graphql'; import { toast } from 'sonner'; diff --git a/packages/frontend/admin/src/modules/common.ts b/packages/frontend/admin/src/modules/common.ts index 2447cb6c32eb0..046e44fc98448 100644 --- a/packages/frontend/admin/src/modules/common.ts +++ b/packages/frontend/admin/src/modules/common.ts @@ -1,5 +1,3 @@ -import { useMutateQueryResource } from '@affine/core/components/hooks/use-mutation'; -import { useQuery } from '@affine/core/components/hooks/use-query'; import type { GetCurrentUserFeaturesQuery } from '@affine/graphql'; import { adminServerConfigQuery, @@ -7,6 +5,9 @@ import { getCurrentUserFeaturesQuery, } from '@affine/graphql'; +import { useMutateQueryResource } from '../use-mutation'; +import { useQuery } from '../use-query'; + export const useServerConfig = () => { const { data } = useQuery({ query: adminServerConfigQuery, diff --git a/packages/frontend/admin/src/modules/config/use-server-service-configs.ts b/packages/frontend/admin/src/modules/config/use-server-service-configs.ts index a027e3598bf98..bcd439ca0f81e 100644 --- a/packages/frontend/admin/src/modules/config/use-server-service-configs.ts +++ b/packages/frontend/admin/src/modules/config/use-server-service-configs.ts @@ -1,4 +1,4 @@ -import { useQueryImmutable } from '@affine/core/components/hooks/use-query'; +import { useQueryImmutable } from '@affine/admin/use-query'; import { getServerServiceConfigsQuery } from '@affine/graphql'; import { useMemo } from 'react'; diff --git a/packages/frontend/admin/src/modules/settings/use-get-server-runtime-config.ts b/packages/frontend/admin/src/modules/settings/use-get-server-runtime-config.ts index 594efa58fff10..cc0988d399470 100644 --- a/packages/frontend/admin/src/modules/settings/use-get-server-runtime-config.ts +++ b/packages/frontend/admin/src/modules/settings/use-get-server-runtime-config.ts @@ -1,4 +1,4 @@ -import { useQuery } from '@affine/core/components/hooks/use-query'; +import { useQuery } from '@affine/admin/use-query'; import { getServerRuntimeConfigQuery } from '@affine/graphql'; import { useMemo } from 'react'; diff --git a/packages/frontend/admin/src/modules/settings/use-update-server-runtime-config.ts b/packages/frontend/admin/src/modules/settings/use-update-server-runtime-config.ts index 3c7232fd68b04..7c10f921505b1 100644 --- a/packages/frontend/admin/src/modules/settings/use-update-server-runtime-config.ts +++ b/packages/frontend/admin/src/modules/settings/use-update-server-runtime-config.ts @@ -1,9 +1,9 @@ -import { notify } from '@affine/component'; -import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; import { useMutateQueryResource, useMutation, -} from '@affine/core/components/hooks/use-mutation'; +} from '@affine/admin/use-mutation'; +import { notify } from '@affine/component'; +import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; import { getServerRuntimeConfigQuery, updateServerRuntimeConfigsMutation, diff --git a/packages/frontend/admin/src/use-mutation.ts b/packages/frontend/admin/src/use-mutation.ts new file mode 100644 index 0000000000000..b17150e5fffc5 --- /dev/null +++ b/packages/frontend/admin/src/use-mutation.ts @@ -0,0 +1,94 @@ +import type { + GraphQLQuery, + MutationOptions, + QueryResponse, + QueryVariables, + RecursiveMaybeFields, +} from '@affine/graphql'; +import type { GraphQLError } from 'graphql'; +import { useMemo } from 'react'; +import type { Key } from 'swr'; +import { useSWRConfig } from 'swr'; +import type { + SWRMutationConfiguration, + SWRMutationResponse, +} from 'swr/mutation'; +import useSWRMutation from 'swr/mutation'; + +import { gqlFetcher } from './use-query'; + +/** + * A useSWRMutation wrapper for sending graphql mutations + * + * @example + * + * ```ts + * import { someMutation } from '@affine/graphql' + * + * const { trigger } = useMutation({ + * mutation: someMutation, + * }) + * + * trigger({ name: 'John Doe' }) + */ +export function useMutation( + options: Omit, 'variables'>, + config?: Omit< + SWRMutationConfiguration< + QueryResponse, + GraphQLError, + K, + QueryVariables + >, + 'fetcher' + > +): SWRMutationResponse< + QueryResponse, + GraphQLError, + K, + QueryVariables +>; +export function useMutation( + options: Omit, 'variables'>, + config?: any +) { + return useSWRMutation( + () => ['cloud', options.mutation.id], + (_: unknown[], { arg }: { arg: any }) => + gqlFetcher({ + ...options, + query: options.mutation, + variables: arg, + }), + config + ); +} + +// use this to revalidate all queries that match the filter +export const useMutateQueryResource = () => { + const { mutate } = useSWRConfig(); + const revalidateResource = useMemo( + () => + ( + query: Q, + varsFilter: ( + vars: RecursiveMaybeFields> + ) => boolean = _vars => true + ) => { + return mutate(key => { + const res = + Array.isArray(key) && + key[0] === 'cloud' && + key[1] === query.id && + varsFilter(key[2]); + if (res) { + console.debug('revalidate resource', key); + } + return res; + }); + }, + [mutate] + ); + + return revalidateResource; +}; diff --git a/packages/frontend/admin/src/use-query.ts b/packages/frontend/admin/src/use-query.ts new file mode 100644 index 0000000000000..414a1a6b18dee --- /dev/null +++ b/packages/frontend/admin/src/use-query.ts @@ -0,0 +1,131 @@ +import { + gqlFetcherFactory, + type GraphQLQuery, + type QueryOptions, + type QueryResponse, +} from '@affine/graphql'; +import type { GraphQLError } from 'graphql'; +import { useCallback, useMemo } from 'react'; +import type { SWRConfiguration, SWRResponse } from 'swr'; +import useSWR from 'swr'; +import useSWRImmutable from 'swr/immutable'; +import useSWRInfinite from 'swr/infinite'; + +/** + * A `useSWR` wrapper for sending graphql queries + * + * @example + * + * ```ts + * import { someQuery, someQueryWithNoVars } from '@affine/graphql' + * + * const swrResponse1 = useQuery({ + * query: workspaceByIdQuery, + * variables: { id: '1' } + * }) + * + * const swrResponse2 = useQuery({ + * query: someQueryWithNoVars + * }) + * ``` + */ +type useQueryFn = ( + options?: QueryOptions, + config?: Omit< + SWRConfiguration< + QueryResponse, + GraphQLError, + (options: QueryOptions) => Promise> + >, + 'fetcher' + > +) => SWRResponse< + QueryResponse, + GraphQLError, + { + suspense: true; + } +>; + +const createUseQuery = + (immutable: boolean): useQueryFn => + (options, config) => { + const configWithSuspense: SWRConfiguration = useMemo( + () => ({ + suspense: true, + ...config, + }), + [config] + ); + + const useSWRFn = immutable ? useSWRImmutable : useSWR; + return useSWRFn( + options ? () => ['cloud', options.query.id, options.variables] : null, + options ? () => gqlFetcher(options) : null, + configWithSuspense + ); + }; + +export const useQuery = createUseQuery(false); +export const useQueryImmutable = createUseQuery(true); + +export const gqlFetcher = gqlFetcherFactory('/graphql', window.fetch); + +export function useQueryInfinite( + options: Omit, 'variables'> & { + getVariables: ( + pageIndex: number, + previousPageData: QueryResponse + ) => QueryOptions['variables']; + }, + config?: Omit< + SWRConfiguration< + QueryResponse, + GraphQLError | GraphQLError[], + (options: QueryOptions) => Promise> + >, + 'fetcher' + > +) { + const configWithSuspense: SWRConfiguration = useMemo( + () => ({ + suspense: true, + ...config, + }), + [config] + ); + + const { data, setSize, size, error } = useSWRInfinite< + QueryResponse, + GraphQLError | GraphQLError[] + >( + (pageIndex: number, previousPageData: QueryResponse) => [ + 'cloud', + options.query.id, + options.getVariables(pageIndex, previousPageData), + ], + async ([_, __, variables]) => { + const params = { ...options, variables } as QueryOptions; + return gqlFetcher(params); + }, + configWithSuspense + ); + + const loadingMore = size > 0 && data && !data[size - 1]; + + // TODO(@Peng): find a generic way to know whether or not there are more items to load + const loadMore = useCallback(() => { + if (loadingMore) { + return; + } + setSize(size => size + 1).catch(err => { + console.error(err); + }); + }, [loadingMore, setSize]); + return { + data, + error, + loadingMore, + loadMore, + }; +} From 331aa47a0e01f7eadd4e3bd61bf11f92d3aded46 Mon Sep 17 00:00:00 2001 From: renovate <29139614+renovate@users.noreply.github.com> Date: Mon, 23 Dec 2024 03:24:05 +0000 Subject: [PATCH 09/17] chore: bump up all non-major dependencies (#9180) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | Type | Update | |---|---|---|---|---|---|---|---| | [@aws-sdk/client-s3](https://redirect.github.com/aws/aws-sdk-js-v3/tree/main/clients/client-s3) ([source](https://redirect.github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-s3)) | [`3.712.0` -> `3.717.0`](https://renovatebot.com/diffs/npm/@aws-sdk%2fclient-s3/3.712.0/3.717.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@aws-sdk%2fclient-s3/3.717.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@aws-sdk%2fclient-s3/3.717.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@aws-sdk%2fclient-s3/3.712.0/3.717.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@aws-sdk%2fclient-s3/3.712.0/3.717.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | devDependencies | minor | | [@aws-sdk/client-s3](https://redirect.github.com/aws/aws-sdk-js-v3/tree/main/clients/client-s3) ([source](https://redirect.github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-s3)) | [`3.712.0` -> `3.717.0`](https://renovatebot.com/diffs/npm/@aws-sdk%2fclient-s3/3.712.0/3.717.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@aws-sdk%2fclient-s3/3.717.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@aws-sdk%2fclient-s3/3.717.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@aws-sdk%2fclient-s3/3.712.0/3.717.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@aws-sdk%2fclient-s3/3.712.0/3.717.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | minor | | [@chromatic-com/storybook](https://redirect.github.com/chromaui/addon-visual-tests) | [`3.2.2` -> `3.2.3`](https://renovatebot.com/diffs/npm/@chromatic-com%2fstorybook/3.2.2/3.2.3) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@chromatic-com%2fstorybook/3.2.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@chromatic-com%2fstorybook/3.2.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@chromatic-com%2fstorybook/3.2.2/3.2.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@chromatic-com%2fstorybook/3.2.2/3.2.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | devDependencies | patch | | [@clack/core](https://redirect.github.com/natemoo-re/clack/tree/main/packages/core#readme) ([source](https://redirect.github.com/natemoo-re/clack/tree/HEAD/packages/core)) | [`^0.3.5` -> `^0.4.0`](https://renovatebot.com/diffs/npm/@clack%2fcore/0.3.5/0.4.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@clack%2fcore/0.4.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@clack%2fcore/0.4.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@clack%2fcore/0.3.5/0.4.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@clack%2fcore/0.3.5/0.4.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | devDependencies | minor | | [@clack/prompts](https://redirect.github.com/natemoo-re/clack/tree/main/packages/prompts#readme) ([source](https://redirect.github.com/natemoo-re/clack/tree/HEAD/packages/prompts)) | [`^0.8.2` -> `^0.9.0`](https://renovatebot.com/diffs/npm/@clack%2fprompts/0.8.2/0.9.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@clack%2fprompts/0.9.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@clack%2fprompts/0.9.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@clack%2fprompts/0.8.2/0.9.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@clack%2fprompts/0.8.2/0.9.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | devDependencies | minor | | [@napi-rs/cli](https://redirect.github.com/napi-rs/napi-rs) | [`3.0.0-alpha.64` -> `3.0.0-alpha.65`](https://renovatebot.com/diffs/npm/@napi-rs%2fcli/3.0.0-alpha.64/3.0.0-alpha.65) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@napi-rs%2fcli/3.0.0-alpha.65?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@napi-rs%2fcli/3.0.0-alpha.65?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@napi-rs%2fcli/3.0.0-alpha.64/3.0.0-alpha.65?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@napi-rs%2fcli/3.0.0-alpha.64/3.0.0-alpha.65?slim=true)](https://docs.renovatebot.com/merge-confidence/) | devDependencies | patch | | [@opentelemetry/core](https://redirect.github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-core) ([source](https://redirect.github.com/open-telemetry/opentelemetry-js)) | [`1.29.0` -> `1.30.0`](https://renovatebot.com/diffs/npm/@opentelemetry%2fcore/1.29.0/1.30.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@opentelemetry%2fcore/1.30.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@opentelemetry%2fcore/1.30.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@opentelemetry%2fcore/1.29.0/1.30.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@opentelemetry%2fcore/1.29.0/1.30.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | minor | | [@opentelemetry/exporter-prometheus](https://redirect.github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-exporter-prometheus) ([source](https://redirect.github.com/open-telemetry/opentelemetry-js)) | [`^0.56.0` -> `^0.57.0`](https://renovatebot.com/diffs/npm/@opentelemetry%2fexporter-prometheus/0.56.0/0.57.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@opentelemetry%2fexporter-prometheus/0.57.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@opentelemetry%2fexporter-prometheus/0.57.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@opentelemetry%2fexporter-prometheus/0.56.0/0.57.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@opentelemetry%2fexporter-prometheus/0.56.0/0.57.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | minor | | [@opentelemetry/exporter-zipkin](https://redirect.github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-exporter-zipkin) ([source](https://redirect.github.com/open-telemetry/opentelemetry-js)) | [`1.29.0` -> `1.30.0`](https://renovatebot.com/diffs/npm/@opentelemetry%2fexporter-zipkin/1.29.0/1.30.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@opentelemetry%2fexporter-zipkin/1.30.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@opentelemetry%2fexporter-zipkin/1.30.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@opentelemetry%2fexporter-zipkin/1.29.0/1.30.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@opentelemetry%2fexporter-zipkin/1.29.0/1.30.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | minor | | [@opentelemetry/instrumentation](https://redirect.github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-instrumentation) ([source](https://redirect.github.com/open-telemetry/opentelemetry-js)) | [`^0.56.0` -> `^0.57.0`](https://renovatebot.com/diffs/npm/@opentelemetry%2finstrumentation/0.56.0/0.57.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@opentelemetry%2finstrumentation/0.57.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@opentelemetry%2finstrumentation/0.57.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@opentelemetry%2finstrumentation/0.56.0/0.57.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@opentelemetry%2finstrumentation/0.56.0/0.57.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | minor | | [@opentelemetry/instrumentation-graphql](https://redirect.github.com/open-telemetry/opentelemetry-js-contrib/tree/main/plugins/node/opentelemetry-instrumentation-graphql#readme) ([source](https://redirect.github.com/open-telemetry/opentelemetry-js-contrib)) | [`^0.46.0` -> `^0.47.0`](https://renovatebot.com/diffs/npm/@opentelemetry%2finstrumentation-graphql/0.46.0/0.47.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@opentelemetry%2finstrumentation-graphql/0.47.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@opentelemetry%2finstrumentation-graphql/0.47.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@opentelemetry%2finstrumentation-graphql/0.46.0/0.47.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@opentelemetry%2finstrumentation-graphql/0.46.0/0.47.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | minor | | [@opentelemetry/instrumentation-http](https://redirect.github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-instrumentation-http) ([source](https://redirect.github.com/open-telemetry/opentelemetry-js)) | [`^0.56.0` -> `^0.57.0`](https://renovatebot.com/diffs/npm/@opentelemetry%2finstrumentation-http/0.56.0/0.57.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@opentelemetry%2finstrumentation-http/0.57.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@opentelemetry%2finstrumentation-http/0.57.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@opentelemetry%2finstrumentation-http/0.56.0/0.57.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@opentelemetry%2finstrumentation-http/0.56.0/0.57.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | minor | | [@opentelemetry/instrumentation-ioredis](https://redirect.github.com/open-telemetry/opentelemetry-js-contrib/tree/main/plugins/node/opentelemetry-instrumentation-ioredis#readme) ([source](https://redirect.github.com/open-telemetry/opentelemetry-js-contrib)) | [`^0.46.0` -> `^0.47.0`](https://renovatebot.com/diffs/npm/@opentelemetry%2finstrumentation-ioredis/0.46.0/0.47.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@opentelemetry%2finstrumentation-ioredis/0.47.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@opentelemetry%2finstrumentation-ioredis/0.47.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@opentelemetry%2finstrumentation-ioredis/0.46.0/0.47.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@opentelemetry%2finstrumentation-ioredis/0.46.0/0.47.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | minor | | [@opentelemetry/instrumentation-nestjs-core](https://redirect.github.com/open-telemetry/opentelemetry-js-contrib/tree/main/plugins/node/opentelemetry-instrumentation-nestjs-core#readme) ([source](https://redirect.github.com/open-telemetry/opentelemetry-js-contrib)) | [`^0.43.0` -> `^0.44.0`](https://renovatebot.com/diffs/npm/@opentelemetry%2finstrumentation-nestjs-core/0.43.0/0.44.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@opentelemetry%2finstrumentation-nestjs-core/0.44.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@opentelemetry%2finstrumentation-nestjs-core/0.44.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@opentelemetry%2finstrumentation-nestjs-core/0.43.0/0.44.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@opentelemetry%2finstrumentation-nestjs-core/0.43.0/0.44.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | minor | | [@opentelemetry/instrumentation-socket.io](https://redirect.github.com/open-telemetry/opentelemetry-js-contrib/tree/main/plugins/node/instrumentation-socket.io#readme) ([source](https://redirect.github.com/open-telemetry/opentelemetry-js-contrib)) | [`^0.45.0` -> `^0.46.0`](https://renovatebot.com/diffs/npm/@opentelemetry%2finstrumentation-socket.io/0.45.0/0.46.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@opentelemetry%2finstrumentation-socket.io/0.46.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@opentelemetry%2finstrumentation-socket.io/0.46.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@opentelemetry%2finstrumentation-socket.io/0.45.0/0.46.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@opentelemetry%2finstrumentation-socket.io/0.45.0/0.46.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | minor | | [@opentelemetry/resources](https://redirect.github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-resources) ([source](https://redirect.github.com/open-telemetry/opentelemetry-js)) | [`1.29.0` -> `1.30.0`](https://renovatebot.com/diffs/npm/@opentelemetry%2fresources/1.29.0/1.30.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@opentelemetry%2fresources/1.30.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@opentelemetry%2fresources/1.30.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@opentelemetry%2fresources/1.29.0/1.30.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@opentelemetry%2fresources/1.29.0/1.30.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | minor | | [@opentelemetry/sdk-metrics](https://redirect.github.com/open-telemetry/opentelemetry-js/tree/main/packages/sdk-metrics) ([source](https://redirect.github.com/open-telemetry/opentelemetry-js)) | [`1.29.0` -> `1.30.0`](https://renovatebot.com/diffs/npm/@opentelemetry%2fsdk-metrics/1.29.0/1.30.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@opentelemetry%2fsdk-metrics/1.30.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@opentelemetry%2fsdk-metrics/1.30.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@opentelemetry%2fsdk-metrics/1.29.0/1.30.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@opentelemetry%2fsdk-metrics/1.29.0/1.30.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | minor | | [@opentelemetry/sdk-node](https://redirect.github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-sdk-node) ([source](https://redirect.github.com/open-telemetry/opentelemetry-js)) | [`^0.56.0` -> `^0.57.0`](https://renovatebot.com/diffs/npm/@opentelemetry%2fsdk-node/0.56.0/0.57.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@opentelemetry%2fsdk-node/0.57.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@opentelemetry%2fsdk-node/0.57.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@opentelemetry%2fsdk-node/0.56.0/0.57.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@opentelemetry%2fsdk-node/0.56.0/0.57.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | minor | | [@opentelemetry/sdk-trace-node](https://redirect.github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-sdk-trace-node) ([source](https://redirect.github.com/open-telemetry/opentelemetry-js)) | [`1.29.0` -> `1.30.0`](https://renovatebot.com/diffs/npm/@opentelemetry%2fsdk-trace-node/1.29.0/1.30.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@opentelemetry%2fsdk-trace-node/1.30.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@opentelemetry%2fsdk-trace-node/1.30.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@opentelemetry%2fsdk-trace-node/1.29.0/1.30.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@opentelemetry%2fsdk-trace-node/1.29.0/1.30.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | minor | | [@radix-ui/react-alert-dialog](https://radix-ui.com/primitives) ([source](https://redirect.github.com/radix-ui/primitives)) | [`1.1.3` -> `1.1.4`](https://renovatebot.com/diffs/npm/@radix-ui%2freact-alert-dialog/1.1.3/1.1.4) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@radix-ui%2freact-alert-dialog/1.1.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@radix-ui%2freact-alert-dialog/1.1.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@radix-ui%2freact-alert-dialog/1.1.3/1.1.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@radix-ui%2freact-alert-dialog/1.1.3/1.1.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | patch | | [@radix-ui/react-context-menu](https://radix-ui.com/primitives) ([source](https://redirect.github.com/radix-ui/primitives)) | [`2.2.3` -> `2.2.4`](https://renovatebot.com/diffs/npm/@radix-ui%2freact-context-menu/2.2.3/2.2.4) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@radix-ui%2freact-context-menu/2.2.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@radix-ui%2freact-context-menu/2.2.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@radix-ui%2freact-context-menu/2.2.3/2.2.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@radix-ui%2freact-context-menu/2.2.3/2.2.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | patch | | [@radix-ui/react-dialog](https://radix-ui.com/primitives) ([source](https://redirect.github.com/radix-ui/primitives)) | [`1.1.3` -> `1.1.4`](https://renovatebot.com/diffs/npm/@radix-ui%2freact-dialog/1.1.3/1.1.4) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@radix-ui%2freact-dialog/1.1.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@radix-ui%2freact-dialog/1.1.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@radix-ui%2freact-dialog/1.1.3/1.1.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@radix-ui%2freact-dialog/1.1.3/1.1.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | patch | | [@radix-ui/react-dropdown-menu](https://radix-ui.com/primitives) ([source](https://redirect.github.com/radix-ui/primitives)) | [`2.1.3` -> `2.1.4`](https://renovatebot.com/diffs/npm/@radix-ui%2freact-dropdown-menu/2.1.3/2.1.4) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@radix-ui%2freact-dropdown-menu/2.1.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@radix-ui%2freact-dropdown-menu/2.1.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@radix-ui%2freact-dropdown-menu/2.1.3/2.1.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@radix-ui%2freact-dropdown-menu/2.1.3/2.1.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | patch | | [@radix-ui/react-hover-card](https://radix-ui.com/primitives) ([source](https://redirect.github.com/radix-ui/primitives)) | [`1.1.3` -> `1.1.4`](https://renovatebot.com/diffs/npm/@radix-ui%2freact-hover-card/1.1.3/1.1.4) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@radix-ui%2freact-hover-card/1.1.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@radix-ui%2freact-hover-card/1.1.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@radix-ui%2freact-hover-card/1.1.3/1.1.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@radix-ui%2freact-hover-card/1.1.3/1.1.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | patch | | [@radix-ui/react-menubar](https://radix-ui.com/primitives) ([source](https://redirect.github.com/radix-ui/primitives)) | [`1.1.3` -> `1.1.4`](https://renovatebot.com/diffs/npm/@radix-ui%2freact-menubar/1.1.3/1.1.4) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@radix-ui%2freact-menubar/1.1.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@radix-ui%2freact-menubar/1.1.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@radix-ui%2freact-menubar/1.1.3/1.1.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@radix-ui%2freact-menubar/1.1.3/1.1.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | patch | | [@radix-ui/react-navigation-menu](https://radix-ui.com/primitives) ([source](https://redirect.github.com/radix-ui/primitives)) | [`1.2.2` -> `1.2.3`](https://renovatebot.com/diffs/npm/@radix-ui%2freact-navigation-menu/1.2.2/1.2.3) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@radix-ui%2freact-navigation-menu/1.2.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@radix-ui%2freact-navigation-menu/1.2.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@radix-ui%2freact-navigation-menu/1.2.2/1.2.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@radix-ui%2freact-navigation-menu/1.2.2/1.2.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | patch | | [@radix-ui/react-popover](https://radix-ui.com/primitives) ([source](https://redirect.github.com/radix-ui/primitives)) | [`1.1.3` -> `1.1.4`](https://renovatebot.com/diffs/npm/@radix-ui%2freact-popover/1.1.3/1.1.4) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@radix-ui%2freact-popover/1.1.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@radix-ui%2freact-popover/1.1.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@radix-ui%2freact-popover/1.1.3/1.1.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@radix-ui%2freact-popover/1.1.3/1.1.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | patch | | [@radix-ui/react-select](https://radix-ui.com/primitives) ([source](https://redirect.github.com/radix-ui/primitives)) | [`2.1.3` -> `2.1.4`](https://renovatebot.com/diffs/npm/@radix-ui%2freact-select/2.1.3/2.1.4) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@radix-ui%2freact-select/2.1.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@radix-ui%2freact-select/2.1.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@radix-ui%2freact-select/2.1.3/2.1.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@radix-ui%2freact-select/2.1.3/2.1.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | patch | | [@radix-ui/react-toast](https://radix-ui.com/primitives) ([source](https://redirect.github.com/radix-ui/primitives)) | [`1.2.3` -> `1.2.4`](https://renovatebot.com/diffs/npm/@radix-ui%2freact-toast/1.2.3/1.2.4) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@radix-ui%2freact-toast/1.2.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@radix-ui%2freact-toast/1.2.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@radix-ui%2freact-toast/1.2.3/1.2.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@radix-ui%2freact-toast/1.2.3/1.2.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | patch | | [@radix-ui/react-tooltip](https://radix-ui.com/primitives) ([source](https://redirect.github.com/radix-ui/primitives)) | [`1.1.5` -> `1.1.6`](https://renovatebot.com/diffs/npm/@radix-ui%2freact-tooltip/1.1.5/1.1.6) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@radix-ui%2freact-tooltip/1.1.6?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@radix-ui%2freact-tooltip/1.1.6?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@radix-ui%2freact-tooltip/1.1.5/1.1.6?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@radix-ui%2freact-tooltip/1.1.5/1.1.6?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | patch | | [@sentry/electron](https://redirect.github.com/getsentry/sentry-electron) | [`5.8.0` -> `5.9.0`](https://renovatebot.com/diffs/npm/@sentry%2felectron/5.8.0/5.9.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@sentry%2felectron/5.9.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@sentry%2felectron/5.9.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@sentry%2felectron/5.8.0/5.9.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@sentry%2felectron/5.8.0/5.9.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | devDependencies | minor | | [@sentry/react](https://redirect.github.com/getsentry/sentry-javascript/tree/master/packages/react) ([source](https://redirect.github.com/getsentry/sentry-javascript)) | [`8.45.0` -> `8.47.0`](https://renovatebot.com/diffs/npm/@sentry%2freact/8.45.0/8.47.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@sentry%2freact/8.47.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@sentry%2freact/8.47.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@sentry%2freact/8.45.0/8.47.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@sentry%2freact/8.45.0/8.47.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | minor | | [@sentry/react](https://redirect.github.com/getsentry/sentry-javascript/tree/master/packages/react) ([source](https://redirect.github.com/getsentry/sentry-javascript)) | [`8.45.0` -> `8.47.0`](https://renovatebot.com/diffs/npm/@sentry%2freact/8.45.0/8.47.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@sentry%2freact/8.47.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@sentry%2freact/8.47.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@sentry%2freact/8.45.0/8.47.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@sentry%2freact/8.45.0/8.47.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | devDependencies | minor | | [@shoelace-style/shoelace](https://redirect.github.com/shoelace-style/shoelace) | [`2.19.0` -> `2.19.1`](https://renovatebot.com/diffs/npm/@shoelace-style%2fshoelace/2.19.0/2.19.1) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@shoelace-style%2fshoelace/2.19.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@shoelace-style%2fshoelace/2.19.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@shoelace-style%2fshoelace/2.19.0/2.19.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@shoelace-style%2fshoelace/2.19.0/2.19.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | patch | | @​types/dompurify | [`3.0.5` -> `3.2.0`](https://renovatebot.com/diffs/npm/@types%2fdompurify/3.0.5/3.2.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@types%2fdompurify/3.2.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@types%2fdompurify/3.2.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@types%2fdompurify/3.0.5/3.2.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@types%2fdompurify/3.0.5/3.2.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | devDependencies | minor | | [@types/react](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/react) ([source](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react)) | [`19.0.1` -> `19.0.2`](https://renovatebot.com/diffs/npm/@types%2freact/19.0.1/19.0.2) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@types%2freact/19.0.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@types%2freact/19.0.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@types%2freact/19.0.1/19.0.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@types%2freact/19.0.1/19.0.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | devDependencies | patch | | [@vanilla-extract/css](https://redirect.github.com/vanilla-extract-css/vanilla-extract) ([source](https://redirect.github.com/vanilla-extract-css/vanilla-extract/tree/HEAD/packages/css)) | [`1.16.1` -> `1.17.0`](https://renovatebot.com/diffs/npm/@vanilla-extract%2fcss/1.16.1/1.17.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@vanilla-extract%2fcss/1.17.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@vanilla-extract%2fcss/1.17.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@vanilla-extract%2fcss/1.16.1/1.17.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@vanilla-extract%2fcss/1.16.1/1.17.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | devDependencies | minor | | [@vanilla-extract/vite-plugin](https://redirect.github.com/vanilla-extract-css/vanilla-extract) ([source](https://redirect.github.com/vanilla-extract-css/vanilla-extract/tree/HEAD/packages/vite-plugin)) | [`4.0.18` -> `4.0.19`](https://renovatebot.com/diffs/npm/@vanilla-extract%2fvite-plugin/4.0.18/4.0.19) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@vanilla-extract%2fvite-plugin/4.0.19?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@vanilla-extract%2fvite-plugin/4.0.19?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@vanilla-extract%2fvite-plugin/4.0.18/4.0.19?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@vanilla-extract%2fvite-plugin/4.0.18/4.0.19?slim=true)](https://docs.renovatebot.com/merge-confidence/) | devDependencies | patch | | [@vanilla-extract/webpack-plugin](https://redirect.github.com/vanilla-extract-css/vanilla-extract) ([source](https://redirect.github.com/vanilla-extract-css/vanilla-extract/tree/HEAD/packages/webpack-plugin)) | [`2.3.15` -> `2.3.16`](https://renovatebot.com/diffs/npm/@vanilla-extract%2fwebpack-plugin/2.3.15/2.3.16) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@vanilla-extract%2fwebpack-plugin/2.3.16?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@vanilla-extract%2fwebpack-plugin/2.3.16?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@vanilla-extract%2fwebpack-plugin/2.3.15/2.3.16?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@vanilla-extract%2fwebpack-plugin/2.3.15/2.3.16?slim=true)](https://docs.renovatebot.com/merge-confidence/) | devDependencies | patch | | [anyhow](https://redirect.github.com/dtolnay/anyhow) | `1.0.94` -> `1.0.95` | [![age](https://developer.mend.io/api/mc/badges/age/crate/anyhow/1.0.95?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/crate/anyhow/1.0.95?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/crate/anyhow/1.0.94/1.0.95?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/crate/anyhow/1.0.94/1.0.95?slim=true)](https://docs.renovatebot.com/merge-confidence/) | workspace.dependencies | patch | | [chalk](https://redirect.github.com/chalk/chalk) | [`5.3.0` -> `5.4.1`](https://renovatebot.com/diffs/npm/chalk/5.3.0/5.4.1) | [![age](https://developer.mend.io/api/mc/badges/age/npm/chalk/5.4.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/chalk/5.4.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/chalk/5.3.0/5.4.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/chalk/5.3.0/5.4.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | minor | | [esbuild](https://redirect.github.com/evanw/esbuild) | [`0.24.0` -> `0.24.2`](https://renovatebot.com/diffs/npm/esbuild/0.24.0/0.24.2) | [![age](https://developer.mend.io/api/mc/badges/age/npm/esbuild/0.24.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/esbuild/0.24.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/esbuild/0.24.0/0.24.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/esbuild/0.24.0/0.24.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | devDependencies | patch | | [gradle](https://gradle.org) ([source](https://redirect.github.com/gradle/gradle)) | `8.11.1` -> `8.12` | [![age](https://developer.mend.io/api/mc/badges/age/gradle-version/gradle/8.12?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/gradle-version/gradle/8.12?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/gradle-version/gradle/8.11.1/8.12?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/gradle-version/gradle/8.11.1/8.12?slim=true)](https://docs.renovatebot.com/merge-confidence/) | | minor | | [html-validate](https://html-validate.org) ([source](https://gitlab.com/html-validate/html-validate)) | [`8.27.0` -> `8.29.0`](https://renovatebot.com/diffs/npm/html-validate/8.27.0/8.29.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/html-validate/8.29.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/html-validate/8.29.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/html-validate/8.27.0/8.29.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/html-validate/8.27.0/8.29.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | minor | | [i18next](https://www.i18next.com) ([source](https://redirect.github.com/i18next/i18next)) | [`24.1.0` -> `24.2.0`](https://renovatebot.com/diffs/npm/i18next/24.1.0/24.2.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/i18next/24.2.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/i18next/24.2.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/i18next/24.1.0/24.2.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/i18next/24.1.0/24.2.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | minor | | [ioredis](https://redirect.github.com/luin/ioredis) | [`5.4.1` -> `5.4.2`](https://renovatebot.com/diffs/npm/ioredis/5.4.1/5.4.2) | [![age](https://developer.mend.io/api/mc/badges/age/npm/ioredis/5.4.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/ioredis/5.4.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/ioredis/5.4.1/5.4.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/ioredis/5.4.1/5.4.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | patch | | [jotai](https://redirect.github.com/pmndrs/jotai) | [`2.10.3` -> `2.10.4`](https://renovatebot.com/diffs/npm/jotai/2.10.3/2.10.4) | [![age](https://developer.mend.io/api/mc/badges/age/npm/jotai/2.10.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/jotai/2.10.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/jotai/2.10.3/2.10.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/jotai/2.10.3/2.10.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | patch | | [jotai-effect](https://redirect.github.com/jotaijs/jotai-effect) | [`1.0.5` -> `1.0.7`](https://renovatebot.com/diffs/npm/jotai-effect/1.0.5/1.0.7) | [![age](https://developer.mend.io/api/mc/badges/age/npm/jotai-effect/1.0.7?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/jotai-effect/1.0.7?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/jotai-effect/1.0.5/1.0.7?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/jotai-effect/1.0.5/1.0.7?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | patch | | [katex](https://katex.org) ([source](https://redirect.github.com/KaTeX/KaTeX)) | [`0.16.15` -> `0.16.18`](https://renovatebot.com/diffs/npm/katex/0.16.15/0.16.18) | [![age](https://developer.mend.io/api/mc/badges/age/npm/katex/0.16.18?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/katex/0.16.18?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/katex/0.16.15/0.16.18?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/katex/0.16.15/0.16.18?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | patch | | [keyv](https://redirect.github.com/jaredwray/keyv) | [`5.2.2` -> `5.2.3`](https://renovatebot.com/diffs/npm/keyv/5.2.2/5.2.3) | [![age](https://developer.mend.io/api/mc/badges/age/npm/keyv/5.2.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/keyv/5.2.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/keyv/5.2.2/5.2.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/keyv/5.2.2/5.2.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | patch | | [lucide-react](https://lucide.dev) ([source](https://redirect.github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react)) | [`^0.468.0` -> `^0.469.0`](https://renovatebot.com/diffs/npm/lucide-react/0.468.0/0.469.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/lucide-react/0.469.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/lucide-react/0.469.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/lucide-react/0.468.0/0.469.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/lucide-react/0.468.0/0.469.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | minor | | [mixpanel-browser](https://redirect.github.com/mixpanel/mixpanel-js) | [`2.56.0` -> `2.58.0`](https://renovatebot.com/diffs/npm/mixpanel-browser/2.56.0/2.58.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/mixpanel-browser/2.58.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/mixpanel-browser/2.58.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/mixpanel-browser/2.56.0/2.58.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/mixpanel-browser/2.56.0/2.58.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | minor | | [napi](https://redirect.github.com/napi-rs/napi-rs) | `3.0.0-alpha.23` -> `3.0.0-alpha.24` | [![age](https://developer.mend.io/api/mc/badges/age/crate/napi/3.0.0-alpha.24?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/crate/napi/3.0.0-alpha.24?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/crate/napi/3.0.0-alpha.23/3.0.0-alpha.24?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/crate/napi/3.0.0-alpha.23/3.0.0-alpha.24?slim=true)](https://docs.renovatebot.com/merge-confidence/) | workspace.dependencies | patch | | [napi-build](https://redirect.github.com/napi-rs/napi-rs) | `2.1.3` -> `2.1.4` | [![age](https://developer.mend.io/api/mc/badges/age/crate/napi-build/2.1.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/crate/napi-build/2.1.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/crate/napi-build/2.1.3/2.1.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/crate/napi-build/2.1.3/2.1.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | workspace.dependencies | patch | | [napi-derive](https://redirect.github.com/napi-rs/napi-rs) | `3.0.0-alpha.21` -> `3.0.0-alpha.22` | [![age](https://developer.mend.io/api/mc/badges/age/crate/napi-derive/3.0.0-alpha.22?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/crate/napi-derive/3.0.0-alpha.22?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/crate/napi-derive/3.0.0-alpha.21/3.0.0-alpha.22?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/crate/napi-derive/3.0.0-alpha.21/3.0.0-alpha.22?slim=true)](https://docs.renovatebot.com/merge-confidence/) | workspace.dependencies | patch | | [openai](https://redirect.github.com/openai/openai-node) | [`4.76.3` -> `4.77.0`](https://renovatebot.com/diffs/npm/openai/4.76.3/4.77.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/openai/4.77.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/openai/4.77.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/openai/4.76.3/4.77.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/openai/4.76.3/4.77.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | minor | | [react-hook-form](https://www.react-hook-form.com) ([source](https://redirect.github.com/react-hook-form/react-hook-form)) | [`7.54.1` -> `7.54.2`](https://renovatebot.com/diffs/npm/react-hook-form/7.54.1/7.54.2) | [![age](https://developer.mend.io/api/mc/badges/age/npm/react-hook-form/7.54.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/react-hook-form/7.54.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/react-hook-form/7.54.1/7.54.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/react-hook-form/7.54.1/7.54.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | patch | | [react-router-dom](https://redirect.github.com/remix-run/react-router) ([source](https://redirect.github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom)) | [`6.28.0` -> `6.28.1`](https://renovatebot.com/diffs/npm/react-router-dom/6.28.0/6.28.1) | [![age](https://developer.mend.io/api/mc/badges/age/npm/react-router-dom/6.28.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/react-router-dom/6.28.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/react-router-dom/6.28.0/6.28.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/react-router-dom/6.28.0/6.28.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | patch | | [react-router-dom](https://redirect.github.com/remix-run/react-router) ([source](https://redirect.github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom)) | [`6.28.0` -> `6.28.1`](https://renovatebot.com/diffs/npm/react-router-dom/6.28.0/6.28.1) | [![age](https://developer.mend.io/api/mc/badges/age/npm/react-router-dom/6.28.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/react-router-dom/6.28.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/react-router-dom/6.28.0/6.28.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/react-router-dom/6.28.0/6.28.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | devDependencies | patch | | [serde_json](https://redirect.github.com/serde-rs/json) | `1.0.133` -> `1.0.134` | [![age](https://developer.mend.io/api/mc/badges/age/crate/serde_json/1.0.134?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/crate/serde_json/1.0.134?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/crate/serde_json/1.0.133/1.0.134?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/crate/serde_json/1.0.133/1.0.134?slim=true)](https://docs.renovatebot.com/merge-confidence/) | workspace.dependencies | patch | | [shiki](https://redirect.github.com/shikijs/shiki) ([source](https://redirect.github.com/shikijs/shiki/tree/HEAD/packages/shiki)) | [`1.24.2` -> `1.24.4`](https://renovatebot.com/diffs/npm/shiki/1.24.2/1.24.4) | [![age](https://developer.mend.io/api/mc/badges/age/npm/shiki/1.24.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/shiki/1.24.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/shiki/1.24.2/1.24.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/shiki/1.24.2/1.24.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | patch | | [stripe](https://redirect.github.com/stripe/stripe-node) | [`17.4.0` -> `17.5.0`](https://renovatebot.com/diffs/npm/stripe/17.4.0/17.5.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/stripe/17.5.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/stripe/17.5.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/stripe/17.4.0/17.5.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/stripe/17.4.0/17.5.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | minor | | [tailwindcss](https://tailwindcss.com) ([source](https://redirect.github.com/tailwindlabs/tailwindcss)) | [`3.4.16` -> `3.4.17`](https://renovatebot.com/diffs/npm/tailwindcss/3.4.16/3.4.17) | [![age](https://developer.mend.io/api/mc/badges/age/npm/tailwindcss/3.4.17?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/tailwindcss/3.4.17?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/tailwindcss/3.4.16/3.4.17?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/tailwindcss/3.4.16/3.4.17?slim=true)](https://docs.renovatebot.com/merge-confidence/) | devDependencies | patch | | [tiktoken](https://redirect.github.com/dqbd/tiktoken) | [`1.0.17` -> `1.0.18`](https://renovatebot.com/diffs/npm/tiktoken/1.0.17/1.0.18) | [![age](https://developer.mend.io/api/mc/badges/age/npm/tiktoken/1.0.18?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/tiktoken/1.0.18?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/tiktoken/1.0.17/1.0.18?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/tiktoken/1.0.17/1.0.18?slim=true)](https://docs.renovatebot.com/merge-confidence/) | devDependencies | patch | | [typescript-eslint](https://typescript-eslint.io/packages/typescript-eslint) ([source](https://redirect.github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint)) | [`8.18.0` -> `8.18.1`](https://renovatebot.com/diffs/npm/typescript-eslint/8.18.0/8.18.1) | [![age](https://developer.mend.io/api/mc/badges/age/npm/typescript-eslint/8.18.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/typescript-eslint/8.18.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/typescript-eslint/8.18.0/8.18.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/typescript-eslint/8.18.0/8.18.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | devDependencies | patch | | [undici](https://undici.nodejs.org) ([source](https://redirect.github.com/nodejs/undici)) | [`7.1.0` -> `7.2.0`](https://renovatebot.com/diffs/npm/undici/7.1.0/7.2.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/undici/7.2.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/undici/7.2.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/undici/7.1.0/7.2.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/undici/7.1.0/7.2.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | minor | | [vite](https://vite.dev) ([source](https://redirect.github.com/vitejs/vite/tree/HEAD/packages/vite)) | [`6.0.3` -> `6.0.5`](https://renovatebot.com/diffs/npm/vite/6.0.3/6.0.5) | [![age](https://developer.mend.io/api/mc/badges/age/npm/vite/6.0.5?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/vite/6.0.5?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/vite/6.0.3/6.0.5?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/vite/6.0.3/6.0.5?slim=true)](https://docs.renovatebot.com/merge-confidence/) | resolutions | patch | | [wrangler](https://redirect.github.com/cloudflare/workers-sdk) ([source](https://redirect.github.com/cloudflare/workers-sdk/tree/HEAD/packages/wrangler)) | [`3.95.0` -> `3.99.0`](https://renovatebot.com/diffs/npm/wrangler/3.95.0/3.99.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/wrangler/3.99.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/wrangler/3.99.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/wrangler/3.95.0/3.99.0?slim=true)](https://docs.reno --- Cargo.lock | 26 +- blocksuite/playground/package.json | 2 +- package.json | 2 +- packages/backend/native/package.json | 2 +- packages/backend/server/package.json | 16 +- packages/frontend/admin/package.json | 2 +- .../apps/android/App/app/build.gradle | 4 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- packages/frontend/apps/android/App/gradlew | 3 +- packages/frontend/native/package.json | 2 +- tools/cli/package.json | 4 +- yarn.lock | 2535 +++++++++-------- 12 files changed, 1438 insertions(+), 1162 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4a30f5aca9800..34776a6aaba85 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -208,9 +208,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.94" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" [[package]] name = "arbitrary" @@ -1287,7 +1287,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] @@ -1447,9 +1447,9 @@ dependencies = [ [[package]] name = "napi" -version = "3.0.0-alpha.23" +version = "3.0.0-alpha.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4929caab512f6e9650b53d27b4076f3e0524a1369e5d4ab25965fcc60b31cad" +checksum = "5de0beff58a431b3bd6568b690bcf55d72d8dd7f8e0e613a0193a8a9a8eef26b" dependencies = [ "anyhow", "bitflags", @@ -1463,15 +1463,15 @@ dependencies = [ [[package]] name = "napi-build" -version = "2.1.3" +version = "2.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1c0f5d67ee408a4685b61f5ab7e58605c8ae3f2b4189f0127d804ff13d5560a" +checksum = "db836caddef23662b94e16bf1f26c40eceb09d6aee5d5b06a7ac199320b69b19" [[package]] name = "napi-derive" -version = "3.0.0-alpha.21" +version = "3.0.0-alpha.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c12428d113f2b64cf827a144dddaf2df50c4d93d655d57d83745c2a281e6ec62" +checksum = "81f5e3b98fb51282253a2fa9903ae62273c68d89d6f8f304876de30354e86e1e" dependencies = [ "convert_case", "napi-derive-backend", @@ -1482,9 +1482,9 @@ dependencies = [ [[package]] name = "napi-derive-backend" -version = "2.0.0-alpha.21" +version = "2.0.0-alpha.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5122d26b6f849e524f1b92107364f2b4e9a2e8d41a77b3d6c5b3af75801c60" +checksum = "a986ad1072af191e8e1e10a170644025f5b2ee28f2148f3acda818210960cc1a" dependencies = [ "convert_case", "proc-macro2", @@ -2031,9 +2031,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.133" +version = "1.0.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" dependencies = [ "itoa", "memchr", diff --git a/blocksuite/playground/package.json b/blocksuite/playground/package.json index 47c397ffedc0c..73f2a30c232ba 100644 --- a/blocksuite/playground/package.json +++ b/blocksuite/playground/package.json @@ -20,7 +20,7 @@ "@blocksuite/store": "workspace:*", "@blocksuite/sync": "workspace:*", "@preact/signals-core": "^1.8.0", - "@shoelace-style/shoelace": "2.19.0", + "@shoelace-style/shoelace": "2.19.1", "@toeverything/pdf-viewer": "^0.1.1", "@toeverything/y-indexeddb": "0.10.0-canary.9", "@types/katex": "^0.16.7", diff --git a/package.json b/package.json index bb0a001e30e59..c6f40d7e38b6a 100644 --- a/package.json +++ b/package.json @@ -154,7 +154,7 @@ "which-typed-array": "npm:@nolyfill/which-typed-array@latest", "macos-alias": "npm:@napi-rs/macos-alias@0.0.4", "fs-xattr": "npm:@napi-rs/xattr@latest", - "vite": "6.0.3", + "vite": "6.0.5", "decode-named-character-reference@npm:^1.0.0": "patch:decode-named-character-reference@npm%3A1.0.2#~/.yarn/patches/decode-named-character-reference-npm-1.0.2-db17a755fd.patch", "@atlaskit/pragmatic-drag-and-drop@npm:^1.1.0": "patch:@atlaskit/pragmatic-drag-and-drop@npm%3A1.4.0#~/.yarn/patches/@atlaskit-pragmatic-drag-and-drop-npm-1.4.0-75c45f52d3.patch" } diff --git a/packages/backend/native/package.json b/packages/backend/native/package.json index 918540051b834..126b44e722550 100644 --- a/packages/backend/native/package.json +++ b/packages/backend/native/package.json @@ -33,7 +33,7 @@ "build:debug": "napi build" }, "devDependencies": { - "@napi-rs/cli": "3.0.0-alpha.64", + "@napi-rs/cli": "3.0.0-alpha.65", "lib0": "^0.2.99", "tiktoken": "^1.0.17", "tinybench": "^3.0.7", diff --git a/packages/backend/server/package.json b/packages/backend/server/package.json index 8c985603380ee..9bc3756d1c570 100644 --- a/packages/backend/server/package.json +++ b/packages/backend/server/package.json @@ -40,18 +40,18 @@ "@node-rs/crc32": "^1.10.6", "@opentelemetry/api": "^1.9.0", "@opentelemetry/core": "^1.29.0", - "@opentelemetry/exporter-prometheus": "^0.56.0", + "@opentelemetry/exporter-prometheus": "^0.57.0", "@opentelemetry/exporter-zipkin": "^1.29.0", "@opentelemetry/host-metrics": "^0.35.4", - "@opentelemetry/instrumentation": "^0.56.0", - "@opentelemetry/instrumentation-graphql": "^0.46.0", - "@opentelemetry/instrumentation-http": "^0.56.0", - "@opentelemetry/instrumentation-ioredis": "^0.46.0", - "@opentelemetry/instrumentation-nestjs-core": "^0.43.0", - "@opentelemetry/instrumentation-socket.io": "^0.45.0", + "@opentelemetry/instrumentation": "^0.57.0", + "@opentelemetry/instrumentation-graphql": "^0.47.0", + "@opentelemetry/instrumentation-http": "^0.57.0", + "@opentelemetry/instrumentation-ioredis": "^0.47.0", + "@opentelemetry/instrumentation-nestjs-core": "^0.44.0", + "@opentelemetry/instrumentation-socket.io": "^0.46.0", "@opentelemetry/resources": "^1.29.0", "@opentelemetry/sdk-metrics": "^1.29.0", - "@opentelemetry/sdk-node": "^0.56.0", + "@opentelemetry/sdk-node": "^0.57.0", "@opentelemetry/sdk-trace-node": "^1.29.0", "@opentelemetry/semantic-conventions": "^1.28.0", "@prisma/client": "^5.22.0", diff --git a/packages/frontend/admin/package.json b/packages/frontend/admin/package.json index c3c97b72cf6c2..832d162a6d2c6 100644 --- a/packages/frontend/admin/package.json +++ b/packages/frontend/admin/package.json @@ -39,7 +39,7 @@ "cmdk": "^1.0.4", "embla-carousel-react": "^8.5.1", "input-otp": "^1.4.1", - "lucide-react": "^0.468.0", + "lucide-react": "^0.469.0", "next-themes": "^0.4.4", "react": "^19.0.0", "react-day-picker": "^9.4.3", diff --git a/packages/frontend/apps/android/App/app/build.gradle b/packages/frontend/apps/android/App/app/build.gradle index 1533b00470cbc..295ac6c938981 100644 --- a/packages/frontend/apps/android/App/app/build.gradle +++ b/packages/frontend/apps/android/App/app/build.gradle @@ -53,8 +53,8 @@ dependencies { androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion" androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion" implementation project(':capacitor-cordova-android-plugins') - implementation "net.java.dev.jna:jna:5.15.0@aar" - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0" + implementation "net.java.dev.jna:jna:5.16.0@aar" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.1" implementation 'androidx.core:core-ktx:1.15.0' } diff --git a/packages/frontend/apps/android/App/gradle/wrapper/gradle-wrapper.properties b/packages/frontend/apps/android/App/gradle/wrapper/gradle-wrapper.properties index c1d5e0185987c..e0fd02028bca4 100644 --- a/packages/frontend/apps/android/App/gradle/wrapper/gradle-wrapper.properties +++ b/packages/frontend/apps/android/App/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/packages/frontend/apps/android/App/gradlew b/packages/frontend/apps/android/App/gradlew index f5feea6d6b116..f3b75f3b0d4fa 100755 --- a/packages/frontend/apps/android/App/gradlew +++ b/packages/frontend/apps/android/App/gradlew @@ -86,8 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/packages/frontend/native/package.json b/packages/frontend/native/package.json index effd39e3d626c..e0ebe3ddc7bcd 100644 --- a/packages/frontend/native/package.json +++ b/packages/frontend/native/package.json @@ -33,7 +33,7 @@ } }, "devDependencies": { - "@napi-rs/cli": "3.0.0-alpha.64", + "@napi-rs/cli": "3.0.0-alpha.65", "@types/node": "^20.17.10", "ava": "^6.2.0", "ts-node": "^10.9.2", diff --git a/tools/cli/package.json b/tools/cli/package.json index 34b905b8a6791..bd6302f8b42ac 100644 --- a/tools/cli/package.json +++ b/tools/cli/package.json @@ -7,8 +7,8 @@ "@affine/templates": "workspace:*", "@aws-sdk/client-s3": "^3.709.0", "@blocksuite/affine": "workspace:*", - "@clack/core": "^0.3.5", - "@clack/prompts": "^0.8.2", + "@clack/core": "^0.4.0", + "@clack/prompts": "^0.9.0", "@magic-works/i18n-codegen": "^0.6.1", "@napi-rs/simple-git": "^0.1.19", "@perfsee/webpack": "^1.13.0", diff --git a/yarn.lock b/yarn.lock index 1df85624e949a..3fc850cf02a33 100644 --- a/yarn.lock +++ b/yarn.lock @@ -135,7 +135,7 @@ __metadata: cross-env: "npm:^7.0.3" embla-carousel-react: "npm:^8.5.1" input-otp: "npm:^1.4.1" - lucide-react: "npm:^0.468.0" + lucide-react: "npm:^0.469.0" next-themes: "npm:^0.4.4" react: "npm:^19.0.0" react-day-picker: "npm:^9.4.3" @@ -209,8 +209,8 @@ __metadata: "@affine/templates": "workspace:*" "@aws-sdk/client-s3": "npm:^3.709.0" "@blocksuite/affine": "workspace:*" - "@clack/core": "npm:^0.3.5" - "@clack/prompts": "npm:^0.8.2" + "@clack/core": "npm:^0.4.0" + "@clack/prompts": "npm:^0.9.0" "@magic-works/i18n-codegen": "npm:^0.6.1" "@napi-rs/simple-git": "npm:^0.1.19" "@perfsee/webpack": "npm:^1.13.0" @@ -668,7 +668,7 @@ __metadata: version: 0.0.0-use.local resolution: "@affine/native@workspace:packages/frontend/native" dependencies: - "@napi-rs/cli": "npm:3.0.0-alpha.64" + "@napi-rs/cli": "npm:3.0.0-alpha.65" "@types/node": "npm:^20.17.10" ava: "npm:^6.2.0" ts-node: "npm:^10.9.2" @@ -717,7 +717,7 @@ __metadata: version: 0.0.0-use.local resolution: "@affine/server-native@workspace:packages/backend/native" dependencies: - "@napi-rs/cli": "npm:3.0.0-alpha.64" + "@napi-rs/cli": "npm:3.0.0-alpha.65" lib0: "npm:^0.2.99" tiktoken: "npm:^1.0.17" tinybench: "npm:^3.0.7" @@ -752,18 +752,18 @@ __metadata: "@node-rs/crc32": "npm:^1.10.6" "@opentelemetry/api": "npm:^1.9.0" "@opentelemetry/core": "npm:^1.29.0" - "@opentelemetry/exporter-prometheus": "npm:^0.56.0" + "@opentelemetry/exporter-prometheus": "npm:^0.57.0" "@opentelemetry/exporter-zipkin": "npm:^1.29.0" "@opentelemetry/host-metrics": "npm:^0.35.4" - "@opentelemetry/instrumentation": "npm:^0.56.0" - "@opentelemetry/instrumentation-graphql": "npm:^0.46.0" - "@opentelemetry/instrumentation-http": "npm:^0.56.0" - "@opentelemetry/instrumentation-ioredis": "npm:^0.46.0" - "@opentelemetry/instrumentation-nestjs-core": "npm:^0.43.0" - "@opentelemetry/instrumentation-socket.io": "npm:^0.45.0" + "@opentelemetry/instrumentation": "npm:^0.57.0" + "@opentelemetry/instrumentation-graphql": "npm:^0.47.0" + "@opentelemetry/instrumentation-http": "npm:^0.57.0" + "@opentelemetry/instrumentation-ioredis": "npm:^0.47.0" + "@opentelemetry/instrumentation-nestjs-core": "npm:^0.44.0" + "@opentelemetry/instrumentation-socket.io": "npm:^0.46.0" "@opentelemetry/resources": "npm:^1.29.0" "@opentelemetry/sdk-metrics": "npm:^1.29.0" - "@opentelemetry/sdk-node": "npm:^0.56.0" + "@opentelemetry/sdk-node": "npm:^0.57.0" "@opentelemetry/sdk-trace-node": "npm:^1.29.0" "@opentelemetry/semantic-conventions": "npm:^1.28.0" "@prisma/client": "npm:^5.22.0" @@ -1281,32 +1281,32 @@ __metadata: linkType: hard "@aws-sdk/client-s3@npm:^3.709.0": - version: 3.712.0 - resolution: "@aws-sdk/client-s3@npm:3.712.0" + version: 3.717.0 + resolution: "@aws-sdk/client-s3@npm:3.717.0" dependencies: "@aws-crypto/sha1-browser": "npm:5.2.0" "@aws-crypto/sha256-browser": "npm:5.2.0" "@aws-crypto/sha256-js": "npm:5.2.0" - "@aws-sdk/client-sso-oidc": "npm:3.712.0" - "@aws-sdk/client-sts": "npm:3.712.0" - "@aws-sdk/core": "npm:3.709.0" - "@aws-sdk/credential-provider-node": "npm:3.712.0" - "@aws-sdk/middleware-bucket-endpoint": "npm:3.709.0" - "@aws-sdk/middleware-expect-continue": "npm:3.709.0" - "@aws-sdk/middleware-flexible-checksums": "npm:3.709.0" - "@aws-sdk/middleware-host-header": "npm:3.709.0" - "@aws-sdk/middleware-location-constraint": "npm:3.709.0" - "@aws-sdk/middleware-logger": "npm:3.709.0" - "@aws-sdk/middleware-recursion-detection": "npm:3.709.0" - "@aws-sdk/middleware-sdk-s3": "npm:3.709.0" - "@aws-sdk/middleware-ssec": "npm:3.709.0" - "@aws-sdk/middleware-user-agent": "npm:3.709.0" - "@aws-sdk/region-config-resolver": "npm:3.709.0" - "@aws-sdk/signature-v4-multi-region": "npm:3.709.0" - "@aws-sdk/types": "npm:3.709.0" - "@aws-sdk/util-endpoints": "npm:3.709.0" - "@aws-sdk/util-user-agent-browser": "npm:3.709.0" - "@aws-sdk/util-user-agent-node": "npm:3.712.0" + "@aws-sdk/client-sso-oidc": "npm:3.716.0" + "@aws-sdk/client-sts": "npm:3.716.0" + "@aws-sdk/core": "npm:3.716.0" + "@aws-sdk/credential-provider-node": "npm:3.716.0" + "@aws-sdk/middleware-bucket-endpoint": "npm:3.714.0" + "@aws-sdk/middleware-expect-continue": "npm:3.714.0" + "@aws-sdk/middleware-flexible-checksums": "npm:3.717.0" + "@aws-sdk/middleware-host-header": "npm:3.714.0" + "@aws-sdk/middleware-location-constraint": "npm:3.714.0" + "@aws-sdk/middleware-logger": "npm:3.714.0" + "@aws-sdk/middleware-recursion-detection": "npm:3.714.0" + "@aws-sdk/middleware-sdk-s3": "npm:3.716.0" + "@aws-sdk/middleware-ssec": "npm:3.714.0" + "@aws-sdk/middleware-user-agent": "npm:3.716.0" + "@aws-sdk/region-config-resolver": "npm:3.714.0" + "@aws-sdk/signature-v4-multi-region": "npm:3.716.0" + "@aws-sdk/types": "npm:3.714.0" + "@aws-sdk/util-endpoints": "npm:3.714.0" + "@aws-sdk/util-user-agent-browser": "npm:3.714.0" + "@aws-sdk/util-user-agent-node": "npm:3.716.0" "@aws-sdk/xml-builder": "npm:3.709.0" "@smithy/config-resolver": "npm:^3.0.13" "@smithy/core": "npm:^2.5.5" @@ -1320,21 +1320,21 @@ __metadata: "@smithy/invalid-dependency": "npm:^3.0.11" "@smithy/md5-js": "npm:^3.0.11" "@smithy/middleware-content-length": "npm:^3.0.13" - "@smithy/middleware-endpoint": "npm:^3.2.5" - "@smithy/middleware-retry": "npm:^3.0.30" + "@smithy/middleware-endpoint": "npm:^3.2.6" + "@smithy/middleware-retry": "npm:^3.0.31" "@smithy/middleware-serde": "npm:^3.0.11" "@smithy/middleware-stack": "npm:^3.0.11" "@smithy/node-config-provider": "npm:^3.1.12" "@smithy/node-http-handler": "npm:^3.3.2" "@smithy/protocol-http": "npm:^4.1.8" - "@smithy/smithy-client": "npm:^3.5.0" + "@smithy/smithy-client": "npm:^3.5.1" "@smithy/types": "npm:^3.7.2" "@smithy/url-parser": "npm:^3.0.11" "@smithy/util-base64": "npm:^3.0.0" "@smithy/util-body-length-browser": "npm:^3.0.0" "@smithy/util-body-length-node": "npm:^3.0.0" - "@smithy/util-defaults-mode-browser": "npm:^3.0.30" - "@smithy/util-defaults-mode-node": "npm:^3.0.30" + "@smithy/util-defaults-mode-browser": "npm:^3.0.31" + "@smithy/util-defaults-mode-node": "npm:^3.0.31" "@smithy/util-endpoints": "npm:^2.1.7" "@smithy/util-middleware": "npm:^3.0.11" "@smithy/util-retry": "npm:^3.0.11" @@ -1342,326 +1342,326 @@ __metadata: "@smithy/util-utf8": "npm:^3.0.0" "@smithy/util-waiter": "npm:^3.2.0" tslib: "npm:^2.6.2" - checksum: 10/7d714efb48ea366d835a43f71cda186c3d726ae8ea3427d01ecc5d469768e745ea835ec42dd4a162644b7259705f137b989d8182f7492c1a906d8523b714cefa + checksum: 10/d2c71b30e49698d3d1f43d76344f986805c0b17b26381c096b608090ccfe03917faf2f0aff5da302c7df5d8e09085579fdf4255be995d634ac32218470874f0f languageName: node linkType: hard -"@aws-sdk/client-sso-oidc@npm:3.712.0": - version: 3.712.0 - resolution: "@aws-sdk/client-sso-oidc@npm:3.712.0" +"@aws-sdk/client-sso-oidc@npm:3.716.0": + version: 3.716.0 + resolution: "@aws-sdk/client-sso-oidc@npm:3.716.0" dependencies: "@aws-crypto/sha256-browser": "npm:5.2.0" "@aws-crypto/sha256-js": "npm:5.2.0" - "@aws-sdk/core": "npm:3.709.0" - "@aws-sdk/credential-provider-node": "npm:3.712.0" - "@aws-sdk/middleware-host-header": "npm:3.709.0" - "@aws-sdk/middleware-logger": "npm:3.709.0" - "@aws-sdk/middleware-recursion-detection": "npm:3.709.0" - "@aws-sdk/middleware-user-agent": "npm:3.709.0" - "@aws-sdk/region-config-resolver": "npm:3.709.0" - "@aws-sdk/types": "npm:3.709.0" - "@aws-sdk/util-endpoints": "npm:3.709.0" - "@aws-sdk/util-user-agent-browser": "npm:3.709.0" - "@aws-sdk/util-user-agent-node": "npm:3.712.0" + "@aws-sdk/core": "npm:3.716.0" + "@aws-sdk/credential-provider-node": "npm:3.716.0" + "@aws-sdk/middleware-host-header": "npm:3.714.0" + "@aws-sdk/middleware-logger": "npm:3.714.0" + "@aws-sdk/middleware-recursion-detection": "npm:3.714.0" + "@aws-sdk/middleware-user-agent": "npm:3.716.0" + "@aws-sdk/region-config-resolver": "npm:3.714.0" + "@aws-sdk/types": "npm:3.714.0" + "@aws-sdk/util-endpoints": "npm:3.714.0" + "@aws-sdk/util-user-agent-browser": "npm:3.714.0" + "@aws-sdk/util-user-agent-node": "npm:3.716.0" "@smithy/config-resolver": "npm:^3.0.13" "@smithy/core": "npm:^2.5.5" "@smithy/fetch-http-handler": "npm:^4.1.2" "@smithy/hash-node": "npm:^3.0.11" "@smithy/invalid-dependency": "npm:^3.0.11" "@smithy/middleware-content-length": "npm:^3.0.13" - "@smithy/middleware-endpoint": "npm:^3.2.5" - "@smithy/middleware-retry": "npm:^3.0.30" + "@smithy/middleware-endpoint": "npm:^3.2.6" + "@smithy/middleware-retry": "npm:^3.0.31" "@smithy/middleware-serde": "npm:^3.0.11" "@smithy/middleware-stack": "npm:^3.0.11" "@smithy/node-config-provider": "npm:^3.1.12" "@smithy/node-http-handler": "npm:^3.3.2" "@smithy/protocol-http": "npm:^4.1.8" - "@smithy/smithy-client": "npm:^3.5.0" + "@smithy/smithy-client": "npm:^3.5.1" "@smithy/types": "npm:^3.7.2" "@smithy/url-parser": "npm:^3.0.11" "@smithy/util-base64": "npm:^3.0.0" "@smithy/util-body-length-browser": "npm:^3.0.0" "@smithy/util-body-length-node": "npm:^3.0.0" - "@smithy/util-defaults-mode-browser": "npm:^3.0.30" - "@smithy/util-defaults-mode-node": "npm:^3.0.30" + "@smithy/util-defaults-mode-browser": "npm:^3.0.31" + "@smithy/util-defaults-mode-node": "npm:^3.0.31" "@smithy/util-endpoints": "npm:^2.1.7" "@smithy/util-middleware": "npm:^3.0.11" "@smithy/util-retry": "npm:^3.0.11" "@smithy/util-utf8": "npm:^3.0.0" tslib: "npm:^2.6.2" peerDependencies: - "@aws-sdk/client-sts": ^3.712.0 - checksum: 10/4643800b5c1f5622e380ade3363b11d8e7d8a8ce4821e2e9fa2873440a2b19cf920fba751a4e7f9d40fb7d3e13e8452490868e831de35ad3721d85d62552a4d0 + "@aws-sdk/client-sts": ^3.716.0 + checksum: 10/c10a280fda8ce9bcd22542f57fe193ca8e991dd86e0eadfc7996dc4992af2e94e51efc2373f9af76cfd3f57ff3e53d65cda93d0939ef5433059c8f157c6429c3 languageName: node linkType: hard -"@aws-sdk/client-sso@npm:3.712.0": - version: 3.712.0 - resolution: "@aws-sdk/client-sso@npm:3.712.0" +"@aws-sdk/client-sso@npm:3.716.0": + version: 3.716.0 + resolution: "@aws-sdk/client-sso@npm:3.716.0" dependencies: "@aws-crypto/sha256-browser": "npm:5.2.0" "@aws-crypto/sha256-js": "npm:5.2.0" - "@aws-sdk/core": "npm:3.709.0" - "@aws-sdk/middleware-host-header": "npm:3.709.0" - "@aws-sdk/middleware-logger": "npm:3.709.0" - "@aws-sdk/middleware-recursion-detection": "npm:3.709.0" - "@aws-sdk/middleware-user-agent": "npm:3.709.0" - "@aws-sdk/region-config-resolver": "npm:3.709.0" - "@aws-sdk/types": "npm:3.709.0" - "@aws-sdk/util-endpoints": "npm:3.709.0" - "@aws-sdk/util-user-agent-browser": "npm:3.709.0" - "@aws-sdk/util-user-agent-node": "npm:3.712.0" + "@aws-sdk/core": "npm:3.716.0" + "@aws-sdk/middleware-host-header": "npm:3.714.0" + "@aws-sdk/middleware-logger": "npm:3.714.0" + "@aws-sdk/middleware-recursion-detection": "npm:3.714.0" + "@aws-sdk/middleware-user-agent": "npm:3.716.0" + "@aws-sdk/region-config-resolver": "npm:3.714.0" + "@aws-sdk/types": "npm:3.714.0" + "@aws-sdk/util-endpoints": "npm:3.714.0" + "@aws-sdk/util-user-agent-browser": "npm:3.714.0" + "@aws-sdk/util-user-agent-node": "npm:3.716.0" "@smithy/config-resolver": "npm:^3.0.13" "@smithy/core": "npm:^2.5.5" "@smithy/fetch-http-handler": "npm:^4.1.2" "@smithy/hash-node": "npm:^3.0.11" "@smithy/invalid-dependency": "npm:^3.0.11" "@smithy/middleware-content-length": "npm:^3.0.13" - "@smithy/middleware-endpoint": "npm:^3.2.5" - "@smithy/middleware-retry": "npm:^3.0.30" + "@smithy/middleware-endpoint": "npm:^3.2.6" + "@smithy/middleware-retry": "npm:^3.0.31" "@smithy/middleware-serde": "npm:^3.0.11" "@smithy/middleware-stack": "npm:^3.0.11" "@smithy/node-config-provider": "npm:^3.1.12" "@smithy/node-http-handler": "npm:^3.3.2" "@smithy/protocol-http": "npm:^4.1.8" - "@smithy/smithy-client": "npm:^3.5.0" + "@smithy/smithy-client": "npm:^3.5.1" "@smithy/types": "npm:^3.7.2" "@smithy/url-parser": "npm:^3.0.11" "@smithy/util-base64": "npm:^3.0.0" "@smithy/util-body-length-browser": "npm:^3.0.0" "@smithy/util-body-length-node": "npm:^3.0.0" - "@smithy/util-defaults-mode-browser": "npm:^3.0.30" - "@smithy/util-defaults-mode-node": "npm:^3.0.30" + "@smithy/util-defaults-mode-browser": "npm:^3.0.31" + "@smithy/util-defaults-mode-node": "npm:^3.0.31" "@smithy/util-endpoints": "npm:^2.1.7" "@smithy/util-middleware": "npm:^3.0.11" "@smithy/util-retry": "npm:^3.0.11" "@smithy/util-utf8": "npm:^3.0.0" tslib: "npm:^2.6.2" - checksum: 10/0f8878e93ce4b7c1bb663712cf6c3bd709b804470443c68a38d606eb820f486c208e25971072031635a73c9130ef34cf7efab5b87e8e2187d1d9e9bb06b8db73 + checksum: 10/99a9e370d962f4006dbbdfe92cce8a50f86d8ffe8a4d0b3f5c2b3b5b1aa3c47bb35cc7b031a74b9de7e37c73149e97ca638c834662f9988db0172438dfb78220 languageName: node linkType: hard -"@aws-sdk/client-sts@npm:3.712.0": - version: 3.712.0 - resolution: "@aws-sdk/client-sts@npm:3.712.0" +"@aws-sdk/client-sts@npm:3.716.0": + version: 3.716.0 + resolution: "@aws-sdk/client-sts@npm:3.716.0" dependencies: "@aws-crypto/sha256-browser": "npm:5.2.0" "@aws-crypto/sha256-js": "npm:5.2.0" - "@aws-sdk/client-sso-oidc": "npm:3.712.0" - "@aws-sdk/core": "npm:3.709.0" - "@aws-sdk/credential-provider-node": "npm:3.712.0" - "@aws-sdk/middleware-host-header": "npm:3.709.0" - "@aws-sdk/middleware-logger": "npm:3.709.0" - "@aws-sdk/middleware-recursion-detection": "npm:3.709.0" - "@aws-sdk/middleware-user-agent": "npm:3.709.0" - "@aws-sdk/region-config-resolver": "npm:3.709.0" - "@aws-sdk/types": "npm:3.709.0" - "@aws-sdk/util-endpoints": "npm:3.709.0" - "@aws-sdk/util-user-agent-browser": "npm:3.709.0" - "@aws-sdk/util-user-agent-node": "npm:3.712.0" + "@aws-sdk/client-sso-oidc": "npm:3.716.0" + "@aws-sdk/core": "npm:3.716.0" + "@aws-sdk/credential-provider-node": "npm:3.716.0" + "@aws-sdk/middleware-host-header": "npm:3.714.0" + "@aws-sdk/middleware-logger": "npm:3.714.0" + "@aws-sdk/middleware-recursion-detection": "npm:3.714.0" + "@aws-sdk/middleware-user-agent": "npm:3.716.0" + "@aws-sdk/region-config-resolver": "npm:3.714.0" + "@aws-sdk/types": "npm:3.714.0" + "@aws-sdk/util-endpoints": "npm:3.714.0" + "@aws-sdk/util-user-agent-browser": "npm:3.714.0" + "@aws-sdk/util-user-agent-node": "npm:3.716.0" "@smithy/config-resolver": "npm:^3.0.13" "@smithy/core": "npm:^2.5.5" "@smithy/fetch-http-handler": "npm:^4.1.2" "@smithy/hash-node": "npm:^3.0.11" "@smithy/invalid-dependency": "npm:^3.0.11" "@smithy/middleware-content-length": "npm:^3.0.13" - "@smithy/middleware-endpoint": "npm:^3.2.5" - "@smithy/middleware-retry": "npm:^3.0.30" + "@smithy/middleware-endpoint": "npm:^3.2.6" + "@smithy/middleware-retry": "npm:^3.0.31" "@smithy/middleware-serde": "npm:^3.0.11" "@smithy/middleware-stack": "npm:^3.0.11" "@smithy/node-config-provider": "npm:^3.1.12" "@smithy/node-http-handler": "npm:^3.3.2" "@smithy/protocol-http": "npm:^4.1.8" - "@smithy/smithy-client": "npm:^3.5.0" + "@smithy/smithy-client": "npm:^3.5.1" "@smithy/types": "npm:^3.7.2" "@smithy/url-parser": "npm:^3.0.11" "@smithy/util-base64": "npm:^3.0.0" "@smithy/util-body-length-browser": "npm:^3.0.0" "@smithy/util-body-length-node": "npm:^3.0.0" - "@smithy/util-defaults-mode-browser": "npm:^3.0.30" - "@smithy/util-defaults-mode-node": "npm:^3.0.30" + "@smithy/util-defaults-mode-browser": "npm:^3.0.31" + "@smithy/util-defaults-mode-node": "npm:^3.0.31" "@smithy/util-endpoints": "npm:^2.1.7" "@smithy/util-middleware": "npm:^3.0.11" "@smithy/util-retry": "npm:^3.0.11" "@smithy/util-utf8": "npm:^3.0.0" tslib: "npm:^2.6.2" - checksum: 10/23d5f5dd77728015b2258907a973da86122236c1b21459eca45112058a3db8bdede4a8455098bff308b7f64907909eb85250ec50367292cc66ab5e159a4dc21f + checksum: 10/d67f4122f6f6fb97380f86911c2f2e64584f8aff94d65032fcbe32ab89771655d245f593f10479beb51af23e136d5cae44de0a71462c0327dddf35642b102e27 languageName: node linkType: hard -"@aws-sdk/core@npm:3.709.0": - version: 3.709.0 - resolution: "@aws-sdk/core@npm:3.709.0" +"@aws-sdk/core@npm:3.716.0": + version: 3.716.0 + resolution: "@aws-sdk/core@npm:3.716.0" dependencies: - "@aws-sdk/types": "npm:3.709.0" + "@aws-sdk/types": "npm:3.714.0" "@smithy/core": "npm:^2.5.5" "@smithy/node-config-provider": "npm:^3.1.12" "@smithy/property-provider": "npm:^3.1.11" "@smithy/protocol-http": "npm:^4.1.8" "@smithy/signature-v4": "npm:^4.2.4" - "@smithy/smithy-client": "npm:^3.5.0" + "@smithy/smithy-client": "npm:^3.5.1" "@smithy/types": "npm:^3.7.2" "@smithy/util-middleware": "npm:^3.0.11" fast-xml-parser: "npm:4.4.1" tslib: "npm:^2.6.2" - checksum: 10/007ad075b4a072f4734fc31aa5a8f40eea1ea4a13ad9428cdb290e5e0c4cb6e4f6eb2f051a6954ab1ab2d506a60c1caa5506d7e7829e912ff7201825427c1aa9 + checksum: 10/13b7905f3a9c997137cc9cf1d8ad900d49f531437c9176394a1ca76ccd2220c058e303e361d43fd928bbd2281e911046436ec81175ee577f6cb752a261a3068a languageName: node linkType: hard -"@aws-sdk/credential-provider-env@npm:3.709.0": - version: 3.709.0 - resolution: "@aws-sdk/credential-provider-env@npm:3.709.0" +"@aws-sdk/credential-provider-env@npm:3.716.0": + version: 3.716.0 + resolution: "@aws-sdk/credential-provider-env@npm:3.716.0" dependencies: - "@aws-sdk/core": "npm:3.709.0" - "@aws-sdk/types": "npm:3.709.0" + "@aws-sdk/core": "npm:3.716.0" + "@aws-sdk/types": "npm:3.714.0" "@smithy/property-provider": "npm:^3.1.11" "@smithy/types": "npm:^3.7.2" tslib: "npm:^2.6.2" - checksum: 10/b6b8ba53704bbdb2927ab529bfeb397addf1a2e78903f786dbd9a60fb45009a5ef95857fe0f36a9b9a37ce730c0b5b99154a815de32dfb8fb20eb014f6f1b188 + checksum: 10/17507c5652d9f10472d5393863662582e02cdde5a506dc402fd214f16adb509d44a5c571d497f21710f74973ae962390fc2f8d3ebdcf882ba6556b6123df1dce languageName: node linkType: hard -"@aws-sdk/credential-provider-http@npm:3.709.0": - version: 3.709.0 - resolution: "@aws-sdk/credential-provider-http@npm:3.709.0" +"@aws-sdk/credential-provider-http@npm:3.716.0": + version: 3.716.0 + resolution: "@aws-sdk/credential-provider-http@npm:3.716.0" dependencies: - "@aws-sdk/core": "npm:3.709.0" - "@aws-sdk/types": "npm:3.709.0" + "@aws-sdk/core": "npm:3.716.0" + "@aws-sdk/types": "npm:3.714.0" "@smithy/fetch-http-handler": "npm:^4.1.2" "@smithy/node-http-handler": "npm:^3.3.2" "@smithy/property-provider": "npm:^3.1.11" "@smithy/protocol-http": "npm:^4.1.8" - "@smithy/smithy-client": "npm:^3.5.0" + "@smithy/smithy-client": "npm:^3.5.1" "@smithy/types": "npm:^3.7.2" "@smithy/util-stream": "npm:^3.3.2" tslib: "npm:^2.6.2" - checksum: 10/99d87ebf6909200b3129d416adb842e97e298611084d42576b37180452972eac1d219f46a9726943061f79bd21658f6726e07e59c5ee5872030c87a1a2f095f4 + checksum: 10/2a8a50c5b7cca0bf04a4f9bf49472f62203397f513ce8d59d53b3cfdd1f79ad4a20952de19f994ae83a3b4d0b703bca5d066110a0ad742329b1c531efac1f316 languageName: node linkType: hard -"@aws-sdk/credential-provider-ini@npm:3.712.0": - version: 3.712.0 - resolution: "@aws-sdk/credential-provider-ini@npm:3.712.0" +"@aws-sdk/credential-provider-ini@npm:3.716.0": + version: 3.716.0 + resolution: "@aws-sdk/credential-provider-ini@npm:3.716.0" dependencies: - "@aws-sdk/core": "npm:3.709.0" - "@aws-sdk/credential-provider-env": "npm:3.709.0" - "@aws-sdk/credential-provider-http": "npm:3.709.0" - "@aws-sdk/credential-provider-process": "npm:3.709.0" - "@aws-sdk/credential-provider-sso": "npm:3.712.0" - "@aws-sdk/credential-provider-web-identity": "npm:3.709.0" - "@aws-sdk/types": "npm:3.709.0" + "@aws-sdk/core": "npm:3.716.0" + "@aws-sdk/credential-provider-env": "npm:3.716.0" + "@aws-sdk/credential-provider-http": "npm:3.716.0" + "@aws-sdk/credential-provider-process": "npm:3.716.0" + "@aws-sdk/credential-provider-sso": "npm:3.716.0" + "@aws-sdk/credential-provider-web-identity": "npm:3.716.0" + "@aws-sdk/types": "npm:3.714.0" "@smithy/credential-provider-imds": "npm:^3.2.8" "@smithy/property-provider": "npm:^3.1.11" "@smithy/shared-ini-file-loader": "npm:^3.1.12" "@smithy/types": "npm:^3.7.2" tslib: "npm:^2.6.2" peerDependencies: - "@aws-sdk/client-sts": ^3.712.0 - checksum: 10/d1ab90a11bb32b955a90eb368428fd9743949da20ff3eec6541ffe3fa03cc7b2627ae02d086dc13b0b08bb249dfdd7ce1180408ade6bd5bbe76398712c1b61d6 + "@aws-sdk/client-sts": ^3.716.0 + checksum: 10/a256a7e606d63811f1921823472c9e4edc90384684c47b288419f40603a4bdd46273910a7b2bb3675bb88946df0514835d60c551485d49497615498c05cd06ca languageName: node linkType: hard -"@aws-sdk/credential-provider-node@npm:3.712.0": - version: 3.712.0 - resolution: "@aws-sdk/credential-provider-node@npm:3.712.0" +"@aws-sdk/credential-provider-node@npm:3.716.0": + version: 3.716.0 + resolution: "@aws-sdk/credential-provider-node@npm:3.716.0" dependencies: - "@aws-sdk/credential-provider-env": "npm:3.709.0" - "@aws-sdk/credential-provider-http": "npm:3.709.0" - "@aws-sdk/credential-provider-ini": "npm:3.712.0" - "@aws-sdk/credential-provider-process": "npm:3.709.0" - "@aws-sdk/credential-provider-sso": "npm:3.712.0" - "@aws-sdk/credential-provider-web-identity": "npm:3.709.0" - "@aws-sdk/types": "npm:3.709.0" + "@aws-sdk/credential-provider-env": "npm:3.716.0" + "@aws-sdk/credential-provider-http": "npm:3.716.0" + "@aws-sdk/credential-provider-ini": "npm:3.716.0" + "@aws-sdk/credential-provider-process": "npm:3.716.0" + "@aws-sdk/credential-provider-sso": "npm:3.716.0" + "@aws-sdk/credential-provider-web-identity": "npm:3.716.0" + "@aws-sdk/types": "npm:3.714.0" "@smithy/credential-provider-imds": "npm:^3.2.8" "@smithy/property-provider": "npm:^3.1.11" "@smithy/shared-ini-file-loader": "npm:^3.1.12" "@smithy/types": "npm:^3.7.2" tslib: "npm:^2.6.2" - checksum: 10/752b982d9b3709248e72c3ef4f467ee396bf9092fde05da7d8b9b20a9e9ed7498a208b0de1118a63f980e758942b51bb4345a80b4c5695aec59d7dd810721665 + checksum: 10/2763b518fdf6ce80ad02b0fa8185eedcdf8ed113f6a9184684ed5431a9c3794be53c82ad8e1b143aa8463f27d76d2696711dbdbfb6d228efebc5a16cbb5abf80 languageName: node linkType: hard -"@aws-sdk/credential-provider-process@npm:3.709.0": - version: 3.709.0 - resolution: "@aws-sdk/credential-provider-process@npm:3.709.0" +"@aws-sdk/credential-provider-process@npm:3.716.0": + version: 3.716.0 + resolution: "@aws-sdk/credential-provider-process@npm:3.716.0" dependencies: - "@aws-sdk/core": "npm:3.709.0" - "@aws-sdk/types": "npm:3.709.0" + "@aws-sdk/core": "npm:3.716.0" + "@aws-sdk/types": "npm:3.714.0" "@smithy/property-provider": "npm:^3.1.11" "@smithy/shared-ini-file-loader": "npm:^3.1.12" "@smithy/types": "npm:^3.7.2" tslib: "npm:^2.6.2" - checksum: 10/bed76b0dd6067e187e7a195f61c2d9081d2902f36f4be1f823f07aefd5c1052c7502e481d8dbf3e4033029a68b618bf5e3938b84dd500ebd566faa5d2d93e907 + checksum: 10/61c765a3289ae2cac3573b97a5df308eba8daa5250a1621ff139261a898a55eda9447c7a71199bb4d6f0a592e0bd3cda5dd1c211f863d51f6dd4de834922d8b4 languageName: node linkType: hard -"@aws-sdk/credential-provider-sso@npm:3.712.0": - version: 3.712.0 - resolution: "@aws-sdk/credential-provider-sso@npm:3.712.0" +"@aws-sdk/credential-provider-sso@npm:3.716.0": + version: 3.716.0 + resolution: "@aws-sdk/credential-provider-sso@npm:3.716.0" dependencies: - "@aws-sdk/client-sso": "npm:3.712.0" - "@aws-sdk/core": "npm:3.709.0" - "@aws-sdk/token-providers": "npm:3.709.0" - "@aws-sdk/types": "npm:3.709.0" + "@aws-sdk/client-sso": "npm:3.716.0" + "@aws-sdk/core": "npm:3.716.0" + "@aws-sdk/token-providers": "npm:3.714.0" + "@aws-sdk/types": "npm:3.714.0" "@smithy/property-provider": "npm:^3.1.11" "@smithy/shared-ini-file-loader": "npm:^3.1.12" "@smithy/types": "npm:^3.7.2" tslib: "npm:^2.6.2" - checksum: 10/dee11bf95ae64c6b90dcb8064b362b93812d40ee5335cc8b648f429bb4611cd82b02d20709739f1e1e4d41368014de6a09c7f0d2a3bffc8a16197d617c94f6ae + checksum: 10/141e11fdc34abfddec57d76cf905ca0deb180c37f4a0b45e9eaa3d6a1f0c90ec697f003dd9a5f8cff1452cda6271280aa7c567d71d857c52eb34c0c3354b21ed languageName: node linkType: hard -"@aws-sdk/credential-provider-web-identity@npm:3.709.0": - version: 3.709.0 - resolution: "@aws-sdk/credential-provider-web-identity@npm:3.709.0" +"@aws-sdk/credential-provider-web-identity@npm:3.716.0": + version: 3.716.0 + resolution: "@aws-sdk/credential-provider-web-identity@npm:3.716.0" dependencies: - "@aws-sdk/core": "npm:3.709.0" - "@aws-sdk/types": "npm:3.709.0" + "@aws-sdk/core": "npm:3.716.0" + "@aws-sdk/types": "npm:3.714.0" "@smithy/property-provider": "npm:^3.1.11" "@smithy/types": "npm:^3.7.2" tslib: "npm:^2.6.2" peerDependencies: - "@aws-sdk/client-sts": ^3.709.0 - checksum: 10/7154d0883babaa7f75d252936e96f078a09fe354e3c62ad202e4eec27a761a178f04a0a5252eeaa26b7961def7a84afe897aa2683a0de8ce5c305404c8322537 + "@aws-sdk/client-sts": ^3.716.0 + checksum: 10/f5cdc876e05fe503048913bbec7b4688725bd10bf0cd92eab3a5fe9687ecd082aee7ecf52dd4e5fcd105c84e70fe768ae85e95b397d4f7b9f69310f819d38e91 languageName: node linkType: hard -"@aws-sdk/middleware-bucket-endpoint@npm:3.709.0": - version: 3.709.0 - resolution: "@aws-sdk/middleware-bucket-endpoint@npm:3.709.0" +"@aws-sdk/middleware-bucket-endpoint@npm:3.714.0": + version: 3.714.0 + resolution: "@aws-sdk/middleware-bucket-endpoint@npm:3.714.0" dependencies: - "@aws-sdk/types": "npm:3.709.0" + "@aws-sdk/types": "npm:3.714.0" "@aws-sdk/util-arn-parser": "npm:3.693.0" "@smithy/node-config-provider": "npm:^3.1.12" "@smithy/protocol-http": "npm:^4.1.8" "@smithy/types": "npm:^3.7.2" "@smithy/util-config-provider": "npm:^3.0.0" tslib: "npm:^2.6.2" - checksum: 10/2f4f17278651cf26bbbd2be20936f1578efc89f08c69e31fe82b8c067279099a5ddd08565772bab1bfe7737c7f6f21218371bd1764ca52d646ca1dcb627fc7bd + checksum: 10/b3f816754d22243e8f7cd9a04a34a2e83ff9d5eead9c2f7df7d09e6372e8b4ee63f16e837e65e983b4b4104b93481a5db22394b37427b91e88706bc8024466f7 languageName: node linkType: hard -"@aws-sdk/middleware-expect-continue@npm:3.709.0": - version: 3.709.0 - resolution: "@aws-sdk/middleware-expect-continue@npm:3.709.0" +"@aws-sdk/middleware-expect-continue@npm:3.714.0": + version: 3.714.0 + resolution: "@aws-sdk/middleware-expect-continue@npm:3.714.0" dependencies: - "@aws-sdk/types": "npm:3.709.0" + "@aws-sdk/types": "npm:3.714.0" "@smithy/protocol-http": "npm:^4.1.8" "@smithy/types": "npm:^3.7.2" tslib: "npm:^2.6.2" - checksum: 10/bbdb28396916e02aab02f3dc4246cb878799c2fe8b1a4c7e10a1a66893d48fccc2550789f96eb59f94c814a6c1bb3bad8656bcea4a9621afca86a6344235b4b8 + checksum: 10/1f2089b61a3eceb078f317021da5e8a470d5504f7f7192efe0b052b8b8aac160c274afa380720620f3cc8f0dd3ef9e1d2946b0d6ea72eeff479fa90a7ce36e42 languageName: node linkType: hard -"@aws-sdk/middleware-flexible-checksums@npm:3.709.0": - version: 3.709.0 - resolution: "@aws-sdk/middleware-flexible-checksums@npm:3.709.0" +"@aws-sdk/middleware-flexible-checksums@npm:3.717.0": + version: 3.717.0 + resolution: "@aws-sdk/middleware-flexible-checksums@npm:3.717.0" dependencies: "@aws-crypto/crc32": "npm:5.2.0" "@aws-crypto/crc32c": "npm:5.2.0" "@aws-crypto/util": "npm:5.2.0" - "@aws-sdk/core": "npm:3.709.0" - "@aws-sdk/types": "npm:3.709.0" + "@aws-sdk/core": "npm:3.716.0" + "@aws-sdk/types": "npm:3.714.0" "@smithy/is-array-buffer": "npm:^3.0.0" "@smithy/node-config-provider": "npm:^3.1.12" "@smithy/protocol-http": "npm:^4.1.8" @@ -1670,154 +1670,154 @@ __metadata: "@smithy/util-stream": "npm:^3.3.2" "@smithy/util-utf8": "npm:^3.0.0" tslib: "npm:^2.6.2" - checksum: 10/2caee08ca05078401a9ca003e060603cb13e0620c0eb57ba009553dcfbf1ac1fb147ba4ff65a7a44d3c8e80e55942e7472ec57b3522eca43815995d1693e548b + checksum: 10/fc2e32aa7597f1a0c34d8d229c3070c203c098069226ee1650124b54b0774ba5b3b614a29b8a9f0a7dfcb8da4ea135d93b40cc53d90c883dd374d9cc5fd5813d languageName: node linkType: hard -"@aws-sdk/middleware-host-header@npm:3.709.0": - version: 3.709.0 - resolution: "@aws-sdk/middleware-host-header@npm:3.709.0" +"@aws-sdk/middleware-host-header@npm:3.714.0": + version: 3.714.0 + resolution: "@aws-sdk/middleware-host-header@npm:3.714.0" dependencies: - "@aws-sdk/types": "npm:3.709.0" + "@aws-sdk/types": "npm:3.714.0" "@smithy/protocol-http": "npm:^4.1.8" "@smithy/types": "npm:^3.7.2" tslib: "npm:^2.6.2" - checksum: 10/783dc791b1102eb3554d34a8e1cdad8f4d6d63b711a8c62a03c40f45d79bbb5eb3304bdddcb8eb2d3ddfb25ead9bb967138fc523a4430e1fddfc175e78d2b618 + checksum: 10/197c7e99123f68a3e160121e50c05548c6c17edbab1f5ef50b76b819eec431a56b41f5beb340670da028d1dad432efef540c6e25c52c96393d83cb392d10d799 languageName: node linkType: hard -"@aws-sdk/middleware-location-constraint@npm:3.709.0": - version: 3.709.0 - resolution: "@aws-sdk/middleware-location-constraint@npm:3.709.0" +"@aws-sdk/middleware-location-constraint@npm:3.714.0": + version: 3.714.0 + resolution: "@aws-sdk/middleware-location-constraint@npm:3.714.0" dependencies: - "@aws-sdk/types": "npm:3.709.0" + "@aws-sdk/types": "npm:3.714.0" "@smithy/types": "npm:^3.7.2" tslib: "npm:^2.6.2" - checksum: 10/2526b57e7e009a12ccbe6d065f23b0ce6967efeed38011ba0e87736982e4ff4e7f140446082cf4c1313c1500dfc1ab13ca13fed9954c2c1f091351052b762911 + checksum: 10/d05d93ab432e291cbcacfa9f2fb7e2c9448be68f36d4ab89d36f0e4bd25784d4e32cbcf177af645f8669f398b8d4cdea0c25985ea33f0c2a8aa22b5be7c3bf75 languageName: node linkType: hard -"@aws-sdk/middleware-logger@npm:3.709.0": - version: 3.709.0 - resolution: "@aws-sdk/middleware-logger@npm:3.709.0" +"@aws-sdk/middleware-logger@npm:3.714.0": + version: 3.714.0 + resolution: "@aws-sdk/middleware-logger@npm:3.714.0" dependencies: - "@aws-sdk/types": "npm:3.709.0" + "@aws-sdk/types": "npm:3.714.0" "@smithy/types": "npm:^3.7.2" tslib: "npm:^2.6.2" - checksum: 10/0905894811514b9a43ff4cc00fe1827ecb11f9fb8fb98c889c258557bac6b8e29e9be652e11def47df695dbcfd7d6b6b00eb2e62d5dc8da1102c1ee24cf7b270 + checksum: 10/652d894e2b0a65f7c1fa727fde83f2f7fc7e6ef48f334e356d31ea8ca2cb6c19623f18118aa9f1f08b431e2d626763ace1bbe656f65405de34e8c8b8d243a57d languageName: node linkType: hard -"@aws-sdk/middleware-recursion-detection@npm:3.709.0": - version: 3.709.0 - resolution: "@aws-sdk/middleware-recursion-detection@npm:3.709.0" +"@aws-sdk/middleware-recursion-detection@npm:3.714.0": + version: 3.714.0 + resolution: "@aws-sdk/middleware-recursion-detection@npm:3.714.0" dependencies: - "@aws-sdk/types": "npm:3.709.0" + "@aws-sdk/types": "npm:3.714.0" "@smithy/protocol-http": "npm:^4.1.8" "@smithy/types": "npm:^3.7.2" tslib: "npm:^2.6.2" - checksum: 10/cf7ca8124bf79680fbcf426db299d8445cbca7fbc1433e4de5408905f01fa161e7340809e8e187c71554ebc8a2d65f3fdd0ebf421558b049ce4d83f9da654185 + checksum: 10/091b3db8f0e0e4d15b5aca7f4073a4ab70e08281030a877d2f7f4e3d02f3b9c61f27d8c405695b3e324eb1d7425a6dd07ef1975b1a1e15002c07cd80aa14b46c languageName: node linkType: hard -"@aws-sdk/middleware-sdk-s3@npm:3.709.0": - version: 3.709.0 - resolution: "@aws-sdk/middleware-sdk-s3@npm:3.709.0" +"@aws-sdk/middleware-sdk-s3@npm:3.716.0": + version: 3.716.0 + resolution: "@aws-sdk/middleware-sdk-s3@npm:3.716.0" dependencies: - "@aws-sdk/core": "npm:3.709.0" - "@aws-sdk/types": "npm:3.709.0" + "@aws-sdk/core": "npm:3.716.0" + "@aws-sdk/types": "npm:3.714.0" "@aws-sdk/util-arn-parser": "npm:3.693.0" "@smithy/core": "npm:^2.5.5" "@smithy/node-config-provider": "npm:^3.1.12" "@smithy/protocol-http": "npm:^4.1.8" "@smithy/signature-v4": "npm:^4.2.4" - "@smithy/smithy-client": "npm:^3.5.0" + "@smithy/smithy-client": "npm:^3.5.1" "@smithy/types": "npm:^3.7.2" "@smithy/util-config-provider": "npm:^3.0.0" "@smithy/util-middleware": "npm:^3.0.11" "@smithy/util-stream": "npm:^3.3.2" "@smithy/util-utf8": "npm:^3.0.0" tslib: "npm:^2.6.2" - checksum: 10/22b47f0cce2ef9d29267326b8c1f9c803a0146b79bb18f9978b0c1037e68b19fc76a136a3a432d1067493005197ba7cc9275e35ffc036affdb50eea57df9acec + checksum: 10/a4dc06bb2477ac46814ff9e11ef8ffc72d001ee4172ad8f00d0a2c76587f66ffdf173b41172c4b201459dd71c9f1f3d8c0ce9ae4fe025b198734ec8c50d62938 languageName: node linkType: hard -"@aws-sdk/middleware-ssec@npm:3.709.0": - version: 3.709.0 - resolution: "@aws-sdk/middleware-ssec@npm:3.709.0" +"@aws-sdk/middleware-ssec@npm:3.714.0": + version: 3.714.0 + resolution: "@aws-sdk/middleware-ssec@npm:3.714.0" dependencies: - "@aws-sdk/types": "npm:3.709.0" + "@aws-sdk/types": "npm:3.714.0" "@smithy/types": "npm:^3.7.2" tslib: "npm:^2.6.2" - checksum: 10/f0e730ef070eb2ae5136ccc2bcec89572e8811feb3b5a53982e73d9eb9bb5ec6df906aad0a160330aa7bb45bc5283ded99b85e5b343826590386dc6cf98e5b4f + checksum: 10/b2455187bb5eec1aff6dcd7cdb22600014a439400b893d9f2d9445f12ecfdc23fffbd0b46b6fb907c6f92e10a3edf1a59b6f76e0b0220009e8dfcaa07391abac languageName: node linkType: hard -"@aws-sdk/middleware-user-agent@npm:3.709.0": - version: 3.709.0 - resolution: "@aws-sdk/middleware-user-agent@npm:3.709.0" +"@aws-sdk/middleware-user-agent@npm:3.716.0": + version: 3.716.0 + resolution: "@aws-sdk/middleware-user-agent@npm:3.716.0" dependencies: - "@aws-sdk/core": "npm:3.709.0" - "@aws-sdk/types": "npm:3.709.0" - "@aws-sdk/util-endpoints": "npm:3.709.0" + "@aws-sdk/core": "npm:3.716.0" + "@aws-sdk/types": "npm:3.714.0" + "@aws-sdk/util-endpoints": "npm:3.714.0" "@smithy/core": "npm:^2.5.5" "@smithy/protocol-http": "npm:^4.1.8" "@smithy/types": "npm:^3.7.2" tslib: "npm:^2.6.2" - checksum: 10/d605b09a7a170ab1c4b04d7dfaef68e02c6738345fac5831d4e2d1e9566199154b7eb34a2ec697723ab02f17a94fe0a51f0cde2543c8cf7c2719160a477d71fc + checksum: 10/589e252007115d0f9036cb006e027aa30a2f085b170366e152e7c8622abd0f66ab3540183e8f74b581637c788cf6dc41f8c4ee9b8d2a0f37a2a7d5a919da1645 languageName: node linkType: hard -"@aws-sdk/region-config-resolver@npm:3.709.0": - version: 3.709.0 - resolution: "@aws-sdk/region-config-resolver@npm:3.709.0" +"@aws-sdk/region-config-resolver@npm:3.714.0": + version: 3.714.0 + resolution: "@aws-sdk/region-config-resolver@npm:3.714.0" dependencies: - "@aws-sdk/types": "npm:3.709.0" + "@aws-sdk/types": "npm:3.714.0" "@smithy/node-config-provider": "npm:^3.1.12" "@smithy/types": "npm:^3.7.2" "@smithy/util-config-provider": "npm:^3.0.0" "@smithy/util-middleware": "npm:^3.0.11" tslib: "npm:^2.6.2" - checksum: 10/261db3dfe422ab23451b593d4b0543b48f92584c702d3ad8e5f494c35a9e1b3ab9fc3e0197ad0ebe9d7564c5407bc302a15df903c6c057d0891bf5b7edbf6377 + checksum: 10/47288fb535dd2351fed3b3e1b4a5da0f8465736cf954c6a22e3ebcdbf6ad762fb23e8dbc596f3531de4ae7a32ee302c0e14c5212894770cd856747a268b5009b languageName: node linkType: hard -"@aws-sdk/signature-v4-multi-region@npm:3.709.0": - version: 3.709.0 - resolution: "@aws-sdk/signature-v4-multi-region@npm:3.709.0" +"@aws-sdk/signature-v4-multi-region@npm:3.716.0": + version: 3.716.0 + resolution: "@aws-sdk/signature-v4-multi-region@npm:3.716.0" dependencies: - "@aws-sdk/middleware-sdk-s3": "npm:3.709.0" - "@aws-sdk/types": "npm:3.709.0" + "@aws-sdk/middleware-sdk-s3": "npm:3.716.0" + "@aws-sdk/types": "npm:3.714.0" "@smithy/protocol-http": "npm:^4.1.8" "@smithy/signature-v4": "npm:^4.2.4" "@smithy/types": "npm:^3.7.2" tslib: "npm:^2.6.2" - checksum: 10/347ac8307ca4a69c73257dd9f265b2a19a39495002b5dc43fa101098a9b9a075dc6103ce0df8fb6a5df55682806c2e09a2d6ced93713489e9849c86072b76794 + checksum: 10/5090a824cbb11596aecf484da2a2e7281d06bc4ed27f397e373215afdc8e3c4a8fe1d8c32be4345002fd588354e166d93c103f788b7e5f176692a5955881e4e6 languageName: node linkType: hard -"@aws-sdk/token-providers@npm:3.709.0": - version: 3.709.0 - resolution: "@aws-sdk/token-providers@npm:3.709.0" +"@aws-sdk/token-providers@npm:3.714.0": + version: 3.714.0 + resolution: "@aws-sdk/token-providers@npm:3.714.0" dependencies: - "@aws-sdk/types": "npm:3.709.0" + "@aws-sdk/types": "npm:3.714.0" "@smithy/property-provider": "npm:^3.1.11" "@smithy/shared-ini-file-loader": "npm:^3.1.12" "@smithy/types": "npm:^3.7.2" tslib: "npm:^2.6.2" peerDependencies: - "@aws-sdk/client-sso-oidc": ^3.709.0 - checksum: 10/c4084f52b4716b9ec5fb966a7cba3041f540dce356bca230af9bfb4d41612830796af81fa227bddc7a55d4a284c9c33875929e60764295213a161412fc5752d3 + "@aws-sdk/client-sso-oidc": ^3.714.0 + checksum: 10/ed89afc1429b2b3a4171bcdc5299d4a9d634bdb13bc015c3282826c8108e271377354932216338143b998defc5dce6da02fbd7aeb96bafeb888ab3d242a22a9c languageName: node linkType: hard -"@aws-sdk/types@npm:3.709.0, @aws-sdk/types@npm:^3.222.0": - version: 3.709.0 - resolution: "@aws-sdk/types@npm:3.709.0" +"@aws-sdk/types@npm:3.714.0, @aws-sdk/types@npm:^3.222.0": + version: 3.714.0 + resolution: "@aws-sdk/types@npm:3.714.0" dependencies: "@smithy/types": "npm:^3.7.2" tslib: "npm:^2.6.2" - checksum: 10/d59be3211b5a6168ff7a4eafc584a0d71a07cc41d19001e14c824ee6ff7647016c957edad3fc51c4ddc2b20bcb1fd66cfe3e07afb8d7d3e714f6ffb2f75db705 + checksum: 10/e06001800ded7e3b2258985e188c12aa9b6a649717d7d7d9446741b814de306f9462d64e812bb51d57903755ef002aa2cba795556feb0eb056f2909f9e09a867 languageName: node linkType: hard @@ -1830,15 +1830,15 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/util-endpoints@npm:3.709.0": - version: 3.709.0 - resolution: "@aws-sdk/util-endpoints@npm:3.709.0" +"@aws-sdk/util-endpoints@npm:3.714.0": + version: 3.714.0 + resolution: "@aws-sdk/util-endpoints@npm:3.714.0" dependencies: - "@aws-sdk/types": "npm:3.709.0" + "@aws-sdk/types": "npm:3.714.0" "@smithy/types": "npm:^3.7.2" "@smithy/util-endpoints": "npm:^2.1.7" tslib: "npm:^2.6.2" - checksum: 10/6eaa657413af94daf15abec04344811c551bdf94d9f93a60a5ab6c9befca082efc82b0b0c226afc979ce8ea686d3b2195c1ade2a364ede91f09549d1a57fc47b + checksum: 10/05beed12f55ba401ed290b2ba891bdd630d2c2f470eaac46b849bebd0d8f39e83951082c7dc352f0e473255eb4cd40453d2a8d085ed7978348bb359b81c7fd32 languageName: node linkType: hard @@ -1851,24 +1851,24 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/util-user-agent-browser@npm:3.709.0": - version: 3.709.0 - resolution: "@aws-sdk/util-user-agent-browser@npm:3.709.0" +"@aws-sdk/util-user-agent-browser@npm:3.714.0": + version: 3.714.0 + resolution: "@aws-sdk/util-user-agent-browser@npm:3.714.0" dependencies: - "@aws-sdk/types": "npm:3.709.0" + "@aws-sdk/types": "npm:3.714.0" "@smithy/types": "npm:^3.7.2" bowser: "npm:^2.11.0" tslib: "npm:^2.6.2" - checksum: 10/4f1c1b78aaf77004d04886669cf9dbba0d2380163f4243b9df66acb509dcbcae63dddd273008fb81612129b4dd57375b78328719f2885ade00760f5d07e4b948 + checksum: 10/28aadd11c4b650c583db99b6cb8b150e788cc0aad3d2ccdab1f7a0c1b4d200b2dcc22d9a2eab7b9919f2b82cb162f6f23c46e06254dd1686441ecc4ce07682e9 languageName: node linkType: hard -"@aws-sdk/util-user-agent-node@npm:3.712.0": - version: 3.712.0 - resolution: "@aws-sdk/util-user-agent-node@npm:3.712.0" +"@aws-sdk/util-user-agent-node@npm:3.716.0": + version: 3.716.0 + resolution: "@aws-sdk/util-user-agent-node@npm:3.716.0" dependencies: - "@aws-sdk/middleware-user-agent": "npm:3.709.0" - "@aws-sdk/types": "npm:3.709.0" + "@aws-sdk/middleware-user-agent": "npm:3.716.0" + "@aws-sdk/types": "npm:3.714.0" "@smithy/node-config-provider": "npm:^3.1.12" "@smithy/types": "npm:^3.7.2" tslib: "npm:^2.6.2" @@ -1877,7 +1877,7 @@ __metadata: peerDependenciesMeta: aws-crt: optional: true - checksum: 10/60ff697af9fbd07647838e8ece23cadb3e8e5cda59edfa8223ebd68f6acc8ecb7fb461c979346bed6d78e5103ea9a81315bb4895068cf4aebb159a49223872d3 + checksum: 10/f8f114a0c764653470735839dfc9065863cd9d059998563d6db27c804d05ebd830f772cfd55934567e7738e4d9a5d7b68efbfc29533de54507bc526db4127b15 languageName: node linkType: hard @@ -3576,7 +3576,7 @@ __metadata: "@blocksuite/store": "workspace:*" "@blocksuite/sync": "workspace:*" "@preact/signals-core": "npm:^1.8.0" - "@shoelace-style/shoelace": "npm:2.19.0" + "@shoelace-style/shoelace": "npm:2.19.1" "@toeverything/pdf-viewer": "npm:^0.1.1" "@toeverything/y-indexeddb": "npm:0.10.0-canary.9" "@tweakpane/core": "npm:^2.0.4" @@ -3783,8 +3783,8 @@ __metadata: linkType: hard "@chromatic-com/storybook@npm:^3.2.2": - version: 3.2.2 - resolution: "@chromatic-com/storybook@npm:3.2.2" + version: 3.2.3 + resolution: "@chromatic-com/storybook@npm:3.2.3" dependencies: chromatic: "npm:^11.15.0" filesize: "npm:^10.0.12" @@ -3793,28 +3793,28 @@ __metadata: strip-ansi: "npm:^7.1.0" peerDependencies: storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - checksum: 10/71338edf56cdbc855074c78981f2e1612b364cd864fa99bbda5c0aad147769b9f476de2fd76816102fd504efc5c0c54ba559d5ac9e3828d53278fe7000863d54 + checksum: 10/6aa32ff8a227c5ada0667457a36d5db1946f9d89b8fb64153150445c8b67f26647aa48a6afce21f6dc2cad4905418f37c2fd0b12d26c3a42a36edf45b52efb7b languageName: node linkType: hard -"@clack/core@npm:0.3.5, @clack/core@npm:^0.3.5": - version: 0.3.5 - resolution: "@clack/core@npm:0.3.5" +"@clack/core@npm:0.4.0, @clack/core@npm:^0.4.0": + version: 0.4.0 + resolution: "@clack/core@npm:0.4.0" dependencies: picocolors: "npm:^1.0.0" sisteransi: "npm:^1.0.5" - checksum: 10/329840301b91df2957d6d3a5832946d6a3c8683aeccf98b77f559c518a9e7b75f5e59392228a51fc97ae950cf21438f1b77fb5529affd93df0106f52d9cc0881 + checksum: 10/ae649efac991068528a0ff7528c15a50130d5f25b558ee648aede9bd72fec5b5f3848b110f44d6191620ea790567bb37ce6ef76c0f048b2998b3c941f8248d5b languageName: node linkType: hard -"@clack/prompts@npm:^0.8.2": - version: 0.8.2 - resolution: "@clack/prompts@npm:0.8.2" +"@clack/prompts@npm:^0.9.0": + version: 0.9.0 + resolution: "@clack/prompts@npm:0.9.0" dependencies: - "@clack/core": "npm:0.3.5" + "@clack/core": "npm:0.4.0" picocolors: "npm:^1.0.0" sisteransi: "npm:^1.0.5" - checksum: 10/06859acc2cc8919255592150f898d08c93e6d6041d22b92fafa55f48265a681ab3506bde76fad5a03be3ea6f46e8408e1f1b1d88d259a0169e30b6f8b28acbfe + checksum: 10/1b73b28ca4d4a85d89eacf7f16df1beea918a2c5be0ef99aba8e4144f0ad60ae6e1c57c5fa534739f1ed9ce2b23f580208f71cebbe34edd975443241b9872489 languageName: node linkType: hard @@ -3827,51 +3827,41 @@ __metadata: languageName: node linkType: hard -"@cloudflare/workerd-darwin-64@npm:1.20241205.0": - version: 1.20241205.0 - resolution: "@cloudflare/workerd-darwin-64@npm:1.20241205.0" +"@cloudflare/workerd-darwin-64@npm:1.20241218.0": + version: 1.20241218.0 + resolution: "@cloudflare/workerd-darwin-64@npm:1.20241218.0" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@cloudflare/workerd-darwin-arm64@npm:1.20241205.0": - version: 1.20241205.0 - resolution: "@cloudflare/workerd-darwin-arm64@npm:1.20241205.0" +"@cloudflare/workerd-darwin-arm64@npm:1.20241218.0": + version: 1.20241218.0 + resolution: "@cloudflare/workerd-darwin-arm64@npm:1.20241218.0" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@cloudflare/workerd-linux-64@npm:1.20241205.0": - version: 1.20241205.0 - resolution: "@cloudflare/workerd-linux-64@npm:1.20241205.0" +"@cloudflare/workerd-linux-64@npm:1.20241218.0": + version: 1.20241218.0 + resolution: "@cloudflare/workerd-linux-64@npm:1.20241218.0" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"@cloudflare/workerd-linux-arm64@npm:1.20241205.0": - version: 1.20241205.0 - resolution: "@cloudflare/workerd-linux-arm64@npm:1.20241205.0" +"@cloudflare/workerd-linux-arm64@npm:1.20241218.0": + version: 1.20241218.0 + resolution: "@cloudflare/workerd-linux-arm64@npm:1.20241218.0" conditions: os=linux & cpu=arm64 languageName: node linkType: hard -"@cloudflare/workerd-windows-64@npm:1.20241205.0": - version: 1.20241205.0 - resolution: "@cloudflare/workerd-windows-64@npm:1.20241205.0" +"@cloudflare/workerd-windows-64@npm:1.20241218.0": + version: 1.20241218.0 + resolution: "@cloudflare/workerd-windows-64@npm:1.20241218.0" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@cloudflare/workers-shared@npm:0.11.0": - version: 0.11.0 - resolution: "@cloudflare/workers-shared@npm:0.11.0" - dependencies: - mime: "npm:^3.0.0" - zod: "npm:^3.22.3" - checksum: 10/6665d189fd41568e838a4d2a1474dda6c7fe3c200b0dbf2923cc72edff38b775e90970d4322e72bb837d52ba744ba179590228ae5f43c0a1b1ff9e7b09a2e1c8 - languageName: node - linkType: hard - "@commitlint/cli@npm:^19.6.0, @commitlint/cli@npm:^19.6.1": version: 19.6.1 resolution: "@commitlint/cli@npm:19.6.1" @@ -4938,6 +4928,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/aix-ppc64@npm:0.24.2": + version: 0.24.2 + resolution: "@esbuild/aix-ppc64@npm:0.24.2" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/android-arm64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/android-arm64@npm:0.17.19" @@ -4959,6 +4956,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm64@npm:0.24.2": + version: 0.24.2 + resolution: "@esbuild/android-arm64@npm:0.24.2" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/android-arm@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/android-arm@npm:0.17.19" @@ -4980,6 +4984,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm@npm:0.24.2": + version: 0.24.2 + resolution: "@esbuild/android-arm@npm:0.24.2" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + "@esbuild/android-x64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/android-x64@npm:0.17.19" @@ -5001,6 +5012,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-x64@npm:0.24.2": + version: 0.24.2 + resolution: "@esbuild/android-x64@npm:0.24.2" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + "@esbuild/darwin-arm64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/darwin-arm64@npm:0.17.19" @@ -5022,6 +5040,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-arm64@npm:0.24.2": + version: 0.24.2 + resolution: "@esbuild/darwin-arm64@npm:0.24.2" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/darwin-x64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/darwin-x64@npm:0.17.19" @@ -5043,6 +5068,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-x64@npm:0.24.2": + version: 0.24.2 + resolution: "@esbuild/darwin-x64@npm:0.24.2" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@esbuild/freebsd-arm64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/freebsd-arm64@npm:0.17.19" @@ -5064,6 +5096,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-arm64@npm:0.24.2": + version: 0.24.2 + resolution: "@esbuild/freebsd-arm64@npm:0.24.2" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/freebsd-x64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/freebsd-x64@npm:0.17.19" @@ -5085,6 +5124,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-x64@npm:0.24.2": + version: 0.24.2 + resolution: "@esbuild/freebsd-x64@npm:0.24.2" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/linux-arm64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/linux-arm64@npm:0.17.19" @@ -5106,6 +5152,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm64@npm:0.24.2": + version: 0.24.2 + resolution: "@esbuild/linux-arm64@npm:0.24.2" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/linux-arm@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/linux-arm@npm:0.17.19" @@ -5127,6 +5180,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm@npm:0.24.2": + version: 0.24.2 + resolution: "@esbuild/linux-arm@npm:0.24.2" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + "@esbuild/linux-ia32@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/linux-ia32@npm:0.17.19" @@ -5148,6 +5208,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ia32@npm:0.24.2": + version: 0.24.2 + resolution: "@esbuild/linux-ia32@npm:0.24.2" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/linux-loong64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/linux-loong64@npm:0.17.19" @@ -5169,6 +5236,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-loong64@npm:0.24.2": + version: 0.24.2 + resolution: "@esbuild/linux-loong64@npm:0.24.2" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + "@esbuild/linux-mips64el@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/linux-mips64el@npm:0.17.19" @@ -5190,6 +5264,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-mips64el@npm:0.24.2": + version: 0.24.2 + resolution: "@esbuild/linux-mips64el@npm:0.24.2" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + "@esbuild/linux-ppc64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/linux-ppc64@npm:0.17.19" @@ -5211,6 +5292,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ppc64@npm:0.24.2": + version: 0.24.2 + resolution: "@esbuild/linux-ppc64@npm:0.24.2" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/linux-riscv64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/linux-riscv64@npm:0.17.19" @@ -5232,6 +5320,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-riscv64@npm:0.24.2": + version: 0.24.2 + resolution: "@esbuild/linux-riscv64@npm:0.24.2" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + "@esbuild/linux-s390x@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/linux-s390x@npm:0.17.19" @@ -5253,6 +5348,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-s390x@npm:0.24.2": + version: 0.24.2 + resolution: "@esbuild/linux-s390x@npm:0.24.2" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + "@esbuild/linux-x64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/linux-x64@npm:0.17.19" @@ -5274,6 +5376,20 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-x64@npm:0.24.2": + version: 0.24.2 + resolution: "@esbuild/linux-x64@npm:0.24.2" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/netbsd-arm64@npm:0.24.2": + version: 0.24.2 + resolution: "@esbuild/netbsd-arm64@npm:0.24.2" + conditions: os=netbsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/netbsd-x64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/netbsd-x64@npm:0.17.19" @@ -5295,6 +5411,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/netbsd-x64@npm:0.24.2": + version: 0.24.2 + resolution: "@esbuild/netbsd-x64@npm:0.24.2" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/openbsd-arm64@npm:0.23.1": version: 0.23.1 resolution: "@esbuild/openbsd-arm64@npm:0.23.1" @@ -5309,6 +5432,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/openbsd-arm64@npm:0.24.2": + version: 0.24.2 + resolution: "@esbuild/openbsd-arm64@npm:0.24.2" + conditions: os=openbsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/openbsd-x64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/openbsd-x64@npm:0.17.19" @@ -5330,6 +5460,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/openbsd-x64@npm:0.24.2": + version: 0.24.2 + resolution: "@esbuild/openbsd-x64@npm:0.24.2" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/sunos-x64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/sunos-x64@npm:0.17.19" @@ -5351,6 +5488,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/sunos-x64@npm:0.24.2": + version: 0.24.2 + resolution: "@esbuild/sunos-x64@npm:0.24.2" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + "@esbuild/win32-arm64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/win32-arm64@npm:0.17.19" @@ -5372,6 +5516,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-arm64@npm:0.24.2": + version: 0.24.2 + resolution: "@esbuild/win32-arm64@npm:0.24.2" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/win32-ia32@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/win32-ia32@npm:0.17.19" @@ -5393,6 +5544,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-ia32@npm:0.24.2": + version: 0.24.2 + resolution: "@esbuild/win32-ia32@npm:0.24.2" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/win32-x64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/win32-x64@npm:0.17.19" @@ -5414,6 +5572,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-x64@npm:0.24.2": + version: 0.24.2 + resolution: "@esbuild/win32-x64@npm:0.24.2" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.4.0": version: 4.4.1 resolution: "@eslint-community/eslint-utils@npm:4.4.1" @@ -7081,12 +7246,12 @@ __metadata: languageName: node linkType: hard -"@keyv/serialize@npm:^1.0.1": - version: 1.0.1 - resolution: "@keyv/serialize@npm:1.0.1" +"@keyv/serialize@npm:^1.0.2": + version: 1.0.2 + resolution: "@keyv/serialize@npm:1.0.2" dependencies: buffer: "npm:^6.0.3" - checksum: 10/b47418bed4fb17e16e136d1071f71d12de6f566bbf4d484a611a2d7063e0e79aedb3164b51153054101b6b7eb2a2b349469d438f70a5fbac22a68f136a535ec4 + checksum: 10/6a42a5778a6b4542f6903ba7e6a17c5bd116441798d75c95fba9908c76c7606db527fad710b5c54abc6175e49b1bbaaafe3b836ad4b91e1af701394134f1d504 languageName: node linkType: hard @@ -7301,9 +7466,9 @@ __metadata: languageName: node linkType: hard -"@napi-rs/cli@npm:3.0.0-alpha.64": - version: 3.0.0-alpha.64 - resolution: "@napi-rs/cli@npm:3.0.0-alpha.64" +"@napi-rs/cli@npm:3.0.0-alpha.65": + version: 3.0.0-alpha.65 + resolution: "@napi-rs/cli@npm:3.0.0-alpha.65" dependencies: "@inquirer/prompts": "npm:^7.0.0" "@napi-rs/cross-toolchain": "npm:^0.0.16" @@ -7330,7 +7495,7 @@ __metadata: bin: napi: ./dist/cli.js napi-raw: ./cli.mjs - checksum: 10/7f9f800e2eed5ad1476a7dcdf7ab024fd056cff3f036e34b6c057fc0f92c7f4c9c521cb85396f1326ef511c452ce0c46f68860a5d4a8a9fde356ef89795cda11 + checksum: 10/455cce2d09f673a2506cac5f515e1e592354ff35473072e4a5bbbe3c9afbeab3a4c10e2b262890345cb15ed4581fd9b8ff41fcfd3eeba5e4d9b922d8bd99fbc7 languageName: node linkType: hard @@ -9203,15 +9368,6 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/api-logs@npm:0.52.1": - version: 0.52.1 - resolution: "@opentelemetry/api-logs@npm:0.52.1" - dependencies: - "@opentelemetry/api": "npm:^1.0.0" - checksum: 10/7515667a41a38014ffda70674c0b77c9c68417cde9f8ce8840e675308b4431f99d879e8d347f1b08486561617f914c07ee704ad6ed8a6522dabc3a81ac39dc88 - languageName: node - linkType: hard - "@opentelemetry/api-logs@npm:0.53.0": version: 0.53.0 resolution: "@opentelemetry/api-logs@npm:0.53.0" @@ -9221,21 +9377,21 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/api-logs@npm:0.54.2": - version: 0.54.2 - resolution: "@opentelemetry/api-logs@npm:0.54.2" +"@opentelemetry/api-logs@npm:0.56.0": + version: 0.56.0 + resolution: "@opentelemetry/api-logs@npm:0.56.0" dependencies: "@opentelemetry/api": "npm:^1.3.0" - checksum: 10/97d887be03ca4a2e69574cc9160464bda00f2a167cc850656ade44b6690a75855d9334983b73827dc44c3672958bc478197f261eae11c2ac68a6df9260c9c3df + checksum: 10/5a6e25015acada7449d11124e9adbbe6670e1e9f7e8b46c60360ac89bb1537f2be326bcf18c66dcbcdee9f34e3a18bd4807c5a40faa0a4ac0135cb3675efb2a9 languageName: node linkType: hard -"@opentelemetry/api-logs@npm:0.56.0": - version: 0.56.0 - resolution: "@opentelemetry/api-logs@npm:0.56.0" +"@opentelemetry/api-logs@npm:0.57.0": + version: 0.57.0 + resolution: "@opentelemetry/api-logs@npm:0.57.0" dependencies: "@opentelemetry/api": "npm:^1.3.0" - checksum: 10/5a6e25015acada7449d11124e9adbbe6670e1e9f7e8b46c60360ac89bb1537f2be326bcf18c66dcbcdee9f34e3a18bd4807c5a40faa0a4ac0135cb3675efb2a9 + checksum: 10/8dd3092f8f2b1fb34a7ace38377925d0ed9bc8633a0a93868ba6a174efabac147e5d36f3060545a39315b209eb0e7ca42514e7a1192a3898bd32506cc7babbe5 languageName: node linkType: hard @@ -9246,154 +9402,203 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/context-async-hooks@npm:1.29.0, @opentelemetry/context-async-hooks@npm:^1.25.1": - version: 1.29.0 - resolution: "@opentelemetry/context-async-hooks@npm:1.29.0" +"@opentelemetry/context-async-hooks@npm:1.30.0, @opentelemetry/context-async-hooks@npm:^1.29.0": + version: 1.30.0 + resolution: "@opentelemetry/context-async-hooks@npm:1.30.0" peerDependencies: "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/bfd960bd03d8c5b2d92447fd7435b86ff919299b56821fe8f99f2ffe19831f0ecadba25bd52a37faff0f98c4fecdcc76e6886e56a3844b367360c6232674ae0e + checksum: 10/0d9a4c2eeeceff55b8267123fa3d36f7659afb71e41a09f4d9980c66178d0dbbfb12a3f995d04a7eae80e8a381a9436801b4f9be845aaca0b44e7ea2eff43478 languageName: node linkType: hard -"@opentelemetry/core@npm:1.26.0": - version: 1.26.0 - resolution: "@opentelemetry/core@npm:1.26.0" +"@opentelemetry/core@npm:1.29.0": + version: 1.29.0 + resolution: "@opentelemetry/core@npm:1.29.0" dependencies: - "@opentelemetry/semantic-conventions": "npm:1.27.0" + "@opentelemetry/semantic-conventions": "npm:1.28.0" peerDependencies: "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/474b6bcf42cd2825d56f915eb0d6e6cdcb37777a11fc2618fc2fa50754f4b9b5df23944f3aab186cb3ab930db5c3a81efa3183362802314a966930110346e6a4 + checksum: 10/eb62dce11cb0cb637acfb3582ad25e48766d9fb37b6b70e57f3c60521c7680a85431a0853c50d98cc8e807e5e3c2fddda314623d879e932bf1a5f629344b39ce languageName: node linkType: hard -"@opentelemetry/core@npm:1.29.0, @opentelemetry/core@npm:^1.1.0, @opentelemetry/core@npm:^1.25.1, @opentelemetry/core@npm:^1.29.0, @opentelemetry/core@npm:^1.8.0": - version: 1.29.0 - resolution: "@opentelemetry/core@npm:1.29.0" +"@opentelemetry/core@npm:1.30.0, @opentelemetry/core@npm:^1.1.0, @opentelemetry/core@npm:^1.26.0, @opentelemetry/core@npm:^1.29.0, @opentelemetry/core@npm:^1.8.0": + version: 1.30.0 + resolution: "@opentelemetry/core@npm:1.30.0" dependencies: "@opentelemetry/semantic-conventions": "npm:1.28.0" peerDependencies: "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/eb62dce11cb0cb637acfb3582ad25e48766d9fb37b6b70e57f3c60521c7680a85431a0853c50d98cc8e807e5e3c2fddda314623d879e932bf1a5f629344b39ce + checksum: 10/6984b7a2ce32a7de0c29fa9a3b03eed0bae730ba61706668939cad6331b168614f6102b5088f71daf4b41686ea31b3082349a850d3da4ff7e48934882be2c0df languageName: node linkType: hard -"@opentelemetry/exporter-logs-otlp-grpc@npm:0.56.0": - version: 0.56.0 - resolution: "@opentelemetry/exporter-logs-otlp-grpc@npm:0.56.0" +"@opentelemetry/exporter-logs-otlp-grpc@npm:0.57.0": + version: 0.57.0 + resolution: "@opentelemetry/exporter-logs-otlp-grpc@npm:0.57.0" dependencies: "@grpc/grpc-js": "npm:^1.7.1" - "@opentelemetry/core": "npm:1.29.0" - "@opentelemetry/otlp-grpc-exporter-base": "npm:0.56.0" - "@opentelemetry/otlp-transformer": "npm:0.56.0" - "@opentelemetry/sdk-logs": "npm:0.56.0" + "@opentelemetry/core": "npm:1.30.0" + "@opentelemetry/otlp-grpc-exporter-base": "npm:0.57.0" + "@opentelemetry/otlp-transformer": "npm:0.57.0" + "@opentelemetry/sdk-logs": "npm:0.57.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/e798faebab931c25941d7d6ebac91dbc0f0fd86b9a6ec641b12ba76ce4675a68acd4d7099ef41d5c040650b0ee2881c61f5cab053dcd280f3cb6dcc29fc450d5 + checksum: 10/b8fdf1de6da8649e5f44bda0d497b00c1c62cdc0cc099e052ba325da760a68dd68f1a4274995603e353ccbd1d614a2d297b41ff957ffb99c2f35791755da0159 languageName: node linkType: hard -"@opentelemetry/exporter-logs-otlp-http@npm:0.56.0": - version: 0.56.0 - resolution: "@opentelemetry/exporter-logs-otlp-http@npm:0.56.0" +"@opentelemetry/exporter-logs-otlp-http@npm:0.57.0": + version: 0.57.0 + resolution: "@opentelemetry/exporter-logs-otlp-http@npm:0.57.0" dependencies: - "@opentelemetry/api-logs": "npm:0.56.0" - "@opentelemetry/core": "npm:1.29.0" - "@opentelemetry/otlp-exporter-base": "npm:0.56.0" - "@opentelemetry/otlp-transformer": "npm:0.56.0" - "@opentelemetry/sdk-logs": "npm:0.56.0" + "@opentelemetry/api-logs": "npm:0.57.0" + "@opentelemetry/core": "npm:1.30.0" + "@opentelemetry/otlp-exporter-base": "npm:0.57.0" + "@opentelemetry/otlp-transformer": "npm:0.57.0" + "@opentelemetry/sdk-logs": "npm:0.57.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/f2313074b0b124a36c292a9f97d6865df4c8d97ff2c4f95a122212c32a7ab91f0a4b6c1e01c2d1fe8c6619d245be81c7d7ab5178ddf2b8cfec15b224ae38c9e5 + checksum: 10/906dca72cef2416a44941901101ee816b121f18893aea3e9b167ea2c48ebd24d6ea927edac66063eca0b15775cf2bad49faba54ca15ae7cb0f49f2e37145d6cf languageName: node linkType: hard -"@opentelemetry/exporter-logs-otlp-proto@npm:0.56.0": - version: 0.56.0 - resolution: "@opentelemetry/exporter-logs-otlp-proto@npm:0.56.0" +"@opentelemetry/exporter-logs-otlp-proto@npm:0.57.0": + version: 0.57.0 + resolution: "@opentelemetry/exporter-logs-otlp-proto@npm:0.57.0" dependencies: - "@opentelemetry/api-logs": "npm:0.56.0" - "@opentelemetry/core": "npm:1.29.0" - "@opentelemetry/otlp-exporter-base": "npm:0.56.0" - "@opentelemetry/otlp-transformer": "npm:0.56.0" - "@opentelemetry/resources": "npm:1.29.0" - "@opentelemetry/sdk-logs": "npm:0.56.0" - "@opentelemetry/sdk-trace-base": "npm:1.29.0" + "@opentelemetry/api-logs": "npm:0.57.0" + "@opentelemetry/core": "npm:1.30.0" + "@opentelemetry/otlp-exporter-base": "npm:0.57.0" + "@opentelemetry/otlp-transformer": "npm:0.57.0" + "@opentelemetry/resources": "npm:1.30.0" + "@opentelemetry/sdk-logs": "npm:0.57.0" + "@opentelemetry/sdk-trace-base": "npm:1.30.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/3d062bd054108a0d40da1295ea11f68d4b71f9cf665bde479498939680497a54e1d542f358bc9b04b3332da15c95b1bdb570f7b60ca1a96909a2675d9f26a1ae + checksum: 10/23230645d1c3a7657bdc68849e9b629dee0dd47b8d9a82ab4da73598966f71910d1598657d9ce76c25bea1e884079f8bfb20b1a6b4511d656f086e81ba3cc8f0 languageName: node linkType: hard -"@opentelemetry/exporter-prometheus@npm:^0.56.0": - version: 0.56.0 - resolution: "@opentelemetry/exporter-prometheus@npm:0.56.0" +"@opentelemetry/exporter-metrics-otlp-grpc@npm:0.57.0": + version: 0.57.0 + resolution: "@opentelemetry/exporter-metrics-otlp-grpc@npm:0.57.0" dependencies: - "@opentelemetry/core": "npm:1.29.0" - "@opentelemetry/resources": "npm:1.29.0" - "@opentelemetry/sdk-metrics": "npm:1.29.0" + "@grpc/grpc-js": "npm:^1.7.1" + "@opentelemetry/core": "npm:1.30.0" + "@opentelemetry/exporter-metrics-otlp-http": "npm:0.57.0" + "@opentelemetry/otlp-exporter-base": "npm:0.57.0" + "@opentelemetry/otlp-grpc-exporter-base": "npm:0.57.0" + "@opentelemetry/otlp-transformer": "npm:0.57.0" + "@opentelemetry/resources": "npm:1.30.0" + "@opentelemetry/sdk-metrics": "npm:1.30.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/3a0e2b47bc5a3be3a4a3edb5d8ea57e5e106187132c8ed0943b00df277bf77a3cfcfd2151149872cab1bcf27013b0b3bc658cc397cba5ba6c9bd4e2a14628c1b + checksum: 10/97f023a3401ef4fd2891d43e041da21b91701db86ea60e1305c584278aa511bf7f8f0e3e5ffca065d80f217da8aeb2d9649e90b21d499b67153a0e5dcbf291a6 languageName: node linkType: hard -"@opentelemetry/exporter-trace-otlp-grpc@npm:0.56.0": - version: 0.56.0 - resolution: "@opentelemetry/exporter-trace-otlp-grpc@npm:0.56.0" +"@opentelemetry/exporter-metrics-otlp-http@npm:0.57.0": + version: 0.57.0 + resolution: "@opentelemetry/exporter-metrics-otlp-http@npm:0.57.0" + dependencies: + "@opentelemetry/core": "npm:1.30.0" + "@opentelemetry/otlp-exporter-base": "npm:0.57.0" + "@opentelemetry/otlp-transformer": "npm:0.57.0" + "@opentelemetry/resources": "npm:1.30.0" + "@opentelemetry/sdk-metrics": "npm:1.30.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/4a443a8d1801a6ecbd22beafbf885e816cff2c31ad826bad21184238c3d10ee790fd09fdac4386ac0ba9fba48e262ce75be842e22eb24bac277ac601ec91fcd3 + languageName: node + linkType: hard + +"@opentelemetry/exporter-metrics-otlp-proto@npm:0.57.0": + version: 0.57.0 + resolution: "@opentelemetry/exporter-metrics-otlp-proto@npm:0.57.0" + dependencies: + "@opentelemetry/core": "npm:1.30.0" + "@opentelemetry/exporter-metrics-otlp-http": "npm:0.57.0" + "@opentelemetry/otlp-exporter-base": "npm:0.57.0" + "@opentelemetry/otlp-transformer": "npm:0.57.0" + "@opentelemetry/resources": "npm:1.30.0" + "@opentelemetry/sdk-metrics": "npm:1.30.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/af8fbd8725350f3cb4a00434275c9d2d67484f06fbe029ad52898034c5e133f82e8a6f3bfcef8d1679da6c294218e649366595f72f294574f5d08df61069e1bc + languageName: node + linkType: hard + +"@opentelemetry/exporter-prometheus@npm:0.57.0, @opentelemetry/exporter-prometheus@npm:^0.57.0": + version: 0.57.0 + resolution: "@opentelemetry/exporter-prometheus@npm:0.57.0" + dependencies: + "@opentelemetry/core": "npm:1.30.0" + "@opentelemetry/resources": "npm:1.30.0" + "@opentelemetry/sdk-metrics": "npm:1.30.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/0d7efa65f097b564c5f84307d4769184569d7ff744be9de9c73b825bb7e05376ec39773d546095aa997807348dec3fc79995d5e0c828388d4fbcb2be8a94561f + languageName: node + linkType: hard + +"@opentelemetry/exporter-trace-otlp-grpc@npm:0.57.0": + version: 0.57.0 + resolution: "@opentelemetry/exporter-trace-otlp-grpc@npm:0.57.0" dependencies: "@grpc/grpc-js": "npm:^1.7.1" - "@opentelemetry/core": "npm:1.29.0" - "@opentelemetry/otlp-grpc-exporter-base": "npm:0.56.0" - "@opentelemetry/otlp-transformer": "npm:0.56.0" - "@opentelemetry/resources": "npm:1.29.0" - "@opentelemetry/sdk-trace-base": "npm:1.29.0" + "@opentelemetry/core": "npm:1.30.0" + "@opentelemetry/otlp-grpc-exporter-base": "npm:0.57.0" + "@opentelemetry/otlp-transformer": "npm:0.57.0" + "@opentelemetry/resources": "npm:1.30.0" + "@opentelemetry/sdk-trace-base": "npm:1.30.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/3092a8c1ae7e092904b135760c7512636e132fc53c1657e5c9beda50ed104cfb0e85fed0fe53c53d95afd6b9403f6a9767106dd581ed5ae4559120c7d0616faa + checksum: 10/43ebc1cfa323643e747b78273d9c9d23f70fc7f5630de4ed1e30f601c01946bcaa423e4a798f3e024948b59deb24604126b50e733a20fc50513b47021010197c languageName: node linkType: hard -"@opentelemetry/exporter-trace-otlp-http@npm:0.56.0": - version: 0.56.0 - resolution: "@opentelemetry/exporter-trace-otlp-http@npm:0.56.0" +"@opentelemetry/exporter-trace-otlp-http@npm:0.57.0": + version: 0.57.0 + resolution: "@opentelemetry/exporter-trace-otlp-http@npm:0.57.0" dependencies: - "@opentelemetry/core": "npm:1.29.0" - "@opentelemetry/otlp-exporter-base": "npm:0.56.0" - "@opentelemetry/otlp-transformer": "npm:0.56.0" - "@opentelemetry/resources": "npm:1.29.0" - "@opentelemetry/sdk-trace-base": "npm:1.29.0" + "@opentelemetry/core": "npm:1.30.0" + "@opentelemetry/otlp-exporter-base": "npm:0.57.0" + "@opentelemetry/otlp-transformer": "npm:0.57.0" + "@opentelemetry/resources": "npm:1.30.0" + "@opentelemetry/sdk-trace-base": "npm:1.30.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/2c9a0fb15ace10ca904c27a06d84b2a60b38ae989d045c3c8466419a5439d3dd5d0584481036ee1675cbf8eb403b863c7d51a086c10ffb5435235375062b33af + checksum: 10/cbc089b800d97c4bda73c2d91c64176e85a40cd3d3201ac290f89954bd5e973248070bedcb12951c202ee5c0bfaca364ca5d8f038da34b45855c7080d550fd96 languageName: node linkType: hard -"@opentelemetry/exporter-trace-otlp-proto@npm:0.56.0": - version: 0.56.0 - resolution: "@opentelemetry/exporter-trace-otlp-proto@npm:0.56.0" +"@opentelemetry/exporter-trace-otlp-proto@npm:0.57.0": + version: 0.57.0 + resolution: "@opentelemetry/exporter-trace-otlp-proto@npm:0.57.0" dependencies: - "@opentelemetry/core": "npm:1.29.0" - "@opentelemetry/otlp-exporter-base": "npm:0.56.0" - "@opentelemetry/otlp-transformer": "npm:0.56.0" - "@opentelemetry/resources": "npm:1.29.0" - "@opentelemetry/sdk-trace-base": "npm:1.29.0" + "@opentelemetry/core": "npm:1.30.0" + "@opentelemetry/otlp-exporter-base": "npm:0.57.0" + "@opentelemetry/otlp-transformer": "npm:0.57.0" + "@opentelemetry/resources": "npm:1.30.0" + "@opentelemetry/sdk-trace-base": "npm:1.30.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/85197740a1744a16f10ee355e250c4e7a77250454266cf9bad1875beb245dd39ba4fb19e87322fae65bf038075698fa5d1b284cc7d76160ee6eff279b728bef3 + checksum: 10/f88137accd811653d4f0a99bbd5e3177fe5d3ed2badc764047d3f25eeaf475cb52ac3b3f8b3f07fb9db828068aebdd925e5420f80aba4af922836078b7adfe04 languageName: node linkType: hard -"@opentelemetry/exporter-zipkin@npm:1.29.0, @opentelemetry/exporter-zipkin@npm:^1.29.0": - version: 1.29.0 - resolution: "@opentelemetry/exporter-zipkin@npm:1.29.0" +"@opentelemetry/exporter-zipkin@npm:1.30.0, @opentelemetry/exporter-zipkin@npm:^1.29.0": + version: 1.30.0 + resolution: "@opentelemetry/exporter-zipkin@npm:1.30.0" dependencies: - "@opentelemetry/core": "npm:1.29.0" - "@opentelemetry/resources": "npm:1.29.0" - "@opentelemetry/sdk-trace-base": "npm:1.29.0" + "@opentelemetry/core": "npm:1.30.0" + "@opentelemetry/resources": "npm:1.30.0" + "@opentelemetry/sdk-trace-base": "npm:1.30.0" "@opentelemetry/semantic-conventions": "npm:1.28.0" peerDependencies: "@opentelemetry/api": ^1.0.0 - checksum: 10/8fdd2873723c48a00b9e82bfc4d878887cb65f68b7843d32e402677758481ce523759f30aa800bd538441dcca85a83765f0b4ae720d1309dbdce0c858fc93ec4 + checksum: 10/9be4fccb983576dae4a74ba9f4cf47c717a2f9300b2c491a4aa0aa074b176a02573cd3473d03d1daa688c3068e1ac432f253fea28ea856778024da8530a3e2a1 languageName: node linkType: hard @@ -9409,105 +9614,94 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/instrumentation-amqplib@npm:^0.43.0": - version: 0.43.0 - resolution: "@opentelemetry/instrumentation-amqplib@npm:0.43.0" +"@opentelemetry/instrumentation-amqplib@npm:^0.45.0": + version: 0.45.0 + resolution: "@opentelemetry/instrumentation-amqplib@npm:0.45.0" dependencies: "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.54.0" + "@opentelemetry/instrumentation": "npm:^0.56.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/5d632e1b1ee8ac6a596aed90aa9e0fe5c9b0a5e1dd34d2bb209bf19227d945f535d07e6d03a2325ae1e90858923535a356745ef6580723531e532923b178d039 + checksum: 10/7a0be9861a12a7baf1a63fecd4eb221eecb9cb422223d1b62f183b14c1999e9ba039450a60bbaad139abab040d48dc843d4c93e8247b39215e31066e4ff9aa17 languageName: node linkType: hard -"@opentelemetry/instrumentation-connect@npm:0.40.0": - version: 0.40.0 - resolution: "@opentelemetry/instrumentation-connect@npm:0.40.0" +"@opentelemetry/instrumentation-connect@npm:0.42.0": + version: 0.42.0 + resolution: "@opentelemetry/instrumentation-connect@npm:0.42.0" dependencies: "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.54.0" + "@opentelemetry/instrumentation": "npm:^0.56.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" "@types/connect": "npm:3.4.36" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/31d6adb3fbc04d4e831730562f57f8c54c6844e5214a31c70d5e855b7363202822d320cca603132ff9e4b4a597ecd4dcfb32ca2725cf5cd226fa239bf8fcd779 + checksum: 10/f03090f36028d654fcbb649d52ec57613b407aceb7e3d61978ab4f737c3617f4dd7061612139e0cb856a9df566d1ff0202f2c426ee3dacaf2a68c1ea5a34be63 languageName: node linkType: hard -"@opentelemetry/instrumentation-dataloader@npm:0.12.0": - version: 0.12.0 - resolution: "@opentelemetry/instrumentation-dataloader@npm:0.12.0" +"@opentelemetry/instrumentation-dataloader@npm:0.15.0": + version: 0.15.0 + resolution: "@opentelemetry/instrumentation-dataloader@npm:0.15.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.53.0" + "@opentelemetry/instrumentation": "npm:^0.56.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/d560b519a6be6572a3bd3707f2035f4e1f8e50b95eee109ee138b9ebfadd1ec7bca288aeabb54e8299746eae9457001162dac6ccd92af5ba7449301e0bb139bd + checksum: 10/2a13e5159f5977f8d14955d6ce6994a6c6249ae25da10975aca782d0a74d74355143cdc4c47c99f36abd71d6097b610aa34611bb64a5d042948bbb788c26dd77 languageName: node linkType: hard -"@opentelemetry/instrumentation-express@npm:0.44.0": - version: 0.44.0 - resolution: "@opentelemetry/instrumentation-express@npm:0.44.0" +"@opentelemetry/instrumentation-express@npm:0.46.0": + version: 0.46.0 + resolution: "@opentelemetry/instrumentation-express@npm:0.46.0" dependencies: "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.54.0" + "@opentelemetry/instrumentation": "npm:^0.56.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/a2ae344c1c2b8346f6957dfadbe4c789a0abf08a5dbcd424c41b320faa5b72d9a399406041792ab6a18093b428958b067d3c66dcd492d9cc5d97a17347d3f88a + checksum: 10/757b074aece5ad34ac082c54f3c4a6aadcbdf6cfb55510de5d77acc3e75e28a3c7f8b80ba5727b5d47ffa70790d0fdc672fc67d5f3854ba2acbb011424ce914c languageName: node linkType: hard -"@opentelemetry/instrumentation-fastify@npm:0.41.0": - version: 0.41.0 - resolution: "@opentelemetry/instrumentation-fastify@npm:0.41.0" +"@opentelemetry/instrumentation-fastify@npm:0.43.0": + version: 0.43.0 + resolution: "@opentelemetry/instrumentation-fastify@npm:0.43.0" dependencies: "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.54.0" + "@opentelemetry/instrumentation": "npm:^0.56.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/6f1af8af8b4ef213a1edde1ba14bc59635078d8953562bc48c431b42503ca91e6a837076093682eb8765a9490b61afdce4e33f966d19c4e436e4c1ffacb70ea1 + checksum: 10/aee85c357ad0f0a88935919aa5bd09547e0b81fd11fc83aabc298fa84e4b3f7d6e64aa03f6273849ce93d11cad591a743a2b73f7838ddf26f54a48b53906259c languageName: node linkType: hard -"@opentelemetry/instrumentation-fs@npm:0.16.0": - version: 0.16.0 - resolution: "@opentelemetry/instrumentation-fs@npm:0.16.0" +"@opentelemetry/instrumentation-fs@npm:0.18.0": + version: 0.18.0 + resolution: "@opentelemetry/instrumentation-fs@npm:0.18.0" dependencies: "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.54.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/01ac3a8c488a85cbd63e8cdb62e4ab228af569c05d731c4615ff90a4fe699e2e619b626d6838f03e7aaeb715a695d6e45a5ba4c5a976e748c04276719924efb9 - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-generic-pool@npm:0.39.0": - version: 0.39.0 - resolution: "@opentelemetry/instrumentation-generic-pool@npm:0.39.0" - dependencies: - "@opentelemetry/instrumentation": "npm:^0.53.0" + "@opentelemetry/instrumentation": "npm:^0.56.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/37b476cdddaf3fa2f83a340dcd6949e70cbead45cf747a953099fdb422cb0e89fd52017d0ca01e74283e5af4caa788eb4d163f81e4f21e6ba8e89d0a0dbc99c5 + checksum: 10/634c23bac8aee7325be2cf2d64f0856d11a9de746f942fec52ebd96a6a7159c63913758c78295c9cb909d950f72c73e1e2685f4d9ed5f9754254ff0ce678dcc6 languageName: node linkType: hard -"@opentelemetry/instrumentation-graphql@npm:0.44.0": - version: 0.44.0 - resolution: "@opentelemetry/instrumentation-graphql@npm:0.44.0" +"@opentelemetry/instrumentation-generic-pool@npm:0.42.0": + version: 0.42.0 + resolution: "@opentelemetry/instrumentation-generic-pool@npm:0.42.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.54.0" + "@opentelemetry/instrumentation": "npm:^0.56.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/fca5234a9adf5bee2b7a0613372e9f5b8cfc4c64243ec84b5fdbae149b1e15ca7398b5eb8c755c8cf82816c7963b080dd6e85d1403e80057dc87ebf81498699a + checksum: 10/09c2cf4b64798ce92ddc0214c6c0443bb7800f94a2a8578f2276f3d8a5dd7018b4f5ec9d82d771b4cedd29b949760ca333c1a4fec0d43244ba338af36ad150b2 languageName: node linkType: hard -"@opentelemetry/instrumentation-graphql@npm:^0.46.0": +"@opentelemetry/instrumentation-graphql@npm:0.46.0": version: 0.46.0 resolution: "@opentelemetry/instrumentation-graphql@npm:0.46.0" dependencies: @@ -9518,34 +9712,31 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/instrumentation-hapi@npm:0.41.0": - version: 0.41.0 - resolution: "@opentelemetry/instrumentation-hapi@npm:0.41.0" +"@opentelemetry/instrumentation-graphql@npm:^0.47.0": + version: 0.47.0 + resolution: "@opentelemetry/instrumentation-graphql@npm:0.47.0" dependencies: - "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.53.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@opentelemetry/instrumentation": "npm:^0.57.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/5025db3e785476757947915e9512d454f565eabc883757d7a122e134f3cb2e5d418142f916e5ab4b2db2bfb9c59ab105f602c19af268442ae07106b5b547fa64 + checksum: 10/1699c89735dd9a1f25df236ba66052aca4a93e4d894657b8495249f0a7ad67691e05ac2db5e3110c85b5c15a22c19325ecee9c70c0eacacf4ec93e8f8370a654 languageName: node linkType: hard -"@opentelemetry/instrumentation-http@npm:0.53.0": - version: 0.53.0 - resolution: "@opentelemetry/instrumentation-http@npm:0.53.0" +"@opentelemetry/instrumentation-hapi@npm:0.44.0": + version: 0.44.0 + resolution: "@opentelemetry/instrumentation-hapi@npm:0.44.0" dependencies: - "@opentelemetry/core": "npm:1.26.0" - "@opentelemetry/instrumentation": "npm:0.53.0" - "@opentelemetry/semantic-conventions": "npm:1.27.0" - semver: "npm:^7.5.2" + "@opentelemetry/core": "npm:^1.8.0" + "@opentelemetry/instrumentation": "npm:^0.56.0" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/c00e71f7a5a03723bf13e55e74dcc8e44d61b87fc38c50821fa6bf86a09d3eca68a62a4ccc6f35e70a6529c36d134eca77889852869d7a5a9b2af73f3fb5f097 + checksum: 10/d8eafb0213ea8c6971b7a21dd3a3f7ea988954cdf23e708c5fc8bd132267709fd449823d496ac2bb8cf93cf164fa452edb16089c307cefcabb54bc99f20b203e languageName: node linkType: hard -"@opentelemetry/instrumentation-http@npm:^0.56.0": +"@opentelemetry/instrumentation-http@npm:0.56.0": version: 0.56.0 resolution: "@opentelemetry/instrumentation-http@npm:0.56.0" dependencies: @@ -9560,20 +9751,22 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/instrumentation-ioredis@npm:0.43.0": - version: 0.43.0 - resolution: "@opentelemetry/instrumentation-ioredis@npm:0.43.0" +"@opentelemetry/instrumentation-http@npm:^0.57.0": + version: 0.57.0 + resolution: "@opentelemetry/instrumentation-http@npm:0.57.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.53.0" - "@opentelemetry/redis-common": "npm:^0.36.2" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@opentelemetry/core": "npm:1.30.0" + "@opentelemetry/instrumentation": "npm:0.57.0" + "@opentelemetry/semantic-conventions": "npm:1.28.0" + forwarded-parse: "npm:2.1.2" + semver: "npm:^7.5.2" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/fa405f521134a375c3ae1894d39da2a62bd021695fbc6a28d7efe61202d9a3b895047cf59353d6773e5d8528aea24a63841110ba48800132f5aac47615603c10 + checksum: 10/d56b8d3a54aceea74a4c18dcae70a16e9e448b90b2c65886d8ee17b1a8bf5c56fef60e3ceefadf47702c6d0500f2d6b73a6dbe5710f77df40e726ff447810c3e languageName: node linkType: hard -"@opentelemetry/instrumentation-ioredis@npm:^0.46.0": +"@opentelemetry/instrumentation-ioredis@npm:0.46.0": version: 0.46.0 resolution: "@opentelemetry/instrumentation-ioredis@npm:0.46.0" dependencies: @@ -9586,118 +9779,119 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/instrumentation-kafkajs@npm:0.4.0": - version: 0.4.0 - resolution: "@opentelemetry/instrumentation-kafkajs@npm:0.4.0" +"@opentelemetry/instrumentation-ioredis@npm:^0.47.0": + version: 0.47.0 + resolution: "@opentelemetry/instrumentation-ioredis@npm:0.47.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.54.0" + "@opentelemetry/instrumentation": "npm:^0.57.0" + "@opentelemetry/redis-common": "npm:^0.36.2" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/e5abcbbf2a458c3754d8a5790cf364384c84f51929ec66973ae1390020ef945a4be3d42db214a6738362a9d319e03ad6df0abc9470b2107568728d1e42f7ea94 + checksum: 10/3a885546c950db88ac71c2506544d3e977c561fbfdbe53b4e9d071a017968d5b6ef347dbd64954ae2d315fdd0209429832156438d9eb904bb6c576ed2ff79af1 languageName: node linkType: hard -"@opentelemetry/instrumentation-knex@npm:0.41.0": - version: 0.41.0 - resolution: "@opentelemetry/instrumentation-knex@npm:0.41.0" +"@opentelemetry/instrumentation-kafkajs@npm:0.6.0": + version: 0.6.0 + resolution: "@opentelemetry/instrumentation-kafkajs@npm:0.6.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.54.0" + "@opentelemetry/instrumentation": "npm:^0.56.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/273dbaf08f5256e2f8390b7846532baba6f4f9f39593c01d7fc756af346906b21214ac7b7007d4b7b7c2279acda4180b9ac89bc0e40befc1ccec80a64e2b75cb + checksum: 10/85723b216fea262d65b6aad9a24242842b0716e06424c6a94a1b9299470c3756dc9d128816f7053bf68eb46bde41d545385e47ac680e2aeff609b3fbc7c6b135 languageName: node linkType: hard -"@opentelemetry/instrumentation-koa@npm:0.43.0": +"@opentelemetry/instrumentation-knex@npm:0.43.0": version: 0.43.0 - resolution: "@opentelemetry/instrumentation-koa@npm:0.43.0" + resolution: "@opentelemetry/instrumentation-knex@npm:0.43.0" dependencies: - "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.53.0" + "@opentelemetry/instrumentation": "npm:^0.56.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/b494196962c0840651e5fdec7350a8d9f443ee9e682e4c20c8b47ed82c6c34875adc7fd467ac04c3838edbf14bf79aafddb889f2755fc1957f27275a08442e83 + checksum: 10/bb801ba2198b33e0b4feba84313ad54d502969d5fddea9de26ebbeb1a59ade61f03eed2aea983f43682b53102de837916f6dd44eede3a55237980dc99527bf6c languageName: node linkType: hard -"@opentelemetry/instrumentation-lru-memoizer@npm:0.40.0": - version: 0.40.0 - resolution: "@opentelemetry/instrumentation-lru-memoizer@npm:0.40.0" +"@opentelemetry/instrumentation-koa@npm:0.46.0": + version: 0.46.0 + resolution: "@opentelemetry/instrumentation-koa@npm:0.46.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.53.0" + "@opentelemetry/core": "npm:^1.8.0" + "@opentelemetry/instrumentation": "npm:^0.56.0" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/07bb795faedb0c01bf7dd2cc660431b2303fd1f3a904b3fcc06eb601fde94653f8391a40ccf101a391893187a68381ab6ea8a284118fff328d32b130fac2ea6c + checksum: 10/63d2209e7acb53d7183cd1926656d388a60cca34f3517a26af101acdb4e5612c123071076828a49e18f288a5d694a2f7994f95ee48dab8312f6c2904321d2e4a languageName: node linkType: hard -"@opentelemetry/instrumentation-mongodb@npm:0.48.0": - version: 0.48.0 - resolution: "@opentelemetry/instrumentation-mongodb@npm:0.48.0" +"@opentelemetry/instrumentation-lru-memoizer@npm:0.43.0": + version: 0.43.0 + resolution: "@opentelemetry/instrumentation-lru-memoizer@npm:0.43.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.54.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@opentelemetry/instrumentation": "npm:^0.56.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/84b6cef3d80086a05783c211cbfb62bf9ef59c9b17dcfd943fb13570eb2fe45c91dd07823a2aa81e2f81e3689a20cf0d02c6f765d4f9a1af67b75b44cd0293c9 + checksum: 10/5c1ea5e1f453dea989285199fe8385f31f17982fbc14eb504dc801f5e3a97f8e3fdc4d0f23212ec3cc522e84c1b5750073192b4a33b8a8302cdd18f4935280cb languageName: node linkType: hard -"@opentelemetry/instrumentation-mongoose@npm:0.42.0": - version: 0.42.0 - resolution: "@opentelemetry/instrumentation-mongoose@npm:0.42.0" +"@opentelemetry/instrumentation-mongodb@npm:0.50.0": + version: 0.50.0 + resolution: "@opentelemetry/instrumentation-mongodb@npm:0.50.0" dependencies: - "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.53.0" + "@opentelemetry/instrumentation": "npm:^0.56.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/58c3ba89ce43830451dcc105a2ebf352b296cf6b1b8f6194ac69c1fa39c18e50ee0092f8e514a27046cf35e0ade391425f7adf0e6e6b1fd8dbbec2b01f393be2 + checksum: 10/beee37f0be94c614c620ea431ea7315c9293ea14f3b8f751075a5b4d663ed3daa4fe73ef9f58af812ab38d6b59d511da6252386d1fc7aab4d60b4384a8c2065c languageName: node linkType: hard -"@opentelemetry/instrumentation-mysql2@npm:0.41.0": - version: 0.41.0 - resolution: "@opentelemetry/instrumentation-mysql2@npm:0.41.0" +"@opentelemetry/instrumentation-mongoose@npm:0.45.0": + version: 0.45.0 + resolution: "@opentelemetry/instrumentation-mongoose@npm:0.45.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.53.0" + "@opentelemetry/core": "npm:^1.8.0" + "@opentelemetry/instrumentation": "npm:^0.56.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" - "@opentelemetry/sql-common": "npm:^0.40.1" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/40f48b3f87bda347db2332020f0880223f49a894e0312d03e1f86aa48b8335b6db65955ea775b8bec2a687672bdbd9c0997294acdd4cf51765da0e22e1d98a35 + checksum: 10/a4872b8c7783ace3510f954957aaf6880dee89c00f44a55153e463b5ffeacfdb896ca285cb8b816a746e7dec7f69901d0625c959cbdb09f4dfcba123dfa82213 languageName: node linkType: hard -"@opentelemetry/instrumentation-mysql@npm:0.41.0": - version: 0.41.0 - resolution: "@opentelemetry/instrumentation-mysql@npm:0.41.0" +"@opentelemetry/instrumentation-mysql2@npm:0.44.0": + version: 0.44.0 + resolution: "@opentelemetry/instrumentation-mysql2@npm:0.44.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.53.0" + "@opentelemetry/instrumentation": "npm:^0.56.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" - "@types/mysql": "npm:2.15.26" + "@opentelemetry/sql-common": "npm:^0.40.1" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/20ff56edc0b74cf8be2dd5960e210a6c20568169af5768fd78bb33f5a626e271fe2ac6cf7ad0e9629ff932a18feac04db99fffa3c867b27c679523dd2f4570d3 + checksum: 10/8ba5e66d3f994a3473df480bfd417b3e870b2fa25ae77b74ed1e516afb4ad38d1625d36c3947b02d9e52298edda1ea5f2df008a7c5bc8543455d760deb9fe3e1 languageName: node linkType: hard -"@opentelemetry/instrumentation-nestjs-core@npm:0.40.0": - version: 0.40.0 - resolution: "@opentelemetry/instrumentation-nestjs-core@npm:0.40.0" +"@opentelemetry/instrumentation-mysql@npm:0.44.0": + version: 0.44.0 + resolution: "@opentelemetry/instrumentation-mysql@npm:0.44.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.53.0" + "@opentelemetry/instrumentation": "npm:^0.56.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@types/mysql": "npm:2.15.26" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/421f3e18c651b74383d5cd6a231431ecda3e49262f934dca27bf2272fe58334cbe2acf2f62ce5d82c0893d6f899e2921dfc6a6f78ab27f84a35bd8bfb77df9e4 + checksum: 10/7043e7446280be01ff12c88cbe843d131543c48ee73894b2862e9970cf380b43538f82a31fe32569d1fc2627acf0ccd1635e7ba3858ae93d7163a15b293036a8 languageName: node linkType: hard -"@opentelemetry/instrumentation-nestjs-core@npm:^0.43.0": +"@opentelemetry/instrumentation-nestjs-core@npm:0.43.0": version: 0.43.0 resolution: "@opentelemetry/instrumentation-nestjs-core@npm:0.43.0" dependencies: @@ -9709,84 +9903,81 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/instrumentation-pg@npm:0.44.0": +"@opentelemetry/instrumentation-nestjs-core@npm:^0.44.0": version: 0.44.0 - resolution: "@opentelemetry/instrumentation-pg@npm:0.44.0" + resolution: "@opentelemetry/instrumentation-nestjs-core@npm:0.44.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.53.0" + "@opentelemetry/instrumentation": "npm:^0.57.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/0191ec6c6784c27a2c8c21438a1e7e3b8752bd9d691098f88ae7a18124ac5f5b221271ea264728ecc600803a85ca488b0193066dc86f9a9e71da3c6e7296f0ee + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-pg@npm:0.49.0": + version: 0.49.0 + resolution: "@opentelemetry/instrumentation-pg@npm:0.49.0" + dependencies: + "@opentelemetry/core": "npm:^1.26.0" + "@opentelemetry/instrumentation": "npm:^0.56.0" + "@opentelemetry/semantic-conventions": "npm:1.27.0" "@opentelemetry/sql-common": "npm:^0.40.1" "@types/pg": "npm:8.6.1" "@types/pg-pool": "npm:2.0.6" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/d902682a3630ff1ef392624165b46a2b4fe0fd696f42a588030f2c4ba73ccd2631792cf6b122bad0dfddb929044b96c285f63517704e7ccaf699a77150f5f3d9 + checksum: 10/04457e441872a0afecfa471f04bbecdf4b62782d110cd1a75423f9ff12c34396896056dda99923690f4a26c572fccc0c28d25fbd26e71f07ca2778e61fc3a7d1 languageName: node linkType: hard -"@opentelemetry/instrumentation-redis-4@npm:0.42.0": - version: 0.42.0 - resolution: "@opentelemetry/instrumentation-redis-4@npm:0.42.0" +"@opentelemetry/instrumentation-redis-4@npm:0.45.0": + version: 0.45.0 + resolution: "@opentelemetry/instrumentation-redis-4@npm:0.45.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.53.0" + "@opentelemetry/instrumentation": "npm:^0.56.0" "@opentelemetry/redis-common": "npm:^0.36.2" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/d5ff240b826525cdc9935ab2885f65ea5c5d77ad31e9ee8142e6840b1c1603db025370b67fb828580a242fe7ff815d1335ff3845c48d8b94070f3683f71b0898 + checksum: 10/8e2cd153936681b9a1e13a1e059df18d31a8d2a85c0db11e3d3b00c496f360efe3c864acac9e87de80cc13318f17ce35ecfe0aea22d502b42109cd1164e39619 languageName: node linkType: hard -"@opentelemetry/instrumentation-socket.io@npm:^0.45.0": - version: 0.45.0 - resolution: "@opentelemetry/instrumentation-socket.io@npm:0.45.0" +"@opentelemetry/instrumentation-socket.io@npm:^0.46.0": + version: 0.46.0 + resolution: "@opentelemetry/instrumentation-socket.io@npm:0.46.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.56.0" + "@opentelemetry/instrumentation": "npm:^0.57.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/fa0550011cde7a06f5c25f655c9ea1d0df284d63e2eb61599add27a16225d952b2c573364478654f9a0694f4716334f5618ffa9e8991bb078b202b78041cd7c0 + checksum: 10/361c9163e3288b6aa93d354568e8370d7546d8c5104623c11902c9d665678e58490350e6401f5cda1cfadf22f5d1884c4f05801d53e942091a7b29c022ace8c8 languageName: node linkType: hard -"@opentelemetry/instrumentation-tedious@npm:0.15.0": - version: 0.15.0 - resolution: "@opentelemetry/instrumentation-tedious@npm:0.15.0" +"@opentelemetry/instrumentation-tedious@npm:0.17.0": + version: 0.17.0 + resolution: "@opentelemetry/instrumentation-tedious@npm:0.17.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.54.0" + "@opentelemetry/instrumentation": "npm:^0.56.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" "@types/tedious": "npm:^4.0.14" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/adbc5a444a28c4732aafd8a8742e1ffea100325a91005b8417cd50625952c537842f3d6f6c9c29aa468e0b5d850b285fa43617cde0bcbc463da207b38d0eee08 + checksum: 10/62594f42fddc7af06aa14575e70d821551842671d2f2013e583d58d508d6ca8e6749faf89453e8254e6257483390cf8fd9ca0076327850d54e088d436f13ac51 languageName: node linkType: hard -"@opentelemetry/instrumentation-undici@npm:0.6.0": - version: 0.6.0 - resolution: "@opentelemetry/instrumentation-undici@npm:0.6.0" +"@opentelemetry/instrumentation-undici@npm:0.9.0": + version: 0.9.0 + resolution: "@opentelemetry/instrumentation-undici@npm:0.9.0" dependencies: "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.53.0" + "@opentelemetry/instrumentation": "npm:^0.56.0" peerDependencies: "@opentelemetry/api": ^1.7.0 - checksum: 10/97291ecca9ff936dc4a418b380542f4dbb1f891692df44292dd61dc9e39aa1c347b70666cda5c30fbd78969d3b6ea602a6bafb30566b65eec0e00bcac459b2c4 - languageName: node - linkType: hard - -"@opentelemetry/instrumentation@npm:0.53.0, @opentelemetry/instrumentation@npm:^0.49 || ^0.50 || ^0.51 || ^0.52.0 || ^0.53.0, @opentelemetry/instrumentation@npm:^0.53.0": - version: 0.53.0 - resolution: "@opentelemetry/instrumentation@npm:0.53.0" - dependencies: - "@opentelemetry/api-logs": "npm:0.53.0" - "@types/shimmer": "npm:^1.2.0" - import-in-the-middle: "npm:^1.8.1" - require-in-the-middle: "npm:^7.1.1" - semver: "npm:^7.5.2" - shimmer: "npm:^1.2.1" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/4b994c8568a503a15655cba249b1dbdef3f67dfda37938abba6267ba75b6d72a9aa276be4b0c8874e86f98ab89d92877e1874e0565a7e67f062c43dfcbbb16a5 + checksum: 10/e7300fb0353cc0648cc8a1779287e6780b6aaf3b4f13698a203367cfb9d571677e8f5897b3e79cfede196e72007ac2a1f4fbb8128a61ceb2b85517ebb30effd4 languageName: node linkType: hard @@ -9806,27 +9997,27 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/instrumentation@npm:^0.49 || ^0.50 || ^0.51 || ^0.52.0": - version: 0.52.1 - resolution: "@opentelemetry/instrumentation@npm:0.52.1" +"@opentelemetry/instrumentation@npm:0.57.0, @opentelemetry/instrumentation@npm:^0.57.0": + version: 0.57.0 + resolution: "@opentelemetry/instrumentation@npm:0.57.0" dependencies: - "@opentelemetry/api-logs": "npm:0.52.1" - "@types/shimmer": "npm:^1.0.2" + "@opentelemetry/api-logs": "npm:0.57.0" + "@types/shimmer": "npm:^1.2.0" import-in-the-middle: "npm:^1.8.1" require-in-the-middle: "npm:^7.1.1" semver: "npm:^7.5.2" shimmer: "npm:^1.2.1" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/87761bd593f2b905d88d0531a3a2a7f4b0186334ae413b4c172a86bd4de0fd6d2f906a1bfd9dd7bd172a228a44fa7a680f5802a1570dfe2fadad0768e80bd7a8 + checksum: 10/f6ccd277393421a769de3408770264d51fa020b258b1a7492f1b3812b7277e76800ae1d0ac8894b70629112126669f9b11b8be01135676d40ffa37c2d373d2f5 languageName: node linkType: hard -"@opentelemetry/instrumentation@npm:^0.54.0": - version: 0.54.2 - resolution: "@opentelemetry/instrumentation@npm:0.54.2" +"@opentelemetry/instrumentation@npm:^0.49 || ^0.50 || ^0.51 || ^0.52.0 || ^0.53.0": + version: 0.53.0 + resolution: "@opentelemetry/instrumentation@npm:0.53.0" dependencies: - "@opentelemetry/api-logs": "npm:0.54.2" + "@opentelemetry/api-logs": "npm:0.53.0" "@types/shimmer": "npm:^1.2.0" import-in-the-middle: "npm:^1.8.1" require-in-the-middle: "npm:^7.1.1" @@ -9834,72 +10025,72 @@ __metadata: shimmer: "npm:^1.2.1" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/1c570fb2e55d2ea7dcc45103afb53ffc331efb675dc404783639c0ed4c93e4e0fa04751672f75ca2a633ca03943e520cf802ee0291e79fa33be54a097af46fc6 + checksum: 10/4b994c8568a503a15655cba249b1dbdef3f67dfda37938abba6267ba75b6d72a9aa276be4b0c8874e86f98ab89d92877e1874e0565a7e67f062c43dfcbbb16a5 languageName: node linkType: hard -"@opentelemetry/otlp-exporter-base@npm:0.56.0": - version: 0.56.0 - resolution: "@opentelemetry/otlp-exporter-base@npm:0.56.0" +"@opentelemetry/otlp-exporter-base@npm:0.57.0": + version: 0.57.0 + resolution: "@opentelemetry/otlp-exporter-base@npm:0.57.0" dependencies: - "@opentelemetry/core": "npm:1.29.0" - "@opentelemetry/otlp-transformer": "npm:0.56.0" + "@opentelemetry/core": "npm:1.30.0" + "@opentelemetry/otlp-transformer": "npm:0.57.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/a640641ae86a9dceeecb84e32e4b8cd56be9d2af202e44ad84022380530bcda4b27b4f7089b87238db8088930256550d385aeca200369021a8085e1288ef34db + checksum: 10/5a4e9b691b221b2218fd018e72ed8072cb9fb278f873f763aa5c6ac84ba38d32e33d6a0b5bca746cffcdccccdb567f4129856ad6fadd0d769e8cfaad9f695dda languageName: node linkType: hard -"@opentelemetry/otlp-grpc-exporter-base@npm:0.56.0": - version: 0.56.0 - resolution: "@opentelemetry/otlp-grpc-exporter-base@npm:0.56.0" +"@opentelemetry/otlp-grpc-exporter-base@npm:0.57.0": + version: 0.57.0 + resolution: "@opentelemetry/otlp-grpc-exporter-base@npm:0.57.0" dependencies: "@grpc/grpc-js": "npm:^1.7.1" - "@opentelemetry/core": "npm:1.29.0" - "@opentelemetry/otlp-exporter-base": "npm:0.56.0" - "@opentelemetry/otlp-transformer": "npm:0.56.0" + "@opentelemetry/core": "npm:1.30.0" + "@opentelemetry/otlp-exporter-base": "npm:0.57.0" + "@opentelemetry/otlp-transformer": "npm:0.57.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/8dd8117357035fff225d23637d0d2298b842f1b5611bf17612aff666a41068ad4d986868818a501a2136f91d788905fb99952b19d60e96fd08f2898422dc22a3 + checksum: 10/2a060b4dd8676a43d42f13a5bd5be72d8ea9f442b8965796ca044c4ea4c9f7349406ff0ec5c3bc3d55e4bde8d227c56aff6b9d787c6a3fe035aa8d0f0e162b5a languageName: node linkType: hard -"@opentelemetry/otlp-transformer@npm:0.56.0": - version: 0.56.0 - resolution: "@opentelemetry/otlp-transformer@npm:0.56.0" +"@opentelemetry/otlp-transformer@npm:0.57.0": + version: 0.57.0 + resolution: "@opentelemetry/otlp-transformer@npm:0.57.0" dependencies: - "@opentelemetry/api-logs": "npm:0.56.0" - "@opentelemetry/core": "npm:1.29.0" - "@opentelemetry/resources": "npm:1.29.0" - "@opentelemetry/sdk-logs": "npm:0.56.0" - "@opentelemetry/sdk-metrics": "npm:1.29.0" - "@opentelemetry/sdk-trace-base": "npm:1.29.0" + "@opentelemetry/api-logs": "npm:0.57.0" + "@opentelemetry/core": "npm:1.30.0" + "@opentelemetry/resources": "npm:1.30.0" + "@opentelemetry/sdk-logs": "npm:0.57.0" + "@opentelemetry/sdk-metrics": "npm:1.30.0" + "@opentelemetry/sdk-trace-base": "npm:1.30.0" protobufjs: "npm:^7.3.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/d673b0295fac96b45535e86dea54c7c8e1270c161579de908cf7724fee00f4d5b49652da0a886dcac49c5d12a0d8f2b8be1cf639f4094bc0d16eec7808bddb78 + checksum: 10/9277a0d307f5956f389dcc65d5d3a61be64b0c7c9fa22d86900a9e10bf191b473279d4504848e7ecab1cdb863a99bbe15d2e920f13c162fb327d832a781b1e68 languageName: node linkType: hard -"@opentelemetry/propagator-b3@npm:1.29.0": - version: 1.29.0 - resolution: "@opentelemetry/propagator-b3@npm:1.29.0" +"@opentelemetry/propagator-b3@npm:1.30.0": + version: 1.30.0 + resolution: "@opentelemetry/propagator-b3@npm:1.30.0" dependencies: - "@opentelemetry/core": "npm:1.29.0" + "@opentelemetry/core": "npm:1.30.0" peerDependencies: "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/002e9400d9b605f1af1140dc4d20579d1494d7eb4c93e72862ee5207cc46cd3ed54f41eb42a0a00f0676b3e8f0f1afb2df36918582d833c97fbf4a23f5632f75 + checksum: 10/4788ebb579bb635d02cb9b3e469cc8cfecb683852d35977af6012c1976a4308d37fb0df86aba5f75fd459c504e28262f62c9c729589e75f4f75e396671888330 languageName: node linkType: hard -"@opentelemetry/propagator-jaeger@npm:1.29.0": - version: 1.29.0 - resolution: "@opentelemetry/propagator-jaeger@npm:1.29.0" +"@opentelemetry/propagator-jaeger@npm:1.30.0": + version: 1.30.0 + resolution: "@opentelemetry/propagator-jaeger@npm:1.30.0" dependencies: - "@opentelemetry/core": "npm:1.29.0" + "@opentelemetry/core": "npm:1.30.0" peerDependencies: "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/75849bcfaa60823cc8b2bb10a4094ef7d933d998c84977f8319930ed7fdba37aba5906ba56f5ed96f28e07bb2cf5e185b7340993c06a36135a1de25de945df74 + checksum: 10/716f25f506087068d529fa03b9d70f4ff1d3d0ee945e9386e073950e8afebd55b787c35e217022a6694841a299039b08376405f69ac6d212e10559810afd24d1 languageName: node linkType: hard @@ -9910,95 +10101,99 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/resources@npm:1.29.0, @opentelemetry/resources@npm:^1.26.0, @opentelemetry/resources@npm:^1.29.0": - version: 1.29.0 - resolution: "@opentelemetry/resources@npm:1.29.0" +"@opentelemetry/resources@npm:1.30.0, @opentelemetry/resources@npm:^1.29.0": + version: 1.30.0 + resolution: "@opentelemetry/resources@npm:1.30.0" dependencies: - "@opentelemetry/core": "npm:1.29.0" + "@opentelemetry/core": "npm:1.30.0" "@opentelemetry/semantic-conventions": "npm:1.28.0" peerDependencies: "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/677b9e3478a380e93383a223a01ccade21dde7381924a4f859b2309ea82e79da9e7257338791a5b2699f763b0c198ec7e0ed6a12f03e467f5e0d8287757f0f66 + checksum: 10/c9f9bac18b09ff6ad5bcb127de11a8dbd46a39154ac2659ab1c9f94781a9e4fd41d7989d44fc09627f489c903ad9476e8b1d2d958eb9293e05f3a00840760b17 languageName: node linkType: hard -"@opentelemetry/sdk-logs@npm:0.56.0": - version: 0.56.0 - resolution: "@opentelemetry/sdk-logs@npm:0.56.0" +"@opentelemetry/sdk-logs@npm:0.57.0": + version: 0.57.0 + resolution: "@opentelemetry/sdk-logs@npm:0.57.0" dependencies: - "@opentelemetry/api-logs": "npm:0.56.0" - "@opentelemetry/core": "npm:1.29.0" - "@opentelemetry/resources": "npm:1.29.0" + "@opentelemetry/api-logs": "npm:0.57.0" + "@opentelemetry/core": "npm:1.30.0" + "@opentelemetry/resources": "npm:1.30.0" peerDependencies: "@opentelemetry/api": ">=1.4.0 <1.10.0" - checksum: 10/36f7ef72ce9f1ccc6f8bb1b99a142801bb3e6e4e2f2dda30de713144213c62b144530bb49590379bc413757c8d170fba874619d933f42d0370c5473c22fdeea0 + checksum: 10/c2181d45f6738e4e0e89159db740ac42eeb40b5d26670ef6d04b686ebc3d95cc6d4690b29dd67fb64c49fda1151e7110d8eccb90a3cc7f33018599e4f693d3af languageName: node linkType: hard -"@opentelemetry/sdk-metrics@npm:1.29.0, @opentelemetry/sdk-metrics@npm:^1.29.0, @opentelemetry/sdk-metrics@npm:^1.8.0": - version: 1.29.0 - resolution: "@opentelemetry/sdk-metrics@npm:1.29.0" +"@opentelemetry/sdk-metrics@npm:1.30.0, @opentelemetry/sdk-metrics@npm:^1.29.0, @opentelemetry/sdk-metrics@npm:^1.8.0": + version: 1.30.0 + resolution: "@opentelemetry/sdk-metrics@npm:1.30.0" dependencies: - "@opentelemetry/core": "npm:1.29.0" - "@opentelemetry/resources": "npm:1.29.0" + "@opentelemetry/core": "npm:1.30.0" + "@opentelemetry/resources": "npm:1.30.0" peerDependencies: "@opentelemetry/api": ">=1.3.0 <1.10.0" - checksum: 10/399f28da37d7176f4017cffb2b9c3d3db65bbe397e5f7232c0b2df1a9162f094d7e460e64058c959aca787a80d48e0f98b9b65dfb711b72f41fa04b5b03b0e23 - languageName: node - linkType: hard - -"@opentelemetry/sdk-node@npm:^0.56.0": - version: 0.56.0 - resolution: "@opentelemetry/sdk-node@npm:0.56.0" - dependencies: - "@opentelemetry/api-logs": "npm:0.56.0" - "@opentelemetry/core": "npm:1.29.0" - "@opentelemetry/exporter-logs-otlp-grpc": "npm:0.56.0" - "@opentelemetry/exporter-logs-otlp-http": "npm:0.56.0" - "@opentelemetry/exporter-logs-otlp-proto": "npm:0.56.0" - "@opentelemetry/exporter-trace-otlp-grpc": "npm:0.56.0" - "@opentelemetry/exporter-trace-otlp-http": "npm:0.56.0" - "@opentelemetry/exporter-trace-otlp-proto": "npm:0.56.0" - "@opentelemetry/exporter-zipkin": "npm:1.29.0" - "@opentelemetry/instrumentation": "npm:0.56.0" - "@opentelemetry/resources": "npm:1.29.0" - "@opentelemetry/sdk-logs": "npm:0.56.0" - "@opentelemetry/sdk-metrics": "npm:1.29.0" - "@opentelemetry/sdk-trace-base": "npm:1.29.0" - "@opentelemetry/sdk-trace-node": "npm:1.29.0" + checksum: 10/07e1aada97018ef2ee0028bbf24737855c7b0692e4c0e40b916c06abffb7dc43920f3939ac872be9ac3ce7a2caa9d6f25504b917e9e0c595be4ef9199a573aaa + languageName: node + linkType: hard + +"@opentelemetry/sdk-node@npm:^0.57.0": + version: 0.57.0 + resolution: "@opentelemetry/sdk-node@npm:0.57.0" + dependencies: + "@opentelemetry/api-logs": "npm:0.57.0" + "@opentelemetry/core": "npm:1.30.0" + "@opentelemetry/exporter-logs-otlp-grpc": "npm:0.57.0" + "@opentelemetry/exporter-logs-otlp-http": "npm:0.57.0" + "@opentelemetry/exporter-logs-otlp-proto": "npm:0.57.0" + "@opentelemetry/exporter-metrics-otlp-grpc": "npm:0.57.0" + "@opentelemetry/exporter-metrics-otlp-http": "npm:0.57.0" + "@opentelemetry/exporter-metrics-otlp-proto": "npm:0.57.0" + "@opentelemetry/exporter-prometheus": "npm:0.57.0" + "@opentelemetry/exporter-trace-otlp-grpc": "npm:0.57.0" + "@opentelemetry/exporter-trace-otlp-http": "npm:0.57.0" + "@opentelemetry/exporter-trace-otlp-proto": "npm:0.57.0" + "@opentelemetry/exporter-zipkin": "npm:1.30.0" + "@opentelemetry/instrumentation": "npm:0.57.0" + "@opentelemetry/resources": "npm:1.30.0" + "@opentelemetry/sdk-logs": "npm:0.57.0" + "@opentelemetry/sdk-metrics": "npm:1.30.0" + "@opentelemetry/sdk-trace-base": "npm:1.30.0" + "@opentelemetry/sdk-trace-node": "npm:1.30.0" "@opentelemetry/semantic-conventions": "npm:1.28.0" peerDependencies: "@opentelemetry/api": ">=1.3.0 <1.10.0" - checksum: 10/ba89ba4ef5ba7b4d75fb167b4bc5247602202d85d0da431c863932a83a313bd82aaad00f0fb3f3ef75a47242c474a5066a967649a585ee0f7cdbec6ddedfa39b + checksum: 10/f3500eae8306c2323ea700d05c8b8efca11ab561054d9a80e10591eb0518a0c277528741a81e77338e35e6ab8cf95a73e5f5be1eb2c3b5919b3dbe871fb2b1dd languageName: node linkType: hard -"@opentelemetry/sdk-trace-base@npm:1.29.0, @opentelemetry/sdk-trace-base@npm:^1.22, @opentelemetry/sdk-trace-base@npm:^1.26.0": - version: 1.29.0 - resolution: "@opentelemetry/sdk-trace-base@npm:1.29.0" +"@opentelemetry/sdk-trace-base@npm:1.30.0, @opentelemetry/sdk-trace-base@npm:^1.22, @opentelemetry/sdk-trace-base@npm:^1.29.0": + version: 1.30.0 + resolution: "@opentelemetry/sdk-trace-base@npm:1.30.0" dependencies: - "@opentelemetry/core": "npm:1.29.0" - "@opentelemetry/resources": "npm:1.29.0" + "@opentelemetry/core": "npm:1.30.0" + "@opentelemetry/resources": "npm:1.30.0" "@opentelemetry/semantic-conventions": "npm:1.28.0" peerDependencies: "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/97080188cd2ded16cc489ad5414255f0e63a81ca3f8778f604cdb1409bf95691529d7f9233d37788ac14a6ff36b736fcf274ef39bba3fdf421e7201642d2d5b4 + checksum: 10/2e926c0cf29b6319a263fd7a1dbf157170e75b7ab329b11b1990130dd1591cea4590d67c6208ef7d63bf5742e4ab5811d64672804568438f2e751dbe1ac98958 languageName: node linkType: hard -"@opentelemetry/sdk-trace-node@npm:1.29.0, @opentelemetry/sdk-trace-node@npm:^1.29.0": - version: 1.29.0 - resolution: "@opentelemetry/sdk-trace-node@npm:1.29.0" +"@opentelemetry/sdk-trace-node@npm:1.30.0, @opentelemetry/sdk-trace-node@npm:^1.29.0": + version: 1.30.0 + resolution: "@opentelemetry/sdk-trace-node@npm:1.30.0" dependencies: - "@opentelemetry/context-async-hooks": "npm:1.29.0" - "@opentelemetry/core": "npm:1.29.0" - "@opentelemetry/propagator-b3": "npm:1.29.0" - "@opentelemetry/propagator-jaeger": "npm:1.29.0" - "@opentelemetry/sdk-trace-base": "npm:1.29.0" + "@opentelemetry/context-async-hooks": "npm:1.30.0" + "@opentelemetry/core": "npm:1.30.0" + "@opentelemetry/propagator-b3": "npm:1.30.0" + "@opentelemetry/propagator-jaeger": "npm:1.30.0" + "@opentelemetry/sdk-trace-base": "npm:1.30.0" semver: "npm:^7.5.2" peerDependencies: "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/4af09076649bdfbd1b3b12a8d0f9fd9569cb8e821f449a9096aa64253970ab57188d93eb7fcb108c9067e8e7deadf79cdd5146939493d35795e0e66fb9d64aae + checksum: 10/bb070ec0b259b8fb4c8058e078476766d40a0b5e0cc819b9ebb8ecd9deedf4d46ec60bf6b4f28be37d1565350dd9e318e4f20c6d3e45d51c815cc89ce2afe45d languageName: node linkType: hard @@ -10488,18 +10683,7 @@ __metadata: languageName: node linkType: hard -"@prisma/instrumentation@npm:5.19.1": - version: 5.19.1 - resolution: "@prisma/instrumentation@npm:5.19.1" - dependencies: - "@opentelemetry/api": "npm:^1.8" - "@opentelemetry/instrumentation": "npm:^0.49 || ^0.50 || ^0.51 || ^0.52.0" - "@opentelemetry/sdk-trace-base": "npm:^1.22" - checksum: 10/62029ace33406901d1dfee136d4ae83b51d5787fbcdb104378edc890310e1989a0b0c95c1eb28fe8bfc314565aebee48189aebee600486859383d8981993045b - languageName: node - linkType: hard - -"@prisma/instrumentation@npm:^5.22.0": +"@prisma/instrumentation@npm:5.22.0, @prisma/instrumentation@npm:^5.22.0": version: 5.22.0 resolution: "@prisma/instrumentation@npm:5.22.0" dependencies: @@ -10625,13 +10809,13 @@ __metadata: linkType: hard "@radix-ui/react-alert-dialog@npm:^1.1.3": - version: 1.1.3 - resolution: "@radix-ui/react-alert-dialog@npm:1.1.3" + version: 1.1.4 + resolution: "@radix-ui/react-alert-dialog@npm:1.1.4" dependencies: "@radix-ui/primitive": "npm:1.1.1" "@radix-ui/react-compose-refs": "npm:1.1.1" "@radix-ui/react-context": "npm:1.1.1" - "@radix-ui/react-dialog": "npm:1.1.3" + "@radix-ui/react-dialog": "npm:1.1.4" "@radix-ui/react-primitive": "npm:2.0.1" "@radix-ui/react-slot": "npm:1.1.1" peerDependencies: @@ -10644,7 +10828,7 @@ __metadata: optional: true "@types/react-dom": optional: true - checksum: 10/1b5253915c996ea29ba300470053afc8f34ab245eb1285ee2ebad2ed363809e76b71e04052ef33f0b532e9e5b3f4930d4380add330d2aac68cf11dd3f0bd43c8 + checksum: 10/bb436dc29f7aeec93eaeb77d385d07b2b69e837abdc73e5be3f59a19479346763f8065a1d780502fe3891a767374b0544a6fc109266a8d347ee23e22759db625 languageName: node linkType: hard @@ -10796,12 +10980,12 @@ __metadata: linkType: hard "@radix-ui/react-context-menu@npm:^2.2.3": - version: 2.2.3 - resolution: "@radix-ui/react-context-menu@npm:2.2.3" + version: 2.2.4 + resolution: "@radix-ui/react-context-menu@npm:2.2.4" dependencies: "@radix-ui/primitive": "npm:1.1.1" "@radix-ui/react-context": "npm:1.1.1" - "@radix-ui/react-menu": "npm:2.1.3" + "@radix-ui/react-menu": "npm:2.1.4" "@radix-ui/react-primitive": "npm:2.0.1" "@radix-ui/react-use-callback-ref": "npm:1.1.0" "@radix-ui/react-use-controllable-state": "npm:1.1.0" @@ -10815,7 +10999,7 @@ __metadata: optional: true "@types/react-dom": optional: true - checksum: 10/f0cdcc49b2384b6b2ec5c401d1ddd0ce9232bd0a464a280062d20762a86a27366148a148ea2b6b3beb7143fc27fb36e0e78e689fd0acd754f858d6543bb17d4c + checksum: 10/351a94dc371b12e57b43197d2be12350f67150645922e29dbaff36e4d7ac26a94e32cb7e0a388a7278bd568c30e35929851030012496b98797335eb741c0bb5d languageName: node linkType: hard @@ -10832,14 +11016,14 @@ __metadata: languageName: node linkType: hard -"@radix-ui/react-dialog@npm:1.1.3, @radix-ui/react-dialog@npm:^1.1.1, @radix-ui/react-dialog@npm:^1.1.2, @radix-ui/react-dialog@npm:^1.1.3": - version: 1.1.3 - resolution: "@radix-ui/react-dialog@npm:1.1.3" +"@radix-ui/react-dialog@npm:1.1.4, @radix-ui/react-dialog@npm:^1.1.1, @radix-ui/react-dialog@npm:^1.1.2, @radix-ui/react-dialog@npm:^1.1.3": + version: 1.1.4 + resolution: "@radix-ui/react-dialog@npm:1.1.4" dependencies: "@radix-ui/primitive": "npm:1.1.1" "@radix-ui/react-compose-refs": "npm:1.1.1" "@radix-ui/react-context": "npm:1.1.1" - "@radix-ui/react-dismissable-layer": "npm:1.1.2" + "@radix-ui/react-dismissable-layer": "npm:1.1.3" "@radix-ui/react-focus-guards": "npm:1.1.1" "@radix-ui/react-focus-scope": "npm:1.1.1" "@radix-ui/react-id": "npm:1.1.0" @@ -10849,7 +11033,7 @@ __metadata: "@radix-ui/react-slot": "npm:1.1.1" "@radix-ui/react-use-controllable-state": "npm:1.1.0" aria-hidden: "npm:^1.1.1" - react-remove-scroll: "npm:2.6.0" + react-remove-scroll: "npm:^2.6.1" peerDependencies: "@types/react": "*" "@types/react-dom": "*" @@ -10860,7 +11044,7 @@ __metadata: optional: true "@types/react-dom": optional: true - checksum: 10/70efa2688a599cb43f0b9d3b6730a5d44339bbe20446c16a98f2ae86b72fa1e2e4389e705d9eb1c368cefd4895b0e058a49d65d3b412d5436cac8a39545ff857 + checksum: 10/61edc875340a6e89228d9a42403456b2351e6c12e316bc372dfde1e616464f29d91cfed8cbda03fde634ef5b3d2046195173b20e623888ddf8316dd433aab28a languageName: node linkType: hard @@ -10877,9 +11061,9 @@ __metadata: languageName: node linkType: hard -"@radix-ui/react-dismissable-layer@npm:1.1.2": - version: 1.1.2 - resolution: "@radix-ui/react-dismissable-layer@npm:1.1.2" +"@radix-ui/react-dismissable-layer@npm:1.1.3": + version: 1.1.3 + resolution: "@radix-ui/react-dismissable-layer@npm:1.1.3" dependencies: "@radix-ui/primitive": "npm:1.1.1" "@radix-ui/react-compose-refs": "npm:1.1.1" @@ -10896,19 +11080,19 @@ __metadata: optional: true "@types/react-dom": optional: true - checksum: 10/cc67988d8341aaa870773486de689f22c1414ec15ee748baa4f2c16c900acc7f8d0a2c6f1b19c765d76da87b06d688c1c308241ae306f0ab72acd4fbfe26e717 + checksum: 10/9905ff3d8d630223fd40bf31cdd8027b6e750cecd31aa04c2a5912e6e628f72973e58032bb944a5f4685dd888256a306a1c296a6e18648187974455a9660d95f languageName: node linkType: hard "@radix-ui/react-dropdown-menu@npm:^2.1.3": - version: 2.1.3 - resolution: "@radix-ui/react-dropdown-menu@npm:2.1.3" + version: 2.1.4 + resolution: "@radix-ui/react-dropdown-menu@npm:2.1.4" dependencies: "@radix-ui/primitive": "npm:1.1.1" "@radix-ui/react-compose-refs": "npm:1.1.1" "@radix-ui/react-context": "npm:1.1.1" "@radix-ui/react-id": "npm:1.1.0" - "@radix-ui/react-menu": "npm:2.1.3" + "@radix-ui/react-menu": "npm:2.1.4" "@radix-ui/react-primitive": "npm:2.0.1" "@radix-ui/react-use-controllable-state": "npm:1.1.0" peerDependencies: @@ -10921,7 +11105,7 @@ __metadata: optional: true "@types/react-dom": optional: true - checksum: 10/157e0ae1ad07d8d8c4e11b9b1604223d95501685b558314b68b9e1e05b02dac1884237c2837dcd4caea9652974634f8e6ea1b1bf59e2f06a78fde2b3e50099c5 + checksum: 10/63978b6e73431c9b21b7e9926e6e202caccac2016831f580fbabfca8d64eb89da4747e366c528421499fab550de10bcd8c5a21216d8a2536d3f3949f06bedb4e languageName: node linkType: hard @@ -10960,13 +11144,13 @@ __metadata: linkType: hard "@radix-ui/react-hover-card@npm:^1.1.3": - version: 1.1.3 - resolution: "@radix-ui/react-hover-card@npm:1.1.3" + version: 1.1.4 + resolution: "@radix-ui/react-hover-card@npm:1.1.4" dependencies: "@radix-ui/primitive": "npm:1.1.1" "@radix-ui/react-compose-refs": "npm:1.1.1" "@radix-ui/react-context": "npm:1.1.1" - "@radix-ui/react-dismissable-layer": "npm:1.1.2" + "@radix-ui/react-dismissable-layer": "npm:1.1.3" "@radix-ui/react-popper": "npm:1.2.1" "@radix-ui/react-portal": "npm:1.1.3" "@radix-ui/react-presence": "npm:1.1.2" @@ -10982,7 +11166,7 @@ __metadata: optional: true "@types/react-dom": optional: true - checksum: 10/0959e6217b17946f08e59ecc0220109d8170a606fb3f342d57317ff6aa4749e0ec335fce5cd295ac550cc55c8fb89c0d1a6496c73189dedde05fdfc9d91067f5 + checksum: 10/bfdd0fe4d271cd2c1810ab50a4d2c5090b7b91afa0b7a0e74aa371f488be8ba3c14cf552c5aa35da8a14bcfe97a905df95dc2bc09b14efec597e190ff4ec0d10 languageName: node linkType: hard @@ -11020,16 +11204,16 @@ __metadata: languageName: node linkType: hard -"@radix-ui/react-menu@npm:2.1.3": - version: 2.1.3 - resolution: "@radix-ui/react-menu@npm:2.1.3" +"@radix-ui/react-menu@npm:2.1.4": + version: 2.1.4 + resolution: "@radix-ui/react-menu@npm:2.1.4" dependencies: "@radix-ui/primitive": "npm:1.1.1" "@radix-ui/react-collection": "npm:1.1.1" "@radix-ui/react-compose-refs": "npm:1.1.1" "@radix-ui/react-context": "npm:1.1.1" "@radix-ui/react-direction": "npm:1.1.0" - "@radix-ui/react-dismissable-layer": "npm:1.1.2" + "@radix-ui/react-dismissable-layer": "npm:1.1.3" "@radix-ui/react-focus-guards": "npm:1.1.1" "@radix-ui/react-focus-scope": "npm:1.1.1" "@radix-ui/react-id": "npm:1.1.0" @@ -11041,7 +11225,7 @@ __metadata: "@radix-ui/react-slot": "npm:1.1.1" "@radix-ui/react-use-callback-ref": "npm:1.1.0" aria-hidden: "npm:^1.1.1" - react-remove-scroll: "npm:2.6.0" + react-remove-scroll: "npm:^2.6.1" peerDependencies: "@types/react": "*" "@types/react-dom": "*" @@ -11052,13 +11236,13 @@ __metadata: optional: true "@types/react-dom": optional: true - checksum: 10/19f782ab7462e3b1d2e401896a6a1bf96023eee0acfc58630c226126b6f3a0294993fe7824513cff979b6aeea5c36251cc8b831a921c8cbcab177b42b2f3627c + checksum: 10/c1b9b417515e9a0c08a9db646c2a81d1ceb236adb1629ee033c6f98e8675ab34b924e2d35d1aab605a58dab9ac509adb33875a05bbce6202270e3a54a4a6fece languageName: node linkType: hard "@radix-ui/react-menubar@npm:^1.1.3": - version: 1.1.3 - resolution: "@radix-ui/react-menubar@npm:1.1.3" + version: 1.1.4 + resolution: "@radix-ui/react-menubar@npm:1.1.4" dependencies: "@radix-ui/primitive": "npm:1.1.1" "@radix-ui/react-collection": "npm:1.1.1" @@ -11066,7 +11250,7 @@ __metadata: "@radix-ui/react-context": "npm:1.1.1" "@radix-ui/react-direction": "npm:1.1.0" "@radix-ui/react-id": "npm:1.1.0" - "@radix-ui/react-menu": "npm:2.1.3" + "@radix-ui/react-menu": "npm:2.1.4" "@radix-ui/react-primitive": "npm:2.0.1" "@radix-ui/react-roving-focus": "npm:1.1.1" "@radix-ui/react-use-controllable-state": "npm:1.1.0" @@ -11080,20 +11264,20 @@ __metadata: optional: true "@types/react-dom": optional: true - checksum: 10/c96fc749be744ad4597722ed2740277551aa71b420a11399ad14d166d2711386b206775f8e16e0427f0f3cd6b4f9d0393b01378eed7a8837335ee6499ea875b8 + checksum: 10/ea298969f5e23c0bf7c749ec78139c6b2a7db64bfa8075da64054bcd07f82884cf4f064e20cea971bb649ceb802b020047a3c0cde0a8869a35f9019ee16b7b00 languageName: node linkType: hard "@radix-ui/react-navigation-menu@npm:^1.2.2": - version: 1.2.2 - resolution: "@radix-ui/react-navigation-menu@npm:1.2.2" + version: 1.2.3 + resolution: "@radix-ui/react-navigation-menu@npm:1.2.3" dependencies: "@radix-ui/primitive": "npm:1.1.1" "@radix-ui/react-collection": "npm:1.1.1" "@radix-ui/react-compose-refs": "npm:1.1.1" "@radix-ui/react-context": "npm:1.1.1" "@radix-ui/react-direction": "npm:1.1.0" - "@radix-ui/react-dismissable-layer": "npm:1.1.2" + "@radix-ui/react-dismissable-layer": "npm:1.1.3" "@radix-ui/react-id": "npm:1.1.0" "@radix-ui/react-presence": "npm:1.1.2" "@radix-ui/react-primitive": "npm:2.0.1" @@ -11112,18 +11296,18 @@ __metadata: optional: true "@types/react-dom": optional: true - checksum: 10/3bd34fed054f05cb1052a100635869d8e89ebe54739c3008316aca8de2578d8e991b6aa2825c384fddf5a929efcf84ec64511eae8e0e170414b5f4350b98b9bd + checksum: 10/0f9033c7037715bbbad3e6b45ceca14a4476e9994bbb58dc7f77e8c753c19458e1865d311ed728d758e3d141d1e7abb8ee1c1c2ba2187b8aca8e7c679d94d35b languageName: node linkType: hard "@radix-ui/react-popover@npm:^1.1.3": - version: 1.1.3 - resolution: "@radix-ui/react-popover@npm:1.1.3" + version: 1.1.4 + resolution: "@radix-ui/react-popover@npm:1.1.4" dependencies: "@radix-ui/primitive": "npm:1.1.1" "@radix-ui/react-compose-refs": "npm:1.1.1" "@radix-ui/react-context": "npm:1.1.1" - "@radix-ui/react-dismissable-layer": "npm:1.1.2" + "@radix-ui/react-dismissable-layer": "npm:1.1.3" "@radix-ui/react-focus-guards": "npm:1.1.1" "@radix-ui/react-focus-scope": "npm:1.1.1" "@radix-ui/react-id": "npm:1.1.0" @@ -11134,7 +11318,7 @@ __metadata: "@radix-ui/react-slot": "npm:1.1.1" "@radix-ui/react-use-controllable-state": "npm:1.1.0" aria-hidden: "npm:^1.1.1" - react-remove-scroll: "npm:2.6.0" + react-remove-scroll: "npm:^2.6.1" peerDependencies: "@types/react": "*" "@types/react-dom": "*" @@ -11145,7 +11329,7 @@ __metadata: optional: true "@types/react-dom": optional: true - checksum: 10/36965cb6e3550126f31f65682a5ea57fe137df4b21e5aa12daa59ae5cbef063dac31c02977b2e9ad829d506c71372fc2ee6099566db7a9654d41ffeb3947cee2 + checksum: 10/8f21bc61a752291319f64327fa9b2b310c93efe55bb6e0fec7d72a4d59e1e80b28a25ad13c3b9ac600bc0c0e68f155f2226ef76c54d9e8226b4066b70611a4f3 languageName: node linkType: hard @@ -11339,8 +11523,8 @@ __metadata: linkType: hard "@radix-ui/react-select@npm:^2.1.3": - version: 2.1.3 - resolution: "@radix-ui/react-select@npm:2.1.3" + version: 2.1.4 + resolution: "@radix-ui/react-select@npm:2.1.4" dependencies: "@radix-ui/number": "npm:1.1.0" "@radix-ui/primitive": "npm:1.1.1" @@ -11348,7 +11532,7 @@ __metadata: "@radix-ui/react-compose-refs": "npm:1.1.1" "@radix-ui/react-context": "npm:1.1.1" "@radix-ui/react-direction": "npm:1.1.0" - "@radix-ui/react-dismissable-layer": "npm:1.1.2" + "@radix-ui/react-dismissable-layer": "npm:1.1.3" "@radix-ui/react-focus-guards": "npm:1.1.1" "@radix-ui/react-focus-scope": "npm:1.1.1" "@radix-ui/react-id": "npm:1.1.0" @@ -11362,7 +11546,7 @@ __metadata: "@radix-ui/react-use-previous": "npm:1.1.0" "@radix-ui/react-visually-hidden": "npm:1.1.1" aria-hidden: "npm:^1.1.1" - react-remove-scroll: "npm:2.6.0" + react-remove-scroll: "npm:^2.6.1" peerDependencies: "@types/react": "*" "@types/react-dom": "*" @@ -11373,7 +11557,7 @@ __metadata: optional: true "@types/react-dom": optional: true - checksum: 10/2b5633751fb89f4f1636004e25d9c1fb77a31dc704cd8cce39f47720b8d6b4a8c6cac040470f1a69a5b2f1972604fa5a50a749f764003674147aa9edb9acc282 + checksum: 10/acf3d5cd77be0dd117f04385b5524c7ca3296cf89dbdd468fe3bfc0e8da1a326543ba6e5026d73b96023e56a12d9cd72ae431ac62e6ce7396b8c1c012344b717 languageName: node linkType: hard @@ -11492,14 +11676,14 @@ __metadata: linkType: hard "@radix-ui/react-toast@npm:^1.2.3": - version: 1.2.3 - resolution: "@radix-ui/react-toast@npm:1.2.3" + version: 1.2.4 + resolution: "@radix-ui/react-toast@npm:1.2.4" dependencies: "@radix-ui/primitive": "npm:1.1.1" "@radix-ui/react-collection": "npm:1.1.1" "@radix-ui/react-compose-refs": "npm:1.1.1" "@radix-ui/react-context": "npm:1.1.1" - "@radix-ui/react-dismissable-layer": "npm:1.1.2" + "@radix-ui/react-dismissable-layer": "npm:1.1.3" "@radix-ui/react-portal": "npm:1.1.3" "@radix-ui/react-presence": "npm:1.1.2" "@radix-ui/react-primitive": "npm:2.0.1" @@ -11517,7 +11701,7 @@ __metadata: optional: true "@types/react-dom": optional: true - checksum: 10/59a5b2868d00df1acd01bd47dc7c39d15c995a990b61b14d1c496b607c0941abdb668f34d561234050a8865ba34c51e28064e7dbcfc8da7557ef2923c1561ff8 + checksum: 10/55d69414a4ca3d6ccbcfa980e3ea1d618c76b8d669ec5522ba9aad4445f42ab5b0022bbebffe928f72b36525baae68bc61cc046fe158b13ccd3dc43f15e59a83 languageName: node linkType: hard @@ -11593,13 +11777,13 @@ __metadata: linkType: hard "@radix-ui/react-tooltip@npm:^1.1.5": - version: 1.1.5 - resolution: "@radix-ui/react-tooltip@npm:1.1.5" + version: 1.1.6 + resolution: "@radix-ui/react-tooltip@npm:1.1.6" dependencies: "@radix-ui/primitive": "npm:1.1.1" "@radix-ui/react-compose-refs": "npm:1.1.1" "@radix-ui/react-context": "npm:1.1.1" - "@radix-ui/react-dismissable-layer": "npm:1.1.2" + "@radix-ui/react-dismissable-layer": "npm:1.1.3" "@radix-ui/react-id": "npm:1.1.0" "@radix-ui/react-popper": "npm:1.2.1" "@radix-ui/react-portal": "npm:1.1.3" @@ -11618,7 +11802,7 @@ __metadata: optional: true "@types/react-dom": optional: true - checksum: 10/69aa63b7ecdbebd3cd2cb04adc079106e12cf860af3d5ce4065e1722fce322e6648b16733a5f502b00e9d50f2418b559e5d3851503b152c3ff8873d2787784d4 + checksum: 10/c2210c292e67714aaade5230352619bf0151885ce82ff080af1a5f697928e32ed73f406cbf3452ca014a54e0b9182572d6bc16084b9ee2d7ffa2e2fcde45f299 languageName: node linkType: hard @@ -11960,79 +12144,41 @@ __metadata: languageName: node linkType: hard -"@sentry-internal/browser-utils@npm:8.42.0": - version: 8.42.0 - resolution: "@sentry-internal/browser-utils@npm:8.42.0" - dependencies: - "@sentry/core": "npm:8.42.0" - checksum: 10/de90d698cedb4ae612b89e585b51bd013f022b16fb8bce4951909af600f9acb4ed8c5965f26b70d19b09341bff1729957434e20b8d8bf59e77eddea97e30ca6e - languageName: node - linkType: hard - -"@sentry-internal/browser-utils@npm:8.45.0": - version: 8.45.0 - resolution: "@sentry-internal/browser-utils@npm:8.45.0" - dependencies: - "@sentry/core": "npm:8.45.0" - checksum: 10/7564747f1a5a21a99f4c4cb1d5e2853c18a9374ff1ccf9721c8a509c3450df8eabd44418b2a0d88e76e94f1c7bb93c3b75b0b8498b010a04eaa076be2096fe16 - languageName: node - linkType: hard - -"@sentry-internal/feedback@npm:8.42.0": - version: 8.42.0 - resolution: "@sentry-internal/feedback@npm:8.42.0" - dependencies: - "@sentry/core": "npm:8.42.0" - checksum: 10/2e6eab58ba50a7bc92972229f20313d5cbc2b991dda79a44a8cd7b8d981a29e7594508585e3d6618a7829db8c2ffe0a017860a465a64ecc7ba5c67a0de10cbb0 - languageName: node - linkType: hard - -"@sentry-internal/feedback@npm:8.45.0": - version: 8.45.0 - resolution: "@sentry-internal/feedback@npm:8.45.0" +"@sentry-internal/browser-utils@npm:8.47.0": + version: 8.47.0 + resolution: "@sentry-internal/browser-utils@npm:8.47.0" dependencies: - "@sentry/core": "npm:8.45.0" - checksum: 10/580dcb15f69cd7d9052d0b0461b170bb086571bad938cb0c44e8305d8662fba99f55f644b61c935b3cc1b188da31187f465499ecf8c96cf6f31123e1c7055965 + "@sentry/core": "npm:8.47.0" + checksum: 10/39f2a93ca8d661d7ec771572ec5961d37832ce5832a3ef27845d06d06742e1586c464deefd5702442d808ff9a29266c158716aa58a2f0ee29dd8f23c46450d99 languageName: node linkType: hard -"@sentry-internal/replay-canvas@npm:8.42.0": - version: 8.42.0 - resolution: "@sentry-internal/replay-canvas@npm:8.42.0" +"@sentry-internal/feedback@npm:8.47.0": + version: 8.47.0 + resolution: "@sentry-internal/feedback@npm:8.47.0" dependencies: - "@sentry-internal/replay": "npm:8.42.0" - "@sentry/core": "npm:8.42.0" - checksum: 10/60da91e2dbcba5fe4ee0fa7d609b4e4455acb8a6a958aa38c70377369c194dc8fb98ff02afce356a68ff2e917df6ab96a1dba756e2181c95178ec39c5676c33b + "@sentry/core": "npm:8.47.0" + checksum: 10/11f7ee6b1201b107d653ef2c2670cc0ebf76992a09a53ee3d0e88936d898aed90495aceda62a7e516cd4b4b6ef509d9ca99e7fedc0866d7d0e75deb13f7bb064 languageName: node linkType: hard -"@sentry-internal/replay-canvas@npm:8.45.0": - version: 8.45.0 - resolution: "@sentry-internal/replay-canvas@npm:8.45.0" +"@sentry-internal/replay-canvas@npm:8.47.0": + version: 8.47.0 + resolution: "@sentry-internal/replay-canvas@npm:8.47.0" dependencies: - "@sentry-internal/replay": "npm:8.45.0" - "@sentry/core": "npm:8.45.0" - checksum: 10/72164ee7a2af509f0d6311fb69a01173601de7e8a341757cc6e11715775f9a37947b8d735c1fb555d7d9e5028db96c9b7b36e33a473f3548fea3a4c584b84355 + "@sentry-internal/replay": "npm:8.47.0" + "@sentry/core": "npm:8.47.0" + checksum: 10/ee23cb9b5aaa122abd9ad19d8c5c01939b3e350d503d15b019c487ce6b73fcabb324a2c6453b670e4cdc179d495a1b2e9d4217e2d4b85fa32878a675871dc61c languageName: node linkType: hard -"@sentry-internal/replay@npm:8.42.0": - version: 8.42.0 - resolution: "@sentry-internal/replay@npm:8.42.0" +"@sentry-internal/replay@npm:8.47.0": + version: 8.47.0 + resolution: "@sentry-internal/replay@npm:8.47.0" dependencies: - "@sentry-internal/browser-utils": "npm:8.42.0" - "@sentry/core": "npm:8.42.0" - checksum: 10/c269207f66412029413a43f041168711f4a247f571f072478e9e3e69f0d11477d1833409680c4beabda83b76ae648f878f428d78a2bc0e6d433954054de1f3c3 - languageName: node - linkType: hard - -"@sentry-internal/replay@npm:8.45.0": - version: 8.45.0 - resolution: "@sentry-internal/replay@npm:8.45.0" - dependencies: - "@sentry-internal/browser-utils": "npm:8.45.0" - "@sentry/core": "npm:8.45.0" - checksum: 10/66a31326aa431e930ed6c4e6ffdb3b5eca2cb9305366d852063d90fbaff8aec8d2bddda8c14526d39c44f186bbcc535f26c84344b7cbfa2953eb4d4bf6569a62 + "@sentry-internal/browser-utils": "npm:8.47.0" + "@sentry/core": "npm:8.47.0" + checksum: 10/115a9d53604fcd133a4a4a7430c7a88d0257c9f225c9599f5ab1f4314191bbac109aed72ac22cc1569e1910008eb82dfa34f8435d8b806c6f77792ed24b81518 languageName: node linkType: hard @@ -12043,29 +12189,16 @@ __metadata: languageName: node linkType: hard -"@sentry/browser@npm:8.42.0": - version: 8.42.0 - resolution: "@sentry/browser@npm:8.42.0" +"@sentry/browser@npm:8.47.0": + version: 8.47.0 + resolution: "@sentry/browser@npm:8.47.0" dependencies: - "@sentry-internal/browser-utils": "npm:8.42.0" - "@sentry-internal/feedback": "npm:8.42.0" - "@sentry-internal/replay": "npm:8.42.0" - "@sentry-internal/replay-canvas": "npm:8.42.0" - "@sentry/core": "npm:8.42.0" - checksum: 10/572519305c53627ec0ac772551962c79939f18ea444ce8e25da6f1cee9c0758aa5c6e471a35d1ed7cc59d20e556a09d52fb7f8cf0d52e1b4362bc13b3a53ddc2 - languageName: node - linkType: hard - -"@sentry/browser@npm:8.45.0": - version: 8.45.0 - resolution: "@sentry/browser@npm:8.45.0" - dependencies: - "@sentry-internal/browser-utils": "npm:8.45.0" - "@sentry-internal/feedback": "npm:8.45.0" - "@sentry-internal/replay": "npm:8.45.0" - "@sentry-internal/replay-canvas": "npm:8.45.0" - "@sentry/core": "npm:8.45.0" - checksum: 10/33db3e11f50a3b44226ffc1458c46a9bae7bec1162411e62fa04608d11542abef72dbeaeca5daebe88f5c5caad5c5cc06e8b3a77e208186ae53612357e400d2c + "@sentry-internal/browser-utils": "npm:8.47.0" + "@sentry-internal/feedback": "npm:8.47.0" + "@sentry-internal/replay": "npm:8.47.0" + "@sentry-internal/replay-canvas": "npm:8.47.0" + "@sentry/core": "npm:8.47.0" + checksum: 10/481f613b39c17b35113c4eb1d1755f5168878f6d0f0c63e2c7af9a5ec85a76129034431ccedc261cd5d883255e28f92a2a8d9654060a05426fa1092c58c9e9ad languageName: node linkType: hard @@ -12171,29 +12304,22 @@ __metadata: languageName: node linkType: hard -"@sentry/core@npm:8.42.0": - version: 8.42.0 - resolution: "@sentry/core@npm:8.42.0" - checksum: 10/c57629adf2512f0f581bf9e2190e9d3ee8c754866eb7fcb8f0897f2849d9b84ed02ec1286852ebdba74d41cc2977614e4db7e898094d5bcc40a869a2e280aafb - languageName: node - linkType: hard - -"@sentry/core@npm:8.45.0": - version: 8.45.0 - resolution: "@sentry/core@npm:8.45.0" - checksum: 10/2df0ec9f5fb43794a867d50efe9b5e8caa5c0dfcd3774ab6a46b035c0a8b4849c48ec5d422f3857355aff7d1e6fd5eccea32dc296b148fa92c1ee2de4ee19853 +"@sentry/core@npm:8.47.0": + version: 8.47.0 + resolution: "@sentry/core@npm:8.47.0" + checksum: 10/53a47a93f974e0d4c390498690f9098320c5a884bf3b58c20e7143e0f632238ec93e7db7bf9f4fbab168ec05ef8dc5506ffd0e33e0ddebe8da4fdbbfdca49e21 languageName: node linkType: hard "@sentry/electron@npm:^5.8.0": - version: 5.8.0 - resolution: "@sentry/electron@npm:5.8.0" + version: 5.9.0 + resolution: "@sentry/electron@npm:5.9.0" dependencies: - "@sentry/browser": "npm:8.42.0" - "@sentry/core": "npm:8.42.0" - "@sentry/node": "npm:8.42.0" + "@sentry/browser": "npm:8.47.0" + "@sentry/core": "npm:8.47.0" + "@sentry/node": "npm:8.47.0" deepmerge: "npm:4.3.1" - checksum: 10/fb7dcc71e94cf7a1133325554d4a628295c6256f3db5dc0e1e8c0454c71cfa502c9555cc87fa55ba9e01ff314b75f226f4946bea5a2429d6d5eff18960392d80 + checksum: 10/0a1774d32eef0a44266c5faab430964b6bde3c071ab96778de36e87d2919d89168ae06cd8ca2d1cf8d893ed9d5ebba3cac1595bc26fe1c36f38c7c1a3592a9a1 languageName: node linkType: hard @@ -12208,74 +12334,74 @@ __metadata: languageName: node linkType: hard -"@sentry/node@npm:8.42.0": - version: 8.42.0 - resolution: "@sentry/node@npm:8.42.0" +"@sentry/node@npm:8.47.0": + version: 8.47.0 + resolution: "@sentry/node@npm:8.47.0" dependencies: "@opentelemetry/api": "npm:^1.9.0" - "@opentelemetry/context-async-hooks": "npm:^1.25.1" - "@opentelemetry/core": "npm:^1.25.1" - "@opentelemetry/instrumentation": "npm:^0.54.0" - "@opentelemetry/instrumentation-amqplib": "npm:^0.43.0" - "@opentelemetry/instrumentation-connect": "npm:0.40.0" - "@opentelemetry/instrumentation-dataloader": "npm:0.12.0" - "@opentelemetry/instrumentation-express": "npm:0.44.0" - "@opentelemetry/instrumentation-fastify": "npm:0.41.0" - "@opentelemetry/instrumentation-fs": "npm:0.16.0" - "@opentelemetry/instrumentation-generic-pool": "npm:0.39.0" - "@opentelemetry/instrumentation-graphql": "npm:0.44.0" - "@opentelemetry/instrumentation-hapi": "npm:0.41.0" - "@opentelemetry/instrumentation-http": "npm:0.53.0" - "@opentelemetry/instrumentation-ioredis": "npm:0.43.0" - "@opentelemetry/instrumentation-kafkajs": "npm:0.4.0" - "@opentelemetry/instrumentation-knex": "npm:0.41.0" - "@opentelemetry/instrumentation-koa": "npm:0.43.0" - "@opentelemetry/instrumentation-lru-memoizer": "npm:0.40.0" - "@opentelemetry/instrumentation-mongodb": "npm:0.48.0" - "@opentelemetry/instrumentation-mongoose": "npm:0.42.0" - "@opentelemetry/instrumentation-mysql": "npm:0.41.0" - "@opentelemetry/instrumentation-mysql2": "npm:0.41.0" - "@opentelemetry/instrumentation-nestjs-core": "npm:0.40.0" - "@opentelemetry/instrumentation-pg": "npm:0.44.0" - "@opentelemetry/instrumentation-redis-4": "npm:0.42.0" - "@opentelemetry/instrumentation-tedious": "npm:0.15.0" - "@opentelemetry/instrumentation-undici": "npm:0.6.0" - "@opentelemetry/resources": "npm:^1.26.0" - "@opentelemetry/sdk-trace-base": "npm:^1.26.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - "@prisma/instrumentation": "npm:5.19.1" - "@sentry/core": "npm:8.42.0" - "@sentry/opentelemetry": "npm:8.42.0" + "@opentelemetry/context-async-hooks": "npm:^1.29.0" + "@opentelemetry/core": "npm:^1.29.0" + "@opentelemetry/instrumentation": "npm:^0.56.0" + "@opentelemetry/instrumentation-amqplib": "npm:^0.45.0" + "@opentelemetry/instrumentation-connect": "npm:0.42.0" + "@opentelemetry/instrumentation-dataloader": "npm:0.15.0" + "@opentelemetry/instrumentation-express": "npm:0.46.0" + "@opentelemetry/instrumentation-fastify": "npm:0.43.0" + "@opentelemetry/instrumentation-fs": "npm:0.18.0" + "@opentelemetry/instrumentation-generic-pool": "npm:0.42.0" + "@opentelemetry/instrumentation-graphql": "npm:0.46.0" + "@opentelemetry/instrumentation-hapi": "npm:0.44.0" + "@opentelemetry/instrumentation-http": "npm:0.56.0" + "@opentelemetry/instrumentation-ioredis": "npm:0.46.0" + "@opentelemetry/instrumentation-kafkajs": "npm:0.6.0" + "@opentelemetry/instrumentation-knex": "npm:0.43.0" + "@opentelemetry/instrumentation-koa": "npm:0.46.0" + "@opentelemetry/instrumentation-lru-memoizer": "npm:0.43.0" + "@opentelemetry/instrumentation-mongodb": "npm:0.50.0" + "@opentelemetry/instrumentation-mongoose": "npm:0.45.0" + "@opentelemetry/instrumentation-mysql": "npm:0.44.0" + "@opentelemetry/instrumentation-mysql2": "npm:0.44.0" + "@opentelemetry/instrumentation-nestjs-core": "npm:0.43.0" + "@opentelemetry/instrumentation-pg": "npm:0.49.0" + "@opentelemetry/instrumentation-redis-4": "npm:0.45.0" + "@opentelemetry/instrumentation-tedious": "npm:0.17.0" + "@opentelemetry/instrumentation-undici": "npm:0.9.0" + "@opentelemetry/resources": "npm:^1.29.0" + "@opentelemetry/sdk-trace-base": "npm:^1.29.0" + "@opentelemetry/semantic-conventions": "npm:^1.28.0" + "@prisma/instrumentation": "npm:5.22.0" + "@sentry/core": "npm:8.47.0" + "@sentry/opentelemetry": "npm:8.47.0" import-in-the-middle: "npm:^1.11.2" - checksum: 10/16fecc94259876b332640baf520b8db0ab9cd861fad90bb0b39bdef6928eef86f72c5ea76dee4a4ab8f3e094c681def6c981a22d7c628d043a7fd5b65dd433fa + checksum: 10/e5676b0523ac20de8259913e8218ad2e6588d4dd461a06c5922ca312e4bb971ba58ec54b9536ebaa34fa2f2908b3acca82e29ae7b29cdde4d47427def034c2eb languageName: node linkType: hard -"@sentry/opentelemetry@npm:8.42.0": - version: 8.42.0 - resolution: "@sentry/opentelemetry@npm:8.42.0" +"@sentry/opentelemetry@npm:8.47.0": + version: 8.47.0 + resolution: "@sentry/opentelemetry@npm:8.47.0" dependencies: - "@sentry/core": "npm:8.42.0" + "@sentry/core": "npm:8.47.0" peerDependencies: "@opentelemetry/api": ^1.9.0 - "@opentelemetry/core": ^1.25.1 - "@opentelemetry/instrumentation": ^0.54.0 - "@opentelemetry/sdk-trace-base": ^1.26.0 - "@opentelemetry/semantic-conventions": ^1.27.0 - checksum: 10/84c28fd096e46cddf490ce25dae4e940ecd7bd562fd010b4427b7e12ec93186c0215d2f47362f8be0da8641881580172fea8bcf37f4abdb29660003f8f1d273b + "@opentelemetry/core": ^1.29.0 + "@opentelemetry/instrumentation": ^0.56.0 + "@opentelemetry/sdk-trace-base": ^1.29.0 + "@opentelemetry/semantic-conventions": ^1.28.0 + checksum: 10/c36545ffce34c5d0e2032a0dcb80343d0f7c76184dfb9e9cee60bc9cc53759370c89b44472b02f7a167e1e98db90a9dee9f42c74085cb79660b9e6571f89b606 languageName: node linkType: hard "@sentry/react@npm:^8.44.0": - version: 8.45.0 - resolution: "@sentry/react@npm:8.45.0" + version: 8.47.0 + resolution: "@sentry/react@npm:8.47.0" dependencies: - "@sentry/browser": "npm:8.45.0" - "@sentry/core": "npm:8.45.0" + "@sentry/browser": "npm:8.47.0" + "@sentry/core": "npm:8.47.0" hoist-non-react-statics: "npm:^3.3.2" peerDependencies: react: ^16.14.0 || 17.x || 18.x || 19.x - checksum: 10/b0b1125d2284f77d6c887fe8f808058d841780fe202c7fe801e6d3e5dc050730e45a472be2d4ee6329c9889313826b003cad2f6927c6f0d6971c606f77f3ad3c + checksum: 10/6fa446825dda6b046231edb58599c0c26b5808a5a04c6f9d3379400508ffede86cb9c595b3c2065695169a99e44150a731cf3fe723c73bd6f9b4bad208f44e78 languageName: node linkType: hard @@ -12292,52 +12418,52 @@ __metadata: languageName: node linkType: hard -"@shikijs/core@npm:1.24.2": - version: 1.24.2 - resolution: "@shikijs/core@npm:1.24.2" +"@shikijs/core@npm:1.24.4": + version: 1.24.4 + resolution: "@shikijs/core@npm:1.24.4" dependencies: - "@shikijs/engine-javascript": "npm:1.24.2" - "@shikijs/engine-oniguruma": "npm:1.24.2" - "@shikijs/types": "npm:1.24.2" - "@shikijs/vscode-textmate": "npm:^9.3.0" + "@shikijs/engine-javascript": "npm:1.24.4" + "@shikijs/engine-oniguruma": "npm:1.24.4" + "@shikijs/types": "npm:1.24.4" + "@shikijs/vscode-textmate": "npm:^9.3.1" "@types/hast": "npm:^3.0.4" - hast-util-to-html: "npm:^9.0.3" - checksum: 10/5716f13808ac15f2705cc340a3a336563d34d391fe6888c58e865a5a9ad4ce6b398f4013c1dafebf128cc422b8fbff3b57c83faae7fcba5f7043c3cf41f16ac6 + hast-util-to-html: "npm:^9.0.4" + checksum: 10/90d6a6eff4af1837de67857fba60d5ece5d1afbd3f52fde9e4afcde3cc7d12eb60a54cc6e7cd79840e974945db30e726bb54fa1254747aadb1d892daa4d54682 languageName: node linkType: hard -"@shikijs/engine-javascript@npm:1.24.2": - version: 1.24.2 - resolution: "@shikijs/engine-javascript@npm:1.24.2" +"@shikijs/engine-javascript@npm:1.24.4": + version: 1.24.4 + resolution: "@shikijs/engine-javascript@npm:1.24.4" dependencies: - "@shikijs/types": "npm:1.24.2" - "@shikijs/vscode-textmate": "npm:^9.3.0" - oniguruma-to-es: "npm:0.7.0" - checksum: 10/ee87f3e8e69485d175c2f54bf38058c3590c5faa9dcd1d608e67fe308b4fe43e6f753f5f71d1cc3bd720d0721b7d24d00d0df54d73c57162040e03bdc791bec1 + "@shikijs/types": "npm:1.24.4" + "@shikijs/vscode-textmate": "npm:^9.3.1" + oniguruma-to-es: "npm:0.8.1" + checksum: 10/528be972df1386bfcd8364d3875db91e561ad480b18bd5dbe36388365eac2856b6564441a4da312d31a579d9267272fa7d13ea54b4d583961492cef831685037 languageName: node linkType: hard -"@shikijs/engine-oniguruma@npm:1.24.2, @shikijs/engine-oniguruma@npm:^1.24.2": - version: 1.24.2 - resolution: "@shikijs/engine-oniguruma@npm:1.24.2" +"@shikijs/engine-oniguruma@npm:1.24.4, @shikijs/engine-oniguruma@npm:^1.24.2": + version: 1.24.4 + resolution: "@shikijs/engine-oniguruma@npm:1.24.4" dependencies: - "@shikijs/types": "npm:1.24.2" - "@shikijs/vscode-textmate": "npm:^9.3.0" - checksum: 10/07368ddac4bc603881982927096b22575c247279ab01f0669df62a14e27c3cfc76919f7ab3efea1eacc3a5f59e6ba30f51e7ddc375d7dc8e541f87b338a8293c + "@shikijs/types": "npm:1.24.4" + "@shikijs/vscode-textmate": "npm:^9.3.1" + checksum: 10/be1cbb307a3af59f1b16c80410b5fe9297fcaeef744d2c0bf0f8271b4efa846cb6480b72c16b38fc72824a2e36b2ffa2a0e9b119a2a66653e8c984791797b777 languageName: node linkType: hard -"@shikijs/types@npm:1.24.2, @shikijs/types@npm:^1.24.2": - version: 1.24.2 - resolution: "@shikijs/types@npm:1.24.2" +"@shikijs/types@npm:1.24.4, @shikijs/types@npm:^1.24.2": + version: 1.24.4 + resolution: "@shikijs/types@npm:1.24.4" dependencies: - "@shikijs/vscode-textmate": "npm:^9.3.0" + "@shikijs/vscode-textmate": "npm:^9.3.1" "@types/hast": "npm:^3.0.4" - checksum: 10/9428dc8556a66deae7897a68cbf5e5ec62e7fc2de36edcd524ca6625c9cea13a2ad2df967e445c9f06dfc9bb10115f7b3e7cbf23ef126f0606a1e421a8167de6 + checksum: 10/e1a62924de179b08312d170cfe521a3f4f9d806faade937bb9294b1c186104e2856d06a11493d98d47b1b20c955b3cf1e98fa71fa550e8673204e100f101fe0e languageName: node linkType: hard -"@shikijs/vscode-textmate@npm:^9.3.0": +"@shikijs/vscode-textmate@npm:^9.3.0, @shikijs/vscode-textmate@npm:^9.3.1": version: 9.3.1 resolution: "@shikijs/vscode-textmate@npm:9.3.1" checksum: 10/cb4ec8da2db02deb5cb5b45dff1c27e83aa07f179cb23688e258377ebbaad6b1677dbf8b856f2bf845a6c909670c053244b02db138dda875e9b078da5d465b95 @@ -12358,9 +12484,9 @@ __metadata: languageName: node linkType: hard -"@shoelace-style/shoelace@npm:2.19.0": - version: 2.19.0 - resolution: "@shoelace-style/shoelace@npm:2.19.0" +"@shoelace-style/shoelace@npm:2.19.1": + version: 2.19.1 + resolution: "@shoelace-style/shoelace@npm:2.19.1" dependencies: "@ctrl/tinycolor": "npm:^4.1.0" "@floating-ui/dom": "npm:^1.6.12" @@ -12370,7 +12496,7 @@ __metadata: composed-offset-position: "npm:^0.0.6" lit: "npm:^3.2.1" qr-creator: "npm:^1.0.0" - checksum: 10/437b6dc65c97f192bb4f63a9d5eb519c64ac2dac9f688bb2fc71964ae2ecd544e4bf9d2f122183f7741a22257cc542addc447439b1143cf52dc33a2367ade060 + checksum: 10/5ccd716695b245d8a421a5e1176aa23f9ed4bdc8ef54a1485302c280bd6550439b92ec0745b0b7ed92042d08cb236835e4360bde8d2652c9fbd92e152efdfe46 languageName: node linkType: hard @@ -12717,9 +12843,9 @@ __metadata: languageName: node linkType: hard -"@smithy/middleware-endpoint@npm:^3.2.5": - version: 3.2.5 - resolution: "@smithy/middleware-endpoint@npm:3.2.5" +"@smithy/middleware-endpoint@npm:^3.2.6": + version: 3.2.6 + resolution: "@smithy/middleware-endpoint@npm:3.2.6" dependencies: "@smithy/core": "npm:^2.5.5" "@smithy/middleware-serde": "npm:^3.0.11" @@ -12729,24 +12855,24 @@ __metadata: "@smithy/url-parser": "npm:^3.0.11" "@smithy/util-middleware": "npm:^3.0.11" tslib: "npm:^2.6.2" - checksum: 10/942d56c0f54f4694c474022009ee57ec29e5d05eee131989348cd46dbb9a74d035987d20a3049e5e51556c8eafc18f45c09e0b2f285e79c991634cc9b3d6175a + checksum: 10/c740d68d45868676c10ef397ea91e196bc05fd8835b6acb4963533b80cb4dbf5a262b013695808bdda641214dfdc8b77c239d17e545628e760140e6ac772b62d languageName: node linkType: hard -"@smithy/middleware-retry@npm:^3.0.30": - version: 3.0.30 - resolution: "@smithy/middleware-retry@npm:3.0.30" +"@smithy/middleware-retry@npm:^3.0.31": + version: 3.0.31 + resolution: "@smithy/middleware-retry@npm:3.0.31" dependencies: "@smithy/node-config-provider": "npm:^3.1.12" "@smithy/protocol-http": "npm:^4.1.8" "@smithy/service-error-classification": "npm:^3.0.11" - "@smithy/smithy-client": "npm:^3.5.0" + "@smithy/smithy-client": "npm:^3.5.1" "@smithy/types": "npm:^3.7.2" "@smithy/util-middleware": "npm:^3.0.11" "@smithy/util-retry": "npm:^3.0.11" tslib: "npm:^2.6.2" uuid: "npm:^9.0.1" - checksum: 10/c3937573107643a004954899ac9827b8df5cfb80bf8f685a5b870883914bc6d020c4f682a9a1a65f9544471edd0a5030a6f5e3331ed76b6f43ead2f5ce96d15e + checksum: 10/5c7b58d4900dae64ab5143a9ce617852fa9114e443d941e085453767ee2d399e5a7e78786ec922d6af17e143e805f2030f59fcb8f2a2d5a2936c0415bbe11c28 languageName: node linkType: hard @@ -12871,18 +12997,18 @@ __metadata: languageName: node linkType: hard -"@smithy/smithy-client@npm:^3.5.0": - version: 3.5.0 - resolution: "@smithy/smithy-client@npm:3.5.0" +"@smithy/smithy-client@npm:^3.5.1": + version: 3.5.1 + resolution: "@smithy/smithy-client@npm:3.5.1" dependencies: "@smithy/core": "npm:^2.5.5" - "@smithy/middleware-endpoint": "npm:^3.2.5" + "@smithy/middleware-endpoint": "npm:^3.2.6" "@smithy/middleware-stack": "npm:^3.0.11" "@smithy/protocol-http": "npm:^4.1.8" "@smithy/types": "npm:^3.7.2" "@smithy/util-stream": "npm:^3.3.2" tslib: "npm:^2.6.2" - checksum: 10/67fd9022d4b7bb25555f12c3474331eee41027d36df66ea219006a3d2d43f37f61283e71ea863832a40b4f2e3e911b7b309cc11b7bb041d3523716719018ee58 + checksum: 10/5a8f3d6620a896fe306950d24d8ccf592d2414a56f95aee1aa3b0a6c86dfce5c5096035ef05870237d5ab036c69bbd0965c8903a7ff6915e99edc826ea9b0c3a languageName: node linkType: hard @@ -12964,31 +13090,31 @@ __metadata: languageName: node linkType: hard -"@smithy/util-defaults-mode-browser@npm:^3.0.30": - version: 3.0.30 - resolution: "@smithy/util-defaults-mode-browser@npm:3.0.30" +"@smithy/util-defaults-mode-browser@npm:^3.0.31": + version: 3.0.31 + resolution: "@smithy/util-defaults-mode-browser@npm:3.0.31" dependencies: "@smithy/property-provider": "npm:^3.1.11" - "@smithy/smithy-client": "npm:^3.5.0" + "@smithy/smithy-client": "npm:^3.5.1" "@smithy/types": "npm:^3.7.2" bowser: "npm:^2.11.0" tslib: "npm:^2.6.2" - checksum: 10/8ed3079a7ae85a404b35b73a5d23b8c00ed0a4efdd5af8bb2a15797728e62a916168fb7b8c5a2eb16aae407c7291450db9f5a5c4278254adba617e6ea4792fa3 + checksum: 10/c8f3af45afd643f484e3309ed0ad24ea05d1d860fa26f81961230f93a46d69da59c25e068d02a0ddeebd6dc29212ebc13df00bd48cd0c5881dd6d8166a95662f languageName: node linkType: hard -"@smithy/util-defaults-mode-node@npm:^3.0.30": - version: 3.0.30 - resolution: "@smithy/util-defaults-mode-node@npm:3.0.30" +"@smithy/util-defaults-mode-node@npm:^3.0.31": + version: 3.0.31 + resolution: "@smithy/util-defaults-mode-node@npm:3.0.31" dependencies: "@smithy/config-resolver": "npm:^3.0.13" "@smithy/credential-provider-imds": "npm:^3.2.8" "@smithy/node-config-provider": "npm:^3.1.12" "@smithy/property-provider": "npm:^3.1.11" - "@smithy/smithy-client": "npm:^3.5.0" + "@smithy/smithy-client": "npm:^3.5.1" "@smithy/types": "npm:^3.7.2" tslib: "npm:^2.6.2" - checksum: 10/3dd13476b0e9bc3cecfa689a8449930714c3e0b9286bf0da79effa2469c48db6ac08264cd2b82c39181b89db679dfee98847143ad46c9acf80ad4bd0da7899fc + checksum: 10/ffd1537b9ce16b0c3676a1843c201b0fa5929f50646fa2a22f0015314615b263b6d45f1a1556a0ad8d66d7ebddd6f2a50ef711ea6e821f79ee87b0000b8a390c languageName: node linkType: hard @@ -14740,11 +14866,11 @@ __metadata: linkType: hard "@types/react@npm:^19.0.1": - version: 19.0.1 - resolution: "@types/react@npm:19.0.1" + version: 19.0.2 + resolution: "@types/react@npm:19.0.2" dependencies: csstype: "npm:^3.0.2" - checksum: 10/930dd4904047059c48ae64a90fc5e8078b5bac0a14c9d927917e5a07e88e4e5073ddc944cbde90a955f9f815c23b7112caea63e407bc423913073bedecb097aa + checksum: 10/b355cfa22814e934b381c4f6de67c66652255377c3ddc6a757ea195ccbd0e7095aadfe1a28713d8ab1221222b8f2ec237903f4ec0e54eaf656ac832782d25dd2 languageName: node linkType: hard @@ -14817,7 +14943,7 @@ __metadata: languageName: node linkType: hard -"@types/shimmer@npm:^1.0.2, @types/shimmer@npm:^1.2.0": +"@types/shimmer@npm:^1.2.0": version: 1.2.0 resolution: "@types/shimmer@npm:1.2.0" checksum: 10/f081a31d826ce7bfe8cc7ba8129d2b1dffae44fd580eba4fcf741237646c4c2494ae6de2cada4b7713d138f35f4bc512dbf01311d813dee82020f97d7d8c491c @@ -14970,15 +15096,15 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:8.18.0": - version: 8.18.0 - resolution: "@typescript-eslint/eslint-plugin@npm:8.18.0" +"@typescript-eslint/eslint-plugin@npm:8.18.1": + version: 8.18.1 + resolution: "@typescript-eslint/eslint-plugin@npm:8.18.1" dependencies: "@eslint-community/regexpp": "npm:^4.10.0" - "@typescript-eslint/scope-manager": "npm:8.18.0" - "@typescript-eslint/type-utils": "npm:8.18.0" - "@typescript-eslint/utils": "npm:8.18.0" - "@typescript-eslint/visitor-keys": "npm:8.18.0" + "@typescript-eslint/scope-manager": "npm:8.18.1" + "@typescript-eslint/type-utils": "npm:8.18.1" + "@typescript-eslint/utils": "npm:8.18.1" + "@typescript-eslint/visitor-keys": "npm:8.18.1" graphemer: "npm:^1.4.0" ignore: "npm:^5.3.1" natural-compare: "npm:^1.4.0" @@ -14987,23 +15113,23 @@ __metadata: "@typescript-eslint/parser": ^8.0.0 || ^8.0.0-alpha.0 eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <5.8.0" - checksum: 10/fc163212ab626b8880bcc6c166da6e1c907c1e9eac720a217e58bec64af3866dc18e990a15a7dcd9593643f390d921625a89fb235a7e126fbb0a2f52e4abf0f5 + checksum: 10/ec061a9c64477260d1ef0fc6283d8754838181e17aa90b3b8b9a70936a2ca4bae11607070917a7701e13f5301ced2b6da4b4b6e5cf525c484f97481e540b5111 languageName: node linkType: hard -"@typescript-eslint/parser@npm:8.18.0, @typescript-eslint/parser@npm:^8.18.0": - version: 8.18.0 - resolution: "@typescript-eslint/parser@npm:8.18.0" +"@typescript-eslint/parser@npm:8.18.1, @typescript-eslint/parser@npm:^8.18.0": + version: 8.18.1 + resolution: "@typescript-eslint/parser@npm:8.18.1" dependencies: - "@typescript-eslint/scope-manager": "npm:8.18.0" - "@typescript-eslint/types": "npm:8.18.0" - "@typescript-eslint/typescript-estree": "npm:8.18.0" - "@typescript-eslint/visitor-keys": "npm:8.18.0" + "@typescript-eslint/scope-manager": "npm:8.18.1" + "@typescript-eslint/types": "npm:8.18.1" + "@typescript-eslint/typescript-estree": "npm:8.18.1" + "@typescript-eslint/visitor-keys": "npm:8.18.1" debug: "npm:^4.3.4" peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <5.8.0" - checksum: 10/5f4a1c431868ee677a6a1f55197c26c5c6e528a07fd8d8dee3648697c3617343693709c9f77cba86f8bdc1738c5727f5badfd3a9745f0e0719edb77fd0c01ba3 + checksum: 10/09a601ef8b837962e5bb2687358520f337f9d0bbac5c6d5e159654faa5caaffb24d990e8d6bc4dc51ff5008dd9e182315c35bc5e9e3789090ccef8b8040e7659 languageName: node linkType: hard @@ -15023,7 +15149,7 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:8.18.0, @typescript-eslint/scope-manager@npm:^8.1.0": +"@typescript-eslint/scope-manager@npm:8.18.0": version: 8.18.0 resolution: "@typescript-eslint/scope-manager@npm:8.18.0" dependencies: @@ -15033,18 +15159,28 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:8.18.0": - version: 8.18.0 - resolution: "@typescript-eslint/type-utils@npm:8.18.0" +"@typescript-eslint/scope-manager@npm:8.18.1, @typescript-eslint/scope-manager@npm:^8.1.0": + version: 8.18.1 + resolution: "@typescript-eslint/scope-manager@npm:8.18.1" dependencies: - "@typescript-eslint/typescript-estree": "npm:8.18.0" - "@typescript-eslint/utils": "npm:8.18.0" + "@typescript-eslint/types": "npm:8.18.1" + "@typescript-eslint/visitor-keys": "npm:8.18.1" + checksum: 10/14f7c09924c3a006b20752e5204b33c2b6974fc00bea16c23f471e65f2fb089fcbd3fb5296bcfd6727ac95c32ba24ebb15ba84fbf1deadc17b4cc5ca7f41c72a + languageName: node + linkType: hard + +"@typescript-eslint/type-utils@npm:8.18.1": + version: 8.18.1 + resolution: "@typescript-eslint/type-utils@npm:8.18.1" + dependencies: + "@typescript-eslint/typescript-estree": "npm:8.18.1" + "@typescript-eslint/utils": "npm:8.18.1" debug: "npm:^4.3.4" ts-api-utils: "npm:^1.3.0" peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <5.8.0" - checksum: 10/d857a0b6a52aad10dfd51465b8fc667f579c4a590e7fedd372f834abd2fb438186e2ebc25b61f8a5e4a90d40ebdf614367088d73ec7fe5ac0e8c9dc47ae02258 + checksum: 10/cde53d05f4ca6e172239918cba2b560b9f837aa1fc7d5220784b1a6af9c8c525db020a5160822087e320305492fe359b7fb191420789b5f1e47a01e0cda21ac9 languageName: node linkType: hard @@ -15055,6 +15191,13 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/types@npm:8.18.1": + version: 8.18.1 + resolution: "@typescript-eslint/types@npm:8.18.1" + checksum: 10/57a6141ba17be929291a644991f3a76f94fce330376f6a079decb20fb53378d636ad6878f8f9b6fcb8244cf1ca8b118f9e8901ae04cf3de2aa9f9ff57791d97a + languageName: node + linkType: hard + "@typescript-eslint/typescript-estree@npm:8.18.0": version: 8.18.0 resolution: "@typescript-eslint/typescript-estree@npm:8.18.0" @@ -15073,7 +15216,25 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/utils@npm:8.18.0, @typescript-eslint/utils@npm:^8.1.0, @typescript-eslint/utils@npm:^8.13.0": +"@typescript-eslint/typescript-estree@npm:8.18.1": + version: 8.18.1 + resolution: "@typescript-eslint/typescript-estree@npm:8.18.1" + dependencies: + "@typescript-eslint/types": "npm:8.18.1" + "@typescript-eslint/visitor-keys": "npm:8.18.1" + debug: "npm:^4.3.4" + fast-glob: "npm:^3.3.2" + is-glob: "npm:^4.0.3" + minimatch: "npm:^9.0.4" + semver: "npm:^7.6.0" + ts-api-utils: "npm:^1.3.0" + peerDependencies: + typescript: ">=4.8.4 <5.8.0" + checksum: 10/8ecc1b50b9fc32116eee1b3b00f3fb29cf18026c0bbb50ab5f6e01db58ef62b8ac01824f2950f132479be6e1b82466a2bfd1e2cb4525aa8dbce4c27fc2494cfc + languageName: node + linkType: hard + +"@typescript-eslint/utils@npm:8.18.0": version: 8.18.0 resolution: "@typescript-eslint/utils@npm:8.18.0" dependencies: @@ -15088,6 +15249,21 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/utils@npm:8.18.1, @typescript-eslint/utils@npm:^8.1.0, @typescript-eslint/utils@npm:^8.13.0": + version: 8.18.1 + resolution: "@typescript-eslint/utils@npm:8.18.1" + dependencies: + "@eslint-community/eslint-utils": "npm:^4.4.0" + "@typescript-eslint/scope-manager": "npm:8.18.1" + "@typescript-eslint/types": "npm:8.18.1" + "@typescript-eslint/typescript-estree": "npm:8.18.1" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: ">=4.8.4 <5.8.0" + checksum: 10/7b33d2ac273ad606a3dcb776bcf02c901812952550cdc93d4ece272b3b0e5d2a4e05fa92f9bd466f4a296ddd5992902d3b6623aa1c29d09e8e392897103e42a8 + languageName: node + linkType: hard + "@typescript-eslint/visitor-keys@npm:8.18.0": version: 8.18.0 resolution: "@typescript-eslint/visitor-keys@npm:8.18.0" @@ -15098,6 +15274,16 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/visitor-keys@npm:8.18.1": + version: 8.18.1 + resolution: "@typescript-eslint/visitor-keys@npm:8.18.1" + dependencies: + "@typescript-eslint/types": "npm:8.18.1" + eslint-visitor-keys: "npm:^4.2.0" + checksum: 10/00e88b1640a68c3afea08731395eb09a8216892248fee819cb7526e99093256743239d6b9e880a499f1c0ddfe2ffa4d1ad895d9e778b5d42e702d5880db1a594 + languageName: node + linkType: hard + "@ungap/structured-clone@npm:^1.0.0": version: 1.2.0 resolution: "@ungap/structured-clone@npm:1.2.0" @@ -15105,18 +15291,18 @@ __metadata: languageName: node linkType: hard -"@vanilla-extract/babel-plugin-debug-ids@npm:^1.1.0": - version: 1.1.0 - resolution: "@vanilla-extract/babel-plugin-debug-ids@npm:1.1.0" +"@vanilla-extract/babel-plugin-debug-ids@npm:^1.2.0": + version: 1.2.0 + resolution: "@vanilla-extract/babel-plugin-debug-ids@npm:1.2.0" dependencies: "@babel/core": "npm:^7.23.9" - checksum: 10/0d967e6383a5bd987ded23dd83e781d6a66a583787e6cd356e122d75990f1e02c771c65aff6f52d57dfc0a65f03d4e0c5fe2104e49c0ba0903480db79152be4e + checksum: 10/6d3493c30a321e2570e3851286dfcf64eedc62dfe582b755668de482098e42fa0be6ed95a24c5ee760784948ab4ffaaa3b42058a11dea242fe18d704b5a4996c languageName: node linkType: hard -"@vanilla-extract/css@npm:^1.16.1": - version: 1.16.1 - resolution: "@vanilla-extract/css@npm:1.16.1" +"@vanilla-extract/css@npm:^1.16.1, @vanilla-extract/css@npm:^1.17.0": + version: 1.17.0 + resolution: "@vanilla-extract/css@npm:1.17.0" dependencies: "@emotion/hash": "npm:^0.9.0" "@vanilla-extract/private": "npm:^1.0.6" @@ -15130,7 +15316,7 @@ __metadata: media-query-parser: "npm:^2.0.2" modern-ahocorasick: "npm:^1.0.0" picocolors: "npm:^1.0.0" - checksum: 10/0301a4cf0ca874acc94f264686209c3dce60bad468421515b3f6ccde57a92c0d3997e772d19d9a924e34395e1e1fe890fe8447ed421e3d0ee01175231d2fc663 + checksum: 10/5b811bdce6c4474a3b2c5919b98aca170a7ae297ea9f86f6261a36b32a2ce530d5f2c30da757588de29f943069568322daa624412c837f34f31a8a62c96524f3 languageName: node linkType: hard @@ -15143,14 +15329,14 @@ __metadata: languageName: node linkType: hard -"@vanilla-extract/integration@npm:^7.1.11": - version: 7.1.11 - resolution: "@vanilla-extract/integration@npm:7.1.11" +"@vanilla-extract/integration@npm:^7.1.12": + version: 7.1.12 + resolution: "@vanilla-extract/integration@npm:7.1.12" dependencies: "@babel/core": "npm:^7.23.9" "@babel/plugin-syntax-typescript": "npm:^7.23.3" - "@vanilla-extract/babel-plugin-debug-ids": "npm:^1.1.0" - "@vanilla-extract/css": "npm:^1.16.1" + "@vanilla-extract/babel-plugin-debug-ids": "npm:^1.2.0" + "@vanilla-extract/css": "npm:^1.17.0" dedent: "npm:^1.5.3" esbuild: "npm:esbuild@>=0.17.6 <0.24.0" eval: "npm:0.1.8" @@ -15159,7 +15345,7 @@ __metadata: mlly: "npm:^1.4.2" vite: "npm:^5.0.11" vite-node: "npm:^1.2.0" - checksum: 10/ed448231afdd702ecd4ec587281f32933d6cbc3ed77ea57f7c3cbad05682f1d4e5192c74c9628a1a3f66cb15dadfcd2e258183f31bf7360d4ee1e98b6d38d960 + checksum: 10/37144b62e5c205cd13831c1df3581b990df54cc28c73603307a116263e6f1fe8926cc9539ad81f51529e36ea97d090227dce78553f3bdbaf9b45effb58c78287 languageName: node linkType: hard @@ -15171,27 +15357,27 @@ __metadata: linkType: hard "@vanilla-extract/vite-plugin@npm:^4.0.18": - version: 4.0.18 - resolution: "@vanilla-extract/vite-plugin@npm:4.0.18" + version: 4.0.19 + resolution: "@vanilla-extract/vite-plugin@npm:4.0.19" dependencies: - "@vanilla-extract/integration": "npm:^7.1.11" + "@vanilla-extract/integration": "npm:^7.1.12" peerDependencies: vite: ^4.0.3 || ^5.0.0 - checksum: 10/213f0b6bdc436d6aa51d4d04e306382eb7dd21ff0e832c882deb616203460ac48c9de295983d92592b5bac975f110916bcf7762e24b56726378638f77cb45213 + checksum: 10/5f9e8a2edf6bec04b35129f5a8426b2e389c5f8342726b0ea418e3aaff708d126c6aeddd1878f159a12ea74280697a5477109c38ebd637eb310f521c011b3aaf languageName: node linkType: hard "@vanilla-extract/webpack-plugin@npm:^2.3.15": - version: 2.3.15 - resolution: "@vanilla-extract/webpack-plugin@npm:2.3.15" + version: 2.3.16 + resolution: "@vanilla-extract/webpack-plugin@npm:2.3.16" dependencies: - "@vanilla-extract/integration": "npm:^7.1.11" + "@vanilla-extract/integration": "npm:^7.1.12" debug: "npm:^4.3.1" loader-utils: "npm:^2.0.0" picocolors: "npm:^1.0.0" peerDependencies: webpack: ^4.30.0 || ^5.20.2 - checksum: 10/c8c005384457feb4ed877686b1ecf2e3ad6cbe638115eae46c8fd1f2e43a2d29c2e2e2370fee54476906baa97e602416faae66e90279fc3e80fa890c06463b8c + checksum: 10/40da34a38b7cb3091b978b40ad8d6414dc3b2bd978e4c192ab06a42cc4c55049c1765148697cd71b79b95816c5842f7395c841d30de290da5feade4c6703b77f languageName: node linkType: hard @@ -17400,7 +17586,14 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^5.0.0, chalk@npm:^5.0.1, chalk@npm:^5.3.0, chalk@npm:~5.3.0": +"chalk@npm:^5.0.0, chalk@npm:^5.0.1, chalk@npm:^5.3.0": + version: 5.4.1 + resolution: "chalk@npm:5.4.1" + checksum: 10/29df3ffcdf25656fed6e95962e2ef86d14dfe03cd50e7074b06bad9ffbbf6089adbb40f75c00744d843685c8d008adaf3aed31476780312553caf07fa86e5bc7 + languageName: node + linkType: hard + +"chalk@npm:~5.3.0": version: 5.3.0 resolution: "chalk@npm:5.3.0" checksum: 10/6373caaab21bd64c405bfc4bd9672b145647fc9482657b5ea1d549b3b2765054e9d3d928870cdf764fb4aad67555f5061538ff247b8310f110c5c888d92397ea @@ -20222,7 +20415,7 @@ __metadata: languageName: node linkType: hard -"esbuild@npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0, esbuild@npm:^0.24.0": +"esbuild@npm:0.24.0": version: 0.24.0 resolution: "esbuild@npm:0.24.0" dependencies: @@ -20305,6 +20498,92 @@ __metadata: languageName: node linkType: hard +"esbuild@npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0, esbuild@npm:^0.24.0": + version: 0.24.2 + resolution: "esbuild@npm:0.24.2" + dependencies: + "@esbuild/aix-ppc64": "npm:0.24.2" + "@esbuild/android-arm": "npm:0.24.2" + "@esbuild/android-arm64": "npm:0.24.2" + "@esbuild/android-x64": "npm:0.24.2" + "@esbuild/darwin-arm64": "npm:0.24.2" + "@esbuild/darwin-x64": "npm:0.24.2" + "@esbuild/freebsd-arm64": "npm:0.24.2" + "@esbuild/freebsd-x64": "npm:0.24.2" + "@esbuild/linux-arm": "npm:0.24.2" + "@esbuild/linux-arm64": "npm:0.24.2" + "@esbuild/linux-ia32": "npm:0.24.2" + "@esbuild/linux-loong64": "npm:0.24.2" + "@esbuild/linux-mips64el": "npm:0.24.2" + "@esbuild/linux-ppc64": "npm:0.24.2" + "@esbuild/linux-riscv64": "npm:0.24.2" + "@esbuild/linux-s390x": "npm:0.24.2" + "@esbuild/linux-x64": "npm:0.24.2" + "@esbuild/netbsd-arm64": "npm:0.24.2" + "@esbuild/netbsd-x64": "npm:0.24.2" + "@esbuild/openbsd-arm64": "npm:0.24.2" + "@esbuild/openbsd-x64": "npm:0.24.2" + "@esbuild/sunos-x64": "npm:0.24.2" + "@esbuild/win32-arm64": "npm:0.24.2" + "@esbuild/win32-ia32": "npm:0.24.2" + "@esbuild/win32-x64": "npm:0.24.2" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-arm64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-arm64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10/95425071c9f24ff88bf61e0710b636ec0eb24ddf8bd1f7e1edef3044e1221104bbfa7bbb31c18018c8c36fa7902c5c0b843f829b981ebc89160cf5eebdaa58f4 + languageName: node + linkType: hard + "esbuild@npm:esbuild@>=0.17.6 <0.24.0": version: 0.23.1 resolution: "esbuild@npm:0.23.1" @@ -22550,9 +22829,9 @@ __metadata: languageName: node linkType: hard -"hast-util-to-html@npm:^9.0.0, hast-util-to-html@npm:^9.0.3": - version: 9.0.3 - resolution: "hast-util-to-html@npm:9.0.3" +"hast-util-to-html@npm:^9.0.0, hast-util-to-html@npm:^9.0.4": + version: 9.0.4 + resolution: "hast-util-to-html@npm:9.0.4" dependencies: "@types/hast": "npm:^3.0.0" "@types/unist": "npm:^3.0.0" @@ -22565,7 +22844,7 @@ __metadata: space-separated-tokens: "npm:^2.0.0" stringify-entities: "npm:^4.0.0" zwitch: "npm:^2.0.4" - checksum: 10/cdf860be567137d045490b0f27590bcafc7032f0725a84667e8950d7bf2ce175d0dfc635b7ce05f3a8d1963ac4c74cae4d93513047429aad909222decdb2f7d1 + checksum: 10/a0b4ed9058e57fa2ca010d10c077fda78d2ab2af99f5bd09fe4b9948970025ac4a2a1a03ec7e2e0f3b0444066b1b35d602fa3e9fbd9b7fc9cdd35d0cafa909ca languageName: node linkType: hard @@ -22736,8 +23015,8 @@ __metadata: linkType: hard "html-validate@npm:^8.27.0": - version: 8.27.0 - resolution: "html-validate@npm:8.27.0" + version: 8.29.0 + resolution: "html-validate@npm:8.29.0" dependencies: "@html-validate/stylish": "npm:^4.1.0" "@sidvind/better-ajv-errors": "npm:3.0.1" @@ -22763,7 +23042,7 @@ __metadata: optional: true bin: html-validate: bin/html-validate.js - checksum: 10/6fbced77ac357836b9a55a5cf2da86f7d03b544e1d3d0526232be301f9a12eeaa29f4421d5e6c692ca78f5196b0c36d879f37b9e666a7f67035066103f415590 + checksum: 10/4b730067ed9ccd8c1a38a452495f13bdc16bd2bbecbc8166546f66ed8e25ca7e82e7a8245d73caad886fc3b11b3ccecd347b463c4280556c45a979eb07b6462d languageName: node linkType: hard @@ -23052,8 +23331,8 @@ __metadata: linkType: hard "i18next@npm:^24.1.0": - version: 24.1.0 - resolution: "i18next@npm:24.1.0" + version: 24.2.0 + resolution: "i18next@npm:24.2.0" dependencies: "@babel/runtime": "npm:^7.23.2" peerDependencies: @@ -23061,7 +23340,7 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 10/3a71b5052d84388a9097b594c2b8dc8bfba3f3198d1ca89217e5fb4836d528aa1aee54432269ae092c439e2a12bb71c2abe05abc03cd3e657a001fe5e6883b51 + checksum: 10/43e15d15fcf046c0fb7a44f7de6c224e17145c66334e81bb45b36b0e3e550cc849f80f9de406f3805f57c2050fd8d33bed561933dfaca5e4681a8addc1957be1 languageName: node linkType: hard @@ -23350,8 +23629,8 @@ __metadata: linkType: hard "ioredis@npm:^5.4.1": - version: 5.4.1 - resolution: "ioredis@npm:5.4.1" + version: 5.4.2 + resolution: "ioredis@npm:5.4.2" dependencies: "@ioredis/commands": "npm:^1.1.1" cluster-key-slot: "npm:^1.1.0" @@ -23362,7 +23641,7 @@ __metadata: redis-errors: "npm:^1.2.0" redis-parser: "npm:^3.0.0" standard-as-callback: "npm:^2.1.0" - checksum: 10/9043b812ac58065e80c759d130602cc64490fcaeaacf93723453fda04c7ba61dab0e2f50380eacb045592378ededf44f270c0d43e13e3e8b8d7c5a8d7fecb823 + checksum: 10/1ba306dfb5ec03b07f797e7c55da99207448ce5f733ffd46b03a215ec4db73bd572adb7cf20c993e04f437e80ba4ebe261077d0fc98ed14162cd1b09aa4b8634 languageName: node linkType: hard @@ -24109,11 +24388,11 @@ __metadata: linkType: hard "jotai-effect@npm:^1.0.5": - version: 1.0.5 - resolution: "jotai-effect@npm:1.0.5" + version: 1.0.7 + resolution: "jotai-effect@npm:1.0.7" peerDependencies: jotai: ">=2.5.0" - checksum: 10/b085ebfc5f97e8422f485742869048161cef80fea1b7403a2d22756c7b61fece6f3ee68d53d106a8a682b32455a289368b2d0bb2f0f861130017ae5d0e6939ba + checksum: 10/caa339834bea6e1f9a4ad3acb63ebff5873fb8a58ef3d41c9be09b7ba4f8a726b2faea48da65281adac1044385b4ee1f51c66394c50fe1aa94cb81d92a404e77 languageName: node linkType: hard @@ -24128,8 +24407,8 @@ __metadata: linkType: hard "jotai@npm:^2.10.3": - version: 2.10.3 - resolution: "jotai@npm:2.10.3" + version: 2.10.4 + resolution: "jotai@npm:2.10.4" peerDependencies: "@types/react": ">=17.0.0" react: ">=17.0.0" @@ -24138,7 +24417,7 @@ __metadata: optional: true react: optional: true - checksum: 10/b81722db9575a72f45ee451b5fac9bdc2c119d4dadfdda2606e646d6aee309d02e26afabeaa5939c722f5e78bac21de4c9ee15fb5ea2815a924be87dd1ff4faf + checksum: 10/4ea2be5150c211178dba25716e5d090896f1cdca60a8be466db9eb3f5dd7e445695193d8d36c7d1e0bc0aa887bdd99dae31c191950046ae4f40556c3de4cb2fe languageName: node linkType: hard @@ -24389,22 +24668,22 @@ __metadata: linkType: hard "katex@npm:^0.16.0, katex@npm:^0.16.11": - version: 0.16.15 - resolution: "katex@npm:0.16.15" + version: 0.16.18 + resolution: "katex@npm:0.16.18" dependencies: commander: "npm:^8.3.0" bin: katex: cli.js - checksum: 10/4c1076a066d600809c790def05d88c2a4caba1f695a3a61b44a5085e4fb0f513f3c2acc42720e62df7b08561647d3a96361e775d219d5637cfcc15aa90493747 + checksum: 10/5c90a770969eaaa18cb62e5a2f4937704e4378e6c5848e02138b3a681bc4bda5faf3fba33a12642f042345c429fafb4af48c2e36ecbe953f7db2cacb7cedc28f languageName: node linkType: hard "keyv@npm:*, keyv@npm:^5.2.2": - version: 5.2.2 - resolution: "keyv@npm:5.2.2" + version: 5.2.3 + resolution: "keyv@npm:5.2.3" dependencies: - "@keyv/serialize": "npm:^1.0.1" - checksum: 10/8d29bb25355a29fe91d3b3f49bf5fa30970b8eebce7aec0be8b93be9379099945deec4c668ffd039ea48cfb22a98831c6531a22e688303796620a46f16560936 + "@keyv/serialize": "npm:^1.0.2" + checksum: 10/47b4e9deb33e6a80e5ea79f3022ed3a14bc9fe553b7527ffff0a70b10c7a6c1a5d7e49b9bcfdbd8e8b9fb4632d68baa19d09e82628bcf853103e750e56d49a9e languageName: node linkType: hard @@ -25096,12 +25375,12 @@ __metadata: languageName: node linkType: hard -"lucide-react@npm:^0.468.0": - version: 0.468.0 - resolution: "lucide-react@npm:0.468.0" +"lucide-react@npm:^0.469.0": + version: 0.469.0 + resolution: "lucide-react@npm:0.469.0" peerDependencies: - react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc - checksum: 10/b61907e20c529a6040e43901ba3e4dbcae226b7f9faf9a3c03ccbd3acab1b8241d309e481d6103eca584ed21fb1471204d6d729539079c09ab3313174bf40efc + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10/8cf1f5b125354f1ac4304bb0eead52f369fd67866199bc37a63c856369c97003702b90522e257971670e75acaaf1e55c05c8e4960aaa7ba96dc084d3f0505102 languageName: node linkType: hard @@ -26120,9 +26399,9 @@ __metadata: languageName: node linkType: hard -"miniflare@npm:3.20241205.0": - version: 3.20241205.0 - resolution: "miniflare@npm:3.20241205.0" +"miniflare@npm:3.20241218.0": + version: 3.20241218.0 + resolution: "miniflare@npm:3.20241218.0" dependencies: "@cspotcode/source-map-support": "npm:0.8.1" acorn: "npm:^8.8.0" @@ -26132,13 +26411,13 @@ __metadata: glob-to-regexp: "npm:^0.4.1" stoppable: "npm:^1.1.0" undici: "npm:^5.28.4" - workerd: "npm:1.20241205.0" + workerd: "npm:1.20241218.0" ws: "npm:^8.18.0" youch: "npm:^3.2.2" zod: "npm:^3.22.3" bin: miniflare: bootstrap.js - checksum: 10/0efbb456890f0293af129b4850c6e420530384d674ed02e95b9195fdabba270b6016bd44cc52b92a4d9be037b0e522949d6ab463c17dfadc86014fefcba461b7 + checksum: 10/caa49c80dc39eaa652d7688fe5914dab8d207d8db17e498c50de1554c73992d068c7e40b73bcdec30046ae2f915d1012aa7a7958e4428d60336ad455fa8235d0 languageName: node linkType: hard @@ -26343,11 +26622,11 @@ __metadata: linkType: hard "mixpanel-browser@npm:^2.56.0": - version: 2.56.0 - resolution: "mixpanel-browser@npm:2.56.0" + version: 2.58.0 + resolution: "mixpanel-browser@npm:2.58.0" dependencies: rrweb: "npm:2.0.0-alpha.13" - checksum: 10/2fe134e1fc28f22e957ef4bc89a133b1386143673583321df437688926b75c76b7fa61aa1ccf64d15121a99c0f51ad5f7f3d89dda9219d18849b34dcdf8ff0a6 + checksum: 10/c57d460d542a92c21ac7f0730543f03429c0d9d99fa82e0098309fda8d981a30a981673e2ef8749394bf9bc34ef55e24df48fe778a06510eae54252e16bcdac3 languageName: node linkType: hard @@ -27219,14 +27498,14 @@ __metadata: languageName: node linkType: hard -"oniguruma-to-es@npm:0.7.0": - version: 0.7.0 - resolution: "oniguruma-to-es@npm:0.7.0" +"oniguruma-to-es@npm:0.8.1": + version: 0.8.1 + resolution: "oniguruma-to-es@npm:0.8.1" dependencies: emoji-regex-xs: "npm:^1.0.0" regex: "npm:^5.0.2" - regex-recursion: "npm:^4.3.0" - checksum: 10/766f2c4a9a9eb97070914ebbd78517d073c58f2558994cffb58b064facf860b8f568c7146281e527c796631e93ac23cc1c4b897436189033785429a4486ad41d + regex-recursion: "npm:^5.0.0" + checksum: 10/ce79f62d186eebfc38566a418448d4e57fd1301414ff4affaba4b2236e304f2c89f3cab4138283151d937a172ff42e976c231b16e24ae61b7c4607fb11151df1 languageName: node linkType: hard @@ -27266,8 +27545,8 @@ __metadata: linkType: hard "openai@npm:^4.76.2": - version: 4.76.3 - resolution: "openai@npm:4.76.3" + version: 4.77.0 + resolution: "openai@npm:4.77.0" dependencies: "@types/node": "npm:^18.11.18" "@types/node-fetch": "npm:^2.6.4" @@ -27283,7 +27562,7 @@ __metadata: optional: true bin: openai: bin/cli - checksum: 10/98856dd17ac3ec2cb787ab4b6d4bd1b7c81a725856c5fc7eecc1557d4b28078e79aa45ec382d9f2bdd7ec8d59478e04a0ec5cb2baedee42c965cbc4d5151b88e + checksum: 10/51ef7522d3c3589401b9bb0d49a3bd9df2a84648b996b3452dd4bae99c8e6b27d63a415c41847900954197051b5598de85a76f12c6b2deab33d8d70ceab5d7c3 languageName: node linkType: hard @@ -29170,11 +29449,11 @@ __metadata: linkType: hard "react-hook-form@npm:^7.54.1": - version: 7.54.1 - resolution: "react-hook-form@npm:7.54.1" + version: 7.54.2 + resolution: "react-hook-form@npm:7.54.2" peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 - checksum: 10/64060da8960a50584002bd5549f59dcb4d9c9b223b21ced1096a539ac520a3d9b65a8eb08c62d7f00b751f421ac133e3f82f23e01292dd2775d93f85791fd2f3 + checksum: 10/b156d15b6246c76d0275e5722d9056014693e014d0e3dec06e44bf2672ee549aaba4366de5144d18c4cab29e631f3b2b84269d4fd5727ca17aad9b970fde6960 languageName: node linkType: hard @@ -29228,38 +29507,38 @@ __metadata: languageName: node linkType: hard -"react-remove-scroll-bar@npm:^2.3.6": - version: 2.3.6 - resolution: "react-remove-scroll-bar@npm:2.3.6" +"react-remove-scroll-bar@npm:^2.3.7": + version: 2.3.8 + resolution: "react-remove-scroll-bar@npm:2.3.8" dependencies: - react-style-singleton: "npm:^2.2.1" + react-style-singleton: "npm:^2.2.2" tslib: "npm:^2.0.0" peerDependencies: - "@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + "@types/react": "*" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 peerDependenciesMeta: "@types/react": optional: true - checksum: 10/5ab8eda61d5b10825447d11e9c824486c929351a471457c22452caa19b6898e18c3af6a46c3fa68010c713baed1eb9956106d068b4a1058bdcf97a1a9bbed734 + checksum: 10/6c0f8cff98b9f49a4ee2263f1eedf12926dced5ce220fbe83bd93544460e2a7ec8ec39b35d1b2a75d2fced0b2d64afeb8e66f830431ca896e05a20585f9fc350 languageName: node linkType: hard -"react-remove-scroll@npm:2.6.0": - version: 2.6.0 - resolution: "react-remove-scroll@npm:2.6.0" +"react-remove-scroll@npm:^2.6.1": + version: 2.6.2 + resolution: "react-remove-scroll@npm:2.6.2" dependencies: - react-remove-scroll-bar: "npm:^2.3.6" + react-remove-scroll-bar: "npm:^2.3.7" react-style-singleton: "npm:^2.2.1" tslib: "npm:^2.1.0" - use-callback-ref: "npm:^1.3.0" + use-callback-ref: "npm:^1.3.3" use-sidecar: "npm:^1.1.2" peerDependencies: - "@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + "@types/react": "*" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true - checksum: 10/9fac79e1c2ed2c85729bfe82f61ef4ae5ce51f478736a13892a9a11e05cbd4e9599f9f0e012cb5fc0719e18dc1dd687ab61f516193228615df636db8b851245e + checksum: 10/e6d7e8c42793ae24c9d4a6cb3ebdf3499c1b413fb0c93c3fbf047d875396e944e88a0520dd7db3a20db37125ffadef45b1ce8cb77b74da44daf47c5eb2155c9a languageName: node linkType: hard @@ -29274,43 +29553,42 @@ __metadata: linkType: hard "react-router-dom@npm:^6.28.0": - version: 6.28.0 - resolution: "react-router-dom@npm:6.28.0" + version: 6.28.1 + resolution: "react-router-dom@npm:6.28.1" dependencies: "@remix-run/router": "npm:1.21.0" - react-router: "npm:6.28.0" + react-router: "npm:6.28.1" peerDependencies: react: ">=16.8" react-dom: ">=16.8" - checksum: 10/e637825132ea96c3514ef7b8322f9bf0b752a942d6b4ffc4c20e389b5911726adf3dba8208ed4b97bf5b9c3bd465d9d1a1db1a58a610a8d528f18d890e0b143f + checksum: 10/ce00a6e89add5aeed0f3c881714752be8642ad1e8dd70d337a6bb71b59656afc0385b730bfad21d094f198d0497eb47ed930d8be09ce50e6dcc1eea6c9ab79a2 languageName: node linkType: hard -"react-router@npm:6.28.0": - version: 6.28.0 - resolution: "react-router@npm:6.28.0" +"react-router@npm:6.28.1": + version: 6.28.1 + resolution: "react-router@npm:6.28.1" dependencies: "@remix-run/router": "npm:1.21.0" peerDependencies: react: ">=16.8" - checksum: 10/f021a644513144884a567d9c2dcc432e8e3233f931378c219c5a3b5b842340f0faca86225a708bafca1e9010965afe1a7dada28aef5b7b6138c885c0552d9a7d + checksum: 10/5bf02fe9f43c49580ee162824c4e3597accbed37df8e0b0705d90f56c647ae2c4c19695fcef39ed2ea7434c6865b93afbddcf4643a5e51d77299577474070c39 languageName: node linkType: hard -"react-style-singleton@npm:^2.2.1": - version: 2.2.1 - resolution: "react-style-singleton@npm:2.2.1" +"react-style-singleton@npm:^2.2.1, react-style-singleton@npm:^2.2.2": + version: 2.2.3 + resolution: "react-style-singleton@npm:2.2.3" dependencies: get-nonce: "npm:^1.0.0" - invariant: "npm:^2.2.4" tslib: "npm:^2.0.0" peerDependencies: - "@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + "@types/react": "*" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true - checksum: 10/80c58fd6aac3594e351e2e7b048d8a5b09508adb21031a38b3c40911fe58295572eddc640d4b20a7be364842c8ed1120fe30097e22ea055316b375b88d4ff02a + checksum: 10/62498094ff3877a37f351b29e6cad9e38b2eb1ac3c0cb27ebf80aee96554f80b35e17bdb552bcd7ac8b7cb9904fea93ea5668f2057c73d38f90b5d46bb9b27ab languageName: node linkType: hard @@ -29566,12 +29844,12 @@ __metadata: languageName: node linkType: hard -"regex-recursion@npm:^4.3.0": - version: 4.3.0 - resolution: "regex-recursion@npm:4.3.0" +"regex-recursion@npm:^5.0.0": + version: 5.0.0 + resolution: "regex-recursion@npm:5.0.0" dependencies: regex-utilities: "npm:^2.3.0" - checksum: 10/bbb7fcd6542c980cb3a4571186928826b263759e89bbc1c7b313d9f1064b6b1878c414a696b9cee01156a42225e508a62003f3edaab52a0a3344debf3211ebd8 + checksum: 10/0955c6595d2decbe7fc6ce43e1dd1a0a52c1a1c870560bd933d4437e243f87e05a02d3f5215f833c9f5b6c4475cb79c65455310eb8397e284c9ee55d4c8ccb9c languageName: node linkType: hard @@ -30811,16 +31089,16 @@ __metadata: linkType: hard "shiki@npm:^1.12.0, shiki@npm:^1.14.1": - version: 1.24.2 - resolution: "shiki@npm:1.24.2" - dependencies: - "@shikijs/core": "npm:1.24.2" - "@shikijs/engine-javascript": "npm:1.24.2" - "@shikijs/engine-oniguruma": "npm:1.24.2" - "@shikijs/types": "npm:1.24.2" - "@shikijs/vscode-textmate": "npm:^9.3.0" + version: 1.24.4 + resolution: "shiki@npm:1.24.4" + dependencies: + "@shikijs/core": "npm:1.24.4" + "@shikijs/engine-javascript": "npm:1.24.4" + "@shikijs/engine-oniguruma": "npm:1.24.4" + "@shikijs/types": "npm:1.24.4" + "@shikijs/vscode-textmate": "npm:^9.3.1" "@types/hast": "npm:^3.0.4" - checksum: 10/e17158f2db3f14bcc109e9051eb575ec11405a56012c02238f03a6b259529f077e0d976f527203ee7f2b4a7cc92952180fd7f469444b35e5cc43ef74a5c9fd4b + checksum: 10/a0e5702b0cdf6febd70d5b77c1c7c06bad369348891762c7d84e71480458693a64184e120f1b28897b980d747bfb4c9b16df8ea1b6d88d1fdaaf6086493b391d languageName: node linkType: hard @@ -31702,12 +31980,12 @@ __metadata: linkType: hard "stripe@npm:^17.4.0": - version: 17.4.0 - resolution: "stripe@npm:17.4.0" + version: 17.5.0 + resolution: "stripe@npm:17.5.0" dependencies: "@types/node": "npm:>=8.1.0" qs: "npm:^6.11.0" - checksum: 10/19d783858dbb65db5c8a95f495773b933e6b15107a960eebb4ba29af38c5ef69ce33ad4b8ec0138827bb5d50e2c166322d0270affccbd6d1b99f5b8d7b924c84 + checksum: 10/1d795e8dc5226105d33ce02340f805451b9d6fbd327f38063061f303db73eb2425cb24be5e948a20975af8b496ac3def52aa5ff41bf4c0b204d18f9f46a766de languageName: node linkType: hard @@ -31975,8 +32253,8 @@ __metadata: linkType: hard "tailwindcss@npm:^3.4.16": - version: 3.4.16 - resolution: "tailwindcss@npm:3.4.16" + version: 3.4.17 + resolution: "tailwindcss@npm:3.4.17" dependencies: "@alloc/quick-lru": "npm:^5.2.0" arg: "npm:^5.0.2" @@ -32003,7 +32281,7 @@ __metadata: bin: tailwind: lib/cli.js tailwindcss: lib/cli.js - checksum: 10/d84b3d9bd8f3d53021b6754e3d7efa704cf32f72714dea2036d955fe46ea4154180d2c47593881a9c524229f9efc13fa924fa6347fc8969427036329ee8a9338 + checksum: 10/b0e00533ae3800223b5b71af9cb1dd9bfea5ef5ffa01300f1ced99de9511487aa41e03106173e4168c56c8f6600ee21c98c1d75a5def23cddf9b39b4ad71210d languageName: node linkType: hard @@ -32219,9 +32497,9 @@ __metadata: linkType: hard "tiktoken@npm:^1.0.17": - version: 1.0.17 - resolution: "tiktoken@npm:1.0.17" - checksum: 10/da536ed8a9bbcd32d79869e808165c905785b011bfa318bec9db917350bc8caeceb53c39caceb891feac1ac72fee4a49fcf4af319d2fbe64861fbe6148c28662 + version: 1.0.18 + resolution: "tiktoken@npm:1.0.18" + checksum: 10/af75830ac076dbf013bf90854e3af0070017d5afd9efe07d56a6d94cf1cd6d218e13c68897e8b21550297c44b44c2fbd95a83eddf9ddc503016f14c0d7aa041b languageName: node linkType: hard @@ -32804,16 +33082,16 @@ __metadata: linkType: hard "typescript-eslint@npm:^8.18.0": - version: 8.18.0 - resolution: "typescript-eslint@npm:8.18.0" + version: 8.18.1 + resolution: "typescript-eslint@npm:8.18.1" dependencies: - "@typescript-eslint/eslint-plugin": "npm:8.18.0" - "@typescript-eslint/parser": "npm:8.18.0" - "@typescript-eslint/utils": "npm:8.18.0" + "@typescript-eslint/eslint-plugin": "npm:8.18.1" + "@typescript-eslint/parser": "npm:8.18.1" + "@typescript-eslint/utils": "npm:8.18.1" peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <5.8.0" - checksum: 10/e39d39e25d3916b3c94715db3cb84cf7564b92e08ea026a5d6116a1bd6c8e0c1bfcadad2d26bdba195a59b0e0c1bed296f50b78a66f3516e13e9a6c380546719 + checksum: 10/2be2a14c10fc0988f50e63899e21980c53f6686b60bdda61750577e1481f3e857cf1d5de360849288a220cc053da84e678ca304935d885fe6365afc27e0b9fd2 languageName: node linkType: hard @@ -32955,9 +33233,9 @@ __metadata: linkType: hard "undici@npm:^7.1.0": - version: 7.1.0 - resolution: "undici@npm:7.1.0" - checksum: 10/2197a002cac6ac5720c7b16d0264f838308331908cde60817269fff808f57f746b74578ae56879f62ec8491b6c8001dd8560e746275636efdd6889791d63a321 + version: 7.2.0 + resolution: "undici@npm:7.2.0" + checksum: 10/35828910a11b8230e36147555ff6d65ce65c1a534ce6a92cb08a324b2c96fd52d7adb7a9864c877dea3149f678118fa233d43f4812eb8151678cf4aa6b4926d5 languageName: node linkType: hard @@ -33305,18 +33583,18 @@ __metadata: languageName: node linkType: hard -"use-callback-ref@npm:^1.3.0": - version: 1.3.2 - resolution: "use-callback-ref@npm:1.3.2" +"use-callback-ref@npm:^1.3.3": + version: 1.3.3 + resolution: "use-callback-ref@npm:1.3.3" dependencies: tslib: "npm:^2.0.0" peerDependencies: - "@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + "@types/react": "*" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true - checksum: 10/3be76eae71b52ab233b4fde974eddeff72e67e6723100a0c0297df4b0d60daabedfa706ffb314d0a52645f2c1235e50fdbd53d99f374eb5df68c74d412e98a9b + checksum: 10/adf06a7b6a27d3651c325ac9b66d2b82ccacaed7450b85b211d123e91d9a23cb5a587fcc6db5b4fd07ac7233e5abf024d30cf02ddc2ec46bca712151c0836151 languageName: node linkType: hard @@ -33593,11 +33871,11 @@ __metadata: languageName: node linkType: hard -"vite@npm:6.0.3": - version: 6.0.3 - resolution: "vite@npm:6.0.3" +"vite@npm:6.0.5": + version: 6.0.5 + resolution: "vite@npm:6.0.5" dependencies: - esbuild: "npm:^0.24.0" + esbuild: "npm:0.24.0" fsevents: "npm:~2.3.3" postcss: "npm:^8.4.49" rollup: "npm:^4.23.0" @@ -33641,7 +33919,7 @@ __metadata: optional: true bin: vite: bin/vite.js - checksum: 10/eca0949b8cbc887e78977515d8fc22eaa2d03425d60a0a422f2db1da9d26bd1b431b2815a273c798e8e3fe176a99e105c3d87b0ba615ca19b8bf19e0334d807a + checksum: 10/582bc53c14ae8c8dcb6887a1a24fb554ad6f19446ae0f9df18a47e7ab8787a08687e94a2f4c8636001233207640f91d081e31b00cc3ef9d90908a4bae26410b4 languageName: node linkType: hard @@ -34071,15 +34349,15 @@ __metadata: languageName: node linkType: hard -"workerd@npm:1.20241205.0": - version: 1.20241205.0 - resolution: "workerd@npm:1.20241205.0" +"workerd@npm:1.20241218.0": + version: 1.20241218.0 + resolution: "workerd@npm:1.20241218.0" dependencies: - "@cloudflare/workerd-darwin-64": "npm:1.20241205.0" - "@cloudflare/workerd-darwin-arm64": "npm:1.20241205.0" - "@cloudflare/workerd-linux-64": "npm:1.20241205.0" - "@cloudflare/workerd-linux-arm64": "npm:1.20241205.0" - "@cloudflare/workerd-windows-64": "npm:1.20241205.0" + "@cloudflare/workerd-darwin-64": "npm:1.20241218.0" + "@cloudflare/workerd-darwin-arm64": "npm:1.20241218.0" + "@cloudflare/workerd-linux-64": "npm:1.20241218.0" + "@cloudflare/workerd-linux-arm64": "npm:1.20241218.0" + "@cloudflare/workerd-windows-64": "npm:1.20241218.0" dependenciesMeta: "@cloudflare/workerd-darwin-64": optional: true @@ -34093,16 +34371,15 @@ __metadata: optional: true bin: workerd: bin/workerd - checksum: 10/c66c79f290a247f86f28182218f4d1c54736b6a954827a447299fd7b38a4d9483b3ee10c936335a336594001c174e62d578f7a95d2044f56b56d1162eb9aa719 + checksum: 10/dcf152edf5dff9ab82858df1e70ef2550258b52ee682a5d370d60cd40cc6993c0a136bfe0d6a7c39428a333cf2d440106024087766aff1530c4cddaad2e53610 languageName: node linkType: hard "wrangler@npm:^3.95.0": - version: 3.95.0 - resolution: "wrangler@npm:3.95.0" + version: 3.99.0 + resolution: "wrangler@npm:3.99.0" dependencies: "@cloudflare/kv-asset-handler": "npm:0.3.4" - "@cloudflare/workers-shared": "npm:0.11.0" "@esbuild-plugins/node-globals-polyfill": "npm:^0.2.3" "@esbuild-plugins/node-modules-polyfill": "npm:^0.2.2" blake3-wasm: "npm:^2.1.5" @@ -34111,17 +34388,17 @@ __metadata: esbuild: "npm:0.17.19" fsevents: "npm:~2.3.2" itty-time: "npm:^1.0.6" - miniflare: "npm:3.20241205.0" + miniflare: "npm:3.20241218.0" nanoid: "npm:^3.3.3" path-to-regexp: "npm:^6.3.0" resolve: "npm:^1.22.8" selfsigned: "npm:^2.0.1" source-map: "npm:^0.6.1" unenv: "npm:unenv-nightly@2.0.0-20241204-140205-a5d5190" - workerd: "npm:1.20241205.0" + workerd: "npm:1.20241218.0" xxhash-wasm: "npm:^1.0.1" peerDependencies: - "@cloudflare/workers-types": ^4.20241205.0 + "@cloudflare/workers-types": ^4.20241218.0 dependenciesMeta: fsevents: optional: true @@ -34131,7 +34408,7 @@ __metadata: bin: wrangler: bin/wrangler.js wrangler2: bin/wrangler.js - checksum: 10/fc5404e649dc223104ef28281e48d85161cb6732833c050d8ae2546bcd267d83f228d4dbf102d4885f334e77d51691d8f6e29365ff07fc5d77574f35ef7a1d8c + checksum: 10/d176e1442fb9f6607f34e0af73ebed79c7957a6b7c4dc0df4ccd3d42cfd0a00d5a29a57420cf6e0c598c410678e8e2f12877af84acb38d1dd88a1fbd87565fe2 languageName: node linkType: hard From e0d2f509143bec571029f083543e95eebc758b53 Mon Sep 17 00:00:00 2001 From: Saul-Mirone Date: Mon, 23 Dec 2024 03:40:03 +0000 Subject: [PATCH 10/17] fix: drop on editor should prevent default (#9239) --- .../widgets/drag-handle/watchers/drag-event-watcher.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blocksuite/blocks/src/root-block/widgets/drag-handle/watchers/drag-event-watcher.ts b/blocksuite/blocks/src/root-block/widgets/drag-handle/watchers/drag-event-watcher.ts index f35a1eb1ee0b5..8b6b6d47dc7b3 100644 --- a/blocksuite/blocks/src/root-block/widgets/drag-handle/watchers/drag-event-watcher.ts +++ b/blocksuite/blocks/src/root-block/widgets/drag-handle/watchers/drag-event-watcher.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ import type { EmbedCardStyle, NoteBlockModel } from '@blocksuite/affine-model'; import { EMBED_CARD_HEIGHT, @@ -238,6 +237,8 @@ export class DragEventWatcher { const state = context.get('dndState'); const event = state.raw; + event.preventDefault(); + const { clientX, clientY } = event; const point = new Point(clientX, clientY); const element = getClosestBlockComponentByPoint(point.clone()); @@ -262,7 +263,6 @@ export class DragEventWatcher { const index = parent.children.indexOf(model) + (result.type === 'before' ? 0 : 1); - event.preventDefault(); if (matchFlavours(parent, ['affine:note'])) { const snapshot = this._deserializeSnapshot(state); From 97e448fc5759e59899fbbbe1e22fdc5de140b67c Mon Sep 17 00:00:00 2001 From: renovate <29139614+renovate@users.noreply.github.com> Date: Mon, 23 Dec 2024 04:02:52 +0000 Subject: [PATCH 11/17] chore: bump up html-validate version to v9 (#9242) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [html-validate](https://html-validate.org) ([source](https://gitlab.com/html-validate/html-validate)) | [`^8.27.0` -> `^9.0.0`](https://renovatebot.com/diffs/npm/html-validate/8.29.0/9.0.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/html-validate/9.0.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/html-validate/9.0.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/html-validate/8.29.0/9.0.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/html-validate/8.29.0/9.0.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes
    html-validate/html-validate (html-validate) ### [`v9.0.0`](https://gitlab.com/html-validate/html-validate/blob/HEAD/CHANGELOG.md#900-2024-12-23) [Compare Source](https://gitlab.com/html-validate/html-validate/compare/v8.29.0...v9.0.0) ##### ⚠ BREAKING CHANGES - **meta:** The deprecated metadata property expressions have been removed and can be replaced with callback functions. This gives greater control for the metadata author, provides better IDE support and is more reusable when querying the metadata directly. - **api:** `Config.merge(..)` will return a `Promise` when used with an async loader or resolver. - **config:** This change affects all users. The following deprecated configuration presets has been removed: - `htmlvalidate:recommended` - `htmlvalidate:document` - `html-validate:a17y` - **cli:** CLI uses ESM (with `esmResolver`). For most part this shouldn't affect anything but in some cases you might need slight configuration migration. See Migration Guide for details. refactor(cli): use ESM in CLI - **deps:** Requires NodeJS v18 or later. - **api:** The deprecated `tag:open` and `tag:close` events has been removed, use `tag:begin` and `tag:end` instead. - **api:** The `Config.resolve()` method can return a `Promise` if any underlying loader or resolver has returned a `Promise`. It is recommended to assume it returns a `Promise` and always `await` the result: ```diff -const resolved = config.resolve(); +const resolved = await config.resolve(); ``` If you need synchronous code ensure the configuration, the loader and the resolver all returns synchronous results, e.g. the `staticResolver` with synchronous code. - **api:** The `HtmlValidate.getConfigurationSchema()` method is now async and returns a `Promise`. If you use this method you need to await the result: ```diff -const schema = htmlvalidate.getConfigurationSchema(); +const schema = await htmlvalidate.getConfigurationSchema(); ``` - **api:** If you are writing your own transformers they may now optionally return a `Promise`. If you are using `test-utils` to write unit tests you must now resolve the promise. ```diff import { transformSource } from "html-validate/test-utils"; -const result = transformSource(transformer, source); +const result = await transformSource(transformer, source); ``` This is no matter if your transformer is actually async or not. - **api:** The `CLI.isIgnored(..)` method has been removed from the public API. There is no replacement. If you need this method open an issue describing the use-case. - **api:** If you are using the `CLI` class most methods are now async and returns `Promise`. There is no synchronous version of these API calls. - **api:** `Config.fromFile(..)` and `Config.fromObject(..)` will return a Promise when used with an async loader or resolver. - **api:** `ConfigLoader` methods can optionally return a `Promise` for async operation. For most use-cases this will not require any changes. - **api:** The `ConfigLoader.globalConfig` property has been replaced with `ConfigLoader.getGlobalConfig()` (async) and `ConfigLoader.getGlobalConfigSync()` (sync). - **api:** The redundant and deprecated `Config.init()` method has been removed. Remove any calls to the method: ```diff const config = Config.fromObject({ /* ... */ }); -config.init(); ``` ##### Features - **api:** `CLI.isIgnored()` made private ([9e3679a](https://gitlab.com/html-validate/html-validate/commit/9e3679a13a701cbb108ea64ff187ee235124cd21)) - **api:** `CLI` methods async and return Promise ([677c73e](https://gitlab.com/html-validate/html-validate/commit/677c73e51a78b12731837e24a6f6fb33859b5889)) - **api:** `Config.fromFile` and `Config.fromObject` can return `Promise` ([b126361](https://gitlab.com/html-validate/html-validate/commit/b1263617015a54550a13ba6d09c23c82c8c87f10)) - **api:** `Config.merge(..)` can return `Promise` ([cccb313](https://gitlab.com/html-validate/html-validate/commit/cccb313c67ea342a59d7c9887fef8163e9463808)) - **api:** `Config.resolve()` can return `Promise` ([09159f3](https://gitlab.com/html-validate/html-validate/commit/09159f39c579ac97021533c26d0b3def7959ef00)) - **api:** `ConfigLoader`s can optionally return `Promise` for async operation ([6041581](https://gitlab.com/html-validate/html-validate/commit/6041581eb879f68d6ad8bf45b2f216af994cac7d)) - **api:** `FileSystemConfigLoader` uses `esmResolver` by default ([dd4cfb1](https://gitlab.com/html-validate/html-validate/commit/dd4cfb1e1b00c892ca8ad8127cd2589ed5d6a110)) - **api:** `HtmlValidate.getConfigurationSchema()` returns promise ([f10ec1a](https://gitlab.com/html-validate/html-validate/commit/f10ec1a650700778dc52f288f0f7b3e2c6ba4873)) - **api:** allow transformers to return single source ([fd126da](https://gitlab.com/html-validate/html-validate/commit/fd126dab3f69db38407736ea0b885710e98466ad)) - **api:** new `esmResolver` using `import(..)` (available for both nodejs and browser) ([81b4777](https://gitlab.com/html-validate/html-validate/commit/81b47770836e6e44bc917ca739a796e31dd7c1f6)), closes [#​230](https://gitlab.com/html-validate/html-validate/issues/230) - **api:** remove deprecated `Config.init()` ([0bd8ab7](https://gitlab.com/html-validate/html-validate/commit/0bd8ab787a57c72698eb3e250973e26c1a99da89)) - **api:** remove deprecated `tag:open` and `tag:close` events ([88ac65e](https://gitlab.com/html-validate/html-validate/commit/88ac65ee6bc281023153caa81785ea6158631e00)) - **api:** replace `ConfigLoader.globalConfig` with `ConfigLoader.getGlobalConfig()` ([a64935a](https://gitlab.com/html-validate/html-validate/commit/a64935af918d79fb8f485e1ceb228db6f762ccf7)) - **api:** resolvers may optionally return `Promise` for async operation ([fe3c6a6](https://gitlab.com/html-validate/html-validate/commit/fe3c6a63624f7e4eb0641adf881ba1bc130eb3c6)) - **api:** transformers may optionally return `Promise` for async operation ([823da19](https://gitlab.com/html-validate/html-validate/commit/823da1998e5a6eb3956bb4bd0fa7821bfcd81caf)) - **cli:** cli uses esm (with `esmResolver`) ([3e4759e](https://gitlab.com/html-validate/html-validate/commit/3e4759e4a3c259157f331ddee72c3338df80e365)) - **config:** remove deprecated configuration presets ([dbf5cf4](https://gitlab.com/html-validate/html-validate/commit/dbf5cf405b2970840e3df4fc21af40fa34d797ea)) - **config:** support `.htmlvalidate.mjs` configuration files ([0ffd9b5](https://gitlab.com/html-validate/html-validate/commit/0ffd9b5f0ee9f6de757dadf1a11099dcf6952b06)), closes [#​125](https://gitlab.com/html-validate/html-validate/issues/125) - **deps:** require node 18 or later ([d4f3bcb](https://gitlab.com/html-validate/html-validate/commit/d4f3bcb0a6b21d56a063afeea9942a2aa342466c)) - **meta:** remove deprecated property expressions ([a77043c](https://gitlab.com/html-validate/html-validate/commit/a77043cff36220e598796c4b640a31e81d14a6ae))
    --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/toeverything/AFFiNE). --- packages/backend/server/package.json | 2 +- yarn.lock | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/backend/server/package.json b/packages/backend/server/package.json index 9bc3756d1c570..53d8055427f15 100644 --- a/packages/backend/server/package.json +++ b/packages/backend/server/package.json @@ -65,7 +65,7 @@ "graphql": "^16.9.0", "graphql-scalars": "^1.24.0", "graphql-upload": "^17.0.0", - "html-validate": "^8.27.0", + "html-validate": "^9.0.0", "ioredis": "^5.4.1", "is-mobile": "^5.0.0", "keyv": "^5.2.2", diff --git a/yarn.lock b/yarn.lock index 3fc850cf02a33..08b98ec18ebed 100644 --- a/yarn.lock +++ b/yarn.lock @@ -791,7 +791,7 @@ __metadata: graphql: "npm:^16.9.0" graphql-scalars: "npm:^1.24.0" graphql-upload: "npm:^17.0.0" - html-validate: "npm:^8.27.0" + html-validate: "npm:^9.0.0" ioredis: "npm:^5.4.1" is-mobile: "npm:^5.0.0" keyv: "npm:^5.2.2" @@ -23014,9 +23014,9 @@ __metadata: languageName: node linkType: hard -"html-validate@npm:^8.27.0": - version: 8.29.0 - resolution: "html-validate@npm:8.29.0" +"html-validate@npm:^9.0.0": + version: 9.0.0 + resolution: "html-validate@npm:9.0.0" dependencies: "@html-validate/stylish": "npm:^4.1.0" "@sidvind/better-ajv-errors": "npm:3.0.1" @@ -23041,8 +23041,8 @@ __metadata: vitest: optional: true bin: - html-validate: bin/html-validate.js - checksum: 10/4b730067ed9ccd8c1a38a452495f13bdc16bd2bbecbc8166546f66ed8e25ca7e82e7a8245d73caad886fc3b11b3ccecd347b463c4280556c45a979eb07b6462d + html-validate: bin/html-validate.mjs + checksum: 10/56f7fe8404aee38428ebf130b4793ca5eeec8f55e562109c368717015f6ec5a706c2376759c2eb212802aa01a918a55bdd168894d9473989e1fe65f001bd991c languageName: node linkType: hard From c775b2e264ce8c6896f34155a0f11561ec51697a Mon Sep 17 00:00:00 2001 From: pengx17 Date: Mon, 23 Dec 2024 04:19:06 +0000 Subject: [PATCH 12/17] fix(core): text-renderer should be readonly (#9244) fix AF-2010, AF-2009 --- .../blocksuite/presets/_common/components/text-renderer.ts | 7 +++++++ .../src/blocksuite/presets/_common/utils/markdown-utils.ts | 1 - .../core/src/blocksuite/presets/ai/utils/editor-actions.ts | 2 ++ .../block-suite-editor/bi-directional-link-panel.css.ts | 5 +++++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/frontend/core/src/blocksuite/presets/_common/components/text-renderer.ts b/packages/frontend/core/src/blocksuite/presets/_common/components/text-renderer.ts index e3204dff57912..6c5c798be3bff 100644 --- a/packages/frontend/core/src/blocksuite/presets/_common/components/text-renderer.ts +++ b/packages/frontend/core/src/blocksuite/presets/_common/components/text-renderer.ts @@ -195,6 +195,7 @@ export class TextRenderer extends WithDisposable(ShadowlessElement) { if (latestAnswer && schema) { markDownToDoc(schema, latestAnswer, this.options.additionalMiddlewares) .then(doc => { + this.disposeDoc(); this._doc = doc.blockCollection.getDoc({ query: this._query, }); @@ -232,9 +233,15 @@ export class TextRenderer extends WithDisposable(ShadowlessElement) { } } + private disposeDoc() { + this._doc?.dispose(); + this._doc?.collection.dispose(); + } + override disconnectedCallback() { super.disconnectedCallback(); this._clearTimer(); + this.disposeDoc(); } override render() { diff --git a/packages/frontend/core/src/blocksuite/presets/_common/utils/markdown-utils.ts b/packages/frontend/core/src/blocksuite/presets/_common/utils/markdown-utils.ts index 3892190d33c96..b67975588b90a 100644 --- a/packages/frontend/core/src/blocksuite/presets/_common/utils/markdown-utils.ts +++ b/packages/frontend/core/src/blocksuite/presets/_common/utils/markdown-utils.ts @@ -194,7 +194,6 @@ export async function markDownToDoc( const collection = new DocCollection({ schema, }); - collection.awarenessStore.awareness.destroy(); collection.meta.initialize(); const middlewares = [defaultImageProxyMiddleware]; if (additionalMiddlewares) { diff --git a/packages/frontend/core/src/blocksuite/presets/ai/utils/editor-actions.ts b/packages/frontend/core/src/blocksuite/presets/ai/utils/editor-actions.ts index 8f4e84ee93403..811cbca063b08 100644 --- a/packages/frontend/core/src/blocksuite/presets/ai/utils/editor-actions.ts +++ b/packages/frontend/core/src/blocksuite/presets/ai/utils/editor-actions.ts @@ -150,5 +150,7 @@ export const copyText = async (host: EditorHost, text: string) => { .flatMap(model => model.children); const slice = Slice.fromModels(previewDoc, models); await host.std.clipboard.copySlice(slice); + previewDoc.dispose(); + previewDoc.collection.dispose(); return true; }; diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-editor/bi-directional-link-panel.css.ts b/packages/frontend/core/src/components/blocksuite/block-suite-editor/bi-directional-link-panel.css.ts index 330ce6336aa30..a6103298791cd 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-editor/bi-directional-link-panel.css.ts +++ b/packages/frontend/core/src/components/blocksuite/block-suite-editor/bi-directional-link-panel.css.ts @@ -98,6 +98,7 @@ export const linkPreviewContainer = style({ }); export const linkPreview = style({ + cursor: 'default', border: `0.5px solid ${cssVarV2('backlinks/blockBorder')}`, borderRadius: '8px', padding: '8px', @@ -111,6 +112,10 @@ export const linkPreview = style({ }, }); +globalStyle(`${linkPreview} *`, { + cursor: 'default', +}); + export const linkPreviewRenderer = style({ cursor: 'pointer', }); From 2ea79d25ad13b66c9772de930129481e4c7baafb Mon Sep 17 00:00:00 2001 From: pengx17 Date: Mon, 23 Dec 2024 04:37:59 +0000 Subject: [PATCH 13/17] chore(core): events for backlinks (#9228) fix AF-2008 --- .../bi-directional-link-panel.tsx | 26 +++++++++++++++++-- packages/frontend/track/src/events.ts | 5 ++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-editor/bi-directional-link-panel.tsx b/packages/frontend/core/src/components/blocksuite/block-suite-editor/bi-directional-link-panel.tsx index f38a17eef4de1..21df2041567df 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-editor/bi-directional-link-panel.tsx +++ b/packages/frontend/core/src/components/blocksuite/block-suite-editor/bi-directional-link-panel.tsx @@ -12,6 +12,7 @@ import { import { toURLSearchParams } from '@affine/core/modules/navigation'; import { WorkbenchLink } from '@affine/core/modules/workbench'; import { useI18n } from '@affine/i18n'; +import track from '@affine/track'; import type { JobMiddleware } from '@blocksuite/affine/store'; import { ToggleExpandIcon } from '@blocksuite/icons/rc'; import * as Collapsible from '@radix-ui/react-collapsible'; @@ -93,8 +94,16 @@ const CollapsibleSection = ({ docId, linkDocId ); + + const handleToggle = useCallback(() => { + setOpen(!open); + track.doc.biDirectionalLinksPanel.$.toggle({ + type: open ? 'collapse' : 'expand', + }); + }, [open, setOpen]); + return ( - + {title} {length ? ( @@ -197,6 +206,9 @@ export const BiDirectionalLinkPanel = () => { const handleClickShow = useCallback(() => { setShow(!show); + track.doc.biDirectionalLinksPanel.$.toggle({ + type: show ? 'collapse' : 'expand', + }); }, [show, setShow]); const textRendererOptions = useMemo(() => { @@ -243,7 +255,14 @@ export const BiDirectionalLinkPanel = () => { {backlinkGroups.map(linkGroup => ( } + title={ + { + track.doc.biDirectionalLinksPanel.backlinkTitle.navigate(); + }} + /> + } length={linkGroup.links.length} docId={docService.doc.id} linkDocId={linkGroup.docId} @@ -289,6 +308,9 @@ export const BiDirectionalLinkPanel = () => { to={to} key={link.blockId} className={styles.linkPreview} + onClick={() => { + track.doc.biDirectionalLinksPanel.backlinkPreview.navigate(); + }} > {edgelessLink ? ( <> diff --git a/packages/frontend/track/src/events.ts b/packages/frontend/track/src/events.ts index 8d2554622aaab..9e2da883d175e 100644 --- a/packages/frontend/track/src/events.ts +++ b/packages/frontend/track/src/events.ts @@ -322,6 +322,11 @@ const PageEvents = { sidepanel: { property: ['addProperty'], }, + biDirectionalLinksPanel: { + $: ['toggle'], + backlinkTitle: ['toggle', 'navigate'], + backlinkPreview: ['navigate'], + }, }, edgeless: {}, workspace: { From 129f94ee781662f2cf3bf6030ac419ef7fc673a9 Mon Sep 17 00:00:00 2001 From: EYHN Date: Mon, 23 Dec 2024 04:53:59 +0000 Subject: [PATCH 14/17] refactor(core): move infra modules to core (#9207) --- packages/common/infra/src/index.ts | 37 ----- .../infra/src/modules/db/entities/table.ts | 35 ----- .../common/infra/src/modules/storage/index.ts | 35 ----- .../workspace/__tests__/workspace.spec.ts | 31 ---- .../workspace/testing/testing-provider.ts | 145 ------------------ packages/frontend/apps/android/src/app.tsx | 8 +- .../frontend/apps/electron/renderer/app.tsx | 14 +- .../apps/electron/renderer/shell/app.tsx | 9 +- packages/frontend/apps/ios/src/app.tsx | 10 +- packages/frontend/apps/mobile/src/app.tsx | 8 +- packages/frontend/apps/web/src/app.tsx | 8 +- .../error-basic/info-logger.tsx | 7 +- .../components/affine/ai-onboarding/index.tsx | 3 +- .../src/components/affine/awareness/index.tsx | 3 +- .../components/affine/empty/collections.tsx | 3 +- .../core/src/components/affine/empty/docs.tsx | 3 +- .../affine/page-history-modal/data.ts | 6 +- .../page-history-modal/history-modal.tsx | 3 +- .../quota-reached-modal/cloud-quota-modal.tsx | 3 +- .../quota-reached-modal/local-quota-modal.tsx | 3 +- .../affine/reference-link/index.tsx | 8 +- .../affine/share-page-modal/index.tsx | 2 +- .../share-menu/share-menu.tsx | 7 +- .../bi-directional-link-panel.tsx | 10 +- .../block-suite-editor/lit-adaper.tsx | 7 +- .../specs/custom/root-block.ts | 9 +- .../specs/custom/spec-patchers.tsx | 5 +- .../block-suite-editor/specs/edgeless.ts | 6 +- .../block-suite-editor/specs/page.ts | 6 +- .../menu/history-tips-modal/index.tsx | 3 +- .../block-suite-header/menu/index.tsx | 2 +- .../block-suite-header/title/index.tsx | 9 +- .../block-suite-page-list/utils.tsx | 3 +- .../icons/doc-property-icon.tsx | 2 +- .../doc-properties/icons/icons-selector.tsx | 2 +- .../doc-properties/manager/index.tsx | 11 +- .../menu/create-doc-property.tsx | 9 +- .../doc-properties/menu/edit-doc-property.tsx | 3 +- .../doc-properties/sidebar/index.tsx | 3 +- .../src/components/doc-properties/table.tsx | 8 +- .../doc-properties/tags-inline-editor.tsx | 8 +- .../types/created-updated-by.tsx | 3 +- .../components/doc-properties/types/date.tsx | 3 +- .../doc-properties/types/doc-primary-mode.tsx | 3 +- .../doc-properties/types/edgeless-theme.tsx | 3 +- .../doc-properties/types/journal.tsx | 2 +- .../doc-properties/types/page-width.tsx | 3 +- .../components/doc-properties/types/tags.tsx | 3 +- .../components/doc-properties/types/types.ts | 2 +- .../hooks/affine/use-all-page-list-config.tsx | 3 +- .../affine/use-block-suite-meta-helper.ts | 4 +- .../hooks/affine/use-enable-cloud.tsx | 9 +- ...se-register-blocksuite-editor-commands.tsx | 4 +- .../use-register-copy-link-commands.tsx | 2 +- .../components/hooks/affine/use-sign-out.ts | 9 +- .../hooks/use-block-suite-page-meta.ts | 4 +- .../core/src/components/hooks/use-journal.ts | 3 +- .../hooks/use-register-workspace-commands.ts | 7 +- .../components/hooks/use-workspace-info.ts | 7 +- .../src/components/hooks/use-workspace.ts | 8 +- .../src/components/over-capacity/index.tsx | 3 +- .../src/components/page-detail-editor.tsx | 5 +- .../virtualized-collection-list.tsx | 3 +- .../page-list/docs/page-list-header.tsx | 10 +- .../components/page-list/docs/select-page.tsx | 7 +- .../page-list/docs/virtualized-page-list.tsx | 4 +- .../components/page-list/operation-cell.tsx | 12 +- .../page-list/tags/virtualized-tag-list.tsx | 3 +- .../use-all-doc-display-properties.ts | 3 +- .../page-list/view/collection-operations.tsx | 8 +- .../page-list/virtualized-trash-list.tsx | 3 +- .../providers/workspace-side-effects.tsx | 4 +- .../src/components/pure/help-island/index.tsx | 8 +- .../pure/trash-page-footer/index.tsx | 4 +- .../src/components/root-app-sidebar/index.tsx | 2 +- .../root-app-sidebar/trash-button.tsx | 9 +- .../core/src/components/sign-in/sign-in.tsx | 7 +- .../frontend/core/src/components/top-tip.tsx | 3 +- .../src/components/workspace-avatar/index.tsx | 8 +- .../components/workspace-selector/index.tsx | 9 +- .../add-server/index.tsx | 7 +- .../add-workspace/index.tsx | 7 +- .../user-with-workspace-list/index.tsx | 13 +- .../workspace-list/index.tsx | 10 +- .../workspace-card/index.tsx | 12 +- .../workspace-card/pure-workspace-card.tsx | 4 +- .../components/ai-island/container.tsx | 9 +- .../components/app-container/index.tsx | 2 +- .../dialogs/collection-editor/rules-mode.tsx | 3 +- .../dialogs/create-workspace/index.tsx | 9 +- .../src/desktop/dialogs/doc-info/index.tsx | 4 +- .../desktop/dialogs/doc-info/info-modal.tsx | 10 +- .../src/desktop/dialogs/doc-info/time-row.tsx | 9 +- .../desktop/dialogs/enable-cloud/index.tsx | 8 +- .../desktop/dialogs/import-template/index.tsx | 11 +- .../dialogs/import-workspace/index.tsx | 3 +- .../core/src/desktop/dialogs/import/index.tsx | 3 +- .../desktop/dialogs/selectors/collection.tsx | 3 +- .../src/desktop/dialogs/selectors/tag.tsx | 3 +- .../general-setting/appearance/index.tsx | 7 +- .../general-setting/editor/general.tsx | 8 +- .../experimental-features/index.tsx | 13 +- .../dialogs/setting/general-setting/index.tsx | 7 +- .../src/desktop/dialogs/setting/index.tsx | 7 +- .../dialogs/setting/setting-sidebar/index.tsx | 16 +- .../core/src/desktop/dialogs/setting/types.ts | 2 +- .../workspace-setting/billing/index.tsx | 8 +- .../setting/workspace-setting/index.tsx | 2 +- .../delete-leave-workspace/delete/index.tsx | 2 +- .../delete-leave-workspace/index.tsx | 11 +- .../enable-cloud.tsx | 9 +- .../new-workspace-setting-detail/export.tsx | 11 +- .../new-workspace-setting-detail/labels.tsx | 3 +- .../members/index.tsx | 3 +- .../members/member-list.tsx | 2 +- .../new-workspace-setting-detail/profile.tsx | 5 +- .../new-workspace-setting-detail/sharing.tsx | 3 +- .../new-workspace-setting-detail/types.ts | 2 +- .../workspace-setting/properties/index.tsx | 8 +- .../core/src/desktop/pages/index/index.tsx | 2 +- .../desktop/pages/root/custom-theme/index.tsx | 7 +- .../core/src/desktop/pages/root/index.tsx | 7 +- .../desktop/pages/upgrade-to-team/index.tsx | 11 +- .../pages/workspace/all-collection/index.tsx | 3 +- .../workspace/all-page/all-page-filter.tsx | 3 +- .../workspace/all-page/all-page-header.tsx | 3 +- .../pages/workspace/all-page/all-page.tsx | 8 +- .../pages/workspace/attachment/index.tsx | 9 +- .../pages/workspace/collection/index.tsx | 10 +- .../detail-page/detail-page-header.tsx | 3 +- .../detail-page/detail-page-wrapper.tsx | 11 +- .../workspace/detail-page/detail-page.tsx | 10 +- .../workspace/detail-page/tabs/journal.tsx | 13 +- .../src/desktop/pages/workspace/index.tsx | 15 +- .../workspace/layouts/workspace-layout.tsx | 8 +- .../pages/workspace/share/share-page.tsx | 8 +- .../src/desktop/pages/workspace/tag/index.tsx | 9 +- .../desktop/pages/workspace/trash-page.tsx | 8 +- .../src/mobile/components/app-tabs/create.tsx | 3 +- .../mobile/components/app-tabs/tab-item.tsx | 8 +- .../mobile/components/doc-info/doc-info.tsx | 10 +- .../mobile/components/doc-info/doc-scope.tsx | 4 +- .../explorer/nodes/collection/index.tsx | 10 +- .../explorer/nodes/collection/operations.tsx | 9 +- .../components/explorer/nodes/doc/index.tsx | 6 +- .../explorer/nodes/doc/operations.tsx | 12 +- .../explorer/nodes/folder/index.tsx | 9 +- .../components/explorer/nodes/tag/index.tsx | 7 +- .../explorer/nodes/tag/operations.tsx | 14 +- .../explorer/sections/favorites/index.tsx | 7 +- .../workspace-selector/current-card.tsx | 5 +- .../components/workspace-selector/index.tsx | 3 +- .../components/workspace-selector/menu.tsx | 9 +- .../mobile/dialogs/selectors/doc-selector.tsx | 3 +- .../dialogs/setting/experimental/index.tsx | 9 +- .../core/src/mobile/modules/search/index.ts | 3 +- .../pages/workspace/collection/detail.tsx | 9 +- .../detail/journal-conflict-block.tsx | 4 +- .../detail/menu/journal-conflicts.tsx | 13 +- .../detail/menu/journal-today-activity.tsx | 13 +- .../workspace/detail/mobile-detail-page.tsx | 8 +- .../detail/page-header-more-button.tsx | 3 +- .../detail/page-header-share-button.tsx | 4 +- .../core/src/mobile/pages/workspace/index.tsx | 7 +- .../src/mobile/pages/workspace/layout.tsx | 15 +- .../src/mobile/pages/workspace/tag/detail.tsx | 7 +- .../src/mobile/views/all-docs/doc/list.tsx | 3 +- .../src/mobile/views/recent-docs/index.tsx | 3 +- .../src/modules/app-sidebar/impls/storage.ts | 7 +- .../core/src/modules/app-sidebar/index.ts | 3 +- .../app-sidebar/services/app-sidebar.ts | 3 +- .../views/add-page-button/index.tsx | 3 +- .../src/modules/app-sidebar/views/index.tsx | 4 +- .../core/src/modules/at-menu-config/index.ts | 9 +- .../modules/at-menu-config/services/index.ts | 3 +- .../modules/cloud/entities/cloud-doc-meta.ts | 3 +- .../cloud/entities/workspace-invoices.ts | 2 +- .../cloud/entities/workspace-subscription.ts | 3 +- .../frontend/core/src/modules/cloud/index.ts | 14 +- .../core/src/modules/cloud/services/auth.ts | 3 +- .../src/modules/cloud/services/websocket.ts | 3 +- .../core/src/modules/cloud/stores/auth.ts | 2 +- .../src/modules/cloud/stores/server-list.ts | 2 +- .../src/modules/cloud/stores/subscription.ts | 3 +- .../core/src/modules/collection/index.ts | 7 +- .../modules/collection/services/collection.ts | 3 +- .../core}/src/modules/db/entities/db.ts | 8 +- .../core/src/modules/db/entities/table.ts | 39 +++++ .../core}/src/modules/db/index.ts | 3 +- .../core}/src/modules/db/schema/index.ts | 0 .../core}/src/modules/db/schema/schema.ts | 8 +- .../core}/src/modules/db/services/db.ts | 11 +- .../desktop-api/service/desktop-api.ts | 3 +- .../core/src/modules/dialogs/constant.ts | 3 +- .../core/src/modules/dialogs/index.ts | 2 +- .../frontend/core/src/modules/dnd/index.ts | 9 +- .../core/src/modules/dnd/services/index.ts | 3 +- .../src/modules/doc-display-meta/index.ts | 10 +- .../services/doc-display-meta.ts | 7 +- .../core/src/modules/doc-info/index.ts | 8 +- .../services/doc-database-backlinks.ts | 2 +- .../core/src/modules/doc-info/types.ts | 4 +- .../doc-database-backlink-info.tsx | 8 +- .../doc-link/entities/doc-backlinks.ts | 2 +- .../modules/doc-link/entities/doc-links.ts | 2 +- .../core/src/modules/doc-link/index.ts | 10 +- .../core}/src/modules/doc/constants.ts | 0 .../core}/src/modules/doc/entities/doc.ts | 2 +- .../src/modules/doc/entities/property-list.ts | 9 +- .../src/modules/doc/entities/record-list.ts | 3 +- .../core}/src/modules/doc/entities/record.ts | 3 +- .../core}/src/modules/doc/events/index.ts | 3 +- .../core}/src/modules/doc/index.ts | 3 +- .../core}/src/modules/doc/scopes/doc.ts | 2 +- .../core}/src/modules/doc/services/doc.ts | 3 +- .../core}/src/modules/doc/services/docs.ts | 9 +- .../src/modules/doc/stores/doc-properties.ts | 6 +- .../core}/src/modules/doc/stores/docs.ts | 8 +- .../docs-search/entities/docs-indexer.ts | 10 +- .../core/src/modules/docs-search/index.ts | 6 +- .../docs-search/services/docs-search.ts | 10 +- .../modules/docs-search/worker/in-worker.ts | 3 +- .../__test__/editor-setting.spec.ts | 51 ------ .../editor-setting/impls/global-state.ts | 2 +- .../modules/editor-setting/impls/user-db.ts | 2 +- .../core/src/modules/editor-setting/index.ts | 7 +- .../editor-setting/services/editor-setting.ts | 11 +- .../services/spell-check-setting.ts | 2 +- .../src/modules/editor/entities/editor.ts | 3 +- .../frontend/core/src/modules/editor/index.ts | 10 +- .../explorer/entities/explore-section.ts | 2 +- .../core/src/modules/explorer/index.ts | 8 +- .../explorer/views/nodes/collection/index.tsx | 4 +- .../views/nodes/collection/operations.tsx | 9 +- .../explorer/views/nodes/doc/index.tsx | 6 +- .../explorer/views/nodes/doc/operations.tsx | 11 +- .../explorer/views/nodes/folder/index.tsx | 9 +- .../explorer/views/nodes/tag/index.tsx | 7 +- .../explorer/views/nodes/tag/operations.tsx | 11 +- .../views/sections/favorites/index.tsx | 7 +- .../sections/migration-favorites/index.tsx | 3 +- .../core/src/modules/favorite/index.ts | 9 +- .../modules/favorite/services/old/adapter.ts | 4 +- .../src/modules/favorite/stores/favorite.ts | 3 +- .../src/modules/feature-flag/constant.ts | 0 .../modules/feature-flag/entities/flags.ts | 3 +- .../core}/src/modules/feature-flag/index.ts | 3 +- .../feature-flag/services/feature-flag.ts | 2 +- .../core}/src/modules/feature-flag/types.ts | 0 .../global-context/entities/global-context.ts | 5 +- .../core}/src/modules/global-context/index.ts | 3 +- .../global-context/services/global-context.ts | 3 +- .../core/src/modules/i18n/entities/i18n.ts | 3 +- .../frontend/core/src/modules/i18n/index.ts | 3 +- .../core/src/modules/import-template/index.ts | 3 +- .../import-template/services/import.ts | 6 +- packages/frontend/core/src/modules/index.ts | 21 ++- .../core/src/modules/journal/index.ts | 10 +- .../modules/journal/services/journal-doc.ts | 3 +- .../src/modules/journal/services/journal.ts | 3 +- .../core/src/modules/journal/store/journal.ts | 3 +- .../core}/src/modules/lifecycle/index.ts | 3 +- .../modules/lifecycle/service/lifecycle.ts | 2 +- .../core/src/modules/navigation/index.ts | 3 +- .../core/src/modules/open-in-app/index.ts | 8 +- .../src/modules/open-in-app/services/index.ts | 3 +- .../core/src/modules/organize/index.ts | 8 +- .../src/modules/organize/stores/folder.ts | 3 +- .../frontend/core/src/modules/pdf/index.ts | 2 +- .../core/src/modules/peek-view/index.ts | 3 +- .../core/src/modules/peek-view/view/utils.ts | 11 +- .../modules/permissions/entities/members.ts | 2 +- .../permissions/entities/permission.ts | 2 +- .../core/src/modules/permissions/index.ts | 8 +- .../permissions/services/permission.ts | 2 +- .../src/modules/quicksearch/impls/commands.ts | 2 +- .../src/modules/quicksearch/impls/docs.ts | 2 +- .../quicksearch/impls/external-links.ts | 2 +- .../src/modules/quicksearch/impls/links.ts | 3 +- .../core/src/modules/quicksearch/index.ts | 18 +-- .../src/modules/quicksearch/services/cmdk.ts | 3 +- .../quicksearch/services/recent-pages.ts | 8 +- .../core/src/modules/quota/entities/quota.ts | 2 +- .../frontend/core/src/modules/quota/index.ts | 7 +- .../src/modules/quota/views/quota-check.tsx | 8 +- .../share-doc/entities/share-docs-list.ts | 3 +- .../modules/share-doc/entities/share-info.ts | 3 +- .../core/src/modules/share-doc/index.ts | 11 +- .../share-doc/services/share-docs-list.ts | 2 +- .../share-setting/entities/share-setting.ts | 2 +- .../core/src/modules/share-setting/index.ts | 7 +- .../src/modules/storage/impls/electron.ts | 2 +- .../core/src/modules/storage/impls/storage.ts | 7 +- .../core/src/modules/storage/index.ts | 28 +++- .../src/modules/storage/providers/global.ts | 3 +- .../src/modules/storage/services/global.ts | 3 +- .../core/src/modules/tag/entities/tag-list.ts | 2 +- .../core/src/modules/tag/entities/tag.ts | 2 +- .../frontend/core/src/modules/tag/index.ts | 9 +- .../core/src/modules/tag/stores/tag.ts | 3 +- .../core/src/modules/telemetry/index.ts | 3 +- .../modules/telemetry/services/telemetry.ts | 10 +- .../core/src/modules/theme-editor/index.ts | 3 +- .../theme-editor/services/theme-editor.ts | 2 +- .../frontend/core/src/modules/theme/index.ts | 3 +- .../modules/theme/services/edgeless-theme.ts | 5 +- .../core/src/modules/workbench/index.ts | 8 +- .../services/workbench-view-state.ts | 2 +- .../modules/workbench/view/workbench-link.tsx | 7 +- .../modules/workspace-engine/impls/cloud.ts | 18 ++- .../modules/workspace-engine/impls/local.ts | 19 ++- .../src/modules/workspace-engine/index.ts | 8 +- .../src/modules/workspace/entities/engine.ts | 10 +- .../src/modules/workspace/entities/list.ts | 3 +- .../src/modules/workspace/entities/profile.ts | 10 +- .../modules/workspace/entities/workspace.ts | 3 +- .../src/modules/workspace/events/index.ts | 3 +- .../src/modules/workspace/global-schema.ts | 3 +- .../src/modules/workspace/impls/storage.ts | 3 +- .../core}/src/modules/workspace/index.ts | 12 +- .../core}/src/modules/workspace/metadata.ts | 0 .../src/modules/workspace/open-options.ts | 0 .../modules/workspace/providers/flavour.ts | 16 +- .../modules/workspace/providers/storage.ts | 3 +- .../src/modules/workspace/scopes/workspace.ts | 3 +- .../src/modules/workspace/services/destroy.ts | 3 +- .../src/modules/workspace/services/engine.ts | 3 +- .../src/modules/workspace/services/factory.ts | 7 +- .../modules/workspace/services/flavours.ts | 3 +- .../src/modules/workspace/services/list.ts | 3 +- .../src/modules/workspace/services/profile.ts | 4 +- .../src/modules/workspace/services/repo.ts | 3 +- .../modules/workspace/services/transform.ts | 2 +- .../modules/workspace/services/workspace.ts | 3 +- .../modules/workspace/services/workspaces.ts | 3 +- .../modules/workspace/stores/profile-cache.ts | 2 +- .../frontend/core/src/utils/first-app-data.ts | 5 +- 337 files changed, 908 insertions(+), 1367 deletions(-) delete mode 100644 packages/common/infra/src/modules/db/entities/table.ts delete mode 100644 packages/common/infra/src/modules/storage/index.ts delete mode 100644 packages/common/infra/src/modules/workspace/__tests__/workspace.spec.ts delete mode 100644 packages/common/infra/src/modules/workspace/testing/testing-provider.ts rename packages/frontend/{component => core}/src/components/workspace-avatar/index.tsx (93%) rename packages/{common/infra => frontend/core}/src/modules/db/entities/db.ts (87%) create mode 100644 packages/frontend/core/src/modules/db/entities/table.ts rename packages/{common/infra => frontend/core}/src/modules/db/index.ts (92%) rename packages/{common/infra => frontend/core}/src/modules/db/schema/index.ts (100%) rename packages/{common/infra => frontend/core}/src/modules/db/schema/schema.ts (94%) rename packages/{common/infra => frontend/core}/src/modules/db/services/db.ts (95%) rename packages/{common/infra => frontend/core}/src/modules/doc/constants.ts (100%) rename packages/{common/infra => frontend/core}/src/modules/doc/entities/doc.ts (97%) rename packages/{common/infra => frontend/core}/src/modules/doc/entities/property-list.ts (93%) rename packages/{common/infra => frontend/core}/src/modules/doc/entities/record-list.ts (95%) rename packages/{common/infra => frontend/core}/src/modules/doc/entities/record.ts (95%) rename packages/{common/infra => frontend/core}/src/modules/doc/events/index.ts (69%) rename packages/{common/infra => frontend/core}/src/modules/doc/index.ts (96%) rename packages/{common/infra => frontend/core}/src/modules/doc/scopes/doc.ts (84%) rename packages/{common/infra => frontend/core}/src/modules/doc/services/doc.ts (74%) rename packages/{common/infra => frontend/core}/src/modules/doc/services/docs.ts (95%) rename packages/{common/infra => frontend/core}/src/modules/doc/stores/doc-properties.ts (98%) rename packages/{common/infra => frontend/core}/src/modules/doc/stores/docs.ts (96%) delete mode 100644 packages/frontend/core/src/modules/editor-setting/__test__/editor-setting.spec.ts rename packages/{common/infra => frontend/core}/src/modules/feature-flag/constant.ts (100%) rename packages/{common/infra => frontend/core}/src/modules/feature-flag/entities/flags.ts (95%) rename packages/{common/infra => frontend/core}/src/modules/feature-flag/index.ts (89%) rename packages/{common/infra => frontend/core}/src/modules/feature-flag/services/feature-flag.ts (95%) rename packages/{common/infra => frontend/core}/src/modules/feature-flag/types.ts (100%) rename packages/{common/infra => frontend/core}/src/modules/global-context/entities/global-context.ts (89%) rename packages/{common/infra => frontend/core}/src/modules/global-context/index.ts (85%) rename packages/{common/infra => frontend/core}/src/modules/global-context/services/global-context.ts (78%) rename packages/{common/infra => frontend/core}/src/modules/lifecycle/index.ts (82%) rename packages/{common/infra => frontend/core}/src/modules/lifecycle/service/lifecycle.ts (91%) rename packages/{common/infra => frontend/core}/src/modules/storage/providers/global.ts (89%) rename packages/{common/infra => frontend/core}/src/modules/storage/services/global.ts (91%) rename packages/{common/infra => frontend/core}/src/modules/workspace/entities/engine.ts (92%) rename packages/{common/infra => frontend/core}/src/modules/workspace/entities/list.ts (93%) rename packages/{common/infra => frontend/core}/src/modules/workspace/entities/profile.ts (97%) rename packages/{common/infra => frontend/core}/src/modules/workspace/entities/workspace.ts (96%) rename packages/{common/infra => frontend/core}/src/modules/workspace/events/index.ts (85%) rename packages/{common/infra => frontend/core}/src/modules/workspace/global-schema.ts (78%) rename packages/{common/infra => frontend/core}/src/modules/workspace/impls/storage.ts (96%) rename packages/{common/infra => frontend/core}/src/modules/workspace/index.ts (91%) rename packages/{common/infra => frontend/core}/src/modules/workspace/metadata.ts (100%) rename packages/{common/infra => frontend/core}/src/modules/workspace/open-options.ts (100%) rename packages/{common/infra => frontend/core}/src/modules/workspace/providers/flavour.ts (88%) rename packages/{common/infra => frontend/core}/src/modules/workspace/providers/storage.ts (75%) rename packages/{common/infra => frontend/core}/src/modules/workspace/scopes/workspace.ts (87%) rename packages/{common/infra => frontend/core}/src/modules/workspace/services/destroy.ts (92%) rename packages/{common/infra => frontend/core}/src/modules/workspace/services/engine.ts (92%) rename packages/{common/infra => frontend/core}/src/modules/workspace/services/factory.ts (89%) rename packages/{common/infra => frontend/core}/src/modules/workspace/services/flavours.ts (82%) rename packages/{common/infra => frontend/core}/src/modules/workspace/services/list.ts (76%) rename packages/{common/infra => frontend/core}/src/modules/workspace/services/profile.ts (85%) rename packages/{common/infra => frontend/core}/src/modules/workspace/services/repo.ts (97%) rename packages/{common/infra => frontend/core}/src/modules/workspace/services/transform.ts (97%) rename packages/{common/infra => frontend/core}/src/modules/workspace/services/workspace.ts (88%) rename packages/{common/infra => frontend/core}/src/modules/workspace/services/workspaces.ts (97%) rename packages/{common/infra => frontend/core}/src/modules/workspace/stores/profile-cache.ts (95%) diff --git a/packages/common/infra/src/index.ts b/packages/common/infra/src/index.ts index 07b6c95cb75e1..0b783016e0d1c 100644 --- a/packages/common/infra/src/index.ts +++ b/packages/common/infra/src/index.ts @@ -4,44 +4,7 @@ export * from './blocksuite'; export * from './framework'; export * from './initialization'; export * from './livedata'; -export * from './modules/db'; -export * from './modules/doc'; -export * from './modules/feature-flag'; -export * from './modules/global-context'; -export * from './modules/lifecycle'; -export * from './modules/storage'; -export * from './modules/workspace'; export * from './orm'; export * from './storage'; export * from './sync'; export * from './utils'; - -import type { Framework } from './framework'; -import { configureWorkspaceDBModule } from './modules/db'; -import { configureDocModule } from './modules/doc'; -import { configureFeatureFlagModule } from './modules/feature-flag'; -import { configureGlobalContextModule } from './modules/global-context'; -import { configureLifecycleModule } from './modules/lifecycle'; -import { - configureGlobalStorageModule, - configureTestingGlobalStorage, -} from './modules/storage'; -import { - configureTestingWorkspaceProvider, - configureWorkspaceModule, -} from './modules/workspace'; - -export function configureInfraModules(framework: Framework) { - configureWorkspaceModule(framework); - configureDocModule(framework); - configureWorkspaceDBModule(framework); - configureGlobalStorageModule(framework); - configureGlobalContextModule(framework); - configureLifecycleModule(framework); - configureFeatureFlagModule(framework); -} - -export function configureTestingInfraModules(framework: Framework) { - configureTestingGlobalStorage(framework); - configureTestingWorkspaceProvider(framework); -} diff --git a/packages/common/infra/src/modules/db/entities/table.ts b/packages/common/infra/src/modules/db/entities/table.ts deleted file mode 100644 index 348493d99cb5f..0000000000000 --- a/packages/common/infra/src/modules/db/entities/table.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Entity } from '../../../framework'; -import type { Table as OrmTable, TableSchemaBuilder } from '../../../orm'; -import type { WorkspaceService } from '../../workspace'; - -export class WorkspaceDBTable< - Schema extends TableSchemaBuilder, -> extends Entity<{ - table: OrmTable; - storageDocId: string; -}> { - readonly table = this.props.table; - - constructor(private readonly workspaceService: WorkspaceService) { - super(); - } - - isSyncing$ = this.workspaceService.workspace.engine.doc - .docState$(this.props.storageDocId) - .map(docState => docState.syncing); - - isLoading$ = this.workspaceService.workspace.engine.doc - .docState$(this.props.storageDocId) - .map(docState => docState.loading); - - create = this.table.create.bind(this.table); - update = this.table.update.bind(this.table); - get = this.table.get.bind(this.table); - // eslint-disable-next-line rxjs/finnish - get$ = this.table.get$.bind(this.table); - find = this.table.find.bind(this.table); - // eslint-disable-next-line rxjs/finnish - find$ = this.table.find$.bind(this.table); - keys = this.table.keys.bind(this.table); - delete = this.table.delete.bind(this.table); -} diff --git a/packages/common/infra/src/modules/storage/index.ts b/packages/common/infra/src/modules/storage/index.ts deleted file mode 100644 index 2032f44a663b4..0000000000000 --- a/packages/common/infra/src/modules/storage/index.ts +++ /dev/null @@ -1,35 +0,0 @@ -export { - GlobalCache, - GlobalSessionState, - GlobalState, -} from './providers/global'; -export { - GlobalCacheService, - GlobalSessionStateService, - GlobalStateService, -} from './services/global'; - -import type { Framework } from '../../framework'; -import { MemoryMemento } from '../../storage'; -import { - GlobalCache, - GlobalSessionState, - GlobalState, -} from './providers/global'; -import { - GlobalCacheService, - GlobalSessionStateService, - GlobalStateService, -} from './services/global'; - -export const configureGlobalStorageModule = (framework: Framework) => { - framework.service(GlobalStateService, [GlobalState]); - framework.service(GlobalCacheService, [GlobalCache]); - framework.service(GlobalSessionStateService, [GlobalSessionState]); -}; - -export const configureTestingGlobalStorage = (framework: Framework) => { - framework.impl(GlobalCache, MemoryMemento); - framework.impl(GlobalState, MemoryMemento); - framework.impl(GlobalSessionState, MemoryMemento); -}; diff --git a/packages/common/infra/src/modules/workspace/__tests__/workspace.spec.ts b/packages/common/infra/src/modules/workspace/__tests__/workspace.spec.ts deleted file mode 100644 index d0f9699211e46..0000000000000 --- a/packages/common/infra/src/modules/workspace/__tests__/workspace.spec.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { describe, expect, test } from 'vitest'; - -import { Framework } from '../../../framework'; -import { configureTestingGlobalStorage } from '../../storage'; -import { - configureTestingWorkspaceProvider, - configureWorkspaceModule, - Workspace, - WorkspacesService, -} from '..'; - -describe('Workspace System', () => { - test('create workspace', async () => { - const framework = new Framework(); - configureTestingGlobalStorage(framework); - configureWorkspaceModule(framework); - configureTestingWorkspaceProvider(framework); - - const provider = framework.provider(); - const workspaceService = provider.get(WorkspacesService); - expect(workspaceService.list.workspaces$.value.length).toBe(0); - - const workspace = workspaceService.open({ - metadata: await workspaceService.create('local'), - }); - - expect(workspace.workspace).toBeInstanceOf(Workspace); - - expect(workspaceService.list.workspaces$.value.length).toBe(1); - }); -}); diff --git a/packages/common/infra/src/modules/workspace/testing/testing-provider.ts b/packages/common/infra/src/modules/workspace/testing/testing-provider.ts deleted file mode 100644 index d71b785325114..0000000000000 --- a/packages/common/infra/src/modules/workspace/testing/testing-provider.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { DocCollection, nanoid } from '@blocksuite/affine/store'; -import { map } from 'rxjs'; -import { applyUpdate, encodeStateAsUpdate } from 'yjs'; - -import { Service } from '../../../framework'; -import { LiveData } from '../../../livedata'; -import { wrapMemento } from '../../../storage'; -import { - type BlobStorage, - type DocStorage, - MemoryDocStorage, -} from '../../../sync'; -import { MemoryBlobStorage } from '../../../sync/blob/blob'; -import type { GlobalState } from '../../storage'; -import type { WorkspaceProfileInfo } from '../entities/profile'; -import { getAFFiNEWorkspaceSchema } from '../global-schema'; -import type { WorkspaceMetadata } from '../metadata'; -import type { - WorkspaceEngineProvider, - WorkspaceFlavourProvider, - WorkspaceFlavoursProvider, -} from '../providers/flavour'; - -class TestingWorkspaceLocalProvider implements WorkspaceFlavourProvider { - flavour = 'local'; - - store = wrapMemento(this.globalStore, 'testing/'); - workspaceListStore = wrapMemento(this.store, 'workspaces/'); - docStorage = new MemoryDocStorage(wrapMemento(this.store, 'docs/')); - - constructor(private readonly globalStore: GlobalState) {} - - async deleteWorkspace(id: string): Promise { - const list = this.workspaceListStore.get('list') ?? []; - const newList = list.filter(meta => meta.id !== id); - this.workspaceListStore.set('list', newList); - } - async createWorkspace( - initial: ( - docCollection: DocCollection, - blobStorage: BlobStorage, - docStorage: DocStorage - ) => Promise - ): Promise { - const id = nanoid(); - const meta = { id, flavour: 'local' }; - - const blobStorage = new MemoryBlobStorage( - wrapMemento(this.store, id + '/blobs/') - ); - - const docCollection = new DocCollection({ - id: id, - idGenerator: () => nanoid(), - schema: getAFFiNEWorkspaceSchema(), - blobSources: { - main: blobStorage, - }, - }); - - // apply initial state - await initial(docCollection, blobStorage, this.docStorage); - - // save workspace to storage - await this.docStorage.doc.set(id, encodeStateAsUpdate(docCollection.doc)); - for (const subdocs of docCollection.doc.getSubdocs()) { - await this.docStorage.doc.set(subdocs.guid, encodeStateAsUpdate(subdocs)); - } - - const list = this.workspaceListStore.get('list') ?? []; - this.workspaceListStore.set('list', [...list, meta]); - - docCollection.dispose(); - - return { id, flavour: 'local' }; - } - workspaces$ = LiveData.from( - this.workspaceListStore - .watch('list') - .pipe(map(m => m ?? [])), - [] - ); - async getWorkspaceProfile( - id: string - ): Promise { - const data = await this.docStorage.doc.get(id); - - if (!data) { - return; - } - - const bs = new DocCollection({ - id, - schema: getAFFiNEWorkspaceSchema(), - }); - - applyUpdate(bs.doc, data); - - bs.dispose(); - - return { - name: bs.meta.name, - avatar: bs.meta.avatar, - isOwner: true, - }; - } - getWorkspaceBlob(id: string, blob: string): Promise { - return new MemoryBlobStorage(wrapMemento(this.store, id + '/blobs/')).get( - blob - ); - } - getEngineProvider(workspaceId: string): WorkspaceEngineProvider { - return { - getDocStorage: () => { - return this.docStorage; - }, - getAwarenessConnections() { - return []; - }, - getDocServer() { - return null; - }, - getLocalBlobStorage: () => { - return new MemoryBlobStorage( - wrapMemento(this.store, workspaceId + '/blobs/') - ); - }, - getRemoteBlobStorages() { - return []; - }, - }; - } -} - -export class TestingWorkspaceFlavoursProvider - extends Service - implements WorkspaceFlavoursProvider -{ - constructor(private readonly globalStore: GlobalState) { - super(); - } - workspaceFlavours$ = new LiveData([ - new TestingWorkspaceLocalProvider(this.globalStore), - ]); -} diff --git a/packages/frontend/apps/android/src/app.tsx b/packages/frontend/apps/android/src/app.tsx index 5a19af732a6e3..03f7012020b41 100644 --- a/packages/frontend/apps/android/src/app.tsx +++ b/packages/frontend/apps/android/src/app.tsx @@ -4,6 +4,7 @@ import { configureMobileModules } from '@affine/core/mobile/modules'; import { router } from '@affine/core/mobile/router'; import { configureCommonModules } from '@affine/core/modules'; import { I18nProvider } from '@affine/core/modules/i18n'; +import { LifecycleService } from '@affine/core/modules/lifecycle'; import { configureLocalStorageStateStorageImpls } from '@affine/core/modules/storage'; import { configureIndexedDBUserspaceStorageProvider } from '@affine/core/modules/userspace'; import { configureBrowserWorkbenchModule } from '@affine/core/modules/workbench'; @@ -11,12 +12,7 @@ import { configureBrowserWorkspaceFlavours, configureIndexedDBWorkspaceEngineStorageProvider, } from '@affine/core/modules/workspace-engine'; -import { - Framework, - FrameworkRoot, - getCurrentStore, - LifecycleService, -} from '@toeverything/infra'; +import { Framework, FrameworkRoot, getCurrentStore } from '@toeverything/infra'; import { Suspense } from 'react'; import { RouterProvider } from 'react-router-dom'; diff --git a/packages/frontend/apps/electron/renderer/app.tsx b/packages/frontend/apps/electron/renderer/app.tsx index 75a5d3d061b9d..be4fc2ba5e994 100644 --- a/packages/frontend/apps/electron/renderer/app.tsx +++ b/packages/frontend/apps/electron/renderer/app.tsx @@ -10,12 +10,15 @@ import { DesktopApiService, } from '@affine/core/modules/desktop-api'; import { GlobalDialogService } from '@affine/core/modules/dialogs'; +import { DocsService } from '@affine/core/modules/doc'; import { configureSpellCheckSettingModule, EditorSettingService, } from '@affine/core/modules/editor-setting'; import { configureFindInPageModule } from '@affine/core/modules/find-in-page'; +import { GlobalContextService } from '@affine/core/modules/global-context'; import { I18nProvider } from '@affine/core/modules/i18n'; +import { LifecycleService } from '@affine/core/modules/lifecycle'; import { configureElectronStateStorageImpls } from '@affine/core/modules/storage'; import { ClientSchemeProvider, @@ -26,6 +29,7 @@ import { configureDesktopWorkbenchModule, WorkbenchService, } from '@affine/core/modules/workbench'; +import { WorkspacesService } from '@affine/core/modules/workspace'; import { configureBrowserWorkspaceFlavours, configureSqliteWorkspaceEngineStorageProvider, @@ -33,15 +37,7 @@ import { import createEmotionCache from '@affine/core/utils/create-emotion-cache'; import { apis, events } from '@affine/electron-api'; import { CacheProvider } from '@emotion/react'; -import { - DocsService, - Framework, - FrameworkRoot, - getCurrentStore, - GlobalContextService, - LifecycleService, - WorkspacesService, -} from '@toeverything/infra'; +import { Framework, FrameworkRoot, getCurrentStore } from '@toeverything/infra'; import { Suspense } from 'react'; import { RouterProvider } from 'react-router-dom'; diff --git a/packages/frontend/apps/electron/renderer/shell/app.tsx b/packages/frontend/apps/electron/renderer/shell/app.tsx index 6c0767a0d770f..0a4b964fc4771 100644 --- a/packages/frontend/apps/electron/renderer/shell/app.tsx +++ b/packages/frontend/apps/electron/renderer/shell/app.tsx @@ -9,13 +9,12 @@ import { } from '@affine/core/modules/app-tabs-header'; import { configureDesktopApiModule } from '@affine/core/modules/desktop-api'; import { configureI18nModule, I18nProvider } from '@affine/core/modules/i18n'; -import { configureElectronStateStorageImpls } from '@affine/core/modules/storage'; -import { configureAppThemeModule } from '@affine/core/modules/theme'; import { + configureElectronStateStorageImpls, configureGlobalStorageModule, - Framework, - FrameworkRoot, -} from '@toeverything/infra'; +} from '@affine/core/modules/storage'; +import { configureAppThemeModule } from '@affine/core/modules/theme'; +import { Framework, FrameworkRoot } from '@toeverything/infra'; import * as styles from './app.css'; diff --git a/packages/frontend/apps/ios/src/app.tsx b/packages/frontend/apps/ios/src/app.tsx index 620d1af131c51..887fe9b5c7c45 100644 --- a/packages/frontend/apps/ios/src/app.tsx +++ b/packages/frontend/apps/ios/src/app.tsx @@ -13,7 +13,9 @@ import { ValidatorProvider, WebSocketAuthProvider, } from '@affine/core/modules/cloud'; +import { GlobalContextService } from '@affine/core/modules/global-context'; import { I18nProvider } from '@affine/core/modules/i18n'; +import { LifecycleService } from '@affine/core/modules/lifecycle'; import { configureLocalStorageStateStorageImpls } from '@affine/core/modules/storage'; import { PopupWindowProvider } from '@affine/core/modules/url'; import { ClientSchemeProvider } from '@affine/core/modules/url/providers/client-schema'; @@ -28,13 +30,7 @@ import { App as CapacitorApp } from '@capacitor/app'; import { Browser } from '@capacitor/browser'; import { Haptics } from '@capacitor/haptics'; import { Keyboard, KeyboardStyle } from '@capacitor/keyboard'; -import { - Framework, - FrameworkRoot, - getCurrentStore, - GlobalContextService, - LifecycleService, -} from '@toeverything/infra'; +import { Framework, FrameworkRoot, getCurrentStore } from '@toeverything/infra'; import { useTheme } from 'next-themes'; import { Suspense, useEffect } from 'react'; import { RouterProvider } from 'react-router-dom'; diff --git a/packages/frontend/apps/mobile/src/app.tsx b/packages/frontend/apps/mobile/src/app.tsx index e05bdf78b93bb..e099a50f596b7 100644 --- a/packages/frontend/apps/mobile/src/app.tsx +++ b/packages/frontend/apps/mobile/src/app.tsx @@ -5,6 +5,7 @@ import { HapticProvider } from '@affine/core/mobile/modules/haptics'; import { router } from '@affine/core/mobile/router'; import { configureCommonModules } from '@affine/core/modules'; import { I18nProvider } from '@affine/core/modules/i18n'; +import { LifecycleService } from '@affine/core/modules/lifecycle'; import { configureLocalStorageStateStorageImpls } from '@affine/core/modules/storage'; import { PopupWindowProvider } from '@affine/core/modules/url'; import { configureIndexedDBUserspaceStorageProvider } from '@affine/core/modules/userspace'; @@ -13,12 +14,7 @@ import { configureBrowserWorkspaceFlavours, configureIndexedDBWorkspaceEngineStorageProvider, } from '@affine/core/modules/workspace-engine'; -import { - Framework, - FrameworkRoot, - getCurrentStore, - LifecycleService, -} from '@toeverything/infra'; +import { Framework, FrameworkRoot, getCurrentStore } from '@toeverything/infra'; import { Suspense } from 'react'; import { RouterProvider } from 'react-router-dom'; diff --git a/packages/frontend/apps/web/src/app.tsx b/packages/frontend/apps/web/src/app.tsx index 7909b8ac89889..95c16d191e5d3 100644 --- a/packages/frontend/apps/web/src/app.tsx +++ b/packages/frontend/apps/web/src/app.tsx @@ -3,6 +3,7 @@ import { AppContainer } from '@affine/core/desktop/components/app-container'; import { router } from '@affine/core/desktop/router'; import { configureCommonModules } from '@affine/core/modules'; import { I18nProvider } from '@affine/core/modules/i18n'; +import { LifecycleService } from '@affine/core/modules/lifecycle'; import { OpenInAppGuard } from '@affine/core/modules/open-in-app'; import { configureLocalStorageStateStorageImpls } from '@affine/core/modules/storage'; import { PopupWindowProvider } from '@affine/core/modules/url'; @@ -14,12 +15,7 @@ import { } from '@affine/core/modules/workspace-engine'; import createEmotionCache from '@affine/core/utils/create-emotion-cache'; import { CacheProvider } from '@emotion/react'; -import { - Framework, - FrameworkRoot, - getCurrentStore, - LifecycleService, -} from '@toeverything/infra'; +import { Framework, FrameworkRoot, getCurrentStore } from '@toeverything/infra'; import { Suspense } from 'react'; import { RouterProvider } from 'react-router-dom'; diff --git a/packages/frontend/core/src/components/affine/affine-error-boundary/error-basic/info-logger.tsx b/packages/frontend/core/src/components/affine/affine-error-boundary/error-basic/info-logger.tsx index 93f2461e54b81..4ef6119caf1fd 100644 --- a/packages/frontend/core/src/components/affine/affine-error-boundary/error-basic/info-logger.tsx +++ b/packages/frontend/core/src/components/affine/affine-error-boundary/error-basic/info-logger.tsx @@ -1,8 +1,5 @@ -import { - GlobalContextService, - useLiveData, - useServices, -} from '@toeverything/infra'; +import { GlobalContextService } from '@affine/core/modules/global-context'; +import { useLiveData, useServices } from '@toeverything/infra'; import { useEffect } from 'react'; import { useLocation, useParams } from 'react-router-dom'; diff --git a/packages/frontend/core/src/components/affine/ai-onboarding/index.tsx b/packages/frontend/core/src/components/affine/ai-onboarding/index.tsx index 5c94350c877fe..4ef7b91d20f6e 100644 --- a/packages/frontend/core/src/components/affine/ai-onboarding/index.tsx +++ b/packages/frontend/core/src/components/affine/ai-onboarding/index.tsx @@ -1,4 +1,5 @@ -import { FeatureFlagService, useService } from '@toeverything/infra'; +import { FeatureFlagService } from '@affine/core/modules/feature-flag'; +import { useService } from '@toeverything/infra'; import { Suspense, useCallback, useEffect, useState } from 'react'; import { AIOnboardingEdgeless } from './edgeless.dialog'; diff --git a/packages/frontend/core/src/components/affine/awareness/index.tsx b/packages/frontend/core/src/components/affine/awareness/index.tsx index 63bce634b436e..b1531e24894f4 100644 --- a/packages/frontend/core/src/components/affine/awareness/index.tsx +++ b/packages/frontend/core/src/components/affine/awareness/index.tsx @@ -1,4 +1,5 @@ -import { useLiveData, useService, WorkspaceService } from '@toeverything/infra'; +import { WorkspaceService } from '@affine/core/modules/workspace'; +import { useLiveData, useService } from '@toeverything/infra'; import { useEffect } from 'react'; import { AuthService } from '../../../modules/cloud'; diff --git a/packages/frontend/core/src/components/affine/empty/collections.tsx b/packages/frontend/core/src/components/affine/empty/collections.tsx index 8ae47d7a93e23..efd5c8927f897 100644 --- a/packages/frontend/core/src/components/affine/empty/collections.tsx +++ b/packages/frontend/core/src/components/affine/empty/collections.tsx @@ -1,9 +1,10 @@ import { usePromptModal } from '@affine/component'; import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-helper'; import { CollectionService } from '@affine/core/modules/collection'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { useI18n } from '@affine/i18n'; import { ViewLayersIcon } from '@blocksuite/icons/rc'; -import { useService, WorkspaceService } from '@toeverything/infra'; +import { useService } from '@toeverything/infra'; import { nanoid } from 'nanoid'; import { useCallback } from 'react'; diff --git a/packages/frontend/core/src/components/affine/empty/docs.tsx b/packages/frontend/core/src/components/affine/empty/docs.tsx index 878020993f60f..fe09a9f210b8c 100644 --- a/packages/frontend/core/src/components/affine/empty/docs.tsx +++ b/packages/frontend/core/src/components/affine/empty/docs.tsx @@ -1,8 +1,9 @@ import { TagService } from '@affine/core/modules/tag'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { isNewTabTrigger } from '@affine/core/utils'; import { useI18n } from '@affine/i18n'; import { AllDocsIcon } from '@blocksuite/icons/rc'; -import { useLiveData, useService, WorkspaceService } from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import { type MouseEvent, useCallback } from 'react'; import { usePageHelper } from '../../blocksuite/block-suite-page-list/utils'; diff --git a/packages/frontend/core/src/components/affine/page-history-modal/data.ts b/packages/frontend/core/src/components/affine/page-history-modal/data.ts index 588b1d591acbc..e46c04c4e7519 100644 --- a/packages/frontend/core/src/components/affine/page-history-modal/data.ts +++ b/packages/frontend/core/src/components/affine/page-history-modal/data.ts @@ -1,13 +1,14 @@ import { useDocMetaHelper } from '@affine/core/components/hooks/use-block-suite-page-meta'; import { useDocCollectionPage } from '@affine/core/components/hooks/use-block-suite-workspace-page'; import { FetchService, GraphQLService } from '@affine/core/modules/cloud'; +import { getAFFiNEWorkspaceSchema } from '@affine/core/modules/workspace'; import { DebugLogger } from '@affine/debug'; import type { ListHistoryQuery } from '@affine/graphql'; import { listHistoryQuery, recoverDocMutation } from '@affine/graphql'; import { i18nTime } from '@affine/i18n'; import { assertEquals } from '@blocksuite/affine/global/utils'; import { DocCollection } from '@blocksuite/affine/store'; -import { getAFFiNEWorkspaceSchema, useService } from '@toeverything/infra'; +import { useService } from '@toeverything/infra'; import { useEffect, useMemo } from 'react'; import useSWRImmutable from 'swr/immutable'; import { @@ -227,6 +228,7 @@ export function revertUpdate( snapshotStateVector ); const undoManager = new UndoManager( + // oxlint-disable array-callback-return [...snapshotDoc.share.keys()].map(key => { const type = getMetadata(key); if (type === 'Text') { @@ -236,7 +238,7 @@ export function revertUpdate( } else if (type === 'Array') { return snapshotDoc.getArray(key); } - // eslint-disable-next-line array-callback-return + throw new Error('Unknown type'); }) ); diff --git a/packages/frontend/core/src/components/affine/page-history-modal/history-modal.tsx b/packages/frontend/core/src/components/affine/page-history-modal/history-modal.tsx index 902a6b8c89816..e66774cebe888 100644 --- a/packages/frontend/core/src/components/affine/page-history-modal/history-modal.tsx +++ b/packages/frontend/core/src/components/affine/page-history-modal/history-modal.tsx @@ -7,6 +7,7 @@ import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta'; import { EditorService } from '@affine/core/modules/editor'; import { WorkspacePermissionService } from '@affine/core/modules/permissions'; import { WorkspaceQuotaService } from '@affine/core/modules/quota'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { i18nTime, Trans, useI18n } from '@affine/i18n'; import { track } from '@affine/track'; import type { DocMode } from '@blocksuite/affine/blocks'; @@ -17,7 +18,7 @@ import type { import { CloseIcon, ToggleCollapseIcon } from '@blocksuite/icons/rc'; import * as Collapsible from '@radix-ui/react-collapsible'; import type { DialogContentProps } from '@radix-ui/react-dialog'; -import { useLiveData, useService, WorkspaceService } from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import { atom, useAtom } from 'jotai'; import type { PropsWithChildren } from 'react'; import { diff --git a/packages/frontend/core/src/components/affine/quota-reached-modal/cloud-quota-modal.tsx b/packages/frontend/core/src/components/affine/quota-reached-modal/cloud-quota-modal.tsx index de300bb9a4910..0c375e0155a61 100644 --- a/packages/frontend/core/src/components/affine/quota-reached-modal/cloud-quota-modal.tsx +++ b/packages/frontend/core/src/components/affine/quota-reached-modal/cloud-quota-modal.tsx @@ -4,9 +4,10 @@ import { UserQuotaService } from '@affine/core/modules/cloud'; import { GlobalDialogService } from '@affine/core/modules/dialogs'; import { WorkspacePermissionService } from '@affine/core/modules/permissions'; import { WorkspaceQuotaService } from '@affine/core/modules/quota'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { type I18nString, useI18n } from '@affine/i18n'; import { track } from '@affine/track'; -import { useLiveData, useService, WorkspaceService } from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import { useAtom } from 'jotai'; import { useCallback, useEffect, useMemo } from 'react'; diff --git a/packages/frontend/core/src/components/affine/quota-reached-modal/local-quota-modal.tsx b/packages/frontend/core/src/components/affine/quota-reached-modal/local-quota-modal.tsx index 7f547cdc5bfa5..9703c672fc124 100644 --- a/packages/frontend/core/src/components/affine/quota-reached-modal/local-quota-modal.tsx +++ b/packages/frontend/core/src/components/affine/quota-reached-modal/local-quota-modal.tsx @@ -1,7 +1,8 @@ import { ConfirmModal } from '@affine/component/ui/modal'; import { openQuotaModalAtom } from '@affine/core/components/atoms'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { useI18n } from '@affine/i18n'; -import { useService, WorkspaceService } from '@toeverything/infra'; +import { useService } from '@toeverything/infra'; import { useAtom } from 'jotai'; import { useCallback, useEffect } from 'react'; diff --git a/packages/frontend/core/src/components/affine/reference-link/index.tsx b/packages/frontend/core/src/components/affine/reference-link/index.tsx index 76f1ca891df8a..4b6a7036fd488 100644 --- a/packages/frontend/core/src/components/affine/reference-link/index.tsx +++ b/packages/frontend/core/src/components/affine/reference-link/index.tsx @@ -1,3 +1,4 @@ +import { DocsService } from '@affine/core/modules/doc'; import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta'; import { JournalService } from '@affine/core/modules/journal'; import { PeekViewService } from '@affine/core/modules/peek-view/services/peek-view'; @@ -6,12 +7,7 @@ import { WorkbenchLink } from '@affine/core/modules/workbench'; import { track } from '@affine/track'; import type { DocMode } from '@blocksuite/affine/blocks'; import type { DocCollection } from '@blocksuite/affine/store'; -import { - DocsService, - LiveData, - useLiveData, - useService, -} from '@toeverything/infra'; +import { LiveData, useLiveData, useService } from '@toeverything/infra'; import clsx from 'clsx'; import { nanoid } from 'nanoid'; import { diff --git a/packages/frontend/core/src/components/affine/share-page-modal/index.tsx b/packages/frontend/core/src/components/affine/share-page-modal/index.tsx index 5e794ddb5ab75..b4d5947245424 100644 --- a/packages/frontend/core/src/components/affine/share-page-modal/index.tsx +++ b/packages/frontend/core/src/components/affine/share-page-modal/index.tsx @@ -1,7 +1,7 @@ import { useEnableCloud } from '@affine/core/components/hooks/affine/use-enable-cloud'; +import type { Workspace } from '@affine/core/modules/workspace'; import { track } from '@affine/track'; import type { Doc } from '@blocksuite/affine/store'; -import { type Workspace } from '@toeverything/infra'; import { useCallback } from 'react'; import { ShareMenu } from './share-menu'; diff --git a/packages/frontend/core/src/components/affine/share-page-modal/share-menu/share-menu.tsx b/packages/frontend/core/src/components/affine/share-page-modal/share-menu/share-menu.tsx index 71e14603b3e7c..a9e0076f49b95 100644 --- a/packages/frontend/core/src/components/affine/share-page-modal/share-menu/share-menu.tsx +++ b/packages/frontend/core/src/components/affine/share-page-modal/share-menu/share-menu.tsx @@ -2,14 +2,11 @@ import { Tabs, Tooltip } from '@affine/component'; import { Button } from '@affine/component/ui/button'; import { Menu } from '@affine/component/ui/menu'; import { ShareInfoService } from '@affine/core/modules/share-doc'; +import type { WorkspaceMetadata } from '@affine/core/modules/workspace'; import { useI18n } from '@affine/i18n'; import type { Doc } from '@blocksuite/affine/store'; import { LockIcon, PublishIcon } from '@blocksuite/icons/rc'; -import { - useLiveData, - useService, - type WorkspaceMetadata, -} from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import { forwardRef, type PropsWithChildren, type Ref, useEffect } from 'react'; import * as styles from './index.css'; diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-editor/bi-directional-link-panel.tsx b/packages/frontend/core/src/components/blocksuite/block-suite-editor/bi-directional-link-panel.tsx index 21df2041567df..9225fa6fac65f 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-editor/bi-directional-link-panel.tsx +++ b/packages/frontend/core/src/components/blocksuite/block-suite-editor/bi-directional-link-panel.tsx @@ -4,27 +4,29 @@ import { useLitPortalFactory, } from '@affine/component'; import { TextRenderer } from '@affine/core/blocksuite/presets'; +import { DocService } from '@affine/core/modules/doc'; import { type Backlink, DocLinksService, type Link, } from '@affine/core/modules/doc-link'; import { toURLSearchParams } from '@affine/core/modules/navigation'; +import { GlobalSessionStateService } from '@affine/core/modules/storage'; import { WorkbenchLink } from '@affine/core/modules/workbench'; +import { + getAFFiNEWorkspaceSchema, + WorkspaceService, +} from '@affine/core/modules/workspace'; import { useI18n } from '@affine/i18n'; import track from '@affine/track'; import type { JobMiddleware } from '@blocksuite/affine/store'; import { ToggleExpandIcon } from '@blocksuite/icons/rc'; import * as Collapsible from '@radix-ui/react-collapsible'; import { - DocService, - getAFFiNEWorkspaceSchema, - GlobalSessionStateService, LiveData, useFramework, useLiveData, useServices, - WorkspaceService, } from '@toeverything/infra'; import React, { Fragment, diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-editor/lit-adaper.tsx b/packages/frontend/core/src/components/blocksuite/block-suite-editor/lit-adaper.tsx index f566687231769..d6ab8e67889b4 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-editor/lit-adaper.tsx +++ b/packages/frontend/core/src/components/blocksuite/block-suite-editor/lit-adaper.tsx @@ -3,6 +3,8 @@ import { useConfirmModal, useLitPortalFactory, } from '@affine/component'; +import type { DocCustomPropertyInfo } from '@affine/core/modules/db'; +import { DocService, DocsService } from '@affine/core/modules/doc'; import type { DatabaseRow, DatabaseValueCell, @@ -12,6 +14,7 @@ import { EditorSettingService } from '@affine/core/modules/editor-setting'; import { JournalService } from '@affine/core/modules/journal'; import { toURLSearchParams } from '@affine/core/modules/navigation'; import { PeekViewService } from '@affine/core/modules/peek-view/services/peek-view'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import track from '@affine/track'; import type { DocMode } from '@blocksuite/affine/blocks'; import { @@ -21,14 +24,10 @@ import { } from '@blocksuite/affine/presets'; import type { Doc } from '@blocksuite/affine/store'; import { - type DocCustomPropertyInfo, - DocService, - DocsService, useFramework, useLiveData, useService, useServices, - WorkspaceService, } from '@toeverything/infra'; import React, { forwardRef, diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/root-block.ts b/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/root-block.ts index d4e1bb7f002df..c2cd8cb89110e 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/root-block.ts +++ b/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/root-block.ts @@ -2,8 +2,10 @@ import { AIEdgelessRootBlockSpec, AIPageRootBlockSpec, } from '@affine/core/blocksuite/presets/ai'; +import { DocService, DocsService } from '@affine/core/modules/doc'; import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta'; import { EditorSettingService } from '@affine/core/modules/editor-setting'; +import { FeatureFlagService } from '@affine/core/modules/feature-flag'; import { AppThemeService } from '@affine/core/modules/theme'; import { mixpanel } from '@affine/track'; import { @@ -39,12 +41,7 @@ import { } from '@blocksuite/affine-shared/utils'; import type { Container } from '@blocksuite/global/di'; import { LinkedPageIcon, PageIcon } from '@blocksuite/icons/lit'; -import { - DocService, - DocsService, - FeatureFlagService, - type FrameworkProvider, -} from '@toeverything/infra'; +import { type FrameworkProvider } from '@toeverything/infra'; import type { TemplateResult } from 'lit'; import type { Observable } from 'rxjs'; import { combineLatest, map } from 'rxjs'; diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/spec-patchers.tsx b/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/spec-patchers.tsx index 2d69923dc366c..374f633b3bb4b 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/spec-patchers.tsx +++ b/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/spec-patchers.tsx @@ -8,6 +8,7 @@ import { type useConfirmModal, } from '@affine/component'; import { WorkspaceServerService } from '@affine/core/modules/cloud'; +import { type DocService, DocsService } from '@affine/core/modules/doc'; import type { EditorService } from '@affine/core/modules/editor'; import { EditorSettingService } from '@affine/core/modules/editor-setting'; import { resolveLinkToDoc } from '@affine/core/modules/navigation'; @@ -22,6 +23,7 @@ import { import { ExternalLinksQuickSearchSession } from '@affine/core/modules/quicksearch/impls/external-links'; import { JournalsQuickSearchSession } from '@affine/core/modules/quicksearch/impls/journals'; import { WorkbenchService } from '@affine/core/modules/workbench'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { isNewTabTrigger } from '@affine/core/utils'; import { DebugLogger } from '@affine/debug'; import { track } from '@affine/track'; @@ -62,10 +64,7 @@ import type { ReferenceParams } from '@blocksuite/affine-model'; import { AIChatBlockSchema, type DocProps, - type DocService, - DocsService, type FrameworkProvider, - WorkspaceService, } from '@toeverything/infra'; import { type TemplateResult } from 'lit'; import { customElement } from 'lit/decorators.js'; diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/edgeless.ts b/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/edgeless.ts index a1c13dda0af36..3133d5b1863f2 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/edgeless.ts +++ b/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/edgeless.ts @@ -1,3 +1,4 @@ +import { FeatureFlagService } from '@affine/core/modules/feature-flag'; import { builtInTemplates as builtInEdgelessTemplates } from '@affine/templates/edgeless'; import { builtInTemplates as builtInStickersTemplates } from '@affine/templates/stickers'; import type { ExtensionType } from '@blocksuite/affine/block-std'; @@ -10,10 +11,7 @@ import { EdgelessTextBlockSpec, FrameBlockSpec, } from '@blocksuite/affine/blocks'; -import { - FeatureFlagService, - type FrameworkProvider, -} from '@toeverything/infra'; +import { type FrameworkProvider } from '@toeverything/infra'; import { AIBlockSpecs, DefaultBlockSpecs } from './common'; import { createEdgelessRootBlockSpec } from './custom/root-block'; diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/page.ts b/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/page.ts index 102daeb76b04e..aee6a9f6c1ab7 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/page.ts +++ b/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/page.ts @@ -1,13 +1,11 @@ +import { FeatureFlagService } from '@affine/core/modules/feature-flag'; import type { ExtensionType } from '@blocksuite/affine/block-std'; import { NoteBlockSpec, PageSurfaceBlockSpec, PageSurfaceRefBlockSpec, } from '@blocksuite/affine/blocks'; -import { - FeatureFlagService, - type FrameworkProvider, -} from '@toeverything/infra'; +import { type FrameworkProvider } from '@toeverything/infra'; import { AIBlockSpecs, DefaultBlockSpecs } from './common'; import { createPageRootBlockSpec } from './custom/root-block'; diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-header/menu/history-tips-modal/index.tsx b/packages/frontend/core/src/components/blocksuite/block-suite-header/menu/history-tips-modal/index.tsx index 3cdc1ad3f85f6..32960dfd2d3c8 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-header/menu/history-tips-modal/index.tsx +++ b/packages/frontend/core/src/components/blocksuite/block-suite-header/menu/history-tips-modal/index.tsx @@ -1,7 +1,8 @@ import { OverlayModal } from '@affine/component'; import { useEnableCloud } from '@affine/core/components/hooks/affine/use-enable-cloud'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { useI18n } from '@affine/i18n'; -import { useService, WorkspaceService } from '@toeverything/infra'; +import { useService } from '@toeverything/infra'; import { useCallback } from 'react'; import TopSvg from './top-svg'; diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-header/menu/index.tsx b/packages/frontend/core/src/components/blocksuite/block-suite-header/menu/index.tsx index 08499929993a4..7df4bef2b0f14 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-header/menu/index.tsx +++ b/packages/frontend/core/src/components/blocksuite/block-suite-header/menu/index.tsx @@ -19,6 +19,7 @@ import { EditorService } from '@affine/core/modules/editor'; import { OpenInAppService } from '@affine/core/modules/open-in-app/services'; import { WorkbenchService } from '@affine/core/modules/workbench'; import { ViewService } from '@affine/core/modules/workbench/services/view'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { useI18n } from '@affine/i18n'; import { track } from '@affine/track'; import type { Doc } from '@blocksuite/affine/store'; @@ -42,7 +43,6 @@ import { useLiveData, useService, useServiceOptional, - WorkspaceService, } from '@toeverything/infra'; import { useCallback, useState } from 'react'; diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-header/title/index.tsx b/packages/frontend/core/src/components/blocksuite/block-suite-header/title/index.tsx index b9921bc514e4c..f9de0447ab761 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-header/title/index.tsx +++ b/packages/frontend/core/src/components/blocksuite/block-suite-header/title/index.tsx @@ -1,13 +1,10 @@ import type { InlineEditProps } from '@affine/component'; import { InlineEdit } from '@affine/component'; import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; +import { DocsService } from '@affine/core/modules/doc'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { track } from '@affine/track'; -import { - DocsService, - useLiveData, - useService, - WorkspaceService, -} from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import clsx from 'clsx'; import type { HTMLAttributes } from 'react'; diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-page-list/utils.tsx b/packages/frontend/core/src/components/blocksuite/block-suite-page-list/utils.tsx index 45ad23dba9ac4..3ce6b6a3583aa 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-page-list/utils.tsx +++ b/packages/frontend/core/src/components/blocksuite/block-suite-page-list/utils.tsx @@ -1,10 +1,11 @@ import { toast } from '@affine/component'; import { AppSidebarService } from '@affine/core/modules/app-sidebar'; +import { DocsService } from '@affine/core/modules/doc'; import { EditorSettingService } from '@affine/core/modules/editor-setting'; import { WorkbenchService } from '@affine/core/modules/workbench'; import { type DocMode } from '@blocksuite/affine/blocks'; import type { DocCollection } from '@blocksuite/affine/store'; -import { type DocProps, DocsService, useServices } from '@toeverything/infra'; +import { type DocProps, useServices } from '@toeverything/infra'; import { useCallback, useMemo } from 'react'; export const usePageHelper = (docCollection: DocCollection) => { diff --git a/packages/frontend/core/src/components/doc-properties/icons/doc-property-icon.tsx b/packages/frontend/core/src/components/doc-properties/icons/doc-property-icon.tsx index 52625becd28d5..94ecb0e5a0ddc 100644 --- a/packages/frontend/core/src/components/doc-properties/icons/doc-property-icon.tsx +++ b/packages/frontend/core/src/components/doc-properties/icons/doc-property-icon.tsx @@ -1,5 +1,5 @@ +import type { DocCustomPropertyInfo } from '@affine/core/modules/db'; import * as icons from '@blocksuite/icons/rc'; -import type { DocCustomPropertyInfo } from '@toeverything/infra'; import type { SVGProps } from 'react'; import { diff --git a/packages/frontend/core/src/components/doc-properties/icons/icons-selector.tsx b/packages/frontend/core/src/components/doc-properties/icons/icons-selector.tsx index d8c2416b728e5..ab37ad1e8ccb3 100644 --- a/packages/frontend/core/src/components/doc-properties/icons/icons-selector.tsx +++ b/packages/frontend/core/src/components/doc-properties/icons/icons-selector.tsx @@ -1,6 +1,6 @@ import { Menu, Scrollable } from '@affine/component'; +import type { DocCustomPropertyInfo } from '@affine/core/modules/db'; import { useI18n } from '@affine/i18n'; -import type { DocCustomPropertyInfo } from '@toeverything/infra'; import { chunk } from 'lodash-es'; import { type DocPropertyIconName, DocPropertyIconNames } from './constant'; diff --git a/packages/frontend/core/src/components/doc-properties/manager/index.tsx b/packages/frontend/core/src/components/doc-properties/manager/index.tsx index c9fe1ae62f06a..0d2a9d31049a4 100644 --- a/packages/frontend/core/src/components/doc-properties/manager/index.tsx +++ b/packages/frontend/core/src/components/doc-properties/manager/index.tsx @@ -6,16 +6,13 @@ import { useDraggable, useDropTarget, } from '@affine/component'; +import type { DocCustomPropertyInfo } from '@affine/core/modules/db'; +import { DocsService } from '@affine/core/modules/doc'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import type { AffineDNDData } from '@affine/core/types/dnd'; import { useI18n } from '@affine/i18n'; import { MoreHorizontalIcon } from '@blocksuite/icons/rc'; -import { - type DocCustomPropertyInfo, - DocsService, - useLiveData, - useService, - WorkspaceService, -} from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import clsx from 'clsx'; import { type HTMLProps, useCallback, useState } from 'react'; diff --git a/packages/frontend/core/src/components/doc-properties/menu/create-doc-property.tsx b/packages/frontend/core/src/components/doc-properties/menu/create-doc-property.tsx index fdc0a6c707cf3..036970ddc938f 100644 --- a/packages/frontend/core/src/components/doc-properties/menu/create-doc-property.tsx +++ b/packages/frontend/core/src/components/doc-properties/menu/create-doc-property.tsx @@ -1,12 +1,9 @@ import { MenuItem, MenuSeparator } from '@affine/component'; +import type { DocCustomPropertyInfo } from '@affine/core/modules/db'; +import { DocsService } from '@affine/core/modules/doc'; import { generateUniqueNameInSequence } from '@affine/core/utils/unique-name'; import { useI18n } from '@affine/i18n'; -import { - type DocCustomPropertyInfo, - DocsService, - useLiveData, - useService, -} from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import { useCallback } from 'react'; import { diff --git a/packages/frontend/core/src/components/doc-properties/menu/edit-doc-property.tsx b/packages/frontend/core/src/components/doc-properties/menu/edit-doc-property.tsx index 74650a7670791..37eed0a6ac52c 100644 --- a/packages/frontend/core/src/components/doc-properties/menu/edit-doc-property.tsx +++ b/packages/frontend/core/src/components/doc-properties/menu/edit-doc-property.tsx @@ -4,9 +4,10 @@ import { MenuSeparator, useConfirmModal, } from '@affine/component'; +import { DocsService } from '@affine/core/modules/doc'; import { Trans, useI18n } from '@affine/i18n'; import { DeleteIcon, InvisibleIcon, ViewIcon } from '@blocksuite/icons/rc'; -import { DocsService, useLiveData, useService } from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import { type KeyboardEventHandler, type MouseEvent, diff --git a/packages/frontend/core/src/components/doc-properties/sidebar/index.tsx b/packages/frontend/core/src/components/doc-properties/sidebar/index.tsx index 8927f8cdd4406..7ffc7a0f0f42f 100644 --- a/packages/frontend/core/src/components/doc-properties/sidebar/index.tsx +++ b/packages/frontend/core/src/components/doc-properties/sidebar/index.tsx @@ -1,4 +1,5 @@ import { Divider, IconButton, Tooltip } from '@affine/component'; +import { DocsService } from '@affine/core/modules/doc'; import { generateUniqueNameInSequence } from '@affine/core/utils/unique-name'; import { useI18n } from '@affine/i18n'; import track from '@affine/track'; @@ -7,7 +8,7 @@ import { Content as CollapsibleContent, Root as CollapsibleRoot, } from '@radix-ui/react-collapsible'; -import { DocsService, useLiveData, useService } from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import { useCallback, useState } from 'react'; import { DocPropertyManager } from '../manager'; diff --git a/packages/frontend/core/src/components/doc-properties/table.tsx b/packages/frontend/core/src/components/doc-properties/table.tsx index 81dc3eb6b56ad..9774aa5d1cec3 100644 --- a/packages/frontend/core/src/components/doc-properties/table.tsx +++ b/packages/frontend/core/src/components/doc-properties/table.tsx @@ -8,22 +8,20 @@ import { useDraggable, useDropTarget, } from '@affine/component'; +import type { DocCustomPropertyInfo } from '@affine/core/modules/db'; +import { DocService, DocsService } from '@affine/core/modules/doc'; import { DocDatabaseBacklinkInfo } from '@affine/core/modules/doc-info'; import type { DatabaseRow, DatabaseValueCell, } from '@affine/core/modules/doc-info/types'; -import { WorkbenchService } from '@affine/core/modules/workbench'; -import { ViewService } from '@affine/core/modules/workbench/services/view'; +import { ViewService, WorkbenchService } from '@affine/core/modules/workbench'; import type { AffineDNDData } from '@affine/core/types/dnd'; import { useI18n } from '@affine/i18n'; import { track } from '@affine/track'; import { PlusIcon, PropertyIcon, ToggleExpandIcon } from '@blocksuite/icons/rc'; import * as Collapsible from '@radix-ui/react-collapsible'; import { - type DocCustomPropertyInfo, - DocService, - DocsService, useLiveData, useService, useServiceOptional, diff --git a/packages/frontend/core/src/components/doc-properties/tags-inline-editor.tsx b/packages/frontend/core/src/components/doc-properties/tags-inline-editor.tsx index e496df67393ad..8475f9535e722 100644 --- a/packages/frontend/core/src/components/doc-properties/tags-inline-editor.tsx +++ b/packages/frontend/core/src/components/doc-properties/tags-inline-editor.tsx @@ -1,12 +1,8 @@ import { TagService, useDeleteTagConfirmModal } from '@affine/core/modules/tag'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { useI18n } from '@affine/i18n'; import { TagsIcon } from '@blocksuite/icons/rc'; -import { - LiveData, - useLiveData, - useService, - WorkspaceService, -} from '@toeverything/infra'; +import { LiveData, useLiveData, useService } from '@toeverything/infra'; import { useCallback, useMemo } from 'react'; import { useAsyncCallback } from '../hooks/affine-async-hooks'; diff --git a/packages/frontend/core/src/components/doc-properties/types/created-updated-by.tsx b/packages/frontend/core/src/components/doc-properties/types/created-updated-by.tsx index 89e8f49a4a1cc..aed8192107d74 100644 --- a/packages/frontend/core/src/components/doc-properties/types/created-updated-by.tsx +++ b/packages/frontend/core/src/components/doc-properties/types/created-updated-by.tsx @@ -1,7 +1,8 @@ import { Avatar, PropertyValue } from '@affine/component'; import { CloudDocMetaService } from '@affine/core/modules/cloud/services/cloud-doc-meta'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { useI18n } from '@affine/i18n'; -import { useLiveData, useService, WorkspaceService } from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import { useEffect, useMemo } from 'react'; import { userWrapper } from './created-updated-by.css'; diff --git a/packages/frontend/core/src/components/doc-properties/types/date.tsx b/packages/frontend/core/src/components/doc-properties/types/date.tsx index 21cd34e1ac2f8..52ad9a2a41fc9 100644 --- a/packages/frontend/core/src/components/doc-properties/types/date.tsx +++ b/packages/frontend/core/src/components/doc-properties/types/date.tsx @@ -1,6 +1,7 @@ import { DatePicker, Menu, PropertyValue, Tooltip } from '@affine/component'; +import { DocService } from '@affine/core/modules/doc'; import { i18nTime, useI18n } from '@affine/i18n'; -import { DocService, useLiveData, useServices } from '@toeverything/infra'; +import { useLiveData, useServices } from '@toeverything/infra'; import * as styles from './date.css'; import type { PropertyValueProps } from './types'; diff --git a/packages/frontend/core/src/components/doc-properties/types/doc-primary-mode.tsx b/packages/frontend/core/src/components/doc-properties/types/doc-primary-mode.tsx index c64f0110d5ef7..d1f8b8546a716 100644 --- a/packages/frontend/core/src/components/doc-properties/types/doc-primary-mode.tsx +++ b/packages/frontend/core/src/components/doc-properties/types/doc-primary-mode.tsx @@ -4,9 +4,10 @@ import { RadioGroup, type RadioItem, } from '@affine/component'; +import { DocService } from '@affine/core/modules/doc'; import { useI18n } from '@affine/i18n'; import type { DocMode } from '@blocksuite/affine/blocks'; -import { DocService, useLiveData, useService } from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import { useCallback, useMemo } from 'react'; import * as styles from './doc-primary-mode.css'; diff --git a/packages/frontend/core/src/components/doc-properties/types/edgeless-theme.tsx b/packages/frontend/core/src/components/doc-properties/types/edgeless-theme.tsx index 91c27061796f4..b27bb1ebe8409 100644 --- a/packages/frontend/core/src/components/doc-properties/types/edgeless-theme.tsx +++ b/packages/frontend/core/src/components/doc-properties/types/edgeless-theme.tsx @@ -1,6 +1,7 @@ import { PropertyValue, RadioGroup, type RadioItem } from '@affine/component'; +import { DocService } from '@affine/core/modules/doc'; import { useI18n } from '@affine/i18n'; -import { DocService, useLiveData, useService } from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import { useCallback, useMemo } from 'react'; import * as styles from './edgeless-theme.css'; diff --git a/packages/frontend/core/src/components/doc-properties/types/journal.tsx b/packages/frontend/core/src/components/doc-properties/types/journal.tsx index b32cdd6e4f925..0c3dbc89c9dea 100644 --- a/packages/frontend/core/src/components/doc-properties/types/journal.tsx +++ b/packages/frontend/core/src/components/doc-properties/types/journal.tsx @@ -1,11 +1,11 @@ import { Checkbox, DatePicker, Menu, PropertyValue } from '@affine/component'; import { MobileJournalConflictList } from '@affine/core/mobile/pages/workspace/detail/menu/journal-conflicts'; +import { DocService } from '@affine/core/modules/doc'; import { JournalService } from '@affine/core/modules/journal'; import { WorkbenchService } from '@affine/core/modules/workbench'; import { ViewService } from '@affine/core/modules/workbench/services/view'; import { i18nTime, useI18n } from '@affine/i18n'; import { - DocService, useLiveData, useService, useServiceOptional, diff --git a/packages/frontend/core/src/components/doc-properties/types/page-width.tsx b/packages/frontend/core/src/components/doc-properties/types/page-width.tsx index 4e7b0fcdf5c9c..d9d344a863c17 100644 --- a/packages/frontend/core/src/components/doc-properties/types/page-width.tsx +++ b/packages/frontend/core/src/components/doc-properties/types/page-width.tsx @@ -1,7 +1,8 @@ import { PropertyValue, RadioGroup, type RadioItem } from '@affine/component'; +import { DocService } from '@affine/core/modules/doc'; import { EditorSettingService } from '@affine/core/modules/editor-setting'; import { useI18n } from '@affine/i18n'; -import { DocService, useLiveData, useService } from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import { useCallback, useMemo } from 'react'; import { container } from './page-width.css'; diff --git a/packages/frontend/core/src/components/doc-properties/types/tags.tsx b/packages/frontend/core/src/components/doc-properties/types/tags.tsx index 11339f5171cea..456403a8ba05a 100644 --- a/packages/frontend/core/src/components/doc-properties/types/tags.tsx +++ b/packages/frontend/core/src/components/doc-properties/types/tags.tsx @@ -1,7 +1,8 @@ import { PropertyValue } from '@affine/component'; +import { DocService } from '@affine/core/modules/doc'; import { TagService } from '@affine/core/modules/tag'; import { useI18n } from '@affine/i18n'; -import { DocService, useLiveData, useService } from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import { TagsInlineEditor } from '../tags-inline-editor'; import * as styles from './tags.css'; diff --git a/packages/frontend/core/src/components/doc-properties/types/types.ts b/packages/frontend/core/src/components/doc-properties/types/types.ts index aaa1fc101eb38..b53dac916e445 100644 --- a/packages/frontend/core/src/components/doc-properties/types/types.ts +++ b/packages/frontend/core/src/components/doc-properties/types/types.ts @@ -1,4 +1,4 @@ -import type { DocCustomPropertyInfo } from '@toeverything/infra'; +import type { DocCustomPropertyInfo } from '@affine/core/modules/db'; export interface PropertyValueProps { propertyInfo?: DocCustomPropertyInfo; diff --git a/packages/frontend/core/src/components/hooks/affine/use-all-page-list-config.tsx b/packages/frontend/core/src/components/hooks/affine/use-all-page-list-config.tsx index 77490a5966857..b924280694072 100644 --- a/packages/frontend/core/src/components/hooks/affine/use-all-page-list-config.tsx +++ b/packages/frontend/core/src/components/hooks/affine/use-all-page-list-config.tsx @@ -3,10 +3,11 @@ import { useBlockSuiteDocMeta } from '@affine/core/components/hooks/use-block-su import { FavoriteTag } from '@affine/core/components/page-list/components/favorite-tag'; import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/favorite'; import { ShareDocsListService } from '@affine/core/modules/share-doc'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { PublicPageMode } from '@affine/graphql'; import { useI18n } from '@affine/i18n'; import type { DocCollection, DocMeta } from '@blocksuite/affine/store'; -import { useLiveData, useService, WorkspaceService } from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import { type ReactNode, useCallback, useEffect, useMemo } from 'react'; export type AllPageListConfig = { diff --git a/packages/frontend/core/src/components/hooks/affine/use-block-suite-meta-helper.ts b/packages/frontend/core/src/components/hooks/affine/use-block-suite-meta-helper.ts index 6efe884a6fc4e..e9bbaab56e367 100644 --- a/packages/frontend/core/src/components/hooks/affine/use-block-suite-meta-helper.ts +++ b/packages/frontend/core/src/components/hooks/affine/use-block-suite-meta-helper.ts @@ -1,8 +1,10 @@ import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; import { useDocMetaHelper } from '@affine/core/components/hooks/use-block-suite-page-meta'; import { useDocCollectionHelper } from '@affine/core/components/hooks/use-block-suite-workspace-helper'; +import { DocsService } from '@affine/core/modules/doc'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import type { DocMode } from '@blocksuite/affine/blocks'; -import { DocsService, useService, WorkspaceService } from '@toeverything/infra'; +import { useService } from '@toeverything/infra'; import { useCallback } from 'react'; import { applyUpdate, encodeStateAsUpdate } from 'yjs'; diff --git a/packages/frontend/core/src/components/hooks/affine/use-enable-cloud.tsx b/packages/frontend/core/src/components/hooks/affine/use-enable-cloud.tsx index 5d765f3d30a05..6b5a7127bf8e3 100644 --- a/packages/frontend/core/src/components/hooks/affine/use-enable-cloud.tsx +++ b/packages/frontend/core/src/components/hooks/affine/use-enable-cloud.tsx @@ -1,13 +1,10 @@ import { notify, useConfirmModal } from '@affine/component'; import { AuthService, ServersService } from '@affine/core/modules/cloud'; import { GlobalDialogService } from '@affine/core/modules/dialogs'; +import type { Workspace } from '@affine/core/modules/workspace'; +import { WorkspacesService } from '@affine/core/modules/workspace'; import { useI18n } from '@affine/i18n'; -import type { Workspace } from '@toeverything/infra'; -import { - useLiveData, - useService, - WorkspacesService, -} from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import { useCallback } from 'react'; import { useNavigateHelper } from '../use-navigate-helper'; diff --git a/packages/frontend/core/src/components/hooks/affine/use-register-blocksuite-editor-commands.tsx b/packages/frontend/core/src/components/hooks/affine/use-register-blocksuite-editor-commands.tsx index b5cfd0bd3f052..b422bb0650701 100644 --- a/packages/frontend/core/src/components/hooks/affine/use-register-blocksuite-editor-commands.tsx +++ b/packages/frontend/core/src/components/hooks/affine/use-register-blocksuite-editor-commands.tsx @@ -4,10 +4,12 @@ import { registerAffineCommand, } from '@affine/core/commands'; import { WorkspaceDialogService } from '@affine/core/modules/dialogs'; +import { DocService } from '@affine/core/modules/doc'; import type { Editor } from '@affine/core/modules/editor'; import { EditorSettingService } from '@affine/core/modules/editor-setting'; import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/favorite'; import { OpenInAppService } from '@affine/core/modules/open-in-app'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { useI18n } from '@affine/i18n'; import { track } from '@affine/track'; import { @@ -17,11 +19,9 @@ import { PageIcon, } from '@blocksuite/icons/rc'; import { - DocService, useLiveData, useService, useServiceOptional, - WorkspaceService, } from '@toeverything/infra'; import { useSetAtom } from 'jotai'; import { useCallback, useEffect } from 'react'; diff --git a/packages/frontend/core/src/components/hooks/affine/use-register-copy-link-commands.tsx b/packages/frontend/core/src/components/hooks/affine/use-register-copy-link-commands.tsx index 7184aa4cfc7ec..02f45ec8a0f52 100644 --- a/packages/frontend/core/src/components/hooks/affine/use-register-copy-link-commands.tsx +++ b/packages/frontend/core/src/components/hooks/affine/use-register-copy-link-commands.tsx @@ -4,8 +4,8 @@ import { } from '@affine/core/commands'; import { useSharingUrl } from '@affine/core/components/hooks/affine/use-share-url'; import { useIsActiveView } from '@affine/core/modules/workbench'; +import type { WorkspaceMetadata } from '@affine/core/modules/workspace'; import { track } from '@affine/track'; -import { type WorkspaceMetadata } from '@toeverything/infra'; import { useEffect } from 'react'; export function useRegisterCopyLinkCommands({ diff --git a/packages/frontend/core/src/components/hooks/affine/use-sign-out.ts b/packages/frontend/core/src/components/hooks/affine/use-sign-out.ts index d9127fa960f65..66b61311f1649 100644 --- a/packages/frontend/core/src/components/hooks/affine/use-sign-out.ts +++ b/packages/frontend/core/src/components/hooks/affine/use-sign-out.ts @@ -4,13 +4,10 @@ import { useConfirmModal, } from '@affine/component'; import { AuthService, ServerService } from '@affine/core/modules/cloud'; +import { GlobalContextService } from '@affine/core/modules/global-context'; +import { WorkspacesService } from '@affine/core/modules/workspace'; import { useI18n } from '@affine/i18n'; -import { - GlobalContextService, - useLiveData, - useService, - WorkspacesService, -} from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import { useCallback } from 'react'; import { useNavigateHelper } from '../use-navigate-helper'; diff --git a/packages/frontend/core/src/components/hooks/use-block-suite-page-meta.ts b/packages/frontend/core/src/components/hooks/use-block-suite-page-meta.ts index 467765cd86f38..9821a15a5d631 100644 --- a/packages/frontend/core/src/components/hooks/use-block-suite-page-meta.ts +++ b/packages/frontend/core/src/components/hooks/use-block-suite-page-meta.ts @@ -1,5 +1,7 @@ +import { DocsService } from '@affine/core/modules/doc'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import type { DocCollection, DocMeta } from '@blocksuite/affine/store'; -import { DocsService, useService, WorkspaceService } from '@toeverything/infra'; +import { useService } from '@toeverything/infra'; import { useCallback, useMemo } from 'react'; import { useAsyncCallback } from './affine-async-hooks'; diff --git a/packages/frontend/core/src/components/hooks/use-journal.ts b/packages/frontend/core/src/components/hooks/use-journal.ts index 7c679865dbb55..241f953075e18 100644 --- a/packages/frontend/core/src/components/hooks/use-journal.ts +++ b/packages/frontend/core/src/components/hooks/use-journal.ts @@ -1,3 +1,4 @@ +import { DocsService } from '@affine/core/modules/doc'; import { EditorSettingService } from '@affine/core/modules/editor-setting'; import { JOURNAL_DATE_FORMAT, @@ -6,7 +7,7 @@ import { } from '@affine/core/modules/journal'; import { i18nTime } from '@affine/i18n'; import { track } from '@affine/track'; -import { DocsService, useService, useServices } from '@toeverything/infra'; +import { useService, useServices } from '@toeverything/infra'; import dayjs from 'dayjs'; import { useCallback, useMemo } from 'react'; diff --git a/packages/frontend/core/src/components/hooks/use-register-workspace-commands.ts b/packages/frontend/core/src/components/hooks/use-register-workspace-commands.ts index f3030e98b88c3..160e930a2a743 100644 --- a/packages/frontend/core/src/components/hooks/use-register-workspace-commands.ts +++ b/packages/frontend/core/src/components/hooks/use-register-workspace-commands.ts @@ -3,13 +3,10 @@ import { DesktopApiService } from '@affine/core/modules/desktop-api'; import { GlobalDialogService } from '@affine/core/modules/dialogs'; import { I18nService } from '@affine/core/modules/i18n'; import { UrlService } from '@affine/core/modules/url'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { useI18n } from '@affine/i18n'; import type { AffineEditorContainer } from '@blocksuite/affine/presets'; -import { - useService, - useServiceOptional, - WorkspaceService, -} from '@toeverything/infra'; +import { useService, useServiceOptional } from '@toeverything/infra'; import { useStore } from 'jotai'; import { useTheme } from 'next-themes'; import { useEffect } from 'react'; diff --git a/packages/frontend/core/src/components/hooks/use-workspace-info.ts b/packages/frontend/core/src/components/hooks/use-workspace-info.ts index 05bfcf1876acc..aad7957d7d78b 100644 --- a/packages/frontend/core/src/components/hooks/use-workspace-info.ts +++ b/packages/frontend/core/src/components/hooks/use-workspace-info.ts @@ -1,9 +1,8 @@ -import type { WorkspaceMetadata } from '@toeverything/infra'; import { - useLiveData, - useService, + type WorkspaceMetadata, WorkspacesService, -} from '@toeverything/infra'; +} from '@affine/core/modules/workspace'; +import { useLiveData, useService } from '@toeverything/infra'; import { useEffect } from 'react'; export function useWorkspaceInfo(meta?: WorkspaceMetadata) { diff --git a/packages/frontend/core/src/components/hooks/use-workspace.ts b/packages/frontend/core/src/components/hooks/use-workspace.ts index 0a69933303245..5f187368fe12f 100644 --- a/packages/frontend/core/src/components/hooks/use-workspace.ts +++ b/packages/frontend/core/src/components/hooks/use-workspace.ts @@ -1,5 +1,9 @@ -import type { Workspace, WorkspaceMetadata } from '@toeverything/infra'; -import { useService, WorkspacesService } from '@toeverything/infra'; +import { + type Workspace, + type WorkspaceMetadata, + WorkspacesService, +} from '@affine/core/modules/workspace'; +import { useService } from '@toeverything/infra'; import { useEffect, useState } from 'react'; /** diff --git a/packages/frontend/core/src/components/over-capacity/index.tsx b/packages/frontend/core/src/components/over-capacity/index.tsx index 8df383bafb010..09c96a8e1281d 100644 --- a/packages/frontend/core/src/components/over-capacity/index.tsx +++ b/packages/frontend/core/src/components/over-capacity/index.tsx @@ -1,8 +1,9 @@ import { notify } from '@affine/component'; import { GlobalDialogService } from '@affine/core/modules/dialogs'; import { WorkspacePermissionService } from '@affine/core/modules/permissions'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { useI18n } from '@affine/i18n'; -import { useLiveData, useService, WorkspaceService } from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import { debounce } from 'lodash-es'; import { useCallback, useEffect } from 'react'; diff --git a/packages/frontend/core/src/components/page-detail-editor.tsx b/packages/frontend/core/src/components/page-detail-editor.tsx index fec5deff4a401..a50387520bba4 100644 --- a/packages/frontend/core/src/components/page-detail-editor.tsx +++ b/packages/frontend/core/src/components/page-detail-editor.tsx @@ -1,12 +1,13 @@ import './page-detail-editor.css'; import type { AffineEditorContainer } from '@blocksuite/affine/presets'; -import { DocService, useLiveData, useService } from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import { cssVar } from '@toeverything/theme'; import clsx from 'clsx'; import type { CSSProperties } from 'react'; import { useMemo } from 'react'; +import { DocService } from '../modules/doc'; import { EditorService } from '../modules/editor'; import { EditorSettingService, @@ -16,7 +17,7 @@ import { BlockSuiteEditor as Editor } from './blocksuite/block-suite-editor'; import * as styles from './page-detail-editor.css'; declare global { - // eslint-disable-next-line no-var + // oxlint-disable-next-line no-var var currentEditor: AffineEditorContainer | undefined; } diff --git a/packages/frontend/core/src/components/page-list/collections/virtualized-collection-list.tsx b/packages/frontend/core/src/components/page-list/collections/virtualized-collection-list.tsx index 52372ddbb46c8..54709ebc8ac99 100644 --- a/packages/frontend/core/src/components/page-list/collections/virtualized-collection-list.tsx +++ b/packages/frontend/core/src/components/page-list/collections/virtualized-collection-list.tsx @@ -1,7 +1,8 @@ import { useDeleteCollectionInfo } from '@affine/core/components/hooks/affine/use-delete-collection-info'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import type { Collection, DeleteCollectionInfo } from '@affine/env/filter'; import { Trans } from '@affine/i18n'; -import { useService, WorkspaceService } from '@toeverything/infra'; +import { useService } from '@toeverything/infra'; import { useCallback, useMemo, useRef, useState } from 'react'; import { CollectionService } from '../../../modules/collection'; diff --git a/packages/frontend/core/src/components/page-list/docs/page-list-header.tsx b/packages/frontend/core/src/components/page-list/docs/page-list-header.tsx index 375b4fe94547d..3da0ad6b052d2 100644 --- a/packages/frontend/core/src/components/page-list/docs/page-list-header.tsx +++ b/packages/frontend/core/src/components/page-list/docs/page-list-header.tsx @@ -8,9 +8,11 @@ import { } from '@affine/component'; import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-helper'; import { WorkspaceDialogService } from '@affine/core/modules/dialogs'; +import type { DocRecord } from '@affine/core/modules/doc'; import type { Tag } from '@affine/core/modules/tag'; import { TagService } from '@affine/core/modules/tag'; import { WorkbenchService } from '@affine/core/modules/workbench'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { isNewTabTrigger } from '@affine/core/utils'; import type { Collection } from '@affine/env/filter'; import { useI18n } from '@affine/i18n'; @@ -21,13 +23,7 @@ import { SearchIcon, ViewLayersIcon, } from '@blocksuite/icons/rc'; -import type { DocRecord } from '@toeverything/infra'; -import { - useLiveData, - useService, - useServices, - WorkspaceService, -} from '@toeverything/infra'; +import { useLiveData, useService, useServices } from '@toeverything/infra'; import clsx from 'clsx'; import { useCallback, useMemo, useState } from 'react'; import { Link } from 'react-router-dom'; diff --git a/packages/frontend/core/src/components/page-list/docs/select-page.tsx b/packages/frontend/core/src/components/page-list/docs/select-page.tsx index a80db7652af55..eac410f3e596c 100644 --- a/packages/frontend/core/src/components/page-list/docs/select-page.tsx +++ b/packages/frontend/core/src/components/page-list/docs/select-page.tsx @@ -2,15 +2,12 @@ import { IconButton, Menu, toast } from '@affine/component'; import { useBlockSuiteDocMeta } from '@affine/core/components/hooks/use-block-suite-page-meta'; import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/favorite'; import { ShareDocsListService } from '@affine/core/modules/share-doc'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { PublicPageMode } from '@affine/graphql'; import { Trans, useI18n } from '@affine/i18n'; import type { DocMeta } from '@blocksuite/affine/store'; import { FilterIcon } from '@blocksuite/icons/rc'; -import { - useLiveData, - useServices, - WorkspaceService, -} from '@toeverything/infra'; +import { useLiveData, useServices } from '@toeverything/infra'; import { type ReactNode, useCallback, useEffect, useState } from 'react'; import { AffineShapeIcon, FavoriteTag } from '..'; diff --git a/packages/frontend/core/src/components/page-list/docs/virtualized-page-list.tsx b/packages/frontend/core/src/components/page-list/docs/virtualized-page-list.tsx index 0d0fb67a835a3..32aa9164fe9b8 100644 --- a/packages/frontend/core/src/components/page-list/docs/virtualized-page-list.tsx +++ b/packages/frontend/core/src/components/page-list/docs/virtualized-page-list.tsx @@ -1,11 +1,13 @@ import { toast, useConfirmModal } from '@affine/component'; import { useBlockSuiteDocMeta } from '@affine/core/components/hooks/use-block-suite-page-meta'; import { CollectionService } from '@affine/core/modules/collection'; +import { DocsService } from '@affine/core/modules/doc'; import type { Tag } from '@affine/core/modules/tag'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import type { Collection, Filter } from '@affine/env/filter'; import { Trans, useI18n } from '@affine/i18n'; import type { DocMeta } from '@blocksuite/affine/store'; -import { DocsService, useService, WorkspaceService } from '@toeverything/infra'; +import { useService } from '@toeverything/infra'; import { useCallback, useMemo, useRef, useState } from 'react'; import { ListFloatingToolbar } from '../components/list-floating-toolbar'; diff --git a/packages/frontend/core/src/components/page-list/operation-cell.tsx b/packages/frontend/core/src/components/page-list/operation-cell.tsx index 225bcf0adaf8f..1f84d797f6d0a 100644 --- a/packages/frontend/core/src/components/page-list/operation-cell.tsx +++ b/packages/frontend/core/src/components/page-list/operation-cell.tsx @@ -9,11 +9,14 @@ import { import { useBlockSuiteMetaHelper } from '@affine/core/components/hooks/affine/use-block-suite-meta-helper'; import { useCatchEventCallback } from '@affine/core/components/hooks/use-catch-event-hook'; import { WorkspaceDialogService } from '@affine/core/modules/dialogs'; +import { DocsService } from '@affine/core/modules/doc'; import { CompatibleFavoriteItemsAdapter, FavoriteService, } from '@affine/core/modules/favorite'; +import { FeatureFlagService } from '@affine/core/modules/feature-flag'; import { WorkbenchService } from '@affine/core/modules/workbench'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import type { Collection, DeleteCollectionInfo } from '@affine/env/filter'; import { useI18n } from '@affine/i18n'; import { track } from '@affine/track'; @@ -32,14 +35,7 @@ import { ResetIcon, SplitViewIcon, } from '@blocksuite/icons/rc'; -import { - DocsService, - FeatureFlagService, - useLiveData, - useService, - useServices, - WorkspaceService, -} from '@toeverything/infra'; +import { useLiveData, useService, useServices } from '@toeverything/infra'; import type { MouseEvent } from 'react'; import { useCallback, useState } from 'react'; diff --git a/packages/frontend/core/src/components/page-list/tags/virtualized-tag-list.tsx b/packages/frontend/core/src/components/page-list/tags/virtualized-tag-list.tsx index cc7d485089e9a..74c2d1292fa20 100644 --- a/packages/frontend/core/src/components/page-list/tags/virtualized-tag-list.tsx +++ b/packages/frontend/core/src/components/page-list/tags/virtualized-tag-list.tsx @@ -1,6 +1,7 @@ import type { Tag } from '@affine/core/modules/tag'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { Trans } from '@affine/i18n'; -import { useService, WorkspaceService } from '@toeverything/infra'; +import { useService } from '@toeverything/infra'; import { useCallback, useMemo, useRef, useState } from 'react'; import { ListFloatingToolbar } from '../components/list-floating-toolbar'; diff --git a/packages/frontend/core/src/components/page-list/use-all-doc-display-properties.ts b/packages/frontend/core/src/components/page-list/use-all-doc-display-properties.ts index 60496c143c832..cbd25e55e42ef 100644 --- a/packages/frontend/core/src/components/page-list/use-all-doc-display-properties.ts +++ b/packages/frontend/core/src/components/page-list/use-all-doc-display-properties.ts @@ -1,4 +1,5 @@ -import { useService, WorkspaceService } from '@toeverything/infra'; +import { WorkspaceService } from '@affine/core/modules/workspace'; +import { useService } from '@toeverything/infra'; import { useAtom } from 'jotai'; import { atomWithStorage } from 'jotai/utils'; import { useCallback } from 'react'; diff --git a/packages/frontend/core/src/components/page-list/view/collection-operations.tsx b/packages/frontend/core/src/components/page-list/view/collection-operations.tsx index 6e67b3910bd83..4ff46438816ce 100644 --- a/packages/frontend/core/src/components/page-list/view/collection-operations.tsx +++ b/packages/frontend/core/src/components/page-list/view/collection-operations.tsx @@ -3,6 +3,7 @@ import { Menu, MenuItem, usePromptModal } from '@affine/component'; import { useDeleteCollectionInfo } from '@affine/core/components/hooks/affine/use-delete-collection-info'; import { WorkspaceDialogService } from '@affine/core/modules/dialogs'; import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/favorite'; +import { FeatureFlagService } from '@affine/core/modules/feature-flag'; import { WorkbenchService } from '@affine/core/modules/workbench'; import type { Collection } from '@affine/env/filter'; import { useI18n } from '@affine/i18n'; @@ -14,12 +15,7 @@ import { PlusIcon, SplitViewIcon, } from '@blocksuite/icons/rc'; -import { - FeatureFlagService, - useLiveData, - useService, - useServices, -} from '@toeverything/infra'; +import { useLiveData, useService, useServices } from '@toeverything/infra'; import type { PropsWithChildren, ReactElement } from 'react'; import { useCallback, useMemo } from 'react'; diff --git a/packages/frontend/core/src/components/page-list/virtualized-trash-list.tsx b/packages/frontend/core/src/components/page-list/virtualized-trash-list.tsx index 8dbbffd04a81a..61983aee561d3 100644 --- a/packages/frontend/core/src/components/page-list/virtualized-trash-list.tsx +++ b/packages/frontend/core/src/components/page-list/virtualized-trash-list.tsx @@ -1,9 +1,10 @@ import { toast, useConfirmModal } from '@affine/component'; import { useBlockSuiteMetaHelper } from '@affine/core/components/hooks/affine/use-block-suite-meta-helper'; import { useBlockSuiteDocMeta } from '@affine/core/components/hooks/use-block-suite-page-meta'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { Trans, useI18n } from '@affine/i18n'; import type { DocMeta } from '@blocksuite/affine/store'; -import { useService, WorkspaceService } from '@toeverything/infra'; +import { useService } from '@toeverything/infra'; import { useCallback, useMemo, useRef, useState } from 'react'; import { ListFloatingToolbar } from './components/list-floating-toolbar'; diff --git a/packages/frontend/core/src/components/providers/workspace-side-effects.tsx b/packages/frontend/core/src/components/providers/workspace-side-effects.tsx index 29b43380e34ab..66394eaac0d99 100644 --- a/packages/frontend/core/src/components/providers/workspace-side-effects.tsx +++ b/packages/frontend/core/src/components/providers/workspace-side-effects.tsx @@ -14,22 +14,22 @@ import { GraphQLService, } from '@affine/core/modules/cloud'; import { GlobalDialogService } from '@affine/core/modules/dialogs'; +import { DocsService } from '@affine/core/modules/doc'; import { EditorSettingService } from '@affine/core/modules/editor-setting'; import { useRegisterNavigationCommands } from '@affine/core/modules/navigation/view/use-register-navigation-commands'; import { QuickSearchContainer } from '@affine/core/modules/quicksearch'; import { WorkbenchService } from '@affine/core/modules/workbench'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { useI18n } from '@affine/i18n'; import track from '@affine/track'; import { type DocMode, ZipTransformer } from '@blocksuite/affine/blocks'; import { - DocsService, effect, fromPromise, onStart, throwIfAborted, useService, useServices, - WorkspaceService, } from '@toeverything/infra'; import { useSetAtom } from 'jotai'; import { useEffect } from 'react'; diff --git a/packages/frontend/core/src/components/pure/help-island/index.tsx b/packages/frontend/core/src/components/pure/help-island/index.tsx index f10ae72b15388..a7e8011217fb8 100644 --- a/packages/frontend/core/src/components/pure/help-island/index.tsx +++ b/packages/frontend/core/src/components/pure/help-island/index.tsx @@ -1,15 +1,11 @@ import { Tooltip } from '@affine/component/ui/tooltip'; import { GlobalDialogService } from '@affine/core/modules/dialogs'; import type { SettingTab } from '@affine/core/modules/dialogs/constant'; +import { GlobalContextService } from '@affine/core/modules/global-context'; import { UrlService } from '@affine/core/modules/url'; import { useI18n } from '@affine/i18n'; import { CloseIcon, NewIcon } from '@blocksuite/icons/rc'; -import { - GlobalContextService, - useLiveData, - useService, - useServices, -} from '@toeverything/infra'; +import { useLiveData, useService, useServices } from '@toeverything/infra'; import { useCallback, useState } from 'react'; import { ContactIcon, HelpIcon, KeyboardIcon } from './icons'; diff --git a/packages/frontend/core/src/components/pure/trash-page-footer/index.tsx b/packages/frontend/core/src/components/pure/trash-page-footer/index.tsx index 03af2df089efb..5423bd0cd8515 100644 --- a/packages/frontend/core/src/components/pure/trash-page-footer/index.tsx +++ b/packages/frontend/core/src/components/pure/trash-page-footer/index.tsx @@ -1,8 +1,10 @@ import { Button } from '@affine/component/ui/button'; import { ConfirmModal } from '@affine/component/ui/modal'; +import { DocService } from '@affine/core/modules/doc'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { useI18n } from '@affine/i18n'; import { DeleteIcon, ResetIcon } from '@blocksuite/icons/rc'; -import { DocService, useService, WorkspaceService } from '@toeverything/infra'; +import { useService } from '@toeverything/infra'; import { useCallback, useState } from 'react'; import { useAppSettingHelper } from '../../../components/hooks/affine/use-app-setting-helper'; diff --git a/packages/frontend/core/src/components/root-app-sidebar/index.tsx b/packages/frontend/core/src/components/root-app-sidebar/index.tsx index 6f003c918a46d..b984b973cfb4e 100644 --- a/packages/frontend/core/src/components/root-app-sidebar/index.tsx +++ b/packages/frontend/core/src/components/root-app-sidebar/index.tsx @@ -22,6 +22,7 @@ import { } from '@affine/core/modules/explorer'; import { ExplorerTags } from '@affine/core/modules/explorer/views/sections/tags'; import { CMDKQuickSearchService } from '@affine/core/modules/quicksearch/services/cmdk'; +import type { Workspace } from '@affine/core/modules/workspace'; import { useI18n } from '@affine/i18n'; import { track } from '@affine/track'; import type { Doc } from '@blocksuite/affine/store'; @@ -32,7 +33,6 @@ import { JournalIcon, SettingsIcon, } from '@blocksuite/icons/rc'; -import type { Workspace } from '@toeverything/infra'; import { useLiveData, useService, useServices } from '@toeverything/infra'; import type { ReactElement } from 'react'; import { memo, useCallback } from 'react'; diff --git a/packages/frontend/core/src/components/root-app-sidebar/trash-button.tsx b/packages/frontend/core/src/components/root-app-sidebar/trash-button.tsx index 862379b4be21d..466348814f5d4 100644 --- a/packages/frontend/core/src/components/root-app-sidebar/trash-button.tsx +++ b/packages/frontend/core/src/components/root-app-sidebar/trash-button.tsx @@ -4,14 +4,11 @@ import { useDropTarget, } from '@affine/component'; import { MenuLinkItem } from '@affine/core/modules/app-sidebar/views'; +import { DocsService } from '@affine/core/modules/doc'; +import { GlobalContextService } from '@affine/core/modules/global-context'; import type { AffineDNDData } from '@affine/core/types/dnd'; import { useI18n } from '@affine/i18n'; -import { - DocsService, - GlobalContextService, - useLiveData, - useService, -} from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; export const TrashButton = () => { const t = useI18n(); diff --git a/packages/frontend/core/src/components/sign-in/sign-in.tsx b/packages/frontend/core/src/components/sign-in/sign-in.tsx index 60d06e3f0d5b5..57d13924aa1cb 100644 --- a/packages/frontend/core/src/components/sign-in/sign-in.tsx +++ b/packages/frontend/core/src/components/sign-in/sign-in.tsx @@ -3,14 +3,11 @@ import { AuthInput, ModalHeader } from '@affine/component/auth-components'; import { OAuth } from '@affine/core/components/affine/auth/oauth'; import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; import { AuthService, ServerService } from '@affine/core/modules/cloud'; +import { FeatureFlagService } from '@affine/core/modules/feature-flag'; import { ServerDeploymentType } from '@affine/graphql'; import { Trans, useI18n } from '@affine/i18n'; import { ArrowRightBigIcon, PublishIcon } from '@blocksuite/icons/rc'; -import { - FeatureFlagService, - useLiveData, - useService, -} from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import { cssVar } from '@toeverything/theme'; import { type Dispatch, diff --git a/packages/frontend/core/src/components/top-tip.tsx b/packages/frontend/core/src/components/top-tip.tsx index 0e703a3d1b6f4..98b1dbec8744f 100644 --- a/packages/frontend/core/src/components/top-tip.tsx +++ b/packages/frontend/core/src/components/top-tip.tsx @@ -1,11 +1,12 @@ import { BrowserWarning, LocalDemoTips } from '@affine/component/affine-banner'; import { Trans, useI18n } from '@affine/i18n'; -import { useLiveData, useService, type Workspace } from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import { useCallback, useState } from 'react'; import { useEnableCloud } from '../components/hooks/affine/use-enable-cloud'; import { AuthService } from '../modules/cloud'; import { GlobalDialogService } from '../modules/dialogs'; +import type { Workspace } from '../modules/workspace'; const minimumChromeVersion = 106; diff --git a/packages/frontend/component/src/components/workspace-avatar/index.tsx b/packages/frontend/core/src/components/workspace-avatar/index.tsx similarity index 93% rename from packages/frontend/component/src/components/workspace-avatar/index.tsx rename to packages/frontend/core/src/components/workspace-avatar/index.tsx index 3e849f89c82ad..1c12315861f3e 100644 --- a/packages/frontend/component/src/components/workspace-avatar/index.tsx +++ b/packages/frontend/core/src/components/workspace-avatar/index.tsx @@ -1,13 +1,11 @@ +import { Avatar, type AvatarProps } from '@affine/component'; import { - useLiveData, - useService, type WorkspaceMetadata, WorkspacesService, -} from '@toeverything/infra'; +} from '@affine/core/modules/workspace'; +import { useLiveData, useService } from '@toeverything/infra'; import { useEffect, useLayoutEffect, useState } from 'react'; -import { Avatar, type AvatarProps } from '../../ui/avatar'; - const cache = new Map(); /** diff --git a/packages/frontend/core/src/components/workspace-selector/index.tsx b/packages/frontend/core/src/components/workspace-selector/index.tsx index 0aa3e0bd69a3d..1f77cc8795721 100644 --- a/packages/frontend/core/src/components/workspace-selector/index.tsx +++ b/packages/frontend/core/src/components/workspace-selector/index.tsx @@ -1,13 +1,12 @@ import { Menu, type MenuProps } from '@affine/component'; import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-helper'; -import { track } from '@affine/track'; +import { GlobalContextService } from '@affine/core/modules/global-context'; import { - GlobalContextService, - useLiveData, - useServices, type WorkspaceMetadata, WorkspacesService, -} from '@toeverything/infra'; +} from '@affine/core/modules/workspace'; +import { track } from '@affine/track'; +import { useLiveData, useServices } from '@toeverything/infra'; import { useCallback, useEffect, useState } from 'react'; import { UserWithWorkspaceList } from './user-with-workspace-list'; diff --git a/packages/frontend/core/src/components/workspace-selector/user-with-workspace-list/add-server/index.tsx b/packages/frontend/core/src/components/workspace-selector/user-with-workspace-list/add-server/index.tsx index e10bbf907cf32..0753b9154267a 100644 --- a/packages/frontend/core/src/components/workspace-selector/user-with-workspace-list/add-server/index.tsx +++ b/packages/frontend/core/src/components/workspace-selector/user-with-workspace-list/add-server/index.tsx @@ -1,11 +1,8 @@ import { MenuItem } from '@affine/component/ui/menu'; +import { FeatureFlagService } from '@affine/core/modules/feature-flag'; import { useI18n } from '@affine/i18n'; import { PlusIcon } from '@blocksuite/icons/rc'; -import { - FeatureFlagService, - useLiveData, - useService, -} from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import * as styles from './index.css'; diff --git a/packages/frontend/core/src/components/workspace-selector/user-with-workspace-list/add-workspace/index.tsx b/packages/frontend/core/src/components/workspace-selector/user-with-workspace-list/add-workspace/index.tsx index 1ec5b58dcff3c..564fd9a0b9f11 100644 --- a/packages/frontend/core/src/components/workspace-selector/user-with-workspace-list/add-workspace/index.tsx +++ b/packages/frontend/core/src/components/workspace-selector/user-with-workspace-list/add-workspace/index.tsx @@ -1,11 +1,8 @@ import { MenuItem } from '@affine/component/ui/menu'; +import { FeatureFlagService } from '@affine/core/modules/feature-flag'; import { useI18n } from '@affine/i18n'; import { ImportIcon, PlusIcon } from '@blocksuite/icons/rc'; -import { - FeatureFlagService, - useLiveData, - useService, -} from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import * as styles from './index.css'; diff --git a/packages/frontend/core/src/components/workspace-selector/user-with-workspace-list/index.tsx b/packages/frontend/core/src/components/workspace-selector/user-with-workspace-list/index.tsx index b5b7e1780f6d9..22accb5c84a7b 100644 --- a/packages/frontend/core/src/components/workspace-selector/user-with-workspace-list/index.tsx +++ b/packages/frontend/core/src/components/workspace-selector/user-with-workspace-list/index.tsx @@ -2,16 +2,15 @@ import { Divider } from '@affine/component/ui/divider'; import { MenuItem } from '@affine/component/ui/menu'; import { AuthService } from '@affine/core/modules/cloud'; import { GlobalDialogService } from '@affine/core/modules/dialogs'; -import { useI18n } from '@affine/i18n'; -import { track } from '@affine/track'; -import { Logo1Icon } from '@blocksuite/icons/rc'; +import { FeatureFlagService } from '@affine/core/modules/feature-flag'; import { - FeatureFlagService, - useLiveData, - useService, type WorkspaceMetadata, WorkspacesService, -} from '@toeverything/infra'; +} from '@affine/core/modules/workspace'; +import { useI18n } from '@affine/i18n'; +import { track } from '@affine/track'; +import { Logo1Icon } from '@blocksuite/icons/rc'; +import { useLiveData, useService } from '@toeverything/infra'; import { useCallback } from 'react'; import { AddServer } from './add-server'; diff --git a/packages/frontend/core/src/components/workspace-selector/user-with-workspace-list/workspace-list/index.tsx b/packages/frontend/core/src/components/workspace-selector/user-with-workspace-list/workspace-list/index.tsx index 1257c7e7fc122..e39cd5acceac9 100644 --- a/packages/frontend/core/src/components/workspace-selector/user-with-workspace-list/workspace-list/index.tsx +++ b/packages/frontend/core/src/components/workspace-selector/user-with-workspace-list/workspace-list/index.tsx @@ -11,6 +11,12 @@ import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-he import type { Server } from '@affine/core/modules/cloud'; import { AuthService, ServersService } from '@affine/core/modules/cloud'; import { GlobalDialogService } from '@affine/core/modules/dialogs'; +import { GlobalContextService } from '@affine/core/modules/global-context'; +import { + type WorkspaceMetadata, + WorkspaceService, + WorkspacesService, +} from '@affine/core/modules/workspace'; import { ServerDeploymentType } from '@affine/graphql'; import { useI18n } from '@affine/i18n'; import { @@ -20,15 +26,11 @@ import { PlusIcon, TeamWorkspaceIcon, } from '@blocksuite/icons/rc'; -import type { WorkspaceMetadata } from '@toeverything/infra'; import { FrameworkScope, - GlobalContextService, useLiveData, useService, useServiceOptional, - WorkspaceService, - WorkspacesService, } from '@toeverything/infra'; import { useCallback, useMemo } from 'react'; diff --git a/packages/frontend/core/src/components/workspace-selector/workspace-card/index.tsx b/packages/frontend/core/src/components/workspace-selector/workspace-card/index.tsx index aeded8663e473..6f22c88e8cbd7 100644 --- a/packages/frontend/core/src/components/workspace-selector/workspace-card/index.tsx +++ b/packages/frontend/core/src/components/workspace-selector/workspace-card/index.tsx @@ -1,9 +1,12 @@ import { Button, Skeleton, Tooltip } from '@affine/component'; import { Loading } from '@affine/component/ui/loading'; -import { WorkspaceAvatar } from '@affine/component/workspace-avatar'; import { useSystemOnline } from '@affine/core/components/hooks/use-system-online'; import { useWorkspace } from '@affine/core/components/hooks/use-workspace'; import { useWorkspaceInfo } from '@affine/core/components/hooks/use-workspace-info'; +import type { + WorkspaceMetadata, + WorkspaceProfileInfo, +} from '@affine/core/modules/workspace'; import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant'; import { ArrowDownSmallIcon, @@ -17,17 +20,14 @@ import { TeamWorkspaceIcon, UnsyncIcon, } from '@blocksuite/icons/rc'; -import { - useLiveData, - type WorkspaceMetadata, - type WorkspaceProfileInfo, -} from '@toeverything/infra'; +import { useLiveData } from '@toeverything/infra'; import { cssVar } from '@toeverything/theme'; import clsx from 'clsx'; import type { HTMLAttributes } from 'react'; import { forwardRef, useCallback, useEffect, useState } from 'react'; import { useCatchEventCallback } from '../../hooks/use-catch-event-hook'; +import { WorkspaceAvatar } from '../../workspace-avatar'; import * as styles from './styles.css'; export { PureWorkspaceCard } from './pure-workspace-card'; diff --git a/packages/frontend/core/src/components/workspace-selector/workspace-card/pure-workspace-card.tsx b/packages/frontend/core/src/components/workspace-selector/workspace-card/pure-workspace-card.tsx index 6e70d429893df..10a682485d7c7 100644 --- a/packages/frontend/core/src/components/workspace-selector/workspace-card/pure-workspace-card.tsx +++ b/packages/frontend/core/src/components/workspace-selector/workspace-card/pure-workspace-card.tsx @@ -1,13 +1,13 @@ import { Skeleton } from '@affine/component'; -import { WorkspaceAvatar } from '@affine/component/workspace-avatar'; import { useWorkspaceInfo } from '@affine/core/components/hooks/use-workspace-info'; +import type { WorkspaceMetadata } from '@affine/core/modules/workspace'; import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant'; import { DoneIcon } from '@blocksuite/icons/rc'; -import { type WorkspaceMetadata } from '@toeverything/infra'; import clsx from 'clsx'; import type { HTMLAttributes } from 'react'; import { forwardRef } from 'react'; +import { WorkspaceAvatar } from '../../workspace-avatar'; import * as styles from './styles.css'; export const PureWorkspaceCard = forwardRef< diff --git a/packages/frontend/core/src/desktop/components/ai-island/container.tsx b/packages/frontend/core/src/desktop/components/ai-island/container.tsx index d9edf6035c86d..0f9ddafe0c253 100644 --- a/packages/frontend/core/src/desktop/components/ai-island/container.tsx +++ b/packages/frontend/core/src/desktop/components/ai-island/container.tsx @@ -1,9 +1,6 @@ -import { - DocsService, - GlobalContextService, - useLiveData, - useService, -} from '@toeverything/infra'; +import { DocsService } from '@affine/core/modules/doc'; +import { GlobalContextService } from '@affine/core/modules/global-context'; +import { useLiveData, useService } from '@toeverything/infra'; import clsx from 'clsx'; import type { PropsWithChildren, ReactElement } from 'react'; diff --git a/packages/frontend/core/src/desktop/components/app-container/index.tsx b/packages/frontend/core/src/desktop/components/app-container/index.tsx index 249958723ce18..8160793dde938 100644 --- a/packages/frontend/core/src/desktop/components/app-container/index.tsx +++ b/packages/frontend/core/src/desktop/components/app-container/index.tsx @@ -8,11 +8,11 @@ import { } from '@affine/core/modules/app-sidebar/views'; import { AppTabsHeader } from '@affine/core/modules/app-tabs-header'; import { NavigationButtons } from '@affine/core/modules/navigation'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { useLiveData, useService, useServiceOptional, - WorkspaceService, } from '@toeverything/infra'; import clsx from 'clsx'; import { diff --git a/packages/frontend/core/src/desktop/dialogs/collection-editor/rules-mode.tsx b/packages/frontend/core/src/desktop/dialogs/collection-editor/rules-mode.tsx index 8f1b894ece342..db46d69e4e78c 100644 --- a/packages/frontend/core/src/desktop/dialogs/collection-editor/rules-mode.tsx +++ b/packages/frontend/core/src/desktop/dialogs/collection-editor/rules-mode.tsx @@ -8,6 +8,7 @@ import { type ListItem, ListScrollContainer, } from '@affine/core/components/page-list'; +import { DocsService } from '@affine/core/modules/doc'; import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/favorite'; import type { Collection } from '@affine/env/filter'; import { Trans, useI18n } from '@affine/i18n'; @@ -18,7 +19,7 @@ import { PageIcon, ToggleCollapseIcon, } from '@blocksuite/icons/rc'; -import { DocsService, useLiveData, useService } from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import { cssVar } from '@toeverything/theme'; import clsx from 'clsx'; import type { ReactNode } from 'react'; diff --git a/packages/frontend/core/src/desktop/dialogs/create-workspace/index.tsx b/packages/frontend/core/src/desktop/dialogs/create-workspace/index.tsx index b1f5b451f9e3a..17b08cebc1aea 100644 --- a/packages/frontend/core/src/desktop/dialogs/create-workspace/index.tsx +++ b/packages/frontend/core/src/desktop/dialogs/create-workspace/index.tsx @@ -8,14 +8,11 @@ import { type GLOBAL_DIALOG_SCHEMA, GlobalDialogService, } from '@affine/core/modules/dialogs'; +import { FeatureFlagService } from '@affine/core/modules/feature-flag'; +import { WorkspacesService } from '@affine/core/modules/workspace'; import { useI18n } from '@affine/i18n'; import { track } from '@affine/track'; -import { - FeatureFlagService, - useLiveData, - useService, - WorkspacesService, -} from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import { useCallback, useState } from 'react'; import { buildShowcaseWorkspace } from '../../../utils/first-app-data'; diff --git a/packages/frontend/core/src/desktop/dialogs/doc-info/index.tsx b/packages/frontend/core/src/desktop/dialogs/doc-info/index.tsx index 04231c7abc6ba..f888cfb264a16 100644 --- a/packages/frontend/core/src/desktop/dialogs/doc-info/index.tsx +++ b/packages/frontend/core/src/desktop/dialogs/doc-info/index.tsx @@ -2,8 +2,8 @@ import { Modal, Scrollable } from '@affine/component'; import { BlocksuiteHeaderTitle } from '@affine/core/components/blocksuite/block-suite-header/title'; import type { DialogComponentProps } from '@affine/core/modules/dialogs'; import type { WORKSPACE_DIALOG_SCHEMA } from '@affine/core/modules/dialogs/constant'; -import type { Doc } from '@toeverything/infra'; -import { DocsService, FrameworkScope, useService } from '@toeverything/infra'; +import { type Doc, DocsService } from '@affine/core/modules/doc'; +import { FrameworkScope, useService } from '@toeverything/infra'; import { useEffect, useState } from 'react'; import { InfoTable } from './info-modal'; diff --git a/packages/frontend/core/src/desktop/dialogs/doc-info/info-modal.tsx b/packages/frontend/core/src/desktop/dialogs/doc-info/info-modal.tsx index 2edaa3f217355..e968c0cd80086 100644 --- a/packages/frontend/core/src/desktop/dialogs/doc-info/info-modal.tsx +++ b/packages/frontend/core/src/desktop/dialogs/doc-info/info-modal.tsx @@ -7,6 +7,8 @@ import { } from '@affine/component'; import { CreatePropertyMenuItems } from '@affine/core/components/doc-properties/menu/create-doc-property'; import { DocPropertyRow } from '@affine/core/components/doc-properties/table'; +import type { DocCustomPropertyInfo } from '@affine/core/modules/db'; +import { DocsService } from '@affine/core/modules/doc'; import { DocDatabaseBacklinkInfo } from '@affine/core/modules/doc-info'; import type { DatabaseRow, @@ -16,13 +18,7 @@ import { DocsSearchService } from '@affine/core/modules/docs-search'; import { useI18n } from '@affine/i18n'; import track from '@affine/track'; import { PlusIcon } from '@blocksuite/icons/rc'; -import { - type DocCustomPropertyInfo, - DocsService, - LiveData, - useLiveData, - useServices, -} from '@toeverything/infra'; +import { LiveData, useLiveData, useServices } from '@toeverything/infra'; import { useCallback, useMemo, useState } from 'react'; import * as styles from './info-modal.css'; diff --git a/packages/frontend/core/src/desktop/dialogs/doc-info/time-row.tsx b/packages/frontend/core/src/desktop/dialogs/doc-info/time-row.tsx index 11d62fd06dc7e..bfcced52138b5 100644 --- a/packages/frontend/core/src/desktop/dialogs/doc-info/time-row.tsx +++ b/packages/frontend/core/src/desktop/dialogs/doc-info/time-row.tsx @@ -1,12 +1,9 @@ import { PropertyName, PropertyRoot, PropertyValue } from '@affine/component'; +import { DocsService } from '@affine/core/modules/doc'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { i18nTime, useI18n } from '@affine/i18n'; import { DateTimeIcon, HistoryIcon } from '@blocksuite/icons/rc'; -import { - DocsService, - useLiveData, - useService, - WorkspaceService, -} from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import clsx from 'clsx'; import type { ConfigType } from 'dayjs'; import { useDebouncedValue } from 'foxact/use-debounced-value'; diff --git a/packages/frontend/core/src/desktop/dialogs/enable-cloud/index.tsx b/packages/frontend/core/src/desktop/dialogs/enable-cloud/index.tsx index 29d099e287b5c..244eaab4172ba 100644 --- a/packages/frontend/core/src/desktop/dialogs/enable-cloud/index.tsx +++ b/packages/frontend/core/src/desktop/dialogs/enable-cloud/index.tsx @@ -12,14 +12,10 @@ import { type GLOBAL_DIALOG_SCHEMA, GlobalDialogService, } from '@affine/core/modules/dialogs'; +import { WorkspacesService } from '@affine/core/modules/workspace'; import { useI18n } from '@affine/i18n'; import { CloudWorkspaceIcon } from '@blocksuite/icons/rc'; -import { - FrameworkScope, - useLiveData, - useService, - WorkspacesService, -} from '@toeverything/infra'; +import { FrameworkScope, useLiveData, useService } from '@toeverything/infra'; import { useCallback, useState } from 'react'; import * as styles from './dialog.css'; diff --git a/packages/frontend/core/src/desktop/dialogs/import-template/index.tsx b/packages/frontend/core/src/desktop/dialogs/import-template/index.tsx index 79fe01d3133a5..388ac6c6acaf7 100644 --- a/packages/frontend/core/src/desktop/dialogs/import-template/index.tsx +++ b/packages/frontend/core/src/desktop/dialogs/import-template/index.tsx @@ -12,15 +12,14 @@ import { ImportTemplateService, TemplateDownloaderService, } from '@affine/core/modules/import-template'; -import { useI18n } from '@affine/i18n'; -import type { DocMode } from '@blocksuite/affine/blocks'; -import { AllDocsIcon } from '@blocksuite/icons/rc'; import { - useLiveData, - useService, type WorkspaceMetadata, WorkspacesService, -} from '@toeverything/infra'; +} from '@affine/core/modules/workspace'; +import { useI18n } from '@affine/i18n'; +import type { DocMode } from '@blocksuite/affine/blocks'; +import { AllDocsIcon } from '@blocksuite/icons/rc'; +import { useLiveData, useService } from '@toeverything/infra'; import { cssVar } from '@toeverything/theme'; import { useCallback, useEffect, useState } from 'react'; diff --git a/packages/frontend/core/src/desktop/dialogs/import-workspace/index.tsx b/packages/frontend/core/src/desktop/dialogs/import-workspace/index.tsx index dafb9b90617f0..80e227835e718 100644 --- a/packages/frontend/core/src/desktop/dialogs/import-workspace/index.tsx +++ b/packages/frontend/core/src/desktop/dialogs/import-workspace/index.tsx @@ -3,11 +3,12 @@ import { type DialogComponentProps, type GLOBAL_DIALOG_SCHEMA, } from '@affine/core/modules/dialogs'; +import { WorkspacesService } from '@affine/core/modules/workspace'; import { _addLocalWorkspace } from '@affine/core/modules/workspace-engine'; import { DebugLogger } from '@affine/debug'; import { apis } from '@affine/electron-api'; import { useI18n } from '@affine/i18n'; -import { useService, WorkspacesService } from '@toeverything/infra'; +import { useService } from '@toeverything/infra'; import { useLayoutEffect } from 'react'; const logger = new DebugLogger('ImportWorkspaceDialog'); diff --git a/packages/frontend/core/src/desktop/dialogs/import/index.tsx b/packages/frontend/core/src/desktop/dialogs/import/index.tsx index 4b1adf89f1d69..2c3532a9bf160 100644 --- a/packages/frontend/core/src/desktop/dialogs/import/index.tsx +++ b/packages/frontend/core/src/desktop/dialogs/import/index.tsx @@ -5,6 +5,7 @@ import type { WORKSPACE_DIALOG_SCHEMA, } from '@affine/core/modules/dialogs'; import { UrlService } from '@affine/core/modules/url'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { DebugLogger } from '@affine/debug'; import { useI18n } from '@affine/i18n'; import track from '@affine/track'; @@ -24,7 +25,7 @@ import { PageIcon, ZipIcon, } from '@blocksuite/icons/rc'; -import { useService, WorkspaceService } from '@toeverything/infra'; +import { useService } from '@toeverything/infra'; import { cssVar } from '@toeverything/theme'; import { cssVarV2 } from '@toeverything/theme/v2'; import { diff --git a/packages/frontend/core/src/desktop/dialogs/selectors/collection.tsx b/packages/frontend/core/src/desktop/dialogs/selectors/collection.tsx index 39a3c26c9316b..a73b1c6bf58cc 100644 --- a/packages/frontend/core/src/desktop/dialogs/selectors/collection.tsx +++ b/packages/frontend/core/src/desktop/dialogs/selectors/collection.tsx @@ -13,8 +13,9 @@ import { CollectionService } from '@affine/core/modules/collection'; import type { DialogComponentProps } from '@affine/core/modules/dialogs'; import type { WORKSPACE_DIALOG_SCHEMA } from '@affine/core/modules/dialogs/constant'; import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/favorite'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { useI18n } from '@affine/i18n'; -import { useLiveData, useService, WorkspaceService } from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import { cssVar } from '@toeverything/theme'; import { useCallback, useMemo, useState } from 'react'; diff --git a/packages/frontend/core/src/desktop/dialogs/selectors/tag.tsx b/packages/frontend/core/src/desktop/dialogs/selectors/tag.tsx index 574eb76604789..d48bbaa2ebd78 100644 --- a/packages/frontend/core/src/desktop/dialogs/selectors/tag.tsx +++ b/packages/frontend/core/src/desktop/dialogs/selectors/tag.tsx @@ -15,8 +15,9 @@ import type { } from '@affine/core/modules/dialogs'; import { FavoriteService } from '@affine/core/modules/favorite'; import { TagService } from '@affine/core/modules/tag'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { useI18n } from '@affine/i18n'; -import { useLiveData, useService, WorkspaceService } from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import { cssVar } from '@toeverything/theme'; import { useCallback, useMemo, useState } from 'react'; diff --git a/packages/frontend/core/src/desktop/dialogs/setting/general-setting/appearance/index.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/appearance/index.tsx index d95ec0454968e..a91a46221c50b 100644 --- a/packages/frontend/core/src/desktop/dialogs/setting/general-setting/appearance/index.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/appearance/index.tsx @@ -6,12 +6,9 @@ import { SettingWrapper, } from '@affine/component/setting-components'; import { LanguageMenu } from '@affine/core/components/affine/language-menu'; +import { FeatureFlagService } from '@affine/core/modules/feature-flag'; import { useI18n } from '@affine/i18n'; -import { - FeatureFlagService, - useLiveData, - useService, -} from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import { useTheme } from 'next-themes'; import { useCallback, useMemo } from 'react'; diff --git a/packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/general.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/general.tsx index abc4fcd217e99..42f40b2c44062 100644 --- a/packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/general.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/general.tsx @@ -24,6 +24,7 @@ import { fontStyleOptions, } from '@affine/core/modules/editor-setting'; import { SpellCheckSettingService } from '@affine/core/modules/editor-setting/services/spell-check-setting'; +import { FeatureFlagService } from '@affine/core/modules/feature-flag'; import { type FontData, SystemFontFamilyService, @@ -31,12 +32,7 @@ import { import { Trans, useI18n } from '@affine/i18n'; import type { DocMode } from '@blocksuite/affine/blocks'; import { DoneIcon, SearchIcon } from '@blocksuite/icons/rc'; -import { - FeatureFlagService, - useLiveData, - useService, - useServices, -} from '@toeverything/infra'; +import { useLiveData, useService, useServices } from '@toeverything/infra'; import clsx from 'clsx'; import { forwardRef, diff --git a/packages/frontend/core/src/desktop/dialogs/setting/general-setting/experimental-features/index.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/experimental-features/index.tsx index 59c8148160ca2..072c7c11386af 100644 --- a/packages/frontend/core/src/desktop/dialogs/setting/general-setting/experimental-features/index.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/experimental-features/index.tsx @@ -1,6 +1,11 @@ import { Button, Checkbox, Loading, Switch, Tooltip } from '@affine/component'; import { SettingHeader } from '@affine/component/setting-components'; import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; +import { + AFFINE_FLAGS, + FeatureFlagService, + type Flag, +} from '@affine/core/modules/feature-flag'; import { useI18n } from '@affine/i18n'; import { ArrowRightSmallIcon, @@ -8,13 +13,7 @@ import { EmailIcon, GithubIcon, } from '@blocksuite/icons/rc'; -import { - AFFINE_FLAGS, - FeatureFlagService, - type Flag, - useLiveData, - useServices, -} from '@toeverything/infra'; +import { useLiveData, useServices } from '@toeverything/infra'; import { useAtom } from 'jotai'; import { atomWithStorage } from 'jotai/utils'; import { Suspense, useCallback, useState } from 'react'; diff --git a/packages/frontend/core/src/desktop/dialogs/setting/general-setting/index.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/index.tsx index caa311f141fc9..310ab85ba428c 100644 --- a/packages/frontend/core/src/desktop/dialogs/setting/general-setting/index.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/index.tsx @@ -1,5 +1,6 @@ import { UserFeatureService } from '@affine/core/modules/cloud/services/user-feature'; import type { SettingTab } from '@affine/core/modules/dialogs/constant'; +import { FeatureFlagService } from '@affine/core/modules/feature-flag'; import { useI18n } from '@affine/i18n'; import { AppearanceIcon, @@ -8,11 +9,7 @@ import { KeyboardIcon, PenIcon, } from '@blocksuite/icons/rc'; -import { - FeatureFlagService, - useLiveData, - useServices, -} from '@toeverything/infra'; +import { useLiveData, useServices } from '@toeverything/infra'; import type { ReactElement, SVGProps } from 'react'; import { useEffect } from 'react'; diff --git a/packages/frontend/core/src/desktop/dialogs/setting/index.tsx b/packages/frontend/core/src/desktop/dialogs/setting/index.tsx index af936f3c14a24..d1638e9c4ecca 100644 --- a/packages/frontend/core/src/desktop/dialogs/setting/index.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/index.tsx @@ -8,13 +8,10 @@ import type { GLOBAL_DIALOG_SCHEMA, } from '@affine/core/modules/dialogs'; import type { SettingTab } from '@affine/core/modules/dialogs/constant'; +import type { WorkspaceMetadata } from '@affine/core/modules/workspace'; import { Trans } from '@affine/i18n'; import { ContactWithUsIcon } from '@blocksuite/icons/rc'; -import { - useLiveData, - useService, - type WorkspaceMetadata, -} from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import { debounce } from 'lodash-es'; import { Suspense, diff --git a/packages/frontend/core/src/desktop/dialogs/setting/setting-sidebar/index.tsx b/packages/frontend/core/src/desktop/dialogs/setting/setting-sidebar/index.tsx index c2369d5f9ee90..c66430c97689e 100644 --- a/packages/frontend/core/src/desktop/dialogs/setting/setting-sidebar/index.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/setting-sidebar/index.tsx @@ -4,26 +4,24 @@ import { } from '@affine/component/setting-components'; import { Avatar } from '@affine/component/ui/avatar'; import { Tooltip } from '@affine/component/ui/tooltip'; -import { WorkspaceAvatar } from '@affine/component/workspace-avatar'; import { UserPlanButton } from '@affine/core/components/affine/auth/user-plan-button'; import { useCatchEventCallback } from '@affine/core/components/hooks/use-catch-event-hook'; import { useWorkspaceInfo } from '@affine/core/components/hooks/use-workspace-info'; +import { WorkspaceAvatar } from '@affine/core/components/workspace-avatar'; import { AuthService } from '@affine/core/modules/cloud'; import { UserFeatureService } from '@affine/core/modules/cloud/services/user-feature'; import { GlobalDialogService } from '@affine/core/modules/dialogs'; import type { SettingTab } from '@affine/core/modules/dialogs/constant'; +import { GlobalContextService } from '@affine/core/modules/global-context'; +import { + type WorkspaceMetadata, + WorkspacesService, +} from '@affine/core/modules/workspace'; import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant'; import { useI18n } from '@affine/i18n'; import { track } from '@affine/track'; import { Logo1Icon } from '@blocksuite/icons/rc'; -import type { WorkspaceMetadata } from '@toeverything/infra'; -import { - GlobalContextService, - useLiveData, - useService, - useServices, - WorkspacesService, -} from '@toeverything/infra'; +import { useLiveData, useService, useServices } from '@toeverything/infra'; import clsx from 'clsx'; import { type MouseEvent, diff --git a/packages/frontend/core/src/desktop/dialogs/setting/types.ts b/packages/frontend/core/src/desktop/dialogs/setting/types.ts index 58a0a45415322..8d4e56076d77b 100644 --- a/packages/frontend/core/src/desktop/dialogs/setting/types.ts +++ b/packages/frontend/core/src/desktop/dialogs/setting/types.ts @@ -1,5 +1,5 @@ import type { SettingTab } from '@affine/core/modules/dialogs/constant'; -import type { WorkspaceMetadata } from '@toeverything/infra'; +import type { WorkspaceMetadata } from '@affine/core/modules/workspace'; export interface SettingState { activeTab: SettingTab; diff --git a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/billing/index.tsx b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/billing/index.tsx index b67ec2f146bbf..ace01bd9d561f 100644 --- a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/billing/index.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/billing/index.tsx @@ -17,6 +17,7 @@ import { } from '@affine/core/modules/cloud'; import { WorkspaceQuotaService } from '@affine/core/modules/quota'; import { UrlService } from '@affine/core/modules/url'; +import type { WorkspaceMetadata } from '@affine/core/modules/workspace'; import { createCustomerPortalMutation, type InvoicesQuery, @@ -26,12 +27,7 @@ import { UserFriendlyError, } from '@affine/graphql'; import { useI18n } from '@affine/i18n'; -import { - FrameworkScope, - useLiveData, - useService, - type WorkspaceMetadata, -} from '@toeverything/infra'; +import { FrameworkScope, useLiveData, useService } from '@toeverything/infra'; import { cssVar } from '@toeverything/theme'; import { useCallback, useEffect, useMemo, useState } from 'react'; diff --git a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/index.tsx b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/index.tsx index 79f5334d3eec6..1b89588a27ec8 100644 --- a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/index.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/index.tsx @@ -1,5 +1,5 @@ import type { SettingTab } from '@affine/core/modules/dialogs/constant'; -import type { WorkspaceMetadata } from '@toeverything/infra'; +import type { WorkspaceMetadata } from '@affine/core/modules/workspace'; import type { SettingState } from '../types'; import { WorkspaceSettingBilling } from './billing'; diff --git a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/delete-leave-workspace/delete/index.tsx b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/delete-leave-workspace/delete/index.tsx index 68bea254399b6..58d4a809eaf39 100644 --- a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/delete-leave-workspace/delete/index.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/delete-leave-workspace/delete/index.tsx @@ -2,9 +2,9 @@ import { Input } from '@affine/component'; import type { ConfirmModalProps } from '@affine/component/ui/modal'; import { ConfirmModal } from '@affine/component/ui/modal'; import { useWorkspaceInfo } from '@affine/core/components/hooks/use-workspace-info'; +import type { WorkspaceMetadata } from '@affine/core/modules/workspace'; import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant'; import { Trans, useI18n } from '@affine/i18n'; -import type { WorkspaceMetadata } from '@toeverything/infra'; import { useCallback, useState } from 'react'; import * as styles from './style.css'; diff --git a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/delete-leave-workspace/index.tsx b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/delete-leave-workspace/index.tsx index b0cbd633c5d8e..38bcccdf4253b 100644 --- a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/delete-leave-workspace/index.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/delete-leave-workspace/index.tsx @@ -2,16 +2,15 @@ import { notify } from '@affine/component'; import { SettingRow } from '@affine/component/setting-components'; import { ConfirmModal } from '@affine/component/ui/modal'; import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; +import { GlobalContextService } from '@affine/core/modules/global-context'; import { WorkspacePermissionService } from '@affine/core/modules/permissions'; -import { useI18n } from '@affine/i18n'; -import { ArrowRightSmallIcon } from '@blocksuite/icons/rc'; import { - GlobalContextService, - useLiveData, - useServices, WorkspaceService, WorkspacesService, -} from '@toeverything/infra'; +} from '@affine/core/modules/workspace'; +import { useI18n } from '@affine/i18n'; +import { ArrowRightSmallIcon } from '@blocksuite/icons/rc'; +import { useLiveData, useServices } from '@toeverything/infra'; import { useCallback, useEffect, useState } from 'react'; import { diff --git a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/enable-cloud.tsx b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/enable-cloud.tsx index 69ca382628b41..658ce5d6f9a4c 100644 --- a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/enable-cloud.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/enable-cloud.tsx @@ -1,14 +1,13 @@ import { SettingRow } from '@affine/component/setting-components'; import { Button } from '@affine/component/ui/button'; import { useEnableCloud } from '@affine/core/components/hooks/affine/use-enable-cloud'; -import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant'; -import { useI18n } from '@affine/i18n'; import { - useLiveData, - useService, type Workspace, WorkspaceService, -} from '@toeverything/infra'; +} from '@affine/core/modules/workspace'; +import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant'; +import { useI18n } from '@affine/i18n'; +import { useLiveData, useService } from '@toeverything/infra'; import { useCallback } from 'react'; export interface PublishPanelProps { diff --git a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/export.tsx b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/export.tsx index 4c730246e14f7..03170ff60046e 100644 --- a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/export.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/export.tsx @@ -5,14 +5,13 @@ import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hoo import { useSystemOnline } from '@affine/core/components/hooks/use-system-online'; import { DesktopApiService } from '@affine/core/modules/desktop-api'; import { WorkspacePermissionService } from '@affine/core/modules/permissions'; +import type { + Workspace, + WorkspaceMetadata, +} from '@affine/core/modules/workspace'; import { useI18n } from '@affine/i18n'; import track from '@affine/track'; -import { - useLiveData, - useService, - type Workspace, - type WorkspaceMetadata, -} from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import { useState } from 'react'; interface ExportPanelProps { diff --git a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/labels.tsx b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/labels.tsx index 27c3517f5fd00..2eae0d924b0b5 100644 --- a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/labels.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/labels.tsx @@ -1,6 +1,7 @@ import { WorkspacePermissionService } from '@affine/core/modules/permissions'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { useI18n } from '@affine/i18n'; -import { useLiveData, useService, WorkspaceService } from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import { cssVarV2 } from '@toeverything/theme/v2'; import { useEffect, useMemo } from 'react'; diff --git a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/members/index.tsx b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/members/index.tsx index d41b06bf93710..f80f51366977a 100644 --- a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/members/index.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/members/index.tsx @@ -2,8 +2,9 @@ import { Button, Tooltip } from '@affine/component'; import { SettingRow } from '@affine/component/setting-components'; import { AffineErrorBoundary } from '@affine/core/components/affine/affine-error-boundary'; import { useWorkspaceInfo } from '@affine/core/components/hooks/use-workspace-info'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { useI18n } from '@affine/i18n'; -import { useService, WorkspaceService } from '@toeverything/infra'; +import { useService } from '@toeverything/infra'; import type { ReactElement } from 'react'; import type { SettingState } from '../../../types'; diff --git a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/members/member-list.tsx b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/members/member-list.tsx index 6a801c27840e9..3ca49f255b8a9 100644 --- a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/members/member-list.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/members/member-list.tsx @@ -6,6 +6,7 @@ import { WorkspaceMembersService, WorkspacePermissionService, } from '@affine/core/modules/permissions'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { Permission, UserFriendlyError, @@ -17,7 +18,6 @@ import { useEnsureLiveData, useLiveData, useService, - WorkspaceService, } from '@toeverything/infra'; import clsx from 'clsx'; import { clamp } from 'lodash-es'; diff --git a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/profile.tsx b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/profile.tsx index 85470a315c42b..70268c9618cee 100644 --- a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/profile.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/profile.tsx @@ -1,14 +1,15 @@ import { FlexWrapper, Input, notify, Wrapper } from '@affine/component'; import { Button } from '@affine/component/ui/button'; -import { WorkspaceAvatar } from '@affine/component/workspace-avatar'; import { useCatchEventCallback } from '@affine/core/components/hooks/use-catch-event-hook'; import { Upload } from '@affine/core/components/pure/file-upload'; +import { WorkspaceAvatar } from '@affine/core/components/workspace-avatar'; import { WorkspacePermissionService } from '@affine/core/modules/permissions'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { validateAndReduceImage } from '@affine/core/utils/reduce-image'; import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant'; import { useI18n } from '@affine/i18n'; import { CameraIcon } from '@blocksuite/icons/rc'; -import { useLiveData, useService, WorkspaceService } from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import type { KeyboardEvent } from 'react'; import { useCallback, useEffect, useState } from 'react'; diff --git a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/sharing.tsx b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/sharing.tsx index b2a4d014cd072..a4e773684f98a 100644 --- a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/sharing.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/sharing.tsx @@ -6,8 +6,9 @@ import { import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; import { WorkspacePermissionService } from '@affine/core/modules/permissions'; import { WorkspaceShareSettingService } from '@affine/core/modules/share-setting'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { useI18n } from '@affine/i18n'; -import { useLiveData, useService, WorkspaceService } from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; export const SharingPanel = () => { const workspace = useService(WorkspaceService).workspace; diff --git a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/types.ts b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/types.ts index e16ef68a5d40d..6604851b7d13a 100644 --- a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/types.ts +++ b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/types.ts @@ -1,4 +1,4 @@ -import type { WorkspaceMetadata } from '@toeverything/infra'; +import type { WorkspaceMetadata } from '@affine/core/modules/workspace'; import type { SettingState } from '../../types'; diff --git a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/properties/index.tsx b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/properties/index.tsx index ae2123de61374..55a3fd360d9c2 100644 --- a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/properties/index.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/properties/index.tsx @@ -3,13 +3,11 @@ import { SettingHeader } from '@affine/component/setting-components'; import { DocPropertyManager } from '@affine/core/components/doc-properties/manager'; import { CreatePropertyMenuItems } from '@affine/core/components/doc-properties/menu/create-doc-property'; import { useWorkspaceInfo } from '@affine/core/components/hooks/use-workspace-info'; +import type { DocCustomPropertyInfo } from '@affine/core/modules/db'; +import type { WorkspaceMetadata } from '@affine/core/modules/workspace'; import { Trans, useI18n } from '@affine/i18n'; import track from '@affine/track'; -import { - type DocCustomPropertyInfo, - FrameworkScope, - type WorkspaceMetadata, -} from '@toeverything/infra'; +import { FrameworkScope } from '@toeverything/infra'; import { useCallback } from 'react'; import { useWorkspace } from '../../../../../components/hooks/use-workspace'; diff --git a/packages/frontend/core/src/desktop/pages/index/index.tsx b/packages/frontend/core/src/desktop/pages/index/index.tsx index 819323a40fc0e..187c180eb5665 100644 --- a/packages/frontend/core/src/desktop/pages/index/index.tsx +++ b/packages/frontend/core/src/desktop/pages/index/index.tsx @@ -1,4 +1,5 @@ import { DesktopApiService } from '@affine/core/modules/desktop-api'; +import { WorkspacesService } from '@affine/core/modules/workspace'; import { buildShowcaseWorkspace, createFirstAppData, @@ -7,7 +8,6 @@ import { useLiveData, useService, useServiceOptional, - WorkspacesService, } from '@toeverything/infra'; import { type ReactNode, diff --git a/packages/frontend/core/src/desktop/pages/root/custom-theme/index.tsx b/packages/frontend/core/src/desktop/pages/root/custom-theme/index.tsx index 9451ae7ff9a62..f3922872b66bd 100644 --- a/packages/frontend/core/src/desktop/pages/root/custom-theme/index.tsx +++ b/packages/frontend/core/src/desktop/pages/root/custom-theme/index.tsx @@ -1,9 +1,6 @@ +import { FeatureFlagService } from '@affine/core/modules/feature-flag'; import { ThemeEditorService } from '@affine/core/modules/theme-editor'; -import { - FeatureFlagService, - useLiveData, - useServices, -} from '@toeverything/infra'; +import { useLiveData, useServices } from '@toeverything/infra'; import { useTheme } from 'next-themes'; import { useEffect } from 'react'; diff --git a/packages/frontend/core/src/desktop/pages/root/index.tsx b/packages/frontend/core/src/desktop/pages/root/index.tsx index 9c22d23248ce4..0b7aff417d611 100644 --- a/packages/frontend/core/src/desktop/pages/root/index.tsx +++ b/packages/frontend/core/src/desktop/pages/root/index.tsx @@ -1,10 +1,7 @@ import { NotificationCenter } from '@affine/component'; import { DefaultServerService } from '@affine/core/modules/cloud'; -import { - FrameworkScope, - GlobalContextService, - useService, -} from '@toeverything/infra'; +import { GlobalContextService } from '@affine/core/modules/global-context'; +import { FrameworkScope, useService } from '@toeverything/infra'; import { useEffect, useState } from 'react'; import { Outlet } from 'react-router-dom'; diff --git a/packages/frontend/core/src/desktop/pages/upgrade-to-team/index.tsx b/packages/frontend/core/src/desktop/pages/upgrade-to-team/index.tsx index cd404980ee0cb..35c694c938bda 100644 --- a/packages/frontend/core/src/desktop/pages/upgrade-to-team/index.tsx +++ b/packages/frontend/core/src/desktop/pages/upgrade-to-team/index.tsx @@ -13,17 +13,16 @@ import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hoo import { useWorkspaceInfo } from '@affine/core/components/hooks/use-workspace-info'; import { PureWorkspaceCard } from '@affine/core/components/workspace-selector/workspace-card'; import { AuthService } from '@affine/core/modules/cloud'; +import { + type WorkspaceMetadata, + WorkspacesService, +} from '@affine/core/modules/workspace'; import { buildShowcaseWorkspace } from '@affine/core/utils/first-app-data'; import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant'; import { SubscriptionPlan, SubscriptionRecurring } from '@affine/graphql'; import { type I18nString, Trans, useI18n } from '@affine/i18n'; import { DoneIcon, NewPageIcon } from '@blocksuite/icons/rc'; -import { - useLiveData, - useService, - type WorkspaceMetadata, - WorkspacesService, -} from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import { useCallback, useMemo, useState } from 'react'; import { useSearchParams } from 'react-router-dom'; diff --git a/packages/frontend/core/src/desktop/pages/workspace/all-collection/index.tsx b/packages/frontend/core/src/desktop/pages/workspace/all-collection/index.tsx index d102027b17fcd..267e341f3e9d9 100644 --- a/packages/frontend/core/src/desktop/pages/workspace/all-collection/index.tsx +++ b/packages/frontend/core/src/desktop/pages/workspace/all-collection/index.tsx @@ -10,8 +10,9 @@ import { ViewIcon, ViewTitle, } from '@affine/core/modules/workbench/view/view-meta'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { useI18n } from '@affine/i18n'; -import { useLiveData, useService, WorkspaceService } from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import { nanoid } from 'nanoid'; import { useCallback, useMemo, useState } from 'react'; diff --git a/packages/frontend/core/src/desktop/pages/workspace/all-page/all-page-filter.tsx b/packages/frontend/core/src/desktop/pages/workspace/all-page/all-page-filter.tsx index 7975a32b1d5ed..a4c50adc19f8a 100644 --- a/packages/frontend/core/src/desktop/pages/workspace/all-page/all-page-filter.tsx +++ b/packages/frontend/core/src/desktop/pages/workspace/all-page/all-page-filter.tsx @@ -1,6 +1,7 @@ import { CollectionService } from '@affine/core/modules/collection'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import type { Collection, Filter } from '@affine/env/filter'; -import { useService, WorkspaceService } from '@toeverything/infra'; +import { useService } from '@toeverything/infra'; import { useCallback } from 'react'; import { filterContainerStyle } from '../../../../components/filter-container.css'; diff --git a/packages/frontend/core/src/desktop/pages/workspace/all-page/all-page-header.tsx b/packages/frontend/core/src/desktop/pages/workspace/all-page/all-page-header.tsx index 8896f1ec401f0..9bccaad0f1762 100644 --- a/packages/frontend/core/src/desktop/pages/workspace/all-page/all-page-header.tsx +++ b/packages/frontend/core/src/desktop/pages/workspace/all-page/all-page-header.tsx @@ -8,11 +8,12 @@ import { Header } from '@affine/core/components/pure/header'; import { WorkspaceModeFilterTab } from '@affine/core/components/pure/workspace-mode-filter-tab'; import { WorkspaceDialogService } from '@affine/core/modules/dialogs'; import { WorkbenchService } from '@affine/core/modules/workbench'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { isNewTabTrigger } from '@affine/core/utils'; import type { Filter } from '@affine/env/filter'; import { track } from '@affine/track'; import { PlusIcon } from '@blocksuite/icons/rc'; -import { useServices, WorkspaceService } from '@toeverything/infra'; +import { useServices } from '@toeverything/infra'; import clsx from 'clsx'; import { useCallback } from 'react'; diff --git a/packages/frontend/core/src/desktop/pages/workspace/all-page/all-page.tsx b/packages/frontend/core/src/desktop/pages/workspace/all-page/all-page.tsx index 0c6c032a0bc7a..db690a12617c5 100644 --- a/packages/frontend/core/src/desktop/pages/workspace/all-page/all-page.tsx +++ b/packages/frontend/core/src/desktop/pages/workspace/all-page/all-page.tsx @@ -4,13 +4,11 @@ import { useFilteredPageMetas, VirtualizedPageList, } from '@affine/core/components/page-list'; +import { GlobalContextService } from '@affine/core/modules/global-context'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import type { Filter } from '@affine/env/filter'; import { useI18n } from '@affine/i18n'; -import { - GlobalContextService, - useService, - WorkspaceService, -} from '@toeverything/infra'; +import { useService } from '@toeverything/infra'; import { useEffect, useState } from 'react'; import { diff --git a/packages/frontend/core/src/desktop/pages/workspace/attachment/index.tsx b/packages/frontend/core/src/desktop/pages/workspace/attachment/index.tsx index 5f042fe7a3809..da04ac40ae86c 100644 --- a/packages/frontend/core/src/desktop/pages/workspace/attachment/index.tsx +++ b/packages/frontend/core/src/desktop/pages/workspace/attachment/index.tsx @@ -1,12 +1,7 @@ import { Skeleton } from '@affine/component'; +import { type Doc, DocsService } from '@affine/core/modules/doc'; import { type AttachmentBlockModel } from '@blocksuite/affine/blocks'; -import { - type Doc, - DocsService, - FrameworkScope, - useLiveData, - useService, -} from '@toeverything/infra'; +import { FrameworkScope, useLiveData, useService } from '@toeverything/infra'; import { type ReactElement, useLayoutEffect, useState } from 'react'; import { useParams } from 'react-router-dom'; diff --git a/packages/frontend/core/src/desktop/pages/workspace/collection/index.tsx b/packages/frontend/core/src/desktop/pages/workspace/collection/index.tsx index 61cf826f63127..11eb3b852d205 100644 --- a/packages/frontend/core/src/desktop/pages/workspace/collection/index.tsx +++ b/packages/frontend/core/src/desktop/pages/workspace/collection/index.tsx @@ -3,16 +3,12 @@ import { EmptyCollectionDetail } from '@affine/core/components/affine/empty/coll import { VirtualizedPageList } from '@affine/core/components/page-list'; import { CollectionService } from '@affine/core/modules/collection'; import { WorkspaceDialogService } from '@affine/core/modules/dialogs'; +import { GlobalContextService } from '@affine/core/modules/global-context'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import type { Collection } from '@affine/env/filter'; import { useI18n } from '@affine/i18n'; import { ViewLayersIcon } from '@blocksuite/icons/rc'; -import { - GlobalContextService, - useLiveData, - useService, - useServices, - WorkspaceService, -} from '@toeverything/infra'; +import { useLiveData, useService, useServices } from '@toeverything/infra'; import { useCallback, useEffect, useState } from 'react'; import { useParams } from 'react-router-dom'; diff --git a/packages/frontend/core/src/desktop/pages/workspace/detail-page/detail-page-header.tsx b/packages/frontend/core/src/desktop/pages/workspace/detail-page/detail-page-header.tsx index bbe446b366c24..233f04eb50c35 100644 --- a/packages/frontend/core/src/desktop/pages/workspace/detail-page/detail-page-header.tsx +++ b/packages/frontend/core/src/desktop/pages/workspace/detail-page/detail-page-header.tsx @@ -20,11 +20,12 @@ import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta'; import { EditorService } from '@affine/core/modules/editor'; import { JournalService } from '@affine/core/modules/journal'; import { ViewIcon, ViewTitle } from '@affine/core/modules/workbench'; +import type { Workspace } from '@affine/core/modules/workspace'; import type { AffineDNDData } from '@affine/core/types/dnd'; import { useI18n } from '@affine/i18n'; import { track } from '@affine/track'; import type { Doc } from '@blocksuite/affine/store'; -import { useLiveData, useService, type Workspace } from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import { forwardRef, useCallback, useEffect, useRef, useState } from 'react'; import * as styles from './detail-page-header.css'; diff --git a/packages/frontend/core/src/desktop/pages/workspace/detail-page/detail-page-wrapper.tsx b/packages/frontend/core/src/desktop/pages/workspace/detail-page/detail-page-wrapper.tsx index 640b4db16bd27..2468461c0122a 100644 --- a/packages/frontend/core/src/desktop/pages/workspace/detail-page/detail-page-wrapper.tsx +++ b/packages/frontend/core/src/desktop/pages/workspace/detail-page/detail-page-wrapper.tsx @@ -1,14 +1,9 @@ +import { type Doc, DocsService } from '@affine/core/modules/doc'; import type { Editor } from '@affine/core/modules/editor'; import { EditorsService } from '@affine/core/modules/editor'; import { ViewService } from '@affine/core/modules/workbench/services/view'; -import type { Doc } from '@toeverything/infra'; -import { - DocsService, - FrameworkScope, - useLiveData, - useService, - WorkspaceService, -} from '@toeverything/infra'; +import { WorkspaceService } from '@affine/core/modules/workspace'; +import { FrameworkScope, useLiveData, useService } from '@toeverything/infra'; import { type PropsWithChildren, type ReactNode, diff --git a/packages/frontend/core/src/desktop/pages/workspace/detail-page/detail-page.tsx b/packages/frontend/core/src/desktop/pages/workspace/detail-page/detail-page.tsx index 3b2dd82e6366a..716ee695a5b81 100644 --- a/packages/frontend/core/src/desktop/pages/workspace/detail-page/detail-page.tsx +++ b/packages/frontend/core/src/desktop/pages/workspace/detail-page/detail-page.tsx @@ -7,9 +7,13 @@ import { EditorOutlineViewer } from '@affine/core/components/blocksuite/outline- import { DocPropertySidebar } from '@affine/core/components/doc-properties/sidebar'; import { useAppSettingHelper } from '@affine/core/components/hooks/affine/use-app-setting-helper'; import { useDocMetaHelper } from '@affine/core/components/hooks/use-block-suite-page-meta'; +import { DocService } from '@affine/core/modules/doc'; import { EditorService } from '@affine/core/modules/editor'; +import { FeatureFlagService } from '@affine/core/modules/feature-flag'; +import { GlobalContextService } from '@affine/core/modules/global-context'; import { RecentDocsService } from '@affine/core/modules/quicksearch'; -import { ViewService } from '@affine/core/modules/workbench/services/view'; +import { ViewService } from '@affine/core/modules/workbench'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { RefNodeSlotsProvider } from '@blocksuite/affine/blocks'; import { DisposableGroup } from '@blocksuite/affine/global/utils'; import { type AffineEditorContainer } from '@blocksuite/affine/presets'; @@ -21,14 +25,10 @@ import { TodayIcon, } from '@blocksuite/icons/rc'; import { - DocService, - FeatureFlagService, FrameworkScope, - GlobalContextService, useLiveData, useService, useServices, - WorkspaceService, } from '@toeverything/infra'; import clsx from 'clsx'; import { memo, useCallback, useEffect, useRef, useState } from 'react'; diff --git a/packages/frontend/core/src/desktop/pages/workspace/detail-page/tabs/journal.tsx b/packages/frontend/core/src/desktop/pages/workspace/detail-page/tabs/journal.tsx index b21460eaac6b2..a278efca06a95 100644 --- a/packages/frontend/core/src/desktop/pages/workspace/detail-page/tabs/journal.tsx +++ b/packages/frontend/core/src/desktop/pages/workspace/detail-page/tabs/journal.tsx @@ -10,18 +10,17 @@ import { } from '@affine/component'; import { useJournalRouteHelper } from '@affine/core/components/hooks/use-journal'; import { MoveToTrash } from '@affine/core/components/page-list'; +import { + type DocRecord, + DocService, + DocsService, +} from '@affine/core/modules/doc'; import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta'; import { JournalService } from '@affine/core/modules/journal'; import { WorkbenchLink } from '@affine/core/modules/workbench'; import { useI18n } from '@affine/i18n'; import { CalendarXmarkIcon, EditIcon } from '@blocksuite/icons/rc'; -import type { DocRecord } from '@toeverything/infra'; -import { - DocService, - DocsService, - useLiveData, - useService, -} from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import { assignInlineVars } from '@vanilla-extract/dynamic'; import clsx from 'clsx'; import dayjs from 'dayjs'; diff --git a/packages/frontend/core/src/desktop/pages/workspace/index.tsx b/packages/frontend/core/src/desktop/pages/workspace/index.tsx index 4d3d46c222203..49f3b98f0d4c8 100644 --- a/packages/frontend/core/src/desktop/pages/workspace/index.tsx +++ b/packages/frontend/core/src/desktop/pages/workspace/index.tsx @@ -6,15 +6,18 @@ import { WorkspaceServerService, } from '@affine/core/modules/cloud'; import { DndService } from '@affine/core/modules/dnd/services'; +import { GlobalContextService } from '@affine/core/modules/global-context'; +import { + type Workspace, + type WorkspaceMetadata, + WorkspacesService, +} from '@affine/core/modules/workspace'; import { ZipTransformer } from '@blocksuite/affine/blocks'; -import type { Workspace, WorkspaceMetadata } from '@toeverything/infra'; import { FrameworkScope, - GlobalContextService, useLiveData, useService, useServices, - WorkspacesService, } from '@toeverything/infra'; import type { PropsWithChildren, ReactElement } from 'react'; import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; @@ -31,11 +34,11 @@ declare global { /** * @internal debug only */ - // eslint-disable-next-line no-var + // oxlint-disable-next-line no-var var currentWorkspace: Workspace | undefined; - // eslint-disable-next-line no-var + // oxlint-disable-next-line no-var var exportWorkspaceSnapshot: (docs?: string[]) => Promise; - // eslint-disable-next-line no-var + // oxlint-disable-next-line no-var var importWorkspaceSnapshot: () => Promise; interface WindowEventMap { 'affine:workspace:change': CustomEvent<{ id: string }>; diff --git a/packages/frontend/core/src/desktop/pages/workspace/layouts/workspace-layout.tsx b/packages/frontend/core/src/desktop/pages/workspace/layouts/workspace-layout.tsx index 38b5ee51b5a2e..f287d6671fbb4 100644 --- a/packages/frontend/core/src/desktop/pages/workspace/layouts/workspace-layout.tsx +++ b/packages/frontend/core/src/desktop/pages/workspace/layouts/workspace-layout.tsx @@ -12,12 +12,8 @@ import { WorkspaceDialogs } from '@affine/core/desktop/dialogs'; import { PeekViewManagerModal } from '@affine/core/modules/peek-view'; import { QuotaCheck } from '@affine/core/modules/quota'; import { WorkbenchService } from '@affine/core/modules/workbench'; -import { - LiveData, - useLiveData, - useService, - WorkspaceService, -} from '@toeverything/infra'; +import { WorkspaceService } from '@affine/core/modules/workspace'; +import { LiveData, useLiveData, useService } from '@toeverything/infra'; import type { PropsWithChildren } from 'react'; export const WorkspaceLayout = function WorkspaceLayout({ diff --git a/packages/frontend/core/src/desktop/pages/workspace/share/share-page.tsx b/packages/frontend/core/src/desktop/pages/workspace/share/share-page.tsx index 8d4219960a702..c53adc0873b68 100644 --- a/packages/frontend/core/src/desktop/pages/workspace/share/share-page.tsx +++ b/packages/frontend/core/src/desktop/pages/workspace/share/share-page.tsx @@ -10,6 +10,7 @@ import { FetchService, GraphQLService, } from '@affine/core/modules/cloud'; +import { type Doc, DocsService } from '@affine/core/modules/doc'; import { type Editor, type EditorSelector, @@ -19,6 +20,10 @@ import { import { PeekViewManagerModal } from '@affine/core/modules/peek-view'; import { ShareReaderService } from '@affine/core/modules/share-doc'; import { ViewIcon, ViewTitle } from '@affine/core/modules/workbench'; +import { + type Workspace, + WorkspacesService, +} from '@affine/core/modules/workspace'; import { CloudBlobStorage } from '@affine/core/modules/workspace-engine'; import { useI18n } from '@affine/i18n'; import { @@ -29,16 +34,13 @@ import { import type { AffineEditorContainer } from '@blocksuite/affine/presets'; import { DisposableGroup } from '@blocksuite/global/utils'; import { Logo1Icon } from '@blocksuite/icons/rc'; -import type { Doc, Workspace } from '@toeverything/infra'; import { - DocsService, EmptyBlobStorage, FrameworkScope, ReadonlyDocStorage, useLiveData, useService, useServices, - WorkspacesService, } from '@toeverything/infra'; import clsx from 'clsx'; import { diff --git a/packages/frontend/core/src/desktop/pages/workspace/tag/index.tsx b/packages/frontend/core/src/desktop/pages/workspace/tag/index.tsx index 5736e69661a7d..cf7d355d65fd0 100644 --- a/packages/frontend/core/src/desktop/pages/workspace/tag/index.tsx +++ b/packages/frontend/core/src/desktop/pages/workspace/tag/index.tsx @@ -3,6 +3,7 @@ import { TagPageListHeader, VirtualizedPageList, } from '@affine/core/components/page-list'; +import { GlobalContextService } from '@affine/core/modules/global-context'; import { TagService } from '@affine/core/modules/tag'; import { useIsActiveView, @@ -11,12 +12,8 @@ import { ViewIcon, ViewTitle, } from '@affine/core/modules/workbench'; -import { - GlobalContextService, - useLiveData, - useService, - WorkspaceService, -} from '@toeverything/infra'; +import { WorkspaceService } from '@affine/core/modules/workspace'; +import { useLiveData, useService } from '@toeverything/infra'; import { useEffect, useMemo } from 'react'; import { useParams } from 'react-router-dom'; diff --git a/packages/frontend/core/src/desktop/pages/workspace/trash-page.tsx b/packages/frontend/core/src/desktop/pages/workspace/trash-page.tsx index ee39e6c0c61d6..8886a11a88617 100644 --- a/packages/frontend/core/src/desktop/pages/workspace/trash-page.tsx +++ b/packages/frontend/core/src/desktop/pages/workspace/trash-page.tsx @@ -4,14 +4,12 @@ import { VirtualizedTrashList, } from '@affine/core/components/page-list'; import { Header } from '@affine/core/components/pure/header'; +import { GlobalContextService } from '@affine/core/modules/global-context'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { useI18n } from '@affine/i18n'; import { assertExists } from '@blocksuite/affine/global/utils'; import { DeleteIcon } from '@blocksuite/icons/rc'; -import { - GlobalContextService, - useService, - WorkspaceService, -} from '@toeverything/infra'; +import { useService } from '@toeverything/infra'; import { useEffect } from 'react'; import { diff --git a/packages/frontend/core/src/mobile/components/app-tabs/create.tsx b/packages/frontend/core/src/mobile/components/app-tabs/create.tsx index 94681eb6c0d0b..1c638b6202992 100644 --- a/packages/frontend/core/src/mobile/components/app-tabs/create.tsx +++ b/packages/frontend/core/src/mobile/components/app-tabs/create.tsx @@ -1,8 +1,9 @@ import { usePageHelper } from '@affine/core/components/blocksuite/block-suite-page-list/utils'; import { WorkbenchService } from '@affine/core/modules/workbench'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import track from '@affine/track'; import { EditIcon } from '@blocksuite/icons/rc'; -import { useService, WorkspaceService } from '@toeverything/infra'; +import { useService } from '@toeverything/infra'; import { useCallback } from 'react'; import type { AppTabCustomFCProps } from './data'; diff --git a/packages/frontend/core/src/mobile/components/app-tabs/tab-item.tsx b/packages/frontend/core/src/mobile/components/app-tabs/tab-item.tsx index f5194ddd2a848..c4297062bc1c3 100644 --- a/packages/frontend/core/src/mobile/components/app-tabs/tab-item.tsx +++ b/packages/frontend/core/src/mobile/components/app-tabs/tab-item.tsx @@ -1,9 +1,5 @@ -import { - GlobalCacheService, - LiveData, - useLiveData, - useService, -} from '@toeverything/infra'; +import { GlobalCacheService } from '@affine/core/modules/storage'; +import { LiveData, useLiveData, useService } from '@toeverything/infra'; import { type PropsWithChildren, useCallback, useMemo } from 'react'; import { tabItem } from './styles.css'; diff --git a/packages/frontend/core/src/mobile/components/doc-info/doc-info.tsx b/packages/frontend/core/src/mobile/components/doc-info/doc-info.tsx index a959a7ce9464c..a86d00afd163a 100644 --- a/packages/frontend/core/src/mobile/components/doc-info/doc-info.tsx +++ b/packages/frontend/core/src/mobile/components/doc-info/doc-info.tsx @@ -13,17 +13,13 @@ import { import { CreatePropertyMenuItems } from '@affine/core/components/doc-properties/menu/create-doc-property'; import { LinksRow } from '@affine/core/desktop/dialogs/doc-info/links-row'; import { TimeRow } from '@affine/core/desktop/dialogs/doc-info/time-row'; +import type { DocCustomPropertyInfo } from '@affine/core/modules/db'; +import { DocsService } from '@affine/core/modules/doc'; import { DocDatabaseBacklinkInfo } from '@affine/core/modules/doc-info'; import { DocsSearchService } from '@affine/core/modules/docs-search'; import { useI18n } from '@affine/i18n'; import { PlusIcon } from '@blocksuite/icons/rc'; -import { - type DocCustomPropertyInfo, - DocsService, - LiveData, - useLiveData, - useServices, -} from '@toeverything/infra'; +import { LiveData, useLiveData, useServices } from '@toeverything/infra'; import { Suspense, useCallback, useMemo, useState } from 'react'; import * as styles from './doc-info.css'; diff --git a/packages/frontend/core/src/mobile/components/doc-info/doc-scope.tsx b/packages/frontend/core/src/mobile/components/doc-info/doc-scope.tsx index 6643551ae0619..8a735c9dff7d8 100644 --- a/packages/frontend/core/src/mobile/components/doc-info/doc-scope.tsx +++ b/packages/frontend/core/src/mobile/components/doc-info/doc-scope.tsx @@ -1,5 +1,5 @@ -import type { Doc } from '@toeverything/infra'; -import { DocsService, FrameworkScope, useService } from '@toeverything/infra'; +import { type Doc, DocsService } from '@affine/core/modules/doc'; +import { FrameworkScope, useService } from '@toeverything/infra'; import { type PropsWithChildren, useEffect, useState } from 'react'; export const DocFrameScope = ({ diff --git a/packages/frontend/core/src/mobile/components/explorer/nodes/collection/index.tsx b/packages/frontend/core/src/mobile/components/explorer/nodes/collection/index.tsx index 2426edbd69621..645b9d0dc2dc0 100644 --- a/packages/frontend/core/src/mobile/components/explorer/nodes/collection/index.tsx +++ b/packages/frontend/core/src/mobile/components/explorer/nodes/collection/index.tsx @@ -2,8 +2,10 @@ import { MenuItem, notify } from '@affine/component'; import { filterPage } from '@affine/core/components/page-list'; import { CollectionService } from '@affine/core/modules/collection'; import { WorkspaceDialogService } from '@affine/core/modules/dialogs'; +import { DocsService } from '@affine/core/modules/doc'; import type { NodeOperation } from '@affine/core/modules/explorer'; import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/favorite'; +import { GlobalContextService } from '@affine/core/modules/global-context'; import { ShareDocsListService } from '@affine/core/modules/share-doc'; import type { Collection } from '@affine/env/filter'; import { PublicPageMode } from '@affine/graphql'; @@ -11,13 +13,7 @@ import { useI18n } from '@affine/i18n'; import track from '@affine/track'; import type { DocMeta } from '@blocksuite/affine/store'; import { FilterMinusIcon, ViewLayersIcon } from '@blocksuite/icons/rc'; -import { - DocsService, - GlobalContextService, - LiveData, - useLiveData, - useServices, -} from '@toeverything/infra'; +import { LiveData, useLiveData, useServices } from '@toeverything/infra'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { AddItemPlaceholder } from '../../layouts/add-item-placeholder'; diff --git a/packages/frontend/core/src/mobile/components/explorer/nodes/collection/operations.tsx b/packages/frontend/core/src/mobile/components/explorer/nodes/collection/operations.tsx index 1d803551090fe..468b0475b8d1f 100644 --- a/packages/frontend/core/src/mobile/components/explorer/nodes/collection/operations.tsx +++ b/packages/frontend/core/src/mobile/components/explorer/nodes/collection/operations.tsx @@ -11,7 +11,9 @@ import { IsFavoriteIcon } from '@affine/core/components/pure/icons'; import { CollectionService } from '@affine/core/modules/collection'; import type { NodeOperation } from '@affine/core/modules/explorer'; import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/favorite'; +import { FeatureFlagService } from '@affine/core/modules/feature-flag'; import { WorkbenchService } from '@affine/core/modules/workbench'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { useI18n } from '@affine/i18n'; import { track } from '@affine/track'; import { @@ -21,12 +23,7 @@ import { PlusIcon, SplitViewIcon, } from '@blocksuite/icons/rc'; -import { - FeatureFlagService, - useLiveData, - useServices, - WorkspaceService, -} from '@toeverything/infra'; +import { useLiveData, useServices } from '@toeverything/infra'; import { useCallback, useMemo } from 'react'; import { CollectionRenameSubMenu } from './dialog'; diff --git a/packages/frontend/core/src/mobile/components/explorer/nodes/doc/index.tsx b/packages/frontend/core/src/mobile/components/explorer/nodes/doc/index.tsx index e3a86ba0bed9d..0a957c83101f8 100644 --- a/packages/frontend/core/src/mobile/components/explorer/nodes/doc/index.tsx +++ b/packages/frontend/core/src/mobile/components/explorer/nodes/doc/index.tsx @@ -1,13 +1,13 @@ import { Loading } from '@affine/component'; import { WorkspaceDialogService } from '@affine/core/modules/dialogs'; +import { DocsService } from '@affine/core/modules/doc'; import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta'; import { DocsSearchService } from '@affine/core/modules/docs-search'; import type { NodeOperation } from '@affine/core/modules/explorer'; +import { FeatureFlagService } from '@affine/core/modules/feature-flag'; +import { GlobalContextService } from '@affine/core/modules/global-context'; import { useI18n } from '@affine/i18n'; import { - DocsService, - FeatureFlagService, - GlobalContextService, LiveData, useLiveData, useService, diff --git a/packages/frontend/core/src/mobile/components/explorer/nodes/doc/operations.tsx b/packages/frontend/core/src/mobile/components/explorer/nodes/doc/operations.tsx index a2fcf793a0e66..3f8f38150ea92 100644 --- a/packages/frontend/core/src/mobile/components/explorer/nodes/doc/operations.tsx +++ b/packages/frontend/core/src/mobile/components/explorer/nodes/doc/operations.tsx @@ -10,9 +10,12 @@ import { usePageHelper } from '@affine/core/components/blocksuite/block-suite-pa import { useBlockSuiteMetaHelper } from '@affine/core/components/hooks/affine/use-block-suite-meta-helper'; import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; import { IsFavoriteIcon } from '@affine/core/components/pure/icons'; +import { DocsService } from '@affine/core/modules/doc'; import type { NodeOperation } from '@affine/core/modules/explorer'; import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/favorite'; +import { FeatureFlagService } from '@affine/core/modules/feature-flag'; import { WorkbenchService } from '@affine/core/modules/workbench'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { preventDefault } from '@affine/core/utils'; import { useI18n } from '@affine/i18n'; import { track } from '@affine/track'; @@ -25,14 +28,7 @@ import { PlusIcon, SplitViewIcon, } from '@blocksuite/icons/rc'; -import { - DocsService, - FeatureFlagService, - useLiveData, - useService, - useServices, - WorkspaceService, -} from '@toeverything/infra'; +import { useLiveData, useService, useServices } from '@toeverything/infra'; import { useCallback, useMemo } from 'react'; import { DocFrameScope, DocInfoSheet } from '../../../doc-info'; diff --git a/packages/frontend/core/src/mobile/components/explorer/nodes/folder/index.tsx b/packages/frontend/core/src/mobile/components/explorer/nodes/folder/index.tsx index bf0e0bc74cb83..2e8a6b1aceb35 100644 --- a/packages/frontend/core/src/mobile/components/explorer/nodes/folder/index.tsx +++ b/packages/frontend/core/src/mobile/components/explorer/nodes/folder/index.tsx @@ -13,10 +13,12 @@ import type { NodeOperation, } from '@affine/core/modules/explorer'; import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/favorite'; +import { FeatureFlagService } from '@affine/core/modules/feature-flag'; import { type FolderNode, OrganizeService, } from '@affine/core/modules/organize'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { useI18n } from '@affine/i18n'; import track from '@affine/track'; import { @@ -29,12 +31,7 @@ import { RemoveFolderIcon, TagsIcon, } from '@blocksuite/icons/rc'; -import { - FeatureFlagService, - useLiveData, - useServices, - WorkspaceService, -} from '@toeverything/infra'; +import { useLiveData, useServices } from '@toeverything/infra'; import { difference } from 'lodash-es'; import { useCallback, useMemo, useState } from 'react'; diff --git a/packages/frontend/core/src/mobile/components/explorer/nodes/tag/index.tsx b/packages/frontend/core/src/mobile/components/explorer/nodes/tag/index.tsx index 271ab27625535..404b0dae0e10d 100644 --- a/packages/frontend/core/src/mobile/components/explorer/nodes/tag/index.tsx +++ b/packages/frontend/core/src/mobile/components/explorer/nodes/tag/index.tsx @@ -1,12 +1,9 @@ import type { NodeOperation } from '@affine/core/modules/explorer'; +import { GlobalContextService } from '@affine/core/modules/global-context'; import type { Tag } from '@affine/core/modules/tag'; import { TagService } from '@affine/core/modules/tag'; import { useI18n } from '@affine/i18n'; -import { - GlobalContextService, - useLiveData, - useServices, -} from '@toeverything/infra'; +import { useLiveData, useServices } from '@toeverything/infra'; import clsx from 'clsx'; import { useCallback, useMemo, useState } from 'react'; diff --git a/packages/frontend/core/src/mobile/components/explorer/nodes/tag/operations.tsx b/packages/frontend/core/src/mobile/components/explorer/nodes/tag/operations.tsx index e98793836e248..cc53c9d5353a6 100644 --- a/packages/frontend/core/src/mobile/components/explorer/nodes/tag/operations.tsx +++ b/packages/frontend/core/src/mobile/components/explorer/nodes/tag/operations.tsx @@ -8,10 +8,14 @@ import { import { usePageHelper } from '@affine/core/components/blocksuite/block-suite-page-list/utils'; import { IsFavoriteIcon } from '@affine/core/components/pure/icons'; import { WorkspaceDialogService } from '@affine/core/modules/dialogs'; +import { DocsService } from '@affine/core/modules/doc'; import type { NodeOperation } from '@affine/core/modules/explorer'; import { FavoriteService } from '@affine/core/modules/favorite'; +import { FeatureFlagService } from '@affine/core/modules/feature-flag'; +import { GlobalCacheService } from '@affine/core/modules/storage'; import { TagService } from '@affine/core/modules/tag'; import { WorkbenchService } from '@affine/core/modules/workbench'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { useI18n } from '@affine/i18n'; import { track } from '@affine/track'; import { @@ -20,15 +24,7 @@ import { PlusIcon, SplitViewIcon, } from '@blocksuite/icons/rc'; -import { - DocsService, - FeatureFlagService, - GlobalCacheService, - useLiveData, - useService, - useServices, - WorkspaceService, -} from '@toeverything/infra'; +import { useLiveData, useService, useServices } from '@toeverything/infra'; import { useCallback, useMemo } from 'react'; import { TagRenameSubMenu } from './dialog'; diff --git a/packages/frontend/core/src/mobile/components/explorer/sections/favorites/index.tsx b/packages/frontend/core/src/mobile/components/explorer/sections/favorites/index.tsx index 4343501ca3f90..1c0aebb47bd80 100644 --- a/packages/frontend/core/src/mobile/components/explorer/sections/favorites/index.tsx +++ b/packages/frontend/core/src/mobile/components/explorer/sections/favorites/index.tsx @@ -5,12 +5,9 @@ import { } from '@affine/core/modules/explorer'; import type { FavoriteSupportTypeUnion } from '@affine/core/modules/favorite'; import { FavoriteService } from '@affine/core/modules/favorite'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { useI18n } from '@affine/i18n'; -import { - useLiveData, - useServices, - WorkspaceService, -} from '@toeverything/infra'; +import { useLiveData, useServices } from '@toeverything/infra'; import { useCallback } from 'react'; import { AddItemPlaceholder } from '../../layouts/add-item-placeholder'; diff --git a/packages/frontend/core/src/mobile/components/workspace-selector/current-card.tsx b/packages/frontend/core/src/mobile/components/workspace-selector/current-card.tsx index 515af7de59276..5a153219fabe5 100644 --- a/packages/frontend/core/src/mobile/components/workspace-selector/current-card.tsx +++ b/packages/frontend/core/src/mobile/components/workspace-selector/current-card.tsx @@ -1,9 +1,10 @@ import { Avatar } from '@affine/component'; -import { WorkspaceAvatar } from '@affine/component/workspace-avatar'; import { useWorkspaceInfo } from '@affine/core/components/hooks/use-workspace-info'; +import { WorkspaceAvatar } from '@affine/core/components/workspace-avatar'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant'; import { ArrowDownSmallIcon } from '@blocksuite/icons/rc'; -import { useServiceOptional, WorkspaceService } from '@toeverything/infra'; +import { useServiceOptional } from '@toeverything/infra'; import clsx from 'clsx'; import { forwardRef, type HTMLAttributes } from 'react'; diff --git a/packages/frontend/core/src/mobile/components/workspace-selector/index.tsx b/packages/frontend/core/src/mobile/components/workspace-selector/index.tsx index 6b6213c6eae6f..8a52ae63c11a7 100644 --- a/packages/frontend/core/src/mobile/components/workspace-selector/index.tsx +++ b/packages/frontend/core/src/mobile/components/workspace-selector/index.tsx @@ -1,6 +1,7 @@ import { MobileMenu } from '@affine/component'; +import { WorkspacesService } from '@affine/core/modules/workspace'; import { track } from '@affine/track'; -import { useServiceOptional, WorkspacesService } from '@toeverything/infra'; +import { useServiceOptional } from '@toeverything/infra'; import { forwardRef, type HTMLAttributes, diff --git a/packages/frontend/core/src/mobile/components/workspace-selector/menu.tsx b/packages/frontend/core/src/mobile/components/workspace-selector/menu.tsx index 5d21ac7caed32..966fd390c3c2e 100644 --- a/packages/frontend/core/src/mobile/components/workspace-selector/menu.tsx +++ b/packages/frontend/core/src/mobile/components/workspace-selector/menu.tsx @@ -1,15 +1,14 @@ import { IconButton } from '@affine/component'; -import { WorkspaceAvatar } from '@affine/component/workspace-avatar'; import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-helper'; import { useWorkspaceInfo } from '@affine/core/components/hooks/use-workspace-info'; -import { CloseIcon, CollaborationIcon } from '@blocksuite/icons/rc'; +import { WorkspaceAvatar } from '@affine/core/components/workspace-avatar'; import { - useLiveData, - useService, type WorkspaceMetadata, WorkspaceService, WorkspacesService, -} from '@toeverything/infra'; +} from '@affine/core/modules/workspace'; +import { CloseIcon, CollaborationIcon } from '@blocksuite/icons/rc'; +import { useLiveData, useService } from '@toeverything/infra'; import clsx from 'clsx'; import { type HTMLAttributes, useCallback, useMemo } from 'react'; diff --git a/packages/frontend/core/src/mobile/dialogs/selectors/doc-selector.tsx b/packages/frontend/core/src/mobile/dialogs/selectors/doc-selector.tsx index 652707b584b40..66996fd9754f8 100644 --- a/packages/frontend/core/src/mobile/dialogs/selectors/doc-selector.tsx +++ b/packages/frontend/core/src/mobile/dialogs/selectors/doc-selector.tsx @@ -3,9 +3,10 @@ import type { DialogComponentProps, WORKSPACE_DIALOG_SCHEMA, } from '@affine/core/modules/dialogs'; +import { DocsService } from '@affine/core/modules/doc'; import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta'; import { useI18n } from '@affine/i18n'; -import { DocsService, useLiveData, useService } from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import { cssVarV2 } from '@toeverything/theme/v2'; import { useMemo } from 'react'; diff --git a/packages/frontend/core/src/mobile/dialogs/setting/experimental/index.tsx b/packages/frontend/core/src/mobile/dialogs/setting/experimental/index.tsx index be57480425884..19c3d3a5c44fe 100644 --- a/packages/frontend/core/src/mobile/dialogs/setting/experimental/index.tsx +++ b/packages/frontend/core/src/mobile/dialogs/setting/experimental/index.tsx @@ -1,13 +1,12 @@ import { Switch } from '@affine/component'; -import { useI18n } from '@affine/i18n'; -import { ArrowRightSmallIcon } from '@blocksuite/icons/rc'; import { AFFINE_FLAGS, FeatureFlagService, type Flag, - useLiveData, - useService, -} from '@toeverything/infra'; +} from '@affine/core/modules/feature-flag'; +import { useI18n } from '@affine/i18n'; +import { ArrowRightSmallIcon } from '@blocksuite/icons/rc'; +import { useLiveData, useService } from '@toeverything/infra'; import { useCallback, useState } from 'react'; import { SettingGroup } from '../group'; diff --git a/packages/frontend/core/src/mobile/modules/search/index.ts b/packages/frontend/core/src/mobile/modules/search/index.ts index b4984f20b8dd3..5c6e1ddebabbe 100644 --- a/packages/frontend/core/src/mobile/modules/search/index.ts +++ b/packages/frontend/core/src/mobile/modules/search/index.ts @@ -1,4 +1,5 @@ -import { type Framework, WorkspaceScope } from '@toeverything/infra'; +import { WorkspaceScope } from '@affine/core/modules/workspace'; +import { type Framework } from '@toeverything/infra'; import { MobileSearchService } from './service/search'; diff --git a/packages/frontend/core/src/mobile/pages/workspace/collection/detail.tsx b/packages/frontend/core/src/mobile/pages/workspace/collection/detail.tsx index 814954ee3ca59..1259f94c944a5 100644 --- a/packages/frontend/core/src/mobile/pages/workspace/collection/detail.tsx +++ b/packages/frontend/core/src/mobile/pages/workspace/collection/detail.tsx @@ -1,12 +1,9 @@ import { notify, useThemeColorV2 } from '@affine/component'; import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-helper'; import { CollectionService } from '@affine/core/modules/collection'; -import { - GlobalContextService, - useLiveData, - useServices, - WorkspaceService, -} from '@toeverything/infra'; +import { GlobalContextService } from '@affine/core/modules/global-context'; +import { WorkspaceService } from '@affine/core/modules/workspace'; +import { useLiveData, useServices } from '@toeverything/infra'; import { useCallback, useEffect } from 'react'; import { useParams } from 'react-router-dom'; diff --git a/packages/frontend/core/src/mobile/pages/workspace/detail/journal-conflict-block.tsx b/packages/frontend/core/src/mobile/pages/workspace/detail/journal-conflict-block.tsx index ec007e73421ab..bac0632bc9b50 100644 --- a/packages/frontend/core/src/mobile/pages/workspace/detail/journal-conflict-block.tsx +++ b/packages/frontend/core/src/mobile/pages/workspace/detail/journal-conflict-block.tsx @@ -1,11 +1,11 @@ import { IconButton, Menu } from '@affine/component'; +import { type DocRecord, DocsService } from '@affine/core/modules/doc'; import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta'; import { JournalService } from '@affine/core/modules/journal'; import { WorkbenchLink } from '@affine/core/modules/workbench'; import { useI18n } from '@affine/i18n'; import { EditIcon, TodayIcon } from '@blocksuite/icons/rc'; -import type { DocRecord } from '@toeverything/infra'; -import { DocsService, useLiveData, useService } from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import { useMemo } from 'react'; import * as styles from './journal-conflict-block.css'; diff --git a/packages/frontend/core/src/mobile/pages/workspace/detail/menu/journal-conflicts.tsx b/packages/frontend/core/src/mobile/pages/workspace/detail/menu/journal-conflicts.tsx index 53ee5379c1187..ec4d8b134aac8 100644 --- a/packages/frontend/core/src/mobile/pages/workspace/detail/menu/journal-conflicts.tsx +++ b/packages/frontend/core/src/mobile/pages/workspace/detail/menu/journal-conflicts.tsx @@ -6,18 +6,17 @@ import { useConfirmModal, } from '@affine/component'; import { MoveToTrash } from '@affine/core/components/page-list'; +import { + type DocRecord, + DocService, + DocsService, +} from '@affine/core/modules/doc'; import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta'; import { JournalService } from '@affine/core/modules/journal'; import { WorkbenchLink } from '@affine/core/modules/workbench'; import { useI18n } from '@affine/i18n'; import { CalendarXmarkIcon, EditIcon, TodayIcon } from '@blocksuite/icons/rc'; -import type { DocRecord } from '@toeverything/infra'; -import { - DocService, - DocsService, - useLiveData, - useService, -} from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import { type MouseEvent, useCallback, useMemo } from 'react'; import * as styles from './journal-conflicts.css'; diff --git a/packages/frontend/core/src/mobile/pages/workspace/detail/menu/journal-today-activity.tsx b/packages/frontend/core/src/mobile/pages/workspace/detail/menu/journal-today-activity.tsx index a4942a144b6e4..7ffad30918b60 100644 --- a/packages/frontend/core/src/mobile/pages/workspace/detail/menu/journal-today-activity.tsx +++ b/packages/frontend/core/src/mobile/pages/workspace/detail/menu/journal-today-activity.tsx @@ -1,17 +1,16 @@ import { MenuItem, MenuSeparator, MobileMenuSub } from '@affine/component'; import { sortPagesByDate } from '@affine/core/desktop/pages/workspace/detail-page/tabs/journal'; +import { + type DocRecord, + DocService, + DocsService, +} from '@affine/core/modules/doc'; import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta'; import { JournalService } from '@affine/core/modules/journal'; import { WorkbenchLink } from '@affine/core/modules/workbench'; import { useI18n } from '@affine/i18n'; import { HistoryIcon } from '@blocksuite/icons/rc'; -import type { DocRecord } from '@toeverything/infra'; -import { - DocService, - DocsService, - useLiveData, - useService, -} from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import dayjs from 'dayjs'; import { type ReactNode, useCallback, useMemo } from 'react'; diff --git a/packages/frontend/core/src/mobile/pages/workspace/detail/mobile-detail-page.tsx b/packages/frontend/core/src/mobile/pages/workspace/detail/mobile-detail-page.tsx index 055e5aace7c6f..342233b3b87c6 100644 --- a/packages/frontend/core/src/mobile/pages/workspace/detail/mobile-detail-page.tsx +++ b/packages/frontend/core/src/mobile/pages/workspace/detail/mobile-detail-page.tsx @@ -10,11 +10,15 @@ import { PageDetailEditor } from '@affine/core/components/page-detail-editor'; import { DetailPageWrapper } from '@affine/core/desktop/pages/workspace/detail-page/detail-page-wrapper'; import { PageHeader } from '@affine/core/mobile/components'; import { useGlobalEvent } from '@affine/core/mobile/hooks/use-global-events'; +import { DocService } from '@affine/core/modules/doc'; import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta'; import { EditorService } from '@affine/core/modules/editor'; +import { FeatureFlagService } from '@affine/core/modules/feature-flag'; +import { GlobalContextService } from '@affine/core/modules/global-context'; import { JournalService } from '@affine/core/modules/journal'; import { WorkbenchService } from '@affine/core/modules/workbench'; import { ViewService } from '@affine/core/modules/workbench/services/view'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { i18nTime } from '@affine/i18n'; import { BookmarkBlockService, @@ -28,14 +32,10 @@ import { import { DisposableGroup } from '@blocksuite/affine/global/utils'; import { type AffineEditorContainer } from '@blocksuite/affine/presets'; import { - DocService, - FeatureFlagService, FrameworkScope, - GlobalContextService, useLiveData, useService, useServices, - WorkspaceService, } from '@toeverything/infra'; import { cssVarV2 } from '@toeverything/theme/v2'; import clsx from 'clsx'; diff --git a/packages/frontend/core/src/mobile/pages/workspace/detail/page-header-more-button.tsx b/packages/frontend/core/src/mobile/pages/workspace/detail/page-header-more-button.tsx index 56510085d9d00..32b538516981f 100644 --- a/packages/frontend/core/src/mobile/pages/workspace/detail/page-header-more-button.tsx +++ b/packages/frontend/core/src/mobile/pages/workspace/detail/page-header-more-button.tsx @@ -9,6 +9,7 @@ import { useFavorite } from '@affine/core/components/blocksuite/block-suite-head import { IsFavoriteIcon } from '@affine/core/components/pure/icons'; import { EditorOutlinePanel } from '@affine/core/desktop/pages/workspace/detail-page/tabs/outline'; import { DocInfoSheet } from '@affine/core/mobile/components'; +import { DocService } from '@affine/core/modules/doc'; import { EditorService } from '@affine/core/modules/editor'; import { ViewService } from '@affine/core/modules/workbench/services/view'; import { preventDefault } from '@affine/core/utils'; @@ -21,7 +22,7 @@ import { PageIcon, TocIcon, } from '@blocksuite/icons/rc'; -import { DocService, useLiveData, useService } from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import { useCallback, useEffect, useState } from 'react'; import { JournalConflictsMenuItem } from './menu/journal-conflicts'; diff --git a/packages/frontend/core/src/mobile/pages/workspace/detail/page-header-share-button.tsx b/packages/frontend/core/src/mobile/pages/workspace/detail/page-header-share-button.tsx index c1d83a980f052..dbd7daf545724 100644 --- a/packages/frontend/core/src/mobile/pages/workspace/detail/page-header-share-button.tsx +++ b/packages/frontend/core/src/mobile/pages/workspace/detail/page-header-share-button.tsx @@ -1,8 +1,10 @@ import { IconButton, MobileMenu } from '@affine/component'; import { SharePage } from '@affine/core/components/affine/share-page-modal/share-menu/share-page'; import { useEnableCloud } from '@affine/core/components/hooks/affine/use-enable-cloud'; +import { DocService } from '@affine/core/modules/doc'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { ShareiOsIcon } from '@blocksuite/icons/rc'; -import { DocService, useServices, WorkspaceService } from '@toeverything/infra'; +import { useServices } from '@toeverything/infra'; import * as styles from './page-header-share-button.css'; diff --git a/packages/frontend/core/src/mobile/pages/workspace/index.tsx b/packages/frontend/core/src/mobile/pages/workspace/index.tsx index e172ce9ecb416..62036df3f5901 100644 --- a/packages/frontend/core/src/mobile/pages/workspace/index.tsx +++ b/packages/frontend/core/src/mobile/pages/workspace/index.tsx @@ -2,11 +2,8 @@ import { AffineErrorBoundary } from '@affine/core/components/affine/affine-error import { AffineErrorComponent } from '@affine/core/components/affine/affine-error-boundary/affine-error-fallback'; import { PageNotFound } from '@affine/core/desktop/pages/404'; import { workbenchRoutes } from '@affine/core/mobile/workbench-router'; -import { - useLiveData, - useServices, - WorkspacesService, -} from '@toeverything/infra'; +import { WorkspacesService } from '@affine/core/modules/workspace'; +import { useLiveData, useServices } from '@toeverything/infra'; import { lazy as reactLazy, Suspense, diff --git a/packages/frontend/core/src/mobile/pages/workspace/layout.tsx b/packages/frontend/core/src/mobile/pages/workspace/layout.tsx index 565540505887f..e1f699cdf0bf4 100644 --- a/packages/frontend/core/src/mobile/pages/workspace/layout.tsx +++ b/packages/frontend/core/src/mobile/pages/workspace/layout.tsx @@ -10,15 +10,14 @@ import { DefaultServerService, WorkspaceServerService, } from '@affine/core/modules/cloud'; +import { GlobalContextService } from '@affine/core/modules/global-context'; import { PeekViewManagerModal } from '@affine/core/modules/peek-view'; -import type { Workspace, WorkspaceMetadata } from '@toeverything/infra'; -import { - FrameworkScope, - GlobalContextService, - useLiveData, - useServices, - WorkspacesService, -} from '@toeverything/infra'; +import type { + Workspace, + WorkspaceMetadata, +} from '@affine/core/modules/workspace'; +import { WorkspacesService } from '@affine/core/modules/workspace'; +import { FrameworkScope, useLiveData, useServices } from '@toeverything/infra'; import { type PropsWithChildren, useEffect, diff --git a/packages/frontend/core/src/mobile/pages/workspace/tag/detail.tsx b/packages/frontend/core/src/mobile/pages/workspace/tag/detail.tsx index 25e7ffb79bc9d..f7a323b7dcf17 100644 --- a/packages/frontend/core/src/mobile/pages/workspace/tag/detail.tsx +++ b/packages/frontend/core/src/mobile/pages/workspace/tag/detail.tsx @@ -1,11 +1,8 @@ import { useThemeColorV2 } from '@affine/component'; import { PageNotFound } from '@affine/core/desktop/pages/404'; +import { GlobalContextService } from '@affine/core/modules/global-context'; import { TagService } from '@affine/core/modules/tag'; -import { - GlobalContextService, - useLiveData, - useService, -} from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import { useEffect } from 'react'; import { useParams } from 'react-router-dom'; diff --git a/packages/frontend/core/src/mobile/views/all-docs/doc/list.tsx b/packages/frontend/core/src/mobile/views/all-docs/doc/list.tsx index f8e37cdccbd7e..1dc26fd298531 100644 --- a/packages/frontend/core/src/mobile/views/all-docs/doc/list.tsx +++ b/packages/frontend/core/src/mobile/views/all-docs/doc/list.tsx @@ -6,11 +6,12 @@ import { useFilteredPageMetas, } from '@affine/core/components/page-list'; import type { Tag } from '@affine/core/modules/tag'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import type { Collection, Filter } from '@affine/env/filter'; import type { DocMeta } from '@blocksuite/affine/store'; import { ToggleExpandIcon } from '@blocksuite/icons/rc'; import * as Collapsible from '@radix-ui/react-collapsible'; -import { useLiveData, useService, WorkspaceService } from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import { useMemo } from 'react'; import * as styles from './list.css'; diff --git a/packages/frontend/core/src/mobile/views/recent-docs/index.tsx b/packages/frontend/core/src/mobile/views/recent-docs/index.tsx index 3d4f0e60f08d4..64d56e191ef07 100644 --- a/packages/frontend/core/src/mobile/views/recent-docs/index.tsx +++ b/packages/frontend/core/src/mobile/views/recent-docs/index.tsx @@ -1,5 +1,6 @@ import { useBlockSuiteDocMeta } from '@affine/core/components/hooks/use-block-suite-page-meta'; -import { useService, WorkspaceService } from '@toeverything/infra'; +import { WorkspaceService } from '@affine/core/modules/workspace'; +import { useService } from '@toeverything/infra'; import { useMemo } from 'react'; import { DocCard } from '../../components/doc-card'; diff --git a/packages/frontend/core/src/modules/app-sidebar/impls/storage.ts b/packages/frontend/core/src/modules/app-sidebar/impls/storage.ts index 9261e692ff5f5..163b0056bb5bd 100644 --- a/packages/frontend/core/src/modules/app-sidebar/impls/storage.ts +++ b/packages/frontend/core/src/modules/app-sidebar/impls/storage.ts @@ -1,9 +1,6 @@ -import { - type GlobalState, - type Memento, - wrapMemento, -} from '@toeverything/infra'; +import { type Memento, wrapMemento } from '@toeverything/infra'; +import type { GlobalState } from '../../storage'; import type { AppSidebarState } from '../providers/storage'; export class AppSidebarStateImpl implements AppSidebarState { diff --git a/packages/frontend/core/src/modules/app-sidebar/index.ts b/packages/frontend/core/src/modules/app-sidebar/index.ts index 13dcd19facb09..7d7660d17bdf0 100644 --- a/packages/frontend/core/src/modules/app-sidebar/index.ts +++ b/packages/frontend/core/src/modules/app-sidebar/index.ts @@ -1,5 +1,6 @@ -import { type Framework, GlobalState } from '@toeverything/infra'; +import { type Framework } from '@toeverything/infra'; +import { GlobalState } from '../storage'; import { AppSidebar } from './entities/app-sidebar'; import { AppSidebarStateImpl } from './impls/storage'; import { AppSidebarState } from './providers/storage'; diff --git a/packages/frontend/core/src/modules/app-sidebar/services/app-sidebar.ts b/packages/frontend/core/src/modules/app-sidebar/services/app-sidebar.ts index dafb93a619445..cb0f400e5b998 100644 --- a/packages/frontend/core/src/modules/app-sidebar/services/app-sidebar.ts +++ b/packages/frontend/core/src/modules/app-sidebar/services/app-sidebar.ts @@ -1,5 +1,6 @@ -import { GlobalState, Service } from '@toeverything/infra'; +import { Service } from '@toeverything/infra'; +import { GlobalState } from '../../storage'; import { AppSidebar } from '../entities/app-sidebar'; export class AppSidebarService extends Service { diff --git a/packages/frontend/core/src/modules/app-sidebar/views/add-page-button/index.tsx b/packages/frontend/core/src/modules/app-sidebar/views/add-page-button/index.tsx index 725bd23a87b24..f9f21d680505d 100644 --- a/packages/frontend/core/src/modules/app-sidebar/views/add-page-button/index.tsx +++ b/packages/frontend/core/src/modules/app-sidebar/views/add-page-button/index.tsx @@ -1,10 +1,11 @@ import { IconButton } from '@affine/component'; import { usePageHelper } from '@affine/core/components/blocksuite/block-suite-page-list/utils'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { isNewTabTrigger } from '@affine/core/utils'; import { useI18n } from '@affine/i18n'; import track from '@affine/track'; import { PlusIcon } from '@blocksuite/icons/rc'; -import { useService, WorkspaceService } from '@toeverything/infra'; +import { useService } from '@toeverything/infra'; import clsx from 'clsx'; import type React from 'react'; import { type MouseEvent, useCallback } from 'react'; diff --git a/packages/frontend/core/src/modules/app-sidebar/views/index.tsx b/packages/frontend/core/src/modules/app-sidebar/views/index.tsx index fad22d1074b28..9c254d74419c1 100644 --- a/packages/frontend/core/src/modules/app-sidebar/views/index.tsx +++ b/packages/frontend/core/src/modules/app-sidebar/views/index.tsx @@ -8,13 +8,13 @@ import { useLiveData, useService, useServiceOptional, - WorkspaceService, } from '@toeverything/infra'; import clsx from 'clsx'; import { debounce } from 'lodash-es'; import type { PropsWithChildren, ReactElement } from 'react'; import { useCallback, useContext, useEffect, useMemo, useState } from 'react'; +import { WorkspaceService } from '../../workspace'; import { AppSidebarService } from '../services/app-sidebar'; import * as styles from './fallback.css'; import { @@ -285,7 +285,7 @@ const RandomBars = ({ count, header }: { count: number; header?: boolean }) => { /> ) : null} {Array.from({ length: count }).map((_, index) => ( - // eslint-disable-next-line react/no-array-index-key + // oxlint-disable-next-line eslint-plugin-react(no-array-index-key) ))} diff --git a/packages/frontend/core/src/modules/at-menu-config/index.ts b/packages/frontend/core/src/modules/at-menu-config/index.ts index 32a89961b5ded..eca4fe7d3d4a2 100644 --- a/packages/frontend/core/src/modules/at-menu-config/index.ts +++ b/packages/frontend/core/src/modules/at-menu-config/index.ts @@ -1,16 +1,13 @@ -import { - DocsService, - type Framework, - WorkspaceScope, - WorkspaceService, -} from '@toeverything/infra'; +import { type Framework } from '@toeverything/infra'; import { WorkspaceDialogService } from '../dialogs'; +import { DocsService } from '../doc'; import { DocDisplayMetaService } from '../doc-display-meta'; import { DocsSearchService } from '../docs-search'; import { EditorSettingService } from '../editor-setting'; import { JournalService } from '../journal'; import { RecentDocsService } from '../quicksearch'; +import { WorkspaceScope, WorkspaceService } from '../workspace'; import { AtMenuConfigService } from './services'; export function configAtMenuConfigModule(framework: Framework) { diff --git a/packages/frontend/core/src/modules/at-menu-config/services/index.ts b/packages/frontend/core/src/modules/at-menu-config/services/index.ts index 6679d8fa94d52..13456b5dfaa4d 100644 --- a/packages/frontend/core/src/modules/at-menu-config/services/index.ts +++ b/packages/frontend/core/src/modules/at-menu-config/services/index.ts @@ -18,18 +18,19 @@ import { } from '@blocksuite/icons/lit'; import type { DocMeta } from '@blocksuite/store'; import { signal } from '@preact/signals-core'; -import type { DocsService, WorkspaceService } from '@toeverything/infra'; import { Service } from '@toeverything/infra'; import { cssVarV2 } from '@toeverything/theme/v2'; import { html } from 'lit'; import { unsafeHTML } from 'lit/directives/unsafe-html.js'; import type { WorkspaceDialogService } from '../../dialogs'; +import type { DocsService } from '../../doc'; import type { DocDisplayMetaService } from '../../doc-display-meta'; import type { DocsSearchService } from '../../docs-search'; import type { EditorSettingService } from '../../editor-setting'; import { type JournalService, suggestJournalDate } from '../../journal'; import type { RecentDocsService } from '../../quicksearch'; +import type { WorkspaceService } from '../../workspace'; const MAX_DOCS = 3; const LOAD_CHUNK = 100; diff --git a/packages/frontend/core/src/modules/cloud/entities/cloud-doc-meta.ts b/packages/frontend/core/src/modules/cloud/entities/cloud-doc-meta.ts index 94e30a0f82534..3be12ff3ec04e 100644 --- a/packages/frontend/core/src/modules/cloud/entities/cloud-doc-meta.ts +++ b/packages/frontend/core/src/modules/cloud/entities/cloud-doc-meta.ts @@ -1,5 +1,4 @@ import type { GetWorkspacePageMetaByIdQuery } from '@affine/graphql'; -import type { DocService, GlobalCache } from '@toeverything/infra'; import { backoffRetry, catchErrorInto, @@ -13,6 +12,8 @@ import { } from '@toeverything/infra'; import { EMPTY, mergeMap } from 'rxjs'; +import type { DocService } from '../../doc'; +import type { GlobalCache } from '../../storage'; import { isBackendError, isNetworkError } from '../error'; import type { CloudDocMetaStore } from '../stores/cloud-doc-meta'; diff --git a/packages/frontend/core/src/modules/cloud/entities/workspace-invoices.ts b/packages/frontend/core/src/modules/cloud/entities/workspace-invoices.ts index ab85f682457b4..ef2b1eaac8237 100644 --- a/packages/frontend/core/src/modules/cloud/entities/workspace-invoices.ts +++ b/packages/frontend/core/src/modules/cloud/entities/workspace-invoices.ts @@ -1,5 +1,4 @@ import type { InvoicesQuery } from '@affine/graphql'; -import type { WorkspaceService } from '@toeverything/infra'; import { backoffRetry, catchErrorInto, @@ -13,6 +12,7 @@ import { } from '@toeverything/infra'; import { EMPTY, map, mergeMap } from 'rxjs'; +import type { WorkspaceService } from '../../workspace'; import { isBackendError, isNetworkError } from '../error'; import type { WorkspaceServerService } from '../services/workspace-server'; import { InvoicesStore } from '../stores/invoices'; diff --git a/packages/frontend/core/src/modules/cloud/entities/workspace-subscription.ts b/packages/frontend/core/src/modules/cloud/entities/workspace-subscription.ts index 8c6f520950dad..a97640b98b5e8 100644 --- a/packages/frontend/core/src/modules/cloud/entities/workspace-subscription.ts +++ b/packages/frontend/core/src/modules/cloud/entities/workspace-subscription.ts @@ -1,6 +1,5 @@ import type { SubscriptionQuery, SubscriptionRecurring } from '@affine/graphql'; import { SubscriptionPlan } from '@affine/graphql'; -import type { WorkspaceService } from '@toeverything/infra'; import { backoffRetry, catchErrorInto, @@ -14,10 +13,10 @@ import { } from '@toeverything/infra'; import { EMPTY, mergeMap } from 'rxjs'; +import type { WorkspaceService } from '../../workspace'; import { isBackendError, isNetworkError } from '../error'; import type { WorkspaceServerService } from '../services/workspace-server'; import { SubscriptionStore } from '../stores/subscription'; - export type SubscriptionType = NonNullable< SubscriptionQuery['currentUser'] >['subscriptions'][number]; diff --git a/packages/frontend/core/src/modules/cloud/index.ts b/packages/frontend/core/src/modules/cloud/index.ts index ad32ebc1f952c..a6cc96b44f372 100644 --- a/packages/frontend/core/src/modules/cloud/index.ts +++ b/packages/frontend/core/src/modules/cloud/index.ts @@ -33,18 +33,12 @@ export { WorkspaceServerService } from './services/workspace-server'; export { WorkspaceSubscriptionService } from './services/workspace-subscription'; export type { ServerConfig } from './types'; -import { - DocScope, - DocService, - type Framework, - GlobalCache, - GlobalState, - GlobalStateService, - WorkspaceScope, - WorkspaceService, -} from '@toeverything/infra'; +import { type Framework } from '@toeverything/infra'; +import { DocScope, DocService } from '../doc'; +import { GlobalCache, GlobalState, GlobalStateService } from '../storage'; import { UrlService } from '../url'; +import { WorkspaceScope, WorkspaceService } from '../workspace'; import { CloudDocMeta } from './entities/cloud-doc-meta'; import { Invoices } from './entities/invoices'; import { Server } from './entities/server'; diff --git a/packages/frontend/core/src/modules/cloud/services/auth.ts b/packages/frontend/core/src/modules/cloud/services/auth.ts index 7013127308ae1..9812bbbacad44 100644 --- a/packages/frontend/core/src/modules/cloud/services/auth.ts +++ b/packages/frontend/core/src/modules/cloud/services/auth.ts @@ -1,9 +1,10 @@ import { AIProvider } from '@affine/core/blocksuite/presets/ai'; import type { OAuthProviderType } from '@affine/graphql'; import { track } from '@affine/track'; -import { ApplicationFocused, OnEvent, Service } from '@toeverything/infra'; +import { OnEvent, Service } from '@toeverything/infra'; import { distinctUntilChanged, map, skip } from 'rxjs'; +import { ApplicationFocused } from '../../lifecycle'; import type { UrlService } from '../../url'; import { type AuthAccountInfo, AuthSession } from '../entities/session'; import { BackendError } from '../error'; diff --git a/packages/frontend/core/src/modules/cloud/services/websocket.ts b/packages/frontend/core/src/modules/cloud/services/websocket.ts index 6d6fefe9214cc..22db7ea20a1cf 100644 --- a/packages/frontend/core/src/modules/cloud/services/websocket.ts +++ b/packages/frontend/core/src/modules/cloud/services/websocket.ts @@ -1,6 +1,7 @@ -import { ApplicationStarted, OnEvent, Service } from '@toeverything/infra'; +import { OnEvent, Service } from '@toeverything/infra'; import { Manager } from 'socket.io-client'; +import { ApplicationStarted } from '../../lifecycle'; import { AccountChanged } from '../events/account-changed'; import type { WebSocketAuthProvider } from '../provider/websocket-auth'; import type { AuthService } from './auth'; diff --git a/packages/frontend/core/src/modules/cloud/stores/auth.ts b/packages/frontend/core/src/modules/cloud/stores/auth.ts index 6e909c08bb5ee..b909e3885c0fe 100644 --- a/packages/frontend/core/src/modules/cloud/stores/auth.ts +++ b/packages/frontend/core/src/modules/cloud/stores/auth.ts @@ -3,9 +3,9 @@ import { updateUserProfileMutation, uploadAvatarMutation, } from '@affine/graphql'; -import type { GlobalState } from '@toeverything/infra'; import { Store } from '@toeverything/infra'; +import type { GlobalState } from '../../storage'; import type { AuthSessionInfo } from '../entities/session'; import type { FetchService } from '../services/fetch'; import type { GraphQLService } from '../services/graphql'; diff --git a/packages/frontend/core/src/modules/cloud/stores/server-list.ts b/packages/frontend/core/src/modules/cloud/stores/server-list.ts index 8b7cf1d13c0d3..690a2851a6ae9 100644 --- a/packages/frontend/core/src/modules/cloud/stores/server-list.ts +++ b/packages/frontend/core/src/modules/cloud/stores/server-list.ts @@ -1,7 +1,7 @@ -import type { GlobalStateService } from '@toeverything/infra'; import { Store } from '@toeverything/infra'; import { map } from 'rxjs'; +import type { GlobalStateService } from '../../storage'; import { BUILD_IN_SERVERS } from '../constant'; import type { ServerConfig, ServerMetadata } from '../types'; diff --git a/packages/frontend/core/src/modules/cloud/stores/subscription.ts b/packages/frontend/core/src/modules/cloud/stores/subscription.ts index 637e6c4befe12..c2d5a1d844eb0 100644 --- a/packages/frontend/core/src/modules/cloud/stores/subscription.ts +++ b/packages/frontend/core/src/modules/cloud/stores/subscription.ts @@ -12,14 +12,13 @@ import { subscriptionQuery, updateSubscriptionMutation, } from '@affine/graphql'; -import type { GlobalCache } from '@toeverything/infra'; import { Store } from '@toeverything/infra'; +import type { GlobalCache } from '../../storage'; import type { UrlService } from '../../url'; import type { SubscriptionType } from '../entities/subscription'; import type { GraphQLService } from '../services/graphql'; import type { ServerService } from '../services/server'; - const SUBSCRIPTION_CACHE_KEY = 'subscription:'; const getDefaultSubscriptionSuccessCallbackLink = ( diff --git a/packages/frontend/core/src/modules/collection/index.ts b/packages/frontend/core/src/modules/collection/index.ts index d33921f375b07..9fb608c6d29b6 100644 --- a/packages/frontend/core/src/modules/collection/index.ts +++ b/packages/frontend/core/src/modules/collection/index.ts @@ -1,11 +1,8 @@ export { CollectionService } from './services/collection'; -import { - type Framework, - WorkspaceScope, - WorkspaceService, -} from '@toeverything/infra'; +import { type Framework } from '@toeverything/infra'; +import { WorkspaceScope, WorkspaceService } from '../workspace'; import { CollectionService } from './services/collection'; export function configureCollectionModule(framework: Framework) { diff --git a/packages/frontend/core/src/modules/collection/services/collection.ts b/packages/frontend/core/src/modules/collection/services/collection.ts index 41d42ce202e27..de434461f05f4 100644 --- a/packages/frontend/core/src/modules/collection/services/collection.ts +++ b/packages/frontend/core/src/modules/collection/services/collection.ts @@ -3,11 +3,12 @@ import type { DeleteCollectionInfo, DeletedCollection, } from '@affine/env/filter'; -import type { WorkspaceService } from '@toeverything/infra'; import { LiveData, Service } from '@toeverything/infra'; import { Observable } from 'rxjs'; import { Array as YArray } from 'yjs'; +import type { WorkspaceService } from '../../workspace'; + const SETTING_KEY = 'setting'; const COLLECTIONS_KEY = 'collections'; diff --git a/packages/common/infra/src/modules/db/entities/db.ts b/packages/frontend/core/src/modules/db/entities/db.ts similarity index 87% rename from packages/common/infra/src/modules/db/entities/db.ts rename to packages/frontend/core/src/modules/db/entities/db.ts index feb2d33465d92..654a48aab2028 100644 --- a/packages/common/infra/src/modules/db/entities/db.ts +++ b/packages/frontend/core/src/modules/db/entities/db.ts @@ -1,5 +1,9 @@ -import { Entity } from '../../../framework'; -import type { DBSchemaBuilder, TableMap } from '../../../orm'; +import { + type DBSchemaBuilder, + Entity, + type TableMap, +} from '@toeverything/infra'; + import { WorkspaceDBTable } from './table'; export class WorkspaceDB extends Entity<{ diff --git a/packages/frontend/core/src/modules/db/entities/table.ts b/packages/frontend/core/src/modules/db/entities/table.ts new file mode 100644 index 0000000000000..27ba94d11428f --- /dev/null +++ b/packages/frontend/core/src/modules/db/entities/table.ts @@ -0,0 +1,39 @@ +import type { + Table as OrmTable, + TableSchemaBuilder, +} from '@toeverything/infra'; +import { Entity } from '@toeverything/infra'; + +import type { WorkspaceService } from '../../workspace'; + +export class WorkspaceDBTable< + Schema extends TableSchemaBuilder, +> extends Entity<{ + table: OrmTable; + storageDocId: string; +}> { + readonly table = this.props.table; + + constructor(private readonly workspaceService: WorkspaceService) { + super(); + } + + isSyncing$ = this.workspaceService.workspace.engine.doc + .docState$(this.props.storageDocId) + .map(docState => docState.syncing); + + isLoading$ = this.workspaceService.workspace.engine.doc + .docState$(this.props.storageDocId) + .map(docState => docState.loading); + + create = this.table.create.bind(this.table) as typeof this.table.create; + update = this.table.update.bind(this.table) as typeof this.table.update; + get = this.table.get.bind(this.table) as typeof this.table.get; + // eslint-disable-next-line rxjs/finnish + get$ = this.table.get$.bind(this.table) as typeof this.table.get$; + find = this.table.find.bind(this.table) as typeof this.table.find; + // eslint-disable-next-line rxjs/finnish + find$ = this.table.find$.bind(this.table) as typeof this.table.find$; + keys = this.table.keys.bind(this.table) as typeof this.table.keys; + delete = this.table.delete.bind(this.table) as typeof this.table.delete; +} diff --git a/packages/common/infra/src/modules/db/index.ts b/packages/frontend/core/src/modules/db/index.ts similarity index 92% rename from packages/common/infra/src/modules/db/index.ts rename to packages/frontend/core/src/modules/db/index.ts index c4825f2fa0fe9..7b62456b3b046 100644 --- a/packages/common/infra/src/modules/db/index.ts +++ b/packages/frontend/core/src/modules/db/index.ts @@ -1,4 +1,5 @@ -import type { Framework } from '../../framework'; +import type { Framework } from '@toeverything/infra'; + import { WorkspaceScope, WorkspaceService } from '../workspace'; import { WorkspaceDB } from './entities/db'; import { WorkspaceDBTable } from './entities/table'; diff --git a/packages/common/infra/src/modules/db/schema/index.ts b/packages/frontend/core/src/modules/db/schema/index.ts similarity index 100% rename from packages/common/infra/src/modules/db/schema/index.ts rename to packages/frontend/core/src/modules/db/schema/index.ts diff --git a/packages/common/infra/src/modules/db/schema/schema.ts b/packages/frontend/core/src/modules/db/schema/schema.ts similarity index 94% rename from packages/common/infra/src/modules/db/schema/schema.ts rename to packages/frontend/core/src/modules/db/schema/schema.ts index d917526dc611b..857cf20172bd6 100644 --- a/packages/common/infra/src/modules/db/schema/schema.ts +++ b/packages/frontend/core/src/modules/db/schema/schema.ts @@ -1,7 +1,11 @@ +import { + type DBSchemaBuilder, + f, + type ORMEntity, + t, +} from '@toeverything/infra'; import { nanoid } from 'nanoid'; -import { type DBSchemaBuilder, f, type ORMEntity, t } from '../../../orm'; - export const AFFiNE_WORKSPACE_DB_SCHEMA = { folders: { id: f.string().primaryKey().optional().default(nanoid), diff --git a/packages/common/infra/src/modules/db/services/db.ts b/packages/frontend/core/src/modules/db/services/db.ts similarity index 95% rename from packages/common/infra/src/modules/db/services/db.ts rename to packages/frontend/core/src/modules/db/services/db.ts index c7afa13684beb..624924caaa7d2 100644 --- a/packages/common/infra/src/modules/db/services/db.ts +++ b/packages/frontend/core/src/modules/db/services/db.ts @@ -1,9 +1,12 @@ +import { + createORMClient, + type DocStorage, + ObjectPool, + Service, + YjsDBAdapter, +} from '@toeverything/infra'; import { Doc as YDoc } from 'yjs'; -import { Service } from '../../../framework'; -import { createORMClient, YjsDBAdapter } from '../../../orm'; -import type { DocStorage } from '../../../sync'; -import { ObjectPool } from '../../../utils'; import type { WorkspaceService } from '../../workspace'; import { WorkspaceDB, type WorkspaceDBWithTables } from '../entities/db'; import { diff --git a/packages/frontend/core/src/modules/desktop-api/service/desktop-api.ts b/packages/frontend/core/src/modules/desktop-api/service/desktop-api.ts index e44a6e1ad59e6..9f2beb7783193 100644 --- a/packages/frontend/core/src/modules/desktop-api/service/desktop-api.ts +++ b/packages/frontend/core/src/modules/desktop-api/service/desktop-api.ts @@ -5,7 +5,7 @@ import { reactRouterV6BrowserTracingIntegration, setTags, } from '@sentry/react'; -import { ApplicationStarted, OnEvent, Service } from '@toeverything/infra'; +import { OnEvent, Service } from '@toeverything/infra'; import { debounce } from 'lodash-es'; import { useEffect } from 'react'; import { @@ -16,6 +16,7 @@ import { } from 'react-router-dom'; import { AuthService, DefaultServerService, ServersService } from '../../cloud'; +import { ApplicationStarted } from '../../lifecycle'; import type { DesktopApi } from '../entities/electron-api'; @OnEvent(ApplicationStarted, e => e.setupStartListener) diff --git a/packages/frontend/core/src/modules/dialogs/constant.ts b/packages/frontend/core/src/modules/dialogs/constant.ts index 795b295d6fb53..9bb6503bca1f6 100644 --- a/packages/frontend/core/src/modules/dialogs/constant.ts +++ b/packages/frontend/core/src/modules/dialogs/constant.ts @@ -1,5 +1,6 @@ import type { DocMode } from '@blocksuite/affine/blocks'; -import type { WorkspaceMetadata } from '@toeverything/infra'; + +import type { WorkspaceMetadata } from '../workspace'; export type SettingTab = | 'shortcuts' diff --git a/packages/frontend/core/src/modules/dialogs/index.ts b/packages/frontend/core/src/modules/dialogs/index.ts index a32db5bc59ca9..6d98a569ec1c4 100644 --- a/packages/frontend/core/src/modules/dialogs/index.ts +++ b/packages/frontend/core/src/modules/dialogs/index.ts @@ -1,6 +1,6 @@ import type { Framework } from '@toeverything/infra'; -import { WorkspaceScope } from '@toeverything/infra'; +import { WorkspaceScope } from '../workspace'; import { GlobalDialogService } from './services/dialog'; import { WorkspaceDialogService } from './services/workspace-dialog'; diff --git a/packages/frontend/core/src/modules/dnd/index.ts b/packages/frontend/core/src/modules/dnd/index.ts index b8334cda9e92b..fd9729ecefe36 100644 --- a/packages/frontend/core/src/modules/dnd/index.ts +++ b/packages/frontend/core/src/modules/dnd/index.ts @@ -1,10 +1,7 @@ -import { - DocsService, - type Framework, - WorkspaceScope, - WorkspaceService, -} from '@toeverything/infra'; +import { type Framework } from '@toeverything/infra'; +import { DocsService } from '../doc'; +import { WorkspaceScope, WorkspaceService } from '../workspace'; import { DndService } from './services'; export function configureDndModule(framework: Framework) { diff --git a/packages/frontend/core/src/modules/dnd/services/index.ts b/packages/frontend/core/src/modules/dnd/services/index.ts index 12d928a315e39..f02c7fdfa5520 100644 --- a/packages/frontend/core/src/modules/dnd/services/index.ts +++ b/packages/frontend/core/src/modules/dnd/services/index.ts @@ -8,10 +8,11 @@ import type { AffineDNDData } from '@affine/core/types/dnd'; import { BlockStdScope } from '@blocksuite/affine/block-std'; import { DndApiExtensionIdentifier } from '@blocksuite/affine/blocks'; import { type SliceSnapshot } from '@blocksuite/affine/store'; -import type { DocsService, WorkspaceService } from '@toeverything/infra'; import { Service } from '@toeverything/infra'; +import type { DocsService } from '../../doc'; import { resolveLinkToDoc } from '../../navigation'; +import type { WorkspaceService } from '../../workspace'; type Entity = AffineDNDData['draggable']['entity']; type EntityResolver = (data: string) => Entity | null; diff --git a/packages/frontend/core/src/modules/doc-display-meta/index.ts b/packages/frontend/core/src/modules/doc-display-meta/index.ts index 1618b2bbba510..ad1832a8ee6b8 100644 --- a/packages/frontend/core/src/modules/doc-display-meta/index.ts +++ b/packages/frontend/core/src/modules/doc-display-meta/index.ts @@ -1,12 +1,10 @@ -import { - DocsService, - FeatureFlagService, - type Framework, - WorkspaceScope, -} from '@toeverything/infra'; +import { type Framework } from '@toeverything/infra'; +import { DocsService } from '../doc'; +import { FeatureFlagService } from '../feature-flag'; import { I18nService } from '../i18n'; import { JournalService } from '../journal'; +import { WorkspaceScope } from '../workspace'; import { DocDisplayMetaService } from './services/doc-display-meta'; export { DocDisplayMetaService }; diff --git a/packages/frontend/core/src/modules/doc-display-meta/services/doc-display-meta.ts b/packages/frontend/core/src/modules/doc-display-meta/services/doc-display-meta.ts index 830d726c480ca..257218723e484 100644 --- a/packages/frontend/core/src/modules/doc-display-meta/services/doc-display-meta.ts +++ b/packages/frontend/core/src/modules/doc-display-meta/services/doc-display-meta.ts @@ -22,15 +22,12 @@ import { TomorrowIcon, YesterdayIcon, } from '@blocksuite/icons/rc'; -import type { - DocRecord, - DocsService, - FeatureFlagService, -} from '@toeverything/infra'; import { LiveData, Service } from '@toeverything/infra'; import type { Dayjs } from 'dayjs'; import dayjs from 'dayjs'; +import type { DocRecord, DocsService } from '../../doc'; +import type { FeatureFlagService } from '../../feature-flag'; import type { I18nService } from '../../i18n'; import type { JournalService } from '../../journal'; diff --git a/packages/frontend/core/src/modules/doc-info/index.ts b/packages/frontend/core/src/modules/doc-info/index.ts index 285155153ab46..b57fe794746d5 100644 --- a/packages/frontend/core/src/modules/doc-info/index.ts +++ b/packages/frontend/core/src/modules/doc-info/index.ts @@ -1,10 +1,8 @@ -import { - DocsService, - type Framework, - WorkspaceScope, -} from '@toeverything/infra'; +import { type Framework } from '@toeverything/infra'; +import { DocsService } from '../doc/services/docs'; import { DocsSearchService } from '../docs-search'; +import { WorkspaceScope } from '../workspace'; import { DocDatabaseBacklinksService } from './services/doc-database-backlinks'; export { DocDatabaseBacklinkInfo } from './views/database-properties/doc-database-backlink-info'; diff --git a/packages/frontend/core/src/modules/doc-info/services/doc-database-backlinks.ts b/packages/frontend/core/src/modules/doc-info/services/doc-database-backlinks.ts index 6ed370db1d964..083d0523a8c9e 100644 --- a/packages/frontend/core/src/modules/doc-info/services/doc-database-backlinks.ts +++ b/packages/frontend/core/src/modules/doc-info/services/doc-database-backlinks.ts @@ -2,11 +2,11 @@ import { DatabaseBlockDataSource, type DatabaseBlockModel, } from '@blocksuite/affine/blocks'; -import type { DocsService } from '@toeverything/infra'; import { Service } from '@toeverything/infra'; import { isEqual } from 'lodash-es'; import { combineLatest, distinctUntilChanged, map, Observable } from 'rxjs'; +import type { DocsService } from '../../doc'; import type { DocsSearchService } from '../../docs-search'; import type { DatabaseRow, DatabaseValueCell } from '../types'; import { signalToLiveData, signalToObservable } from '../utils'; diff --git a/packages/frontend/core/src/modules/doc-info/types.ts b/packages/frontend/core/src/modules/doc-info/types.ts index 48a62758a1ad8..ca94358e93e1c 100644 --- a/packages/frontend/core/src/modules/doc-info/types.ts +++ b/packages/frontend/core/src/modules/doc-info/types.ts @@ -1,5 +1,7 @@ import type { DatabaseBlockDataSource } from '@blocksuite/affine/blocks'; -import type { Doc, LiveData } from '@toeverything/infra'; +import type { LiveData } from '@toeverything/infra'; + +import type { Doc } from '../doc'; // make database property type to be compatible with DocCustomPropertyInfo export type DatabaseProperty> = { diff --git a/packages/frontend/core/src/modules/doc-info/views/database-properties/doc-database-backlink-info.tsx b/packages/frontend/core/src/modules/doc-info/views/database-properties/doc-database-backlink-info.tsx index db5918fe189b9..8d2616cb320fa 100644 --- a/packages/frontend/core/src/modules/doc-info/views/database-properties/doc-database-backlink-info.tsx +++ b/packages/frontend/core/src/modules/doc-info/views/database-properties/doc-database-backlink-info.tsx @@ -5,15 +5,11 @@ import { PropertyName, } from '@affine/component'; import { AffinePageReference } from '@affine/core/components/affine/reference-link'; +import { DocService } from '@affine/core/modules/doc'; import { useI18n } from '@affine/i18n'; import type { DatabaseBlockDataSource } from '@blocksuite/affine/blocks'; import { DatabaseTableViewIcon, PageIcon } from '@blocksuite/icons/rc'; -import { - DocService, - LiveData, - useLiveData, - useService, -} from '@toeverything/infra'; +import { LiveData, useLiveData, useService } from '@toeverything/infra'; import { Fragment, useMemo } from 'react'; import type { Observable } from 'rxjs'; diff --git a/packages/frontend/core/src/modules/doc-link/entities/doc-backlinks.ts b/packages/frontend/core/src/modules/doc-link/entities/doc-backlinks.ts index fbd0e7251a662..6fcf0ec33d7f5 100644 --- a/packages/frontend/core/src/modules/doc-link/entities/doc-backlinks.ts +++ b/packages/frontend/core/src/modules/doc-link/entities/doc-backlinks.ts @@ -1,6 +1,6 @@ -import type { DocService } from '@toeverything/infra'; import { Entity, LiveData } from '@toeverything/infra'; +import type { DocService } from '../../doc'; import type { DocsSearchService } from '../../docs-search'; export interface Backlink { diff --git a/packages/frontend/core/src/modules/doc-link/entities/doc-links.ts b/packages/frontend/core/src/modules/doc-link/entities/doc-links.ts index 0f76dbb1dbddb..e6a57db4ae505 100644 --- a/packages/frontend/core/src/modules/doc-link/entities/doc-links.ts +++ b/packages/frontend/core/src/modules/doc-link/entities/doc-links.ts @@ -1,6 +1,6 @@ -import type { DocService } from '@toeverything/infra'; import { Entity, LiveData } from '@toeverything/infra'; +import type { DocService } from '../../doc'; import type { DocsSearchService } from '../../docs-search'; export interface Link { diff --git a/packages/frontend/core/src/modules/doc-link/index.ts b/packages/frontend/core/src/modules/doc-link/index.ts index 6db0af6d1417c..c29edc022c5be 100644 --- a/packages/frontend/core/src/modules/doc-link/index.ts +++ b/packages/frontend/core/src/modules/doc-link/index.ts @@ -1,11 +1,9 @@ -import { - DocScope, - DocService, - type Framework, - WorkspaceScope, -} from '@toeverything/infra'; +import { type Framework } from '@toeverything/infra'; +import { DocScope } from '../doc/scopes/doc'; +import { DocService } from '../doc/services/doc'; import { DocsSearchService } from '../docs-search'; +import { WorkspaceScope } from '../workspace'; import { DocBacklinks } from './entities/doc-backlinks'; import { DocLinks } from './entities/doc-links'; import { DocLinksService } from './services/doc-links'; diff --git a/packages/common/infra/src/modules/doc/constants.ts b/packages/frontend/core/src/modules/doc/constants.ts similarity index 100% rename from packages/common/infra/src/modules/doc/constants.ts rename to packages/frontend/core/src/modules/doc/constants.ts diff --git a/packages/common/infra/src/modules/doc/entities/doc.ts b/packages/frontend/core/src/modules/doc/entities/doc.ts similarity index 97% rename from packages/common/infra/src/modules/doc/entities/doc.ts rename to packages/frontend/core/src/modules/doc/entities/doc.ts index 9c06b7887d14d..8c2e50a08c327 100644 --- a/packages/common/infra/src/modules/doc/entities/doc.ts +++ b/packages/frontend/core/src/modules/doc/entities/doc.ts @@ -1,6 +1,6 @@ import type { DocMode, RootBlockModel } from '@blocksuite/affine/blocks'; +import { Entity } from '@toeverything/infra'; -import { Entity } from '../../../framework'; import type { WorkspaceService } from '../../workspace'; import type { DocScope } from '../scopes/doc'; import type { DocsStore } from '../stores/docs'; diff --git a/packages/common/infra/src/modules/doc/entities/property-list.ts b/packages/frontend/core/src/modules/doc/entities/property-list.ts similarity index 93% rename from packages/common/infra/src/modules/doc/entities/property-list.ts rename to packages/frontend/core/src/modules/doc/entities/property-list.ts index ee014be4c31a9..b7a146bc5d189 100644 --- a/packages/common/infra/src/modules/doc/entities/property-list.ts +++ b/packages/frontend/core/src/modules/doc/entities/property-list.ts @@ -1,6 +1,9 @@ -import { Entity } from '../../../framework'; -import { LiveData } from '../../../livedata'; -import { generateFractionalIndexingKeyBetween } from '../../../utils'; +import { + Entity, + generateFractionalIndexingKeyBetween, + LiveData, +} from '@toeverything/infra'; + import type { DocCustomPropertyInfo } from '../../db/schema/schema'; import type { DocPropertiesStore } from '../stores/doc-properties'; diff --git a/packages/common/infra/src/modules/doc/entities/record-list.ts b/packages/frontend/core/src/modules/doc/entities/record-list.ts similarity index 95% rename from packages/common/infra/src/modules/doc/entities/record-list.ts rename to packages/frontend/core/src/modules/doc/entities/record-list.ts index df5e52daa1b20..35001dac84257 100644 --- a/packages/common/infra/src/modules/doc/entities/record-list.ts +++ b/packages/frontend/core/src/modules/doc/entities/record-list.ts @@ -1,8 +1,7 @@ import type { DocMode } from '@blocksuite/affine/blocks'; +import { Entity, LiveData } from '@toeverything/infra'; import { map } from 'rxjs'; -import { Entity } from '../../../framework'; -import { LiveData } from '../../../livedata'; import type { DocsStore } from '../stores/docs'; import { DocRecord } from './record'; diff --git a/packages/common/infra/src/modules/doc/entities/record.ts b/packages/frontend/core/src/modules/doc/entities/record.ts similarity index 95% rename from packages/common/infra/src/modules/doc/entities/record.ts rename to packages/frontend/core/src/modules/doc/entities/record.ts index eeca7c59820b0..50f3f5d6a32b5 100644 --- a/packages/common/infra/src/modules/doc/entities/record.ts +++ b/packages/frontend/core/src/modules/doc/entities/record.ts @@ -1,8 +1,7 @@ import type { DocMode } from '@blocksuite/affine/blocks'; import type { DocMeta } from '@blocksuite/affine/store'; +import { Entity, LiveData } from '@toeverything/infra'; -import { Entity } from '../../../framework'; -import { LiveData } from '../../../livedata'; import type { DocProperties } from '../../db'; import type { DocPropertiesStore } from '../stores/doc-properties'; import type { DocsStore } from '../stores/docs'; diff --git a/packages/common/infra/src/modules/doc/events/index.ts b/packages/frontend/core/src/modules/doc/events/index.ts similarity index 69% rename from packages/common/infra/src/modules/doc/events/index.ts rename to packages/frontend/core/src/modules/doc/events/index.ts index a7720e362eecc..b6475175956b0 100644 --- a/packages/common/infra/src/modules/doc/events/index.ts +++ b/packages/frontend/core/src/modules/doc/events/index.ts @@ -1,4 +1,5 @@ -import { createEvent } from '../../../framework'; +import { createEvent } from '@toeverything/infra'; + import type { DocRecord } from '../entities/record'; export const DocCreated = createEvent('DocCreated'); diff --git a/packages/common/infra/src/modules/doc/index.ts b/packages/frontend/core/src/modules/doc/index.ts similarity index 96% rename from packages/common/infra/src/modules/doc/index.ts rename to packages/frontend/core/src/modules/doc/index.ts index fdb8e4c877196..40c418b6cb614 100644 --- a/packages/common/infra/src/modules/doc/index.ts +++ b/packages/frontend/core/src/modules/doc/index.ts @@ -6,7 +6,8 @@ export { DocScope } from './scopes/doc'; export { DocService } from './services/doc'; export { DocsService } from './services/docs'; -import type { Framework } from '../../framework'; +import type { Framework } from '@toeverything/infra'; + import { WorkspaceDBService } from '../db'; import { WorkspaceScope, WorkspaceService } from '../workspace'; import { Doc } from './entities/doc'; diff --git a/packages/common/infra/src/modules/doc/scopes/doc.ts b/packages/frontend/core/src/modules/doc/scopes/doc.ts similarity index 84% rename from packages/common/infra/src/modules/doc/scopes/doc.ts rename to packages/frontend/core/src/modules/doc/scopes/doc.ts index 6ef46871d028a..ba8db10caf317 100644 --- a/packages/common/infra/src/modules/doc/scopes/doc.ts +++ b/packages/frontend/core/src/modules/doc/scopes/doc.ts @@ -1,6 +1,6 @@ import type { Doc as BlockSuiteDoc } from '@blocksuite/affine/store'; +import { Scope } from '@toeverything/infra'; -import { Scope } from '../../../framework'; import type { DocRecord } from '../entities/record'; export class DocScope extends Scope<{ diff --git a/packages/common/infra/src/modules/doc/services/doc.ts b/packages/frontend/core/src/modules/doc/services/doc.ts similarity index 74% rename from packages/common/infra/src/modules/doc/services/doc.ts rename to packages/frontend/core/src/modules/doc/services/doc.ts index cd79eb110535a..abdd10bc1c93c 100644 --- a/packages/common/infra/src/modules/doc/services/doc.ts +++ b/packages/frontend/core/src/modules/doc/services/doc.ts @@ -1,4 +1,5 @@ -import { Service } from '../../../framework'; +import { Service } from '@toeverything/infra'; + import { Doc } from '../entities/doc'; export class DocService extends Service { diff --git a/packages/common/infra/src/modules/doc/services/docs.ts b/packages/frontend/core/src/modules/doc/services/docs.ts similarity index 95% rename from packages/common/infra/src/modules/doc/services/docs.ts rename to packages/frontend/core/src/modules/doc/services/docs.ts index 4b648a328a637..37fbfd3a80e7d 100644 --- a/packages/common/infra/src/modules/doc/services/docs.ts +++ b/packages/frontend/core/src/modules/doc/services/docs.ts @@ -3,10 +3,13 @@ import { Unreachable } from '@affine/env/constant'; import type { DocMode } from '@blocksuite/affine/blocks'; import type { DeltaInsert } from '@blocksuite/affine/inline'; import type { AffineTextAttributes } from '@blocksuite/affine-shared/types'; +import { + type DocProps, + initDocFromProps, + ObjectPool, + Service, +} from '@toeverything/infra'; -import { Service } from '../../../framework'; -import { type DocProps, initDocFromProps } from '../../../initialization'; -import { ObjectPool } from '../../../utils'; import type { Doc } from '../entities/doc'; import { DocPropertyList } from '../entities/property-list'; import { DocRecordList } from '../entities/record-list'; diff --git a/packages/common/infra/src/modules/doc/stores/doc-properties.ts b/packages/frontend/core/src/modules/doc/stores/doc-properties.ts similarity index 98% rename from packages/common/infra/src/modules/doc/stores/doc-properties.ts rename to packages/frontend/core/src/modules/doc/stores/doc-properties.ts index 197b5fb3017b9..b94e66a992e93 100644 --- a/packages/common/infra/src/modules/doc/stores/doc-properties.ts +++ b/packages/frontend/core/src/modules/doc/stores/doc-properties.ts @@ -1,12 +1,8 @@ +import { Store, yjsObserveByPath, yjsObserveDeep } from '@toeverything/infra'; import { differenceBy, isNil, omitBy } from 'lodash-es'; import { combineLatest, map, switchMap } from 'rxjs'; import { AbstractType as YAbstractType } from 'yjs'; -import { Store } from '../../../framework'; -import { - yjsObserveByPath, - yjsObserveDeep, -} from '../../../utils/yjs-observable'; import type { WorkspaceDBService } from '../../db'; import type { DocCustomPropertyInfo, diff --git a/packages/common/infra/src/modules/doc/stores/docs.ts b/packages/frontend/core/src/modules/doc/stores/docs.ts similarity index 96% rename from packages/common/infra/src/modules/doc/stores/docs.ts rename to packages/frontend/core/src/modules/doc/stores/docs.ts index 4df36193770a3..0fd6c1f78107a 100644 --- a/packages/common/infra/src/modules/doc/stores/docs.ts +++ b/packages/frontend/core/src/modules/doc/stores/docs.ts @@ -1,10 +1,14 @@ import type { DocMode } from '@blocksuite/affine/blocks'; import type { DocMeta } from '@blocksuite/affine/store'; +import { + Store, + yjsObserve, + yjsObserveByPath, + yjsObserveDeep, +} from '@toeverything/infra'; import { distinctUntilChanged, map, switchMap } from 'rxjs'; import { Array as YArray, Map as YMap } from 'yjs'; -import { Store } from '../../../framework'; -import { yjsObserve, yjsObserveByPath, yjsObserveDeep } from '../../../utils'; import type { WorkspaceService } from '../../workspace'; import type { DocPropertiesStore } from './doc-properties'; diff --git a/packages/frontend/core/src/modules/docs-search/entities/docs-indexer.ts b/packages/frontend/core/src/modules/docs-search/entities/docs-indexer.ts index 419b8a504371d..94d421d4208b3 100644 --- a/packages/frontend/core/src/modules/docs-search/entities/docs-indexer.ts +++ b/packages/frontend/core/src/modules/docs-search/entities/docs-indexer.ts @@ -1,20 +1,16 @@ import { DebugLogger } from '@affine/debug'; -import type { - Job, - JobQueue, - WorkspaceLocalState, - WorkspaceService, -} from '@toeverything/infra'; +import type { Job, JobQueue } from '@toeverything/infra'; import { Entity, IndexedDBIndexStorage, IndexedDBJobQueue, JobRunner, LiveData, - WorkspaceDBService, } from '@toeverything/infra'; import { map } from 'rxjs'; +import { WorkspaceDBService } from '../../db'; +import type { WorkspaceLocalState, WorkspaceService } from '../../workspace'; import { blockIndexSchema, docIndexSchema } from '../schema'; import { createWorker, type IndexerWorker } from '../worker/out-worker'; diff --git a/packages/frontend/core/src/modules/docs-search/index.ts b/packages/frontend/core/src/modules/docs-search/index.ts index 03fd0e157ac7a..1f9db66e185e9 100644 --- a/packages/frontend/core/src/modules/docs-search/index.ts +++ b/packages/frontend/core/src/modules/docs-search/index.ts @@ -1,12 +1,12 @@ export { DocsSearchService } from './services/docs-search'; +import { type Framework } from '@toeverything/infra'; + import { - type Framework, WorkspaceLocalState, WorkspaceScope, WorkspaceService, -} from '@toeverything/infra'; - +} from '../workspace'; import { DocsIndexer } from './entities/docs-indexer'; import { DocsSearchService } from './services/docs-search'; diff --git a/packages/frontend/core/src/modules/docs-search/services/docs-search.ts b/packages/frontend/core/src/modules/docs-search/services/docs-search.ts index 0eb1af2ce75ca..792e54d410f6b 100644 --- a/packages/frontend/core/src/modules/docs-search/services/docs-search.ts +++ b/packages/frontend/core/src/modules/docs-search/services/docs-search.ts @@ -1,16 +1,12 @@ import { toURLSearchParams } from '@affine/core/modules/navigation'; import type { ReferenceParams } from '@blocksuite/blocks'; -import type { WorkspaceService } from '@toeverything/infra'; -import { - fromPromise, - OnEvent, - Service, - WorkspaceEngineBeforeStart, -} from '@toeverything/infra'; +import { fromPromise, OnEvent, Service } from '@toeverything/infra'; import { isEmpty, omit } from 'lodash-es'; import { map, type Observable, switchMap } from 'rxjs'; import { z } from 'zod'; +import type { WorkspaceService } from '../../workspace'; +import { WorkspaceEngineBeforeStart } from '../../workspace'; import { DocsIndexer } from '../entities/docs-indexer'; @OnEvent(WorkspaceEngineBeforeStart, s => s.handleWorkspaceEngineBeforeStart) diff --git a/packages/frontend/core/src/modules/docs-search/worker/in-worker.ts b/packages/frontend/core/src/modules/docs-search/worker/in-worker.ts index a6bb1af4d4bcf..8a0e68360b249 100644 --- a/packages/frontend/core/src/modules/docs-search/worker/in-worker.ts +++ b/packages/frontend/core/src/modules/docs-search/worker/in-worker.ts @@ -15,7 +15,7 @@ import { } from '@blocksuite/affine/store'; import type { AffineTextAttributes } from '@blocksuite/affine-shared/types'; import type { DeltaInsert } from '@blocksuite/inline'; -import { Document, getAFFiNEWorkspaceSchema } from '@toeverything/infra'; +import { Document } from '@toeverything/infra'; import { toHexString } from 'lib0/buffer.js'; import { digest as lib0Digest } from 'lib0/hash/sha256'; import { difference, uniq } from 'lodash-es'; @@ -27,6 +27,7 @@ import { Text as YText, } from 'yjs'; +import { getAFFiNEWorkspaceSchema } from '../../workspace/global-schema'; import type { BlockIndexSchema, DocIndexSchema } from '../schema'; import type { WorkerIngoingMessage, diff --git a/packages/frontend/core/src/modules/editor-setting/__test__/editor-setting.spec.ts b/packages/frontend/core/src/modules/editor-setting/__test__/editor-setting.spec.ts deleted file mode 100644 index 3284bab168bd8..0000000000000 --- a/packages/frontend/core/src/modules/editor-setting/__test__/editor-setting.spec.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Framework, GlobalState, MemoryMemento } from '@toeverything/infra'; -import { expect, test } from 'vitest'; - -import { EditorSetting } from '../entities/editor-setting'; -import { GlobalStateEditorSettingProvider } from '../impls/global-state'; -import { EditorSettingProvider } from '../provider/editor-setting-provider'; -import { EditorSettingService } from '../services/editor-setting'; - -test('editor setting service', () => { - const framework = new Framework(); - - framework - .service(EditorSettingService) - .entity(EditorSetting, [EditorSettingProvider]) - .impl(EditorSettingProvider, GlobalStateEditorSettingProvider, [ - GlobalState, - ]) - .impl(GlobalState, MemoryMemento); - - const provider = framework.provider(); - - const editorSettingService = provider.get(EditorSettingService); - - // default value - expect(editorSettingService.editorSetting.get('fontFamily')).toBe('Sans'); - - // set plain object - editorSettingService.editorSetting.set('fontFamily', 'Serif'); - expect(editorSettingService.editorSetting.get('fontFamily')).toBe('Serif'); - - // set nested object - editorSettingService.editorSetting.set('connector', { - stroke: { - dark: '#000000', - light: '#ffffff', - }, - }); - expect(editorSettingService.editorSetting.get('connector').stroke).toEqual({ - dark: '#000000', - light: '#ffffff', - }); - - // invalid font family - editorSettingService.editorSetting.provider.set( - 'fontFamily', - JSON.stringify('abc') - ); - - // fallback to default value - expect(editorSettingService.editorSetting.get('fontFamily')).toBe('Sans'); -}); diff --git a/packages/frontend/core/src/modules/editor-setting/impls/global-state.ts b/packages/frontend/core/src/modules/editor-setting/impls/global-state.ts index 346aca144a93b..3ffcd79afc3dc 100644 --- a/packages/frontend/core/src/modules/editor-setting/impls/global-state.ts +++ b/packages/frontend/core/src/modules/editor-setting/impls/global-state.ts @@ -1,7 +1,7 @@ -import type { GlobalState } from '@toeverything/infra'; import { Service } from '@toeverything/infra'; import { map, type Observable } from 'rxjs'; +import type { GlobalState } from '../../storage'; import type { EditorSettingProvider } from '../provider/editor-setting-provider'; const storageKey = 'editor-setting'; diff --git a/packages/frontend/core/src/modules/editor-setting/impls/user-db.ts b/packages/frontend/core/src/modules/editor-setting/impls/user-db.ts index 5d132230d73f8..15dfbe2284ba8 100644 --- a/packages/frontend/core/src/modules/editor-setting/impls/user-db.ts +++ b/packages/frontend/core/src/modules/editor-setting/impls/user-db.ts @@ -1,8 +1,8 @@ -import type { GlobalState } from '@toeverything/infra'; import { Service } from '@toeverything/infra'; import { map, type Observable, switchMap } from 'rxjs'; import type { ServersService } from '../../cloud'; +import type { GlobalState } from '../../storage'; import { UserDBService } from '../../userspace'; import type { EditorSettingProvider } from '../provider/editor-setting-provider'; diff --git a/packages/frontend/core/src/modules/editor-setting/index.ts b/packages/frontend/core/src/modules/editor-setting/index.ts index fc8771ec4f11b..8095a04a8e8a3 100644 --- a/packages/frontend/core/src/modules/editor-setting/index.ts +++ b/packages/frontend/core/src/modules/editor-setting/index.ts @@ -1,12 +1,9 @@ -import { - type Framework, - GlobalState, - GlobalStateService, -} from '@toeverything/infra'; +import { type Framework } from '@toeverything/infra'; import { ServersService } from '../cloud'; import { DesktopApiService } from '../desktop-api'; import { I18n } from '../i18n'; +import { GlobalState, GlobalStateService } from '../storage'; import { EditorSetting } from './entities/editor-setting'; import { CurrentUserDBEditorSettingProvider } from './impls/user-db'; import { EditorSettingProvider } from './provider/editor-setting-provider'; diff --git a/packages/frontend/core/src/modules/editor-setting/services/editor-setting.ts b/packages/frontend/core/src/modules/editor-setting/services/editor-setting.ts index b13b42f00c1ba..3dd33c2e4a2af 100644 --- a/packages/frontend/core/src/modules/editor-setting/services/editor-setting.ts +++ b/packages/frontend/core/src/modules/editor-setting/services/editor-setting.ts @@ -1,11 +1,8 @@ -import type { Workspace } from '@toeverything/infra'; -import { - DocsService, - OnEvent, - Service, - WorkspaceInitialized, -} from '@toeverything/infra'; +import { OnEvent, Service } from '@toeverything/infra'; +import { DocsService } from '../../doc'; +import type { Workspace } from '../../workspace'; +import { WorkspaceInitialized } from '../../workspace'; import { EditorSetting, type EditorSettingExt, diff --git a/packages/frontend/core/src/modules/editor-setting/services/spell-check-setting.ts b/packages/frontend/core/src/modules/editor-setting/services/spell-check-setting.ts index 7976e1e4aecb0..e981b45286dfc 100644 --- a/packages/frontend/core/src/modules/editor-setting/services/spell-check-setting.ts +++ b/packages/frontend/core/src/modules/editor-setting/services/spell-check-setting.ts @@ -3,11 +3,11 @@ import type { SpellCheckStateSchema, } from '@affine/electron/main/shared-state-schema'; import type { Language } from '@affine/i18n'; -import type { GlobalStateService } from '@toeverything/infra'; import { LiveData, Service } from '@toeverything/infra'; import type { DesktopApiService } from '../../desktop-api'; import type { I18n } from '../../i18n'; +import type { GlobalStateService } from '../../storage'; const SPELL_CHECK_SETTING_KEY: typeof SpellCheckStateKey = 'spellCheckState'; diff --git a/packages/frontend/core/src/modules/editor/entities/editor.ts b/packages/frontend/core/src/modules/editor/entities/editor.ts index 78daa1f08a5fa..bc64e1f48c9eb 100644 --- a/packages/frontend/core/src/modules/editor/entities/editor.ts +++ b/packages/frontend/core/src/modules/editor/entities/editor.ts @@ -10,13 +10,14 @@ import type { } from '@blocksuite/affine/presets'; import type { InlineEditor } from '@blocksuite/inline'; import { effect } from '@preact/signals-core'; -import type { DocService, WorkspaceService } from '@toeverything/infra'; import { Entity, LiveData } from '@toeverything/infra'; import { defaults, isEqual, omit } from 'lodash-es'; import { skip } from 'rxjs'; +import type { DocService } from '../../doc'; import { paramsParseOptions, preprocessParams } from '../../navigation/utils'; import type { WorkbenchView } from '../../workbench'; +import type { WorkspaceService } from '../../workspace'; import { EditorScope } from '../scopes/editor'; import type { EditorSelector } from '../types'; diff --git a/packages/frontend/core/src/modules/editor/index.ts b/packages/frontend/core/src/modules/editor/index.ts index 7b2eb8ee3abb9..edd9ec66300bb 100644 --- a/packages/frontend/core/src/modules/editor/index.ts +++ b/packages/frontend/core/src/modules/editor/index.ts @@ -1,11 +1,7 @@ -import { - DocScope, - DocService, - type Framework, - WorkspaceScope, - WorkspaceService, -} from '@toeverything/infra'; +import { type Framework } from '@toeverything/infra'; +import { DocScope, DocService } from '../doc'; +import { WorkspaceScope, WorkspaceService } from '../workspace'; import { Editor } from './entities/editor'; import { EditorScope } from './scopes/editor'; import { EditorService } from './services/editor'; diff --git a/packages/frontend/core/src/modules/explorer/entities/explore-section.ts b/packages/frontend/core/src/modules/explorer/entities/explore-section.ts index 2c4e281c384a3..3bfba344db357 100644 --- a/packages/frontend/core/src/modules/explorer/entities/explore-section.ts +++ b/packages/frontend/core/src/modules/explorer/entities/explore-section.ts @@ -1,7 +1,7 @@ -import type { GlobalCache } from '@toeverything/infra'; import { Entity, LiveData } from '@toeverything/infra'; import { map } from 'rxjs'; +import type { GlobalCache } from '../../storage'; import type { CollapsibleSectionName } from '../types'; const DEFAULT_COLLAPSABLE_STATE: Record = { diff --git a/packages/frontend/core/src/modules/explorer/index.ts b/packages/frontend/core/src/modules/explorer/index.ts index 96f9d0b4d8887..3d1e72a8c0196 100644 --- a/packages/frontend/core/src/modules/explorer/index.ts +++ b/packages/frontend/core/src/modules/explorer/index.ts @@ -1,9 +1,7 @@ -import { - type Framework, - GlobalCache, - WorkspaceScope, -} from '@toeverything/infra'; +import { type Framework } from '@toeverything/infra'; +import { GlobalCache } from '../storage'; +import { WorkspaceScope } from '../workspace'; import { ExplorerSection } from './entities/explore-section'; import { ExplorerService } from './services/explorer'; export { ExplorerService } from './services/explorer'; diff --git a/packages/frontend/core/src/modules/explorer/views/nodes/collection/index.tsx b/packages/frontend/core/src/modules/explorer/views/nodes/collection/index.tsx index 22a8c097a072f..746c6be8ffb42 100644 --- a/packages/frontend/core/src/modules/explorer/views/nodes/collection/index.tsx +++ b/packages/frontend/core/src/modules/explorer/views/nodes/collection/index.tsx @@ -8,7 +8,9 @@ import { import { filterPage } from '@affine/core/components/page-list'; import { CollectionService } from '@affine/core/modules/collection'; import { WorkspaceDialogService } from '@affine/core/modules/dialogs'; +import { DocsService } from '@affine/core/modules/doc'; import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/favorite'; +import { GlobalContextService } from '@affine/core/modules/global-context'; import { ShareDocsListService } from '@affine/core/modules/share-doc'; import type { AffineDNDData } from '@affine/core/types/dnd'; import type { Collection } from '@affine/env/filter'; @@ -18,8 +20,6 @@ import { track } from '@affine/track'; import type { DocMeta } from '@blocksuite/affine/store'; import { FilterMinusIcon } from '@blocksuite/icons/rc'; import { - DocsService, - GlobalContextService, LiveData, useLiveData, useService, diff --git a/packages/frontend/core/src/modules/explorer/views/nodes/collection/operations.tsx b/packages/frontend/core/src/modules/explorer/views/nodes/collection/operations.tsx index 6fd8e4dee669e..d5537d4eef4c5 100644 --- a/packages/frontend/core/src/modules/explorer/views/nodes/collection/operations.tsx +++ b/packages/frontend/core/src/modules/explorer/views/nodes/collection/operations.tsx @@ -9,7 +9,9 @@ import { useDeleteCollectionInfo } from '@affine/core/components/hooks/affine/us import { IsFavoriteIcon } from '@affine/core/components/pure/icons'; import { CollectionService } from '@affine/core/modules/collection'; import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/favorite'; +import { FeatureFlagService } from '@affine/core/modules/feature-flag'; import { WorkbenchService } from '@affine/core/modules/workbench'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { useI18n } from '@affine/i18n'; import { track } from '@affine/track'; import { @@ -19,12 +21,7 @@ import { PlusIcon, SplitViewIcon, } from '@blocksuite/icons/rc'; -import { - FeatureFlagService, - useLiveData, - useServices, - WorkspaceService, -} from '@toeverything/infra'; +import { useLiveData, useServices } from '@toeverything/infra'; import { useCallback, useMemo } from 'react'; import type { NodeOperation } from '../../tree/types'; diff --git a/packages/frontend/core/src/modules/explorer/views/nodes/doc/index.tsx b/packages/frontend/core/src/modules/explorer/views/nodes/doc/index.tsx index 526f5c5dc007e..a5ee5ebf24d8d 100644 --- a/packages/frontend/core/src/modules/explorer/views/nodes/doc/index.tsx +++ b/packages/frontend/core/src/modules/explorer/views/nodes/doc/index.tsx @@ -7,15 +7,15 @@ import { } from '@affine/component'; import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; import { WorkspaceDialogService } from '@affine/core/modules/dialogs'; +import { DocsService } from '@affine/core/modules/doc'; import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta'; import { DocsSearchService } from '@affine/core/modules/docs-search'; +import { FeatureFlagService } from '@affine/core/modules/feature-flag'; +import { GlobalContextService } from '@affine/core/modules/global-context'; import type { AffineDNDData } from '@affine/core/types/dnd'; import { useI18n } from '@affine/i18n'; import { track } from '@affine/track'; import { - DocsService, - FeatureFlagService, - GlobalContextService, LiveData, useLiveData, useService, diff --git a/packages/frontend/core/src/modules/explorer/views/nodes/doc/operations.tsx b/packages/frontend/core/src/modules/explorer/views/nodes/doc/operations.tsx index 82a6ac17b6c65..c25df37c79637 100644 --- a/packages/frontend/core/src/modules/explorer/views/nodes/doc/operations.tsx +++ b/packages/frontend/core/src/modules/explorer/views/nodes/doc/operations.tsx @@ -9,8 +9,11 @@ import { usePageHelper } from '@affine/core/components/blocksuite/block-suite-pa import { useBlockSuiteMetaHelper } from '@affine/core/components/hooks/affine/use-block-suite-meta-helper'; import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; import { IsFavoriteIcon } from '@affine/core/components/pure/icons'; +import { DocsService } from '@affine/core/modules/doc'; import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/favorite'; +import { FeatureFlagService } from '@affine/core/modules/feature-flag'; import { WorkbenchService } from '@affine/core/modules/workbench'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { useI18n } from '@affine/i18n'; import { track } from '@affine/track'; import { @@ -22,13 +25,7 @@ import { PlusIcon, SplitViewIcon, } from '@blocksuite/icons/rc'; -import { - DocsService, - FeatureFlagService, - useLiveData, - useServices, - WorkspaceService, -} from '@toeverything/infra'; +import { useLiveData, useServices } from '@toeverything/infra'; import { useCallback, useMemo } from 'react'; import type { NodeOperation } from '../../tree/types'; diff --git a/packages/frontend/core/src/modules/explorer/views/nodes/folder/index.tsx b/packages/frontend/core/src/modules/explorer/views/nodes/folder/index.tsx index e49fb08a51f39..3d154d6acc4df 100644 --- a/packages/frontend/core/src/modules/explorer/views/nodes/folder/index.tsx +++ b/packages/frontend/core/src/modules/explorer/views/nodes/folder/index.tsx @@ -12,10 +12,12 @@ import { import { usePageHelper } from '@affine/core/components/blocksuite/block-suite-page-list/utils'; import { WorkspaceDialogService } from '@affine/core/modules/dialogs'; import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/favorite'; +import { FeatureFlagService } from '@affine/core/modules/feature-flag'; import { type FolderNode, OrganizeService, } from '@affine/core/modules/organize'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import type { AffineDNDData } from '@affine/core/types/dnd'; import { Unreachable } from '@affine/env/constant'; import { useI18n } from '@affine/i18n'; @@ -29,12 +31,7 @@ import { RemoveFolderIcon, TagsIcon, } from '@blocksuite/icons/rc'; -import { - FeatureFlagService, - useLiveData, - useServices, - WorkspaceService, -} from '@toeverything/infra'; +import { useLiveData, useServices } from '@toeverything/infra'; import { difference } from 'lodash-es'; import { useCallback, useMemo, useState } from 'react'; diff --git a/packages/frontend/core/src/modules/explorer/views/nodes/tag/index.tsx b/packages/frontend/core/src/modules/explorer/views/nodes/tag/index.tsx index 0a17b2edabb58..3487310cdca52 100644 --- a/packages/frontend/core/src/modules/explorer/views/nodes/tag/index.tsx +++ b/packages/frontend/core/src/modules/explorer/views/nodes/tag/index.tsx @@ -3,16 +3,13 @@ import { type DropTargetOptions, toast, } from '@affine/component'; +import { GlobalContextService } from '@affine/core/modules/global-context'; import type { Tag } from '@affine/core/modules/tag'; import { TagService } from '@affine/core/modules/tag'; import type { AffineDNDData } from '@affine/core/types/dnd'; import { useI18n } from '@affine/i18n'; import { track } from '@affine/track'; -import { - GlobalContextService, - useLiveData, - useServices, -} from '@toeverything/infra'; +import { useLiveData, useServices } from '@toeverything/infra'; import clsx from 'clsx'; import { useCallback, useMemo, useState } from 'react'; diff --git a/packages/frontend/core/src/modules/explorer/views/nodes/tag/operations.tsx b/packages/frontend/core/src/modules/explorer/views/nodes/tag/operations.tsx index a9592e3e50c28..e99afb41d007d 100644 --- a/packages/frontend/core/src/modules/explorer/views/nodes/tag/operations.tsx +++ b/packages/frontend/core/src/modules/explorer/views/nodes/tag/operations.tsx @@ -1,9 +1,12 @@ import { IconButton, MenuItem, MenuSeparator, toast } from '@affine/component'; import { usePageHelper } from '@affine/core/components/blocksuite/block-suite-page-list/utils'; import { IsFavoriteIcon } from '@affine/core/components/pure/icons'; +import { DocsService } from '@affine/core/modules/doc'; import { FavoriteService } from '@affine/core/modules/favorite'; +import { FeatureFlagService } from '@affine/core/modules/feature-flag'; import { TagService } from '@affine/core/modules/tag'; import { WorkbenchService } from '@affine/core/modules/workbench'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { useI18n } from '@affine/i18n'; import { track } from '@affine/track'; import { @@ -12,13 +15,7 @@ import { PlusIcon, SplitViewIcon, } from '@blocksuite/icons/rc'; -import { - DocsService, - FeatureFlagService, - useLiveData, - useServices, - WorkspaceService, -} from '@toeverything/infra'; +import { useLiveData, useServices } from '@toeverything/infra'; import { useCallback, useMemo } from 'react'; import type { NodeOperation } from '../../tree/types'; diff --git a/packages/frontend/core/src/modules/explorer/views/sections/favorites/index.tsx b/packages/frontend/core/src/modules/explorer/views/sections/favorites/index.tsx index 165b273258a59..57923bdf5ddfe 100644 --- a/packages/frontend/core/src/modules/explorer/views/sections/favorites/index.tsx +++ b/packages/frontend/core/src/modules/explorer/views/sections/favorites/index.tsx @@ -13,16 +13,13 @@ import { FavoriteService, isFavoriteSupportType, } from '@affine/core/modules/favorite'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import type { AffineDNDData } from '@affine/core/types/dnd'; import { isNewTabTrigger } from '@affine/core/utils'; import { useI18n } from '@affine/i18n'; import { track } from '@affine/track'; import { PlusIcon } from '@blocksuite/icons/rc'; -import { - useLiveData, - useServices, - WorkspaceService, -} from '@toeverything/infra'; +import { useLiveData, useServices } from '@toeverything/infra'; import { type MouseEventHandler, useCallback } from 'react'; import { ExplorerService } from '../../../services/explorer'; diff --git a/packages/frontend/core/src/modules/explorer/views/sections/migration-favorites/index.tsx b/packages/frontend/core/src/modules/explorer/views/sections/migration-favorites/index.tsx index 6e8c4049998f0..b4dc95e5ca8c3 100644 --- a/packages/frontend/core/src/modules/explorer/views/sections/migration-favorites/index.tsx +++ b/packages/frontend/core/src/modules/explorer/views/sections/migration-favorites/index.tsx @@ -1,10 +1,11 @@ import { IconButton, useConfirmModal } from '@affine/component'; +import { DocsService } from '@affine/core/modules/doc'; import { ExplorerTreeRoot } from '@affine/core/modules/explorer/views/tree'; import { MigrationFavoriteItemsAdapter } from '@affine/core/modules/favorite'; import { Trans, useI18n } from '@affine/i18n'; import { track } from '@affine/track'; import { BroomIcon, HelpIcon } from '@blocksuite/icons/rc'; -import { DocsService, useLiveData, useServices } from '@toeverything/infra'; +import { useLiveData, useServices } from '@toeverything/infra'; import { useCallback } from 'react'; import { CollapsibleSection } from '../../layouts/collapsible-section'; diff --git a/packages/frontend/core/src/modules/favorite/index.ts b/packages/frontend/core/src/modules/favorite/index.ts index e6cf92e7553a8..b5eae5315bae3 100644 --- a/packages/frontend/core/src/modules/favorite/index.ts +++ b/packages/frontend/core/src/modules/favorite/index.ts @@ -1,11 +1,8 @@ -import { - type Framework, - WorkspaceDBService, - WorkspaceScope, - WorkspaceService, -} from '@toeverything/infra'; +import { type Framework } from '@toeverything/infra'; import { WorkspaceServerService } from '../cloud'; +import { WorkspaceDBService } from '../db'; +import { WorkspaceScope, WorkspaceService } from '../workspace'; import { FavoriteList } from './entities/favorite-list'; import { FavoriteService } from './services/favorite'; import { diff --git a/packages/frontend/core/src/modules/favorite/services/old/adapter.ts b/packages/frontend/core/src/modules/favorite/services/old/adapter.ts index 069ba49a22aa4..3bcf5fcc63d28 100644 --- a/packages/frontend/core/src/modules/favorite/services/old/adapter.ts +++ b/packages/frontend/core/src/modules/favorite/services/old/adapter.ts @@ -1,7 +1,6 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ // the adapter is to bridge the workspace rootdoc & native js bindings +import type { WorkspaceService } from '@affine/core/modules/workspace'; import { createYProxy, type Y } from '@blocksuite/affine/store'; -import type { WorkspaceService } from '@toeverything/infra'; import { LiveData, Service } from '@toeverything/infra'; import { defaultsDeep } from 'lodash-es'; import { Observable } from 'rxjs'; @@ -155,6 +154,7 @@ class WorkspacePropertiesAdapter { setJournalPageDateString(id: string, date: string) { this.ensurePageProperties(id); const pageProperties = this.pageProperties?.[id]; + // oxlint-disable-next-line no-non-null-assertion pageProperties!.system[PageSystemPropertyId.Journal].value = date; } diff --git a/packages/frontend/core/src/modules/favorite/stores/favorite.ts b/packages/frontend/core/src/modules/favorite/stores/favorite.ts index 520a22dacaaa7..c4ca68453f93c 100644 --- a/packages/frontend/core/src/modules/favorite/stores/favorite.ts +++ b/packages/frontend/core/src/modules/favorite/stores/favorite.ts @@ -1,8 +1,9 @@ -import type { WorkspaceDBService, WorkspaceService } from '@toeverything/infra'; import { LiveData, Store } from '@toeverything/infra'; import { map } from 'rxjs'; import { AuthService, type WorkspaceServerService } from '../../cloud'; +import type { WorkspaceDBService } from '../../db'; +import type { WorkspaceService } from '../../workspace'; import type { FavoriteSupportTypeUnion } from '../constant'; import { isFavoriteSupportType } from '../constant'; diff --git a/packages/common/infra/src/modules/feature-flag/constant.ts b/packages/frontend/core/src/modules/feature-flag/constant.ts similarity index 100% rename from packages/common/infra/src/modules/feature-flag/constant.ts rename to packages/frontend/core/src/modules/feature-flag/constant.ts diff --git a/packages/common/infra/src/modules/feature-flag/entities/flags.ts b/packages/frontend/core/src/modules/feature-flag/entities/flags.ts similarity index 95% rename from packages/common/infra/src/modules/feature-flag/entities/flags.ts rename to packages/frontend/core/src/modules/feature-flag/entities/flags.ts index b21f1c58ac124..8d6e71da83ef5 100644 --- a/packages/common/infra/src/modules/feature-flag/entities/flags.ts +++ b/packages/frontend/core/src/modules/feature-flag/entities/flags.ts @@ -1,7 +1,6 @@ +import { Entity, LiveData } from '@toeverything/infra'; import { NEVER } from 'rxjs'; -import { Entity } from '../../../framework'; -import { LiveData } from '../../../livedata'; import type { GlobalStateService } from '../../storage'; import { AFFINE_FLAGS } from '../constant'; import type { FlagInfo } from '../types'; diff --git a/packages/common/infra/src/modules/feature-flag/index.ts b/packages/frontend/core/src/modules/feature-flag/index.ts similarity index 89% rename from packages/common/infra/src/modules/feature-flag/index.ts rename to packages/frontend/core/src/modules/feature-flag/index.ts index e52725bf5e83e..39390ccea3c2b 100644 --- a/packages/common/infra/src/modules/feature-flag/index.ts +++ b/packages/frontend/core/src/modules/feature-flag/index.ts @@ -1,4 +1,5 @@ -import type { Framework } from '../../framework'; +import type { Framework } from '@toeverything/infra'; + import { GlobalStateService } from '../storage'; import { Flags } from './entities/flags'; import { FeatureFlagService } from './services/feature-flag'; diff --git a/packages/common/infra/src/modules/feature-flag/services/feature-flag.ts b/packages/frontend/core/src/modules/feature-flag/services/feature-flag.ts similarity index 95% rename from packages/common/infra/src/modules/feature-flag/services/feature-flag.ts rename to packages/frontend/core/src/modules/feature-flag/services/feature-flag.ts index efe1fbc84e695..cc0ee7a127d9f 100644 --- a/packages/common/infra/src/modules/feature-flag/services/feature-flag.ts +++ b/packages/frontend/core/src/modules/feature-flag/services/feature-flag.ts @@ -1,6 +1,6 @@ +import { OnEvent, Service } from '@toeverything/infra'; import { distinctUntilChanged, skip } from 'rxjs'; -import { OnEvent, Service } from '../../../framework'; import { ApplicationStarted } from '../../lifecycle'; import type { Workspace } from '../../workspace'; import { WorkspaceInitialized } from '../../workspace/events'; diff --git a/packages/common/infra/src/modules/feature-flag/types.ts b/packages/frontend/core/src/modules/feature-flag/types.ts similarity index 100% rename from packages/common/infra/src/modules/feature-flag/types.ts rename to packages/frontend/core/src/modules/feature-flag/types.ts diff --git a/packages/common/infra/src/modules/global-context/entities/global-context.ts b/packages/frontend/core/src/modules/global-context/entities/global-context.ts similarity index 89% rename from packages/common/infra/src/modules/global-context/entities/global-context.ts rename to packages/frontend/core/src/modules/global-context/entities/global-context.ts index 61121004bbbdf..a589a3b789a1c 100644 --- a/packages/common/infra/src/modules/global-context/entities/global-context.ts +++ b/packages/frontend/core/src/modules/global-context/entities/global-context.ts @@ -1,8 +1,5 @@ import type { DocMode } from '@blocksuite/affine/blocks'; - -import { Entity } from '../../../framework'; -import { LiveData } from '../../../livedata'; -import { MemoryMemento } from '../../../storage'; +import { Entity, LiveData, MemoryMemento } from '@toeverything/infra'; export class GlobalContext extends Entity { memento = new MemoryMemento(); diff --git a/packages/common/infra/src/modules/global-context/index.ts b/packages/frontend/core/src/modules/global-context/index.ts similarity index 85% rename from packages/common/infra/src/modules/global-context/index.ts rename to packages/frontend/core/src/modules/global-context/index.ts index f93aa05a0e0d7..481238ed39bfe 100644 --- a/packages/common/infra/src/modules/global-context/index.ts +++ b/packages/frontend/core/src/modules/global-context/index.ts @@ -1,6 +1,7 @@ export { GlobalContextService } from './services/global-context'; -import type { Framework } from '../../framework'; +import type { Framework } from '@toeverything/infra'; + import { GlobalContext } from './entities/global-context'; import { GlobalContextService } from './services/global-context'; diff --git a/packages/common/infra/src/modules/global-context/services/global-context.ts b/packages/frontend/core/src/modules/global-context/services/global-context.ts similarity index 78% rename from packages/common/infra/src/modules/global-context/services/global-context.ts rename to packages/frontend/core/src/modules/global-context/services/global-context.ts index a0a8db0dab0fd..028272c2627a9 100644 --- a/packages/common/infra/src/modules/global-context/services/global-context.ts +++ b/packages/frontend/core/src/modules/global-context/services/global-context.ts @@ -1,4 +1,5 @@ -import { Service } from '../../../framework'; +import { Service } from '@toeverything/infra'; + import { GlobalContext } from '../entities/global-context'; export class GlobalContextService extends Service { diff --git a/packages/frontend/core/src/modules/i18n/entities/i18n.ts b/packages/frontend/core/src/modules/i18n/entities/i18n.ts index 83991f351956c..a343c7cd83acc 100644 --- a/packages/frontend/core/src/modules/i18n/entities/i18n.ts +++ b/packages/frontend/core/src/modules/i18n/entities/i18n.ts @@ -6,10 +6,11 @@ import { type Language, SUPPORTED_LANGUAGES, } from '@affine/i18n'; -import type { GlobalCache } from '@toeverything/infra'; import { effect, Entity, fromPromise, LiveData } from '@toeverything/infra'; import { catchError, EMPTY, exhaustMap, mergeMap } from 'rxjs'; +import type { GlobalCache } from '../../storage'; + export type LanguageInfo = { key: Language; name: string; diff --git a/packages/frontend/core/src/modules/i18n/index.ts b/packages/frontend/core/src/modules/i18n/index.ts index 9f031446e4225..0d78e556b868d 100644 --- a/packages/frontend/core/src/modules/i18n/index.ts +++ b/packages/frontend/core/src/modules/i18n/index.ts @@ -1,5 +1,6 @@ -import { type Framework, GlobalCache } from '@toeverything/infra'; +import { type Framework } from '@toeverything/infra'; +import { GlobalCache } from '../storage'; import { I18nProvider } from './context'; import { I18n, type LanguageInfo } from './entities/i18n'; import { I18nService } from './services/i18n'; diff --git a/packages/frontend/core/src/modules/import-template/index.ts b/packages/frontend/core/src/modules/import-template/index.ts index a0d4c8e42d9c6..b8c86069cd96b 100644 --- a/packages/frontend/core/src/modules/import-template/index.ts +++ b/packages/frontend/core/src/modules/import-template/index.ts @@ -1,6 +1,7 @@ -import { type Framework, WorkspacesService } from '@toeverything/infra'; +import { type Framework } from '@toeverything/infra'; import { FetchService } from '../cloud'; +import { WorkspacesService } from '../workspace'; import { ImportTemplateDialog } from './entities/dialog'; import { TemplateDownloader } from './entities/downloader'; import { TemplateDownloaderService } from './services/downloader'; diff --git a/packages/frontend/core/src/modules/import-template/services/import.ts b/packages/frontend/core/src/modules/import-template/services/import.ts index 27ae74f783346..1cb24b3b7584c 100644 --- a/packages/frontend/core/src/modules/import-template/services/import.ts +++ b/packages/frontend/core/src/modules/import-template/services/import.ts @@ -1,6 +1,8 @@ import { type DocMode, ZipTransformer } from '@blocksuite/affine/blocks'; -import type { WorkspaceMetadata, WorkspacesService } from '@toeverything/infra'; -import { DocsService, Service } from '@toeverything/infra'; +import { Service } from '@toeverything/infra'; + +import { DocsService } from '../../doc'; +import type { WorkspaceMetadata, WorkspacesService } from '../../workspace'; export class ImportTemplateService extends Service { constructor(private readonly workspacesService: WorkspacesService) { diff --git a/packages/frontend/core/src/modules/index.ts b/packages/frontend/core/src/modules/index.ts index 9e274b0d17363..ad4dffb68d802 100644 --- a/packages/frontend/core/src/modules/index.ts +++ b/packages/frontend/core/src/modules/index.ts @@ -1,12 +1,14 @@ import { configureQuotaModule } from '@affine/core/modules/quota'; -import { configureInfraModules, type Framework } from '@toeverything/infra'; +import { type Framework } from '@toeverything/infra'; import { configureAppSidebarModule } from './app-sidebar'; import { configAtMenuConfigModule } from './at-menu-config'; import { configureCloudModule } from './cloud'; import { configureCollectionModule } from './collection'; +import { configureWorkspaceDBModule } from './db'; import { configureDialogModule } from './dialogs'; import { configureDndModule } from './dnd'; +import { configureDocModule } from './doc'; import { configureDocDisplayMetaModule } from './doc-display-meta'; import { configureDocInfoModule } from './doc-info'; import { configureDocLinksModule } from './doc-link'; @@ -15,9 +17,12 @@ import { configureEditorModule } from './editor'; import { configureEditorSettingModule } from './editor-setting'; import { configureExplorerModule } from './explorer'; import { configureFavoriteModule } from './favorite'; +import { configureFeatureFlagModule } from './feature-flag'; +import { configureGlobalContextModule } from './global-context'; import { configureI18nModule } from './i18n'; import { configureImportTemplateModule } from './import-template'; import { configureJournalModule } from './journal'; +import { configureLifecycleModule } from './lifecycle'; import { configureNavigationModule } from './navigation'; import { configureOpenInApp } from './open-in-app'; import { configureOrganizeModule } from './organize'; @@ -27,7 +32,10 @@ import { configurePermissionsModule } from './permissions'; import { configureQuickSearchModule } from './quicksearch'; import { configureShareDocsModule } from './share-doc'; import { configureShareSettingModule } from './share-setting'; -import { configureCommonGlobalStorageImpls } from './storage'; +import { + configureCommonGlobalStorageImpls, + configureGlobalStorageModule, +} from './storage'; import { configureSystemFontFamilyModule } from './system-font-family'; import { configureTagModule } from './tag'; import { configureTelemetryModule } from './telemetry'; @@ -35,10 +43,17 @@ import { configureAppThemeModule } from './theme'; import { configureThemeEditorModule } from './theme-editor'; import { configureUrlModule } from './url'; import { configureUserspaceModule } from './userspace'; +import { configureWorkspaceModule } from './workspace'; export function configureCommonModules(framework: Framework) { configureI18nModule(framework); - configureInfraModules(framework); + configureWorkspaceModule(framework); + configureDocModule(framework); + configureWorkspaceDBModule(framework); + configureGlobalStorageModule(framework); + configureGlobalContextModule(framework); + configureLifecycleModule(framework); + configureFeatureFlagModule(framework); configureCollectionModule(framework); configureNavigationModule(framework); configureTagModule(framework); diff --git a/packages/frontend/core/src/modules/journal/index.ts b/packages/frontend/core/src/modules/journal/index.ts index 47838bc69fb3a..3f581038b80bc 100644 --- a/packages/frontend/core/src/modules/journal/index.ts +++ b/packages/frontend/core/src/modules/journal/index.ts @@ -1,12 +1,8 @@ -import { - DocScope, - DocService, - DocsService, - type Framework, - WorkspaceScope, -} from '@toeverything/infra'; +import { type Framework } from '@toeverything/infra'; +import { DocScope, DocService, DocsService } from '../doc'; import { EditorSettingService } from '../editor-setting'; +import { WorkspaceScope } from '../workspace'; import { JournalService } from './services/journal'; import { JournalDocService } from './services/journal-doc'; import { JournalStore } from './store/journal'; diff --git a/packages/frontend/core/src/modules/journal/services/journal-doc.ts b/packages/frontend/core/src/modules/journal/services/journal-doc.ts index ead865bc1b582..4bdf255b1f884 100644 --- a/packages/frontend/core/src/modules/journal/services/journal-doc.ts +++ b/packages/frontend/core/src/modules/journal/services/journal-doc.ts @@ -1,5 +1,6 @@ -import { type DocService, Service } from '@toeverything/infra'; +import { Service } from '@toeverything/infra'; +import type { DocService } from '../../doc'; import type { JournalService } from './journal'; export class JournalDocService extends Service { diff --git a/packages/frontend/core/src/modules/journal/services/journal.ts b/packages/frontend/core/src/modules/journal/services/journal.ts index d0620bc908746..6796d668461a6 100644 --- a/packages/frontend/core/src/modules/journal/services/journal.ts +++ b/packages/frontend/core/src/modules/journal/services/journal.ts @@ -1,8 +1,9 @@ import { Text } from '@blocksuite/affine/store'; -import type { DocProps, DocsService } from '@toeverything/infra'; +import type { DocProps } from '@toeverything/infra'; import { initDocFromProps, LiveData, Service } from '@toeverything/infra'; import dayjs from 'dayjs'; +import type { DocsService } from '../../doc'; import type { EditorSettingService } from '../../editor-setting'; import type { JournalStore } from '../store/journal'; diff --git a/packages/frontend/core/src/modules/journal/store/journal.ts b/packages/frontend/core/src/modules/journal/store/journal.ts index 9b935c997b353..a44d9edfd1b0f 100644 --- a/packages/frontend/core/src/modules/journal/store/journal.ts +++ b/packages/frontend/core/src/modules/journal/store/journal.ts @@ -1,7 +1,8 @@ -import type { DocsService } from '@toeverything/infra'; import { LiveData, Store } from '@toeverything/infra'; import type { Observable } from 'rxjs'; +import type { DocsService } from '../../doc'; + function isJournalString(j?: string | false) { return j ? !!j?.match(/^\d{4}-\d{2}-\d{2}$/) : false; } diff --git a/packages/common/infra/src/modules/lifecycle/index.ts b/packages/frontend/core/src/modules/lifecycle/index.ts similarity index 82% rename from packages/common/infra/src/modules/lifecycle/index.ts rename to packages/frontend/core/src/modules/lifecycle/index.ts index aeea99c451710..dcd4cecdfa58d 100644 --- a/packages/common/infra/src/modules/lifecycle/index.ts +++ b/packages/frontend/core/src/modules/lifecycle/index.ts @@ -1,4 +1,5 @@ -import type { Framework } from '../../framework'; +import type { Framework } from '@toeverything/infra'; + import { LifecycleService } from './service/lifecycle'; export { diff --git a/packages/common/infra/src/modules/lifecycle/service/lifecycle.ts b/packages/frontend/core/src/modules/lifecycle/service/lifecycle.ts similarity index 91% rename from packages/common/infra/src/modules/lifecycle/service/lifecycle.ts rename to packages/frontend/core/src/modules/lifecycle/service/lifecycle.ts index 51d1148b68bb2..3e3ef8c0f0f36 100644 --- a/packages/common/infra/src/modules/lifecycle/service/lifecycle.ts +++ b/packages/frontend/core/src/modules/lifecycle/service/lifecycle.ts @@ -1,4 +1,4 @@ -import { createEvent, Service } from '../../../framework'; +import { createEvent, Service } from '@toeverything/infra'; /** * Event that is emitted when application is started. diff --git a/packages/frontend/core/src/modules/navigation/index.ts b/packages/frontend/core/src/modules/navigation/index.ts index a6adcbef3fea0..48e0f0a44338c 100644 --- a/packages/frontend/core/src/modules/navigation/index.ts +++ b/packages/frontend/core/src/modules/navigation/index.ts @@ -6,9 +6,10 @@ export { } from './utils'; export { NavigationButtons } from './view/navigation-buttons'; -import { type Framework, WorkspaceScope } from '@toeverything/infra'; +import { type Framework } from '@toeverything/infra'; import { WorkbenchService } from '../workbench/services/workbench'; +import { WorkspaceScope } from '../workspace'; import { Navigator } from './entities/navigator'; import { NavigatorService } from './services/navigator'; diff --git a/packages/frontend/core/src/modules/open-in-app/index.ts b/packages/frontend/core/src/modules/open-in-app/index.ts index f5312b4c45b4f..0de7f851ec052 100644 --- a/packages/frontend/core/src/modules/open-in-app/index.ts +++ b/packages/frontend/core/src/modules/open-in-app/index.ts @@ -1,9 +1,7 @@ -import { - type Framework, - GlobalState, - WorkspacesService, -} from '@toeverything/infra'; +import { type Framework } from '@toeverything/infra'; +import { GlobalState } from '../storage'; +import { WorkspacesService } from '../workspace'; import { OpenInAppService } from './services'; export { OpenInAppService, OpenLinkMode } from './services'; diff --git a/packages/frontend/core/src/modules/open-in-app/services/index.ts b/packages/frontend/core/src/modules/open-in-app/services/index.ts index 2064dcaa103a0..0cf07fbb8c1d9 100644 --- a/packages/frontend/core/src/modules/open-in-app/services/index.ts +++ b/packages/frontend/core/src/modules/open-in-app/services/index.ts @@ -1,8 +1,9 @@ -import type { GlobalState, WorkspacesService } from '@toeverything/infra'; import { LiveData, OnEvent, Service } from '@toeverything/infra'; import { resolveLinkToDoc } from '../../navigation'; +import type { GlobalState } from '../../storage'; import { WorkbenchLocationChanged } from '../../workbench/services/workbench'; +import type { WorkspacesService } from '../../workspace'; import { getLocalWorkspaceIds } from '../../workspace-engine/impls/local'; const storageKey = 'open-link-mode'; diff --git a/packages/frontend/core/src/modules/organize/index.ts b/packages/frontend/core/src/modules/organize/index.ts index 89388b1f17342..8cb8fe16a190f 100644 --- a/packages/frontend/core/src/modules/organize/index.ts +++ b/packages/frontend/core/src/modules/organize/index.ts @@ -1,9 +1,7 @@ -import { - type Framework, - WorkspaceDBService, - WorkspaceScope, -} from '@toeverything/infra'; +import { type Framework } from '@toeverything/infra'; +import { WorkspaceDBService } from '../db'; +import { WorkspaceScope } from '../workspace'; import { FolderNode } from './entities/folder-node'; import { FolderTree } from './entities/folder-tree'; import { OrganizeService } from './services/organize'; diff --git a/packages/frontend/core/src/modules/organize/stores/folder.ts b/packages/frontend/core/src/modules/organize/stores/folder.ts index fd107324b4f52..f423b5201c8b2 100644 --- a/packages/frontend/core/src/modules/organize/stores/folder.ts +++ b/packages/frontend/core/src/modules/organize/stores/folder.ts @@ -1,6 +1,7 @@ -import type { WorkspaceDBService } from '@toeverything/infra'; import { Store } from '@toeverything/infra'; +import type { WorkspaceDBService } from '../../db'; + export class FolderStore extends Store { constructor(private readonly dbService: WorkspaceDBService) { super(); diff --git a/packages/frontend/core/src/modules/pdf/index.ts b/packages/frontend/core/src/modules/pdf/index.ts index 998173d9ce585..5a6a4f5a15c86 100644 --- a/packages/frontend/core/src/modules/pdf/index.ts +++ b/packages/frontend/core/src/modules/pdf/index.ts @@ -1,6 +1,6 @@ import type { Framework } from '@toeverything/infra'; -import { WorkspaceScope } from '@toeverything/infra'; +import { WorkspaceScope } from '../workspace'; import { PDF } from './entities/pdf'; import { PDFPage } from './entities/pdf-page'; import { PDFService } from './services/pdf'; diff --git a/packages/frontend/core/src/modules/peek-view/index.ts b/packages/frontend/core/src/modules/peek-view/index.ts index 0956ad759653e..ad170c5961dac 100644 --- a/packages/frontend/core/src/modules/peek-view/index.ts +++ b/packages/frontend/core/src/modules/peek-view/index.ts @@ -1,6 +1,7 @@ -import { type Framework, WorkspaceScope } from '@toeverything/infra'; +import { type Framework } from '@toeverything/infra'; import { WorkbenchService } from '../workbench'; +import { WorkspaceScope } from '../workspace'; import { PeekViewEntity } from './entities/peek-view'; import { PeekViewService } from './services/peek-view'; diff --git a/packages/frontend/core/src/modules/peek-view/view/utils.ts b/packages/frontend/core/src/modules/peek-view/view/utils.ts index 27dadbcc61268..183cbaa4b3f44 100644 --- a/packages/frontend/core/src/modules/peek-view/view/utils.ts +++ b/packages/frontend/core/src/modules/peek-view/view/utils.ts @@ -1,15 +1,12 @@ import type { DefaultOpenProperty } from '@affine/core/components/doc-properties'; import type { DocMode } from '@blocksuite/affine/blocks'; -import type { Doc } from '@toeverything/infra'; -import { - DocsService, - useLiveData, - useService, - WorkspaceService, -} from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import { useEffect, useLayoutEffect, useRef, useState } from 'react'; +import type { Doc } from '../../doc'; +import { DocsService } from '../../doc'; import { type Editor, type EditorSelector, EditorsService } from '../../editor'; +import { WorkspaceService } from '../../workspace'; export const useEditor = ( pageId: string, diff --git a/packages/frontend/core/src/modules/permissions/entities/members.ts b/packages/frontend/core/src/modules/permissions/entities/members.ts index e0cf37df89491..4b810a74e92ac 100644 --- a/packages/frontend/core/src/modules/permissions/entities/members.ts +++ b/packages/frontend/core/src/modules/permissions/entities/members.ts @@ -1,5 +1,4 @@ import type { GetMembersByWorkspaceIdQuery } from '@affine/graphql'; -import type { WorkspaceService } from '@toeverything/infra'; import { backoffRetry, catchErrorInto, @@ -13,6 +12,7 @@ import { import { EMPTY, map, mergeMap, switchMap } from 'rxjs'; import { isBackendError, isNetworkError } from '../../cloud'; +import type { WorkspaceService } from '../../workspace'; import type { WorkspaceMembersStore } from '../stores/members'; export type Member = diff --git a/packages/frontend/core/src/modules/permissions/entities/permission.ts b/packages/frontend/core/src/modules/permissions/entities/permission.ts index 6e8ad03b269e5..e9e4eb761efba 100644 --- a/packages/frontend/core/src/modules/permissions/entities/permission.ts +++ b/packages/frontend/core/src/modules/permissions/entities/permission.ts @@ -3,7 +3,6 @@ import type { Permission, WorkspaceInviteLinkExpireTime, } from '@affine/graphql'; -import type { WorkspaceService } from '@toeverything/infra'; import { backoffRetry, catchErrorInto, @@ -17,6 +16,7 @@ import { import { EMPTY, exhaustMap, mergeMap } from 'rxjs'; import { isBackendError, isNetworkError } from '../../cloud'; +import type { WorkspaceService } from '../../workspace'; import type { WorkspacePermissionStore } from '../stores/permission'; const logger = new DebugLogger('affine:workspace-permission'); diff --git a/packages/frontend/core/src/modules/permissions/index.ts b/packages/frontend/core/src/modules/permissions/index.ts index ca67a5aab8f1a..feefe3a7b99a3 100644 --- a/packages/frontend/core/src/modules/permissions/index.ts +++ b/packages/frontend/core/src/modules/permissions/index.ts @@ -2,14 +2,14 @@ export type { Member } from './entities/members'; export { WorkspaceMembersService } from './services/members'; export { WorkspacePermissionService } from './services/permission'; +import { type Framework } from '@toeverything/infra'; + +import { WorkspaceServerService } from '../cloud'; import { - type Framework, WorkspaceScope, WorkspaceService, WorkspacesService, -} from '@toeverything/infra'; - -import { WorkspaceServerService } from '../cloud'; +} from '../workspace'; import { WorkspaceMembers } from './entities/members'; import { WorkspacePermission } from './entities/permission'; import { WorkspaceMembersService } from './services/members'; diff --git a/packages/frontend/core/src/modules/permissions/services/permission.ts b/packages/frontend/core/src/modules/permissions/services/permission.ts index 092e08fad8d4c..73f89f421c776 100644 --- a/packages/frontend/core/src/modules/permissions/services/permission.ts +++ b/packages/frontend/core/src/modules/permissions/services/permission.ts @@ -1,6 +1,6 @@ -import type { WorkspaceService, WorkspacesService } from '@toeverything/infra'; import { Service } from '@toeverything/infra'; +import type { WorkspaceService, WorkspacesService } from '../../workspace'; import { WorkspacePermission } from '../entities/permission'; import type { WorkspacePermissionStore } from '../stores/permission'; diff --git a/packages/frontend/core/src/modules/quicksearch/impls/commands.ts b/packages/frontend/core/src/modules/quicksearch/impls/commands.ts index e275a5a73aa76..b6480284d33ee 100644 --- a/packages/frontend/core/src/modules/quicksearch/impls/commands.ts +++ b/packages/frontend/core/src/modules/quicksearch/impls/commands.ts @@ -5,10 +5,10 @@ import { PreconditionStrategy, } from '@affine/core/commands'; import type { DocMode } from '@blocksuite/affine/blocks'; -import type { GlobalContextService } from '@toeverything/infra'; import { Entity, LiveData } from '@toeverything/infra'; import Fuse from 'fuse.js'; +import type { GlobalContextService } from '../../global-context'; import type { QuickSearchSession } from '../providers/quick-search-provider'; import type { QuickSearchGroup } from '../types/group'; import type { QuickSearchItem } from '../types/item'; diff --git a/packages/frontend/core/src/modules/quicksearch/impls/docs.ts b/packages/frontend/core/src/modules/quicksearch/impls/docs.ts index 64115daf5a8ad..a2edce49ae4cd 100644 --- a/packages/frontend/core/src/modules/quicksearch/impls/docs.ts +++ b/packages/frontend/core/src/modules/quicksearch/impls/docs.ts @@ -1,4 +1,3 @@ -import type { DocRecord, DocsService } from '@toeverything/infra'; import { effect, Entity, @@ -9,6 +8,7 @@ import { import { truncate } from 'lodash-es'; import { EMPTY, map, mergeMap, of, switchMap } from 'rxjs'; +import type { DocRecord, DocsService } from '../../doc'; import type { DocDisplayMetaService } from '../../doc-display-meta'; import type { DocsSearchService } from '../../docs-search'; import type { QuickSearchSession } from '../providers/quick-search-provider'; diff --git a/packages/frontend/core/src/modules/quicksearch/impls/external-links.ts b/packages/frontend/core/src/modules/quicksearch/impls/external-links.ts index d65bc016ecd78..56196aa3f6959 100644 --- a/packages/frontend/core/src/modules/quicksearch/impls/external-links.ts +++ b/packages/frontend/core/src/modules/quicksearch/impls/external-links.ts @@ -1,9 +1,9 @@ import { LinkIcon } from '@blocksuite/icons/rc'; -import type { WorkspaceService } from '@toeverything/infra'; import { Entity, LiveData } from '@toeverything/infra'; import { resolveLinkToDoc } from '../../navigation'; import { isLink } from '../../navigation/utils'; +import type { WorkspaceService } from '../../workspace'; import type { QuickSearchSession } from '../providers/quick-search-provider'; import type { QuickSearchItem } from '../types/item'; diff --git a/packages/frontend/core/src/modules/quicksearch/impls/links.ts b/packages/frontend/core/src/modules/quicksearch/impls/links.ts index 756c152e1c043..353163d8ad797 100644 --- a/packages/frontend/core/src/modules/quicksearch/impls/links.ts +++ b/packages/frontend/core/src/modules/quicksearch/impls/links.ts @@ -1,12 +1,13 @@ import type { ReferenceParams } from '@blocksuite/affine/blocks'; import { BlockLinkIcon, EdgelessIcon, PageIcon } from '@blocksuite/icons/rc'; -import type { DocsService, WorkspaceService } from '@toeverything/infra'; import { Entity, LiveData } from '@toeverything/infra'; import { omit, truncate } from 'lodash-es'; +import type { DocsService } from '../../doc'; import type { DocDisplayMetaService } from '../../doc-display-meta'; import { resolveLinkToDoc } from '../../navigation'; import { isLink } from '../../navigation/utils'; +import type { WorkspaceService } from '../../workspace'; import type { QuickSearchSession } from '../providers/quick-search-provider'; import type { QuickSearchItem } from '../types/item'; diff --git a/packages/frontend/core/src/modules/quicksearch/index.ts b/packages/frontend/core/src/modules/quicksearch/index.ts index 85cd2445e0680..b469c89b9f772 100644 --- a/packages/frontend/core/src/modules/quicksearch/index.ts +++ b/packages/frontend/core/src/modules/quicksearch/index.ts @@ -1,19 +1,19 @@ -import { - DocsService, - type Framework, - GlobalContextService, - WorkspaceLocalState, - WorkspaceScope, - WorkspaceService, -} from '@toeverything/infra'; +import { type Framework } from '@toeverything/infra'; import { CollectionService } from '../collection'; import { WorkspaceDialogService } from '../dialogs'; -import { DocDisplayMetaService } from '../doc-display-meta/services/doc-display-meta'; +import { DocsService } from '../doc'; +import { DocDisplayMetaService } from '../doc-display-meta'; import { DocsSearchService } from '../docs-search'; +import { GlobalContextService } from '../global-context'; import { JournalService } from '../journal'; import { TagService } from '../tag'; import { WorkbenchService } from '../workbench'; +import { + WorkspaceLocalState, + WorkspaceScope, + WorkspaceService, +} from '../workspace'; import { QuickSearch } from './entities/quick-search'; import { CollectionsQuickSearchSession } from './impls/collections'; import { CommandsQuickSearchSession } from './impls/commands'; diff --git a/packages/frontend/core/src/modules/quicksearch/services/cmdk.ts b/packages/frontend/core/src/modules/quicksearch/services/cmdk.ts index c6b57992dab19..f64d5a52f7d02 100644 --- a/packages/frontend/core/src/modules/quicksearch/services/cmdk.ts +++ b/packages/frontend/core/src/modules/quicksearch/services/cmdk.ts @@ -1,8 +1,9 @@ import { track } from '@affine/track'; import { Text } from '@blocksuite/affine/store'; -import type { DocProps, DocsService } from '@toeverything/infra'; +import type { DocProps } from '@toeverything/infra'; import { Service } from '@toeverything/infra'; +import type { DocsService } from '../../doc'; import { EditorSettingService } from '../../editor-setting'; import type { WorkbenchService } from '../../workbench'; import { CollectionsQuickSearchSession } from '../impls/collections'; diff --git a/packages/frontend/core/src/modules/quicksearch/services/recent-pages.ts b/packages/frontend/core/src/modules/quicksearch/services/recent-pages.ts index 1712e3a36bc1b..34106cbbc52ae 100644 --- a/packages/frontend/core/src/modules/quicksearch/services/recent-pages.ts +++ b/packages/frontend/core/src/modules/quicksearch/services/recent-pages.ts @@ -1,10 +1,8 @@ -import type { - DocRecord, - DocsService, - WorkspaceLocalState, -} from '@toeverything/infra'; import { Service } from '@toeverything/infra'; +import type { DocRecord, DocsService } from '../../doc'; +import type { WorkspaceLocalState } from '../../workspace'; + const RECENT_PAGES_LIMIT = 3; // adjust this? const RECENT_PAGES_KEY = 'recent-pages'; diff --git a/packages/frontend/core/src/modules/quota/entities/quota.ts b/packages/frontend/core/src/modules/quota/entities/quota.ts index 6f2ac774fec5c..19e2ed4ad255b 100644 --- a/packages/frontend/core/src/modules/quota/entities/quota.ts +++ b/packages/frontend/core/src/modules/quota/entities/quota.ts @@ -1,6 +1,5 @@ import { DebugLogger } from '@affine/debug'; import type { WorkspaceQuotaQuery } from '@affine/graphql'; -import type { WorkspaceService } from '@toeverything/infra'; import { backoffRetry, catchErrorInto, @@ -17,6 +16,7 @@ import bytes from 'bytes'; import { EMPTY, map, mergeMap } from 'rxjs'; import { isBackendError, isNetworkError } from '../../cloud'; +import type { WorkspaceService } from '../../workspace'; import type { WorkspaceQuotaStore } from '../stores/quota'; type QuotaType = WorkspaceQuotaQuery['workspace']['quota']; diff --git a/packages/frontend/core/src/modules/quota/index.ts b/packages/frontend/core/src/modules/quota/index.ts index 790fe42796f67..db5b5bc2bc19c 100644 --- a/packages/frontend/core/src/modules/quota/index.ts +++ b/packages/frontend/core/src/modules/quota/index.ts @@ -1,13 +1,10 @@ export { WorkspaceQuotaService } from './services/quota'; export { QuotaCheck } from './views/quota-check'; -import { - type Framework, - WorkspaceScope, - WorkspaceService, -} from '@toeverything/infra'; +import { type Framework } from '@toeverything/infra'; import { WorkspaceServerService } from '../cloud'; +import { WorkspaceScope, WorkspaceService } from '../workspace'; import { WorkspaceQuota } from './entities/quota'; import { WorkspaceQuotaService } from './services/quota'; import { WorkspaceQuotaStore } from './stores/quota'; diff --git a/packages/frontend/core/src/modules/quota/views/quota-check.tsx b/packages/frontend/core/src/modules/quota/views/quota-check.tsx index 62394d451b3a4..e28fa19fe64bc 100644 --- a/packages/frontend/core/src/modules/quota/views/quota-check.tsx +++ b/packages/frontend/core/src/modules/quota/views/quota-check.tsx @@ -1,14 +1,10 @@ import { useConfirmModal } from '@affine/component'; import { GlobalDialogService } from '@affine/core/modules/dialogs'; import { type I18nString, useI18n } from '@affine/i18n'; -import { - useLiveData, - useService, - type WorkspaceMetadata, - WorkspacesService, -} from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import { useCallback, useEffect } from 'react'; +import { type WorkspaceMetadata, WorkspacesService } from '../../workspace'; import { WorkspaceQuotaService } from '../services/quota'; import * as styles from './styles.css'; diff --git a/packages/frontend/core/src/modules/share-doc/entities/share-docs-list.ts b/packages/frontend/core/src/modules/share-doc/entities/share-docs-list.ts index 6a29d4b9343c0..17fb637cb8a64 100644 --- a/packages/frontend/core/src/modules/share-doc/entities/share-docs-list.ts +++ b/packages/frontend/core/src/modules/share-doc/entities/share-docs-list.ts @@ -1,6 +1,5 @@ import { DebugLogger } from '@affine/debug'; import type { GetWorkspacePublicPagesQuery } from '@affine/graphql'; -import type { GlobalCache, WorkspaceService } from '@toeverything/infra'; import { backoffRetry, catchErrorInto, @@ -15,6 +14,8 @@ import { import { EMPTY, mergeMap } from 'rxjs'; import { isBackendError, isNetworkError } from '../../cloud'; +import type { GlobalCache } from '../../storage'; +import type { WorkspaceService } from '../../workspace'; import type { ShareDocsStore } from '../stores/share-docs'; type ShareDocListType = diff --git a/packages/frontend/core/src/modules/share-doc/entities/share-info.ts b/packages/frontend/core/src/modules/share-doc/entities/share-info.ts index b99c3c8773361..926646d0e3d5d 100644 --- a/packages/frontend/core/src/modules/share-doc/entities/share-info.ts +++ b/packages/frontend/core/src/modules/share-doc/entities/share-info.ts @@ -2,7 +2,6 @@ import type { GetWorkspacePublicPageByIdQuery, PublicPageMode, } from '@affine/graphql'; -import type { DocService, WorkspaceService } from '@toeverything/infra'; import { backoffRetry, catchErrorInto, @@ -17,6 +16,8 @@ import { import { switchMap } from 'rxjs'; import { isBackendError, isNetworkError } from '../../cloud'; +import type { DocService } from '../../doc'; +import type { WorkspaceService } from '../../workspace'; import type { ShareStore } from '../stores/share'; type ShareInfoType = GetWorkspacePublicPageByIdQuery['workspace']['publicPage']; diff --git a/packages/frontend/core/src/modules/share-doc/index.ts b/packages/frontend/core/src/modules/share-doc/index.ts index 3feaa92939d34..c1a06f4bbc1d2 100644 --- a/packages/frontend/core/src/modules/share-doc/index.ts +++ b/packages/frontend/core/src/modules/share-doc/index.ts @@ -3,16 +3,15 @@ export { ShareDocsListService } from './services/share-docs-list'; export { ShareInfoService } from './services/share-info'; export { ShareReaderService } from './services/share-reader'; +import { type Framework } from '@toeverything/infra'; + +import { RawFetchProvider, WorkspaceServerService } from '../cloud'; +import { DocScope, DocService } from '../doc'; import { - DocScope, - DocService, - type Framework, WorkspaceLocalCache, WorkspaceScope, WorkspaceService, -} from '@toeverything/infra'; - -import { RawFetchProvider, WorkspaceServerService } from '../cloud'; +} from '../workspace'; import { ShareDocsList } from './entities/share-docs-list'; import { ShareInfo } from './entities/share-info'; import { ShareReader } from './entities/share-reader'; diff --git a/packages/frontend/core/src/modules/share-doc/services/share-docs-list.ts b/packages/frontend/core/src/modules/share-doc/services/share-docs-list.ts index b291fcf87b65e..ac4d2f54759ee 100644 --- a/packages/frontend/core/src/modules/share-doc/services/share-docs-list.ts +++ b/packages/frontend/core/src/modules/share-doc/services/share-docs-list.ts @@ -1,6 +1,6 @@ -import type { WorkspaceService } from '@toeverything/infra'; import { Service } from '@toeverything/infra'; +import type { WorkspaceService } from '../../workspace'; import { ShareDocsList } from '../entities/share-docs-list'; export class ShareDocsListService extends Service { diff --git a/packages/frontend/core/src/modules/share-setting/entities/share-setting.ts b/packages/frontend/core/src/modules/share-setting/entities/share-setting.ts index f77bccadd06bb..e837f676b7f79 100644 --- a/packages/frontend/core/src/modules/share-setting/entities/share-setting.ts +++ b/packages/frontend/core/src/modules/share-setting/entities/share-setting.ts @@ -1,6 +1,5 @@ import { DebugLogger } from '@affine/debug'; import type { GetWorkspaceConfigQuery, InviteLink } from '@affine/graphql'; -import type { WorkspaceService } from '@toeverything/infra'; import { backoffRetry, catchErrorInto, @@ -14,6 +13,7 @@ import { import { EMPTY, exhaustMap, mergeMap } from 'rxjs'; import { isBackendError, isNetworkError } from '../../cloud'; +import type { WorkspaceService } from '../../workspace'; import type { WorkspaceShareSettingStore } from '../stores/share-setting'; type EnableAi = GetWorkspaceConfigQuery['workspace']['enableAi']; diff --git a/packages/frontend/core/src/modules/share-setting/index.ts b/packages/frontend/core/src/modules/share-setting/index.ts index 3e24a82c49e57..7297c8db90d75 100644 --- a/packages/frontend/core/src/modules/share-setting/index.ts +++ b/packages/frontend/core/src/modules/share-setting/index.ts @@ -1,12 +1,9 @@ export { WorkspaceShareSettingService } from './services/share-setting'; -import { - type Framework, - WorkspaceScope, - WorkspaceService, -} from '@toeverything/infra'; +import { type Framework } from '@toeverything/infra'; import { WorkspaceServerService } from '../cloud'; +import { WorkspaceScope, WorkspaceService } from '../workspace'; import { WorkspaceShareSetting } from './entities/share-setting'; import { WorkspaceShareSettingService } from './services/share-setting'; import { WorkspaceShareSettingStore } from './stores/share-setting'; diff --git a/packages/frontend/core/src/modules/storage/impls/electron.ts b/packages/frontend/core/src/modules/storage/impls/electron.ts index 8e31e31db65e0..7cbb6795aed2a 100644 --- a/packages/frontend/core/src/modules/storage/impls/electron.ts +++ b/packages/frontend/core/src/modules/storage/impls/electron.ts @@ -1,7 +1,7 @@ -import type { GlobalCache, GlobalState } from '@toeverything/infra'; import { Observable } from 'rxjs'; import type { DesktopApiService } from '../../desktop-api'; +import type { GlobalCache, GlobalState } from '../providers/global'; export class ElectronGlobalState implements GlobalState { constructor(private readonly electronApi: DesktopApiService) {} diff --git a/packages/frontend/core/src/modules/storage/impls/storage.ts b/packages/frontend/core/src/modules/storage/impls/storage.ts index 99aa43caca647..d17141dd29b46 100644 --- a/packages/frontend/core/src/modules/storage/impls/storage.ts +++ b/packages/frontend/core/src/modules/storage/impls/storage.ts @@ -1,10 +1,11 @@ +import type { Memento } from '@toeverything/infra'; +import { Observable } from 'rxjs'; + import type { GlobalCache, GlobalSessionState, GlobalState, - Memento, -} from '@toeverything/infra'; -import { Observable } from 'rxjs'; +} from '../providers/global'; export class StorageMemento implements Memento { constructor( diff --git a/packages/frontend/core/src/modules/storage/index.ts b/packages/frontend/core/src/modules/storage/index.ts index 3f2cf1c5e9830..7387b70c8f202 100644 --- a/packages/frontend/core/src/modules/storage/index.ts +++ b/packages/frontend/core/src/modules/storage/index.ts @@ -1,9 +1,15 @@ -import { - type Framework, +export { GlobalCache, GlobalSessionState, GlobalState, -} from '@toeverything/infra'; +} from './providers/global'; +export { + GlobalCacheService, + GlobalSessionStateService, + GlobalStateService, +} from './services/global'; + +import { type Framework } from '@toeverything/infra'; import { DesktopApiService } from '../desktop-api'; import { ElectronGlobalCache, ElectronGlobalState } from './impls/electron'; @@ -12,6 +18,22 @@ import { LocalStorageGlobalState, SessionStorageGlobalSessionState, } from './impls/storage'; +import { + GlobalCache, + GlobalSessionState, + GlobalState, +} from './providers/global'; +import { + GlobalCacheService, + GlobalSessionStateService, + GlobalStateService, +} from './services/global'; + +export const configureGlobalStorageModule = (framework: Framework) => { + framework.service(GlobalStateService, [GlobalState]); + framework.service(GlobalCacheService, [GlobalCache]); + framework.service(GlobalSessionStateService, [GlobalSessionState]); +}; export function configureLocalStorageStateStorageImpls(framework: Framework) { framework.impl(GlobalCache, LocalStorageGlobalCache); diff --git a/packages/common/infra/src/modules/storage/providers/global.ts b/packages/frontend/core/src/modules/storage/providers/global.ts similarity index 89% rename from packages/common/infra/src/modules/storage/providers/global.ts rename to packages/frontend/core/src/modules/storage/providers/global.ts index 5f127e9521d1c..36f643c25d60c 100644 --- a/packages/common/infra/src/modules/storage/providers/global.ts +++ b/packages/frontend/core/src/modules/storage/providers/global.ts @@ -1,5 +1,4 @@ -import { createIdentifier } from '../../../framework'; -import type { Memento } from '../../../storage'; +import { createIdentifier, type Memento } from '@toeverything/infra'; /** * A memento object that stores the entire application state. diff --git a/packages/common/infra/src/modules/storage/services/global.ts b/packages/frontend/core/src/modules/storage/services/global.ts similarity index 91% rename from packages/common/infra/src/modules/storage/services/global.ts rename to packages/frontend/core/src/modules/storage/services/global.ts index d121b46a5facb..1d81810b41785 100644 --- a/packages/common/infra/src/modules/storage/services/global.ts +++ b/packages/frontend/core/src/modules/storage/services/global.ts @@ -1,4 +1,5 @@ -import { Service } from '../../../framework'; +import { Service } from '@toeverything/infra'; + import type { GlobalCache, GlobalSessionState, diff --git a/packages/frontend/core/src/modules/tag/entities/tag-list.ts b/packages/frontend/core/src/modules/tag/entities/tag-list.ts index efc5f473ffb0d..0bd77c61dd79f 100644 --- a/packages/frontend/core/src/modules/tag/entities/tag-list.ts +++ b/packages/frontend/core/src/modules/tag/entities/tag-list.ts @@ -1,6 +1,6 @@ -import type { DocsService } from '@toeverything/infra'; import { Entity, LiveData } from '@toeverything/infra'; +import type { DocsService } from '../../doc'; import { Tag } from '../entities/tag'; import type { TagStore } from '../stores/tag'; diff --git a/packages/frontend/core/src/modules/tag/entities/tag.ts b/packages/frontend/core/src/modules/tag/entities/tag.ts index 3c1076a1ee068..bee222f2d143a 100644 --- a/packages/frontend/core/src/modules/tag/entities/tag.ts +++ b/packages/frontend/core/src/modules/tag/entities/tag.ts @@ -1,6 +1,6 @@ -import type { DocsService } from '@toeverything/infra'; import { Entity, LiveData } from '@toeverything/infra'; +import type { DocsService } from '../../doc'; import type { TagStore } from '../stores/tag'; import { databaseTagColorToAffineLabel } from './utils'; diff --git a/packages/frontend/core/src/modules/tag/index.ts b/packages/frontend/core/src/modules/tag/index.ts index 5874b8072c1dc..9303bc0ab1753 100644 --- a/packages/frontend/core/src/modules/tag/index.ts +++ b/packages/frontend/core/src/modules/tag/index.ts @@ -6,13 +6,10 @@ export { export { TagService } from './service/tag'; export { useDeleteTagConfirmModal } from './view/delete-tag-modal'; -import { - DocsService, - type Framework, - WorkspaceScope, - WorkspaceService, -} from '@toeverything/infra'; +import { type Framework } from '@toeverything/infra'; +import { DocsService } from '../doc'; +import { WorkspaceScope, WorkspaceService } from '../workspace'; import { Tag } from './entities/tag'; import { TagList } from './entities/tag-list'; import { TagService } from './service/tag'; diff --git a/packages/frontend/core/src/modules/tag/stores/tag.ts b/packages/frontend/core/src/modules/tag/stores/tag.ts index f22435d2f64b9..5f8e20a6663f8 100644 --- a/packages/frontend/core/src/modules/tag/stores/tag.ts +++ b/packages/frontend/core/src/modules/tag/stores/tag.ts @@ -1,10 +1,11 @@ import type { Tag, Tag as TagSchema } from '@affine/env/filter'; import type { DocsPropertiesMeta } from '@blocksuite/affine/store'; -import type { WorkspaceService } from '@toeverything/infra'; import { LiveData, Store } from '@toeverything/infra'; import { nanoid } from 'nanoid'; import { Observable } from 'rxjs'; +import type { WorkspaceService } from '../../workspace'; + export class TagStore extends Store { get properties() { return this.workspaceService.workspace.docCollection.meta.properties; diff --git a/packages/frontend/core/src/modules/telemetry/index.ts b/packages/frontend/core/src/modules/telemetry/index.ts index f44e97605b3d4..691b0d21031af 100644 --- a/packages/frontend/core/src/modules/telemetry/index.ts +++ b/packages/frontend/core/src/modules/telemetry/index.ts @@ -1,6 +1,7 @@ -import { type Framework, GlobalContextService } from '@toeverything/infra'; +import { type Framework } from '@toeverything/infra'; import { ServersService } from '../cloud'; +import { GlobalContextService } from '../global-context'; import { TelemetryService } from './services/telemetry'; export function configureTelemetryModule(framework: Framework) { diff --git a/packages/frontend/core/src/modules/telemetry/services/telemetry.ts b/packages/frontend/core/src/modules/telemetry/services/telemetry.ts index 4ab89d43ee372..3a111fe8a8e10 100644 --- a/packages/frontend/core/src/modules/telemetry/services/telemetry.ts +++ b/packages/frontend/core/src/modules/telemetry/services/telemetry.ts @@ -1,13 +1,9 @@ import { mixpanel } from '@affine/track'; -import type { GlobalContextService } from '@toeverything/infra'; -import { - ApplicationStarted, - LiveData, - OnEvent, - Service, -} from '@toeverything/infra'; +import { LiveData, OnEvent, Service } from '@toeverything/infra'; import type { AuthAccountInfo, Server, ServersService } from '../../cloud'; +import type { GlobalContextService } from '../../global-context'; +import { ApplicationStarted } from '../../lifecycle'; @OnEvent(ApplicationStarted, e => e.onApplicationStart) export class TelemetryService extends Service { diff --git a/packages/frontend/core/src/modules/theme-editor/index.ts b/packages/frontend/core/src/modules/theme-editor/index.ts index 20ad54662ec5b..7e3e001409314 100644 --- a/packages/frontend/core/src/modules/theme-editor/index.ts +++ b/packages/frontend/core/src/modules/theme-editor/index.ts @@ -1,5 +1,6 @@ -import { type Framework, GlobalState } from '@toeverything/infra'; +import { type Framework } from '@toeverything/infra'; +import { GlobalState } from '../storage'; import { ThemeEditorService } from './services/theme-editor'; export { ThemeEditorService }; diff --git a/packages/frontend/core/src/modules/theme-editor/services/theme-editor.ts b/packages/frontend/core/src/modules/theme-editor/services/theme-editor.ts index 8d5683693b09f..8ad5c91f46b3c 100644 --- a/packages/frontend/core/src/modules/theme-editor/services/theme-editor.ts +++ b/packages/frontend/core/src/modules/theme-editor/services/theme-editor.ts @@ -1,7 +1,7 @@ -import type { GlobalState } from '@toeverything/infra'; import { LiveData, Service } from '@toeverything/infra'; import { map } from 'rxjs'; +import type { GlobalState } from '../../storage'; import type { CustomTheme } from '../types'; export class ThemeEditorService extends Service { diff --git a/packages/frontend/core/src/modules/theme/index.ts b/packages/frontend/core/src/modules/theme/index.ts index f54ba14281b4e..027454eb9f53c 100644 --- a/packages/frontend/core/src/modules/theme/index.ts +++ b/packages/frontend/core/src/modules/theme/index.ts @@ -1,8 +1,9 @@ export { AppThemeService } from './services/theme'; -import { type Framework, WorkspaceScope } from '@toeverything/infra'; +import { type Framework } from '@toeverything/infra'; import { EditorSettingService } from '../editor-setting'; +import { WorkspaceScope } from '../workspace'; import { AppTheme } from './entities/theme'; import { EdgelessThemeService } from './services/edgeless-theme'; import { AppThemeService } from './services/theme'; diff --git a/packages/frontend/core/src/modules/theme/services/edgeless-theme.ts b/packages/frontend/core/src/modules/theme/services/edgeless-theme.ts index d9806bf708525..ac3506e6255c4 100644 --- a/packages/frontend/core/src/modules/theme/services/edgeless-theme.ts +++ b/packages/frontend/core/src/modules/theme/services/edgeless-theme.ts @@ -1,6 +1,7 @@ -import type { DocRecord } from '@toeverything/infra'; -import { DocCreated, OnEvent, Service } from '@toeverything/infra'; +import { OnEvent, Service } from '@toeverything/infra'; +import type { DocRecord } from '../../doc'; +import { DocCreated } from '../../doc'; import type { EditorSettingService } from '../../editor-setting'; import type { EdgelessDefaultTheme } from '../../editor-setting/schema'; import type { AppThemeService } from './theme'; diff --git a/packages/frontend/core/src/modules/workbench/index.ts b/packages/frontend/core/src/modules/workbench/index.ts index 0a534128319ea..c2349d5fea15c 100644 --- a/packages/frontend/core/src/modules/workbench/index.ts +++ b/packages/frontend/core/src/modules/workbench/index.ts @@ -11,13 +11,11 @@ export type { WorkbenchLinkProps } from './view/workbench-link'; export { WorkbenchLink } from './view/workbench-link'; export { WorkbenchRoot } from './view/workbench-root'; -import { - type Framework, - GlobalStateService, - WorkspaceScope, -} from '@toeverything/infra'; +import { type Framework } from '@toeverything/infra'; import { DesktopApiService } from '../desktop-api'; +import { GlobalStateService } from '../storage'; +import { WorkspaceScope } from '../workspace'; import { SidebarTab } from './entities/sidebar-tab'; import { View } from './entities/view'; import { Workbench } from './entities/workbench'; diff --git a/packages/frontend/core/src/modules/workbench/services/workbench-view-state.ts b/packages/frontend/core/src/modules/workbench/services/workbench-view-state.ts index 18d3694cc47ad..d88dfea3c3fa9 100644 --- a/packages/frontend/core/src/modules/workbench/services/workbench-view-state.ts +++ b/packages/frontend/core/src/modules/workbench/services/workbench-view-state.ts @@ -1,8 +1,8 @@ -import type { GlobalStateService } from '@toeverything/infra'; import { createIdentifier, Service } from '@toeverything/infra'; import { nanoid } from 'nanoid'; import type { DesktopApiService, TabViewsMetaSchema } from '../../desktop-api'; +import type { GlobalStateService } from '../../storage'; import type { ViewIconName } from '../constants'; export type WorkbenchDefaultState = { diff --git a/packages/frontend/core/src/modules/workbench/view/workbench-link.tsx b/packages/frontend/core/src/modules/workbench/view/workbench-link.tsx index 429d42ae4ca39..98c6a5b156d1f 100644 --- a/packages/frontend/core/src/modules/workbench/view/workbench-link.tsx +++ b/packages/frontend/core/src/modules/workbench/view/workbench-link.tsx @@ -1,13 +1,10 @@ import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; import { isNewTabTrigger } from '@affine/core/utils'; -import { - FeatureFlagService, - useLiveData, - useServices, -} from '@toeverything/infra'; +import { useLiveData, useServices } from '@toeverything/infra'; import { type To } from 'history'; import { forwardRef, type MouseEvent } from 'react'; +import { FeatureFlagService } from '../../feature-flag'; import { WorkbenchService } from '../services/workbench'; export type WorkbenchLinkProps = React.PropsWithChildren< diff --git a/packages/frontend/core/src/modules/workspace-engine/impls/cloud.ts b/packages/frontend/core/src/modules/workspace-engine/impls/cloud.ts index 19559bfd6cb74..f047a31bb488c 100644 --- a/packages/frontend/core/src/modules/workspace-engine/impls/cloud.ts +++ b/packages/frontend/core/src/modules/workspace-engine/impls/cloud.ts @@ -13,19 +13,11 @@ import { effect, exhaustMapSwitchUntilChanged, fromPromise, - getAFFiNEWorkspaceSchema, - type GlobalState, LiveData, ObjectPool, onComplete, onStart, Service, - type Workspace, - type WorkspaceEngineProvider, - type WorkspaceFlavourProvider, - type WorkspaceFlavoursProvider, - type WorkspaceMetadata, - type WorkspaceProfileInfo, } from '@toeverything/infra'; import { isEqual } from 'lodash-es'; import { nanoid } from 'nanoid'; @@ -41,6 +33,16 @@ import { WebSocketService, WorkspaceServerService, } from '../../cloud'; +import type { GlobalState } from '../../storage'; +import { + getAFFiNEWorkspaceSchema, + type Workspace, + type WorkspaceEngineProvider, + type WorkspaceFlavourProvider, + type WorkspaceFlavoursProvider, + type WorkspaceMetadata, + type WorkspaceProfileInfo, +} from '../../workspace'; import type { WorkspaceEngineStorageProvider } from '../providers/engine'; import { BroadcastChannelAwarenessConnection } from './engine/awareness-broadcast-channel'; import { CloudAwarenessConnection } from './engine/awareness-cloud'; diff --git a/packages/frontend/core/src/modules/workspace-engine/impls/local.ts b/packages/frontend/core/src/modules/workspace-engine/impls/local.ts index 9fe869d144949..45c60b0f201a1 100644 --- a/packages/frontend/core/src/modules/workspace-engine/impls/local.ts +++ b/packages/frontend/core/src/modules/workspace-engine/impls/local.ts @@ -4,23 +4,22 @@ import type { BlobStorage, DocStorage, FrameworkProvider, - WorkspaceEngineProvider, - WorkspaceFlavourProvider, - WorkspaceFlavoursProvider, - WorkspaceMetadata, - WorkspaceProfileInfo, -} from '@toeverything/infra'; -import { - getAFFiNEWorkspaceSchema, - LiveData, - Service, } from '@toeverything/infra'; +import { LiveData, Service } from '@toeverything/infra'; import { isEqual } from 'lodash-es'; import { nanoid } from 'nanoid'; import { Observable } from 'rxjs'; import { encodeStateAsUpdate } from 'yjs'; import { DesktopApiService } from '../../desktop-api'; +import { + getAFFiNEWorkspaceSchema, + type WorkspaceEngineProvider, + type WorkspaceFlavourProvider, + type WorkspaceFlavoursProvider, + type WorkspaceMetadata, + type WorkspaceProfileInfo, +} from '../../workspace'; import type { WorkspaceEngineStorageProvider } from '../providers/engine'; import { BroadcastChannelAwarenessConnection } from './engine/awareness-broadcast-channel'; import { StaticBlobStorage } from './engine/blob-static'; diff --git a/packages/frontend/core/src/modules/workspace-engine/index.ts b/packages/frontend/core/src/modules/workspace-engine/index.ts index 4175a499b6e4c..bde7581b45c44 100644 --- a/packages/frontend/core/src/modules/workspace-engine/index.ts +++ b/packages/frontend/core/src/modules/workspace-engine/index.ts @@ -1,11 +1,9 @@ -import { - type Framework, - GlobalState, - WorkspaceFlavoursProvider, -} from '@toeverything/infra'; +import { type Framework } from '@toeverything/infra'; import { ServersService } from '../cloud/services/servers'; import { DesktopApiService } from '../desktop-api'; +import { GlobalState } from '../storage'; +import { WorkspaceFlavoursProvider } from '../workspace'; import { CloudWorkspaceFlavoursProvider } from './impls/cloud'; import { IndexedDBBlobStorage } from './impls/engine/blob-indexeddb'; import { SqliteBlobStorage } from './impls/engine/blob-sqlite'; diff --git a/packages/common/infra/src/modules/workspace/entities/engine.ts b/packages/frontend/core/src/modules/workspace/entities/engine.ts similarity index 92% rename from packages/common/infra/src/modules/workspace/entities/engine.ts rename to packages/frontend/core/src/modules/workspace/entities/engine.ts index fb6354ccd1beb..8433cf6c57e58 100644 --- a/packages/common/infra/src/modules/workspace/entities/engine.ts +++ b/packages/frontend/core/src/modules/workspace/entities/engine.ts @@ -1,8 +1,12 @@ +import { + AwarenessEngine, + BlobEngine, + DocEngine, + Entity, + throwIfAborted, +} from '@toeverything/infra'; import type { Doc as YDoc } from 'yjs'; -import { Entity } from '../../../framework'; -import { AwarenessEngine, BlobEngine, DocEngine } from '../../../sync'; -import { throwIfAborted } from '../../../utils'; import { WorkspaceEngineBeforeStart } from '../events'; import type { WorkspaceEngineProvider } from '../providers/flavour'; import type { WorkspaceService } from '../services/workspace'; diff --git a/packages/common/infra/src/modules/workspace/entities/list.ts b/packages/frontend/core/src/modules/workspace/entities/list.ts similarity index 93% rename from packages/common/infra/src/modules/workspace/entities/list.ts rename to packages/frontend/core/src/modules/workspace/entities/list.ts index 54fda37b0f693..8d0384515a020 100644 --- a/packages/common/infra/src/modules/workspace/entities/list.ts +++ b/packages/frontend/core/src/modules/workspace/entities/list.ts @@ -1,7 +1,6 @@ +import { Entity, LiveData } from '@toeverything/infra'; import { combineLatest, map, of, switchMap } from 'rxjs'; -import { Entity } from '../../../framework'; -import { LiveData } from '../../../livedata'; import type { WorkspaceMetadata } from '../metadata'; import type { WorkspaceFlavoursService } from '../services/flavours'; diff --git a/packages/common/infra/src/modules/workspace/entities/profile.ts b/packages/frontend/core/src/modules/workspace/entities/profile.ts similarity index 97% rename from packages/common/infra/src/modules/workspace/entities/profile.ts rename to packages/frontend/core/src/modules/workspace/entities/profile.ts index a4ed2445f842c..e621055fa18ab 100644 --- a/packages/common/infra/src/modules/workspace/entities/profile.ts +++ b/packages/frontend/core/src/modules/workspace/entities/profile.ts @@ -1,15 +1,15 @@ import { DebugLogger } from '@affine/debug'; -import { isEqual } from 'lodash-es'; -import { catchError, EMPTY, exhaustMap, mergeMap } from 'rxjs'; - -import { Entity } from '../../../framework'; import { effect, + Entity, fromPromise, LiveData, onComplete, onStart, -} from '../../../livedata'; +} from '@toeverything/infra'; +import { isEqual } from 'lodash-es'; +import { catchError, EMPTY, exhaustMap, mergeMap } from 'rxjs'; + import type { WorkspaceMetadata } from '../metadata'; import type { WorkspaceFlavourProvider } from '../providers/flavour'; import type { WorkspaceFlavoursService } from '../services/flavours'; diff --git a/packages/common/infra/src/modules/workspace/entities/workspace.ts b/packages/frontend/core/src/modules/workspace/entities/workspace.ts similarity index 96% rename from packages/common/infra/src/modules/workspace/entities/workspace.ts rename to packages/frontend/core/src/modules/workspace/entities/workspace.ts index 0d01fc62de87c..12d83e689bc6a 100644 --- a/packages/common/infra/src/modules/workspace/entities/workspace.ts +++ b/packages/frontend/core/src/modules/workspace/entities/workspace.ts @@ -1,10 +1,9 @@ import { DocCollection } from '@blocksuite/affine/store'; +import { Entity, LiveData } from '@toeverything/infra'; import { nanoid } from 'nanoid'; import { Observable } from 'rxjs'; import type { Awareness } from 'y-protocols/awareness.js'; -import { Entity } from '../../../framework'; -import { LiveData } from '../../../livedata'; import { WorkspaceDBService } from '../../db'; import { getAFFiNEWorkspaceSchema } from '../global-schema'; import type { WorkspaceScope } from '../scopes/workspace'; diff --git a/packages/common/infra/src/modules/workspace/events/index.ts b/packages/frontend/core/src/modules/workspace/events/index.ts similarity index 85% rename from packages/common/infra/src/modules/workspace/events/index.ts rename to packages/frontend/core/src/modules/workspace/events/index.ts index 9e0c68c01d150..4da388a729334 100644 --- a/packages/common/infra/src/modules/workspace/events/index.ts +++ b/packages/frontend/core/src/modules/workspace/events/index.ts @@ -1,4 +1,5 @@ -import { createEvent } from '../../../framework'; +import { createEvent } from '@toeverything/infra'; + import type { WorkspaceEngine } from '../entities/engine'; import type { Workspace } from '../entities/workspace'; diff --git a/packages/common/infra/src/modules/workspace/global-schema.ts b/packages/frontend/core/src/modules/workspace/global-schema.ts similarity index 78% rename from packages/common/infra/src/modules/workspace/global-schema.ts rename to packages/frontend/core/src/modules/workspace/global-schema.ts index 5f8dc895487e2..49f1da210c195 100644 --- a/packages/common/infra/src/modules/workspace/global-schema.ts +++ b/packages/frontend/core/src/modules/workspace/global-schema.ts @@ -1,7 +1,6 @@ import { AffineSchemas } from '@blocksuite/affine/blocks/schemas'; import { Schema } from '@blocksuite/affine/store'; - -import { AIChatBlockSchema } from '../../blocksuite/blocks/ai-chat-block/ai-chat-model'; +import { AIChatBlockSchema } from '@toeverything/infra'; let _schema: Schema | null = null; export function getAFFiNEWorkspaceSchema() { diff --git a/packages/common/infra/src/modules/workspace/impls/storage.ts b/packages/frontend/core/src/modules/workspace/impls/storage.ts similarity index 96% rename from packages/common/infra/src/modules/workspace/impls/storage.ts rename to packages/frontend/core/src/modules/workspace/impls/storage.ts index fd85f0ff4c4f3..033feaebaa9ff 100644 --- a/packages/common/infra/src/modules/workspace/impls/storage.ts +++ b/packages/frontend/core/src/modules/workspace/impls/storage.ts @@ -1,4 +1,5 @@ -import { type Memento, wrapMemento } from '../../../storage'; +import { type Memento, wrapMemento } from '@toeverything/infra'; + import type { GlobalCache, GlobalState } from '../../storage'; import type { WorkspaceLocalCache, diff --git a/packages/common/infra/src/modules/workspace/index.ts b/packages/frontend/core/src/modules/workspace/index.ts similarity index 91% rename from packages/common/infra/src/modules/workspace/index.ts rename to packages/frontend/core/src/modules/workspace/index.ts index 763909270591a..130d043d7665d 100644 --- a/packages/common/infra/src/modules/workspace/index.ts +++ b/packages/frontend/core/src/modules/workspace/index.ts @@ -12,7 +12,8 @@ export { WorkspaceScope } from './scopes/workspace'; export { WorkspaceService } from './services/workspace'; export { WorkspacesService } from './services/workspaces'; -import type { Framework } from '../../framework'; +import type { Framework } from '@toeverything/infra'; + import { GlobalCache, GlobalState } from '../storage'; import { WorkspaceEngine } from './entities/engine'; import { WorkspaceList } from './entities/list'; @@ -36,7 +37,6 @@ import { WorkspaceTransformService } from './services/transform'; import { WorkspaceService } from './services/workspace'; import { WorkspacesService } from './services/workspaces'; import { WorkspaceProfileCacheStore } from './stores/profile-cache'; -import { TestingWorkspaceFlavoursProvider } from './testing/testing-provider'; export function configureWorkspaceModule(framework: Framework) { framework @@ -82,11 +82,3 @@ export function configureWorkspaceModule(framework: Framework) { GlobalCache, ]); } - -export function configureTestingWorkspaceProvider(framework: Framework) { - framework.impl( - WorkspaceFlavoursProvider('LOCAL'), - TestingWorkspaceFlavoursProvider, - [GlobalState] - ); -} diff --git a/packages/common/infra/src/modules/workspace/metadata.ts b/packages/frontend/core/src/modules/workspace/metadata.ts similarity index 100% rename from packages/common/infra/src/modules/workspace/metadata.ts rename to packages/frontend/core/src/modules/workspace/metadata.ts diff --git a/packages/common/infra/src/modules/workspace/open-options.ts b/packages/frontend/core/src/modules/workspace/open-options.ts similarity index 100% rename from packages/common/infra/src/modules/workspace/open-options.ts rename to packages/frontend/core/src/modules/workspace/open-options.ts diff --git a/packages/common/infra/src/modules/workspace/providers/flavour.ts b/packages/frontend/core/src/modules/workspace/providers/flavour.ts similarity index 88% rename from packages/common/infra/src/modules/workspace/providers/flavour.ts rename to packages/frontend/core/src/modules/workspace/providers/flavour.ts index ce2a24fc3ab7a..c194aaee4ab73 100644 --- a/packages/common/infra/src/modules/workspace/providers/flavour.ts +++ b/packages/frontend/core/src/modules/workspace/providers/flavour.ts @@ -1,13 +1,13 @@ import type { DocCollection } from '@blocksuite/affine/store'; +import { + type AwarenessConnection, + type BlobStorage, + createIdentifier, + type DocServer, + type DocStorage, + type LiveData, +} from '@toeverything/infra'; -import { createIdentifier } from '../../../framework'; -import type { LiveData } from '../../../livedata'; -import type { - AwarenessConnection, - BlobStorage, - DocServer, - DocStorage, -} from '../../../sync'; import type { WorkspaceProfileInfo } from '../entities/profile'; import type { Workspace } from '../entities/workspace'; import type { WorkspaceMetadata } from '../metadata'; diff --git a/packages/common/infra/src/modules/workspace/providers/storage.ts b/packages/frontend/core/src/modules/workspace/providers/storage.ts similarity index 75% rename from packages/common/infra/src/modules/workspace/providers/storage.ts rename to packages/frontend/core/src/modules/workspace/providers/storage.ts index 08090d671cdcc..a266a58a8cf4b 100644 --- a/packages/common/infra/src/modules/workspace/providers/storage.ts +++ b/packages/frontend/core/src/modules/workspace/providers/storage.ts @@ -1,5 +1,4 @@ -import { createIdentifier } from '../../../framework'; -import type { Memento } from '../../../storage'; +import { createIdentifier, type Memento } from '@toeverything/infra'; export interface WorkspaceLocalState extends Memento {} export interface WorkspaceLocalCache extends Memento {} diff --git a/packages/common/infra/src/modules/workspace/scopes/workspace.ts b/packages/frontend/core/src/modules/workspace/scopes/workspace.ts similarity index 87% rename from packages/common/infra/src/modules/workspace/scopes/workspace.ts rename to packages/frontend/core/src/modules/workspace/scopes/workspace.ts index 934eee8ebd6af..8a8ac82d7117d 100644 --- a/packages/common/infra/src/modules/workspace/scopes/workspace.ts +++ b/packages/frontend/core/src/modules/workspace/scopes/workspace.ts @@ -1,4 +1,5 @@ -import { Scope } from '../../../framework'; +import { Scope } from '@toeverything/infra'; + import type { WorkspaceOpenOptions } from '../open-options'; import type { WorkspaceEngineProvider } from '../providers/flavour'; diff --git a/packages/common/infra/src/modules/workspace/services/destroy.ts b/packages/frontend/core/src/modules/workspace/services/destroy.ts similarity index 92% rename from packages/common/infra/src/modules/workspace/services/destroy.ts rename to packages/frontend/core/src/modules/workspace/services/destroy.ts index 71a090e5416b7..40e7d85e90b61 100644 --- a/packages/common/infra/src/modules/workspace/services/destroy.ts +++ b/packages/frontend/core/src/modules/workspace/services/destroy.ts @@ -1,4 +1,5 @@ -import { Service } from '../../../framework'; +import { Service } from '@toeverything/infra'; + import type { WorkspaceMetadata } from '../metadata'; import type { WorkspaceFlavoursService } from './flavours'; diff --git a/packages/common/infra/src/modules/workspace/services/engine.ts b/packages/frontend/core/src/modules/workspace/services/engine.ts similarity index 92% rename from packages/common/infra/src/modules/workspace/services/engine.ts rename to packages/frontend/core/src/modules/workspace/services/engine.ts index 93eba3e04a1e4..10907d6e802db 100644 --- a/packages/common/infra/src/modules/workspace/services/engine.ts +++ b/packages/frontend/core/src/modules/workspace/services/engine.ts @@ -1,4 +1,5 @@ -import { Service } from '../../../framework'; +import { Service } from '@toeverything/infra'; + import { WorkspaceEngine } from '../entities/engine'; import type { WorkspaceScope } from '../scopes/workspace'; diff --git a/packages/common/infra/src/modules/workspace/services/factory.ts b/packages/frontend/core/src/modules/workspace/services/factory.ts similarity index 89% rename from packages/common/infra/src/modules/workspace/services/factory.ts rename to packages/frontend/core/src/modules/workspace/services/factory.ts index 2442e227dc7eb..bd642360608ba 100644 --- a/packages/common/infra/src/modules/workspace/services/factory.ts +++ b/packages/frontend/core/src/modules/workspace/services/factory.ts @@ -1,7 +1,10 @@ import type { DocCollection } from '@blocksuite/affine/store'; +import { + type BlobStorage, + type DocStorage, + Service, +} from '@toeverything/infra'; -import { Service } from '../../../framework'; -import type { BlobStorage, DocStorage } from '../../../sync'; import type { WorkspaceFlavoursService } from './flavours'; export class WorkspaceFactoryService extends Service { diff --git a/packages/common/infra/src/modules/workspace/services/flavours.ts b/packages/frontend/core/src/modules/workspace/services/flavours.ts similarity index 82% rename from packages/common/infra/src/modules/workspace/services/flavours.ts rename to packages/frontend/core/src/modules/workspace/services/flavours.ts index c248eb36e0630..21a84380f66b5 100644 --- a/packages/common/infra/src/modules/workspace/services/flavours.ts +++ b/packages/frontend/core/src/modules/workspace/services/flavours.ts @@ -1,7 +1,6 @@ +import { LiveData, Service } from '@toeverything/infra'; import { combineLatest, map } from 'rxjs'; -import { Service } from '../../../framework'; -import { LiveData } from '../../../livedata'; import type { WorkspaceFlavoursProvider } from '../providers/flavour'; export class WorkspaceFlavoursService extends Service { diff --git a/packages/common/infra/src/modules/workspace/services/list.ts b/packages/frontend/core/src/modules/workspace/services/list.ts similarity index 76% rename from packages/common/infra/src/modules/workspace/services/list.ts rename to packages/frontend/core/src/modules/workspace/services/list.ts index 7521f8b60cb13..53c86bce3438a 100644 --- a/packages/common/infra/src/modules/workspace/services/list.ts +++ b/packages/frontend/core/src/modules/workspace/services/list.ts @@ -1,4 +1,5 @@ -import { Service } from '../../../framework'; +import { Service } from '@toeverything/infra'; + import { WorkspaceList } from '../entities/list'; export class WorkspaceListService extends Service { diff --git a/packages/common/infra/src/modules/workspace/services/profile.ts b/packages/frontend/core/src/modules/workspace/services/profile.ts similarity index 85% rename from packages/common/infra/src/modules/workspace/services/profile.ts rename to packages/frontend/core/src/modules/workspace/services/profile.ts index 01f06f760f05f..3ee7d29feb6ef 100644 --- a/packages/common/infra/src/modules/workspace/services/profile.ts +++ b/packages/frontend/core/src/modules/workspace/services/profile.ts @@ -1,5 +1,5 @@ -import { Service } from '../../../framework'; -import { ObjectPool } from '../../../utils'; +import { ObjectPool, Service } from '@toeverything/infra'; + import { WorkspaceProfile } from '../entities/profile'; import type { WorkspaceMetadata } from '../metadata'; diff --git a/packages/common/infra/src/modules/workspace/services/repo.ts b/packages/frontend/core/src/modules/workspace/services/repo.ts similarity index 97% rename from packages/common/infra/src/modules/workspace/services/repo.ts rename to packages/frontend/core/src/modules/workspace/services/repo.ts index 1fee2f1cfa85d..57fe2cf4075ee 100644 --- a/packages/common/infra/src/modules/workspace/services/repo.ts +++ b/packages/frontend/core/src/modules/workspace/services/repo.ts @@ -1,7 +1,6 @@ import { DebugLogger } from '@affine/debug'; +import { ObjectPool, Service } from '@toeverything/infra'; -import { Service } from '../../../framework'; -import { ObjectPool } from '../../../utils'; import type { Workspace } from '../entities/workspace'; import { WorkspaceInitialized } from '../events'; import type { WorkspaceOpenOptions } from '../open-options'; diff --git a/packages/common/infra/src/modules/workspace/services/transform.ts b/packages/frontend/core/src/modules/workspace/services/transform.ts similarity index 97% rename from packages/common/infra/src/modules/workspace/services/transform.ts rename to packages/frontend/core/src/modules/workspace/services/transform.ts index b84ddeb4d7ce2..dc5ecad53e273 100644 --- a/packages/common/infra/src/modules/workspace/services/transform.ts +++ b/packages/frontend/core/src/modules/workspace/services/transform.ts @@ -1,7 +1,7 @@ import { assertEquals } from '@blocksuite/affine/global/utils'; +import { Service } from '@toeverything/infra'; import { applyUpdate } from 'yjs'; -import { Service } from '../../../framework'; import { transformWorkspaceDBLocalToCloud } from '../../db'; import type { Workspace } from '../entities/workspace'; import type { WorkspaceMetadata } from '../metadata'; diff --git a/packages/common/infra/src/modules/workspace/services/workspace.ts b/packages/frontend/core/src/modules/workspace/services/workspace.ts similarity index 88% rename from packages/common/infra/src/modules/workspace/services/workspace.ts rename to packages/frontend/core/src/modules/workspace/services/workspace.ts index 40ae067de7f09..6266f9a82f1a1 100644 --- a/packages/common/infra/src/modules/workspace/services/workspace.ts +++ b/packages/frontend/core/src/modules/workspace/services/workspace.ts @@ -1,4 +1,5 @@ -import { Service } from '../../../framework'; +import { Service } from '@toeverything/infra'; + import { Workspace } from '../entities/workspace'; export class WorkspaceService extends Service { diff --git a/packages/common/infra/src/modules/workspace/services/workspaces.ts b/packages/frontend/core/src/modules/workspace/services/workspaces.ts similarity index 97% rename from packages/common/infra/src/modules/workspace/services/workspaces.ts rename to packages/frontend/core/src/modules/workspace/services/workspaces.ts index 455982105aa60..4d08882421dad 100644 --- a/packages/common/infra/src/modules/workspace/services/workspaces.ts +++ b/packages/frontend/core/src/modules/workspace/services/workspaces.ts @@ -1,4 +1,5 @@ -import { Service } from '../../../framework'; +import { Service } from '@toeverything/infra'; + import type { WorkspaceMetadata } from '..'; import type { WorkspaceDestroyService } from './destroy'; import type { WorkspaceFactoryService } from './factory'; diff --git a/packages/common/infra/src/modules/workspace/stores/profile-cache.ts b/packages/frontend/core/src/modules/workspace/stores/profile-cache.ts similarity index 95% rename from packages/common/infra/src/modules/workspace/stores/profile-cache.ts rename to packages/frontend/core/src/modules/workspace/stores/profile-cache.ts index 621739d40ec71..b6bf8d736a218 100644 --- a/packages/common/infra/src/modules/workspace/stores/profile-cache.ts +++ b/packages/frontend/core/src/modules/workspace/stores/profile-cache.ts @@ -1,6 +1,6 @@ +import { Store } from '@toeverything/infra'; import { map } from 'rxjs'; -import { Store } from '../../../framework'; import type { GlobalCache } from '../../storage'; import type { WorkspaceProfileInfo } from '../entities/profile'; diff --git a/packages/frontend/core/src/utils/first-app-data.ts b/packages/frontend/core/src/utils/first-app-data.ts index 57c977d9b5c16..368bd7acf1e2f 100644 --- a/packages/frontend/core/src/utils/first-app-data.ts +++ b/packages/frontend/core/src/utils/first-app-data.ts @@ -2,8 +2,9 @@ import { DebugLogger } from '@affine/debug'; import { DEFAULT_WORKSPACE_NAME } from '@affine/env/constant'; import onboardingUrl from '@affine/templates/onboarding.zip'; import { ZipTransformer } from '@blocksuite/affine/blocks'; -import type { WorkspacesService } from '@toeverything/infra'; -import { DocsService } from '@toeverything/infra'; + +import { DocsService } from '../modules/doc'; +import type { WorkspacesService } from '../modules/workspace'; export async function buildShowcaseWorkspace( workspacesService: WorkspacesService, From 9fb5e0db63e48c915d184cc070c5cf5a7041f2c5 Mon Sep 17 00:00:00 2001 From: CatsJuice Date: Mon, 23 Dec 2024 07:07:57 +0000 Subject: [PATCH 15/17] perf(core): optimize scrolling performance for doc list (#9250) - before ![CleanShot 2024-12-23 at 14.45.47.gif](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/LakojjjzZNf6ogjOVwKE/3e62773f-00f8-4464-a342-c3cd5c5b6edb.gif) - after ![CleanShot 2024-12-23 at 14.46.22.gif](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/LakojjjzZNf6ogjOVwKE/d713ea5e-ba2d-4251-b93a-a1d0ececc7eb.gif) --- .../page-list/docs/virtualized-page-list.tsx | 8 +++---- .../src/components/page-list/page-group.tsx | 22 ++++++++++--------- .../src/components/page-list/page-header.tsx | 8 +++---- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/packages/frontend/core/src/components/page-list/docs/virtualized-page-list.tsx b/packages/frontend/core/src/components/page-list/docs/virtualized-page-list.tsx index 32aa9164fe9b8..68e0f1c6ba8cb 100644 --- a/packages/frontend/core/src/components/page-list/docs/virtualized-page-list.tsx +++ b/packages/frontend/core/src/components/page-list/docs/virtualized-page-list.tsx @@ -8,7 +8,7 @@ import type { Collection, Filter } from '@affine/env/filter'; import { Trans, useI18n } from '@affine/i18n'; import type { DocMeta } from '@blocksuite/affine/store'; import { useService } from '@toeverything/infra'; -import { useCallback, useMemo, useRef, useState } from 'react'; +import { memo, useCallback, useMemo, useRef, useState } from 'react'; import { ListFloatingToolbar } from '../components/list-floating-toolbar'; import { usePageItemGroupDefinitions } from '../group-definitions'; @@ -50,7 +50,7 @@ const usePageOperationsRenderer = () => { return pageOperationsRenderer; }; -export const VirtualizedPageList = ({ +export const VirtualizedPageList = memo(function VirtualizedPageList({ tag, collection, filters, @@ -62,7 +62,7 @@ export const VirtualizedPageList = ({ filters?: Filter[]; listItem?: DocMeta[]; setHideHeaderCreateNewPage?: (hide: boolean) => void; -}) => { +}) { const t = useI18n(); const listRef = useRef(null); const [showFloatingToolbar, setShowFloatingToolbar] = useState(false); @@ -202,4 +202,4 @@ export const VirtualizedPageList = ({ /> ); -}; +}); diff --git a/packages/frontend/core/src/components/page-list/page-group.tsx b/packages/frontend/core/src/components/page-list/page-group.tsx index 7f6524c9224da..50cfa8476012e 100644 --- a/packages/frontend/core/src/components/page-list/page-group.tsx +++ b/packages/frontend/core/src/components/page-list/page-group.tsx @@ -36,11 +36,9 @@ import type { } from './types'; import { shallowEqual } from './utils'; -export const ItemGroupHeader = ({ - id, - items, - label, -}: ItemGroupProps) => { +export const ItemGroupHeader = memo(function ItemGroupHeader< + T extends ListItem, +>({ id, items, label }: ItemGroupProps) { const [collapseState, setCollapseState] = useAtom(groupCollapseStateAtom); const collapsed = collapseState[id]; const onExpandedClicked: MouseEventHandler = useCallback( @@ -113,7 +111,7 @@ export const ItemGroupHeader = ({ ) : null; -}; +}); export const ItemGroup = ({ id, @@ -218,7 +216,9 @@ const listsPropsAtom = selectAtom( shallowEqual ); -export const PageListItemRenderer = (item: ListItem) => { +export const PageListItemRenderer = memo(function PageListItemRenderer( + item: ListItem +) { const props = useAtomValue(listsPropsAtom); const { selectionActive } = useAtomValue(selectionStateAtom); const groups = useAtomValue(groupsAtom); @@ -238,7 +238,7 @@ export const PageListItemRenderer = (item: ListItem) => { )} /> ); -}; +}); export const CollectionListItemRenderer = memo((item: ListItem) => { const props = useAtomValue(listsPropsAtom); @@ -256,7 +256,9 @@ export const CollectionListItemRenderer = memo((item: ListItem) => { CollectionListItemRenderer.displayName = 'CollectionListItemRenderer'; -export const TagListItemRenderer = (item: ListItem) => { +export const TagListItemRenderer = memo(function TagListItemRenderer( + item: ListItem +) { const props = useAtomValue(listsPropsAtom); const { selectionActive } = useAtomValue(selectionStateAtom); const tag = item as TagMeta; @@ -268,7 +270,7 @@ export const TagListItemRenderer = (item: ListItem) => { })} /> ); -}; +}); function tagIdToTagOption( tagId: string, diff --git a/packages/frontend/core/src/components/page-list/page-header.tsx b/packages/frontend/core/src/components/page-list/page-header.tsx index a37d05c50381e..88e8e09733881 100644 --- a/packages/frontend/core/src/components/page-list/page-header.tsx +++ b/packages/frontend/core/src/components/page-list/page-header.tsx @@ -6,7 +6,7 @@ import { MultiSelectIcon } from '@blocksuite/icons/rc'; import clsx from 'clsx'; import { selectAtom } from 'jotai/utils'; import type { MouseEventHandler } from 'react'; -import { useCallback } from 'react'; +import { memo, useCallback } from 'react'; import { ListHeaderCell } from './components/list-header-cell'; import * as styles from './page-header.css'; @@ -82,11 +82,11 @@ export const ListHeaderTitleCell = () => { const hideHeaderAtom = selectAtom(listPropsAtom, props => props?.hideHeader); // the table header for page list -export const ListTableHeader = ({ +export const ListTableHeader = memo(function ListTableHeader({ headerCols, }: { headerCols: HeaderColDef[]; -}) => { +}) { const [sorter, setSorter] = useAtom(sorterAtom); const hideHeader = useAtomValue(hideHeaderAtom); const selectionState = useAtomValue(selectionStateAtom); @@ -136,4 +136,4 @@ export const ListTableHeader = ({ })} ); -}; +}); From 5716fbf0ed9094111d62c53b3a7bb089a3de6b18 Mon Sep 17 00:00:00 2001 From: CatsJuice Date: Mon, 23 Dec 2024 07:22:58 +0000 Subject: [PATCH 16/17] chore: remove unnecessary log (#9251) --- .../common/infra/src/sync/indexer/impl/indexeddb/data-struct.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/common/infra/src/sync/indexer/impl/indexeddb/data-struct.ts b/packages/common/infra/src/sync/indexer/impl/indexeddb/data-struct.ts index e0637056e8ba6..d769d9b069b3c 100644 --- a/packages/common/infra/src/sync/indexer/impl/indexeddb/data-struct.ts +++ b/packages/common/infra/src/sync/indexer/impl/indexeddb/data-struct.ts @@ -269,8 +269,6 @@ export class DataStruct { nodes.push(this.resultNode(record, options, match, nid)); } - console.log(nodes); - return { pagination: { count: match.size(), From a187f2345269a3d1156ce4250d8039e87e1e984d Mon Sep 17 00:00:00 2001 From: donteatfriedrice Date: Mon, 23 Dec 2024 07:54:58 +0000 Subject: [PATCH 17/17] chore: each block maintain its own adapter extension (#9224) --- .../embed-figma-block/adapters/extension.ts | 11 +++ .../src/embed-figma-block/embed-figma-spec.ts | 4 +- .../embed-github-block/adapters/extension.ts | 11 +++ .../embed-github-block/embed-github-spec.ts | 4 +- .../adapters/extension.ts | 11 +++ .../embed-linked-doc-spec.ts | 4 +- .../embed-loom-block/adapters/extension.ts | 11 +++ .../src/embed-loom-block/embed-loom-spec.ts | 4 +- .../adapters/extension.ts | 11 +++ .../embed-synced-doc-spec.ts | 4 +- .../embed-youtube-block/adapters/extension.ts | 11 +++ .../embed-youtube-block/embed-youtube-spec.ts | 4 +- .../block-list/src/adapters/extension.ts | 13 ++++ blocksuite/affine/block-list/src/list-spec.ts | 4 +- .../block-paragraph/src/adapters/extension.ts | 13 ++++ .../block-paragraph/src/paragraph-spec.ts | 4 +- .../block-surface/src/adapters/extension.ts | 2 - .../blocks/src/_common/adapters/extension.ts | 11 --- .../adapters/html-adapter/block-matcher.ts | 64 +++-------------- .../_common/adapters/html-adapter/index.ts | 5 +- .../blocks/src/_common/adapters/index.ts | 1 + .../adapters/markdown/block-matcher.ts | 70 +++---------------- .../src/_common/adapters/markdown/index.ts | 5 +- .../adapters/notion-html/block-matcher.ts | 64 +++-------------- .../src/_common/adapters/notion-html/index.ts | 5 +- .../adapters/plain-text/block-matcher.ts | 52 ++------------ blocksuite/blocks/src/_specs/common.ts | 7 +- .../src/attachment-block/attachment-spec.ts | 2 + .../src/bookmark-block/adapters/extension.ts | 13 ++++ .../src/bookmark-block/bookmark-spec.ts | 4 +- .../src/code-block/adapters/extension.ts | 13 ++++ .../blocks/src/code-block/code-block-spec.ts | 4 +- .../src/database-block/adapters/extension.ts | 11 +++ .../src/database-block/database-spec.ts | 4 +- .../src/divider-block/adapters/extension.ts | 13 ++++ .../blocks/src/divider-block/divider-spec.ts | 5 +- .../src/image-block/adapters/extension.ts | 11 +++ .../blocks/src/image-block/image-spec.ts | 4 +- .../src/latex-block/adapters/extension.ts | 11 +++ .../blocks/src/latex-block/latex-spec.ts | 4 +- .../src/root-block/adapters/extension.ts | 11 +++ .../root-block/edgeless/edgeless-root-spec.ts | 4 +- .../src/root-block/page/page-root-spec.ts | 4 +- 43 files changed, 264 insertions(+), 264 deletions(-) create mode 100644 blocksuite/affine/block-embed/src/embed-figma-block/adapters/extension.ts create mode 100644 blocksuite/affine/block-embed/src/embed-github-block/adapters/extension.ts create mode 100644 blocksuite/affine/block-embed/src/embed-linked-doc-block/adapters/extension.ts create mode 100644 blocksuite/affine/block-embed/src/embed-loom-block/adapters/extension.ts create mode 100644 blocksuite/affine/block-embed/src/embed-synced-doc-block/adapters/extension.ts create mode 100644 blocksuite/affine/block-embed/src/embed-youtube-block/adapters/extension.ts create mode 100644 blocksuite/affine/block-list/src/adapters/extension.ts create mode 100644 blocksuite/affine/block-paragraph/src/adapters/extension.ts create mode 100644 blocksuite/blocks/src/bookmark-block/adapters/extension.ts create mode 100644 blocksuite/blocks/src/code-block/adapters/extension.ts create mode 100644 blocksuite/blocks/src/database-block/adapters/extension.ts create mode 100644 blocksuite/blocks/src/divider-block/adapters/extension.ts create mode 100644 blocksuite/blocks/src/image-block/adapters/extension.ts create mode 100644 blocksuite/blocks/src/latex-block/adapters/extension.ts create mode 100644 blocksuite/blocks/src/root-block/adapters/extension.ts diff --git a/blocksuite/affine/block-embed/src/embed-figma-block/adapters/extension.ts b/blocksuite/affine/block-embed/src/embed-figma-block/adapters/extension.ts new file mode 100644 index 0000000000000..1b8017c5a4e8d --- /dev/null +++ b/blocksuite/affine/block-embed/src/embed-figma-block/adapters/extension.ts @@ -0,0 +1,11 @@ +import type { ExtensionType } from '@blocksuite/block-std'; + +import { EmbedFigmaBlockHtmlAdapterExtension } from './html.js'; +import { EmbedFigmaMarkdownAdapterExtension } from './markdown.js'; +import { EmbedFigmaBlockPlainTextAdapterExtension } from './plain-text.js'; + +export const EmbedFigmaBlockAdapterExtensions: ExtensionType[] = [ + EmbedFigmaBlockHtmlAdapterExtension, + EmbedFigmaMarkdownAdapterExtension, + EmbedFigmaBlockPlainTextAdapterExtension, +]; diff --git a/blocksuite/affine/block-embed/src/embed-figma-block/embed-figma-spec.ts b/blocksuite/affine/block-embed/src/embed-figma-block/embed-figma-spec.ts index 5712740887162..9165e699a45f7 100644 --- a/blocksuite/affine/block-embed/src/embed-figma-block/embed-figma-spec.ts +++ b/blocksuite/affine/block-embed/src/embed-figma-block/embed-figma-spec.ts @@ -5,6 +5,7 @@ import { } from '@blocksuite/block-std'; import { literal } from 'lit/static-html.js'; +import { EmbedFigmaBlockAdapterExtensions } from './adapters/extension.js'; import { EmbedFigmaBlockService } from './embed-figma-service.js'; export const EmbedFigmaBlockSpec: ExtensionType[] = [ @@ -15,4 +16,5 @@ export const EmbedFigmaBlockSpec: ExtensionType[] = [ ? literal`affine-embed-edgeless-figma-block` : literal`affine-embed-figma-block`; }), -]; + EmbedFigmaBlockAdapterExtensions, +].flat(); diff --git a/blocksuite/affine/block-embed/src/embed-github-block/adapters/extension.ts b/blocksuite/affine/block-embed/src/embed-github-block/adapters/extension.ts new file mode 100644 index 0000000000000..5c6fa5cb57be6 --- /dev/null +++ b/blocksuite/affine/block-embed/src/embed-github-block/adapters/extension.ts @@ -0,0 +1,11 @@ +import type { ExtensionType } from '@blocksuite/block-std'; + +import { EmbedGithubBlockHtmlAdapterExtension } from './html.js'; +import { EmbedGithubMarkdownAdapterExtension } from './markdown.js'; +import { EmbedGithubBlockPlainTextAdapterExtension } from './plain-text.js'; + +export const EmbedGithubBlockAdapterExtensions: ExtensionType[] = [ + EmbedGithubBlockHtmlAdapterExtension, + EmbedGithubMarkdownAdapterExtension, + EmbedGithubBlockPlainTextAdapterExtension, +]; diff --git a/blocksuite/affine/block-embed/src/embed-github-block/embed-github-spec.ts b/blocksuite/affine/block-embed/src/embed-github-block/embed-github-spec.ts index e86cd30db4fee..44045362d8aab 100644 --- a/blocksuite/affine/block-embed/src/embed-github-block/embed-github-spec.ts +++ b/blocksuite/affine/block-embed/src/embed-github-block/embed-github-spec.ts @@ -5,6 +5,7 @@ import { } from '@blocksuite/block-std'; import { literal } from 'lit/static-html.js'; +import { EmbedGithubBlockAdapterExtensions } from './adapters/extension.js'; import { EmbedGithubBlockService } from './embed-github-service.js'; export const EmbedGithubBlockSpec: ExtensionType[] = [ @@ -15,4 +16,5 @@ export const EmbedGithubBlockSpec: ExtensionType[] = [ ? literal`affine-embed-edgeless-github-block` : literal`affine-embed-github-block`; }), -]; + EmbedGithubBlockAdapterExtensions, +].flat(); diff --git a/blocksuite/affine/block-embed/src/embed-linked-doc-block/adapters/extension.ts b/blocksuite/affine/block-embed/src/embed-linked-doc-block/adapters/extension.ts new file mode 100644 index 0000000000000..5f75ffafe9cab --- /dev/null +++ b/blocksuite/affine/block-embed/src/embed-linked-doc-block/adapters/extension.ts @@ -0,0 +1,11 @@ +import type { ExtensionType } from '@blocksuite/block-std'; + +import { EmbedLinkedDocHtmlAdapterExtension } from './html.js'; +import { EmbedLinkedDocMarkdownAdapterExtension } from './markdown.js'; +import { EmbedLinkedDocBlockPlainTextAdapterExtension } from './plain-text.js'; + +export const EmbedLinkedDocBlockAdapterExtensions: ExtensionType[] = [ + EmbedLinkedDocHtmlAdapterExtension, + EmbedLinkedDocMarkdownAdapterExtension, + EmbedLinkedDocBlockPlainTextAdapterExtension, +]; diff --git a/blocksuite/affine/block-embed/src/embed-linked-doc-block/embed-linked-doc-spec.ts b/blocksuite/affine/block-embed/src/embed-linked-doc-block/embed-linked-doc-spec.ts index 7b6289c4651e9..70d4f4256b298 100644 --- a/blocksuite/affine/block-embed/src/embed-linked-doc-block/embed-linked-doc-spec.ts +++ b/blocksuite/affine/block-embed/src/embed-linked-doc-block/embed-linked-doc-spec.ts @@ -5,6 +5,7 @@ import { } from '@blocksuite/block-std'; import { literal } from 'lit/static-html.js'; +import { EmbedLinkedDocBlockAdapterExtensions } from './adapters/extension.js'; import { commands } from './commands/index.js'; export const EmbedLinkedDocBlockSpec: ExtensionType[] = [ @@ -14,4 +15,5 @@ export const EmbedLinkedDocBlockSpec: ExtensionType[] = [ ? literal`affine-embed-edgeless-linked-doc-block` : literal`affine-embed-linked-doc-block`; }), -]; + EmbedLinkedDocBlockAdapterExtensions, +].flat(); diff --git a/blocksuite/affine/block-embed/src/embed-loom-block/adapters/extension.ts b/blocksuite/affine/block-embed/src/embed-loom-block/adapters/extension.ts new file mode 100644 index 0000000000000..13afbf167cafd --- /dev/null +++ b/blocksuite/affine/block-embed/src/embed-loom-block/adapters/extension.ts @@ -0,0 +1,11 @@ +import type { ExtensionType } from '@blocksuite/block-std'; + +import { EmbedLoomBlockHtmlAdapterExtension } from './html.js'; +import { EmbedLoomMarkdownAdapterExtension } from './markdown.js'; +import { EmbedLoomBlockPlainTextAdapterExtension } from './plain-text.js'; + +export const EmbedLoomBlockAdapterExtensions: ExtensionType[] = [ + EmbedLoomBlockHtmlAdapterExtension, + EmbedLoomMarkdownAdapterExtension, + EmbedLoomBlockPlainTextAdapterExtension, +]; diff --git a/blocksuite/affine/block-embed/src/embed-loom-block/embed-loom-spec.ts b/blocksuite/affine/block-embed/src/embed-loom-block/embed-loom-spec.ts index 65e4a803da0c9..4752629d1b66a 100644 --- a/blocksuite/affine/block-embed/src/embed-loom-block/embed-loom-spec.ts +++ b/blocksuite/affine/block-embed/src/embed-loom-block/embed-loom-spec.ts @@ -5,6 +5,7 @@ import { } from '@blocksuite/block-std'; import { literal } from 'lit/static-html.js'; +import { EmbedLoomBlockAdapterExtensions } from './adapters/extension.js'; import { EmbedLoomBlockService } from './embed-loom-service.js'; export const EmbedLoomBlockSpec: ExtensionType[] = [ @@ -15,4 +16,5 @@ export const EmbedLoomBlockSpec: ExtensionType[] = [ ? literal`affine-embed-edgeless-loom-block` : literal`affine-embed-loom-block`; }), -]; + EmbedLoomBlockAdapterExtensions, +].flat(); diff --git a/blocksuite/affine/block-embed/src/embed-synced-doc-block/adapters/extension.ts b/blocksuite/affine/block-embed/src/embed-synced-doc-block/adapters/extension.ts new file mode 100644 index 0000000000000..76a25436c51a2 --- /dev/null +++ b/blocksuite/affine/block-embed/src/embed-synced-doc-block/adapters/extension.ts @@ -0,0 +1,11 @@ +import type { ExtensionType } from '@blocksuite/block-std'; + +import { EmbedSyncedDocBlockHtmlAdapterExtension } from './html.js'; +import { EmbedSyncedDocBlockMarkdownAdapterExtension } from './markdown.js'; +import { EmbedSyncedDocBlockPlainTextAdapterExtension } from './plain-text.js'; + +export const EmbedSyncedDocBlockAdapterExtensions: ExtensionType[] = [ + EmbedSyncedDocBlockHtmlAdapterExtension, + EmbedSyncedDocBlockMarkdownAdapterExtension, + EmbedSyncedDocBlockPlainTextAdapterExtension, +]; diff --git a/blocksuite/affine/block-embed/src/embed-synced-doc-block/embed-synced-doc-spec.ts b/blocksuite/affine/block-embed/src/embed-synced-doc-block/embed-synced-doc-spec.ts index a398623153dfe..e82625ef302d0 100644 --- a/blocksuite/affine/block-embed/src/embed-synced-doc-block/embed-synced-doc-spec.ts +++ b/blocksuite/affine/block-embed/src/embed-synced-doc-block/embed-synced-doc-spec.ts @@ -5,6 +5,7 @@ import { } from '@blocksuite/block-std'; import { literal } from 'lit/static-html.js'; +import { EmbedSyncedDocBlockAdapterExtensions } from './adapters/extension.js'; import { EmbedSyncedDocBlockService } from './embed-synced-doc-service.js'; export const EmbedSyncedDocBlockSpec: ExtensionType[] = [ @@ -15,4 +16,5 @@ export const EmbedSyncedDocBlockSpec: ExtensionType[] = [ ? literal`affine-embed-edgeless-synced-doc-block` : literal`affine-embed-synced-doc-block`; }), -]; + EmbedSyncedDocBlockAdapterExtensions, +].flat(); diff --git a/blocksuite/affine/block-embed/src/embed-youtube-block/adapters/extension.ts b/blocksuite/affine/block-embed/src/embed-youtube-block/adapters/extension.ts new file mode 100644 index 0000000000000..8ef9c011bb6e0 --- /dev/null +++ b/blocksuite/affine/block-embed/src/embed-youtube-block/adapters/extension.ts @@ -0,0 +1,11 @@ +import type { ExtensionType } from '@blocksuite/block-std'; + +import { EmbedYoutubeBlockHtmlAdapterExtension } from './html.js'; +import { EmbedYoutubeMarkdownAdapterExtension } from './markdown.js'; +import { EmbedYoutubeBlockPlainTextAdapterExtension } from './plain-text.js'; + +export const EmbedYoutubeBlockAdapterExtensions: ExtensionType[] = [ + EmbedYoutubeBlockHtmlAdapterExtension, + EmbedYoutubeMarkdownAdapterExtension, + EmbedYoutubeBlockPlainTextAdapterExtension, +]; diff --git a/blocksuite/affine/block-embed/src/embed-youtube-block/embed-youtube-spec.ts b/blocksuite/affine/block-embed/src/embed-youtube-block/embed-youtube-spec.ts index 881699b6644fa..f18387cb86791 100644 --- a/blocksuite/affine/block-embed/src/embed-youtube-block/embed-youtube-spec.ts +++ b/blocksuite/affine/block-embed/src/embed-youtube-block/embed-youtube-spec.ts @@ -5,6 +5,7 @@ import { } from '@blocksuite/block-std'; import { literal } from 'lit/static-html.js'; +import { EmbedYoutubeBlockAdapterExtensions } from './adapters/extension.js'; import { EmbedYoutubeBlockService } from './embed-youtube-service.js'; export const EmbedYoutubeBlockSpec: ExtensionType[] = [ @@ -15,4 +16,5 @@ export const EmbedYoutubeBlockSpec: ExtensionType[] = [ ? literal`affine-embed-edgeless-youtube-block` : literal`affine-embed-youtube-block`; }), -]; + EmbedYoutubeBlockAdapterExtensions, +].flat(); diff --git a/blocksuite/affine/block-list/src/adapters/extension.ts b/blocksuite/affine/block-list/src/adapters/extension.ts new file mode 100644 index 0000000000000..3dc1f0f5d5577 --- /dev/null +++ b/blocksuite/affine/block-list/src/adapters/extension.ts @@ -0,0 +1,13 @@ +import type { ExtensionType } from '@blocksuite/block-std'; + +import { ListBlockHtmlAdapterExtension } from './html.js'; +import { ListBlockMarkdownAdapterExtension } from './markdown.js'; +import { ListBlockNotionHtmlAdapterExtension } from './notion-html.js'; +import { ListBlockPlainTextAdapterExtension } from './plain-text.js'; + +export const ListBlockAdapterExtensions: ExtensionType[] = [ + ListBlockHtmlAdapterExtension, + ListBlockMarkdownAdapterExtension, + ListBlockPlainTextAdapterExtension, + ListBlockNotionHtmlAdapterExtension, +]; diff --git a/blocksuite/affine/block-list/src/list-spec.ts b/blocksuite/affine/block-list/src/list-spec.ts index 763ccc7b62cf6..6877850cbe6cd 100644 --- a/blocksuite/affine/block-list/src/list-spec.ts +++ b/blocksuite/affine/block-list/src/list-spec.ts @@ -6,6 +6,7 @@ import { } from '@blocksuite/block-std'; import { literal } from 'lit/static-html.js'; +import { ListBlockAdapterExtensions } from './adapters/extension.js'; import { commands } from './commands/index.js'; import { ListKeymapExtension, ListTextKeymapExtension } from './list-keymap.js'; import { ListBlockService, ListDragHandleOption } from './list-service.js'; @@ -18,4 +19,5 @@ export const ListBlockSpec: ExtensionType[] = [ ListKeymapExtension, ListTextKeymapExtension, ListDragHandleOption, -]; + ListBlockAdapterExtensions, +].flat(); diff --git a/blocksuite/affine/block-paragraph/src/adapters/extension.ts b/blocksuite/affine/block-paragraph/src/adapters/extension.ts new file mode 100644 index 0000000000000..80c8cf65a263b --- /dev/null +++ b/blocksuite/affine/block-paragraph/src/adapters/extension.ts @@ -0,0 +1,13 @@ +import type { ExtensionType } from '@blocksuite/block-std'; + +import { ParagraphBlockHtmlAdapterExtension } from './html.js'; +import { ParagraphBlockMarkdownAdapterExtension } from './markdown.js'; +import { ParagraphBlockNotionHtmlAdapterExtension } from './notion-html.js'; +import { ParagraphBlockPlainTextAdapterExtension } from './plain-text.js'; + +export const ParagraphBlockAdapterExtensions: ExtensionType[] = [ + ParagraphBlockHtmlAdapterExtension, + ParagraphBlockMarkdownAdapterExtension, + ParagraphBlockPlainTextAdapterExtension, + ParagraphBlockNotionHtmlAdapterExtension, +]; diff --git a/blocksuite/affine/block-paragraph/src/paragraph-spec.ts b/blocksuite/affine/block-paragraph/src/paragraph-spec.ts index 046e857af3b49..0f68f863bbe85 100644 --- a/blocksuite/affine/block-paragraph/src/paragraph-spec.ts +++ b/blocksuite/affine/block-paragraph/src/paragraph-spec.ts @@ -6,6 +6,7 @@ import { } from '@blocksuite/block-std'; import { literal } from 'lit/static-html.js'; +import { ParagraphBlockAdapterExtensions } from './adapters/extension.js'; import { commands } from './commands/index.js'; import { ParagraphDragHandleOption } from './paragraph-drag-extension.js'; import { @@ -22,4 +23,5 @@ export const ParagraphBlockSpec: ExtensionType[] = [ ParagraphTextKeymapExtension, ParagraphKeymapExtension, ParagraphDragHandleOption, -]; + ParagraphBlockAdapterExtensions, +].flat(); diff --git a/blocksuite/affine/block-surface/src/adapters/extension.ts b/blocksuite/affine/block-surface/src/adapters/extension.ts index daa6faec96dfb..46f2de881f395 100644 --- a/blocksuite/affine/block-surface/src/adapters/extension.ts +++ b/blocksuite/affine/block-surface/src/adapters/extension.ts @@ -1,4 +1,3 @@ -import { SurfaceBlockHtmlAdapterExtension } from './html-adapter/html.js'; import { EdgelessSurfaceBlockMarkdownAdapterExtension, SurfaceBlockMarkdownAdapterExtension, @@ -11,7 +10,6 @@ import { export const SurfaceBlockAdapterExtensions = [ SurfaceBlockPlainTextAdapterExtension, SurfaceBlockMarkdownAdapterExtension, - SurfaceBlockHtmlAdapterExtension, ]; export const EdgelessSurfaceBlockAdapterExtensions = [ diff --git a/blocksuite/blocks/src/_common/adapters/extension.ts b/blocksuite/blocks/src/_common/adapters/extension.ts index c2fde47ffa99b..228113d74817b 100644 --- a/blocksuite/blocks/src/_common/adapters/extension.ts +++ b/blocksuite/blocks/src/_common/adapters/extension.ts @@ -1,16 +1,12 @@ import type { ExtensionType } from '@blocksuite/block-std'; import { AttachmentAdapterFactoryExtension } from './attachment.js'; -import { BlockHtmlAdapterExtensions } from './html-adapter/block-matcher.js'; import { HtmlAdapterFactoryExtension } from './html-adapter/html.js'; import { ImageAdapterFactoryExtension } from './image.js'; -import { BlockMarkdownAdapterExtensions } from './markdown/block-matcher.js'; import { MarkdownAdapterFactoryExtension } from './markdown/markdown.js'; import { MixTextAdapterFactoryExtension } from './mix-text.js'; -import { BlockNotionHtmlAdapterExtensions } from './notion-html/block-matcher.js'; import { NotionHtmlAdapterFactoryExtension } from './notion-html/notion-html.js'; import { NotionTextAdapterFactoryExtension } from './notion-text.js'; -import { BlockPlainTextAdapterExtensions } from './plain-text/block-matcher.js'; import { PlainTextAdapterFactoryExtension } from './plain-text/plain-text.js'; export const AdapterFactoryExtensions: ExtensionType[] = [ @@ -23,10 +19,3 @@ export const AdapterFactoryExtensions: ExtensionType[] = [ NotionHtmlAdapterFactoryExtension, MixTextAdapterFactoryExtension, ]; - -export const BlockAdapterMatcherExtensions: ExtensionType[] = [ - BlockPlainTextAdapterExtensions, - BlockMarkdownAdapterExtensions, - BlockHtmlAdapterExtensions, - BlockNotionHtmlAdapterExtensions, -].flat(); diff --git a/blocksuite/blocks/src/_common/adapters/html-adapter/block-matcher.ts b/blocksuite/blocks/src/_common/adapters/html-adapter/block-matcher.ts index 5a51a08a0182e..1fa564266b5b3 100644 --- a/blocksuite/blocks/src/_common/adapters/html-adapter/block-matcher.ts +++ b/blocksuite/blocks/src/_common/adapters/html-adapter/block-matcher.ts @@ -1,51 +1,20 @@ import { - EmbedFigmaBlockHtmlAdapterExtension, embedFigmaBlockHtmlAdapterMatcher, - EmbedGithubBlockHtmlAdapterExtension, embedGithubBlockHtmlAdapterMatcher, embedLinkedDocBlockHtmlAdapterMatcher, - EmbedLinkedDocHtmlAdapterExtension, - EmbedLoomBlockHtmlAdapterExtension, embedLoomBlockHtmlAdapterMatcher, - EmbedSyncedDocBlockHtmlAdapterExtension, embedSyncedDocBlockHtmlAdapterMatcher, - EmbedYoutubeBlockHtmlAdapterExtension, embedYoutubeBlockHtmlAdapterMatcher, } from '@blocksuite/affine-block-embed'; -import { - ListBlockHtmlAdapterExtension, - listBlockHtmlAdapterMatcher, -} from '@blocksuite/affine-block-list'; -import { - ParagraphBlockHtmlAdapterExtension, - paragraphBlockHtmlAdapterMatcher, -} from '@blocksuite/affine-block-paragraph'; -import type { ExtensionType } from '@blocksuite/block-std'; +import { listBlockHtmlAdapterMatcher } from '@blocksuite/affine-block-list'; +import { paragraphBlockHtmlAdapterMatcher } from '@blocksuite/affine-block-paragraph'; -import { - BookmarkBlockHtmlAdapterExtension, - bookmarkBlockHtmlAdapterMatcher, -} from '../../../bookmark-block/adapters/html.js'; -import { - CodeBlockHtmlAdapterExtension, - codeBlockHtmlAdapterMatcher, -} from '../../../code-block/adapters/html.js'; -import { - DatabaseBlockHtmlAdapterExtension, - databaseBlockHtmlAdapterMatcher, -} from '../../../database-block/adapters/html.js'; -import { - DividerBlockHtmlAdapterExtension, - dividerBlockHtmlAdapterMatcher, -} from '../../../divider-block/adapters/html.js'; -import { - ImageBlockHtmlAdapterExtension, - imageBlockHtmlAdapterMatcher, -} from '../../../image-block/adapters/html.js'; -import { - RootBlockHtmlAdapterExtension, - rootBlockHtmlAdapterMatcher, -} from '../../../root-block/adapters/html.js'; +import { bookmarkBlockHtmlAdapterMatcher } from '../../../bookmark-block/adapters/html.js'; +import { codeBlockHtmlAdapterMatcher } from '../../../code-block/adapters/html.js'; +import { databaseBlockHtmlAdapterMatcher } from '../../../database-block/adapters/html.js'; +import { dividerBlockHtmlAdapterMatcher } from '../../../divider-block/adapters/html.js'; +import { imageBlockHtmlAdapterMatcher } from '../../../image-block/adapters/html.js'; +import { rootBlockHtmlAdapterMatcher } from '../../../root-block/adapters/html.js'; export const defaultBlockHtmlAdapterMatchers = [ listBlockHtmlAdapterMatcher, @@ -63,20 +32,3 @@ export const defaultBlockHtmlAdapterMatchers = [ embedLinkedDocBlockHtmlAdapterMatcher, embedSyncedDocBlockHtmlAdapterMatcher, ]; - -export const BlockHtmlAdapterExtensions: ExtensionType[] = [ - ListBlockHtmlAdapterExtension, - ParagraphBlockHtmlAdapterExtension, - CodeBlockHtmlAdapterExtension, - DividerBlockHtmlAdapterExtension, - ImageBlockHtmlAdapterExtension, - RootBlockHtmlAdapterExtension, - EmbedYoutubeBlockHtmlAdapterExtension, - EmbedFigmaBlockHtmlAdapterExtension, - EmbedLoomBlockHtmlAdapterExtension, - EmbedGithubBlockHtmlAdapterExtension, - BookmarkBlockHtmlAdapterExtension, - DatabaseBlockHtmlAdapterExtension, - EmbedLinkedDocHtmlAdapterExtension, - EmbedSyncedDocBlockHtmlAdapterExtension, -]; diff --git a/blocksuite/blocks/src/_common/adapters/html-adapter/index.ts b/blocksuite/blocks/src/_common/adapters/html-adapter/index.ts index cf7542123772b..bec195ca8eeca 100644 --- a/blocksuite/blocks/src/_common/adapters/html-adapter/index.ts +++ b/blocksuite/blocks/src/_common/adapters/html-adapter/index.ts @@ -1,7 +1,4 @@ -export { - BlockHtmlAdapterExtensions, - defaultBlockHtmlAdapterMatchers, -} from './block-matcher.js'; +export { defaultBlockHtmlAdapterMatchers } from './block-matcher.js'; export { HtmlAdapter, HtmlAdapterFactoryExtension, diff --git a/blocksuite/blocks/src/_common/adapters/index.ts b/blocksuite/blocks/src/_common/adapters/index.ts index a1766dd7de764..d6e8c7b24033b 100644 --- a/blocksuite/blocks/src/_common/adapters/index.ts +++ b/blocksuite/blocks/src/_common/adapters/index.ts @@ -1,4 +1,5 @@ export * from './attachment.js'; +export * from './extension.js'; export * from './html-adapter/html.js'; export * from './image.js'; export * from './markdown/index.js'; diff --git a/blocksuite/blocks/src/_common/adapters/markdown/block-matcher.ts b/blocksuite/blocks/src/_common/adapters/markdown/block-matcher.ts index 28b1260389bc0..ca9b2323d5335 100644 --- a/blocksuite/blocks/src/_common/adapters/markdown/block-matcher.ts +++ b/blocksuite/blocks/src/_common/adapters/markdown/block-matcher.ts @@ -1,55 +1,21 @@ import { embedFigmaBlockMarkdownAdapterMatcher, - EmbedFigmaMarkdownAdapterExtension, embedGithubBlockMarkdownAdapterMatcher, - EmbedGithubMarkdownAdapterExtension, embedLinkedDocBlockMarkdownAdapterMatcher, - EmbedLinkedDocMarkdownAdapterExtension, embedLoomBlockMarkdownAdapterMatcher, - EmbedLoomMarkdownAdapterExtension, - EmbedSyncedDocBlockMarkdownAdapterExtension, embedSyncedDocBlockMarkdownAdapterMatcher, embedYoutubeBlockMarkdownAdapterMatcher, - EmbedYoutubeMarkdownAdapterExtension, } from '@blocksuite/affine-block-embed'; -import { - ListBlockMarkdownAdapterExtension, - listBlockMarkdownAdapterMatcher, -} from '@blocksuite/affine-block-list'; -import { - ParagraphBlockMarkdownAdapterExtension, - paragraphBlockMarkdownAdapterMatcher, -} from '@blocksuite/affine-block-paragraph'; -import type { ExtensionType } from '@blocksuite/block-std'; +import { listBlockMarkdownAdapterMatcher } from '@blocksuite/affine-block-list'; +import { paragraphBlockMarkdownAdapterMatcher } from '@blocksuite/affine-block-paragraph'; -import { - BookmarkBlockMarkdownAdapterExtension, - bookmarkBlockMarkdownAdapterMatcher, -} from '../../../bookmark-block/adapters/markdown.js'; -import { - CodeBlockMarkdownAdapterExtension, - codeBlockMarkdownAdapterMatcher, -} from '../../../code-block/adapters/markdown.js'; -import { - DatabaseBlockMarkdownAdapterExtension, - databaseBlockMarkdownAdapterMatcher, -} from '../../../database-block/adapters/markdown.js'; -import { - DividerBlockMarkdownAdapterExtension, - dividerBlockMarkdownAdapterMatcher, -} from '../../../divider-block/adapters/markdown.js'; -import { - ImageBlockMarkdownAdapterExtension, - imageBlockMarkdownAdapterMatcher, -} from '../../../image-block/adapters/markdown.js'; -import { - LatexBlockMarkdownAdapterExtension, - latexBlockMarkdownAdapterMatcher, -} from '../../../latex-block/adapters/markdown.js'; -import { - RootBlockMarkdownAdapterExtension, - rootBlockMarkdownAdapterMatcher, -} from '../../../root-block/adapters/markdown.js'; +import { bookmarkBlockMarkdownAdapterMatcher } from '../../../bookmark-block/adapters/markdown.js'; +import { codeBlockMarkdownAdapterMatcher } from '../../../code-block/adapters/markdown.js'; +import { databaseBlockMarkdownAdapterMatcher } from '../../../database-block/adapters/markdown.js'; +import { dividerBlockMarkdownAdapterMatcher } from '../../../divider-block/adapters/markdown.js'; +import { imageBlockMarkdownAdapterMatcher } from '../../../image-block/adapters/markdown.js'; +import { latexBlockMarkdownAdapterMatcher } from '../../../latex-block/adapters/markdown.js'; +import { rootBlockMarkdownAdapterMatcher } from '../../../root-block/adapters/markdown.js'; export const defaultBlockMarkdownAdapterMatchers = [ embedFigmaBlockMarkdownAdapterMatcher, @@ -68,21 +34,3 @@ export const defaultBlockMarkdownAdapterMatchers = [ latexBlockMarkdownAdapterMatcher, rootBlockMarkdownAdapterMatcher, ]; - -export const BlockMarkdownAdapterExtensions: ExtensionType[] = [ - EmbedFigmaMarkdownAdapterExtension, - EmbedGithubMarkdownAdapterExtension, - EmbedLinkedDocMarkdownAdapterExtension, - EmbedLoomMarkdownAdapterExtension, - EmbedSyncedDocBlockMarkdownAdapterExtension, - EmbedYoutubeMarkdownAdapterExtension, - ListBlockMarkdownAdapterExtension, - ParagraphBlockMarkdownAdapterExtension, - BookmarkBlockMarkdownAdapterExtension, - CodeBlockMarkdownAdapterExtension, - DatabaseBlockMarkdownAdapterExtension, - DividerBlockMarkdownAdapterExtension, - ImageBlockMarkdownAdapterExtension, - LatexBlockMarkdownAdapterExtension, - RootBlockMarkdownAdapterExtension, -]; diff --git a/blocksuite/blocks/src/_common/adapters/markdown/index.ts b/blocksuite/blocks/src/_common/adapters/markdown/index.ts index 2e63d55602944..ec387f385d904 100644 --- a/blocksuite/blocks/src/_common/adapters/markdown/index.ts +++ b/blocksuite/blocks/src/_common/adapters/markdown/index.ts @@ -1,7 +1,4 @@ -export { - BlockMarkdownAdapterExtensions, - defaultBlockMarkdownAdapterMatchers, -} from './block-matcher.js'; +export { defaultBlockMarkdownAdapterMatchers } from './block-matcher.js'; export { MarkdownAdapter, MarkdownAdapterFactoryExtension, diff --git a/blocksuite/blocks/src/_common/adapters/notion-html/block-matcher.ts b/blocksuite/blocks/src/_common/adapters/notion-html/block-matcher.ts index 544479e36b2b4..6013a505d2fea 100644 --- a/blocksuite/blocks/src/_common/adapters/notion-html/block-matcher.ts +++ b/blocksuite/blocks/src/_common/adapters/notion-html/block-matcher.ts @@ -1,46 +1,15 @@ -import { - ListBlockNotionHtmlAdapterExtension, - listBlockNotionHtmlAdapterMatcher, -} from '@blocksuite/affine-block-list'; -import { - ParagraphBlockNotionHtmlAdapterExtension, - paragraphBlockNotionHtmlAdapterMatcher, -} from '@blocksuite/affine-block-paragraph'; +import { listBlockNotionHtmlAdapterMatcher } from '@blocksuite/affine-block-list'; +import { paragraphBlockNotionHtmlAdapterMatcher } from '@blocksuite/affine-block-paragraph'; import type { BlockNotionHtmlAdapterMatcher } from '@blocksuite/affine-shared/adapters'; -import type { ExtensionType } from '@blocksuite/block-std'; -import { - AttachmentBlockNotionHtmlAdapterExtension, - attachmentBlockNotionHtmlAdapterMatcher, -} from '../../../attachment-block/adapters/notion-html.js'; -import { - BookmarkBlockNotionHtmlAdapterExtension, - bookmarkBlockNotionHtmlAdapterMatcher, -} from '../../../bookmark-block/adapters/notion-html.js'; -import { - CodeBlockNotionHtmlAdapterExtension, - codeBlockNotionHtmlAdapterMatcher, -} from '../../../code-block/adapters/notion-html.js'; -import { - DatabaseBlockNotionHtmlAdapterExtension, - databaseBlockNotionHtmlAdapterMatcher, -} from '../../../database-block/adapters/notion-html.js'; -import { - DividerBlockNotionHtmlAdapterExtension, - dividerBlockNotionHtmlAdapterMatcher, -} from '../../../divider-block/adapters/notion-html.js'; -import { - ImageBlockNotionHtmlAdapterExtension, - imageBlockNotionHtmlAdapterMatcher, -} from '../../../image-block/adapters/notion-html.js'; -import { - LatexBlockNotionHtmlAdapterExtension, - latexBlockNotionHtmlAdapterMatcher, -} from '../../../latex-block/adapters/notion-html.js'; -import { - RootBlockNotionHtmlAdapterExtension, - rootBlockNotionHtmlAdapterMatcher, -} from '../../../root-block/adapters/notion-html.js'; +import { attachmentBlockNotionHtmlAdapterMatcher } from '../../../attachment-block/adapters/notion-html.js'; +import { bookmarkBlockNotionHtmlAdapterMatcher } from '../../../bookmark-block/adapters/notion-html.js'; +import { codeBlockNotionHtmlAdapterMatcher } from '../../../code-block/adapters/notion-html.js'; +import { databaseBlockNotionHtmlAdapterMatcher } from '../../../database-block/adapters/notion-html.js'; +import { dividerBlockNotionHtmlAdapterMatcher } from '../../../divider-block/adapters/notion-html.js'; +import { imageBlockNotionHtmlAdapterMatcher } from '../../../image-block/adapters/notion-html.js'; +import { latexBlockNotionHtmlAdapterMatcher } from '../../../latex-block/adapters/notion-html.js'; +import { rootBlockNotionHtmlAdapterMatcher } from '../../../root-block/adapters/notion-html.js'; export const defaultBlockNotionHtmlAdapterMatchers: BlockNotionHtmlAdapterMatcher[] = [ @@ -55,16 +24,3 @@ export const defaultBlockNotionHtmlAdapterMatchers: BlockNotionHtmlAdapterMatche attachmentBlockNotionHtmlAdapterMatcher, latexBlockNotionHtmlAdapterMatcher, ]; - -export const BlockNotionHtmlAdapterExtensions: ExtensionType[] = [ - ListBlockNotionHtmlAdapterExtension, - ParagraphBlockNotionHtmlAdapterExtension, - CodeBlockNotionHtmlAdapterExtension, - DividerBlockNotionHtmlAdapterExtension, - ImageBlockNotionHtmlAdapterExtension, - RootBlockNotionHtmlAdapterExtension, - BookmarkBlockNotionHtmlAdapterExtension, - DatabaseBlockNotionHtmlAdapterExtension, - AttachmentBlockNotionHtmlAdapterExtension, - LatexBlockNotionHtmlAdapterExtension, -]; diff --git a/blocksuite/blocks/src/_common/adapters/notion-html/index.ts b/blocksuite/blocks/src/_common/adapters/notion-html/index.ts index 0282c785b19f1..54badc175de53 100644 --- a/blocksuite/blocks/src/_common/adapters/notion-html/index.ts +++ b/blocksuite/blocks/src/_common/adapters/notion-html/index.ts @@ -1,7 +1,4 @@ -export { - BlockNotionHtmlAdapterExtensions, - defaultBlockNotionHtmlAdapterMatchers, -} from './block-matcher.js'; +export { defaultBlockNotionHtmlAdapterMatchers } from './block-matcher.js'; export { NotionHtmlAdapter, NotionHtmlAdapterFactoryExtension, diff --git a/blocksuite/blocks/src/_common/adapters/plain-text/block-matcher.ts b/blocksuite/blocks/src/_common/adapters/plain-text/block-matcher.ts index dd89b6d990efe..8d469ea1e60ac 100644 --- a/blocksuite/blocks/src/_common/adapters/plain-text/block-matcher.ts +++ b/blocksuite/blocks/src/_common/adapters/plain-text/block-matcher.ts @@ -1,44 +1,19 @@ import { - EmbedFigmaBlockPlainTextAdapterExtension, embedFigmaBlockPlainTextAdapterMatcher, - EmbedGithubBlockPlainTextAdapterExtension, embedGithubBlockPlainTextAdapterMatcher, - EmbedLinkedDocBlockPlainTextAdapterExtension, embedLinkedDocBlockPlainTextAdapterMatcher, - EmbedLoomBlockPlainTextAdapterExtension, embedLoomBlockPlainTextAdapterMatcher, - EmbedSyncedDocBlockPlainTextAdapterExtension, embedSyncedDocBlockPlainTextAdapterMatcher, - EmbedYoutubeBlockPlainTextAdapterExtension, embedYoutubeBlockPlainTextAdapterMatcher, } from '@blocksuite/affine-block-embed'; -import { - ListBlockPlainTextAdapterExtension, - listBlockPlainTextAdapterMatcher, -} from '@blocksuite/affine-block-list'; -import { - ParagraphBlockPlainTextAdapterExtension, - paragraphBlockPlainTextAdapterMatcher, -} from '@blocksuite/affine-block-paragraph'; +import { listBlockPlainTextAdapterMatcher } from '@blocksuite/affine-block-list'; +import { paragraphBlockPlainTextAdapterMatcher } from '@blocksuite/affine-block-paragraph'; import type { BlockPlainTextAdapterMatcher } from '@blocksuite/affine-shared/adapters'; -import type { ExtensionType } from '@blocksuite/block-std'; -import { - BookmarkBlockPlainTextAdapterExtension, - bookmarkBlockPlainTextAdapterMatcher, -} from '../../../bookmark-block/adapters/plain-text.js'; -import { - CodeBlockPlainTextAdapterExtension, - codeBlockPlainTextAdapterMatcher, -} from '../../../code-block/adapters/plain-text.js'; -import { - DividerBlockPlainTextAdapterExtension, - dividerBlockPlainTextAdapterMatcher, -} from '../../../divider-block/adapters/plain-text.js'; -import { - LatexBlockPlainTextAdapterExtension, - latexBlockPlainTextAdapterMatcher, -} from '../../../latex-block/adapters/plain-text.js'; +import { bookmarkBlockPlainTextAdapterMatcher } from '../../../bookmark-block/adapters/plain-text.js'; +import { codeBlockPlainTextAdapterMatcher } from '../../../code-block/adapters/plain-text.js'; +import { dividerBlockPlainTextAdapterMatcher } from '../../../divider-block/adapters/plain-text.js'; +import { latexBlockPlainTextAdapterMatcher } from '../../../latex-block/adapters/plain-text.js'; export const defaultBlockPlainTextAdapterMatchers: BlockPlainTextAdapterMatcher[] = [ @@ -55,18 +30,3 @@ export const defaultBlockPlainTextAdapterMatchers: BlockPlainTextAdapterMatcher[ embedSyncedDocBlockPlainTextAdapterMatcher, latexBlockPlainTextAdapterMatcher, ]; - -export const BlockPlainTextAdapterExtensions: ExtensionType[] = [ - ParagraphBlockPlainTextAdapterExtension, - ListBlockPlainTextAdapterExtension, - DividerBlockPlainTextAdapterExtension, - CodeBlockPlainTextAdapterExtension, - BookmarkBlockPlainTextAdapterExtension, - EmbedFigmaBlockPlainTextAdapterExtension, - EmbedGithubBlockPlainTextAdapterExtension, - EmbedLoomBlockPlainTextAdapterExtension, - EmbedYoutubeBlockPlainTextAdapterExtension, - EmbedLinkedDocBlockPlainTextAdapterExtension, - EmbedSyncedDocBlockPlainTextAdapterExtension, - LatexBlockPlainTextAdapterExtension, -]; diff --git a/blocksuite/blocks/src/_specs/common.ts b/blocksuite/blocks/src/_specs/common.ts index 100265d9cacd4..b26960d4e43c6 100644 --- a/blocksuite/blocks/src/_specs/common.ts +++ b/blocksuite/blocks/src/_specs/common.ts @@ -5,10 +5,7 @@ import { RichTextExtensions } from '@blocksuite/affine-components/rich-text'; import { EditPropsStore } from '@blocksuite/affine-shared/services'; import type { ExtensionType } from '@blocksuite/block-std'; -import { - AdapterFactoryExtensions, - BlockAdapterMatcherExtensions, -} from '../_common/adapters/extension.js'; +import { AdapterFactoryExtensions } from '../_common/adapters/extension.js'; import { AttachmentBlockSpec } from '../attachment-block/attachment-spec.js'; import { BookmarkBlockSpec } from '../bookmark-block/bookmark-spec.js'; import { CodeBlockSpec } from '../code-block/code-block-spec.js'; @@ -35,7 +32,6 @@ export const CommonFirstPartyBlockSpecs: ExtensionType[] = [ BookmarkBlockSpec, AttachmentBlockSpec, EmbedExtensions, - BlockAdapterMatcherExtensions, AdapterFactoryExtensions, ].flat(); @@ -53,6 +49,5 @@ export const EdgelessFirstPartyBlockSpecs: ExtensionType[] = [ BookmarkBlockSpec, AttachmentBlockSpec, EmbedExtensions, - BlockAdapterMatcherExtensions, AdapterFactoryExtensions, ].flat(); diff --git a/blocksuite/blocks/src/attachment-block/attachment-spec.ts b/blocksuite/blocks/src/attachment-block/attachment-spec.ts index 34f371df8d079..c968a5b49fc0f 100644 --- a/blocksuite/blocks/src/attachment-block/attachment-spec.ts +++ b/blocksuite/blocks/src/attachment-block/attachment-spec.ts @@ -5,6 +5,7 @@ import { } from '@blocksuite/block-std'; import { literal } from 'lit/static-html.js'; +import { AttachmentBlockNotionHtmlAdapterExtension } from './adapters/notion-html.js'; import { AttachmentBlockService, AttachmentDragHandleOption, @@ -25,4 +26,5 @@ export const AttachmentBlockSpec: ExtensionType[] = [ AttachmentDragHandleOption, AttachmentEmbedConfigExtension(), AttachmentEmbedService, + AttachmentBlockNotionHtmlAdapterExtension, ]; diff --git a/blocksuite/blocks/src/bookmark-block/adapters/extension.ts b/blocksuite/blocks/src/bookmark-block/adapters/extension.ts new file mode 100644 index 0000000000000..41b0f9a52d001 --- /dev/null +++ b/blocksuite/blocks/src/bookmark-block/adapters/extension.ts @@ -0,0 +1,13 @@ +import type { ExtensionType } from '@blocksuite/block-std'; + +import { BookmarkBlockHtmlAdapterExtension } from './html.js'; +import { BookmarkBlockMarkdownAdapterExtension } from './markdown.js'; +import { BookmarkBlockNotionHtmlAdapterExtension } from './notion-html.js'; +import { BookmarkBlockPlainTextAdapterExtension } from './plain-text.js'; + +export const BookmarkBlockAdapterExtensions: ExtensionType[] = [ + BookmarkBlockHtmlAdapterExtension, + BookmarkBlockMarkdownAdapterExtension, + BookmarkBlockNotionHtmlAdapterExtension, + BookmarkBlockPlainTextAdapterExtension, +]; diff --git a/blocksuite/blocks/src/bookmark-block/bookmark-spec.ts b/blocksuite/blocks/src/bookmark-block/bookmark-spec.ts index 2bf50b1ac4a80..e7342b1a476b2 100644 --- a/blocksuite/blocks/src/bookmark-block/bookmark-spec.ts +++ b/blocksuite/blocks/src/bookmark-block/bookmark-spec.ts @@ -6,6 +6,7 @@ import { } from '@blocksuite/block-std'; import { literal } from 'lit/static-html.js'; +import { BookmarkBlockAdapterExtensions } from './adapters/extension.js'; import { BookmarkBlockService, BookmarkDragHandleOption, @@ -22,4 +23,5 @@ export const BookmarkBlockSpec: ExtensionType[] = [ : literal`affine-bookmark`; }), BookmarkDragHandleOption, -]; + BookmarkBlockAdapterExtensions, +].flat(); diff --git a/blocksuite/blocks/src/code-block/adapters/extension.ts b/blocksuite/blocks/src/code-block/adapters/extension.ts new file mode 100644 index 0000000000000..b93faadad8493 --- /dev/null +++ b/blocksuite/blocks/src/code-block/adapters/extension.ts @@ -0,0 +1,13 @@ +import type { ExtensionType } from '@blocksuite/block-std'; + +import { CodeBlockHtmlAdapterExtension } from './html.js'; +import { CodeBlockMarkdownAdapterExtension } from './markdown.js'; +import { CodeBlockNotionHtmlAdapterExtension } from './notion-html.js'; +import { CodeBlockPlainTextAdapterExtension } from './plain-text.js'; + +export const CodeBlockAdapterExtensions: ExtensionType[] = [ + CodeBlockHtmlAdapterExtension, + CodeBlockMarkdownAdapterExtension, + CodeBlockPlainTextAdapterExtension, + CodeBlockNotionHtmlAdapterExtension, +]; diff --git a/blocksuite/blocks/src/code-block/code-block-spec.ts b/blocksuite/blocks/src/code-block/code-block-spec.ts index 5b95b7bed25b4..eca27aab8591a 100644 --- a/blocksuite/blocks/src/code-block/code-block-spec.ts +++ b/blocksuite/blocks/src/code-block/code-block-spec.ts @@ -7,6 +7,7 @@ import { import { literal, unsafeStatic } from 'lit/static-html.js'; import { AFFINE_CODE_TOOLBAR_WIDGET } from '../root-block/widgets/code-toolbar/index.js'; +import { CodeBlockAdapterExtensions } from './adapters/extension.js'; import { CodeBlockInlineManagerExtension, CodeBlockUnitSpecExtension, @@ -22,4 +23,5 @@ export const CodeBlockSpec: ExtensionType[] = [ }), CodeBlockInlineManagerExtension, CodeBlockUnitSpecExtension, -]; + CodeBlockAdapterExtensions, +].flat(); diff --git a/blocksuite/blocks/src/database-block/adapters/extension.ts b/blocksuite/blocks/src/database-block/adapters/extension.ts new file mode 100644 index 0000000000000..ce7de5923b772 --- /dev/null +++ b/blocksuite/blocks/src/database-block/adapters/extension.ts @@ -0,0 +1,11 @@ +import type { ExtensionType } from '@blocksuite/block-std'; + +import { DatabaseBlockHtmlAdapterExtension } from './html.js'; +import { DatabaseBlockMarkdownAdapterExtension } from './markdown.js'; +import { DatabaseBlockNotionHtmlAdapterExtension } from './notion-html.js'; + +export const DatabaseBlockAdapterExtensions: ExtensionType[] = [ + DatabaseBlockHtmlAdapterExtension, + DatabaseBlockMarkdownAdapterExtension, + DatabaseBlockNotionHtmlAdapterExtension, +]; diff --git a/blocksuite/blocks/src/database-block/database-spec.ts b/blocksuite/blocks/src/database-block/database-spec.ts index d3277c2d79720..ae617f6fc9cf4 100644 --- a/blocksuite/blocks/src/database-block/database-spec.ts +++ b/blocksuite/blocks/src/database-block/database-spec.ts @@ -7,6 +7,7 @@ import { import { DatabaseSelectionExtension } from '@blocksuite/data-view'; import { literal } from 'lit/static-html.js'; +import { DatabaseBlockAdapterExtensions } from './adapters/extension.js'; import { commands } from './commands.js'; import { DatabaseDragHandleOption } from './config.js'; import { DatabaseBlockService } from './database-service.js'; @@ -18,4 +19,5 @@ export const DatabaseBlockSpec: ExtensionType[] = [ BlockViewExtension('affine:database', literal`affine-database`), DatabaseDragHandleOption, DatabaseSelectionExtension, -]; + DatabaseBlockAdapterExtensions, +].flat(); diff --git a/blocksuite/blocks/src/divider-block/adapters/extension.ts b/blocksuite/blocks/src/divider-block/adapters/extension.ts new file mode 100644 index 0000000000000..83e73de96fda2 --- /dev/null +++ b/blocksuite/blocks/src/divider-block/adapters/extension.ts @@ -0,0 +1,13 @@ +import type { ExtensionType } from '@blocksuite/block-std'; + +import { DividerBlockHtmlAdapterExtension } from './html.js'; +import { DividerBlockMarkdownAdapterExtension } from './markdown.js'; +import { DividerBlockNotionHtmlAdapterExtension } from './notion-html.js'; +import { DividerBlockPlainTextAdapterExtension } from './plain-text.js'; + +export const DividerBlockAdapterExtensions: ExtensionType[] = [ + DividerBlockHtmlAdapterExtension, + DividerBlockMarkdownAdapterExtension, + DividerBlockNotionHtmlAdapterExtension, + DividerBlockPlainTextAdapterExtension, +]; diff --git a/blocksuite/blocks/src/divider-block/divider-spec.ts b/blocksuite/blocks/src/divider-block/divider-spec.ts index fa6d715ecafb0..ca2f50352ec91 100644 --- a/blocksuite/blocks/src/divider-block/divider-spec.ts +++ b/blocksuite/blocks/src/divider-block/divider-spec.ts @@ -1,6 +1,9 @@ import { BlockViewExtension, type ExtensionType } from '@blocksuite/block-std'; import { literal } from 'lit/static-html.js'; +import { DividerBlockAdapterExtensions } from './adapters/extension.js'; + export const DividerBlockSpec: ExtensionType[] = [ BlockViewExtension('affine:divider', literal`affine-divider`), -]; + DividerBlockAdapterExtensions, +].flat(); diff --git a/blocksuite/blocks/src/image-block/adapters/extension.ts b/blocksuite/blocks/src/image-block/adapters/extension.ts new file mode 100644 index 0000000000000..bdfd0cc7823fb --- /dev/null +++ b/blocksuite/blocks/src/image-block/adapters/extension.ts @@ -0,0 +1,11 @@ +import type { ExtensionType } from '@blocksuite/block-std'; + +import { ImageBlockHtmlAdapterExtension } from './html.js'; +import { ImageBlockMarkdownAdapterExtension } from './markdown.js'; +import { ImageBlockNotionHtmlAdapterExtension } from './notion-html.js'; + +export const ImageBlockAdapterExtensions: ExtensionType[] = [ + ImageBlockHtmlAdapterExtension, + ImageBlockMarkdownAdapterExtension, + ImageBlockNotionHtmlAdapterExtension, +]; diff --git a/blocksuite/blocks/src/image-block/image-spec.ts b/blocksuite/blocks/src/image-block/image-spec.ts index 30e73efa9d775..cc3409744bae7 100644 --- a/blocksuite/blocks/src/image-block/image-spec.ts +++ b/blocksuite/blocks/src/image-block/image-spec.ts @@ -8,6 +8,7 @@ import { } from '@blocksuite/block-std'; import { literal } from 'lit/static-html.js'; +import { ImageBlockAdapterExtensions } from './adapters/extension.js'; import { commands } from './commands/index.js'; import { ImageBlockService, ImageDragHandleOption } from './image-service.js'; @@ -29,4 +30,5 @@ export const ImageBlockSpec: ExtensionType[] = [ }), ImageDragHandleOption, ImageSelectionExtension, -]; + ImageBlockAdapterExtensions, +].flat(); diff --git a/blocksuite/blocks/src/latex-block/adapters/extension.ts b/blocksuite/blocks/src/latex-block/adapters/extension.ts new file mode 100644 index 0000000000000..353f255bab3e8 --- /dev/null +++ b/blocksuite/blocks/src/latex-block/adapters/extension.ts @@ -0,0 +1,11 @@ +import type { ExtensionType } from '@blocksuite/block-std'; + +import { LatexBlockMarkdownAdapterExtension } from './markdown.js'; +import { LatexBlockNotionHtmlAdapterExtension } from './notion-html.js'; +import { LatexBlockPlainTextAdapterExtension } from './plain-text.js'; + +export const LatexBlockAdapterExtensions: ExtensionType[] = [ + LatexBlockMarkdownAdapterExtension, + LatexBlockNotionHtmlAdapterExtension, + LatexBlockPlainTextAdapterExtension, +]; diff --git a/blocksuite/blocks/src/latex-block/latex-spec.ts b/blocksuite/blocks/src/latex-block/latex-spec.ts index eeb86c1aaa346..0d15e6a70542d 100644 --- a/blocksuite/blocks/src/latex-block/latex-spec.ts +++ b/blocksuite/blocks/src/latex-block/latex-spec.ts @@ -5,9 +5,11 @@ import { } from '@blocksuite/block-std'; import { literal } from 'lit/static-html.js'; +import { LatexBlockAdapterExtensions } from './adapters/extension.js'; import { commands } from './commands.js'; export const LatexBlockSpec: ExtensionType[] = [ BlockViewExtension('affine:latex', literal`affine-latex`), CommandExtension(commands), -]; + LatexBlockAdapterExtensions, +].flat(); diff --git a/blocksuite/blocks/src/root-block/adapters/extension.ts b/blocksuite/blocks/src/root-block/adapters/extension.ts new file mode 100644 index 0000000000000..20e46a2615b40 --- /dev/null +++ b/blocksuite/blocks/src/root-block/adapters/extension.ts @@ -0,0 +1,11 @@ +import type { ExtensionType } from '@blocksuite/block-std'; + +import { RootBlockHtmlAdapterExtension } from './html.js'; +import { RootBlockMarkdownAdapterExtension } from './markdown.js'; +import { RootBlockNotionHtmlAdapterExtension } from './notion-html.js'; + +export const RootBlockAdapterExtensions: ExtensionType[] = [ + RootBlockHtmlAdapterExtension, + RootBlockMarkdownAdapterExtension, + RootBlockNotionHtmlAdapterExtension, +]; diff --git a/blocksuite/blocks/src/root-block/edgeless/edgeless-root-spec.ts b/blocksuite/blocks/src/root-block/edgeless/edgeless-root-spec.ts index c901ed94475ff..b74d354bebe0a 100644 --- a/blocksuite/blocks/src/root-block/edgeless/edgeless-root-spec.ts +++ b/blocksuite/blocks/src/root-block/edgeless/edgeless-root-spec.ts @@ -18,6 +18,7 @@ import { ToolController } from '@blocksuite/block-std/gfx'; import { literal, unsafeStatic } from 'lit/static-html.js'; import { ExportManagerExtension } from '../../_common/export-manager/export-manager.js'; +import { RootBlockAdapterExtensions } from '../adapters/extension.js'; import { commands } from '../commands/index.js'; import { AFFINE_DOC_REMOTE_SELECTION_WIDGET } from '../widgets/doc-remote-selection/doc-remote-selection.js'; import { AFFINE_DRAG_HANDLE_WIDGET } from '../widgets/drag-handle/consts.js'; @@ -96,7 +97,8 @@ const EdgelessCommonExtension: ExtensionType[] = [ ToolController, DNDAPIExtension, DocDisplayMetaService, -]; + RootBlockAdapterExtensions, +].flat(); export const EdgelessRootBlockSpec: ExtensionType[] = [ ...EdgelessCommonExtension, diff --git a/blocksuite/blocks/src/root-block/page/page-root-spec.ts b/blocksuite/blocks/src/root-block/page/page-root-spec.ts index 565c5915dcbe0..21a340555d38f 100644 --- a/blocksuite/blocks/src/root-block/page/page-root-spec.ts +++ b/blocksuite/blocks/src/root-block/page/page-root-spec.ts @@ -16,6 +16,7 @@ import { import { literal, unsafeStatic } from 'lit/static-html.js'; import { ExportManagerExtension } from '../../_common/export-manager/export-manager.js'; +import { RootBlockAdapterExtensions } from '../adapters/extension.js'; import { commands } from '../commands/index.js'; import { AFFINE_DOC_REMOTE_SELECTION_WIDGET } from '../widgets/doc-remote-selection/doc-remote-selection.js'; import { AFFINE_DRAG_HANDLE_WIDGET } from '../widgets/drag-handle/consts.js'; @@ -73,4 +74,5 @@ export const PageRootBlockSpec: ExtensionType[] = [ ExportManagerExtension, DNDAPIExtension, DocDisplayMetaService, -]; + RootBlockAdapterExtensions, +].flat();