Compare commits

...

28 Commits

Author SHA1 Message Date
Peng Xiao adaee0ef5f feat(component): sortable 2025-03-31 17:21:52 +08:00
EYHN baf1aad412 fix(core): fix flaky e2e test (#11308) 2025-03-31 09:10:54 +00:00
EYHN 231956fd39 feat(core): track for notifications (#11298) 2025-03-31 08:38:29 +00:00
EYHN 73c7815a6d feat(core): adjust notification style (#11296) 2025-03-31 08:38:28 +00:00
Fangdun Tsai 6850871bfb fix(editor): fix callout tests (#11301) 2025-03-31 08:37:20 +00:00
doouding 18cb4199fa fix: note should hide collapse button in presentation mode (#11292)
Fixes [BS-1003](https://linear.app/affine-design/issue/BS-1003/ppt-演示状态下-note-会显示折叠箭头)
2025-03-31 16:17:44 +08:00
EYHN 24c382d3aa feat(core): enable callout in canary (#11302) 2025-03-31 08:10:18 +00:00
pengx17 8bea31698e fix(electron): tray menu icon adapt to dark theme (#11288)
fix AF-2431
2025-03-31 07:23:01 +00:00
forehalo 94d5a42355 chore(core): allow quick export (#11295) 2025-03-31 06:58:17 +00:00
donteatfriedrice b2aa3084ec feat(editor): support to drag embed iframe from note to surface (#11267)
Close [BS-2807](https://linear.app/affine-design/issue/BS-2807/note-中与-surface-中-embed-iframe-block-互相拖动时的优化)
2025-03-31 06:23:11 +00:00
renovate 00c5f48a7d chore: bump up mime-types version to v3 (#11274)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [mime-types](https://redirect.github.com/jshttp/mime-types) | [`^2.1.35` -> `^3.0.0`](https://renovatebot.com/diffs/npm/mime-types/2.1.35/3.0.1) | [![age](https://developer.mend.io/api/mc/badges/age/npm/mime-types/3.0.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/mime-types/3.0.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/mime-types/2.1.35/3.0.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/mime-types/2.1.35/3.0.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) |

---

### Release Notes

<details>
<summary>jshttp/mime-types (mime-types)</summary>

### [`v3.0.1`](https://redirect.github.com/jshttp/mime-types/blob/HEAD/HISTORY.md#301--2025-03-26)

[Compare Source](https://redirect.github.com/jshttp/mime-types/compare/v3.0.0...v3.0.1)

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

-   deps: mime-db@1.54.0

### [`v3.0.0`](https://redirect.github.com/jshttp/mime-types/blob/HEAD/HISTORY.md#300--2024-08-31)

[Compare Source](https://redirect.github.com/jshttp/mime-types/compare/2.1.35...v3.0.0)

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

-   Drop support for node <18
-   deps: mime-db@1.53.0
-   resolve extension conflicts with mime-score ([#&#8203;119](https://redirect.github.com/jshttp/mime-types/issues/119))
    -   asc -> application/pgp-signature is now application/pgp-keys
    -   mpp -> application/vnd.ms-project is now application/dash-patch+xml
    -   ac -> application/vnd.nokia.n-gage.ac+xml is now application/pkix-attr-cert
    -   bdoc -> application/x-bdoc is now application/bdoc
    -   wmz -> application/x-msmetafile is now application/x-ms-wmz
    -   xsl -> application/xslt+xml is now application/xml
    -   wav -> audio/wave is now audio/wav
    -   rtf -> text/rtf is now application/rtf
    -   xml -> text/xml is now application/xml
    -   mp4 -> video/mp4 is now application/mp4
    -   mpg4 -> video/mp4 is now application/mp4

</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:eyJjcmVhdGVkSW5WZXIiOiIzOS4yMDcuMSIsInVwZGF0ZWRJblZlciI6IjM5LjIwNy4xIiwidGFyZ2V0QnJhbmNoIjoiY2FuYXJ5IiwibGFiZWxzIjpbImRlcGVuZGVuY2llcyJdfQ==-->
2025-03-31 05:49:12 +00:00
pengx17 1306a9733b feat(core): some enhancements to recording (#11287)
- Added a check to verify if AI is enabled before attempting to transcribe meeting recordings
- Improved error handling for empty recordings
- Fixed the recording timeout logic to ensure it only stops the correct recording session
2025-03-31 05:37:17 +00:00
CatsJuice 7c41ddb789 chore(core): update right sidebar border color (#11222) 2025-03-31 05:11:03 +00:00
forehalo 57ec22ec2e fix(core): do not pass flavor in space id (#11285) 2025-03-31 04:47:00 +00:00
CatsJuice a91193c921 fix(core): hide readwise setting if not connected (#11107) 2025-03-31 04:08:02 +00:00
CatsJuice 7477ba6d37 feat(core): support sending success feedback via MessagePort for web clipper (#11256) 2025-03-31 03:54:44 +00:00
Mirone 9f939d823e fix(editor): slash menu e2e (#11289) 2025-03-31 11:13:34 +08:00
pengx17 61b3f82bfe fix(electron): should not record affine app itself (#11277)
fix AF-2428
2025-03-29 11:56:44 +00:00
pengx17 a94bef6738 fix(core): incorrect animated icon color & sizes (#11276) 2025-03-29 11:56:43 +00:00
doodlewind dffb89c388 feat(editor): add list block turbo renderer scaffold (#11266)
This PR allows placeholder in turbo renderer to cover list block as a basic scaffold.

![image.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/lEGcysB4lFTEbCwZ8jMv/eda28656-e56e-4845-9fe6-885e70841697.png)
2025-03-29 04:49:25 +00:00
akumatus ac815142b3 refactor(core): add request time out error for ai (#11244)
### Why make this change?
Seperate front end timeout errors from server side errors.

### What changed?
- Add `RequestTimeoutError` which extends from `BaseAIError`.
- Track as `request timeout` instead of `server error`.
2025-03-29 04:27:40 +00:00
doouding ee66545ac9 fix: mind map created in page mode has incorrect style (#11265)
Fixes [BS-2878](https://linear.app/affine-design/issue/BS-2878/slashmenu插入mindmap,style没有应用上)
2025-03-29 04:13:29 +00:00
doouding fcc2ec9d66 feat: use block card to render edgeless dnd preview (#11261)
Related issue [BS-2610](https://linear.app/affine-design/issue/BS-2610/多选的拖拽:如果保护不支持预览的-block,则直接显示-icon-block-名称的方式做-fallback).

Use simpler way to render edgeless dnd preview.

![image.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/57vl0IUaypligEBYUOO0/845d43ac-27e0-45fe-8289-2e6467c59108.png)
2025-03-29 04:13:28 +00:00
yoyoyohamapi 317d3e7ea6 test(core): split and enhance copilot e2e tests (#11007)
### TL;DR

Split and enhance copilot e2e tests.

### What Changed

#### Tests Structure

The e2e tests are organized into the following categories:

1. **Basic Tests (`/basic`)**: Tests for verifying core AI capabilities including feature onboarding, authorization workflows, and basic chat interactions.
2. **Chat Interaction Tests (`/chat-with`)**: Tests for verifying the AI's interaction with various ​object types, such as attachments, images, text content, Edgeless elements, etc.
3. **AI Action Tests (`/ai-action`)**: Tests for verifying the AI's actions, such as text translation, gramma correction, etc.
4. **Insertion Tests (`/insertion`)**: Tests for verifying answer insertion functionality.

#### Tests Writing

Writing a copilot test cases is easier and clear

e.g.
```ts
test('support chat with specified doc', async ({ page, utils }) => {
  // Initialize the doc
  await focusDocTitle(page);
  await page.keyboard.insertText('Test Doc');
  await page.keyboard.press('Enter');
  await page.keyboard.insertText('EEee is a cute cat');

  await utils.chatPanel.chatWithDoc(page, 'Test Doc');

  await utils.chatPanel.makeChat(page, 'What is EEee?');
  await utils.chatPanel.waitForHistory(page, [
    {
      role: 'user',
      content: 'What is EEee?',
    },
    {
      role: 'assistant',
      status: 'success',
    },
  ]);

  const { content } = await utils.chatPanel.getLatestAssistantMessage(page);
  expect(content).toMatch(/EEee/);
});
```

#### Summary

||Cases|
|------|----|
|Before|19||
|After|151||

> Close BS-2769
2025-03-29 03:41:09 +00:00
renovate a709ed2ef1 chore: bump up linter (major) (#11272)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [eslint-import-resolver-typescript](https://redirect.github.com/import-js/eslint-import-resolver-typescript) | [`^3.7.0` -> `^4.0.0`](https://renovatebot.com/diffs/npm/eslint-import-resolver-typescript/3.8.3/4.2.5) | [![age](https://developer.mend.io/api/mc/badges/age/npm/eslint-import-resolver-typescript/4.2.5?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/eslint-import-resolver-typescript/4.2.5?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/eslint-import-resolver-typescript/3.8.3/4.2.5?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/eslint-import-resolver-typescript/3.8.3/4.2.5?slim=true)](https://docs.renovatebot.com/merge-confidence/) |
| [eslint-plugin-unicorn](https://redirect.github.com/sindresorhus/eslint-plugin-unicorn) | [`^57.0.0` -> `^58.0.0`](https://renovatebot.com/diffs/npm/eslint-plugin-unicorn/57.0.0/58.0.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/eslint-plugin-unicorn/58.0.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/eslint-plugin-unicorn/58.0.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/eslint-plugin-unicorn/57.0.0/58.0.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/eslint-plugin-unicorn/57.0.0/58.0.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) |

---

### Release Notes

<details>
<summary>import-js/eslint-import-resolver-typescript (eslint-import-resolver-typescript)</summary>

### [`v4.2.5`](https://redirect.github.com/import-js/eslint-import-resolver-typescript/blob/HEAD/CHANGELOG.md#425)

[Compare Source](https://redirect.github.com/import-js/eslint-import-resolver-typescript/compare/v4.2.4...v4.2.5)

##### Patch Changes

-   [#&#8203;410](https://redirect.github.com/import-js/eslint-import-resolver-typescript/pull/410) [`ec59d22`](https://redirect.github.com/import-js/eslint-import-resolver-typescript/commit/ec59d22fdd1ec8093dcb97da626c28ea346f41e3) Thanks [@&#8203;JounQin](https://redirect.github.com/JounQin)! - fix: absolute path aliasing should not be skipped

### [`v4.2.4`](https://redirect.github.com/import-js/eslint-import-resolver-typescript/blob/HEAD/CHANGELOG.md#424)

[Compare Source](https://redirect.github.com/import-js/eslint-import-resolver-typescript/compare/v4.2.3...v4.2.4)

##### Patch Changes

-   [#&#8203;407](https://redirect.github.com/import-js/eslint-import-resolver-typescript/pull/407) [`6b183ff`](https://redirect.github.com/import-js/eslint-import-resolver-typescript/commit/6b183fff1b42dfb1514545b91021dfa73ab4a1c5) Thanks [@&#8203;JounQin](https://redirect.github.com/JounQin)! - chore: migrate to rebranding `unrs-resolver` with new targets supported:

    -   `i686-pc-windows-msvc`
    -   `armv7-unknown-linux-musleabihf`
    -   `powerpc64le-unknown-linux-gnu`
    -   `s390x-unknown-linux-gnu`

### [`v4.2.3`](https://redirect.github.com/import-js/eslint-import-resolver-typescript/blob/HEAD/CHANGELOG.md#423)

[Compare Source](https://redirect.github.com/import-js/eslint-import-resolver-typescript/compare/v4.2.2...v4.2.3)

##### Patch Changes

-   [#&#8203;402](https://redirect.github.com/import-js/eslint-import-resolver-typescript/pull/402) [`f21bf15`](https://redirect.github.com/import-js/eslint-import-resolver-typescript/commit/f21bf152311cdaa85bdf390bba2824c56cb111da) Thanks [@&#8203;SunsetTechuila](https://redirect.github.com/SunsetTechuila)! - fix: don't resolve not implemented node modules in `bun`

    `is-bun-module` is marked as `dependency`, again, for correctness, see [`isBunImplementedNodeModule`](https://redirect.github.com/SunsetTechuila/is-bun-module#isbunimplementednodemodulemodulename-bunversion) for more details

    For `Bun` users: you don't need to install `is-bun-module` any more but `bun: true` option is still required if you're running without `bun --bun` nor [`run#bun`](https://bun.sh/docs/runtime/bunfig#run-bun-auto-alias-node-to-bun) enabled

### [`v4.2.2`](https://redirect.github.com/import-js/eslint-import-resolver-typescript/blob/HEAD/CHANGELOG.md#422)

[Compare Source](https://redirect.github.com/import-js/eslint-import-resolver-typescript/compare/v4.2.1...v4.2.2)

##### Patch Changes

-   [#&#8203;397](https://redirect.github.com/import-js/eslint-import-resolver-typescript/pull/397) [`14a7688`](https://redirect.github.com/import-js/eslint-import-resolver-typescript/commit/14a76885499cf99b0e5ea588aeb916a881c4efcb) Thanks [@&#8203;JounQin](https://redirect.github.com/JounQin)! - chore: bump `rspack-resolver` for better P'n'P support

    Now `rspack-resolver` resolves `pnpapi` natively.

### [`v4.2.1`](https://redirect.github.com/import-js/eslint-import-resolver-typescript/blob/HEAD/CHANGELOG.md#421)

[Compare Source](https://redirect.github.com/import-js/eslint-import-resolver-typescript/compare/v4.2.0...v4.2.1)

##### Patch Changes

-   [#&#8203;394](https://redirect.github.com/import-js/eslint-import-resolver-typescript/pull/394) [`9f11f6b`](https://redirect.github.com/import-js/eslint-import-resolver-typescript/commit/9f11f6bb94f1f9eae6794eea3e4624b80ceac305) Thanks [@&#8203;JounQin](https://redirect.github.com/JounQin)! - fix: don't set empty `configFile` when no `tsconfig` found

-   [#&#8203;394](https://redirect.github.com/import-js/eslint-import-resolver-typescript/pull/394) [`9f11f6b`](https://redirect.github.com/import-js/eslint-import-resolver-typescript/commit/9f11f6bb94f1f9eae6794eea3e4624b80ceac305) Thanks [@&#8203;JounQin](https://redirect.github.com/JounQin)! - chore: bump `rspack-resolver` to v1.2.0

### [`v4.2.0`](https://redirect.github.com/import-js/eslint-import-resolver-typescript/blob/HEAD/CHANGELOG.md#420)

[Compare Source](https://redirect.github.com/import-js/eslint-import-resolver-typescript/compare/v4.1.1...v4.2.0)

##### Minor Changes

-   [#&#8203;391](https://redirect.github.com/import-js/eslint-import-resolver-typescript/pull/391) [`c8121e5`](https://redirect.github.com/import-js/eslint-import-resolver-typescript/commit/c8121e5eb4ce25a79396ae75df16d35fc67acbc6) Thanks [@&#8203;JounQin](https://redirect.github.com/JounQin)! - feat: make `is-bun-module` as optional peer dependency

    Technically this is a BREAKING CHANGE, but considering we just raise out v4 recently and this only affects `bun` users, `bun --bun eslint` even works without this dependency, so I'd consider this as a minor change.

    So for `bun` users, there are three options:

    1.  install `is-bun-module` dependency manually and use `bun: true` option
    2.  run `eslint` with `bun --bun eslint` w/o `bun: true` option
    3.  enable `run#bun` in [`bunfig.toml`](https://bun.sh/docs/runtime/bunfig#run-bun-auto-alias-node-to-bun) w/o `bun: true` option

### [`v4.1.1`](https://redirect.github.com/import-js/eslint-import-resolver-typescript/blob/HEAD/CHANGELOG.md#411)

[Compare Source](https://redirect.github.com/import-js/eslint-import-resolver-typescript/compare/v4.1.0...v4.1.1)

##### Patch Changes

-   [#&#8203;389](https://redirect.github.com/import-js/eslint-import-resolver-typescript/pull/389) [`1b97d8a`](https://redirect.github.com/import-js/eslint-import-resolver-typescript/commit/1b97d8a5913e15bdfcf5f64152e8a4173b18dab1) Thanks [@&#8203;JounQin](https://redirect.github.com/JounQin)! - fix: should prefer `module.isBuiltin` when `process.versions.bun` available

### [`v4.1.0`](https://redirect.github.com/import-js/eslint-import-resolver-typescript/blob/HEAD/CHANGELOG.md#410)

[Compare Source](https://redirect.github.com/import-js/eslint-import-resolver-typescript/compare/v4.0.0...v4.1.0)

##### Minor Changes

-   [#&#8203;387](https://redirect.github.com/import-js/eslint-import-resolver-typescript/pull/387) [`ef5cd10`](https://redirect.github.com/import-js/eslint-import-resolver-typescript/commit/ef5cd1083207d560b35694b99ccfefa4a1234acb) Thanks [@&#8203;JounQin](https://redirect.github.com/JounQin)! - feat: add a new `bun?: boolean` option for `bun` users - close [#&#8203;386](https://redirect.github.com/import-js/eslint-import-resolver-typescript/issues/386)

    `process.versions.bun` is unavailable even with `bun eslint` due to its own design,
    but checking `bun` modules for non-bun users is incorrect behavior and just wasting time,
    so a new option is added for such case, you can still run with `bun --bun eslint` without this option enabled

### [`v4.0.0`](https://redirect.github.com/import-js/eslint-import-resolver-typescript/blob/HEAD/CHANGELOG.md#400)

[Compare Source](https://redirect.github.com/import-js/eslint-import-resolver-typescript/compare/v3.10.0...v4.0.0)

##### Major Changes

-   [#&#8203;368](https://redirect.github.com/import-js/eslint-import-resolver-typescript/pull/368) [`2fd7c2e`](https://redirect.github.com/import-js/eslint-import-resolver-typescript/commit/2fd7c2ea63f30c9990e19a52dbd07fd8131558e9) Thanks [@&#8203;JounQin](https://redirect.github.com/JounQin)! - feat!: rewrite, speed up by using [`rspack-resolver`](https://redirect.github.com/unrs/rspack-resolver) which supports `references` natively under the hood

    BREAKING CHANGES:

    -   drop Node 14 support, Node `^16.17.0 || >=18.6` is now required
    -   `alwaysTryTypes` is enabled by default, you can set it as `false` to opt-out
    -   array type of `project` is discouraged but still supported, single `project` with `references` are encouraged for better performance, you can enable `noWarnOnMultipleProjects` option to supress the warning message
    -   root `tsconfig.json` or `jsconfig.json` will be used automatically if no `project` provided

### [`v3.10.0`](https://redirect.github.com/import-js/eslint-import-resolver-typescript/releases/tag/v3.10.0)

[Compare Source](https://redirect.github.com/import-js/eslint-import-resolver-typescript/compare/v3.9.1...v3.10.0)

##### Minor Changes

-   [#&#8203;413](https://redirect.github.com/import-js/eslint-import-resolver-typescript/pull/413) [`89c2795`](https://redirect.github.com/import-js/eslint-import-resolver-typescript/commit/89c2795cde0ddf0c38c941ee4cf5d4ce1f3ac842) Thanks [@&#8203;JounQin](https://redirect.github.com/JounQin)! - chore: housekeeping, bump all (dev) deps

    Migrate `rspack-resolver` to rebranding [`unrs-resolver`](https://redirect.github.com/unrs/unrs-resolver) for more targets support and other bug fixes

**Full Changelog**: https://github.com/import-js/eslint-import-resolver-typescript/compare/v3.9.1...v3.10.0

### [`v3.9.1`](https://redirect.github.com/import-js/eslint-import-resolver-typescript/blob/HEAD/CHANGELOG.md#391)

[Compare Source](https://redirect.github.com/import-js/eslint-import-resolver-typescript/compare/v3.9.0...v3.9.1)

##### Patch Changes

-   [#&#8203;382](https://redirect.github.com/import-js/eslint-import-resolver-typescript/pull/382) [`4a9176e`](https://redirect.github.com/import-js/eslint-import-resolver-typescript/commit/4a9176e6e2b6013dc24b5634aea42feebd324e41) Thanks [@&#8203;JounQin](https://redirect.github.com/JounQin)! - fix: use [`rspack-resolver`](https://redirect.github.com/unrs/rspack-resolver) fork for pnp support

### [`v3.9.0`](https://redirect.github.com/import-js/eslint-import-resolver-typescript/blob/HEAD/CHANGELOG.md#390)

[Compare Source](https://redirect.github.com/import-js/eslint-import-resolver-typescript/compare/v3.8.7...v3.9.0)

##### Minor Changes

-   [#&#8203;379](https://redirect.github.com/import-js/eslint-import-resolver-typescript/pull/379) [`6814443`](https://redirect.github.com/import-js/eslint-import-resolver-typescript/commit/681444336fc66104b9b490838a67ea7bf8ac8b61) Thanks [@&#8203;JounQin](https://redirect.github.com/JounQin)! - feat: migrate `enhanced-resolve` to `oxc-resolver`

### [`v3.8.7`](https://redirect.github.com/import-js/eslint-import-resolver-typescript/blob/HEAD/CHANGELOG.md#387)

[Compare Source](https://redirect.github.com/import-js/eslint-import-resolver-typescript/compare/v3.8.6...v3.8.7)

##### Patch Changes

-   [#&#8203;377](https://redirect.github.com/import-js/eslint-import-resolver-typescript/pull/377) [`a14fdd9`](https://redirect.github.com/import-js/eslint-import-resolver-typescript/commit/a14fdd95011c4c09b74f71854410f684c0f04bc5) Thanks [@&#8203;carlocorradini](https://redirect.github.com/carlocorradini)! - fix: include mapper with no files and force non-dynamic projects to use absolute paths

### [`v3.8.6`](https://redirect.github.com/import-js/eslint-import-resolver-typescript/blob/HEAD/CHANGELOG.md#386)

[Compare Source](https://redirect.github.com/import-js/eslint-import-resolver-typescript/compare/v3.8.5...v3.8.6)

##### Patch Changes

-   [#&#8203;374](https://redirect.github.com/import-js/eslint-import-resolver-typescript/pull/374) [`c9d5ab0`](https://redirect.github.com/import-js/eslint-import-resolver-typescript/commit/c9d5ab0fa963bd891b6f2ae312ae3ec10a397b7c) Thanks [@&#8203;JounQin](https://redirect.github.com/JounQin)! - fix: add support for importing with .js extension as tsx importee

### [`v3.8.5`](https://redirect.github.com/import-js/eslint-import-resolver-typescript/blob/HEAD/CHANGELOG.md#385)

[Compare Source](https://redirect.github.com/import-js/eslint-import-resolver-typescript/compare/v3.8.4...v3.8.5)

##### Patch Changes

-   [#&#8203;372](https://redirect.github.com/import-js/eslint-import-resolver-typescript/pull/372) [`366eeaf`](https://redirect.github.com/import-js/eslint-import-resolver-typescript/commit/366eeaf8ba87adf7c2e165b0a73406292c002ad9) Thanks [@&#8203;carlocorradini](https://redirect.github.com/carlocorradini)! - fix: if file has no corresponding mapper function, apply all of them, starting with the nearest one.

### [`v3.8.4`](https://redirect.github.com/import-js/eslint-import-resolver-typescript/blob/HEAD/CHANGELOG.md#384)

[Compare Source](https://redirect.github.com/import-js/eslint-import-resolver-typescript/compare/v3.8.3...v3.8.4)

##### Patch Changes

-   [#&#8203;370](https://redirect.github.com/import-js/eslint-import-resolver-typescript/pull/370) [`c940785`](https://redirect.github.com/import-js/eslint-import-resolver-typescript/commit/c94078504cfb6fd17b775c53d268962a56a2d118) Thanks [@&#8203;JounQin](https://redirect.github.com/JounQin)! - fix: support multiple matching ts paths

</details>

<details>
<summary>sindresorhus/eslint-plugin-unicorn (eslint-plugin-unicorn)</summary>

### [`v58.0.0`](https://redirect.github.com/sindresorhus/eslint-plugin-unicorn/releases/tag/v58.0.0)

[Compare Source](https://redirect.github.com/sindresorhus/eslint-plugin-unicorn/compare/v57.0.0...v58.0.0)

##### Potentially breaking

-   Update `engines.node` in package.json to match real compatibility ([#&#8203;2581](https://redirect.github.com/sindresorhus/eslint-plugin-unicorn/issues/2581))  [`e48a620`](https://redirect.github.com/sindresorhus/eslint-plugin-unicorn/commit/e48a620)

##### Improvements

-   `escape-case`: Add [case option](https://redirect.github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/escape-case.md#options) ([#&#8203;2559](https://redirect.github.com/sindresorhus/eslint-plugin-unicorn/issues/2559))  [`0f6048c`](https://redirect.github.com/sindresorhus/eslint-plugin-unicorn/commit/0f6048c)
-   `number-literal-case`: Add [`hexadecimalValue` option](https://redirect.github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/number-literal-case.md#hexadecimalvalue) ([#&#8203;2559](https://redirect.github.com/sindresorhus/eslint-plugin-unicorn/issues/2559))  [`0f6048c`](https://redirect.github.com/sindresorhus/eslint-plugin-unicorn/commit/0f6048c)
-   `prevent-abbreviations`: Preserve `iOS` ([#&#8203;2560](https://redirect.github.com/sindresorhus/eslint-plugin-unicorn/issues/2560))  [`e8798da`](https://redirect.github.com/sindresorhus/eslint-plugin-unicorn/commit/e8798da)

##### Fixes

-   `no-unnecessary-polyfills`: Fix browserslist field name ([#&#8203;2603](https://redirect.github.com/sindresorhus/eslint-plugin-unicorn/issues/2603))  [`1a4c76f`](https://redirect.github.com/sindresorhus/eslint-plugin-unicorn/commit/1a4c76f)
-   `no-unnecessary-polyfills`: Fix crash on checking `es6-error` module ([#&#8203;2582](https://redirect.github.com/sindresorhus/eslint-plugin-unicorn/issues/2582))  [`66de41a`](https://redirect.github.com/sindresorhus/eslint-plugin-unicorn/commit/66de41a)
-   `no-accessor-recursion`: Fix exception when used in CommonJS ([#&#8203;2574](https://redirect.github.com/sindresorhus/eslint-plugin-unicorn/issues/2574))  [`ca1e432`](https://redirect.github.com/sindresorhus/eslint-plugin-unicorn/commit/ca1e432)

***

</details>

---

### Configuration

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

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

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

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

---

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

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/toeverything/AFFiNE).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4yMDcuMSIsInVwZGF0ZWRJblZlciI6IjM5LjIwNy4xIiwidGFyZ2V0QnJhbmNoIjoiY2FuYXJ5IiwibGFiZWxzIjpbImRlcGVuZGVuY2llcyJdfQ==-->
2025-03-28 14:50:49 +00:00
forehalo 1b93d3d8d2 chore(server): bump nestjs and express (#11259) 2025-03-28 14:00:19 +00:00
renovate efab5d4270 chore: bump up all non-major dependencies (#11215)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence | Type | Update |
|---|---|---|---|---|---|---|---|
| [@aws-sdk/client-s3](https://redirect.github.com/aws/aws-sdk-js-v3/tree/main/clients/client-s3) ([source](https://redirect.github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-s3)) | [`3.775.0` -> `3.777.0`](https://renovatebot.com/diffs/npm/@aws-sdk%2fclient-s3/3.775.0/3.777.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@aws-sdk%2fclient-s3/3.777.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@aws-sdk%2fclient-s3/3.777.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@aws-sdk%2fclient-s3/3.775.0/3.777.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@aws-sdk%2fclient-s3/3.775.0/3.777.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | minor |
| [@graphql-codegen/typescript](https://redirect.github.com/dotansimha/graphql-code-generator) ([source](https://redirect.github.com/dotansimha/graphql-code-generator/tree/HEAD/packages/plugins/typescript/typescript)) | [`4.1.5` -> `4.1.6`](https://renovatebot.com/diffs/npm/@graphql-codegen%2ftypescript/4.1.5/4.1.6) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@graphql-codegen%2ftypescript/4.1.6?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@graphql-codegen%2ftypescript/4.1.6?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@graphql-codegen%2ftypescript/4.1.5/4.1.6?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@graphql-codegen%2ftypescript/4.1.5/4.1.6?slim=true)](https://docs.renovatebot.com/merge-confidence/) | devDependencies | patch |
| [@graphql-codegen/typescript-operations](https://redirect.github.com/dotansimha/graphql-code-generator) ([source](https://redirect.github.com/dotansimha/graphql-code-generator/tree/HEAD/packages/plugins/typescript/operations)) | [`4.5.1` -> `4.6.0`](https://renovatebot.com/diffs/npm/@graphql-codegen%2ftypescript-operations/4.5.1/4.6.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@graphql-codegen%2ftypescript-operations/4.6.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@graphql-codegen%2ftypescript-operations/4.6.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@graphql-codegen%2ftypescript-operations/4.5.1/4.6.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@graphql-codegen%2ftypescript-operations/4.5.1/4.6.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | devDependencies | minor |
| [@sentry/esbuild-plugin](https://redirect.github.com/getsentry/sentry-javascript-bundler-plugins/tree/main/packages/esbuild-plugin) ([source](https://redirect.github.com/getsentry/sentry-javascript-bundler-plugins)) | [`3.2.2` -> `3.2.4`](https://renovatebot.com/diffs/npm/@sentry%2fesbuild-plugin/3.2.2/3.2.4) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@sentry%2fesbuild-plugin/3.2.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@sentry%2fesbuild-plugin/3.2.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@sentry%2fesbuild-plugin/3.2.2/3.2.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@sentry%2fesbuild-plugin/3.2.2/3.2.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | devDependencies | patch |
| [@sentry/react](https://redirect.github.com/getsentry/sentry-javascript/tree/master/packages/react) ([source](https://redirect.github.com/getsentry/sentry-javascript)) | [`9.9.0` -> `9.10.0`](https://renovatebot.com/diffs/npm/@sentry%2freact/9.9.0/9.10.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@sentry%2freact/9.10.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@sentry%2freact/9.10.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@sentry%2freact/9.9.0/9.10.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@sentry%2freact/9.9.0/9.10.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | minor |
| [@sentry/react](https://redirect.github.com/getsentry/sentry-javascript/tree/master/packages/react) ([source](https://redirect.github.com/getsentry/sentry-javascript)) | [`9.9.0` -> `9.10.0`](https://renovatebot.com/diffs/npm/@sentry%2freact/9.9.0/9.10.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@sentry%2freact/9.10.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@sentry%2freact/9.10.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@sentry%2freact/9.9.0/9.10.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@sentry%2freact/9.9.0/9.10.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | devDependencies | minor |
| [@sentry/webpack-plugin](https://redirect.github.com/getsentry/sentry-javascript-bundler-plugins/tree/main/packages/webpack-plugin) ([source](https://redirect.github.com/getsentry/sentry-javascript-bundler-plugins)) | [`3.2.2` -> `3.2.4`](https://renovatebot.com/diffs/npm/@sentry%2fwebpack-plugin/3.2.2/3.2.4) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@sentry%2fwebpack-plugin/3.2.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@sentry%2fwebpack-plugin/3.2.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@sentry%2fwebpack-plugin/3.2.2/3.2.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@sentry%2fwebpack-plugin/3.2.2/3.2.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | patch |
| [@slack/web-api](https://tools.slack.dev/node-slack-sdk/web-api) ([source](https://redirect.github.com/slackapi/node-slack-sdk)) | [`7.9.0` -> `7.9.1`](https://renovatebot.com/diffs/npm/@slack%2fweb-api/7.9.0/7.9.1) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@slack%2fweb-api/7.9.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@slack%2fweb-api/7.9.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@slack%2fweb-api/7.9.0/7.9.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@slack%2fweb-api/7.9.0/7.9.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | patch |
| [@storybook/addon-essentials](https://redirect.github.com/storybookjs/storybook/tree/next/code/addons/essentials) ([source](https://redirect.github.com/storybookjs/storybook/tree/HEAD/code/addons/essentials)) | [`8.6.9` -> `8.6.11`](https://renovatebot.com/diffs/npm/@storybook%2faddon-essentials/8.6.9/8.6.11) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@storybook%2faddon-essentials/8.6.11?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@storybook%2faddon-essentials/8.6.11?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@storybook%2faddon-essentials/8.6.9/8.6.11?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@storybook%2faddon-essentials/8.6.9/8.6.11?slim=true)](https://docs.renovatebot.com/merge-confidence/) | devDependencies | patch |
| [@storybook/addon-interactions](https://redirect.github.com/storybookjs/storybook/tree/next/code/addons/interactions) ([source](https://redirect.github.com/storybookjs/storybook/tree/HEAD/code/addons/interactions)) | [`8.6.9` -> `8.6.11`](https://renovatebot.com/diffs/npm/@storybook%2faddon-interactions/8.6.9/8.6.11) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@storybook%2faddon-interactions/8.6.11?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@storybook%2faddon-interactions/8.6.11?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@storybook%2faddon-interactions/8.6.9/8.6.11?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@storybook%2faddon-interactions/8.6.9/8.6.11?slim=true)](https://docs.renovatebot.com/merge-confidence/) | devDependencies | patch |
| [@storybook/addon-links](https://redirect.github.com/storybookjs/storybook/tree/next/code/addons/links) ([source](https://redirect.github.com/storybookjs/storybook/tree/HEAD/code/addons/links)) | [`8.6.9` -> `8.6.11`](https://renovatebot.com/diffs/npm/@storybook%2faddon-links/8.6.9/8.6.11) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@storybook%2faddon-links/8.6.11?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@storybook%2faddon-links/8.6.11?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@storybook%2faddon-links/8.6.9/8.6.11?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@storybook%2faddon-links/8.6.9/8.6.11?slim=true)](https://docs.renovatebot.com/merge-confidence/) | devDependencies | patch |
| [@storybook/addon-mdx-gfm](https://redirect.github.com/storybookjs/storybook/tree/next/code/addons/gfm) ([source](https://redirect.github.com/storybookjs/storybook/tree/HEAD/code/addons/gfm)) | [`8.6.9` -> `8.6.11`](https://renovatebot.com/diffs/npm/@storybook%2faddon-mdx-gfm/8.6.9/8.6.11) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@storybook%2faddon-mdx-gfm/8.6.11?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@storybook%2faddon-mdx-gfm/8.6.11?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@storybook%2faddon-mdx-gfm/8.6.9/8.6.11?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@storybook%2faddon-mdx-gfm/8.6.9/8.6.11?slim=true)](https://docs.renovatebot.com/merge-confidence/) | devDependencies | patch |
| [@storybook/react](https://redirect.github.com/storybookjs/storybook/tree/next/code/renderers/react) ([source](https://redirect.github.com/storybookjs/storybook/tree/HEAD/code/renderers/react)) | [`8.6.9` -> `8.6.11`](https://renovatebot.com/diffs/npm/@storybook%2freact/8.6.9/8.6.11) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@storybook%2freact/8.6.11?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@storybook%2freact/8.6.11?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@storybook%2freact/8.6.9/8.6.11?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@storybook%2freact/8.6.9/8.6.11?slim=true)](https://docs.renovatebot.com/merge-confidence/) | devDependencies | patch |
| [@storybook/react-vite](https://redirect.github.com/storybookjs/storybook/tree/next/code/frameworks/react-vite) ([source](https://redirect.github.com/storybookjs/storybook/tree/HEAD/code/frameworks/react-vite)) | [`8.6.9` -> `8.6.11`](https://renovatebot.com/diffs/npm/@storybook%2freact-vite/8.6.9/8.6.11) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@storybook%2freact-vite/8.6.11?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@storybook%2freact-vite/8.6.11?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@storybook%2freact-vite/8.6.9/8.6.11?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@storybook%2freact-vite/8.6.9/8.6.11?slim=true)](https://docs.renovatebot.com/merge-confidence/) | devDependencies | patch |
| [@types/mixpanel-browser](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/mixpanel-browser) ([source](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/mixpanel-browser)) | [`2.51.0` -> `2.54.0`](https://renovatebot.com/diffs/npm/@types%2fmixpanel-browser/2.51.0/2.54.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@types%2fmixpanel-browser/2.54.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@types%2fmixpanel-browser/2.54.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@types%2fmixpanel-browser/2.51.0/2.54.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@types%2fmixpanel-browser/2.51.0/2.54.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | devDependencies | minor |
| [@types/node](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node) ([source](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node)) | [`22.13.13` -> `22.13.14`](https://renovatebot.com/diffs/npm/@types%2fnode/22.13.13/22.13.14) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@types%2fnode/22.13.14?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@types%2fnode/22.13.14?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@types%2fnode/22.13.13/22.13.14?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@types%2fnode/22.13.13/22.13.14?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | patch |
| [@types/node](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node) ([source](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node)) | [`22.13.13` -> `22.13.14`](https://renovatebot.com/diffs/npm/@types%2fnode/22.13.13/22.13.14) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@types%2fnode/22.13.14?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@types%2fnode/22.13.14?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@types%2fnode/22.13.13/22.13.14?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@types%2fnode/22.13.13/22.13.14?slim=true)](https://docs.renovatebot.com/merge-confidence/) | devDependencies | patch |
| [@types/semver](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/semver) ([source](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/semver)) | [`7.5.8` -> `7.7.0`](https://renovatebot.com/diffs/npm/@types%2fsemver/7.5.8/7.7.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@types%2fsemver/7.7.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@types%2fsemver/7.7.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@types%2fsemver/7.5.8/7.7.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@types%2fsemver/7.5.8/7.7.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | devDependencies | minor |
| [ai](https://sdk.vercel.ai/docs) ([source](https://redirect.github.com/vercel/ai)) | [`4.2.5` -> `4.2.8`](https://renovatebot.com/diffs/npm/ai/4.2.5/4.2.8) | [![age](https://developer.mend.io/api/mc/badges/age/npm/ai/4.2.8?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/ai/4.2.8?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/ai/4.2.5/4.2.8?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/ai/4.2.5/4.2.8?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | patch |
| [apollographql/apollo-ios](https://redirect.github.com/apollographql/apollo-ios) | `from: "1.18.0"` -> `from: "1.19.0"` | [![age](https://developer.mend.io/api/mc/badges/age/git-tags/https:%2f%2fgithub.com%2fapollographql%2fapollo-ios.git/1.19.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/git-tags/https:%2f%2fgithub.com%2fapollographql%2fapollo-ios.git/1.19.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/git-tags/https:%2f%2fgithub.com%2fapollographql%2fapollo-ios.git/1.18.0/1.19.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/git-tags/https:%2f%2fgithub.com%2fapollographql%2fapollo-ios.git/1.18.0/1.19.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) |  | minor |
| [apollographql/apollo-ios](https://redirect.github.com/apollographql/apollo-ios) | `1.18.0` -> `1.19.0` | [![age](https://developer.mend.io/api/mc/badges/age/git-tags/https:%2f%2fgithub.com%2fapollographql%2fapollo-ios/1.19.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/git-tags/https:%2f%2fgithub.com%2fapollographql%2fapollo-ios/1.19.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/git-tags/https:%2f%2fgithub.com%2fapollographql%2fapollo-ios/1.18.0/1.19.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/git-tags/https:%2f%2fgithub.com%2fapollographql%2fapollo-ios/1.18.0/1.19.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) |  | minor |
| [bullmq](https://bullmq.io/) ([source](https://redirect.github.com/taskforcesh/bullmq)) | [`5.44.4` -> `5.45.0`](https://renovatebot.com/diffs/npm/bullmq/5.44.4/5.45.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/bullmq/5.45.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/bullmq/5.45.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/bullmq/5.44.4/5.45.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/bullmq/5.44.4/5.45.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | minor |
| [electron](https://redirect.github.com/electron/electron) | [`35.1.0` -> `35.1.2`](https://renovatebot.com/diffs/npm/electron/35.1.0/35.1.2) | [![age](https://developer.mend.io/api/mc/badges/age/npm/electron/35.1.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/electron/35.1.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/electron/35.1.0/35.1.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/electron/35.1.0/35.1.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | devDependencies | patch |
| [electron-log](https://redirect.github.com/megahertz/electron-log) | [`5.3.2` -> `5.3.3`](https://renovatebot.com/diffs/npm/electron-log/5.3.2/5.3.3) | [![age](https://developer.mend.io/api/mc/badges/age/npm/electron-log/5.3.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/electron-log/5.3.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/electron-log/5.3.2/5.3.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/electron-log/5.3.2/5.3.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | devDependencies | patch |
| [eventsource-parser](https://redirect.github.com/rexxars/eventsource-parser) | [`3.0.0` -> `3.0.1`](https://renovatebot.com/diffs/npm/eventsource-parser/3.0.0/3.0.1) | [![age](https://developer.mend.io/api/mc/badges/age/npm/eventsource-parser/3.0.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/eventsource-parser/3.0.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/eventsource-parser/3.0.0/3.0.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/eventsource-parser/3.0.0/3.0.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | patch |
| [mixpanel-browser](https://redirect.github.com/mixpanel/mixpanel-js) | [`2.61.2` -> `2.62.0`](https://renovatebot.com/diffs/npm/mixpanel-browser/2.61.2/2.62.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/mixpanel-browser/2.62.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/mixpanel-browser/2.62.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/mixpanel-browser/2.61.2/2.62.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/mixpanel-browser/2.61.2/2.62.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | minor |
| [nestjs-cls](https://papooch.github.io/nestjs-cls/) ([source](https://redirect.github.com/Papooch/nestjs-cls)) | [`5.4.1` -> `5.4.2`](https://renovatebot.com/diffs/npm/nestjs-cls/5.4.1/5.4.2) | [![age](https://developer.mend.io/api/mc/badges/age/npm/nestjs-cls/5.4.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/nestjs-cls/5.4.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/nestjs-cls/5.4.1/5.4.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/nestjs-cls/5.4.1/5.4.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | patch |
| [once_cell](https://redirect.github.com/matklad/once_cell) | `1.21.1` -> `1.21.2` | [![age](https://developer.mend.io/api/mc/badges/age/crate/once_cell/1.21.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/crate/once_cell/1.21.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/crate/once_cell/1.21.1/1.21.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/crate/once_cell/1.21.1/1.21.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | workspace.dependencies | patch |
| [openai](https://redirect.github.com/openai/openai-node) | [`4.89.0` -> `4.90.0`](https://renovatebot.com/diffs/npm/openai/4.89.0/4.90.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/openai/4.90.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/openai/4.90.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/openai/4.89.0/4.90.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/openai/4.89.0/4.90.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | minor |
| [sonner](https://sonner.emilkowal.ski/) ([source](https://redirect.github.com/emilkowalski/sonner)) | [`2.0.1` -> `2.0.2`](https://renovatebot.com/diffs/npm/sonner/2.0.1/2.0.2) | [![age](https://developer.mend.io/api/mc/badges/age/npm/sonner/2.0.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/sonner/2.0.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/sonner/2.0.1/2.0.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/sonner/2.0.1/2.0.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | patch |
| [storybook](https://redirect.github.com/storybookjs/storybook/tree/next/code/lib/cli) ([source](https://redirect.github.com/storybookjs/storybook/tree/HEAD/code/lib/cli)) | [`8.6.9` -> `8.6.11`](https://renovatebot.com/diffs/npm/storybook/8.6.9/8.6.11) | [![age](https://developer.mend.io/api/mc/badges/age/npm/storybook/8.6.11?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/storybook/8.6.11?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/storybook/8.6.9/8.6.11?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/storybook/8.6.9/8.6.11?slim=true)](https://docs.renovatebot.com/merge-confidence/) | devDependencies | patch |
| [swiftlang/swift-cmark](https://redirect.github.com/swiftlang/swift-cmark) | `from: "0.4.0"` -> `from: "0.5.0"` | [![age](https://developer.mend.io/api/mc/badges/age/git-tags/https:%2f%2fgithub.com%2fswiftlang%2fswift-cmark/0.5.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/git-tags/https:%2f%2fgithub.com%2fswiftlang%2fswift-cmark/0.5.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/git-tags/https:%2f%2fgithub.com%2fswiftlang%2fswift-cmark/0.4.0/0.5.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/git-tags/https:%2f%2fgithub.com%2fswiftlang%2fswift-cmark/0.4.0/0.5.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) |  | minor |
| [undici](https://undici.nodejs.org) ([source](https://redirect.github.com/nodejs/undici)) | [`7.5.0` -> `7.6.0`](https://renovatebot.com/diffs/npm/undici/7.5.0/7.6.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/undici/7.6.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/undici/7.6.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/undici/7.5.0/7.6.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/undici/7.5.0/7.6.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | minor |
| [webm-muxer](https://redirect.github.com/Vanilagy/webm-muxer) | [`5.1.0` -> `5.1.1`](https://renovatebot.com/diffs/npm/webm-muxer/5.1.0/5.1.1) | [![age](https://developer.mend.io/api/mc/badges/age/npm/webm-muxer/5.1.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/webm-muxer/5.1.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/webm-muxer/5.1.0/5.1.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/webm-muxer/5.1.0/5.1.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | patch |
| [webpack-dev-server](https://redirect.github.com/webpack/webpack-dev-server) | [`5.2.0` -> `5.2.1`](https://renovatebot.com/diffs/npm/webpack-dev-server/5.2.0/5.2.1) | [![age](https://developer.mend.io/api/mc/badges/age/npm/webpack-dev-server/5.2.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/webpack-dev-server/5.2.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/webpack-dev-server/5.2.0/5.2.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/webpack-dev-server/5.2.0/5.2.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | dependencies | patch |
| [yarn](https://redirect.github.com/yarnpkg/berry) ([source](https://redirect.github.com/yarnpkg/berry/tree/HEAD/packages/yarnpkg-cli)) | [`4.7.0` -> `4.8.0`](https://renovatebot.com/diffs/npm/yarn/4.7.0/4.8.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@yarnpkg%2fcli/4.8.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@yarnpkg%2fcli/4.8.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@yarnpkg%2fcli/4.7.0/4.8.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@yarnpkg%2fcli/4.7.0/4.8.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | packageManager | minor |

---

### Release Notes

<details>
<summary>aws/aws-sdk-js-v3 (@&#8203;aws-sdk/client-s3)</summary>

### [`v3.777.0`](https://redirect.github.com/aws/aws-sdk-js-v3/blob/HEAD/clients/client-s3/CHANGELOG.md#37770-2025-03-27)

[Compare Source](https://redirect.github.com/aws/aws-sdk-js-v3/compare/v3.775.0...v3.777.0)

**Note:** Version bump only for package [@&#8203;aws-sdk/client-s3](https://redirect.github.com/aws-sdk/client-s3)

</details>

<details>
<summary>dotansimha/graphql-code-generator (@&#8203;graphql-codegen/typescript)</summary>

### [`v4.1.6`](https://redirect.github.com/dotansimha/graphql-code-generator/blob/HEAD/packages/plugins/typescript/typescript/CHANGELOG.md#416)

[Compare Source](https://redirect.github.com/dotansimha/graphql-code-generator/compare/@graphql-codegen/typescript@4.1.5...@graphql-codegen/typescript@4.1.6)

##### Patch Changes

-   Updated dependencies \[[`f6909d1`](https://redirect.github.com/dotansimha/graphql-code-generator/commit/f6909d1797c15b79a0afb7ec089471763a485bfc)]:
    -   [@&#8203;graphql-codegen/visitor-plugin-common](https://redirect.github.com/graphql-codegen/visitor-plugin-common)[@&#8203;5](https://redirect.github.com/5).8.0

</details>

<details>
<summary>dotansimha/graphql-code-generator (@&#8203;graphql-codegen/typescript-operations)</summary>

### [`v4.6.0`](https://redirect.github.com/dotansimha/graphql-code-generator/blob/HEAD/packages/plugins/typescript/operations/CHANGELOG.md#460)

[Compare Source](https://redirect.github.com/dotansimha/graphql-code-generator/compare/@graphql-codegen/typescript-operations@4.5.1...@graphql-codegen/typescript-operations@4.6.0)

##### Minor Changes

-   [#&#8203;10323](https://redirect.github.com/dotansimha/graphql-code-generator/pull/10323) [`f3cf4df`](https://redirect.github.com/dotansimha/graphql-code-generator/commit/f3cf4df358a896c5df0a7d8909c2fbf192e10c01) Thanks [@&#8203;eddeee888](https://redirect.github.com/eddeee888)! - Add support for `nullability.errorHandlingClient`. This allows clients to get stronger types with [semantic nullability](https://redirect.github.com/graphql/graphql-wg/blob/main/rfcs/SemanticNullability.md)-enabled schemas.

##### Patch Changes

-   Updated dependencies \[[`f6909d1`](https://redirect.github.com/dotansimha/graphql-code-generator/commit/f6909d1797c15b79a0afb7ec089471763a485bfc)]:
    -   [@&#8203;graphql-codegen/visitor-plugin-common](https://redirect.github.com/graphql-codegen/visitor-plugin-common)[@&#8203;5](https://redirect.github.com/5).8.0
    -   [@&#8203;graphql-codegen/typescript](https://redirect.github.com/graphql-codegen/typescript)[@&#8203;4](https://redirect.github.com/4).1.6

</details>

<details>
<summary>getsentry/sentry-javascript-bundler-plugins (@&#8203;sentry/esbuild-plugin)</summary>

### [`v3.2.4`](https://redirect.github.com/getsentry/sentry-javascript-bundler-plugins/blob/HEAD/CHANGELOG.md#324)

[Compare Source](https://redirect.github.com/getsentry/sentry-javascript-bundler-plugins/compare/3.2.3...3.2.4)

-   Revert "feat(core): Use path instead of debug IDs as artifact names for debug ID upload ([#&#8203;700](https://redirect.github.com/getsentry/sentry-javascript-bundler-plugins/issues/700))" ([#&#8203;709](https://redirect.github.com/getsentry/sentry-javascript-bundler-plugins/issues/709))
-   ref: Remove deprecated use of `useArtifacBundles` ([#&#8203;707](https://redirect.github.com/getsentry/sentry-javascript-bundler-plugins/issues/707))

### [`v3.2.3`](https://redirect.github.com/getsentry/sentry-javascript-bundler-plugins/blob/HEAD/CHANGELOG.md#323)

[Compare Source](https://redirect.github.com/getsentry/sentry-javascript-bundler-plugins/compare/3.2.2...3.2.3)

-   feat(core): Use path instead of debug IDs as artifact names for debug ID upload ([#&#8203;700](https://redirect.github.com/getsentry/sentry-javascript-bundler-plugins/issues/700))
-   feat(webpack): Primarily use `contentHash` for debug ID hash ([#&#8203;702](https://redirect.github.com/getsentry/sentry-javascript-bundler-plugins/issues/702))
-   feat: Detect Vercel commits and env ([#&#8203;694](https://redirect.github.com/getsentry/sentry-javascript-bundler-plugins/issues/694))
-   feat: Default to automatically setting commits on release ([#&#8203;692](https://redirect.github.com/getsentry/sentry-javascript-bundler-plugins/issues/692))

</details>

<details>
<summary>getsentry/sentry-javascript (@&#8203;sentry/react)</summary>

### [`v9.10.0`](https://redirect.github.com/getsentry/sentry-javascript/releases/tag/9.10.0)

[Compare Source](https://redirect.github.com/getsentry/sentry-javascript/compare/9.9.0...9.10.0)

##### Important Changes

-   **feat: Add support for logs**

    -   feat(node): Add logging public APIs to Node SDKs ([#&#8203;15764](https://redirect.github.com/getsentry/sentry-javascript/pull/15764))
    -   feat(core): Add support for `beforeSendLog` ([#&#8203;15814](https://redirect.github.com/getsentry/sentry-javascript/pull/15814))
    -   feat(core): Add support for parameterizing logs ([#&#8203;15812](https://redirect.github.com/getsentry/sentry-javascript/pull/15812))
    -   fix: Remove critical log severity level ([#&#8203;15824](https://redirect.github.com/getsentry/sentry-javascript/pull/15824))

    All JavaScript SDKs other than `@sentry/cloudflare` and `@sentry/deno` now support sending logs via dedicated methods as part of Sentry's [upcoming logging product](https://redirect.github.com/getsentry/sentry/discussions/86804).

    Logging is gated by an experimental option, `_experiments.enableLogs`.

    ```js
    Sentry.init({
      dsn: 'PUBLIC_DSN',
      // `enableLogs` must be set to true to use the logging features
      _experiments: { enableLogs: true },
    });

    const { trace, debug, info, warn, error, fatal, fmt } = Sentry.logger;

    trace('Starting database connection', { database: 'users' });
    debug('Cache miss for user', { userId: 123 });
    error('Failed to process payment', { orderId: 'order_123', amount: 99.99 });
    fatal('Database connection pool exhausted', { database: 'users', activeConnections: 100 });

    // Structured logging via the `fmt` helper function. When you use `fmt`, the string template and parameters are sent separately so they can be queried independently in Sentry.

    info(fmt(`Updated profile for user ${userId}`));
    warn(fmt(`Rate limit approaching for endpoint ${endpoint}. Requests: ${requests}, Limit: ${limit}`));
    ```

    With server-side SDKs like `@sentry/node`, `@sentry/bun` or server-side of `@sentry/nextjs` or `@sentry/sveltekit`, you can do structured logging without needing the `fmt` helper function.

    ```js
    const { info, warn } = Sentry.logger;

    info('User %s logged in successfully', [123]);
    warn('Failed to load user %s data', [123], { errorCode: 404 });
    ```

    To filter logs, or update them before they are sent to Sentry, you can use the `_experiments.beforeSendLog` option.

-   **feat(browser): Add `diagnoseSdkConnectivity()` function to programmatically detect possible connectivity issues ([#&#8203;15821](https://redirect.github.com/getsentry/sentry-javascript/pull/15821))**

    The `diagnoseSdkConnectivity()` function can be used to programmatically detect possible connectivity issues with the Sentry SDK.

    ```js
    const result = await Sentry.diagnoseSdkConnectivity();
    ```

    The result will be an object with the following properties:

    -   `"no-client-active"`: There was no active client when the function was called. This possibly means that the SDK was not initialized yet.
    -   `"sentry-unreachable"`: The Sentry SaaS servers were not reachable. This likely means that there is an ad blocker active on the page or that there are other connection issues.
    -   `undefined`: The SDK is working as expected.

-   **SDK Tracing Performance Improvements for Node SDKs**

    -   feat: Stop using `dropUndefinedKeys` ([#&#8203;15796](https://redirect.github.com/getsentry/sentry-javascript/pull/15796))
    -   feat(node): Only add span listeners for instrumentation when used ([#&#8203;15802](https://redirect.github.com/getsentry/sentry-javascript/pull/15802))
    -   ref: Avoid `dropUndefinedKeys` for `spanToJSON` calls ([#&#8203;15792](https://redirect.github.com/getsentry/sentry-javascript/pull/15792))
    -   ref: Avoid using `SentryError` for PromiseBuffer control flow ([#&#8203;15822](https://redirect.github.com/getsentry/sentry-javascript/pull/15822))
    -   ref: Stop using `dropUndefinedKeys` in SpanExporter ([#&#8203;15794](https://redirect.github.com/getsentry/sentry-javascript/pull/15794))
    -   ref(core): Avoid using `SentryError` for event processing control flow ([#&#8203;15823](https://redirect.github.com/getsentry/sentry-javascript/pull/15823))
    -   ref(node): Avoid `dropUndefinedKeys` in Node SDK init ([#&#8203;15797](https://redirect.github.com/getsentry/sentry-javascript/pull/15797))
    -   ref(opentelemetry): Avoid sampling work for non-root spans ([#&#8203;15820](https://redirect.github.com/getsentry/sentry-javascript/pull/15820))

    We've been hard at work making performance improvements to the Sentry Node SDKs (`@sentry/node`, `@sentry/aws-serverless`, `@sentry/nestjs`, etc.). We've seen that upgrading from `9.7.0` to `9.10.0` leads to 30-40% improvement in request latency for HTTP web-server applications that use tracing with high sample rates. Non web-server applications and non-tracing applications will see smaller improvements.

##### Other Changes

-   chore(deps): Bump `rrweb` to `2.35.0` ([#&#8203;15825](https://redirect.github.com/getsentry/sentry-javascript/pull/15825))
-   deps: Bump bundler plugins to `3.2.3` ([#&#8203;15829](https://redirect.github.com/getsentry/sentry-javascript/pull/15829))
-   feat: Always truncate stored breadcrumb messages to 2kb ([#&#8203;15819](https://redirect.github.com/getsentry/sentry-javascript/pull/15819))
-   feat(nextjs): Disable server webpack-handling for static builds ([#&#8203;15751](https://redirect.github.com/getsentry/sentry-javascript/pull/15751))
-   fix(nuxt): Don't override Nuxt options if undefined ([#&#8203;15795](https://redirect.github.com/getsentry/sentry-javascript/pull/15795))

#### Bundle size 📦

| Path                                                             | Size              |
| ---------------------------------------------------------------- | ----------------- |
| [@&#8203;sentry/browser](https://redirect.github.com/sentry/browser)                                                  | 23.08 KB  |
| [@&#8203;sentry/browser](https://redirect.github.com/sentry/browser) - with treeshaking flags                         | 22.88 KB  |
| [@&#8203;sentry/browser](https://redirect.github.com/sentry/browser) (incl. Tracing)                                  | 36.49 KB  |
| [@&#8203;sentry/browser](https://redirect.github.com/sentry/browser) (incl. Tracing, Replay)                          | 73.65 KB  |
| [@&#8203;sentry/browser](https://redirect.github.com/sentry/browser) (incl. Tracing, Replay) - with treeshaking flags | 67 KB     |
| [@&#8203;sentry/browser](https://redirect.github.com/sentry/browser) (incl. Tracing, Replay with Canvas)              | 78.3 KB   |
| [@&#8203;sentry/browser](https://redirect.github.com/sentry/browser) (incl. Tracing, Replay, Feedback)                | 90.87 KB  |
| [@&#8203;sentry/browser](https://redirect.github.com/sentry/browser) (incl. Feedback)                                 | 40.21 KB  |
| [@&#8203;sentry/browser](https://redirect.github.com/sentry/browser) (incl. sendFeedback)                             | 27.71 KB  |
| [@&#8203;sentry/browser](https://redirect.github.com/sentry/browser) (incl. FeedbackAsync)                            | 32.5 KB   |
| [@&#8203;sentry/react](https://redirect.github.com/sentry/react)                                                    | 24.86 KB  |
| [@&#8203;sentry/react](https://redirect.github.com/sentry/react) (incl. Tracing)                                    | 38.39 KB  |
| [@&#8203;sentry/vue](https://redirect.github.com/sentry/vue)                                                      | 27.3 KB   |
| [@&#8203;sentry/vue](https://redirect.github.com/sentry/vue) (incl. Tracing)                                      | 38.18 KB  |
| [@&#8203;sentry/svelte](https://redirect.github.com/sentry/svelte)                                                   | 23.12 KB  |
| CDN Bundle                                                       | 24.33 KB  |
| CDN Bundle (incl. Tracing)                                       | 36.51 KB  |
| CDN Bundle (incl. Tracing, Replay)                               | 71.53 KB  |
| CDN Bundle (incl. Tracing, Replay, Feedback)                     | 76.71 KB  |
| CDN Bundle - uncompressed                                        | 70.93 KB  |
| CDN Bundle (incl. Tracing) - uncompressed                        | 108.11 KB |
| CDN Bundle (incl. Tracing, Replay) - uncompressed                | 219.4 KB  |
| CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed      | 231.97 KB |
| [@&#8203;sentry/nextjs](https://redirect.github.com/sentry/nextjs) (client)                                          | 39.68 KB  |
| [@&#8203;sentry/sveltekit](https://redirect.github.com/sentry/sveltekit) (client)                                       | 36.92 KB  |
| [@&#8203;sentry/node](https://redirect.github.com/sentry/node)                                                     | 142.91 KB |
| [@&#8203;sentry/node](https://redirect.github.com/sentry/node) - without tracing                                   | 96.12 KB  |
| [@&#8203;sentry/aws-serverless](https://redirect.github.com/sentry/aws-serverless)                                           | 120.46 KB |

</details>

<details>
<summary>slackapi/node-slack-sdk (@&#8203;slack/web-api)</summary>

### [`v7.9.1`](https://redirect.github.com/slackapi/node-slack-sdk/compare/@slack/web-api@7.9.0...@slack/web-api@7.9.1)

[Compare Source](https://redirect.github.com/slackapi/node-slack-sdk/compare/@slack/web-api@7.9.0...@slack/web-api@7.9.1)

</details>

<details>
<summary>storybookjs/storybook (@&#8203;storybook/addon-essentials)</summary>

### [`v8.6.11`](https://redirect.github.com/storybookjs/storybook/compare/v8.6.10...2afd30d75089f27a8029a1ac320d7698873b163f)

[Compare Source](https://redirect.github.com/storybookjs/storybook/compare/v8.6.10...v8.6.11)

### [`v8.6.10`](https://redirect.github.com/storybookjs/storybook/blob/HEAD/CHANGELOG.md#8610)

[Compare Source](https://redirect.github.com/storybookjs/storybook/compare/v8.6.9...v8.6.10)

-   Addon-docs: Fix non-string handling in Stories block - [#&#8203;30913](https://redirect.github.com/storybookjs/storybook/pull/30913), thanks [@&#8203;JamesIves](https://redirect.github.com/JamesIves)!
-   Nextjs: Fix styled-jsx optimize vite warnings - [#&#8203;30932](https://redirect.github.com/storybookjs/storybook/pull/30932), thanks [@&#8203;kasperpeulen](https://redirect.github.com/kasperpeulen)!
-   React: Fix actImplementation is not a function - [#&#8203;30929](https://redirect.github.com/storybookjs/storybook/pull/30929), thanks [@&#8203;kasperpeulen](https://redirect.github.com/kasperpeulen)!

</details>

<details>
<summary>storybookjs/storybook (@&#8203;storybook/addon-interactions)</summary>

### [`v8.6.11`](https://redirect.github.com/storybookjs/storybook/blob/HEAD/CHANGELOG.md#8611)

[Compare Source](https://redirect.github.com/storybookjs/storybook/compare/v8.6.10...v8.6.11)

-   Angular: Fix zone.js support for Angular libraries - [#&#8203;30941](https://redirect.github.com/storybookjs/storybook/pull/30941), thanks [@&#8203;valentinpalkovic](https://redirect.github.com/valentinpalkovic)!

### [`v8.6.10`](https://redirect.github.com/storybookjs/storybook/blob/HEAD/CHANGELOG.md#8610)

[Compare Source](https://redirect.github.com/storybookjs/storybook/compare/v8.6.9...v8.6.10)

-   Addon-docs: Fix non-string handling in Stories block - [#&#8203;30913](https://redirect.github.com/storybookjs/storybook/pull/30913), thanks [@&#8203;JamesIves](https://redirect.github.com/JamesIves)!
-   Nextjs: Fix styled-jsx optimize vite warnings - [#&#8203;30932](https://redirect.github.com/storybookjs/storybook/pull/30932), thanks [@&#8203;kasperpeulen](https://redirect.github.com/kasperpeulen)!
-   React: Fix actImplementation is not a function - [#&#8203;30929](https://redirect.github.com/storybookjs/storybook/pull/30929), thanks [@&#8203;kasperpeulen](https://redirect.github.com/kasperpeulen)!

</details>

<details>
<summary>vercel/ai (ai)</summary>

### [`v4.2.8`](https://redirect.github.com/vercel/ai/releases/tag/ai%404.2.8)

[Compare Source](https://redirect.github.com/vercel/ai/compare/ai@4.2.7...ai@4.2.8)

##### Patch Changes

-   [`65243ce`](https://redirect.github.com/vercel/ai/commit/65243ce): fix (ui): introduce step start parts
-   Updated dependencies \[[`65243ce`](https://redirect.github.com/vercel/ai/commit/65243ce)]
    -   [@&#8203;ai-sdk/ui-utils](https://redirect.github.com/ai-sdk/ui-utils)[@&#8203;1](https://redirect.github.com/1).2.2
    -   [@&#8203;ai-sdk/react](https://redirect.github.com/ai-sdk/react)[@&#8203;1](https://redirect.github.com/1).2.3

### [`v4.2.7`](https://redirect.github.com/vercel/ai/releases/tag/ai%404.2.7)

[Compare Source](https://redirect.github.com/vercel/ai/compare/ai@4.2.6...ai@4.2.7)

##### Patch Changes

-   [`e14c066`](https://redirect.github.com/vercel/ai/commit/e14c066): fix (ai/core): convert user ui messages with only parts (no content) to core messages

### [`v4.2.6`](https://redirect.github.com/vercel/ai/releases/tag/ai%404.2.6)

[Compare Source](https://redirect.github.com/vercel/ai/compare/ai@4.2.5...ai@4.2.6)

##### Patch Changes

-   [`625591b`](https://redirect.github.com/vercel/ai/commit/625591b): feat (ai/core): auto-complete for provider registry
-   [`6a1506f`](https://redirect.github.com/vercel/ai/commit/6a1506f): feat (ai/core): custom separator support for provider registry
-   [`ea3d998`](https://redirect.github.com/vercel/ai/commit/ea3d998): chore (ai/core): move provider registry to stable

</details>

<details>
<summary>apollographql/apollo-ios (apollographql/apollo-ios)</summary>

### [`v1.19.0`](https://redirect.github.com/apollographql/apollo-ios/blob/HEAD/CHANGELOG.md#v1190)

[Compare Source](https://redirect.github.com/apollographql/apollo-ios/compare/1.18.0...1.19.0)

##### New

-   **New function to mutate the properties of a local cache mutation fragment. ([#&#8203;3433](https://redirect.github.com/apollographql/apollo-ios/issues/3443)):** Removal of the setter for type conditions made it difficult to work with the properties on those types. A new `mutateIfFulfilled` function was added to facilitate that workflow while still preventing a fragment from being added or removed from an existing model. See PR [#&#8203;608](https://redirect.github.com/apollographql/apollo-ios-dev/pull/608).
-   **Configure `URLRequest` timeout interval ([#&#8203;3522](https://redirect.github.com/apollographql/apollo-ios/issues/3522)):** Added a request context specialization protocol (`RequestContextTimeoutConfigurable`) that specifies options for configuring the timeout interval of a `URLRequest`. See PR [#&#8203;618](https://redirect.github.com/apollographql/apollo-ios-dev/pull/618).

</details>

<details>
<summary>taskforcesh/bullmq (bullmq)</summary>

### [`v5.45.0`](https://redirect.github.com/taskforcesh/bullmq/releases/tag/v5.45.0)

[Compare Source](https://redirect.github.com/taskforcesh/bullmq/compare/v5.44.4...v5.45.0)

##### Features

-   add deduplicated job id to the deduplicated event ([0f21c10](https://redirect.github.com/taskforcesh/bullmq/commit/0f21c10bc9fd9a2290e8dde3c9b43bc366fcb15a))

</details>

<details>
<summary>electron/electron (electron)</summary>

### [`v35.1.2`](https://redirect.github.com/electron/electron/releases/tag/v35.1.2): electron v35.1.2

[Compare Source](https://redirect.github.com/electron/electron/compare/v35.1.1...v35.1.2)

### Release Notes for v35.1.2

#### Fixes

-   Fixed an issue where `navigationHistory.restore()` failed to restore the `userAgent` if it was overridden. [#&#8203;46300](https://redirect.github.com/electron/electron/pull/46300) <span style="font-size:small;">(Also in [34](https://redirect.github.com/electron/electron/pull/46298), [36](https://redirect.github.com/electron/electron/pull/46299))</span>

#### Other Changes

-   Security: backported fix for CVE-2025-2783. [#&#8203;46303](https://redirect.github.com/electron/electron/pull/46303)
-   Updated Chromium to 134.0.6998.178. [#&#8203;46287](https://redirect.github.com/electron/electron/pull/46287)

### [`v35.1.1`](https://redirect.github.com/electron/electron/releases/tag/v35.1.1): electron v35.1.1

[Compare Source](https://redirect.github.com/electron/electron/compare/v35.1.0...v35.1.1)

### Release Notes for v35.1.1

#### Fixes

-   Fixed build failure when building with printing disabled. [#&#8203;46285](https://redirect.github.com/electron/electron/pull/46285) <span style="font-size:small;">(Also in [34](https://redirect.github.com/electron/electron/pull/46286), [36](https://redirect.github.com/electron/electron/pull/46284))</span>

</details>

<details>
<summary>megahertz/electron-log (electron-log)</summary>

### [`v5.3.3`](https://redirect.github.com/megahertz/electron-log/compare/v5.3.2...v5.3.3)

[Compare Source](https://redirect.github.com/megahertz/electron-log/compare/v5.3.2...v5.3.3)

</details>

<details>
<summary>rexxars/eventsource-parser (eventsource-parser)</summary>

### [`v3.0.1`](https://redirect.github.com/rexxars/eventsource-parser/blob/HEAD/CHANGELOG.md#301-2025-03-27)

[Compare Source](https://redirect.github.com/rexxars/eventsource-parser/compare/v3.0.0...v3.0.1)

##### Bug Fixes

-   optimize `splitLines` function ([8952917](https://redirect.github.com/rexxars/eventsource-parser/commit/8952917a6f5b3d8c97175d00980538edc96b611d))
-   throw helpful error if passing function to `createParser()` ([4cd3a44](https://redirect.github.com/rexxars/eventsource-parser/commit/4cd3a443f21c441be29e524637a3a603d4425a12))

</details>

<details>
<summary>mixpanel/mixpanel-js (mixpanel-browser)</summary>

### [`v2.62.0`](https://redirect.github.com/mixpanel/mixpanel-js/compare/v2.61.2...3e3d5731642dd3e3ac543521155d3c51c8a37261)

[Compare Source](https://redirect.github.com/mixpanel/mixpanel-js/compare/v2.61.2...v2.62.0)

</details>

<details>
<summary>Papooch/nestjs-cls (nestjs-cls)</summary>

### [`v5.4.2`](https://redirect.github.com/Papooch/nestjs-cls/releases/tag/nestjs-cls%405.4.2)

[Compare Source](https://redirect.github.com/Papooch/nestjs-cls/compare/nestjs-cls@5.4.1...nestjs-cls@5.4.2)

##### Bug Fixes

-   **core**: un-deprecate wrongly deprecated parts of the plugin API ([#&#8203;228](https://redirect.github.com/Papooch/nestjs-cls/issues/228)) ([11ca429](https://redirect.github.com/Papooch/nestjs-cls/commits/11ca429))

</details>

<details>
<summary>matklad/once_cell (once_cell)</summary>

### [`v1.21.2`](https://redirect.github.com/matklad/once_cell/blob/HEAD/CHANGELOG.md#1212)

[Compare Source](https://redirect.github.com/matklad/once_cell/compare/v1.21.1...v1.21.2)

-   Relax success ordering from AcqRel to Release in `race`: [#&#8203;278](https://redirect.github.com/matklad/once_cell/pull/278).

</details>

<details>
<summary>openai/openai-node (openai)</summary>

### [`v4.90.0`](https://redirect.github.com/openai/openai-node/blob/HEAD/CHANGELOG.md#4900-2025-03-27)

[Compare Source](https://redirect.github.com/openai/openai-node/compare/v4.89.1...v4.90.0)

Full Changelog: [v4.89.1...v4.90.0](https://redirect.github.com/openai/openai-node/compare/v4.89.1...v4.90.0)

##### Features

-   **api:** add `get /chat/completions` endpoint ([2d6710a](https://redirect.github.com/openai/openai-node/commit/2d6710a1f9dd4f768d9c73e9c9f5f93c737cdc66))

##### Bug Fixes

-   **audio:** correctly handle transcription streaming ([2a9b603](https://redirect.github.com/openai/openai-node/commit/2a9b60336cd40a4d4fb9b898ece49170ad648fd0))
-   **internal:** work around [https://github.com/vercel/next.js/issues/76881](https://redirect.github.com/vercel/next.js/issues/76881) ([#&#8203;1427](https://redirect.github.com/openai/openai-node/issues/1427)) ([b467e94](https://redirect.github.com/openai/openai-node/commit/b467e949476621e8e92587a83c9de6fab35b2b9d))

##### Chores

-   add hash of OpenAPI spec/config inputs to .stats.yml ([45db35e](https://redirect.github.com/openai/openai-node/commit/45db35e34be560c75bf36224cc153c6d0e6e2a88))
-   **api:** updates to supported Voice IDs ([#&#8203;1424](https://redirect.github.com/openai/openai-node/issues/1424)) ([404f4db](https://redirect.github.com/openai/openai-node/commit/404f4db41a2ee651f5bfdaa7b8881e1bf015f058))
-   **client:** expose headers on some streaming errors ([#&#8203;1423](https://redirect.github.com/openai/openai-node/issues/1423)) ([b0783cc](https://redirect.github.com/openai/openai-node/commit/b0783cc6221b68f1738e759b393756a7d0e540a3))

### [`v4.89.1`](https://redirect.github.com/openai/openai-node/blob/HEAD/CHANGELOG.md#4891-2025-03-26)

[Compare Source](https://redirect.github.com/openai/openai-node/compare/v4.89.0...v4.89.1)

Full Changelog: [v4.89.0...v4.89.1](https://redirect.github.com/openai/openai-node/compare/v4.89.0...v4.89.1)

##### Bug Fixes

-   avoid type error in certain environments ([#&#8203;1413](https://redirect.github.com/openai/openai-node/issues/1413)) ([d3f6f8f](https://redirect.github.com/openai/openai-node/commit/d3f6f8f9c7511a98cc5795756fee49a30e44d485))
-   **client:** remove duplicate types ([#&#8203;1410](https://redirect.github.com/openai/openai-node/issues/1410)) ([338878b](https://redirect.github.com/openai/openai-node/commit/338878bf484dac5a4fadf50592b1f8d1045cd4b6))
-   **exports:** add missing type exports ([#&#8203;1417](https://redirect.github.com/openai/openai-node/issues/1417)) ([2d15ada](https://redirect.github.com/openai/openai-node/commit/2d15ada0e0d81a4e0d097dddbe99be2222c4c0ef))

##### Chores

-   **internal:** version bump ([#&#8203;1408](https://redirect.github.com/openai/openai-node/issues/1408)) ([9c0949a](https://redirect.github.com/openai/openai-node/commit/9c0949a93c3e181d327f820dbc2a4b0ad77258e9))

</details>

<details>
<summary>emilkowalski/sonner (sonner)</summary>

### [`v2.0.2`](https://redirect.github.com/emilkowalski/sonner/releases/tag/v2.0.2)

[Compare Source](https://redirect.github.com/emilkowalski/sonner/compare/v2.0.1...v2.0.2)

#### What's Changed

-   fix: isExtendedResult. Check if promiseData is an object and not a valid React Element by [@&#8203;diegotraid](https://redirect.github.com/diegotraid) in [https://github.com/emilkowalski/sonner/pull/595](https://redirect.github.com/emilkowalski/sonner/pull/595)
-   fix: toast.dismiss without an id doesn't dismiss by [@&#8203;emilkowalski](https://redirect.github.com/emilkowalski) in [https://github.com/emilkowalski/sonner/pull/609](https://redirect.github.com/emilkowalski/sonner/pull/609)
-   f

</details>

---

### Configuration

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

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

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

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

---

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

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/toeverything/AFFiNE).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4yMDcuMSIsInVwZGF0ZWRJblZlciI6IjM5LjIwNy4xIiwidGFyZ2V0QnJhbmNoIjoiY2FuYXJ5IiwibGFiZWxzIjpbImRlcGVuZGVuY2llcyJdfQ==-->
2025-03-28 13:44:55 +00:00
forehalo 64c7fb1d66 chore(server): validate function not actually used (#11263) 2025-03-28 12:51:19 +00:00
178 changed files with 7790 additions and 2696 deletions
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -12,4 +12,4 @@ npmPublishAccess: public
npmPublishRegistry: "https://registry.npmjs.org"
yarnPath: .yarn/releases/yarn-4.7.0.cjs
yarnPath: .yarn/releases/yarn-4.8.0.cjs
Generated
+2 -2
View File
@@ -2501,9 +2501,9 @@ dependencies = [
[[package]]
name = "once_cell"
version = "1.21.1"
version = "1.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc"
checksum = "c2806eaa3524762875e21c3dcd057bc4b7bfa01ce4da8d46be1cd43649e1cc6b"
[[package]]
name = "oorandom"
@@ -11,10 +11,10 @@ import { property, query } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { styleMap } from 'lit/directives/style-map.js';
import { ERROR_CARD_DEFAULT_HEIGHT } from '../consts';
import type { EmbedIframeStatusCardOptions } from '../types';
const LINK_EDIT_POPUP_OFFSET = 12;
const ERROR_CARD_DEFAULT_HEIGHT = 114;
export class EmbedIframeErrorCard extends WithDisposable(LitElement) {
static override styles = css`
@@ -24,7 +24,7 @@ export class EmbedIframeErrorCard extends WithDisposable(LitElement) {
}
.affine-embed-iframe-error-card {
container: affine-embed-iframe-error-card / inline-size;
container: affine-embed-iframe-error-card / size;
display: flex;
box-sizing: border-box;
user-select: none;
@@ -41,7 +41,6 @@ export class EmbedIframeErrorCard extends WithDisposable(LitElement) {
display: flex;
flex-direction: column;
gap: 4px;
flex: 1 0 0;
.error-title {
display: flex;
@@ -64,6 +63,9 @@ export class EmbedIframeErrorCard extends WithDisposable(LitElement) {
font-style: normal;
font-weight: 600;
line-height: 22px; /* 157.143% */
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
@@ -119,12 +121,6 @@ export class EmbedIframeErrorCard extends WithDisposable(LitElement) {
}
}
}
@container affine-embed-iframe-error-card (width < 480px) {
.error-banner {
display: none;
}
}
}
.affine-embed-iframe-error-card.horizontal {
@@ -133,12 +129,19 @@ export class EmbedIframeErrorCard extends WithDisposable(LitElement) {
.error-content {
align-items: flex-start;
flex: 1 0 0;
.error-message {
height: 40px;
align-items: flex-start;
}
}
@container affine-embed-iframe-error-card (width < 480px) {
.error-banner {
display: none;
}
}
}
.affine-embed-iframe-error-card.vertical {
@@ -155,6 +158,18 @@ export class EmbedIframeErrorCard extends WithDisposable(LitElement) {
align-items: center;
}
}
.icon-box {
svg {
transform: scale(1.6) translateY(-14px);
}
}
@container affine-embed-iframe-error-card (height < 300px) or (width < 300px) {
.error-banner {
display: none;
}
}
}
`;
@@ -216,10 +231,10 @@ export class EmbedIframeErrorCard extends WithDisposable(LitElement) {
<div class=${cardClasses} style=${cardStyle}>
<div class="error-content">
<div class="error-title">
<div class="error-icon">
<span class="error-icon">
${InformationIcon({ width: '16px', height: '16px' })}
</div>
<div class="error-title-text">This link couldnt be loaded.</div>
</span>
<span class="error-title-text">This link couldnt be loaded.</span>
</div>
<div class="error-message">
${this.error?.message || 'Failed to load embedded content'}
@@ -244,8 +259,7 @@ export class EmbedIframeErrorCard extends WithDisposable(LitElement) {
</div>
</div>
<div class="error-banner">
<!-- TODO: add error banner icon -->
<div class="icon-box"></div>
<div class="icon-box">${EmbedIframeErrorIcon}</div>
</div>
</div>
`;
@@ -280,3 +294,25 @@ export class EmbedIframeErrorCard extends WithDisposable(LitElement) {
height: ERROR_CARD_DEFAULT_HEIGHT,
};
}
export const EmbedIframeErrorIcon = html`<svg
width="204"
height="102"
viewBox="0 0 204 102"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clip-path="url(#clip0_2676_106795)">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M94.6838 8.45092L106.173 31.9276L84.6593 57.0514L90.5888 64.9202C88.6083 64.6092 86.5089 65.0701 84.7813 66.3719L78.4802 71.1202C75.0967 73.6698 74.4207 78.4796 76.9704 81.8631C79.5201 85.2467 84.3299 85.9227 87.7134 83.373L89.4487 82.0654C90.3714 81.37 90.5558 80.0582 89.8604 79.1354C89.1651 78.2127 87.8533 78.0283 86.9305 78.7237L85.1952 80.0313C83.6573 81.1902 81.471 80.883 80.3121 79.345C79.1531 77.807 79.4604 75.6208 80.9984 74.4618L87.2995 69.7136C88.8375 68.5547 91.0237 68.8619 92.1827 70.3999C92.8645 71.3047 94.1389 71.4996 95.0582 70.8513L95.8982 71.966L94.6469 72.9089C93.109 74.0679 90.9227 73.7606 89.7638 72.2227C89.0684 71.2999 87.7566 71.1155 86.8339 71.8109C85.9111 72.5062 85.7267 73.818 86.4221 74.7408C88.9718 78.1243 93.7816 78.8003 97.1651 76.2506L98.4164 75.3077L99.8156 77.1646L86.8434 102.707L89.291 114.735L42.1397 108.108L56.3354 7.10072C56.6429 4.91308 58.6655 3.38889 60.8532 3.69634L94.6838 8.45092ZM122.987 12.4287L119.974 33.8672L95.4607 58.4925C98.7006 56.8928 102.722 57.7678 104.976 60.7594C107.526 64.1429 106.85 68.9527 103.466 71.5024L102.718 72.0665L105.949 78.0266L92.2105 103.461L92.9872 115.254L147.108 122.86L161.304 21.8531C161.611 19.6654 160.087 17.6428 157.899 17.3353L122.987 12.4287ZM100.701 68.3471L100.948 68.1607C102.486 67.0018 102.793 64.8155 101.634 63.2775C100.625 61.9381 98.8364 61.5321 97.3755 62.2152L100.701 68.3471ZM88.8231 36.502C84.6277 35.9124 80.7486 38.8354 80.159 43.0308L79.1885 49.9367C79.0277 51.0809 79.8249 52.1388 80.9691 52.2996C82.1133 52.4604 83.1712 51.6632 83.332 50.519L84.3025 43.6132C84.5705 41.7062 86.3337 40.3775 88.2407 40.6455L95.1466 41.6161C96.2908 41.7769 97.3487 40.9797 97.5095 39.8355C97.6703 38.6913 96.8731 37.6334 95.7289 37.4726L88.8231 36.502ZM115.065 40.1901C113.921 40.0293 112.863 40.8265 112.702 41.9707C112.542 43.1149 113.339 44.1728 114.483 44.3336L121.389 45.3042C123.296 45.5722 124.625 47.3354 124.357 49.2424L123.386 56.1483C123.225 57.2925 124.022 58.3504 125.167 58.5112C126.311 58.672 127.369 57.8748 127.529 56.7306L128.5 49.8247C129.09 45.6293 126.167 41.7503 121.971 41.1607L115.065 40.1901ZM123.031 73.7041C124.176 73.8649 124.973 74.9228 124.812 76.067L123.841 82.9728C123.252 87.1682 119.373 90.0913 115.177 89.5017L106.89 88.337C105.746 88.1762 104.949 87.1183 105.11 85.9741C105.27 84.8299 106.328 84.0327 107.473 84.1935L115.76 85.3582C117.667 85.6262 119.43 84.2975 119.698 82.3905L120.668 75.4847C120.829 74.3405 121.887 73.5433 123.031 73.7041Z"
fill="#E6E6E6"
/>
</g>
<defs>
<clipPath id="clip0_2676_106795">
<rect width="204" height="102" fill="white" />
</clipPath>
</defs>
</svg>`;
@@ -3,16 +3,22 @@ import { WithDisposable } from '@blocksuite/global/lit';
import { EmbedIcon } from '@blocksuite/icons/lit';
import { baseTheme } from '@toeverything/theme';
import { css, html, LitElement, unsafeCSS } from 'lit';
import { property } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { styleMap } from 'lit/directives/style-map.js';
import { IDLE_CARD_DEFAULT_HEIGHT } from '../consts';
import type { EmbedIframeStatusCardOptions } from '../types';
export class EmbedIframeIdleCard extends WithDisposable(LitElement) {
static override styles = css`
:host {
width: 100%;
height: 100%;
}
.affine-embed-iframe-idle-card {
width: 100%;
height: 48px;
container: affine-embed-iframe-idle-card / size;
box-sizing: border-box;
display: flex;
align-items: center;
@@ -23,8 +29,6 @@ export class EmbedIframeIdleCard extends WithDisposable(LitElement) {
.icon {
display: flex;
width: 24px;
height: 24px;
justify-content: center;
align-items: center;
color: ${unsafeCSSVarV2('icon/secondary')};
@@ -48,18 +52,81 @@ export class EmbedIframeIdleCard extends WithDisposable(LitElement) {
.affine-embed-iframe-idle-card:hover {
cursor: pointer;
}
.affine-embed-iframe-idle-card.horizontal {
flex-direction: row;
.icon {
width: 24px;
height: 24px;
svg {
width: 24px;
height: 24px;
}
}
}
.affine-embed-iframe-idle-card.vertical {
flex-direction: column;
justify-content: center;
overflow: hidden;
gap: 12px;
.icon {
width: 176px;
height: 112px;
overflow-y: hidden;
svg {
width: 112px;
height: 112px;
transform: rotate(12deg) translateY(18%);
}
}
.text {
text-align: center;
white-space: normal;
word-break: break-word;
}
@container affine-embed-iframe-idle-card (height < 180px) {
.icon {
display: none;
}
}
}
`;
override render() {
const { layout, width, height } = this.options;
const cardClasses = classMap({
'affine-embed-iframe-idle-card': true,
horizontal: layout === 'horizontal',
vertical: layout === 'vertical',
});
const cardWidth = width ? `${width}px` : '100%';
const cardHeight = height ? `${height}px` : '100%';
const cardStyle = styleMap({
width: cardWidth,
height: cardHeight,
});
return html`
<div class="affine-embed-iframe-idle-card">
<span class="icon">
${EmbedIcon({ width: '24px', height: '24px' })}
</span>
<div class=${cardClasses} style=${cardStyle}>
<span class="icon"> ${EmbedIcon()} </span>
<span class="text">
Embed anything (Google Drive, Google Docs, Spotify, Miro…)
</span>
</div>
`;
}
@property({ attribute: false })
accessor options: EmbedIframeStatusCardOptions = {
layout: 'horizontal',
height: IDLE_CARD_DEFAULT_HEIGHT,
};
}
@@ -104,6 +104,7 @@ export class EmbedIframeLinkInputBase extends WithDisposable(LitElement) {
this.disposables.addFromEvent(this, 'cut', stopPropagation);
this.disposables.addFromEvent(this, 'copy', stopPropagation);
this.disposables.addFromEvent(this, 'paste', stopPropagation);
this.disposables.addFromEvent(this, 'pointerdown', stopPropagation);
}
get store() {
@@ -8,10 +8,9 @@ import { classMap } from 'lit/directives/class-map.js';
import { styleMap } from 'lit/directives/style-map.js';
import { getEmbedCardIcons } from '../../common/utils';
import { LOADING_CARD_DEFAULT_HEIGHT } from '../consts';
import type { EmbedIframeStatusCardOptions } from '../types';
const LOADING_CARD_DEFAULT_HEIGHT = 114;
export class EmbedIframeLoadingCard extends LitElement {
static override styles = css`
:host {
@@ -20,7 +19,7 @@ export class EmbedIframeLoadingCard extends LitElement {
}
.affine-embed-iframe-loading-card {
container: affine-embed-iframe-loading-card / inline-size;
container: affine-embed-iframe-loading-card / size;
display: flex;
box-sizing: border-box;
border-radius: 8px;
@@ -147,6 +146,12 @@ export class EmbedIframeLoadingCard extends LitElement {
}
}
}
@container affine-embed-iframe-loading-card (height < 240px) {
.loading-banner {
display: none;
}
}
}
`;
@@ -6,3 +6,7 @@ export const DEFAULT_IFRAME_HEIGHT = 152;
export const DEFAULT_IFRAME_WIDTH = '100%';
export const LINK_CREATE_POPUP_OFFSET = 4;
export const IDLE_CARD_DEFAULT_HEIGHT = 48;
export const LOADING_CARD_DEFAULT_HEIGHT = 114;
export const ERROR_CARD_DEFAULT_HEIGHT = 114;
@@ -34,7 +34,10 @@ import {
DEFAULT_IFRAME_HEIGHT,
DEFAULT_IFRAME_WIDTH,
EMBED_IFRAME_DEFAULT_CONTAINER_BORDER_RADIUS,
ERROR_CARD_DEFAULT_HEIGHT,
IDLE_CARD_DEFAULT_HEIGHT,
LINK_CREATE_POPUP_OFFSET,
LOADING_CARD_DEFAULT_HEIGHT,
} from './consts.js';
import { embedIframeBlockStyles } from './style.js';
import type { EmbedIframeStatusCardOptions } from './types.js';
@@ -109,10 +112,23 @@ export class EmbedIframeBlockComponent extends CaptionedBlockComponent<EmbedIfra
return flag ?? false;
}
get _horizontalCardHeight(): number {
switch (this.status$.value) {
case 'idle':
return IDLE_CARD_DEFAULT_HEIGHT;
case 'loading':
return LOADING_CARD_DEFAULT_HEIGHT;
case 'error':
return ERROR_CARD_DEFAULT_HEIGHT;
default:
return LOADING_CARD_DEFAULT_HEIGHT;
}
}
get _statusCardOptions(): EmbedIframeStatusCardOptions {
return this.inSurface
? { layout: 'vertical' }
: { layout: 'horizontal', height: 114 };
: { layout: 'horizontal', height: this._horizontalCardHeight };
}
open = () => {
@@ -257,19 +273,21 @@ export class EmbedIframeBlockComponent extends CaptionedBlockComponent<EmbedIfra
};
protected _handleClick = () => {
// We don't need to select the block when the block is in the surface
if (this.inSurface) {
return;
}
// when the block is in idle status and the url is not set, clear the selection
// and show the link input popup
if (this.isIdle$.value && !this.model.props.url) {
this.selectionManager.clear(['block']);
// when the block is in the surface, clear the surface selection
// otherwise, clear the block selection
this.selectionManager.clear([this.inSurface ? 'surface' : 'block']);
this.toggleLinkInputPopup();
return;
}
// We don't need to select the block when the block is in the surface
if (this.inSurface) {
return;
}
// otherwise, select the block
this._selectBlock();
};
@@ -311,7 +329,9 @@ export class EmbedIframeBlockComponent extends CaptionedBlockComponent<EmbedIfra
private readonly _renderContent = () => {
if (this.isIdle$.value) {
return html`<embed-iframe-idle-card></embed-iframe-idle-card>`;
return html`<embed-iframe-idle-card
.options=${this._statusCardOptions}
></embed-iframe-idle-card>`;
}
if (this.isLoading$.value) {
@@ -356,6 +376,7 @@ export class EmbedIframeBlockComponent extends CaptionedBlockComponent<EmbedIfra
})
);
// if the iframe url is not set, refresh the data to get the iframe url
if (!this.model.props.iframeUrl) {
this.doc.withoutTransact(() => {
this.refreshData().catch(console.error);
@@ -11,6 +11,7 @@
"license": "MIT",
"dependencies": {
"@blocksuite/affine-components": "workspace:*",
"@blocksuite/affine-gfx-turbo-renderer": "workspace:*",
"@blocksuite/affine-inline-preset": "workspace:*",
"@blocksuite/affine-model": "workspace:*",
"@blocksuite/affine-rich-text": "workspace:*",
@@ -34,7 +35,8 @@
},
"exports": {
".": "./src/index.ts",
"./effects": "./src/effects.ts"
"./effects": "./src/effects.ts",
"./turbo-painter": "./src/turbo/list-painter.worker.ts"
},
"files": [
"src",
@@ -3,3 +3,5 @@ export * from './commands';
export { correctNumberedListsOrderToPrev } from './commands/utils';
export * from './list-block.js';
export * from './list-spec.js';
export * from './turbo/list-layout-handler';
export * from './turbo/list-painter.worker';
@@ -0,0 +1,145 @@
import type { Rect } from '@blocksuite/affine-gfx-turbo-renderer';
import {
BlockLayoutHandlerExtension,
BlockLayoutHandlersIdentifier,
getSentenceRects,
segmentSentences,
} from '@blocksuite/affine-gfx-turbo-renderer';
import type { Container } from '@blocksuite/global/di';
import type { GfxBlockComponent } from '@blocksuite/std';
import { clientToModelCoord } from '@blocksuite/std/gfx';
import type { ListLayout } from './list-painter.worker';
export class ListLayoutHandlerExtension extends BlockLayoutHandlerExtension<ListLayout> {
readonly blockType = 'affine:list';
static override setup(di: Container) {
di.addImpl(
BlockLayoutHandlersIdentifier('list'),
ListLayoutHandlerExtension
);
}
queryLayout(component: GfxBlockComponent): ListLayout | null {
// Select all list items within this list block
const listItemSelector =
'.affine-list-block-container .affine-list-rich-text-wrapper [data-v-text="true"]';
const listItemNodes = component.querySelectorAll(listItemSelector);
if (listItemNodes.length === 0) return null;
const viewportRecord = component.gfx.viewport.deserializeRecord(
component.dataset.viewportState
);
if (!viewportRecord) return null;
const { zoom, viewScale } = viewportRecord;
const list: ListLayout = {
type: 'affine:list',
items: [],
};
listItemNodes.forEach(listItemNode => {
const listItemWrapper = listItemNode.closest(
'.affine-list-rich-text-wrapper'
);
if (!listItemWrapper) return;
// Determine list item type based on class
let itemType: 'bulleted' | 'numbered' | 'todo' | 'toggle' = 'bulleted';
let checked = false;
let collapsed = false;
let prefix = '';
if (listItemWrapper.classList.contains('affine-list--checked')) {
checked = true;
}
const parentListBlock = listItemWrapper.closest(
'.affine-list-block-container'
)?.parentElement;
if (parentListBlock) {
if (parentListBlock.dataset.listType === 'numbered') {
itemType = 'numbered';
const orderVal = parentListBlock.dataset.listOrder;
if (orderVal) {
prefix = orderVal + '.';
}
} else if (parentListBlock.dataset.listType === 'todo') {
itemType = 'todo';
} else if (parentListBlock.dataset.listType === 'toggle') {
itemType = 'toggle';
collapsed = parentListBlock.dataset.collapsed === 'true';
} else {
itemType = 'bulleted';
}
}
const computedStyle = window.getComputedStyle(listItemNode);
const fontSizeStr = computedStyle.fontSize;
const fontSize = parseInt(fontSizeStr);
const sentences = segmentSentences(listItemNode.textContent || '');
const sentenceLayouts = sentences.map(sentence => {
const sentenceRects = getSentenceRects(listItemNode, sentence);
return {
text: sentence,
rects: sentenceRects.map(({ text, rect }) => {
const [modelX, modelY] = clientToModelCoord(viewportRecord, [
rect.x,
rect.y,
]);
return {
text,
rect: {
x: modelX,
y: modelY,
w: rect.w / zoom / viewScale,
h: rect.h / zoom / viewScale,
},
};
}),
fontSize,
type: itemType,
prefix,
checked,
collapsed,
};
});
list.items.push(...sentenceLayouts);
});
return list;
}
calculateBound(layout: ListLayout) {
let minX = Infinity;
let minY = Infinity;
let maxX = -Infinity;
let maxY = -Infinity;
layout.items.forEach(item => {
item.rects.forEach(r => {
minX = Math.min(minX, r.rect.x);
minY = Math.min(minY, r.rect.y);
maxX = Math.max(maxX, r.rect.x + r.rect.w);
maxY = Math.max(maxY, r.rect.y + r.rect.h);
});
});
const rect: Rect = {
x: minX,
y: minY,
w: maxX - minX,
h: maxY - minY,
};
return {
rect,
subRects: layout.items.flatMap(s => s.rects.map(r => r.rect)),
};
}
}
@@ -0,0 +1,114 @@
import type {
BlockLayout,
BlockLayoutPainter,
TextRect,
WorkerToHostMessage,
} from '@blocksuite/affine-gfx-turbo-renderer';
import {
BlockLayoutPainterExtension,
getBaseline,
} from '@blocksuite/affine-gfx-turbo-renderer/painter';
interface ListItemLayout {
text: string;
rects: TextRect[];
fontSize: number;
type: 'bulleted' | 'numbered' | 'todo' | 'toggle';
prefix?: string;
checked?: boolean;
collapsed?: boolean;
}
export interface ListLayout extends BlockLayout {
type: 'affine:list';
items: ListItemLayout[];
}
const debugListBorder = false;
function isListLayout(layout: BlockLayout): layout is ListLayout {
return layout.type === 'affine:list';
}
class ListLayoutPainter implements BlockLayoutPainter {
private static readonly supportFontFace =
typeof FontFace !== 'undefined' &&
typeof self !== 'undefined' &&
'fonts' in self;
static readonly font = ListLayoutPainter.supportFontFace
? new FontFace(
'Inter',
`url(https://fonts.gstatic.com/s/inter/v18/UcCo3FwrK3iLTcviYwYZ8UA3.woff2)`
)
: null;
static fontLoaded = !ListLayoutPainter.supportFontFace;
static {
if (ListLayoutPainter.supportFontFace && ListLayoutPainter.font) {
// @ts-expect-error worker fonts API
self.fonts.add(ListLayoutPainter.font);
ListLayoutPainter.font
.load()
.then(() => {
ListLayoutPainter.fontLoaded = true;
})
.catch(error => {
console.error('Failed to load Inter font:', error);
});
}
}
paint(
ctx: OffscreenCanvasRenderingContext2D,
layout: BlockLayout,
layoutBaseX: number,
layoutBaseY: number
): void {
if (!ListLayoutPainter.fontLoaded) {
const message: WorkerToHostMessage = {
type: 'paintError',
error: 'Font not loaded',
blockType: 'affine:list',
};
self.postMessage(message);
return;
}
if (!isListLayout(layout)) return;
const renderedPositions = new Set<string>();
layout.items.forEach(item => {
const fontSize = item.fontSize;
const baselineY = getBaseline(fontSize);
ctx.font = `${fontSize}px Inter`;
ctx.strokeStyle = 'yellow';
// Render the text content
item.rects.forEach(textRect => {
const x = textRect.rect.x - layoutBaseX;
const y = textRect.rect.y - layoutBaseY;
const posKey = `${x},${y}`;
// Only render if we haven't rendered at this position before
if (renderedPositions.has(posKey)) return;
if (debugListBorder) {
ctx.strokeRect(x, y, textRect.rect.w, textRect.rect.h);
}
ctx.fillStyle = 'black';
ctx.fillText(textRect.text, x, y + baselineY);
renderedPositions.add(posKey);
});
});
}
}
export const ListLayoutPainterExtension = BlockLayoutPainterExtension(
'affine:list',
ListLayoutPainter
);
@@ -8,6 +8,7 @@
"include": ["./src"],
"references": [
{ "path": "../../components" },
{ "path": "../../gfx/turbo-renderer" },
{ "path": "../../inlines/preset" },
{ "path": "../../model" },
{ "path": "../../rich-text" },
@@ -216,6 +216,8 @@ export class EdgelessNoteBlockComponent extends toGfxBlockComponent(
const { borderRadius } = edgeless.style;
const { collapse = false, collapsedHeight, scale = 1 } = edgeless;
const { tool } = this.gfx;
const bound = Bound.deserialize(xywh);
const height = bound.h / scale;
@@ -280,7 +282,9 @@ export class EdgelessNoteBlockComponent extends toGfxBlockComponent(
.editing=${this._editing}
></edgeless-note-mask>
${isCollapsable && (!this.model.isPageBlock() || !hasHeader)
${isCollapsable &&
tool.currentToolName$.value !== 'frameNavigator' &&
(!this.model.isPageBlock() || !hasHeader)
? html`<div
class="${classMap({
[styles.collapseButton]: true,
@@ -3,5 +3,5 @@ export * from './commands';
export * from './paragraph-block.js';
export * from './paragraph-block-config.js';
export * from './paragraph-spec.js';
export * from './turbo/paragraph-layout-provider.js';
export * from './turbo/paragraph-painter.worker.js';
export * from './turbo/paragraph-layout-handler';
export * from './turbo/paragraph-painter.worker';
@@ -15,8 +15,10 @@ export class ParagraphLayoutHandlerExtension extends BlockLayoutHandlerExtension
readonly blockType = 'affine:paragraph';
static override setup(di: Container) {
const layoutHandler = new ParagraphLayoutHandlerExtension();
di.addImpl(BlockLayoutHandlersIdentifier, layoutHandler);
di.addImpl(
BlockLayoutHandlersIdentifier('paragraph'),
ParagraphLayoutHandlerExtension
);
}
queryLayout(component: GfxBlockComponent): ParagraphLayout | null {
@@ -4,7 +4,10 @@ import type {
TextRect,
WorkerToHostMessage,
} from '@blocksuite/affine-gfx-turbo-renderer';
import { BlockLayoutPainterExtension } from '@blocksuite/affine-gfx-turbo-renderer/painter';
import {
BlockLayoutPainterExtension,
getBaseline,
} from '@blocksuite/affine-gfx-turbo-renderer/painter';
interface SentenceLayout {
text: string;
@@ -17,25 +20,8 @@ export interface ParagraphLayout extends BlockLayout {
sentences: SentenceLayout[];
}
const meta = {
emSize: 2048,
hHeadAscent: 1984,
hHeadDescent: -494,
};
const debugSentenceBorder = false;
function getBaseline(fontSize: number) {
const lineHeight = 1.2 * fontSize;
const A = fontSize * (meta.hHeadAscent / meta.emSize); // ascent
const D = fontSize * (meta.hHeadDescent / meta.emSize); // descent
const AD = A + Math.abs(D); // ascent + descent
const L = lineHeight - AD; // leading
const y = A + L / 2;
return y;
}
function isParagraphLayout(layout: BlockLayout): layout is ParagraphLayout {
return layout.type === 'affine:paragraph';
}
@@ -1,4 +1,9 @@
import { FileDropExtension } from '@blocksuite/affine-components/drop-indicator';
import { ConnectorElementView } from '@blocksuite/affine-gfx-connector';
import { GroupElementView } from '@blocksuite/affine-gfx-group';
import { MindMapView } from '@blocksuite/affine-gfx-mindmap';
import { ShapeElementView } from '@blocksuite/affine-gfx-shape';
import { TextElementView } from '@blocksuite/affine-gfx-text';
import { NoteBlockSchema } from '@blocksuite/affine-model';
import {
DNDAPIExtension,
@@ -27,6 +32,19 @@ import {
viewportOverlayWidget,
} from './widgets';
/**
* Why do we add these extensions into CommonSpecs?
* Because in some cases we need to create edgeless elements in page mode.
* And these view may contain some logic when elements initialize.
*/
const EdgelessElementViews = [
ConnectorElementView,
MindMapView,
GroupElementView,
TextElementView,
ShapeElementView,
];
export const CommonSpecs: ExtensionType[] = [
FlavourExtension('affine:page'),
DocModeService,
@@ -38,6 +56,7 @@ export const CommonSpecs: ExtensionType[] = [
ToolbarRegistryExtension,
...RootBlockAdapterExtensions,
...clipboardConfigs,
...EdgelessElementViews,
modalWidget,
innerModalWidget,
@@ -1,8 +1,3 @@
import { ConnectorElementView } from '@blocksuite/affine-gfx-connector';
import { GroupElementView } from '@blocksuite/affine-gfx-group';
import { MindMapView } from '@blocksuite/affine-gfx-mindmap';
import { ShapeElementView } from '@blocksuite/affine-gfx-shape';
import { TextElementView } from '@blocksuite/affine-gfx-text';
import { ViewportElementExtension } from '@blocksuite/affine-shared/services';
import { autoConnectWidget } from '@blocksuite/affine-widget-edgeless-auto-connect';
import { edgelessToolbarWidget } from '@blocksuite/affine-widget-edgeless-toolbar';
@@ -90,20 +85,11 @@ const EdgelessClipboardConfigs: ExtensionType[] = [
EdgelessClipboardEmbedSyncedDocConfig,
];
export const gfxElementViews = [
ConnectorElementView,
MindMapView,
GroupElementView,
TextElementView,
ShapeElementView,
];
const EdgelessCommonExtension: ExtensionType[] = [
CommonSpecs,
ToolController,
EdgelessRootService,
ViewportElementExtension('.affine-edgeless-viewport'),
...gfxElementViews,
...quickTools,
...seniorTools,
...EdgelessClipboardConfigs,
@@ -105,3 +105,20 @@ export class ViewportLayoutPainter {
}
};
}
const meta = {
emSize: 2048,
hHeadAscent: 1984,
hHeadDescent: -494,
};
export function getBaseline(fontSize: number) {
const lineHeight = 1.2 * fontSize;
const A = fontSize * (meta.hHeadAscent / meta.emSize); // ascent
const D = fontSize * (meta.hHeadDescent / meta.emSize); // descent
const AD = A + Math.abs(D); // ascent + descent
const L = lineHeight - AD; // leading
const y = A + L / 2;
return y;
}
@@ -11,6 +11,7 @@
"license": "MIT",
"dependencies": {
"@blocksuite/affine-block-callout": "workspace:*",
"@blocksuite/affine-block-embed": "workspace:*",
"@blocksuite/affine-block-list": "workspace:*",
"@blocksuite/affine-block-note": "workspace:*",
"@blocksuite/affine-block-paragraph": "workspace:*",
@@ -0,0 +1,133 @@
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
import {
EmbedIcon,
FrameIcon,
ImageIcon,
PageIcon,
ShapeIcon,
} from '@blocksuite/icons/lit';
import { css, html, LitElement } from 'lit';
import { property } from 'lit/decorators.js';
import { repeat } from 'lit/directives/repeat.js';
import { styleMap } from 'lit/directives/style-map.js';
const BLOCK_PREVIEW_ICON_MAP: Record<
string,
{
icon: typeof ShapeIcon;
name: string;
}
> = {
shape: {
icon: ShapeIcon,
name: 'Edgeless shape',
},
'affine:image': {
icon: ImageIcon,
name: 'Image block',
},
'affine:note': {
icon: PageIcon,
name: 'Note block',
},
'affine:frame': {
icon: FrameIcon,
name: 'Frame block',
},
'affine:embed-': {
icon: EmbedIcon,
name: 'Embed block',
},
};
declare global {
interface HTMLElementTagNameMap {
'edgeless-dnd-preview-element': EdgelessDndPreviewElement;
}
}
export const EDGELESS_DND_PREVIEW_ELEMENT = 'edgeless-dnd-preview-element';
export class EdgelessDndPreviewElement extends LitElement {
static override styles = css`
.edgeless-dnd-preview-container {
position: relative;
padding: 12px;
width: 264px;
height: 80px;
}
.edgeless-dnd-preview-block {
display: flex;
position: absolute;
width: 234px;
align-items: flex-start;
box-sizing: border-box;
border-radius: 8px;
background-color: ${unsafeCSSVarV2(
'layer/background/overlayPanel',
'#FBFBFC'
)};
padding: 8px 20px;
gap: 8px;
transform-origin: center;
font-family: var(--affine-font-family);
box-shadow: 0px 0px 0px 0.5px #e3e3e4 inset;
}
.edgeless-dnd-preview-block > svg {
color: ${unsafeCSSVarV2('icon/primary', '#77757D')};
}
.edgeless-dnd-preview-block > .text {
color: ${unsafeCSSVarV2('text/primary', '#121212')};
font-size: 14px;
line-height: 24px;
}
`;
@property({ type: Array })
accessor elementTypes: {
type: string;
}[] = [];
private _getPreviewIcon(type: string) {
if (BLOCK_PREVIEW_ICON_MAP[type]) {
return BLOCK_PREVIEW_ICON_MAP[type];
}
if (type.startsWith('affine:embed-')) {
return BLOCK_PREVIEW_ICON_MAP['affine:embed-'];
}
return {
icon: ShapeIcon,
name: 'Edgeless content',
};
}
override render() {
const blocks = repeat(this.elementTypes.slice(0, 3), ({ type }, index) => {
const { icon, name } = this._getPreviewIcon(type);
return html`<div
class="edgeless-dnd-preview-block"
style=${styleMap({
transform: `rotate(${index * -2}deg)`,
zIndex: 3 - index,
})}
>
${icon({ width: '24px', height: '24px' })}
<span class="text">${name}</span>
</div>`;
});
return html`<div class="edgeless-dnd-preview-container">${blocks}</div>`;
}
}
@@ -1,6 +1,14 @@
import {
EDGELESS_DND_PREVIEW_ELEMENT,
EdgelessDndPreviewElement,
} from './components/edgeless-preview/preview';
import { AFFINE_DRAG_HANDLE_WIDGET } from './consts';
import { AffineDragHandleWidget } from './drag-handle';
export function effects() {
customElements.define(AFFINE_DRAG_HANDLE_WIDGET, AffineDragHandleWidget);
customElements.define(
EDGELESS_DND_PREVIEW_ELEMENT,
EdgelessDndPreviewElement
);
}
@@ -1,19 +1,11 @@
import { SurfaceBlockModel } from '@blocksuite/affine-block-surface';
import { RootBlockModel } from '@blocksuite/affine-model';
import {
DocModeExtension,
DocModeProvider,
EditorSettingExtension,
EditorSettingProvider,
} from '@blocksuite/affine-shared/services';
import { matchModels, SpecProvider } from '@blocksuite/affine-shared/utils';
import {
type BlockComponent,
BlockStdScope,
BlockViewIdentifier,
LifeCycleWatcher,
} from '@blocksuite/std';
import { GfxControllerIdentifier } from '@blocksuite/std/gfx';
import { SpecProvider } from '@blocksuite/affine-shared/utils';
import { BlockStdScope, BlockViewIdentifier } from '@blocksuite/std';
import type {
BlockModel,
BlockViewType,
@@ -24,14 +16,11 @@ import type {
import { signal } from '@preact/signals-core';
import { literal } from 'lit/static-html.js';
import { EdgelessDndPreviewElement } from '../components/edgeless-preview/preview.js';
import type { AffineDragHandleWidget } from '../drag-handle.js';
import { getSnapshotRect } from '../utils.js';
export class PreviewHelper {
private readonly _calculateQuery = (
selectedIds: string[],
mode: 'block' | 'gfx'
): Query => {
private readonly _calculateQuery = (selectedIds: string[]): Query => {
const ids: Array<{ id: string; viewType: BlockViewType }> = selectedIds.map(
id => ({
id,
@@ -58,22 +47,10 @@ export class PreviewHelper {
}
const children = model.children ?? [];
if (
mode === 'gfx' &&
matchModels(model, [RootBlockModel, SurfaceBlockModel])
) {
children.forEach(child => {
if (selectedIds.includes(child.id)) {
ids.push({ viewType: 'display', id: child.id });
addChildren(child.id);
}
});
} else {
children.forEach(child => {
ids.push({ viewType: 'display', id: child.id });
addChildren(child.id);
});
}
children.forEach(child => {
ids.push({ viewType: 'display', id: child.id });
addChildren(child.id);
});
};
selectedIds.forEach(addChildren);
@@ -83,28 +60,16 @@ export class PreviewHelper {
};
};
getPreviewStd = (
blockIds: string[],
snapshot: SliceSnapshot,
mode: 'block' | 'gfx'
) => {
getPreviewStd = (blockIds: string[]) => {
const widget = this.widget;
const std = widget.std;
const sourceGfx = std.get(GfxControllerIdentifier);
const isEdgeless = mode === 'gfx';
blockIds = blockIds.slice();
if (isEdgeless) {
blockIds.push(sourceGfx.surface!.id, std.store.root!.id);
}
const docModeService = std.get(DocModeProvider);
const editorSetting = std.get(EditorSettingProvider).peek();
const query = this._calculateQuery(blockIds as string[], mode);
const query = this._calculateQuery(blockIds as string[]);
const store = widget.doc.doc.getStore({ query });
const previewSpec = SpecProvider._.getSpec(
isEdgeless ? 'preview:edgeless' : 'preview:page'
);
const previewSpec = SpecProvider._.getSpec('preview:page');
const settingSignal = signal({ ...editorSetting });
const extensions = [
DocModeExtension(docModeService),
@@ -134,35 +99,6 @@ export class PreviewHelper {
} as ExtensionType,
];
if (isEdgeless) {
class PreviewViewportInitializer extends LifeCycleWatcher {
static override key = 'preview-viewport-initializer';
override mounted(): void {
const rect = getSnapshotRect(snapshot);
if (!rect) {
return;
}
this.std.view.viewUpdated.subscribe(payload => {
if (payload.type !== 'block') return;
if (payload.view.model.flavour === 'affine:page') {
const gfx = this.std.get(GfxControllerIdentifier);
(
payload.view as BlockComponent & { overrideBackground: string }
).overrideBackground = 'transparent';
gfx.viewport.setViewportByBound(rect);
}
});
}
}
extensions.push(PreviewViewportInitializer);
}
previewSpec.extend(extensions);
settingSignal.value = {
@@ -177,50 +113,92 @@ export class PreviewHelper {
let width: number = 500;
let height;
let scale = 1;
if (isEdgeless) {
const rect = getSnapshotRect(snapshot);
if (rect) {
width = rect.w;
height = rect.h;
} else {
height = 500;
}
scale = sourceGfx.viewport.zoom;
} else {
const noteBlock = this.widget.host.querySelector('affine-note');
width = noteBlock?.offsetWidth ?? noteBlock?.clientWidth ?? 500;
}
const noteBlock = this.widget.host.querySelector('affine-note');
width = noteBlock?.offsetWidth ?? noteBlock?.clientWidth ?? 500;
return {
scale,
previewStd,
width,
height,
};
};
private _extractBlockTypes(snapshot: SliceSnapshot) {
const blockTypes: {
type: string;
}[] = [];
snapshot.content.forEach(block => {
if (block.flavour === 'affine:surface') {
Object.values(
block.props.elements as Record<string, { id: string; type: string }>
).forEach(elem => {
blockTypes.push({
type: elem.type,
});
});
} else {
blockTypes.push({
type: block.flavour,
});
}
});
return blockTypes;
}
getPreviewElement = (options: {
blockIds: string[];
snapshot: SliceSnapshot;
mode: 'block' | 'gfx';
}) => {
const { blockIds, snapshot, mode } = options;
if (mode === 'block') {
const { previewStd, width, height } = this.getPreviewStd(blockIds);
const previewTemplate = previewStd.render();
return {
width,
height,
element: previewTemplate,
};
} else {
const blockTypes = this._extractBlockTypes(snapshot);
const edgelessPreview = new EdgelessDndPreviewElement();
edgelessPreview.elementTypes = blockTypes;
return {
left: 12,
top: 12,
element: edgelessPreview,
};
}
};
renderDragPreview = (options: {
blockIds: string[];
snapshot: SliceSnapshot;
container: HTMLElement;
mode: 'block' | 'gfx';
}): void => {
const { blockIds, snapshot, container, mode } = options;
const { previewStd, width, height, scale } = this.getPreviewStd(
blockIds,
snapshot,
mode
);
const previewTemplate = previewStd.render();
}): { x: number; y: number } => {
const { container } = options;
const { width, height, element, left, top } =
this.getPreviewElement(options);
container.style.transform = `scale(${scale})`;
container.style.width = `${width}px`;
if (height) {
container.style.height = `${height}px`;
}
container.append(previewTemplate);
container.style.position = 'absolute';
container.style.left = left ? `${left}px` : '';
container.style.top = top ? `${top}px` : '';
container.style.width = width ? `${width}px` : '';
container.style.height = height ? `${height}px` : '';
container.append(element);
return {
x: left ?? 0,
y: top ?? 0,
};
};
constructor(readonly widget: AffineDragHandleWidget) {}
@@ -1,3 +1,7 @@
import {
EMBED_IFRAME_DEFAULT_HEIGHT_IN_SURFACE,
EMBED_IFRAME_DEFAULT_WIDTH_IN_SURFACE,
} from '@blocksuite/affine-block-embed';
import { ParagraphBlockComponent } from '@blocksuite/affine-block-paragraph';
import { DropIndicator } from '@blocksuite/affine-components/drop-indicator';
import {
@@ -511,6 +515,7 @@ export class DragEventWatcher {
this._mergeSnapshotToCurDoc(snapshot, point).catch(console.error);
} else {
this._dropAsGfxBlock(snapshot, point);
this.widget.selectionHelper.selection.clear(['block']);
}
} else {
this._onPageDrop(dropBlock, dragPayload, dropPayload, point);
@@ -1052,7 +1057,10 @@ export class DragEventWatcher {
Bound.deserialize(block.props.xywh as SerializedXYWH) ??
new Bound(0, 0, 0, 0);
if (
if (block.flavour === 'affine:embed-iframe') {
blockBound.w = EMBED_IFRAME_DEFAULT_WIDTH_IN_SURFACE;
blockBound.h = EMBED_IFRAME_DEFAULT_HEIGHT_IN_SURFACE;
} else if (
block.flavour === 'affine:attachment' ||
block.flavour === 'affine:bookmark' ||
block.flavour.startsWith('affine:embed-')
@@ -1132,17 +1140,22 @@ export class DragEventWatcher {
this._dropToModel(surfaceSnapshot, this.gfx.surface!.id)
.then(slices => {
slices?.content.forEach((block, idx) => {
if (
block.id === content[idx].id &&
(block.flavour === 'affine:image' ||
if (block.id === content[idx].id) {
if (block.flavour === 'affine:embed-iframe') {
store.updateBlock(block.id, {
xywh: content[idx].props.xywh,
});
} else if (
block.flavour === 'affine:image' ||
block.flavour === 'affine:attachment' ||
block.flavour === 'affine:bookmark' ||
block.flavour.startsWith('affine:embed-'))
) {
store.updateBlock(block.id, {
xywh: content[idx].props.xywh,
style: content[idx].props.style,
});
block.flavour.startsWith('affine:embed-')
) {
store.updateBlock(block.id, {
xywh: content[idx].props.xywh,
style: content[idx].props.style,
});
}
}
});
})
@@ -1160,7 +1173,11 @@ export class DragEventWatcher {
this._dropToModel(pageSnapshot, this.widget.doc.root!.id)
.then(slices => {
slices?.content.forEach((block, idx) => {
if (
if (block.flavour === 'affine:embed-iframe') {
store.updateBlock(block.id, {
xywh: content[idx].props.xywh,
});
} else if (
block.flavour === 'affine:attachment' ||
block.flavour.startsWith('affine:embed-')
) {
@@ -1403,14 +1420,14 @@ export class DragEventWatcher {
const { snapshot, fromMode } = source.data.bsEntity;
this.previewHelper.renderDragPreview({
const offset = this.previewHelper.renderDragPreview({
blockIds: source.data?.bsEntity?.modelIds,
snapshot,
container,
mode: fromMode ?? 'block',
});
setOffset({ x: 0, y: 0 });
setOffset(offset);
},
setDragData: () => {
const { fromMode, snapshot } = this._getDraggedSnapshot();
@@ -8,6 +8,7 @@
"include": ["./src"],
"references": [
{ "path": "../../blocks/block-callout" },
{ "path": "../../blocks/block-embed" },
{ "path": "../../blocks/block-list" },
{ "path": "../../blocks/block-note" },
{ "path": "../../blocks/block-paragraph" },
+3 -3
View File
@@ -71,13 +71,13 @@
"electron": "^35.0.0",
"eslint": "^9.16.0",
"eslint-config-prettier": "^10.0.0",
"eslint-import-resolver-typescript": "^3.7.0",
"eslint-import-resolver-typescript": "^4.0.0",
"eslint-plugin-import-x": "^4.5.0",
"eslint-plugin-react": "^7.37.2",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-sonarjs": "^3.0.1",
"eslint-plugin-unicorn": "^57.0.0",
"eslint-plugin-unicorn": "^58.0.0",
"happy-dom": "^17.0.0",
"husky": "^9.1.7",
"lint-staged": "^15.2.11",
@@ -92,7 +92,7 @@
"vite": "^6.0.3",
"vitest": "3.0.9"
},
"packageManager": "yarn@4.7.0",
"packageManager": "yarn@4.8.0",
"resolutions": {
"array-buffer-byte-length": "npm:@nolyfill/array-buffer-byte-length@^1",
"array-includes": "npm:@nolyfill/array-includes@^1",
+16 -15
View File
@@ -26,24 +26,24 @@
},
"dependencies": {
"@ai-sdk/google": "^1.1.19",
"@apollo/server": "^4.11.2",
"@apollo/server": "^4.11.3",
"@aws-sdk/client-s3": "^3.709.0",
"@fal-ai/serverless-client": "^0.15.0",
"@google-cloud/opentelemetry-cloud-monitoring-exporter": "^0.20.0",
"@google-cloud/opentelemetry-cloud-trace-exporter": "^2.4.1",
"@google-cloud/opentelemetry-resource-util": "^2.4.0",
"@nestjs-cls/transactional": "^2.4.4",
"@nestjs-cls/transactional-adapter-prisma": "^1.2.7",
"@nestjs/apollo": "^12.2.2",
"@nestjs/bullmq": "^10.2.3",
"@nestjs/common": "^10.4.15",
"@nestjs/core": "^10.4.15",
"@nestjs/graphql": "^12.2.2",
"@nestjs/platform-express": "^10.4.15",
"@nestjs/platform-socket.io": "^10.4.15",
"@nestjs/schedule": "^4.1.2",
"@nestjs/throttler": "6.4.0",
"@nestjs/websockets": "^10.4.15",
"@nestjs-cls/transactional": "^2.6.1",
"@nestjs-cls/transactional-adapter-prisma": "^1.2.19",
"@nestjs/apollo": "^13.0.4",
"@nestjs/bullmq": "^11.0.2",
"@nestjs/common": "^11.0.12",
"@nestjs/core": "^11.0.12",
"@nestjs/graphql": "^13.0.4",
"@nestjs/platform-express": "^11.0.12",
"@nestjs/platform-socket.io": "^11.0.12",
"@nestjs/schedule": "^5.0.1",
"@nestjs/throttler": "^6.4.0",
"@nestjs/websockets": "^11.0.12",
"@node-rs/argon2": "^2.0.2",
"@node-rs/crc32": "^1.10.6",
"@opentelemetry/api": "^1.9.0",
@@ -73,7 +73,7 @@
"dotenv": "^16.4.7",
"eventemitter2": "^6.4.9",
"eventsource-parser": "^3.0.0",
"express": "^4.21.2",
"express": "^5.0.1",
"fast-xml-parser": "^5.0.0",
"get-stream": "^9.0.1",
"graphql": "^16.9.0",
@@ -120,7 +120,8 @@
"@faker-js/faker": "^9.6.0",
"@nestjs/testing": "patch:@nestjs/testing@npm%3A10.4.15#~/.yarn/patches/@nestjs-testing-npm-10.4.15-d591a1705a.patch",
"@types/cookie-parser": "^1.4.8",
"@types/express": "^4.17.21",
"@types/express": "^5.0.1",
"@types/express-serve-static-core": "^5.0.6",
"@types/graphql-upload": "^17.0.0",
"@types/http-errors": "^2.0.4",
"@types/lodash-es": "^4.17.12",
@@ -37,7 +37,7 @@ export type ConfigDescriptor<T> = {
type ConfigDefineDescriptor<T> = {
desc: string;
default: T;
validate?: (value: T) => boolean;
validate?: (value: T) => z.SafeParseReturnType<T, T>;
shape?: z.ZodType<T>;
env?: string | [string, EnvConfigType];
link?: string;
@@ -158,7 +158,7 @@ function standardizeDescriptor<T>(
default: desc.default,
type,
validate: (value: T) => {
return shape.safeParse(value);
return desc.validate ? desc.validate(value) : shape.safeParse(value);
},
env,
link: desc.link,
@@ -257,7 +257,15 @@ export function getDefaultConfig(): AppConfigSchema {
const { success, error } = desc.validate(defaultValue);
if (!success) {
throw error;
throw new Error(
error.issues
.map(issue => {
return `Invalid config for module [${module}] with key [${key}]
Value: ${JSON.stringify(defaultValue)}
Error: ${issue.message}`;
})
.join('\n')
);
}
set(modulizedConfig, key, defaultValue);
@@ -23,7 +23,7 @@ defineModuleConfig('redis', {
desc: 'The database index of redis server to be used(Must be less than 10).',
default: 0,
env: ['REDIS_SERVER_DATABASE', 'integer'],
validate: val => val >= 0 && val < 10,
shape: z.number().int().nonnegative().max(10),
},
host: {
desc: 'The host of the redis server.',
@@ -9,12 +9,12 @@ export interface ServerFlags {
declare global {
interface AppConfigSchema {
server: {
externalUrl: string;
externalUrl?: string;
https: boolean;
host: string;
port: number;
path: string;
name: string | undefined;
name?: string;
};
flags: ServerFlags;
}
@@ -29,9 +29,16 @@ defineModuleConfig('server', {
desc: `Base url of AFFiNE server, used for generating external urls.
Default to be \`[server.protocol]://[server.host][:server.port]\` if not specified.
`,
default: 'http://localhost:3010',
default: '',
env: 'AFFINE_SERVER_EXTERNAL_URL',
shape: z.string().url(),
validate: val => {
// allow to be nullable and empty string
if (!val) {
return { success: true, data: val };
}
return z.string().url().safeParse(val);
},
},
https: {
desc: 'Whether the server is hosted on a ssl enabled domain (https://).',
@@ -60,7 +60,7 @@ export class DocRendererController {
}
@Public()
@Get('/*')
@Get('/*path')
async render(@Req() req: Request, @Res() res: Response) {
const assets: HtmlAssets =
env.namespaces.canary &&
@@ -62,7 +62,7 @@ export class StaticFilesResolver implements OnModuleInit {
// fallback all unknown routes
app.get(
[basePath + '/admin', basePath + '/admin/*'],
[basePath + '/admin', basePath + '/admin/*path'],
this.check.use,
(_req, res) => {
res.sendFile(
@@ -101,11 +101,13 @@ export class StaticFilesResolver implements OnModuleInit {
redirect: false,
index: false,
fallthrough: true,
immutable: true,
dotfiles: 'ignore',
})
);
// fallback all unknown routes
app.get([basePath, basePath + '/*'], this.check.use, (req, res) => {
app.get([basePath, basePath + '/*path'], this.check.use, (req, res) => {
const mobile =
env.namespaces.canary &&
isMobile({
@@ -12,7 +12,7 @@ import { Text } from '@blocksuite/affine/store';
import type { BlobEngine } from '@blocksuite/affine/sync';
import type { FrameworkProvider } from '@toeverything/infra';
import { getCurrentWorkspace } from './utils';
import { getCurrentWorkspace, isAiEnabled } from './utils';
const logger = new DebugLogger('electron-renderer:recording');
@@ -44,6 +44,7 @@ export function setupRecordingEvents(frameworkProvider: FrameworkProvider) {
frameworkProvider.get(EditorSettingService);
const docsService = workspace.scope.get(DocsService);
const editorSetting = editorSettingService.editorSetting;
const aiEnabled = isAiEnabled(frameworkProvider);
const timestamp = i18nTime(status.startTime, {
absolute: {
@@ -96,7 +97,10 @@ export function setupRecordingEvents(frameworkProvider: FrameworkProvider) {
MeetingSettingsService
);
if (!meetingSettingsService.settings.autoTranscription) {
if (
!meetingSettingsService.settings.autoTranscription ||
!aiEnabled
) {
// auto transcription is disabled,
// so we don't need to transcribe the recording by default
return;
@@ -1,3 +1,5 @@
import { ServersService } from '@affine/core/modules/cloud';
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
import { GlobalContextService } from '@affine/core/modules/global-context';
import { WorkspacesService } from '@affine/core/modules/workspace';
import type { FrameworkProvider } from '@toeverything/infra';
@@ -21,3 +23,23 @@ export function getCurrentWorkspace(frameworkProvider: FrameworkProvider) {
[Symbol.dispose]: dispose,
};
}
export function getCurrentServerService(frameworkProvider: FrameworkProvider) {
const currentServerId = frameworkProvider
.get(GlobalContextService)
.globalContext.serverId.get();
const serversService = frameworkProvider.get(ServersService);
const serverRef = currentServerId
? serversService.servers$.value.find(
server => server.id === currentServerId
)
: null;
return serverRef;
}
export function isAiEnabled(frameworkProvider: FrameworkProvider) {
const featureFlagService = frameworkProvider.get(FeatureFlagService);
const serverService = getCurrentServerService(frameworkProvider);
const aiConfig = serverService?.features$.value.copilot;
return featureFlagService.flags.enable_ai.$ && aiConfig;
}
@@ -94,6 +94,7 @@ export function Recording() {
let id: number | undefined;
try {
const result = await apis?.recording?.getCurrentRecording();
if (!result) {
return;
}
@@ -389,6 +389,7 @@ function getAllApps(): TappableAppInfo[] {
(v): v is TappableAppInfo =>
v !== null &&
!v.bundleIdentifier.startsWith('com.apple') &&
!v.bundleIdentifier.startsWith('pro.affine') &&
v.processId !== process.pid
);
return filteredApps;
@@ -470,11 +471,11 @@ export function setupRecordingFeature() {
shareableContent = new ShareableContent();
setupMediaListeners();
}
// reset all states
recordingStatus$.next(null);
setupAppGroups();
setupNewRunningAppGroup();
setupRecordingListeners();
// reset all states
recordingStatus$.next(null);
return true;
} catch (error) {
logger.error('failed to setup recording feature', error);
@@ -498,10 +499,6 @@ function normalizeAppGroupInfo(
export function newRecording(
appGroup?: AppGroupInfo | number
): RecordingStatus | null {
if (!shareableContent) {
return null; // likely called on unsupported platform
}
return recordingStateMachine.dispatch({
type: 'NEW_RECORDING',
appGroup: normalizeAppGroupInfo(appGroup),
@@ -516,14 +513,17 @@ export function startRecording(
appGroup: normalizeAppGroupInfo(appGroup),
});
if (state?.status === 'recording') {
// set a timeout to stop the recording after MAX_DURATION_FOR_TRANSCRIPTION
setTimeout(() => {
// set a timeout to stop the recording after MAX_DURATION_FOR_TRANSCRIPTION
setTimeout(() => {
if (
state?.status === 'recording' &&
state.id === recordingStatus$.value?.id
) {
stopRecording(state.id).catch(err => {
logger.error('failed to stop recording', err);
});
}, MAX_DURATION_FOR_TRANSCRIPTION);
}
}
}, MAX_DURATION_FOR_TRANSCRIPTION);
return state;
}
@@ -548,30 +548,48 @@ export async function stopRecording(id: number) {
return;
}
const recordingStatus = recordingStateMachine.dispatch({
type: 'STOP_RECORDING',
id,
filepath: String(recording.file.path),
sampleRate: recording.stream.sampleRate,
numberOfChannels: recording.stream.channels,
});
if (!recordingStatus) {
logger.error('No recording status to stop');
return;
}
const { file } = recording;
file.end();
// Wait for file to finish writing
await new Promise<void>(resolve => {
file.on('finish', () => {
resolve();
try {
await new Promise<void>((resolve, reject) => {
file.on('finish', () => {
// check if the file is empty
const stats = fs.statSync(file.path);
if (stats.size === 0) {
logger.error(`Recording ${id} is empty`);
reject(new Error('Recording is empty'));
}
resolve();
});
});
const recordingStatus = recordingStateMachine.dispatch({
type: 'STOP_RECORDING',
id,
filepath: String(recording.file.path),
sampleRate: recording.stream.sampleRate,
numberOfChannels: recording.stream.channels,
});
});
return serializeRecordingStatus(recordingStatus);
if (!recordingStatus) {
logger.error('No recording status to stop');
return;
}
return serializeRecordingStatus(recordingStatus);
} catch (error: unknown) {
logger.error('Failed to stop recording', error);
const recordingStatus = recordingStateMachine.dispatch({
type: 'CREATE_BLOCK_FAILED',
id,
error: error instanceof Error ? error : undefined,
});
if (!recordingStatus) {
logger.error('No recording status to stop');
return;
}
return serializeRecordingStatus(recordingStatus);
}
}
export async function readyRecording(id: number, buffer: Buffer) {
@@ -64,6 +64,10 @@ function buildMenuConfig(config: TrayMenuConfig): MenuItemConstructorOptions[] {
}
if (nativeIcon) {
nativeIcon = nativeIcon.resize({ width: 20, height: 20 });
// string icon should be template image
if (typeof icon === 'string') {
nativeIcon.setTemplateImage(true);
}
}
const submenuConfig = submenu ? buildMenuConfig(submenu) : undefined;
menuConfig.push({
@@ -14,7 +14,7 @@ let package = Package(
.library(name: "AffineGraphQL", targets: ["AffineGraphQL"]),
],
dependencies: [
.package(url: "https://github.com/apollographql/apollo-ios", exact: "1.18.0"),
.package(url: "https://github.com/apollographql/apollo-ios", exact: "1.19.0"),
],
targets: [
.target(
@@ -16,7 +16,7 @@ let package = Package(
dependencies: [
.package(path: "../AffineGraphQL"),
.package(path: "../MarkdownView"),
.package(url: "https://github.com/apollographql/apollo-ios.git", from: "1.18.0"),
.package(url: "https://github.com/apollographql/apollo-ios.git", from: "1.19.0"),
.package(url: "https://github.com/LaunchDarkly/swift-eventsource.git", from: "3.3.0"),
.package(url: "https://github.com/apple/swift-collections", from: "1.1.4"),
.package(url: "https://github.com/Lakr233/ChidoriMenu", from: "2.4.3"),
@@ -14,7 +14,7 @@ let package = Package(
],
dependencies: [
.package(url: "https://github.com/JohnSundell/Splash", from: "0.16.0"),
.package(url: "https://github.com/swiftlang/swift-cmark", from: "0.4.0"),
.package(url: "https://github.com/swiftlang/swift-cmark", from: "0.5.0"),
],
targets: [
.target(name: "MarkdownView", dependencies: [
@@ -83,7 +83,7 @@ export const controlButton = style({
justifyContent: 'center',
borderRadius: '50%',
backgroundColor: cssVarV2('layer/background/secondary'),
color: cssVarV2('text/primary'),
color: cssVarV2('icon/primary'),
});
export const controls = style({
@@ -0,0 +1,3 @@
# Sortable
Migrated from https://github.com/clauderic/dnd-kit
@@ -0,0 +1,123 @@
import type { ElementDragType } from '@atlaskit/pragmatic-drag-and-drop/types';
import React, {
useEffect,
useLayoutEffect,
useMemo,
useRef,
useState,
} from 'react';
import { rectSortingStrategy } from './strategies';
import type {
ClientRect,
Disabled,
SortingStrategy,
UniqueIdentifier,
} from './types';
import {
getSortedRects,
itemsEqual,
normalizeDisabled,
useUniqueId,
} from './utilities';
export interface Props {
children: React.ReactNode;
items: (UniqueIdentifier | { id: UniqueIdentifier })[];
strategy?: SortingStrategy;
disabled?: boolean | Disabled;
}
const ID_PREFIX = 'Sortable';
interface ContextDescriptor {
activeIndex: number;
containerId: string;
disableTransforms: boolean;
items: {
id: UniqueIdentifier;
}[];
overIndex: number;
sortedRects: ClientRect[];
strategy: SortingStrategy;
disabled: Disabled;
}
export const Context = React.createContext<ContextDescriptor>({
activeIndex: -1,
containerId: ID_PREFIX,
disableTransforms: false,
items: [],
overIndex: -1,
sortedRects: [],
strategy: rectSortingStrategy,
disabled: {
draggable: false,
droppable: false,
},
});
export function SortableContext({
children,
items: userDefinedItems,
strategy = rectSortingStrategy,
disabled: disabledProp = false,
}: Props) {
const [active, setActive] = useState<ElementDragType | null>(null);
const { active, droppableRects, over, measureDroppableContainers } =
useDndContext();
const containerId = useUniqueId(ID_PREFIX, id);
const items = useMemo<UniqueIdentifier[]>(
() =>
userDefinedItems.map(item =>
typeof item === 'object' && 'id' in item ? item.id : item
),
[userDefinedItems]
);
const isDragging = active != null;
const activeIndex = active ? items.indexOf(active.id) : -1;
const overIndex = over ? items.indexOf(over.id) : -1;
const previousItemsRef = useRef(items);
const itemsHaveChanged = !itemsEqual(items, previousItemsRef.current);
const disableTransforms =
(overIndex !== -1 && activeIndex === -1) || itemsHaveChanged;
const disabled = normalizeDisabled(disabledProp);
useLayoutEffect(() => {
if (itemsHaveChanged && isDragging) {
measureDroppableContainers(items);
}
}, [itemsHaveChanged, items, isDragging, measureDroppableContainers]);
useEffect(() => {
previousItemsRef.current = items;
}, [items]);
const contextValue = useMemo(
(): ContextDescriptor => ({
activeIndex,
containerId,
disabled,
disableTransforms,
items,
overIndex,
sortedRects: getSortedRects(items, droppableRects),
strategy,
}),
// eslint-disable-next-line react-hooks/exhaustive-deps
[
activeIndex,
containerId,
disabled.draggable,
disabled.droppable,
disableTransforms,
items,
overIndex,
droppableRects,
strategy,
]
);
return <Context.Provider value={contextValue}>{children}</Context.Provider>;
}
@@ -0,0 +1,25 @@
import type { SortingStrategy } from './types';
import { arrayMove } from './utilities';
export const rectSortingStrategy: SortingStrategy = ({
rects,
activeIndex,
overIndex,
index,
}) => {
const newRects = arrayMove(rects, overIndex, activeIndex);
const oldRect = rects[index];
const newRect = newRects[index];
if (!newRect || !oldRect) {
return null;
}
return {
x: newRect.left - oldRect.left,
y: newRect.top - oldRect.top,
scaleX: newRect.width / oldRect.width,
scaleY: newRect.height / oldRect.height,
};
};
@@ -0,0 +1,32 @@
export type Transform = {
x: number;
y: number;
scaleX: number;
scaleY: number;
};
export interface ClientRect {
width: number;
height: number;
top: number;
left: number;
right: number;
bottom: number;
}
export type SortingStrategy = (args: {
activeNodeRect: ClientRect | null;
activeIndex: number;
index: number;
rects: ClientRect[];
overIndex: number;
}) => Transform | null;
export type UniqueIdentifier = string | number;
export type RectMap = Map<UniqueIdentifier, ClientRect>;
export interface Disabled {
draggable?: boolean;
droppable?: boolean;
}
@@ -0,0 +1,76 @@
import { useMemo } from 'react';
import type { ClientRect, Disabled, RectMap, UniqueIdentifier } from './types';
let ids: Record<string, number> = {};
export function useUniqueId(prefix: string, value?: string) {
return useMemo(() => {
if (value) {
return value;
}
const id = ids[prefix] == null ? 0 : ids[prefix] + 1;
ids[prefix] = id;
return `${prefix}-${id}`;
}, [prefix, value]);
}
/**
* Move an array item to a different position. Returns a new array with the item moved to the new position.
*/
export function arrayMove<T>(array: T[], from: number, to: number): T[] {
const newArray = array.slice();
newArray.splice(
to < 0 ? newArray.length + to : to,
0,
newArray.splice(from, 1)[0]
);
return newArray;
}
export function getSortedRects(items: UniqueIdentifier[], rects: RectMap) {
return items.reduce<ClientRect[]>(
(accumulator, id, index) => {
const rect = rects.get(id);
if (rect) {
accumulator[index] = rect;
}
return accumulator;
},
Array.from({ length: items.length })
);
}
export function itemsEqual(a: UniqueIdentifier[], b: UniqueIdentifier[]) {
if (a === b) {
return true;
}
if (a.length !== b.length) {
return false;
}
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) {
return false;
}
}
return true;
}
export function normalizeDisabled(disabled: boolean | Disabled): Disabled {
if (typeof disabled === 'boolean') {
return {
draggable: disabled,
droppable: disabled,
};
}
return disabled;
}
@@ -2,6 +2,10 @@ import { cssVarV2 } from '@toeverything/theme/v2';
import { globalStyle, style } from '@vanilla-extract/css';
export const root = style({
display: 'inline-flex',
height: '1em',
width: '1em',
alignItems: 'center',
justifyContent: 'center',
});
// replace primary colors to cssVarV2('icon/primary')
@@ -28,14 +32,14 @@ const backgroundSecondaryColors = [
globalStyle(
`${root} :is(${iconPrimaryColors.map(color => `path[fill="${color}"]`).join(',')})`,
{
fill: cssVarV2('icon/primary'),
fill: 'currentColor',
}
);
globalStyle(
`${root} :is(${iconPrimaryColors.map(color => `path[stroke="${color}"]`).join(',')})`,
{
stroke: cssVarV2('icon/primary'),
stroke: 'currentColor',
}
);
@@ -49,6 +49,7 @@ import {
export const translateSubItem: AISubItemConfig[] = translateLangs.map(lang => {
return {
type: lang,
testId: `action-translate-${lang}`,
handler: actionToHandler('translate', AIStarIconWithAnimation, { lang }),
};
});
@@ -56,6 +57,7 @@ export const translateSubItem: AISubItemConfig[] = translateLangs.map(lang => {
export const toneSubItem: AISubItemConfig[] = textTones.map(tone => {
return {
type: tone,
testId: `action-change-tone-${tone.toLowerCase()}`,
handler: actionToHandler('changeTone', AIStarIconWithAnimation, { tone }),
};
});
@@ -66,6 +68,7 @@ export function createImageFilterSubItem(
return imageFilterStyles.map(style => {
return {
type: style,
testId: `action-image-filter-${style.toLowerCase().replace(' ', '-')}`,
handler: actionToHandler(
'filterImage',
AIImageIconWithAnimation,
@@ -84,6 +87,7 @@ export function createImageProcessingSubItem(
return imageProcessingTypes.map(type => {
return {
type,
testId: `action-image-processing-${type.toLowerCase().replace(' ', '-')}`,
handler: actionToHandler(
'processImage',
AIImageIconWithAnimation,
@@ -146,36 +150,42 @@ const EditAIGroup: AIItemGroupConfig = {
items: [
{
name: 'Translate to',
testId: 'action-translate',
icon: LanguageIcon(),
showWhen: textBlockShowWhen,
subItem: translateSubItem,
},
{
name: 'Change tone to',
testId: 'action-change-tone',
icon: ToneIcon(),
showWhen: textBlockShowWhen,
subItem: toneSubItem,
},
{
name: 'Improve writing',
testId: 'action-improve-writing',
icon: ImproveWritingIcon(),
showWhen: textBlockShowWhen,
handler: actionToHandler('improveWriting', AIStarIconWithAnimation),
},
{
name: 'Make it longer',
testId: 'action-make-it-longer',
icon: LongerIcon(),
showWhen: textBlockShowWhen,
handler: actionToHandler('makeLonger', AIStarIconWithAnimation),
},
{
name: 'Make it shorter',
testId: 'action-make-it-shorter',
icon: ShorterIcon(),
showWhen: textBlockShowWhen,
handler: actionToHandler('makeShorter', AIStarIconWithAnimation),
},
{
name: 'Continue writing',
testId: 'action-continue-writing',
icon: PenIcon(),
showWhen: textBlockShowWhen,
handler: actionToHandler('continueWriting', AIPenIconWithAnimation),
@@ -188,30 +198,35 @@ const DraftAIGroup: AIItemGroupConfig = {
items: [
{
name: 'Write an article about this',
testId: 'action-write-article',
icon: PenIcon(),
showWhen: textBlockShowWhen,
handler: actionToHandler('writeArticle', AIPenIconWithAnimation),
},
{
name: 'Write a tweet about this',
testId: 'action-write-twitter-post',
icon: PenIcon(),
showWhen: textBlockShowWhen,
handler: actionToHandler('writeTwitterPost', AIPenIconWithAnimation),
},
{
name: 'Write a poem about this',
testId: 'action-write-poem',
icon: PenIcon(),
showWhen: textBlockShowWhen,
handler: actionToHandler('writePoem', AIPenIconWithAnimation),
},
{
name: 'Write a blog post about this',
testId: 'action-write-blog-post',
icon: PenIcon(),
showWhen: textBlockShowWhen,
handler: actionToHandler('writeBlogPost', AIPenIconWithAnimation),
},
{
name: 'Brainstorm ideas about this',
testId: 'action-brainstorm',
icon: PenIcon(),
showWhen: textBlockShowWhen,
handler: actionToHandler('brainstorm', AIPenIconWithAnimation),
@@ -224,36 +239,42 @@ const ReviewWIthAIGroup: AIItemGroupConfig = {
items: [
{
name: 'Fix spelling',
testId: 'action-fix-spelling',
icon: DoneIcon(),
showWhen: textBlockShowWhen,
handler: actionToHandler('fixSpelling', AIStarIconWithAnimation),
},
{
name: 'Fix grammar',
testId: 'action-fix-grammar',
icon: DoneIcon(),
showWhen: textBlockShowWhen,
handler: actionToHandler('improveGrammar', AIStarIconWithAnimation),
},
{
name: 'Explain this image',
testId: 'action-explain-image',
icon: PenIcon(),
showWhen: imageBlockShowWhen,
handler: actionToHandler('explainImage', AIStarIconWithAnimation),
},
{
name: 'Explain this code',
testId: 'action-explain-code',
icon: ExplainIcon(),
showWhen: codeBlockShowWhen,
handler: actionToHandler('explainCode', AIStarIconWithAnimation),
},
{
name: 'Check code error',
testId: 'action-check-code-error',
icon: ExplainIcon(),
showWhen: codeBlockShowWhen,
handler: actionToHandler('checkCodeErrors', AIStarIconWithAnimation),
},
{
name: 'Explain selection',
testId: 'action-explain-selection',
icon: SelectionIcon(),
showWhen: textBlockShowWhen,
handler: actionToHandler('explain', AIStarIconWithAnimation),
@@ -266,12 +287,14 @@ const GenerateWithAIGroup: AIItemGroupConfig = {
items: [
{
name: 'Summarize',
testId: 'action-summarize',
icon: PenIcon(),
showWhen: textBlockShowWhen,
handler: actionToHandler('summary', AIPenIconWithAnimation),
},
{
name: 'Generate headings',
testId: 'action-generate-headings',
icon: PenIcon(),
beta: true,
handler: actionToHandler('createHeadings', AIPenIconWithAnimation),
@@ -293,24 +316,28 @@ const GenerateWithAIGroup: AIItemGroupConfig = {
},
{
name: 'Generate an image',
testId: 'action-generate-image',
icon: ImageIcon(),
showWhen: textBlockShowWhen,
handler: actionToHandler('createImage', AIImageIconWithAnimation),
},
{
name: 'Generate outline',
testId: 'action-generate-outline',
icon: PenIcon(),
showWhen: textBlockShowWhen,
handler: actionToHandler('writeOutline', AIPenIconWithAnimation),
},
{
name: 'Brainstorm ideas with mind map',
testId: 'action-brainstorm-mindmap',
icon: MindmapIcon(),
showWhen: textBlockShowWhen,
handler: actionToHandler('brainstormMindmap', AIPenIconWithAnimation),
},
{
name: 'Generate presentation',
testId: 'action-generate-presentation',
icon: PresentationIcon(),
showWhen: textBlockShowWhen,
handler: actionToHandler('createSlides', AIPresentationIconWithAnimation),
@@ -318,6 +345,7 @@ const GenerateWithAIGroup: AIItemGroupConfig = {
},
{
name: 'Make it real',
testId: 'action-make-it-real',
icon: MakeItRealIcon(),
beta: true,
showWhen: textBlockShowWhen,
@@ -325,6 +353,7 @@ const GenerateWithAIGroup: AIItemGroupConfig = {
},
{
name: 'Find actions',
testId: 'action-find-actions',
icon: SearchIcon(),
showWhen: textBlockShowWhen,
handler: actionToHandler('findActions', AIStarIconWithAnimation),
@@ -338,6 +367,7 @@ const OthersAIGroup: AIItemGroupConfig = {
items: [
{
name: 'Continue with AI',
testId: 'action-continue-with-ai',
icon: CommentIcon(),
handler: host => {
const panel = getAIPanelWidget(host);
@@ -366,6 +396,7 @@ export function buildAIImageItemGroups(): AIItemGroupConfig[] {
items: [
{
name: 'Explain this image',
testId: 'action-explain-image',
icon: ImageIcon(),
showWhen: () => true,
handler: actionToHandler(
@@ -382,6 +413,7 @@ export function buildAIImageItemGroups(): AIItemGroupConfig[] {
items: [
{
name: 'Generate an image',
testId: 'action-generate-image',
icon: ImageIcon(),
showWhen: () => true,
handler: actionToHandler(
@@ -393,6 +425,7 @@ export function buildAIImageItemGroups(): AIItemGroupConfig[] {
},
{
name: 'Image processing',
testId: 'action-image-processing',
icon: ImageIcon(),
showWhen: () => true,
subItem: createImageProcessingSubItem(blockActionTrackerOptions),
@@ -401,6 +434,7 @@ export function buildAIImageItemGroups(): AIItemGroupConfig[] {
},
{
name: 'AI image filter',
testId: 'action-ai-image-filter',
icon: ImproveWritingIcon(),
showWhen: () => true,
subItem: createImageFilterSubItem(blockActionTrackerOptions),
@@ -409,6 +443,7 @@ export function buildAIImageItemGroups(): AIItemGroupConfig[] {
},
{
name: 'Generate a caption',
testId: 'action-generate-caption',
icon: PenIcon(),
showWhen: () => true,
beta: true,
@@ -432,6 +467,7 @@ export function buildAICodeItemGroups(): AIItemGroupConfig[] {
items: [
{
name: 'Explain this code',
testId: 'action-explain-code',
icon: ExplainIcon(),
showWhen: () => true,
handler: actionToHandler(
@@ -443,6 +479,7 @@ export function buildAICodeItemGroups(): AIItemGroupConfig[] {
},
{
name: 'Check code error',
testId: 'action-check-code-error',
icon: ExplainIcon(),
showWhen: () => true,
handler: actionToHandler(
@@ -10,8 +10,8 @@ import {
buildFinishConfig,
buildGeneratingConfig,
} from '../ai-panel';
import type { AIError, AIItemGroupConfig } from '../components/ai-item/types';
import { AIProvider } from '../provider';
import { type AIItemGroupConfig } from '../components/ai-item/types';
import { type AIError, AIProvider } from '../provider';
import { reportResponse } from '../utils/action-reporter';
import { getAIPanelWidget } from '../utils/ai-widgets';
import { AIContext } from '../utils/context';
@@ -20,8 +20,7 @@ import type { TemplateResult } from 'lit';
import { getContentFromSlice } from '../../utils';
import { AIChatBlockModel } from '../blocks';
import type { AIError } from '../components/ai-item/types';
import { AIProvider } from '../provider';
import { type AIError, AIProvider } from '../provider';
import { reportResponse } from '../utils/action-reporter';
import { getAIPanelWidget } from '../utils/ai-widgets';
import { AIContext } from '../utils/context';
@@ -462,7 +461,6 @@ export function noteBlockOrTextShowWhen(
host: EditorHost
) {
const selected = getCopilotSelectedElems(host);
return selected.some(
el =>
el instanceof NoteBlockModel ||
@@ -85,6 +85,7 @@ export function discard(
return {
name: 'Discard',
icon: DeleteIcon(),
testId: 'answer-discard',
showWhen: () => !!panel.answer,
handler: () => {
panel.discard();
@@ -96,6 +97,7 @@ export function retry(panel: AffineAIPanelWidget): AIItemConfig {
return {
name: 'Retry',
icon: ResetIcon(),
testId: 'answer-retry',
handler: () => {
reportResponse('result:retry');
panel.generate();
@@ -123,6 +125,7 @@ export function createInsertItems<T extends keyof BlockSuitePresets.AIActions>(
icon: html`<div style=${styleMap({ height: '20px', width: '20px' })}>
${LightLoadingIcon}
</div>`,
testId: 'answer-insert-below-loading',
showWhen: () => {
const panel = getAIPanelWidget(host);
const data = ctx.get();
@@ -137,6 +140,8 @@ export function createInsertItems<T extends keyof BlockSuitePresets.AIActions>(
{
name: buttonText,
icon: InsertBelowIcon(),
testId:
buttonText === 'Replace' ? 'answer-replace' : `answer-insert-below`,
showWhen: () => {
const panel = getAIPanelWidget(host);
const data = ctx.get();
@@ -191,6 +196,7 @@ export function asCaption<T extends keyof BlockSuitePresets.AIActions>(
return {
name: 'Use as caption',
icon: PenIcon(),
testId: 'answer-use-as-caption',
showWhen: () => {
const panel = getAIPanelWidget(host);
return id === 'generateCaption' && !!panel.answer;
@@ -553,9 +559,11 @@ export function actionToResponse<T extends keyof BlockSuitePresets.AIActions>(
responses: [
{
name: 'Response',
testId: 'answer-responses',
items: [
{
name: 'Continue in chat',
testId: 'answer-continue-in-chat',
icon: ChatWithAiIcon({}),
handler: () => {
reportResponse('result:continue-in-chat');
@@ -53,6 +53,7 @@ function asCaption<T extends keyof BlockSuitePresets.AIActions>(
return {
name: 'Use as caption',
icon: PenIcon(),
testId: 'answer-use-as-caption',
showWhen: () => {
const panel = getAIPanelWidget(host);
return id === 'generateCaption' && !!panel.answer;
@@ -79,6 +80,7 @@ function createNewNote(host: EditorHost): AIItemConfig {
return {
name: 'Create new note',
icon: PageIcon(),
testId: 'answer-create-new-note',
showWhen: () => {
const panel = getAIPanelWidget(host);
return !!panel.answer && isInsideEdgelessEditor(host);
@@ -147,9 +149,11 @@ function buildPageResponseConfig<T extends keyof BlockSuitePresets.AIActions>(
return [
{
name: 'Response',
testId: 'answer-responses',
items: [
{
name: 'Insert below',
testId: 'answer-insert-below',
icon: InsertBelowIcon(),
showWhen: () =>
!!panel.answer && (!id || !INSERT_ABOVE_ACTIONS.includes(id)),
@@ -161,6 +165,7 @@ function buildPageResponseConfig<T extends keyof BlockSuitePresets.AIActions>(
},
{
name: 'Insert above',
testId: 'answer-insert-above',
icon: InsertTopIcon(),
showWhen: () =>
!!panel.answer && !!id && INSERT_ABOVE_ACTIONS.includes(id),
@@ -173,6 +178,7 @@ function buildPageResponseConfig<T extends keyof BlockSuitePresets.AIActions>(
asCaption(host, id),
{
name: 'Replace selection',
testId: 'answer-replace',
icon: ReplaceIcon(),
showWhen: () =>
!!panel.answer && !EXCLUDING_REPLACE_ACTIONS.includes(id),
@@ -187,10 +193,12 @@ function buildPageResponseConfig<T extends keyof BlockSuitePresets.AIActions>(
},
{
name: '',
testId: 'answer-common-responses',
items: [
{
name: 'Continue in chat',
icon: ChatWithAiIcon(),
testId: 'answer-continue-in-chat',
handler: () => {
reportResponse('result:continue-in-chat');
AIProvider.slots.requestOpenWithChat.next({ host });
@@ -200,6 +208,7 @@ function buildPageResponseConfig<T extends keyof BlockSuitePresets.AIActions>(
{
name: 'Regenerate',
icon: ResetIcon(),
testId: 'answer-regenerate',
handler: () => {
reportResponse('result:retry');
panel.generate();
@@ -208,6 +217,7 @@ function buildPageResponseConfig<T extends keyof BlockSuitePresets.AIActions>(
{
name: 'Discard',
icon: DeleteIcon(),
testId: 'answer-discard',
handler: () => {
panel.discard();
},
@@ -225,6 +235,7 @@ export function buildErrorResponseConfig(panel: AffineAIPanelWidget) {
{
name: 'Retry',
icon: ResetIcon(),
testId: 'error-retry',
showWhen: () => true,
handler: () => {
reportResponse('result:retry');
@@ -234,6 +245,7 @@ export function buildErrorResponseConfig(panel: AffineAIPanelWidget) {
{
name: 'Discard',
icon: DeleteIcon(),
testId: 'error-discard',
showWhen: () => !!panel.answer,
handler: () => {
panel.discard();
@@ -142,6 +142,7 @@ export class ActionWrapper extends WithDisposable(LitElement) {
<slot></slot>
<div
class="action-name"
data-testid="action-name"
@click=${() => (this.promptShow = !this.promptShow)}
>
${icons[item.action] ? icons[item.action] : DoneIcon()}
@@ -152,22 +153,27 @@ export class ActionWrapper extends WithDisposable(LitElement) {
</div>
${this.promptShow
? html`
<div class="answer-prompt">
<div class="answer-prompt" data-testid="answer-prompt">
<div class="subtitle">Answer</div>
${HISTORY_IMAGE_ACTIONS.includes(item.action)
? images &&
html`<chat-content-images
.images=${images}
data-testid="generated-image"
></chat-content-images>`
: nothing}
${answer
? createTextRenderer(this.host, { customHeading: true })(answer)
? createTextRenderer(this.host, {
customHeading: true,
testId: 'chat-message-action-answer',
})(answer)
: nothing}
${originalText
? html`<div class="subtitle prompt">Prompt</div>
${createTextRenderer(this.host, { customHeading: true })(
item.messages[0].content + originalText
)}`
${createTextRenderer(this.host, {
customHeading: true,
testId: 'chat-message-action-prompt',
})(item.messages[0].content + originalText)}`
: nothing}
</div>
`
@@ -1,6 +1,3 @@
import './action-wrapper';
import '../content/images';
import { WithDisposable } from '@blocksuite/affine/global/lit';
import type { EditorHost } from '@blocksuite/affine/std';
import { ShadowlessElement } from '@blocksuite/affine/std';
@@ -27,7 +24,10 @@ export class ActionImageToText extends WithDisposable(ShadowlessElement) {
})}
>
${answer
? html`<chat-content-images .images=${answer}></chat-content-images>`
? html`<chat-content-images
data-testid="original-images"
.images=${answer}
></chat-content-images>`
: nothing}
</div>
</action-wrapper>`;
@@ -17,13 +17,19 @@ export class ActionImage extends WithDisposable(ShadowlessElement) {
@property({ attribute: false })
accessor host!: EditorHost;
@property({ attribute: 'data-testid', reflect: true })
accessor testId = 'action-image';
protected override render() {
const images = this.item.messages[0].attachments;
return html`<action-wrapper .host=${this.host} .item=${this.item}>
<div style=${styleMap({ marginBottom: '12px' })}>
${images
? html`<chat-content-images .images=${images}></chat-content-images>`
? html`<chat-content-images
.images=${images}
data-testid="original-image"
></chat-content-images>`
: nothing}
</div>
</action-wrapper>`;
@@ -54,6 +54,7 @@ export class ActionText extends WithDisposable(LitElement) {
border: isCode ? 'none' : '1px solid var(--affine-border-color)',
})}
class="original-text"
data-testid="original-text"
>
${createTextRenderer(this.host, {
customHeading: true,
@@ -47,6 +47,9 @@ export class AILoading extends WithDisposable(LitElement) {
@property({ attribute: false })
accessor stopGenerating!: () => void;
@property({ attribute: 'data-testid', reflect: true })
accessor testId = 'ai-loading';
override render() {
return html`
<div class="generating-tip">
@@ -1,6 +1,6 @@
import type { Signal } from '@preact/signals-core';
import type { AIError } from '../components/ai-item/types';
import type { AIError } from '../provider';
export type ChatMessage = {
id: string;
@@ -43,6 +43,7 @@ export class ChatPanelChips extends SignalWatcher(
.chips-wrapper {
display: flex;
flex-wrap: wrap;
margin: 0 -4px 0 -4px;
}
.add-button,
.collapse-button,
@@ -99,6 +100,9 @@ export class ChatPanelChips extends SignalWatcher(
@property({ attribute: false })
accessor searchMenuConfig!: SearchMenuConfig;
@property({ attribute: 'data-testid', reflect: true })
accessor testId = 'chat-panel-chips';
@query('.add-button')
accessor addButton!: HTMLDivElement;
@@ -137,7 +141,11 @@ export class ChatPanelChips extends SignalWatcher(
const chips = isCollapsed ? allChips.slice(0, 1) : allChips;
return html`<div class="chips-wrapper">
<div class="add-button" @click=${this._toggleAddDocMenu}>
<div
class="add-button"
data-testid="chat-panel-with-button"
@click=${this._toggleAddDocMenu}
>
${PlusIcon()}
</div>
${repeat(
@@ -14,8 +14,7 @@ import { property, query, state } from 'lit/decorators.js';
import { repeat } from 'lit/directives/repeat.js';
import { ChatAbortIcon, ChatSendIcon } from '../_common/icons';
import type { AIError } from '../components/ai-item/types';
import { AIProvider } from '../provider';
import { type AIError, AIProvider } from '../provider';
import { reportResponse } from '../utils/action-reporter';
import { readBlobAsURL } from '../utils/image';
import type { AINetworkSearchConfig, DocDisplayConfig } from './chat-config';
@@ -220,6 +219,9 @@ export class ChatPanelInput extends SignalWatcher(WithDisposable(LitElement)) {
@property({ attribute: false })
accessor docDisplayConfig!: DocDisplayConfig;
@property({ attribute: 'data-testid', reflect: true })
accessor testId = 'chat-panel-input-container';
private get _isNetworkActive() {
return (
!!this.networkSearchConfig.visible.value &&
@@ -335,7 +337,10 @@ export class ChatPanelInput extends SignalWatcher(WithDisposable(LitElement)) {
`
: nothing}
${this.chatContextValue.quote
? html`<div class="chat-selection-quote">
? html`<div
class="chat-selection-quote"
data-testid="chat-selection-quote"
>
${repeat(
getFirstTwoLines(this.chatContextValue.quote),
line => line,
@@ -420,6 +425,7 @@ export class ChatPanelInput extends SignalWatcher(WithDisposable(LitElement)) {
: nothing}
${images.length < MaximumImageCount
? html`<div
data-testid="chat-panel-input-image-upload"
class="image-upload"
aria-disabled=${uploadDisabled}
@click=${uploadDisabled ? undefined : this._uploadImageFiles}
@@ -434,6 +440,7 @@ export class ChatPanelInput extends SignalWatcher(WithDisposable(LitElement)) {
this.updateContext({ status: 'success' });
reportResponse('aborted:stop');
}}
data-testid="chat-panel-stop"
>
${ChatAbortIcon}
</div>`
@@ -14,8 +14,7 @@ import { repeat } from 'lit/directives/repeat.js';
import { debounce } from 'lodash-es';
import { AffineIcon } from '../_common/icons';
import { type AIError, UnauthorizedError } from '../components/ai-item/types';
import { AIProvider } from '../provider';
import { type AIError, AIProvider, UnauthorizedError } from '../provider';
import {
type ChatContextValue,
type ChatMessage,
@@ -31,7 +30,7 @@ export class ChatPanelMessages extends WithDisposable(ShadowlessElement) {
position: relative;
}
.chat-panel-messages {
.chat-panel-messages-container {
display: flex;
flex-direction: column;
gap: 24px;
@@ -157,9 +156,16 @@ export class ChatPanelMessages extends WithDisposable(ShadowlessElement) {
@property({ attribute: false })
accessor previewSpecBuilder!: SpecBuilder;
@query('.chat-panel-messages')
@query('.chat-panel-messages-container')
accessor messagesContainer: HTMLDivElement | null = null;
@property({
type: String,
attribute: 'data-testid',
reflect: true,
})
accessor testId = 'chat-panel-messages';
getScrollContainer(): HTMLDivElement | null {
return this.messagesContainer;
}
@@ -168,12 +174,13 @@ export class ChatPanelMessages extends WithDisposable(ShadowlessElement) {
return this.isLoading ||
!this.host?.doc.get(FeatureFlagService).getFlag('enable_ai_onboarding')
? nothing
: html`<div class="onboarding-wrapper">
: html`<div class="onboarding-wrapper" data-testid="ai-onboarding">
${repeat(
AIPreloadConfig,
config => config.text,
config => {
return html`<div
data-testid=${config.testId}
@click=${() => config.handler()}
class="onboarding-item"
>
@@ -220,7 +227,8 @@ export class ChatPanelMessages extends WithDisposable(ShadowlessElement) {
return html`
<div
class="chat-panel-messages"
class="chat-panel-messages-container"
data-testid="chat-panel-messages-container"
@scroll=${() => this._debouncedOnScroll()}
>
${filteredItems.length === 0
@@ -232,8 +240,12 @@ export class ChatPanelMessages extends WithDisposable(ShadowlessElement) {
)}
<div class="messages-placeholder-title" data-loading=${isLoading}>
${this.isLoading
? 'AFFiNE AI is loading history...'
: 'What can I help you with?'}
? html`<span data-testid="chat-panel-loading-state"
>AFFiNE AI is loading history...</span
>`
: html`<span data-testid="chat-panel-empty-state"
>What can I help you with?</span
>`}
</div>
${this._renderAIOnboarding()}
</div> `
@@ -268,7 +280,11 @@ export class ChatPanelMessages extends WithDisposable(ShadowlessElement) {
)}
</div>
${showDownIndicator && filteredItems.length > 0
? html`<div class="down-indicator" @click=${this._onDownIndicatorClick}>
? html`<div
data-testid="chat-panel-scroll-down-indicator"
class="down-indicator"
@click=${this._onDownIndicatorClick}
>
${ArrowDownIcon()}
</div>`
: nothing}
@@ -44,6 +44,8 @@ export type MenuItem = {
name: string | TemplateResult<1>;
icon: TemplateResult<1>;
action: MenuAction;
suffix?: string | TemplateResult<1>;
testId?: string;
};
export type MenuAction = () => Promise<void> | void;
@@ -140,6 +142,7 @@ export class ChatPanelAddPopover extends SignalWatcher(
{
key: 'tags',
name: 'Tags',
testId: 'ai-chat-with-tags',
icon: TagsIcon(),
action: () => {
this._toggleMode(AddPopoverMode.Tags);
@@ -148,6 +151,7 @@ export class ChatPanelAddPopover extends SignalWatcher(
{
key: 'collections',
name: 'Collections',
testId: 'ai-chat-with-collections',
icon: CollectionsIcon(),
action: () => {
this._toggleMode(AddPopoverMode.Collections);
@@ -176,6 +180,7 @@ export class ChatPanelAddPopover extends SignalWatcher(
{
key: 'files',
name: 'Upload files (pdf, txt, csv)',
testId: 'ai-chat-with-files',
icon: UploadIcon(),
action: this._addFileChip,
},
@@ -330,13 +335,14 @@ export class ChatPanelAddPopover extends SignalWatcher(
${repeat(
items,
item => item.key,
({ key, name, icon, action }, idx) => {
({ key, name, icon, action, testId }, idx) => {
const curIdx = startIndex + idx;
return html`<icon-button
width="280px"
height="30px"
data-id=${key}
data-index=${curIdx}
data-testid=${testId}
.text=${name}
hover=${this._activatedIndex === curIdx}
@click=${() => action()?.catch(console.error)}
@@ -37,6 +37,9 @@ export class ChatContentPureText extends ShadowlessElement {
@property({ attribute: false })
accessor text: string = '';
@property({ attribute: 'data-testid', reflect: true })
accessor testId = 'chat-content-pure-text';
protected override render() {
return this.text.length > 0
? html`<div class="chat-content-pure-text">${this.text}</div>`
@@ -16,6 +16,9 @@ export class ChatMessageAction extends WithDisposable(ShadowlessElement) {
@property({ attribute: false })
accessor item!: ChatAction;
@property({ attribute: 'data-testid', reflect: true })
accessor testId = 'chat-message-action';
renderHeader() {
return html`
<div class="user-info">
@@ -12,8 +12,8 @@ import {
EdgelessEditorActions,
PageEditorActions,
} from '../../_common/chat-actions-handle';
import { type AIError } from '../../components/ai-item/types';
import { AIChatErrorRenderer } from '../../messages/error';
import { type AIError } from '../../provider';
import { type ChatMessage, isChatMessage } from '../chat-context';
export class ChatMessageAssistant extends WithDisposable(ShadowlessElement) {
@@ -34,7 +34,7 @@ export class ChatMessageAssistant extends WithDisposable(ShadowlessElement) {
@property({ attribute: false })
accessor isLast: boolean = false;
@property({ attribute: false })
@property({ attribute: 'data-status', reflect: true })
accessor status: string = 'idle';
@property({ attribute: false })
@@ -49,6 +49,9 @@ export class ChatMessageAssistant extends WithDisposable(ShadowlessElement) {
@property({ attribute: false })
accessor retry!: () => void;
@property({ attribute: 'data-testid', reflect: true })
accessor testId = 'chat-message-assistant';
renderHeader() {
const isWithDocs =
'content' in this.item &&
@@ -31,6 +31,9 @@ export class ChatMessageUser extends WithDisposable(ShadowlessElement) {
@property({ attribute: false })
accessor item!: ChatMessage;
@property({ attribute: 'data-testid', reflect: true })
accessor testId = 'chat-message-user';
renderContent() {
const { item } = this;
@@ -41,7 +44,7 @@ export class ChatMessageUser extends WithDisposable(ShadowlessElement) {
.images=${item.attachments}
></chat-content-images>`
: nothing}
<div class="text-content-wrapper">
<div class="text-content-wrapper" data-test-id="chat-content-user-text">
<chat-content-pure-text .text=${item.content}></chat-content-pure-text>
</div>
`;
@@ -17,6 +17,7 @@ export const AIPreloadConfig = [
{
icon: LanguageIcon(),
text: 'Read a foreign language article with AI',
testId: 'read-foreign-language-article-with-ai',
handler: () => {
AIProvider.slots.requestInsertTemplate.next({
template: readAforeign,
@@ -27,6 +28,7 @@ export const AIPreloadConfig = [
{
icon: MindmapIcon(),
text: 'Tidy an article with AI MindMap Action',
testId: 'tidy-an-article-with-ai-mindmap-action',
handler: () => {
AIProvider.slots.requestInsertTemplate.next({
template: TidyMindMapV3,
@@ -37,6 +39,7 @@ export const AIPreloadConfig = [
{
icon: ImageIcon(),
text: 'Add illustrations to the article',
testId: 'add-illustrations-to-the-article',
handler: () => {
AIProvider.slots.requestInsertTemplate.next({
template: redHat,
@@ -47,6 +50,7 @@ export const AIPreloadConfig = [
{
icon: PenIcon(),
text: 'Complete writing with AI',
testId: 'complete-writing-with-ai',
handler: () => {
AIProvider.slots.requestInsertTemplate.next({
template: completeWritingWithAI,
@@ -57,6 +61,7 @@ export const AIPreloadConfig = [
{
icon: SendIcon(),
text: 'Freely communicate with AI',
testId: 'freely-communicate-with-ai',
handler: () => {
AIProvider.slots.requestInsertTemplate.next({
template: freelyCommunicateWithAI,
@@ -87,6 +87,7 @@ export class AIItemList extends WithDisposable(LitElement) {
createLitPortal({
template: html`<ai-sub-item-list
data-testid=${item.testId ? item.testId + '-menu' : ''}
.item=${item}
.host=${this.host}
.onClick=${this.onClick}
@@ -141,6 +142,9 @@ export class AIItemList extends WithDisposable(LitElement) {
@property({ attribute: false })
accessor onClick: (() => void) | undefined = undefined;
@property({ attribute: 'data-testid', reflect: true })
accessor testId = 'ai-item-list';
}
declare global {
@@ -23,8 +23,10 @@ export class AIItem extends WithDisposable(LitElement) {
override render() {
const { item } = this;
const className = item.name.split(' ').join('-').toLocaleLowerCase();
const testId = item.testId;
return html`<div
data-testid=${testId}
class="menu-item ${className}"
@pointerdown=${(e: MouseEvent) => e.stopPropagation()}
@click=${() => {
@@ -70,6 +70,7 @@ export class AISubItemList extends WithDisposable(LitElement) {
subItem => subItem.type,
subItem =>
html`<div
data-testid=${subItem.testId}
class="menu-item"
@click=${() => this._handleClick(subItem)}
>
@@ -4,11 +4,13 @@ import type { TemplateResult } from 'lit';
export interface AIItemGroupConfig {
name?: string;
testId?: string;
items: AIItemConfig[];
}
export interface AIItemConfig {
name: string;
testId: string;
icon: TemplateResult | (() => HTMLElement);
showWhen?: (
chain: Chain<InitCommandCtx>,
@@ -23,46 +25,6 @@ export interface AIItemConfig {
export interface AISubItemConfig {
type: string;
testId?: string;
handler?: (host: EditorHost) => void;
}
abstract class BaseAIError extends Error {
abstract readonly type: AIErrorType;
}
export enum AIErrorType {
GeneralNetworkError = 'GeneralNetworkError',
PaymentRequired = 'PaymentRequired',
Unauthorized = 'Unauthorized',
}
export class UnauthorizedError extends BaseAIError {
readonly type = AIErrorType.Unauthorized;
constructor() {
super('Unauthorized');
}
}
// user has used up the quota
export class PaymentRequiredError extends BaseAIError {
readonly type = AIErrorType.PaymentRequired;
constructor() {
super('Payment required');
}
}
// general 500x error
export class GeneralNetworkError extends BaseAIError {
readonly type = AIErrorType.GeneralNetworkError;
constructor(message: string = 'Network error') {
super(message);
}
}
export type AIError =
| UnauthorizedError
| PaymentRequiredError
| GeneralNetworkError;
@@ -131,6 +131,7 @@ export class AskAIButton extends WithDisposable(LitElement) {
});
return html`<div
class="ask-ai-button"
data-testid="ask-ai-button"
style=${buttonStyles}
${toggleType === 'hover' ? ref(this._whenHover.setReference) : nothing}
@click=${this._toggleAIPanel}
@@ -110,7 +110,11 @@ export class AskAIToolbarButton extends WithDisposable(LitElement) {
};
override render() {
return html`<div class="ask-ai-button" @click=${this._onClick}>
return html`<div
class="ask-ai-button"
data-testid="ask-ai-button"
@click=${this._onClick}
>
<ask-ai-icon .size=${'middle'}></ask-ai-icon>
</div>`;
}
@@ -92,6 +92,9 @@ export class ChatActionList extends LitElement {
@property({ attribute: false })
accessor withMargin = false;
@property({ attribute: 'data-testid', reflect: true })
accessor testId = 'chat-action-list';
override render() {
const { actions } = this;
if (!actions.length) {
@@ -127,6 +127,9 @@ export class ChatCopyMore extends WithDisposable(LitElement) {
@property({ attribute: false })
accessor retry = () => {};
@property({ attribute: 'data-testid', reflect: true })
accessor testId = 'chat-actions';
private _toggle() {
this._morePopper?.toggle();
}
@@ -197,7 +200,11 @@ export class ChatCopyMore extends WithDisposable(LitElement) {
: nothing}
${isLast
? nothing
: html`<div class="button more" @click=${this._toggle}>
: html`<div
class="button more"
data-testid="action-more-button"
@click=${this._toggle}
>
${MoreHorizontalIcon({ width: '20px', height: '20px' })}
</div> `}
</div>
@@ -84,6 +84,7 @@ export type TextRendererOptions = {
customHeading?: boolean;
extensions?: ExtensionType[];
additionalMiddlewares?: TransformerMiddleware[];
testId?: string;
};
export const CustomPageEditorBlockSpecs: ExtensionType[] = [
@@ -290,13 +291,13 @@ export class TextRenderer extends WithDisposable(ShadowlessElement) {
return nothing;
}
const { customHeading } = this.options;
const { customHeading, testId } = this.options;
const classes = classMap({
'text-renderer-container': true,
'custom-heading': !!customHeading,
});
return html`
<div class=${classes}>
<div class=${classes} data-testid=${testId}>
${keyed(
this._doc,
html`<div class="ai-answer-text-editor affine-page-viewport">
@@ -61,6 +61,7 @@ import {
const translateSubItem = translateLangs.map(lang => {
return {
type: lang,
testId: `action-translate-${lang}`,
handler: actionToHandler('translate', AIStarIconWithAnimation, { lang }),
};
});
@@ -68,6 +69,7 @@ const translateSubItem = translateLangs.map(lang => {
const toneSubItem = textTones.map(tone => {
return {
type: tone,
testId: `action-change-tone-${tone.toLowerCase()}`,
handler: actionToHandler('changeTone', AIStarIconWithAnimation, { tone }),
};
});
@@ -75,6 +77,7 @@ const toneSubItem = textTones.map(tone => {
export const imageFilterSubItem = imageFilterStyles.map(style => {
return {
type: style,
testId: `action-image-filter-${style.toLowerCase().replace(' ', '-')}`,
handler: actionToHandler(
'filterImage',
AIImageIconWithAnimation,
@@ -89,6 +92,7 @@ export const imageFilterSubItem = imageFilterStyles.map(style => {
export const imageProcessingSubItem = imageProcessingTypes.map(type => {
return {
type,
testId: `action-image-processing-${type.toLowerCase().replace(' ', '-')}`,
handler: actionToHandler(
'processImage',
AIImageIconWithAnimation,
@@ -105,6 +109,7 @@ const othersGroup: AIItemGroupConfig = {
items: [
{
name: 'Continue with AI',
testId: 'action-continue-with-ai',
icon: CommentIcon({ width: '20px', height: '20px' }),
showWhen: () => true,
handler: host => {
@@ -125,18 +130,21 @@ const editGroup: AIItemGroupConfig = {
items: [
{
name: 'Translate to',
testId: 'action-translate',
icon: LanguageIcon(),
showWhen: noteBlockOrTextShowWhen,
subItem: translateSubItem,
},
{
name: 'Change tone to',
testId: 'action-change-tone',
icon: ToneIcon(),
showWhen: noteBlockOrTextShowWhen,
subItem: toneSubItem,
},
{
name: 'Improve writing',
testId: 'action-improve-writing',
icon: ImproveWritingIcon(),
showWhen: noteBlockOrTextShowWhen,
handler: actionToHandler('improveWriting', AIStarIconWithAnimation),
@@ -144,18 +152,21 @@ const editGroup: AIItemGroupConfig = {
{
name: 'Make it longer',
testId: 'action-make-it-longer',
icon: LongerIcon(),
showWhen: noteBlockOrTextShowWhen,
handler: actionToHandler('makeLonger', AIStarIconWithAnimation),
},
{
name: 'Make it shorter',
testId: 'action-make-it-shorter',
icon: ShorterIcon(),
showWhen: noteBlockOrTextShowWhen,
handler: actionToHandler('makeShorter', AIStarIconWithAnimation),
},
{
name: 'Continue writing',
testId: 'action-continue-writing',
icon: PenIcon(),
showWhen: noteBlockOrTextShowWhen,
handler: actionToHandler('continueWriting', AIPenIconWithAnimation),
@@ -168,30 +179,35 @@ const draftGroup: AIItemGroupConfig = {
items: [
{
name: 'Write an article about this',
testId: 'action-write-article',
icon: PenIcon(),
showWhen: noteBlockOrTextShowWhen,
handler: actionToHandler('writeArticle', AIPenIconWithAnimation),
},
{
name: 'Write a tweet about this',
testId: 'action-write-twitter-post',
icon: PenIcon(),
showWhen: noteBlockOrTextShowWhen,
handler: actionToHandler('writeTwitterPost', AIPenIconWithAnimation),
},
{
name: 'Write a poem about this',
testId: 'action-write-poem',
icon: PenIcon(),
showWhen: noteBlockOrTextShowWhen,
handler: actionToHandler('writePoem', AIPenIconWithAnimation),
},
{
name: 'Write a blog post about this',
testId: 'action-write-blog-post',
icon: PenIcon(),
showWhen: noteBlockOrTextShowWhen,
handler: actionToHandler('writeBlogPost', AIPenIconWithAnimation),
},
{
name: 'Brainstorm ideas about this',
testId: 'action-brainstorm',
icon: PenIcon(),
showWhen: noteBlockOrTextShowWhen,
handler: actionToHandler('brainstorm', AIPenIconWithAnimation),
@@ -205,18 +221,21 @@ const reviewGroup: AIItemGroupConfig = {
{
name: 'Fix spelling',
icon: PenIcon(),
testId: 'action-fix-spelling',
showWhen: noteBlockOrTextShowWhen,
handler: actionToHandler('fixSpelling', AIStarIconWithAnimation),
},
{
name: 'Fix grammar',
icon: PenIcon(),
testId: 'action-fix-grammar',
showWhen: noteBlockOrTextShowWhen,
handler: actionToHandler('improveGrammar', AIStarIconWithAnimation),
},
{
name: 'Explain this image',
icon: PenIcon(),
testId: 'action-explain-image',
showWhen: imageOnlyShowWhen,
handler: actionToHandler(
'explainImage',
@@ -228,18 +247,21 @@ const reviewGroup: AIItemGroupConfig = {
{
name: 'Explain this code',
icon: ExplainIcon(),
testId: 'action-explain-code',
showWhen: noteWithCodeBlockShowWen,
handler: actionToHandler('explainCode', AIStarIconWithAnimation),
},
{
name: 'Check code error',
icon: ExplainIcon(),
testId: 'action-check-code-error',
showWhen: noteWithCodeBlockShowWen,
handler: actionToHandler('checkCodeErrors', AIStarIconWithAnimation),
},
{
name: 'Explain selection',
icon: SelectionIcon({ width: '20px', height: '20px' }),
testId: 'action-explain-selection',
showWhen: noteBlockOrTextShowWhen,
handler: actionToHandler('explain', AIStarIconWithAnimation),
},
@@ -252,19 +274,22 @@ const generateGroup: AIItemGroupConfig = {
{
name: 'Summarize',
icon: PenIcon(),
testId: 'action-summarize',
showWhen: noteBlockOrTextShowWhen,
handler: actionToHandler('summary', AIPenIconWithAnimation),
},
{
name: 'Generate headings',
icon: PenIcon(),
handler: actionToHandler('createHeadings', AIPenIconWithAnimation),
testId: 'action-generate-headings',
showWhen: noteBlockOrTextShowWhen,
handler: actionToHandler('createHeadings', AIPenIconWithAnimation),
beta: true,
},
{
name: 'Generate an image',
icon: ImageIcon(),
testId: 'action-generate-image',
showWhen: notAllAIChatBlockShowWhen,
handler: actionToHandler(
'createImage',
@@ -339,12 +364,14 @@ const generateGroup: AIItemGroupConfig = {
{
name: 'Generate outline',
icon: PenIcon(),
testId: 'action-generate-outline',
showWhen: noteBlockOrTextShowWhen,
handler: actionToHandler('writeOutline', AIPenIconWithAnimation),
},
{
name: 'Expand from this mind map node',
icon: MindmapNodeIcon(),
testId: 'action-expand-mindmap-node',
showWhen: mindmapChildShowWhen,
handler: actionToHandler(
'expandMindmap',
@@ -370,12 +397,14 @@ const generateGroup: AIItemGroupConfig = {
{
name: 'Brainstorm ideas with mind map',
icon: MindmapIcon(),
testId: 'action-brainstorm-mindmap',
showWhen: noteBlockOrTextShowWhen,
handler: actionToHandler('brainstormMindmap', AIMindMapIconWithAnimation),
},
{
name: 'Regenerate mind map',
icon: MindmapIcon(),
testId: 'action-regenerate-mindmap',
showWhen: mindmapRootShowWhen,
handler: actionToHandler(
'brainstormMindmap',
@@ -388,6 +417,7 @@ const generateGroup: AIItemGroupConfig = {
{
name: 'Generate presentation',
icon: PresentationIcon(),
testId: 'action-generate-presentation',
showWhen: noteBlockOrTextShowWhen,
handler: actionToHandler('createSlides', AIPresentationIconWithAnimation),
beta: true,
@@ -395,6 +425,7 @@ const generateGroup: AIItemGroupConfig = {
{
name: 'Make it real',
icon: MakeItRealIcon({ width: '20px', height: '20px' }),
testId: 'action-make-it-real',
beta: true,
showWhen: notAllAIChatBlockShowWhen,
handler: actionToHandler(
@@ -476,6 +507,7 @@ const generateGroup: AIItemGroupConfig = {
{
name: 'AI image filter',
icon: PenIcon(),
testId: 'action-ai-image-filter',
showWhen: imageOnlyShowWhen,
subItem: imageFilterSubItem,
subItemOffset: [12, -4],
@@ -484,6 +516,7 @@ const generateGroup: AIItemGroupConfig = {
{
name: 'Image processing',
icon: ImageIcon(),
testId: 'action-image-processing',
showWhen: imageOnlyShowWhen,
subItem: imageProcessingSubItem,
subItemOffset: [12, -6],
@@ -492,6 +525,7 @@ const generateGroup: AIItemGroupConfig = {
{
name: 'Generate a caption',
icon: PenIcon(),
testId: 'action-generate-caption',
showWhen: imageOnlyShowWhen,
beta: true,
handler: actionToHandler(
@@ -504,6 +538,7 @@ const generateGroup: AIItemGroupConfig = {
{
name: 'Find actions',
icon: SearchIcon(),
testId: 'action-find-actions',
showWhen: noteBlockOrTextShowWhen,
handler: actionToHandler('findActions', AIStarIconWithAnimation),
beta: true,
@@ -10,10 +10,10 @@ import { property } from 'lit/decorators.js';
import {
type AIError,
AIProvider,
PaymentRequiredError,
UnauthorizedError,
} from '../components/ai-item/types';
import { AIProvider } from '../provider';
} from '../provider';
export class AIErrorWrapper extends SignalWatcher(WithDisposable(LitElement)) {
static override styles = css`
@@ -181,6 +181,9 @@ export class AIErrorWrapper extends SignalWatcher(WithDisposable(LitElement)) {
@property({ attribute: false })
accessor showDetailPanel: boolean = false;
@property({ attribute: 'data-testid', reflect: true })
accessor testId = 'ai-error';
}
const PaymentRequiredErrorRenderer = (host: EditorHost) => html`
@@ -107,7 +107,7 @@ export const createImageRenderer: (
object-fit: contain;
}
</style>
<div class="ai-answer-image">
<div class="ai-answer-image" data-testid="ai-answer-image">
<img src=${answer}></img>
</div>`;
@@ -14,8 +14,7 @@ import {
PROMPT_NAME_AFFINE_AI,
PROMPT_NAME_NETWORK_SEARCH,
} from '../chat-panel/const';
import type { AIError } from '../components/ai-item/types';
import { AIProvider } from '../provider';
import { type AIError, AIProvider } from '../provider';
import { reportResponse } from '../utils/action-reporter';
import { readBlobAsURL } from '../utils/image';
import { stopPropagation } from '../utils/selection-utils';
@@ -188,12 +187,21 @@ export class ChatBlockInput extends SignalWatcher(LitElement) {
`
: nothing}
${images.length < MaximumImageCount
? html`<div class="image-upload" @click=${this._handleImageUpload}>
? html`<div
data-testid="chat-block-input-image-upload"
class="image-upload"
@click=${this._handleImageUpload}
>
${ImageIcon()}
</div>`
: nothing}
${status === 'transmitting'
? html`<div @click=${this._handleAbort}>${ChatAbortIcon}</div>`
? html`<div
@click=${this._handleAbort}
data-testid="chat-panel-peek-view-stop"
>
${ChatAbortIcon}
</div>`
: html`<div
@click=${this._onTextareaSend}
class="chat-panel-send"
@@ -28,10 +28,9 @@ import {
ChatMessagesSchema,
} from '../blocks';
import type { AINetworkSearchConfig } from '../chat-panel/chat-config';
import type { AIError } from '../components/ai-item/types';
import type { TextRendererOptions } from '../components/text-renderer';
import { AIChatErrorRenderer } from '../messages/error';
import { AIProvider } from '../provider';
import { type AIError, AIProvider } from '../provider';
import { PeekViewStyles } from './styles';
import type { ChatContext } from './types';
import { calcChildBound } from './utils';
@@ -1,5 +1,5 @@
import type { ChatMessage } from '../blocks';
import type { AIError } from '../components/ai-item/types';
import type { AIError } from '../provider';
export type ChatStatus =
| 'success'
@@ -5,8 +5,9 @@ import { Subject } from 'rxjs';
import type { ChatContextValue } from '../chat-panel/chat-context';
import {
PaymentRequiredError,
RequestTimeoutError,
UnauthorizedError,
} from '../components/ai-item/types';
} from './error';
export interface AIUserInfo {
id: string;
@@ -36,6 +37,7 @@ export type ActionEventType =
| 'aborted:login-required'
| 'aborted:server-error'
| 'aborted:stop'
| 'aborted:timeout'
| 'result:insert'
| 'result:replace'
| 'result:use-as-caption'
@@ -199,7 +201,13 @@ export class AIProvider {
options,
event: 'error',
});
if (err instanceof PaymentRequiredError) {
if (err instanceof RequestTimeoutError) {
slots.actions.next({
action: id,
options,
event: 'aborted:timeout',
});
} else if (err instanceof PaymentRequiredError) {
slots.actions.next({
action: id,
options,
@@ -1,8 +1,3 @@
import {
GeneralNetworkError,
PaymentRequiredError,
UnauthorizedError,
} from '@affine/core/blocksuite/ai/components/ai-item/types';
import { showAILoginRequiredAtom } from '@affine/core/components/affine/auth/ai-login-required';
import type { UserFriendlyError } from '@affine/error';
import {
@@ -31,6 +26,12 @@ import {
} from '@affine/graphql';
import { getCurrentStore } from '@toeverything/infra';
import {
GeneralNetworkError,
PaymentRequiredError,
UnauthorizedError,
} from './error';
type OptionsField<T extends GraphQLQuery> =
RequestOptions<T>['variables'] extends { options: infer U } ? U : never;
@@ -0,0 +1,51 @@
abstract class BaseAIError extends Error {
abstract readonly type: AIErrorType;
}
export enum AIErrorType {
GeneralNetworkError = 'GeneralNetworkError',
PaymentRequired = 'PaymentRequired',
Unauthorized = 'Unauthorized',
RequestTimeout = 'RequestTimeout',
}
export class UnauthorizedError extends BaseAIError {
readonly type = AIErrorType.Unauthorized;
constructor() {
super('Unauthorized');
}
}
// user has used up the quota
export class PaymentRequiredError extends BaseAIError {
readonly type = AIErrorType.PaymentRequired;
constructor() {
super('Payment required');
}
}
// general 500x error
export class GeneralNetworkError extends BaseAIError {
readonly type = AIErrorType.GeneralNetworkError;
constructor(message: string = 'Network error') {
super(message);
}
}
// request timeout
export class RequestTimeoutError extends BaseAIError {
readonly type = AIErrorType.RequestTimeout;
constructor(message: string = 'Request timeout') {
super(message);
}
}
export type AIError =
| UnauthorizedError
| PaymentRequiredError
| GeneralNetworkError
| RequestTimeoutError;
@@ -1,4 +1,5 @@
import { handleError } from './copilot-client';
import { RequestTimeoutError } from './error';
export function delay(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
@@ -85,7 +86,7 @@ export function toTextStream(
messagePromise,
delay(timeout).then(() => {
if (!signal?.aborted) {
throw new Error('Timeout');
throw new RequestTimeoutError();
}
}),
])
@@ -1,3 +1,4 @@
export * from './ai-provider';
export * from './copilot-client';
export * from './error';
export * from './setup-provider';
@@ -39,6 +39,7 @@ type AIActionEventProperties = {
| 'policy wall'
| 'server error'
| 'login required'
| 'request timeout'
| 'insert'
| 'replace'
| 'use as caption'
@@ -193,6 +194,8 @@ function inferControl(
return 'server error';
} else if (event.event === 'aborted:login-required') {
return 'login required';
} else if (event.event === 'aborted:timeout') {
return 'request timeout';
} else if (event.options.control === 'chat-send') {
return 'AI chat send button';
} else if (event.options.control === 'block-action-bar') {
@@ -31,10 +31,9 @@ import { property, query } from 'lit/decorators.js';
import { choose } from 'lit/directives/choose.js';
import { literal, unsafeStatic } from 'lit/static-html.js';
import type { AIError } from '../../components/ai-item/types.js';
import { type AIError } from '../../provider';
import type { AIPanelGenerating } from './components/index.js';
import type { AffineAIPanelState, AffineAIPanelWidgetConfig } from './type.js';
export const AFFINE_AI_PANEL_WIDGET = 'affine-ai-panel-widget';
export class AffineAIPanelWidget extends WidgetComponent {
@@ -517,7 +516,12 @@ export class AffineAIPanelWidget extends WidgetComponent {
],
]);
return html`<div class="ai-panel-container">${mainTemplate}</div>`;
return html`<div
class="ai-panel-container"
data-testid="ai-panel-container"
>
${mainTemplate}
</div>`;
}
protected override willUpdate(changed: PropertyValues): void {
@@ -74,9 +74,12 @@ export class AIFinishTip extends WithDisposable(LitElement) {
${this.copy?.allowed
? html`<div class="right">
${this.copied
? html`<div class="copied">${AIDoneIcon}</div>`
? html`<div class="copied" data-testid="answer-copied">
${AIDoneIcon}
</div>`
: html`<div
class="copy"
data-testid="answer-copy-button"
@click=${async () => {
this.copied = !!(await this.copy?.onCopy());
if (this.copied) {
@@ -86,7 +86,7 @@ export class AIPanelAnswer extends WithDisposable(LitElement) {
return html`
<div class="answer">
<div class="answer-head">Answer</div>
<div class="answer-body">
<div class="answer-body" data-testid="answer-content">
<slot></slot>
</div>
</div>
@@ -104,7 +104,10 @@ export class AIPanelAnswer extends WithDisposable(LitElement) {
${index !== 0
? html`<ai-panel-divider></ai-panel-divider>`
: nothing}
<div class="response-list-container">
<div
class="response-list-container"
data-testid=${group.testId}
>
<ai-item-list
.host=${this.host}
.groups=${[group]}
@@ -143,6 +146,9 @@ export class AIPanelAnswer extends WithDisposable(LitElement) {
@property({ attribute: false })
accessor host!: EditorHost;
@property({ attribute: 'data-testid', reflect: true })
accessor testId = 'ai-penel-answer';
}
declare global {
@@ -5,10 +5,8 @@ import { css, html, LitElement, nothing, unsafeCSS } from 'lit';
import { property } from 'lit/decorators.js';
import { choose } from 'lit/directives/choose.js';
import {
AIErrorType,
type AIItemGroupConfig,
} from '../../../../components/ai-item/types.js';
import { type AIItemGroupConfig } from '../../../../components/ai-item/types.js';
import { AIErrorType } from '../../../../provider';
import type { AIPanelErrorConfig, CopyConfig } from '../../type.js';
import { filterAIItemGroup } from '../../utils.js';
@@ -210,7 +208,7 @@ export class AIPanelError extends WithDisposable(LitElement) {
);
return html`
<div class="error">
<div class="error" data-testid="ai-error">
<div class="answer-tip">
<div class="answer-label">Answer</div>
<slot></slot>
@@ -85,10 +85,10 @@ export class AIPanelGenerating extends WithDisposable(LitElement) {
.showHeader=${!this.withAnswer}
></generating-placeholder>`
: nothing}
<div class="generating-tip">
<div class="generating-tip" data-testid="ai-generating">
<div class="left">${generatingIcon}</div>
<div class="text">AI is generating...</div>
<div @click=${this.stopGenerating} class="right">
<div @click=${this.stopGenerating} class="right" data-testid="ai-stop">
<span class="stop-icon">${AIStopIcon}</span>
<span class="esc-label">ESC</span>
</div>
@@ -1,10 +1,8 @@
import type { Signal } from '@preact/signals-core';
import type { nothing, TemplateResult } from 'lit';
import type {
AIError,
AIItemGroupConfig,
} from '../../components/ai-item/types';
import type { AIItemGroupConfig } from '../../components/ai-item/types';
import type { AIError } from '../../provider';
export interface CopyConfig {
allowed: boolean;
@@ -62,6 +62,7 @@ export class EdgelessCopilotToolbarEntry extends WithDisposable(LitElement) {
return html`<edgeless-tool-icon-button
aria-label="Ask AI"
class="copilot-icon-button"
data-testid="ask-ai-button"
@click=${this._onClick}
>
${AIStarIcon} <span class="label medium">Ask AI</span>
@@ -1,4 +1,8 @@
import { ListLayoutPainterExtension } from '@blocksuite/affine/blocks/list';
import { ParagraphLayoutPainterExtension } from '@blocksuite/affine/blocks/paragraph';
import { ViewportLayoutPainter } from '@blocksuite/affine/gfx/turbo-renderer';
new ViewportLayoutPainter([ParagraphLayoutPainterExtension]);
new ViewportLayoutPainter([
ParagraphLayoutPainterExtension,
ListLayoutPainterExtension,
]);
@@ -1,3 +1,4 @@
import { ListLayoutHandlerExtension } from '@blocksuite/affine/blocks/list';
import { ParagraphLayoutHandlerExtension } from '@blocksuite/affine/blocks/paragraph';
import {
TurboRendererConfigFactory,
@@ -20,6 +21,7 @@ function createPainterWorker() {
export function patchTurboRendererExtension() {
return [
ParagraphLayoutHandlerExtension,
ListLayoutHandlerExtension,
TurboRendererConfigFactory({
options: {
zoomThreshold: 1,
@@ -1,6 +1,6 @@
import { cssVar } from '@toeverything/theme';
import { cssVarV2 } from '@toeverything/theme/v2';
import { style } from '@vanilla-extract/css';
import { keyframes, style } from '@vanilla-extract/css';
export const containerScrollViewport = style({
maxHeight: '272px',
@@ -14,10 +14,39 @@ export const itemList = style({
});
export const listEmpty = style({
color: cssVarV2('text/placeholder'),
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
gap: '2px',
height: '184px',
padding: '16px 45px',
});
export const listEmptyIconContainer = style({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: '40px',
height: '40px',
marginBottom: '14px',
borderRadius: '50%',
backgroundColor: cssVarV2('layer/background/secondary'),
color: cssVarV2('icon/primary'),
});
export const listEmptyTitle = style({
color: cssVarV2('text/primary'),
fontSize: '14px',
lineHeight: '22px',
padding: '4px 2px',
textAlign: 'center',
});
export const listEmptyDescription = style({
color: cssVarV2('text/secondary'),
fontSize: '14px',
lineHeight: '20px',
textAlign: 'center',
});
export const error = style({
@@ -36,12 +65,37 @@ export const itemContainer = style({
gap: '8px',
cursor: 'pointer',
selectors: {
[`&:hover:not([data-disabled="true"])`]: {
[`&:hover:not([data-disabled="true"],:has(button:hover))`]: {
backgroundColor: cssVarV2('layer/background/hoverOverlay'),
},
},
});
export const itemSkeletonContainer = style({
opacity: 0,
animation: `${keyframes({
'0%': { opacity: 0 },
'100%': { opacity: 1 },
})} 500ms ease forwards 1s`,
});
export const itemDeleteButton = style({
position: 'absolute',
right: '10px',
bottom: '8px',
width: '20px',
height: '20px',
backgroundColor: cssVarV2('button/iconButtonSolid'),
border: `0.5px solid ${cssVarV2('layer/insideBorder/border')}`,
boxShadow: cssVar('buttonShadow'),
opacity: 0,
selectors: {
[`${itemContainer}:hover &`]: {
opacity: 1,
},
},
});
export const itemMain = style({
display: 'flex',
flexDirection: 'column',
@@ -62,29 +116,10 @@ export const itemNotSupported = style({
lineHeight: '22px',
});
export const itemDeleteButton = style({
position: 'absolute',
right: '10px',
bottom: '8px',
width: '20px',
height: '20px',
backgroundColor: cssVarV2('button/iconButtonSolid'),
border: `0.5px solid ${cssVarV2('layer/insideBorder/border')}`,
boxShadow: cssVar('buttonShadow'),
opacity: 0,
selectors: {
[`${itemContainer}:hover &`]: {
opacity: 1,
},
},
});
export const itemNameLabel = style({
fontWeight: 'bold',
fontWeight: '500',
color: cssVarV2('text/primary'),
display: 'inline-flex',
alignItems: 'center',
gap: '4px',
display: 'inline',
verticalAlign: 'top',
selectors: {
[`&[data-inactived="true"]`]: {
@@ -95,4 +130,11 @@ export const itemNameLabel = style({
export const itemActionButton = style({
width: 'fit-content',
borderRadius: '4px',
});
export const itemNameLabelIcon = style({
verticalAlign: 'top',
marginRight: '4px',
color: cssVarV2('icon/primary'),
});

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