Compare commits

..

62 Commits

Author SHA1 Message Date
renovate[bot]
5464d1a9ce chore: bump up multer version to v2.1.0 [SECURITY] (#14544)
This PR contains the following updates:

| Package | Change |
[Age](https://docs.renovatebot.com/merge-confidence/) |
[Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [multer](https://redirect.github.com/expressjs/multer) | [`2.0.2` →
`2.1.0`](https://renovatebot.com/diffs/npm/multer/2.0.2/2.1.0) |
![age](https://developer.mend.io/api/mc/badges/age/npm/multer/2.1.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/multer/2.0.2/2.1.0?slim=true)
|

### GitHub Vulnerability Alerts

####
[CVE-2026-2359](https://redirect.github.com/expressjs/multer/security/advisories/GHSA-v52c-386h-88mc)

### Impact

A vulnerability in Multer versions < 2.1.0 allows an attacker to trigger
a Denial of Service (DoS) by dropping connection during file upload,
potentially causing resource exhaustion.

### Patches

Users should upgrade to `2.1.0`

### Workarounds

None

####
[CVE-2026-3304](https://redirect.github.com/expressjs/multer/security/advisories/GHSA-xf7r-hgr6-v32p)

### Impact

A vulnerability in Multer versions < 2.1.0 allows an attacker to trigger
a Denial of Service (DoS) by sending malformed requests, potentially
causing resource exhaustion.

### Patches

Users should upgrade to `2.1.0`

### Workarounds

None

---

### Release Notes

<details>
<summary>expressjs/multer (multer)</summary>

###
[`v2.1.0`](https://redirect.github.com/expressjs/multer/blob/HEAD/CHANGELOG.md#210)

[Compare
Source](https://redirect.github.com/expressjs/multer/compare/v2.0.2...v2.1.0)

- Add `defParamCharset` option for UTF-8 filename support
([#&#8203;1210](https://redirect.github.com/expressjs/multer/pull/1210))
- Fix [CVE-2026-2359](https://www.cve.org/CVERecord?id=CVE-2026-2359)
([GHSA-v52c-386h-88mc](https://redirect.github.com/expressjs/multer/security/advisories/GHSA-v52c-386h-88mc))
- Fix [CVE-2026-3304](https://www.cve.org/CVERecord?id=CVE-2026-3304)
([GHSA-xf7r-hgr6-v32p](https://redirect.github.com/expressjs/multer/security/advisories/GHSA-xf7r-hgr6-v32p))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "" (UTC), Automerge - At any time (no
schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/toeverything/AFFiNE).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My40My4yIiwidXBkYXRlZEluVmVyIjoiNDMuNDMuMiIsInRhcmdldEJyYW5jaCI6ImNhbmFyeSIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-02 13:59:38 +08:00
renovate[bot]
4c40dcacd9 chore: bump up actions/setup-go action to v6 (#14553)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [actions/setup-go](https://redirect.github.com/actions/setup-go) |
action | major | `v5` → `v6` |

---

### Release Notes

<details>
<summary>actions/setup-go (actions/setup-go)</summary>

### [`v6`](https://redirect.github.com/actions/setup-go/compare/v5...v6)

[Compare
Source](https://redirect.github.com/actions/setup-go/compare/v5...v6)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/toeverything/AFFiNE).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My40My4yIiwidXBkYXRlZEluVmVyIjoiNDMuNDMuMiIsInRhcmdldEJyYW5jaCI6ImNhbmFyeSIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-02 13:59:16 +08:00
renovate[bot]
76d28aaa38 chore: bump up @types/supertest version to v7 (#14546)
This PR contains the following updates:

| Package | Change |
[Age](https://docs.renovatebot.com/merge-confidence/) |
[Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
|
[@types/supertest](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/supertest)
([source](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/supertest))
| [`^6.0.2` →
`^7.0.0`](https://renovatebot.com/diffs/npm/@types%2fsupertest/6.0.3/7.2.0)
|
![age](https://developer.mend.io/api/mc/badges/age/npm/@types%2fsupertest/7.2.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@types%2fsupertest/6.0.3/7.2.0?slim=true)
|

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/toeverything/AFFiNE).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My40My4yIiwidXBkYXRlZEluVmVyIjoiNDMuNDMuMiIsInRhcmdldEJyYW5jaCI6ImNhbmFyeSIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-02 13:58:48 +08:00
renovate[bot]
86f48240ce chore: bump up actions/github-script action to v8 (#14551)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[actions/github-script](https://redirect.github.com/actions/github-script)
| action | major | `v7` → `v8` |

---

### Release Notes

<details>
<summary>actions/github-script (actions/github-script)</summary>

###
[`v8`](https://redirect.github.com/actions/github-script/releases/tag/v8):
.0.0

[Compare
Source](https://redirect.github.com/actions/github-script/compare/v7...v8)

##### What's Changed

- Update Node.js version support to 24.x by
[@&#8203;salmanmkc](https://redirect.github.com/salmanmkc) in
[#&#8203;637](https://redirect.github.com/actions/github-script/pull/637)
- README for updating actions/github-script from v7 to v8 by
[@&#8203;sneha-krip](https://redirect.github.com/sneha-krip) in
[#&#8203;653](https://redirect.github.com/actions/github-script/pull/653)

##### ⚠️ Minimum Compatible Runner Version

**v2.327.1**\
[Release
Notes](https://redirect.github.com/actions/runner/releases/tag/v2.327.1)

Make sure your runner is updated to this version or newer to use this
release.

##### New Contributors

- [@&#8203;salmanmkc](https://redirect.github.com/salmanmkc) made their
first contribution in
[#&#8203;637](https://redirect.github.com/actions/github-script/pull/637)
- [@&#8203;sneha-krip](https://redirect.github.com/sneha-krip) made
their first contribution in
[#&#8203;653](https://redirect.github.com/actions/github-script/pull/653)

**Full Changelog**:
<https://github.com/actions/github-script/compare/v7.1.0...v8.0.0>

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/toeverything/AFFiNE).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My40My4yIiwidXBkYXRlZEluVmVyIjoiNDMuNDMuMiIsInRhcmdldEJyYW5jaCI6ImNhbmFyeSIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-02 13:58:23 +08:00
DarkSky
c5d622531c feat: refactor copilot module (#14537) 2026-03-02 13:57:55 +08:00
renovate[bot]
60acd81d4b chore: bump up actions/labeler action to v6 (#14552)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [actions/labeler](https://redirect.github.com/actions/labeler) |
action | major | `v5` → `v6` |

---

### Release Notes

<details>
<summary>actions/labeler (actions/labeler)</summary>

### [`v6`](https://redirect.github.com/actions/labeler/compare/v5...v6)

[Compare
Source](https://redirect.github.com/actions/labeler/compare/v5...v6)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/toeverything/AFFiNE).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My40My4yIiwidXBkYXRlZEluVmVyIjoiNDMuNDMuMiIsInRhcmdldEJyYW5jaCI6ImNhbmFyeSIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-02 11:59:25 +08:00
renovate[bot]
78f567a178 chore: bump up actions/checkout action to v6 (#14550)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [actions/checkout](https://redirect.github.com/actions/checkout) |
action | major | `v4` → `v6` |

---

### Release Notes

<details>
<summary>actions/checkout (actions/checkout)</summary>

### [`v6`](https://redirect.github.com/actions/checkout/compare/v5...v6)

[Compare
Source](https://redirect.github.com/actions/checkout/compare/v5...v6)

### [`v5`](https://redirect.github.com/actions/checkout/compare/v4...v5)

[Compare
Source](https://redirect.github.com/actions/checkout/compare/v4...v5)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/toeverything/AFFiNE).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My40My4yIiwidXBkYXRlZEluVmVyIjoiNDMuNDMuMiIsInRhcmdldEJyYW5jaCI6ImNhbmFyeSIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-02 10:58:53 +08:00
renovate[bot]
784382cfb1 chore: bump up actions/cache action to v5 (#14549)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [actions/cache](https://redirect.github.com/actions/cache) | action |
major | `v4` → `v5` |

---

### Release Notes

<details>
<summary>actions/cache (actions/cache)</summary>

### [`v5`](https://redirect.github.com/actions/cache/compare/v4...v5)

[Compare
Source](https://redirect.github.com/actions/cache/compare/v4...v5)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/toeverything/AFFiNE).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My40My4yIiwidXBkYXRlZEluVmVyIjoiNDMuNDMuMiIsInRhcmdldEJyYW5jaCI6ImNhbmFyeSIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-02 10:58:27 +08:00
renovate[bot]
342451be1b chore: bump up actions/attest-build-provenance action to v4 (#14547)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[actions/attest-build-provenance](https://redirect.github.com/actions/attest-build-provenance)
| action | major | `v2` → `v4` |

---

### Release Notes

<details>
<summary>actions/attest-build-provenance
(actions/attest-build-provenance)</summary>

###
[`v4`](https://redirect.github.com/actions/attest-build-provenance/compare/v3...v4)

[Compare
Source](https://redirect.github.com/actions/attest-build-provenance/compare/v3...v4)

###
[`v3`](https://redirect.github.com/actions/attest-build-provenance/compare/v2...v3)

[Compare
Source](https://redirect.github.com/actions/attest-build-provenance/compare/v2...v3)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/toeverything/AFFiNE).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My40My4yIiwidXBkYXRlZEluVmVyIjoiNDMuNDMuMiIsInRhcmdldEJyYW5jaCI6ImNhbmFyeSIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-02 10:44:37 +08:00
renovate[bot]
2b6146727b chore: bump up RevenueCat/purchases-ios-spm version to from: "5.60.0" (#14545) 2026-03-02 08:48:25 +08:00
renovate[bot]
d5245a3273 chore: bump up Recouse/EventSource version to from: "0.1.7" (#14541)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [Recouse/EventSource](https://redirect.github.com/Recouse/EventSource)
| patch | `from: "0.1.5"` → `from: "0.1.7"` |

---

### Release Notes

<details>
<summary>Recouse/EventSource (Recouse/EventSource)</summary>

###
[`v0.1.7`](https://redirect.github.com/Recouse/EventSource/releases/tag/0.1.7)

[Compare
Source](https://redirect.github.com/Recouse/EventSource/compare/0.1.6...0.1.7)

#### What's Changed

- Separate timeout interval values for request and resource by
[@&#8203;Recouse](https://redirect.github.com/Recouse) in
[#&#8203;46](https://redirect.github.com/Recouse/EventSource/pull/46)

**Full Changelog**:
<https://github.com/Recouse/EventSource/compare/0.1.6...0.1.7>

###
[`v0.1.6`](https://redirect.github.com/Recouse/EventSource/releases/tag/0.1.6)

[Compare
Source](https://redirect.github.com/Recouse/EventSource/compare/0.1.5...0.1.6)

#### What's Changed

- Fix visionOS availability error for split(by:) method by
[@&#8203;danielseidl](https://redirect.github.com/danielseidl) in
[#&#8203;45](https://redirect.github.com/Recouse/EventSource/pull/45)

#### New Contributors

- [@&#8203;danielseidl](https://redirect.github.com/danielseidl) made
their first contribution in
[#&#8203;45](https://redirect.github.com/Recouse/EventSource/pull/45)

**Full Changelog**:
<https://github.com/Recouse/EventSource/compare/0.1.5...0.1.6>

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/toeverything/AFFiNE).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My40My4yIiwidXBkYXRlZEluVmVyIjoiNDMuNDMuMiIsInRhcmdldEJyYW5jaCI6ImNhbmFyeSIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-02 06:26:20 +08:00
renovate[bot]
fff63562b1 chore: bump up Lakr233/MarkdownView version to from: "3.6.3" (#14540)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
|
[Lakr233/MarkdownView](https://redirect.github.com/Lakr233/MarkdownView)
| patch | `from: "3.6.2"` → `from: "3.6.3"` |

---

### Release Notes

<details>
<summary>Lakr233/MarkdownView (Lakr233/MarkdownView)</summary>

###
[`v3.6.3`](https://redirect.github.com/Lakr233/MarkdownView/compare/3.6.2...3.6.3)

[Compare
Source](https://redirect.github.com/Lakr233/MarkdownView/compare/3.6.2...3.6.3)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/toeverything/AFFiNE).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My40My4yIiwidXBkYXRlZEluVmVyIjoiNDMuNDMuMiIsInRhcmdldEJyYW5jaCI6ImNhbmFyeSIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-02 06:26:03 +08:00
renovate[bot]
4136abdd97 chore: bump up rustc version to v1.93.1 (#14542)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [rustc](https://redirect.github.com/rust-lang/rust) | patch | `1.93.0`
→ `1.93.1` |

---

### Release Notes

<details>
<summary>rust-lang/rust (rustc)</summary>

###
[`v1.93.1`](https://redirect.github.com/rust-lang/rust/blob/HEAD/RELEASES.md#Version-1931-2026-02-12)

[Compare
Source](https://redirect.github.com/rust-lang/rust/compare/1.93.0...1.93.1)

\===========================

<a id="1.93.1"></a>

- [Don't try to recover keyword as non-keyword
identifier](https://redirect.github.com/rust-lang/rust/pull/150590),
fixing an ICE that especially [affected
rustfmt](https://redirect.github.com/rust-lang/rustfmt/issues/6739).
- [Fix `clippy::panicking_unwrap` false-positive on field access with
implicit
deref](https://redirect.github.com/rust-lang/rust-clippy/pull/16196).
- [Revert "Update wasm-related dependencies in
CI"](https://redirect.github.com/rust-lang/rust/pull/152259), fixing
file descriptor leaks on the `wasm32-wasip2` target.

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/toeverything/AFFiNE).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My40My4yIiwidXBkYXRlZEluVmVyIjoiNDMuNDMuMiIsInRhcmdldEJyYW5jaCI6ImNhbmFyeSIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-02 06:25:51 +08:00
renovate[bot]
e249e2e884 chore: bump up opentelemetry (#14543)
This PR contains the following updates:

| Package | Change |
[Age](https://docs.renovatebot.com/merge-confidence/) |
[Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
|
[@opentelemetry/exporter-prometheus](https://redirect.github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-exporter-prometheus)
([source](https://redirect.github.com/open-telemetry/opentelemetry-js))
| [`^0.211.0` →
`^0.212.0`](https://renovatebot.com/diffs/npm/@opentelemetry%2fexporter-prometheus/0.211.0/0.212.0)
|
![age](https://developer.mend.io/api/mc/badges/age/npm/@opentelemetry%2fexporter-prometheus/0.212.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@opentelemetry%2fexporter-prometheus/0.211.0/0.212.0?slim=true)
|
|
[@opentelemetry/exporter-zipkin](https://redirect.github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-exporter-zipkin)
([source](https://redirect.github.com/open-telemetry/opentelemetry-js))
| [`2.5.0` →
`2.5.1`](https://renovatebot.com/diffs/npm/@opentelemetry%2fexporter-zipkin/2.5.0/2.5.1)
|
![age](https://developer.mend.io/api/mc/badges/age/npm/@opentelemetry%2fexporter-zipkin/2.5.1?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@opentelemetry%2fexporter-zipkin/2.5.0/2.5.1?slim=true)
|
|
[@opentelemetry/host-metrics](https://redirect.github.com/open-telemetry/opentelemetry-js-contrib/tree/main/packages/host-metrics#readme)
([source](https://redirect.github.com/open-telemetry/opentelemetry-js-contrib/tree/HEAD/packages/host-metrics))
| [`0.38.2` →
`0.38.3`](https://renovatebot.com/diffs/npm/@opentelemetry%2fhost-metrics/0.38.2/0.38.3)
|
![age](https://developer.mend.io/api/mc/badges/age/npm/@opentelemetry%2fhost-metrics/0.38.3?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@opentelemetry%2fhost-metrics/0.38.2/0.38.3?slim=true)
|
|
[@opentelemetry/instrumentation](https://redirect.github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-instrumentation)
([source](https://redirect.github.com/open-telemetry/opentelemetry-js))
| [`^0.211.0` →
`^0.212.0`](https://renovatebot.com/diffs/npm/@opentelemetry%2finstrumentation/0.211.0/0.212.0)
|
![age](https://developer.mend.io/api/mc/badges/age/npm/@opentelemetry%2finstrumentation/0.212.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@opentelemetry%2finstrumentation/0.211.0/0.212.0?slim=true)
|
|
[@opentelemetry/instrumentation-graphql](https://redirect.github.com/open-telemetry/opentelemetry-js-contrib/tree/main/packages/instrumentation-graphql#readme)
([source](https://redirect.github.com/open-telemetry/opentelemetry-js-contrib/tree/HEAD/packages/instrumentation-graphql))
| [`^0.58.0` →
`^0.60.0`](https://renovatebot.com/diffs/npm/@opentelemetry%2finstrumentation-graphql/0.58.0/0.60.0)
|
![age](https://developer.mend.io/api/mc/badges/age/npm/@opentelemetry%2finstrumentation-graphql/0.60.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@opentelemetry%2finstrumentation-graphql/0.58.0/0.60.0?slim=true)
|
|
[@opentelemetry/instrumentation-http](https://redirect.github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-instrumentation-http)
([source](https://redirect.github.com/open-telemetry/opentelemetry-js))
| [`^0.211.0` →
`^0.212.0`](https://renovatebot.com/diffs/npm/@opentelemetry%2finstrumentation-http/0.211.0/0.212.0)
|
![age](https://developer.mend.io/api/mc/badges/age/npm/@opentelemetry%2finstrumentation-http/0.212.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@opentelemetry%2finstrumentation-http/0.211.0/0.212.0?slim=true)
|
|
[@opentelemetry/instrumentation-ioredis](https://redirect.github.com/open-telemetry/opentelemetry-js-contrib/tree/main/packages/instrumentation-ioredis#readme)
([source](https://redirect.github.com/open-telemetry/opentelemetry-js-contrib/tree/HEAD/packages/instrumentation-ioredis))
| [`^0.59.0` →
`^0.60.0`](https://renovatebot.com/diffs/npm/@opentelemetry%2finstrumentation-ioredis/0.59.0/0.60.0)
|
![age](https://developer.mend.io/api/mc/badges/age/npm/@opentelemetry%2finstrumentation-ioredis/0.60.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@opentelemetry%2finstrumentation-ioredis/0.59.0/0.60.0?slim=true)
|
|
[@opentelemetry/instrumentation-nestjs-core](https://redirect.github.com/open-telemetry/opentelemetry-js-contrib/tree/main/packages/instrumentation-nestjs-core#readme)
([source](https://redirect.github.com/open-telemetry/opentelemetry-js-contrib/tree/HEAD/packages/instrumentation-nestjs-core))
| [`^0.57.0` →
`^0.58.0`](https://renovatebot.com/diffs/npm/@opentelemetry%2finstrumentation-nestjs-core/0.57.0/0.58.0)
|
![age](https://developer.mend.io/api/mc/badges/age/npm/@opentelemetry%2finstrumentation-nestjs-core/0.58.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@opentelemetry%2finstrumentation-nestjs-core/0.57.0/0.58.0?slim=true)
|
|
[@opentelemetry/instrumentation-socket.io](https://redirect.github.com/open-telemetry/opentelemetry-js-contrib/tree/main/packages/instrumentation-socket.io#readme)
([source](https://redirect.github.com/open-telemetry/opentelemetry-js-contrib/tree/HEAD/packages/instrumentation-socket.io))
| [`^0.57.0` →
`^0.59.0`](https://renovatebot.com/diffs/npm/@opentelemetry%2finstrumentation-socket.io/0.57.0/0.59.0)
|
![age](https://developer.mend.io/api/mc/badges/age/npm/@opentelemetry%2finstrumentation-socket.io/0.59.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@opentelemetry%2finstrumentation-socket.io/0.57.0/0.59.0?slim=true)
|
|
[@opentelemetry/sdk-metrics](https://redirect.github.com/open-telemetry/opentelemetry-js/tree/main/packages/sdk-metrics)
([source](https://redirect.github.com/open-telemetry/opentelemetry-js))
| [`2.5.0` →
`2.5.1`](https://renovatebot.com/diffs/npm/@opentelemetry%2fsdk-metrics/2.5.0/2.5.1)
|
![age](https://developer.mend.io/api/mc/badges/age/npm/@opentelemetry%2fsdk-metrics/2.5.1?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@opentelemetry%2fsdk-metrics/2.5.0/2.5.1?slim=true)
|
|
[@opentelemetry/sdk-node](https://redirect.github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-sdk-node)
([source](https://redirect.github.com/open-telemetry/opentelemetry-js))
| [`^0.211.0` →
`^0.212.0`](https://renovatebot.com/diffs/npm/@opentelemetry%2fsdk-node/0.211.0/0.212.0)
|
![age](https://developer.mend.io/api/mc/badges/age/npm/@opentelemetry%2fsdk-node/0.212.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@opentelemetry%2fsdk-node/0.211.0/0.212.0?slim=true)
|
|
[@opentelemetry/sdk-trace-node](https://redirect.github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-sdk-trace-node)
([source](https://redirect.github.com/open-telemetry/opentelemetry-js))
| [`2.5.0` →
`2.5.1`](https://renovatebot.com/diffs/npm/@opentelemetry%2fsdk-trace-node/2.5.0/2.5.1)
|
![age](https://developer.mend.io/api/mc/badges/age/npm/@opentelemetry%2fsdk-trace-node/2.5.1?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@opentelemetry%2fsdk-trace-node/2.5.0/2.5.1?slim=true)
|

---

### Release Notes

<details>
<summary>open-telemetry/opentelemetry-js
(@&#8203;opentelemetry/exporter-prometheus)</summary>

###
[`v0.212.0`](38924cbff2...ad92be4c2c)

[Compare
Source](38924cbff2...ad92be4c2c)

</details>

<details>
<summary>open-telemetry/opentelemetry-js-contrib
(@&#8203;opentelemetry/host-metrics)</summary>

###
[`v0.38.3`](https://redirect.github.com/open-telemetry/opentelemetry-js-contrib/blob/HEAD/packages/host-metrics/CHANGELOG.md#0383-2026-02-25)

[Compare
Source](7a5f3c0a09...630937db15)

##### Bug Fixes

- **instrumentation-host-metrics:** unpin and update to
systeminformation@^5.31.1
([#&#8203;3392](https://redirect.github.com/open-telemetry/opentelemetry-js-contrib/issues/3392))
([e4ffdb4](e4ffdb43d1))

</details>

<details>
<summary>open-telemetry/opentelemetry-js-contrib
(@&#8203;opentelemetry/instrumentation-graphql)</summary>

###
[`v0.60.0`](https://redirect.github.com/open-telemetry/opentelemetry-js-contrib/blob/HEAD/packages/instrumentation-graphql/CHANGELOG.md#0600-2026-02-25)

[Compare
Source](0b33a118f2...630937db15)

##### Features

- **instrumentation-graphql:** add parent name in attributes of resolver
span
([#&#8203;3287](https://redirect.github.com/open-telemetry/opentelemetry-js-contrib/issues/3287))
([ea2a90a](ea2a90a87b))

###
[`v0.59.0`](https://redirect.github.com/open-telemetry/opentelemetry-js-contrib/blob/HEAD/packages/instrumentation-graphql/CHANGELOG.md#0590-2026-02-16)

[Compare
Source](7a5f3c0a09...0b33a118f2)

##### Features

- **deps:** update deps matching "@&#8203;opentelemetry/\*"
([#&#8203;3383](https://redirect.github.com/open-telemetry/opentelemetry-js-contrib/issues/3383))
([d3ac785](d3ac7851d6))

</details>

<details>
<summary>open-telemetry/opentelemetry-js-contrib
(@&#8203;opentelemetry/instrumentation-ioredis)</summary>

###
[`v0.60.0`](https://redirect.github.com/open-telemetry/opentelemetry-js-contrib/blob/HEAD/packages/instrumentation-ioredis/CHANGELOG.md#0600-2026-02-16)

[Compare
Source](7a5f3c0a09...0b33a118f2)

##### Features

- **deps:** update deps matching "@&#8203;opentelemetry/\*"
([#&#8203;3383](https://redirect.github.com/open-telemetry/opentelemetry-js-contrib/issues/3383))
([d3ac785](d3ac7851d6))

##### Dependencies

- The following workspace dependencies were updated
  - devDependencies
-
[@&#8203;opentelemetry/contrib-test-utils](https://redirect.github.com/opentelemetry/contrib-test-utils)
bumped from ^0.58.0 to ^0.59.0

</details>

<details>
<summary>open-telemetry/opentelemetry-js-contrib
(@&#8203;opentelemetry/instrumentation-nestjs-core)</summary>

###
[`v0.58.0`](https://redirect.github.com/open-telemetry/opentelemetry-js-contrib/blob/HEAD/packages/instrumentation-nestjs-core/CHANGELOG.md#0580-2026-02-16)

[Compare
Source](7a5f3c0a09...0b33a118f2)

##### Features

- **deps:** update deps matching "@&#8203;opentelemetry/\*"
([#&#8203;3383](https://redirect.github.com/open-telemetry/opentelemetry-js-contrib/issues/3383))
([d3ac785](d3ac7851d6))

</details>

<details>
<summary>open-telemetry/opentelemetry-js-contrib
(@&#8203;opentelemetry/instrumentation-socket.io)</summary>

###
[`v0.59.0`](https://redirect.github.com/open-telemetry/opentelemetry-js-contrib/blob/HEAD/packages/instrumentation-socket.io/CHANGELOG.md#0590-2026-02-25)

[Compare
Source](0b33a118f2...630937db15)

##### Features

- **deps:** lock file maintenance
([#&#8203;3261](https://redirect.github.com/open-telemetry/opentelemetry-js-contrib/issues/3261))
([540926b](540926bffe))

###
[`v0.58.0`](https://redirect.github.com/open-telemetry/opentelemetry-js-contrib/blob/HEAD/packages/instrumentation-socket.io/CHANGELOG.md#0580-2026-02-16)

[Compare
Source](7a5f3c0a09...0b33a118f2)

##### Features

- **deps:** update deps matching "@&#8203;opentelemetry/\*"
([#&#8203;3383](https://redirect.github.com/open-telemetry/opentelemetry-js-contrib/issues/3383))
([d3ac785](d3ac7851d6))

##### Dependencies

- The following workspace dependencies were updated
  - devDependencies
-
[@&#8203;opentelemetry/contrib-test-utils](https://redirect.github.com/opentelemetry/contrib-test-utils)
bumped from ^0.58.0 to ^0.59.0

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config
help](https://redirect.github.com/renovatebot/renovate/discussions) if
that's undesired.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/toeverything/AFFiNE).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My40My4yIiwidXBkYXRlZEluVmVyIjoiNDMuNDMuMiIsInRhcmdldEJyYW5jaCI6ImNhbmFyeSIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-01 21:19:09 +00:00
GyeongSu Han
2e95d91093 fix: restore Accept header and prevent encoded response in GitHub OAuth token request (regression from #14061) (#14538)
## 📝 Summary

This PR fixes a regression that caused the following error during GitHub
OAuth login:

> Unable to parse JSON response from
[https://github.com/login/oauth/access_token](https://github.com/login/oauth/access_token)

Related issue:
[https://github.com/toeverything/AFFiNE/issues/14334](https://github.com/toeverything/AFFiNE/issues/14334)
Regression introduced in:
[https://github.com/toeverything/AFFiNE/pull/14061](https://github.com/toeverything/AFFiNE/pull/14061)

---

## 🎯 Background

GitHub’s OAuth access token endpoint returns different response formats
depending on the request headers.

To receive a JSON response, the request must include:

```
Accept: application/json
```

If the `Accept` header is missing, GitHub responds with:

```
application/x-www-form-urlencoded
```

The current implementation assumes a JSON response and parses it
directly.
When a non-JSON response is returned, JSON parsing fails,
breaking the OAuth login flow.

---

## 🔍 Traffic Analysis (tcpdump)

Network path:

affine-graphql → (HTTPS) → envoy → (HTTP, tcpdump) → envoy → GitHub

### Observed Request

```
POST /login/oauth/access_token HTTP/1.1
host: github-proxy.com
content-type: application/x-www-form-urlencoded
accept: */*
...
```

### Observed Response

```
HTTP/1.1 200 OK
date: Sat, 28 Feb 2026 14:47:43 GMT
content-type: application/x-www-form-urlencoded; charset=utf-8
...
```

The `Accept` header was `*/*` instead of `application/json`,
causing GitHub to return a form-urlencoded response.

---

## 🐛 Root Cause

PR #14061 introduced a side effect in the request configuration.

Although the `Accept` header was initially defined,
the request options were later overwritten by the `init` parameter.

Because `init.headers` replaced the previously defined headers object,
the required header was lost.

Resulting in:

* Missing `Accept: application/json`
* GitHub returning `application/x-www-form-urlencoded`
* JSON parsing failure
* OAuth login failure

---

## 🔧 Changes

### 1️⃣ Fix header overwrite order

* Process the incoming `init` parameter first
* Explicitly overwrite required headers afterward
* Ensure `Accept: application/json` is always enforced

---

## 💥 Breaking Changes

None.

---

## 🧪 How to Test

1. Configure GitHub OAuth.
2. Attempt login via GitHub.
3. Verify that:

   * The request contains `Accept: application/json`
   * The response content-type is `application/json`
   * No JSON parsing error occurs
   * OAuth login completes successfully

---

## 📌 Notes

This change restores correct OAuth behavior and prevents regression
caused by header overwriting introduced in #14061.

The same header overwrite pattern identified in this issue
was also found in the calendar module and has been corrected there as
well.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Refactor**
* Improved backend HTTP header handling for external integrations to
avoid unintended header overrides, ensuring content-type and encoding
hints are applied consistently and improving reliability of service
requests.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-03-01 14:32:28 +00:00
DarkSky
2cb171f553 feat: cleanup webpack deps (#14530)
#### PR Dependency Tree


* **PR #14530** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Breaking Changes**
  * Webpack bundler support removed from the build system
* Bundler selection parameter removed from build and development
commands

* **Refactor**
  * Build configuration consolidated to a single bundler approach
* Webpack-specific build paths and workflows removed; development server
simplified

* **Chores**
  * Removed webpack-related dev dependencies and tooling
  * Updated package build scripts for a unified bundle command

* **Dependencies**
* Upgraded Sentry packages across frontend packages
(react/electron/esbuild plugin)
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-28 00:24:08 +08:00
DarkSky
a4e2242b8d chore: bump playwright (#13947)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Chores**
* Updated Playwright test tooling to 1.58.2 across the repository and
test packages.

* **Tests**
* Improved end-to-end robustness: replaced fragile timing/coordinate
logic with element-based interactions, added polling/retry checks for
flaky asserts and async state, and simplified input/rename flows to
reduce test flakiness.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-27 22:56:43 +08:00
DarkSky
c90f173821 chore: bump deps (#14526)
#### PR Dependency Tree


* **PR #14526** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Chores**
* Updated Storybook component development tooling to version 10.2.13 for
improved stability and performance
  * Removed Chromatic integration from the component preview system

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-27 20:17:06 +08:00
DarkSky
e1e0ac2345 chore: cleanup deps (#14525)
#### PR Dependency Tree


* **PR #14525** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Chores**
  * Removed an unused development dependency.
* Updated dotLottie/Lottie-related dependency versions across packages
and replaced a removed player dependency with the new package.

* **Refactor**
* AI animated icons now re-export from a shared component and are loaded
only in the browser, reducing upfront bundle weight and centralizing
icon assets.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-27 11:56:54 +08:00
DarkSky
bdccf4e9fd fix: typo 2026-02-27 10:20:35 +08:00
DarkSky
11cf1928b5 fix(server): transaction error (#14518)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Events can be dispatched in a detached context to avoid inheriting the
current transaction.

* **Bug Fixes**
* Improved resilience and error handling for event processing (graceful
handling of deleted workspaces and ignorable DB errors).
  * More reliable owner assignment flow when changing document owners.

* **Tests**
  * Added tests for doc content staleness with deleted workspaces.
  * Added permission event tests for missing workspace/editor scenarios.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-26 19:53:22 +08:00
donqu1xotevincent
5215c73166 chore(ios): update description (#14522) 2026-02-26 19:49:50 +08:00
DarkSky
895e774569 fix: static file handle & ws connect 2026-02-25 11:41:42 +08:00
DarkSky
79460072bb fix: old client compatibility 2026-02-24 23:58:10 +08:00
DarkSky
41b3b0e82e feat: handle calendar sync failed (#14510)
#### PR Dependency Tree


* **PR #14510** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Improved calendar sync reliability with exponential backoff for
repeated failures.
* Better handling of token refresh failures with automatic account
invalidation and cleanup when needed.
* Subscriptions are now automatically disabled and related events
removed when the calendar provider reports missing resources.

* **Tests**
* Added comprehensive tests covering sync failures, backoff behavior,
token refresh handling, skipping retries during backoff, and recovery.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-24 22:45:47 +08:00
DarkSky
9c99293c92 fix: linux release 2026-02-24 19:46:11 +08:00
DarkSky
6aba4350ac fix: deps link 2026-02-24 18:09:57 +08:00
DarkSky
046e126054 feat: bump typescript (#14507)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Chores**
  * Upgraded TypeScript toolchain to v5.9.3 across packages and tooling.
* Removed legacy ts-node and migrated developer tooling to newer
runtimes (tsx/SWC) where applicable.
* **Documentation**
* Updated developer CLI docs and runtime behavior notes to reflect the
new loader/runtime for running TypeScript files; no changes to public
APIs or end-user behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-24 13:22:46 +08:00
DarkSky
c2c7dde06c chore: bump deps (#14506)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Chores**
  * Version bumped to 0.26.3 across the project and Helm charts.
  * Removed an unused dependency (minimatch) from multiple packages.
* Updated build/tooling and packaging metadata, including packaging
maker replacement.
  * Adjusted app release metadata and platform packaging config.

* **Tests**
* Updated test snapshots to reflect minor presentational styling
adjustments.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-24 09:11:41 +08:00
DarkSky
5fb1c11a96 fix: image proxy url (#14505)
#### PR Dependency Tree


* **PR #14505** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Improvements**
* Better image-proxy detection to avoid double-proxying already proxied
images.
* Improved runtime image proxy configuration so images load consistently
across deployments.
* More robust image URL handling for optimized image loading and fewer
redundant requests.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-24 03:29:17 +08:00
passabilities.eth
3e39dbb298 fix(mobile): fixed toolbar position (#14329)
## Summary
Fixed the text formatting toolbar not working properly on mobile web
browsers.

## Problem
The toolbar had multiple issues on mobile devices:
- It would render off-screen or be covered by the virtual keyboard
- The flag-based rendering system caused visibility issues on mobile
- Long-press text selection didn't trigger the toolbar
- Wide toolbars could overflow the viewport

<img
src="https://github.com/user-attachments/assets/8f54590c-1d2c-4c87-abab-32206df17ebf"
width="250">

## Solution
- Use fixed positioning at bottom of screen on mobile devices
- Position toolbar above virtual keyboard using Visual Viewport API
- Handle toolbar visibility directly via `selectionchange` event
- Bypass flag-based rendering system on mobile to avoid rendering issues
- Add `touchend` listener to handle long-press text selection
- Limit toolbar max-width to viewport minus padding
- Enable horizontal scrolling for overflow content

<img
src="https://github.com/user-attachments/assets/45130860-f01a-45c1-87c5-d43264f88613"
width="250">

## Test plan
- [x] Tested on mobile Safari (iOS)
- [x] Tested on mobile Chrome (Android)
- [x] Verified desktop browsers still work correctly
- [x] Verified the toolbar is fixed to the bottom of the screen and
above virtual keyboard
- [x] Verified long-press text selection triggers toolbar

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Improvements**
* Mobile toolbar now anchors to the bottom, adapts width, and
repositions dynamically to stay above on-screen keyboards.
* Toolbar visibility is context-aware, showing when native-like text
selections occur and hiding otherwise; touch interactions are handled
for reliable toggling.
  * Desktop experience and public APIs remain unchanged.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: DarkSky <darksky2048@gmail.com>
2026-02-24 01:36:09 +08:00
DarkSky
e617740974 chore: improve cors 2026-02-24 00:51:08 +08:00
DarkSky
744c78abbb feat(infra): improve ci perf (#14503)
#### PR Dependency Tree


* **PR #14503** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Chores**
* Improved CI efficiency with targeted Rust test detection and
workspace-scoped builds for faster, more focused runs.
* Added workflow static-analysis configuration and refined
Node/Playwright/Electron build flags and platform controls.
* Added a new test workspace dependency and TypeScript project reference
for blocksuite tests; updated workspace wiring for focused builds.
  * Cleaned CI environment handling for server test orchestration.

* **Bug Fixes**
* Hardened mobile release steps (safer credential handling and quoting)
to improve iOS/Android publish reliability.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-23 21:52:17 +08:00
DarkSky
91c5869053 feat: improve selfhosted login (#14502)
fix #13397
fix #14011

#### PR Dependency Tree


* **PR #14502** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Centralized CORS policy with dynamic origin validation applied to
server and realtime connections
* Improved sign-in flows with contextual, localized error hints and
toast notifications
* Centralized network-error normalization and conditional OAuth provider
fetching

* **Bug Fixes**
* Better feedback for self-hosted connection failures and clearer
authentication error handling
* More robust handling of network-related failures with user-friendly
messages
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-23 21:23:01 +08:00
DarkSky
6d805b302c docs: cleanup outdated infos 2026-02-23 21:03:13 +08:00
Pixel Perfect
fb9f49b948 fix(data-view): preserve filtering on hidden properties (#14500)
Fixes issue #14036 where hiding a column used in filters caused empty
table/kanban results.

Root cause: filter evaluation built the row map from visible properties
only.

Change: evaluate filters using full property set (propertiesRaw$) so
hidden filtered columns still participate.

Added unit regressions for both table and kanban hidden-column filtering
behavior.

Verified this does fix the filtering issue for hidden columns:

<img width="3440" height="1440" alt="Screenshot of before and after
views of a database with hidden columns and filtering on said column"
src="https://github.com/user-attachments/assets/c1e2674f-06be-44e9-97bd-63593172f05b"
/>

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Fixed filtering in Kanban and Table views so filters evaluate against
all properties (including hidden/raw columns), ensuring consistent
results regardless of column visibility.
* **Tests**
* Added tests covering filtering behavior with hidden and filtered
columns to prevent regressions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-23 20:45:12 +08:00
DarkSky
ef6717e59a fix(editor): editor behavior and styles (#14498)
fix #14269 
fix #13920
fix #13977
fix #13953
fix #13895
fix #13905
fix #14136
fix #14357
fix #14491

#### PR Dependency Tree


* **PR #14498** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
  * Callout and toolbar defaults now reliably show grey backgrounds
  * Keyboard shortcuts behave better across layouts and non-ASCII input
  * Deleted workspaces no longer appear in local listings

* **New Features**
  * Cell editing now respects pre-entry validation hooks
* Scrollbars use themeable variables and include Chromium compatibility
fixes

* **Style**
  * Minor UI color adjustment for hidden properties

* **Tests**
  * Added unit tests for table column handling and keymap behavior
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-23 06:37:16 +08:00
DarkSky
ad988dbd1e chore: trim useless files for client (#14488)
#### PR Dependency Tree


* **PR #14488** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Chores**
* Improved Electron build to trim unused locale files on macOS, Windows,
and Linux while always preserving English fallbacks; added post-build
cleanup and stricter packaging ignore rules to exclude tests, examples,
scripts, docs, README, and build metadata.
* **Style**
  * Reformatted a TypeScript type annotation for consistency.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-22 17:17:42 +08:00
DarkSky
3d01766f55 fix: history may duplicate on concurrency (#14487)
#### PR Dependency Tree


* **PR #14487** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Enhanced history record creation to prevent duplicate entries in
concurrent scenarios.

* **Tests**
  * Added validation for idempotent history record creation.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-22 02:13:51 +08:00
DarkSky
2414aa5848 feat: improve admin build (#14485)
#### PR Dependency Tree


* **PR #14485** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Chores**
  * Admin static assets now served under /admin for self-hosted installs
  * CLI is directly executable from the command line
  * Build tooling supports a configurable self-hosted public path
  * Updated admin package script for adding UI components
* Added a PostCSS dependency and plugin to the build toolchain for admin
builds

* **Style**
* Switched queue module to a local queuedash stylesheet, added queuedash
Tailwind layer, and scoped queuedash styles for the admin UI

* **Bug Fixes**
  * Improved error propagation in the Electron renderer
* Migration compatibility to repair a legacy checksum during native
storage upgrades

* **Tests**
  * Added tests covering the migration repair flow
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-21 23:25:05 +08:00
DarkSky
0de1bd0da8 feat: bump deps (#14484) 2026-02-21 08:04:18 +08:00
DarkSky
186ec5431d fix: android build 2026-02-21 05:12:12 +08:00
DarkSky
da57bfe8e7 fix: enhance MCP token handling (#14483)
fix #14475 

#### PR Dependency Tree


* **PR #14483** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

* **New Features**
* Enhanced MCP server token management with improved security—tokens now
display only once with redaction support.
* Updated token creation and deletion workflows with clearer UI state
controls.
* Added tooltip guidance when copying configuration with redacted
tokens.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-21 04:14:14 +08:00
DarkSky
c9bffc13b5 feat: improve mobile native impl (#14481)
fix #13529 

#### PR Dependency Tree


* **PR #14481** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Mobile blob caching with file-backed storage for faster loads and
reduced network usage
* Blob decoding with lazy refresh on token-read failures for improved
reliability
  * Full-text search/indexing exposed to mobile apps
* Document sync APIs and peer clock management for robust cross-device
sync

* **Tests**
* Added unit tests covering payload decoding, cache safety, and
concurrency

* **Dependencies**
* Added an LRU cache dependency and a new mobile-shared package for
shared mobile logic
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-21 04:13:24 +08:00
DarkSky
d8cc0acdd0 chore: update flags 2026-02-19 10:18:43 +08:00
Neo
35e1411407 fix: docTitle unexpectedly translated (#14467)
fix #14465

In Chinese mode, the document with the specified name may not be
displayed correctly in the sidebar, and it may be mistaken for the
translation of the content that needs to be translated.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Fixed document title display in navigation panels on desktop and
mobile to properly render without additional processing steps.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-17 13:45:31 +00:00
DarkSky
8f833388eb feat: improve admin panel design (#14464) 2026-02-17 17:40:29 +08:00
DarkSky
850e646ab9 fix: electon rendering on windows (#14456)
fix #14450
fix #14401
fix #13983
fix #12766
fix #14404
fix #12019

#### PR Dependency Tree


* **PR #14456** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added new tab navigation functions: `switchTab`, `switchToNextTab`,
and `switchToPreviousTab`.

* **Bug Fixes**
  * Improved bounds validation for tab view resizing.
  * Enhanced tab lifecycle management during navigation events.
  * Refined background throttling behavior for active tabs.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-16 14:08:26 +08:00
DarkSky
728e02cab7 feat: bump eslint & oxlint (#14452)
#### PR Dependency Tree


* **PR #14452** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Improved null-safety, dependency tracking, upload validation, and
error logging for more reliable uploads, clipboard, calendar linking,
telemetry, PDF/theme printing, and preview/zoom behavior.
* Tightened handling of all-day calendar events (missing date now
reported).

* **Deprecations**
  * Removed deprecated RadioButton and RadioButtonGroup; use RadioGroup.

* **Chores**
* Unified and upgraded linting/config, reorganized imports, and
standardized binary handling for more consistent builds and tooling.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-16 13:52:08 +08:00
DarkSky
792164edd1 fix: sign 2026-02-16 12:23:26 +08:00
DarkSky
e3177e6837 feat: normalize search text (#14449)
#### PR Dependency Tree


* **PR #14449** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Improvements**
* Search text normalization now applied consistently across doc titles,
search results, and highlights for uniform display formatting.

* **Tests**
* Added comprehensive test coverage for search text normalization
utility.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-16 08:07:04 +08:00
DarkSky
42f2d2b337 feat: support markdown preview (#14447) 2026-02-15 21:05:52 +08:00
DarkSky
9d7f4acaf1 fix: s3 upload compatibility (#14445)
fix #14432 

#### PR Dependency Tree


* **PR #14445** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Refactor**
* Improved file upload handling to ensure consistent support for
different data formats during object and multipart uploads.
* Enhanced type safety throughout storage and workflow components by
removing unnecessary type assertions.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-15 19:16:36 +08:00
DarkSky
9a1f600fc9 chore: update i18n status 2026-02-15 14:59:52 +08:00
steffenrapp
0f906ad623 feat(i18n): update German translation (#14444)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Chat panel: session management, history loading, embedding progress,
and deletion flow
* Document analytics: views, unique visitors, guest metrics, charts,
viewers and paywall messaging
* Calendar integration: expanded account/provider states, errors and
flow copy; DOCX import tooltip
  * Appearance: image antialiasing option and window-behavior toggles
  * Workspace sharing: visibility controls and related tooltips

* **Improvements**
* Expanded error and empty-state wording, subscription/payment
description, and experimental feature labels
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-15 14:57:47 +08:00
DarkSky
09aa65c52a feat: improve ci 2026-02-15 14:53:35 +08:00
DarkSky
25227a09f7 feat: improve grouping perf in edgeless (#14442)
fix #14433 

#### PR Dependency Tree


* **PR #14442** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
  * Level-of-detail thumbnails for large images.
  * Adaptive pacing for snapping, distribution and other alignment work.
  * RAF coalescer utility to batch high-frequency updates.
  * Operation timing utility to measure synchronous work.

* **Improvements**
* Batch group/ungroup reparenting that preserves element order and
selection.
  * Coalesced panning and drag updates to reduce jitter.
* Connector/group indexing for more reliable updates, deletions and
sync.
  * Throttled viewport refresh behavior.

* **Documentation**
  * Docs added for RAF coalescer and measureOperation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-15 03:17:22 +08:00
DarkSky
c0694c589b fix: editor style (#14440)
#### PR Dependency Tree


* **PR #14440** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Style**
* Refined CSS styling rules in workspace detail pages for improved
layout rendering consistency.
* Enhanced editor container display handling during loading states to
ensure proper layout adjustments.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-14 19:12:24 +08:00
DarkSky
819402d9f1 feat: asset upload with retry 2026-02-14 17:24:22 +08:00
DarkSky
33bc3e2fe9 feat: improve ci (#14438)
#### PR Dependency Tree


* **PR #14438** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Chores**
* Refined PR trigger automation to run only on open/reopen/synchronize
events
* Split native CI into platform-specific builds (Linux, Windows, macOS)
for more reliable pipelines
* Added conditional Copilot test gating to run API/E2E tests only when
relevant
* Added conditional PR-title lint skip when edits don't change the title
  * Improved test result uploads and artifact handling for gated flows
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-14 16:59:49 +08:00
DarkSky
2b71b3f345 feat: improve test & bundler (#14434)
#### PR Dependency Tree


* **PR #14434** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Introduced rspack bundler as an alternative to webpack for optimized
builds.

* **Tests & Quality**
* Added comprehensive editor semantic tests covering markdown, hotkeys,
and slash-menu operations.
* Expanded CI cross-browser testing to Chromium, Firefox, and WebKit;
improved shape-rendering tests to account for zoom.

* **Bug Fixes**
  * Corrected CSS overlay styling for development servers.
  * Fixed TypeScript typings for build tooling.

* **Other**
  * Document duplication now produces consistent "(n)" suffixes.
  * French i18n completeness increased to 100%.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-14 16:09:09 +08:00
dcornuel-del
3bc28ba78c feat(i18n): update French translations for various keys (#14437)
ajout de definition

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Documentation**
* Enhanced French language support with improved grammar, gender
neutrality, and consistency across UI text.
  * Added French translations for new AI-powered features.
* Refined French phrasing in prompts, tooltips, and messages for better
clarity and natural language flow.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-14 14:43:22 +08:00
667 changed files with 23444 additions and 13070 deletions

View File

@@ -222,7 +222,7 @@
},
"SMTP.sender": {
"type": "string",
"description": "Sender of all the emails (e.g. \"AFFiNE Self Hosted <noreply@example.com>\")\n@default \"AFFiNE Self Hosted <noreply@example.com>\"\n@environment `MAILER_SENDER`",
"description": "Sender of all the emails (e.g. \"AFFiNE Self Hosted &lt;noreply@example.com&gt;\")\n@default \"AFFiNE Self Hosted <noreply@example.com>\"\n@environment `MAILER_SENDER`",
"default": "AFFiNE Self Hosted <noreply@example.com>"
},
"SMTP.ignoreTLS": {
@@ -262,7 +262,7 @@
},
"fallbackSMTP.sender": {
"type": "string",
"description": "Sender of all the emails (e.g. \"AFFiNE Self Hosted <noreply@example.com>\")\n@default \"\"",
"description": "Sender of all the emails (e.g. \"AFFiNE Self Hosted &lt;noreply@example.com&gt;\")\n@default \"\"",
"default": ""
},
"fallbackSMTP.ignoreTLS": {
@@ -988,6 +988,16 @@
}
}
},
"providers.profiles": {
"type": "array",
"description": "The profile list for copilot providers.\n@default []",
"default": []
},
"providers.defaults": {
"type": "object",
"description": "The default provider ids for model output types and global fallback.\n@default {}",
"default": {}
},
"providers.openai": {
"type": "object",
"description": "The config for the openai provider.\n@default {\"apiKey\":\"\",\"baseURL\":\"https://api.openai.com/v1\"}\n@link https://github.com/openai/openai-node",

View File

@@ -3,6 +3,6 @@ contact_links:
- name: Something else?
url: https://github.com/toeverything/AFFiNE/discussions
about: Feel free to ask and answer questions over in GitHub Discussions
- name: AFFiNE Community Support
url: https://community.affine.pro
- name: AFFiNE Community Support (Discord)
url: https://affine.pro/redirect/discord
about: AFFiNE Community - a place to ask, learn and engage with others

20
.github/actionlint.yaml vendored Normal file
View File

@@ -0,0 +1,20 @@
self-hosted-runner:
# Labels of self-hosted runner in array of strings.
labels:
- win-signer
# Configuration variables in array of strings defined in your repository or
# organization. `null` means disabling configuration variables check.
# Empty array means no configuration variable is allowed.
config-variables: null
# Configuration for file paths. The keys are glob patterns to match to file
# paths relative to the repository root. The values are the configurations for
# the file paths. Note that the path separator is always '/'.
# The following configurations are available.
#
# "ignore" is an array of regular expression patterns. Matched error messages
# are ignored. This is similar to the "-ignore" command line option.
paths:
# .github/workflows/**/*.yml:
# ignore: []

View File

@@ -93,7 +93,7 @@ runs:
run: node -e "const p = $(yarn config cacheFolder --json).effective; console.log('yarn_global_cache=' + p)" >> $GITHUB_OUTPUT
- name: Cache non-full yarn cache on Linux
uses: actions/cache@v4
uses: actions/cache@v5
if: ${{ inputs.full-cache != 'true' && runner.os == 'Linux' }}
with:
path: |
@@ -105,7 +105,7 @@ runs:
# and the decompression performance on Windows is very terrible
# so we reduce the number of cached files on non-Linux systems by remove node_modules from cache path.
- name: Cache non-full yarn cache on non-Linux
uses: actions/cache@v4
uses: actions/cache@v5
if: ${{ inputs.full-cache != 'true' && runner.os != 'Linux' }}
with:
path: |
@@ -113,7 +113,7 @@ runs:
key: node_modules-cache-${{ github.job }}-${{ runner.os }}-${{ runner.arch }}-${{ steps.system-info.outputs.name }}-${{ steps.system-info.outputs.release }}-${{ steps.system-info.outputs.version }}
- name: Cache full yarn cache on Linux
uses: actions/cache@v4
uses: actions/cache@v5
if: ${{ inputs.full-cache == 'true' && runner.os == 'Linux' }}
with:
path: |
@@ -122,7 +122,7 @@ runs:
key: node_modules-cache-full-${{ runner.os }}-${{ runner.arch }}-${{ steps.system-info.outputs.name }}-${{ steps.system-info.outputs.release }}-${{ steps.system-info.outputs.version }}
- name: Cache full yarn cache on non-Linux
uses: actions/cache@v4
uses: actions/cache@v5
if: ${{ inputs.full-cache == 'true' && runner.os != 'Linux' }}
with:
path: |
@@ -154,7 +154,7 @@ runs:
# Note: Playwright's cache directory is hard coded because that's what it
# says to do in the docs. There doesn't appear to be a command that prints
# it out for us.
- uses: actions/cache@v4
- uses: actions/cache@v5
id: playwright-cache
if: ${{ inputs.playwright-install == 'true' }}
with:
@@ -189,7 +189,7 @@ runs:
run: |
echo "version=$(yarn why --json electron | grep -h 'workspace:.' | jq --raw-output '.children[].locator' | sed -e 's/@playwright\/test@.*://' | head -n 1)" >> $GITHUB_OUTPUT
- uses: actions/cache@v4
- uses: actions/cache@v5
id: electron-cache
if: ${{ inputs.electron-install == 'true' }}
with:

View File

@@ -7,7 +7,6 @@ inputs:
ios-app-version:
description: 'iOS App Store Version (Optional, use App version if empty)'
required: false
type: string
runs:
using: 'composite'
steps:

View File

@@ -3,4 +3,4 @@ name: affine
description: AFFiNE cloud chart
type: application
version: 0.0.0
appVersion: "0.26.1"
appVersion: "0.26.3"

View File

@@ -3,7 +3,7 @@ name: doc
description: AFFiNE doc server
type: application
version: 0.0.0
appVersion: "0.26.1"
appVersion: "0.26.3"
dependencies:
- name: gcloud-sql-proxy
version: 0.0.0

View File

@@ -3,7 +3,7 @@ name: front
description: AFFiNE front server
type: application
version: 0.0.0
appVersion: "0.26.1"
appVersion: "0.26.3"
dependencies:
- name: gcloud-sql-proxy
version: 0.0.0

View File

@@ -96,12 +96,20 @@ spec:
httpGet:
path: /info
port: http
initialDelaySeconds: {{ .Values.probe.initialDelaySeconds }}
initialDelaySeconds: {{ default .Values.probe.initialDelaySeconds .Values.probe.liveness.initialDelaySeconds }}
timeoutSeconds: {{ default .Values.probe.timeoutSeconds .Values.probe.liveness.timeoutSeconds }}
periodSeconds: {{ default .Values.probe.periodSeconds .Values.probe.liveness.periodSeconds }}
failureThreshold: {{ default .Values.probe.failureThreshold .Values.probe.liveness.failureThreshold }}
successThreshold: {{ default .Values.probe.successThreshold .Values.probe.liveness.successThreshold }}
readinessProbe:
httpGet:
path: /info
port: http
initialDelaySeconds: {{ .Values.probe.initialDelaySeconds }}
initialDelaySeconds: {{ default .Values.probe.initialDelaySeconds .Values.probe.readiness.initialDelaySeconds }}
timeoutSeconds: {{ default .Values.probe.timeoutSeconds .Values.probe.readiness.timeoutSeconds }}
periodSeconds: {{ default .Values.probe.periodSeconds .Values.probe.readiness.periodSeconds }}
failureThreshold: {{ default .Values.probe.failureThreshold .Values.probe.readiness.failureThreshold }}
successThreshold: {{ default .Values.probe.successThreshold .Values.probe.readiness.successThreshold }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.nodeSelector }}

View File

@@ -31,13 +31,21 @@ podSecurityContext:
resources:
limits:
cpu: '1'
memory: 2Gi
memory: 4Gi
requests:
cpu: '1'
memory: 2Gi
probe:
initialDelaySeconds: 20
timeoutSeconds: 5
periodSeconds: 10
failureThreshold: 6
successThreshold: 1
liveness:
initialDelaySeconds: 60
failureThreshold: 12
readiness: {}
services:
sync:

View File

@@ -3,7 +3,7 @@ name: graphql
description: AFFiNE GraphQL server
type: application
version: 0.0.0
appVersion: "0.26.1"
appVersion: "0.26.3"
dependencies:
- name: gcloud-sql-proxy
version: 0.0.0

View File

@@ -1,6 +1,10 @@
name: 'Pull Request Labeler'
on:
- pull_request_target
pull_request_target:
types:
- opened
- reopened
- synchronize
jobs:
triage:
@@ -9,5 +13,5 @@ jobs:
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/labeler@v5
- uses: actions/checkout@v6
- uses: actions/labeler@v6

View File

@@ -24,7 +24,7 @@ jobs:
runs-on: ubuntu-latest
environment: ${{ inputs.build-type }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup Version
uses: ./.github/actions/setup-version
with:
@@ -57,7 +57,7 @@ jobs:
runs-on: ubuntu-latest
environment: ${{ inputs.build-type }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup Version
uses: ./.github/actions/setup-version
with:
@@ -89,7 +89,7 @@ jobs:
runs-on: ubuntu-latest
environment: ${{ inputs.build-type }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup Version
uses: ./.github/actions/setup-version
with:
@@ -132,7 +132,7 @@ jobs:
file: server-native.armv7.node
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup Version
uses: ./.github/actions/setup-version
with:
@@ -166,7 +166,7 @@ jobs:
needs:
- build-server-native
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup Version
uses: ./.github/actions/setup-version
with:
@@ -202,7 +202,7 @@ jobs:
- build-mobile
- build-admin
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Download server dist
uses: actions/download-artifact@v4
with:

View File

@@ -46,7 +46,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
@@ -67,10 +67,27 @@ jobs:
name: Lint
runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup Go (for actionlint)
uses: actions/setup-go@v6
with:
go-version: 'stable'
- name: Install actionlint
shell: bash
run: |
set -euo pipefail
go install github.com/rhysd/actionlint/cmd/actionlint@v1.7.11
- name: Run actionlint
shell: bash
run: |
set -euo pipefail
"$(go env GOPATH)/bin/actionlint"
- name: Run oxlint
# oxlint is fast, so wrong code will fail quickly
run: yarn dlx $(node -e "console.log(require('./package.json').scripts['lint:ox'].replace('oxlint', 'oxlint@' + require('./package.json').devDependencies.oxlint))")
run: |
set -euo pipefail
oxlint_version="$(node -e "console.log(require('./package.json').devDependencies.oxlint)")"
yarn dlx "oxlint@${oxlint_version}" --deny-warnings
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
@@ -94,7 +111,7 @@ jobs:
env:
NODE_OPTIONS: --max-old-space-size=14384
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
@@ -108,20 +125,45 @@ jobs:
run: |
yarn affine bs-docs build
git checkout packages/frontend/i18n/src/i18n-completenesses.json
git status --porcelain | grep . && {
if git status --porcelain | grep -q .; then
echo "Run 'yarn typecheck && yarn affine bs-docs build' and make sure all changes are submitted"
exit 1
} || {
else
echo "All changes are submitted"
}
fi
rust-test-filter:
name: Rust test filter
runs-on: ubuntu-latest
outputs:
run-rust: ${{ steps.rust-filter.outputs.rust }}
steps:
- uses: actions/checkout@v6
- uses: dorny/paths-filter@v3
id: rust-filter
with:
filters: |
rust:
- '**/*.rs'
- '**/Cargo.toml'
- '**/Cargo.lock'
- '.cargo/**'
- 'rust-toolchain*'
- '.github/actions/build-rust/**'
lint-rust:
name: Lint Rust
if: ${{ needs.rust-test-filter.outputs.run-rust == 'true' }}
runs-on: ubuntu-latest
needs:
- rust-test-filter
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- uses: ./.github/actions/build-rust
with:
target: x86_64-unknown-linux-gnu
package: 'affine'
no-build: 'true'
- name: fmt check
run: |
@@ -140,7 +182,7 @@ jobs:
needs:
- build-server-native
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
@@ -159,21 +201,23 @@ jobs:
yarn affine i18n build
yarn affine server genconfig
git checkout packages/frontend/i18n/src/i18n-completenesses.json
git status --porcelain | grep . && {
if git status --porcelain | grep -q .; then
echo "Run 'yarn affine init && yarn affine gql build && yarn affine i18n build && yarn affine server genconfig' and make sure all changes are submitted"
exit 1
} || {
else
echo "All changes are submitted"
}
fi
check-yarn-binary:
name: Check yarn binary
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Run check
run: |
yarn set version $(node -e "console.log(require('./package.json').packageManager.split('@')[1])")
set -euo pipefail
yarn_version="$(node -e "console.log(require('./package.json').packageManager.split('@')[1])")"
yarn set version "$yarn_version"
git diff --exit-code
e2e-blocksuite-test:
@@ -184,10 +228,11 @@ jobs:
matrix:
shard: [1, 2]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
extra-flags: workspaces focus @affine/monorepo @affine-test/blocksuite @blocksuite/playground @blocksuite/integration-test
playwright-install: true
playwright-platform: 'chromium'
electron-install: false
@@ -210,18 +255,14 @@ jobs:
e2e-blocksuite-cross-browser-test:
name: E2E BlockSuite Cross Browser Test
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
shard: [1]
browser: ['chromium', 'firefox', 'webkit']
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
extra-flags: workspaces focus @affine/monorepo @affine-test/blocksuite @blocksuite/playground @blocksuite/integration-test
playwright-install: true
playwright-platform: ${{ matrix.browser }}
playwright-platform: 'chromium,firefox,webkit'
electron-install: false
full-cache: true
@@ -229,15 +270,15 @@ jobs:
run: yarn workspace @blocksuite/playground build
- name: Run playwright tests
env:
BROWSER: ${{ matrix.browser }}
run: yarn workspace @affine-test/blocksuite test "cross-platform/" --forbid-only --shard=${{ matrix.shard }}/${{ strategy.job-total }}
run: |
yarn workspace @blocksuite/integration-test test:unit
yarn workspace @affine-test/blocksuite test "cross-platform/" --forbid-only
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results-e2e-bs-cross-browser-${{ matrix.browser }}-${{ matrix.shard }}
name: test-results-e2e-bs-cross-browser
path: ./test-results
if-no-files-found: ignore
@@ -253,10 +294,11 @@ jobs:
matrix:
shard: [1, 2, 3, 4, 5]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
extra-flags: workspaces focus @affine/monorepo @affine-test/affine-local @affine/web @affine/server
playwright-install: true
playwright-platform: 'chromium'
electron-install: false
@@ -284,10 +326,11 @@ jobs:
matrix:
shard: [1, 2]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
extra-flags: workspaces focus @affine/monorepo @affine-test/affine-mobile @affine/mobile
playwright-install: true
electron-install: false
full-cache: true
@@ -307,7 +350,7 @@ jobs:
name: Unit Test
runs-on: ubuntu-latest
needs:
- build-native
- build-native-linux
env:
DISTRIBUTION: web
strategy:
@@ -315,12 +358,13 @@ jobs:
matrix:
shard: [1, 2, 3]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
electron-install: true
playwright-install: true
playwright-platform: 'chromium,firefox,webkit'
full-cache: true
- name: Download affine.linux-x64-gnu.node
@@ -341,21 +385,13 @@ jobs:
name: affine
fail_ci_if_error: false
build-native:
name: Build AFFiNE native (${{ matrix.spec.target }})
runs-on: ${{ matrix.spec.os }}
build-native-linux:
name: Build AFFiNE native (x86_64-unknown-linux-gnu)
runs-on: ubuntu-latest
env:
CARGO_PROFILE_RELEASE_DEBUG: '1'
strategy:
fail-fast: false
matrix:
spec:
- { os: ubuntu-latest, target: x86_64-unknown-linux-gnu }
- { os: macos-latest, target: x86_64-apple-darwin }
- { os: macos-latest, target: aarch64-apple-darwin }
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
@@ -366,7 +402,46 @@ jobs:
working-directory: ${{ github.workspace }}
shell: bash
run: |
export PLATFORM_ARCH_ABI=$(node -e "console.log(require('@napi-rs/cli').parseTriple('${{ matrix.spec.target }}').platformArchABI)")
PLATFORM_ARCH_ABI="$(node -e "console.log(require('@napi-rs/cli').parseTriple('x86_64-unknown-linux-gnu').platformArchABI)")"
echo "filename=affine.$PLATFORM_ARCH_ABI.node" >> "$GITHUB_OUTPUT"
- name: Build AFFiNE native
uses: ./.github/actions/build-rust
with:
target: x86_64-unknown-linux-gnu
package: '@affine/native'
- name: Upload ${{ steps.filename.outputs.filename }}
uses: actions/upload-artifact@v4
if: always()
with:
name: ${{ steps.filename.outputs.filename }}
path: ${{ github.workspace }}/packages/frontend/native/${{ steps.filename.outputs.filename }}
if-no-files-found: error
build-native-macos:
name: Build AFFiNE native (${{ matrix.spec.target }})
runs-on: ${{ matrix.spec.os }}
env:
CARGO_PROFILE_RELEASE_DEBUG: '1'
strategy:
fail-fast: false
matrix:
spec:
- { os: macos-latest, target: x86_64-apple-darwin }
- { os: macos-latest, target: aarch64-apple-darwin }
steps:
- uses: actions/checkout@v6
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
extra-flags: workspaces focus @affine/native
electron-install: false
- name: Setup filename
id: filename
working-directory: ${{ github.workspace }}
shell: bash
run: |
PLATFORM_ARCH_ABI="$(node -e "console.log(require('@napi-rs/cli').parseTriple('${{ matrix.spec.target }}').platformArchABI)")"
echo "filename=affine.$PLATFORM_ARCH_ABI.node" >> "$GITHUB_OUTPUT"
- name: Build AFFiNE native
uses: ./.github/actions/build-rust
@@ -383,7 +458,7 @@ jobs:
# Split Windows build because it's too slow
# and other ci jobs required linux native
build-windows-native:
build-native-windows:
name: Build AFFiNE native (${{ matrix.spec.target }})
runs-on: ${{ matrix.spec.os }}
env:
@@ -396,7 +471,7 @@ jobs:
- { os: windows-latest, target: aarch64-pc-windows-msvc }
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- uses: samypr100/setup-dev-drive@v3
with:
workspace-copy: true
@@ -415,7 +490,7 @@ jobs:
working-directory: ${{ env.DEV_DRIVE_WORKSPACE }}
shell: bash
run: |
export PLATFORM_ARCH_ABI=$(node -e "console.log(require('@napi-rs/cli').parseTriple('${{ matrix.spec.target }}').platformArchABI)")
PLATFORM_ARCH_ABI="$(node -e "console.log(require('@napi-rs/cli').parseTriple('${{ matrix.spec.target }}').platformArchABI)")"
echo "filename=affine.$PLATFORM_ARCH_ABI.node" >> "$GITHUB_OUTPUT"
- name: Build AFFiNE native
uses: ./.github/actions/build-rust
@@ -436,7 +511,7 @@ jobs:
env:
CARGO_PROFILE_RELEASE_DEBUG: '1'
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
@@ -459,10 +534,11 @@ jobs:
name: Build @affine/electron renderer
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
extra-flags: workspaces focus @affine/monorepo @affine/electron-renderer @affine/nbstore @toeverything/infra
electron-install: false
full-cache: true
- name: Build Electron renderer
@@ -483,9 +559,9 @@ jobs:
name: Native Unit Test
runs-on: ubuntu-latest
needs:
- build-native
- build-native-linux
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
@@ -539,11 +615,12 @@ jobs:
ports:
- 9308:9308
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
extra-flags: workspaces focus @affine/monorepo @affine/server
electron-install: false
full-cache: true
@@ -577,8 +654,6 @@ jobs:
runs-on: ubuntu-latest
needs:
- build-server-native
strategy:
fail-fast: false
env:
NODE_ENV: test
DATABASE_URL: postgresql://affine:affine@localhost:5432/affine
@@ -621,11 +696,12 @@ jobs:
stack-version: 9.0.1
security-enabled: false
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
extra-flags: workspaces focus @affine/monorepo @affine/server
electron-install: false
full-cache: true
@@ -642,8 +718,6 @@ jobs:
run: yarn affine @affine/server test:coverage "**/*/*elasticsearch.spec.ts" --forbid-only
env:
CARGO_TARGET_DIR: '${{ github.workspace }}/target'
CI_NODE_INDEX: ${{ matrix.node_index }}
CI_NODE_TOTAL: ${{ matrix.total_nodes }}
- name: Upload server test coverage results
uses: codecov/codecov-action@v5
@@ -685,11 +759,12 @@ jobs:
ports:
- 9308:9308
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
extra-flags: workspaces focus @affine/monorepo @affine/server
electron-install: false
full-cache: true
@@ -716,13 +791,16 @@ jobs:
miri:
name: miri code check
if: ${{ needs.rust-test-filter.outputs.run-rust == 'true' }}
runs-on: ubuntu-latest
needs:
- rust-test-filter
env:
RUST_BACKTRACE: full
CARGO_TERM_COLOR: always
MIRIFLAGS: -Zmiri-backtrace=full -Zmiri-tree-borrows
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
@@ -741,13 +819,16 @@ jobs:
loom:
name: loom thread test
if: ${{ needs.rust-test-filter.outputs.run-rust == 'true' }}
runs-on: ubuntu-latest
needs:
- rust-test-filter
env:
RUSTFLAGS: --cfg loom
RUST_BACKTRACE: full
CARGO_TERM_COLOR: always
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
@@ -764,11 +845,14 @@ jobs:
fuzzing:
name: fuzzing
if: ${{ needs.rust-test-filter.outputs.run-rust == 'true' }}
runs-on: ubuntu-latest
needs:
- rust-test-filter
env:
CARGO_TERM_COLOR: always
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
@@ -800,14 +884,18 @@ jobs:
rust-test:
name: Run native tests
if: ${{ needs.rust-test-filter.outputs.run-rust == 'true' }}
runs-on: ubuntu-latest
needs:
- rust-test-filter
env:
CARGO_TERM_COLOR: always
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup Rust
uses: ./.github/actions/build-rust
with:
target: x86_64-unknown-linux-gnu
package: 'affine'
no-build: 'true'
@@ -819,11 +907,51 @@ jobs:
- name: Run tests
run: cargo nextest run --workspace --exclude affine_server_native --features use-as-lib --release --no-fail-fast
copilot-test-filter:
name: Copilot test filter
runs-on: ubuntu-latest
outputs:
run-api: ${{ steps.decision.outputs.run_api }}
run-e2e: ${{ steps.decision.outputs.run_e2e }}
steps:
- uses: actions/checkout@v6
- uses: dorny/paths-filter@v3
id: copilot-filter
with:
filters: |
api:
- 'packages/backend/server/src/plugins/copilot/**'
- 'packages/backend/server/tests/copilot.*'
e2e:
- 'packages/backend/server/src/plugins/copilot/**'
- 'packages/backend/server/tests/copilot.*'
- 'packages/frontend/core/src/blocksuite/ai/**'
- 'packages/frontend/core/src/modules/workspace-indexer-embedding/**'
- 'tests/affine-cloud-copilot/**'
- name: Decide test scope
id: decision
run: |
if [[ "${{ steps.copilot-filter.outputs.api }}" == "true" ]]; then
echo "run_api=true" >> "$GITHUB_OUTPUT"
else
echo "run_api=false" >> "$GITHUB_OUTPUT"
fi
if [[ "${{ steps.copilot-filter.outputs.e2e }}" == "true" ]]; then
echo "run_e2e=true" >> "$GITHUB_OUTPUT"
else
echo "run_e2e=false" >> "$GITHUB_OUTPUT"
fi
copilot-api-test:
name: Server Copilot Api Test
if: ${{ needs.copilot-test-filter.outputs.run-api == 'true' }}
runs-on: ubuntu-latest
needs:
- build-server-native
- copilot-test-filter
env:
NODE_ENV: test
DISTRIBUTION: web
@@ -855,55 +983,32 @@ jobs:
ports:
- 9308:9308
steps:
- uses: actions/checkout@v4
- name: Check blocksuite update
id: check-blocksuite-update
env:
BASE_REF: ${{ github.base_ref }}
run: |
if node ./scripts/detect-blocksuite-update.mjs "$BASE_REF"; then
echo "skip=false" >> $GITHUB_OUTPUT
else
echo "skip=true" >> $GITHUB_OUTPUT
fi
- uses: dorny/paths-filter@v3
id: apifilter
with:
filters: |
changed:
- 'packages/backend/server/src/plugins/copilot/**'
- 'packages/backend/server/tests/copilot.*'
- uses: actions/checkout@v6
- name: Setup Node.js
if: ${{ steps.check-blocksuite-update.outputs.skip != 'true' || steps.apifilter.outputs.changed == 'true' }}
uses: ./.github/actions/setup-node
with:
extra-flags: workspaces focus @affine/monorepo @affine/server
electron-install: false
full-cache: true
- name: Download server-native.node
if: ${{ steps.check-blocksuite-update.outputs.skip != 'true' || steps.apifilter.outputs.changed == 'true' }}
uses: actions/download-artifact@v4
with:
name: server-native.node
path: ./packages/backend/native
- name: Prepare Server Test Environment
if: ${{ steps.check-blocksuite-update.outputs.skip != 'true' || steps.apifilter.outputs.changed == 'true' }}
env:
SERVER_CONFIG: ${{ secrets.TEST_SERVER_CONFIG }}
uses: ./.github/actions/server-test-env
- name: Run server tests
if: ${{ steps.check-blocksuite-update.outputs.skip != 'true' || steps.apifilter.outputs.changed == 'true' }}
run: yarn affine @affine/server test:copilot:coverage --forbid-only
env:
CARGO_TARGET_DIR: '${{ github.workspace }}/target'
- name: Upload server test coverage results
if: ${{ steps.check-blocksuite-update.outputs.skip != 'true' || steps.apifilter.outputs.changed == 'true' }}
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
@@ -914,6 +1019,7 @@ jobs:
copilot-e2e-test:
name: Frontend Copilot E2E Test
if: ${{ needs.copilot-test-filter.outputs.run-e2e == 'true' }}
runs-on: ubuntu-latest
env:
DISTRIBUTION: web
@@ -928,6 +1034,7 @@ jobs:
shardTotal: [5]
needs:
- build-server-native
- copilot-test-filter
services:
postgres:
image: pgvector/pgvector:pg16
@@ -949,54 +1056,29 @@ jobs:
ports:
- 9308:9308
steps:
- uses: actions/checkout@v4
- name: Check blocksuite update
id: check-blocksuite-update
env:
BASE_REF: ${{ github.base_ref }}
run: |
if node ./scripts/detect-blocksuite-update.mjs "$BASE_REF"; then
echo "skip=false" >> $GITHUB_OUTPUT
else
echo "skip=true" >> $GITHUB_OUTPUT
fi
- uses: dorny/paths-filter@v3
id: e2efilter
with:
filters: |
changed:
- 'packages/backend/server/src/plugins/copilot/**'
- 'packages/backend/server/tests/copilot.*'
- 'packages/frontend/core/src/blocksuite/ai/**'
- 'packages/frontend/core/src/modules/workspace-indexer-embedding/**'
- 'tests/affine-cloud-copilot/**'
- uses: actions/checkout@v6
- name: Setup Node.js
if: ${{ steps.check-blocksuite-update.outputs.skip != 'true' || steps.e2efilter.outputs.changed == 'true' }}
uses: ./.github/actions/setup-node
with:
extra-flags: workspaces focus @affine/monorepo @affine-test/affine-cloud-copilot @affine/web @affine/server
playwright-install: true
playwright-platform: 'chromium'
electron-install: false
hard-link-nm: false
- name: Download server-native.node
if: ${{ steps.check-blocksuite-update.outputs.skip != 'true' || steps.e2efilter.outputs.changed == 'true' }}
uses: actions/download-artifact@v4
with:
name: server-native.node
path: ./packages/backend/native
- name: Prepare Server Test Environment
if: ${{ steps.check-blocksuite-update.outputs.skip != 'true' || steps.e2efilter.outputs.changed == 'true' }}
env:
SERVER_CONFIG: ${{ secrets.TEST_SERVER_CONFIG }}
uses: ./.github/actions/server-test-env
- name: Run Copilot E2E Test ${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
if: ${{ steps.check-blocksuite-update.outputs.skip != 'true' || steps.e2efilter.outputs.changed == 'true' }}
uses: ./.github/actions/copilot-test
with:
script: yarn affine @affine-test/affine-cloud-copilot e2e --forbid-only --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
@@ -1006,7 +1088,7 @@ jobs:
runs-on: ubuntu-latest
needs:
- build-server-native
- build-native
- build-native-linux
env:
DISTRIBUTION: web
DATABASE_URL: postgresql://affine:affine@localhost:5432/affine
@@ -1057,12 +1139,15 @@ jobs:
ports:
- 9308:9308
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
extra-flags: workspaces focus @affine/monorepo @affine-test/affine-cloud @affine-test/affine-desktop-cloud @affine/web @affine/server @affine/electron @affine/electron-renderer @affine/nbstore @toeverything/infra
playwright-install: true
playwright-platform: 'chromium'
electron-install: ${{ matrix.tests.shard == 'desktop' && 'true' || 'false' }}
hard-link-nm: false
- name: Download server-native.node
@@ -1099,7 +1184,9 @@ jobs:
runs-on: ${{ matrix.spec.os }}
needs:
- build-electron-renderer
- build-native
- build-native-linux
- build-native-macos
- build-native-windows
strategy:
fail-fast: false
matrix:
@@ -1133,13 +1220,14 @@ jobs:
test: true,
}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup Node.js
uses: ./.github/actions/setup-node
timeout-minutes: 10
with:
extra-flags: workspaces focus @affine/electron @affine/monorepo @affine-test/affine-desktop @affine/nbstore @toeverything/infra
playwright-install: true
playwright-install: ${{ matrix.spec.test && 'true' || 'false' }}
playwright-platform: 'chromium'
hard-link-nm: false
enableScripts: false
@@ -1147,7 +1235,7 @@ jobs:
id: filename
shell: bash
run: |
export PLATFORM_ARCH_ABI=$(node -e "console.log(require('@napi-rs/cli').parseTriple('${{ matrix.spec.target }}').platformArchABI)")
PLATFORM_ARCH_ABI="$(node -e "console.log(require('@napi-rs/cli').parseTriple('${{ matrix.spec.target }}').platformArchABI)")"
echo "filename=affine.$PLATFORM_ARCH_ABI.node" >> "$GITHUB_OUTPUT"
- name: Download ${{ steps.filename.outputs.filename }}
@@ -1182,84 +1270,6 @@ jobs:
if: ${{ matrix.spec.test && matrix.spec.os != 'ubuntu-latest' }}
run: yarn affine @affine-test/affine-desktop e2e
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results-e2e-${{ matrix.spec.os }}-${{ matrix.spec.arch }}
path: ./test-results
if-no-files-found: ignore
desktop-bundle-check:
name: Desktop bundle check (${{ matrix.spec.os }}, ${{ matrix.spec.platform }}, ${{ matrix.spec.arch }}, ${{ matrix.spec.target }}, ${{ matrix.spec.test }})
runs-on: ${{ matrix.spec.os }}
needs:
- build-electron-renderer
- build-native
strategy:
fail-fast: false
matrix:
spec:
- {
os: macos-latest,
platform: macos,
arch: x64,
target: x86_64-apple-darwin,
test: false,
}
- {
os: macos-latest,
platform: macos,
arch: arm64,
target: aarch64-apple-darwin,
test: true,
}
- {
os: ubuntu-latest,
platform: linux,
arch: x64,
target: x86_64-unknown-linux-gnu,
test: true,
}
- {
os: windows-latest,
platform: windows,
arch: x64,
target: x86_64-pc-windows-msvc,
test: true,
}
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: ./.github/actions/setup-node
timeout-minutes: 10
with:
extra-flags: workspaces focus @affine/electron @affine/monorepo @affine-test/affine-desktop @affine/nbstore @toeverything/infra
playwright-install: true
hard-link-nm: false
enableScripts: false
- name: Setup filename
id: filename
shell: bash
run: |
export PLATFORM_ARCH_ABI=$(node -e "console.log(require('@napi-rs/cli').parseTriple('${{ matrix.spec.target }}').platformArchABI)")
echo "filename=affine.$PLATFORM_ARCH_ABI.node" >> "$GITHUB_OUTPUT"
- name: Download ${{ steps.filename.outputs.filename }}
uses: actions/download-artifact@v4
with:
name: ${{ steps.filename.outputs.filename }}
path: ./packages/frontend/native
- name: Download web artifact
uses: ./.github/actions/download-web
with:
path: packages/frontend/apps/electron/resources/web-static
- name: Build Desktop Layers
run: yarn affine @affine/electron build
- name: Make bundle (macOS)
if: ${{ matrix.spec.target == 'aarch64-apple-darwin' }}
env:
@@ -1299,6 +1309,14 @@ jobs:
run: |
yarn affine @affine/electron node ./scripts/macos-arm64-output-check.ts
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results-e2e-${{ matrix.spec.os }}-${{ matrix.spec.arch }}
path: ./test-results
if-no-files-found: ignore
test-done:
needs:
- analyze
@@ -1312,8 +1330,9 @@ jobs:
- e2e-blocksuite-cross-browser-test
- e2e-mobile-test
- unit-test
- build-native
- build-windows-native
- build-native-linux
- build-native-macos
- build-native-windows
- build-server-native
- build-electron-renderer
- native-unit-test
@@ -1323,10 +1342,11 @@ jobs:
- server-test
- server-e2e-test
- rust-test
- rust-test-filter
- copilot-test-filter
- copilot-api-test
- copilot-e2e-test
- desktop-test
- desktop-bundle-check
- cloud-e2e-test
if: always()
runs-on: ubuntu-latest

View File

@@ -10,7 +10,7 @@ jobs:
env:
CARGO_PROFILE_RELEASE_DEBUG: '1'
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
@@ -64,7 +64,7 @@ jobs:
ports:
- 9308:9308
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup Node.js
uses: ./.github/actions/setup-node
@@ -134,7 +134,7 @@ jobs:
ports:
- 9308:9308
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup Node.js
uses: ./.github/actions/setup-node
@@ -167,7 +167,7 @@ jobs:
runs-on: ubuntu-latest
name: Post test result message
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup Node.js

View File

@@ -16,8 +16,9 @@ jobs:
check-pull-request-title:
name: Check pull request title
runs-on: ubuntu-latest
if: ${{ github.event.action != 'edited' || github.event.changes.title != null }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v4
with:

View File

@@ -35,7 +35,7 @@ jobs:
- build-images
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Deploy to ${{ inputs.build-type }}
uses: ./.github/actions/deploy
with:

View File

@@ -69,7 +69,7 @@ jobs:
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_RELEASE: ${{ inputs.app_version }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup Version
uses: ./.github/actions/setup-version
@@ -174,18 +174,18 @@ jobs:
run: |
mkdir -p builds
mv packages/frontend/apps/electron/out/*/make/zip/linux/${{ inputs.arch }}/*.zip ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ inputs.arch }}.zip
mv packages/frontend/apps/electron/out/*/make/*.AppImage ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ inputs.arch }}.appimage
mv packages/frontend/apps/electron/out/*/make/AppImage/${{ inputs.arch }}/*.AppImage ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ inputs.arch }}.appimage
mv packages/frontend/apps/electron/out/*/make/deb/${{ inputs.arch }}/*.deb ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ inputs.arch }}.deb
mv packages/frontend/apps/electron/out/*/make/flatpak/*/*.flatpak ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ inputs.arch }}.flatpak
- uses: actions/attest-build-provenance@v2
- uses: actions/attest-build-provenance@v4
if: ${{ inputs.platform == 'darwin' }}
with:
subject-path: |
./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-macos-${{ inputs.arch }}.zip
./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-macos-${{ inputs.arch }}.dmg
- uses: actions/attest-build-provenance@v2
- uses: actions/attest-build-provenance@v4
if: ${{ inputs.platform == 'linux' }}
with:
subject-path: |

View File

@@ -48,7 +48,7 @@ jobs:
runs-on: ubuntu-latest
environment: ${{ inputs.build-type }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup Version
uses: ./.github/actions/setup-version
with:
@@ -187,7 +187,7 @@ jobs:
FILES_TO_BE_SIGNED_x64: ${{ steps.get_files_to_be_signed.outputs.FILES_TO_BE_SIGNED_x64 }}
FILES_TO_BE_SIGNED_arm64: ${{ steps.get_files_to_be_signed.outputs.FILES_TO_BE_SIGNED_arm64 }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup Version
uses: ./.github/actions/setup-version
with:
@@ -201,13 +201,44 @@ jobs:
nmHoistingLimits: workspaces
env:
npm_config_arch: ${{ matrix.spec.arch }}
- name: Download and overwrite packaged artifacts
- name: Download packaged artifacts
uses: actions/download-artifact@v4
with:
name: packaged-${{ matrix.spec.platform }}-${{ matrix.spec.arch }}
path: packaged-unsigned
- name: unzip packaged artifacts
run: Expand-Archive -Path packaged-unsigned/archive.zip -DestinationPath packages/frontend/apps/electron/out
- name: Download signed packaged file diff
uses: actions/download-artifact@v4
with:
name: signed-packaged-${{ matrix.spec.platform }}-${{ matrix.spec.arch }}
path: .
- name: unzip file
run: Expand-Archive -Path signed.zip -DestinationPath packages/frontend/apps/electron/out
path: signed-packaged-diff
- name: Apply signed packaged file diff
shell: pwsh
run: |
$DiffRoot = 'signed-packaged-diff/files'
$TargetRoot = 'packages/frontend/apps/electron/out'
if (!(Test-Path -LiteralPath $DiffRoot)) {
throw "Signed diff directory not found: $DiffRoot"
}
Copy-Item -Path (Join-Path $DiffRoot '*') -Destination $TargetRoot -Recurse -Force
$ManifestPath = 'signed-packaged-diff/manifest.json'
if (Test-Path -LiteralPath $ManifestPath) {
$ManifestEntries = @(Get-Content -LiteralPath $ManifestPath | ConvertFrom-Json)
foreach ($Entry in $ManifestEntries) {
$TargetPath = Join-Path $TargetRoot $Entry.path
if (!(Test-Path -LiteralPath $TargetPath -PathType Leaf)) {
throw "Applied signed file not found: $($Entry.path)"
}
$TargetHash = (Get-FileHash -Algorithm SHA256 -LiteralPath $TargetPath).Hash
if ($TargetHash -ne $Entry.sha256) {
throw "Signed file hash mismatch: $($Entry.path)"
}
}
}
- name: Make squirrel.windows installer
run: yarn affine @affine/electron make-squirrel --platform=${{ matrix.spec.platform }} --arch=${{ matrix.spec.arch }}
@@ -267,13 +298,44 @@ jobs:
arch: arm64
runs-on: ${{ matrix.spec.runner }}
steps:
- name: Download and overwrite installer artifacts
- name: Download installer artifacts
uses: actions/download-artifact@v4
with:
name: installer-${{ matrix.spec.platform }}-${{ matrix.spec.arch }}
path: installer-unsigned
- name: unzip installer artifacts
run: Expand-Archive -Path installer-unsigned/archive.zip -DestinationPath packages/frontend/apps/electron/out/${{ env.BUILD_TYPE }}/make
- name: Download signed installer file diff
uses: actions/download-artifact@v4
with:
name: signed-installer-${{ matrix.spec.platform }}-${{ matrix.spec.arch }}
path: .
- name: unzip file
run: Expand-Archive -Path signed.zip -DestinationPath packages/frontend/apps/electron/out/${{ env.BUILD_TYPE }}/make
path: signed-installer-diff
- name: Apply signed installer file diff
shell: pwsh
run: |
$DiffRoot = 'signed-installer-diff/files'
$TargetRoot = 'packages/frontend/apps/electron/out/${{ env.BUILD_TYPE }}/make'
if (!(Test-Path -LiteralPath $DiffRoot)) {
throw "Signed diff directory not found: $DiffRoot"
}
Copy-Item -Path (Join-Path $DiffRoot '*') -Destination $TargetRoot -Recurse -Force
$ManifestPath = 'signed-installer-diff/manifest.json'
if (Test-Path -LiteralPath $ManifestPath) {
$ManifestEntries = @(Get-Content -LiteralPath $ManifestPath | ConvertFrom-Json)
foreach ($Entry in $ManifestEntries) {
$TargetPath = Join-Path $TargetRoot $Entry.path
if (!(Test-Path -LiteralPath $TargetPath -PathType Leaf)) {
throw "Applied signed file not found: $($Entry.path)"
}
$TargetHash = (Get-FileHash -Algorithm SHA256 -LiteralPath $TargetPath).Hash
if ($TargetHash -ne $Entry.sha256) {
throw "Signed file hash mismatch: $($Entry.path)"
}
}
}
- name: Save artifacts
run: |
@@ -282,7 +344,7 @@ jobs:
mv packages/frontend/apps/electron/out/*/make/squirrel.windows/${{ matrix.spec.arch }}/*.exe ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-windows-${{ matrix.spec.arch }}.exe
mv packages/frontend/apps/electron/out/*/make/nsis.windows/${{ matrix.spec.arch }}/*.exe ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-windows-${{ matrix.spec.arch }}.nsis.exe
- uses: actions/attest-build-provenance@v2
- uses: actions/attest-build-provenance@v4
with:
subject-path: |
./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-windows-${{ matrix.spec.arch }}.zip
@@ -307,7 +369,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Download Artifacts (macos-x64)
uses: actions/download-artifact@v4
with:

View File

@@ -26,7 +26,7 @@ jobs:
runs-on: ubuntu-latest
environment: ${{ inputs.build-type }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup Version
uses: ./.github/actions/setup-version
with:
@@ -54,7 +54,7 @@ jobs:
build-android-web:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup Version
uses: ./.github/actions/setup-version
with:
@@ -83,7 +83,7 @@ jobs:
needs:
- build-ios-web
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup Version
uses: ./.github/actions/setup-version
with:
@@ -128,9 +128,9 @@ jobs:
- name: Testflight
working-directory: packages/frontend/apps/ios/App
run: |
echo -n "${{ env.BUILD_PROVISION_PROFILE }}" | base64 --decode -o $PP_PATH
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles
printf '%s' "$BUILD_PROVISION_PROFILE" | base64 --decode -o "$PP_PATH"
mkdir -p "$HOME/Library/MobileDevice/Provisioning Profiles"
cp "$PP_PATH" "$HOME/Library/MobileDevice/Provisioning Profiles"
fastlane beta
env:
BUILD_TARGET: distribution
@@ -147,7 +147,7 @@ jobs:
needs:
- build-android-web
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup Version
uses: ./.github/actions/setup-version
with:
@@ -160,7 +160,9 @@ jobs:
- name: Load Google Service file
env:
DATA: ${{ secrets.FIREBASE_ANDROID_GOOGLE_SERVICE_JSON }}
run: echo $DATA | base64 -di > packages/frontend/apps/android/App/app/google-services.json
run: |
set -euo pipefail
printf '%s' "$DATA" | base64 -di > packages/frontend/apps/android/App/app/google-services.json
- name: Setup Node.js
uses: ./.github/actions/setup-node
timeout-minutes: 10

View File

@@ -55,7 +55,7 @@ jobs:
GIT_SHORT_HASH: ${{ steps.prepare.outputs.GIT_SHORT_HASH }}
BUILD_TYPE: ${{ steps.prepare.outputs.BUILD_TYPE }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Prepare Release
id: prepare
uses: ./.github/actions/prepare-release
@@ -72,7 +72,7 @@ jobs:
steps:
- name: Decide whether to release
id: decide
uses: actions/github-script@v7
uses: actions/github-script@v8
with:
script: |
const buildType = '${{ needs.prepare.outputs.BUILD_TYPE }}'
@@ -148,7 +148,7 @@ jobs:
name: Wait for approval
with:
secret: ${{ secrets.GITHUB_TOKEN }}
approvers: darkskygit,pengx17,L-Sun,EYHN
approvers: darkskygit
minimum-approvals: 1
fail-on-denial: true
issue-title: Please confirm to release docker image

View File

@@ -30,13 +30,43 @@ jobs:
run: |
cd ${{ env.ARCHIVE_DIR }}/out
signtool sign /tr http://timestamp.globalsign.com/tsa/r6advanced1 /td sha256 /fd sha256 /a ${{ inputs.files }}
- name: zip file
shell: cmd
- name: collect signed file diff
shell: powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File {0}
run: |
cd ${{ env.ARCHIVE_DIR }}
7za a signed.zip .\out\*
$OutDir = Join-Path '${{ env.ARCHIVE_DIR }}' 'out'
$DiffDir = Join-Path '${{ env.ARCHIVE_DIR }}' 'signed-diff'
$FilesDir = Join-Path $DiffDir 'files'
New-Item -ItemType Directory -Path $FilesDir -Force | Out-Null
$SignedFiles = [regex]::Matches('${{ inputs.files }}', '"([^"]+)"') | ForEach-Object { $_.Groups[1].Value }
if ($SignedFiles.Count -eq 0) {
throw 'No files to sign were provided.'
}
$Manifest = @()
foreach ($RelativePath in $SignedFiles) {
$SourcePath = Join-Path $OutDir $RelativePath
if (!(Test-Path -LiteralPath $SourcePath -PathType Leaf)) {
throw "Signed file not found: $RelativePath"
}
$TargetPath = Join-Path $FilesDir $RelativePath
$TargetDir = Split-Path -Parent $TargetPath
if ($TargetDir) {
New-Item -ItemType Directory -Path $TargetDir -Force | Out-Null
}
Copy-Item -LiteralPath $SourcePath -Destination $TargetPath -Force
$Manifest += [PSCustomObject]@{
path = $RelativePath
sha256 = (Get-FileHash -Algorithm SHA256 -LiteralPath $TargetPath).Hash
}
}
$Manifest | ConvertTo-Json -Depth 4 | Out-File -FilePath (Join-Path $DiffDir 'manifest.json') -Encoding utf8
Write-Host "Collected $($SignedFiles.Count) signed files."
- name: upload
uses: actions/upload-artifact@v4
with:
name: signed-${{ inputs.artifact-name }}
path: ${{ env.ARCHIVE_DIR }}/signed.zip
path: ${{ env.ARCHIVE_DIR }}/signed-diff

View File

@@ -5,6 +5,10 @@
"correctness": "error",
"perf": "error"
},
"env": {
"builtin": true,
"es2026": true
},
"ignorePatterns": [
"**/node_modules",
".yarn",
@@ -44,6 +48,34 @@
"**/test-blocks.json"
],
"rules": {
"no-empty-static-block": "error",
"no-misleading-character-class": "error",
"no-new-native-nonconstructor": "error",
"no-unused-private-class-members": "error",
"no-useless-backreference": "error",
"react/display-name": "error",
"react/rules-of-hooks": "error",
"react/exhaustive-deps": "warn",
"@typescript-eslint/prefer-for-of": "error",
"@typescript-eslint/no-unsafe-function-type": "error",
"@typescript-eslint/no-wrapper-object-types": "error",
"no-restricted-imports": [
"error",
{
"patterns": [
{
"group": ["**/dist"],
"message": "Don't import from dist",
"allowTypeImports": false
},
{
"group": ["**/src"],
"message": "Don't import from src",
"allowTypeImports": false
}
]
}
],
"no-await-in-loop": "allow",
"no-redeclare": "allow",
"promise/no-callback-in-promise": "allow",
@@ -70,6 +102,14 @@
"no-func-assign": "error",
"no-global-assign": "error",
"no-unused-vars": "error",
"no-unused-expressions": [
"error",
{
"allowShortCircuit": true,
"allowTernary": true,
"allowTaggedTemplates": true
}
],
"no-ex-assign": "error",
"no-loss-of-precision": "error",
"no-fallthrough": "error",
@@ -126,6 +166,7 @@
"react/no-render-return-value": "error",
"react/jsx-no-target-blank": "error",
"react/jsx-no-comment-textnodes": "error",
"react/no-array-index-key": "off",
"typescript/consistent-type-imports": "error",
"typescript/no-non-null-assertion": "error",
"typescript/triple-slash-reference": "error",
@@ -241,6 +282,42 @@
"typescript/consistent-type-imports": "off",
"import/no-cycle": "off"
}
},
{
"files": [
"packages/**/*.{ts,tsx}",
"tools/**/*.{ts,tsx}",
"blocksuite/**/*.{ts,tsx}"
],
"rules": {
"react/exhaustive-deps": [
"warn",
{
"additionalHooks": "(useAsyncCallback|useCatchEventCallback|useDraggable|useDropTarget|useRefEffect)"
}
]
}
},
{
"files": [
"**/__tests__/**/*",
"**/*.stories.tsx",
"**/*.spec.ts",
"**/tests/**/*",
"scripts/**/*",
"**/benchmark/**/*",
"**/__debug__/**/*",
"**/e2e/**/*"
],
"rules": {
"no-restricted-imports": "off"
}
},
{
"files": ["**/*.{ts,js,mjs}"],
"rules": {
"react/rules-of-hooks": "off"
}
}
]
}

View File

@@ -17,7 +17,7 @@
"explorer.fileNesting.enabled": true,
"explorer.fileNesting.patterns": {
"*.js": "${capture}.js.map, ${capture}.min.js, ${capture}.d.ts, ${capture}.d.ts.map",
"package.json": ".browserslist*, .circleci*, .codecov, .commitlint*, .cz-config.js, .czrc, .dlint.json, .dprint.json, .editorconfig, .eslint*, eslint.*, .firebase*, .flowconfig, .github*, .gitlab*, .gitpod*, .huskyrc*, .jslint*, .lighthouserc.*, .lintstagedrc*, .markdownlint*, .mocha*, .node-version, .nodemon*, .npm*, .nvmrc, .pm2*, .pnp.*, .pnpm*, .prettier*, .releaserc*, .sentry*, .stackblitz*, .styleci*, .stylelint*, .tazerc*, .textlint*, .tool-versions, .travis*, .versionrc*, .vscode*, .watchman*, .xo-config*, .yamllint*, .yarnrc*, Procfile, api-extractor.json, apollo.config.*, appveyor*, ava.config.*, azure-pipelines*, bower.json, build.config.*, commitlint*, dangerfile*, dlint.json, dprint.json, firebase.json, grunt*, gulp*, histoire.config.*, jasmine.*, jenkins*, jest.config.*, jsconfig.*, karma*, lerna*, lighthouserc.*, lint-staged*, nest-cli.*, netlify*, nodemon*, nx.*, package-lock.json, package.nls*.json, phpcs.xml, playwright.config.*, pm2.*, pnpm*, prettier*, pullapprove*, puppeteer.config.*, pyrightconfig.json, release-tasks.sh, renovate*, rollup.config.*, stylelint*, tsconfig.*, tsdoc.*, tslint*, tsup.config.*, turbo*, typedoc*, unlighthouse*, vercel*, vetur.config.*, vitest.*, webpack*, workspace.json, xo.config.*, yarn*, babel.*, .babelrc, project.json, oxlint.json, nyc.config.*",
"package.json": ".browserslist*, .circleci*, .codecov, .commitlint*, .cz-config.js, .czrc, .dlint.json, .dprint.json, .editorconfig, .eslint*, eslint.*, .firebase*, .flowconfig, .github*, .gitlab*, .gitpod*, .huskyrc*, .jslint*, .lighthouserc.*, .lintstagedrc*, .markdownlint*, .mocha*, .node-version, .nodemon*, .npm*, .nvmrc, .pm2*, .pnp.*, .pnpm*, .prettier*, .releaserc*, .sentry*, .stackblitz*, .styleci*, .stylelint*, .tazerc*, .textlint*, .tool-versions, .travis*, .versionrc*, .vscode*, .watchman*, .xo-config*, .yamllint*, .yarnrc*, Procfile, api-extractor.json, apollo.config.*, appveyor*, ava.config.*, azure-pipelines*, bower.json, build.config.*, commitlint*, dangerfile*, dlint.json, dprint.json, firebase.json, grunt*, gulp*, histoire.config.*, jasmine.*, jenkins*, jest.config.*, jsconfig.*, karma*, lerna*, lighthouserc.*, lint-staged*, nest-cli.*, netlify*, nodemon*, nx.*, package-lock.json, package.nls*.json, phpcs.xml, playwright.config.*, pm2.*, pnpm*, prettier*, pullapprove*, puppeteer.config.*, pyrightconfig.json, release-tasks.sh, renovate*, rollup.config.*, stylelint*, tsconfig.*, tsdoc.*, tslint*, tsup.config.*, turbo*, typedoc*, unlighthouse*, vercel*, vetur.config.*, vitest.*, webpack*, workspace.json, xo.config.*, yarn*, babel.*, .babelrc, project.json, .oxlintrc.json, oxlint.json, nyc.config.*",
"Cargo.toml": "Cargo.lock, rust-toolchain*, rustfmt.toml, .taplo.toml",
"README.md": "LICENSE*, CHANGELOG.md, CODE_OF_CONDUCT.md, CONTRIBUTING.md, SECURITY.md, README.*",
".gitignore": ".gitattributes, .dockerignore, .eslintignore, .prettierignore, .stylelintignore, .tslintignore, .yarnignore"

526
Cargo.lock generated
View File

@@ -111,10 +111,12 @@ dependencies = [
"base64-simd",
"chrono",
"homedir",
"lru",
"objc2",
"objc2-foundation",
"sqlx",
"thiserror 2.0.17",
"tokio",
"uniffi",
]
@@ -179,6 +181,7 @@ dependencies = [
"chrono",
"file-format",
"infer",
"llm_adapter",
"mimalloc",
"mp4parse",
"napi",
@@ -186,6 +189,8 @@ dependencies = [
"napi-derive",
"rand 0.9.2",
"rayon",
"serde",
"serde_json",
"sha3",
"tiktoken-rs",
"tokio",
@@ -243,7 +248,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed7572b7ba83a31e20d1b48970ee402d2e3e0537dcfe0a3ff4d6eb7508617d43"
dependencies = [
"alsa-sys",
"bitflags 2.10.0",
"bitflags 2.11.0",
"cfg-if",
"libc",
]
@@ -456,6 +461,12 @@ dependencies = [
"num-traits",
]
[[package]]
name = "atomic-waker"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "auto_enums"
version = "0.8.7"
@@ -474,6 +485,28 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "aws-lc-rs"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9a7b350e3bb1767102698302bc37256cbd48422809984b98d292c40e2579aa9"
dependencies = [
"aws-lc-sys",
"zeroize",
]
[[package]]
name = "aws-lc-sys"
version = "0.37.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b092fe214090261288111db7a2b2c2118e5a7f30dc2569f1732c4069a6840549"
dependencies = [
"cc",
"cmake",
"dunce",
"fs_extra",
]
[[package]]
name = "base64"
version = "0.22.1"
@@ -531,7 +564,7 @@ version = "0.72.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
dependencies = [
"bitflags 2.10.0",
"bitflags 2.11.0",
"cexpr",
"clang-sys",
"itertools 0.13.0",
@@ -581,9 +614,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.10.0"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
dependencies = [
"serde_core",
]
@@ -902,6 +935,15 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
[[package]]
name = "cmake"
version = "0.1.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d"
dependencies = [
"cc",
]
[[package]]
name = "colorchoice"
version = "1.0.4"
@@ -981,7 +1023,7 @@ version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1"
dependencies = [
"bitflags 2.10.0",
"bitflags 2.11.0",
"core-foundation",
"core-graphics-types",
"foreign-types",
@@ -994,7 +1036,7 @@ version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97"
dependencies = [
"bitflags 2.10.0",
"bitflags 2.11.0",
"core-foundation",
"core-graphics-types",
"foreign-types",
@@ -1007,7 +1049,7 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb"
dependencies = [
"bitflags 2.10.0",
"bitflags 2.11.0",
"core-foundation",
"libc",
]
@@ -1377,7 +1419,7 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec"
dependencies = [
"bitflags 2.10.0",
"bitflags 2.11.0",
"block2",
"libc",
"objc2",
@@ -1440,6 +1482,12 @@ version = "0.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5"
[[package]]
name = "dunce"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
[[package]]
name = "ecb"
version = "0.1.2"
@@ -1662,6 +1710,12 @@ dependencies = [
"autocfg",
]
[[package]]
name = "fs_extra"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
[[package]]
name = "futf"
version = "0.1.5"
@@ -1826,9 +1880,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"r-efi",
"wasip2",
"wasm-bindgen",
]
[[package]]
@@ -1878,7 +1934,7 @@ version = "1.41.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0c43e7c3212bd992c11b6b9796563388170950521ae8487f5cdf6f6e792f1c8"
dependencies = [
"bitflags 2.10.0",
"bitflags 2.11.0",
"proc-macro2",
"quote",
"syn 1.0.109",
@@ -2001,6 +2057,105 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "http"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a"
dependencies = [
"bytes",
"itoa",
]
[[package]]
name = "http-body"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
dependencies = [
"bytes",
"http",
]
[[package]]
name = "http-body-util"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
dependencies = [
"bytes",
"futures-core",
"http",
"http-body",
"pin-project-lite",
]
[[package]]
name = "httparse"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
[[package]]
name = "hyper"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11"
dependencies = [
"atomic-waker",
"bytes",
"futures-channel",
"futures-core",
"http",
"http-body",
"httparse",
"itoa",
"pin-project-lite",
"pin-utils",
"smallvec",
"tokio",
"want",
]
[[package]]
name = "hyper-rustls"
version = "0.27.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
dependencies = [
"http",
"hyper",
"hyper-util",
"rustls",
"rustls-pki-types",
"tokio",
"tokio-rustls",
"tower-service",
]
[[package]]
name = "hyper-util"
version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
dependencies = [
"base64",
"bytes",
"futures-channel",
"futures-util",
"http",
"http-body",
"hyper",
"ipnet",
"libc",
"percent-encoding",
"pin-project-lite",
"socket2",
"tokio",
"tower-service",
"tracing",
]
[[package]]
name = "iana-time-zone"
version = "0.1.64"
@@ -2251,6 +2406,22 @@ dependencies = [
"leaky-cow",
]
[[package]]
name = "ipnet"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
[[package]]
name = "iri-string"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a"
dependencies = [
"memchr",
"serde",
]
[[package]]
name = "is-terminal"
version = "0.4.17"
@@ -2374,9 +2545,9 @@ dependencies = [
[[package]]
name = "keccak"
version = "0.1.5"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654"
checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653"
dependencies = [
"cpufeatures",
]
@@ -2488,7 +2659,7 @@ version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616"
dependencies = [
"bitflags 2.10.0",
"bitflags 2.11.0",
"libc",
"redox_syscall 0.7.0",
]
@@ -2516,6 +2687,19 @@ version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
[[package]]
name = "llm_adapter"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8dd9a548766bccf8b636695e8d514edee672d180e96a16ab932c971783b4e353"
dependencies = [
"base64",
"reqwest",
"serde",
"serde_json",
"thiserror 2.0.17",
]
[[package]]
name = "lock_api"
version = "0.4.14"
@@ -2553,7 +2737,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59fa2559e99ba0f26a12458aabc754432c805bbb8cba516c427825a997af1fb7"
dependencies = [
"aes",
"bitflags 2.10.0",
"bitflags 2.11.0",
"cbc",
"ecb",
"encoding_rs",
@@ -2572,6 +2756,21 @@ dependencies = [
"weezl",
]
[[package]]
name = "lru"
version = "0.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593"
dependencies = [
"hashbrown 0.16.1",
]
[[package]]
name = "lru-slab"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
[[package]]
name = "mac"
version = "0.1.1"
@@ -2730,7 +2929,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "000f205daae6646003fdc38517be6232af2b150bad4b67bdaf4c5aadb119d738"
dependencies = [
"anyhow",
"bitflags 2.10.0",
"bitflags 2.11.0",
"chrono",
"ctor",
"futures",
@@ -2790,7 +2989,7 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7"
dependencies = [
"bitflags 2.10.0",
"bitflags 2.11.0",
"jni-sys",
"log",
"ndk-sys",
@@ -2825,7 +3024,7 @@ version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
dependencies = [
"bitflags 2.10.0",
"bitflags 2.11.0",
"cfg-if",
"cfg_aliases",
"libc",
@@ -2989,7 +3188,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
dependencies = [
"bitflags 2.10.0",
"bitflags 2.11.0",
"dispatch2",
"objc2",
]
@@ -3006,7 +3205,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
dependencies = [
"bitflags 2.10.0",
"bitflags 2.11.0",
"block2",
"libc",
"objc2",
@@ -3063,6 +3262,12 @@ version = "11.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
[[package]]
name = "openssl-probe"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
[[package]]
name = "ordered-float"
version = "5.1.0"
@@ -3458,7 +3663,7 @@ checksum = "bee689443a2bd0a16ab0348b52ee43e3b2d1b1f931c8aa5c9f8de4c86fbe8c40"
dependencies = [
"bit-set 0.8.0",
"bit-vec 0.8.0",
"bitflags 2.10.0",
"bitflags 2.11.0",
"num-traits",
"rand 0.9.2",
"rand_chacha 0.9.0",
@@ -3486,7 +3691,7 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e8bbe1a966bd2f362681a44f6edce3c2310ac21e4d5067a6e7ec396297a6ea0"
dependencies = [
"bitflags 2.10.0",
"bitflags 2.11.0",
"getopts",
"memchr",
"pulldown-cmark-escape",
@@ -3505,6 +3710,62 @@ version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quinn"
version = "0.11.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20"
dependencies = [
"bytes",
"cfg_aliases",
"pin-project-lite",
"quinn-proto",
"quinn-udp",
"rustc-hash 2.1.1",
"rustls",
"socket2",
"thiserror 2.0.17",
"tokio",
"tracing",
"web-time",
]
[[package]]
name = "quinn-proto"
version = "0.11.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31"
dependencies = [
"aws-lc-rs",
"bytes",
"getrandom 0.3.4",
"lru-slab",
"rand 0.9.2",
"ring",
"rustc-hash 2.1.1",
"rustls",
"rustls-pki-types",
"slab",
"thiserror 2.0.17",
"tinyvec",
"tracing",
"web-time",
]
[[package]]
name = "quinn-udp"
version = "0.5.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd"
dependencies = [
"cfg_aliases",
"libc",
"once_cell",
"socket2",
"tracing",
"windows-sys 0.60.2",
]
[[package]]
name = "quote"
version = "1.0.43"
@@ -3652,7 +3913,7 @@ version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
"bitflags 2.10.0",
"bitflags 2.11.0",
]
[[package]]
@@ -3661,7 +3922,7 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27"
dependencies = [
"bitflags 2.10.0",
"bitflags 2.11.0",
]
[[package]]
@@ -3693,6 +3954,45 @@ version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
[[package]]
name = "reqwest"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801"
dependencies = [
"base64",
"bytes",
"futures-channel",
"futures-core",
"futures-util",
"http",
"http-body",
"http-body-util",
"hyper",
"hyper-rustls",
"hyper-util",
"js-sys",
"log",
"percent-encoding",
"pin-project-lite",
"quinn",
"rustls",
"rustls-pki-types",
"rustls-platform-verifier",
"serde",
"serde_json",
"sync_wrapper",
"tokio",
"tokio-rustls",
"tower",
"tower-http",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]]
name = "ring"
version = "0.17.14"
@@ -3820,7 +4120,7 @@ version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
dependencies = [
"bitflags 2.10.0",
"bitflags 2.11.0",
"errno",
"libc",
"linux-raw-sys",
@@ -3833,6 +4133,7 @@ version = "0.23.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b"
dependencies = [
"aws-lc-rs",
"once_cell",
"ring",
"rustls-pki-types",
@@ -3841,21 +4142,62 @@ dependencies = [
"zeroize",
]
[[package]]
name = "rustls-native-certs"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63"
dependencies = [
"openssl-probe",
"rustls-pki-types",
"schannel",
"security-framework",
]
[[package]]
name = "rustls-pki-types"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282"
dependencies = [
"web-time",
"zeroize",
]
[[package]]
name = "rustls-platform-verifier"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784"
dependencies = [
"core-foundation",
"core-foundation-sys",
"jni",
"log",
"once_cell",
"rustls",
"rustls-native-certs",
"rustls-platform-verifier-android",
"rustls-webpki",
"security-framework",
"security-framework-sys",
"webpki-root-certs",
"windows-sys 0.61.2",
]
[[package]]
name = "rustls-platform-verifier-android"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
[[package]]
name = "rustls-webpki"
version = "0.103.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52"
dependencies = [
"aws-lc-rs",
"ring",
"rustls-pki-types",
"untrusted",
@@ -3894,6 +4236,15 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "schannel"
version = "0.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1"
dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "scoped-tls"
version = "1.0.1"
@@ -3942,6 +4293,29 @@ dependencies = [
"syn 2.0.114",
]
[[package]]
name = "security-framework"
version = "3.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
dependencies = [
"bitflags 2.11.0",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "semver"
version = "1.0.27"
@@ -4258,7 +4632,7 @@ checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526"
dependencies = [
"atoi",
"base64",
"bitflags 2.10.0",
"bitflags 2.11.0",
"byteorder",
"bytes",
"chrono",
@@ -4301,7 +4675,7 @@ checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46"
dependencies = [
"atoi",
"base64",
"bitflags 2.10.0",
"bitflags 2.11.0",
"byteorder",
"chrono",
"crc",
@@ -4667,6 +5041,15 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "sync_wrapper"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
dependencies = [
"futures-core",
]
[[package]]
name = "synstructure"
version = "0.13.2"
@@ -4858,6 +5241,16 @@ dependencies = [
"syn 2.0.114",
]
[[package]]
name = "tokio-rustls"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
dependencies = [
"rustls",
"tokio",
]
[[package]]
name = "tokio-stream"
version = "0.1.18"
@@ -4901,13 +5294,58 @@ dependencies = [
[[package]]
name = "toml_parser"
version = "1.0.6+spec-1.1.0"
version = "1.0.9+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44"
checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4"
dependencies = [
"winnow",
]
[[package]]
name = "tower"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4"
dependencies = [
"futures-core",
"futures-util",
"pin-project-lite",
"sync_wrapper",
"tokio",
"tower-layer",
"tower-service",
]
[[package]]
name = "tower-http"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
dependencies = [
"bitflags 2.11.0",
"bytes",
"futures-util",
"http",
"http-body",
"iri-string",
"pin-project-lite",
"tower",
"tower-layer",
"tower-service",
]
[[package]]
name = "tower-layer"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
[[package]]
name = "tower-service"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
[[package]]
name = "tracing"
version = "0.1.44"
@@ -5110,6 +5548,12 @@ dependencies = [
"tree-sitter-language",
]
[[package]]
name = "try-lock"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "type1-encoding-parser"
version = "0.1.0"
@@ -5430,6 +5874,15 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "want"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
dependencies = [
"try-lock",
]
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
@@ -5519,6 +5972,25 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "web-time"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "webpki-root-certs"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca"
dependencies = [
"rustls-pki-types",
]
[[package]]
name = "webpki-roots"
version = "0.26.11"

View File

@@ -44,8 +44,10 @@ resolver = "3"
lasso = { version = "0.7", features = ["multi-threaded"] }
lib0 = { version = "0.16", features = ["lib0-serde"] }
libc = "0.2"
llm_adapter = "0.1.1"
log = "0.4"
loom = { version = "0.7", features = ["checkpoint"] }
lru = "0.16"
memory-indexer = "0.3.0"
mimalloc = "0.1"
mp4parse = "0.17"

View File

@@ -90,10 +90,10 @@ Thanks for checking us out, we appreciate your interest and sincerely hope that
## Contributing
| Bug Reports | Feature Requests | Questions/Discussions | AFFiNE Community |
| --------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | ---------------------------------------------------------- |
| [Create a bug report](https://github.com/toeverything/AFFiNE/issues/new?assignees=&labels=bug%2Cproduct-review&template=BUG-REPORT.yml&title=TITLE) | [Submit a feature request](https://github.com/toeverything/AFFiNE/issues/new?assignees=&labels=feat%2Cproduct-review&template=FEATURE-REQUEST.yml&title=TITLE) | [Check GitHub Discussion](https://github.com/toeverything/AFFiNE/discussions) | [Visit the AFFiNE Community](https://community.affine.pro) |
| Something isn't working as expected | An idea for a new feature, or improvements | Discuss and ask questions | A place to ask, learn and engage with others |
| Bug Reports | Feature Requests | Questions/Discussions | AFFiNE Community |
| --------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | ----------------------------------------------------------------- |
| [Create a bug report](https://github.com/toeverything/AFFiNE/issues/new?assignees=&labels=bug%2Cproduct-review&template=BUG-REPORT.yml&title=TITLE) | [Submit a feature request](https://github.com/toeverything/AFFiNE/issues/new?assignees=&labels=feat%2Cproduct-review&template=FEATURE-REQUEST.yml&title=TITLE) | [Check GitHub Discussion](https://github.com/toeverything/AFFiNE/discussions) | [Visit the AFFiNE's Discord](https://affine.pro/redirect/discord) |
| Something isn't working as expected | An idea for a new feature, or improvements | Discuss and ask questions | A place to ask, learn and engage with others |
Calling all developers, testers, tech writers and more! Contributions of all types are more than welcome, you can read more in [docs/types-of-contributions.md](docs/types-of-contributions.md). If you are interested in contributing code, read our [docs/CONTRIBUTING.md](docs/CONTRIBUTING.md) and feel free to check out our GitHub issues to get stuck in to show us what youre made of.
@@ -101,11 +101,9 @@ Calling all developers, testers, tech writers and more! Contributions of all typ
For **bug reports**, **feature requests** and other **suggestions** you can also [create a new issue](https://github.com/toeverything/AFFiNE/issues/new/choose) and choose the most appropriate template for your feedback.
For **translation** and **language support** you can visit our [i18n General Space](https://community.affine.pro/c/i18n-general).
For **translation** and **language support** you can visit our [Discord](https://affine.pro/redirect/discord).
Looking for **other ways to contribute** and wondering where to start? Check out the [AFFiNE Ambassador program](https://community.affine.pro/c/start-here/affine-ambassador), we work closely with passionate community members and provide them with a wide range of support and resources.
If you have questions, you are welcome to contact us. One of the best places to get more info and learn more is in the [AFFiNE Community](https://community.affine.pro) where you can engage with other like-minded individuals.
If you have questions, you are welcome to contact us. One of the best places to get more info and learn more is in the [Discord](https://affine.pro/redirect/discord) where you can engage with other like-minded individuals.
## Templates
@@ -182,20 +180,16 @@ Begin with Docker to deploy your own feature-rich, unrestricted version of AFFiN
[![Run on ClawCloud](https://raw.githubusercontent.com/ClawCloud/Run-Template/refs/heads/main/Run-on-ClawCloud.svg)](https://template.run.claw.cloud/?openapp=system-fastdeploy%3FtemplateName%3Daffine)
## Hiring
Some amazing companies, including AFFiNE, are looking for developers! Are you interested in joining AFFiNE or its partners? Check out our [Discord channel](https://affine.pro/redirect/discord) for some of the latest jobs available.
## Feature Request
For feature requests, please see [community.affine.pro](https://community.affine.pro/c/feature-requests/).
For feature requests, please see [discussions](https://github.com/toeverything/AFFiNE/discussions/categories/ideas).
## Building
### Codespaces
From the GitHub repo main page, click the green "Code" button and select "Create codespace on master". This will open a new Codespace with the (supposedly auto-forked
AFFiNE repo cloned, built, and ready to go.
AFFiNE repo cloned, built, and ready to go).
### Local

View File

@@ -296,7 +296,7 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.26.1",
"version": "0.26.3",
"devDependencies": {
"@vanilla-extract/vite-plugin": "^5.0.0",
"msw": "^2.12.4",

View File

@@ -26,7 +26,6 @@
"@toeverything/theme": "^1.1.23",
"file-type": "^21.0.0",
"lit": "^3.2.0",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2",
"zod": "^3.25.76"
},
@@ -41,5 +40,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.26.1"
"version": "0.26.3"
}

View File

@@ -26,7 +26,6 @@
"@preact/signals-core": "^1.8.0",
"@toeverything/theme": "^1.1.23",
"lit": "^3.2.0",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2",
"yjs": "^13.6.27",
"zod": "^3.25.76"
@@ -45,5 +44,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.26.1"
"version": "0.26.3"
}

View File

@@ -108,7 +108,9 @@ export class BookmarkBlockComponent extends CaptionedBlockComponent<BookmarkBloc
}
open = () => {
window.open(this.link, '_blank');
const link = this.link;
if (!link) return;
window.open(link, '_blank', 'noopener,noreferrer');
};
refreshData = () => {

View File

@@ -30,7 +30,6 @@
"@types/mdast": "^4.0.4",
"emoji-mart": "^5.6.0",
"lit": "^3.2.0",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2",
"zod": "^3.25.76"
},
@@ -45,5 +44,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.26.1"
"version": "0.26.3"
}

View File

@@ -216,9 +216,13 @@ export class CalloutBlockComponent extends CaptionedBlockComponent<CalloutBlockM
override renderBlock() {
const icon = this.model.props.icon$.value;
const backgroundColorName = this.model.props.backgroundColorName$.value;
const normalizedBackgroundName =
backgroundColorName === 'default' || backgroundColorName === ''
? 'grey'
: backgroundColorName;
const backgroundColor = (
cssVarV2.block.callout.background as Record<string, string>
)[backgroundColorName ?? ''];
)[normalizedBackgroundName ?? 'grey'];
const iconContent = getIcon(icon);

View File

@@ -68,14 +68,14 @@ const backgroundColorAction = {
${repeat(colors, color => {
const isDefault = color === 'default';
const value = isDefault
? null
? cssVarV2.block.callout.background.grey
: `var(--affine-text-highlight-${color})`;
const displayName = `${color} Background`;
return html`
<editor-menu-action
data-testid="background-${color}"
@click=${() => updateBackground(color)}
@click=${() => updateBackground(isDefault ? 'grey' : color)}
>
<affine-text-duotone-icon
style=${styleMap({

View File

@@ -31,7 +31,6 @@
"@toeverything/theme": "^1.1.23",
"@types/mdast": "^4.0.4",
"lit": "^3.2.0",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2",
"shiki": "^3.19.0",
"zod": "^3.25.76"
@@ -48,5 +47,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.26.1"
"version": "0.26.3"
}

View File

@@ -27,6 +27,16 @@ export const codeBlockStyles = css`
${scrollbarStyle('.affine-code-block-container rich-text')}
/* In Chromium 121+, non-auto scrollbar-width/color override ::-webkit-scrollbar styles. */
@supports not selector(::-webkit-scrollbar) {
.affine-code-block-container rich-text {
scrollbar-width: thin;
scrollbar-color: ${unsafeCSSVarV2('icon/secondary', '#b1b1b1')}
transparent;
scrollbar-gutter: stable both-edges;
}
}
.affine-code-block-container .inline-editor {
font-family: var(--affine-font-code-family);
font-variant-ligatures: none;

View File

@@ -27,7 +27,6 @@
"@toeverything/theme": "^1.1.23",
"@types/mdast": "^4.0.4",
"lit": "^3.2.0",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2",
"zod": "^3.25.76"
},
@@ -42,5 +41,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.26.1"
"version": "0.26.3"
}

View File

@@ -32,7 +32,6 @@
"@types/mdast": "^4.0.4",
"date-fns": "^4.0.0",
"lit": "^3.2.0",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2",
"yjs": "^13.6.27",
"zod": "^3.25.76"
@@ -48,5 +47,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.26.1"
"version": "0.26.3"
}

View File

@@ -24,7 +24,6 @@
"@toeverything/theme": "^1.1.23",
"@types/mdast": "^4.0.4",
"lit": "^3.2.0",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2",
"zod": "^3.25.76"
},
@@ -39,5 +38,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.26.1"
"version": "0.26.3"
}

View File

@@ -28,7 +28,6 @@
"@preact/signals-core": "^1.8.0",
"@toeverything/theme": "^1.1.23",
"lit": "^3.2.0",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2",
"zod": "^3.25.76"
},
@@ -43,5 +42,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.26.1"
"version": "0.26.3"
}

View File

@@ -30,7 +30,6 @@
"@types/lodash-es": "^4.17.12",
"lit": "^3.2.0",
"lodash-es": "^4.17.23",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2",
"yjs": "^13.6.27",
"zod": "^3.25.76"
@@ -49,5 +48,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.26.1"
"version": "0.26.3"
}

View File

@@ -30,7 +30,6 @@
"@types/lodash-es": "^4.17.12",
"lit": "^3.2.0",
"lodash-es": "^4.17.23",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2",
"yjs": "^13.6.27",
"zod": "^3.25.76"
@@ -49,5 +48,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.26.1"
"version": "0.26.3"
}

View File

@@ -28,7 +28,6 @@
"@toeverything/theme": "^1.1.23",
"@types/mdast": "^4.0.4",
"lit": "^3.2.0",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2",
"yjs": "^13.6.27",
"zod": "^3.25.76"
@@ -44,5 +43,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.26.1"
"version": "0.26.3"
}

View File

@@ -28,7 +28,6 @@
"@toeverything/theme": "^1.1.23",
"file-type": "^21.0.0",
"lit": "^3.2.0",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2",
"zod": "^3.25.76"
},
@@ -44,5 +43,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.26.1"
"version": "0.26.3"
}

View File

@@ -26,6 +26,11 @@ import {
@Peekable()
export class ImageEdgelessBlockComponent extends GfxBlockComponent<ImageBlockModel> {
private static readonly LOD_MIN_IMAGE_BYTES = 1024 * 1024;
private static readonly LOD_MIN_IMAGE_PIXELS = 1920 * 1080;
private static readonly LOD_MAX_ZOOM = 0.4;
private static readonly LOD_THUMBNAIL_MAX_EDGE = 256;
static override styles = css`
affine-edgeless-image {
position: relative;
@@ -63,6 +68,11 @@ export class ImageEdgelessBlockComponent extends GfxBlockComponent<ImageBlockMod
width: 100%;
height: 100%;
}
affine-edgeless-image .resizable-img {
position: relative;
overflow: hidden;
}
`;
resourceController = new ResourceController(
@@ -70,6 +80,12 @@ export class ImageEdgelessBlockComponent extends GfxBlockComponent<ImageBlockMod
'Image'
);
private _lodThumbnailUrl: string | null = null;
private _lodSourceUrl: string | null = null;
private _lodGeneratingSourceUrl: string | null = null;
private _lodGenerationToken = 0;
private _lastShouldUseLod = false;
get blobUrl() {
return this.resourceController.blobUrl$.value;
}
@@ -96,6 +112,134 @@ export class ImageEdgelessBlockComponent extends GfxBlockComponent<ImageBlockMod
});
}
private _isLargeImage() {
const { width = 0, height = 0, size = 0 } = this.model.props;
const pixels = width * height;
return (
size >= ImageEdgelessBlockComponent.LOD_MIN_IMAGE_BYTES ||
pixels >= ImageEdgelessBlockComponent.LOD_MIN_IMAGE_PIXELS
);
}
private _shouldUseLod(blobUrl: string | null, zoom = this.gfx.viewport.zoom) {
return (
Boolean(blobUrl) &&
this._isLargeImage() &&
zoom <= ImageEdgelessBlockComponent.LOD_MAX_ZOOM
);
}
private _revokeLodThumbnail() {
if (!this._lodThumbnailUrl) {
return;
}
URL.revokeObjectURL(this._lodThumbnailUrl);
this._lodThumbnailUrl = null;
}
private _resetLodSource(blobUrl: string | null) {
if (this._lodSourceUrl === blobUrl) {
return;
}
this._lodGenerationToken += 1;
this._lodGeneratingSourceUrl = null;
this._lodSourceUrl = blobUrl;
this._revokeLodThumbnail();
}
private _createImageElement(src: string) {
return new Promise<HTMLImageElement>((resolve, reject) => {
const image = new Image();
image.decoding = 'async';
image.onload = () => resolve(image);
image.onerror = () => reject(new Error('Failed to load image'));
image.src = src;
});
}
private _createThumbnailBlob(image: HTMLImageElement) {
const maxEdge = ImageEdgelessBlockComponent.LOD_THUMBNAIL_MAX_EDGE;
const longestEdge = Math.max(image.naturalWidth, image.naturalHeight);
const scale = longestEdge > maxEdge ? maxEdge / longestEdge : 1;
const targetWidth = Math.max(1, Math.round(image.naturalWidth * scale));
const targetHeight = Math.max(1, Math.round(image.naturalHeight * scale));
const canvas = document.createElement('canvas');
canvas.width = targetWidth;
canvas.height = targetHeight;
const ctx = canvas.getContext('2d');
if (!ctx) {
return Promise.resolve<Blob | null>(null);
}
ctx.imageSmoothingEnabled = true;
ctx.imageSmoothingQuality = 'low';
ctx.drawImage(image, 0, 0, targetWidth, targetHeight);
return new Promise<Blob | null>(resolve => {
canvas.toBlob(resolve);
});
}
private _ensureLodThumbnail(blobUrl: string) {
if (
this._lodThumbnailUrl ||
this._lodGeneratingSourceUrl === blobUrl ||
!this._shouldUseLod(blobUrl)
) {
return;
}
const token = ++this._lodGenerationToken;
this._lodGeneratingSourceUrl = blobUrl;
void this._createImageElement(blobUrl)
.then(image => this._createThumbnailBlob(image))
.then(blob => {
if (!blob || token !== this._lodGenerationToken || !this.isConnected) {
return;
}
const thumbnailUrl = URL.createObjectURL(blob);
if (token !== this._lodGenerationToken || !this.isConnected) {
URL.revokeObjectURL(thumbnailUrl);
return;
}
this._revokeLodThumbnail();
this._lodThumbnailUrl = thumbnailUrl;
if (this._shouldUseLod(this.blobUrl)) {
this.requestUpdate();
}
})
.catch(err => {
if (token !== this._lodGenerationToken || !this.isConnected) {
return;
}
console.error(err);
})
.finally(() => {
if (token === this._lodGenerationToken) {
this._lodGeneratingSourceUrl = null;
}
});
}
private _updateLodFromViewport(zoom: number) {
const shouldUseLod = this._shouldUseLod(this.blobUrl, zoom);
if (shouldUseLod === this._lastShouldUseLod) {
return;
}
this._lastShouldUseLod = shouldUseLod;
if (shouldUseLod && this.blobUrl) {
this._ensureLodThumbnail(this.blobUrl);
}
this.requestUpdate();
}
override connectedCallback() {
super.connectedCallback();
@@ -108,14 +252,32 @@ export class ImageEdgelessBlockComponent extends GfxBlockComponent<ImageBlockMod
this.disposables.add(
this.model.props.sourceId$.subscribe(() => {
this._resetLodSource(null);
this.refreshData();
})
);
this.disposables.add(
this.gfx.viewport.viewportUpdated.subscribe(({ zoom }) => {
this._updateLodFromViewport(zoom);
})
);
this._lastShouldUseLod = this._shouldUseLod(this.blobUrl);
}
override disconnectedCallback() {
this._lodGenerationToken += 1;
this._lodGeneratingSourceUrl = null;
this._lodSourceUrl = null;
this._revokeLodThumbnail();
super.disconnectedCallback();
}
override renderGfxBlock() {
const blobUrl = this.blobUrl;
const { rotate = 0, size = 0, caption = 'Image' } = this.model.props;
this._resetLodSource(blobUrl);
const containerStyleMap = styleMap({
display: 'flex',
@@ -138,6 +300,13 @@ export class ImageEdgelessBlockComponent extends GfxBlockComponent<ImageBlockMod
});
const { loading, icon, description, error, needUpload } = resovledState;
const shouldUseLod = this._shouldUseLod(blobUrl);
if (shouldUseLod && blobUrl) {
this._ensureLodThumbnail(blobUrl);
}
this._lastShouldUseLod = shouldUseLod;
const imageUrl =
shouldUseLod && this._lodThumbnailUrl ? this._lodThumbnailUrl : blobUrl;
return html`
<div class="affine-image-container" style=${containerStyleMap}>
@@ -149,7 +318,7 @@ export class ImageEdgelessBlockComponent extends GfxBlockComponent<ImageBlockMod
class="drag-target"
draggable="false"
loading="lazy"
src=${blobUrl}
src=${imageUrl ?? ''}
alt=${caption}
@error=${this._handleError}
/>

View File

@@ -30,7 +30,6 @@
"@types/mdast": "^4.0.4",
"katex": "^0.16.27",
"lit": "^3.2.0",
"minimatch": "^10.1.1",
"remark-math": "^6.0.0",
"rxjs": "^7.8.2",
"zod": "^3.25.76"
@@ -46,5 +45,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.26.1"
"version": "0.26.3"
}

View File

@@ -27,7 +27,6 @@
"@toeverything/theme": "^1.1.23",
"@types/mdast": "^4.0.4",
"lit": "^3.2.0",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2",
"zod": "^3.25.76"
},
@@ -46,5 +45,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.26.1"
"version": "0.26.3"
}

View File

@@ -33,7 +33,6 @@
"@vanilla-extract/css": "^1.17.0",
"lit": "^3.2.0",
"lodash-es": "^4.17.23",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2",
"zod": "^3.25.76"
},
@@ -49,5 +48,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.26.1"
"version": "0.26.3"
}

View File

@@ -26,7 +26,6 @@
"@toeverything/theme": "^1.1.23",
"@types/mdast": "^4.0.4",
"lit": "^3.2.0",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2",
"zod": "^3.25.76"
},
@@ -42,5 +41,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.26.1"
"version": "0.26.3"
}

View File

@@ -50,7 +50,6 @@
"html2canvas": "^1.4.1",
"lit": "^3.2.0",
"lodash-es": "^4.17.23",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2",
"yjs": "^13.6.27",
"zod": "^3.25.76"
@@ -67,5 +66,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.26.1"
"version": "0.26.3"
}

View File

@@ -33,7 +33,11 @@ import {
ReleaseFromGroupIcon,
UnlockIcon,
} from '@blocksuite/icons/lit';
import type { GfxModel } from '@blocksuite/std/gfx';
import {
batchAddChildren,
batchRemoveChildren,
type GfxModel,
} from '@blocksuite/std/gfx';
import { html } from 'lit';
import { renderAlignmentMenu } from './alignment';
@@ -61,14 +65,13 @@ export const builtinMiscToolbarConfig = {
const group = firstModel.group;
// oxlint-disable-next-line unicorn/prefer-dom-node-remove
group.removeChild(firstModel);
batchRemoveChildren(group, [firstModel]);
firstModel.index = ctx.gfx.layer.generateIndex();
const parent = group.group;
if (parent && parent instanceof GroupElementModel) {
parent.addChild(firstModel);
batchAddChildren(parent, [firstModel]);
}
},
},
@@ -255,9 +258,12 @@ export const builtinMiscToolbarConfig = {
// release other elements from their groups and group with top element
otherElements.forEach(element => {
// oxlint-disable-next-line unicorn/prefer-dom-node-remove
element.group?.removeChild(element);
topElement.group?.addChild(element);
if (element.group) {
batchRemoveChildren(element.group, [element]);
}
if (topElement.group) {
batchAddChildren(topElement.group, [element]);
}
});
if (otherElements.length === 0) {

View File

@@ -45,5 +45,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.26.1"
"version": "0.26.3"
}

View File

@@ -46,5 +46,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.26.1"
"version": "0.26.3"
}

View File

@@ -40,10 +40,146 @@ export const SurfaceBlockSchemaExtension =
export class SurfaceBlockModel extends BaseSurfaceModel {
private readonly _disposables: DisposableGroup = new DisposableGroup();
private readonly _connectorIdsByEndpoint = new Map<string, Set<string>>();
private readonly _connectorIndexDisposables = new DisposableGroup();
private readonly _connectorEndpoints = new Map<
string,
{ sourceId: string | null; targetId: string | null }
>();
private _addConnectorEndpoint(endpointId: string, connectorId: string) {
const connectorIds = this._connectorIdsByEndpoint.get(endpointId);
if (connectorIds) {
connectorIds.add(connectorId);
return;
}
this._connectorIdsByEndpoint.set(endpointId, new Set([connectorId]));
}
private _isConnectorModel(model: unknown): model is ConnectorElementModel {
return (
!!model &&
typeof model === 'object' &&
'type' in model &&
(model as { type?: string }).type === 'connector'
);
}
private _removeConnectorEndpoint(endpointId: string, connectorId: string) {
const connectorIds = this._connectorIdsByEndpoint.get(endpointId);
if (!connectorIds) {
return;
}
connectorIds.delete(connectorId);
if (connectorIds.size === 0) {
this._connectorIdsByEndpoint.delete(endpointId);
}
}
private _removeConnectorFromIndex(connectorId: string) {
const endpoints = this._connectorEndpoints.get(connectorId);
if (!endpoints) {
return;
}
if (endpoints.sourceId) {
this._removeConnectorEndpoint(endpoints.sourceId, connectorId);
}
if (endpoints.targetId) {
this._removeConnectorEndpoint(endpoints.targetId, connectorId);
}
this._connectorEndpoints.delete(connectorId);
}
private _rebuildConnectorIndex() {
this._connectorIdsByEndpoint.clear();
this._connectorEndpoints.clear();
this.getElementsByType('connector').forEach(connector => {
this._setConnectorEndpoints(connector as ConnectorElementModel);
});
}
private _setConnectorEndpoints(connector: ConnectorElementModel) {
const sourceId = connector.source?.id ?? null;
const targetId = connector.target?.id ?? null;
const previousEndpoints = this._connectorEndpoints.get(connector.id);
if (
previousEndpoints?.sourceId === sourceId &&
previousEndpoints?.targetId === targetId
) {
return;
}
if (previousEndpoints?.sourceId) {
this._removeConnectorEndpoint(previousEndpoints.sourceId, connector.id);
}
if (previousEndpoints?.targetId) {
this._removeConnectorEndpoint(previousEndpoints.targetId, connector.id);
}
if (sourceId) {
this._addConnectorEndpoint(sourceId, connector.id);
}
if (targetId) {
this._addConnectorEndpoint(targetId, connector.id);
}
this._connectorEndpoints.set(connector.id, {
sourceId,
targetId,
});
}
override _init() {
this._extendElement(elementsCtorMap);
super._init();
this._rebuildConnectorIndex();
this._connectorIndexDisposables.add(
this.elementAdded.subscribe(({ id }) => {
const model = this.getElementById(id);
if (this._isConnectorModel(model)) {
this._setConnectorEndpoints(model);
}
})
);
this._connectorIndexDisposables.add(
this.elementUpdated.subscribe(({ id, props }) => {
if (!props['source'] && !props['target']) {
return;
}
const model = this.getElementById(id);
if (this._isConnectorModel(model)) {
this._setConnectorEndpoints(model);
}
})
);
this._connectorIndexDisposables.add(
this.elementRemoved.subscribe(({ id, type }) => {
if (type === 'connector') {
this._removeConnectorFromIndex(id);
}
})
);
this.deleted.subscribe(() => {
this._connectorIndexDisposables.dispose();
this._connectorIdsByEndpoint.clear();
this._connectorEndpoints.clear();
});
this.store.provider
.getAll(surfaceMiddlewareIdentifier)
.forEach(({ middleware }) => {
@@ -52,13 +188,31 @@ export class SurfaceBlockModel extends BaseSurfaceModel {
}
getConnectors(id: string) {
const connectors = this.getElementsByType(
'connector'
) as unknown[] as ConnectorElementModel[];
const connectorIds = this._connectorIdsByEndpoint.get(id);
return connectors.filter(
connector => connector.source?.id === id || connector.target?.id === id
);
if (!connectorIds?.size) {
return [];
}
const staleConnectorIds: string[] = [];
const connectors: ConnectorElementModel[] = [];
connectorIds.forEach(connectorId => {
const model = this.getElementById(connectorId);
if (!this._isConnectorModel(model)) {
staleConnectorIds.push(connectorId);
return;
}
connectors.push(model);
});
staleConnectorIds.forEach(connectorId => {
this._removeConnectorFromIndex(connectorId);
});
return connectors;
}
override getElementsByType<K extends keyof SurfaceElementModelMap>(

View File

@@ -42,5 +42,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.26.1"
"version": "0.26.3"
}

View File

@@ -19,7 +19,7 @@
"@blocksuite/sync": "workspace:*",
"@floating-ui/dom": "^1.6.13",
"@lit/context": "^1.1.2",
"@lottiefiles/dotlottie-wc": "^0.5.0",
"@lottiefiles/dotlottie-wc": "^0.9.4",
"@preact/signals-core": "^1.8.0",
"@toeverything/theme": "^1.1.23",
"@types/hast": "^3.0.4",
@@ -82,5 +82,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.26.1"
"version": "0.26.3"
}

View File

@@ -1,4 +1,8 @@
import { getHostName } from '@blocksuite/affine-shared/utils';
import {
getHostName,
isValidUrl,
normalizeUrl,
} from '@blocksuite/affine-shared/utils';
import { PropTypes, requiredProperties } from '@blocksuite/std';
import { css, LitElement } from 'lit';
import { property } from 'lit/decorators.js';
@@ -44,15 +48,27 @@ export class LinkPreview extends LitElement {
override render() {
const { url } = this;
const normalizedUrl = normalizeUrl(url);
const safeUrl =
normalizedUrl && isValidUrl(normalizedUrl) ? normalizedUrl : null;
const hostName = getHostName(safeUrl ?? url);
if (!safeUrl) {
return html`
<span class="affine-link-preview">
<span>${hostName}</span>
</span>
`;
}
return html`
<a
class="affine-link-preview"
rel="noopener noreferrer"
target="_blank"
href=${url}
href=${safeUrl}
>
<span>${getHostName(url)}</span>
<span>${hostName}</span>
</a>
`;
}

View File

@@ -48,5 +48,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.26.1"
"version": "0.26.3"
}

View File

@@ -4,6 +4,7 @@ import { describe, expect, it, vi } from 'vitest';
import type { GroupBy } from '../core/common/types.js';
import type { DataSource } from '../core/data-source/base.js';
import { DetailSelection } from '../core/detail/selection.js';
import type { FilterGroup } from '../core/filter/types.js';
import { groupByMatchers } from '../core/group-by/define.js';
import { t } from '../core/logical/type-presets.js';
import type { DataViewCellLifeCycle } from '../core/property/index.js';
@@ -17,7 +18,10 @@ import {
pickKanbanGroupColumn,
resolveKanbanGroupBy,
} from '../view-presets/kanban/group-by-utils.js';
import { materializeKanbanColumns } from '../view-presets/kanban/kanban-view-manager.js';
import {
KanbanSingleView,
materializeKanbanColumns,
} from '../view-presets/kanban/kanban-view-manager.js';
import type { KanbanCard } from '../view-presets/kanban/pc/card.js';
import { KanbanDragController } from '../view-presets/kanban/pc/controller/drag.js';
import type { KanbanGroup } from '../view-presets/kanban/pc/group.js';
@@ -270,6 +274,73 @@ describe('kanban', () => {
});
});
describe('filtering', () => {
const sharedFilter: FilterGroup = {
type: 'group',
op: 'and',
conditions: [
{
type: 'filter',
left: {
type: 'ref',
name: 'status',
},
function: 'is',
args: [{ type: 'literal', value: 'Done' }],
},
],
};
const sharedTitleProperty = {
id: 'title',
cellGetOrCreate: () => ({
jsonValue$: {
value: 'Task 1',
},
}),
};
it('evaluates filters with hidden columns', () => {
const statusProperty = {
id: 'status',
cellGetOrCreate: () => ({
jsonValue$: {
value: 'Done',
},
}),
};
const view = {
filter$: { value: sharedFilter },
// Simulate status being hidden in current view.
properties$: { value: [sharedTitleProperty] },
propertiesRaw$: { value: [sharedTitleProperty, statusProperty] },
} as unknown as KanbanSingleView;
expect(KanbanSingleView.prototype.isShow.call(view, 'row-1')).toBe(true);
});
it('returns false when hidden filtered column does not match', () => {
const statusProperty = {
id: 'status',
cellGetOrCreate: () => ({
jsonValue$: {
value: 'In Progress',
},
}),
};
const view = {
filter$: { value: sharedFilter },
// Simulate status being hidden in current view.
properties$: { value: [sharedTitleProperty] },
propertiesRaw$: { value: [sharedTitleProperty, statusProperty] },
} as unknown as KanbanSingleView;
expect(KanbanSingleView.prototype.isShow.call(view, 'row-1')).toBe(false);
});
});
describe('drag indicator', () => {
it('shows drop preview when insert position exists', () => {
const controller = createDragController();

View File

@@ -1,15 +1,21 @@
import { describe, expect, test } from 'vitest';
import type { FilterGroup } from '../core/filter/types.js';
import { numberFormats } from '../property-presets/number/utils/formats.js';
import {
formatNumber,
NumberFormatSchema,
parseNumber,
} from '../property-presets/number/utils/formatter.js';
import { DEFAULT_COLUMN_WIDTH } from '../view-presets/table/consts.js';
import { mobileEffects } from '../view-presets/table/mobile/effect.js';
import type { MobileTableGroup } from '../view-presets/table/mobile/group.js';
import { pcEffects } from '../view-presets/table/pc/effect.js';
import type { TableGroup } from '../view-presets/table/pc/group.js';
import {
materializeTableColumns,
TableSingleView,
} from '../view-presets/table/table-view-manager.js';
/** @vitest-environment happy-dom */
@@ -41,6 +47,146 @@ describe('TableGroup', () => {
});
});
describe('table column materialization', () => {
test('appends missing properties while preserving existing order and state', () => {
const columns = [
{ id: 'status', width: 240, hide: true },
{ id: 'title', width: 320 },
];
const next = materializeTableColumns(columns, ['title', 'status', 'date']);
expect(next).toEqual([
{ id: 'status', width: 240, hide: true },
{ id: 'title', width: 320 },
{ id: 'date', width: DEFAULT_COLUMN_WIDTH },
]);
});
test('drops stale columns that no longer exist in data source', () => {
const columns = [
{ id: 'title', width: 320 },
{ id: 'removed', width: 200, hide: true },
];
const next = materializeTableColumns(columns, ['title']);
expect(next).toEqual([{ id: 'title', width: 320 }]);
});
test('returns original reference when columns are already materialized', () => {
const columns = [
{ id: 'title', width: 320 },
{ id: 'status', width: 240, hide: true },
];
const next = materializeTableColumns(columns, ['title', 'status']);
expect(next).toBe(columns);
});
test('supports type-aware default width when materializing missing columns', () => {
const next = materializeTableColumns([], ['title', 'status'], id =>
id === 'title' ? 260 : DEFAULT_COLUMN_WIDTH
);
expect(next).toEqual([
{ id: 'title', width: 260 },
{ id: 'status', width: DEFAULT_COLUMN_WIDTH },
]);
});
});
describe('table filtering', () => {
test('evaluates filters with hidden columns', () => {
const filter: FilterGroup = {
type: 'group',
op: 'and',
conditions: [
{
type: 'filter',
left: {
type: 'ref',
name: 'status',
},
function: 'is',
args: [{ type: 'literal', value: 'Done' }],
},
],
};
const titleProperty = {
id: 'title',
cellGetOrCreate: () => ({
jsonValue$: {
value: 'Task 1',
},
}),
};
const statusProperty = {
id: 'status',
cellGetOrCreate: () => ({
jsonValue$: {
value: 'Done',
},
}),
};
const view = {
filter$: { value: filter },
// Simulate status being hidden in current view.
properties$: { value: [titleProperty] },
propertiesRaw$: { value: [titleProperty, statusProperty] },
} as unknown as TableSingleView;
expect(TableSingleView.prototype.isShow.call(view, 'row-1')).toBe(true);
});
test('returns false when hidden filtered column does not match', () => {
const filter: FilterGroup = {
type: 'group',
op: 'and',
conditions: [
{
type: 'filter',
left: {
type: 'ref',
name: 'status',
},
function: 'is',
args: [{ type: 'literal', value: 'Done' }],
},
],
};
const titleProperty = {
id: 'title',
cellGetOrCreate: () => ({
jsonValue$: {
value: 'Task 1',
},
}),
};
const statusProperty = {
id: 'status',
cellGetOrCreate: () => ({
jsonValue$: {
value: 'In Progress',
},
}),
};
const view = {
filter$: { value: filter },
// Simulate status being hidden in current view.
properties$: { value: [titleProperty] },
propertiesRaw$: { value: [titleProperty, statusProperty] },
} as unknown as TableSingleView;
expect(TableSingleView.prototype.isShow.call(view, 'row-1')).toBe(false);
});
});
describe('number formatter', () => {
test('number format menu should expose all schema formats', () => {
const menuFormats = numberFormats.map(format => format.type);

View File

@@ -67,7 +67,7 @@ export const autoScrollOnBoundary = (
};
const cancelBoxListen = effect(() => {
box.value;
void box.value;
startUpdate();
});

View File

@@ -349,7 +349,7 @@ export class KanbanSingleView extends SingleViewBase<KanbanViewData> {
isShow(rowId: string): boolean {
if (this.filter$.value?.conditions.length) {
const rowMap = Object.fromEntries(
this.properties$.value.map(column => [
this.propertiesRaw$.value.map(column => [
column.id,
column.cellGetOrCreate(rowId).jsonValue$.value,
])

View File

@@ -54,7 +54,9 @@ export class DatabaseCellContainer extends SignalWatcher(
const selectionView = this.selectionView;
if (selectionView) {
const selection = selectionView.selection;
if (selection && this.isSelected(selection) && editing) {
const shouldEnterEditMode =
editing && this.cell?.beforeEnterEditMode() !== false;
if (selection && this.isSelected(selection) && shouldEnterEditMode) {
selectionView.selection = TableViewAreaSelection.create({
groupKey: this.groupKey,
focus: {

View File

@@ -24,12 +24,12 @@ import {
DataViewUIBase,
DataViewUILogicBase,
} from '../../../core/view/data-view-base.js';
import { LEFT_TOOL_BAR_WIDTH } from '../consts.js';
import {
type TableSingleView,
TableViewRowSelection,
type TableViewSelectionWithType,
} from '../../index.js';
import { LEFT_TOOL_BAR_WIDTH } from '../consts.js';
} from '../selection.js';
import type { TableSingleView } from '../table-view-manager.js';
import { TableClipboardController } from './controller/clipboard.js';
import { TableDragController } from './controller/drag.js';
import { TableHotkeysController } from './controller/hotkeys.js';

View File

@@ -57,7 +57,9 @@ export class TableViewCellContainer extends SignalWatcher(
const selectionView = this.selectionController;
if (selectionView) {
const selection = selectionView.selection;
if (selection && this.isSelected(selection) && editing) {
const shouldEnterEditMode =
editing && this.cell?.beforeEnterEditMode() !== false;
if (selection && this.isSelected(selection) && shouldEnterEditMode) {
selectionView.selection = TableViewAreaSelection.create({
groupKey: this.groupKey,
focus: {

View File

@@ -26,6 +26,52 @@ import type { ViewManager } from '../../core/view-manager/view-manager.js';
import { DEFAULT_COLUMN_MIN_WIDTH, DEFAULT_COLUMN_WIDTH } from './consts.js';
import type { TableViewData } from './define.js';
export const materializeColumnsByPropertyIds = (
columns: TableColumnData[],
propertyIds: string[],
getDefaultWidth: (id: string) => number = () => DEFAULT_COLUMN_WIDTH
) => {
const needShow = new Set(propertyIds);
const orderedColumns: TableColumnData[] = [];
for (const column of columns) {
if (needShow.has(column.id)) {
orderedColumns.push(column);
needShow.delete(column.id);
}
}
for (const id of needShow) {
orderedColumns.push({ id, width: getDefaultWidth(id), hide: undefined });
}
return orderedColumns;
};
export const materializeTableColumns = (
columns: TableColumnData[],
propertyIds: string[],
getDefaultWidth?: (id: string) => number
) => {
const nextColumns = materializeColumnsByPropertyIds(
columns,
propertyIds,
getDefaultWidth
);
const unchanged =
columns.length === nextColumns.length &&
columns.every((column, index) => {
const nextColumn = nextColumns[index];
return (
nextColumn != null &&
column.id === nextColumn.id &&
column.hide === nextColumn.hide
);
});
return unchanged ? columns : nextColumns;
};
export class TableSingleView extends SingleViewBase<TableViewData> {
propertiesRaw$ = computed(() => {
const needShow = new Set(this.dataSource.properties$.value);
@@ -220,14 +266,10 @@ export class TableSingleView extends SingleViewBase<TableViewData> {
return this.data$.value?.mode ?? 'table';
}
constructor(viewManager: ViewManager, viewId: string) {
super(viewManager, viewId);
}
isShow(rowId: string): boolean {
if (this.filter$.value?.conditions.length) {
const rowMap = Object.fromEntries(
this.properties$.value.map(column => [
this.propertiesRaw$.value.map(column => [
column.id,
column.cellGetOrCreate(rowId).jsonValue$.value,
])
@@ -290,6 +332,33 @@ export class TableSingleView extends SingleViewBase<TableViewData> {
});
}
);
private materializeColumns() {
const data = this.data$.value;
if (!data) {
return;
}
const nextColumns = materializeTableColumns(
data.columns,
this.dataSource.properties$.value,
id => this.propertyGetOrCreate(id).width$.value
);
if (nextColumns === data.columns) {
return;
}
this.dataUpdate(() => ({ columns: nextColumns }));
}
constructor(viewManager: ViewManager, viewId: string) {
super(viewManager, viewId);
// Materialize view columns on view activation so newly added properties
// can participate in hide/order operations in table.
queueMicrotask(() => {
this.materializeColumns();
});
}
}
type TableColumnData = TableViewData['columns'][number];

View File

@@ -26,5 +26,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.26.1"
"version": "0.26.3"
}

View File

@@ -60,10 +60,9 @@ export class BaseExtensionProvider<
* @param context - The context object containing scope and registration function
* @param option - Optional configuration options for the provider
*/
setup(context: Context<Scope>, option?: Options) {
setup(_context: Context<Scope>, option?: Options) {
if (option) {
this.schema.parse(option);
}
context;
}
}

View File

@@ -42,5 +42,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.26.1"
"version": "0.26.3"
}

View File

@@ -35,5 +35,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.26.1"
"version": "0.26.3"
}

View File

@@ -26,7 +26,6 @@
"@preact/signals-core": "^1.8.0",
"@toeverything/theme": "^1.1.23",
"lit": "^3.2.0",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2",
"zod": "^3.25.76"
},
@@ -40,5 +39,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.26.1"
"version": "0.26.3"
}

View File

@@ -28,7 +28,6 @@
"@types/lodash-es": "^4.17.12",
"lit": "^3.2.0",
"lodash-es": "^4.17.23",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2",
"zod": "^3.25.76"
},
@@ -42,5 +41,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.26.1"
"version": "0.26.3"
}

View File

@@ -27,7 +27,6 @@
"@toeverything/theme": "^1.1.23",
"@vanilla-extract/css": "^1.17.0",
"lit": "^3.2.0",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2",
"zod": "^3.25.76"
},
@@ -41,5 +40,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.26.1"
"version": "0.26.3"
}

View File

@@ -27,7 +27,6 @@
"@types/lodash-es": "^4.17.12",
"lit": "^3.2.0",
"lodash-es": "^4.17.23",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2",
"yjs": "^13.6.27",
"zod": "^3.25.76"
@@ -43,5 +42,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.26.1"
"version": "0.26.3"
}

View File

@@ -28,7 +28,6 @@
"@types/lodash-es": "^4.17.12",
"lit": "^3.2.0",
"lodash-es": "^4.17.23",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2",
"yjs": "^13.6.27",
"zod": "^3.25.76"
@@ -44,5 +43,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.26.1"
"version": "0.26.3"
}

View File

@@ -884,7 +884,7 @@ export class ConnectionOverlay extends Overlay {
private _setupThemeListener(): void {
const themeService = this.gfx.std.get(ThemeProvider);
this._themeDisposer = effect(() => {
themeService.theme$;
void themeService.theme$.value;
this._emphasisColor = this._getEmphasisColor();
});
}

View File

@@ -84,6 +84,8 @@ export const connectorWatcher: SurfaceMiddleware = (
);
return () => {
pendingFlag = false;
pendingList.clear();
disposables.forEach(d => d.unsubscribe());
};
};

View File

@@ -26,13 +26,16 @@
"@preact/signals-core": "^1.8.0",
"@toeverything/theme": "^1.1.23",
"@types/lodash-es": "^4.17.12",
"fractional-indexing": "^3.2.0",
"lit": "^3.2.0",
"lodash-es": "^4.17.23",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2",
"yjs": "^13.6.27",
"zod": "^3.25.76"
},
"devDependencies": {
"vitest": "^3.2.4"
},
"exports": {
".": "./src/index.ts",
"./view": "./src/view.ts",
@@ -44,5 +47,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.26.1"
"version": "0.26.3"
}

View File

@@ -0,0 +1,152 @@
import { beforeEach, describe, expect, test, vi } from 'vitest';
vi.mock('fractional-indexing', () => ({
generateKeyBetween: vi.fn(),
generateNKeysBetween: vi.fn(),
}));
import { generateKeyBetween, generateNKeysBetween } from 'fractional-indexing';
import { ungroupCommand } from '../command/group-api.js';
type TestElement = {
id: string;
index: string;
group: TestElement | null;
childElements: TestElement[];
removeChildren?: (elements: TestElement[]) => void;
addChildren?: (elements: TestElement[]) => void;
};
const mockedGenerateNKeysBetween = vi.mocked(generateNKeysBetween);
const mockedGenerateKeyBetween = vi.mocked(generateKeyBetween);
const createElement = (
id: string,
index: string,
group: TestElement | null
): TestElement => ({
id,
index,
group,
childElements: [],
});
const createUngroupFixture = () => {
const parent = createElement('parent', 'p0', null);
const left = createElement('left', 'a0', parent);
const right = createElement('right', 'a0', parent);
const group = createElement('group', 'm0', parent);
const childA = createElement('child-a', 'c0', group);
const childB = createElement('child-b', 'c1', group);
group.childElements = [childB, childA];
parent.childElements = [left, group, right];
parent.removeChildren = vi.fn();
parent.addChildren = vi.fn();
group.removeChildren = vi.fn();
const elementOrder = new Map<TestElement, number>([
[left, 0],
[group, 1],
[right, 2],
[childA, 3],
[childB, 4],
]);
const selectionSet = vi.fn();
const gfx = {
layer: {
compare: (a: TestElement, b: TestElement) =>
(elementOrder.get(a) ?? 0) - (elementOrder.get(b) ?? 0),
},
selection: {
set: selectionSet,
},
};
const std = {
get: vi.fn(() => gfx),
store: {
transact: (callback: () => void) => callback(),
},
};
return {
childA,
childB,
group,
parent,
selectionSet,
std,
};
};
describe('ungroupCommand', () => {
beforeEach(() => {
mockedGenerateNKeysBetween.mockReset();
mockedGenerateKeyBetween.mockReset();
});
test('falls back to open-ended key generation when sibling interval is invalid', () => {
const fixture = createUngroupFixture();
mockedGenerateNKeysBetween
.mockImplementationOnce(() => {
throw new Error('interval reversed');
})
.mockReturnValueOnce(['n0', 'n1']);
const next = vi.fn();
ungroupCommand(
{
std: fixture.std,
group: fixture.group as any,
} as any,
next
);
expect(mockedGenerateNKeysBetween).toHaveBeenNthCalledWith(
1,
'a0',
'a0',
2
);
expect(mockedGenerateNKeysBetween).toHaveBeenNthCalledWith(
2,
'a0',
null,
2
);
expect(fixture.childA.index).toBe('n0');
expect(fixture.childB.index).toBe('n1');
expect(fixture.selectionSet).toHaveBeenCalledWith({
editing: false,
elements: ['child-a', 'child-b'],
});
expect(next).toHaveBeenCalledTimes(1);
});
test('falls back to key-by-key generation when all batched strategies fail', () => {
const fixture = createUngroupFixture();
mockedGenerateNKeysBetween.mockImplementation(() => {
throw new Error('invalid range');
});
let seq = 0;
mockedGenerateKeyBetween.mockImplementation(() => `k${seq++}`);
ungroupCommand(
{
std: fixture.std,
group: fixture.group as any,
} as any,
vi.fn()
);
expect(mockedGenerateNKeysBetween).toHaveBeenCalledTimes(4);
expect(mockedGenerateKeyBetween).toHaveBeenCalledTimes(2);
expect(fixture.childA.index).toBe('k0');
expect(fixture.childB.index).toBe('k1');
});
});

View File

@@ -4,7 +4,80 @@ import {
MindmapElementModel,
} from '@blocksuite/affine-model';
import type { Command } from '@blocksuite/std';
import { GfxControllerIdentifier, type GfxModel } from '@blocksuite/std/gfx';
import {
batchAddChildren,
batchRemoveChildren,
type GfxController,
GfxControllerIdentifier,
type GfxModel,
measureOperation,
} from '@blocksuite/std/gfx';
import { generateKeyBetween, generateNKeysBetween } from 'fractional-indexing';
const getTopLevelOrderedElements = (gfx: GfxController) => {
const topLevelElements = gfx.layer.layers.reduce<GfxModel[]>(
(elements, layer) => {
layer.elements.forEach(element => {
if (element.group === null) {
elements.push(element as GfxModel);
}
});
return elements;
},
[]
);
topLevelElements.sort((a, b) => gfx.layer.compare(a, b));
return topLevelElements;
};
const buildUngroupIndexes = (
orderedElements: GfxModel[],
afterIndex: string | null,
beforeIndex: string | null,
fallbackAnchorIndex: string
) => {
if (orderedElements.length === 0) {
return [];
}
const count = orderedElements.length;
const tryGenerateN = (left: string | null, right: string | null) => {
try {
const generated = generateNKeysBetween(left, right, count);
return generated.length === count ? generated : null;
} catch {
return null;
}
};
const tryGenerateOneByOne = (left: string | null, right: string | null) => {
try {
let cursor = left;
return orderedElements.map(() => {
cursor = generateKeyBetween(cursor, right);
return cursor;
});
} catch {
return null;
}
};
// Preferred: keep ungrouped children in the original group slot.
return (
tryGenerateN(afterIndex, beforeIndex) ??
// Fallback: ignore the upper bound when legacy/broken data has reversed interval.
tryGenerateN(afterIndex, null) ??
// Fallback: use group index as anchor when sibling interval is unavailable.
tryGenerateN(fallbackAnchorIndex, null) ??
// Last resort: always valid.
tryGenerateN(null, null) ??
// Defensive fallback for unexpected library behavior.
tryGenerateOneByOne(null, null) ??
[]
);
};
export const createGroupCommand: Command<
{ elements: GfxModel[] | string[] },
@@ -39,96 +112,118 @@ export const createGroupFromSelectedCommand: Command<
{},
{ groupId: string }
> = (ctx, next) => {
const { std } = ctx;
const gfx = std.get(GfxControllerIdentifier);
const { selection, surface } = gfx;
measureOperation('edgeless:create-group-from-selected', () => {
const { std } = ctx;
const gfx = std.get(GfxControllerIdentifier);
const { selection, surface } = gfx;
if (!surface) {
return;
}
if (!surface) {
return;
}
if (
selection.selectedElements.length === 0 ||
!selection.selectedElements.every(
element =>
element.group === selection.firstElement.group &&
!(element.group instanceof MindmapElementModel)
)
) {
return;
}
if (
selection.selectedElements.length === 0 ||
!selection.selectedElements.every(
element =>
element.group === selection.firstElement.group &&
!(element.group instanceof MindmapElementModel)
)
) {
return;
}
const parent = selection.firstElement.group as GroupElementModel;
const parent = selection.firstElement.group;
let groupId: string | undefined;
std.store.transact(() => {
const [_, result] = std.command.exec(createGroupCommand, {
elements: selection.selectedElements,
});
if (parent !== null) {
selection.selectedElements.forEach(element => {
// oxlint-disable-next-line unicorn/prefer-dom-node-remove
parent.removeChild(element);
if (!result.groupId) {
return;
}
groupId = result.groupId;
const group = surface.getElementById(groupId);
if (parent !== null && group) {
batchRemoveChildren(parent, selection.selectedElements);
batchAddChildren(parent, [group]);
}
});
}
const [_, result] = std.command.exec(createGroupCommand, {
elements: selection.selectedElements,
if (!groupId) {
return;
}
selection.set({
editing: false,
elements: [groupId],
});
next({ groupId });
});
if (!result.groupId) {
return;
}
const group = surface.getElementById(result.groupId);
if (parent !== null && group) {
parent.addChild(group);
}
selection.set({
editing: false,
elements: [result.groupId],
});
next({ groupId: result.groupId });
};
export const ungroupCommand: Command<{ group: GroupElementModel }, {}> = (
ctx,
next
) => {
const { std, group } = ctx;
const gfx = std.get(GfxControllerIdentifier);
const { selection } = gfx;
const parent = group.group as GroupElementModel;
const elements = group.childElements;
measureOperation('edgeless:ungroup', () => {
const { std, group } = ctx;
const gfx = std.get(GfxControllerIdentifier);
const { selection } = gfx;
const parent = group.group;
const elements = [...group.childElements];
if (group instanceof MindmapElementModel) {
return;
}
if (group instanceof MindmapElementModel) {
return;
}
if (parent !== null) {
// oxlint-disable-next-line unicorn/prefer-dom-node-remove
parent.removeChild(group);
}
const orderedElements = [...elements].sort((a, b) =>
gfx.layer.compare(a, b)
);
const siblings = parent
? [...parent.childElements].sort((a, b) => gfx.layer.compare(a, b))
: getTopLevelOrderedElements(gfx);
const groupPosition = siblings.indexOf(group);
const beforeSiblingIndex =
groupPosition > 0 ? (siblings[groupPosition - 1]?.index ?? null) : null;
const afterSiblingIndex =
groupPosition === -1
? null
: (siblings[groupPosition + 1]?.index ?? null);
const nextIndexes = buildUngroupIndexes(
orderedElements,
beforeSiblingIndex,
afterSiblingIndex,
group.index
);
elements.forEach(element => {
// oxlint-disable-next-line unicorn/prefer-dom-node-remove
group.removeChild(element);
});
std.store.transact(() => {
if (parent !== null) {
batchRemoveChildren(parent, [group]);
}
// keep relative index order of group children after ungroup
elements
.sort((a, b) => gfx.layer.compare(a, b))
.forEach(element => {
std.store.transact(() => {
element.index = gfx.layer.generateIndex();
batchRemoveChildren(group, elements);
// keep relative index order of group children after ungroup
orderedElements.forEach((element, idx) => {
const index = nextIndexes[idx];
if (element.index !== index) {
element.index = index;
}
});
if (parent !== null) {
batchAddChildren(parent, orderedElements);
}
});
if (parent !== null) {
elements.forEach(element => {
parent.addChild(element);
selection.set({
editing: false,
elements: orderedElements.map(ele => ele.id),
});
}
selection.set({
editing: false,
elements: elements.map(ele => ele.id),
next();
});
next();
};

View File

@@ -0,0 +1,25 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
esbuild: {
target: 'es2018',
},
test: {
globalSetup: '../../../scripts/vitest-global.js',
include: ['src/__tests__/**/*.unit.spec.ts'],
testTimeout: 1000,
coverage: {
provider: 'istanbul',
reporter: ['lcov'],
reportsDirectory: '../../../.coverage/affine-gfx-group',
},
onConsoleLog(log, type) {
if (log.includes('lit.dev/msg/dev-mode')) {
return false;
}
console.warn(`Unexpected ${type} log`, log);
throw new Error(log);
},
environment: 'happy-dom',
},
});

View File

@@ -30,7 +30,6 @@
"@types/lodash-es": "^4.17.12",
"lit": "^3.2.0",
"lodash-es": "^4.17.23",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2",
"yjs": "^13.6.27",
"zod": "^3.25.76"
@@ -45,5 +44,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.26.1"
"version": "0.26.3"
}

View File

@@ -34,7 +34,6 @@
"@types/lodash-es": "^4.17.12",
"lit": "^3.2.0",
"lodash-es": "^4.17.23",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2",
"simple-xml-to-json": "^1.2.2",
"yjs": "^13.6.27",
@@ -51,5 +50,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.26.1"
"version": "0.26.3"
}

View File

@@ -30,7 +30,6 @@
"@types/lodash-es": "^4.17.12",
"lit": "^3.2.0",
"lodash-es": "^4.17.23",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2",
"yjs": "^13.6.27",
"zod": "^3.25.76"
@@ -45,5 +44,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.26.1"
"version": "0.26.3"
}

View File

@@ -27,11 +27,13 @@
"@types/lodash-es": "^4.17.12",
"lit": "^3.2.0",
"lodash-es": "^4.17.23",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2",
"yjs": "^13.6.27",
"zod": "^3.25.76"
},
"devDependencies": {
"vitest": "^3.2.4"
},
"exports": {
".": "./src/index.ts",
"./view": "./src/view.ts"
@@ -42,5 +44,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.26.1"
"version": "0.26.3"
}

View File

@@ -0,0 +1,73 @@
import { describe, expect, test } from 'vitest';
import {
AdaptiveCooldownController,
AdaptiveStrideController,
} from '../snap/adaptive-load-controller.js';
describe('AdaptiveStrideController', () => {
test('increases stride under heavy cost and respects maxStride', () => {
const controller = new AdaptiveStrideController({
heavyCostMs: 6,
maxStride: 3,
recoveryCostMs: 2,
});
controller.reportCost(10);
controller.reportCost(12);
controller.reportCost(15);
// stride should be capped at 3, so only every 3rd tick runs.
expect(controller.shouldSkip()).toBe(false);
expect(controller.shouldSkip()).toBe(true);
expect(controller.shouldSkip()).toBe(true);
expect(controller.shouldSkip()).toBe(false);
});
test('decreases stride when cost recovers and reset clears state', () => {
const controller = new AdaptiveStrideController({
heavyCostMs: 8,
maxStride: 4,
recoveryCostMs: 3,
});
controller.reportCost(12);
controller.reportCost(12);
controller.reportCost(1);
// From stride 3 recovered to stride 2: run every other tick.
expect(controller.shouldSkip()).toBe(false);
expect(controller.shouldSkip()).toBe(true);
expect(controller.shouldSkip()).toBe(false);
controller.reset();
expect(controller.shouldSkip()).toBe(false);
expect(controller.shouldSkip()).toBe(false);
});
});
describe('AdaptiveCooldownController', () => {
test('enters cooldown when cost exceeds threshold', () => {
const controller = new AdaptiveCooldownController({
cooldownFrames: 2,
maxCostMs: 5,
});
controller.reportCost(9);
expect(controller.shouldRun()).toBe(false);
expect(controller.shouldRun()).toBe(false);
expect(controller.shouldRun()).toBe(true);
});
test('reset exits cooldown immediately', () => {
const controller = new AdaptiveCooldownController({
cooldownFrames: 3,
maxCostMs: 5,
});
controller.reportCost(6);
expect(controller.shouldRun()).toBe(false);
controller.reset();
expect(controller.shouldRun()).toBe(true);
});
});

View File

@@ -0,0 +1,177 @@
import { EdgelessLegacySlotIdentifier } from '@blocksuite/affine-block-surface';
import { MouseButton } from '@blocksuite/std/gfx';
import { afterEach, describe, expect, test, vi } from 'vitest';
import { PanTool } from '../tools/pan-tool.js';
type PointerDownHandler = (event: {
raw: {
button: number;
preventDefault: () => void;
};
}) => unknown;
const mockRaf = () => {
let callback: FrameRequestCallback | undefined;
const requestAnimationFrameMock = vi
.fn()
.mockImplementation((cb: FrameRequestCallback) => {
callback = cb;
return 1;
});
const cancelAnimationFrameMock = vi.fn();
vi.stubGlobal('requestAnimationFrame', requestAnimationFrameMock);
vi.stubGlobal('cancelAnimationFrame', cancelAnimationFrameMock);
return {
getCallback: () => callback,
requestAnimationFrameMock,
cancelAnimationFrameMock,
};
};
const createToolFixture = (options?: {
currentToolName?: string;
currentToolOptions?: Record<string, unknown>;
}) => {
const applyDeltaCenter = vi.fn();
const selectionSet = vi.fn();
const setTool = vi.fn();
const navigatorSettingUpdated = {
next: vi.fn(),
};
const currentToolName = options?.currentToolName;
const currentToolOption = {
toolType: currentToolName
? ({
toolName: currentToolName,
} as any)
: undefined,
options: options?.currentToolOptions,
};
const gfx = {
viewport: {
zoom: 2,
applyDeltaCenter,
},
selection: {
surfaceSelections: [{ elements: ['shape-1'] }],
set: selectionSet,
},
tool: {
currentTool$: {
peek: () => null,
},
currentToolOption$: {
peek: () => currentToolOption,
},
setTool,
},
std: {
get: (identifier: unknown) => {
if (identifier === EdgelessLegacySlotIdentifier) {
return { navigatorSettingUpdated };
}
return null;
},
},
doc: {},
};
const tool = new PanTool(gfx as any);
return {
applyDeltaCenter,
navigatorSettingUpdated,
selectionSet,
setTool,
tool,
};
};
afterEach(() => {
vi.unstubAllGlobals();
});
describe('PanTool', () => {
test('flushes accumulated delta on dragEnd', () => {
mockRaf();
const { tool, applyDeltaCenter } = createToolFixture();
tool.dragStart({ x: 100, y: 100 } as any);
tool.dragMove({ x: 80, y: 60 } as any);
tool.dragMove({ x: 70, y: 40 } as any);
expect(applyDeltaCenter).not.toHaveBeenCalled();
tool.dragEnd({} as any);
expect(applyDeltaCenter).toHaveBeenCalledTimes(1);
expect(applyDeltaCenter).toHaveBeenCalledWith(15, 30);
expect(tool.panning$.value).toBe(false);
});
test('cancel in unmounted drops pending deltas', () => {
mockRaf();
const { tool, applyDeltaCenter } = createToolFixture();
tool.dragStart({ x: 100, y: 100 } as any);
tool.dragMove({ x: 80, y: 60 } as any);
tool.unmounted();
tool.dragEnd({} as any);
expect(applyDeltaCenter).not.toHaveBeenCalled();
});
test('middle click temporary pan restores frameNavigator with restoredAfterPan', () => {
const { tool, navigatorSettingUpdated, selectionSet, setTool } =
createToolFixture({
currentToolName: 'frameNavigator',
currentToolOptions: { mode: 'fit' },
});
const hooks: Partial<Record<'pointerDown', PointerDownHandler>> = {};
(tool as any).eventTarget = {
addHook: (eventName: 'pointerDown', handler: PointerDownHandler) => {
hooks[eventName] = handler;
},
};
tool.mounted();
const preventDefault = vi.fn();
const pointerDown = hooks.pointerDown!;
const ret = pointerDown({
raw: {
button: MouseButton.MIDDLE,
preventDefault,
},
});
expect(ret).toBe(false);
expect(preventDefault).toHaveBeenCalledTimes(1);
expect(navigatorSettingUpdated.next).toHaveBeenCalledWith({
blackBackground: false,
});
expect(setTool).toHaveBeenNthCalledWith(1, PanTool, {
panning: true,
});
document.dispatchEvent(
new PointerEvent('pointerup', { button: MouseButton.MIDDLE })
);
expect(selectionSet).toHaveBeenCalledWith([{ elements: ['shape-1'] }]);
expect(setTool).toHaveBeenNthCalledWith(
2,
expect.objectContaining({
toolName: 'frameNavigator',
}),
{
mode: 'fit',
restoredAfterPan: true,
}
);
});
});

View File

@@ -0,0 +1,65 @@
export class AdaptiveStrideController {
private _stride = 1;
private _ticks = 0;
constructor(
private readonly _options: {
heavyCostMs: number;
maxStride: number;
recoveryCostMs: number;
}
) {}
reportCost(costMs: number) {
if (costMs > this._options.heavyCostMs) {
this._stride = Math.min(this._options.maxStride, this._stride + 1);
return;
}
if (costMs < this._options.recoveryCostMs && this._stride > 1) {
this._stride -= 1;
}
}
reset() {
this._stride = 1;
this._ticks = 0;
}
shouldSkip() {
const shouldSkip = this._stride > 1 && this._ticks % this._stride !== 0;
this._ticks += 1;
return shouldSkip;
}
}
export class AdaptiveCooldownController {
private _remainingFrames = 0;
constructor(
private readonly _options: {
cooldownFrames: number;
maxCostMs: number;
}
) {}
reportCost(costMs: number) {
if (costMs > this._options.maxCostMs) {
this._remainingFrames = this._options.cooldownFrames;
}
}
reset() {
this._remainingFrames = 0;
}
shouldRun() {
if (this._remainingFrames <= 0) {
return true;
}
this._remainingFrames -= 1;
return false;
}
}

View File

@@ -8,11 +8,18 @@ import {
InteractivityExtension,
} from '@blocksuite/std/gfx';
import { AdaptiveStrideController } from './adaptive-load-controller';
import type { SnapOverlay } from './snap-overlay';
export class SnapExtension extends InteractivityExtension {
static override key = 'snap-manager';
private static readonly MAX_ALIGN_SKIP_STRIDE = 3;
private static readonly ALIGN_HEAVY_COST_MS = 5;
private static readonly ALIGN_RECOVERY_COST_MS = 2;
get snapOverlay() {
return this.std.getOptional(
OverlayIdentifier('snap-manager')
@@ -29,6 +36,11 @@ export class SnapExtension extends InteractivityExtension {
}
let alignBound: Bound | null = null;
const alignStride = new AdaptiveStrideController({
heavyCostMs: SnapExtension.ALIGN_HEAVY_COST_MS,
maxStride: SnapExtension.MAX_ALIGN_SKIP_STRIDE,
recoveryCostMs: SnapExtension.ALIGN_RECOVERY_COST_MS,
});
return {
onDragStart() {
@@ -42,6 +54,7 @@ export class SnapExtension extends InteractivityExtension {
return pre;
}, [] as GfxModel[])
);
alignStride.reset();
},
onDragMove(context: ExtensionDragMoveContext) {
if (
@@ -53,14 +66,22 @@ export class SnapExtension extends InteractivityExtension {
return;
}
if (alignStride.shouldSkip()) {
return;
}
const currentBound = alignBound.moveDelta(context.dx, context.dy);
const alignStart = performance.now();
const alignRst = snapOverlay.align(currentBound);
const alignCost = performance.now() - alignStart;
alignStride.reportCost(alignCost);
context.dx = alignRst.dx + context.dx;
context.dy = alignRst.dy + context.dy;
},
clear() {
alignBound = null;
alignStride.reset();
snapOverlay.clear();
},
};

View File

@@ -6,6 +6,8 @@ import {
import { almostEqual, Bound, type IVec, Point } from '@blocksuite/global/gfx';
import type { GfxModel } from '@blocksuite/std/gfx';
import { AdaptiveCooldownController } from './adaptive-load-controller';
interface Distance {
horiz?: {
/**
@@ -35,6 +37,9 @@ interface Distance {
const ALIGN_THRESHOLD = 8;
const DISTRIBUTION_LINE_OFFSET = 1;
const STROKE_WIDTH = 2;
const DISTRIBUTE_ALIGN_MAX_CANDIDATES = 160;
const DISTRIBUTE_ALIGN_MAX_COST_MS = 5;
const DISTRIBUTE_ALIGN_COOLDOWN_FRAMES = 2;
export class SnapOverlay extends Overlay {
static override overlayName: string = 'snap-manager';
@@ -75,6 +80,11 @@ export class SnapOverlay extends Overlay {
vertical: [],
};
private readonly _distributeCooldown = new AdaptiveCooldownController({
cooldownFrames: DISTRIBUTE_ALIGN_COOLDOWN_FRAMES,
maxCostMs: DISTRIBUTE_ALIGN_MAX_COST_MS,
});
override clear() {
this._referenceBounds = {
vertical: [],
@@ -87,6 +97,7 @@ export class SnapOverlay extends Overlay {
};
this._distributedAlignLines = [];
this._skippedElements.clear();
this._distributeCooldown.reset();
super.clear();
}
@@ -673,13 +684,24 @@ export class SnapOverlay extends Overlay {
}
}
// point align priority is higher than distribute align
if (rst.dx === 0) {
this._alignDistributeHorizontally(rst, bound, threshold, viewport);
}
const shouldTryDistribute =
this._referenceBounds.all.length <= DISTRIBUTE_ALIGN_MAX_CANDIDATES &&
this._distributeCooldown.shouldRun();
if (rst.dy === 0) {
this._alignDistributeVertically(rst, bound, threshold, viewport);
if (shouldTryDistribute) {
const distributeStart = performance.now();
// point align priority is higher than distribute align
if (rst.dx === 0) {
this._alignDistributeHorizontally(rst, bound, threshold, viewport);
}
if (rst.dy === 0) {
this._alignDistributeVertically(rst, bound, threshold, viewport);
}
const distributeCost = performance.now() - distributeStart;
this._distributeCooldown.reportCost(distributeCost);
}
this._renderer?.refresh();
@@ -776,24 +798,26 @@ export class SnapOverlay extends Overlay {
});
const verticalBounds: Bound[] = [];
const horizBounds: Bound[] = [];
const allBounds: Bound[] = [];
const allCandidateElements = new Set<GfxModel>();
vertCandidates.forEach(candidate => {
if (skipped.has(candidate) || this._isSkippedElement(candidate)) return;
verticalBounds.push(candidate.elementBound);
allBounds.push(candidate.elementBound);
const bound = candidate.elementBound;
verticalBounds.push(bound);
allCandidateElements.add(candidate);
});
horizCandidates.forEach(candidate => {
if (skipped.has(candidate) || this._isSkippedElement(candidate)) return;
horizBounds.push(candidate.elementBound);
allBounds.push(candidate.elementBound);
const bound = candidate.elementBound;
horizBounds.push(bound);
allCandidateElements.add(candidate);
});
this._referenceBounds = {
horizontal: horizBounds,
vertical: verticalBounds,
all: allBounds,
all: [...allCandidateElements].map(element => element.elementBound),
};
}

View File

@@ -4,7 +4,12 @@ import {
} from '@blocksuite/affine-block-surface';
import { on } from '@blocksuite/affine-shared/utils';
import type { PointerEventState } from '@blocksuite/std';
import { BaseTool, MouseButton, type ToolOptions } from '@blocksuite/std/gfx';
import {
BaseTool,
createRafCoalescer,
MouseButton,
type ToolOptions,
} from '@blocksuite/std/gfx';
import { Signal } from '@preact/signals-core';
interface RestorablePresentToolOptions {
@@ -21,13 +26,30 @@ export class PanTool extends BaseTool<PanToolOption> {
private _lastPoint: [number, number] | null = null;
private _pendingDelta: [number, number] = [0, 0];
private readonly _deltaFlushCoalescer = createRafCoalescer<void>(() => {
this._flushPendingDelta();
});
readonly panning$ = new Signal<boolean>(false);
private _flushPendingDelta() {
if (this._pendingDelta[0] === 0 && this._pendingDelta[1] === 0) {
return;
}
const [deltaX, deltaY] = this._pendingDelta;
this._pendingDelta = [0, 0];
this.gfx.viewport.applyDeltaCenter(deltaX, deltaY);
}
override get allowDragWithRightButton(): boolean {
return true;
}
override dragEnd(_: PointerEventState): void {
this._deltaFlushCoalescer.flush();
this._lastPoint = null;
this.panning$.value = false;
}
@@ -43,12 +65,14 @@ export class PanTool extends BaseTool<PanToolOption> {
const deltaY = lastY - e.y;
this._lastPoint = [e.x, e.y];
viewport.applyDeltaCenter(deltaX / zoom, deltaY / zoom);
this._pendingDelta[0] += deltaX / zoom;
this._pendingDelta[1] += deltaY / zoom;
this._deltaFlushCoalescer.schedule(undefined);
}
override dragStart(e: PointerEventState): void {
this._lastPoint = [e.x, e.y];
this._pendingDelta = [0, 0];
this.panning$.value = true;
}
@@ -120,4 +144,8 @@ export class PanTool extends BaseTool<PanToolOption> {
return false;
});
}
override unmounted(): void {
this._deltaFlushCoalescer.cancel();
}
}

View File

@@ -0,0 +1,25 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
esbuild: {
target: 'es2018',
},
test: {
globalSetup: '../../../scripts/vitest-global.js',
include: ['src/__tests__/**/*.unit.spec.ts'],
testTimeout: 1000,
coverage: {
provider: 'istanbul',
reporter: ['lcov'],
reportsDirectory: '../../../.coverage/affine-gfx-pointer',
},
onConsoleLog(log, type) {
if (log.includes('lit.dev/msg/dev-mode')) {
return false;
}
console.warn(`Unexpected ${type} log`, log);
throw new Error(log);
},
environment: 'happy-dom',
},
});

View File

@@ -28,7 +28,6 @@
"@types/lodash-es": "^4.17.12",
"lit": "^3.2.0",
"lodash-es": "^4.17.23",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2",
"yjs": "^13.6.27",
"zod": "^3.25.76"
@@ -44,5 +43,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.26.1"
"version": "0.26.3"
}

View File

@@ -29,7 +29,6 @@
"@types/lodash-es": "^4.17.12",
"lit": "^3.2.0",
"lodash-es": "^4.17.23",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2",
"yjs": "^13.6.27",
"zod": "^3.25.76"
@@ -44,5 +43,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.26.1"
"version": "0.26.3"
}

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/await-thenable */
import type {
Template,
TemplateCategory,

View File

@@ -27,7 +27,6 @@
"@types/lodash-es": "^4.17.12",
"lit": "^3.2.0",
"lodash-es": "^4.17.23",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2",
"yjs": "^13.6.27",
"zod": "^3.25.76"
@@ -43,5 +42,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.26.1"
"version": "0.26.3"
}

View File

@@ -25,5 +25,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.26.1"
"version": "0.26.3"
}

View File

@@ -42,5 +42,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.26.1"
"version": "0.26.3"
}

View File

@@ -47,5 +47,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.26.1"
"version": "0.26.3"
}

View File

@@ -4,6 +4,7 @@ import type { FootNote } from '@blocksuite/affine-model';
import { CitationProvider } from '@blocksuite/affine-shared/services';
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
import { isValidUrl, normalizeUrl } from '@blocksuite/affine-shared/utils';
import { WithDisposable } from '@blocksuite/global/lit';
import {
BlockSelection,
@@ -152,7 +153,9 @@ export class AffineFootnoteNode extends WithDisposable(ShadowlessElement) {
};
private readonly _handleUrlReference = (url: string) => {
window.open(url, '_blank');
const normalizedUrl = normalizeUrl(url);
if (!normalizedUrl || !isValidUrl(normalizedUrl)) return;
window.open(normalizedUrl, '_blank', 'noopener,noreferrer');
};
private readonly _updateFootnoteAttributes = (footnote: FootNote) => {

Some files were not shown because too many files have changed in this diff Show More