refactor(editor): remove selection global types (#9532)

Closes: [BS-2217](https://linear.app/affine-design/issue/BS-2217/remove-global-types-in-selection)
This commit is contained in:
Saul-Mirone
2025-01-06 03:45:10 +00:00
parent 8669936f2f
commit fc863e484c
105 changed files with 501 additions and 358 deletions

View File

@@ -7,7 +7,12 @@ import {
Slot,
} from '@blocksuite/global/utils';
import type { CursorSelection, SurfaceSelection } from '../selection/index.js';
import {
BlockSelection,
CursorSelection,
SurfaceSelection,
TextSelection,
} from '../selection/index.js';
import type { GfxController } from './controller.js';
import { GfxExtension, GfxExtensionIdentifier } from './extension.js';
import type { GfxModel } from './model/model.js';
@@ -215,9 +220,9 @@ export class GfxSelectionManager extends GfxExtension {
this.disposable.add(
this.stdSelection.slots.changed.on(selections => {
const { cursor = [], surface = [] } = groupBy(selections, sel => {
if (sel.is('surface')) {
if (sel.is(SurfaceSelection)) {
return 'surface';
} else if (sel.is('cursor')) {
} else if (sel.is(CursorSelection)) {
return 'cursor';
}
@@ -261,15 +266,15 @@ export class GfxSelectionManager extends GfxExtension {
let hasBlockSelection = false;
selections.forEach(selection => {
if (selection.is('text')) {
if (selection.is(TextSelection)) {
hasTextSelection = true;
}
if (selection.is('block')) {
if (selection.is(BlockSelection)) {
hasBlockSelection = true;
}
if (selection.is('surface')) {
if (selection.is(SurfaceSelection)) {
const surfaceSelections = surfaceMap.get(id) ?? [];
surfaceSelections.push(selection);
surfaceMap.set(id, surfaceSelections);
@@ -277,7 +282,7 @@ export class GfxSelectionManager extends GfxExtension {
selection.elements.forEach(id => selectedSet.add(id));
}
if (selection.is('cursor')) {
if (selection.is(CursorSelection)) {
cursorMap.set(id, selection);
}
});
@@ -318,7 +323,7 @@ export class GfxSelectionManager extends GfxExtension {
if (elements.length > 0 && this.surfaceModel) {
instances.push(
this.stdSelection.create(
'surface',
SurfaceSelection,
this.surfaceModel.id,
elements,
selection.editing ?? false,
@@ -331,7 +336,7 @@ export class GfxSelectionManager extends GfxExtension {
instances = instances.concat(
blocks.map(blockId =>
this.stdSelection.create(
'surface',
SurfaceSelection,
blockId,
[blockId],
selection.editing ?? false,
@@ -368,7 +373,11 @@ export class GfxSelectionManager extends GfxExtension {
}
setCursor(cursor: CursorSelection | IPoint) {
const instance = this.stdSelection.create('cursor', cursor.x, cursor.y);
const instance = this.stdSelection.create(
CursorSelection,
cursor.x,
cursor.y
);
this.stdSelection.setGroup('gfx', [...this.surfaceSelections, instance]);
}

View File

@@ -1,7 +1,7 @@
import type { InlineRange, InlineRangeProvider } from '@blocksuite/inline';
import { signal } from '@preact/signals-core';
import type { TextSelection } from '../selection/index.js';
import { TextSelection } from '../selection/index.js';
import type { BlockComponent } from '../view/element/block-component.js';
export const getInlineRangeProvider: (
@@ -40,7 +40,7 @@ export const getInlineRangeProvider: (
}
const elementRange = rangeManager.textSelectionToRange(
selectionManager.create('text', {
selectionManager.create(TextSelection, {
from: {
index: 0,
blockId: element.blockId,
@@ -72,7 +72,7 @@ export const getInlineRangeProvider: (
if (!inlineRange) {
selectionManager.clear(['text']);
} else {
const textSelection = selectionManager.create('text', {
const textSelection = selectionManager.create(TextSelection, {
from: {
blockId: element.blockId,
index: inlineRange.index,

View File

@@ -1,7 +1,7 @@
import { throttle } from '@blocksuite/global/utils';
import type { BlockModel } from '@blocksuite/store';
import type { BaseSelection, TextSelection } from '../selection/index.js';
import { type BaseSelection, 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';
@@ -30,7 +30,7 @@ export class RangeBinding {
};
private readonly _onBeforeInput = (event: InputEvent) => {
const selection = this.selectionManager.find('text');
const selection = this.selectionManager.find(TextSelection);
if (!selection) return;
if (event.isComposing) return;
@@ -74,7 +74,7 @@ export class RangeBinding {
});
});
const newSelection = this.selectionManager.create('text', {
const newSelection = this.selectionManager.create(TextSelection, {
from: {
blockId: from.blockId,
index: from.index + (event.data?.length ?? 0),
@@ -95,7 +95,7 @@ export class RangeBinding {
};
private readonly _onCompositionStart = () => {
const selection = this.selectionManager.find('text');
const selection = this.selectionManager.find(TextSelection);
if (!selection) return;
const { from, to } = selection;
@@ -153,7 +153,7 @@ export class RangeBinding {
await this.host.updateComplete;
const selection = this.selectionManager.create('text', {
const selection = this.selectionManager.create(TextSelection, {
from: {
blockId: from.blockId,
index: from.index + (event.data?.length ?? 0),
@@ -249,7 +249,7 @@ export class RangeBinding {
private readonly _onStdSelectionChanged = (selections: BaseSelection[]) => {
const text =
selections.find((selection): selection is TextSelection =>
selection.is('text')
selection.is(TextSelection)
) ?? null;
if (text === this._prevTextSelection) {

View File

@@ -1,7 +1,7 @@
import { INLINE_ROOT_ATTR, type InlineRootElement } from '@blocksuite/inline';
import { LifeCycleWatcher } from '../extension/index.js';
import type { 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_QUERY_EXCLUDE_ATTR, RANGE_SYNC_EXCLUDE_ATTR } from './consts.js';
@@ -163,7 +163,7 @@ export class RangeManager extends LifeCycleWatcher {
return null;
}
return this.std.host.selection.create('text', {
return this.std.host.selection.create(TextSelection, {
from: {
blockId: startBlock.blockId,
index: startInlineRange.index,

View File

@@ -1,10 +1,6 @@
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
type SelectionConstructor<T = unknown> = {
type: string;
group: string;
new (...args: unknown[]): T;
};
import type { SelectionConstructor } from './manager';
export type BaseSelectionOptions = {
blockId: string;
@@ -21,9 +17,8 @@ export abstract class BaseSelection {
return (this.constructor as SelectionConstructor).group;
}
get type(): BlockSuite.SelectionType {
return (this.constructor as SelectionConstructor)
.type as BlockSuite.SelectionType;
get type(): string {
return (this.constructor as SelectionConstructor).type as string;
}
constructor({ blockId }: BaseSelectionOptions) {
@@ -39,10 +34,10 @@ export abstract class BaseSelection {
abstract equals(other: BaseSelection): boolean;
is<T extends BlockSuite.SelectionType>(
is<T extends SelectionConstructor>(
type: T
): this is BlockSuite.SelectionInstance[T] {
return this.type === type;
): this is T extends SelectionConstructor<infer U> ? U : never {
return this.type === type.type;
}
abstract toJSON(): Record<string, unknown>;

View File

@@ -1,27 +1,3 @@
import type {
BlockSelection,
CursorSelection,
SurfaceSelection,
TextSelection,
} from './variants/index.js';
export * from './base.js';
export * from './manager.js';
export * from './variants/index.js';
declare global {
namespace BlockSuite {
interface Selection {
block: typeof BlockSelection;
cursor: typeof CursorSelection;
surface: typeof SurfaceSelection;
text: typeof TextSelection;
}
type SelectionType = keyof Selection;
type SelectionInstance = {
[P in SelectionType]: InstanceType<Selection[P]>;
};
}
}

View File

@@ -8,11 +8,12 @@ import { SelectionIdentifier } from '../identifier.js';
import type { BlockStdScope } from '../scope/index.js';
import type { BaseSelection } from './base.js';
export interface SelectionConstructor {
export interface SelectionConstructor<T extends BaseSelection = BaseSelection> {
type: string;
group: string;
new (...args: any[]): BaseSelection;
fromJSON(json: Record<string, unknown>): BaseSelection;
new (...args: any[]): T;
fromJSON(json: Record<string, unknown>): T;
}
export class SelectionManager extends LifeCycleWatcher {
@@ -143,18 +144,11 @@ export class SelectionManager extends LifeCycleWatcher {
}
}
create<T extends BlockSuite.SelectionType>(
type: T,
...args: ConstructorParameters<BlockSuite.Selection[T]>
): BlockSuite.SelectionInstance[T] {
const ctor = this._selectionConstructors[type];
if (!ctor) {
throw new BlockSuiteError(
ErrorCode.SelectionError,
`Unknown selection type: ${type}`
);
}
return new ctor(...args) as BlockSuite.SelectionInstance[T];
create<T extends SelectionConstructor>(
Type: T,
...args: ConstructorParameters<T>
): InstanceType<T> {
return new Type(...args) as InstanceType<T>;
}
dispose() {
@@ -162,27 +156,23 @@ export class SelectionManager extends LifeCycleWatcher {
this.disposables.dispose();
}
filter<T extends BlockSuite.SelectionType>(type: T) {
filter<T extends SelectionConstructor>(type: T) {
return this.filter$(type).value;
}
filter$<T extends BlockSuite.SelectionType>(type: T) {
filter$<T extends SelectionConstructor>(type: T) {
return computed(() =>
this.value.filter((sel): sel is BlockSuite.SelectionInstance[T] =>
sel.is(type)
)
this.value.filter((sel): sel is InstanceType<T> => sel.is(type))
);
}
find<T extends BlockSuite.SelectionType>(type: T) {
find<T extends SelectionConstructor>(type: T) {
return this.find$(type).value;
}
find$<T extends BlockSuite.SelectionType>(type: T) {
find$<T extends SelectionConstructor>(type: T) {
return computed(() =>
this.value.find((sel): sel is BlockSuite.SelectionInstance[T] =>
sel.is(type)
)
this.value.find((sel): sel is InstanceType<T> => sel.is(type))
);
}