Compare commits

...

114 Commits

Author SHA1 Message Date
L-Sun
ac76e5b949 chore: enable webview debugging for Android 2025-10-02 21:48:29 +08:00
L-Sun
0bc1005b96 fix(core): infinitied loop 2025-09-26 15:48:24 +08:00
L-Sun
34a3c83d84 fix(editor): prevent SwiftKey IME double input (#13590)
Close
[BS-3610](https://linear.app/affine-design/issue/BS-3610/bug-每次按空格会出现重复单词-,特定输入法,比如swiftkey)

#### PR Dependency Tree

* **PR #13591**
  * **PR #13590** 👈

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

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

- Bug Fixes
- Android: More reliable Backspace/delete handling, preventing missed
inputs and double-deletions.
- Android: Cursor/selection is correctly restored after merging a
paragraph with the previous block.
- Android: Smoother IME composition input; captures correct composition
range.
- Deletion across lines and around embeds/empty lines is more
consistent.
- Chores
- Internal event handling updated to improve Android compatibility and
stability (no user-facing changes).
<!-- end of auto-generated comment: release notes by coderabbit.ai -->





#### PR Dependency Tree


* **PR #13591**
  * **PR #13590** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)
2025-09-16 17:02:54 +08:00
L-Sun
fd717af3db fix(core): update and fix oxlint error (#13591)
#### PR Dependency Tree


* **PR #13591** 👈
  * **PR #13590**

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

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

- Bug Fixes
- Improved drag-and-drop stability: draggables, drop targets, and
monitors now respond when option sources or external data change.
- Improved async actions and permission checks to always use the latest
callbacks and error handlers.

- Chores
  - Lint/Prettier configs updated to ignore the Git directory.
  - Upgraded oxlint dev dependency.

- Tests
- Updated several end-to-end tests for more reliable text selection,
focus handling, and timing.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-09-16 16:47:43 +08:00
renovate[bot]
039976ee6d chore: bump up vite version to v6.3.6 [SECURITY] (#13573)
This PR contains the following updates:

| Package | Change | Age | Confidence |
|---|---|---|---|
| [vite](https://vite.dev)
([source](https://redirect.github.com/vitejs/vite/tree/HEAD/packages/vite))
| [`6.3.5` ->
`6.3.6`](https://renovatebot.com/diffs/npm/vite/6.3.5/6.3.6) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/vite/6.3.6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/vite/6.3.5/6.3.6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

### GitHub Vulnerability Alerts

####
[CVE-2025-58751](https://redirect.github.com/vitejs/vite/security/advisories/GHSA-g4jq-h2w9-997c)

### Summary
Files starting with the same name with the public directory were served
bypassing the `server.fs` settings.

### Impact
Only apps that match the following conditions are affected:

- explicitly exposes the Vite dev server to the network (using --host or
[`server.host` config
option](https://vitejs.dev/config/server-options.html#server-host))
- uses [the public directory
feature](https://vite.dev/guide/assets.html#the-public-directory)
(enabled by default)
- a symlink exists in the public directory

### Details
The
[servePublicMiddleware](9719497ade/packages/vite/src/node/server/middlewares/static.ts (L79))
function is in charge of serving public files from the server. It
returns the
[viteServePublicMiddleware](9719497ade/packages/vite/src/node/server/middlewares/static.ts (L106))
function which runs the needed tests and serves the page. The
viteServePublicMiddleware function [checks if the publicFiles variable
is
defined](9719497ade/packages/vite/src/node/server/middlewares/static.ts (L111)),
and then uses it to determine if the requested page is public. In the
case that the publicFiles is undefined, the code will treat the
requested page as a public page, and go on with the serving function.
[publicFiles may be undefined if there is a symbolic link anywhere
inside the public
directory](9719497ade/packages/vite/src/node/publicDir.ts (L21)).
In that case, every requested page will be passed to the public serving
function. The serving function is based on the
[sirv](https://redirect.github.com/lukeed/sirv) library. Vite patches
the library to add the possibility to test loading access to pages, but
when the public page middleware [disables this
functionality](9719497ade/packages/vite/src/node/server/middlewares/static.ts (L89))
since public pages are meant to be available always, regardless of
whether they are in the allow or deny list.

In the case of public pages, the serving function is [provided with the
path to the public
directory](9719497ade/packages/vite/src/node/server/middlewares/static.ts (L85))
as a root directory. The code of the sirv library [uses the join
function to get the full path to the requested
file](d061616827/packages/sirv/index.mjs (L42)).
For example, if the public directory is "/www/public", and the requested
file is "myfile", the code will join them to the string
"/www/public/myfile". The code will then pass this string to the
normalize function. Afterwards, the code will [use the string's
startsWith
function](d061616827/packages/sirv/index.mjs (L43))
to determine whether the created path is within the given directory or
not. Only if it is, it will be served.

Since [sirv trims the trailing slash of the public
directory](d061616827/packages/sirv/index.mjs (L119)),
the string's startsWith function may return true even if the created
path is not within the public directory. For example, if the server's
root is at "/www", and the public directory is at "/www/p", if the
created path will be "/www/private.txt", the startsWith function will
still return true, because the string "/www/private.txt" starts with 
"/www/p". To achieve this, the attacker will use ".." to ask for the
file "../private.txt". The code will then join it to the "/www/p"
string, and will receive "/www/p/../private.txt". Then, the normalize
function will return "/www/private.txt", which will then be passed to
the startsWith function, which will return true, and the processing of
the page will continue without checking the deny list (since this is the
public directory middleware which doesn't check that).

### PoC
Execute the following shell commands:

```
npm  create  vite@latest
cd vite-project/
mkdir p
cd p
ln -s a b
cd ..
echo  'import path from "node:path"; import { defineConfig } from "vite"; export default defineConfig({publicDir: path.resolve(__dirname, "p/"), server: {fs: {deny: [path.resolve(__dirname, "private.txt")]}}})' > vite.config.js
echo  "secret" > private.txt
npm install
npm run dev
```

Then, in a different shell, run the following command:

`curl -v --path-as-is 'http://localhost:5173/private.txt'`

You will receive a 403 HTTP Response,  because private.txt is denied.

Now in the same shell run the following command:

`curl -v --path-as-is 'http://localhost:5173/../private.txt'`

You will receive the contents of private.txt.

### Related links
-
f0113f3f82

####
[CVE-2025-58752](https://redirect.github.com/vitejs/vite/security/advisories/GHSA-jqfw-vq24-v9c3)

### Summary
Any HTML files on the machine were served regardless of the `server.fs`
settings.

### Impact

Only apps that match the following conditions are affected:

- explicitly exposes the Vite dev server to the network (using --host or
[server.host config
option](https://vitejs.dev/config/server-options.html#server-host))
- `appType: 'spa'` (default) or `appType: 'mpa'` is used

This vulnerability also affects the preview server. The preview server
allowed HTML files not under the output directory to be served.

### Details
The
[serveStaticMiddleware](9719497ade/packages/vite/src/node/server/middlewares/static.ts (L123))
function is in charge of serving static files from the server. It
returns the
[viteServeStaticMiddleware](9719497ade/packages/vite/src/node/server/middlewares/static.ts (L136))
function which runs the needed tests and serves the page. The
viteServeStaticMiddleware function [checks if the extension of the
requested file is
".html"](9719497ade/packages/vite/src/node/server/middlewares/static.ts (L144)).
If so, it doesn't serve the page. Instead, the server will go on to the
next middlewares, in this case
[htmlFallbackMiddleware](9719497ade/packages/vite/src/node/server/middlewares/htmlFallback.ts (L14)),
and then to
[indexHtmlMiddleware](9719497ade/packages/vite/src/node/server/middlewares/indexHtml.ts (L438)).
These middlewares don't perform any test against allow or deny rules,
and they don't make sure that the accessed file is in the root directory
of the server. They just find the file and send back its contents to the
client.

### PoC
Execute the following shell commands:

```
npm  create  vite@latest
cd vite-project/
echo  "secret" > /tmp/secret.html
npm install
npm run dev
```

Then, in a different shell, run the following command:

`curl -v --path-as-is
'http://localhost:5173/../../../../../../../../../../../tmp/secret.html'`

The contents of /tmp/secret.html will be returned.

This will also work for HTML files that are in the root directory of the
project, but are in the deny list (or not in the allow list). Test that
by stopping the running server (CTRL+C), and running the following
commands in the server's shell:

```
echo  'import path from "node:path"; import { defineConfig } from "vite"; export default defineConfig({server: {fs: {deny: [path.resolve(__dirname, "secret_files/*")]}}})'  >  [vite.config.js](http://vite.config.js)
mkdir secret_files
echo "secret txt" > secret_files/secret.txt
echo "secret html" > secret_files/secret.html
npm run dev

```

Then, in a different shell, run the following command:

`curl -v --path-as-is 'http://localhost:5173/secret_files/secret.txt'`

You will receive a 403 HTTP Response,  because everything in the
secret_files directory is denied.

Now in the same shell run the following command:

`curl -v --path-as-is 'http://localhost:5173/secret_files/secret.html'`

You will receive the contents of secret_files/secret.html.

---

### Release Notes

<details>
<summary>vitejs/vite (vite)</summary>

###
[`v6.3.6`](https://redirect.github.com/vitejs/vite/releases/tag/v6.3.6)

[Compare
Source](https://redirect.github.com/vitejs/vite/compare/v6.3.5...v6.3.6)

Please refer to
[CHANGELOG.md](https://redirect.github.com/vitejs/vite/blob/v6.3.6/packages/vite/CHANGELOG.md)
for details.

</details>

---

### Configuration

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

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

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

🔕 **Ignore**: Close this PR and you won't be reminded about these
updates 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:eyJjcmVhdGVkSW5WZXIiOiI0MS45Ny4xMCIsInVwZGF0ZWRJblZlciI6IjQxLjk3LjEwIiwidGFyZ2V0QnJhbmNoIjoiY2FuYXJ5IiwibGFiZWxzIjpbImRlcGVuZGVuY2llcyJdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-14 01:55:54 +08:00
dependabot[bot]
e158e11608 chore: bump sha.js from 2.4.11 to 2.4.12 (#13560)
Bumps [sha.js](https://github.com/crypto-browserify/sha.js) from 2.4.11
to 2.4.12.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/browserify/sha.js/blob/master/CHANGELOG.md">sha.js's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/browserify/sha.js/compare/v2.4.11...v2.4.12">v2.4.12</a>
- 2025-07-01</h2>
<h3>Commits</h3>
<ul>
<li>[eslint] switch to eslint <a
href="7acadfbd3a"><code>7acadfb</code></a></li>
<li>[meta] add <code>auto-changelog</code> <a
href="b46e7116eb"><code>b46e711</code></a></li>
<li>[eslint] fix package.json indentation <a
href="df9d521e16"><code>df9d521</code></a></li>
<li>[Tests] migrate from travis to GHA <a
href="c43c64adc6"><code>c43c64a</code></a></li>
<li>[Fix] support multi-byte wide typed arrays <a
href="f2a258e9f2"><code>f2a258e</code></a></li>
<li>[meta] reorder package.json <a
href="d8d77c0a72"><code>d8d77c0</code></a></li>
<li>[meta] add <code>npmignore</code> <a
href="35aec35c66"><code>35aec35</code></a></li>
<li>[Tests] avoid console logs <a
href="73e33ae0ca"><code>73e33ae</code></a></li>
<li>[Tests] fix tests run in batch <a
href="262913006e"><code>2629130</code></a></li>
<li>[Tests] drop node requirement to 0.10 <a
href="00c7f234aa"><code>00c7f23</code></a></li>
<li>[Dev Deps] update <code>buffer</code>,
<code>hash-test-vectors</code>, <code>standard</code>,
<code>tape</code>, <code>typedarray</code> <a
href="92b5de5f67"><code>92b5de5</code></a></li>
<li>[Tests] drop node requirement to v3 <a
href="9b5eca80fd"><code>9b5eca8</code></a></li>
<li>[meta] set engines to <code>&amp;gt;= 4</code> <a
href="807084c5c0"><code>807084c</code></a></li>
<li>Only apps should have lockfiles <a
href="c72789c7a1"><code>c72789c</code></a></li>
<li>[Deps] update <code>inherits</code>, <code>safe-buffer</code> <a
href="5428cfc6f7"><code>5428cfc</code></a></li>
<li>[Dev Deps] update <code>@ljharb/eslint-config</code> <a
href="2dbe0aab41"><code>2dbe0aa</code></a></li>
<li>update README to reflect LICENSE <a
href="8938256dbb"><code>8938256</code></a></li>
<li>[Dev Deps] add missing peer dep <a
href="d52889688c"><code>d528896</code></a></li>
<li>[Dev Deps] remove unused <code>buffer</code> dep <a
href="94ca7247f4"><code>94ca724</code></a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="eb4ea2fd3d"><code>eb4ea2f</code></a>
v2.4.12</li>
<li><a
href="d8d77c0a72"><code>d8d77c0</code></a>
[meta] reorder package.json</li>
<li><a
href="df9d521e16"><code>df9d521</code></a>
[eslint] fix package.json indentation</li>
<li><a
href="35aec35c66"><code>35aec35</code></a>
[meta] add <code>npmignore</code></li>
<li><a
href="d52889688c"><code>d528896</code></a>
[Dev Deps] add missing peer dep</li>
<li><a
href="b46e7116eb"><code>b46e711</code></a>
[meta] add <code>auto-changelog</code></li>
<li><a
href="94ca7247f4"><code>94ca724</code></a>
[Dev Deps] remove unused <code>buffer</code> dep</li>
<li><a
href="2dbe0aab41"><code>2dbe0aa</code></a>
[Dev Deps] update <code>@ljharb/eslint-config</code></li>
<li><a
href="73e33ae0ca"><code>73e33ae</code></a>
[Tests] avoid console logs</li>
<li><a
href="f2a258e9f2"><code>f2a258e</code></a>
[Fix] support multi-byte wide typed arrays</li>
<li>Additional commits viewable in <a
href="https://github.com/crypto-browserify/sha.js/compare/v2.4.11...v2.4.12">compare
view</a></li>
</ul>
</details>
<details>
<summary>Maintainer changes</summary>
<p>This version was pushed to npm by <a
href="https://www.npmjs.com/~ljharb">ljharb</a>, a new releaser for
sha.js since your current version.</p>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=sha.js&package-manager=npm_and_yarn&previous-version=2.4.11&new-version=2.4.12)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/toeverything/AFFiNE/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-07 00:17:51 +08:00
renovate[bot]
18faaa38a0 chore: bump up mermaid version to v10.9.4 [SECURITY] (#13518)
This PR contains the following updates:

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

### GitHub Vulnerability Alerts

####
[CVE-2025-54881](https://redirect.github.com/mermaid-js/mermaid/security/advisories/GHSA-7rqq-prvp-x9jh)

### Summary
In the default configuration of mermaid 11.9.0, user supplied input for
sequence diagram labels is passed to `innerHTML` during calculation of
element size, causing XSS.

### Details
Sequence diagram node labels with KaTeX delimiters are passed through
`calculateMathMLDimensions`. This method passes the full label to
`innerHTML` which allows allows malicious users to inject arbitrary HTML
and cause XSS when mermaid-js is used in it's default configuration
(with KaTeX support enabled).

The vulnerability lies here:

```ts
export const calculateMathMLDimensions = async (text: string, config: MermaidConfig) => {
  text = await renderKatex(text, config);
  const divElem = document.createElement('div');
  divElem.innerHTML = text; // XSS sink, text has not been sanitized.
  divElem.id = 'katex-temp';
  divElem.style.visibility = 'hidden';
  divElem.style.position = 'absolute';
  divElem.style.top = '0';
  const body = document.querySelector('body');
  body?.insertAdjacentElement('beforeend', divElem);
  const dim = { width: divElem.clientWidth, height: divElem.clientHeight };
  divElem.remove();
  return dim;
};
```

The `calculateMathMLDimensions` method was introduced in
5c69e5fdb004a6d0a2abe97e23d26e223a059832 two years ago, which was
released in [Mermaid
10.9.0](https://redirect.github.com/mermaid-js/mermaid/releases/tag/v10.9.0).

### PoC
Render the following diagram and observe the modified DOM.

```
sequenceDiagram
    participant A as Alice<img src="x" onerror="document.write(`xss on ${document.domain}`)">$$\\text{Alice}$$
    A->>John: Hello John, how are you?
    Alice-)John: See you later!
```

Here is a PoC on mermaid.live:
https://mermaid.live/edit#pako:eNpVUMtOwzAQ_BWzyoFKaRTyaFILiio4IK7ckA-1km1iKbaLY6spUf4dJ0AF68uOZ2dm7REqXSNQ6PHDoarwWfDGcMkUudaJGysqceLKkj3hPdl3osJ7IRvSm-qBwcCAaIXGaONRrSsnUdnobITF28PQ954lwXglai25UNNhxWAXBMyXxcGOi-3kL_5k79e73atuFSUv2HWazH1IWn0m3CC5aPf4b3p2WK--BW-4DJCOWzQ3TM0HQmiMqIFa4zAEicZv4iGMsw0D26JEBtS3NR656ywDpiYv869_11r-Ko12TQv0yLveI3eqfcjP111HUNVonrRTFuhdsVgAHWEAmuRxlG7SuEzKMi-yJAnhAjTLIk_EcbFJtuk2y9MphM8lM47KIp--AOZghtU

### Impact
XSS on all sites that use mermaid and render user supplied diagrams
without further sanitization.

### Remediation
The value of the `text` argument for the `calculateMathMLDimensions`
method needs to be sanitized before getting passed on to `innerHTML`.

---

### Release Notes

<details>
<summary>mermaid-js/mermaid (mermaid)</summary>

###
[`v10.9.4`](https://redirect.github.com/mermaid-js/mermaid/releases/tag/v10.9.4)

[Compare
Source](https://redirect.github.com/mermaid-js/mermaid/compare/v10.9.3...v10.9.4)

This release backports the fix for GHSA-7rqq-prvp-x9jh from
[v11.10.0](https://redirect.github.com/mermaid-js/mermaid/releases/tag/mermaid%4011.10.0),
preventing a potential XSS attack in labels in sequence diagrams.

See:
[`9d68517`](9d685178d2)
(on `main` branch)
See:
[`7509b06`](7509b066f1)
(backported commit)

**Full Changelog**:
<https://github.com/mermaid-js/mermaid/compare/v10.9.3...v10.9.4>

</details>

---

### Configuration

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

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

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

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

---

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

---

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

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-25 14:37:24 +08:00
DarkSky
e2156ea135 feat(server): integrate blob to context (#13491) 2025-08-15 17:35:45 +08:00
L-Sun
795bfb2f95 fix(ios): enable horizontal scroll for database (#13494)
Close
[BS-3625](https://linear.app/affine-design/issue/BS-3625/移动端database-table-view无法横向滚动)

#### PR Dependency Tree


* **PR #13494** 👈

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

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

## Summary by CodeRabbit

* **Bug Fixes**
* Improved iOS mobile table view scrolling: horizontal overflow is no
longer forcibly hidden, preventing clipped content and enabling smoother
horizontal navigation.
* Users can now access columns that previously appeared truncated on
narrow screens.
  * Vertical scrolling behavior remains unchanged.
  * No impact on non‑iOS devices.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-15 06:12:33 +00:00
L-Sun
0710da15c6 fix(editor): hook of database is invoked repeatedly (#13493)
Close
[AF-2789](https://linear.app/affine-design/issue/AF-2789/安卓客户端日期没了)

#### PR Dependency Tree


* **PR #13493** 👈

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

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

## Summary by CodeRabbit

* **Bug Fixes**
* Improved editing mode stability in mobile Kanban cells by preventing
redundant enter/exit transitions, resulting in smoother interactions and
reduced flicker.
* Enhanced mobile Table cells to avoid duplicate editing state changes,
minimizing unnecessary updates and improving responsiveness.
* Overall, editing transitions are now idempotent across affected mobile
views, reducing visual jitter and improving performance during edit
operations.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-15 06:09:34 +00:00
Peng Xiao
693ae9c834 fix(core): pasted code artifact should be inserted as codeblock (#13492)
fix AI-417

#### PR Dependency Tree


* **PR #13492** 👈

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

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

## Summary by CodeRabbit

* **New Features**
* Copying code snippets now uses a rich format for improved paste
fidelity in compatible editors.
* Preserves code block formatting and language when pasted, reducing
manual cleanup.
* Continues to support plain text and HTML paste for broad
compatibility.
  * Works more reliably when moving content within the app.
  * Existing copy confirmation remains unchanged.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-15 05:28:44 +00:00
L-Sun
9d38f79395 fix(editor): deactivate editor when selection out of editor (#13490)
Close
[AI-415](https://linear.app/affine-design/issue/AI-415/code-artifact-复制更好的支持code-block和插入正文)

#### PR Dependency Tree


* **PR #13490** 👈

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

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

* **Bug Fixes**
* Editor now deactivates when text selection moves outside the app,
preventing unintended interactions.
* Better handling when selection changes to external content, reducing
cases where the editor stayed active incorrectly.

* **Stability**
* Improved reliability around selection, focus, and visibility changes
to avoid accidental edits or actions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


#### PR Dependency Tree


* **PR #13490** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)
2025-08-14 14:49:53 +08:00
L-Sun
680f3b3006 feat(editor): impl shape text with dom renderer (#13471)
#### PR Dependency Tree


* **PR #13464**
  * **PR #13465**
    * **PR #13471** 👈
      * **PR #13472**
        * **PR #13473**

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

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

* **New Features**
* DOM rendering added for groups, mind maps and connectors so group
titles/outlines and mindmap connectors are visible on canvas.
* Shapes now support right-to-left text with proper vertical alignment.
* **Improvements**
  * Connector labels scale with viewport zoom for crisper display.
* Group-related selections (including nested groups) now update visuals
consistently.
* **Performance**
* Reduced DOM churn and fewer redraws during rendering and selection
changes.
* **Refactor**
* Renderer import/export surfaces consolidated with no user-facing
behavior changes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-14 04:29:13 +00:00
Peng Xiao
fbf234f9fa fix(core): code artifact copy should retain the original format (#13489)
fix AI-415

#### PR Dependency Tree


* **PR #13489** 👈

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

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

## Summary by CodeRabbit

* **Bug Fixes**
* Improved code block stability to prevent layout shifts and overlapping
during syntax highlighting.
  * Ensured consistent height and alignment for code snippets.

* **Style**
* Refined code block appearance for clearer, more polished presentation.

* **Chores**
* Internal adjustments to support more reliable rendering of highlighted
code.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-14 04:24:28 +00:00
L-Sun
e9ede5213e fix(core): incorrect position of mobile notification card (#13485)
#### PR Dependency Tree


* **PR #13485** 👈

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

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

## Summary by CodeRabbit

* **Style**
* Improved mobile toast notification layout for better responsiveness
across screen sizes.
* Replaced fixed left alignment with dynamic edge offsets, ensuring
consistent spacing near screen edges.
* Removed forced centering and rigid width constraints to reduce
clipping and overlap on narrow viewports.
  * Visual behavior only; no changes to interaction or functionality.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-14 02:23:47 +00:00
德布劳外 · 贾贵
aea6f81937 fix(core): remove attachment chip failed (#13468)
> CLOSE PD-2697

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

* **Bug Fixes**
* Removing an attachment chip now also removes duplicate attachments
with the same source, preventing duplicate attachments in the AI chat
chip list.
* Removing a selected context chip now also removes duplicate selected
contexts with the same identifier, preventing duplicate context chips.
* Attachments from different sources and chips of other types (document,
file, tag, collection) remain unaffected.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-14 02:15:48 +00:00
L-Sun
66c2bf3151 fix(editor): incorrect z-index in dom renderer (#13465)
#### PR Dependency Tree


* **PR #13464**
  * **PR #13465** 👈
    * **PR #13471**
      * **PR #13472**
        * **PR #13473**

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

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

## Summary by CodeRabbit

* **Bug Fixes**
* Improved stacking order across canvas elements (shapes, connectors,
brush, highlighter), reducing unexpected overlap.
* Corrected z-index application for placeholders and fully rendered
elements to ensure consistent layering during edits.
* **Refactor**
* Centralized z-index handling for canvas elements to provide
predictable, uniform layering behavior across the app.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-14 11:10:32 +08:00
L-Sun
aa052096c1 feat(editor): brush and highlighter dom renderer (#13464)
#### PR Dependency Tree


* **PR #13464** 👈
  * **PR #13465**

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

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

* **New Features**
* DOM-based SVG rendering for Brush and Highlighter with zoom, rotation,
layering and improved visualization.

* **Refactor**
* Consolidated renderer exports into a single entry point for simpler
integration.

* **Chores**
* Updated view registrations to include the new DOM renderer extensions.
  * Improved highlighter sizing consistency based on serialized bounds.

* **Revert**
  * Removed highlighter renderer registration from the shape module.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


#### PR Dependency Tree


* **PR #13464** 👈
  * **PR #13465**
    * **PR #13471**
      * **PR #13472**
        * **PR #13473**

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)
2025-08-14 02:08:36 +00:00
Wu Yue
c2f3018eb7 fix(core): missing lit component props (#13482)
Close [AI-413](https://linear.app/affine-design/issue/AI-413)

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

## Summary by CodeRabbit

* **Bug Fixes**
* Chat messages now scroll vertically, preventing content from being cut
off.
* Chat actions are no longer displayed or fetched, reducing unnecessary
loading.
* Peek view chat composer behavior is aligned with the main chat,
ensuring consistent feature availability across views.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-13 08:57:51 +00:00
DarkSky
dd9d8adbf8 fix(server): multi step tool call (#13486)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- Bug Fixes
- Enforced a consistent step limit for AI responses across providers,
preventing excessively long generations in both text and streaming modes
for more predictable results.

- Refactor
- Centralized step limit configuration into a shared provider, ensuring
uniform behavior across providers and simplifying future maintenance.
- Standardized application of step limits in text generation and
streaming flows to align provider behavior and improve overall
reliability.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-13 08:10:42 +00:00
L-Sun
7e0de251cb fix(editor): remove patch of key-binding in andriod (#13483)
In recent versions of Android (or maybe webview), the
`KeyboardEvent.key` for the backspace key now has the correct value.
This PR remove the patch since it will trigger two delete actions when
press backspace at the first character of paragraph"

Related PR https://github.com/toeverything/AFFiNE/issues/10523

#### PR Dependency Tree


* **PR #13483** 👈

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

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

## Summary by CodeRabbit

* **Refactor**
* Streamlined keyboard shortcut handling for greater consistency across
platforms.
* Reduced overhead by consolidating event bindings; no change to
expected shortcut behavior for end-users.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-13 06:34:07 +00:00
L-Sun
5c73fc9767 chore(editor): adjust notification of database editing (#13484)
#### PR Dependency Tree


* **PR #13484** 👈

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

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

## Summary by CodeRabbit

- Bug Fixes
- Reduced repeated mobile editing notifications; the notice now appears
once and only reappears after you dismiss it.
- More consistent notification behavior on mobile for a less disruptive
editing experience.

- Refactor
- Streamlined internal event handling to improve reliability and reduce
potential listener leaks, resulting in smoother interactions.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-13 06:33:38 +00:00
Peng Xiao
a0c22b7d06 fix(core): manage payment details entry adjustment (#13481)
#### PR Dependency Tree


* **PR #13481** 👈

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

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

- Bug Fixes
- The “Update payment method” prompt now appears only when your
subscription is past due.
- Payment Method section now shows whenever a paid plan record exists
(loading placeholders unchanged).
- Action button styling adjusts for past-due subscriptions (uses the
alternate/secondary style).
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-13 04:42:29 +00:00
DarkSky
072557eba1 feat(server): adapt gpt5 (#13478)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- New Features
- Added GPT-5 family and made GPT-5/-mini the new defaults for Copilot
scenarios and prompts.

- Bug Fixes
- Improved streaming chunk formats and reasoning/text semantics,
consistent attachment mediaType handling, and more reliable reranking
via log-prob handling.

- Refactor
- Unified maxOutputTokens usage; removed per-call step caps and migrated
several tools to a unified inputSchema shape.

- Chores
- Upgraded AI SDK dependencies and bumped an internal dependency
version.

- Tests
- Updated mocks and tests to reference GPT-5 variants and new stream
formats.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-13 02:32:15 +00:00
Peng Xiao
fda7e9008d fix(core): show past due in ui (#13477)
fix CLOUD-238, CLOUD-239

#### PR Dependency Tree


* **PR #13477** 👈

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

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

## Summary by CodeRabbit

* **New Features**
* Payment method management is now always available directly within AI
and Pro plan cards.
* **Bug Fixes**
* Past-due subscriptions are now included in subscription status
results, ensuring they appear in billing views.
* **Style**
* Plan actions are moved inline within each plan’s description for a
cleaner, more compact layout.
  * Actions are grouped horizontally with improved spacing.
  * Minor class name and spacing tweaks for consistent styling.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-12 09:00:32 +00:00
Jachin
678dc15365 feat(editor): add mermaid code preview (#13456)
<img width="971" height="681" alt="iShot_2025-08-10_14 29 01"
src="https://github.com/user-attachments/assets/eff3e6d5-3129-42ac-aceb-994c18f675ab"
/>


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

* **New Features**
* Mermaid diagram previews for code blocks with interactive zoom, pan,
and reset controls.
* Improved rendering feedback with loading, error states, retry
behavior, and fallback messaging.

* **Chores**
  * Added Mermaid as a frontend dependency to enable diagram rendering.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: L-Sun <zover.v@gmail.com>
2025-08-12 03:00:01 +00:00
Jachin
ef99c376ec fix(editor): fix import zip with cjk filename (#13458)
fix #12721 

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

* **Bug Fixes**
* Improved handling of non‑ASCII filenames when unzipping archives: the
extractor now tries alternative encodings and validates results so
filenames are preserved and displayed correctly after extraction. This
change reduces corrupt or garbled names while keeping existing
extraction behavior otherwise unchanged.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-12 02:51:23 +00:00
DarkSky
65f679c4f0 fix(server): frequent embedding (#13475)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- New Features
- Smarter embedding pipeline skips re-embedding when content hasn’t
changed; added content sanitization for embeddings and workspace content
retrieval.
- Bug Fixes
- Re-embedding now requires both a document update and the last
embedding being older than 10 minutes, reducing unnecessary work.
- Refactor
- Consolidated embedding preprocessing and moved sanitization utilities
into shared models; upserts now refresh stored content.
- Tests
- Expanded snapshot-based tests covering multiple time/age scenarios for
embedding decision logic.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-12 01:45:41 +00:00
DarkSky
125564b7d2 fix(server): improve outdated embedding cleanup (#13476)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Prevents accidental deletion of placeholder documents during embedding
cleanup.
* Improves accuracy when identifying documents to remove, using multiple
data sources.
* Skips unnecessary cleanup when no embeddings or snapshots exist,
reducing noise and overhead.
* **Chores**
* Streamlined and centralized document filtering logic to ensure
consistent cleanup behavior.
* Parallelized data checks to make cleanup more efficient without
changing user workflows.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-12 01:20:21 +00:00
Wu Yue
aa20e7ba66 fix(core): copilot tool restore (#13470)
Close [AI-410](https://linear.app/affine-design/issue/AI-410)

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

- New Features
  - None

- Bug Fixes
- Middle-click panning now reliably returns to the previously active
tool, including after using Copilot or frame navigation.
- Smoother, more responsive transition into panning to reduce accidental
selections.

- Refactor
- Simplified AI panel click-outside handling with no change to
user-visible behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-11 15:52:50 +00:00
德布劳外 · 贾贵
01e8458075 refactor(core): add selected chip synchronously (#13469)
> CLOSE PD-2698

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

## Summary by CodeRabbit

- **Refactor**
- Optimized context chip handling in the AI chat composer to process
additions concurrently.
- Improves responsiveness when adding multiple documents or attachments
as context, reducing wait times and making the composing experience
smoother.
- No changes to visible functionality; users should notice faster
updates when selecting several items at once.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-11 09:42:18 +00:00
Wu Yue
0d9f6770bf fix(core): right click on edgeless will also damage other functions (#13466)
Close [AI-411](https://linear.app/affine-design/issue/AI-411)

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

## Summary by CodeRabbit

- Bug Fixes
- Releasing the mouse now always ends panning, preventing stuck states.
  - Actions cancel correctly when you release without dragging.

- Refactor
- More consistent Copilot activation: use right-click or Ctrl (⌘ on Mac)
+ left-click.
- Smoother switching to Copilot with improved drag-state reset and
cleanup.
- Removed automatic restoration of previous selection when activating
Copilot.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-11 08:11:21 +00:00
L-Sun
5ef81ba74b chore(ios): disable dom renderer (#13462)
#### PR Dependency Tree


* **PR #13462** 👈

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

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

## Summary by CodeRabbit

* **Chores**
* Updated default configuration: The DOM-based renderer is now disabled
by default on all platforms. Previously, it was enabled by default on
iOS. This change standardizes the out-of-the-box experience across
devices. If you rely on the DOM renderer, you can still enable it via
feature flags in your environment or settings.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-11 05:03:18 +00:00
DarkSky
4ffa3b5ccc fix(server): fulfill empty embedding for trashed docs (#13461)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- New Features
  - None
- Bug Fixes
- Ensures a placeholder embedding is always created when content is
empty or after deletion, reducing errors and improving Copilot
stability.
- Refactor
- Centralized empty-embedding handling for consistent behavior across
workflows.
- Standardized embedding dimension configuration to a single source for
reliability.
- Chores
- Simplified internal embedding module surface and imports for
maintainability.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-11 03:23:45 +00:00
fengmk2
07b9b4fb8d chore: use latest oxlint version (#13457)
oxlint-tsgolint install fails had been fixed

see https://github.com/oxc-project/oxc/issues/12892



#### PR Dependency Tree


* **PR #13457** 👈

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

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

## Summary by CodeRabbit

* **Chores**
* Updated the version range for a development dependency to allow for
newer compatible releases.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-11 03:09:39 +00:00
L-Sun
f7461dd3d9 chore(ios): enable edgeless dom renderer (#13460)
#### PR Dependency Tree


* **PR #13460** 👈

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

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

## Summary by CodeRabbit

- New Features
- The DOM renderer setting is now configurable across all builds, not
just beta/canary. This expands access to the feature flag for all users,
enabling broader experimentation and customization.
- Users on stable releases can now enable or disable the DOM renderer
through standard configuration, ensuring consistent behavior across
release channels.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-11 02:03:19 +00:00
fengmk2
343c717930 chore(server): add new darkskygit to stable image approvers (#13449)
#### PR Dependency Tree


* **PR #13449** 👈

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

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

* **Chores**
* Expanded the list of approvers for the manual approval step in the
release workflow.
* Added more keywords that can be used to deny approval during the
release process.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-10 09:53:04 +00:00
Peng Xiao
bc1bd59f7b fix(electron): disable LoadBrowserProcessSpecificV8Snapshot (#13450)
Crash report:


```
Thread 0 Crashed:
0   Electron Framework            	       0x113462de8 logging::LogMessage::HandleFatal(unsigned long, std::__Cr::basic_string<char, std::__Cr::char_traits<char>, std::__Cr::allocator<char>> const&) const
1   Electron Framework            	       0x113462d20 logging::LogMessage::HandleFatal(unsigned long, std::__Cr::basic_string<char, std::__Cr::char_traits<char>, std::__Cr::allocator<char>> const&) const
2   Electron Framework            	       0x10f04d7c8 logging::LogMessage::Flush()
3   Electron Framework            	       0x113462ea0 logging::LogMessageFatal::~LogMessageFatal()
4   Electron Framework            	       0x10fd28f44 std::__Cr::basic_ostream<char, std::__Cr::char_traits<char>>& std::__Cr::operator<<<std::__Cr::char_traits<char>>(std::__Cr::basic_ostream<char, std::__Cr::char_traits<char>>&, char const*)
5   Electron Framework            	       0x11082e900 gin::V8Initializer::LoadV8SnapshotFromFile(base::File, base::MemoryMappedFile::Region*, gin::V8SnapshotFileType)
6   Electron Framework            	       0x114451da0 gin::V8Initializer::LoadV8SnapshotFromFileName(std::__Cr::basic_string_view<char, std::__Cr::char_traits<char>>, gin::V8SnapshotFileType)
7   Electron Framework            	       0x110f03e0c content::ContentMainRunnerImpl::Initialize(content::ContentMainParams)
8   Electron Framework            	       0x1100ae594 content::RunContentProcess(content::ContentMainParams, content::ContentMainRunner*)
9   Electron Framework            	       0x1100ae1f8 content::ContentMain(content::ContentMainParams)
10  Electron Framework            	       0x110911c10 ElectronMain
11  dyld                          	       0x19b5d5924 start + 6400
```

#### PR Dependency Tree


* **PR #13450** 👈

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

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

## Summary by CodeRabbit

* **Chores**
* Updated Electron Forge configuration to remove a specific setting
related to browser process snapshots. No impact on visible features or
functionality.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-09 02:36:09 +00:00
fengmk2
c7afc880e6 feat(server): auto fix doc summary (#13448)
close AF-2787

<img width="2424" height="412" alt="image"
src="https://github.com/user-attachments/assets/d6dedff5-1904-48b1-8a36-c3189104e45b"
/>



#### PR Dependency Tree


* **PR #13448** 👈

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

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

## Summary by CodeRabbit

* **New Features**
* Introduced an automated system that regularly detects and repairs
documents with missing summaries in all workspaces.
* Added background processing to ensure document summaries are kept
up-to-date without manual intervention.

* **Tests**
* Added new tests to verify detection of documents with empty or
non-empty summaries.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-08 13:40:02 +00:00
DarkSky
3cfb0a43af feat(server): add hints for context files (#13444)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Attachments (files) are now included in the conversation context,
allowing users to reference files during chat sessions.
* Added a new "blobRead" tool enabling secure, permission-checked
reading of attachment content in chat sessions.

* **Improvements**
* Enhanced chat session preparation to always include relevant context
files.
* System messages now clearly display attached files and selected
content only when available, improving context clarity for users.
* Updated tool-calling guidelines to ensure user workspace is searched
even when attachment content suffices.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-08 09:32:52 +00:00
Wu Yue
4005f40b16 fix(core): missing hide edgeless copilot panel logic (#13445)
Close [AI-409](https://linear.app/affine-design/issue/AI-409)

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

## Summary by CodeRabbit

* **Bug Fixes**
* Improved the behavior when continuing in AI Chat by ensuring the
copilot panel is properly hidden before switching panels for a smoother
user experience.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-08 08:37:49 +00:00
德布劳外 · 贾贵
5fd7dfc8aa refactor(core): display selected doc & attachment chip (#13443)
<img width="1275" height="997" alt="截屏2025-08-08 15 13 59"
src="https://github.com/user-attachments/assets/b429239d-84dc-490d-ad1e-957652e3caba"
/>


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

## Summary by CodeRabbit

* **New Features**
* Introduced support for attachment chips in AI chat, allowing
individual attachments to be displayed, added, and removed as separate
chips.
* Added a new visual component for displaying attachment chips in the
chat interface.

* **Improvements**
* Enhanced chat composer to handle attachments and document chips
separately, improving clarity and control over shared content.
* Expanded criteria for triggering chat actions to include both document
and attachment selections.

* **Refactor**
* Updated context management to process attachments individually rather
than in batches, streamlining the addition and removal of context items.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-08 07:34:04 +00:00
Jachin
009288dee2 chore: replaces the MailHog Docker container with Mailpit (#13439)
This PR replaces the MailHog Docker container with Mailpit.

Reasons for this change:

- MailHog is no longer maintained.
- Mailpit is an actively developed, open-source alternative.
- Fully compatible as a drop-in replacement.
- Lightweight and Fast: Built with Go, the official Docker image is only
12.5MB.

This change improves performance and ensures we are using a maintained
tool for local email testing.

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

* **Chores**
* Replaced the email testing service with a new one that offers a
similar web interface and SMTP port.
* Updated configuration to enhance message storage and persistence for
email testing in development environments.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-08 06:15:37 +00:00
EYHN
52a9c86219 feat(core): enable battery save mode for mobile (#13441)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
  * Battery save mode is now enabled by default on mobile devices.
* Users will see an updated, more detailed description for battery save
mode.
* Battery save mode can now be configured by all users, not just in
certain builds.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-08 02:32:38 +00:00
DarkSky
af7fefd59a feat(electron): enhance fuses (#13437)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Chores**
* Updated Electron app configuration to enhance security and integrity
with additional runtime protection options.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-07 14:10:43 +00:00
DarkSky
94cf32ead2 fix(server): unstable test (#13436)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Tests**
* Improved test reliability by automatically cleaning up workspace
snapshots during embedding status checks in end-to-end tests.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-07 09:37:22 +00:00
德布劳外 · 贾贵
ffbd21e42a feat: continue answer in ai chat (#13431)
> CLOSE AF-2786

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

* **New Features**
* Added support for including HTML content from the "make it real"
action in AI chat context and prompts.
* Users can now continue AI responses in chat with richer context,
including HTML, for certain AI actions.

* **Improvements**
* Enhanced token counting and context handling in chat to account for
HTML content.
* Refined chat continuation logic for smoother user experience across
various AI actions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-07 05:12:44 +00:00
EYHN
c54ccda881 fix(editor): allow right click on reference (#13259)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Improved click event handling on reference elements to prevent
unintended behavior from right-clicks.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-07 04:55:37 +00:00
EYHN
747b11b128 fix(android): fix android blob upload (#13435)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Improved WebView configuration to allow loading mixed content (HTTP
and HTTPS) in the Android app.
* Enhanced robustness when retrieving upload timestamps, preventing
potential errors if data is missing or undefined.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-07 03:43:30 +00:00
fengmk2
bc3b41378d chore(server): add ai document link on admin panel (#13428)
close AF-2766
<img width="2082" height="654" alt="image"
src="https://github.com/user-attachments/assets/efba776c-91cd-4d59-a2a6-e00f68c61be1"
/>



#### PR Dependency Tree


* **PR #13428** 👈

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

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

## Summary by CodeRabbit

* **New Features**
* Configuration descriptions for the copilot plugin now include direct
links to relevant documentation for easier access to more information.

* **Style**
* Improved display of configuration descriptions to support and render
HTML content.

* **Refactor**
* The AI navigation item in the admin panel has been disabled and is no
longer visible.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-07 03:16:29 +00:00
德布劳外 · 贾贵
a6c78dbcce feat(core): extract selected docs (#13426)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## Summary by CodeRabbit

* **New Features**
* Added support for handling and extracting embedded document references
within selected content in AI chat features.
* Documents associated with selected context chips are now properly
managed alongside attachments, improving context handling in AI chat
interactions.

* **Bug Fixes**
* Ensured that the state of context chips accurately reflects the
presence of attachments and documents.

* **Documentation**
* Updated type definitions to include support for document references in
relevant AI chat contexts.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

> CLOSE AF-2783
2025-08-07 02:53:59 +00:00
fengmk2
542c8e2c1d chore: fix oxlint errors (#13434)
#### PR Dependency Tree


* **PR #13434** 👈

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

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

## Summary by CodeRabbit

* **Style**
* Improved clarity of TypeScript error suppression comments across
various test files and helper scripts. Comments now specify the reasons
for ignoring specific type errors, enhancing code readability for
developers.
* **Chores**
* Updated inline comments without affecting application functionality or
user experience. No changes to features, logic, or test outcomes.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-07 02:53:25 +00:00
L-Sun
21c758b6d6 chore(editor): enable dom renderer for beta ios (#13427)
#### PR Dependency Tree


* **PR #13427** 👈

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

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

## Summary by CodeRabbit

* **New Features**
* Introduced a feature flag to enable or disable mobile database
editing.
* Added user notifications on mobile when attempting to edit databases
if the feature is not enabled.

* **Bug Fixes**
* Improved selection handling in mobile Kanban and Table views to ensure
correct behavior.
* Prevented add group and filter actions in readonly views or data
sources.

* **Style**
  * Adjusted toast notifications to allow for variable height.
* Updated horizontal overflow behavior for mobile table views,
specifically targeting iOS devices.
* Refined keyboard toolbar styling for more consistent height and
padding.

* **Chores**
* Updated feature flag configuration to better support mobile and
iOS-specific features.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-06 08:15:19 +00:00
DarkSky
9677bdf50d feat(server): skip cleanup for stale workspace (#13418)
fix AI-408

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

* **New Features**
* Added a new field to workspaces to track the last time embeddings were
checked.
* Cleanup jobs for workspace embeddings now skip workspaces that haven't
changed in over 30 days or have no embeddings, improving efficiency.
* Cleanup jobs are now automatically triggered when a workspace is
updated.

* **Improvements**
* Enhanced workspace selection for cleanup and indexing tasks to use
more precise filters and batching.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-06 08:11:50 +00:00
EYHN
713f926247 feat(core): hide search locally button when battery save enabled (#13423)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Integrated a feature flag to control "battery save mode" within quick
search for documentation.

* **Behavior Changes**
  * Local search is now enabled by default for non-cloud workspaces.
* The "search locally" option is hidden when battery save mode is
active.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-06 05:50:05 +00:00
L-Sun
99a7b7f676 chore(editor): mobile database editing experimental flag (#13425)
#### PR Dependency Tree


* **PR #13425** 👈

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

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

## Summary by CodeRabbit

* **New Features**
* Introduced a feature flag to enable or disable mobile database
editing.
* Added user notifications on mobile when attempting to edit databases
if the feature is not enabled.

* **Bug Fixes**
* Prevented addition of filters and group actions in readonly or
restricted mobile editing states.
* Fixed issues with selection handling in mobile Kanban and Table views
by ensuring correct context binding.

* **Style**
  * Improved toast notification styling to allow dynamic height.
* Adjusted mobile table view styles for better compatibility on iOS
devices.

* **Chores**
* Updated feature flag configuration to support mobile database editing
control.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-06 04:55:00 +00:00
Cats Juice
44ef06de36 feat(core): peek doc in ai doc-read tool result (#13424)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Enhanced document read results with clickable cards that open a peek
view of the referenced document.
* Added support for displaying document identifiers in document read
results.

* **Bug Fixes**
* Improved compatibility with older document read results that may lack
a document identifier.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-06 04:01:07 +00:00
EYHN
e735ada758 feat(ios): enable ai button (#13422)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* The AI button feature on mobile is now enabled by default only on iOS
devices, instead of being limited to canary builds.
  
* **Chores**
  * Updated internal configuration for mobile feature availability.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-06 03:14:34 +00:00
德布劳外 · 贾贵
40ccb7642c refactor(core): show selected content chip if needed (#13415)
> CLOSE AF-2784

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

## Summary by CodeRabbit

* **Bug Fixes**
* Improved reliability when handling AI chat actions by ensuring valid
context is present before proceeding.
* Enhanced error handling and logging for failed context extraction in
AI chat features.

* **New Features**
* Context extraction is now performed asynchronously before opening the
AI Chat, providing more accurate and relevant chat context.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-06 01:39:25 +00:00
德布劳外 · 贾贵
f303ec14df fix(core): generate image from text group (#13417)
> CLOSE AF-2785

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

## Summary by CodeRabbit

* **New Features**
* Streamlined AI action groups by consolidating image generation and
text generation actions under a unified "generate from text" group.
* Image processing and filtering actions are now organized into a
distinct "touch up image" group for improved clarity in dynamic image
options.

* **Refactor**
* Simplified and reorganized AI action groups for a more intuitive user
experience.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-06 01:38:58 +00:00
Lakr
531fbf0eed fix: 🚑 replace problematic attachment count (#13416)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Improved attachment handling in chat by updating the way attachments
are counted, ensuring only files and images are included. Document
attachments are no longer counted in this process.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-06 09:37:37 +08:00
德布劳外 · 贾贵
6ffa60c501 feat(core): extract edgeless selected images (#13420)
> CLOSE AF-2782

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

* **New Features**
* Added support for extracting image files from selected elements in
edgeless editor mode, allowing users to retrieve image files alongside
canvas snapshots.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-05 10:43:18 +00:00
DarkSky
46acf9aa4f chore(server): update config naming (#13419)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Updated scenario names and options for Copilot, including new and
renamed scenarios such as "audio_transcribing,"
"complex_text_generation," "quick_decision_making,"
"quick_text_generation," and "polish_and_summarize."
* Enhanced support for customizing and overriding default model
assignments in Copilot scenarios.

* **Bug Fixes**
* Improved consistency and clarity in scenario configuration and prompt
selection.

* **Documentation**
* Updated descriptions in configuration interfaces to better explain the
ability to use custom models and override defaults.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-05 10:26:18 +00:00
Lakr
d398aa9a71 chore: added mime-type in gql (#13414)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Improved file and image attachment handling by including MIME type
information for uploads.
* Added a new query to fetch document summaries by workspace and
document IDs.

* **Refactor**
* Minor adjustments to method signatures and property initializations to
streamline code and maintain consistency.
* Updated access levels for certain properties and methods to internal,
enhancing encapsulation.

* **Style**
  * Formatting and whitespace clean-up for improved code readability.

No changes to user-facing functionality or behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-05 08:10:40 +00:00
Cats Juice
36d58cd6c5 fix(core): prevent navigating when clicking doc title in ai chat (#13412)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Style**
* Updated search result titles to remove special styling and clickable
highlighting.

* **Bug Fixes**
* Improved consistency of click behavior by making entire search result
items clickable, rather than just the title text.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-05 06:30:40 +00:00
Peng Xiao
d2a73b6d4e fix(electron): disable runAsNode fuse (#13406)
fix AF-2781




#### PR Dependency Tree


* **PR #13406** 👈

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

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

## Summary by CodeRabbit

* **Chores**
* Updated Electron app configuration to include an additional plugin for
enhanced packaging options.
* Added a new development dependency to support the updated
configuration.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-04 13:38:12 +00:00
DarkSky
0fcb4cb0fe feat(server): scenario mapping (#13404)
fix AI-404

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

* **New Features**
* Introduced scenario-based configuration for copilot, allowing default
model assignments for various AI use cases.
  * Added a new image generation model to the available options.

* **Improvements**
* Refined copilot provider settings by removing deprecated fallback
options and standardizing base URL configuration.
* Enhanced prompt management to support scenario-driven updates and
improved configuration handling.
* Updated admin and settings interfaces to support new scenario
configurations.

* **Bug Fixes**
* Removed deprecated or unused prompts and related references across
platforms for consistency.

* **Other**
* Improved test coverage and updated test assets to reflect prompt and
scenario changes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-04 09:50:38 +00:00
Wu Yue
7a93db4d12 fix(core): ai image upload failed (#13405)
Close [AI-407](https://linear.app/affine-design/issue/AI-407)

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

* **Bug Fixes**
* Ensured that images included in the chat context are now properly sent
as attachments during AI chat interactions.

* **Tests**
* Enhanced chat tests to verify that the AI correctly identifies images
of kittens or cats in its responses.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-04 09:29:36 +00:00
DarkSky
c31504baaf fix(server): missing embedding search (#13401)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Enhanced search functionality to include results from additional
"blob" data sources, providing more comprehensive search results.

* **Bug Fixes**
* Improved messaging to ensure "No results found" is only shown when no
relevant results exist across all data sources.

* **Tests**
* Updated test cases to reflect new keyword contexts, improving
validation accuracy for search-related features.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-04 08:19:59 +00:00
DarkSky
76eedf3b76 chore(server): downscale sql proxy (#13393)
<img width="1199" height="190" alt="image"
src="https://github.com/user-attachments/assets/e1adec4a-5a62-454a-ad0d-26f50872e10b"
/>


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

* **Chores**
  * Reduced the number of gcloud-sql-proxy replicas from 3 to 2.
* Lowered memory and CPU resource limits for the gcloud-sql-proxy
container.
  * Added resource requests to optimize container performance.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-02 10:00:44 +00:00
forehalo
37e859484d fix: bump on-headers 2025-08-01 17:33:13 +08:00
EYHN
1ceed6c145 feat(core): support better battery save mode (#13383)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Introduced a Document Summary module, enabling live and cached
document summaries with cloud revalidation.
  * Added a feature flag for enabling battery save mode.
* Added explicit pause and resume controls for sync operations,
accessible via UI events and programmatically.

* **Improvements**
* Enhanced sync and indexing logic to support pausing, resuming, and
battery save mode, with improved job prioritization.
* Updated navigation and preview components to use the new document
summary service and improved priority handling.
* Improved logging and state reporting for sync and indexing processes.
* Refined backlink handling with reactive loading states and cloud
revalidation.
* Replaced backlink and link management to use a new dedicated document
links service.
* Enhanced workspace engine to conditionally enable battery save mode
based on feature flags and workspace flavor.

* **Bug Fixes**
* Removed unnecessary debug console logs from various components for
cleaner output.

* **Refactor**
* Replaced battery save mode methods with explicit pause/resume methods
throughout the app and services.
* Modularized and streamlined document summary and sync-related code for
better maintainability.
* Restructured backlink components to improve visibility handling and
data fetching.
* Simplified and improved document backlink data fetching with retry and
loading state management.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-01 08:31:31 +00:00
Lakr
1661ab1790 feat: fix several view model issue (#13388)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Error messages in chat cells are now clearly displayed with improved
formatting and dynamic height adjustment for better readability.
* Introduced the ability to remove specific chat cell view models from a
session.

* **Bug Fixes**
* Enhanced error handling to automatically remove invalid chat cell view
models when a message creation fails.

* **Other Improvements**
* Improved internal logic for handling message attachments and added
more detailed debug logging for the copilot response lifecycle.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-01 07:24:33 +00:00
DarkSky
5cbcf6f907 feat(server): add fallback model and baseurl in schema (#13375)
fix AI-398

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

## Summary by CodeRabbit

* **New Features**
* Added support for specifying fallback models for multiple AI
providers, enhancing reliability when primary models are unavailable.
* Providers can now fetch and update their list of available models
dynamically from external APIs.
* Configuration options expanded to allow custom base URLs for certain
providers.

* **Bug Fixes**
* Improved model selection logic to use fallback models if the requested
model is not available online.

* **Chores**
* Updated backend dependencies to include authentication support for
Google services.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-01 07:22:48 +00:00
Lakr
19790c1b9e feat: update MarkdownView render (#13387)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Introduced support for managing context blobs, including adding and
removing blobs within contexts.
  * Added the ability to generate and revoke user access tokens.
  * Implemented queries to list user access tokens and context blobs.

* **Improvements**
* Enhanced context object queries to include blobs and updated related
data structures.
* Updated type references for improved schema alignment and consistency.

* **Bug Fixes**
* Removed obsolete or incorrect error fields from certain context and
document queries.

* **Chores**
  * Upgraded the MarkdownView dependency to version 3.4.1.
  * Updated internal type names for better clarity and maintainability.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-01 04:38:10 +00:00
L-Sun
916887e9dc fix(editor): virtual keyboard closes unexpectedly when backspace is pressed after a block (#13386)
Close
[AF-2764](https://linear.app/affine-design/issue/AF-2764/移动端没法删除图片和其他非文本block)

#### PR Dependency Tree


* **PR #13386** 👈

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

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

## Summary by CodeRabbit

* **New Features**
* Improved virtual keyboard handling on mobile devices to prevent
unexpected keyboard closure during certain editing actions.
* Added new signals for keyboard height and safe area, enhancing UI
responsiveness and adaptability to keyboard state.

* **Refactor**
* Streamlined keyboard toolbar logic for more reliable panel height
calculation and smoother panel open/close transitions.
* Simplified and modernized the approach to toolbar visibility and input
mode restoration.

* **Style**
* Updated keyboard toolbar and panel styling for better positioning and
layout consistency across devices.

* **Bug Fixes**
* Fixed an issue where the virtual keyboard could be incorrectly
reported as visible when a physical keyboard is connected.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-01 04:27:45 +00:00
Wu Yue
3c9fe48c6c fix(core): ai chat scrolldown indicator (#13382)
Close [AI-401](https://linear.app/affine-design/issue/AI-401)

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

* **Refactor**
* Improved scrolling behavior in the AI chat messages panel by making
the entire panel the scroll container, resulting in more consistent
scroll handling.
* Adjusted the position of the down-indicator for better visibility
during scrolling.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-01 03:56:12 +00:00
德布劳外 · 贾贵
a088874c41 feat(core): selected context ui (#13379)
<img width="1133" height="982" alt="截屏2025-07-31 17 56 24"
src="https://github.com/user-attachments/assets/5f2d577b-5b25-44ed-896a-17fe212de0f8"
/>
<img width="1151" height="643" alt="截屏2025-07-31 17 55 32"
src="https://github.com/user-attachments/assets/b2320023-ab75-4455-9c24-d133fda1b7e1"
/>

> CLOSE AF-2771 AF-2772 AF-2778

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

* **New Features**
* Added support for sending detailed object information (JSON snapshot
and markdown) to AI when using "Continue with AI", enhancing AI's
context awareness.
* Introduced a new chip type for selected context attachments in the AI
chat interface, allowing users to manage and view detailed context
fragments.
* Added feature flags to enable or disable sending detailed context
objects to AI and to require journal confirmation.
* New settings and localization for the "Send detailed object
information to AI" feature.

* **Improvements**
* Enhanced chat input and composer to handle context processing states
and prevent sending messages while context is being processed.
* Improved context management with batch addition and removal of context
blobs.

* **Bug Fixes**
* Fixed UI rendering to properly display and manage new selected context
chips.

* **Documentation**
* Updated localization and settings to reflect new experimental AI
features.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-01 03:39:38 +00:00
L-Sun
4e1f047cf2 refactor(editor): always show keyboard toolbar in mobile (#13384)
Close
[AF-2756](https://linear.app/affine-design/issue/AF-2756/激活输入区的时候,展示toolbar,适配不弹虚拟键盘的场景,比如实体键盘)

#### PR Dependency Tree


* **PR #13384** 👈

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

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

* **New Features**
* Improved virtual keyboard handling by introducing static keyboard
height and app tab safe area tracking for more consistent toolbar
behavior.

* **Bug Fixes**
* Enhanced keyboard visibility detection on Android and iOS, especially
when a physical keyboard is connected.

* **Refactor**
* Simplified and streamlined keyboard toolbar logic, including delayed
panel closing and refined height calculations.
* Removed unused or redundant toolbar closing methods and position
management logic.

* **Style**
* Updated toolbar and panel styles for better positioning and layout
consistency.
  * Adjusted and removed certain mobile-specific padding styles.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


#### PR Dependency Tree


* **PR #13384** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)
2025-08-01 01:58:19 +00:00
Cats Juice
cd29028311 feat(core): center peek doc in chat semantic/keyword search result (#13380)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added the ability to preview documents directly from AI chat search
results using a new document peek view.
* Search result items in AI chat are now clickable, allowing for quick
document previews without leaving the chat interface.

* **Style**
* Updated clickable item styles in search results for improved visual
feedback and consistency.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-01 01:57:28 +00:00
德布劳外 · 贾贵
2990a96ec9 refactor(core): ai menu grouping & text (#13376)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Refactor**
* Reorganized and renamed AI action groups for improved clarity, now
categorizing actions by content type (text, code, image) and function
(edit, draft, review, generate).
* Split broad groups into more specific ones, such as "review image,"
"review code," and "review text."
* Updated group and action names for consistency (e.g., "Continue with
AI" is now "Continue in AI Chat").
* **Documentation**
  * Updated descriptions to reflect new group and action names.

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

> CLOSE AF-2777 AF-2776
2025-07-31 14:32:55 +00:00
DarkSky
4833539eb3 fix(server): get blob from correct storage (#13374)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Improved permission checks when adding context blobs to ensure only
authorized users can perform this action.

* **Refactor**
* Streamlined background processing of blob embeddings by removing
user-specific parameters and simplifying job handling.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-31 09:56:43 +00:00
DarkSky
61fa3ef6f6 feat(server): add fallback smtp config (#13377)
fix AF-2749

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

## Summary by CodeRabbit

* **New Features**
* Added support for configuring a fallback SMTP server for outgoing
emails.
* Introduced the ability to specify email domains that will always use
the fallback SMTP server.
* Enhanced email sending to automatically route messages to the
appropriate SMTP server based on recipient domain.

* **Documentation**
* Updated configuration options and descriptions in the admin interface
to reflect new fallback SMTP settings.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-31 09:56:30 +00:00
德布劳外 · 贾贵
77950cfc1b feat(core): extract md & snapshot & attachments from selected (#13312)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## Summary by CodeRabbit

* **New Features**
* Enhanced extraction of selected content in the editor to include
document snapshots, markdown summaries, and attachments for both
edgeless and page modes.
* Attachments related to selected content are now available in chat and
input contexts, providing additional metadata.
* Added utility to identify and retrieve selected attachments in editor
content.

* **Bug Fixes**
* Improved consistency in attachment retrieval when extracting selected
content.

* **Chores**
* Updated dependencies and workspace references to include new block
suite components.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

> CLOSE AF-2770
2025-07-31 09:53:09 +00:00
Wu Yue
826afc209e refactor(core): simplify ai test cases (#13378)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Tests**
  * Updated test cases to use a new test asset describing AFFiNE.
* Adjusted assertions to check for "AFFiNE" in results instead of
previous keywords.
* Separated and refined the "Continue writing" test for clearer
validation.
  * Improved assertion messages for clarity.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-31 09:52:28 +00:00
Cats Juice
75cc9b432b feat(core): open external link in web search result (#13362)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Search results now include clickable links that open in a new tab when
available, improving navigation from AI-generated results.

* **Style**
* Enhanced visual feedback for linked search results, including updated
cursor and hover effects for better user experience.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Wu Yue <akumatus@gmail.com>
2025-07-31 08:09:11 +00:00
Wu Yue
dfce0116b6 fix(core): remove network search button on ask ai input (#13373)
Close [AI-395](https://linear.app/affine-design/issue/AI-395)

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

## Summary by CodeRabbit

* **Refactor**
* Removed the network search feature and its related UI elements from
the AI input panel. The input panel now only includes the input textarea
and send button.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-31 06:43:32 +00:00
Yii
8d889fc3c7 feat(server): basic mcp server (#13298)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Introduced a new endpoint for MCP (Model Context Protocol) server
interaction under `/api/workspaces/:workspaceId/mcp`, enabling advanced
document reading and search capabilities within workspaces.
* Added support for semantic and keyword search tools, as well as
document reading through the MCP server, with user access control and
input validation.

* **Improvements**
* Enhanced metadata handling in semantic search results for improved
clarity.
* Streamlined internal imports and refactored utility functions for
better maintainability.

* **Chores**
  * Added a new SDK dependency to the backend server package.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-31 06:12:50 +00:00
Yii
49e8f339d4 feat(server): support access token (#13372)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Introduced user access tokens, enabling users to generate, list, and
revoke personal access tokens via the GraphQL API.
* Added GraphQL mutations and queries for managing access tokens,
including token creation (with optional expiration), listing, and
revocation.
* Implemented authentication support for private API endpoints using
access tokens in addition to session cookies.

* **Bug Fixes**
  * None.

* **Tests**
* Added comprehensive tests for access token creation, listing,
revocation, expiration handling, and authentication using tokens.

* **Chores**
* Updated backend models, schema, and database migrations to support
access token functionality.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-31 05:55:10 +00:00
DarkSky
feb42e34be feat(server): attachment embedding (#13348)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added support for managing "blobs" in Copilot context, including
adding and removing blobs via new GraphQL mutations and UI fields.
* Introduced tracking and querying of blob embeddings within workspaces,
enabling search and similarity matching for blob content.
* Extended Copilot context and workspace APIs, schema, and UI to display
and manage blobs alongside existing documents and files.

* **Bug Fixes**
* Updated context and embedding status logic to handle blobs, ensuring
accurate status reporting and embedding management.

* **Tests**
* Added and updated test cases and snapshots to cover blob embedding
insertion, matching, and removal scenarios.

* **Documentation**
* Updated GraphQL schema and TypeScript types to reflect new
blob-related fields and mutations.

* **Chores**
* Refactored and cleaned up code to support new blob entity and
embedding logic, including renaming and updating internal methods and
types.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-30 22:07:28 +00:00
DarkSky
b6a5bc052e chore(server): down scale service (#13367)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Chores**
* Reduced the number of deployment replicas for web, graphql, sync,
renderer, and doc components across all build types (stable, beta,
canary).

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-30 09:16:13 +00:00
Hwang
1ce4cc6560 feat(server): enhance chat prompt with motivational content (#13360)
Don't hold back. Give it your all.

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

## Summary by CodeRabbit

* **New Features**
* Enhanced AI assistant responses with a more encouraging system
message: "Don't hold back. Give it your all."

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-30 07:32:35 +00:00
德布劳外 · 贾贵
7c1a9957b3 fix(core): falky translate e2e (#13363)
> CLOSE AF-2774

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

## Summary by CodeRabbit

* **Tests**
* Updated translation tests to use Simplified Chinese instead of German
as the target language.
* Adjusted expected results in assertions to match Chinese characters
"苹果" instead of the German word "Apfel" across relevant test cases.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-30 06:52:04 +00:00
Wu Yue
603f2a1e5a fix(core): ai message resending (#13359)
Close [AI-395](https://linear.app/affine-design/issue/AI-395)

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

* **Bug Fixes**
* Improved chat stability by resetting chat action signals after
processing to prevent repeated triggers.

* **New Features**
* Added end-to-end tests for new chat session creation and chat pinning
functionality to enhance reliability.

* **Enhancements**
* Enhanced chat toolbar with test identifiers and pinned state
attributes for better accessibility and testing.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: fengmk2 <fengmk2@gmail.com>
2025-07-30 06:44:44 +00:00
德布劳外 · 贾贵
b61807d005 fix(core): ai chat with text e2e falky (#13361)
> CLOSE AF-2773

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

## Summary by CodeRabbit

* **Tests**
* Updated AI chat translation tests to use Simplified Chinese instead of
German, adjusting expected results and assertions accordingly.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-30 04:20:49 +00:00
Wu Yue
69e23e6a42 fix(core): fallback to default icon if image icon load error (#13349)
Close [AI-286](https://linear.app/affine-design/issue/AI-286)

<img width="586" height="208" alt="截屏2025-07-29 18 23 52"
src="https://github.com/user-attachments/assets/15eadb38-8cb9-4418-8f13-de7b1a3a3beb"
/>


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

## Summary by CodeRabbit

* **New Features**
* Enhanced image icon handling with a fallback display if an icon image
fails to load.

* **Style**
* Unified and improved styling for icons to ensure a consistent
appearance across result and footer sections.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-30 02:24:28 +00:00
Wu Yue
f7a094053e feat(core): add ai workspace all docs switch (#13345)
Close [AI-397](https://linear.app/affine-design/issue/AI-397)

<img width="272" height="186" alt="截屏2025-07-29 11 54 20"
src="https://github.com/user-attachments/assets/e171fb57-66cf-4244-894d-c27b18cbe83a"
/>


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

* **New Features**
* Introduced an AI tools configuration service, allowing users to
customize AI tool usage (e.g., workspace search, reading docs) in chat
and AI features.
* Added a toggle in chat preferences for enabling or disabling
workspace-wide document search.
* AI chat components now respect user-configured tool settings across
chat, retry, and playground scenarios.

* **Improvements**
* Enhanced chat and AI interfaces to propagate and honor user tool
configuration throughout the frontend and backend.
* Made draft and tool configuration services optional and safely handled
their absence in chat components.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-30 02:10:39 +00:00
L-Sun
091bac1047 fix(editor): add comment entire to inner toolbar (#13304)
Close
[BS-3624](https://linear.app/affine-design/issue/BS-3624/page模式单选图片的时候希望有comment-按钮)




#### PR Dependency Tree


* **PR #13304** 👈

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

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

* **New Features**
* Added a comment button to the image and surface reference block
toolbars for easier commenting.

* **Refactor**
* Simplified array flattening operations across multiple components and
utilities by replacing `.map(...).flat()` with `.flatMap(...)`,
improving code readability and maintainability.

* **Bug Fixes**
* Improved comment creation logic to allow adding comments even when
selections exist.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-29 13:21:56 +08:00
dependabot[bot]
bd161c54b2 chore: bump form-data from 4.0.2 to 4.0.4 (#13342)
Bumps [form-data](https://github.com/form-data/form-data) from 4.0.2 to
4.0.4.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/form-data/form-data/releases">form-data's
releases</a>.</em></p>
<blockquote>
<h2>v4.0.4</h2>
<h2><a
href="https://github.com/form-data/form-data/compare/v4.0.3...v4.0.4">v4.0.4</a>
- 2025-07-16</h2>
<h3>Commits</h3>
<ul>
<li>[meta] add <code>auto-changelog</code> <a
href="811f68282f"><code>811f682</code></a></li>
<li>[Tests] handle predict-v8-randomness failures in node &lt; 17 and
node &gt; 23 <a
href="1d11a76434"><code>1d11a76</code></a></li>
<li>[Fix] Switch to using <code>crypto</code> random for boundary values
<a
href="3d1723080e"><code>3d17230</code></a></li>
<li>[Tests] fix linting errors <a
href="5e340800b5"><code>5e34080</code></a></li>
<li>[meta] actually ensure the readme backup isn’t published <a
href="316c82ba93"><code>316c82b</code></a></li>
<li>[Dev Deps] update <code>@ljharb/eslint-config</code> <a
href="58c25d7640"><code>58c25d7</code></a></li>
<li>[meta] fix readme capitalization <a
href="2300ca1959"><code>2300ca1</code></a></li>
</ul>
<h2>v4.0.3</h2>
<h2><a
href="https://github.com/form-data/form-data/compare/v4.0.2...v4.0.3">v4.0.3</a>
- 2025-06-05</h2>
<h3>Fixed</h3>
<ul>
<li>[Fix] <code>append</code>: avoid a crash on nullish values <a
href="https://redirect.github.com/form-data/form-data/issues/577"><code>[#577](https://github.com/form-data/form-data/issues/577)</code></a></li>
</ul>
<h3>Commits</h3>
<ul>
<li>[eslint] use a shared config <a
href="426ba9ac44"><code>426ba9a</code></a></li>
<li>[eslint] fix some spacing issues <a
href="20941917f0"><code>2094191</code></a></li>
<li>[Refactor] use <code>hasown</code> <a
href="81ab41b46f"><code>81ab41b</code></a></li>
<li>[Fix] validate boundary type in <code>setBoundary()</code> method <a
href="8d8e469309"><code>8d8e469</code></a></li>
<li>[Tests] add tests to check the behavior of <code>getBoundary</code>
with non-strings <a
href="837b8a1f75"><code>837b8a1</code></a></li>
<li>[Dev Deps] remove unused deps <a
href="870e4e6659"><code>870e4e6</code></a></li>
<li>[meta] remove local commit hooks <a
href="e6e83ccb54"><code>e6e83cc</code></a></li>
<li>[Dev Deps] update <code>eslint</code> <a
href="4066fd6f65"><code>4066fd6</code></a></li>
<li>[meta] fix scripts to use prepublishOnly <a
href="c4bbb13c0e"><code>c4bbb13</code></a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/form-data/form-data/blob/master/CHANGELOG.md">form-data's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/form-data/form-data/compare/v4.0.3...v4.0.4">v4.0.4</a>
- 2025-07-16</h2>
<h3>Commits</h3>
<ul>
<li>[meta] add <code>auto-changelog</code> <a
href="811f68282f"><code>811f682</code></a></li>
<li>[Tests] handle predict-v8-randomness failures in node &lt; 17 and
node &gt; 23 <a
href="1d11a76434"><code>1d11a76</code></a></li>
<li>[Fix] Switch to using <code>crypto</code> random for boundary values
<a
href="3d1723080e"><code>3d17230</code></a></li>
<li>[Tests] fix linting errors <a
href="5e340800b5"><code>5e34080</code></a></li>
<li>[meta] actually ensure the readme backup isn’t published <a
href="316c82ba93"><code>316c82b</code></a></li>
<li>[Dev Deps] update <code>@ljharb/eslint-config</code> <a
href="58c25d7640"><code>58c25d7</code></a></li>
<li>[meta] fix readme capitalization <a
href="2300ca1959"><code>2300ca1</code></a></li>
</ul>
<h2><a
href="https://github.com/form-data/form-data/compare/v4.0.2...v4.0.3">v4.0.3</a>
- 2025-06-05</h2>
<h3>Fixed</h3>
<ul>
<li>[Fix] <code>append</code>: avoid a crash on nullish values <a
href="https://redirect.github.com/form-data/form-data/issues/577"><code>[#577](https://github.com/form-data/form-data/issues/577)</code></a></li>
</ul>
<h3>Commits</h3>
<ul>
<li>[eslint] use a shared config <a
href="426ba9ac44"><code>426ba9a</code></a></li>
<li>[eslint] fix some spacing issues <a
href="20941917f0"><code>2094191</code></a></li>
<li>[Refactor] use <code>hasown</code> <a
href="81ab41b46f"><code>81ab41b</code></a></li>
<li>[Fix] validate boundary type in <code>setBoundary()</code> method <a
href="8d8e469309"><code>8d8e469</code></a></li>
<li>[Tests] add tests to check the behavior of <code>getBoundary</code>
with non-strings <a
href="837b8a1f75"><code>837b8a1</code></a></li>
<li>[Dev Deps] remove unused deps <a
href="870e4e6659"><code>870e4e6</code></a></li>
<li>[meta] remove local commit hooks <a
href="e6e83ccb54"><code>e6e83cc</code></a></li>
<li>[Dev Deps] update <code>eslint</code> <a
href="4066fd6f65"><code>4066fd6</code></a></li>
<li>[meta] fix scripts to use prepublishOnly <a
href="c4bbb13c0e"><code>c4bbb13</code></a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="41996f5ac7"><code>41996f5</code></a>
v4.0.4</li>
<li><a
href="316c82ba93"><code>316c82b</code></a>
[meta] actually ensure the readme backup isn’t published</li>
<li><a
href="2300ca1959"><code>2300ca1</code></a>
[meta] fix readme capitalization</li>
<li><a
href="811f68282f"><code>811f682</code></a>
[meta] add <code>auto-changelog</code></li>
<li><a
href="5e340800b5"><code>5e34080</code></a>
[Tests] fix linting errors</li>
<li><a
href="1d11a76434"><code>1d11a76</code></a>
[Tests] handle predict-v8-randomness failures in node &lt; 17 and node
&gt; 23</li>
<li><a
href="58c25d7640"><code>58c25d7</code></a>
[Dev Deps] update <code>@ljharb/eslint-config</code></li>
<li><a
href="3d1723080e"><code>3d17230</code></a>
[Fix] Switch to using <code>crypto</code> random for boundary
values</li>
<li><a
href="d8d67dc8ac"><code>d8d67dc</code></a>
v4.0.3</li>
<li><a
href="e6e83ccb54"><code>e6e83cc</code></a>
[meta] remove local commit hooks</li>
<li>Additional commits viewable in <a
href="https://github.com/form-data/form-data/compare/v4.0.2...v4.0.4">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=form-data&package-manager=npm_and_yarn&previous-version=4.0.2&new-version=4.0.4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/toeverything/AFFiNE/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-29 09:52:46 +08:00
DarkSky
61d2382643 chore(server): improve citation in chat (#13267)
fix AI-357

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

* **New Features**
* Improved prompt handling to conditionally include document fragments
based on the presence of documents in user queries.

* **Refactor**
* Updated system prompts to focus solely on document fragments, removing
references to file fragments for a more streamlined user experience.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-28 15:22:08 +00:00
Lakr
4586e4a18f feat: adopt new backend api for attachment (#13336)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Introduced a new query for applying document updates using AI,
allowing merged markdown responses.
* Added support for an optional file upload field when creating chat
messages.

* **Improvements**
* Enhanced recent Copilot sessions query with pagination by adding an
offset parameter.
* Refined attachment handling in chat responses to better distinguish
between single and multiple file uploads, improving reliability.

* **Bug Fixes**
  * Minor update to error handling for clearer messaging.

* **Chores**
* Cleaned up and updated iOS project configuration files for improved
build consistency.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-28 07:23:03 +00:00
Wu Yue
30c42fc51b fix(core): add document content params for section edit tool (#13334)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Section editing now uses the full document context to ensure edits are
consistent with the overall tone, style, and structure.
* Cleaner output for edited sections, with internal markdown comments
removed.

* **Improvements**
* Enhanced instructions and descriptions for section editing, providing
clearer guidance and examples for users.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-28 05:36:39 +00:00
DarkSky
627771948f feat: paged query for outdated embedding cleanup (#13335)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Refactor**
* Improved the workspace cleanup process for trashed document embeddings
to use a more efficient, incremental batching approach, resulting in
better performance and reliability for large numbers of workspaces. No
visible changes to user interface or functionality.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-28 05:26:51 +00:00
DarkSky
0e3691e54e feat: add cache for tokenizer (#13333)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Performance Improvements**
* Improved the efficiency of token encoder retrieval, resulting in
faster response times when working with supported models.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-28 03:50:39 +00:00
DarkSky
8fd0d5c1e8 chore: update cert timestamp (#13300)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Chores**
* Updated the timestamp server URL used in the Windows Signer workflow
for code signing.

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

Co-authored-by: fengmk2 <fengmk2@gmail.com>
2025-07-28 02:53:45 +00:00
Peng Xiao
13763e80bb fix(core): nav sidebar should have default bg (#13265)
fix AF-2724

#### PR Dependency Tree


* **PR #13265** 👈

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

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

## Summary by CodeRabbit

* **Style**
* Updated sidebar background color to apply in additional display
scenarios, ensuring a more consistent appearance across different modes.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-28 02:43:38 +00:00
Yii
6a1b53dd11 fix(core): do not create first app if local workspace disabled (#13289)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Prevented creation of initial app data when local workspace
functionality is disabled, ensuring correct behavior based on user
settings.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-28 02:42:02 +00:00
EYHN
9899fad000 feat(editor): put current user in first on database user select (#13320) 2025-07-27 07:53:17 +00:00
EYHN
be55442f38 feat(core): remove empty workspace (#13317)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added the ability to remove an empty workspace directly from the
workspace card when you are the owner.
* Workspace cards now display a "Remove" button for eligible workspaces.
* **Improvements**
* Workspace information now indicates if a workspace is empty, improving
clarity for users.
* **Bug Fixes**
* Enhanced accuracy in displaying workspace status by updating how
workspace profile data is handled.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-25 10:26:55 +00:00
EYHN
1dd4bbbaba feat(core): cache navigation collapsed state (#13315)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Refactor**
* Collapsible section state in navigation panels is now managed using a
unified path-based approach, enabling more consistent and centralized
control across desktop and mobile interfaces.
* The collapsed/expanded state of navigation sections and nodes is now
persistently tracked using hierarchical paths, improving reliability
across sessions and devices.
* Internal state management is streamlined, with local state replaced by
a shared service, resulting in more predictable navigation behavior.

* **Chores**
* Removed obsolete types and legacy section management logic for
improved maintainability.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-25 10:19:21 +00:00
EYHN
7409940cc6 feat(core): add context menu to card view (#13258)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added a context menu to document cards, allowing additional actions
when enabled.

* **Improvements**
* The context menu is now conditionally enabled based on live data,
ensuring it only appears when relevant.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-25 10:07:17 +00:00
Wu Yue
0d43350afd feat(core): add section edit tool (#13313)
Close [AI-396](https://linear.app/affine-design/issue/AI-396)

<img width="798" height="294" alt="截屏2025-07-25 11 30 32"
src="https://github.com/user-attachments/assets/6366dab2-688b-470b-8b24-29a2d50a38c9"
/>



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

## Summary by CodeRabbit

* **New Features**
* Introduced a "Section Edit" AI tool for expert editing of specific
markdown sections based on user instructions, preserving formatting and
style.
* Added a new interface and UI component for section editing, allowing
users to view, copy, insert, or save edited content directly from chat
interactions.

* **Improvements**
* Enhanced AI chat and tool rendering to support and display section
editing results.
* Updated chat input handling for improved draft management and message
sending order.

* **Other Changes**
* Registered the new section editing tool in the system for seamless
integration.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-25 09:02:52 +00:00
renovate[bot]
ff9a4f4322 chore: bump up nestjs (#13288)
This PR contains the following updates:

| Package | Change | Age | Confidence |
|---|---|---|---|
|
[@nestjs-cls/transactional-adapter-prisma](https://papooch.github.io/nestjs-cls/)
([source](https://redirect.github.com/Papooch/nestjs-cls)) | [`1.2.24`
->
`1.3.0`](https://renovatebot.com/diffs/npm/@nestjs-cls%2ftransactional-adapter-prisma/1.2.24/1.3.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@nestjs-cls%2ftransactional-adapter-prisma/1.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@nestjs-cls%2ftransactional-adapter-prisma/1.2.24/1.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
| [@nestjs/common](https://nestjs.com)
([source](https://redirect.github.com/nestjs/nest/tree/HEAD/packages/common))
| [`11.1.3` ->
`11.1.5`](https://renovatebot.com/diffs/npm/@nestjs%2fcommon/11.1.3/11.1.5)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@nestjs%2fcommon/11.1.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@nestjs%2fcommon/11.1.3/11.1.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
| [@nestjs/core](https://nestjs.com)
([source](https://redirect.github.com/nestjs/nest/tree/HEAD/packages/core))
| [`11.1.3` ->
`11.1.5`](https://renovatebot.com/diffs/npm/@nestjs%2fcore/11.1.3/11.1.5)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@nestjs%2fcore/11.1.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@nestjs%2fcore/11.1.3/11.1.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
| [@nestjs/platform-express](https://nestjs.com)
([source](https://redirect.github.com/nestjs/nest/tree/HEAD/packages/platform-express))
| [`11.1.3` ->
`11.1.5`](https://renovatebot.com/diffs/npm/@nestjs%2fplatform-express/11.1.3/11.1.5)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@nestjs%2fplatform-express/11.1.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@nestjs%2fplatform-express/11.1.3/11.1.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
| [@nestjs/platform-socket.io](https://nestjs.com)
([source](https://redirect.github.com/nestjs/nest/tree/HEAD/packages/platform-socket.io))
| [`11.1.3` ->
`11.1.5`](https://renovatebot.com/diffs/npm/@nestjs%2fplatform-socket.io/11.1.3/11.1.5)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@nestjs%2fplatform-socket.io/11.1.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@nestjs%2fplatform-socket.io/11.1.3/11.1.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
| [@nestjs/websockets](https://redirect.github.com/nestjs/nest)
([source](https://redirect.github.com/nestjs/nest/tree/HEAD/packages/websockets))
| [`11.1.3` ->
`11.1.5`](https://renovatebot.com/diffs/npm/@nestjs%2fwebsockets/11.1.3/11.1.5)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@nestjs%2fwebsockets/11.1.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@nestjs%2fwebsockets/11.1.3/11.1.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>Papooch/nestjs-cls
(@&#8203;nestjs-cls/transactional-adapter-prisma)</summary>

###
[`v1.3.0`](https://redirect.github.com/Papooch/nestjs-cls/releases/tag/%40nestjs-cls/transactional-adapter-prisma%401.3.0)

[Compare
Source](https://redirect.github.com/Papooch/nestjs-cls/compare/@nestjs-cls/transactional-adapter-prisma@1.2.24...@nestjs-cls/transactional-adapter-prisma@1.3.0)

##### Features

- **transactional-adapter-prisma**: add support for nested transactions
([c49c766](https://redirect.github.com/Papooch/nestjs-cls/commits/c49c766))
- **transactional-adapter-prisma**: add support for nested transactions
([#&#8203;353](https://redirect.github.com/Papooch/nestjs-cls/issues/353))
([c49c766](https://redirect.github.com/Papooch/nestjs-cls/commits/c49c766))

</details>

<details>
<summary>nestjs/nest (@&#8203;nestjs/common)</summary>

###
[`v11.1.5`](https://redirect.github.com/nestjs/nest/compare/v11.1.4...9bb0560e79743cc0bd2ce198c65e21332200c3ad)

[Compare
Source](https://redirect.github.com/nestjs/nest/compare/v11.1.4...v11.1.5)

###
[`v11.1.4`](https://redirect.github.com/nestjs/nest/compare/v11.1.3...1f101ac8b0a5bb5b97a7caf6634fcea8d65196e0)

[Compare
Source](https://redirect.github.com/nestjs/nest/compare/v11.1.3...v11.1.4)

</details>

<details>
<summary>nestjs/nest (@&#8203;nestjs/core)</summary>

###
[`v11.1.5`](https://redirect.github.com/nestjs/nest/compare/v11.1.4...9bb0560e79743cc0bd2ce198c65e21332200c3ad)

[Compare
Source](https://redirect.github.com/nestjs/nest/compare/v11.1.4...v11.1.5)

###
[`v11.1.4`](https://redirect.github.com/nestjs/nest/compare/v11.1.3...1f101ac8b0a5bb5b97a7caf6634fcea8d65196e0)

[Compare
Source](https://redirect.github.com/nestjs/nest/compare/v11.1.3...v11.1.4)

</details>

<details>
<summary>nestjs/nest (@&#8203;nestjs/platform-express)</summary>

###
[`v11.1.5`](https://redirect.github.com/nestjs/nest/compare/v11.1.4...9bb0560e79743cc0bd2ce198c65e21332200c3ad)

[Compare
Source](https://redirect.github.com/nestjs/nest/compare/v11.1.4...v11.1.5)

###
[`v11.1.4`](https://redirect.github.com/nestjs/nest/compare/v11.1.3...1f101ac8b0a5bb5b97a7caf6634fcea8d65196e0)

[Compare
Source](https://redirect.github.com/nestjs/nest/compare/v11.1.3...v11.1.4)

</details>

<details>
<summary>nestjs/nest (@&#8203;nestjs/platform-socket.io)</summary>

###
[`v11.1.5`](https://redirect.github.com/nestjs/nest/releases/tag/v11.1.5)

[Compare
Source](https://redirect.github.com/nestjs/nest/compare/v11.1.4...v11.1.5)

#### v11.1.5 (2025-07-18)

##### Dependencies

- `platform-express`
- [#&#8203;15425](https://redirect.github.com/nestjs/nest/pull/15425)
chore(deps): bump multer from 2.0.1 to 2.0.2 in
/packages/platform-express
([@&#8203;dependabot\[bot\]](https://redirect.github.com/apps/dependabot))

###
[`v11.1.4`](https://redirect.github.com/nestjs/nest/releases/tag/v11.1.4)

[Compare
Source](https://redirect.github.com/nestjs/nest/compare/v11.1.3...v11.1.4)

##### v11.1.4 (2025-07-16)

##### Bug fixes

- `platform-fastify`
- [#&#8203;15385](https://redirect.github.com/nestjs/nest/pull/15385)
fix(testing): auto-init fastify adapter for middleware registration
([@&#8203;mag123c](https://redirect.github.com/mag123c))
- `core`, `testing`
- [#&#8203;15405](https://redirect.github.com/nestjs/nest/pull/15405)
fix(core): fix race condition in class dependency resolution
([@&#8203;hajekjiri](https://redirect.github.com/hajekjiri))
- `core`
- [#&#8203;15333](https://redirect.github.com/nestjs/nest/pull/15333)
fix(core): Make flattenRoutePath return a valid module
([@&#8203;gentunian](https://redirect.github.com/gentunian))
- `microservices`
- [#&#8203;15305](https://redirect.github.com/nestjs/nest/pull/15305)
fix(microservices): Revisit RMQ pattern matching with wildcards
([@&#8203;getlarge](https://redirect.github.com/getlarge))
- [#&#8203;15250](https://redirect.github.com/nestjs/nest/pull/15250)
fix(constants): update RMQ\_DEFAULT\_QUEUE to an empty string
([@&#8203;EeeasyCode](https://redirect.github.com/EeeasyCode))

##### Enhancements

- `platform-fastify`
- [#&#8203;14789](https://redirect.github.com/nestjs/nest/pull/14789)
feat(fastify): add decorator for custom schema
([@&#8203;piotrfrankowski](https://redirect.github.com/piotrfrankowski))
- `common`, `core`, `microservices`, `platform-express`,
`platform-fastify`, `websockets`
- [#&#8203;15386](https://redirect.github.com/nestjs/nest/pull/15386)
feat: enhance introspection capabilities
([@&#8203;kamilmysliwiec](https://redirect.github.com/kamilmysliwiec))
- `core`
- [#&#8203;15374](https://redirect.github.com/nestjs/nest/pull/15374)
feat: supporting fine async storage control
([@&#8203;Farenheith](https://redirect.github.com/Farenheith))

##### Dependencies

- `platform-ws`
- [#&#8203;15350](https://redirect.github.com/nestjs/nest/pull/15350)
chore(deps): bump ws from 8.18.2 to 8.18.3
([@&#8203;dependabot\[bot\]](https://redirect.github.com/apps/dependabot))
- `platform-fastify`
- [#&#8203;15278](https://redirect.github.com/nestjs/nest/pull/15278)
chore(deps): bump fastify from 5.3.3 to 5.4.0
([@&#8203;dependabot\[bot\]](https://redirect.github.com/apps/dependabot))

##### Committers: 11

- Alexey Filippov
([@&#8203;SocketSomeone](https://redirect.github.com/SocketSomeone))
- EFIcats ([@&#8203;ext4cats](https://redirect.github.com/ext4cats))
- Edouard Maleix
([@&#8203;getlarge](https://redirect.github.com/getlarge))
- JaeHo Jang ([@&#8203;mag123c](https://redirect.github.com/mag123c))
- Jiri Hajek
([@&#8203;hajekjiri](https://redirect.github.com/hajekjiri))
- Kamil Mysliwiec
([@&#8203;kamilmysliwiec](https://redirect.github.com/kamilmysliwiec))
- Khan / 이창민
([@&#8203;EeeasyCode](https://redirect.github.com/EeeasyCode))
- Peter F.
([@&#8203;piotrfrankowski](https://redirect.github.com/piotrfrankowski))
- Sebastian ([@&#8203;gentunian](https://redirect.github.com/gentunian))
- Thiago Oliveira Santos
([@&#8203;Farenheith](https://redirect.github.com/Farenheith))
- jochong ([@&#8203;jochongs](https://redirect.github.com/jochongs))

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-22 06:05:37 +00:00
renovate[bot]
8cfaee8232 chore: bump up on-headers version to v1.1.0 [SECURITY] (#13260)
This PR contains the following updates:

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

### GitHub Vulnerability Alerts

####
[CVE-2025-7339](https://redirect.github.com/jshttp/on-headers/security/advisories/GHSA-76c9-3jph-rj3q)

### Impact

A bug in on-headers versions `< 1.1.0` may result in response headers
being inadvertently modified when an array is passed to
`response.writeHead()`

### Patches

Users should upgrade to `1.1.0`

### Workarounds

Uses are encouraged to upgrade to `1.1.0`, but this issue can be worked
around by passing an object to `response.writeHead()` rather than an
array.

---

### Release Notes

<details>
<summary>jshttp/on-headers (on-headers)</summary>

###
[`v1.1.0`](https://redirect.github.com/jshttp/on-headers/blob/HEAD/HISTORY.md#110--2025-07-17)

[Compare
Source](https://redirect.github.com/jshttp/on-headers/compare/v1.0.2...v1.1.0)

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

- Fix [CVE-2025-7339](https://www.cve.org/CVERecord?id=CVE-2025-7339)
([GHSA-76c9-3jph-rj3q](https://redirect.github.com/jshttp/on-headers/security/advisories/GHSA-76c9-3jph-rj3q))

</details>

---

### Configuration

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

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

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

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

---

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

---

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

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-22 02:15:02 +00:00
389 changed files with 10221 additions and 2590 deletions

View File

@@ -18,11 +18,19 @@ services:
ports:
- 6379:6379
mailhog:
image: mailhog/mailhog:latest
# https://mailpit.axllent.org/docs/install/docker/
mailpit:
image: axllent/mailpit:latest
ports:
- 1025:1025
- 8025:8025
environment:
MP_MAX_MESSAGES: 5000
MP_DATABASE: /data/mailpit.db
MP_SMTP_AUTH_ACCEPT_ANY: 1
MP_SMTP_AUTH_ALLOW_INSECURE: 1
volumes:
- mailpit_data:/data
# https://manual.manticoresearch.com/Starting_the_server/Docker
manticoresearch:
@@ -87,4 +95,5 @@ networks:
volumes:
postgres_data:
manticoresearch_data:
mailpit_data:
elasticsearch_data:

View File

@@ -219,6 +219,41 @@
"type": "boolean",
"description": "Whether ignore email server's TSL certification verification. Enable it for self-signed certificates.\n@default false\n@environment `MAILER_IGNORE_TLS`",
"default": false
},
"fallbackDomains": {
"type": "array",
"description": "The emails from these domains are always sent using the fallback SMTP server.\n@default []",
"default": []
},
"fallbackSMTP.host": {
"type": "string",
"description": "Host of the email server (e.g. smtp.gmail.com)\n@default \"\"",
"default": ""
},
"fallbackSMTP.port": {
"type": "number",
"description": "Port of the email server (they commonly are 25, 465 or 587)\n@default 465",
"default": 465
},
"fallbackSMTP.username": {
"type": "string",
"description": "Username used to authenticate the email server\n@default \"\"",
"default": ""
},
"fallbackSMTP.password": {
"type": "string",
"description": "Password used to authenticate the email server\n@default \"\"",
"default": ""
},
"fallbackSMTP.sender": {
"type": "string",
"description": "Sender of all the emails (e.g. \"AFFiNE Team <noreply@affine.pro>\")\n@default \"\"",
"default": ""
},
"fallbackSMTP.ignoreTLS": {
"type": "boolean",
"description": "Whether ignore email server's TSL certification verification. Enable it for self-signed certificates.\n@default false",
"default": false
}
}
},
@@ -629,14 +664,34 @@
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether to enable the copilot plugin.\n@default false",
"description": "Whether to enable the copilot plugin. <br> Document: <a href=\"https://docs.affine.pro/self-host-affine/administer/ai\" target=\"_blank\">https://docs.affine.pro/self-host-affine/administer/ai</a>\n@default false",
"default": false
},
"scenarios": {
"type": "object",
"description": "Use custom models in scenarios and override default settings.\n@default {\"override_enabled\":false,\"scenarios\":{\"audio_transcribing\":\"gemini-2.5-flash\",\"chat\":\"claude-sonnet-4@20250514\",\"embedding\":\"gemini-embedding-001\",\"image\":\"gpt-image-1\",\"rerank\":\"gpt-4.1\",\"coding\":\"claude-sonnet-4@20250514\",\"complex_text_generation\":\"gpt-4o-2024-08-06\",\"quick_decision_making\":\"gpt-5-mini\",\"quick_text_generation\":\"gemini-2.5-flash\",\"polish_and_summarize\":\"gemini-2.5-flash\"}}",
"default": {
"override_enabled": false,
"scenarios": {
"audio_transcribing": "gemini-2.5-flash",
"chat": "claude-sonnet-4@20250514",
"embedding": "gemini-embedding-001",
"image": "gpt-image-1",
"rerank": "gpt-4.1",
"coding": "claude-sonnet-4@20250514",
"complex_text_generation": "gpt-4o-2024-08-06",
"quick_decision_making": "gpt-5-mini",
"quick_text_generation": "gemini-2.5-flash",
"polish_and_summarize": "gemini-2.5-flash"
}
}
},
"providers.openai": {
"type": "object",
"description": "The config for the openai provider.\n@default {\"apiKey\":\"\"}\n@link https://github.com/openai/openai-node",
"description": "The config for the openai provider.\n@default {\"apiKey\":\"\",\"baseURL\":\"https://api.openai.com/v1\"}\n@link https://github.com/openai/openai-node",
"default": {
"apiKey": ""
"apiKey": "",
"baseURL": "https://api.openai.com/v1"
}
},
"providers.fal": {
@@ -648,9 +703,10 @@
},
"providers.gemini": {
"type": "object",
"description": "The config for the gemini provider.\n@default {\"apiKey\":\"\"}",
"description": "The config for the gemini provider.\n@default {\"apiKey\":\"\",\"baseURL\":\"https://generativelanguage.googleapis.com/v1beta\"}",
"default": {
"apiKey": ""
"apiKey": "",
"baseURL": "https://generativelanguage.googleapis.com/v1beta"
}
},
"providers.geminiVertex": {
@@ -697,9 +753,10 @@
},
"providers.anthropic": {
"type": "object",
"description": "The config for the anthropic provider.\n@default {\"apiKey\":\"\"}",
"description": "The config for the anthropic provider.\n@default {\"apiKey\":\"\",\"baseURL\":\"https://api.anthropic.com/v1\"}",
"default": {
"apiKey": ""
"apiKey": "",
"baseURL": "https://api.anthropic.com/v1"
}
},
"providers.anthropicVertex": {

View File

@@ -29,25 +29,25 @@ const isInternal = buildType === 'internal';
const replicaConfig = {
stable: {
web: 3,
graphql: Number(process.env.PRODUCTION_GRAPHQL_REPLICA) || 3,
sync: Number(process.env.PRODUCTION_SYNC_REPLICA) || 3,
renderer: Number(process.env.PRODUCTION_RENDERER_REPLICA) || 3,
doc: Number(process.env.PRODUCTION_DOC_REPLICA) || 3,
web: 2,
graphql: Number(process.env.PRODUCTION_GRAPHQL_REPLICA) || 2,
sync: Number(process.env.PRODUCTION_SYNC_REPLICA) || 2,
renderer: Number(process.env.PRODUCTION_RENDERER_REPLICA) || 2,
doc: Number(process.env.PRODUCTION_DOC_REPLICA) || 2,
},
beta: {
web: 2,
graphql: Number(process.env.BETA_GRAPHQL_REPLICA) || 2,
sync: Number(process.env.BETA_SYNC_REPLICA) || 2,
renderer: Number(process.env.BETA_RENDERER_REPLICA) || 2,
doc: Number(process.env.BETA_DOC_REPLICA) || 2,
web: 1,
graphql: Number(process.env.BETA_GRAPHQL_REPLICA) || 1,
sync: Number(process.env.BETA_SYNC_REPLICA) || 1,
renderer: Number(process.env.BETA_RENDERER_REPLICA) || 1,
doc: Number(process.env.BETA_DOC_REPLICA) || 1,
},
canary: {
web: 2,
graphql: 2,
sync: 2,
renderer: 2,
doc: 2,
web: 1,
graphql: 1,
sync: 1,
renderer: 1,
doc: 1,
},
};

View File

@@ -1,4 +1,4 @@
replicaCount: 3
replicaCount: 2
enabled: false
database:
connectionName: ""
@@ -33,8 +33,11 @@ service:
resources:
limits:
memory: "4Gi"
cpu: "2"
memory: "1Gi"
cpu: "1"
requests:
memory: "512Mi"
cpu: "100m"
volumes: []
volumeMounts: []

View File

@@ -74,7 +74,7 @@ jobs:
name: Wait for approval
with:
secret: ${{ secrets.GITHUB_TOKEN }}
approvers: forehalo,fengmk2
approvers: forehalo,fengmk2,darkskygit
minimum-approvals: 1
fail-on-denial: true
issue-title: Please confirm to release docker image
@@ -84,7 +84,7 @@ jobs:
Tag: ghcr.io/toeverything/affine:${{ needs.prepare.outputs.BUILD_TYPE }}
> comment with "approve", "approved", "lgtm", "yes" to approve
> comment with "deny", "deny", "no" to deny
> comment with "deny", "denied", "no" to deny
- name: Login to GitHub Container Registry
uses: docker/login-action@v3

View File

@@ -29,7 +29,7 @@ jobs:
shell: cmd
run: |
cd ${{ env.ARCHIVE_DIR }}/out
signtool sign /tr http://timestamp.sectigo.com /td sha256 /fd sha256 /a ${{ inputs.files }}
signtool sign /tr http://timestamp.globalsign.com/tsa/r6advanced1 /td sha256 /fd sha256 /a ${{ inputs.files }}
- name: zip file
shell: cmd
run: |

View File

@@ -2,6 +2,7 @@
**/node_modules
.yarn
.github/helm
.git
.vscode
.yarnrc.yml
.docker

24
Cargo.lock generated
View File

@@ -93,7 +93,7 @@ dependencies = [
"symphonia",
"thiserror 2.0.12",
"uuid",
"windows 0.61.1",
"windows 0.61.3",
"windows-core 0.61.2",
]
@@ -1691,7 +1691,7 @@ dependencies = [
"libc",
"log",
"rustversion",
"windows 0.61.1",
"windows 0.61.3",
]
[[package]]
@@ -2284,7 +2284,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
dependencies = [
"cfg-if",
"windows-targets 0.48.5",
"windows-targets 0.52.6",
]
[[package]]
@@ -4732,9 +4732,9 @@ dependencies = [
[[package]]
name = "tree-sitter"
version = "0.25.5"
version = "0.25.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac5fff5c47490dfdf473b5228039bfacad9d765d9b6939d26bf7cc064c1c7822"
checksum = "6d7b8994f367f16e6fa14b5aebbcb350de5d7cbea82dc5b00ae997dd71680dd2"
dependencies = [
"cc",
"regex",
@@ -4842,9 +4842,9 @@ dependencies = [
[[package]]
name = "tree-sitter-scala"
version = "0.23.4"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efde5e68b4736e9eac17bfa296c6f104a26bffab363b365eb898c40a63c15d2f"
checksum = "7516aeb3d1f40ede8e3045b163e86993b3434514dd06c34c0b75e782d9a0b251"
dependencies = [
"cc",
"tree-sitter-language",
@@ -5334,7 +5334,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.48.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -5365,9 +5365,9 @@ dependencies = [
[[package]]
name = "windows"
version = "0.61.1"
version = "0.61.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419"
checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
dependencies = [
"windows-collections",
"windows-core 0.61.2",
@@ -5477,9 +5477,9 @@ dependencies = [
[[package]]
name = "windows-link"
version = "0.1.1"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
[[package]]
name = "windows-numerics"

View File

@@ -93,7 +93,7 @@ tree-sitter-javascript = { version = "0.23" }
tree-sitter-kotlin-ng = { version = "1.1" }
tree-sitter-python = { version = "0.23" }
tree-sitter-rust = { version = "0.24" }
tree-sitter-scala = { version = "0.23" }
tree-sitter-scala = { version = "0.24" }
tree-sitter-typescript = { version = "0.23" }
uniffi = "0.29"
url = { version = "2.5" }

View File

@@ -164,8 +164,10 @@ export class DatabaseBlockDataSource extends DataSourceBase {
readonly$: ReadonlySignal<boolean> = computed(() => {
return (
this._model.store.readonly ||
// TODO(@L-Sun): use block level readonly
IS_MOBILE
(IS_MOBILE &&
!this._model.store.provider
.get(FeatureFlagService)
.getFlag('enable_mobile_database_editing'))
);
});

View File

@@ -13,6 +13,7 @@ import {
BlockElementCommentManager,
CommentProviderIdentifier,
DocModeProvider,
FeatureFlagService,
NotificationProvider,
type TelemetryEventMap,
TelemetryProvider,
@@ -34,6 +35,7 @@ import {
uniMap,
} from '@blocksuite/data-view';
import { widgetPresets } from '@blocksuite/data-view/widget-presets';
import { IS_MOBILE } from '@blocksuite/global/env';
import { Rect } from '@blocksuite/global/gfx';
import {
CommentIcon,
@@ -48,6 +50,7 @@ import { autoUpdate } from '@floating-ui/dom';
import { computed, signal } from '@preact/signals-core';
import { html, nothing } from 'lit';
import { repeat } from 'lit/directives/repeat.js';
import { styleMap } from 'lit/directives/style-map.js';
import { popSideDetail } from './components/layout.js';
import { DatabaseConfigExtension } from './config.js';
@@ -349,6 +352,7 @@ export class DatabaseBlockComponent extends CaptionedBlockComponent<DatabaseBloc
this.setAttribute(RANGE_SYNC_EXCLUDE_ATTR, 'true');
this.classList.add(databaseBlockStyles);
this.listenFullWidthChange();
this.handleMobileEditing();
}
listenFullWidthChange() {
@@ -364,6 +368,41 @@ export class DatabaseBlockComponent extends CaptionedBlockComponent<DatabaseBloc
})
);
}
handleMobileEditing() {
if (!IS_MOBILE) return;
let notifyClosed = true;
const handler = () => {
if (
!this.std
.get(FeatureFlagService)
.getFlag('enable_mobile_database_editing')
) {
const notification = this.std.getOptional(NotificationProvider);
if (notification && notifyClosed) {
notifyClosed = false;
notification.notify({
title: html`<div
style=${styleMap({
whiteSpace: 'wrap',
})}
>
Mobile database editing is not supported yet. You can open it in
experimental features, or edit it in desktop mode.
</div>`,
accent: 'warning',
onClose: () => {
notifyClosed = true;
},
});
}
}
};
this.disposables.addFromEvent(this, 'click', handler);
}
private readonly dataViewRootLogic = lazy(
() =>
new DataViewRootUILogic({

View File

@@ -1,6 +1,7 @@
import { ImageBlockModel } from '@blocksuite/affine-model';
import {
ActionPlacement,
blockCommentToolbarButton,
type ToolbarModuleConfig,
ToolbarModuleExtension,
} from '@blocksuite/affine-shared/services';
@@ -49,6 +50,10 @@ const builtinToolbarConfig = {
});
},
},
{
id: 'c.comment',
...blockCommentToolbarButton,
},
{
placement: ActionPlacement.More,
id: 'a.clipboard',

View File

@@ -24,6 +24,7 @@ import {
getPrevContentBlock,
matchModels,
} from '@blocksuite/affine-shared/utils';
import { IS_ANDROID, IS_MOBILE } from '@blocksuite/global/env';
import { BlockSelection, type EditorHost } from '@blocksuite/std';
import type { BlockModel, Text } from '@blocksuite/store';
@@ -78,6 +79,28 @@ export function mergeWithPrev(editorHost: EditorHost, model: BlockModel) {
index: lengthBeforeJoin,
length: 0,
}).catch(console.error);
// due to some IME like Microsoft Swift IME on Android will reset range after join text,
// for example:
//
// $ZERO_WIDTH_FOR_EMPTY_LINE <--- p1
// |aaa <--- p2
//
// after pressing backspace, during beforeinput event, the native range is (p1, 1) -> (p2, 0)
// and after browser and IME handle the event, the native range is (p1, 1) -> (p1, 1)
//
// a|aa <--- p1
//
// so we need to set range again after join text.
if (IS_ANDROID) {
setTimeout(() => {
asyncSetInlineRange(editorHost.std, prevBlock, {
index: lengthBeforeJoin,
length: 0,
}).catch(console.error);
});
}
return true;
}
@@ -91,10 +114,17 @@ export function mergeWithPrev(editorHost: EditorHost, model: BlockModel) {
...EMBED_BLOCK_MODEL_LIST,
])
) {
const selection = editorHost.selection.create(BlockSelection, {
blockId: prevBlock.id,
});
editorHost.selection.setGroup('note', [selection]);
// due to create a block selection will clear text selection, which lead
// the virtual keyboard to be auto closed on mobile. This behavior breaks
// the user experience.
if (!IS_MOBILE) {
const selection = editorHost.selection.create(BlockSelection, {
blockId: prevBlock.id,
});
editorHost.selection.setGroup('note', [selection]);
} else {
doc.deleteBlock(prevBlock);
}
if (model.text?.length === 0) {
doc.deleteBlock(model, {

View File

@@ -634,9 +634,9 @@ export class EdgelessPageKeyboardManager extends PageKeyboardManager {
const movedElements = new Set([
...selectedElements,
...selectedElements
.map(el => (isGfxGroupCompatibleModel(el) ? el.descendantElements : []))
.flat(),
...selectedElements.flatMap(el =>
isGfxGroupCompatibleModel(el) ? el.descendantElements : []
),
]);
movedElements.forEach(element => {

View File

@@ -4,6 +4,6 @@ export * from './clipboard/command';
export * from './edgeless-root-block.js';
export { EdgelessRootService } from './edgeless-root-service.js';
export * from './utils/clipboard-utils.js';
export { sortEdgelessElements } from './utils/clone-utils.js';
export { getElementProps, sortEdgelessElements } from './utils/clone-utils.js';
export { isCanvasElement } from './utils/query.js';
export { EDGELESS_BLOCK_CHILD_PADDING } from '@blocksuite/affine-shared/consts';

View File

@@ -5,6 +5,7 @@ import {
} from '@blocksuite/affine-shared/commands';
import {
ActionPlacement,
blockCommentToolbarButton,
type ToolbarModuleConfig,
} from '@blocksuite/affine-shared/services';
import { CaptionIcon, CopyIcon, DeleteIcon } from '@blocksuite/icons/lit';
@@ -61,6 +62,10 @@ export const surfaceRefToolbarModuleConfig: ToolbarModuleConfig = {
surfaceRefBlock.captionElement.show();
},
},
{
id: 'e.comment',
...blockCommentToolbarButton,
},
{
id: 'a.clipboard',
placement: ActionPlacement.More,

View File

@@ -11,7 +11,7 @@ import {
getBoundWithRotation,
intersects,
} from '@blocksuite/global/gfx';
import type { BlockStdScope } from '@blocksuite/std';
import { type BlockStdScope, SurfaceSelection } from '@blocksuite/std';
import type {
GfxCompatibleInterface,
GridManager,
@@ -298,7 +298,10 @@ export class DomRenderer {
viewportBounds,
zoom
);
Object.assign(domElement.style, geometricStyles);
const zIndexStyle = {
'z-index': this.layerManager.getZIndex(elementModel),
};
Object.assign(domElement.style, geometricStyles, zIndexStyle);
Object.assign(domElement.style, PLACEHOLDER_RESET_STYLES);
// Clear classes specific to shapes, if applicable
@@ -335,7 +338,10 @@ export class DomRenderer {
zoom
);
const opacityStyle = getOpacity(elementModel);
Object.assign(domElement.style, geometricStyles, opacityStyle);
const zIndexStyle = {
'z-index': this.layerManager.getZIndex(elementModel),
};
Object.assign(domElement.style, geometricStyles, opacityStyle, zIndexStyle);
this._renderElement(elementModel, domElement);
}
@@ -384,6 +390,36 @@ export class DomRenderer {
this.refresh();
})
);
// Workaround for the group rendering reactive update when selection changed
let lastSet = new Set<string>();
this._disposables.add(
this.std.selection.filter$(SurfaceSelection).subscribe(selections => {
const groupRelatedSelection = new Set(
selections.flatMap(s =>
s.elements.flatMap(e => {
const element = surfaceModel.getElementById(e);
if (
element &&
(element.type === 'group' || element.groups.length !== 0)
) {
return [element.id, ...element.groups.map(g => g.id)];
}
return [];
})
)
);
if (lastSet.symmetricDifference(groupRelatedSelection).size !== 0) {
lastSet.union(groupRelatedSelection).forEach(g => {
this._markElementDirty(g, UpdateType.ELEMENT_UPDATED);
});
this.refresh();
}
lastSet = groupRelatedSelection;
})
);
}
addOverlay = (overlay: Overlay) => {

View File

@@ -65,7 +65,7 @@ export abstract class DataViewUILogicBase<
return handler(context);
});
}
setSelection(selection?: Selection): void {
setSelection(selection?: Selection) {
this.root.setSelection(selection);
}

View File

@@ -73,7 +73,9 @@ export class MobileKanbanCell extends SignalWatcher(
if (this.view.readonly$.value) {
return;
}
const setSelection = this.kanbanViewLogic.setSelection;
const setSelection = this.kanbanViewLogic.setSelection.bind(
this.kanbanViewLogic
);
const viewId = this.kanbanViewLogic.view.id;
if (setSelection && viewId) {
if (editing && this.cell?.beforeEnterEditMode() === false) {
@@ -101,12 +103,12 @@ export class MobileKanbanCell extends SignalWatcher(
this.disposables.add(
effect(() => {
const isEditing = this.isSelectionEditing$.value;
if (isEditing) {
if (isEditing && !this.isEditing$.peek()) {
this.isEditing$.value = true;
requestAnimationFrame(() => {
this._cell.value?.afterEnterEditingMode();
});
} else {
} else if (!isEditing && this.isEditing$.peek()) {
this._cell.value?.beforeExitEditingMode();
this.isEditing$.value = false;
}

View File

@@ -86,6 +86,9 @@ export class MobileKanbanViewUILogic extends DataViewUILogicBase<
}
renderAddGroup = () => {
if (this.readonly) {
return;
}
const addGroup = this.groupManager.addGroup;
if (!addGroup) {
return;

View File

@@ -68,7 +68,9 @@ export class MobileTableCell extends SignalWatcher(
if (this.view.readonly$.value) {
return;
}
const setSelection = this.tableViewLogic.setSelection;
const setSelection = this.tableViewLogic.setSelection.bind(
this.tableViewLogic
);
const viewId = this.tableViewLogic.view.id;
if (setSelection && viewId) {
if (editing && this.cell?.beforeEnterEditMode() === false) {
@@ -103,13 +105,13 @@ export class MobileTableCell extends SignalWatcher(
this.disposables.add(
effect(() => {
const isEditing = this.isSelectionEditing$.value;
if (isEditing) {
if (isEditing && !this.isEditing$.peek()) {
this.isEditing$.value = true;
const cell = this._cell.value;
requestAnimationFrame(() => {
cell?.afterEnterEditingMode();
});
} else {
} else if (!isEditing && this.isEditing$.peek()) {
this._cell.value?.beforeExitEditingMode();
this.isEditing$.value = false;
}

View File

@@ -5,12 +5,6 @@ export const mobileTableViewWrapper = css({
position: 'relative',
width: '100%',
paddingBottom: '4px',
/**
* Disable horizontal scrolling to prevent crashes on iOS Safari
* See https://github.com/toeverything/AFFiNE/pull/12203
* and https://github.com/toeverything/blocksuite/pull/8784
*/
overflowX: 'hidden',
overflowY: 'hidden',
});

View File

@@ -88,6 +88,9 @@ export class FilterBar extends SignalWatcher(ShadowlessElement) {
};
private readonly addFilter = (e: MouseEvent) => {
if (this.dataViewLogic.root.config.dataSource.readonly$.peek()) {
return;
}
const element = popupTargetFromElement(e.target as HTMLElement);
popCreateFilter(element, {
vars: this.vars,

View File

@@ -68,5 +68,5 @@ export function getHeadingBlocksFromDoc(
ignoreEmpty = false
) {
const notes = getNotesFromStore(store, modes);
return notes.map(note => getHeadingBlocksFromNote(note, ignoreEmpty)).flat();
return notes.flatMap(note => getHeadingBlocksFromNote(note, ignoreEmpty));
}

View File

@@ -1,7 +1,7 @@
export * from './adapter';
export * from './brush-tool';
export * from './element-renderer';
export * from './eraser-tool';
export * from './highlighter-tool';
export * from './renderer';
export * from './toolbar/configs';
export * from './toolbar/senior-tool';

View File

@@ -0,0 +1,69 @@
import {
DomElementRendererExtension,
type DomRenderer,
} from '@blocksuite/affine-block-surface';
import type { BrushElementModel } from '@blocksuite/affine-model';
import { DefaultTheme } from '@blocksuite/affine-model';
export const BrushDomRendererExtension = DomElementRendererExtension(
'brush',
(
model: BrushElementModel,
domElement: HTMLElement,
renderer: DomRenderer
) => {
const { zoom } = renderer.viewport;
const [, , w, h] = model.deserializedXYWH;
// Early return if invalid dimensions
if (w <= 0 || h <= 0) {
return;
}
// Early return if no commands
if (!model.commands) {
return;
}
// Clear previous content
domElement.innerHTML = '';
// Get color value
const color = renderer.getColorValue(model.color, DefaultTheme.black, true);
// Create SVG element
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.style.position = 'absolute';
svg.style.left = '0';
svg.style.top = '0';
svg.style.width = `${w * zoom}px`;
svg.style.height = `${h * zoom}px`;
svg.style.overflow = 'visible';
svg.style.pointerEvents = 'none';
svg.setAttribute('viewBox', `0 0 ${w} ${h}`);
// Apply rotation transform
if (model.rotate !== 0) {
svg.style.transform = `rotate(${model.rotate}deg)`;
svg.style.transformOrigin = 'center';
}
// Create path element for the brush stroke
const pathElement = document.createElementNS(
'http://www.w3.org/2000/svg',
'path'
);
pathElement.setAttribute('d', model.commands);
pathElement.setAttribute('fill', color);
pathElement.setAttribute('stroke', 'none');
svg.append(pathElement);
domElement.replaceChildren(svg);
// Set element size and position
domElement.style.width = `${w * zoom}px`;
domElement.style.height = `${h * zoom}px`;
domElement.style.overflow = 'visible';
domElement.style.pointerEvents = 'none';
}
);

View File

@@ -0,0 +1,73 @@
import {
DomElementRendererExtension,
type DomRenderer,
} from '@blocksuite/affine-block-surface';
import type { HighlighterElementModel } from '@blocksuite/affine-model';
import { DefaultTheme } from '@blocksuite/affine-model';
export const HighlighterDomRendererExtension = DomElementRendererExtension(
'highlighter',
(
model: HighlighterElementModel,
domElement: HTMLElement,
renderer: DomRenderer
) => {
const { zoom } = renderer.viewport;
const [, , w, h] = model.deserializedXYWH;
// Early return if invalid dimensions
if (w <= 0 || h <= 0) {
return;
}
// Early return if no commands
if (!model.commands) {
return;
}
// Clear previous content
domElement.innerHTML = '';
// Get color value
const color = renderer.getColorValue(
model.color,
DefaultTheme.hightlighterColor,
true
);
// Create SVG element
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.style.position = 'absolute';
svg.style.left = '0';
svg.style.top = '0';
svg.style.width = `${w * zoom}px`;
svg.style.height = `${h * zoom}px`;
svg.style.overflow = 'visible';
svg.style.pointerEvents = 'none';
svg.setAttribute('viewBox', `0 0 ${w} ${h}`);
// Apply rotation transform
if (model.rotate !== 0) {
svg.style.transform = `rotate(${model.rotate}deg)`;
svg.style.transformOrigin = 'center';
}
// Create path element for the highlighter stroke
const pathElement = document.createElementNS(
'http://www.w3.org/2000/svg',
'path'
);
pathElement.setAttribute('d', model.commands);
pathElement.setAttribute('fill', color);
pathElement.setAttribute('stroke', 'none');
svg.append(pathElement);
domElement.replaceChildren(svg);
// Set element size and position
domElement.style.width = `${w * zoom}px`;
domElement.style.height = `${h * zoom}px`;
domElement.style.overflow = 'visible';
domElement.style.pointerEvents = 'none';
}
);

View File

@@ -0,0 +1,2 @@
export { BrushDomRendererExtension } from './brush';
export { HighlighterDomRendererExtension } from './highlighter';

View File

@@ -0,0 +1,2 @@
export { BrushElementRendererExtension } from './brush';
export { HighlighterElementRendererExtension } from './highlighter';

View File

@@ -0,0 +1,2 @@
export * from './dom';
export * from './element';

View File

@@ -5,9 +5,14 @@ import {
import { BrushTool } from './brush-tool';
import { effects } from './effects';
import { BrushElementRendererExtension } from './element-renderer';
import { EraserTool } from './eraser-tool';
import { HighlighterTool } from './highlighter-tool';
import {
BrushDomRendererExtension,
BrushElementRendererExtension,
HighlighterDomRendererExtension,
HighlighterElementRendererExtension,
} from './renderer';
import {
brushToolbarExtension,
highlighterToolbarExtension,
@@ -30,6 +35,9 @@ export class BrushViewExtension extends ViewExtensionProvider {
context.register(HighlighterTool);
context.register(BrushElementRendererExtension);
context.register(BrushDomRendererExtension);
context.register(HighlighterElementRendererExtension);
context.register(HighlighterDomRendererExtension);
context.register(brushToolbarExtension);
context.register(highlighterToolbarExtension);

View File

@@ -1,11 +0,0 @@
import { DomElementRendererExtension } from '@blocksuite/affine-block-surface';
import { connectorDomRenderer } from './connector-dom/index.js';
/**
* Extension to register the DOM-based renderer for 'connector' elements.
*/
export const ConnectorDomRendererExtension = DomElementRendererExtension(
'connector',
connectorDomRenderer
);

View File

@@ -1,9 +1,8 @@
export * from './adapter';
export * from './connector-manager';
export * from './connector-tool';
export * from './element-renderer';
export { ConnectorDomRendererExtension } from './element-renderer/connector-dom';
export * from './element-transform';
export * from './renderer';
export * from './text';
export * from './toolbar/config';
export * from './toolbar/quick-tool';

View File

@@ -1,14 +1,18 @@
import type { DomRenderer } from '@blocksuite/affine-block-surface';
import {
DomElementRendererExtension,
type DomRenderer,
} from '@blocksuite/affine-block-surface';
import {
type ConnectorElementModel,
ConnectorMode,
DefaultTheme,
type LocalConnectorElementModel,
type PointStyle,
} from '@blocksuite/affine-model';
import { PointLocation, SVGPathBuilder } from '@blocksuite/global/gfx';
import { isConnectorWithLabel } from '../../connector-manager.js';
import { DEFAULT_ARROW_SIZE } from '../utils.js';
import { isConnectorWithLabel } from '../connector-manager';
import { DEFAULT_ARROW_SIZE } from './utils';
interface PathBounds {
minX: number;
@@ -221,8 +225,8 @@ function renderConnectorLabel(
* @param element - The HTMLElement to apply the connector's styles to.
* @param renderer - The main DOMRenderer instance, providing access to viewport and color utilities.
*/
export const connectorDomRenderer = (
model: ConnectorElementModel,
export const connectorBaseDomRenderer = (
model: ConnectorElementModel | LocalConnectorElementModel,
element: HTMLElement,
renderer: DomRenderer
): void => {
@@ -358,10 +362,21 @@ export const connectorDomRenderer = (
element.style.height = `${model.h * zoom}px`;
element.style.overflow = 'visible';
element.style.pointerEvents = 'none';
// Set z-index for layering
element.style.zIndex = renderer.layerManager.getZIndex(model).toString();
// Render label if present
renderConnectorLabel(model, element, renderer, zoom);
};
export const connectorDomRenderer = (
model: ConnectorElementModel,
element: HTMLElement,
renderer: DomRenderer
): void => {
connectorBaseDomRenderer(model, element, renderer);
renderConnectorLabel(model, element, renderer, renderer.viewport.zoom);
};
/**
* Extension to register the DOM-based renderer for 'connector' elements.
*/
export const ConnectorDomRendererExtension = DomElementRendererExtension(
'connector',
connectorDomRenderer
);

View File

@@ -25,7 +25,7 @@ import {
} from '@blocksuite/global/gfx';
import { deltaInsertsToChunks } from '@blocksuite/std/inline';
import { isConnectorWithLabel } from '../connector-manager.js';
import { isConnectorWithLabel } from '../connector-manager';
import {
DEFAULT_ARROW_SIZE,
getArrowOptions,
@@ -33,7 +33,7 @@ import {
renderCircle,
renderDiamond,
renderTriangle,
} from './utils.js';
} from './utils';
export const connector: ElementRenderer<
ConnectorElementModel | LocalConnectorElementModel

View File

@@ -0,0 +1,2 @@
export * from './dom-renderer';
export * from './element-renderer';

View File

@@ -6,9 +6,11 @@ import {
import { ConnectionOverlay } from './connector-manager';
import { ConnectorTool } from './connector-tool';
import { effects } from './effects';
import { ConnectorElementRendererExtension } from './element-renderer';
import { ConnectorDomRendererExtension } from './element-renderer/connector-dom';
import { ConnectorFilter } from './element-transform';
import {
ConnectorDomRendererExtension,
ConnectorElementRendererExtension,
} from './renderer';
import { connectorToolbarExtension } from './toolbar/config';
import { connectorQuickTool } from './toolbar/quick-tool';
import { ConnectorElementView, ConnectorInteraction } from './view/view';

View File

@@ -1,6 +1,6 @@
export * from './adapter';
export * from './command';
export * from './element-renderer';
export * from './element-view';
export * from './renderer';
export * from './text/text';
export * from './toolbar/config';

View File

@@ -0,0 +1,62 @@
import { DomElementRendererExtension } from '@blocksuite/affine-block-surface';
import { FontWeight, type GroupElementModel } from '@blocksuite/affine-model';
import {
GROUP_TITLE_FONT,
GROUP_TITLE_FONT_SIZE,
GROUP_TITLE_PADDING,
} from './consts';
import { titleRenderParams } from './utils';
export const GroupDomRendererExtension = DomElementRendererExtension(
'group',
(model: GroupElementModel, domElement, renderer) => {
const { zoom } = renderer.viewport;
const [, , w, h] = model.deserializedXYWH;
const renderParams = titleRenderParams(model, zoom);
model.externalXYWH = renderParams.titleBound.serialize();
domElement.innerHTML = '';
domElement.style.outlineColor = '';
domElement.style.outlineWidth = '';
domElement.style.outlineStyle = '';
const elements = renderer.provider.selectedElements?.() || [];
const renderTitle = () => {
const { text } = renderParams;
const titleElement = document.createElement('div');
titleElement.style.transform = `translate(0, -100%)`;
titleElement.style.fontFamily = GROUP_TITLE_FONT;
titleElement.style.fontWeight = `${FontWeight.Regular}`;
titleElement.style.fontStyle = 'normal';
titleElement.style.fontSize = `${GROUP_TITLE_FONT_SIZE}px`;
titleElement.style.color = renderer.getPropertyValue('--affine-blue');
titleElement.style.textAlign = 'left';
titleElement.style.padding = `${GROUP_TITLE_PADDING[0]}px ${GROUP_TITLE_PADDING[1]}px`;
titleElement.textContent = text;
domElement.replaceChildren(titleElement);
};
if (elements.includes(model.id)) {
if (model.showTitle) {
renderTitle();
} else {
domElement.style.outlineColor =
renderer.getPropertyValue('--affine-blue');
domElement.style.outlineWidth = '2px';
domElement.style.outlineStyle = 'solid';
}
} else if (model.childElements.some(child => elements.includes(child.id))) {
domElement.style.outlineColor = '#8FD1FF';
domElement.style.outlineWidth = '2px';
domElement.style.outlineStyle = 'solid';
}
domElement.style.width = `${w * zoom}px`;
domElement.style.height = `${h * zoom}px`;
domElement.style.overflow = 'visible';
domElement.style.pointerEvents = 'none';
}
);

View File

@@ -6,7 +6,7 @@ import {
import type { GroupElementModel } from '@blocksuite/affine-model';
import { Bound } from '@blocksuite/global/gfx';
import { titleRenderParams } from './utils.js';
import { titleRenderParams } from './utils';
export const group: ElementRenderer<GroupElementModel> = (
model,

View File

@@ -0,0 +1,2 @@
export * from './dom-renderer';
export * from './element-renderer';

View File

@@ -13,7 +13,7 @@ import {
GROUP_TITLE_FONT_SIZE,
GROUP_TITLE_OFFSET,
GROUP_TITLE_PADDING,
} from './consts.js';
} from './consts';
export function titleRenderParams(group: GroupElementModel, zoom: number) {
let text = group.title.toString().trim();

View File

@@ -21,7 +21,7 @@ import {
GROUP_TITLE_FONT_SIZE,
GROUP_TITLE_OFFSET,
GROUP_TITLE_PADDING,
} from '../element-renderer/consts';
} from '../renderer/consts';
export function mountGroupTitleEditor(
group: GroupElementModel,

View File

@@ -4,9 +4,12 @@ import {
} from '@blocksuite/affine-ext-loader';
import { effects } from './effects';
import { GroupElementRendererExtension } from './element-renderer';
import { GroupElementView, GroupInteraction } from './element-view';
import { GroupInteractionExtension } from './interaction-ext';
import {
GroupDomRendererExtension,
GroupElementRendererExtension,
} from './renderer';
import { groupToolbarExtension } from './toolbar/config';
export class GroupViewExtension extends ViewExtensionProvider {
@@ -20,6 +23,7 @@ export class GroupViewExtension extends ViewExtensionProvider {
override setup(context: ViewExtensionContext) {
super.setup(context);
context.register(GroupElementRendererExtension);
context.register(GroupDomRendererExtension);
context.register(GroupElementView);
if (this.isEdgeless(context.scope)) {
context.register(groupToolbarExtension);

View File

@@ -1,7 +1,7 @@
export * from './adapter';
export * from './element-renderer';
export * from './indicator-overlay';
export * from './interactivity';
export * from './renderer';
export * from './toolbar/config';
export * from './toolbar/senior-tool';
export * from './utils';

View File

@@ -0,0 +1,65 @@
import { DomElementRendererExtension } from '@blocksuite/affine-block-surface';
import {
connectorBaseDomRenderer,
ConnectorPathGenerator,
} from '@blocksuite/affine-gfx-connector';
import type {
MindmapElementModel,
MindmapNode,
} from '@blocksuite/affine-model';
import type { GfxModel } from '@blocksuite/std/gfx';
export const MindmapDomRendererExtension = DomElementRendererExtension(
'mindmap',
(model: MindmapElementModel, domElement, renderer) => {
const bound = model.elementBound;
const { zoom } = renderer.viewport;
// Set element size and position
domElement.style.width = `${bound.w * zoom}px`;
domElement.style.height = `${bound.h * zoom}px`;
domElement.style.overflow = 'visible';
domElement.style.pointerEvents = 'none';
const newChildren: HTMLDivElement[] = [];
const traverse = (node: MindmapNode) => {
const connectors = model.getConnectors(node);
if (!connectors) return;
connectors.reverse().forEach(result => {
const { connector, outdated } = result;
const elementGetter = (id: string) =>
model.surface.getElementById(id) ??
(model.surface.store.getModelById(id) as GfxModel);
if (outdated) {
ConnectorPathGenerator.updatePath(connector, null, elementGetter);
}
const connectorContainer = document.createElement('div');
connectorContainer.style.position = 'absolute';
connectorContainer.style.transformOrigin = 'top left';
const geometricStyles = {
left: `${(connector.x - bound.x) * zoom}px`,
top: `${(connector.y - bound.y) * zoom}px`,
};
const opacityStyle = { opacity: node.element.opacity };
Object.assign(connectorContainer.style, geometricStyles, opacityStyle);
connectorBaseDomRenderer(connector, connectorContainer, renderer);
newChildren.push(connectorContainer);
});
if (node.detail.collapsed) {
return;
} else {
node.children.forEach(traverse);
}
};
model.tree && traverse(model.tree);
domElement.replaceChildren(...newChildren);
}
);

View File

@@ -0,0 +1,2 @@
export * from './dom-renderer';
export * from './element-renderer';

View File

@@ -4,9 +4,12 @@ import {
} from '@blocksuite/affine-ext-loader';
import { effects } from './effects';
import { MindmapElementRendererExtension } from './element-renderer';
import { MindMapIndicatorOverlay } from './indicator-overlay';
import { MindMapDragExtension } from './interactivity';
import {
MindmapDomRendererExtension,
MindmapElementRendererExtension,
} from './renderer';
import {
mindmapToolbarExtension,
shapeMindmapToolbarExtension,
@@ -25,6 +28,7 @@ export class MindmapViewExtension extends ViewExtensionProvider {
override setup(context: ViewExtensionContext) {
super.setup(context);
context.register(MindmapElementRendererExtension);
context.register(MindmapDomRendererExtension);
context.register(mindMapSeniorTool);
context.register(mindmapToolbarExtension);
context.register(shapeMindmapToolbarExtension);

View File

@@ -1,4 +1,7 @@
import { EdgelessLegacySlotIdentifier } from '@blocksuite/affine-block-surface';
import {
DefaultTool,
EdgelessLegacySlotIdentifier,
} from '@blocksuite/affine-block-surface';
import { on } from '@blocksuite/affine-shared/utils';
import type { PointerEventState } from '@blocksuite/std';
import { BaseTool, MouseButton, type ToolOptions } from '@blocksuite/std/gfx';
@@ -64,12 +67,15 @@ export class PanTool extends BaseTool<PanToolOption> {
const { toolType, options: originalToolOptions } = currentTool;
const selectionToRestore = this.gfx.selection.surfaceSelections;
if (!toolType) return;
// restore to DefaultTool if previous tool is CopilotTool
if (toolType.toolName === 'copilot') {
this.controller.setTool(DefaultTool);
return;
}
let finalOptions: ToolOptions<BaseTool<any>> | undefined =
originalToolOptions;
const PRESENT_TOOL_NAME = 'frameNavigator';
if (toolType.toolName === PRESENT_TOOL_NAME) {
if (toolType.toolName === 'frameNavigator') {
// When restoring PresentTool (frameNavigator) after a temporary pan (e.g., via middle mouse button),
// set 'restoredAfterPan' to true. This allows PresentTool to avoid an unwanted viewport reset
// and maintain the panned position.
@@ -93,15 +99,17 @@ export class PanTool extends BaseTool<PanToolOption> {
});
}
this.controller.setTool(PanTool, {
panning: true,
requestAnimationFrame(() => {
this.controller.setTool(PanTool, {
panning: true,
});
});
const dispose = on(document, 'pointerup', evt => {
if (evt.button === MouseButton.MIDDLE) {
restoreToPrevious();
dispose();
}
dispose();
});
return false;

View File

@@ -1,2 +1 @@
export * from './highlighter';
export * from './shape';

View File

@@ -1,4 +1,5 @@
import type { DomRenderer } from '@blocksuite/affine-block-surface';
import { isRTL } from '@blocksuite/affine-gfx-text';
import type { ShapeElementModel } from '@blocksuite/affine-model';
import { DefaultTheme } from '@blocksuite/affine-model';
import { SVGShapeBuilder } from '@blocksuite/global/gfx';
@@ -99,6 +100,8 @@ export const shapeDomRenderer = (
const unscaledWidth = model.w;
const unscaledHeight = model.h;
const newChildren: Element[] = [];
const fillColor = renderer.getColorValue(
model.fillColor,
DefaultTheme.shapeFillColor,
@@ -170,8 +173,7 @@ export const shapeDomRenderer = (
}
svg.append(polygon);
// Replace existing children to avoid memory leaks
element.replaceChildren(svg);
newChildren.push(svg);
} else {
// Standard rendering for other shapes (e.g., rect, ellipse)
// innerHTML was already cleared by applyShapeSpecificStyles if necessary
@@ -179,9 +181,42 @@ export const shapeDomRenderer = (
applyBorderStyles(model, element, strokeColor, zoom); // Uses standard CSS border
}
applyTransformStyles(model, element);
if (model.textDisplay && model.text) {
const str = model.text.toString();
const textElement = document.createElement('div');
if (isRTL(str)) {
textElement.dir = 'rtl';
}
textElement.style.position = 'absolute';
textElement.style.inset = '0';
textElement.style.display = 'flex';
textElement.style.flexDirection = 'column';
textElement.style.justifyContent =
model.textVerticalAlign === 'center'
? 'center'
: model.textVerticalAlign === 'top'
? 'flex-start'
: 'flex-end';
textElement.style.whiteSpace = 'pre-wrap';
textElement.style.wordBreak = 'break-word';
textElement.style.textAlign = model.textAlign;
textElement.style.alignmentBaseline = 'alphabetic';
textElement.style.fontFamily = model.fontFamily;
textElement.style.fontSize = `${model.fontSize * zoom}px`;
textElement.style.fontWeight = model.fontWeight;
textElement.style.color = renderer.getColorValue(
model.color,
DefaultTheme.shapeTextColor,
true
);
textElement.textContent = str;
newChildren.push(textElement);
}
element.style.zIndex = renderer.layerManager.getZIndex(model).toString();
// Replace existing children to avoid memory leaks
element.replaceChildren(...newChildren);
applyTransformStyles(model, element);
manageClassNames(model, element);
applyShadowStyles(model, element, renderer);

View File

@@ -4,10 +4,7 @@ import {
} from '@blocksuite/affine-ext-loader';
import { effects } from './effects';
import {
HighlighterElementRendererExtension,
ShapeElementRendererExtension,
} from './element-renderer';
import { ShapeElementRendererExtension } from './element-renderer';
import { ShapeDomRendererExtension } from './element-renderer/shape-dom';
import { ShapeElementView, ShapeViewInteraction } from './element-view';
import { ShapeTool } from './shape-tool';
@@ -24,7 +21,6 @@ export class ShapeViewExtension extends ViewExtensionProvider {
override setup(context: ViewExtensionContext) {
super.setup(context);
if (this.isEdgeless(context.scope)) {
context.register(HighlighterElementRendererExtension);
context.register(ShapeElementRendererExtension);
context.register(ShapeDomRendererExtension);
context.register(ShapeElementView);

View File

@@ -103,54 +103,52 @@ export class InlineCommentManager extends LifeCycleWatcher {
id: CommentId,
selections: BaseSelection[]
) => {
const needCommentTexts = selections
.map(selection => {
if (!selection.is(TextSelection)) return [];
const [_, { selectedBlocks }] = this.std.command
.chain()
.pipe(getSelectedBlocksCommand, {
textSelection: selection,
})
.run();
const needCommentTexts = selections.flatMap(selection => {
if (!selection.is(TextSelection)) return [];
const [_, { selectedBlocks }] = this.std.command
.chain()
.pipe(getSelectedBlocksCommand, {
textSelection: selection,
})
.run();
if (!selectedBlocks) return [];
if (!selectedBlocks) return [];
type MakeRequired<T, K extends keyof T> = T & {
[key in K]: NonNullable<T[key]>;
};
type MakeRequired<T, K extends keyof T> = T & {
[key in K]: NonNullable<T[key]>;
};
return selectedBlocks
.map(
({ model }) =>
[model, getInlineEditorByModel(this.std, model)] as const
)
.filter(
(
pair
): pair is [MakeRequired<BlockModel, 'text'>, AffineInlineEditor] =>
!!pair[0].text && !!pair[1]
)
.map(([model, inlineEditor]) => {
let from: TextRangePoint;
let to: TextRangePoint | null;
if (model.id === selection.from.blockId) {
from = selection.from;
to = null;
} else if (model.id === selection.to?.blockId) {
from = selection.to;
to = null;
} else {
from = {
blockId: model.id,
index: 0,
length: model.text.yText.length,
};
to = null;
}
return [new TextSelection({ from, to }), inlineEditor] as const;
});
})
.flat();
return selectedBlocks
.map(
({ model }) =>
[model, getInlineEditorByModel(this.std, model)] as const
)
.filter(
(
pair
): pair is [MakeRequired<BlockModel, 'text'>, AffineInlineEditor] =>
!!pair[0].text && !!pair[1]
)
.map(([model, inlineEditor]) => {
let from: TextRangePoint;
let to: TextRangePoint | null;
if (model.id === selection.from.blockId) {
from = selection.from;
to = null;
} else if (model.id === selection.to?.blockId) {
from = selection.to;
to = null;
} else {
from = {
blockId: model.id,
index: 0,
length: model.text.yText.length,
};
to = null;
}
return [new TextSelection({ from, to }), inlineEditor] as const;
});
});
if (needCommentTexts.length === 0) return;

View File

@@ -150,6 +150,9 @@ export class AffineReference extends WithDisposable(ShadowlessElement) {
readonly open = (event?: Partial<DocLinkClickedEvent>) => {
if (!this.config.interactable) return;
if (event?.event?.button === 2) {
return;
}
this.std.getOptional(RefNodeSlotsProvider)?.docLinkClicked.next({
...this.referenceInfo,

View File

@@ -131,7 +131,7 @@ export class HighlighterElementModel extends GfxPrimitiveElementModel<Highlighte
instance['_local'].delete('commands');
})
@derive((lineWidth: number, instance: Instance) => {
const oldBound = instance.elementBound;
const oldBound = Bound.fromXYWH(instance.deserializedXYWH);
if (
lineWidth === instance.lineWidth ||

View File

@@ -64,7 +64,7 @@ export const blockCommentToolbarButton: Omit<ToolbarAction, 'id'> = {
// may be hover on a block or element, in this case
// the selection is empty, so we need to get the current model
if (model && selections.length === 0) {
if (model) {
if (model instanceof BlockModel) {
commentProvider.addComment([
new BlockSelection({

View File

@@ -15,6 +15,7 @@ export interface BlockSuiteFlags {
enable_shape_shadow_blur: boolean;
enable_mobile_keyboard_toolbar: boolean;
enable_mobile_linked_doc_menu: boolean;
enable_mobile_database_editing: boolean;
enable_block_meta: boolean;
enable_callout: boolean;
enable_edgeless_scribbled_style: boolean;
@@ -41,6 +42,7 @@ export class FeatureFlagService extends StoreExtension {
enable_mobile_keyboard_toolbar: false,
enable_mobile_linked_doc_menu: false,
enable_block_meta: true,
enable_mobile_database_editing: false,
enable_callout: false,
enable_edgeless_scribbled_style: false,
enable_table_virtual_scroll: false,

View File

@@ -5,6 +5,7 @@ import type { Signal } from '@preact/signals-core';
import type { AffineUserInfo } from './types';
export interface UserService {
currentUserInfo$: Signal<AffineUserInfo | null>;
userInfo$(id: string): Signal<AffineUserInfo | null>;
isLoading$(id: string): Signal<boolean>;
error$(id: string): Signal<string | null>; // user friendly error string

View File

@@ -4,6 +4,14 @@ import type { ReadonlySignal } from '@preact/signals-core';
export interface VirtualKeyboardProvider {
readonly visible$: ReadonlySignal<boolean>;
readonly height$: ReadonlySignal<number>;
/**
* The static height of the keyboard, it should record the last non-zero height of virtual keyboard
*/
readonly staticHeight$: ReadonlySignal<number>;
/**
* The safe area of the app tab, it will be used when the keyboard is open or closed
*/
readonly appTabSafeArea$: ReadonlySignal<string>;
}
export interface VirtualKeyboardProviderWithAction

View File

@@ -11,14 +11,12 @@ export function getSelectedRect(selected: GfxModel[]): DOMRect {
return new DOMRect();
}
const lockedElementsByFrame = selected
.map(selectable => {
if (selectable instanceof FrameBlockModel && selectable.isLocked()) {
return selectable.descendantElements;
}
return [];
})
.flat();
const lockedElementsByFrame = selected.flatMap(selectable => {
if (selectable instanceof FrameBlockModel && selectable.isLocked()) {
return selectable.descendantElements;
}
return [];
});
selected = [...new Set([...selected, ...lockedElementsByFrame])];

View File

@@ -114,6 +114,7 @@ export class PreviewHelper {
});
let width: number = 500;
// oxlint-disable-next-line no-unassigned-vars
let height;
const noteBlock = this.widget.host.querySelector('affine-note');

View File

@@ -168,10 +168,6 @@ export type KeyboardSubToolbarConfig = {
export type KeyboardToolbarContext = {
std: BlockStdScope;
rootComponent: BlockComponent;
/**
* Close tool bar, and blur the focus if blur is true, default is false
*/
closeToolbar: (blur?: boolean) => void;
/**
* Close current tool panel and show virtual keyboard
*/

View File

@@ -4,7 +4,7 @@ import {
requiredProperties,
ShadowlessElement,
} from '@blocksuite/std';
import { html, nothing, type PropertyValues } from 'lit';
import { html, nothing } from 'lit';
import { property } from 'lit/decorators.js';
import { repeat } from 'lit/directives/repeat.js';
@@ -71,22 +71,13 @@ export class AffineKeyboardToolPanel extends SignalWatcher(
.map(group => (typeof group === 'function' ? group(this.context) : group))
.filter((group): group is KeyboardToolPanelGroup => group !== null);
return repeat(
groups,
group => group.name,
group => this._renderGroup(group)
);
}
protected override willUpdate(changedProperties: PropertyValues<this>) {
if (changedProperties.has('height')) {
this.style.height = `${this.height}px`;
if (this.height === 0) {
this.style.padding = '0';
} else {
this.style.padding = '';
}
}
return html`<div class="affine-keyboard-tool-panel-container">
${repeat(
groups,
group => group.name,
group => this._renderGroup(group)
)}
</div>`;
}
@property({ attribute: false })
@@ -94,7 +85,4 @@ export class AffineKeyboardToolPanel extends SignalWatcher(
@property({ attribute: false })
accessor context!: KeyboardToolbarContext;
@property({ attribute: false })
accessor height = 0;
}

View File

@@ -8,7 +8,7 @@ import {
requiredProperties,
ShadowlessElement,
} from '@blocksuite/std';
import { effect, type Signal, signal, untracked } from '@preact/signals-core';
import { effect, type Signal, signal } from '@preact/signals-core';
import { html } from 'lit';
import { property } from 'lit/decorators.js';
import { repeat } from 'lit/directives/repeat.js';
@@ -22,7 +22,6 @@ import type {
KeyboardToolbarItem,
KeyboardToolPanelConfig,
} from './config';
import { PositionController } from './position-controller';
import { keyboardToolbarStyles } from './styles';
import {
isKeyboardSubToolBarConfig,
@@ -41,10 +40,7 @@ export class AffineKeyboardToolbar extends SignalWatcher(
) {
static override styles = keyboardToolbarStyles;
/** This field records the panel static height same as the virtual keyboard height */
panelHeight$ = signal(0);
positionController = new PositionController(this);
private readonly _expanded$ = signal(false);
get std() {
return this.rootComponent.std;
@@ -54,9 +50,31 @@ export class AffineKeyboardToolbar extends SignalWatcher(
return this._currentPanelIndex$.value !== -1;
}
private get panelHeight() {
return this._expanded$.value
? `${
this.keyboard.staticHeight$.value !== 0
? this.keyboard.staticHeight$.value
: 330
}px`
: this.keyboard.appTabSafeArea$.value;
}
/**
* Prevent flickering during keyboard opening
*/
private _resetPanelIndexTimeoutId: ReturnType<typeof setTimeout> | null =
null;
private readonly _closeToolPanel = () => {
this._currentPanelIndex$.value = -1;
if (!this.keyboard.visible$.peek()) this.keyboard.show();
if (this._resetPanelIndexTimeoutId) {
clearTimeout(this._resetPanelIndexTimeoutId);
this._resetPanelIndexTimeoutId = null;
}
this._resetPanelIndexTimeoutId = setTimeout(() => {
this._currentPanelIndex$.value = -1;
}, 100);
};
private readonly _currentPanelIndex$ = signal(-1);
@@ -83,6 +101,10 @@ export class AffineKeyboardToolbar extends SignalWatcher(
if (this._currentPanelIndex$.value === index) {
this._closeToolPanel();
} else {
if (this._resetPanelIndexTimeoutId) {
clearTimeout(this._resetPanelIndexTimeoutId);
this._resetPanelIndexTimeoutId = null;
}
this._currentPanelIndex$.value = index;
this.keyboard.hide();
this._scrollCurrentBlockIntoView();
@@ -123,9 +145,6 @@ export class AffineKeyboardToolbar extends SignalWatcher(
return {
std: this.std,
rootComponent: this.rootComponent,
closeToolbar: (blur = false) => {
this.close(blur);
},
closeToolPanel: () => {
this._closeToolPanel();
},
@@ -202,7 +221,7 @@ export class AffineKeyboardToolbar extends SignalWatcher(
}
private _renderItems() {
if (document.activeElement !== this.rootComponent)
if (!this.std.event.active$.value)
return html`<div class="item-container"></div>`;
const goPrevToolbarAction = when(
@@ -226,7 +245,15 @@ export class AffineKeyboardToolbar extends SignalWatcher(
<icon-button
size="36px"
@click=${() => {
this.close(true);
if (this.keyboard.staticHeight$.value === 0) {
this._closeToolPanel();
return;
}
if (this.keyboard.visible$.peek()) {
this.keyboard.hide();
} else {
this.keyboard.show();
}
}}
>
${KeyboardIcon()}
@@ -237,6 +264,23 @@ export class AffineKeyboardToolbar extends SignalWatcher(
override connectedCallback() {
super.connectedCallback();
// There are two cases that `_expanded$` will be true:
// 1. when virtual keyboard is opened, the panel need to be expanded and overlapped by the keyboard,
// so that the toolbar will be on the top of the keyboard.
// 2. the panel is opened, whether the keyboard is closed or not exists (e.g. a physical keyboard connected)
//
// There is one case that `_expanded$` will be false:
// 1. the panel is closed, and the keyboard is closed, the toolbar will be rendered at the bottom of the viewport
this._disposables.add(
effect(() => {
if (this.keyboard.visible$.value || this.panelOpened) {
this._expanded$.value = true;
} else {
this._expanded$.value = false;
}
})
);
// prevent editor blur when click item in toolbar
this.disposables.addFromEvent(this, 'pointerdown', e => {
e.preventDefault();
@@ -260,15 +304,17 @@ export class AffineKeyboardToolbar extends SignalWatcher(
if (this.keyboard.visible$.value) {
this._closeToolPanel();
}
// when keyboard is closed and the panel is not opened, we need to close the toolbar,
// this usually happens when user close keyboard from system side
else if (this.hasUpdated && untracked(() => !this.panelOpened)) {
this.close(true);
}
})
);
this._watchAutoShow();
this.disposables.add(() => {
if (this._resetPanelIndexTimeoutId) {
clearTimeout(this._resetPanelIndexTimeoutId);
this._resetPanelIndexTimeoutId = null;
}
});
}
private _watchAutoShow() {
@@ -331,7 +377,10 @@ export class AffineKeyboardToolbar extends SignalWatcher(
<affine-keyboard-tool-panel
.config=${this._currentPanelConfig}
.context=${this._context}
.height=${this.panelHeight$.value}
style=${styleMap({
height: this.panelHeight,
paddingBottom: this.keyboard.appTabSafeArea$.value,
})}
></affine-keyboard-tool-panel>
`;
}
@@ -339,9 +388,6 @@ export class AffineKeyboardToolbar extends SignalWatcher(
@property({ attribute: false })
accessor keyboard!: VirtualKeyboardProviderWithAction;
@property({ attribute: false })
accessor close: (blur: boolean) => void = () => {};
@property({ attribute: false })
accessor config!: KeyboardToolbarConfig;

View File

@@ -1,42 +0,0 @@
import { type VirtualKeyboardProvider } from '@blocksuite/affine-shared/services';
import { DisposableGroup } from '@blocksuite/global/disposable';
import type { BlockStdScope, ShadowlessElement } from '@blocksuite/std';
import { effect, type Signal } from '@preact/signals-core';
import type { ReactiveController, ReactiveControllerHost } from 'lit';
/**
* This controller is used to control the keyboard toolbar position
*/
export class PositionController implements ReactiveController {
private readonly _disposables = new DisposableGroup();
host: ReactiveControllerHost &
ShadowlessElement & {
std: BlockStdScope;
panelHeight$: Signal<number>;
keyboard: VirtualKeyboardProvider;
panelOpened: boolean;
};
constructor(host: PositionController['host']) {
(this.host = host).addController(this);
}
hostConnected() {
const { keyboard } = this.host;
this._disposables.add(
effect(() => {
if (keyboard.visible$.value) {
this.host.panelHeight$.value = keyboard.height$.value;
}
})
);
this.host.style.bottom = '0px';
}
hostDisconnected() {
this._disposables.dispose();
}
}

View File

@@ -7,6 +7,7 @@ export const keyboardToolbarStyles = css`
position: fixed;
display: block;
width: 100vw;
bottom: 0;
}
.keyboard-toolbar {
@@ -60,14 +61,18 @@ export const keyboardToolbarStyles = css`
export const keyboardToolPanelStyles = css`
affine-keyboard-tool-panel {
display: block;
overflow-y: auto;
box-sizing: border-box;
background-color: ${unsafeCSSVarV2('layer/background/primary')};
}
.affine-keyboard-tool-panel-container {
display: flex;
flex-direction: column;
gap: 24px;
width: 100%;
padding: 16px 4px 8px 8px;
overflow-y: auto;
box-sizing: border-box;
background-color: ${unsafeCSSVarV2('layer/background/primary')};
}
${scrollbarStyle('affine-keyboard-tool-panel')}

View File

@@ -20,18 +20,6 @@ import {
export const AFFINE_KEYBOARD_TOOLBAR_WIDGET = 'affine-keyboard-toolbar-widget';
export class AffineKeyboardToolbarWidget extends WidgetComponent<RootBlockModel> {
private readonly _close = (blur: boolean) => {
if (blur) {
if (document.activeElement === this._docTitle?.inlineEditorContainer) {
this._docTitle?.inlineEditor?.setInlineRange(null);
this._docTitle?.inlineEditor?.eventSource?.blur();
} else if (document.activeElement === this.block?.rootComponent) {
this.std.selection.clear();
}
}
this._show$.value = false;
};
private readonly _show$ = signal(false);
private _initialInputMode: string = '';
@@ -73,29 +61,26 @@ export class AffineKeyboardToolbarWidget extends WidgetComponent<RootBlockModel>
override connectedCallback(): void {
super.connectedCallback();
const rootComponent = this.block?.rootComponent;
if (rootComponent) {
this.disposables.addFromEvent(rootComponent, 'focus', () => {
this._show$.value = true;
});
this.disposables.addFromEvent(rootComponent, 'blur', () => {
this._show$.value = false;
});
this.disposables.add(
effect(() => {
this._show$.value = this.std.event.active$.value;
})
);
if (this.keyboard.fallback) {
this._initialInputMode = rootComponent.inputMode;
this.disposables.add(() => {
rootComponent.inputMode = this._initialInputMode;
});
this.disposables.add(
effect(() => {
// recover input mode when keyboard toolbar is hidden
if (!this._show$.value) {
rootComponent.inputMode = this._initialInputMode;
}
})
);
}
const rootComponent = this.block?.rootComponent;
if (rootComponent && this.keyboard.fallback) {
this._initialInputMode = rootComponent.inputMode;
this.disposables.add(() => {
rootComponent.inputMode = this._initialInputMode;
});
this.disposables.add(
effect(() => {
// recover input mode when keyboard toolbar is hidden
if (!this._show$.value) {
rootComponent.inputMode = this._initialInputMode;
}
})
);
}
if (this._docTitle) {
@@ -129,7 +114,6 @@ export class AffineKeyboardToolbarWidget extends WidgetComponent<RootBlockModel>
.keyboard=${this.keyboard}
.config=${this.config}
.rootComponent=${this.block.rootComponent}
.close=${this._close}
></affine-keyboard-toolbar>`}
></blocksuite-portal>`;
}

View File

@@ -113,11 +113,9 @@ export class LinkedDocPopover extends SignalWatcher(
}
private get _flattenActionList() {
return this._actionGroup
.map(group =>
group.items.map(item => ({ ...item, groupName: group.name }))
)
.flat();
return this._actionGroup.flatMap(group =>
group.items.map(item => ({ ...item, groupName: group.name }))
);
}
private get _query() {

View File

@@ -65,6 +65,98 @@ export class Unzip {
this.unzipped = fflate.unzipSync(new Uint8Array(await blob.arrayBuffer()));
}
private fixFileNameEncoding(fileName: string): string {
try {
// check if contains non-ASCII characters
if (fileName.split('').some(char => char.charCodeAt(0) > 127)) {
// try different encodings
const fixedName = this.tryDifferentEncodings(fileName);
if (fixedName && fixedName !== fileName) {
return fixedName;
}
}
return fileName;
} catch {
return fileName;
}
}
// try different encodings
private tryDifferentEncodings(fileName: string): string | null {
try {
// convert string to bytes
const bytes = new Uint8Array(fileName.length);
for (let i = 0; i < fileName.length; i++) {
bytes[i] = fileName.charCodeAt(i);
}
// try different encodings
// The macOS system zip tool creates archives with UTF-8 encoded filenames.
// However, this implementation doesn't strictly adhere to the ZIP specification.
// Simply forcing UTF-8 encoding when unzipping should resolve filename corruption issues.
const encodings = ['utf-8'];
for (const encoding of encodings) {
try {
const decoder = new TextDecoder(encoding);
const result = decoder.decode(bytes);
// check if decoded result is valid
if (result && this.isValidDecodedString(result)) {
return result;
}
} catch {
// ignore encoding error, try next encoding
}
}
} catch {
// ignore conversion error
}
return null;
}
// check if decoded string is valid
private isValidDecodedString(str: string): boolean {
// check if contains control characters
const controlCharCodes = new Set([
0x00,
0x01,
0x02,
0x03,
0x04,
0x05,
0x06,
0x07,
0x08, // \x00-\x08
0x0b,
0x0c, // \x0B, \x0C
0x0e,
0x0f,
0x10,
0x11,
0x12,
0x13,
0x14,
0x15,
0x16,
0x17,
0x18,
0x19,
0x1a,
0x1b,
0x1c,
0x1d,
0x1e,
0x1f, // \x0E-\x1F
0x7f, // \x7F
]);
return !str
.split('')
.some(char => controlCharCodes.has(char.charCodeAt(0)));
}
*[Symbol.iterator]() {
const keys = Object.keys(this.unzipped ?? {});
let index = 0;
@@ -81,7 +173,10 @@ export class Unzip {
const content = new File([this.unzipped![path]], fileName, {
type: mime ?? '',
}) as Blob;
yield { path, content, index };
const fixedPath = this.fixFileNameEncoding(path);
yield { path: fixedPath, content, index };
index++;
}
}

View File

@@ -142,15 +142,13 @@ export class SlashMenu extends WithDisposable(LitElement) {
// We search first and second layer
if (this._filteredItems.length !== 0 && depth >= 1) break;
queue = queue
.map<typeof queue>(item => {
if (isSubMenuItem(item)) {
return item.subMenu;
} else {
return [];
}
})
.flat();
queue = queue.flatMap(item => {
if (isSubMenuItem(item)) {
return item.subMenu;
} else {
return [];
}
});
depth++;
}

View File

@@ -418,9 +418,9 @@ export class AffineToolbarWidget extends WidgetComponent {
return;
}
const elementIds = selections
.map(s => (s.editing || s.inoperable ? [] : s.elements))
.flat();
const elementIds = selections.flatMap(s =>
s.editing || s.inoperable ? [] : s.elements
);
const count = elementIds.length;
const activated = context.activated && Boolean(count);

View File

@@ -229,8 +229,7 @@ export function renderToolbar(
? module.config.when(context)
: (module.config.when ?? true)
)
.map<ToolbarActions>(module => module.config.actions)
.flat();
.flatMap(module => module.config.actions);
const combined = combine(actions, context);

View File

@@ -91,15 +91,11 @@ export class KeyboardControl {
const disposables = new DisposableGroup();
if (IS_ANDROID) {
disposables.add(
this._dispatcher.add(
'beforeInput',
ctx => {
if (this.composition) return false;
const binding = androidBindKeymapPatch(keymap);
return binding(ctx);
},
options
)
this._dispatcher.add('beforeInput', ctx => {
if (this.composition) return false;
const binding = androidBindKeymapPatch(keymap);
return binding(ctx);
})
);
}

View File

@@ -226,6 +226,18 @@ export class UIEventDispatcher extends LifeCycleWatcher {
this._setActive(false);
}
});
// When the selection is outside the host, the event dispatcher should be inactive
this.disposables.addFromEvent(document, 'selectionchange', () => {
const sel = document.getSelection();
if (!sel || sel.rangeCount === 0) return;
const { anchorNode, focusNode } = sel;
if (
(anchorNode && !this.host.contains(anchorNode)) ||
(focusNode && !this.host.contains(focusNode))
) {
this._setActive(false);
}
});
}
private _buildEventScopeBySelection(name: EventName) {

View File

@@ -104,7 +104,7 @@ export function bindKeymap(
};
}
// In Android, the keypress event dose not contain
// In some IME of Android like, the keypress event dose not contain
// the information about what key is pressed. See
// https://stackoverflow.com/a/68188679
// https://stackoverflow.com/a/66724830

View File

@@ -57,7 +57,7 @@ export type CanvasLayer = BaseLayer<GfxPrimitiveElementModel> & {
type: 'canvas';
/**
* The z-index of canvas layer.
* The z-index of the first element in this canvas layer.
*
* A canvas layer renders all the elements in a single canvas,
* this property is used to render the canvas with correct z-index.
@@ -165,8 +165,7 @@ export class LayerManager extends GfxExtension {
];
curLayer.zIndex = currentCSSZindex;
layers.push(curLayer as LayerManager['layers'][number]);
currentCSSZindex +=
curLayer.type === 'block' ? curLayer.elements.length : 1;
currentCSSZindex += curLayer.elements.length;
}
};
const addLayer = (type: 'canvas' | 'block') => {

View File

@@ -1,3 +1,4 @@
import { IS_ANDROID } from '@blocksuite/global/env';
import type { BaseTextAttributes } from '@blocksuite/store';
import type { InlineEditor } from '../inline-editor.js';
@@ -41,11 +42,10 @@ export class EventService<TextAttributes extends BaseTextAttributes> {
}
};
private readonly _onBeforeInput = (event: InputEvent) => {
private readonly _onBeforeInput = async (event: InputEvent) => {
const range = this.editor.rangeService.getNativeRange();
if (
this.editor.isReadonly ||
this._isComposing ||
!range ||
!this._isRangeCompletelyInRoot(range)
)
@@ -54,33 +54,29 @@ export class EventService<TextAttributes extends BaseTextAttributes> {
let inlineRange = this.editor.toInlineRange(range);
if (!inlineRange) return;
if (this._isComposing) {
if (IS_ANDROID && event.inputType === 'insertCompositionText') {
this._compositionInlineRange = inlineRange;
}
return;
}
let ifHandleTargetRange = true;
if (event.inputType.startsWith('delete')) {
if (
isInEmbedGap(range.commonAncestorContainer) &&
inlineRange.length === 0 &&
inlineRange.index > 0
) {
inlineRange = {
index: inlineRange.index - 1,
length: 1,
};
ifHandleTargetRange = false;
} else if (
isInEmptyLine(range.commonAncestorContainer) &&
inlineRange.length === 0 &&
inlineRange.index > 0
// eslint-disable-next-line sonarjs/no-duplicated-branches
) {
// do not use target range when deleting across lines
if (
event.inputType.startsWith('delete') &&
(isInEmbedGap(range.commonAncestorContainer) ||
// https://github.com/toeverything/blocksuite/issues/5381
inlineRange = {
index: inlineRange.index - 1,
length: 1,
};
ifHandleTargetRange = false;
}
isInEmptyLine(range.commonAncestorContainer)) &&
inlineRange.length === 0 &&
inlineRange.index > 0
) {
// do not use target range when deleting across lines
inlineRange = {
index: inlineRange.index - 1,
length: 1,
};
ifHandleTargetRange = false;
}
if (ifHandleTargetRange) {
@@ -97,11 +93,24 @@ export class EventService<TextAttributes extends BaseTextAttributes> {
}
}
}
if (!inlineRange) return;
event.preventDefault();
if (IS_ANDROID) {
this.editor.rerenderWholeEditor();
await this.editor.waitForUpdate();
if (
event.inputType === 'deleteContentBackward' &&
!(inlineRange.index === 0 && inlineRange.length === 0)
) {
// when press backspace at offset 1, double characters will be removed.
// because we mock backspace key event `androidBindKeymapPatch` in blocksuite/framework/std/src/event/keymap.ts
// so we need to stop the event propagation to prevent the double characters removal.
event.stopPropagation();
}
}
const ctx: BeforeinputHookCtx<TextAttributes> = {
inlineEditor: this.editor,
raw: event,
@@ -346,11 +355,9 @@ export class EventService<TextAttributes extends BaseTextAttributes> {
return;
}
this.editor.disposables.addFromEvent(
eventSource,
'beforeinput',
this._onBeforeInput
);
this.editor.disposables.addFromEvent(eventSource, 'beforeinput', e => {
this._onBeforeInput(e).catch(console.error);
});
this.editor.disposables.addFromEvent(
eventSource,
'compositionstart',

View File

@@ -12,11 +12,7 @@ import type { SurfaceBlockModel } from '../gfx/model/surface/surface-model.js';
export function getLayerEndZIndex(layers: Layer[], layerIndex: number) {
const layer = layers[layerIndex];
return layer
? layer.type === 'block'
? layer.zIndex + layer.elements.length - 1
: layer.zIndex
: 0;
return layer ? layer.zIndex + layer.elements.length - 1 : 0;
}
export function updateLayersZIndex(layers: Layer[], startIdx: number) {
@@ -27,7 +23,7 @@ export function updateLayersZIndex(layers: Layer[], startIdx: number) {
const curLayer = layers[i];
curLayer.zIndex = curIndex;
curIndex += curLayer.type === 'block' ? curLayer.elements.length : 1;
curIndex += curLayer.elements.length;
}
}

View File

@@ -200,7 +200,7 @@ test('layer zindex should update correctly when elements changed', async () => {
expect(service.layer.layers[1].zIndex).toBe(3);
expect(service.layer.layers[2].type).toBe('block');
expect(service.layer.layers[2].zIndex).toBe(4);
expect(service.layer.layers[2].zIndex).toBe(5);
};
assert2StepState();

View File

@@ -9,6 +9,7 @@
"**/node_modules",
".yarn",
".github/helm",
".git",
".vscode",
".yarnrc.yml",
".docker",
@@ -159,6 +160,7 @@
}
],
"unicorn/prefer-array-some": "error",
"unicorn/prefer-array-flat-map": "off",
"unicorn/no-useless-promise-resolve-reject": "error",
"unicorn/no-unnecessary-await": "error",
"unicorn/no-useless-fallback-in-spread": "error",

View File

@@ -82,7 +82,7 @@
"husky": "^9.1.7",
"lint-staged": "^16.0.0",
"msw": "^2.6.8",
"oxlint": "^1.1.0",
"oxlint": "^1.15.0",
"prettier": "^3.4.2",
"semver": "^7.6.3",
"serve": "^14.2.4",
@@ -135,6 +135,7 @@
"object.fromentries": "npm:@nolyfill/object.fromentries@^1",
"object.hasown": "npm:@nolyfill/object.hasown@^1",
"object.values": "npm:@nolyfill/object.values@^1",
"on-headers": "npm:on-headers@^1.1.0",
"reflect.getprototypeof": "npm:@nolyfill/reflect.getprototypeof@^1",
"regexp.prototype.flags": "npm:@nolyfill/regexp.prototype.flags@^1",
"safe-array-concat": "npm:@nolyfill/safe-array-concat@^1",

View File

@@ -1,5 +1,7 @@
use std::collections::HashSet;
use tiktoken_rs::{get_bpe_from_tokenizer, tokenizer::Tokenizer as TiktokenTokenizer};
#[napi]
pub struct Tokenizer {
inner: tiktoken_rs::CoreBPE,
@@ -7,6 +9,10 @@ pub struct Tokenizer {
#[napi]
pub fn from_model_name(model_name: String) -> Option<Tokenizer> {
if model_name.starts_with("gpt-5") {
let bpe = get_bpe_from_tokenizer(TiktokenTokenizer::O200kBase).ok()?;
return Some(Tokenizer { inner: bpe });
}
let bpe = tiktoken_rs::get_bpe_from_model(&model_name).ok()?;
Some(Tokenizer { inner: bpe })
}
@@ -31,7 +37,7 @@ mod tests {
#[test]
fn test_tokenizer() {
let tokenizer = from_model_name("gpt-4.1".to_string()).unwrap();
let tokenizer = from_model_name("gpt-5".to_string()).unwrap();
let content = "Hello, world!";
let count = tokenizer.count(content.to_string(), None);
assert!(count > 0);

View File

@@ -0,0 +1,37 @@
-- CreateTable
/*
Warnings:
- The primary key for the `ai_workspace_embeddings` table will be changed. If it partially fails, the table could be left without primary key constraint.
- The primary key for the `ai_workspace_file_embeddings` table will be changed. If it partially fails, the table could be left without primary key constraint.
*/
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'ai_workspace_embeddings') AND
EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'ai_workspace_file_embeddings') THEN
CREATE TABLE "ai_workspace_blob_embeddings" (
"workspace_id" VARCHAR NOT NULL,
"blob_id" VARCHAR NOT NULL,
"chunk" INTEGER NOT NULL,
"content" VARCHAR NOT NULL,
"embedding" vector(1024) NOT NULL,
"created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "ai_workspace_blob_embeddings_pkey" PRIMARY KEY ("workspace_id","blob_id","chunk")
);
-- CreateIndex
CREATE INDEX "ai_workspace_blob_embeddings_idx" ON "ai_workspace_blob_embeddings"
USING hnsw (embedding vector_cosine_ops);
-- AddForeignKey
ALTER TABLE "ai_workspace_blob_embeddings"
ADD CONSTRAINT "ai_workspace_blob_embeddings_workspace_id_blob_id_fkey"
FOREIGN KEY ("workspace_id", "blob_id")
REFERENCES "blobs"("workspace_id", "key")
ON DELETE CASCADE ON UPDATE CASCADE;
END IF;
END
$$;

View File

@@ -0,0 +1,20 @@
-- CreateTable
CREATE TABLE "access_tokens" (
"id" VARCHAR NOT NULL,
"name" VARCHAR NOT NULL,
"token" VARCHAR NOT NULL,
"user_id" VARCHAR NOT NULL,
"created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"expires_at" TIMESTAMPTZ(3),
CONSTRAINT "access_tokens_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "access_tokens_token_key" ON "access_tokens"("token");
-- CreateIndex
CREATE INDEX "access_tokens_user_id_idx" ON "access_tokens"("user_id");
-- AddForeignKey
ALTER TABLE "access_tokens" ADD CONSTRAINT "access_tokens_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -0,0 +1,5 @@
-- AlterTable
ALTER TABLE "workspaces" ADD COLUMN "last_check_embeddings" TIMESTAMPTZ(3) NOT NULL DEFAULT '1970-01-01 00:00:00 +00:00';
-- CreateIndex
CREATE INDEX "workspaces_last_check_embeddings_idx" ON "workspaces"("last_check_embeddings");

View File

@@ -28,18 +28,19 @@
"dependencies": {
"@affine/reader": "workspace:*",
"@affine/server-native": "workspace:*",
"@ai-sdk/anthropic": "^1.2.12",
"@ai-sdk/google": "^1.2.18",
"@ai-sdk/google-vertex": "^2.2.23",
"@ai-sdk/openai": "^1.3.22",
"@ai-sdk/openai-compatible": "^0.2.14",
"@ai-sdk/perplexity": "^1.1.9",
"@ai-sdk/anthropic": "^2.0.1",
"@ai-sdk/google": "^2.0.4",
"@ai-sdk/google-vertex": "^3.0.5",
"@ai-sdk/openai": "^2.0.10",
"@ai-sdk/openai-compatible": "^1.0.5",
"@ai-sdk/perplexity": "^2.0.1",
"@apollo/server": "^4.11.3",
"@aws-sdk/client-s3": "^3.779.0",
"@aws-sdk/s3-request-presigner": "^3.779.0",
"@fal-ai/serverless-client": "^0.15.0",
"@google-cloud/opentelemetry-cloud-trace-exporter": "^2.4.1",
"@google-cloud/opentelemetry-resource-util": "^2.4.0",
"@modelcontextprotocol/sdk": "^1.16.0",
"@nestjs-cls/transactional": "^2.6.1",
"@nestjs-cls/transactional-adapter-prisma": "^1.2.19",
"@nestjs/apollo": "^13.0.4",
@@ -74,7 +75,7 @@
"@prisma/instrumentation": "^6.7.0",
"@react-email/components": "0.0.38",
"@socket.io/redis-adapter": "^8.3.0",
"ai": "^4.3.4",
"ai": "^5.0.10",
"bullmq": "^5.40.2",
"cookie-parser": "^1.4.7",
"cross-env": "^7.0.3",
@@ -85,6 +86,7 @@
"express": "^5.0.1",
"fast-xml-parser": "^5.0.0",
"get-stream": "^9.0.1",
"google-auth-library": "^10.2.0",
"graphql": "^16.9.0",
"graphql-scalars": "^1.24.0",
"graphql-upload": "^17.0.0",
@@ -103,7 +105,7 @@
"nest-winston": "^1.9.7",
"nestjs-cls": "^6.0.0",
"nodemailer": "^7.0.0",
"on-headers": "^1.0.2",
"on-headers": "^1.1.0",
"piscina": "^5.0.0-alpha.0",
"prisma": "^6.6.0",
"react": "19.1.0",

View File

@@ -49,6 +49,7 @@ model User {
comments Comment[]
replies Reply[]
commentAttachments CommentAttachment[] @relation("createdCommentAttachments")
AccessToken AccessToken[]
@@index([email])
@@map("users")
@@ -110,17 +111,18 @@ model VerificationToken {
model Workspace {
// NOTE: manually set this column type to identity in migration file
sid Int @unique @default(autoincrement())
id String @id @default(uuid()) @db.VarChar
public Boolean
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
sid Int @unique @default(autoincrement())
id String @id @default(uuid()) @db.VarChar
public Boolean
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
// workspace level feature flags
enableAi Boolean @default(true) @map("enable_ai")
enableUrlPreview Boolean @default(false) @map("enable_url_preview")
enableDocEmbedding Boolean @default(true) @map("enable_doc_embedding")
name String? @db.VarChar
avatarKey String? @map("avatar_key") @db.VarChar
indexed Boolean @default(false)
enableAi Boolean @default(true) @map("enable_ai")
enableUrlPreview Boolean @default(false) @map("enable_url_preview")
enableDocEmbedding Boolean @default(true) @map("enable_doc_embedding")
name String? @db.VarChar
avatarKey String? @map("avatar_key") @db.VarChar
indexed Boolean @default(false)
lastCheckEmbeddings DateTime @default("1970-01-01T00:00:00-00:00") @map("last_check_embeddings") @db.Timestamptz(3)
features WorkspaceFeature[]
docs WorkspaceDoc[]
@@ -132,6 +134,7 @@ model Workspace {
comments Comment[]
commentAttachments CommentAttachment[]
@@index([lastCheckEmbeddings])
@@map("workspaces")
}
@@ -568,6 +571,23 @@ model AiWorkspaceFileEmbedding {
@@map("ai_workspace_file_embeddings")
}
model AiWorkspaceBlobEmbedding {
workspaceId String @map("workspace_id") @db.VarChar
blobId String @map("blob_id") @db.VarChar
// a file can be divided into multiple chunks and embedded separately.
chunk Int @db.Integer
content String @db.VarChar
embedding Unsupported("vector(1024)")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
blob Blob @relation(fields: [workspaceId, blobId], references: [workspaceId, key], onDelete: Cascade)
@@id([workspaceId, blobId, chunk])
@@index([embedding], map: "ai_workspace_blob_embeddings_idx")
@@map("ai_workspace_blob_embeddings")
}
enum AiJobStatus {
pending
running
@@ -807,7 +827,8 @@ model Blob {
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
deletedAt DateTime? @map("deleted_at") @db.Timestamptz(3)
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
AiWorkspaceBlobEmbedding AiWorkspaceBlobEmbedding[]
@@id([workspaceId, key])
@@map("blobs")
@@ -931,3 +952,17 @@ model CommentAttachment {
@@id([workspaceId, docId, key])
@@map("comment_attachments")
}
model AccessToken {
id String @id @default(uuid()) @db.VarChar
name String @db.VarChar
token String @unique @db.VarChar
userId String @map("user_id") @db.VarChar
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
expiresAt DateTime? @map("expires_at") @db.Timestamptz(3)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId])
@@map("access_tokens")
}

View File

@@ -96,6 +96,21 @@ test('should be able to visit private api if signed in', async t => {
t.is(res.body.user.id, u1.id);
});
test('should be able to visit private api with access token', async t => {
const models = t.context.app.get(Models);
const token = await models.accessToken.create({
userId: u1.id,
name: 'test',
});
const res = await request(server)
.get('/private')
.set('Authorization', `Bearer ${token.token}`)
.expect(HttpStatus.OK);
t.is(res.body.user.id, u1.id);
});
test('should be able to parse session cookie', async t => {
const spy = Sinon.spy(auth, 'getUserSession');
await request(server)

View File

@@ -1,3 +1,5 @@
import { randomUUID } from 'node:crypto';
import type { ExecutionContext, TestFn } from 'ava';
import ava from 'ava';
import { z } from 'zod';
@@ -5,6 +7,7 @@ import { z } from 'zod';
import { ServerFeature, ServerService } from '../core';
import { AuthService } from '../core/auth';
import { QuotaModule } from '../core/quota';
import { Models } from '../models';
import { CopilotModule } from '../plugins/copilot';
import { prompts, PromptService } from '../plugins/copilot/prompt';
import {
@@ -30,6 +33,8 @@ import { TestAssets } from './utils/copilot';
type Tester = {
auth: AuthService;
module: TestingModule;
models: Models;
service: ServerService;
prompt: PromptService;
factory: CopilotProviderFactory;
workflow: CopilotWorkflowService;
@@ -66,12 +71,15 @@ test.serial.before(async t => {
isCopilotConfigured = service.features.includes(ServerFeature.Copilot);
const auth = module.get(AuthService);
const models = module.get(Models);
const prompt = module.get(PromptService);
const factory = module.get(CopilotProviderFactory);
const workflow = module.get(CopilotWorkflowService);
t.context.module = module;
t.context.auth = auth;
t.context.service = service;
t.context.models = models;
t.context.prompt = prompt;
t.context.factory = factory;
t.context.workflow = workflow;
@@ -84,7 +92,7 @@ test.serial.before(async t => {
});
test.serial.before(async t => {
const { prompt, executors } = t.context;
const { prompt, executors, models, service } = t.context;
executors.image.register();
executors.text.register();
@@ -98,6 +106,28 @@ test.serial.before(async t => {
for (const p of prompts) {
await prompt.set(p.name, p.model, p.messages, p.config);
}
const user = await models.user.create({
email: `${randomUUID()}@affine.pro`,
});
await service.updateConfig(user.id, [
{
module: 'copilot',
key: 'scenarios',
value: {
enabled: true,
scenarios: {
image: 'flux-1/schnell',
rerank: 'gpt-5-mini',
complex_text_generation: 'gpt-5-mini',
coding: 'gpt-5-mini',
quick_decision_making: 'gpt-5-mini',
quick_text_generation: 'gpt-5-mini',
polish_and_summarize: 'gemini-2.5-flash',
},
},
},
]);
});
test.after(async t => {
@@ -384,12 +414,12 @@ The term **“CRDT”** was first introduced by Marc Shapiro, Nuno Preguiça, Ca
role: 'user' as const,
content: 'what is ssot',
params: {
files: [
docs: [
{
blobId: 'SSOT',
fileName: 'Single source of truth - Wikipedia',
docId: 'SSOT',
docTitle: 'Single source of truth - Wikipedia',
fileType: 'text/markdown',
fileContent: TestAssets.SSOT,
docContent: TestAssets.SSOT,
},
],
},
@@ -530,9 +560,8 @@ The term **“CRDT”** was first introduced by Marc Shapiro, Nuno Preguiça, Ca
'Create headings',
'Make it longer',
'Make it shorter',
'Continue writing',
'Section Edit',
'Chat With AFFiNE AI',
'Search With AFFiNE AI',
],
messages: [{ role: 'user' as const, content: TestAssets.SSOT }],
verifier: (t: ExecutionContext<Tester>, result: string) => {
@@ -547,9 +576,18 @@ The term **“CRDT”** was first introduced by Marc Shapiro, Nuno Preguiça, Ca
},
type: 'text' as const,
},
{
promptName: ['Continue writing'],
messages: [{ role: 'user' as const, content: TestAssets.AFFiNE }],
verifier: (t: ExecutionContext<Tester>, result: string) => {
assertNotWrappedInCodeBlock(t, result);
t.assert(result.length > 0, 'should not be empty');
},
type: 'text' as const,
},
{
promptName: ['Brainstorm ideas about this', 'Brainstorm mindmap'],
messages: [{ role: 'user' as const, content: TestAssets.SSOT }],
messages: [{ role: 'user' as const, content: TestAssets.AFFiNE }],
verifier: (t: ExecutionContext<Tester>, result: string) => {
assertNotWrappedInCodeBlock(t, result);
t.assert(checkMDList(result), 'should be a markdown list');
@@ -646,20 +684,7 @@ The term **“CRDT”** was first introduced by Marc Shapiro, Nuno Preguiça, Ca
type: 'image' as const,
},
{
promptName: ['debug:action:dalle3'],
messages: [
{
role: 'user' as const,
content: 'Panda',
},
],
verifier: (t: ExecutionContext<Tester>, link: string) => {
t.truthy(checkUrl(link), 'should be a valid url');
},
type: 'image' as const,
},
{
promptName: ['debug:action:gpt-image-1'],
promptName: ['Generate image'],
messages: [
{
role: 'user' as const,
@@ -707,7 +732,7 @@ for (const {
[
...prompt.finish(
messages.reduce(
// @ts-expect-error
// @ts-expect-error params not typed
(acc, m) => Object.assign(acc, m.params),
{}
)
@@ -777,7 +802,7 @@ for (const {
[
...prompt.finish(
finalMessage.reduce(
// @ts-expect-error
// @ts-expect-error params not typed
(acc, m) => Object.assign(acc, m.params),
params
)

View File

@@ -111,7 +111,7 @@ test.before(async t => {
m.overrideProvider(OpenAIProvider).useClass(MockCopilotProvider);
m.overrideProvider(GeminiGenerativeProvider).useClass(
class MockGenerativeProvider extends MockCopilotProvider {
// @ts-expect-error
// @ts-expect-error type not typed
override type: CopilotProviderType = CopilotProviderType.Gemini;
}
);

View File

@@ -5,12 +5,14 @@ import { ProjectRoot } from '@affine-tools/utils/path';
import { PrismaClient } from '@prisma/client';
import type { TestFn } from 'ava';
import ava from 'ava';
import { nanoid } from 'nanoid';
import Sinon from 'sinon';
import { EventBus, JobQueue } from '../base';
import { ConfigModule } from '../base/config';
import { AuthService } from '../core/auth';
import { QuotaModule } from '../core/quota';
import { StorageModule, WorkspaceBlobStorage } from '../core/storage';
import {
ContextCategories,
CopilotSessionModel,
@@ -68,6 +70,7 @@ type Context = {
db: PrismaClient;
event: EventBus;
workspace: WorkspaceModel;
workspaceStorage: WorkspaceBlobStorage;
copilotSession: CopilotSessionModel;
context: CopilotContextService;
prompt: PromptService;
@@ -114,6 +117,7 @@ test.before(async t => {
},
}),
QuotaModule,
StorageModule,
CopilotModule,
],
tapModule: builder => {
@@ -127,6 +131,7 @@ test.before(async t => {
const db = module.get(PrismaClient);
const event = module.get(EventBus);
const workspace = module.get(WorkspaceModel);
const workspaceStorage = module.get(WorkspaceBlobStorage);
const copilotSession = module.get(CopilotSessionModel);
const prompt = module.get(PromptService);
const factory = module.get(CopilotProviderFactory);
@@ -146,6 +151,7 @@ test.before(async t => {
t.context.db = db;
t.context.event = event;
t.context.workspace = workspace;
t.context.workspaceStorage = workspaceStorage;
t.context.copilotSession = copilotSession;
t.context.prompt = prompt;
t.context.factory = factory;
@@ -206,7 +212,9 @@ test('should be able to manage prompt', async t => {
'should have two messages'
);
await prompt.update(promptName, [{ role: 'system', content: 'hello' }]);
await prompt.update(promptName, {
messages: [{ role: 'system', content: 'hello' }],
});
t.is(
(await prompt.get(promptName))!.finish({}).length,
1,
@@ -365,7 +373,7 @@ test('should be able to update chat session prompt', async t => {
// Update the session
const updatedSessionId = await session.update({
sessionId,
promptName: 'Search With AFFiNE AI',
promptName: 'Chat With AFFiNE AI',
userId,
});
t.is(updatedSessionId, sessionId, 'should update session with same id');
@@ -375,7 +383,7 @@ test('should be able to update chat session prompt', async t => {
t.truthy(updatedSession, 'should retrieve updated session');
t.is(
updatedSession?.config.promptName,
'Search With AFFiNE AI',
'Chat With AFFiNE AI',
'should have updated prompt name'
);
});
@@ -404,7 +412,7 @@ test('should be able to fork chat session', async t => {
// fork session
const s1 = (await session.get(sessionId))!;
// @ts-expect-error
// @ts-expect-error find maybe return undefined
const latestMessageId = s1.finish({}).find(m => m.role === 'assistant')!.id;
const forkedSessionId1 = await session.fork({
userId,
@@ -1333,16 +1341,16 @@ test('TextStreamParser should format different types of chunks correctly', t =>
textDelta: {
chunk: {
type: 'text-delta' as const,
textDelta: 'Hello world',
} as any,
text: 'Hello world',
},
expected: 'Hello world',
description: 'should format text-delta correctly',
},
reasoning: {
chunk: {
type: 'reasoning' as const,
textDelta: 'I need to think about this',
} as any,
type: 'reasoning-delta' as const,
text: 'I need to think about this',
},
expected: '\n> [!]\n> I need to think about this',
description: 'should format reasoning as callout',
},
@@ -1351,8 +1359,8 @@ test('TextStreamParser should format different types of chunks correctly', t =>
type: 'tool-call' as const,
toolName: 'web_search_exa' as const,
toolCallId: 'test-id-1',
args: { query: 'test query', mode: 'AUTO' as const },
} as any,
input: { query: 'test query', mode: 'AUTO' as const },
},
expected: '\n> [!]\n> \n> Searching the web "test query"\n> ',
description: 'should format web search tool call correctly',
},
@@ -1361,8 +1369,8 @@ test('TextStreamParser should format different types of chunks correctly', t =>
type: 'tool-call' as const,
toolName: 'web_crawl_exa' as const,
toolCallId: 'test-id-2',
args: { url: 'https://example.com' },
} as any,
input: { url: 'https://example.com' },
},
expected: '\n> [!]\n> \n> Crawling the web "https://example.com"\n> ',
description: 'should format web crawl tool call correctly',
},
@@ -1371,8 +1379,8 @@ test('TextStreamParser should format different types of chunks correctly', t =>
type: 'tool-result' as const,
toolName: 'web_search_exa' as const,
toolCallId: 'test-id-1',
args: { query: 'test query', mode: 'AUTO' as const },
result: [
input: { query: 'test query', mode: 'AUTO' as const },
output: [
{
title: 'Test Title',
url: 'https://test.com',
@@ -1399,7 +1407,7 @@ test('TextStreamParser should format different types of chunks correctly', t =>
chunk: {
type: 'error' as const,
error: { type: 'testError', message: 'Test error message' },
} as any,
},
errorMessage: 'Test error message',
description: 'should throw error for error chunks',
},
@@ -1429,78 +1437,85 @@ test('TextStreamParser should process a sequence of message chunks', t => {
chunks: [
// Reasoning chunks
{
type: 'reasoning' as const,
textDelta: 'The user is asking about',
} as any,
id: nanoid(),
type: 'reasoning-delta' as const,
text: 'The user is asking about',
},
{
type: 'reasoning' as const,
textDelta: ' recent advances in quantum computing',
} as any,
id: nanoid(),
type: 'reasoning-delta' as const,
text: ' recent advances in quantum computing',
},
{
type: 'reasoning' as const,
textDelta: ' and how it might impact',
} as any,
id: nanoid(),
type: 'reasoning-delta' as const,
text: ' and how it might impact',
},
{
type: 'reasoning' as const,
textDelta: ' cryptography and data security.',
} as any,
id: nanoid(),
type: 'reasoning-delta' as const,
text: ' cryptography and data security.',
},
{
type: 'reasoning' as const,
textDelta:
' I should provide information on quantum supremacy achievements',
} as any,
id: nanoid(),
type: 'reasoning-delta' as const,
text: ' I should provide information on quantum supremacy achievements',
},
// Text delta
{
id: nanoid(),
type: 'text-delta' as const,
textDelta:
'Let me search for the latest breakthroughs in quantum computing and their ',
} as any,
text: 'Let me search for the latest breakthroughs in quantum computing and their ',
},
// Tool call
{
type: 'tool-call' as const,
toolCallId: 'toolu_01ABCxyz123456789',
toolName: 'web_search_exa' as const,
args: {
input: {
query: 'latest quantum computing breakthroughs cryptography impact',
},
} as any,
},
// Tool result
{
type: 'tool-result' as const,
toolCallId: 'toolu_01ABCxyz123456789',
toolName: 'web_search_exa' as const,
args: {
input: {
query: 'latest quantum computing breakthroughs cryptography impact',
},
result: [
output: [
{
title: 'IBM Unveils 1000-Qubit Quantum Processor',
url: 'https://example.com/tech/quantum-computing-milestone',
},
],
} as any,
},
// More text deltas
{
id: nanoid(),
type: 'text-delta' as const,
textDelta: 'implications for security.',
} as any,
text: 'implications for security.',
},
{
id: nanoid(),
type: 'text-delta' as const,
textDelta: '\n\nQuantum computing has made ',
} as any,
text: '\n\nQuantum computing has made ',
},
{
id: nanoid(),
type: 'text-delta' as const,
textDelta: 'remarkable progress in the past year. ',
} as any,
text: 'remarkable progress in the past year. ',
},
{
id: nanoid(),
type: 'text-delta' as const,
textDelta:
'The development of more stable qubits has accelerated research significantly.',
} as any,
text: 'The development of more stable qubits has accelerated research significantly.',
},
],
expected:
'\n> [!]\n> The user is asking about recent advances in quantum computing and how it might impact cryptography and data security. I should provide information on quantum supremacy achievements\n\nLet me search for the latest breakthroughs in quantum computing and their \n> [!]\n> \n> Searching the web "latest quantum computing breakthroughs cryptography impact"\n> \n> \n> \n> [IBM Unveils 1000-Qubit Quantum Processor](https://example.com/tech/quantum-computing-milestone)\n> \n> \n> \n\nimplications for security.\n\nQuantum computing has made remarkable progress in the past year. The development of more stable qubits has accelerated research significantly.',
@@ -1520,14 +1535,25 @@ test('TextStreamParser should process a sequence of message chunks', t => {
// ==================== context ====================
test('should be able to manage context', async t => {
const { context, prompt, session, event, jobs, storage } = t.context;
const {
context,
event,
jobs,
prompt,
session,
storage,
workspace,
workspaceStorage,
} = t.context;
const ws = await workspace.create(userId);
await prompt.set(promptName, 'model', [
{ role: 'system', content: 'hello {{word}}' },
]);
const chatSession = await session.create({
docId: 'test',
workspaceId: 'test',
workspaceId: ws.id,
userId,
promptName,
pinned: false,
@@ -1608,6 +1634,24 @@ test('should be able to manage context', async t => {
t.is(result[0].fileId, file.id, 'should match file id');
}
// blob record
{
const blobId = 'test-blob';
await workspaceStorage.put(session.workspaceId, blobId, buffer);
await jobs.embedPendingBlob({ workspaceId: session.workspaceId, blobId });
const result = await t.context.context.matchWorkspaceBlobs(
session.workspaceId,
'test',
1,
undefined,
1
);
t.is(result.length, 1, 'should match blob embedding');
t.is(result[0].blobId, blobId, 'should match blob id');
}
// doc record
const addDoc = async () => {

View File

@@ -0,0 +1,27 @@
import { faker } from '@faker-js/faker';
import type { AccessToken } from '@prisma/client';
import { Prisma } from '@prisma/client';
import { Mocker } from './factory';
export type MockAccessTokenInput = Omit<
Prisma.AccessTokenUncheckedCreateInput,
'token'
>;
export type MockedAccessToken = AccessToken;
export class MockAccessToken extends Mocker<
MockAccessTokenInput,
MockedAccessToken
> {
override async create(input: MockAccessTokenInput) {
return await this.db.accessToken.create({
data: {
...input,
name: input.name ?? faker.lorem.word(),
token: 'ut_' + faker.string.hexadecimal({ length: 37 }),
},
});
}
}

View File

@@ -57,15 +57,6 @@ export class MockCopilotProvider extends OpenAIProvider {
},
],
},
{
id: 'gpt-4.1',
capabilities: [
{
input: [ModelInputType.Text, ModelInputType.Image],
output: [ModelOutputType.Text, ModelOutputType.Object],
},
],
},
{
id: 'gpt-4.1-2025-04-14',
capabilities: [
@@ -76,7 +67,25 @@ export class MockCopilotProvider extends OpenAIProvider {
],
},
{
id: 'gpt-4.1-mini',
id: 'gpt-5',
capabilities: [
{
input: [ModelInputType.Text, ModelInputType.Image],
output: [ModelOutputType.Text, ModelOutputType.Object],
},
],
},
{
id: 'gpt-5-2025-08-07',
capabilities: [
{
input: [ModelInputType.Text, ModelInputType.Image],
output: [ModelOutputType.Text, ModelOutputType.Object],
},
],
},
{
id: 'gpt-5-mini',
capabilities: [
{
input: [ModelInputType.Text, ModelInputType.Image],

View File

@@ -4,6 +4,7 @@ export * from './user.mock';
export * from './workspace.mock';
export * from './workspace-user.mock';
import { MockAccessToken } from './access-token.mock';
import { MockCopilotProvider } from './copilot.mock';
import { MockDocMeta } from './doc-meta.mock';
import { MockDocSnapshot } from './doc-snapshot.mock';
@@ -26,6 +27,7 @@ export const Mockers = {
DocMeta: MockDocMeta,
DocSnapshot: MockDocSnapshot,
DocUser: MockDocUser,
AccessToken: MockAccessToken,
};
export { MockCopilotProvider, MockEventBus, MockJobQueue, MockMailer };

View File

@@ -74,6 +74,17 @@ Generated by [AVA](https://avajs.dev).
},
]
> should match workspace blob embedding
[
{
blobId: 'blob-test',
chunk: 0,
content: 'blob content',
distance: 0,
},
]
> should find docs to embed
1
@@ -90,6 +101,28 @@ Generated by [AVA](https://avajs.dev).
0
## should check need to be embedded
> document with no embedding should need embedding
true
> document with recent embedding should not need embedding
false
> document updated after embedding and older-than-10m should need embedding
true
> should not need embedding when only 10-minute window passed without updates
false
> should need embedding when doc updated and last embedding older than 10 minutes
true
## should filter outdated doc id style in embedding status
> should include modern doc format

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