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 diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index c6d34e549e316..4fb05573fef45 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 @@ -179,12 +209,17 @@ 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 uses: ./.github/actions/setup-node with: electron-install: true + playwright-install: true full-cache: true - name: Download affine.linux-x64-gnu.node @@ -194,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 @@ -767,6 +802,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/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/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/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-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/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-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/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-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/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/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/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/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/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/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/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/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/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/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/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/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/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..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, @@ -44,7 +43,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 +69,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 +103,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 +114,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 +131,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,10 +233,12 @@ export class DragEventWatcher { return true; }; - private _onDrop = (context: UIEventStateContext) => { + private readonly _onDrop = (context: UIEventStateContext) => { 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); @@ -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/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/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/package.json b/blocksuite/playground/package.json index 6120359211115..73f2a30c232ba 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": { @@ -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", @@ -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/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/__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/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/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 0000000000000..1a51fe7204149 Binary files /dev/null and b/blocksuite/tests-legacy/fixtures/smile.png differ diff --git a/blocksuite/tests-legacy/format-bar.spec.ts b/blocksuite/tests-legacy/format-bar.spec.ts new file mode 100644 index 0000000000000..c9b7dd4fdc921 --- /dev/null +++ b/blocksuite/tests-legacy/format-bar.spec.ts @@ -0,0 +1,1027 @@ +import type { DeltaInsert } from '@inline/types.js'; +import { expect } from '@playwright/test'; + +import { + activeEmbed, + captureHistory, + dragBetweenCoords, + dragBetweenIndices, + enterPlaygroundRoom, + focusRichText, + focusTitle, + getBoundingBox, + getEditorHostLocator, + getPageSnapshot, + getSelectionRect, + initEmptyParagraphState, + initImageState, + initThreeParagraphs, + pasteByKeyboard, + pressArrowDown, + pressArrowUp, + pressEnter, + pressEscape, + pressTab, + scrollToBottom, + scrollToTop, + selectAllBlocksByKeyboard, + selectAllByKeyboard, + setInlineRangeInInlineEditor, + setSelection, + switchReadonly, + type, + undoByKeyboard, + updateBlockType, + waitNextFrame, + withPressKey, +} from './utils/actions/index.js'; +import { + assertAlmostEqual, + assertBlockChildrenIds, + assertExists, + assertLocatorVisible, + assertRichImage, + assertRichTextInlineRange, + assertRichTexts, +} from './utils/asserts.js'; +import { test } from './utils/playwright.js'; +import { getFormatBar } from './utils/query.js'; + +test('should format quick bar show when select 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(); + + 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/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 ); 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..c6f40d7e38b6a 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", @@ -153,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..53d8055427f15 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", @@ -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/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/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/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(), 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/admin/package.json b/packages/frontend/admin/package.json index 9f969a735ef71..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", @@ -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, + }; +} 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/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/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) ); 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/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/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 aa8a649c4e68c..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,13 +4,14 @@ 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 bytes from 'bytes'; +import { useLiveData, useService } from '@toeverything/infra'; 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 +67,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 ( { + setOpen(!open); + track.doc.biDirectionalLinksPanel.$.toggle({ + type: open ? 'collapse' : 'expand', + }); + }, [open, setOpen]); + return ( - + {title} {length ? ( @@ -197,6 +208,9 @@ export const BiDirectionalLinkPanel = () => { const handleClickShow = useCallback(() => { setShow(!show); + track.doc.biDirectionalLinksPanel.$.toggle({ + type: show ? 'collapse' : 'expand', + }); }, [show, setShow]); const textRendererOptions = useMemo(() => { @@ -243,7 +257,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 +310,9 @@ export const BiDirectionalLinkPanel = () => { to={to} key={link.blockId} className={styles.linkPreview} + onClick={() => { + track.doc.biDirectionalLinksPanel.backlinkPreview.navigate(); + }} > {edgelessLink ? ( <> 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..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 @@ -1,12 +1,14 @@ 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 { useCallback, useMemo, useRef, useState } from 'react'; +import { useService } from '@toeverything/infra'; +import { memo, useCallback, useMemo, useRef, useState } from 'react'; import { ListFloatingToolbar } from '../components/list-floating-toolbar'; import { usePageItemGroupDefinitions } from '../group-definitions'; @@ -48,7 +50,7 @@ const usePageOperationsRenderer = () => { return pageOperationsRenderer; }; -export const VirtualizedPageList = ({ +export const VirtualizedPageList = memo(function VirtualizedPageList({ tag, collection, filters, @@ -60,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); @@ -200,4 +202,4 @@ export const VirtualizedPageList = ({ /> ); -}; +}); 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/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 = ({ })} ); -}; +}); 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/general-setting/plans/checkout-slot.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/checkout-slot.tsx index b93918bbde563..252cf517eecee 100644 --- a/packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/checkout-slot.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/checkout-slot.tsx @@ -1,5 +1,8 @@ import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; -import { SubscriptionService } from '@affine/core/modules/cloud'; +import { + SubscriptionService, + UserQuotaService, +} from '@affine/core/modules/cloud'; import { UrlService } from '@affine/core/modules/url'; import type { CreateCheckoutSessionInput } from '@affine/graphql'; import { useService } from '@toeverything/infra'; @@ -7,6 +10,7 @@ import { nanoid } from 'nanoid'; import { type PropsWithChildren, type ReactNode, + useCallback, useEffect, useState, } from 'react'; @@ -35,26 +39,23 @@ export const CheckoutSlot = ({ const urlService = useService(UrlService); const subscriptionService = useService(SubscriptionService); + const userQuotaService = useService(UserQuotaService); + + const revalidate = useCallback(() => { + 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/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/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/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/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/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 8b8d763afaa51..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']; @@ -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 { 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, 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/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: { 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/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/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..08b98ec18ebed 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" @@ -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" @@ -667,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" @@ -716,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" @@ -751,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" @@ -790,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" @@ -1280,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" @@ -1319,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" @@ -1341,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" @@ -1669,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 @@ -1829,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 @@ -1850,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" @@ -1876,7 +1877,7 @@ __metadata: peerDependenciesMeta: aws-crt: optional: true - checksum: 10/60ff697af9fbd07647838e8ece23cadb3e8e5cda59edfa8223ebd68f6acc8ecb7fb461c979346bed6d78e5103ea9a81315bb4895068cf4aebb159a49223872d3 + checksum: 10/f8f114a0c764653470735839dfc9065863cd9d059998563d6db27c804d05ebd830f772cfd55934567e7738e4d9a5d7b68efbfc29533de54507bc526db4127b15 languageName: node linkType: hard @@ -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" @@ -3562,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" @@ -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" @@ -3767,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" @@ -3777,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 @@ -3811,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" @@ -4922,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" @@ -4943,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" @@ -4964,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" @@ -4985,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" @@ -5006,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" @@ -5027,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" @@ -5048,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" @@ -5069,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" @@ -5090,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" @@ -5111,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" @@ -5132,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" @@ -5153,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" @@ -5174,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" @@ -5195,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" @@ -5216,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" @@ -5237,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" @@ -5258,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" @@ -5279,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" @@ -5293,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" @@ -5314,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" @@ -5335,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" @@ -5356,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" @@ -5377,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" @@ -5398,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" @@ -6914,6 +7095,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" @@ -7052,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 @@ -7272,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" @@ -7301,7 +7495,7 @@ __metadata: bin: napi: ./dist/cli.js napi-raw: ./cli.mjs - checksum: 10/7f9f800e2eed5ad1476a7dcdf7ab024fd056cff3f036e34b6c057fc0f92c7f4c9c521cb85396f1326ef511c452ce0c46f68860a5d4a8a9fde356ef89795cda11 + checksum: 10/455cce2d09f673a2506cac5f515e1e592354ff35473072e4a5bbbe3c9afbeab3a4c10e2b262890345cb15ed4581fd9b8ff41fcfd3eeba5e4d9b922d8bd99fbc7 languageName: node linkType: hard @@ -9174,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" @@ -9192,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 @@ -9217,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 @@ -9380,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: @@ -9489,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: @@ -9531,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: @@ -9557,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: @@ -9680,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 @@ -9777,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" @@ -9805,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 @@ -9881,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 @@ -10459,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: @@ -10596,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: @@ -10615,7 +10828,7 @@ __metadata: optional: true "@types/react-dom": optional: true - checksum: 10/1b5253915c996ea29ba300470053afc8f34ab245eb1285ee2ebad2ed363809e76b71e04052ef33f0b532e9e5b3f4930d4380add330d2aac68cf11dd3f0bd43c8 + checksum: 10/bb436dc29f7aeec93eaeb77d385d07b2b69e837abdc73e5be3f59a19479346763f8065a1d780502fe3891a767374b0544a6fc109266a8d347ee23e22759db625 languageName: node linkType: hard @@ -10767,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" @@ -10786,7 +10999,7 @@ __metadata: optional: true "@types/react-dom": optional: true - checksum: 10/f0cdcc49b2384b6b2ec5c401d1ddd0ce9232bd0a464a280062d20762a86a27366148a148ea2b6b3beb7143fc27fb36e0e78e689fd0acd754f858d6543bb17d4c + checksum: 10/351a94dc371b12e57b43197d2be12350f67150645922e29dbaff36e4d7ac26a94e32cb7e0a388a7278bd568c30e35929851030012496b98797335eb741c0bb5d languageName: node linkType: hard @@ -10803,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" @@ -10820,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": "*" @@ -10831,7 +11044,7 @@ __metadata: optional: true "@types/react-dom": optional: true - checksum: 10/70efa2688a599cb43f0b9d3b6730a5d44339bbe20446c16a98f2ae86b72fa1e2e4389e705d9eb1c368cefd4895b0e058a49d65d3b412d5436cac8a39545ff857 + checksum: 10/61edc875340a6e89228d9a42403456b2351e6c12e316bc372dfde1e616464f29d91cfed8cbda03fde634ef5b3d2046195173b20e623888ddf8316dd433aab28a languageName: node linkType: hard @@ -10848,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" @@ -10867,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: @@ -10892,7 +11105,7 @@ __metadata: optional: true "@types/react-dom": optional: true - checksum: 10/157e0ae1ad07d8d8c4e11b9b1604223d95501685b558314b68b9e1e05b02dac1884237c2837dcd4caea9652974634f8e6ea1b1bf59e2f06a78fde2b3e50099c5 + checksum: 10/63978b6e73431c9b21b7e9926e6e202caccac2016831f580fbabfca8d64eb89da4747e366c528421499fab550de10bcd8c5a21216d8a2536d3f3949f06bedb4e languageName: node linkType: hard @@ -10931,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" @@ -10953,7 +11166,7 @@ __metadata: optional: true "@types/react-dom": optional: true - checksum: 10/0959e6217b17946f08e59ecc0220109d8170a606fb3f342d57317ff6aa4749e0ec335fce5cd295ac550cc55c8fb89c0d1a6496c73189dedde05fdfc9d91067f5 + checksum: 10/bfdd0fe4d271cd2c1810ab50a4d2c5090b7b91afa0b7a0e74aa371f488be8ba3c14cf552c5aa35da8a14bcfe97a905df95dc2bc09b14efec597e190ff4ec0d10 languageName: node linkType: hard @@ -10991,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" @@ -11012,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": "*" @@ -11023,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" @@ -11037,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" @@ -11051,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" @@ -11083,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" @@ -11105,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": "*" @@ -11116,7 +11329,7 @@ __metadata: optional: true "@types/react-dom": optional: true - checksum: 10/36965cb6e3550126f31f65682a5ea57fe137df4b21e5aa12daa59ae5cbef063dac31c02977b2e9ad829d506c71372fc2ee6099566db7a9654d41ffeb3947cee2 + checksum: 10/8f21bc61a752291319f64327fa9b2b310c93efe55bb6e0fec7d72a4d59e1e80b28a25ad13c3b9ac600bc0c0e68f155f2226ef76c54d9e8226b4066b70611a4f3 languageName: node linkType: hard @@ -11310,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" @@ -11319,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" @@ -11333,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": "*" @@ -11344,7 +11557,7 @@ __metadata: optional: true "@types/react-dom": optional: true - checksum: 10/2b5633751fb89f4f1636004e25d9c1fb77a31dc704cd8cce39f47720b8d6b4a8c6cac040470f1a69a5b2f1972604fa5a50a749f764003674147aa9edb9acc282 + checksum: 10/acf3d5cd77be0dd117f04385b5524c7ca3296cf89dbdd468fe3bfc0e8da1a326543ba6e5026d73b96023e56a12d9cd72ae431ac62e6ce7396b8c1c012344b717 languageName: node linkType: hard @@ -11463,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" @@ -11488,7 +11701,7 @@ __metadata: optional: true "@types/react-dom": optional: true - checksum: 10/59a5b2868d00df1acd01bd47dc7c39d15c995a990b61b14d1c496b607c0941abdb668f34d561234050a8865ba34c51e28064e7dbcfc8da7557ef2923c1561ff8 + checksum: 10/55d69414a4ca3d6ccbcfa980e3ea1d618c76b8d669ec5522ba9aad4445f42ab5b0022bbebffe928f72b36525baae68bc61cc046fe158b13ccd3dc43f15e59a83 languageName: node linkType: hard @@ -11564,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" @@ -11589,7 +11802,7 @@ __metadata: optional: true "@types/react-dom": optional: true - checksum: 10/69aa63b7ecdbebd3cd2cb04adc079106e12cf860af3d5ce4065e1722fce322e6648b16733a5f502b00e9d50f2418b559e5d3851503b152c3ff8873d2787784d4 + checksum: 10/c2210c292e67714aaade5230352619bf0151885ce82ff080af1a5f697928e32ed73f406cbf3452ca014a54e0b9182572d6bc16084b9ee2d7ffa2e2fcde45f299 languageName: node linkType: hard @@ -11931,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" +"@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.42.0" - checksum: 10/de90d698cedb4ae612b89e585b51bd013f022b16fb8bce4951909af600f9acb4ed8c5965f26b70d19b09341bff1729957434e20b8d8bf59e77eddea97e30ca6e + "@sentry/core": "npm:8.47.0" + checksum: 10/39f2a93ca8d661d7ec771572ec5961d37832ce5832a3ef27845d06d06742e1586c464deefd5702442d808ff9a29266c158716aa58a2f0ee29dd8f23c46450d99 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" +"@sentry-internal/feedback@npm:8.47.0": + version: 8.47.0 + resolution: "@sentry-internal/feedback@npm:8.47.0" dependencies: - "@sentry/core": "npm:8.45.0" - checksum: 10/7564747f1a5a21a99f4c4cb1d5e2853c18a9374ff1ccf9721c8a509c3450df8eabd44418b2a0d88e76e94f1c7bb93c3b75b0b8498b010a04eaa076be2096fe16 + "@sentry/core": "npm:8.47.0" + checksum: 10/11f7ee6b1201b107d653ef2c2670cc0ebf76992a09a53ee3d0e88936d898aed90495aceda62a7e516cd4b4b6ef509d9ca99e7fedc0866d7d0e75deb13f7bb064 languageName: node linkType: hard -"@sentry-internal/feedback@npm:8.42.0": - version: 8.42.0 - resolution: "@sentry-internal/feedback@npm:8.42.0" +"@sentry-internal/replay-canvas@npm:8.47.0": + version: 8.47.0 + resolution: "@sentry-internal/replay-canvas@npm:8.47.0" dependencies: - "@sentry/core": "npm:8.42.0" - checksum: 10/2e6eab58ba50a7bc92972229f20313d5cbc2b991dda79a44a8cd7b8d981a29e7594508585e3d6618a7829db8c2ffe0a017860a465a64ecc7ba5c67a0de10cbb0 + "@sentry-internal/replay": "npm:8.47.0" + "@sentry/core": "npm:8.47.0" + checksum: 10/ee23cb9b5aaa122abd9ad19d8c5c01939b3e350d503d15b019c487ce6b73fcabb324a2c6453b670e4cdc179d495a1b2e9d4217e2d4b85fa32878a675871dc61c 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/replay@npm:8.47.0": + version: 8.47.0 + resolution: "@sentry-internal/replay@npm:8.47.0" dependencies: - "@sentry/core": "npm:8.45.0" - checksum: 10/580dcb15f69cd7d9052d0b0461b170bb086571bad938cb0c44e8305d8662fba99f55f644b61c935b3cc1b188da31187f465499ecf8c96cf6f31123e1c7055965 - 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" - dependencies: - "@sentry-internal/replay": "npm:8.42.0" - "@sentry/core": "npm:8.42.0" - checksum: 10/60da91e2dbcba5fe4ee0fa7d609b4e4455acb8a6a958aa38c70377369c194dc8fb98ff02afce356a68ff2e917df6ab96a1dba756e2181c95178ec39c5676c33b - 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" - dependencies: - "@sentry-internal/replay": "npm:8.45.0" - "@sentry/core": "npm:8.45.0" - checksum: 10/72164ee7a2af509f0d6311fb69a01173601de7e8a341757cc6e11715775f9a37947b8d735c1fb555d7d9e5028db96c9b7b36e33a473f3548fea3a4c584b84355 - languageName: node - linkType: hard - -"@sentry-internal/replay@npm:8.42.0": - version: 8.42.0 - resolution: "@sentry-internal/replay@npm:8.42.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 @@ -12014,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" - 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" +"@sentry/browser@npm:8.47.0": + version: 8.47.0 + resolution: "@sentry/browser@npm:8.47.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 @@ -12142,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 @@ -12179,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 @@ -12263,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 @@ -12329,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" @@ -12341,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 @@ -12688,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" @@ -12700,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 @@ -12842,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 @@ -12935,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 @@ -13696,7 +13851,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: @@ -14711,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 @@ -14788,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 @@ -14941,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" @@ -14958,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 @@ -14994,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: @@ -15004,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 @@ -15026,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" @@ -15044,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: @@ -15059,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" @@ -15069,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" @@ -15076,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" @@ -15101,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 @@ -15114,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" @@ -15130,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 @@ -15142,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 @@ -15199,6 +15414,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 +17460,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 @@ -17343,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 @@ -20165,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: @@ -20248,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" @@ -21875,6 +22211,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" @@ -22486,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" @@ -22501,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 @@ -22671,9 +23014,9 @@ __metadata: languageName: node linkType: hard -"html-validate@npm:^8.27.0": - version: 8.27.0 - resolution: "html-validate@npm:8.27.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" @@ -22698,8 +23041,8 @@ __metadata: vitest: optional: true bin: - html-validate: bin/html-validate.js - checksum: 10/6fbced77ac357836b9a55a5cf2da86f7d03b544e1d3d0526232be301f9a12eeaa29f4421d5e6c692ca78f5196b0c36d879f37b9e666a7f67035066103f415590 + html-validate: bin/html-validate.mjs + checksum: 10/56f7fe8404aee38428ebf130b4793ca5eeec8f55e562109c368717015f6ec5a706c2376759c2eb212802aa01a918a55bdd168894d9473989e1fe65f001bd991c languageName: node linkType: hard @@ -22988,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: @@ -22997,7 +23340,7 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 10/3a71b5052d84388a9097b594c2b8dc8bfba3f3198d1ca89217e5fb4836d528aa1aee54432269ae092c439e2a12bb71c2abe05abc03cd3e657a001fe5e6883b51 + checksum: 10/43e15d15fcf046c0fb7a44f7de6c224e17145c66334e81bb45b36b0e3e550cc849f80f9de406f3805f57c2050fd8d33bed561933dfaca5e4681a8addc1957be1 languageName: node linkType: hard @@ -23286,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" @@ -23298,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 @@ -23892,7 +24235,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: @@ -24045,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 @@ -24064,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" @@ -24074,7 +24417,7 @@ __metadata: optional: true react: optional: true - checksum: 10/b81722db9575a72f45ee451b5fac9bdc2c119d4dadfdda2606e646d6aee309d02e26afabeaa5939c722f5e78bac21de4c9ee15fb5ea2815a924be87dd1ff4faf + checksum: 10/4ea2be5150c211178dba25716e5d090896f1cdca60a8be466db9eb3f5dd7e445695193d8d36c7d1e0bc0aa887bdd99dae31c191950046ae4f40556c3de4cb2fe languageName: node linkType: hard @@ -24092,7 +24435,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: @@ -24325,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 @@ -25032,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 @@ -26056,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" @@ -26068,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 @@ -26279,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 @@ -26402,9 +26745,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 +26758,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 +26774,7 @@ __metadata: optional: true bin: msw: cli/index.js - checksum: 10/20a74a5e49b780567aa3430c0de9f27830208f931d6109087a24566fcb3e68f058ff51b022891bfe6fe0f320afad22b4039593722b928aa9c4ab5b05c2746c4a + checksum: 10/165ccf37d90da0d5271fdb8e01f89f48f7a60fb810038ff73d18c0e5e5ddfdb1266002d19cde61b9ae689ef37c39499b10d9d07e0d16662a31630ce9adce1d77 languageName: node linkType: hard @@ -27155,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 @@ -27202,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" @@ -27219,7 +27562,7 @@ __metadata: optional: true bin: openai: bin/cli - checksum: 10/98856dd17ac3ec2cb787ab4b6d4bd1b7c81a725856c5fc7eecc1557d4b28078e79aa45ec382d9f2bdd7ec8d59478e04a0ec5cb2baedee42c965cbc4d5151b88e + checksum: 10/51ef7522d3c3589401b9bb0d49a3bd9df2a84648b996b3452dd4bae99c8e6b27d63a415c41847900954197051b5598de85a76f12c6b2deab33d8d70ceab5d7c3 languageName: node linkType: hard @@ -29106,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 @@ -29164,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 @@ -29210,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 @@ -29502,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 @@ -30747,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 @@ -31111,7 +31453,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 @@ -31638,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 @@ -31911,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" @@ -31939,7 +32281,7 @@ __metadata: bin: tailwind: lib/cli.js tailwindcss: lib/cli.js - checksum: 10/d84b3d9bd8f3d53021b6754e3d7efa704cf32f72714dea2036d955fe46ea4154180d2c47593881a9c524229f9efc13fa924fa6347fc8969427036329ee8a9338 + checksum: 10/b0e00533ae3800223b5b71af9cb1dd9bfea5ef5ffa01300f1ced99de9511487aa41e03106173e4168c56c8f6600ee21c98c1d75a5def23cddf9b39b4ad71210d languageName: node linkType: hard @@ -32066,6 +32408,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" @@ -32144,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 @@ -32729,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 @@ -32880,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 @@ -33230,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 @@ -33478,6 +33831,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" @@ -33502,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" @@ -33550,7 +33919,7 @@ __metadata: optional: true bin: vite: bin/vite.js - checksum: 10/eca0949b8cbc887e78977515d8fc22eaa2d03425d60a0a422f2db1da9d26bd1b431b2815a273c798e8e3fe176a99e105c3d87b0ba615ca19b8bf19e0334d807a + checksum: 10/582bc53c14ae8c8dcb6887a1a24fb554ad6f19446ae0f9df18a47e7ab8787a08687e94a2f4c8636001233207640f91d081e31b00cc3ef9d90908a4bae26410b4 languageName: node linkType: hard @@ -33980,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 @@ -34002,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" @@ -34020,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 @@ -34040,7 +34408,7 @@ __metadata: bin: wrangler: bin/wrangler.js wrangler2: bin/wrangler.js - checksum: 10/fc5404e649dc223104ef28281e48d85161cb6732833c050d8ae2546bcd267d83f228d4dbf102d4885f334e77d51691d8f6e29365ff07fc5d77574f35ef7a1d8c + checksum: 10/d176e1442fb9f6607f34e0af73ebed79c7957a6b7c4dc0df4ccd3d42cfd0a00d5a29a57420cf6e0c598c410678e8e2f12877af84acb38d1dd88a1fbd87565fe2 languageName: node linkType: hard