diff --git a/blocksuite/affine/block-database/src/database-spec.ts b/blocksuite/affine/block-database/src/database-spec.ts index 86edb47ce6024..c86ba5e1ed655 100644 --- a/blocksuite/affine/block-database/src/database-spec.ts +++ b/blocksuite/affine/block-database/src/database-spec.ts @@ -3,7 +3,6 @@ import { CommandExtension, FlavourExtension, } from '@blocksuite/block-std'; -import { DatabaseSelectionExtension } from '@blocksuite/data-view'; import type { ExtensionType } from '@blocksuite/store'; import { literal } from 'lit/static-html.js'; @@ -16,6 +15,5 @@ export const DatabaseBlockSpec: ExtensionType[] = [ DatabaseBlockService, CommandExtension(commands), BlockViewExtension('affine:database', literal`affine-database`), - DatabaseSelectionExtension, DatabaseBlockAdapterExtensions, ].flat(); diff --git a/blocksuite/affine/block-image/src/components/page-image-block.ts b/blocksuite/affine/block-image/src/components/page-image-block.ts index 8961bfd41f9cc..dc7b21b1bcb98 100644 --- a/blocksuite/affine/block-image/src/components/page-image-block.ts +++ b/blocksuite/affine/block-image/src/components/page-image-block.ts @@ -1,11 +1,12 @@ import { ImageSelection } from '@blocksuite/affine-shared/selection'; -import type { BaseSelection, UIEventStateContext } from '@blocksuite/block-std'; +import type { UIEventStateContext } from '@blocksuite/block-std'; import { BlockSelection, ShadowlessElement, TextSelection, } from '@blocksuite/block-std'; import { WithDisposable } from '@blocksuite/global/utils'; +import type { BaseSelection } from '@blocksuite/store'; import { css, html, type PropertyValues } from 'lit'; import { property, query, state } from 'lit/decorators.js'; import { styleMap } from 'lit/directives/style-map.js'; diff --git a/blocksuite/affine/block-image/src/image-spec.ts b/blocksuite/affine/block-image/src/image-spec.ts index 92068953a3460..a1fbe453bf62e 100644 --- a/blocksuite/affine/block-image/src/image-spec.ts +++ b/blocksuite/affine/block-image/src/image-spec.ts @@ -1,4 +1,3 @@ -import { ImageSelectionExtension } from '@blocksuite/affine-shared/selection'; import { BlockViewExtension, CommandExtension, @@ -29,6 +28,5 @@ export const ImageBlockSpec: ExtensionType[] = [ imageToolbar: literal`affine-image-toolbar-widget`, }), ImageDropOption, - ImageSelectionExtension, ImageBlockAdapterExtensions, ].flat(); diff --git a/blocksuite/affine/block-list/src/list-block.ts b/blocksuite/affine/block-list/src/list-block.ts index 06396e97c6c5b..b6b5b36723eb1 100644 --- a/blocksuite/affine/block-list/src/list-block.ts +++ b/blocksuite/affine/block-list/src/list-block.ts @@ -14,13 +14,14 @@ import { } from '@blocksuite/affine-shared/consts'; import { DocModeProvider } from '@blocksuite/affine-shared/services'; import { getViewportElement } from '@blocksuite/affine-shared/utils'; -import type { BaseSelection, BlockComponent } from '@blocksuite/block-std'; +import type { BlockComponent } from '@blocksuite/block-std'; import { BlockSelection, getInlineRangeProvider, TextSelection, } from '@blocksuite/block-std'; import type { InlineRangeProvider } from '@blocksuite/inline'; +import type { BaseSelection } from '@blocksuite/store'; import { effect } from '@preact/signals-core'; import { html, nothing, type TemplateResult } from 'lit'; import { query, state } from 'lit/decorators.js'; diff --git a/blocksuite/affine/block-note/src/note-service.ts b/blocksuite/affine/block-note/src/note-service.ts index c17882c5e0157..3c9d9913e4bb2 100644 --- a/blocksuite/affine/block-note/src/note-service.ts +++ b/blocksuite/affine/block-note/src/note-service.ts @@ -2,7 +2,6 @@ import { textConversionConfigs } from '@blocksuite/affine-components/rich-text'; import { NoteBlockSchema } from '@blocksuite/affine-model'; import { matchFlavours } from '@blocksuite/affine-shared/utils'; import { - type BaseSelection, type BlockComponent, BlockSelection, BlockService, @@ -11,7 +10,7 @@ import { type UIEventHandler, type UIEventStateContext, } from '@blocksuite/block-std'; -import type { BlockModel } from '@blocksuite/store'; +import type { BaseSelection, BlockModel } from '@blocksuite/store'; import { moveBlockConfigs } from './move-block'; import { quickActionConfig } from './quick-action'; diff --git a/blocksuite/affine/block-surface-ref/src/surface-ref-block.ts b/blocksuite/affine/block-surface-ref/src/surface-ref-block.ts index 8e12194916026..e3099aad33e23 100644 --- a/blocksuite/affine/block-surface-ref/src/surface-ref-block.ts +++ b/blocksuite/affine/block-surface-ref/src/surface-ref-block.ts @@ -26,7 +26,6 @@ import { SpecProvider, } from '@blocksuite/affine-shared/utils'; import { - type BaseSelection, BlockComponent, BlockSelection, BlockServiceWatcher, @@ -47,7 +46,7 @@ import { DisposableGroup, type SerializedXYWH, } from '@blocksuite/global/utils'; -import { type Store } from '@blocksuite/store'; +import type { BaseSelection, Store } from '@blocksuite/store'; import { css, html, nothing, type TemplateResult } from 'lit'; import { query, state } from 'lit/decorators.js'; import { styleMap } from 'lit/directives/style-map.js'; diff --git a/blocksuite/affine/block-surface/src/surface-spec.ts b/blocksuite/affine/block-surface/src/surface-spec.ts index 38e519cab4bef..acd639eda094d 100644 --- a/blocksuite/affine/block-surface/src/surface-spec.ts +++ b/blocksuite/affine/block-surface/src/surface-spec.ts @@ -1,4 +1,3 @@ -import { HighlightSelectionExtension } from '@blocksuite/affine-shared/selection'; import { BlockViewExtension, CommandExtension, @@ -23,7 +22,6 @@ const CommonSurfaceBlockSpec: ExtensionType[] = [ FlavourExtension('affine:surface'), SurfaceBlockService, CommandExtension(commands), - HighlightSelectionExtension, MindMapView, EdgelessCRUDExtension, EdgelessLegacySlotExtension, diff --git a/blocksuite/affine/data-view/src/core/common/selection-schema.ts b/blocksuite/affine/data-view/src/core/common/selection-schema.ts index db6ee5bd567e4..76e0b90393db5 100644 --- a/blocksuite/affine/data-view/src/core/common/selection-schema.ts +++ b/blocksuite/affine/data-view/src/core/common/selection-schema.ts @@ -1,4 +1,4 @@ -import { BaseSelection, SelectionExtension } from '@blocksuite/block-std'; +import { BaseSelection, SelectionExtension } from '@blocksuite/store'; import { z } from 'zod'; import type { DataViewSelection, GetDataViewSelection } from '../types.js'; diff --git a/blocksuite/affine/shared/src/selection/hightlight.ts b/blocksuite/affine/shared/src/selection/hightlight.ts index a4577c727695e..6af6fb016d0c2 100644 --- a/blocksuite/affine/shared/src/selection/hightlight.ts +++ b/blocksuite/affine/shared/src/selection/hightlight.ts @@ -2,7 +2,7 @@ import { type ReferenceParams, ReferenceParamsSchema, } from '@blocksuite/affine-model'; -import { BaseSelection, SelectionExtension } from '@blocksuite/block-std'; +import { BaseSelection, SelectionExtension } from '@blocksuite/store'; export class HighlightSelection extends BaseSelection { static override group = 'scene'; diff --git a/blocksuite/affine/shared/src/selection/image.ts b/blocksuite/affine/shared/src/selection/image.ts index 098c696f44150..f2b15a9a9cfcc 100644 --- a/blocksuite/affine/shared/src/selection/image.ts +++ b/blocksuite/affine/shared/src/selection/image.ts @@ -1,4 +1,4 @@ -import { BaseSelection, SelectionExtension } from '@blocksuite/block-std'; +import { BaseSelection, SelectionExtension } from '@blocksuite/store'; import z from 'zod'; const ImageSelectionSchema = z.object({ diff --git a/blocksuite/affine/widget-drag-handle/src/utils.ts b/blocksuite/affine/widget-drag-handle/src/utils.ts index 16988a00ac31a..20a27f7db1ed2 100644 --- a/blocksuite/affine/widget-drag-handle/src/utils.ts +++ b/blocksuite/affine/widget-drag-handle/src/utils.ts @@ -9,13 +9,9 @@ import { getClosestBlockComponentByPoint, matchFlavours, } from '@blocksuite/affine-shared/utils'; -import type { - BaseSelection, - BlockComponent, - EditorHost, -} from '@blocksuite/block-std'; +import type { BlockComponent, EditorHost } from '@blocksuite/block-std'; import { Point, Rect } from '@blocksuite/global/utils'; -import type { BlockModel } from '@blocksuite/store'; +import type { BaseSelection, BlockModel } from '@blocksuite/store'; import { DRAG_HANDLE_CONTAINER_HEIGHT, diff --git a/blocksuite/affine/widget-remote-selection/src/doc/doc-remote-selection.ts b/blocksuite/affine/widget-remote-selection/src/doc/doc-remote-selection.ts index e6a8e0597f80a..0df138b957e22 100644 --- a/blocksuite/affine/widget-remote-selection/src/doc/doc-remote-selection.ts +++ b/blocksuite/affine/widget-remote-selection/src/doc/doc-remote-selection.ts @@ -1,12 +1,11 @@ import { matchFlavours } from '@blocksuite/affine-shared/utils'; import { - type BaseSelection, BlockSelection, TextSelection, WidgetComponent, } from '@blocksuite/block-std'; import { throttle } from '@blocksuite/global/utils'; -import type { UserInfo } from '@blocksuite/store'; +import type { BaseSelection, UserInfo } from '@blocksuite/store'; import { computed, effect } from '@preact/signals-core'; import { css, html, nothing, type PropertyValues } from 'lit'; import { state } from 'lit/decorators.js'; diff --git a/blocksuite/blocks/src/_specs/common.ts b/blocksuite/blocks/src/_specs/common.ts index a814060a4a414..24e556f486b6c 100644 --- a/blocksuite/blocks/src/_specs/common.ts +++ b/blocksuite/blocks/src/_specs/common.ts @@ -27,6 +27,10 @@ import { RefNodeSlotsExtension, RichTextExtensions, } from '@blocksuite/affine-components/rich-text'; +import { + HighlightSelectionExtension, + ImageSelectionExtension, +} from '@blocksuite/affine-shared/selection'; import { DefaultOpenDocExtension, DocDisplayMetaService, @@ -34,6 +38,13 @@ import { FeatureFlagService, FontLoaderService, } from '@blocksuite/affine-shared/services'; +import { + BlockSelectionExtension, + CursorSelectionExtension, + SurfaceSelectionExtension, + TextSelectionExtension, +} from '@blocksuite/block-std'; +import { DatabaseSelectionExtension } from '@blocksuite/data-view'; import type { ExtensionType } from '@blocksuite/store'; import { AdapterFactoryExtensions } from '../_common/adapters/extension.js'; @@ -77,4 +88,13 @@ export const EdgelessFirstPartyBlockSpecs: ExtensionType[] = [ FontLoaderService, ].flat(); -export const StoreExtensions: ExtensionType[] = [FeatureFlagService]; +export const StoreExtensions: ExtensionType[] = [ + FeatureFlagService, + BlockSelectionExtension, + TextSelectionExtension, + SurfaceSelectionExtension, + CursorSelectionExtension, + HighlightSelectionExtension, + ImageSelectionExtension, + DatabaseSelectionExtension, +]; 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 028fa6be31831..257ace28ad47a 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 @@ -7,9 +7,9 @@ import { getPageRootByElement, stopPropagation, } from '@blocksuite/affine-shared/utils'; -import type { BaseSelection } from '@blocksuite/block-std'; import { WidgetComponent } from '@blocksuite/block-std'; import { assertExists } from '@blocksuite/global/utils'; +import type { BaseSelection } from '@blocksuite/store'; import { autoPlacement, autoUpdate, diff --git a/blocksuite/blocks/src/root-block/widgets/format-bar/format-bar.ts b/blocksuite/blocks/src/root-block/widgets/format-bar/format-bar.ts index 4d373903e9171..8d455afa09edb 100644 --- a/blocksuite/blocks/src/root-block/widgets/format-bar/format-bar.ts +++ b/blocksuite/blocks/src/root-block/widgets/format-bar/format-bar.ts @@ -9,7 +9,6 @@ import { import type { AffineTextAttributes } from '@blocksuite/affine-shared/types'; import { matchFlavours } from '@blocksuite/affine-shared/utils'; import { - type BaseSelection, type BlockComponent, BlockSelection, CursorSelection, @@ -22,6 +21,7 @@ import { DisposableGroup, nextTick, } from '@blocksuite/global/utils'; +import type { BaseSelection } from '@blocksuite/store'; import { autoUpdate, computePosition, diff --git a/blocksuite/framework/block-std/src/extension/index.ts b/blocksuite/framework/block-std/src/extension/index.ts index 8171ad221cde4..20f94f8308ed2 100644 --- a/blocksuite/framework/block-std/src/extension/index.ts +++ b/blocksuite/framework/block-std/src/extension/index.ts @@ -4,7 +4,6 @@ export * from './config.js'; export * from './flavour.js'; export * from './keymap.js'; export * from './lifecycle-watcher.js'; -export * from './selection.js'; export * from './service.js'; export * from './service-watcher.js'; export * from './widget-view-map.js'; diff --git a/blocksuite/framework/block-std/src/extension/selection.ts b/blocksuite/framework/block-std/src/extension/selection.ts deleted file mode 100644 index 33162c92b6e9a..0000000000000 --- a/blocksuite/framework/block-std/src/extension/selection.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { ExtensionType } from '@blocksuite/store'; - -import { SelectionIdentifier } from '../identifier.js'; -import type { SelectionConstructor } from '../selection/index.js'; - -export function SelectionExtension( - selectionCtor: SelectionConstructor -): ExtensionType { - return { - setup: di => { - di.addImpl(SelectionIdentifier(selectionCtor.type), () => selectionCtor); - }, - }; -} diff --git a/blocksuite/framework/block-std/src/identifier.ts b/blocksuite/framework/block-std/src/identifier.ts index 4adf0e4d2d853..571d509c3839f 100644 --- a/blocksuite/framework/block-std/src/identifier.ts +++ b/blocksuite/framework/block-std/src/identifier.ts @@ -4,7 +4,6 @@ import type { Command } from './command/index.js'; import type { EventOptions, UIEventHandler } from './event/index.js'; import type { BlockService, LifeCycleWatcher } from './extension/index.js'; import type { BlockStdScope } from './scope/index.js'; -import type { SelectionConstructor } from './selection/index.js'; import type { BlockViewType, WidgetViewMapType } from './spec/type.js'; export const BlockServiceIdentifier = @@ -33,6 +32,3 @@ export const KeymapIdentifier = createIdentifier<{ getter: (std: BlockStdScope) => Record<string, UIEventHandler>; options?: EventOptions; }>('Keymap'); - -export const SelectionIdentifier = - createIdentifier<SelectionConstructor>('Selection'); diff --git a/blocksuite/framework/block-std/src/range/inline-range-provider.ts b/blocksuite/framework/block-std/src/range/inline-range-provider.ts index 90280f9daed57..5466d0fa964e8 100644 --- a/blocksuite/framework/block-std/src/range/inline-range-provider.ts +++ b/blocksuite/framework/block-std/src/range/inline-range-provider.ts @@ -84,18 +84,21 @@ export const getInlineRangeProvider: ( } }; const inlineRange$: InlineRangeProvider['inlineRange$'] = signal(null); - selectionManager.slots.changed.on(selections => { - const textSelection = selections.find(s => s.type === 'text') as - | TextSelection - | undefined; - const range = rangeManager.value; - if (!range || !textSelection) { - inlineRange$.value = null; - return; - } - const inlineRange = calculateInlineRange(range, textSelection); - inlineRange$.value = inlineRange; - }); + + editorHost.disposables.add( + selectionManager.slots.changed.on(selections => { + const textSelection = selections.find(s => s.type === 'text') as + | TextSelection + | undefined; + const range = rangeManager.value; + if (!range || !textSelection) { + inlineRange$.value = null; + return; + } + const inlineRange = calculateInlineRange(range, textSelection); + inlineRange$.value = inlineRange; + }) + ); return { setInlineRange, diff --git a/blocksuite/framework/block-std/src/range/range-binding.ts b/blocksuite/framework/block-std/src/range/range-binding.ts index 8095d778c2ba5..b3eee6ff2ae13 100644 --- a/blocksuite/framework/block-std/src/range/range-binding.ts +++ b/blocksuite/framework/block-std/src/range/range-binding.ts @@ -1,7 +1,7 @@ import { throttle } from '@blocksuite/global/utils'; -import type { BlockModel } from '@blocksuite/store'; +import type { BaseSelection, BlockModel } from '@blocksuite/store'; -import { type BaseSelection, TextSelection } from '../selection/index.js'; +import { TextSelection } from '../selection/index.js'; import type { BlockComponent } from '../view/element/block-component.js'; import { BLOCK_ID_ATTR } from '../view/index.js'; import { RANGE_SYNC_EXCLUDE_ATTR } from './consts.js'; @@ -247,6 +247,9 @@ export class RangeBinding { }; private readonly _onStdSelectionChanged = (selections: BaseSelection[]) => { + const closestHost = document.activeElement?.closest('editor-host'); + if (closestHost && closestHost !== this.host) return; + const text = selections.find((selection): selection is TextSelection => selection.is(TextSelection) diff --git a/blocksuite/framework/block-std/src/scope/block-std-scope.ts b/blocksuite/framework/block-std/src/scope/block-std-scope.ts index 2019f13e21401..ba220fb61777b 100644 --- a/blocksuite/framework/block-std/src/scope/block-std-scope.ts +++ b/blocksuite/framework/block-std/src/scope/block-std-scope.ts @@ -5,6 +5,7 @@ import { Job, type JobMiddleware, type Store, + StoreSelectionExtension, } from '@blocksuite/store'; import { Clipboard } from '../clipboard/index.js'; @@ -23,13 +24,6 @@ import { StdIdentifier, } from '../identifier.js'; import { RangeManager } from '../range/index.js'; -import { - BlockSelectionExtension, - CursorSelectionExtension, - SelectionManager, - SurfaceSelectionExtension, - TextSelectionExtension, -} from '../selection/index.js'; import { ServiceManager } from '../service/index.js'; import { EditorHost } from '../view/element/index.js'; import { ViewStore } from '../view/view-store.js'; @@ -43,15 +37,10 @@ const internalExtensions = [ ServiceManager, CommandManager, UIEventDispatcher, - SelectionManager, RangeManager, ViewStore, Clipboard, GfxController, - BlockSelectionExtension, - TextSelectionExtension, - SurfaceSelectionExtension, - CursorSelectionExtension, GfxSelectionManager, SurfaceMiddlewareExtension, ViewManager, @@ -107,7 +96,7 @@ export class BlockStdScope { } get selection() { - return this.get(SelectionManager); + return this.get(StoreSelectionExtension); } get view() { diff --git a/blocksuite/framework/block-std/src/selection/index.ts b/blocksuite/framework/block-std/src/selection/index.ts index cdb6841fb72bd..8ac7bfc5bc4d1 100644 --- a/blocksuite/framework/block-std/src/selection/index.ts +++ b/blocksuite/framework/block-std/src/selection/index.ts @@ -1,3 +1 @@ -export * from './base.js'; -export * from './manager.js'; export * from './variants/index.js'; diff --git a/blocksuite/framework/block-std/src/selection/variants/block.ts b/blocksuite/framework/block-std/src/selection/variants/block.ts index dbd75a28ec3f0..765ec2a319f56 100644 --- a/blocksuite/framework/block-std/src/selection/variants/block.ts +++ b/blocksuite/framework/block-std/src/selection/variants/block.ts @@ -1,8 +1,6 @@ +import { BaseSelection, SelectionExtension } from '@blocksuite/store'; import z from 'zod'; -import { SelectionExtension } from '../../extension/selection.js'; -import { BaseSelection } from '../base.js'; - const BlockSelectionSchema = z.object({ blockId: z.string(), }); diff --git a/blocksuite/framework/block-std/src/selection/variants/cursor.ts b/blocksuite/framework/block-std/src/selection/variants/cursor.ts index a6ede90552c3c..9c7d56559e5c7 100644 --- a/blocksuite/framework/block-std/src/selection/variants/cursor.ts +++ b/blocksuite/framework/block-std/src/selection/variants/cursor.ts @@ -1,8 +1,6 @@ +import { BaseSelection, SelectionExtension } from '@blocksuite/store'; import z from 'zod'; -import { SelectionExtension } from '../../extension/selection.js'; -import { BaseSelection } from '../base.js'; - const CursorSelectionSchema = z.object({ x: z.number(), y: z.number(), diff --git a/blocksuite/framework/block-std/src/selection/variants/surface.ts b/blocksuite/framework/block-std/src/selection/variants/surface.ts index 1fe7092e81817..60d9c8e0744db 100644 --- a/blocksuite/framework/block-std/src/selection/variants/surface.ts +++ b/blocksuite/framework/block-std/src/selection/variants/surface.ts @@ -1,8 +1,6 @@ +import { BaseSelection, SelectionExtension } from '@blocksuite/store'; import z from 'zod'; -import { SelectionExtension } from '../../extension/selection.js'; -import { BaseSelection } from '../base.js'; - const SurfaceSelectionSchema = z.object({ blockId: z.string(), elements: z.array(z.string()), diff --git a/blocksuite/framework/block-std/src/selection/variants/text.ts b/blocksuite/framework/block-std/src/selection/variants/text.ts index 29217ead00940..e88c1fd3ae171 100644 --- a/blocksuite/framework/block-std/src/selection/variants/text.ts +++ b/blocksuite/framework/block-std/src/selection/variants/text.ts @@ -1,8 +1,6 @@ +import { BaseSelection, SelectionExtension } from '@blocksuite/store'; import z from 'zod'; -import { SelectionExtension } from '../../extension/selection.js'; -import { BaseSelection } from '../base.js'; - export type TextRangePoint = { blockId: string; index: number; 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 fbe660e006e8d..bd90704f9e68a 100644 --- a/blocksuite/framework/block-std/src/view/element/lit-host.ts +++ b/blocksuite/framework/block-std/src/view/element/lit-host.ts @@ -4,7 +4,11 @@ import { handleError, } from '@blocksuite/global/exceptions'; import { SignalWatcher, Slot, WithDisposable } from '@blocksuite/global/utils'; -import { type BlockModel, Store } from '@blocksuite/store'; +import { + type BlockModel, + Store, + type StoreSelectionExtension, +} from '@blocksuite/store'; import { createContext, provide } from '@lit/context'; import { css, LitElement, nothing, type TemplateResult } from 'lit'; import { property } from 'lit/decorators.js'; @@ -16,7 +20,6 @@ import type { UIEventDispatcher } from '../../event/index.js'; import { WidgetViewMapIdentifier } from '../../identifier.js'; import type { RangeManager } from '../../range/index.js'; import type { BlockStdScope } from '../../scope/block-std-scope.js'; -import type { SelectionManager } from '../../selection/index.js'; import { PropTypes, requiredProperties } from '../decorators/index.js'; import type { ViewStore } from '../view-store.js'; import { BLOCK_ID_ATTR, WIDGET_ID_ATTR } from './consts.js'; @@ -114,7 +117,7 @@ export class EditorHost extends SignalWatcher( return this.std.range; } - get selection(): SelectionManager { + get selection(): StoreSelectionExtension { return this.std.selection; } diff --git a/blocksuite/framework/store/src/extension/index.ts b/blocksuite/framework/store/src/extension/index.ts index 8b7ae56bdbe1c..554a044e137e5 100644 --- a/blocksuite/framework/store/src/extension/index.ts +++ b/blocksuite/framework/store/src/extension/index.ts @@ -1,2 +1,3 @@ export * from './extension'; +export * from './selection'; export * from './store-extension'; diff --git a/blocksuite/framework/block-std/src/selection/base.ts b/blocksuite/framework/store/src/extension/selection/base.ts similarity index 94% rename from blocksuite/framework/block-std/src/selection/base.ts rename to blocksuite/framework/store/src/extension/selection/base.ts index b2433ccbbd382..52a8f84e5c485 100644 --- a/blocksuite/framework/block-std/src/selection/base.ts +++ b/blocksuite/framework/store/src/extension/selection/base.ts @@ -1,6 +1,6 @@ import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions'; -import type { SelectionConstructor } from './manager'; +import type { SelectionConstructor } from './types'; export type BaseSelectionOptions = { blockId: string; diff --git a/blocksuite/framework/store/src/extension/selection/identifier.ts b/blocksuite/framework/store/src/extension/selection/identifier.ts new file mode 100644 index 0000000000000..24f9fdff38475 --- /dev/null +++ b/blocksuite/framework/store/src/extension/selection/identifier.ts @@ -0,0 +1,17 @@ +import { createIdentifier } from '@blocksuite/global/di'; + +import type { ExtensionType } from '../extension'; +import type { SelectionConstructor } from './types'; + +export const SelectionIdentifier = + createIdentifier<SelectionConstructor>('Selection'); + +export function SelectionExtension( + selectionCtor: SelectionConstructor +): ExtensionType { + return { + setup: di => { + di.addImpl(SelectionIdentifier(selectionCtor.type), () => selectionCtor); + }, + }; +} diff --git a/blocksuite/framework/store/src/extension/selection/index.ts b/blocksuite/framework/store/src/extension/selection/index.ts new file mode 100644 index 0000000000000..aabfea3569f34 --- /dev/null +++ b/blocksuite/framework/store/src/extension/selection/index.ts @@ -0,0 +1,4 @@ +export * from './base'; +export * from './identifier'; +export * from './selection-extension'; +export * from './types'; diff --git a/blocksuite/framework/block-std/src/selection/manager.ts b/blocksuite/framework/store/src/extension/selection/selection-extension.ts similarity index 62% rename from blocksuite/framework/block-std/src/selection/manager.ts rename to blocksuite/framework/store/src/extension/selection/selection-extension.ts index 9145f87453c58..258141c1f61f2 100644 --- a/blocksuite/framework/block-std/src/selection/manager.ts +++ b/blocksuite/framework/store/src/extension/selection/selection-extension.ts @@ -1,28 +1,27 @@ import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions'; -import { DisposableGroup, Slot } from '@blocksuite/global/utils'; -import { nanoid, type StackItem } from '@blocksuite/store'; +import { Slot } from '@blocksuite/global/utils'; import { computed, signal } from '@preact/signals-core'; -import { LifeCycleWatcher } from '../extension/index.js'; -import { SelectionIdentifier } from '../identifier.js'; -import type { BlockStdScope } from '../scope/index.js'; -import type { BaseSelection } from './base.js'; +import type { Store } from '../../model'; +import { nanoid } from '../../utils/id-generator'; +import type { StackItem } from '../../yjs'; +import { StoreExtension } from '../store-extension'; +import type { BaseSelection } from './base'; +import { SelectionIdentifier } from './identifier'; +import type { SelectionConstructor } from './types'; -export interface SelectionConstructor<T extends BaseSelection = BaseSelection> { - type: string; - group: string; +export class StoreSelectionExtension extends StoreExtension { + static override readonly key = 'selection'; - new (...args: any[]): T; - fromJSON(json: Record<string, unknown>): T; -} - -export class SelectionManager extends LifeCycleWatcher { - static override readonly key = 'selectionManager'; - - private readonly _id: string; + private readonly _id = `${this.store.id}:${nanoid()}`; + private _selectionConstructors: Record<string, SelectionConstructor> = {}; + private readonly _selections = signal<BaseSelection[]>([]); + private readonly _remoteSelections = signal<Map<number, BaseSelection[]>>( + new Map() + ); private readonly _itemAdded = (event: { stackItem: StackItem }) => { - event.stackItem.meta.set('selection-state', this.value); + event.stackItem.meta.set('selection-state', this._selections.value); }; private readonly _itemPopped = (event: { stackItem: StackItem }) => { @@ -43,50 +42,30 @@ export class SelectionManager extends LifeCycleWatcher { return ctor.fromJSON(json); }; - private readonly _remoteSelections = signal<Map<number, BaseSelection[]>>( - new Map() - ); - - private _selectionConstructors: Record<string, SelectionConstructor> = {}; - - private readonly _selections = signal<BaseSelection[]>([]); - - disposables = new DisposableGroup(); - slots = { changed: new Slot<BaseSelection[]>(), remoteChanged: new Slot<Map<number, BaseSelection[]>>(), }; - private get _store() { - return this.std.workspace.awarenessStore; - } - - get id() { - return this._id; - } - - get remoteSelections() { - return this._remoteSelections.value; - } + constructor(store: Store) { + super(store); - get value() { - return this._selections.value; - } + this.store.provider.getAll(SelectionIdentifier).forEach(ctor => { + [ctor].flat().forEach(ctor => { + this._selectionConstructors[ctor.type] = ctor; + }); + }); - constructor(std: BlockStdScope) { - super(std); - this._id = `${this.std.store.id}:${nanoid()}`; - this._setupDefaultSelections(); - this._store.awareness.on( + this.store.awarenessStore.awareness.on( 'change', (change: { updated: number[]; added: number[]; removed: number[] }) => { const all = change.updated.concat(change.added).concat(change.removed); - const localClientID = this._store.awareness.clientID; + const localClientID = this.store.awarenessStore.awareness.clientID; const exceptLocal = all.filter(id => id !== localClientID); const hasLocal = all.includes(localClientID); if (hasLocal) { - const localSelectionJson = this._store.getLocalSelection(this.id); + const localSelectionJson = + this.store.awarenessStore.getLocalSelection(this._id); const localSelection = localSelectionJson.map(json => { return this._jsonToSelection(json); }); @@ -96,11 +75,11 @@ export class SelectionManager extends LifeCycleWatcher { // Only consider remote selections from other clients if (exceptLocal.length > 0) { const map = new Map<number, BaseSelection[]>(); - this._store.getStates().forEach((state, id) => { - if (id === this._store.awareness.clientID) return; + this.store.awarenessStore.getStates().forEach((state, id) => { + if (id === this.store.awarenessStore.awareness.clientID) return; // selection id starts with the same block collection id from others clients would be considered as remote selections const selection = Object.entries(state.selectionV2) - .filter(([key]) => key.startsWith(this.std.store.id)) + .filter(([key]) => key.startsWith(this.store.id)) .flatMap(([_, selection]) => selection); const selections = selection @@ -122,15 +101,21 @@ export class SelectionManager extends LifeCycleWatcher { map.set(id, selections); }); this._remoteSelections.value = map; + this.slots.remoteChanged.emit(map); } } ); + + this.store.history.on('stack-item-added', this._itemAdded); + this.store.history.on('stack-item-popped', this._itemPopped); } - private _setupDefaultSelections() { - this.std.provider.getAll(SelectionIdentifier).forEach(ctor => { - this.register(ctor); - }); + get value() { + return this._selections.value; + } + + get remoteSelections() { + return this._remoteSelections.value; } clear(types?: string[]) { @@ -151,9 +136,8 @@ export class SelectionManager extends LifeCycleWatcher { return new Type(...args) as InstanceType<T>; } - dispose() { - Object.values(this.slots).forEach(slot => slot.dispose()); - this.disposables.dispose(); + getGroup(group: string) { + return this.value.filter(s => s.group === group); } filter<T extends SelectionConstructor>(type: T) { @@ -176,43 +160,9 @@ export class SelectionManager extends LifeCycleWatcher { ); } - fromJSON(json: Record<string, unknown>[]) { - const selections = json.map(json => { - return this._jsonToSelection(json); - }); - return this.set(selections); - } - - getGroup(group: string) { - return this.value.filter(s => s.group === group); - } - - override mounted() { - if (this.disposables.disposed) { - this.disposables = new DisposableGroup(); - } - this.std.store.history.on('stack-item-added', this._itemAdded); - this.std.store.history.on('stack-item-popped', this._itemPopped); - this.disposables.add( - this._store.slots.update.on(({ id }) => { - if (id === this._store.awareness.clientID) { - return; - } - this.slots.remoteChanged.emit(this.remoteSelections); - }) - ); - } - - register(ctor: SelectionConstructor | SelectionConstructor[]) { - [ctor].flat().forEach(ctor => { - this._selectionConstructors[ctor.type] = ctor; - }); - return this; - } - set(selections: BaseSelection[]) { - this._store.setLocalSelection( - this.id, + this.store.awarenessStore.setLocalSelection( + this._id, selections.map(s => s.toJSON()) ); this.slots.changed.emit(selections); @@ -223,16 +173,15 @@ export class SelectionManager extends LifeCycleWatcher { this.set([...current, ...selections]); } - override unmounted() { - this.std.store.history.off('stack-item-added', this._itemAdded); - this.std.store.history.off('stack-item-popped', this._itemPopped); - this.slots.changed.dispose(); - this.disposables.dispose(); - this.clear(); - } - update(fn: (currentSelections: BaseSelection[]) => BaseSelection[]) { const selections = fn(this.value); this.set(selections); } + + fromJSON(json: Record<string, unknown>[]) { + const selections = json.map(json => { + return this._jsonToSelection(json); + }); + return this.set(selections); + } } diff --git a/blocksuite/framework/store/src/extension/selection/types.ts b/blocksuite/framework/store/src/extension/selection/types.ts new file mode 100644 index 0000000000000..eea8b6c544d25 --- /dev/null +++ b/blocksuite/framework/store/src/extension/selection/types.ts @@ -0,0 +1,9 @@ +import type { BaseSelection } from './base'; + +export interface SelectionConstructor<T extends BaseSelection = BaseSelection> { + type: string; + group: string; + + new (...args: any[]): T; + fromJSON(json: Record<string, unknown>): T; +} diff --git a/blocksuite/framework/store/src/model/store/store.ts b/blocksuite/framework/store/src/model/store/store.ts index 2f028aba920f0..cc10aed1aec3f 100644 --- a/blocksuite/framework/store/src/model/store/store.ts +++ b/blocksuite/framework/store/src/model/store/store.ts @@ -4,6 +4,7 @@ import { type Disposable, Slot } from '@blocksuite/global/utils'; import { signal } from '@preact/signals-core'; import type { ExtensionType } from '../../extension/extension.js'; +import { StoreSelectionExtension } from '../../extension/index.js'; import type { Schema } from '../../schema/index.js'; import { Block, @@ -27,29 +28,33 @@ export type StoreOptions = { extensions?: ExtensionType[]; }; +const internalExtensions = [StoreSelectionExtension]; + export class Store { + readonly userExtensions: ExtensionType[]; + private readonly _provider: ServiceProvider; private readonly _runQuery = (block: Block) => { runQuery(this._query, block); }; - protected readonly _doc: Doc; + private readonly _doc: Doc; - protected readonly _blocks = signal<Record<string, Block>>({}); + private readonly _blocks = signal<Record<string, Block>>({}); - protected readonly _crud: DocCRUD; + private readonly _crud: DocCRUD; - protected readonly _disposeBlockUpdated: Disposable; + private readonly _disposeBlockUpdated: Disposable; - protected readonly _query: Query = { + private readonly _query: Query = { match: [], mode: 'loose', }; - protected _readonly = signal(false); + private readonly _readonly = signal(false); - protected readonly _schema: Schema; + private readonly _schema: Schema; readonly slots: Doc['slots'] & { /** This is always triggered after `doc.load` is called. */ @@ -295,7 +300,12 @@ export class Store { const container = new Container(); container.addImpl(StoreIdentifier, () => this); + internalExtensions.forEach(ext => { + ext.setup(container); + }); + const userExtensions = extensions ?? []; + this.userExtensions = userExtensions; userExtensions.forEach(extension => { extension.setup(container); }); diff --git a/blocksuite/presets/src/__tests__/utils/setup.ts b/blocksuite/presets/src/__tests__/utils/setup.ts index bee73f382fe41..9c2066ececc94 100644 --- a/blocksuite/presets/src/__tests__/utils/setup.ts +++ b/blocksuite/presets/src/__tests__/utils/setup.ts @@ -9,8 +9,8 @@ effects(); import { CommunityCanvasTextFonts, type DocMode, - FeatureFlagService, FontConfigExtension, + StoreExtensions, } from '@blocksuite/blocks'; import { AffineSchemas } from '@blocksuite/blocks/schemas'; import { assertExists } from '@blocksuite/global/utils'; @@ -85,7 +85,7 @@ async function createEditor(collection: TestWorkspace, mode: DocMode = 'page') { export async function setupEditor(mode: DocMode = 'page') { const collection = new TestWorkspace(createCollectionOptions()); - collection.storeExtensions = [FeatureFlagService]; + collection.storeExtensions = StoreExtensions; collection.meta.initialize(); window.collection = collection; diff --git a/blocksuite/tests-legacy/edgeless/note/note.spec.ts b/blocksuite/tests-legacy/edgeless/note/note.spec.ts index d1c092ba0a618..f8b8de7c8da8f 100644 --- a/blocksuite/tests-legacy/edgeless/note/note.spec.ts +++ b/blocksuite/tests-legacy/edgeless/note/note.spec.ts @@ -329,7 +329,6 @@ test('cursor for active and inactive state', async ({ page }) => { await switchEditorMode(page); - await assertTextSelection(page); await page.mouse.click(CENTER_X, CENTER_Y); await waitNextFrame(page); await assertTextSelection(page); diff --git a/blocksuite/tests-legacy/edgeless/paste-block.spec.ts b/blocksuite/tests-legacy/edgeless/paste-block.spec.ts index 1813e62f97864..69b7cd5ce35c2 100644 --- a/blocksuite/tests-legacy/edgeless/paste-block.spec.ts +++ b/blocksuite/tests-legacy/edgeless/paste-block.spec.ts @@ -43,6 +43,7 @@ test.describe('pasting blocks', () => { await focusRichText(page); await initContent(page); await switchEditorMode(page); + await click(page, { x: 0, y: 0 }); const box = await getNoteBoundBoxInEdgeless(page, noteId); await click(page, { x: box.x + 10, diff --git a/packages/frontend/core/src/blocksuite/presets/ai/chat-panel/chat-panel-messages.ts b/packages/frontend/core/src/blocksuite/presets/ai/chat-panel/chat-panel-messages.ts index b2b578f098848..543c7e4def11a 100644 --- a/packages/frontend/core/src/blocksuite/presets/ai/chat-panel/chat-panel-messages.ts +++ b/packages/frontend/core/src/blocksuite/presets/ai/chat-panel/chat-panel-messages.ts @@ -1,4 +1,4 @@ -import type { BaseSelection, EditorHost } from '@blocksuite/affine/block-std'; +import type { EditorHost } from '@blocksuite/affine/block-std'; import { ShadowlessElement } from '@blocksuite/affine/block-std'; import { type AIError, @@ -9,6 +9,7 @@ import { UnauthorizedError, } from '@blocksuite/affine/blocks'; import { WithDisposable } from '@blocksuite/affine/global/utils'; +import type { BaseSelection } from '@blocksuite/affine/store'; import { css, html, nothing } from 'lit'; import { property, query, state } from 'lit/decorators.js'; import { repeat } from 'lit/directives/repeat.js'; diff --git a/tests/affine-local/e2e/links.spec.ts b/tests/affine-local/e2e/links.spec.ts index b8b28e9443f21..949f1e5a761bf 100644 --- a/tests/affine-local/e2e/links.spec.ts +++ b/tests/affine-local/e2e/links.spec.ts @@ -62,6 +62,7 @@ test('not allowed to switch to embed view when linking to the same document', as await expect(peekViewModel.locator('page-editor')).toBeVisible(); await page.keyboard.press('Escape'); await expect(peekViewModel).not.toBeVisible(); + await page.click('body'); await cardLink.click(); await cardToolbar.getByLabel('Switch view').click(); @@ -103,6 +104,7 @@ test('not allowed to switch to embed view when linking to block', async ({ await page.keyboard.press('Escape'); await expect(peekViewModel).not.toBeVisible(); + await page.click('body'); await cardLink.click(); await cardToolbar.getByLabel('More').click(); @@ -131,6 +133,7 @@ test('not allowed to switch to embed view when linking to block', async ({ await page.keyboard.press('Escape'); await expect(peekViewModel).not.toBeVisible(); + await page.click('body'); await otherCardLink.click(); await cardToolbar.getByLabel('Switch view').click();