Compare commits

...

103 Commits

Author SHA1 Message Date
DarkSky
e98f035f97 feat(server): split embedding client (#12809) 2025-06-13 12:37:05 +08:00
fengmk2
1d4bc81e90 fix(server): use Query instead of Args (#12813)
#### PR Dependency Tree


* **PR #12813** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)
2025-06-13 04:12:54 +00:00
EYHN
deeea3428e feat(core): update build in server config (#12807)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Added support for Apple as an OAuth login provider across all server
configurations.
	- Introduced Copilot Embedding as a new server feature.

- **Changes**
- Updated server features to replace Captcha with Indexer in all
configurations.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-13 10:28:49 +08:00
fengmk2
8b0dd3c067 fix(server): increase doc-service liveness probe timeout from 1s to 5s (#12804)
yjs operations may take more than 1 second

before

![image](https://github.com/user-attachments/assets/feb7e375-ea73-4f5f-84f2-d85934f94844)

after

![image](https://github.com/user-attachments/assets/e760ee1f-a1c6-4f9d-8dbc-a4796efbd77a)


#### PR Dependency Tree


* **PR #12804** 👈

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 configurable timeout settings for liveness and readiness probes
in Helm chart deployments, allowing users to specify probe timeout
duration.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-12 21:04:37 +08:00
Richard Lora
8ca17864f1 fix(editor): show added or deleted rows immediately in grouped table and Kanban views (#12731)
https://github.com/user-attachments/assets/214fbe4f-b667-44b7-85a3-77ef4cfa8cca

This PR fixes a bug where adding or deleting rows in a grouped table
view did not visually update the UI until the user manually refreshed
the page or navigated away and back. The issue gave the impression that
the action had not completed.

Same issue for Kanban cards.

The result now is:
Users now see new rows or deleted rows reflected in real-time without
needing to reload or navigate away. This applies to both grouped table
views and Kanban cards.

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

- **Bug Fixes**
- Ensured the UI updates immediately after adding, deleting, or moving
cards and rows in Kanban and Table views on both mobile and desktop.
- Fixed issues where UI changes were not reflected after certain
actions, such as ungrouping, deleting, or inserting items.
- Improved row locking behavior during add and delete operations to
prevent UI inconsistencies.
- **Tests**
- Added comprehensive tests for row operations and menu interactions to
verify UI updates and correct method calls in data views.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: zzj3720 <zuozijian1994@gmail.com>
2025-06-12 16:02:37 +08:00
Aadi
d2664480f7 fix(editor): unable to delete content backward in database title cell (#12738)
fix: https://github.com/toeverything/blocksuite/issues/8578

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

- **New Features**
- Improved keyboard event handling within inline editors for database
headers and table cells, enhancing user control over key interactions
like 'Tab' and 'Escape'.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-12 15:58:52 +08:00
Aadi
b986a39da3 fix(editor): cursor not visible on empty line when line numbers are disabled in code block (#12694)
This fixes an issue where, if line numbers are turned off in a code
block, focusing on an empty line would cause the text cursor to be
hidden.

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

- **Style**
- Updated code block layout to improve appearance when line numbers are
disabled.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: L-Sun <zover.v@gmail.com>
2025-06-12 15:52:54 +08:00
Aadi
097a63362c fix(editor): firefox can't paste image in edgeless (#12729)
fix: https://github.com/toeverything/blocksuite/issues/8718
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## Summary by CodeRabbit

- **Bug Fixes**
- Improved clipboard handling to prevent creating empty notes when
pasting blank text.
- Enhanced detection of files and SVG images in clipboard content for
more reliable pasting behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-12 15:52:14 +08:00
EYHN
7284320355 fix(nbstore): fix search slow (#12800)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Bug Fixes**
- Improved search results by ensuring links to the current document are
properly excluded from related document and database searches.
- Enhanced session management in quick search by ensuring all active
sessions are cleaned up before processing new search results.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-12 15:43:55 +08:00
DarkSky
b4401a8abf chore(server): bump model version (#12798) 2025-06-12 07:40:28 +00:00
Aadi
0351fbcb86 fix(editor): remove duplicate event listeners (#12735)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Bug Fixes**
- Improved keyboard interaction reliability in text and rich text cells
by eliminating duplicate event listeners.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-12 15:13:54 +08:00
Peng Xiao
6eed9c686b fix(component): fix storybook migration v9 (#12797) 2025-06-12 07:01:58 +00:00
L-Sun
8d2214424c fix(editor): behavior of deleting at the start of line (#12787)
Close BS-3182, #12736 




#### PR Dependency Tree


* **PR #12787** 👈

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 the behavior when deleting empty lines and merging blocks,
ensuring more accurate handling of block deletion and cursor focus in
various scenarios.
- **Tests**
- Added new end-to-end tests to verify correct deletion of lines in
edgeless text and paragraph blocks, including checks for block removal
and cursor position.
- Introduced a utility function to retrieve block IDs for testing
purposes.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-12 14:58:03 +08:00
Peng Xiao
d12954f8c3 fix(electron): web dev server ws does not work for electron renderer (#12711) 2025-06-12 14:44:30 +08:00
DarkSky
83733cd828 feat(server): improve prompts (#12788) 2025-06-12 13:31:16 +08:00
DarkSky
ed56f076ed feat(server): improve rerank performance (#12775)
fix AI-183
2025-06-12 13:31:01 +08:00
Cats Juice
2d17c265ca fix: memory leak due to missing unsubscribe (#12777)
- unsubscribe `Signal` not correctly
- missing un-subscription for `Livedata.signal`

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

- **Bug Fixes**
- Improved resource management to ensure subscriptions are properly
cleaned up, reducing potential memory leaks and improving overall app
stability.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-12 12:35:14 +08:00
L-Sun
2a9f7e1835 fix(editor): can not clear embed card alias description (#12794)
Close
[BS-3600](https://linear.app/affine-design/issue/BS-3600/card-view下,编辑alias清空保存无效,无法实现清空)
Close
[BS-3599](https://linear.app/affine-design/issue/BS-3599/page-和canvas-mode下的note,对文本中的-embed-view-doc,隐藏掉-edit-按钮,避免误导,card)



#### PR Dependency Tree


* **PR #12794** 👈

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 edit option has been removed from the toolbar for embedded synced
documents.
- **Bug Fixes**
- The description field is now always included when editing embedded
cards, even if left empty.
- **Tests**
- Removed tests related to editing and view switching of linked document
embeds.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-12 12:33:57 +08:00
Fangdun Tsai
a71904e641 feat(editor): add replace action in attachment toolbar (#12512)
Closes:
[BS-3549](https://linear.app/affine-design/issue/BS-3549/附件-toolbar-上添加-replace-action)

[Screen Recording 2025-06-04 at 15.37.40.mov <span
class="graphite__hidden">(uploaded via Graphite)</span> <img
class="graphite__hidden"
src="https://app.graphite.dev/api/v1/graphite/video/thumbnail/8ypiIKZXudF5a0tIgIzf/480c8690-7ec9-4188-92fd-ee3339afb558.mov"
/>](https://app.graphite.dev/media/video/8ypiIKZXudF5a0tIgIzf/480c8690-7ec9-4188-92fd-ee3339afb558.mov)



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

- **New Features**
- Added the ability to replace attachments directly from the toolbar,
allowing users to select and update files seamlessly.
- **Bug Fixes**
- Improved handling when replacing embedded attachments with unsupported
file types, ensuring the view falls back to a card view as needed.
- **Tests**
- Introduced end-to-end tests to verify attachment replacement and
correct UI behavior in both standard and edgeless editing modes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-11 06:57:31 +00:00
L-Sun
814364489f chore(editor): remove unused codes (#12784)
Continue #12778 

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

## Summary by CodeRabbit

- **Chores**
- Removed internal string utility functions related to markdown prefix
and horizontal rule detection. No impact on user-facing features.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-11 06:40:51 +00:00
L-Sun
24448659a4 fix(editor): support markdown transform when using IME (#12778)
Fix #12284 
Close
[BS-3517](https://linear.app/affine-design/issue/BS-3517/微软新注音输入法无法使用markdown语法)

This PR refactor the markdown transform during inputting, including:
- Transfrom markdown syntax input in `inlineEditor.slots.inputting`,
where we can detect the space character inputed by IME like Microsoft
Bopomofo, but `keydown` event can't.
- Remove `markdown-input.ts` which was used in `KeymapExtension` of
paragraph, and refactor with `InlineMarkdownExtension`
- Adjust existing `InlineMarkdownExtension` since the space is included
in text.
- Add two `InlineMarkdownExtension` for paragraph and list to impl
Heading1-6, number, bullet, to-do list conversion.

Other changes:
- Improve type hint for parameter of `store.addBlock`

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

## Summary by CodeRabbit

- **New Features**
- Added markdown shortcuts for creating code blocks and dividers in the
rich text editor.
- Introduced enhanced paragraph markdown support for headings and
blockquotes with inline markdown patterns.
- Integrated new list markdown extension supporting numbered, bulleted,
and todo lists with checked states.

- **Improvements**
- Updated markdown formatting patterns to require trailing spaces for
links, LaTeX, and inline styles, improving detection accuracy.
- Markdown transformations now respond to input events instead of
keydown for smoother editing experience.
- Added focus management after markdown transformations to maintain
seamless editing flow.

- **Bug Fixes**
- Removed unnecessary prevention of default behavior on space and
shift-space key presses in list and paragraph editors.

- **Refactor**
- Enhanced event handling and typing for editor input events, improving
reliability and maintainability.
- Refined internal prefix text extraction logic for markdown processing.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-11 14:12:28 +08:00
darkskygit
c846c57a12 fix(server): list context status (#12771)
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **Bug Fixes**
  - Improved handling of document statuses to ensure documents without a finished or existing status are now explicitly marked as "processing" instead of remaining undefined.

- **Tests**
  - Added comprehensive new tests and snapshot entries to verify document status merging, including edge cases and concurrent operations, ensuring robust and consistent behavior.

- **Enhancements**
  - Updated context document listings to display the processing status for relevant documents.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-10 02:35:09 +00:00
L-Sun
e82c9d2ddc fix(editor): add title to edgeless page block ai context (#12763)
Close [BS-3590](https://linear.app/affine-design/issue/BS-3590/page-block-的标题没有被作为上下文输入)
2025-06-10 02:14:01 +00:00
yoyoyohamapi
3c29f62224 refactor(core): hide emebedding status tip if completed (#12720)
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Added real-time embedding status tracking and progress messages to the AI chat composer, with automatic updates every 10 seconds.
- **Refactor**
  - Simplified the embedding status tooltip to display a static message, removing dynamic status updates and hover-based refresh.
- **Tests**
  - Enhanced embedding status tooltip test by creating sample documents and extending visibility timeout to 50 seconds.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-10 01:54:13 +00:00
fundon
b5ef361f87 fix(editor): toolbar shaking when setting inline style (#12765)
Closes: [BS-1748](https://linear.app/affine-design/issue/BS-1748/improvement-toolbar-应用样式后会移动)

[Screen Recording 2025-06-09 at 17.59.01.mov <span class="graphite__hidden">(uploaded via Graphite)</span> <img class="graphite__hidden" src="https://app.graphite.dev/api/v1/graphite/video/thumbnail/8ypiIKZXudF5a0tIgIzf/a3941ec8-6b97-48e5-ba9b-484deb792d44.mov" />](https://app.graphite.dev/media/video/8ypiIKZXudF5a0tIgIzf/a3941ec8-6b97-48e5-ba9b-484deb792d44.mov)

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

## Summary by CodeRabbit

- **New Features**
  - Improved animation and transition effects for inline toolbars, providing a smoother user experience when toolbars appear or disappear.
- **Bug Fixes**
  - Ensured that the inline attribute is correctly removed when the toolbar is hidden, preventing visual inconsistencies.
- **Style**
  - Updated toolbar transition timing for more natural animations.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-09 11:19:13 +00:00
mkihr-ojisan
4fa85416ae fix: size of canvas in PDFPageRenderer (#12722)
Co-authored-by: Fangdun Tsai <fundon@pindash.io>
2025-06-09 14:37:44 +08:00
Aadi
f69a98eb8c fix(editor): pasting into database multiselect input auto adds a new tag (#12693)
Co-authored-by: 3720 <zuozijian1994@gmail.com>
2025-06-09 09:37:54 +08:00
renovate
115496aa8e chore: bump up nestjs to v11.1.3 (#12737)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [@nestjs/common](https://nestjs.com) ([source](https://redirect.github.com/nestjs/nest/tree/HEAD/packages/common)) | [`11.1.2` -> `11.1.3`](https://renovatebot.com/diffs/npm/@nestjs%2fcommon/11.1.2/11.1.3) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@nestjs%2fcommon/11.1.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@nestjs%2fcommon/11.1.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@nestjs%2fcommon/11.1.2/11.1.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@nestjs%2fcommon/11.1.2/11.1.3?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.2` -> `11.1.3`](https://renovatebot.com/diffs/npm/@nestjs%2fcore/11.1.2/11.1.3) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@nestjs%2fcore/11.1.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@nestjs%2fcore/11.1.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@nestjs%2fcore/11.1.2/11.1.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@nestjs%2fcore/11.1.2/11.1.3?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.2` -> `11.1.3`](https://renovatebot.com/diffs/npm/@nestjs%2fplatform-express/11.1.2/11.1.3) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@nestjs%2fplatform-express/11.1.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@nestjs%2fplatform-express/11.1.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@nestjs%2fplatform-express/11.1.2/11.1.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@nestjs%2fplatform-express/11.1.2/11.1.3?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.2` -> `11.1.3`](https://renovatebot.com/diffs/npm/@nestjs%2fplatform-socket.io/11.1.2/11.1.3) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@nestjs%2fplatform-socket.io/11.1.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@nestjs%2fplatform-socket.io/11.1.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@nestjs%2fplatform-socket.io/11.1.2/11.1.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@nestjs%2fplatform-socket.io/11.1.2/11.1.3?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.2` -> `11.1.3`](https://renovatebot.com/diffs/npm/@nestjs%2fwebsockets/11.1.2/11.1.3) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@nestjs%2fwebsockets/11.1.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@nestjs%2fwebsockets/11.1.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@nestjs%2fwebsockets/11.1.2/11.1.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@nestjs%2fwebsockets/11.1.2/11.1.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) |

---

### Release Notes

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

### [`v11.1.3`](https://redirect.github.com/nestjs/nest/compare/v11.1.2...1613f503cfd3fa5a6edddc0d7296aeca78f844a7)

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

</details>

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

### [`v11.1.3`](https://redirect.github.com/nestjs/nest/compare/v11.1.2...1613f503cfd3fa5a6edddc0d7296aeca78f844a7)

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

</details>

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

### [`v11.1.3`](https://redirect.github.com/nestjs/nest/compare/v11.1.2...1613f503cfd3fa5a6edddc0d7296aeca78f844a7)

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

</details>

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

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

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

#### v11.1.3 (2025-06-06)

##### Bug fixes

-   `core`
    -   [#&#8203;15201](https://redirect.github.com/nestjs/nest/pull/15201) fix(core): gracefully shutdown the app when repl exits ([@&#8203;dzhlobo](https://redirect.github.com/dzhlobo))

##### Enhancements

-   `common`
    -   [#&#8203;15209](https://redirect.github.com/nestjs/nest/pull/15209) feat: add string array type to disposition ([@&#8203;fjodor-rybakov](https://redirect.github.com/fjodor-rybakov))
-   `common`, `core`
    -   [#&#8203;15203](https://redirect.github.com/nestjs/nest/pull/15203) feat(core): defer initialization connected microservice ([@&#8203;isaryy](https://redirect.github.com/isaryy))

##### Dependencies

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

##### Committers: 3

-   Dmitry Zhlobo ([@&#8203;dzhlobo](https://redirect.github.com/dzhlobo))
-   [@&#8203;fjodor-rybakov](https://redirect.github.com/fjodor-rybakov)
-   [@&#8203;isaryy](https://redirect.github.com/isaryy)

</details>

---

### Configuration

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

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

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

🔕 **Ignore**: Close this PR and you won't be reminded about 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:eyJjcmVhdGVkSW5WZXIiOiI0MC40MC4zIiwidXBkYXRlZEluVmVyIjoiNDAuNDAuMyIsInRhcmdldEJyYW5jaCI6ImNhbmFyeSIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->
2025-06-07 09:03:02 +00:00
EYHN
7aafbf12a5 chore(ios): update ios app version (#12734)
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **Chores**
  - Updated the app version number to 0.22.2 for iOS.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-06 08:54:24 +00:00
pengx17
0f9b7d4a0d fix(electron): increase recording timeout (#12733)
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **Refactor**
  - Removed the automatic timeout that stopped recordings after 1.5 hours. Recordings will no longer be stopped automatically based on duration.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-06 08:40:05 +00:00
renovate
2817b5aec4 chore: bump up @googleapis/androidpublisher version to v28 (#12713)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [@googleapis/androidpublisher](https://redirect.github.com/googleapis/google-api-nodejs-client) | [`^27.0.0` -> `^28.0.0`](https://renovatebot.com/diffs/npm/@googleapis%2fandroidpublisher/27.0.0/28.0.1) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@googleapis%2fandroidpublisher/28.0.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@googleapis%2fandroidpublisher/28.0.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@googleapis%2fandroidpublisher/27.0.0/28.0.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@googleapis%2fandroidpublisher/27.0.0/28.0.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) |

---

### Release Notes

<details>
<summary>googleapis/google-api-nodejs-client (@&#8203;googleapis/androidpublisher)</summary>

### [`v28.0.1`](https://redirect.github.com/googleapis/google-api-nodejs-client/releases/tag/v28.0.1): 28.0.1

[Compare Source](https://redirect.github.com/googleapis/google-api-nodejs-client/compare/v27.0.0...v28.0.1)

This release fixes the release bug in v28.0.0.

</details>

---

### Configuration

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

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

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

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

---

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

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/toeverything/AFFiNE).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC40MC4zIiwidXBkYXRlZEluVmVyIjoiNDAuNDAuMyIsInRhcmdldEJyYW5jaCI6ImNhbmFyeSIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->
2025-06-06 08:24:22 +00:00
forehalo
72e66aca11 ci: do not deny rust nightly warnings 2025-06-06 12:02:36 +08:00
fundon
7d1f2adb7f fix(editor): support copying single image from edgeless and pasting to page (#12709)
Closes: [BS-3586](https://linear.app/affine-design/issue/BS-3586/复制白板图片,然后粘贴到-page,图片失败)

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

- **New Features**
  - Copying a single selected image in edgeless mode now places the image directly onto the system clipboard as a native image blob for smoother pasting.

- **Bug Fixes**
  - Enhanced clipboard handling to better manage image and text data inclusion, with improved fallback for snapshot HTML.

- **Tests**
  - Added an end-to-end test verifying image copy-paste functionality between edgeless and page editor modes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-06 01:17:58 +00:00
akumatus
512a908fd4 fix(core): generate the image cannot enter text prompt (#12717)
Close [AI-167](https://linear.app/affine-design/issue/AI-167)

![截屏2025-06-05 12.15.49.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/sJGviKxfE3Ap685cl5bj/c1afff0e-1197-46dc-ae43-ff7257039509.png)

![截屏2025-06-05 12.14.07.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/sJGviKxfE3Ap685cl5bj/1439b0a7-1cca-4848-aea2-84bc73c536c5.png)

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

- **New Features**
  - Improved AI panel behavior with explicit modes for input and answer generation, providing a more intuitive user experience when interacting with AI features.

- **Refactor**
  - Streamlined AI panel toggling logic for more consistent and predictable panel states during different actions.

- **Tests**
  - Enhanced AI image generation test to simulate user input and send actions for more accurate end-to-end validation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-05 09:17:28 +00:00
liuyi
71be1d424a fix(server): oidc registration (#12723) 2025-06-05 09:16:21 +00:00
renovate
d6a26b8093 chore: bump up multer version to v2.0.1 [SECURITY] (#12716)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [multer](https://redirect.github.com/expressjs/multer) | [`2.0.0` -> `2.0.1`](https://renovatebot.com/diffs/npm/multer/2.0.0/2.0.1) | [![age](https://developer.mend.io/api/mc/badges/age/npm/multer/2.0.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/multer/2.0.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/multer/2.0.0/2.0.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/multer/2.0.0/2.0.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) |

### GitHub Vulnerability Alerts

#### [CVE-2025-48997](https://redirect.github.com/expressjs/multer/security/advisories/GHSA-g5hg-p3ph-g8qg)

### Impact

A vulnerability in Multer versions >=1.4.4-lts.1, <2.0.1 allows an attacker to trigger a Denial of Service (DoS) by sending an upload file request with an empty string field name. This request causes an unhandled exception, leading to a crash of the process.

### Patches

Users should upgrade to `2.0.1`

### Workarounds

None

### References

35a3272b61
[https://github.com/expressjs/multer/issues/1233](https://redirect.github.com/expressjs/multer/issues/1233)
[https://github.com/expressjs/multer/pull/1256](https://redirect.github.com/expressjs/multer/pull/1256)

---

### Release Notes

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

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

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

-   Fix [CVE-2025-48997](https://www.cve.org/CVERecord?id=CVE-2025-48997) ([GHSA-g5hg-p3ph-g8qg](https://redirect.github.com/expressjs/multer/security/advisories/GHSA-g5hg-p3ph-g8qg))

</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:eyJjcmVhdGVkSW5WZXIiOiI0MC40MC4zIiwidXBkYXRlZEluVmVyIjoiNDAuNDAuMyIsInRhcmdldEJyYW5jaCI6ImNhbmFyeSIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->
2025-06-05 07:39:03 +00:00
EYHN
5e05952f6e feat(core): optimize tag performance (#12719)
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **Refactor**
  - Improved tag options management to use a reactive, real-time approach, ensuring tag options are always up to date throughout the application.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-05 07:13:31 +00:00
JimmFly
c1930c5937 chore: adjust general access button styles (#12718)
close AF-2685

When the button is disabled, the frontmost icon is not positioned correctly. This commit is to fix the icon position.

![CleanShot 2025-06-05 at 12 38 55@2x](https://github.com/user-attachments/assets/af2f80bc-69a0-4e33-bc8f-e5e169f769fc)

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

## Summary by CodeRabbit

- **Style**
  - Improved the layout of the share menu trigger text by aligning its content vertically and adding spacing between elements for a cleaner appearance.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-05 06:59:18 +00:00
fengmk2
b7ebd33389 chore(server): ignore rolled back error on the first time (#12714)
close #12692

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

- **Bug Fixes**
  - Improved error handling during migration rollbacks to better recognize and safely skip specific migration errors.
  - Enhanced logging for migration rollback failures to provide clearer information without interrupting the process.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-05 04:00:21 +00:00
Brooooooklyn
de9a3e1428 ci: fix missing environment in build-server-native (#12712)
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **Chores**
  - Updated workflow configuration to set the environment for the build process based on input parameters.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-05 03:44:35 +00:00
fengmk2
374eee9196 chore(server): disable indexer by default (#12710)
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **Chores**
	- Updated the default setting for the indexer feature to be disabled by default.
	- Added a sample environment variable for enabling the indexer in the example configuration file.
	- Introduced a new environment variable for the indexer in the CI workflow configuration.
- **Tests**
	- Adjusted test configurations to explicitly enable the indexer feature during test execution.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-05 03:28:12 +00:00
donteatfriedrice
1bdccdbd57 feat(editor): track citation events (#12664)
Closes: [BS-3551](https://linear.app/affine-design/issue/BS-3551/citation埋点)

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

- **New Features**
  - Enhanced citation tracking across attachments, bookmarks, embedded documents, paragraphs, footnotes, rename modals, and toolbars for actions like editing, deleting, expanding, and hovering on citations.
  - Introduced a centralized citation service to unify citation detection and telemetry event management.
- **Chores**
  - Updated service exports and telemetry modules to include the new citation service and citation-related event types.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-05 03:09:24 +00:00
donteatfriedrice
053efb61f0 fix(editor): should set event dispatcher active as false when document is hidden (#12559)
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
  - The application now automatically becomes inactive when the document is hidden, improving resource management and responsiveness to visibility changes.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-05 02:28:32 +00:00
pengx17
c7aebd0412 fix(electron): revert back electron to v35 (#12704)
v36 breaks worker loading in Electron's renderer
this use to work by turning off "PlzDedicatedWorker"
related to https://github.com/electron/electron/issues/43556

Before we know the root cause, revert back the electron version.

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

## Summary by CodeRabbit

- **Chores**
  - Updated the version of Electron used in the application.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-04 15:17:51 +00:00
renovate
01aa6979eb chore: bump up @nestjs-cls/transactional-adapter-prisma version to v1.2.23 (#12680)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [@nestjs-cls/transactional-adapter-prisma](https://papooch.github.io/nestjs-cls/) ([source](https://redirect.github.com/Papooch/nestjs-cls)) | [`1.2.21` -> `1.2.23`](https://renovatebot.com/diffs/npm/@nestjs-cls%2ftransactional-adapter-prisma/1.2.21/1.2.23) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@nestjs-cls%2ftransactional-adapter-prisma/1.2.23?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@nestjs-cls%2ftransactional-adapter-prisma/1.2.23?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@nestjs-cls%2ftransactional-adapter-prisma/1.2.21/1.2.23?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.21/1.2.23?slim=true)](https://docs.renovatebot.com/merge-confidence/) |

---

### Release Notes

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

### [`v1.2.23`](https://redirect.github.com/Papooch/nestjs-cls/compare/@nestjs-cls/transactional-adapter-prisma@1.2.22...@nestjs-cls/transactional-adapter-prisma@1.2.23)

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

### [`v1.2.22`](https://redirect.github.com/Papooch/nestjs-cls/compare/@nestjs-cls/transactional-adapter-prisma@1.2.21...@nestjs-cls/transactional-adapter-prisma@1.2.22)

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

</details>

---

### Configuration

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

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

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

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

---

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

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/toeverything/AFFiNE).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC4zMy42IiwidXBkYXRlZEluVmVyIjoiNDAuNDAuMyIsInRhcmdldEJyYW5jaCI6ImNhbmFyeSIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->
2025-06-04 13:00:41 +00:00
akumatus
c32f7c7964 fix(core): read-only editor does not support code preview (#12700)
Close [AI-160](https://linear.app/affine-design/issue/AI-160)

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

## Summary by CodeRabbit

- **New Features**
  - Improved preview state management for code blocks, ensuring consistent behavior in both editable and readonly modes.

- **Refactor**
  - Streamlined the way preview state is toggled and displayed for code blocks, resulting in a more reliable and maintainable user experience.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-04 12:41:10 +00:00
fengmk2
d219c92e98 chore(server): ignore never applied rolled back error (#12703)
closes #12701

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

## Summary by CodeRabbit

- **Bug Fixes**
  - Improved error handling during migration rollbacks to prevent unnecessary errors when migrations were never applied.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-04 11:37:32 +00:00
darkskygit
063072457c fix(server): chat with image (#12699)
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **Refactor**
  - Improved the handling of attachments in chat messages for more efficient processing of images and files without impacting user experience.
- **Chores**
  - Added internal logging to enhance monitoring of AI model interactions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-04 08:51:02 +00:00
darkskygit
13fa4f922a fix(server): token calculate (#12667) 2025-06-04 07:09:33 +00:00
fengmk2
f54bc0c047 chore(server): auto roll back failed migrations (#12697) 2025-06-04 14:45:42 +08:00
akumatus
1f0cc51462 fix(core): ai retry missing reasoning and webSearch params (#12690)
Close [AI-165](https://linear.app/affine-design/issue/AI-165)

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

- **New Features**
  - Enhanced chat components to support advanced reasoning and network search options, providing more control over AI-powered interactions.
  - Improved polling for context documents and files, now also triggered by additional chip types for more comprehensive updates.

- **Bug Fixes**
  - Ensured consistent application of configuration settings across all relevant chat components.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-04 03:42:25 +00:00
EYHN
160e4c2a38 feat(core): add title order by (#12696)
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Added support for ordering documents by their title.
  - Introduced a new "title" system property type with an associated icon and display name.

- **Improvements**
  - Enhanced system property types to allow more flexible filtering options.
  - Improved filter condition handling to show an unknown filter UI when filtering methods or values are unavailable.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-04 02:41:55 +00:00
darkskygit
99198e246b fix: migration compatible for postgres (#12659)
fix AI-162

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

- **Chores**
  - Improved database migration scripts to prevent errors by ensuring changes are only applied if relevant tables exist. No visible changes to user features or functionality.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-04 02:21:57 +00:00
darkskygit
44e1eb503f feat(server): improve embedding & rerank speed (#12666)
fix AI-109
2025-06-03 11:12:35 +00:00
CatsJuice
2288cbe54d chore(core): remove calendar integration feature flag (#12689)
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
  - All available integrations are now shown without restriction; calendar integration is always visible.

- **Chores**
  - Removed an obsolete feature flag related to calendar integration.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-03 10:58:19 +00:00
JimmFly
23ff398994 feat(mobile): add delete account function (#12688)
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Introduced a "Delete my account" option in mobile settings with role-based warnings and confirmation modals.
- **Enhancements**
  - Added flexible row and reverse row layout options for modal footers and action buttons on mobile.
- **Localization**
  - Added English translation for the "Delete my account" setting.
- **Style**
  - Updated styles for modal footers and action buttons on mobile.
  - Added styling for account deletion dialog descriptions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-03 10:17:45 +00:00
forehalo
ee931d546e fix(server): oauth should follow sign up restriction (#12683)
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
	- Enforced signup restrictions for OAuth login based on configuration settings. Users will not be able to sign up via OAuth if signup is disabled by the administrator.
- **Bug Fixes**
	- Improved error handling during OAuth login when signup is not permitted.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-03 08:53:00 +00:00
aki-chang-dev
a02eed382d feat(android): chat base feature (#12684)
- **feat(android): chat send & receive**
- **[WIP] feat(android): markdown style for chat**
- **fix(android): fix auto scroll & ai message id replacement**
- **feat(android): replace icons**
- **refactor(android): design system**
- **feat(android): markdown style for chat**

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

- **New Features**
  - Introduced a comprehensive custom theme system with new color palettes, typography, and theme modes (Light, Dark, System).
  - Added support for rendering Markdown-formatted text in chat messages with custom styling.
  - Integrated new vector icons for UI elements such as lists, camera, image, send, close, and more.
  - Added composable icon and icon button components for consistent icon usage across the app.

- **Enhancements**
  - Updated chat UI to use the new theme, icons, and Markdown rendering for AI messages.
  - Improved chat message management and send button state handling with enhanced session retrieval and SSE stream processing.
  - Refined app bar and dropdown menu components with updated icons and theme integration.
  - Enhanced floating action button appearance with tinted vector drawable.
  - Unified UI components and styling under the AFFiNE design system in chat input and app bars.

- **Bug Fixes**
  - Corrected application and theme class naming for consistency.

- **Chores**
  - Added new dependencies for rich text and Markdown support.
  - Updated color and icon resources for a unified visual style.
  - Removed deprecated headers from authentication requests.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-03 07:35:48 +00:00
L-Sun
ab78b8e3ab fix(editor): playground init error (#12565)
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **Bug Fixes**
	- Improved stability when observing document title changes by ensuring internal checks before updating.
	- Enhanced document initialization to reuse existing documents when available, reducing unnecessary duplication and improving performance.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-03 07:18:29 +00:00
yoyoyohamapi
3fe2ac4e46 refactor(core): add to edgeless as note icon (#12656)
### TL;DR

refactor(core): add to edgeless as note icon

> CLOSE AI-152

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

- **Style**
  - Updated the icon for the "Add to Edgeless as Note" chat action to improve visual representation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-03 07:02:04 +00:00
Brooooooklyn
d02aa8c7e0 fix(native): opt out napi-derive noop feature (#12686)
It would cause the napi-derive not work as expect in workspace level

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

- **Refactor**
  - Improved internal handling and type definitions for document parsing, resulting in clearer and more maintainable data structures.
- **Chores**
  - Introduced a new feature flag for mobile native builds, enabling conditional compilation for enhanced flexibility across Android and iOS.
  - Updated build scripts to support the new feature flag for both Android and iOS platforms.
  - Updated iOS app dependencies to newer versions, including Apollo iOS, ChidoriMenu, and swift-collections, and removed SQLite.swift.
- **Tests**
  - Enhanced Rust linting and testing workflows to run selectively across workspace packages with the new feature flag enabled.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-03 06:46:55 +00:00
akumatus
cce756365a feat(core): use claude 4 as default chat model (#12596)
Support [AI-59](https://linear.app/affine-design/issue/AI-59)

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

- **New Features**
  - Updated the default AI model for chat prompts to use Claude Sonnet 4.
- **Bug Fixes**
  - Improved model selection logic to better support reasoning features across more AI models.
- **Tests**
  - Enhanced test cases with consistent instructions for response length.
  - Skipped certain chat-related tests to refine test suite stability.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-03 06:32:27 +00:00
yoyoyohamapi
a88dcc0951 fix(core): copy & paste ai message failed (#12655)
### TL;DR
* fix: ai message copy bug
  * Select a section of content in the Page
  * Choose the user's question from the AI chat conversation history and copy it
  * The copied (pasted) content will be the selected section from the Page

* fix: ai message paste bug
  * Select a section of content in the Page
  * Choose the user's question from the AI chat conversation history and copy it
  * Paste it into the AI Input, and the content will be pasted back into the original Page text

> CLOSE AF-2683

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

- **Bug Fixes**
  - Improved handling of copy and paste events in chat components to prevent unintended interactions with surrounding elements.
  - Enhanced test stability by adding error handling during embedding progress checks.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-03 06:09:44 +00:00
darkskygit
57208a3de4 fix(server): lost context after merge template (#12682)
fix AI-163
fix AI-164

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

## Summary by CodeRabbit

- **Tests**
  - Added a new test to verify multi-turn chat interactions, ensuring accurate handling of chat history and correct responses for translation and explanation requests.
- **Bug Fixes**
  - Improved chat session logic to better merge user messages and attachments, enhancing the accuracy and continuity of multi-step conversations.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-03 05:46:18 +00:00
L-Sun
d8cbeb1bb1 fix(editor): can move frame by dragging title (#12661)
Close [BS-3351](https://linear.app/affine-design/issue/BS-3351/无法通过拖拽frame-title来拖拽frame)

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

- **New Features**
  - Improved rendering performance and consistency for widgets within frames.
  - Frame titles are now directly associated with individual frames and are draggable.

- **Bug Fixes**
  - Selection logic for frames has been refined to better handle locked states and title area interactions.

- **Refactor**
  - Frame title widget and related components have been simplified for clarity and maintainability.
  - Removed dynamic positioning and click toggling from frame titles for a cleaner interaction model.

- **Tests**
  - Added a test to verify that frame titles are draggable.
  - Temporarily disabled tests related to frame title stacking and selection due to ongoing changes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-03 05:14:39 +00:00
yoyoyohamapi
418b38e8de test(core): support fast embedding progress (#12685)
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **Tests**
  - Improved test stability by handling potential errors during embedding progress checks in end-to-end tests.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-03 05:00:21 +00:00
doouding
00ff373c01 fix: tuning drag and resize snapping (#12657)
### Changed
- Better snapping when resize elements

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

- **New Features**
  - Improved resizing behavior with enhanced alignment and snapping during element resizing, supporting rotation and multiple element selection.
  - Alignment lines now display more accurately when resizing elements.

- **Refactor**
  - Resizing logic updated to use scale factors instead of position deltas, enabling smoother and more precise resize operations.
  - Resize event data now includes richer details about handle positions, scaling, and original bounds.
  - Coordinate transformations and scaling now account for rotation and aspect ratio locking more robustly.
  - Cursor updates are disabled during active resize or rotate interactions for a smoother user experience.

- **Tests**
  - Updated resizing tests to use square shapes, ensuring consistent verification of aspect ratio maintenance.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-03 04:12:57 +00:00
darkskygit
39830a410a feat(server): add metrics for copilot job event (#12575)
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **Chores**
	- Improved internal monitoring for AI embedding operations to enhance reliability and performance tracking. No changes to user-facing features.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-03 03:37:02 +00:00
fundon
ef3be4a816 fix(editor): font weight of label on open doc menu (#12672)
Closes: [BS-3496](https://linear.app/affine-design/issue/BS-3496/toolbar-菜单字重)

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

## Summary by CodeRabbit

- **Style**
  - Updated dropdown menu appearance by removing bold styling from button labels.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-03 03:22:18 +00:00
fundon
658393159b fix(editor): should check url origin and ip address url (#12663)
Closes: [BS-3578](https://linear.app/affine-design/issue/BS-3578/复制本的-url-无法识别)

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

- **New Features**
  - Improved URL validation to recognize and allow IPv4 addresses when the origin matches the provided base URL.

- **Tests**
  - Added a test to ensure URLs with IP addresses (e.g., http://127.0.0.1) are considered valid when the origin matches.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-03 03:08:17 +00:00
renovate[bot]
ac3f247f01 chore: bump up apollographql/apollo-ios version to v1.22.0 (#12670)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-03 10:32:12 +08:00
LongYinan
065d9c3b73 ci: fix release-mobile pipeline 2025-06-02 13:28:13 +08:00
LongYinan
2e58c11799 ci: do not use namespace runner anymore 2025-06-01 14:54:12 +08:00
L-Sun
10da3ad28e fix(editor): update card style after dragging it to note (#12660)
Close [BS-3148](https://linear.app/affine-design/issue/BS-3148/拖拽到note后,更新card样式)

### What Changes
- fix the style of card not updated after draggin it from canvas to note
- narrow type of specific card style by using `as const satisfies EmbedCardStyle[]`
- add type hint to the `props`, the second parameter  of `store.updateBlock`

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

- **New Features**
  - Added middleware to automatically update card styles when dragging blocks into notes.
- **Bug Fixes**
  - Ensured that dragging a bookmark card into a note preserves its style.
- **Tests**
  - Introduced an end-to-end test to verify bookmark card style is retained after drag-and-drop.
- **Refactor**
  - Enhanced type safety and clarity for card style configurations and block properties.
- **Chores**
  - Refined type annotations and assertions across multiple block style constants and toolbar configurations.
  - Improved generic typing for block update methods to increase type precision.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-30 10:55:29 +00:00
darkskygit
887a496f8b feat(server): add attachment fallback for ai sdk (#12639)
fix AI-161
2025-05-30 08:39:32 +00:00
darkskygit
ada69c80f6 feat(server): only trigger embedding in workspace sync (#12634)
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **Bug Fixes**
  - Improved handling of workspace embedding events to ensure they are only triggered for workspace-type spaces.

- **Chores**
  - Added additional debug logging for document embedding jobs to aid in monitoring and troubleshooting.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-30 08:04:18 +00:00
doufa
7b82dd656b fix(editor): connector not added as frame child (#12611)
Co-authored-by: L-Sun <zover.v@gmail.com>
2025-05-30 13:29:42 +08:00
EYHN
5c96566dd8 feat(core): save all docs options by mode (#12654)
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **Refactor**
  - Improved state management for display preferences, view mode, and selected collection in the "All Docs" page, making the experience more modular and consistent, especially when using multiple views.
  - Updated the header component to handle view changes more directly, allowing smoother toggling between different document views.

- **New Features**
  - Enhanced support for independent display settings in split view or multiple "All Docs" instances.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-30 04:42:27 +00:00
zzj3720
a35e1b1882 feat(editor): add database filter event tracking (#12645)
close: BS-3568

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

## Summary by CodeRabbit

- **New Features**
	- Added event tracking for filter creation in database views to improve activity monitoring and analytics.

- **Chores**
	- Updated internal event types to support new database view tracking.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-30 04:27:31 +00:00
yoyoyohamapi
756847d3cb fix(core): prevent ai input tip loop-play (#12600)
### TL;DR

* fix(core): prevent ai input tip loop-play

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

- **New Features**
  - Added an option to control whether tips in the AI chat composer scroll continuously or stop after the last tip.

- **Style**
  - Improved layout and spacing in the embedding status tooltip for better readability and alignment.

- **Refactor**
  - Updated the structure of elements in the embedding status tooltip for more consistent formatting.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-30 03:40:11 +00:00
forehalo
3c3a8bb107 feat(server): time duration helper (#12562)
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Introduced support for parsing and converting duration strings (e.g., "1h30m") into milliseconds and seconds.
  - Added utility methods to handle a wide range of time units and their combinations.
  - Added functions to calculate dates offset before or after a given date by specified durations.
- **Tests**
  - Implemented comprehensive automated tests to ensure accurate parsing and conversion of duration strings.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-30 03:21:31 +00:00
forehalo
88eec2cdfb chore(server): disable version check for oauth callback (#12640)
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **Bug Fixes**
  - Improved Apple OAuth login reliability by ensuring client version checks do not block the callback process.

- **New Features**
  - Enhanced OAuth account information by including an optional display name field.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-30 03:05:38 +00:00
renovate
52777b0064 chore: bump up @types/mime-types version to v3 (#12653)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [@types/mime-types](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/mime-types) ([source](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/mime-types)) | [`^2.1.4` -> `^3.0.0`](https://renovatebot.com/diffs/npm/@types%2fmime-types/2.1.4/3.0.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@types%2fmime-types/3.0.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@types%2fmime-types/3.0.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@types%2fmime-types/2.1.4/3.0.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@types%2fmime-types/2.1.4/3.0.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) |

---

### Configuration

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

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

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

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

---

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

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/toeverything/AFFiNE).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC4zMy42IiwidXBkYXRlZEluVmVyIjoiNDAuMzMuNiIsInRhcmdldEJyYW5jaCI6ImNhbmFyeSIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->
2025-05-30 02:49:07 +00:00
JimmFly
00ccd2d865 chore: display join button text based on invitation type (#12650)
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
  - The button on the Request to Join page now dynamically updates its label to show "accept invitation" when an invitation is pending, improving clarity for users responding to workspace invites.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-30 02:18:25 +00:00
doodlewind
5d94bd41a4 feat(editor): support triangle and diamond shape in shape dom renderer (#12331)
![image.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/lEGcysB4lFTEbCwZ8jMv/ebfcee12-cebb-4b98-81e2-f9f670b4de96.png)

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

- **New Features**
  - Improved rendering for diamond and triangle shapes using SVG, resulting in more accurate stroke and fill display.
- **Bug Fixes**
  - Ensured background and border styles do not interfere with SVG-based shapes.
- **Tests**
  - Added tests to verify correct DOM rendering for diamond and triangle shapes.
- **Refactor**
  - Streamlined and clarified the rendering logic for polygonal shapes, separating SVG and CSS rendering paths.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-30 02:04:09 +00:00
fundon
20d8d6131a fix(editor): text color of buttons on toolbar (#12642)
Closes: [BS-3574](https://linear.app/affine-design/issue/BS-3574/affine-light-模式,画板dark-模式,toolbar配色崩坏)

<img width="1068" alt="Screenshot 2025-05-29 at 17 46 38" src="https://github.com/user-attachments/assets/66a731dc-0bc6-4b0c-9712-787a78525ddf" />
<img width="1095" alt="Screenshot 2025-05-29 at 17 46 17" src="https://github.com/user-attachments/assets/3317ea83-837f-4c50-abee-ebb859fce3d9" />
<img width="1075" alt="Screenshot 2025-05-29 at 17 46 05" src="https://github.com/user-attachments/assets/3291810b-3aa1-4fce-aa8b-415be5e10c46" />
<img width="1096" alt="Screenshot 2025-05-29 at 17 45 54" src="https://github.com/user-attachments/assets/a5ad5e41-4eb9-4578-85a6-c6b773a03da9" />

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

## Summary by CodeRabbit

- **Style**
  - Updated toolbar theme styles to include an additional color variable for improved customization.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-30 01:47:50 +00:00
doodlewind
94539ac0d0 perf(editor): lazy rendering for dom renderer (#12638)
Before (brush updated even when it's not being dragged):

https://github.com/user-attachments/assets/e56ce326-56ae-4cac-a5f8-86be35fd8fcd

After (fine-grained element level update):

https://github.com/user-attachments/assets/712f4e22-0830-455d-bbe1-0f575e8920ac

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

- **New Features**
  - Improved rendering performance by introducing incremental updates, ensuring only changed elements are updated instead of re-rendering everything.
  - Enhanced responsiveness when elements are added, removed, or updated, as well as during viewport, size, or zoom changes.

- **Bug Fixes**
  - Reduced unnecessary full re-renders, leading to smoother and more efficient user interactions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-30 01:32:36 +00:00
Brooooooklyn
e1ce42a6fc feat(native): upgrade NAPI-RS to 3.0.0 beta (#12652)
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
	- Added a default export for the native binding in the frontend native module, allowing easier imports.

- **Refactor**
	- Streamlined and updated Rust-to-JavaScript type conversions and lifetime handling for improved safety and consistency.
	- Improved object and array construction in Rust modules for more idiomatic usage.
	- Simplified boolean and null value handling in JavaScript interop layers.

- **Chores**
	- Upgraded several dependencies and development tools to newer versions across backend, frontend, and common packages.
	- Updated build scripts for the frontend native package to simplify commands.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-29 16:09:32 +00:00
renovate
2a7f0162cf chore: bump up nestjs-cls version to v6 (#12648)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [nestjs-cls](https://papooch.github.io/nestjs-cls/) ([source](https://redirect.github.com/Papooch/nestjs-cls)) | [`^5.0.0` -> `^6.0.0`](https://renovatebot.com/diffs/npm/nestjs-cls/5.4.3/6.0.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/nestjs-cls/6.0.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/nestjs-cls/6.0.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/nestjs-cls/5.4.3/6.0.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/nestjs-cls/5.4.3/6.0.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) |

---

### Release Notes

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

### [`v6.0.0`](https://redirect.github.com/Papooch/nestjs-cls/releases/tag/nestjs-cls%406.0.0)

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

##### Breaking Changes

-   The experimental Plugin API has been changed ([4623607](https://redirect.github.com/Papooch/nestjs-cls/commits/4623607))
-   Access to Proxy providers moved to a dedicated `proxy` property on the ClsService ([82cdeef](https://redirect.github.com/Papooch/nestjs-cls/commits/82cdeef))

##### Features

-   **core**: introduce hooks for the Plugin API ([#&#8203;283](https://redirect.github.com/Papooch/nestjs-cls/issues/283)) ([4623607](https://redirect.github.com/Papooch/nestjs-cls/commits/4623607))

</details>

---

### Configuration

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

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

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

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

---

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

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/toeverything/AFFiNE).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC4zMy42IiwidXBkYXRlZEluVmVyIjoiNDAuMzMuNiIsInRhcmdldEJyYW5jaCI6ImNhbmFyeSIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->
2025-05-29 15:45:33 +00:00
renovate
34a5d9dec3 chore: bump up @nestjs-cls/transactional-adapter-prisma version to v1.2.21 (#12643)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [@nestjs-cls/transactional-adapter-prisma](https://papooch.github.io/nestjs-cls/) ([source](https://redirect.github.com/Papooch/nestjs-cls)) | [`1.2.20` -> `1.2.21`](https://renovatebot.com/diffs/npm/@nestjs-cls%2ftransactional-adapter-prisma/1.2.20/1.2.21) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@nestjs-cls%2ftransactional-adapter-prisma/1.2.21?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@nestjs-cls%2ftransactional-adapter-prisma/1.2.21?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@nestjs-cls%2ftransactional-adapter-prisma/1.2.20/1.2.21?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.20/1.2.21?slim=true)](https://docs.renovatebot.com/merge-confidence/) |

---

### Release Notes

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

### [`v1.2.21`](https://redirect.github.com/Papooch/nestjs-cls/compare/@nestjs-cls/transactional-adapter-prisma@1.2.20...@nestjs-cls/transactional-adapter-prisma@1.2.21)

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

</details>

---

### Configuration

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

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

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

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

---

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

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/toeverything/AFFiNE).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC4zMy42IiwidXBkYXRlZEluVmVyIjoiNDAuMzMuNiIsInRhcmdldEJyYW5jaCI6ImNhbmFyeSIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->
2025-05-29 15:29:07 +00:00
renovate
c68598c0e0 chore: bump up opentelemetry (#12183)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [@opentelemetry/exporter-prometheus](https://redirect.github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-exporter-prometheus) ([source](https://redirect.github.com/open-telemetry/opentelemetry-js)) | [`^0.57.0` -> `^0.201.0`](https://renovatebot.com/diffs/npm/@opentelemetry%2fexporter-prometheus/0.57.2/0.201.1) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@opentelemetry%2fexporter-prometheus/0.201.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@opentelemetry%2fexporter-prometheus/0.201.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@opentelemetry%2fexporter-prometheus/0.57.2/0.201.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@opentelemetry%2fexporter-prometheus/0.57.2/0.201.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) |
| [@opentelemetry/host-metrics](https://redirect.github.com/open-telemetry/opentelemetry-js-contrib/tree/main/packages/opentelemetry-host-metrics#readme) ([source](https://redirect.github.com/open-telemetry/opentelemetry-js-contrib)) | [`^0.35.4` -> `^0.36.0`](https://renovatebot.com/diffs/npm/@opentelemetry%2fhost-metrics/0.35.5/0.36.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@opentelemetry%2fhost-metrics/0.36.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@opentelemetry%2fhost-metrics/0.36.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@opentelemetry%2fhost-metrics/0.35.5/0.36.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@opentelemetry%2fhost-metrics/0.35.5/0.36.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) |
| [@opentelemetry/instrumentation](https://redirect.github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-instrumentation) ([source](https://redirect.github.com/open-telemetry/opentelemetry-js)) | [`^0.57.0` -> `^0.201.0`](https://renovatebot.com/diffs/npm/@opentelemetry%2finstrumentation/0.57.2/0.201.1) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@opentelemetry%2finstrumentation/0.201.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@opentelemetry%2finstrumentation/0.201.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@opentelemetry%2finstrumentation/0.57.2/0.201.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@opentelemetry%2finstrumentation/0.57.2/0.201.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) |
| [@opentelemetry/instrumentation-graphql](https://redirect.github.com/open-telemetry/opentelemetry-js-contrib/tree/main/plugins/node/opentelemetry-instrumentation-graphql#readme) ([source](https://redirect.github.com/open-telemetry/opentelemetry-js-contrib)) | [`^0.47.0` -> `^0.49.0`](https://renovatebot.com/diffs/npm/@opentelemetry%2finstrumentation-graphql/0.47.1/0.49.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@opentelemetry%2finstrumentation-graphql/0.49.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@opentelemetry%2finstrumentation-graphql/0.49.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@opentelemetry%2finstrumentation-graphql/0.47.1/0.49.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@opentelemetry%2finstrumentation-graphql/0.47.1/0.49.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) |
| [@opentelemetry/instrumentation-http](https://redirect.github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-instrumentation-http) ([source](https://redirect.github.com/open-telemetry/opentelemetry-js)) | [`^0.57.0` -> `^0.201.0`](https://renovatebot.com/diffs/npm/@opentelemetry%2finstrumentation-http/0.57.2/0.201.1) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@opentelemetry%2finstrumentation-http/0.201.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@opentelemetry%2finstrumentation-http/0.201.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@opentelemetry%2finstrumentation-http/0.57.2/0.201.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@opentelemetry%2finstrumentation-http/0.57.2/0.201.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) |
| [@opentelemetry/instrumentation-ioredis](https://redirect.github.com/open-telemetry/opentelemetry-js-contrib/tree/main/plugins/node/opentelemetry-instrumentation-ioredis#readme) ([source](https://redirect.github.com/open-telemetry/opentelemetry-js-contrib)) | [`^0.47.0` -> `^0.49.0`](https://renovatebot.com/diffs/npm/@opentelemetry%2finstrumentation-ioredis/0.47.1/0.49.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@opentelemetry%2finstrumentation-ioredis/0.49.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@opentelemetry%2finstrumentation-ioredis/0.49.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@opentelemetry%2finstrumentation-ioredis/0.47.1/0.49.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@opentelemetry%2finstrumentation-ioredis/0.47.1/0.49.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) |
| [@opentelemetry/instrumentation-nestjs-core](https://redirect.github.com/open-telemetry/opentelemetry-js-contrib/tree/main/plugins/node/opentelemetry-instrumentation-nestjs-core#readme) ([source](https://redirect.github.com/open-telemetry/opentelemetry-js-contrib)) | [`^0.44.0` -> `^0.47.0`](https://renovatebot.com/diffs/npm/@opentelemetry%2finstrumentation-nestjs-core/0.44.1/0.47.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@opentelemetry%2finstrumentation-nestjs-core/0.47.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@opentelemetry%2finstrumentation-nestjs-core/0.47.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@opentelemetry%2finstrumentation-nestjs-core/0.44.1/0.47.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@opentelemetry%2finstrumentation-nestjs-core/0.44.1/0.47.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) |
| [@opentelemetry/instrumentation-socket.io](https://redirect.github.com/open-telemetry/opentelemetry-js-contrib/tree/main/plugins/node/instrumentation-socket.io#readme) ([source](https://redirect.github.com/open-telemetry/opentelemetry-js-contrib)) | [`^0.46.0` -> `^0.48.0`](https://renovatebot.com/diffs/npm/@opentelemetry%2finstrumentation-socket.io/0.46.1/0.48.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@opentelemetry%2finstrumentation-socket.io/0.48.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@opentelemetry%2finstrumentation-socket.io/0.48.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@opentelemetry%2finstrumentation-socket.io/0.46.1/0.48.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@opentelemetry%2finstrumentation-socket.io/0.46.1/0.48.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) |
| [@opentelemetry/sdk-node](https://redirect.github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-sdk-node) ([source](https://redirect.github.com/open-telemetry/opentelemetry-js)) | [`^0.57.0` -> `^0.201.0`](https://renovatebot.com/diffs/npm/@opentelemetry%2fsdk-node/0.57.2/0.201.1) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@opentelemetry%2fsdk-node/0.201.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@opentelemetry%2fsdk-node/0.201.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@opentelemetry%2fsdk-node/0.57.2/0.201.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@opentelemetry%2fsdk-node/0.57.2/0.201.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) |
| [@opentelemetry/semantic-conventions](https://redirect.github.com/open-telemetry/opentelemetry-js/tree/main/semantic-conventions) ([source](https://redirect.github.com/open-telemetry/opentelemetry-js)) | [`1.33.0` -> `1.34.0`](https://renovatebot.com/diffs/npm/@opentelemetry%2fsemantic-conventions/1.33.0/1.34.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@opentelemetry%2fsemantic-conventions/1.34.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@opentelemetry%2fsemantic-conventions/1.34.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@opentelemetry%2fsemantic-conventions/1.33.0/1.34.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@opentelemetry%2fsemantic-conventions/1.33.0/1.34.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) |

---

### Release Notes

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

### [`v0.201.1`](4ce5bd1651...9dbd1e446b)

[Compare Source](4ce5bd1651...9dbd1e446b)

### [`v0.201.0`](7fde94081e...4ce5bd1651)

[Compare Source](7fde94081e...4ce5bd1651)

### [`v0.200.0`](ac8641a5db...7fde94081e)

[Compare Source](ac8641a5db...7fde94081e)

</details>

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

### [`v0.36.0`](d4d3c4f14f...32abc4c3c0)

[Compare Source](d4d3c4f14f...32abc4c3c0)

</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:eyJjcmVhdGVkSW5WZXIiOiI0MC43LjEiLCJ1cGRhdGVkSW5WZXIiOiI0MC4zMy42IiwidGFyZ2V0QnJhbmNoIjoiY2FuYXJ5IiwibGFiZWxzIjpbImRlcGVuZGVuY2llcyJdfQ==-->
2025-05-29 15:10:07 +00:00
Flrande
9c81c24fbe fix(editor): clear selection after toggle latex editor (#12637)
- **fix(editor): clear selection after toggle latex editor**
- **chore: remove useless test**

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

- **New Features**
  - LaTeX rendering now outputs MathML format for improved accessibility and compatibility.
  - Added support for KaTeX styling to enhance LaTeX display in the playground.

- **Bug Fixes**
  - Improved editor behavior by resetting the selection group before opening the LaTeX editor.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-29 14:37:41 +00:00
congzhou09
517aec79ba fix(editor): invoke subscriber.unsubscribe() during cleanup (#12628) 2025-05-29 22:30:55 +08:00
fengmk2
31a1841e25 chore(server): log removed job id (#12646)
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **Chores**
	- Improved log messages to include job IDs when jobs are removed from the queue, enhancing traceability for users monitoring job activity.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-29 11:48:16 +00:00
L-Sun
625e8392a6 fix(editor): missing block in select-all set (#12627)
This PR fixed that the block is missing in the selecte-all set after undo a dragging from canvas to note. Related to #12473

### Before

https://github.com/user-attachments/assets/828b4f48-689a-4975-bba6-f380f324de3c

### After

https://github.com/user-attachments/assets/9996c1ca-c3ea-415c-ab2b-359d826a1ffa

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

- **Bug Fixes**
  - Improved handling of changes to child elements, ensuring more accurate updates when items are added or removed. This results in more reliable display and interaction with nested components.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-29 11:04:01 +00:00
pengx17
f616bd29d3 fix(core): adjust some uis for sharing (#12486)
fix AF-2660

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

## Summary by CodeRabbit

- **Style**
  - Improved visual appearance of sidebar buttons and quick search input, including reduced sizes, updated padding, and enhanced hover effects.
  - Adjusted layout spacing for quick search and new page elements in the sidebar.
  - Updated share button styling to use the primary variant.

- **New Features**
  - Notification cards now only display messages and action footers when relevant, providing a cleaner interface.

- **Refactor**
  - Removed shortcut hint and spotlight elements from the quick search input for a simplified user experience.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-29 09:40:11 +00:00
CatsJuice
d6b9e9c60a feat(mobile): share page support (#12351)
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
  - Improved server context awareness for workspaces on mobile web.
  - Enhanced handling for missing workspaces by displaying a share page when accessing a document detail route in mobile web environments.

- **Bug Fixes**
  - Workspace list now refreshes automatically when switching workspace IDs.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-29 09:16:13 +00:00
donteatfriedrice
bc67766bb9 fix(editor): cleanup transformer middleware slot subscriptions (#12630)
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
  - Improved resource management by introducing explicit cleanup for various middleware components, ensuring that resources are properly released when no longer needed.

- **Refactor**
  - Updated middleware logic to support cleanup functions, enhancing the stability and performance of the application by preventing potential memory leaks.

- **Chores**
  - Enhanced lifecycle management in core systems to automatically dispose of resources when appropriate.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-29 08:33:30 +00:00
L-Sun
9a96cfded0 fix(editor): viewportElement is undefined in edgeless root block (#12626)
This PR fixed that `rootComponent.viewportElement` is undefeined in edgeless mode, which leads that toast can not be render in playground.

388641bc89/blocksuite/affine/components/src/toast/create.ts (L23-L35)

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

## Summary by CodeRabbit

- **Refactor**
  - Improved internal code organization for better maintainability. No changes to visible features or functionality.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-29 08:10:54 +00:00
L-Sun
77392efaa2 chore(editor): remove feature flag of embed doc with alias (#12620)
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
  - Toolbar actions related to embedding and duplicating documents are now always available without restrictions.

- **Chores**
  - Removed the feature flag controlling embed document alias features for a simpler user experience.

- **Tests**
  - Updated test setup to remove reliance on the deprecated feature flag.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-29 07:55:52 +00:00
L-Sun
927b4f4430 chore(editor): adjust format of date time in slash menu (#12631)
Closes: #12624

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

## Summary by CodeRabbit

- **Refactor**
  - Updated the time formatting to display dates as "yyyy-mm-dd hh:mm" instead of "mm-dd hh:mm".

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-29 07:32:35 +00:00
renovate
9ec1d08d98 chore: bump up @chromatic-com/storybook version to v4 (#12618)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [@chromatic-com/storybook](https://redirect.github.com/chromaui/addon-visual-tests) | [`^3.2.2` -> `^4.0.0`](https://renovatebot.com/diffs/npm/@chromatic-com%2fstorybook/3.2.6/4.0.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@chromatic-com%2fstorybook/4.0.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@chromatic-com%2fstorybook/4.0.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@chromatic-com%2fstorybook/3.2.6/4.0.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@chromatic-com%2fstorybook/3.2.6/4.0.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) |

---

### Release Notes

<details>
<summary>chromaui/addon-visual-tests (@&#8203;chromatic-com/storybook)</summary>

### [`v4.0.0`](https://redirect.github.com/chromaui/addon-visual-tests/compare/v3.2.6...814ef25cc6d4fd763d089f67b21f8b56429d6512)

[Compare Source](https://redirect.github.com/chromaui/addon-visual-tests/compare/v3.2.6...v4.0.0)

</details>

---

### Configuration

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

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

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

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

---

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

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/toeverything/AFFiNE).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC4zMy42IiwidXBkYXRlZEluVmVyIjoiNDAuMzMuNiIsInRhcmdldEJyYW5jaCI6ImNhbmFyeSIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->
2025-05-29 07:17:38 +00:00
JimmFly
86cd92a878 fix(core): add loading status to share page button (#12288)
close AF-2615

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

## Summary by CodeRabbit

- **Enhancements**
  - Improved the share menu's user experience by showing a loading indicator and disabling the public page button during revalidation. This prevents user interaction while the share info is updating.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-29 07:02:43 +00:00
359 changed files with 7099 additions and 3858 deletions

View File

@@ -886,8 +886,8 @@
"properties": {
"enabled": {
"type": "boolean",
"description": "Enable indexer plugin\n@default true\n@environment `AFFINE_INDEXER_ENABLED`",
"default": true
"description": "Enable indexer plugin\n@default false\n@environment `AFFINE_INDEXER_ENABLED`",
"default": false
},
"provider.type": {
"type": "string",

View File

@@ -95,11 +95,13 @@ spec:
path: /info
port: http
initialDelaySeconds: {{ .Values.probe.initialDelaySeconds }}
timeoutSeconds: {{ .Values.probe.timeoutSeconds }}
readinessProbe:
httpGet:
path: /info
port: http
initialDelaySeconds: {{ .Values.probe.initialDelaySeconds }}
timeoutSeconds: {{ .Values.probe.timeoutSeconds }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.nodeSelector }}

View File

@@ -36,6 +36,7 @@ resources:
probe:
initialDelaySeconds: 20
timeoutSeconds: 5
nodeSelector: {}
tolerations: []

View File

@@ -113,6 +113,7 @@ jobs:
build-server-native:
name: Build Server native - ${{ matrix.targets.name }}
runs-on: ubuntu-latest
environment: ${{ github.event.inputs.flavor }}
strategy:
fail-fast: false
matrix:

View File

@@ -20,6 +20,7 @@ env:
COVERAGE: true
MACOSX_DEPLOYMENT_TARGET: '10.13'
DEPLOYMENT_TYPE: affine
AFFINE_INDEXER_ENABLED: true
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -151,7 +152,8 @@ jobs:
- name: Clippy
run: |
rustup component add clippy
cargo clippy --all-targets --all-features -- -D warnings
cargo clippy --workspace --exclude affine_server_native --all-targets --all-features -- -D warnings
cargo clippy -p affine_server_native --all-targets --all-features -- -D warnings
check-git-status:
name: Check Git Status
@@ -825,7 +827,6 @@ jobs:
- optimize_ci
if: needs.optimize_ci.outputs.skip == 'false'
env:
RUSTFLAGS: -D warnings
CARGO_TERM_COLOR: always
steps:
- uses: actions/checkout@v4
@@ -923,7 +924,7 @@ jobs:
uses: taiki-e/install-action@nextest
- name: Run tests
run: cargo nextest run --release --no-fail-fast
run: cargo nextest run --workspace --exclude affine_server_native --features use-as-lib --release --no-fail-fast
copilot-api-test:
name: Server Copilot Api Test

View File

@@ -117,31 +117,10 @@ jobs:
name: android
path: packages/frontend/apps/android/dist
determine-ios-runner:
runs-on: ubuntu-latest
ios:
runs-on: ${{ github.ref_name == 'canary' && 'macos-latest' || 'blaze/macos-14' }}
needs:
- build-ios-web
outputs:
RUNNER: ${{ steps.runner.outputs.RUNNER }}
steps:
- name: Determine Runner
id: runner
# Randomly pick runner with 80% chance for blaze/macos-14 and 20% chance for namespace-profile-macos
# blaze/macos-14 is free but has limited concurrency
run: |
RANDOM_NUMBER=$(( $RANDOM % 100 + 1 ))
if [ $RANDOM_NUMBER -le 20 ]; then
echo "Selected namespace-profile-macos (20% probability)"
echo "RUNNER=namespace-profile-macos" >> $GITHUB_OUTPUT
else
echo "Selected blaze/macos-14 (80% probability)"
echo "RUNNER=blaze/macos-14" >> $GITHUB_OUTPUT
fi
ios:
runs-on: ${{ github.ref_name == 'canary' && 'macos-latest' || needs.determine-ios-runner.outputs.RUNNER }}
needs:
- determine-ios-runner
steps:
- uses: actions/checkout@v4
- name: Download mobile artifact

611
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -47,9 +47,9 @@ log = "0.4"
loom = { version = "0.7", features = ["checkpoint"] }
mimalloc = "0.1"
nanoid = "0.4"
napi = { version = "3.0.0-alpha.31", features = ["async", "chrono_date", "error_anyhow", "napi9", "serde"] }
napi = { version = "3.0.0-beta.3", features = ["async", "chrono_date", "error_anyhow", "napi9", "serde"] }
napi-build = { version = "2" }
napi-derive = { version = "3.0.0-alpha.28" }
napi-derive = { version = "3.0.0-beta.3" }
nom = "8"
notify = { version = "8", features = ["serde"] }
objc2 = "0.6"
@@ -77,12 +77,12 @@ smol_str = "0.3"
sqlx = { version = "0.8", default-features = false, features = ["chrono", "macros", "migrate", "runtime-tokio", "sqlite", "tls-rustls"] }
strum_macros = "0.27.0"
symphonia = { version = "0.5", features = ["all", "opt-simd"] }
text-splitter = "0.25"
text-splitter = "0.27"
thiserror = "2"
tiktoken-rs = "0.6"
tokio = "1.37"
tiktoken-rs = "0.7"
tokio = "1.45"
tree-sitter = { version = "0.25" }
tree-sitter-c = { version = "0.23" }
tree-sitter-c = { version = "0.24" }
tree-sitter-c-sharp = { version = "0.23" }
tree-sitter-cpp = { version = "0.23" }
tree-sitter-go = { version = "0.23" }

View File

@@ -17,11 +17,15 @@ import {
AttachmentBlockStyles,
} from '@blocksuite/affine-model';
import {
CitationProvider,
DocModeProvider,
FileSizeLimitProvider,
TelemetryProvider,
} from '@blocksuite/affine-shared/services';
import { formatSize } from '@blocksuite/affine-shared/utils';
import {
formatSize,
openSingleFileWith,
} from '@blocksuite/affine-shared/utils';
import {
AttachmentIcon,
ResetIcon,
@@ -30,17 +34,18 @@ import {
} from '@blocksuite/icons/lit';
import { BlockSelection } from '@blocksuite/std';
import { nanoid, Slice } from '@blocksuite/store';
import { computed, signal } from '@preact/signals-core';
import { batch, computed, signal } from '@preact/signals-core';
import { html, type TemplateResult } from 'lit';
import { choose } from 'lit/directives/choose.js';
import { type ClassInfo, classMap } from 'lit/directives/class-map.js';
import { guard } from 'lit/directives/guard.js';
import { styleMap } from 'lit/directives/style-map.js';
import { when } from 'lit/directives/when.js';
import { filter } from 'rxjs/operators';
import { AttachmentEmbedProvider } from './embed';
import { styles } from './styles';
import { downloadAttachmentBlob, refreshData } from './utils';
import { downloadAttachmentBlob, getFileType, refreshData } from './utils';
type AttachmentResolvedStateInfo = ResolvedStateInfo & {
kind?: TemplateResult;
@@ -79,8 +84,12 @@ export class AttachmentBlockComponent extends CaptionedBlockComponent<Attachment
return this.std.get(FileSizeLimitProvider).maxFileSize;
}
get citationService() {
return this.std.get(CitationProvider);
}
get isCitation() {
return !!this.model.props.footnoteIdentifier;
return this.citationService.isCitationModel(this.model);
}
convertTo = () => {
@@ -123,12 +132,50 @@ export class AttachmentBlockComponent extends CaptionedBlockComponent<Attachment
// Refreshes the embed component.
reload = () => {
if (this.model.props.embed) {
this._refreshKey$.value = nanoid();
return;
}
batch(() => {
if (this.model.props.embed$.value) {
this._refreshKey$.value = nanoid();
return;
}
this.refreshData();
this.refreshData();
});
};
// Replaces the current attachment.
replace = async () => {
const state = this.resourceController.state$.peek();
if (state.uploading) return;
const file = await openSingleFileWith();
if (!file) return;
const sourceId = await this.std.store.blobSync.set(file);
const type = await getFileType(file);
const { name, size } = file;
let embed = this.model.props.embed$.value ?? false;
this.std.store.captureSync();
this.std.store.transact(() => {
this.std.store.updateBlock(this.blockId, {
name,
size,
type,
sourceId,
embed: false,
});
const provider = this.std.get(AttachmentEmbedProvider);
embed &&= provider.embedded(this.model);
if (embed) {
provider.convertTo(this.model);
}
// Reloads
this.reload();
});
};
private _selectBlock() {
@@ -139,6 +186,34 @@ export class AttachmentBlockComponent extends CaptionedBlockComponent<Attachment
selectionManager.setGroup('note', [blockSelection]);
}
private readonly _trackCitationDeleteEvent = () => {
// Check citation delete event
this._disposables.add(
this.std.store.slots.blockUpdated
.pipe(
filter(payload => {
if (!payload.isLocal) return false;
const { flavour, id, type } = payload;
if (
type !== 'delete' ||
flavour !== this.model.flavour ||
id !== this.model.id
)
return false;
const { model } = payload;
if (!this.citationService.isCitationModel(model)) return false;
return true;
})
)
.subscribe(() => {
this.citationService.trackEvent('Delete');
})
);
};
override connectedCallback() {
super.connectedCallback();
@@ -162,6 +237,8 @@ export class AttachmentBlockComponent extends CaptionedBlockComponent<Attachment
});
});
}
this._trackCitationDeleteEvent();
}
override firstUpdated() {
@@ -367,7 +444,7 @@ export class AttachmentBlockComponent extends CaptionedBlockComponent<Attachment
protected renderEmbedView = () => {
const { model, blobUrl } = this;
if (!model.props.embed || !blobUrl) return null;
if (!model.props.embed$.value || !blobUrl) return null;
const { std, _maxFileSize } = this;
const provider = std.get(AttachmentEmbedProvider);

View File

@@ -1,6 +1,7 @@
import { ConfirmIcon } from '@blocksuite/affine-components/icons';
import { toast } from '@blocksuite/affine-components/toast';
import type { AttachmentBlockModel } from '@blocksuite/affine-model';
import { CitationProvider } from '@blocksuite/affine-shared/services';
import type { EditorHost } from '@blocksuite/std';
import { html } from 'lit';
import { createRef, ref } from 'lit/directives/ref.js';
@@ -33,6 +34,7 @@ export const RenameModal = ({
let fileName = includeExtension ? nameWithoutExtension : originalName;
const extension = includeExtension ? originalExtension : '';
const citationService = editorHost.std.get(CitationProvider);
const abort = () => abortController.abort();
const onConfirm = () => {
@@ -44,6 +46,9 @@ export const RenameModal = ({
model.store.updateBlock(model, {
name: newFileName,
});
if (citationService.isCitationModel(model)) {
citationService.trackEvent('Edit');
}
abort();
};
const onInput = (e: InputEvent) => {

View File

@@ -24,6 +24,7 @@ import {
DownloadIcon,
DuplicateIcon,
EditIcon,
ReplaceIcon,
ResetIcon,
} from '@blocksuite/icons/lit';
import { BlockFlavourIdentifier } from '@blocksuite/std';
@@ -139,27 +140,42 @@ export const attachmentViewDropdownMenu = {
});
};
return html`${keyed(
model,
html`<affine-view-dropdown-menu
@toggle=${onToggle}
.actions=${actions.value}
.context=${ctx}
.viewType$=${viewType$}
></affine-view-dropdown-menu>`
)}`;
return html`<affine-view-dropdown-menu
@toggle=${onToggle}
.actions=${actions.value}
.context=${ctx}
.viewType$=${viewType$}
></affine-view-dropdown-menu>`;
},
} as const satisfies ToolbarActionGroup<ToolbarAction>;
const replaceAction = {
id: 'c.replace',
tooltip: 'Replace attachment',
icon: ReplaceIcon(),
disabled(ctx) {
const block = ctx.getCurrentBlockByType(AttachmentBlockComponent);
if (!block) return true;
const { downloading = false, uploading = false } =
block.resourceController.state$.value;
return downloading || uploading;
},
run(ctx) {
const block = ctx.getCurrentBlockByType(AttachmentBlockComponent);
block?.replace().catch(console.error);
},
} as const satisfies ToolbarAction;
const downloadAction = {
id: 'c.download',
id: 'd.download',
tooltip: 'Download',
icon: DownloadIcon(),
run(ctx) {
const block = ctx.getCurrentBlockByType(AttachmentBlockComponent);
block?.download();
},
when: ctx => {
when(ctx) {
const model = ctx.getCurrentModelByType(AttachmentBlockModel);
if (!model) return false;
// Current citation attachment block does not support download
@@ -168,7 +184,7 @@ const downloadAction = {
} as const satisfies ToolbarAction;
const captionAction = {
id: 'd.caption',
id: 'e.caption',
tooltip: 'Caption',
icon: CaptionIcon(),
run(ctx) {
@@ -221,6 +237,7 @@ const builtinToolbarConfig = {
},
},
attachmentViewDropdownMenu,
replaceAction,
downloadAction,
captionAction,
{
@@ -354,13 +371,17 @@ const builtinSurfaceToolbarConfig = {
)}`;
},
} satisfies ToolbarActionGroup<ToolbarAction>,
{
...replaceAction,
id: 'd.replace',
},
{
...downloadAction,
id: 'd.download',
id: 'e.download',
},
{
...captionAction,
id: 'e.caption',
id: 'f.caption',
},
],
when: ctx => ctx.getSurfaceModelsByType(AttachmentBlockModel).length === 1,

View File

@@ -8,6 +8,7 @@ import type {
} from '@blocksuite/affine-model';
import { ImageProxyService } from '@blocksuite/affine-shared/adapters';
import {
CitationProvider,
DocModeProvider,
LinkPreviewServiceIdentifier,
} from '@blocksuite/affine-shared/services';
@@ -18,6 +19,7 @@ import { html } from 'lit';
import { property, query } from 'lit/decorators.js';
import { type ClassInfo, classMap } from 'lit/directives/class-map.js';
import { type StyleInfo, styleMap } from 'lit/directives/style-map.js';
import { filter } from 'rxjs/operators';
import { refreshBookmarkUrlData } from './utils.js';
@@ -114,11 +116,12 @@ export class BookmarkBlockComponent extends CaptionedBlockComponent<BookmarkBloc
);
};
get citationService() {
return this.std.get(CitationProvider);
}
get isCitation() {
return (
!!this.model.props.footnoteIdentifier &&
this.model.props.style === 'citation'
);
return this.citationService.isCitationModel(this.model);
}
get imageProxyService() {
@@ -166,6 +169,31 @@ export class BookmarkBlockComponent extends CaptionedBlockComponent<BookmarkBloc
></bookmark-card>`;
};
private readonly _trackCitationDeleteEvent = () => {
// Check citation delete event
this._disposables.add(
this.std.store.slots.blockUpdated
.pipe(
filter(payload => {
if (!payload.isLocal) return false;
const { flavour, id, type } = payload;
if (
type !== 'delete' ||
flavour !== this.model.flavour ||
id !== this.model.id
)
return false;
const { model } = payload;
if (!this.citationService.isCitationModel(model)) return false;
return true;
})
)
.subscribe(() => {
this.citationService.trackEvent('Delete');
})
);
};
override connectedCallback() {
super.connectedCallback();
@@ -203,6 +231,8 @@ export class BookmarkBlockComponent extends CaptionedBlockComponent<BookmarkBloc
}
})
);
this._trackCitationDeleteEvent();
}
override disconnectedCallback(): void {

View File

@@ -407,7 +407,7 @@ const builtinSurfaceToolbarConfig = {
if (options?.viewType !== 'embed') return;
const { flavour, styles } = options;
let { style } = model.props;
let style: EmbedCardStyle = model.props.style;
if (!styles.includes(style)) {
style = styles[0];
@@ -482,24 +482,26 @@ const builtinSurfaceToolbarConfig = {
} satisfies ToolbarActionGroup<ToolbarAction>,
{
id: 'b.style',
actions: [
{
id: 'horizontal',
label: 'Large horizontal style',
},
{
id: 'list',
label: 'Small horizontal style',
},
{
id: 'vertical',
label: 'Large vertical style',
},
{
id: 'cube',
label: 'Small vertical style',
},
].filter(action => BookmarkStyles.includes(action.id as EmbedCardStyle)),
actions: (
[
{
id: 'horizontal',
label: 'Large horizontal style',
},
{
id: 'list',
label: 'Small horizontal style',
},
{
id: 'vertical',
label: 'Large vertical style',
},
{
id: 'cube',
label: 'Small vertical style',
},
] as const
).filter(action => BookmarkStyles.includes(action.id)),
content(ctx) {
const model = ctx.getCurrentModelByType(BookmarkBlockModel);
if (!model) return null;

View File

@@ -40,6 +40,16 @@ export class CodeBlockComponent extends CaptionedBlockComponent<CodeBlockModel>
private _inlineRangeProvider: InlineRangeProvider | null = null;
private readonly _localPreview$ = signal<boolean | null>(null);
preview$: Signal<boolean> = computed(() => {
const modelPreview = !!this.model.props.preview$.value;
if (this.store.readonly) {
return this._localPreview$.value ?? modelPreview;
}
return modelPreview;
});
highlightTokens$: Signal<ThemedToken[][]> = signal([]);
languageName$: Signal<string> = computed(() => {
@@ -393,7 +403,7 @@ export class CodeBlockComponent extends CaptionedBlockComponent<CodeBlockModel>
true) &&
(this.model.props.lineNumber ?? true);
const preview = !!this.model.props.preview;
const preview = this.preview$.value;
const previewContext = this.std.getOptional(
CodeBlockPreviewIdentifier(this.model.props.language ?? '')
);
@@ -461,6 +471,14 @@ export class CodeBlockComponent extends CaptionedBlockComponent<CodeBlockModel>
override accessor useCaptionEditor = true;
override accessor useZeroWidth = true;
setPreviewState(preview: boolean) {
if (this.store.readonly) {
this._localPreview$.value = preview;
} else {
this.store.updateBlock(this.model, { preview });
}
}
}
declare global {

View File

@@ -58,11 +58,7 @@ export class PreviewButton extends WithDisposable(SignalWatcher(LitElement)) {
`;
private readonly _toggle = (value: boolean) => {
if (this.blockComponent.store.readonly) return;
this.blockComponent.store.updateBlock(this.blockComponent.model, {
preview: value,
});
this.blockComponent.setPreviewState(value);
const std = this.blockComponent.std;
const mode = std.getOptional(DocModeProvider)?.getEditorMode() ?? 'page';
@@ -77,7 +73,7 @@ export class PreviewButton extends WithDisposable(SignalWatcher(LitElement)) {
};
get preview() {
return !!this.blockComponent.model.props.preview$.value;
return this.blockComponent.preview$.value;
}
override render() {

View File

@@ -0,0 +1,61 @@
import {
type CodeBlockModel,
CodeBlockSchema,
ParagraphBlockModel,
} from '@blocksuite/affine-model';
import { focusTextModel } from '@blocksuite/affine-rich-text';
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
import { matchModels } from '@blocksuite/affine-shared/utils';
import type { BlockComponent } from '@blocksuite/std';
import { InlineMarkdownExtension } from '@blocksuite/std/inline';
export const CodeBlockMarkdownExtension =
InlineMarkdownExtension<AffineTextAttributes>({
name: 'code-block',
pattern: /^```([a-zA-Z0-9]*)\s$/,
action: ({ inlineEditor, inlineRange, prefixText, pattern }) => {
if (inlineEditor.yTextString.slice(0, inlineRange.index).includes('\n')) {
return;
}
const match = prefixText.match(pattern);
if (!match) return;
const language = match[1];
if (!inlineEditor.rootElement) return;
const blockComponent =
inlineEditor.rootElement.closest<BlockComponent>('[data-block-id]');
if (!blockComponent) return;
const { model, std, store } = blockComponent;
if (
matchModels(model, [ParagraphBlockModel]) &&
model.props.type === 'quote'
) {
return;
}
const parent = store.getParent(model);
if (!parent) return;
const index = parent.children.indexOf(model);
store.captureSync();
const codeId = store.addBlock<CodeBlockModel>(
CodeBlockSchema.model.flavour,
{ language },
parent,
index
);
if (model.text && model.text.length > prefixText.length) {
const text = model.text.clone();
store.addBlock('affine:paragraph', { text }, parent, index + 1);
text.delete(0, prefixText.length);
}
store.deleteBlock(model, { bringChildrenTo: parent });
focusTextModel(std, codeId);
},
});

View File

@@ -33,6 +33,10 @@ export const codeBlockStyles = css`
grid-template-columns: auto minmax(0, 1fr);
}
.affine-code-block-container.disable-line-numbers v-line {
grid-template-columns: unset;
}
.affine-code-block-container div:has(> v-line) {
display: grid;
}

View File

@@ -21,6 +21,7 @@ import { CodeKeymapExtension } from './code-keymap.js';
import { AFFINE_CODE_TOOLBAR_WIDGET } from './code-toolbar/index.js';
import { codeSlashMenuConfig } from './configs/slash-menu.js';
import { effects } from './effects.js';
import { CodeBlockMarkdownExtension } from './markdown.js';
const codeToolbarWidget = WidgetViewExtension(
'affine:code',
@@ -44,6 +45,7 @@ export class CodeBlockViewExtension extends ViewExtensionProvider {
BlockViewExtension('affine:code', literal`affine-code`),
SlashMenuConfigExtension('affine:code', codeSlashMenuConfig),
CodeKeymapExtension,
CodeBlockMarkdownExtension,
...getCodeClipboardExtensions(),
]);
context.register([

View File

@@ -331,7 +331,6 @@ export class RichTextCell extends BaseCellRenderer<Text, string> {
this.inlineEditor$.value?.selectAll();
}
};
this.addEventListener('keydown', selectAll);
this.disposables.addFromEvent(this, 'keydown', selectAll);
this.disposables.add(
effect(() => {

View File

@@ -209,10 +209,19 @@ export class HeaderAreaTextCell extends BaseCellRenderer<Text, string> {
}
};
this.addEventListener('keydown', selectAll);
this.disposables.addFromEvent(this, 'keydown', selectAll);
}
private readonly _handleKeyDown = (event: KeyboardEvent) => {
if (event.key !== 'Escape') {
if (event.key === 'Tab') {
event.preventDefault();
return;
}
event.stopPropagation();
}
};
override firstUpdated(props: Map<string, unknown>) {
super.firstUpdated(props);
this.richText.value?.updateComplete
@@ -233,6 +242,12 @@ export class HeaderAreaTextCell extends BaseCellRenderer<Text, string> {
'paste',
this._onPaste
);
const inlineEditor = this.inlineEditor;
if (inlineEditor) {
this.disposables.add(
inlineEditor.slots.keydown.subscribe(this._handleKeyDown)
);
}
}
})
.catch(console.error);

View File

@@ -13,6 +13,7 @@
"@blocksuite/affine-components": "workspace:*",
"@blocksuite/affine-ext-loader": "workspace:*",
"@blocksuite/affine-model": "workspace:*",
"@blocksuite/affine-rich-text": "workspace:*",
"@blocksuite/affine-shared": "workspace:*",
"@blocksuite/global": "workspace:*",
"@blocksuite/std": "workspace:*",

View File

@@ -0,0 +1,63 @@
import {
type DividerBlockModel,
DividerBlockSchema,
ParagraphBlockModel,
ParagraphBlockSchema,
} from '@blocksuite/affine-model';
import { focusTextModel } from '@blocksuite/affine-rich-text';
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
import { matchModels } from '@blocksuite/affine-shared/utils';
import type { BlockComponent } from '@blocksuite/std';
import { InlineMarkdownExtension } from '@blocksuite/std/inline';
export const DividerMarkdownExtension =
InlineMarkdownExtension<AffineTextAttributes>({
name: 'divider',
pattern: /^(-{3,}|\*{3,}|_{3,})\s$/,
action: ({ inlineEditor, inlineRange }) => {
if (inlineEditor.yTextString.slice(0, inlineRange.index).includes('\n')) {
return;
}
if (!inlineEditor.rootElement) return;
const blockComponent =
inlineEditor.rootElement.closest<BlockComponent>('[data-block-id]');
if (!blockComponent) return;
const { model, std, store } = blockComponent;
if (
matchModels(model, [ParagraphBlockModel]) &&
model.props.type !== 'quote'
) {
const parent = store.getParent(model);
if (!parent) return;
const index = parent.children.indexOf(model);
store.captureSync();
inlineEditor.deleteText({
index: 0,
length: inlineRange.index,
});
store.addBlock<DividerBlockModel>(
DividerBlockSchema.model.flavour,
{
children: model.children,
},
parent,
index
);
const nextBlock = parent.children.at(index + 1);
let id = nextBlock?.id;
if (!id) {
id = store.addBlock<ParagraphBlockModel>(
ParagraphBlockSchema.model.flavour,
{},
parent
);
}
focusTextModel(std, id);
}
},
});

View File

@@ -6,6 +6,7 @@ import { BlockViewExtension } from '@blocksuite/std';
import { literal } from 'lit/static-html.js';
import { effects } from './effects';
import { DividerMarkdownExtension } from './markdown';
export class DividerViewExtension extends ViewExtensionProvider {
override name = 'affine-divider-block';
@@ -19,6 +20,7 @@ export class DividerViewExtension extends ViewExtensionProvider {
super.setup(context);
context.register([
BlockViewExtension('affine:divider', literal`affine-divider`),
DividerMarkdownExtension,
]);
}
}

View File

@@ -10,6 +10,7 @@
{ "path": "../../components" },
{ "path": "../../ext-loader" },
{ "path": "../../model" },
{ "path": "../../rich-text" },
{ "path": "../../shared" },
{ "path": "../../../framework/global" },
{ "path": "../../../framework/std" },

View File

@@ -13,7 +13,6 @@ import {
ActionPlacement,
DocDisplayMetaProvider,
EditorSettingProvider,
FeatureFlagService,
type LinkEventType,
type OpenDocMode,
type ToolbarAction,
@@ -216,12 +215,7 @@ const conversionsActionGroup = {
run(ctx) {
const block = ctx.getCurrentBlockByType(EmbedLinkedDocBlockComponent);
if (
ctx.std
.get(FeatureFlagService)
.getFlag('enable_embed_doc_with_alias') &&
isGfxBlockComponent(block)
) {
if (isGfxBlockComponent(block)) {
const editorSetting = ctx.std.getOptional(EditorSettingProvider);
editorSetting?.set?.(
'docCanvasPreferView',
@@ -265,18 +259,18 @@ const builtinToolbarConfig = {
conversionsActionGroup,
{
id: 'c.style',
actions: [
{
id: 'horizontal',
label: 'Large horizontal style',
},
{
id: 'list',
label: 'Small horizontal style',
},
].filter(action =>
EmbedLinkedDocStyles.includes(action.id as EmbedCardStyle)
),
actions: (
[
{
id: 'horizontal',
label: 'Large horizontal style',
},
{
id: 'list',
label: 'Small horizontal style',
},
] as const
).filter(action => EmbedLinkedDocStyles.includes(action.id)),
content(ctx) {
const model = ctx.getCurrentModelByType(EmbedLinkedDocModel);
if (!model) return null;
@@ -374,26 +368,26 @@ const builtinSurfaceToolbarConfig = {
conversionsActionGroup,
{
id: 'c.style',
actions: [
{
id: 'horizontal',
label: 'Large horizontal style',
},
{
id: 'list',
label: 'Small horizontal style',
},
{
id: 'vertical',
label: 'Large vertical style',
},
{
id: 'cube',
label: 'Small vertical style',
},
].filter(action =>
EmbedLinkedDocStyles.includes(action.id as EmbedCardStyle)
),
actions: (
[
{
id: 'horizontal',
label: 'Large horizontal style',
},
{
id: 'list',
label: 'Small horizontal style',
},
{
id: 'vertical',
label: 'Large vertical style',
},
{
id: 'cube',
label: 'Small vertical style',
},
] as const
).filter(action => EmbedLinkedDocStyles.includes(action.id)),
content(ctx) {
const model = ctx.getCurrentModelByType(EmbedLinkedDocModel);
if (!model) return null;

View File

@@ -17,6 +17,7 @@ import {
REFERENCE_NODE,
} from '@blocksuite/affine-shared/consts';
import {
CitationProvider,
DocDisplayMetaProvider,
DocModeProvider,
OpenDocExtensionIdentifier,
@@ -43,6 +44,7 @@ import { repeat } from 'lit/directives/repeat.js';
import { styleMap } from 'lit/directives/style-map.js';
import { when } from 'lit/directives/when.js';
import throttle from 'lodash-es/throttle';
import { filter } from 'rxjs/operators';
import * as Y from 'yjs';
import { renderLinkedDocInCard } from '../common/render-linked-doc';
@@ -254,11 +256,12 @@ export class EmbedLinkedDocBlockComponent extends EmbedBlockComponent<EmbedLinke
return this.store.readonly;
}
get citationService() {
return this.std.get(CitationProvider);
}
get isCitation() {
return (
!!this.model.props.footnoteIdentifier &&
this.model.props.style === 'citation'
);
return this.citationService.isCitationModel(this.model);
}
private readonly _handleDoubleClick = (event: MouseEvent) => {
@@ -454,6 +457,31 @@ export class EmbedLinkedDocBlockComponent extends EmbedBlockComponent<EmbedLinke
);
};
private readonly _trackCitationDeleteEvent = () => {
// Check citation delete event
this._disposables.add(
this.std.store.slots.blockUpdated
.pipe(
filter(payload => {
if (!payload.isLocal) return false;
const { flavour, id, type } = payload;
if (
type !== 'delete' ||
flavour !== this.model.flavour ||
id !== this.model.id
)
return false;
const { model } = payload;
if (!this.citationService.isCitationModel(model)) return false;
return true;
})
)
.subscribe(() => {
this.citationService.trackEvent('Delete');
})
);
};
override connectedCallback() {
super.connectedCallback();
@@ -532,6 +560,8 @@ export class EmbedLinkedDocBlockComponent extends EmbedBlockComponent<EmbedLinke
}
})
);
this._trackCitationDeleteEvent();
}
getInitialState(): {

View File

@@ -17,7 +17,6 @@ import { REFERENCE_NODE } from '@blocksuite/affine-shared/consts';
import {
ActionPlacement,
EditorSettingProvider,
FeatureFlagService,
type LinkEventType,
type OpenDocMode,
type ToolbarAction,
@@ -163,12 +162,7 @@ const conversionsActionGroup = {
label: 'Card view',
run(ctx) {
const block = ctx.getCurrentBlockByType(EmbedSyncedDocBlockComponent);
if (
ctx.std
.get(FeatureFlagService)
.getFlag('enable_embed_doc_with_alias') &&
isGfxBlockComponent(block)
) {
if (isGfxBlockComponent(block)) {
const editorSetting = ctx.std.getOptional(EditorSettingProvider);
editorSetting?.set?.(
'docCanvasPreferView',
@@ -296,8 +290,6 @@ const builtinSurfaceToolbarConfig = {
label: 'Insert to page',
tooltip: 'Insert to page',
icon: InsertIntoPageIcon(),
when: ({ std }) =>
std.get(FeatureFlagService).getFlag('enable_embed_doc_with_alias'),
run: ctx => {
const model = ctx.getCurrentModelByType(EmbedSyncedDocModel);
if (!model) return;
@@ -334,8 +326,6 @@ const builtinSurfaceToolbarConfig = {
tooltip:
'Duplicate as note to create an editable copy, the original remains unchanged.',
icon: DuplicateIcon(),
when: ({ std }) =>
std.get(FeatureFlagService).getFlag('enable_embed_doc_with_alias'),
run: ctx => {
const { gfx } = ctx;

View File

@@ -153,7 +153,7 @@ function createBuiltinToolbarConfigForExternal(
.get(EmbedOptionProvider)
.getEmbedBlockOptions(url);
let { style } = model.props;
let style: EmbedCardStyle = model.props.style;
let flavour = 'affine:bookmark';
if (options?.viewType === 'card') {
@@ -227,7 +227,7 @@ function createBuiltinToolbarConfigForExternal(
if (options?.viewType !== 'embed') return;
const { flavour, styles } = options;
let { style } = model.props;
let style: EmbedCardStyle = model.props.style;
if (!styles.includes(style)) {
style =
@@ -441,7 +441,11 @@ const createBuiltinSurfaceToolbarConfigForExternal = (
let { style } = model.props;
let flavour = 'affine:bookmark';
if (!BookmarkStyles.includes(style)) {
if (
!BookmarkStyles.includes(
style as (typeof BookmarkStyles)[number]
)
) {
style = BookmarkStyles[0];
}
@@ -517,26 +521,26 @@ const createBuiltinSurfaceToolbarConfigForExternal = (
} satisfies ToolbarActionGroup<ToolbarAction>,
{
id: 'c.style',
actions: [
{
id: 'horizontal',
label: 'Large horizontal style',
},
{
id: 'list',
label: 'Small horizontal style',
},
{
id: 'vertical',
label: 'Large vertical style',
},
{
id: 'cube',
label: 'Small vertical style',
},
].filter(action =>
EmbedGithubStyles.includes(action.id as EmbedCardStyle)
),
actions: (
[
{
id: 'horizontal',
label: 'Large horizontal style',
},
{
id: 'list',
label: 'Small horizontal style',
},
{
id: 'vertical',
label: 'Large vertical style',
},
{
id: 'cube',
label: 'Small vertical style',
},
] as const
).filter(action => EmbedGithubStyles.includes(action.id)),
when(ctx) {
return Boolean(ctx.getCurrentModelByType(EmbedGithubModel));
},

View File

@@ -107,10 +107,10 @@ export class EmbedHtmlFullscreenToolbar extends LitElement {
if (this._copied) return;
this.embedHtml.std.clipboard
.writeToClipboard(items => {
items['text/plain'] = this.embedHtml.model.props.html ?? '';
return items;
})
.writeToClipboard(items => ({
...items,
'text/plain': this.embedHtml.model.props.html ?? '',
}))
.then(() => {
this._copied = true;
setTimeout(() => (this._copied = false), 1500);

View File

@@ -16,6 +16,7 @@ import {
import { cssVarV2 } from '@toeverything/theme/v2';
import { html } from 'lit';
import { state } from 'lit/decorators.js';
import { repeat } from 'lit/directives/repeat.js';
import { styleMap } from 'lit/directives/style-map.js';
import {
@@ -87,6 +88,12 @@ export class FrameBlockComponent extends GfxBlockComponent<FrameBlockModel> {
this.gfx.tool.currentToolName$.value === 'frameNavigator';
const frameIndex = this.gfx.layer.getZIndex(model);
const widgets = html`${repeat(
Object.entries(this.widgets),
([id]) => id,
([_, widget]) => widget
)}`;
return html`
<div
class="affine-frame-container"
@@ -102,6 +109,7 @@ export class FrameBlockComponent extends GfxBlockComponent<FrameBlockModel> {
: `1px solid ${cssVarV2('edgeless/frame/border/default')}`,
})}
></div>
${widgets}
`;
}
@@ -178,11 +186,22 @@ export const FrameBlockInteraction =
selectable(context) {
const { model } = context;
const onTitle =
model.externalBound?.containsPoint([
context.position.x,
context.position.y,
]) ?? false;
return (
context.default(context) &&
(model.isLocked() || !isTransparent(model.props.background))
(model.isLocked() ||
!isTransparent(model.props.background) ||
onTitle)
);
},
onSelect(context) {
return context.default(context);
},
};
},
}

View File

@@ -241,20 +241,35 @@ export class EdgelessFrameManager extends GfxExtension {
surfaceModel.elementAdded.subscribe(({ id, local }) => {
const element = surfaceModel.getElementById(id);
if (element && local) {
const frame = this.getFrameFromPoint(element.elementBound.center);
// if the container created with a frame, skip it.
if (
isGfxGroupCompatibleModel(element) &&
frame &&
element.hasChild(frame)
) {
return;
}
// new element may intended to be added to other group
// so we need to wait for the next microtask to check if the element can be added to the frame
// The entire frame detection logic must be in microtask for timing reasons:
//
// 1. For connectors: When elementAdded fires, connectors have invalid bounds [0,0,0,0]
// because their path/bounds are calculated in a separate microtask of updateConnectorPath by connector-watcher.
// We need to wait for that calculation to complete before frame detection.
//
// 2. For shapes: Although they have valid bounds immediately, processing them in microtask
// ensures consistent timing and allows other initialization to complete first.
//
// 3. Group compatibility: Some elements may need to establish their group relationships
// before being considered for frame membership.
//
// By embedding the entire logic in microtask, we ensure:
// - Connectors have proper bounds calculated (not [0,0,0,0])
// - getFrameFromPoint() works correctly with valid element centers
// - All element initialization is complete before frame detection
queueMicrotask(() => {
const frame = this.getFrameFromPoint(element.elementBound.center);
// if the container created with a frame, skip it.
if (
isGfxGroupCompatibleModel(element) &&
frame &&
element.hasChild(frame)
) {
return;
}
// Only add elements that aren't already grouped and have a valid frame
if (!element.group && frame) {
this.addElementsToFrame(frame, [element]);
}

View File

@@ -11,6 +11,7 @@ import {
NativeClipboardProvider,
} from '@blocksuite/affine-shared/services';
import {
convertToPng,
formatSize,
getBlockProps,
isInsidePageEditor,
@@ -111,28 +112,6 @@ export async function resetImageSize(
block.store.updateBlock(model, props);
}
function convertToPng(blob: Blob): Promise<Blob | null> {
return new Promise(resolve => {
const reader = new FileReader();
reader.addEventListener('load', _ => {
const img = new Image();
img.onload = () => {
const c = document.createElement('canvas');
c.width = img.width;
c.height = img.height;
const ctx = c.getContext('2d');
if (!ctx) return;
ctx.drawImage(img, 0, 0);
c.toBlob(resolve, 'image/png');
};
img.onerror = () => resolve(null);
img.src = reader.result as string;
});
reader.addEventListener('error', () => resolve(null));
reader.readAsDataURL(blob);
});
}
export async function copyImageBlob(
block: ImageBlockComponent | ImageEdgelessBlockComponent
) {

View File

@@ -1,6 +1,5 @@
import { textKeymap } from '@blocksuite/affine-inline-preset';
import { ListBlockSchema } from '@blocksuite/affine-model';
import { markdownInput } from '@blocksuite/affine-rich-text';
import { getSelectedModelsCommand } from '@blocksuite/affine-shared/commands';
import { IS_MAC } from '@blocksuite/global/env';
import { KeymapExtension, TextSelection } from '@blocksuite/std';
@@ -125,20 +124,6 @@ export const ListKeymapExtension = KeymapExtension(
ctx.get('keyboardState').raw.preventDefault();
return true;
},
Space: ctx => {
if (!markdownInput(std)) {
return;
}
ctx.get('keyboardState').raw.preventDefault();
return true;
},
'Shift-Space': ctx => {
if (!markdownInput(std)) {
return;
}
ctx.get('keyboardState').raw.preventDefault();
return true;
},
};
},
{

View File

@@ -0,0 +1,91 @@
import {
type ListBlockModel,
ListBlockSchema,
type ListType,
ParagraphBlockModel,
} from '@blocksuite/affine-model';
import { focusTextModel } from '@blocksuite/affine-rich-text';
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
import { matchModels, toNumberedList } from '@blocksuite/affine-shared/utils';
import type { BlockComponent } from '@blocksuite/std';
import { InlineMarkdownExtension } from '@blocksuite/std/inline';
export const ListMarkdownExtension =
InlineMarkdownExtension<AffineTextAttributes>({
name: 'list',
// group 2: number
// group 3: bullet
// group 4: bullet
// group 5: todo
// group 6: todo checked
pattern: /^((\d+\.)|(-)|(\*)|(\[ ?\])|(\[x\]))\s$/,
action: ({ inlineEditor, pattern, inlineRange, prefixText }) => {
if (inlineEditor.yTextString.slice(0, inlineRange.index).includes('\n')) {
return;
}
const match = prefixText.match(pattern);
if (!match) return;
let type: ListType;
if (match[2]) {
type = 'numbered';
} else if (match[3] || match[4]) {
type = 'bulleted';
} else if (match[5] || match[6]) {
type = 'todo';
} else {
return;
}
const checked = match[6] !== undefined;
if (!inlineEditor.rootElement) return;
const blockComponent =
inlineEditor.rootElement.closest<BlockComponent>('[data-block-id]');
if (!blockComponent) return;
const { model, std, store } = blockComponent;
if (!matchModels(model, [ParagraphBlockModel])) return;
if (type !== 'numbered') {
const parent = store.getParent(model);
if (!parent) return;
const index = parent.children.indexOf(model);
store.captureSync();
inlineEditor.deleteText({
index: 0,
length: inlineRange.index,
});
const id = store.addBlock<ListBlockModel>(
ListBlockSchema.model.flavour,
{
type: type,
text: model.text?.clone(),
children: model.children,
...(type === 'todo' ? { checked } : {}),
},
parent,
index
);
store.deleteBlock(model, { deleteChildren: false });
focusTextModel(std, id);
} else {
let order = parseInt(match[2]);
if (!Number.isInteger(order)) order = 1;
store.captureSync();
inlineEditor.deleteText({
index: 0,
length: inlineRange.index,
});
const id = toNumberedList(std, model, order);
if (!id) return;
focusTextModel(std, id);
}
},
});

View File

@@ -7,6 +7,7 @@ import { literal } from 'lit/static-html.js';
import { effects } from './effects.js';
import { ListKeymapExtension, ListTextKeymapExtension } from './list-keymap.js';
import { ListMarkdownExtension } from './markdown.js';
export class ListViewExtension extends ViewExtensionProvider {
override name = 'affine-list-block';
@@ -23,6 +24,7 @@ export class ListViewExtension extends ViewExtensionProvider {
BlockViewExtension('affine:list', literal`affine-list`),
ListKeymapExtension,
ListTextKeymapExtension,
ListMarkdownExtension,
]);
}
}

View File

@@ -0,0 +1,74 @@
import {
ListBlockModel,
ParagraphBlockModel,
ParagraphBlockSchema,
type ParagraphType,
} from '@blocksuite/affine-model';
import { focusTextModel } from '@blocksuite/affine-rich-text';
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
import { matchModels } from '@blocksuite/affine-shared/utils';
import type { BlockComponent } from '@blocksuite/std';
import { InlineMarkdownExtension } from '@blocksuite/std/inline';
export const ParagraphMarkdownExtension =
InlineMarkdownExtension<AffineTextAttributes>({
name: 'heading',
pattern: /^((#{1,6})|(>))\s$/,
action: ({ inlineEditor, pattern, inlineRange, prefixText }) => {
if (inlineEditor.yTextString.slice(0, inlineRange.index).includes('\n')) {
return;
}
const match = prefixText.match(pattern);
if (!match) return;
const type = (
match[2] ? `h${match[2].length}` : 'quote'
) as ParagraphType;
if (!inlineEditor.rootElement) return;
const blockComponent =
inlineEditor.rootElement.closest<BlockComponent>('[data-block-id]');
if (!blockComponent) return;
const { model, std, store } = blockComponent;
if (
!matchModels(model, [ParagraphBlockModel]) &&
matchModels(model, [ListBlockModel])
) {
const parent = store.getParent(model);
if (!parent) return;
const index = parent.children.indexOf(model);
store.captureSync();
inlineEditor.deleteText({
index: 0,
length: inlineRange.index,
});
store.deleteBlock(model, { deleteChildren: false });
const id = store.addBlock<ParagraphBlockModel>(
ParagraphBlockSchema.model.flavour,
{
type: type,
text: model.text?.clone(),
children: model.children,
},
parent,
index
);
focusTextModel(std, id);
} else if (
matchModels(model, [ParagraphBlockModel]) &&
model.props.type !== type
) {
store.captureSync();
inlineEditor.deleteText({
index: 0,
length: inlineRange.index,
});
store.updateBlock(model, { type });
focusTextModel(std, model.id);
}
},
});

View File

@@ -7,7 +7,10 @@ import {
BLOCK_CHILDREN_CONTAINER_PADDING_LEFT,
EDGELESS_TOP_CONTENTEDITABLE_SELECTOR,
} from '@blocksuite/affine-shared/consts';
import { DocModeProvider } from '@blocksuite/affine-shared/services';
import {
CitationProvider,
DocModeProvider,
} from '@blocksuite/affine-shared/services';
import {
calculateCollapsedSiblings,
getNearestHeadingBefore,
@@ -63,6 +66,10 @@ export class ParagraphBlockComponent extends CaptionedBlockComponent<ParagraphBl
?.getPlaceholder(this.model);
}
get citationService() {
return this.std.get(CitationProvider);
}
get attributeRenderer() {
return this.inlineManager.getRenderer();
}
@@ -94,6 +101,12 @@ export class ParagraphBlockComponent extends CaptionedBlockComponent<ParagraphBl
return this.std.get(DefaultInlineManagerExtension.identifier);
}
get hasCitationSiblings() {
return this.collapsedSiblings.some(sibling =>
this.citationService.isCitationModel(sibling)
);
}
override get topContenteditableElement() {
if (this.std.get(DocModeProvider).getEditorMode() === 'edgeless') {
return this.closest<BlockComponent>(
@@ -286,6 +299,13 @@ export class ParagraphBlockComponent extends CaptionedBlockComponent<ParagraphBl
collapsed: value,
});
}
if (this.hasCitationSiblings) {
this.citationService.trackEvent('Expand', {
control: 'Source Button',
type: value ? 'Hide' : 'Show',
});
}
}}
></blocksuite-toggle-button>
`

View File

@@ -7,7 +7,6 @@ import {
import {
focusTextModel,
getInlineEditorByModel,
markdownInput,
} from '@blocksuite/affine-rich-text';
import {
calculateCollapsedSiblings,
@@ -148,10 +147,6 @@ export const ParagraphKeymapExtension = KeymapExtension(
raw.preventDefault();
if (markdownInput(std, model.id)) {
return true;
}
if (model.props.type.startsWith('h') && model.props.collapsed) {
const parent = store.getParent(model);
if (!parent) return true;
@@ -199,20 +194,6 @@ export const ParagraphKeymapExtension = KeymapExtension(
event.preventDefault();
return true;
},
Space: ctx => {
if (!markdownInput(std)) {
return;
}
ctx.get('keyboardState').raw.preventDefault();
return true;
},
'Shift-Space': ctx => {
if (!markdownInput(std)) {
return;
}
ctx.get('keyboardState').raw.preventDefault();
return true;
},
Tab: ctx => {
const [success] = std.command
.chain()

View File

@@ -20,6 +20,7 @@ import { EMBED_BLOCK_MODEL_LIST } from '@blocksuite/affine-shared/consts';
import type { ExtendedModel } from '@blocksuite/affine-shared/types';
import {
focusTitle,
getDocTitleInlineEditor,
getPrevContentBlock,
matchModels,
} from '@blocksuite/affine-shared/utils';
@@ -45,10 +46,6 @@ export function mergeWithPrev(editorHost: EditorHost, model: BlockModel) {
const parent = doc.getParent(model);
if (!parent) return false;
if (matchModels(parent, [EdgelessTextBlockModel])) {
return true;
}
const prevBlock = getPrevContentBlock(editorHost, model);
if (!prevBlock) {
return handleNoPreviousSibling(editorHost, model);
@@ -123,36 +120,63 @@ function handleNoPreviousSibling(editorHost: EditorHost, model: ExtendedModel) {
const parent = doc.getParent(model);
if (!parent) return false;
if (matchModels(parent, [NoteBlockModel]) && parent.isPageBlock()) {
const focusFirstBlockStart = () => {
const firstBlock = parent.firstChild();
if (firstBlock) {
focusTextModel(editorHost.std, firstBlock.id, 0);
}
};
if (matchModels(parent, [NoteBlockModel])) {
const hasTitleEditor = getDocTitleInlineEditor(editorHost);
const rootModel = model.store.root as RootBlockModel;
const title = rootModel.props.title;
const shouldHandleTitle = parent.isPageBlock() && hasTitleEditor;
doc.captureSync();
let textLength = 0;
if (text) {
textLength = text.length;
title.join(text);
if (shouldHandleTitle) {
let textLength = 0;
if (text) {
textLength = text.length;
title.join(text);
}
if (model.children.length > 0 || doc.getNext(model)) {
doc.deleteBlock(model, {
bringChildrenTo: parent,
});
}
// no other blocks, preserve a empty line
else {
text?.clear();
}
focusTitle(editorHost, title.length - textLength);
return true;
}
// Preserve at least one block to be able to focus on container click
if (doc.getNext(model) || model.children.length > 0) {
if (
text?.length === 0 &&
(model.children.length > 0 || doc.getNext(model))
) {
doc.deleteBlock(model, {
bringChildrenTo: parent,
});
} else {
text?.clear();
focusFirstBlockStart();
return true;
}
focusTitle(editorHost, title.length - textLength);
return true;
}
if (
matchModels(parent, [EdgelessTextBlockModel]) ||
model.children.length > 0
matchModels(parent, [EdgelessTextBlockModel]) &&
text?.length === 0 &&
(model.children.length > 0 || doc.getNext(model))
) {
doc.deleteBlock(model, {
bringChildrenTo: parent,
});
focusFirstBlockStart();
return true;
}

View File

@@ -2,9 +2,13 @@ import {
type ViewExtensionContext,
ViewExtensionProvider,
} from '@blocksuite/affine-ext-loader';
import { ParagraphBlockModel } from '@blocksuite/affine-model';
import { BlockViewExtension, FlavourExtension } from '@blocksuite/std';
import { literal } from 'lit/static-html.js';
import { z } from 'zod';
import { effects } from './effects';
import { ParagraphMarkdownExtension } from './markdown.js';
import { ParagraphBlockConfigExtension } from './paragraph-block-config.js';
import {
ParagraphKeymapExtension,
@@ -22,11 +26,6 @@ const placeholders = {
quote: '',
};
import { ParagraphBlockModel } from '@blocksuite/affine-model';
import { z } from 'zod';
import { effects } from './effects';
const optionsSchema = z.object({
getPlaceholder: z.optional(
z.function().args(z.instanceof(ParagraphBlockModel)).returns(z.string())
@@ -61,6 +60,7 @@ export class ParagraphViewExtension extends ViewExtensionProvider<
ParagraphBlockConfigExtension({
getPlaceholder,
}),
ParagraphMarkdownExtension,
]);
}
}

View File

@@ -9,7 +9,10 @@ import {
getSurfaceComponent,
} from '@blocksuite/affine-block-surface';
import { splitIntoLines } from '@blocksuite/affine-gfx-text';
import type { ShapeElementModel } from '@blocksuite/affine-model';
import type {
EmbedCardStyle,
ShapeElementModel,
} from '@blocksuite/affine-model';
import {
BookmarkStyles,
DEFAULT_NOTE_HEIGHT,
@@ -32,6 +35,7 @@ import {
TelemetryProvider,
} from '@blocksuite/affine-shared/services';
import {
convertToPng,
isInsidePageEditor,
isTopLevelBlock,
isUrlInClipboard,
@@ -64,7 +68,7 @@ import * as Y from 'yjs';
import { PageClipboard } from '../../clipboard/index.js';
import { getSortedCloneElements } from '../utils/clone-utils.js';
import { isCanvasElementWithText } from '../utils/query.js';
import { isCanvasElementWithText, isImageBlock } from '../utils/query.js';
import { createElementsFromClipboardDataCommand } from './command.js';
import {
isPureFileInClipboard,
@@ -123,6 +127,49 @@ export class EdgelessClipboardController extends PageClipboard {
return;
}
// Only when an image is selected, it can be pasted normally to page mode.
if (elements.length === 1 && isImageBlock(elements[0])) {
const element = elements[0];
const sourceId = element.props.sourceId$.peek();
if (!sourceId) return;
await this.std.clipboard.writeToClipboard(async items => {
const job = this.std.store.getTransformer();
await job.assetsManager.readFromBlob(sourceId);
let blob = job.assetsManager.getAssets().get(sourceId) ?? null;
if (!blob) {
return items;
}
let type = blob.type;
let supported = false;
try {
supported = ClipboardItem?.supports(type) ?? false;
} catch (err) {
console.error(err);
}
// TODO(@fundon): when converting jpeg to png, image may become larger and exceed the limit.
if (!supported) {
type = 'image/png';
blob = await convertToPng(blob);
}
if (blob) {
return {
...items,
[`${type}`]: blob,
};
}
return items;
});
return;
}
await this.std.clipboard.writeToClipboard(async _items => {
const data = await prepareClipboardData(elements, this.std);
return {
@@ -236,7 +283,7 @@ export class EdgelessClipboardController extends PageClipboard {
const options: Record<string, unknown> = {};
let flavour = 'affine:bookmark';
let style = BookmarkStyles[0];
let style: EmbedCardStyle = BookmarkStyles[0];
let isInternalLink = false;
let isLinkedBlock = false;
@@ -559,6 +606,10 @@ export class EdgelessClipboardController extends PageClipboard {
}
private async _pasteTextContentAsNote(content: BlockSnapshot[] | string) {
if (content === '') {
return;
}
const { x, y } = this.toolManager.lastMousePos$.peek();
const noteProps = {

View File

@@ -69,37 +69,39 @@ export async function prepareClipboardData(
export function isPureFileInClipboard(clipboardData: DataTransfer) {
const types = clipboardData.types;
return (
(types.length === 1 && types[0] === 'Files') ||
(types.length === 2 &&
(types.includes('text/plain') || types.includes('text/html')) &&
types.includes('Files'))
);
const allowedTypes = new Set([
'Files',
'text/plain',
'text/html',
'application/x-moz-file',
]);
return types.includes('Files') && types.every(type => allowedTypes.has(type));
}
export function tryGetSvgFromClipboard(clipboardData: DataTransfer) {
const types = clipboardData.types;
try {
const parser = new DOMParser();
const svgDoc = parser.parseFromString(
clipboardData.getData('text/plain'),
'image/svg+xml'
);
const svg = svgDoc.documentElement;
if (types.length === 1 && types[0] !== 'text/plain') {
if (svg.tagName !== 'svg' || !svg.hasAttribute('xmlns')) {
return null;
}
const svgContent = DOMPurify.sanitize(svgDoc.documentElement, {
USE_PROFILES: { svg: true },
});
const blob = new Blob([svgContent], { type: 'image/svg+xml' });
const file = new File([blob], 'pasted-image.svg', {
type: 'image/svg+xml',
});
return file;
} catch {
return null;
}
const parser = new DOMParser();
const svgDoc = parser.parseFromString(
clipboardData.getData('text/plain'),
'image/svg+xml'
);
const svg = svgDoc.documentElement;
if (svg.tagName !== 'svg' || !svg.hasAttribute('xmlns')) {
return null;
}
const svgContent = DOMPurify.sanitize(svgDoc.documentElement, {
USE_PROFILES: { svg: true },
});
const blob = new Blob([svgContent], { type: 'image/svg+xml' });
const file = new File([blob], 'pasted-image.svg', { type: 'image/svg+xml' });
return file;
}
export function edgelessElementsBoundFromRawData(

View File

@@ -129,7 +129,7 @@ export class EdgelessRootBlockComponent extends BlockComponent<
) as SurfaceBlockModel;
}
private get _viewportElement(): HTMLElement {
get viewportElement(): HTMLElement {
return this.std.get(ViewportElementProvider).viewportElement;
}
@@ -267,7 +267,7 @@ export class EdgelessRootBlockComponent extends BlockComponent<
this.gfx.viewport.onResize();
});
resizeObserver.observe(this._viewportElement);
resizeObserver.observe(this.viewportElement);
this._resizeObserver = resizeObserver;
}

View File

@@ -43,6 +43,25 @@ type RendererOptions = {
surfaceModel: SurfaceBlockModel;
};
const UpdateType = {
ELEMENT_ADDED: 'element-added',
ELEMENT_REMOVED: 'element-removed',
ELEMENT_UPDATED: 'element-updated',
VIEWPORT_CHANGED: 'viewport-changed',
SIZE_CHANGED: 'size-changed',
ZOOM_STATE_CHANGED: 'zoom-state-changed',
} as const;
type UpdateType = (typeof UpdateType)[keyof typeof UpdateType];
interface IncrementalUpdateState {
dirtyElementIds: Set<string>;
viewportDirty: boolean;
sizeDirty: boolean;
usePlaceholderDirty: boolean;
pendingUpdates: Map<string, UpdateType[]>;
}
const PLACEHOLDER_RESET_STYLES = {
border: 'none',
borderRadius: '0',
@@ -141,6 +160,18 @@ export class DomRenderer {
private _sizeUpdatedRafId: number | null = null;
private readonly _updateState: IncrementalUpdateState = {
dirtyElementIds: new Set(),
viewportDirty: false,
sizeDirty: false,
usePlaceholderDirty: false,
pendingUpdates: new Map(),
};
private _lastViewportBounds: Bound | null = null;
private _lastZoom: number | null = null;
private _lastUsePlaceholder: boolean = false;
rootElement: HTMLElement;
private readonly _elementsMap = new Map<string, HTMLElement>();
@@ -186,6 +217,7 @@ export class DomRenderer {
private _initViewport() {
this._disposables.add(
this.viewport.viewportUpdated.subscribe(() => {
this._markViewportDirty();
this.refresh();
})
);
@@ -195,6 +227,7 @@ export class DomRenderer {
if (this._sizeUpdatedRafId) return;
this._sizeUpdatedRafId = requestConnectedFrame(() => {
this._sizeUpdatedRafId = null;
this._markSizeDirty();
this._resetSize();
this._render();
this.refresh();
@@ -208,6 +241,7 @@ export class DomRenderer {
if (this.usePlaceholder !== shouldRenderPlaceholders) {
this.usePlaceholder = shouldRenderPlaceholders;
this._markUsePlaceholderDirty();
this.refresh();
}
})
@@ -307,6 +341,292 @@ export class DomRenderer {
}
private _render() {
this._renderIncremental();
}
private _watchSurface(surfaceModel: SurfaceBlockModel) {
this._disposables.add(
surfaceModel.elementAdded.subscribe(payload => {
this._markElementDirty(payload.id, UpdateType.ELEMENT_ADDED);
this.refresh();
})
);
this._disposables.add(
surfaceModel.elementRemoved.subscribe(payload => {
this._markElementDirty(payload.id, UpdateType.ELEMENT_REMOVED);
this.refresh();
})
);
this._disposables.add(
surfaceModel.localElementAdded.subscribe(payload => {
this._markElementDirty(payload.id, UpdateType.ELEMENT_ADDED);
this.refresh();
})
);
this._disposables.add(
surfaceModel.localElementDeleted.subscribe(payload => {
this._markElementDirty(payload.id, UpdateType.ELEMENT_REMOVED);
this.refresh();
})
);
this._disposables.add(
surfaceModel.localElementUpdated.subscribe(payload => {
this._markElementDirty(payload.model.id, UpdateType.ELEMENT_UPDATED);
this.refresh();
})
);
this._disposables.add(
surfaceModel.elementUpdated.subscribe(payload => {
// ignore externalXYWH update cause it's updated by the renderer
if (payload.props['externalXYWH']) return;
this._markElementDirty(payload.id, UpdateType.ELEMENT_UPDATED);
this.refresh();
})
);
}
addOverlay = (overlay: Overlay) => {
overlay.setRenderer(null);
this._overlays.add(overlay);
this.refresh();
};
attach = (container: HTMLElement) => {
this._container = container;
container.append(this.rootElement);
this._resetSize();
this.refresh();
};
dispose = () => {
this._overlays.forEach(overlay => overlay.dispose());
this._overlays.clear();
this._disposables.dispose();
if (this._refreshRafId) {
cancelAnimationFrame(this._refreshRafId);
this._refreshRafId = null;
}
if (this._sizeUpdatedRafId) {
cancelAnimationFrame(this._sizeUpdatedRafId);
this._sizeUpdatedRafId = null;
}
this.rootElement.remove();
this._elementsMap.clear();
};
generateColorProperty = (color: Color, fallback?: Color) => {
return (
this.provider.generateColorProperty?.(color, fallback) ?? 'transparent'
);
};
getColorScheme = () => {
return this.provider.getColorScheme?.() ?? ColorScheme.Light;
};
getColorValue = (color: Color, fallback?: Color, real?: boolean) => {
return (
this.provider.getColorValue?.(color, fallback, real) ?? 'transparent'
);
};
getPropertyValue = (property: string) => {
return this.provider.getPropertyValue?.(property) ?? '';
};
refresh = () => {
if (this._refreshRafId !== null) return;
this._refreshRafId = requestConnectedFrame(() => {
this._refreshRafId = null;
this._render();
}, this._container);
};
removeOverlay = (overlay: Overlay) => {
if (!this._overlays.has(overlay)) {
return;
}
this._overlays.delete(overlay);
this.refresh();
};
/**
* Mark a specific element as dirty for incremental updates
* @param elementId - The ID of the element to mark as dirty
* @param updateType - The type of update (optional, defaults to ELEMENT_UPDATED)
*/
markElementDirty = (
elementId: string,
updateType: UpdateType = UpdateType.ELEMENT_UPDATED
) => {
this._markElementDirty(elementId, updateType);
};
/**
* Force a full re-render of all elements
*/
forceFullRender = () => {
this._updateState.viewportDirty = true;
this.refresh();
};
private _markElementDirty(elementId: string, updateType: UpdateType) {
this._updateState.dirtyElementIds.add(elementId);
const currentUpdates =
this._updateState.pendingUpdates.get(elementId) || [];
if (!currentUpdates.includes(updateType)) {
currentUpdates.push(updateType);
this._updateState.pendingUpdates.set(elementId, currentUpdates);
}
}
private _markViewportDirty() {
this._updateState.viewportDirty = true;
}
private _markSizeDirty() {
this._updateState.sizeDirty = true;
}
private _markUsePlaceholderDirty() {
this._updateState.usePlaceholderDirty = true;
}
private _clearUpdateState() {
this._updateState.dirtyElementIds.clear();
this._updateState.viewportDirty = false;
this._updateState.sizeDirty = false;
this._updateState.usePlaceholderDirty = false;
this._updateState.pendingUpdates.clear();
}
private _isViewportChanged(): boolean {
const { viewportBounds, zoom } = this.viewport;
if (!this._lastViewportBounds || !this._lastZoom) {
return true;
}
return (
this._lastViewportBounds.x !== viewportBounds.x ||
this._lastViewportBounds.y !== viewportBounds.y ||
this._lastViewportBounds.w !== viewportBounds.w ||
this._lastViewportBounds.h !== viewportBounds.h ||
this._lastZoom !== zoom
);
}
private _isUsePlaceholderChanged(): boolean {
return this._lastUsePlaceholder !== this.usePlaceholder;
}
private _updateLastState() {
const { viewportBounds, zoom } = this.viewport;
this._lastViewportBounds = {
x: viewportBounds.x,
y: viewportBounds.y,
w: viewportBounds.w,
h: viewportBounds.h,
} as Bound;
this._lastZoom = zoom;
this._lastUsePlaceholder = this.usePlaceholder;
}
private _renderIncremental() {
const { viewportBounds, zoom } = this.viewport;
const addedElements: HTMLElement[] = [];
const elementsToRemove: HTMLElement[] = [];
const needsFullRender =
this._isViewportChanged() ||
this._isUsePlaceholderChanged() ||
this._updateState.sizeDirty ||
this._updateState.viewportDirty ||
this._updateState.usePlaceholderDirty;
if (needsFullRender) {
this._renderFull();
this._updateLastState();
this._clearUpdateState();
return;
}
// Only update dirty elements
const elementsFromGrid = this.grid.search(viewportBounds, {
filter: ['canvas', 'local'],
}) as SurfaceElementModel[];
const visibleElementIds = new Set<string>();
// 1. Update dirty elements
for (const elementModel of elementsFromGrid) {
const display = (elementModel.display ?? true) && !elementModel.hidden;
if (
display &&
intersects(getBoundWithRotation(elementModel), viewportBounds)
) {
visibleElementIds.add(elementModel.id);
// Only update dirty elements
if (this._updateState.dirtyElementIds.has(elementModel.id)) {
if (
this.usePlaceholder &&
!(elementModel as GfxCompatibleInterface).forceFullRender
) {
this._renderOrUpdatePlaceholder(
elementModel,
viewportBounds,
zoom,
addedElements
);
} else {
this._renderOrUpdateFullElement(
elementModel,
viewportBounds,
zoom,
addedElements
);
}
}
}
}
// 2. Remove elements that are no longer in the grid
for (const elementId of this._updateState.dirtyElementIds) {
const updateTypes = this._updateState.pendingUpdates.get(elementId) || [];
if (
updateTypes.includes(UpdateType.ELEMENT_REMOVED) ||
!visibleElementIds.has(elementId)
) {
const domElem = this._elementsMap.get(elementId);
if (domElem) {
domElem.remove();
this._elementsMap.delete(elementId);
elementsToRemove.push(domElem);
}
}
}
// 3. Notify changes
if (addedElements.length > 0 || elementsToRemove.length > 0) {
this.elementsUpdated.next({
elements: Array.from(this._elementsMap.values()),
added: addedElements,
removed: elementsToRemove,
});
}
this._updateLastState();
this._clearUpdateState();
}
private _renderFull() {
const { viewportBounds, zoom } = this.viewport;
const addedElements: HTMLElement[] = [];
const elementsToRemove: HTMLElement[] = [];
@@ -387,100 +707,4 @@ export class DomRenderer {
});
}
}
private _watchSurface(surfaceModel: SurfaceBlockModel) {
this._disposables.add(
surfaceModel.elementAdded.subscribe(() => this.refresh())
);
this._disposables.add(
surfaceModel.elementRemoved.subscribe(() => this.refresh())
);
this._disposables.add(
surfaceModel.localElementAdded.subscribe(() => this.refresh())
);
this._disposables.add(
surfaceModel.localElementDeleted.subscribe(() => this.refresh())
);
this._disposables.add(
surfaceModel.localElementUpdated.subscribe(() => this.refresh())
);
this._disposables.add(
surfaceModel.elementUpdated.subscribe(payload => {
// ignore externalXYWH update cause it's updated by the renderer
if (payload.props['externalXYWH']) return;
this.refresh();
})
);
}
addOverlay(overlay: Overlay) {
overlay.setRenderer(null);
this._overlays.add(overlay);
this.refresh();
}
attach(container: HTMLElement) {
this._container = container;
container.append(this.rootElement);
this._resetSize();
this.refresh();
}
dispose(): void {
this._overlays.forEach(overlay => overlay.dispose());
this._overlays.clear();
this._disposables.dispose();
if (this._refreshRafId) {
cancelAnimationFrame(this._refreshRafId);
this._refreshRafId = null;
}
if (this._sizeUpdatedRafId) {
cancelAnimationFrame(this._sizeUpdatedRafId);
this._sizeUpdatedRafId = null;
}
this.rootElement.remove();
this._elementsMap.clear();
}
generateColorProperty(color: Color, fallback?: Color) {
return (
this.provider.generateColorProperty?.(color, fallback) ?? 'transparent'
);
}
getColorScheme() {
return this.provider.getColorScheme?.() ?? ColorScheme.Light;
}
getColorValue(color: Color, fallback?: Color, real?: boolean) {
return (
this.provider.getColorValue?.(color, fallback, real) ?? 'transparent'
);
}
getPropertyValue(property: string) {
return this.provider.getPropertyValue?.(property) ?? '';
}
refresh() {
if (this._refreshRafId !== null) return;
this._refreshRafId = requestConnectedFrame(() => {
this._refreshRafId = null;
this._render();
}, this._container);
}
removeOverlay(overlay: Overlay) {
if (!this._overlays.has(overlay)) {
return;
}
this._overlays.delete(overlay);
this.refresh();
}
}

View File

@@ -647,6 +647,16 @@ export class TableCell extends SignalWatcher(
return this.richText$.value?.inlineEditor;
}
private readonly _handleKeyDown = (e: KeyboardEvent) => {
if (e.key !== 'Escape') {
if (e.key === 'Tab') {
e.preventDefault();
return;
}
e.stopPropagation();
}
};
override connectedCallback() {
super.connectedCallback();
if (this.readonly) {
@@ -659,10 +669,7 @@ export class TableCell extends SignalWatcher(
this.inlineEditor?.selectAll();
}
};
this.addEventListener('keydown', selectAll);
this.disposables.add(() => {
this.removeEventListener('keydown', selectAll);
});
this.disposables.addFromEvent(this, 'keydown', selectAll);
this.disposables.addFromEvent(this, 'click', (e: MouseEvent) => {
e.stopPropagation();
requestAnimationFrame(() => {
@@ -679,6 +686,13 @@ export class TableCell extends SignalWatcher(
}
this.richText$.value?.updateComplete
.then(() => {
const inlineEditor = this.inlineEditor;
if (inlineEditor) {
this.disposables.add(
inlineEditor.slots.keydown.subscribe(this._handleKeyDown)
);
}
this.disposables.add(
effect(() => {
const richText = this.richText$.value;

View File

@@ -195,8 +195,7 @@ export class EmbedCardEditModal extends SignalWatcher(
const description = this.description$.value.trim();
const props: AliasInfo = { title };
if (description) props.description = description;
const props: AliasInfo = { title, description };
this.onSave?.(std, blockComponent, props);

View File

@@ -29,12 +29,6 @@ export class OpenDocDropdownMenu extends SignalWatcher(
gap: unset !important;
}
editor-icon-button {
.label {
font-weight: 400;
}
}
div[data-orientation] {
width: 264px;
gap: 4px;

View File

@@ -9,6 +9,7 @@ const toolbarColorKeys: Array<keyof AffineCssVariables> = [
'--affine-background-overlay-panel-color',
'--affine-v2-layer-background-overlayPanel' as never,
'--affine-v2-layer-insideBorder-blackBorder' as never,
'--affine-v2-icon-primary' as never,
'--affine-background-error-color',
'--affine-background-primary-color',
'--affine-background-tertiary-color',

View File

@@ -112,7 +112,10 @@ export class GroupTrait {
return;
}
const { staticMap, groupInfo } = staticInfo;
const groupMap: Record<string, Group> = { ...staticMap };
const groupMap: Record<string, Group> = {};
Object.entries(staticMap).forEach(([key, group]) => {
groupMap[key] = new Group(key, group.value, groupInfo, this);
});
this.view.rows$.value.forEach(row => {
const value = this.view.cellGetOrCreate(row.rowId, groupInfo.property.id)
.jsonValue$.value;
@@ -182,6 +185,7 @@ export class GroupTrait {
) {}
addToGroup(rowId: string, key: string) {
this.view.lockRows(false);
const groupMap = this.groupDataMap$.value;
const groupInfo = this.groupInfo$.value;
if (!groupMap || !groupInfo) {
@@ -254,6 +258,7 @@ export class GroupTrait {
toGroupKey: string,
position: InsertToPosition
) {
this.view.lockRows(false);
const groupMap = this.groupDataMap$.value;
if (!groupMap) {
return;
@@ -290,6 +295,7 @@ export class GroupTrait {
}
moveGroupTo(groupKey: string, position: InsertToPosition) {
this.view.lockRows(false);
const groups = this.groupsDataList$.value;
if (!groups) {
return;
@@ -305,6 +311,7 @@ export class GroupTrait {
}
removeFromGroup(rowId: string, key: string) {
this.view.lockRows(false);
const groupMap = this.groupDataMap$.value;
if (!groupMap) {
return;
@@ -323,6 +330,7 @@ export class GroupTrait {
}
updateValue(rows: string[], value: unknown) {
this.view.lockRows(false);
const propertyId = this.property$.value?.id;
if (!propertyId) {
return;

View File

@@ -128,6 +128,7 @@ export abstract class SingleViewBase<
);
rowsDelete(rows: string[]): void {
this.lockRows(false);
this.dataSource.rowDelete(rows);
}
@@ -258,6 +259,7 @@ export abstract class SingleViewBase<
abstract propertyGetOrCreate(propertyId: string): Property;
rowAdd(insertPosition: InsertToPosition | number): string {
this.lockRows(false);
return this.dataSource.rowAdd(insertPosition);
}

View File

@@ -61,10 +61,12 @@ export class MobileKanbanGroup extends SignalWatcher(
private readonly clickAddCard = () => {
this.view.addCard('end', this.group.key);
this.requestUpdate();
};
private readonly clickAddCardInStart = () => {
this.view.addCard('start', this.group.key);
this.requestUpdate();
};
private readonly clickGroupOptions = (e: MouseEvent) => {
@@ -79,12 +81,14 @@ export class MobileKanbanGroup extends SignalWatcher(
this.group.rows.forEach(row => {
this.group.manager.removeFromGroup(row.rowId, this.group.key);
});
this.requestUpdate();
},
}),
menu.action({
name: 'Delete Cards',
select: () => {
this.view.rowsDelete(this.group.rows.map(row => row.rowId));
this.requestUpdate();
},
}),
],

View File

@@ -66,7 +66,9 @@ export class MobileKanbanViewUILogic extends DataViewUILogicBase<
addRow = (position: InsertToPosition) => {
if (this.readonly) return;
return this.view.rowAdd(position);
const id = this.view.rowAdd(position);
this.ui$.value?.requestUpdate();
return id;
};
focusFirstCell = () => {};

View File

@@ -83,6 +83,7 @@ export const popCardMenu = (
{ before: true, id: cardId },
groupKey
);
kanbanViewLogic.ui$.value?.requestUpdate();
},
}),
menu.action({
@@ -97,6 +98,7 @@ export const popCardMenu = (
{ before: false, id: cardId },
groupKey
);
kanbanViewLogic.ui$.value?.requestUpdate();
},
}),
],
@@ -111,6 +113,7 @@ export const popCardMenu = (
prefix: DeleteIcon(),
select: () => {
kanbanViewLogic.view.rowsDelete([cardId]);
kanbanViewLogic.ui$.value?.requestUpdate();
},
}),
],

View File

@@ -128,6 +128,7 @@ export class KanbanSelectionController implements ReactiveController {
if (selection.selectionType === 'card') {
this.view.rowsDelete(selection.cards.map(v => v.cardId));
this.selection = undefined;
this.logic.ui$.value?.requestUpdate();
}
}

View File

@@ -110,6 +110,7 @@ export class KanbanGroup extends SignalWatcher(
isEditing: true,
};
});
this.requestUpdate();
};
private readonly clickAddCardInStart = () => {
@@ -127,6 +128,7 @@ export class KanbanGroup extends SignalWatcher(
isEditing: true,
};
});
this.requestUpdate();
};
private readonly clickGroupOptions = (e: MouseEvent) => {
@@ -139,12 +141,14 @@ export class KanbanGroup extends SignalWatcher(
this.group.rows.forEach(row => {
this.group.manager.removeFromGroup(row.rowId, this.group.key);
});
this.requestUpdate();
},
}),
menu.action({
name: 'Delete Cards',
select: () => {
this.view.rowsDelete(this.group.rows.map(row => row.rowId));
this.requestUpdate();
},
}),
]);

View File

@@ -73,6 +73,7 @@ export class KanbanViewUILogic extends DataViewUILogicBase<
rowId,
});
}
this.ui$.value?.requestUpdate();
return rowId;
};

View File

@@ -51,10 +51,12 @@ export class MobileTableGroup extends SignalWatcher(
private readonly clickAddRow = () => {
this.view.rowAdd('end', this.group?.key);
this.requestUpdate();
};
private readonly clickAddRowInStart = () => {
this.view.rowAdd('start', this.group?.key);
this.requestUpdate();
};
private readonly clickGroupOptions = (e: MouseEvent) => {
@@ -77,6 +79,7 @@ export class MobileTableGroup extends SignalWatcher(
name: 'Delete Cards',
select: () => {
this.view.rowsDelete(group.rows.map(row => row.rowId));
this.requestUpdate();
},
}),
]);

View File

@@ -38,6 +38,7 @@ export const popMobileRowMenu = (
prefix: DeleteIcon(),
select: () => {
view.rowsDelete([rowId]);
tableViewLogic.ui$.value?.requestUpdate();
},
}),
],

View File

@@ -44,6 +44,7 @@ export class TableClipboardController implements ReactiveController {
}
if (deleteRows.length) {
this.logic.view.rowsDelete(deleteRows);
this.logic.ui$.value?.requestUpdate();
}
}
this.clipboard
@@ -79,6 +80,14 @@ export class TableClipboardController implements ReactiveController {
const event = _context.get('clipboardState').raw;
event.stopPropagation();
const active = document.activeElement as HTMLElement | null;
if (
active &&
(active.tagName === 'INPUT' || active.tagName === 'TEXTAREA')
) {
return true;
}
const clipboardData = event.clipboardData;
if (!clipboardData) return;

View File

@@ -30,6 +30,7 @@ export class TableHotkeysController implements ReactiveController {
const rows = TableViewRowSelection.rowsIds(selection);
this.selectionController.selection = undefined;
this.logic.view.rowsDelete(rows);
this.logic.ui$.value?.requestUpdate();
return;
}
const {

View File

@@ -376,6 +376,7 @@ export class TableSelectionController implements ReactiveController {
deleteRow(rowId: string) {
this.view.rowsDelete([rowId]);
this.focusToCell('up');
this.logic.ui$.value?.requestUpdate();
}
focusFirstCell() {

View File

@@ -45,6 +45,7 @@ export class TableGroupFooter extends WithDisposable(ShadowlessElement) {
private readonly clickAddRow = () => {
const group = this.group$.value;
const rowId = this.tableViewManager.rowAdd('end', group?.key);
this.requestUpdate();
requestAnimationFrame(() => {
const rowIndex = this.selectionController.getRow(group?.key, rowId)

View File

@@ -58,6 +58,7 @@ export class TableGroupHeader extends SignalWatcher(
return;
}
this.tableViewManager.rowAdd('start', group.key);
this.requestUpdate();
const selectionController = this.selectionController;
selectionController.selection = undefined;
requestAnimationFrame(() => {
@@ -95,6 +96,7 @@ export class TableGroupHeader extends SignalWatcher(
name: 'Delete Cards',
select: () => {
this.tableViewManager.rowsDelete(group.rows.map(row => row.rowId));
this.requestUpdate();
},
}),
]);

View File

@@ -71,6 +71,7 @@ export const popRowMenu = (
prefix: DeleteIcon(),
select: () => {
selectionController.view.rowsDelete(rows);
selectionController.logic.ui$.value?.requestUpdate();
},
}),
],

View File

@@ -43,6 +43,7 @@ export class TableClipboardController implements ReactiveController {
}
if (deleteRows.length) {
this.logic.view.rowsDelete(deleteRows);
this.logic.ui$.value?.requestUpdate();
}
}
this.clipboard
@@ -78,6 +79,14 @@ export class TableClipboardController implements ReactiveController {
const event = _context.get('clipboardState').raw;
event.stopPropagation();
const active = document.activeElement as HTMLElement | null;
if (
active &&
(active.tagName === 'INPUT' || active.tagName === 'TEXTAREA')
) {
return true;
}
const clipboardData = event.clipboardData;
if (!clipboardData) return;

View File

@@ -28,6 +28,7 @@ export class TableHotkeysController implements ReactiveController {
const rows = TableViewRowSelection.rowsIds(selection);
this.selectionController.selection = undefined;
this.logic.view.rowsDelete(rows);
this.logic.ui$.value?.requestUpdate();
return;
}
const {

View File

@@ -351,6 +351,7 @@ export class TableSelectionController implements ReactiveController {
deleteRow(rowId: string) {
this.view.rowsDelete([rowId]);
this.focusToCell('up');
this.logic.ui$.value?.requestUpdate();
}
focusFirstCell() {

View File

@@ -83,6 +83,7 @@ export class TableGroup extends SignalWatcher(
},
isEditing: true,
});
this.requestUpdate();
});
};
@@ -102,6 +103,7 @@ export class TableGroup extends SignalWatcher(
},
isEditing: true,
});
this.requestUpdate();
});
};
@@ -125,6 +127,7 @@ export class TableGroup extends SignalWatcher(
name: 'Delete Cards',
select: () => {
this.view.rowsDelete(group.rows.map(row => row.rowId));
this.requestUpdate();
},
}),
]);

View File

@@ -71,6 +71,7 @@ export const popRowMenu = (
prefix: DeleteIcon(),
select: () => {
selectionController.view.rowsDelete(rows);
selectionController.logic.ui$.value?.requestUpdate();
},
}),
],

View File

@@ -16,5 +16,6 @@ export const renderFilterBar = (props: DataViewWidgetProps) => {
.vars="${filterTrait.view.vars$}"
.filterGroup="${filterTrait.filter$}"
.onChange="${filterTrait.filterSet}"
.dataViewLogic="${props.dataViewLogic}"
></filter-bar>`;
};

View File

@@ -16,6 +16,7 @@ import { property } from 'lit/decorators.js';
import type { Variable } from '../../../core/expression/types.js';
import type { Filter, FilterGroup } from '../../../core/filter/types.js';
import { popCreateFilter } from '../../../core/index.js';
import type { DataViewUILogicBase } from '../../../core/view/data-view-base.js';
import { popFilterGroup } from './group-panel-view.js';
export class FilterBar extends SignalWatcher(ShadowlessElement) {
@@ -99,6 +100,7 @@ export class FilterBar extends SignalWatcher(ShadowlessElement) {
requestAnimationFrame(() => {
this.expandGroup(element, index);
});
this.dataViewLogic.eventTrace('CreateDatabaseFilter', {});
},
});
};
@@ -206,6 +208,9 @@ export class FilterBar extends SignalWatcher(ShadowlessElement) {
@property({ attribute: false })
accessor vars!: ReadonlySignal<Variable[]>;
@property({ attribute: false })
accessor dataViewLogic!: DataViewUILogicBase;
}
declare global {

View File

@@ -26,7 +26,10 @@ import { repeat } from 'lit/directives/repeat.js';
import type { Variable } from '../../../core/expression/types.js';
import type { FilterTrait } from '../../../core/filter/trait.js';
import type { Filter, FilterGroup } from '../../../core/filter/types.js';
import { popCreateFilter } from '../../../core/index.js';
import {
type DataViewUILogicBase,
popCreateFilter,
} from '../../../core/index.js';
import {
type FilterGroupView,
getDepth,
@@ -375,6 +378,7 @@ export const popFilterRoot = (
props: {
filterTrait: FilterTrait;
onBack: () => void;
dataViewLogic: DataViewUILogicBase;
}
) => {
const filterTrait = props.filterTrait;
@@ -414,6 +418,10 @@ export const popFilterRoot = (
...value,
conditions: [...value.conditions, filter],
});
props.dataViewLogic.eventTrace(
'CreateDatabaseFilter',
{}
);
},
},
{ middleware: subMenuMiddleware }

View File

@@ -75,6 +75,7 @@ export class DataViewHeaderToolsFilter extends WidgetBase {
conditions: [filter],
};
this.toggleShowFilter(true);
this.dataViewLogic.eventTrace('CreateDatabaseFilter', {});
},
}
);

View File

@@ -145,13 +145,16 @@ const createSettingMenus = (
popFilterRoot(target, {
filterTrait: filterTrait,
onBack: reopen,
dataViewLogic: dataViewLogic,
});
dataViewLogic.eventTrace('CreateDatabaseFilter', {});
},
});
} else {
popFilterRoot(target, {
filterTrait: filterTrait,
onBack: reopen,
dataViewLogic: dataViewLogic,
});
}
},

View File

@@ -9,6 +9,7 @@ import {
} from '@blocksuite/affine-ext-loader';
import {
AutoClearSelectionService,
CitationService,
DefaultOpenDocExtension,
DNDAPIExtension,
DocDisplayMetaService,
@@ -76,6 +77,7 @@ export class FoundationViewExtension extends ViewExtensionProvider<FoundationVie
FileSizeLimitService,
LinkPreviewCache,
LinkPreviewService,
CitationService,
]);
context.register(clipboardConfigs);
if (this.isEdgeless(context.scope)) {

View File

@@ -192,10 +192,14 @@ export class DocTitle extends WithDisposable(ShadowlessElement) {
this._updateTitleInMeta();
this.requestUpdate();
};
this._rootModel?.props.title.yText.observe(updateMetaTitle);
this._disposables.add(() => {
this._rootModel?.props.title.yText.unobserve(updateMetaTitle);
});
if (this._rootModel) {
const rootModel = this._rootModel;
rootModel.props.title.yText.observe(updateMetaTitle);
this._disposables.add(() => {
rootModel.props.title.yText.unobserve(updateMetaTitle);
});
}
}
override render() {

View File

@@ -1,6 +1,6 @@
import { OverlayIdentifier } from '@blocksuite/affine-block-surface';
import { MindmapElementModel } from '@blocksuite/affine-model';
import { Bound } from '@blocksuite/global/gfx';
import { type Bound } from '@blocksuite/global/gfx';
import {
type DragExtensionInitializeContext,
type ExtensionDragMoveContext,
@@ -74,47 +74,63 @@ export class SnapExtension extends InteractivityExtension {
return {};
}
let alignBound: Bound | null = null;
return {
onResizeStart(context) {
alignBound = snapOverlay.setMovingElements(context.elements);
snapOverlay.setMovingElements(context.elements);
},
onResizeMove(context) {
if (!alignBound || alignBound.w === 0 || alignBound.h === 0) {
return;
const {
handle,
originalBound,
scaleX,
scaleY,
handleSign,
currentHandlePos,
elements,
} = context;
const rotate = elements.length > 1 ? 0 : elements[0].rotate;
const alignDirection: ('vertical' | 'horizontal')[] = [];
let switchDirection = false;
let nx = handleSign.x;
let ny = handleSign.y;
if (handle.length > 6) {
alignDirection.push('vertical', 'horizontal');
} else if (rotate % 90 === 0) {
nx =
handleSign.x * Math.cos((rotate / 180) * Math.PI) -
handleSign.y * Math.sin((rotate / 180) * Math.PI);
ny =
handleSign.x * Math.sin((rotate / 180) * Math.PI) +
handleSign.y * Math.cos((rotate / 180) * Math.PI);
if (Math.abs(nx) > Math.abs(ny)) {
alignDirection.push('horizontal');
} else {
alignDirection.push('vertical');
}
if (rotate % 180 !== 0) {
switchDirection = true;
}
}
const { handle, handleSign, lockRatio } = context;
let { dx, dy } = context;
if (lockRatio) {
const min = Math.min(
Math.abs(dx / alignBound.w),
Math.abs(dy / alignBound.h)
if (alignDirection.length > 0) {
const rst = snapOverlay.alignResize(
currentHandlePos,
alignDirection
);
dx = min * Math.sign(dx) * alignBound.w;
dy = min * Math.sign(dy) * alignBound.h;
const dx = switchDirection ? ny * rst.dy : nx * rst.dx;
const dy = switchDirection ? nx * rst.dx : ny * rst.dy;
context.suggest({
scaleX: scaleX + dx / originalBound.w,
scaleY: scaleY + dy / originalBound.h,
});
}
const currentBound = new Bound(
alignBound.x +
(handle.includes('left') ? -dx * handleSign.xSign : 0),
alignBound.y +
(handle.includes('top') ? -dy * handleSign.ySign : 0),
Math.abs(alignBound.w + dx * handleSign.xSign),
Math.abs(alignBound.h + dy * handleSign.ySign)
);
const alignRst = snapOverlay.align(currentBound);
context.suggest({
dx: alignRst.dx + context.dx,
dy: alignRst.dy + context.dy,
});
},
onResizeEnd() {
alignBound = null;
snapOverlay.clear();
},
};

View File

@@ -3,7 +3,7 @@ import {
ConnectorElementModel,
MindmapElementModel,
} from '@blocksuite/affine-model';
import { almostEqual, Bound, Point } from '@blocksuite/global/gfx';
import { almostEqual, Bound, type IVec, Point } from '@blocksuite/global/gfx';
import type { GfxModel } from '@blocksuite/std/gfx';
interface Distance {
@@ -586,6 +586,60 @@ export class SnapOverlay extends Overlay {
);
}
alignResize(position: IVec, direction: ('vertical' | 'horizontal')[]) {
const rst = { dx: 0, dy: 0 };
const { viewport } = this.gfx;
const threshold = ALIGN_THRESHOLD / viewport.zoom;
const searchBound = new Bound(
position[0] - threshold / 2,
position[1] - threshold / 2,
threshold,
threshold
);
const alignBound = new Bound(position[0], position[1], 0, 0);
this._intraGraphicAlignLines = {
horizontal: [],
vertical: [],
};
this._distributedAlignLines = [];
this._updateAlignCandidates(searchBound);
for (const other of this._referenceBounds.all) {
const closestDistances = this._calculateClosestDistances(
alignBound,
other
);
if (
direction.includes('horizontal') &&
closestDistances.horiz &&
(!this._intraGraphicAlignLines.horizontal.length ||
Math.abs(closestDistances.horiz.distance) < Math.abs(rst.dx))
) {
this._updateXAlignPoint(rst, alignBound, other, closestDistances);
}
if (
direction.includes('vertical') &&
closestDistances.vert &&
(!this._intraGraphicAlignLines.vertical.length ||
Math.abs(closestDistances.vert.distance) < Math.abs(rst.dy))
) {
this._updateYAlignPoint(rst, alignBound, other, closestDistances);
}
}
this._intraGraphicAlignLines.horizontal =
this._intraGraphicAlignLines.horizontal.slice(0, 1);
this._intraGraphicAlignLines.vertical =
this._intraGraphicAlignLines.vertical.slice(0, 1);
this._renderer?.refresh();
return rst;
}
align(bound: Bound): { dx: number; dy: number } {
const rst = { dx: 0, dy: 0 };
const threshold = ALIGN_THRESHOLD / this.gfx.viewport.zoom;

View File

@@ -9,18 +9,35 @@ function applyShapeSpecificStyles(
element: HTMLElement,
zoom: number
) {
if (model.shapeType === 'rect') {
const w = model.w * zoom;
const h = model.h * zoom;
const r = model.radius ?? 0;
const borderRadius =
r < 1 ? `${Math.min(w * r, h * r)}px` : `${r * zoom}px`;
element.style.borderRadius = borderRadius;
} else if (model.shapeType === 'ellipse') {
element.style.borderRadius = '50%';
} else {
element.style.borderRadius = '';
// Reset properties that might be set by different shape types
element.style.removeProperty('clip-path');
element.style.removeProperty('border-radius');
// Clear DOM for shapes that don't use SVG, or if type changes from SVG-based to non-SVG-based
if (model.shapeType !== 'diamond' && model.shapeType !== 'triangle') {
while (element.firstChild) element.firstChild.remove();
}
switch (model.shapeType) {
case 'rect': {
const w = model.w * zoom;
const h = model.h * zoom;
const r = model.radius ?? 0;
const borderRadius =
r < 1 ? `${Math.min(w * r, h * r)}px` : `${r * zoom}px`;
element.style.borderRadius = borderRadius;
break;
}
case 'ellipse':
element.style.borderRadius = '50%';
break;
case 'diamond':
element.style.clipPath = 'polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%)';
break;
case 'triangle':
element.style.clipPath = 'polygon(50% 0%, 100% 100%, 0% 100%)';
break;
}
// No 'else' needed to clear styles, as they are reset at the beginning of the function.
}
function applyBorderStyles(
@@ -78,6 +95,9 @@ export const shapeDomRenderer = (
renderer: DomRenderer
): void => {
const { zoom } = renderer.viewport;
const unscaledWidth = model.w;
const unscaledHeight = model.h;
const fillColor = renderer.getColorValue(
model.fillColor,
DefaultTheme.shapeFillColor,
@@ -89,17 +109,80 @@ export const shapeDomRenderer = (
true
);
element.style.width = `${model.w * zoom}px`;
element.style.height = `${model.h * zoom}px`;
element.style.width = `${unscaledWidth * zoom}px`;
element.style.height = `${unscaledHeight * zoom}px`;
element.style.boxSizing = 'border-box';
// Apply shape-specific clipping, border-radius, and potentially clear innerHTML
applyShapeSpecificStyles(model, element, zoom);
element.style.backgroundColor = model.filled ? fillColor : 'transparent';
if (model.shapeType === 'diamond' || model.shapeType === 'triangle') {
// For diamond and triangle, fill and border are handled by inline SVG
element.style.border = 'none'; // Ensure no standard CSS border interferes
element.style.backgroundColor = 'transparent'; // Host element is transparent
const strokeW = model.strokeWidth;
const halfStroke = strokeW / 2; // Calculate half stroke width for point adjustment
let svgPoints = '';
if (model.shapeType === 'diamond') {
// Adjusted points for diamond
svgPoints = [
`${unscaledWidth / 2},${halfStroke}`,
`${unscaledWidth - halfStroke},${unscaledHeight / 2}`,
`${unscaledWidth / 2},${unscaledHeight - halfStroke}`,
`${halfStroke},${unscaledHeight / 2}`,
].join(' ');
} else {
// triangle
// Adjusted points for triangle
svgPoints = [
`${unscaledWidth / 2},${halfStroke}`,
`${unscaledWidth - halfStroke},${unscaledHeight - halfStroke}`,
`${halfStroke},${unscaledHeight - halfStroke}`,
].join(' ');
}
// Determine if stroke should be visible and its color
const finalStrokeColor =
model.strokeStyle !== 'none' && strokeW > 0 ? strokeColor : 'transparent';
// Determine dash array, only if stroke is visible and style is 'dash'
const finalStrokeDasharray =
model.strokeStyle === 'dash' && finalStrokeColor !== 'transparent'
? '12, 12'
: 'none';
// Determine fill color
const finalFillColor = model.filled ? fillColor : 'transparent';
// Build SVG safely with DOM-API
const SVG_NS = 'http://www.w3.org/2000/svg';
const svg = document.createElementNS(SVG_NS, 'svg');
svg.setAttribute('width', '100%');
svg.setAttribute('height', '100%');
svg.setAttribute('viewBox', `0 0 ${unscaledWidth} ${unscaledHeight}`);
svg.setAttribute('preserveAspectRatio', 'none');
const polygon = document.createElementNS(SVG_NS, 'polygon');
polygon.setAttribute('points', svgPoints);
polygon.setAttribute('fill', finalFillColor);
polygon.setAttribute('stroke', finalStrokeColor);
polygon.setAttribute('stroke-width', String(strokeW));
if (finalStrokeDasharray !== 'none') {
polygon.setAttribute('stroke-dasharray', finalStrokeDasharray);
}
svg.append(polygon);
// Replace existing children to avoid memory leaks
element.replaceChildren(svg);
} else {
// Standard rendering for other shapes (e.g., rect, ellipse)
// innerHTML was already cleared by applyShapeSpecificStyles if necessary
element.style.backgroundColor = model.filled ? fillColor : 'transparent';
applyBorderStyles(model, element, strokeColor, zoom); // Uses standard CSS border
}
applyBorderStyles(model, element, strokeColor, zoom);
applyTransformStyles(model, element);
element.style.boxSizing = 'border-box';
element.style.zIndex = renderer.layerManager.getZIndex(model).toString();
manageClassNames(model, element);

View File

@@ -1,6 +1,7 @@
import { HoverController } from '@blocksuite/affine-components/hover';
import { PeekViewProvider } from '@blocksuite/affine-components/peek';
import type { FootNote } from '@blocksuite/affine-model';
import { CitationProvider } from '@blocksuite/affine-shared/services';
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
import { WithDisposable } from '@blocksuite/global/lit';
@@ -117,6 +118,10 @@ export class AffineFootnoteNode extends WithDisposable(ShadowlessElement) {
return this.std.store.readonly;
}
get citationService() {
return this.std.get(CitationProvider);
}
onFootnoteClick = () => {
if (!this.footnote) {
return;
@@ -215,6 +220,10 @@ export class AffineFootnoteNode extends WithDisposable(ShadowlessElement) {
return null;
}
this.citationService.trackEvent('Hover', {
control: 'Source Footnote',
});
return {
template: this._FootNotePopup(footnote, abortController),
container: this.std.host,

View File

@@ -188,6 +188,8 @@ export class AffineLatexNode extends SignalWatcher(
this._editorAbortController?.abort();
this._editorAbortController = new AbortController();
blockComponent.selection.setGroup('note', []);
const portal = createLitPortal({
template: html`<latex-editor-menu
.std=${this.std}

View File

@@ -10,7 +10,7 @@ export const LatexExtension = InlineMarkdownExtension<AffineTextAttributes>({
name: 'latex',
pattern:
/(?:\$\$)(?<content>[^$]+)(?:\$\$)$|(?<blockPrefix>\$\$\$\$)|(?<inlinePrefix>\$\$)$/g,
/(?:\$\$)(?<content>[^$]+)(?:\$\$)\s$|(?<blockPrefix>\$\$\$\$)\s$|(?<inlinePrefix>\$\$)\s$/g,
action: ({ inlineEditor, prefixText, inlineRange, pattern, undoManager }) => {
const match = pattern.exec(prefixText);
if (!match || !match.groups) return;
@@ -33,22 +33,10 @@ export const LatexExtension = InlineMarkdownExtension<AffineTextAttributes>({
const ifEdgelessText = blockComponent.closest('affine-edgeless-text');
if (blockPrefix === '$$$$') {
inlineEditor.insertText(
{
index: inlineRange.index,
length: 0,
},
' '
);
inlineEditor.setInlineRange({
index: inlineRange.index + 1,
length: 0,
});
undoManager.stopCapturing();
inlineEditor.deleteText({
index: inlineRange.index - 4,
index: inlineRange.index - 5,
length: 5,
});
@@ -88,34 +76,22 @@ export const LatexExtension = InlineMarkdownExtension<AffineTextAttributes>({
}
if (inlinePrefix === '$$') {
inlineEditor.insertText(
{
index: inlineRange.index,
length: 0,
},
' '
);
inlineEditor.setInlineRange({
index: inlineRange.index + 1,
length: 0,
});
undoManager.stopCapturing();
inlineEditor.deleteText({
index: inlineRange.index - 2,
index: inlineRange.index - 3,
length: 3,
});
inlineEditor.insertText(
{
index: inlineRange.index - 2,
index: inlineRange.index - 3,
length: 0,
},
' '
);
inlineEditor.formatText(
{
index: inlineRange.index - 2,
index: inlineRange.index - 3,
length: 1,
},
{
@@ -129,7 +105,7 @@ export const LatexExtension = InlineMarkdownExtension<AffineTextAttributes>({
await inlineEditor.waitForUpdate();
const textPoint = inlineEditor.getTextPoint(
inlineRange.index - 2 + 1
inlineRange.index - 3 + 1
);
if (!textPoint) return;
@@ -159,21 +135,9 @@ export const LatexExtension = InlineMarkdownExtension<AffineTextAttributes>({
if (!content || content.length === 0) return;
inlineEditor.insertText(
{
index: inlineRange.index,
length: 0,
},
' '
);
inlineEditor.setInlineRange({
index: inlineRange.index + 1,
length: 0,
});
undoManager.stopCapturing();
const startIndex = inlineRange.index - 2 - content.length - 2;
const startIndex = inlineRange.index - 1 - 2 - content.length - 2;
inlineEditor.deleteText({
index: startIndex,
length: 2 + content.length + 2 + 1,

View File

@@ -3,27 +3,18 @@ import { InlineMarkdownExtension } from '@blocksuite/std/inline';
export const LinkExtension = InlineMarkdownExtension<AffineTextAttributes>({
name: 'link',
pattern: /.*\[(.+?)\]\((.+?)\)$/,
pattern: /.*\[(.+?)\]\((.+?)\)\s$/,
action: ({ inlineEditor, prefixText, inlineRange, pattern, undoManager }) => {
const match = prefixText.match(pattern);
if (!match) return;
const linkText = match[1];
const linkUrl = match[2];
const annotatedText = match[0].slice(-linkText.length - linkUrl.length - 4);
const startIndex = inlineRange.index - annotatedText.length;
inlineEditor.insertText(
{
index: inlineRange.index,
length: 0,
},
' '
const annotatedText = match[0].slice(
-(linkText.length + linkUrl.length + 4 + 1),
-1
);
inlineEditor.setInlineRange({
index: inlineRange.index + 1,
length: 0,
});
const startIndex = inlineRange.index - annotatedText.length - 1;
undoManager.stopCapturing();

View File

@@ -59,7 +59,7 @@ export const StrikeInlineSpecExtension =
export const CodeInlineSpecExtension =
InlineSpecExtension<AffineTextAttributes>({
name: 'code',
name: 'inline-code',
schema: z.literal(true).optional().nullable().catch(undefined),
match: delta => {
return !!delta.attributes?.code;

View File

@@ -13,7 +13,7 @@ import type { ExtensionType } from '@blocksuite/store';
export const BoldItalicMarkdown = InlineMarkdownExtension<AffineTextAttributes>(
{
name: 'bolditalic',
pattern: /.*\*{3}([^\s*][^*]*[^\s*])\*{3}$|.*\*{3}([^\s*])\*{3}$/,
pattern: /.*\*{3}([^\s*][^*]*[^\s*])\*{3}\s$|.*\*{3}([^\s*])\*{3}\s$/,
action: ({
inlineEditor,
prefixText,
@@ -25,20 +25,11 @@ export const BoldItalicMarkdown = InlineMarkdownExtension<AffineTextAttributes>(
if (!match) return;
const targetText = match[1] ?? match[2];
const annotatedText = match[0].slice(-targetText.length - 3 * 2);
const startIndex = inlineRange.index - annotatedText.length;
inlineEditor.insertText(
{
index: startIndex + annotatedText.length,
length: 0,
},
' '
const annotatedText = match[0].slice(
-(targetText.length + 3 * 2 + 1),
-1
);
inlineEditor.setInlineRange({
index: startIndex + annotatedText.length + 1,
length: 0,
});
const startIndex = inlineRange.index - annotatedText.length - 1;
undoManager.stopCapturing();
@@ -54,18 +45,13 @@ export const BoldItalicMarkdown = InlineMarkdownExtension<AffineTextAttributes>(
);
inlineEditor.deleteText({
index: startIndex + annotatedText.length,
length: 1,
});
inlineEditor.deleteText({
index: startIndex + annotatedText.length - 3,
length: 3,
index: inlineRange.index - 4,
length: 4,
});
inlineEditor.deleteText({
index: startIndex,
length: 3,
});
inlineEditor.setInlineRange({
index: startIndex + annotatedText.length - 6,
length: 0,
@@ -76,26 +62,14 @@ export const BoldItalicMarkdown = InlineMarkdownExtension<AffineTextAttributes>(
export const BoldMarkdown = InlineMarkdownExtension<AffineTextAttributes>({
name: 'bold',
pattern: /.*\*{2}([^\s][^*]*[^\s*])\*{2}$|.*\*{2}([^\s*])\*{2}$/,
pattern: /.*\*{2}([^\s][^*]*[^\s*])\*{2}\s$|.*\*{2}([^\s*])\*{2}\s$/,
action: ({ inlineEditor, prefixText, inlineRange, pattern, undoManager }) => {
const match = prefixText.match(pattern);
if (!match) return;
const targetText = match[1] ?? match[2];
const annotatedText = match[0].slice(-targetText.length - 2 * 2);
const startIndex = inlineRange.index - annotatedText.length;
inlineEditor.insertText(
{
index: startIndex + annotatedText.length,
length: 0,
},
' '
);
inlineEditor.setInlineRange({
index: startIndex + annotatedText.length + 1,
length: 0,
});
const annotatedText = match[0].slice(-(targetText.length + 2 * 2 + 1), -1);
const startIndex = inlineRange.index - annotatedText.length - 1;
undoManager.stopCapturing();
@@ -110,18 +84,13 @@ export const BoldMarkdown = InlineMarkdownExtension<AffineTextAttributes>({
);
inlineEditor.deleteText({
index: startIndex + annotatedText.length,
length: 1,
});
inlineEditor.deleteText({
index: startIndex + annotatedText.length - 2,
length: 2,
index: inlineRange.index - 3,
length: 3,
});
inlineEditor.deleteText({
index: startIndex,
length: 2,
});
inlineEditor.setInlineRange({
index: startIndex + annotatedText.length - 4,
length: 0,
@@ -131,26 +100,14 @@ export const BoldMarkdown = InlineMarkdownExtension<AffineTextAttributes>({
export const ItalicExtension = InlineMarkdownExtension<AffineTextAttributes>({
name: 'italic',
pattern: /.*\*{1}([^\s][^*]*[^\s*])\*{1}$|.*\*{1}([^\s*])\*{1}$/,
pattern: /.*\*{1}([^\s][^*]*[^\s*])\*{1}\s$|.*\*{1}([^\s*])\*{1}\s$/,
action: ({ inlineEditor, prefixText, inlineRange, pattern, undoManager }) => {
const match = prefixText.match(pattern);
if (!match) return;
const targetText = match[1] ?? match[2];
const annotatedText = match[0].slice(-targetText.length - 1 * 2);
const startIndex = inlineRange.index - annotatedText.length;
inlineEditor.insertText(
{
index: startIndex + annotatedText.length,
length: 0,
},
' '
);
inlineEditor.setInlineRange({
index: startIndex + annotatedText.length + 1,
length: 0,
});
const annotatedText = match[0].slice(-(targetText.length + 1 * 2 + 1), -1);
const startIndex = inlineRange.index - annotatedText.length - 1;
undoManager.stopCapturing();
@@ -165,18 +122,13 @@ export const ItalicExtension = InlineMarkdownExtension<AffineTextAttributes>({
);
inlineEditor.deleteText({
index: startIndex + annotatedText.length,
length: 1,
});
inlineEditor.deleteText({
index: startIndex + annotatedText.length - 1,
length: 1,
index: inlineRange.index - 2,
length: 2,
});
inlineEditor.deleteText({
index: startIndex,
length: 1,
});
inlineEditor.setInlineRange({
index: startIndex + annotatedText.length - 2,
length: 0,
@@ -187,7 +139,7 @@ export const ItalicExtension = InlineMarkdownExtension<AffineTextAttributes>({
export const StrikethroughExtension =
InlineMarkdownExtension<AffineTextAttributes>({
name: 'strikethrough',
pattern: /.*~{2}([^\s][^~]*[^\s])~{2}$|.*~{2}([^\s~])~{2}$/,
pattern: /.*~{2}([^\s][^~]*[^\s])~{2}\s$|.*~{2}([^\s~])~{2}\s$/,
action: ({
inlineEditor,
prefixText,
@@ -199,20 +151,11 @@ export const StrikethroughExtension =
if (!match) return;
const targetText = match[1] ?? match[2];
const annotatedText = match[0].slice(-targetText.length - 2 * 2);
const startIndex = inlineRange.index - annotatedText.length;
inlineEditor.insertText(
{
index: startIndex + annotatedText.length,
length: 0,
},
' '
const annotatedText = match[0].slice(
-targetText.length - (2 * 2 + 1),
-1
);
inlineEditor.setInlineRange({
index: startIndex + annotatedText.length + 1,
length: 0,
});
const startIndex = inlineRange.index - annotatedText.length - 1;
undoManager.stopCapturing();
@@ -227,12 +170,8 @@ export const StrikethroughExtension =
);
inlineEditor.deleteText({
index: startIndex + annotatedText.length,
length: 1,
});
inlineEditor.deleteText({
index: startIndex + annotatedText.length - 2,
length: 2,
index: inlineRange.index - 3,
length: 3,
});
inlineEditor.deleteText({
index: startIndex,
@@ -249,7 +188,7 @@ export const StrikethroughExtension =
export const UnderthroughExtension =
InlineMarkdownExtension<AffineTextAttributes>({
name: 'underthrough',
pattern: /.*~{1}([^\s][^~]*[^\s~])~{1}$|.*~{1}([^\s~])~{1}$/,
pattern: /.*~{1}([^\s][^~]*[^\s~])~{1}\s$|.*~{1}([^\s~])~{1}\s$/,
action: ({
inlineEditor,
prefixText,
@@ -261,20 +200,11 @@ export const UnderthroughExtension =
if (!match) return;
const targetText = match[1] ?? match[2];
const annotatedText = match[0].slice(-targetText.length - 1 * 2);
const startIndex = inlineRange.index - annotatedText.length;
inlineEditor.insertText(
{
index: startIndex + annotatedText.length,
length: 0,
},
' '
const annotatedText = match[0].slice(
-(targetText.length + 1 * 2 + 1),
-1
);
inlineEditor.setInlineRange({
index: startIndex + annotatedText.length + 1,
length: 0,
});
const startIndex = inlineRange.index - annotatedText.length - 1;
undoManager.stopCapturing();
@@ -289,12 +219,8 @@ export const UnderthroughExtension =
);
inlineEditor.deleteText({
index: startIndex + annotatedText.length,
length: 1,
});
inlineEditor.deleteText({
index: inlineRange.index - 1,
length: 1,
index: inlineRange.index - 2,
length: 2,
});
inlineEditor.deleteText({
index: startIndex,
@@ -310,26 +236,14 @@ export const UnderthroughExtension =
export const CodeExtension = InlineMarkdownExtension<AffineTextAttributes>({
name: 'code',
pattern: /.*`([^\s][^`]*[^\s])`$|.*`([^\s`])`$/,
pattern: /.*`([^\s][^`]*[^\s])`\s$|.*`([^\s`])`\s$/,
action: ({ inlineEditor, prefixText, inlineRange, pattern, undoManager }) => {
const match = prefixText.match(pattern);
if (!match) return;
const targetText = match[1] ?? match[2];
const annotatedText = match[0].slice(-targetText.length - 1 * 2);
const startIndex = inlineRange.index - annotatedText.length;
inlineEditor.insertText(
{
index: startIndex + annotatedText.length,
length: 0,
},
' '
);
inlineEditor.setInlineRange({
index: startIndex + annotatedText.length + 1,
length: 0,
});
const annotatedText = match[0].slice(-(targetText.length + 1 * 2 + 1), -1);
const startIndex = inlineRange.index - annotatedText.length - 1;
undoManager.stopCapturing();
@@ -344,12 +258,8 @@ export const CodeExtension = InlineMarkdownExtension<AffineTextAttributes>({
);
inlineEditor.deleteText({
index: startIndex + annotatedText.length,
length: 1,
});
inlineEditor.deleteText({
index: startIndex + annotatedText.length - 1,
length: 1,
index: inlineRange.index - 2,
length: 2,
});
inlineEditor.deleteText({
index: startIndex,

View File

@@ -30,11 +30,12 @@ import { AttachmentBlockTransformer } from './attachment-transformer.js';
*/
type BackwardCompatibleUndefined = undefined;
export const AttachmentBlockStyles: EmbedCardStyle[] = [
export const AttachmentBlockStyles = [
'cubeThick',
'horizontalThin',
'pdf',
] as const;
'citation',
] as const satisfies EmbedCardStyle[];
export type AttachmentBlockProps = {
name: string;

View File

@@ -15,13 +15,13 @@ import type {
LinkPreviewData,
} from '../../utils/index.js';
export const BookmarkStyles: EmbedCardStyle[] = [
export const BookmarkStyles = [
'vertical',
'horizontal',
'list',
'cube',
'citation',
] as const;
] as const satisfies EmbedCardStyle[];
export type BookmarkBlockProps = {
style: (typeof BookmarkStyles)[number];

View File

@@ -8,7 +8,7 @@ export type EmbedFigmaBlockUrlData = {
description: string | null;
};
export const EmbedFigmaStyles: EmbedCardStyle[] = ['figma'] as const;
export const EmbedFigmaStyles = ['figma'] as const satisfies EmbedCardStyle[];
export type EmbedFigmaBlockProps = {
style: (typeof EmbedFigmaStyles)[number];

View File

@@ -13,12 +13,12 @@ export type EmbedGithubBlockUrlData = {
assignees: string[] | null;
};
export const EmbedGithubStyles: EmbedCardStyle[] = [
export const EmbedGithubStyles = [
'vertical',
'horizontal',
'list',
'cube',
] as const;
] as const satisfies EmbedCardStyle[];
export type EmbedGithubBlockProps = {
style: (typeof EmbedGithubStyles)[number];

View File

@@ -3,7 +3,7 @@ import { BlockModel } from '@blocksuite/store';
import type { EmbedCardStyle } from '../../../utils/index.js';
import { defineEmbedModel } from '../../../utils/index.js';
export const EmbedHtmlStyles: EmbedCardStyle[] = ['html'] as const;
export const EmbedHtmlStyles = ['html'] as const satisfies EmbedCardStyle[];
export type EmbedHtmlBlockProps = {
style: (typeof EmbedHtmlStyles)[number];

View File

@@ -7,7 +7,7 @@ import { BlockModel } from '@blocksuite/store';
import { type EmbedCardStyle } from '../../../utils/index.js';
export const EmbedIframeStyles: EmbedCardStyle[] = ['figma'] as const;
export const EmbedIframeStyles = ['figma'] as const satisfies EmbedCardStyle[];
export type EmbedIframeBlockProps = {
url: string; // the original url that user input

View File

@@ -4,17 +4,17 @@ import type { ReferenceInfo } from '../../../consts/doc.js';
import type { EmbedCardStyle } from '../../../utils/index.js';
import { defineEmbedModel } from '../../../utils/index.js';
export const EmbedLinkedDocStyles: EmbedCardStyle[] = [
export const EmbedLinkedDocStyles = [
'vertical',
'horizontal',
'list',
'cube',
'horizontalThin',
'citation',
];
] as const satisfies EmbedCardStyle[];
export type EmbedLinkedDocBlockProps = {
style: EmbedCardStyle;
style: (typeof EmbedLinkedDocStyles)[number];
caption: string | null;
footnoteIdentifier: string | null;
} & ReferenceInfo;

View File

@@ -10,7 +10,7 @@ export type EmbedLoomBlockUrlData = {
description: string | null;
};
export const EmbedLoomStyles: EmbedCardStyle[] = ['video'] as const;
export const EmbedLoomStyles = ['video'] as const satisfies EmbedCardStyle[];
export type EmbedLoomBlockProps = {
style: (typeof EmbedLoomStyles)[number];

View File

@@ -5,7 +5,9 @@ import type { ReferenceInfo } from '../../../consts/doc.js';
import type { EmbedCardStyle } from '../../../utils/index.js';
import { defineEmbedModel } from '../../../utils/index.js';
export const EmbedSyncedDocStyles: EmbedCardStyle[] = ['syncedDoc'];
export const EmbedSyncedDocStyles = [
'syncedDoc',
] as const satisfies EmbedCardStyle[];
export type EmbedSyncedDocBlockProps = {
style: EmbedCardStyle;

View File

@@ -1,6 +1,7 @@
import type { GfxModel } from '@blocksuite/std/gfx';
import type { BlockModel } from '@blocksuite/store';
import type { BookmarkBlockModel } from '../bookmark';
import { EmbedFigmaModel } from './figma';
import { EmbedGithubModel } from './github';
import type { EmbedHtmlModel } from './html';
@@ -30,7 +31,10 @@ export type EmbedCardModel = InstanceType<
ExternalEmbedModel | InternalEmbedModel
>;
export type LinkableEmbedModel = EmbedCardModel | EmbedIframeBlockModel;
export type LinkableEmbedModel =
| EmbedCardModel
| EmbedIframeBlockModel
| BookmarkBlockModel;
export type BuiltInEmbedModel = EmbedCardModel | EmbedHtmlModel;

View File

@@ -13,7 +13,7 @@ export type EmbedYoutubeBlockUrlData = {
creatorImage: string | null;
};
export const EmbedYoutubeStyles: EmbedCardStyle[] = ['video'] as const;
export const EmbedYoutubeStyles = ['video'] as const satisfies EmbedCardStyle[];
export type EmbedYoutubeBlockProps = {
style: (typeof EmbedYoutubeStyles)[number];

View File

@@ -10,6 +10,5 @@ export {
onModelTextUpdated,
selectTextModel,
} from './dom';
export { markdownInput } from './markdown';
export { RichText } from './rich-text';
export * from './utils';

View File

@@ -1,42 +0,0 @@
import {
DividerBlockModel,
ParagraphBlockModel,
} from '@blocksuite/affine-model';
import { matchModels } from '@blocksuite/affine-shared/utils';
import type { BlockStdScope } from '@blocksuite/std';
import type { BlockModel } from '@blocksuite/store';
import { focusTextModel } from '../dom.js';
import { beforeConvert } from './utils.js';
export function toDivider(
std: BlockStdScope,
model: BlockModel,
prefix: string
) {
const { store: doc } = std;
if (
matchModels(model, [DividerBlockModel]) ||
(matchModels(model, [ParagraphBlockModel]) && model.props.type === 'quote')
) {
return;
}
const parent = doc.getParent(model);
if (!parent) return;
const index = parent.children.indexOf(model);
beforeConvert(std, model, prefix.length);
const blockProps = {
children: model.children,
};
doc.addBlock('affine:divider', blockProps, parent, index);
const nextBlock = parent.children[index + 1];
let id = nextBlock?.id;
if (!id) {
id = doc.addBlock('affine:paragraph', {}, parent);
}
focusTextModel(std, id);
return id;
}

View File

@@ -1 +0,0 @@
export { markdownInput } from './markdown-input.js';

View File

@@ -1,54 +0,0 @@
import {
type ListProps,
type ListType,
ParagraphBlockModel,
} from '@blocksuite/affine-model';
import { matchModels, toNumberedList } from '@blocksuite/affine-shared/utils';
import type { BlockStdScope } from '@blocksuite/std';
import type { BlockModel } from '@blocksuite/store';
import { focusTextModel } from '../dom.js';
import { beforeConvert } from './utils.js';
export function toList(
std: BlockStdScope,
model: BlockModel,
listType: ListType,
prefix: string,
otherProperties?: Partial<ListProps>
) {
if (!matchModels(model, [ParagraphBlockModel])) {
return;
}
const { store: doc } = std;
const parent = doc.getParent(model);
if (!parent) return;
beforeConvert(std, model, prefix.length);
if (listType !== 'numbered') {
const index = parent.children.indexOf(model);
const blockProps = {
type: listType,
text: model.text?.clone(),
children: model.children,
...otherProperties,
};
doc.deleteBlock(model, {
deleteChildren: false,
});
const id = doc.addBlock('affine:list', blockProps, parent, index);
focusTextModel(std, id);
return id;
}
let order = parseInt(prefix.slice(0, -1));
if (!Number.isInteger(order)) order = 1;
const id = toNumberedList(std, model, order);
if (!id) return;
focusTextModel(std, id);
return id;
}

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