snapshot of https://github.com/toeverything/AFFiNE.git (canary, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Build & Test / Analyze (javascript, affine) (push) Failing after 4s
Build & Test / Analyze (typescript, affine) (push) Failing after 3s
Build & Test / Rust test filter (push) Failing after 4s
Build & Test / Check yarn binary (push) Failing after 3s
Build & Test / E2E BlockSuite Test (3) (push) Failing after 3s
Build & Test / Analyze (javascript, blocksuite) (push) Failing after 3s
Build & Test / Analyze (typescript, blocksuite) (push) Failing after 3s
Build & Test / Mobile native build filter (push) Failing after 3s
Build & Test / E2E BlockSuite Test (1) (push) Failing after 4s
Build & Test / E2E BlockSuite Test (2) (push) Failing after 4s
Build & Test / E2E Mobile Test (1) (push) Failing after 3s
Build & Test / Unit Test (1) (push) Has been skipped
Build & Test / Unit Test (2) (push) Has been skipped
Build & Test / Unit Test (3) (push) Has been skipped
Build & Test / Unit Test (4) (push) Has been skipped
Build & Test / Unit Test (5) (push) Has been skipped
Build & Test / Native Unit Test (push) Has been skipped
Build & Test / E2E BlockSuite Test (4) (push) Failing after 3s
Build & Test / E2E BlockSuite Test (5) (push) Failing after 3s
Build & Test / E2E BlockSuite Cross Browser Test (push) Failing after 3s
Build & Test / E2E Mobile Test (2) (push) Failing after 3s
Build & Test / Build AFFiNE native (x86_64-unknown-linux-gnu) (push) Failing after 3s
Build & Test / Build Server native (push) Failing after 3s
Build & Test / Check Git Status (push) Has been skipped
Build & Test / Server Test (0, 4) (push) Has been skipped
Build & Test / Server Test (1, 4) (push) Has been skipped
Build & Test / Server Test (2, 4) (push) Has been skipped
Build & Test / Server Test (3, 4) (push) Has been skipped
Build & Test / Server Test with Elasticsearch (push) Has been skipped
Build & Test / Build @affine/electron renderer (push) Failing after 3s
Build & Test / Copilot test filter (push) Failing after 4s
Build & Test / Lint Rust (push) Has been skipped
Build & Test / Server Test with Embedded Search (push) Has been skipped
Build & Test / Server Test with Manticore Search (push) Has been skipped
Build & Test / Server E2E Test (push) Has been skipped
Build & Test / Cloud Desktop E2E Test (push) Has been skipped
Build & Test / Cloud E2E Test 1/2 (push) Has been skipped
Build & Test / Cloud E2E Test 2/2 (push) Has been skipped
Build & Test / Build Android app (push) Has been skipped
Build & Test / Test Android app (push) Has been skipped
Build & Test / Run native tests (push) Has been skipped
Build & Test / Server Copilot Api Test (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (1, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (2, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (3, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (4, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (5, 5) (push) Has been skipped
Release / Canary Gate (push) Has been skipped
Build & Test / Build AFFiNE native (aarch64-apple-darwin) (push) Has been cancelled
Build & Test / Lint (push) Has been cancelled
Build & Test / Typecheck (push) Has been cancelled
Build & Test / Build AFFiNE native (x86_64-apple-darwin) (push) Has been cancelled
Build & Test / Desktop Test (ubuntu-latest, linux, x64, x86_64-unknown-linux-gnu, true) (push) Has been cancelled
Build & Test / Desktop Test (windows-latest, windows, x64, x86_64-pc-windows-msvc, true) (push) Has been cancelled
Build & Test / E2E Test (1) (push) Has been cancelled
Build & Test / E2E Test (2) (push) Has been cancelled
Build & Test / E2E Test (3) (push) Has been cancelled
Build & Test / E2E Test (4) (push) Has been cancelled
Build & Test / E2E Test (5) (push) Has been cancelled
Build & Test / Build AFFiNE native (aarch64-pc-windows-msvc) (push) Has been cancelled
Build & Test / Build AFFiNE native (x86_64-pc-windows-msvc) (push) Has been cancelled
Build & Test / Desktop Test (macos-latest, macos, arm64, aarch64-apple-darwin, true) (push) Has been cancelled
Build & Test / Desktop Test (macos-latest, macos, x64, x86_64-apple-darwin, false) (push) Has been cancelled
Build & Test / 3, 2, 1 Launch (push) Has been cancelled
Build & Test / Build iOS app (push) Has been cancelled
Copilot Test Automatically / Setup Test (push) Failing after 1s
Release / Prepare (push) Failing after 2s
Release / Release Cloud (push) Has been skipped
Release / Release Desktop (push) Has been skipped
Release / Release Docker Image (push) Has been skipped
Release / Release Mobile (push) Has been skipped

This commit is contained in:
upstream-mirror
2026-09-16 04:01:20 +05:00
commit 702cc698a7
9990 changed files with 1073618 additions and 0 deletions

View File

@@ -0,0 +1,205 @@
# Selection
Selection is a very common concept in structure editors. It's used for representing the current cursor position or the current selected blocks.
In BlockSuite, we use a data driven approach to represent the selection. It also follows the [CRDT-native data flow](/blog/crdt-native-data-flow), which means the selection state is always derived from serializable data.
## Selection Model
The selection model contains a list of atomic selections. Each selection represents a range of the content. For example, if you have a text block with the following content:
> Hello
>
> World
In the default `PageEditor`, it will be modeled as following block tree nodes:
```
Root Block
Note Block
Paragraph Block 1
Paragraph Block 2
```
So if you select the text partially via mouse drag as following:
![text-selection-example](../images/text-selection-example.png)
The selection model will be:
```ts
[
{
type: 'text',
group: 'note',
from: {
path: ['root_id', 'note_id', 'paragraph_1_id'],
index: 1,
length: 5,
},
to: {
path: ['root_id', 'note_id', 'paragraph_2_id'],
index: 0,
length: 4,
},
},
];
```
If you select the blocks via block level selection like this:
![block-selection-example](../images/block-selection-example.png)
The selection model will be:
```ts
[
{
type: 'block',
group: 'note',
path: ['root_id', 'note_id', 'paragraph_1_id'],
},
{
type: 'block',
group: 'note',
path: ['root_id', 'note_id', 'paragraph_2_id'],
},
];
```
## Types and Groups
Selection model has two important properties: `type` and `group`.
The `type` of a selection means which kind of selection it is. And the `group` of a selection indicates the scope of selection.
Some types of selections can share the same group because they have the same scope. For example, the `text` selection and the `block` selection can share the `note` group because they are both in the `affine:note` block. And you may also have a `cell` and `row` selection in a `table` block, and they can share the `table` group.
## Update Selection State
You can get the selection manager from `std.selection` or `host.selection`. With the selection manager, you can read the selection model from `value`. And you can also write the selection model by `set` and `update`:
```ts
const { selection } = host.std;
const current = selection.value;
const next = transformSelection(current);
selection.set(next);
// This can also be written as:
selection.update(current => transformSelection(current));
```
The `set` method will override all current selections.
You can also create a new selection by using `selection.create` method:
```ts
const blockSelection = selection.create('block', { path: [0, 1, 2] });
```
If you want to pick some selections by `type` from the current selection model, you can reuse the `pick` and `find` methods to help:
```ts
const textSelection: Selection = selection.pick('text');
const blockSelections: Selection[] = selection.find('block');
```
You can also clear all the selections by calling `clear`. If you just want to clear a certain type of selections, you can pass the type as the first argument of `clear` method:
```ts
// clear all selections
selection.clear();
// clear text selection
selection.clear('text');
```
And we also provide a `setGroup` method to override the selections in a specific group. Of course, we also provide a `getGroup` method.
```ts
const noteSelections = selection.getGroup('note');
const nextNoteSelections = yourLogic(noteSelections);
selection.setGroup('note', nextNoteSelections);
```
## Subscribe to Selection Changes
You can subscribe to the selection changes by using `changed` slot.
```ts
selection.slots.changed.on(nextSelection => {
renderSelectionToUI(nextSelection);
});
```
You can also subscribe to the remote selection changes by using `remoteChanged` slot. This is useful when you want to display the selection of other users.
```ts
selection.slots.remoteChanged.on(nextSelectionMap => {
for (const [userId, nextSelection] of nextSelectionMap) {
renderRemoteSelectionToUI(nextSelection, userId);
}
});
```
## Create Custom Selection
You can create your own selection type by extending the `BaseSelection` interface.
```ts
import { BaseSelection, PathFinder } from '@blocksuite/block-std';
import z from 'zod';
const MySelectionSchema = z.object({
path: z.array(z.string()),
});
export class MySelection extends BaseSelection {
static override type = 'mySelection';
static override group = 'note';
override equals(other: BaseSelection): boolean {
if (other instanceof MySelection) {
return PathFinder.equals(this.path, other.path);
}
return false;
}
override toJSON(): Record<string, unknown> {
return {
type: this.type,
path: this.path,
};
}
static override fromJSON(json: Record<string, unknown>): ImageSelection {
MySelectionSchema.parse(json);
return new MySelection({
path: json.path as string[],
});
}
}
declare global {
namespace BlockSuite {
interface Selection {
mySelection: typeof MySelection;
}
}
}
```
After that, you need to register the selection to selection manager:
```ts
selection.register(MySelection);
```
Now you can use the `MySelection` in the selection model.
```ts
const mySelection = selection.create('mySelection', {
path: ['a', 'b', 'c'],
});
```