Compare commits

...

21 Commits

Author SHA1 Message Date
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`](ec59d22fdd) 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`](6b183fff1b) 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`](f21bf15231) 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`](14a7688549) 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`](9f11f6bb94) 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`](9f11f6bb94) 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`](c8121e5eb4) 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`](1b97d8a591) 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`](ef5cd10832) 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`](2fd7c2ea63) 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`](89c2795cde) 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`](4a9176e6e2) 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`](681444336f) 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`](a14fdd9501) 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`](c9d5ab0fa9) 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`](366eeaf8ba) 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`](c94078504c) 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`](f6909d1797)]:
    -   [@&#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`](f3cf4df358) 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`](f6909d1797)]:
    -   [@&#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](0f21c10bc9))

</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](8952917a6f))
-   throw helpful error if passing function to `createParser()` ([4cd3a44](4cd3a443f2))

</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](2d6710a1f9))

##### Bug Fixes

-   **audio:** correctly handle transcription streaming ([2a9b603](2a9b60336c))
-   **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](b467e94947))

##### Chores

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

### [`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](d3f6f8f9c7))
-   **client:** remove duplicate types ([#&#8203;1410](https://redirect.github.com/openai/openai-node/issues/1410)) ([338878b](338878bf48))
-   **exports:** add missing type exports ([#&#8203;1417](https://redirect.github.com/openai/openai-node/issues/1417)) ([2d15ada](2d15ada0e0))

##### Chores

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

</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
pengx17
387f7211bf fix(electron): cannot enable meetings correctly (#11269) 2025-03-28 11:13:48 +00:00
doodlewind
ebee11f573 refactor(editor): enable forceUpdate by default in viewport apis (#11264)
In this way, all downstream callers can be guaranteed by correct viewport fit result, instead of requiring them to set `forceUpdate: true` param explicitly to them. The resizing optimization is an internal exception.
2025-03-28 10:04:55 +00:00
162 changed files with 7220 additions and 2599 deletions

File diff suppressed because one or more lines are too long

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

4
Cargo.lock generated
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"

View File

@@ -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>`;

View File

@@ -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,
};
}

View File

@@ -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() {

View File

@@ -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;
}
}
}
`;

View File

@@ -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;

View File

@@ -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);

View File

@@ -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",

View File

@@ -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';

View File

@@ -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)),
};
}
}

View File

@@ -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
);

View File

@@ -8,6 +8,7 @@
"include": ["./src"],
"references": [
{ "path": "../../components" },
{ "path": "../../gfx/turbo-renderer" },
{ "path": "../../inlines/preset" },
{ "path": "../../model" },
{ "path": "../../rich-text" },

View File

@@ -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';

View File

@@ -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 {

View File

@@ -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';
}

View File

@@ -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,

View File

@@ -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,

View File

@@ -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;
}

View File

@@ -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:*",

View File

@@ -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>`;
}
}

View File

@@ -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
);
}

View File

@@ -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) {}

View File

@@ -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();

View File

@@ -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" },

View File

@@ -144,7 +144,7 @@ export class Viewport {
const newCenterX = initialTopLeftX + width / (2 * this.zoom);
const newCenterY = initialTopLeftY + height / (2 * this.zoom);
this.setCenter(newCenterX, newCenterY);
this.setCenter(newCenterX, newCenterY, false);
this._width = width;
this._height = height;
this._left = left;
@@ -362,7 +362,7 @@ export class Viewport {
* @param centerY The new y coordinate of the center of the viewport.
* @param forceUpdate Whether to force complete any pending resize operations before setting the viewport.
*/
setCenter(centerX: number, centerY: number, forceUpdate = false) {
setCenter(centerX: number, centerY: number, forceUpdate = true) {
if (forceUpdate && this._isResizing) {
this._forceCompleteResize();
}
@@ -405,7 +405,7 @@ export class Viewport {
newZoom: number,
newCenter = Vec.toVec(this.center),
smooth = false,
forceUpdate = smooth
forceUpdate = true
) {
// Force complete any pending resize operations if forceUpdate is true
if (forceUpdate && this._isResizing) {
@@ -445,7 +445,7 @@ export class Viewport {
bound: Bound,
padding: [number, number, number, number] = [0, 0, 0, 0],
smooth = false,
forceUpdate = smooth
forceUpdate = true
) {
let [pt, pr, pb, pl] = padding;
@@ -511,7 +511,7 @@ export class Viewport {
zoom: number,
focusPoint?: IPoint,
wheel = false,
forceUpdate = false
forceUpdate = true
) {
if (forceUpdate && this._isResizing) {
this._forceCompleteResize();

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",

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",

View File

@@ -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);

View File

@@ -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.',

View File

@@ -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://).',

View File

@@ -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 &&

View File

@@ -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({

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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;
@@ -516,14 +517,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 +552,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) {

View File

@@ -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(

View File

@@ -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"),

View File

@@ -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: [

View File

@@ -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({

View File

@@ -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',
}
);

View File

@@ -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(

View File

@@ -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';

View File

@@ -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 ||

View File

@@ -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');

View File

@@ -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();

View File

@@ -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>
`

View File

@@ -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>`;

View File

@@ -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>`;

View File

@@ -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,

View File

@@ -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">

View File

@@ -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;

View File

@@ -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(

View File

@@ -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>`

View File

@@ -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}

View File

@@ -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)}

View File

@@ -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>`

View File

@@ -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">

View File

@@ -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 &&

View File

@@ -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>
`;

View File

@@ -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,

View File

@@ -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 {

View File

@@ -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=${() => {

View File

@@ -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)}
>

View File

@@ -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;

View File

@@ -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}

View File

@@ -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>`;
}

View File

@@ -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) {

View File

@@ -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>

View File

@@ -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">

View File

@@ -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,

View File

@@ -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`

View File

@@ -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>`;

View File

@@ -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"

View File

@@ -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';

View File

@@ -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'

View File

@@ -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,

View File

@@ -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;

View File

@@ -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;

View File

@@ -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();
}
}),
])

View File

@@ -1,3 +1,4 @@
export * from './ai-provider';
export * from './copilot-client';
export * from './error';
export * from './setup-provider';

View File

@@ -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') {

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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>

View File

@@ -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>

View File

@@ -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;

View File

@@ -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>

View File

@@ -90,7 +90,7 @@ export function patchForEdgelessNoteConfig(
]);
const center = Vec.sub(edgelessTitleAnchor, pageTitleAnchor);
gfx.viewport.setCenter(center[0], center[1], true);
gfx.viewport.setCenter(center[0], center[1]);
gfx.viewport.smoothZoom(0.65, undefined, 15);
return true;

View File

@@ -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,
]);

View File

@@ -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,

View File

@@ -47,8 +47,10 @@ export const IntegrationCardHeader = ({
className,
icon,
onSettingClick,
showSetting = true,
...props
}: HTMLAttributes<HTMLHeadElement> & {
showSetting?: boolean;
onSettingClick?: () => void;
icon?: ReactNode;
}) => {
@@ -56,7 +58,7 @@ export const IntegrationCardHeader = ({
<header className={clsx(cardHeader, className)} {...props}>
<IntegrationCardIcon>{icon}</IntegrationCardIcon>
<div className={spaceX} />
<IntegrationSettingIcon onClick={onSettingClick} />
{showSetting ? <IntegrationSettingIcon onClick={onSettingClick} /> : null}
</header>
);
};

View File

@@ -47,6 +47,7 @@ export const ReadwiseIntegration = () => {
<IntegrationCardHeader
icon={<IntegrationTypeIcon type="readwise" />}
onSettingClick={handleOpenSetting}
showSetting={!!token}
/>
<IntegrationCardContent
title={t['com.affine.integration.readwise.name']()}

View File

@@ -21,6 +21,7 @@ import { useCallback, useEffect, useRef, useState } from 'react';
import * as styles from './style.css';
const clipperInput$ = new LiveData<ClipperInput | null>(null);
const port$ = new LiveData<MessagePort | null>(null);
window.addEventListener('message', event => {
if (
@@ -28,6 +29,10 @@ window.addEventListener('message', event => {
event.data.type === 'affine-clipper:import'
) {
clipperInput$.value = event.data.payload;
if (event.ports.length > 0) {
port$.value = event.ports[0];
}
}
});
@@ -90,6 +95,17 @@ export const Component = () => {
[]
);
const handleSuccess = useCallback(() => {
const arg = { type: 'affine-clipper:import:success' };
const port = port$.value;
if (port) {
port.postMessage(arg);
} else {
window.postMessage(arg);
}
window.close();
}, []);
const handleImportToSelectedWorkspace = useAsyncCallback(async () => {
if (clipperInputSnapshot && selectedWorkspace) {
setImporting(true);
@@ -98,17 +114,19 @@ export const Component = () => {
selectedWorkspace,
clipperInputSnapshot
);
window.postMessage({
type: 'affine-clipper:import:success',
});
window.close();
handleSuccess();
} catch (err) {
setImportingError(err);
} finally {
setImporting(false);
}
}
}, [clipperInputSnapshot, importClipperService, selectedWorkspace]);
}, [
clipperInputSnapshot,
handleSuccess,
importClipperService,
selectedWorkspace,
]);
const handleImportToNewWorkspace = useAsyncCallback(async () => {
if (!clipperInputSnapshot) {
@@ -121,16 +139,13 @@ export const Component = () => {
'Workspace',
clipperInputSnapshot
);
window.postMessage({
type: 'affine-clipper:import:success',
});
window.close();
handleSuccess();
} catch (err) {
setImportingError(err);
} finally {
setImporting(false);
}
}, [clipperInputSnapshot, importClipperService]);
}, [clipperInputSnapshot, handleSuccess, importClipperService]);
const handleClickSignIn = useCallback(() => {
window.open(

View File

@@ -53,6 +53,11 @@ export class MeetingSettingsService extends Service {
return;
}
this.globalStateService.globalState.set(MEETING_SETTINGS_KEY, {
...this.settings$.value,
enabled,
});
// when the user enable the recording feature the first time,
// the app may prompt the user to allow the recording feature by MacOS.
// when the user allows the recording feature, the app may be required to restart.
@@ -77,12 +82,6 @@ export class MeetingSettingsService extends Service {
// if the user disabled the recording feature, we need to setup the recording feature
await this.desktopApiService?.handler.recording.disableRecordingFeature();
}
// Only update the state after successful feature setup/disable
this.globalStateService.globalState.set(MEETING_SETTINGS_KEY, {
...this.settings$.value,
enabled,
});
}
setRecordingSavingMode(mode: MeetingSettingsSchema['recordingSavingMode']) {

View File

@@ -58,8 +58,7 @@ function fitViewport(
viewport.setViewportByBound(
Bound.deserialize(newViewport.xywh),
newViewport.padding,
false,
true
false
);
} else {
gfx.fitToScreen({

View File

@@ -43,17 +43,17 @@ export class UserDBEngine extends Entity<{
doc: {
name: this.DocStorageType.identifier,
opts: {
id: `${serverService.server.id}:` + this.userId,
flavour: serverService.server.id,
type: 'userspace',
flavour: serverService.server.id,
id: this.userId,
},
},
docSync: {
name: this.DocSyncStorageType.identifier,
opts: {
id: `${serverService.server.id}:` + this.userId,
type: 'userspace',
flavour: serverService.server.id,
id: this.userId,
},
},
},

View File

@@ -1,4 +1,5 @@
import { cssVar } from '@toeverything/theme';
import { cssVarV2 } from '@toeverything/theme/v2';
import { style } from '@vanilla-extract/css';
export const sidebarContainerInner = style({
@@ -12,11 +13,11 @@ export const sidebarContainerInner = style({
selectors: {
['[data-client-border=true] &']: {
borderRadius: 6,
border: `0.5px solid ${cssVar('borderColor')}`,
border: `0.5px solid ${cssVarV2.layer.insideBorder.border}`,
},
['[data-client-border=true][data-is-floating="true"] &']: {
boxShadow: cssVar('shadow3'),
border: `1px solid ${cssVar('borderColor')}`,
border: `1px solid ${cssVarV2.layer.insideBorder.border}`,
},
},
});
@@ -31,7 +32,7 @@ export const sidebarBodyTarget = style({
});
export const borderTop = style({
borderTop: `0.5px solid ${cssVar('borderColor')}`,
borderTop: `0.5px solid ${cssVarV2.layer.insideBorder.border}`,
});
export const sidebarBodyNoSelection = style({

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