Compare commits

...

16 Commits

Author SHA1 Message Date
DarkSky
79460072bb fix: old client compatibility 2026-02-24 23:58:10 +08:00
DarkSky
41b3b0e82e feat: handle calendar sync failed (#14510)
#### PR Dependency Tree


* **PR #14510** 👈

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 calendar sync reliability with exponential backoff for
repeated failures.
* Better handling of token refresh failures with automatic account
invalidation and cleanup when needed.
* Subscriptions are now automatically disabled and related events
removed when the calendar provider reports missing resources.

* **Tests**
* Added comprehensive tests covering sync failures, backoff behavior,
token refresh handling, skipping retries during backoff, and recovery.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-24 22:45:47 +08:00
DarkSky
9c99293c92 fix: linux release 2026-02-24 19:46:11 +08:00
DarkSky
6aba4350ac fix: deps link 2026-02-24 18:09:57 +08:00
DarkSky
046e126054 feat: bump typescript (#14507)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Chores**
  * Upgraded TypeScript toolchain to v5.9.3 across packages and tooling.
* Removed legacy ts-node and migrated developer tooling to newer
runtimes (tsx/SWC) where applicable.
* **Documentation**
* Updated developer CLI docs and runtime behavior notes to reflect the
new loader/runtime for running TypeScript files; no changes to public
APIs or end-user behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-24 13:22:46 +08:00
DarkSky
c2c7dde06c chore: bump deps (#14506)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Chores**
  * Version bumped to 0.26.3 across the project and Helm charts.
  * Removed an unused dependency (minimatch) from multiple packages.
* Updated build/tooling and packaging metadata, including packaging
maker replacement.
  * Adjusted app release metadata and platform packaging config.

* **Tests**
* Updated test snapshots to reflect minor presentational styling
adjustments.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-24 09:11:41 +08:00
DarkSky
5fb1c11a96 fix: image proxy url (#14505)
#### PR Dependency Tree


* **PR #14505** 👈

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

* **Improvements**
* Better image-proxy detection to avoid double-proxying already proxied
images.
* Improved runtime image proxy configuration so images load consistently
across deployments.
* More robust image URL handling for optimized image loading and fewer
redundant requests.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-24 03:29:17 +08:00
passabilities.eth
3e39dbb298 fix(mobile): fixed toolbar position (#14329)
## Summary
Fixed the text formatting toolbar not working properly on mobile web
browsers.

## Problem
The toolbar had multiple issues on mobile devices:
- It would render off-screen or be covered by the virtual keyboard
- The flag-based rendering system caused visibility issues on mobile
- Long-press text selection didn't trigger the toolbar
- Wide toolbars could overflow the viewport

<img
src="https://github.com/user-attachments/assets/8f54590c-1d2c-4c87-abab-32206df17ebf"
width="250">

## Solution
- Use fixed positioning at bottom of screen on mobile devices
- Position toolbar above virtual keyboard using Visual Viewport API
- Handle toolbar visibility directly via `selectionchange` event
- Bypass flag-based rendering system on mobile to avoid rendering issues
- Add `touchend` listener to handle long-press text selection
- Limit toolbar max-width to viewport minus padding
- Enable horizontal scrolling for overflow content

<img
src="https://github.com/user-attachments/assets/45130860-f01a-45c1-87c5-d43264f88613"
width="250">

## Test plan
- [x] Tested on mobile Safari (iOS)
- [x] Tested on mobile Chrome (Android)
- [x] Verified desktop browsers still work correctly
- [x] Verified the toolbar is fixed to the bottom of the screen and
above virtual keyboard
- [x] Verified long-press text selection triggers toolbar

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

* **Improvements**
* Mobile toolbar now anchors to the bottom, adapts width, and
repositions dynamically to stay above on-screen keyboards.
* Toolbar visibility is context-aware, showing when native-like text
selections occur and hiding otherwise; touch interactions are handled
for reliable toggling.
  * Desktop experience and public APIs remain unchanged.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: DarkSky <darksky2048@gmail.com>
2026-02-24 01:36:09 +08:00
DarkSky
e617740974 chore: improve cors 2026-02-24 00:51:08 +08:00
DarkSky
744c78abbb feat(infra): improve ci perf (#14503)
#### PR Dependency Tree


* **PR #14503** 👈

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

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

* **Chores**
* Improved CI efficiency with targeted Rust test detection and
workspace-scoped builds for faster, more focused runs.
* Added workflow static-analysis configuration and refined
Node/Playwright/Electron build flags and platform controls.
* Added a new test workspace dependency and TypeScript project reference
for blocksuite tests; updated workspace wiring for focused builds.
  * Cleaned CI environment handling for server test orchestration.

* **Bug Fixes**
* Hardened mobile release steps (safer credential handling and quoting)
to improve iOS/Android publish reliability.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-23 21:52:17 +08:00
DarkSky
91c5869053 feat: improve selfhosted login (#14502)
fix #13397
fix #14011

#### PR Dependency Tree


* **PR #14502** 👈

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**
* Centralized CORS policy with dynamic origin validation applied to
server and realtime connections
* Improved sign-in flows with contextual, localized error hints and
toast notifications
* Centralized network-error normalization and conditional OAuth provider
fetching

* **Bug Fixes**
* Better feedback for self-hosted connection failures and clearer
authentication error handling
* More robust handling of network-related failures with user-friendly
messages
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-23 21:23:01 +08:00
DarkSky
6d805b302c docs: cleanup outdated infos 2026-02-23 21:03:13 +08:00
Pixel Perfect
fb9f49b948 fix(data-view): preserve filtering on hidden properties (#14500)
Fixes issue #14036 where hiding a column used in filters caused empty
table/kanban results.

Root cause: filter evaluation built the row map from visible properties
only.

Change: evaluate filters using full property set (propertiesRaw$) so
hidden filtered columns still participate.

Added unit regressions for both table and kanban hidden-column filtering
behavior.

Verified this does fix the filtering issue for hidden columns:

<img width="3440" height="1440" alt="Screenshot of before and after
views of a database with hidden columns and filtering on said column"
src="https://github.com/user-attachments/assets/c1e2674f-06be-44e9-97bd-63593172f05b"
/>

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

* **Bug Fixes**
* Fixed filtering in Kanban and Table views so filters evaluate against
all properties (including hidden/raw columns), ensuring consistent
results regardless of column visibility.
* **Tests**
* Added tests covering filtering behavior with hidden and filtered
columns to prevent regressions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-23 20:45:12 +08:00
DarkSky
ef6717e59a fix(editor): editor behavior and styles (#14498)
fix #14269 
fix #13920
fix #13977
fix #13953
fix #13895
fix #13905
fix #14136
fix #14357
fix #14491

#### PR Dependency Tree


* **PR #14498** 👈

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**
  * Callout and toolbar defaults now reliably show grey backgrounds
  * Keyboard shortcuts behave better across layouts and non-ASCII input
  * Deleted workspaces no longer appear in local listings

* **New Features**
  * Cell editing now respects pre-entry validation hooks
* Scrollbars use themeable variables and include Chromium compatibility
fixes

* **Style**
  * Minor UI color adjustment for hidden properties

* **Tests**
  * Added unit tests for table column handling and keymap behavior
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-23 06:37:16 +08:00
DarkSky
ad988dbd1e chore: trim useless files for client (#14488)
#### PR Dependency Tree


* **PR #14488** 👈

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

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

* **Chores**
* Improved Electron build to trim unused locale files on macOS, Windows,
and Linux while always preserving English fallbacks; added post-build
cleanup and stricter packaging ignore rules to exclude tests, examples,
scripts, docs, README, and build metadata.
* **Style**
  * Reformatted a TypeScript type annotation for consistency.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-22 17:17:42 +08:00
DarkSky
3d01766f55 fix: history may duplicate on concurrency (#14487)
#### PR Dependency Tree


* **PR #14487** 👈

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**
* Enhanced history record creation to prevent duplicate entries in
concurrent scenarios.

* **Tests**
  * Added validation for idempotent history record creation.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-22 02:13:51 +08:00
214 changed files with 3764 additions and 1534 deletions

View File

@@ -3,6 +3,6 @@ contact_links:
- name: Something else? - name: Something else?
url: https://github.com/toeverything/AFFiNE/discussions url: https://github.com/toeverything/AFFiNE/discussions
about: Feel free to ask and answer questions over in GitHub Discussions about: Feel free to ask and answer questions over in GitHub Discussions
- name: AFFiNE Community Support - name: AFFiNE Community Support (Discord)
url: https://community.affine.pro url: https://affine.pro/redirect/discord
about: AFFiNE Community - a place to ask, learn and engage with others about: AFFiNE Community - a place to ask, learn and engage with others

20
.github/actionlint.yaml vendored Normal file
View File

@@ -0,0 +1,20 @@
self-hosted-runner:
# Labels of self-hosted runner in array of strings.
labels:
- win-signer
# Configuration variables in array of strings defined in your repository or
# organization. `null` means disabling configuration variables check.
# Empty array means no configuration variable is allowed.
config-variables: null
# Configuration for file paths. The keys are glob patterns to match to file
# paths relative to the repository root. The values are the configurations for
# the file paths. Note that the path separator is always '/'.
# The following configurations are available.
#
# "ignore" is an array of regular expression patterns. Matched error messages
# are ignored. This is similar to the "-ignore" command line option.
paths:
# .github/workflows/**/*.yml:
# ignore: []

View File

@@ -7,7 +7,6 @@ inputs:
ios-app-version: ios-app-version:
description: 'iOS App Store Version (Optional, use App version if empty)' description: 'iOS App Store Version (Optional, use App version if empty)'
required: false required: false
type: string
runs: runs:
using: 'composite' using: 'composite'
steps: steps:

View File

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

View File

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

View File

@@ -3,7 +3,7 @@ name: front
description: AFFiNE front server description: AFFiNE front server
type: application type: application
version: 0.0.0 version: 0.0.0
appVersion: "0.26.1" appVersion: "0.26.3"
dependencies: dependencies:
- name: gcloud-sql-proxy - name: gcloud-sql-proxy
version: 0.0.0 version: 0.0.0

View File

@@ -96,12 +96,20 @@ spec:
httpGet: httpGet:
path: /info path: /info
port: http port: http
initialDelaySeconds: {{ .Values.probe.initialDelaySeconds }} initialDelaySeconds: {{ default .Values.probe.initialDelaySeconds .Values.probe.liveness.initialDelaySeconds }}
timeoutSeconds: {{ default .Values.probe.timeoutSeconds .Values.probe.liveness.timeoutSeconds }}
periodSeconds: {{ default .Values.probe.periodSeconds .Values.probe.liveness.periodSeconds }}
failureThreshold: {{ default .Values.probe.failureThreshold .Values.probe.liveness.failureThreshold }}
successThreshold: {{ default .Values.probe.successThreshold .Values.probe.liveness.successThreshold }}
readinessProbe: readinessProbe:
httpGet: httpGet:
path: /info path: /info
port: http port: http
initialDelaySeconds: {{ .Values.probe.initialDelaySeconds }} initialDelaySeconds: {{ default .Values.probe.initialDelaySeconds .Values.probe.readiness.initialDelaySeconds }}
timeoutSeconds: {{ default .Values.probe.timeoutSeconds .Values.probe.readiness.timeoutSeconds }}
periodSeconds: {{ default .Values.probe.periodSeconds .Values.probe.readiness.periodSeconds }}
failureThreshold: {{ default .Values.probe.failureThreshold .Values.probe.readiness.failureThreshold }}
successThreshold: {{ default .Values.probe.successThreshold .Values.probe.readiness.successThreshold }}
resources: resources:
{{- toYaml .Values.resources | nindent 12 }} {{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.nodeSelector }} {{- with .Values.nodeSelector }}

View File

@@ -31,13 +31,21 @@ podSecurityContext:
resources: resources:
limits: limits:
cpu: '1' cpu: '1'
memory: 2Gi memory: 4Gi
requests: requests:
cpu: '1' cpu: '1'
memory: 2Gi memory: 2Gi
probe: probe:
initialDelaySeconds: 20 initialDelaySeconds: 20
timeoutSeconds: 5
periodSeconds: 10
failureThreshold: 6
successThreshold: 1
liveness:
initialDelaySeconds: 60
failureThreshold: 12
readiness: {}
services: services:
sync: sync:

View File

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

View File

@@ -68,9 +68,26 @@ jobs:
runs-on: ubuntu-24.04-arm runs-on: ubuntu-24.04-arm
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Setup Go (for actionlint)
uses: actions/setup-go@v5
with:
go-version: 'stable'
- name: Install actionlint
shell: bash
run: |
set -euo pipefail
go install github.com/rhysd/actionlint/cmd/actionlint@v1.7.11
- name: Run actionlint
shell: bash
run: |
set -euo pipefail
"$(go env GOPATH)/bin/actionlint"
- name: Run oxlint - name: Run oxlint
# oxlint is fast, so wrong code will fail quickly # oxlint is fast, so wrong code will fail quickly
run: yarn dlx $(node -e "console.log(require('./package.json').scripts['lint:ox'].replace('oxlint', 'oxlint@' + require('./package.json').devDependencies.oxlint))") run: |
set -euo pipefail
oxlint_version="$(node -e "console.log(require('./package.json').devDependencies.oxlint)")"
yarn dlx "oxlint@${oxlint_version}" --deny-warnings
- name: Setup Node.js - name: Setup Node.js
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node
with: with:
@@ -108,20 +125,45 @@ jobs:
run: | run: |
yarn affine bs-docs build yarn affine bs-docs build
git checkout packages/frontend/i18n/src/i18n-completenesses.json git checkout packages/frontend/i18n/src/i18n-completenesses.json
git status --porcelain | grep . && { if git status --porcelain | grep -q .; then
echo "Run 'yarn typecheck && yarn affine bs-docs build' and make sure all changes are submitted" echo "Run 'yarn typecheck && yarn affine bs-docs build' and make sure all changes are submitted"
exit 1 exit 1
} || { else
echo "All changes are submitted" echo "All changes are submitted"
} fi
rust-test-filter:
name: Rust test filter
runs-on: ubuntu-latest
outputs:
run-rust: ${{ steps.rust-filter.outputs.rust }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: rust-filter
with:
filters: |
rust:
- '**/*.rs'
- '**/Cargo.toml'
- '**/Cargo.lock'
- '.cargo/**'
- 'rust-toolchain*'
- '.github/actions/build-rust/**'
lint-rust: lint-rust:
name: Lint Rust name: Lint Rust
if: ${{ needs.rust-test-filter.outputs.run-rust == 'true' }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs:
- rust-test-filter
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: ./.github/actions/build-rust - uses: ./.github/actions/build-rust
with: with:
target: x86_64-unknown-linux-gnu
package: 'affine'
no-build: 'true' no-build: 'true'
- name: fmt check - name: fmt check
run: | run: |
@@ -159,12 +201,12 @@ jobs:
yarn affine i18n build yarn affine i18n build
yarn affine server genconfig yarn affine server genconfig
git checkout packages/frontend/i18n/src/i18n-completenesses.json git checkout packages/frontend/i18n/src/i18n-completenesses.json
git status --porcelain | grep . && { if git status --porcelain | grep -q .; then
echo "Run 'yarn affine init && yarn affine gql build && yarn affine i18n build && yarn affine server genconfig' and make sure all changes are submitted" echo "Run 'yarn affine init && yarn affine gql build && yarn affine i18n build && yarn affine server genconfig' and make sure all changes are submitted"
exit 1 exit 1
} || { else
echo "All changes are submitted" echo "All changes are submitted"
} fi
check-yarn-binary: check-yarn-binary:
name: Check yarn binary name: Check yarn binary
@@ -173,7 +215,9 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Run check - name: Run check
run: | run: |
yarn set version $(node -e "console.log(require('./package.json').packageManager.split('@')[1])") set -euo pipefail
yarn_version="$(node -e "console.log(require('./package.json').packageManager.split('@')[1])")"
yarn set version "$yarn_version"
git diff --exit-code git diff --exit-code
e2e-blocksuite-test: e2e-blocksuite-test:
@@ -188,6 +232,7 @@ jobs:
- name: Setup Node.js - name: Setup Node.js
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node
with: with:
extra-flags: workspaces focus @affine/monorepo @affine-test/blocksuite @blocksuite/playground @blocksuite/integration-test
playwright-install: true playwright-install: true
playwright-platform: 'chromium' playwright-platform: 'chromium'
electron-install: false electron-install: false
@@ -215,6 +260,7 @@ jobs:
- name: Setup Node.js - name: Setup Node.js
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node
with: with:
extra-flags: workspaces focus @affine/monorepo @affine-test/blocksuite @blocksuite/playground @blocksuite/integration-test
playwright-install: true playwright-install: true
playwright-platform: 'chromium,firefox,webkit' playwright-platform: 'chromium,firefox,webkit'
electron-install: false electron-install: false
@@ -298,6 +344,7 @@ jobs:
- name: Setup Node.js - name: Setup Node.js
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node
with: with:
extra-flags: workspaces focus @affine/monorepo @affine-test/affine-local @affine/web @affine/server
playwright-install: true playwright-install: true
playwright-platform: 'chromium' playwright-platform: 'chromium'
electron-install: false electron-install: false
@@ -329,6 +376,7 @@ jobs:
- name: Setup Node.js - name: Setup Node.js
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node
with: with:
extra-flags: workspaces focus @affine/monorepo @affine-test/affine-mobile @affine/mobile
playwright-install: true playwright-install: true
electron-install: false electron-install: false
full-cache: true full-cache: true
@@ -400,7 +448,7 @@ jobs:
working-directory: ${{ github.workspace }} working-directory: ${{ github.workspace }}
shell: bash shell: bash
run: | run: |
export PLATFORM_ARCH_ABI=$(node -e "console.log(require('@napi-rs/cli').parseTriple('x86_64-unknown-linux-gnu').platformArchABI)") PLATFORM_ARCH_ABI="$(node -e "console.log(require('@napi-rs/cli').parseTriple('x86_64-unknown-linux-gnu').platformArchABI)")"
echo "filename=affine.$PLATFORM_ARCH_ABI.node" >> "$GITHUB_OUTPUT" echo "filename=affine.$PLATFORM_ARCH_ABI.node" >> "$GITHUB_OUTPUT"
- name: Build AFFiNE native - name: Build AFFiNE native
uses: ./.github/actions/build-rust uses: ./.github/actions/build-rust
@@ -439,7 +487,7 @@ jobs:
working-directory: ${{ github.workspace }} working-directory: ${{ github.workspace }}
shell: bash shell: bash
run: | run: |
export PLATFORM_ARCH_ABI=$(node -e "console.log(require('@napi-rs/cli').parseTriple('${{ matrix.spec.target }}').platformArchABI)") PLATFORM_ARCH_ABI="$(node -e "console.log(require('@napi-rs/cli').parseTriple('${{ matrix.spec.target }}').platformArchABI)")"
echo "filename=affine.$PLATFORM_ARCH_ABI.node" >> "$GITHUB_OUTPUT" echo "filename=affine.$PLATFORM_ARCH_ABI.node" >> "$GITHUB_OUTPUT"
- name: Build AFFiNE native - name: Build AFFiNE native
uses: ./.github/actions/build-rust uses: ./.github/actions/build-rust
@@ -488,7 +536,7 @@ jobs:
working-directory: ${{ env.DEV_DRIVE_WORKSPACE }} working-directory: ${{ env.DEV_DRIVE_WORKSPACE }}
shell: bash shell: bash
run: | run: |
export PLATFORM_ARCH_ABI=$(node -e "console.log(require('@napi-rs/cli').parseTriple('${{ matrix.spec.target }}').platformArchABI)") PLATFORM_ARCH_ABI="$(node -e "console.log(require('@napi-rs/cli').parseTriple('${{ matrix.spec.target }}').platformArchABI)")"
echo "filename=affine.$PLATFORM_ARCH_ABI.node" >> "$GITHUB_OUTPUT" echo "filename=affine.$PLATFORM_ARCH_ABI.node" >> "$GITHUB_OUTPUT"
- name: Build AFFiNE native - name: Build AFFiNE native
uses: ./.github/actions/build-rust uses: ./.github/actions/build-rust
@@ -536,6 +584,7 @@ jobs:
- name: Setup Node.js - name: Setup Node.js
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node
with: with:
extra-flags: workspaces focus @affine/monorepo @affine/electron-renderer @affine/nbstore @toeverything/infra
electron-install: false electron-install: false
full-cache: true full-cache: true
- name: Build Electron renderer - name: Build Electron renderer
@@ -617,6 +666,7 @@ jobs:
- name: Setup Node.js - name: Setup Node.js
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node
with: with:
extra-flags: workspaces focus @affine/monorepo @affine/server
electron-install: false electron-install: false
full-cache: true full-cache: true
@@ -697,6 +747,7 @@ jobs:
- name: Setup Node.js - name: Setup Node.js
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node
with: with:
extra-flags: workspaces focus @affine/monorepo @affine/server
electron-install: false electron-install: false
full-cache: true full-cache: true
@@ -713,8 +764,6 @@ jobs:
run: yarn affine @affine/server test:coverage "**/*/*elasticsearch.spec.ts" --forbid-only run: yarn affine @affine/server test:coverage "**/*/*elasticsearch.spec.ts" --forbid-only
env: env:
CARGO_TARGET_DIR: '${{ github.workspace }}/target' CARGO_TARGET_DIR: '${{ github.workspace }}/target'
CI_NODE_INDEX: ${{ matrix.node_index }}
CI_NODE_TOTAL: ${{ matrix.total_nodes }}
- name: Upload server test coverage results - name: Upload server test coverage results
uses: codecov/codecov-action@v5 uses: codecov/codecov-action@v5
@@ -761,6 +810,7 @@ jobs:
- name: Setup Node.js - name: Setup Node.js
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node
with: with:
extra-flags: workspaces focus @affine/monorepo @affine/server
electron-install: false electron-install: false
full-cache: true full-cache: true
@@ -787,7 +837,10 @@ jobs:
miri: miri:
name: miri code check name: miri code check
if: ${{ needs.rust-test-filter.outputs.run-rust == 'true' }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs:
- rust-test-filter
env: env:
RUST_BACKTRACE: full RUST_BACKTRACE: full
CARGO_TERM_COLOR: always CARGO_TERM_COLOR: always
@@ -812,7 +865,10 @@ jobs:
loom: loom:
name: loom thread test name: loom thread test
if: ${{ needs.rust-test-filter.outputs.run-rust == 'true' }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs:
- rust-test-filter
env: env:
RUSTFLAGS: --cfg loom RUSTFLAGS: --cfg loom
RUST_BACKTRACE: full RUST_BACKTRACE: full
@@ -835,7 +891,10 @@ jobs:
fuzzing: fuzzing:
name: fuzzing name: fuzzing
if: ${{ needs.rust-test-filter.outputs.run-rust == 'true' }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs:
- rust-test-filter
env: env:
CARGO_TERM_COLOR: always CARGO_TERM_COLOR: always
steps: steps:
@@ -871,7 +930,10 @@ jobs:
rust-test: rust-test:
name: Run native tests name: Run native tests
if: ${{ needs.rust-test-filter.outputs.run-rust == 'true' }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs:
- rust-test-filter
env: env:
CARGO_TERM_COLOR: always CARGO_TERM_COLOR: always
steps: steps:
@@ -879,6 +941,7 @@ jobs:
- name: Setup Rust - name: Setup Rust
uses: ./.github/actions/build-rust uses: ./.github/actions/build-rust
with: with:
target: x86_64-unknown-linux-gnu
package: 'affine' package: 'affine'
no-build: 'true' no-build: 'true'
@@ -971,6 +1034,7 @@ jobs:
- name: Setup Node.js - name: Setup Node.js
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node
with: with:
extra-flags: workspaces focus @affine/monorepo @affine/server
electron-install: false electron-install: false
full-cache: true full-cache: true
@@ -1043,6 +1107,7 @@ jobs:
- name: Setup Node.js - name: Setup Node.js
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node
with: with:
extra-flags: workspaces focus @affine/monorepo @affine-test/affine-cloud-copilot @affine/web @affine/server
playwright-install: true playwright-install: true
playwright-platform: 'chromium' playwright-platform: 'chromium'
electron-install: false electron-install: false
@@ -1125,7 +1190,10 @@ jobs:
- name: Setup Node.js - name: Setup Node.js
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node
with: with:
extra-flags: workspaces focus @affine/monorepo @affine-test/affine-cloud @affine-test/affine-desktop-cloud @affine/web @affine/server @affine/electron @affine/electron-renderer @affine/nbstore @toeverything/infra
playwright-install: true playwright-install: true
playwright-platform: 'chromium'
electron-install: ${{ matrix.tests.shard == 'desktop' && 'true' || 'false' }}
hard-link-nm: false hard-link-nm: false
- name: Download server-native.node - name: Download server-native.node
@@ -1204,7 +1272,8 @@ jobs:
timeout-minutes: 10 timeout-minutes: 10
with: with:
extra-flags: workspaces focus @affine/electron @affine/monorepo @affine-test/affine-desktop @affine/nbstore @toeverything/infra extra-flags: workspaces focus @affine/electron @affine/monorepo @affine-test/affine-desktop @affine/nbstore @toeverything/infra
playwright-install: true playwright-install: ${{ matrix.spec.test && 'true' || 'false' }}
playwright-platform: 'chromium'
hard-link-nm: false hard-link-nm: false
enableScripts: false enableScripts: false
@@ -1212,7 +1281,7 @@ jobs:
id: filename id: filename
shell: bash shell: bash
run: | run: |
export PLATFORM_ARCH_ABI=$(node -e "console.log(require('@napi-rs/cli').parseTriple('${{ matrix.spec.target }}').platformArchABI)") PLATFORM_ARCH_ABI="$(node -e "console.log(require('@napi-rs/cli').parseTriple('${{ matrix.spec.target }}').platformArchABI)")"
echo "filename=affine.$PLATFORM_ARCH_ABI.node" >> "$GITHUB_OUTPUT" echo "filename=affine.$PLATFORM_ARCH_ABI.node" >> "$GITHUB_OUTPUT"
- name: Download ${{ steps.filename.outputs.filename }} - name: Download ${{ steps.filename.outputs.filename }}
@@ -1319,6 +1388,7 @@ jobs:
- server-test - server-test
- server-e2e-test - server-e2e-test
- rust-test - rust-test
- rust-test-filter
- copilot-test-filter - copilot-test-filter
- copilot-api-test - copilot-api-test
- copilot-e2e-test - copilot-e2e-test

View File

@@ -174,7 +174,7 @@ jobs:
run: | run: |
mkdir -p builds mkdir -p builds
mv packages/frontend/apps/electron/out/*/make/zip/linux/${{ inputs.arch }}/*.zip ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ inputs.arch }}.zip mv packages/frontend/apps/electron/out/*/make/zip/linux/${{ inputs.arch }}/*.zip ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ inputs.arch }}.zip
mv packages/frontend/apps/electron/out/*/make/*.AppImage ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ inputs.arch }}.appimage mv packages/frontend/apps/electron/out/*/make/AppImage/${{ inputs.arch }}/*.AppImage ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ inputs.arch }}.appimage
mv packages/frontend/apps/electron/out/*/make/deb/${{ inputs.arch }}/*.deb ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ inputs.arch }}.deb mv packages/frontend/apps/electron/out/*/make/deb/${{ inputs.arch }}/*.deb ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ inputs.arch }}.deb
mv packages/frontend/apps/electron/out/*/make/flatpak/*/*.flatpak ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ inputs.arch }}.flatpak mv packages/frontend/apps/electron/out/*/make/flatpak/*/*.flatpak ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ inputs.arch }}.flatpak

View File

@@ -128,9 +128,9 @@ jobs:
- name: Testflight - name: Testflight
working-directory: packages/frontend/apps/ios/App working-directory: packages/frontend/apps/ios/App
run: | run: |
echo -n "${{ env.BUILD_PROVISION_PROFILE }}" | base64 --decode -o $PP_PATH printf '%s' "$BUILD_PROVISION_PROFILE" | base64 --decode -o "$PP_PATH"
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles mkdir -p "$HOME/Library/MobileDevice/Provisioning Profiles"
cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles cp "$PP_PATH" "$HOME/Library/MobileDevice/Provisioning Profiles"
fastlane beta fastlane beta
env: env:
BUILD_TARGET: distribution BUILD_TARGET: distribution
@@ -160,7 +160,9 @@ jobs:
- name: Load Google Service file - name: Load Google Service file
env: env:
DATA: ${{ secrets.FIREBASE_ANDROID_GOOGLE_SERVICE_JSON }} DATA: ${{ secrets.FIREBASE_ANDROID_GOOGLE_SERVICE_JSON }}
run: echo $DATA | base64 -di > packages/frontend/apps/android/App/app/google-services.json run: |
set -euo pipefail
printf '%s' "$DATA" | base64 -di > packages/frontend/apps/android/App/app/google-services.json
- name: Setup Node.js - name: Setup Node.js
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node
timeout-minutes: 10 timeout-minutes: 10

View File

@@ -91,8 +91,8 @@ Thanks for checking us out, we appreciate your interest and sincerely hope that
## Contributing ## Contributing
| Bug Reports | Feature Requests | Questions/Discussions | AFFiNE Community | | Bug Reports | Feature Requests | Questions/Discussions | AFFiNE Community |
| --------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | ---------------------------------------------------------- | | --------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | ----------------------------------------------------------------- |
| [Create a bug report](https://github.com/toeverything/AFFiNE/issues/new?assignees=&labels=bug%2Cproduct-review&template=BUG-REPORT.yml&title=TITLE) | [Submit a feature request](https://github.com/toeverything/AFFiNE/issues/new?assignees=&labels=feat%2Cproduct-review&template=FEATURE-REQUEST.yml&title=TITLE) | [Check GitHub Discussion](https://github.com/toeverything/AFFiNE/discussions) | [Visit the AFFiNE Community](https://community.affine.pro) | | [Create a bug report](https://github.com/toeverything/AFFiNE/issues/new?assignees=&labels=bug%2Cproduct-review&template=BUG-REPORT.yml&title=TITLE) | [Submit a feature request](https://github.com/toeverything/AFFiNE/issues/new?assignees=&labels=feat%2Cproduct-review&template=FEATURE-REQUEST.yml&title=TITLE) | [Check GitHub Discussion](https://github.com/toeverything/AFFiNE/discussions) | [Visit the AFFiNE's Discord](https://affine.pro/redirect/discord) |
| Something isn't working as expected | An idea for a new feature, or improvements | Discuss and ask questions | A place to ask, learn and engage with others | | Something isn't working as expected | An idea for a new feature, or improvements | Discuss and ask questions | A place to ask, learn and engage with others |
Calling all developers, testers, tech writers and more! Contributions of all types are more than welcome, you can read more in [docs/types-of-contributions.md](docs/types-of-contributions.md). If you are interested in contributing code, read our [docs/CONTRIBUTING.md](docs/CONTRIBUTING.md) and feel free to check out our GitHub issues to get stuck in to show us what youre made of. Calling all developers, testers, tech writers and more! Contributions of all types are more than welcome, you can read more in [docs/types-of-contributions.md](docs/types-of-contributions.md). If you are interested in contributing code, read our [docs/CONTRIBUTING.md](docs/CONTRIBUTING.md) and feel free to check out our GitHub issues to get stuck in to show us what youre made of.
@@ -101,11 +101,9 @@ Calling all developers, testers, tech writers and more! Contributions of all typ
For **bug reports**, **feature requests** and other **suggestions** you can also [create a new issue](https://github.com/toeverything/AFFiNE/issues/new/choose) and choose the most appropriate template for your feedback. For **bug reports**, **feature requests** and other **suggestions** you can also [create a new issue](https://github.com/toeverything/AFFiNE/issues/new/choose) and choose the most appropriate template for your feedback.
For **translation** and **language support** you can visit our [i18n General Space](https://community.affine.pro/c/i18n-general). For **translation** and **language support** you can visit our [Discord](https://affine.pro/redirect/discord).
Looking for **other ways to contribute** and wondering where to start? Check out the [AFFiNE Ambassador program](https://community.affine.pro/c/start-here/affine-ambassador), we work closely with passionate community members and provide them with a wide range of support and resources. If you have questions, you are welcome to contact us. One of the best places to get more info and learn more is in the [Discord](https://affine.pro/redirect/discord) where you can engage with other like-minded individuals.
If you have questions, you are welcome to contact us. One of the best places to get more info and learn more is in the [AFFiNE Community](https://community.affine.pro) where you can engage with other like-minded individuals.
## Templates ## Templates
@@ -182,20 +180,16 @@ Begin with Docker to deploy your own feature-rich, unrestricted version of AFFiN
[![Run on ClawCloud](https://raw.githubusercontent.com/ClawCloud/Run-Template/refs/heads/main/Run-on-ClawCloud.svg)](https://template.run.claw.cloud/?openapp=system-fastdeploy%3FtemplateName%3Daffine) [![Run on ClawCloud](https://raw.githubusercontent.com/ClawCloud/Run-Template/refs/heads/main/Run-on-ClawCloud.svg)](https://template.run.claw.cloud/?openapp=system-fastdeploy%3FtemplateName%3Daffine)
## Hiring
Some amazing companies, including AFFiNE, are looking for developers! Are you interested in joining AFFiNE or its partners? Check out our [Discord channel](https://affine.pro/redirect/discord) for some of the latest jobs available.
## Feature Request ## Feature Request
For feature requests, please see [community.affine.pro](https://community.affine.pro/c/feature-requests/). For feature requests, please see [discussions](https://github.com/toeverything/AFFiNE/discussions/categories/ideas).
## Building ## Building
### Codespaces ### Codespaces
From the GitHub repo main page, click the green "Code" button and select "Create codespace on master". This will open a new Codespace with the (supposedly auto-forked From the GitHub repo main page, click the green "Code" button and select "Create codespace on master". This will open a new Codespace with the (supposedly auto-forked
AFFiNE repo cloned, built, and ready to go. AFFiNE repo cloned, built, and ready to go).
### Local ### Local

View File

@@ -296,7 +296,7 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1", "version": "0.26.3",
"devDependencies": { "devDependencies": {
"@vanilla-extract/vite-plugin": "^5.0.0", "@vanilla-extract/vite-plugin": "^5.0.0",
"msw": "^2.12.4", "msw": "^2.12.4",

View File

@@ -26,7 +26,6 @@
"@toeverything/theme": "^1.1.23", "@toeverything/theme": "^1.1.23",
"file-type": "^21.0.0", "file-type": "^21.0.0",
"lit": "^3.2.0", "lit": "^3.2.0",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2", "rxjs": "^7.8.2",
"zod": "^3.25.76" "zod": "^3.25.76"
}, },
@@ -41,5 +40,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -26,7 +26,6 @@
"@preact/signals-core": "^1.8.0", "@preact/signals-core": "^1.8.0",
"@toeverything/theme": "^1.1.23", "@toeverything/theme": "^1.1.23",
"lit": "^3.2.0", "lit": "^3.2.0",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2", "rxjs": "^7.8.2",
"yjs": "^13.6.27", "yjs": "^13.6.27",
"zod": "^3.25.76" "zod": "^3.25.76"
@@ -45,5 +44,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -30,7 +30,6 @@
"@types/mdast": "^4.0.4", "@types/mdast": "^4.0.4",
"emoji-mart": "^5.6.0", "emoji-mart": "^5.6.0",
"lit": "^3.2.0", "lit": "^3.2.0",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2", "rxjs": "^7.8.2",
"zod": "^3.25.76" "zod": "^3.25.76"
}, },
@@ -45,5 +44,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -216,9 +216,13 @@ export class CalloutBlockComponent extends CaptionedBlockComponent<CalloutBlockM
override renderBlock() { override renderBlock() {
const icon = this.model.props.icon$.value; const icon = this.model.props.icon$.value;
const backgroundColorName = this.model.props.backgroundColorName$.value; const backgroundColorName = this.model.props.backgroundColorName$.value;
const normalizedBackgroundName =
backgroundColorName === 'default' || backgroundColorName === ''
? 'grey'
: backgroundColorName;
const backgroundColor = ( const backgroundColor = (
cssVarV2.block.callout.background as Record<string, string> cssVarV2.block.callout.background as Record<string, string>
)[backgroundColorName ?? '']; )[normalizedBackgroundName ?? 'grey'];
const iconContent = getIcon(icon); const iconContent = getIcon(icon);

View File

@@ -68,14 +68,14 @@ const backgroundColorAction = {
${repeat(colors, color => { ${repeat(colors, color => {
const isDefault = color === 'default'; const isDefault = color === 'default';
const value = isDefault const value = isDefault
? null ? cssVarV2.block.callout.background.grey
: `var(--affine-text-highlight-${color})`; : `var(--affine-text-highlight-${color})`;
const displayName = `${color} Background`; const displayName = `${color} Background`;
return html` return html`
<editor-menu-action <editor-menu-action
data-testid="background-${color}" data-testid="background-${color}"
@click=${() => updateBackground(color)} @click=${() => updateBackground(isDefault ? 'grey' : color)}
> >
<affine-text-duotone-icon <affine-text-duotone-icon
style=${styleMap({ style=${styleMap({

View File

@@ -31,7 +31,6 @@
"@toeverything/theme": "^1.1.23", "@toeverything/theme": "^1.1.23",
"@types/mdast": "^4.0.4", "@types/mdast": "^4.0.4",
"lit": "^3.2.0", "lit": "^3.2.0",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2", "rxjs": "^7.8.2",
"shiki": "^3.19.0", "shiki": "^3.19.0",
"zod": "^3.25.76" "zod": "^3.25.76"
@@ -48,5 +47,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -27,6 +27,16 @@ export const codeBlockStyles = css`
${scrollbarStyle('.affine-code-block-container rich-text')} ${scrollbarStyle('.affine-code-block-container rich-text')}
/* In Chromium 121+, non-auto scrollbar-width/color override ::-webkit-scrollbar styles. */
@supports not selector(::-webkit-scrollbar) {
.affine-code-block-container rich-text {
scrollbar-width: thin;
scrollbar-color: ${unsafeCSSVarV2('icon/secondary', '#b1b1b1')}
transparent;
scrollbar-gutter: stable both-edges;
}
}
.affine-code-block-container .inline-editor { .affine-code-block-container .inline-editor {
font-family: var(--affine-font-code-family); font-family: var(--affine-font-code-family);
font-variant-ligatures: none; font-variant-ligatures: none;

View File

@@ -27,7 +27,6 @@
"@toeverything/theme": "^1.1.23", "@toeverything/theme": "^1.1.23",
"@types/mdast": "^4.0.4", "@types/mdast": "^4.0.4",
"lit": "^3.2.0", "lit": "^3.2.0",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2", "rxjs": "^7.8.2",
"zod": "^3.25.76" "zod": "^3.25.76"
}, },
@@ -42,5 +41,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -32,7 +32,6 @@
"@types/mdast": "^4.0.4", "@types/mdast": "^4.0.4",
"date-fns": "^4.0.0", "date-fns": "^4.0.0",
"lit": "^3.2.0", "lit": "^3.2.0",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2", "rxjs": "^7.8.2",
"yjs": "^13.6.27", "yjs": "^13.6.27",
"zod": "^3.25.76" "zod": "^3.25.76"
@@ -48,5 +47,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -24,7 +24,6 @@
"@toeverything/theme": "^1.1.23", "@toeverything/theme": "^1.1.23",
"@types/mdast": "^4.0.4", "@types/mdast": "^4.0.4",
"lit": "^3.2.0", "lit": "^3.2.0",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2", "rxjs": "^7.8.2",
"zod": "^3.25.76" "zod": "^3.25.76"
}, },
@@ -39,5 +38,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -28,7 +28,6 @@
"@preact/signals-core": "^1.8.0", "@preact/signals-core": "^1.8.0",
"@toeverything/theme": "^1.1.23", "@toeverything/theme": "^1.1.23",
"lit": "^3.2.0", "lit": "^3.2.0",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2", "rxjs": "^7.8.2",
"zod": "^3.25.76" "zod": "^3.25.76"
}, },
@@ -43,5 +42,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -30,7 +30,6 @@
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"lit": "^3.2.0", "lit": "^3.2.0",
"lodash-es": "^4.17.23", "lodash-es": "^4.17.23",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2", "rxjs": "^7.8.2",
"yjs": "^13.6.27", "yjs": "^13.6.27",
"zod": "^3.25.76" "zod": "^3.25.76"
@@ -49,5 +48,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -30,7 +30,6 @@
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"lit": "^3.2.0", "lit": "^3.2.0",
"lodash-es": "^4.17.23", "lodash-es": "^4.17.23",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2", "rxjs": "^7.8.2",
"yjs": "^13.6.27", "yjs": "^13.6.27",
"zod": "^3.25.76" "zod": "^3.25.76"
@@ -49,5 +48,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -28,7 +28,6 @@
"@toeverything/theme": "^1.1.23", "@toeverything/theme": "^1.1.23",
"@types/mdast": "^4.0.4", "@types/mdast": "^4.0.4",
"lit": "^3.2.0", "lit": "^3.2.0",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2", "rxjs": "^7.8.2",
"yjs": "^13.6.27", "yjs": "^13.6.27",
"zod": "^3.25.76" "zod": "^3.25.76"
@@ -44,5 +43,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -28,7 +28,6 @@
"@toeverything/theme": "^1.1.23", "@toeverything/theme": "^1.1.23",
"file-type": "^21.0.0", "file-type": "^21.0.0",
"lit": "^3.2.0", "lit": "^3.2.0",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2", "rxjs": "^7.8.2",
"zod": "^3.25.76" "zod": "^3.25.76"
}, },
@@ -44,5 +43,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -30,7 +30,6 @@
"@types/mdast": "^4.0.4", "@types/mdast": "^4.0.4",
"katex": "^0.16.27", "katex": "^0.16.27",
"lit": "^3.2.0", "lit": "^3.2.0",
"minimatch": "^10.1.1",
"remark-math": "^6.0.0", "remark-math": "^6.0.0",
"rxjs": "^7.8.2", "rxjs": "^7.8.2",
"zod": "^3.25.76" "zod": "^3.25.76"
@@ -46,5 +45,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -27,7 +27,6 @@
"@toeverything/theme": "^1.1.23", "@toeverything/theme": "^1.1.23",
"@types/mdast": "^4.0.4", "@types/mdast": "^4.0.4",
"lit": "^3.2.0", "lit": "^3.2.0",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2", "rxjs": "^7.8.2",
"zod": "^3.25.76" "zod": "^3.25.76"
}, },
@@ -46,5 +45,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -33,7 +33,6 @@
"@vanilla-extract/css": "^1.17.0", "@vanilla-extract/css": "^1.17.0",
"lit": "^3.2.0", "lit": "^3.2.0",
"lodash-es": "^4.17.23", "lodash-es": "^4.17.23",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2", "rxjs": "^7.8.2",
"zod": "^3.25.76" "zod": "^3.25.76"
}, },
@@ -49,5 +48,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -26,7 +26,6 @@
"@toeverything/theme": "^1.1.23", "@toeverything/theme": "^1.1.23",
"@types/mdast": "^4.0.4", "@types/mdast": "^4.0.4",
"lit": "^3.2.0", "lit": "^3.2.0",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2", "rxjs": "^7.8.2",
"zod": "^3.25.76" "zod": "^3.25.76"
}, },
@@ -42,5 +41,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -50,7 +50,6 @@
"html2canvas": "^1.4.1", "html2canvas": "^1.4.1",
"lit": "^3.2.0", "lit": "^3.2.0",
"lodash-es": "^4.17.23", "lodash-es": "^4.17.23",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2", "rxjs": "^7.8.2",
"yjs": "^13.6.27", "yjs": "^13.6.27",
"zod": "^3.25.76" "zod": "^3.25.76"
@@ -67,5 +66,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -45,5 +45,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -46,5 +46,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -42,5 +42,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -82,5 +82,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -48,5 +48,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -4,6 +4,7 @@ import { describe, expect, it, vi } from 'vitest';
import type { GroupBy } from '../core/common/types.js'; import type { GroupBy } from '../core/common/types.js';
import type { DataSource } from '../core/data-source/base.js'; import type { DataSource } from '../core/data-source/base.js';
import { DetailSelection } from '../core/detail/selection.js'; import { DetailSelection } from '../core/detail/selection.js';
import type { FilterGroup } from '../core/filter/types.js';
import { groupByMatchers } from '../core/group-by/define.js'; import { groupByMatchers } from '../core/group-by/define.js';
import { t } from '../core/logical/type-presets.js'; import { t } from '../core/logical/type-presets.js';
import type { DataViewCellLifeCycle } from '../core/property/index.js'; import type { DataViewCellLifeCycle } from '../core/property/index.js';
@@ -17,7 +18,10 @@ import {
pickKanbanGroupColumn, pickKanbanGroupColumn,
resolveKanbanGroupBy, resolveKanbanGroupBy,
} from '../view-presets/kanban/group-by-utils.js'; } from '../view-presets/kanban/group-by-utils.js';
import { materializeKanbanColumns } from '../view-presets/kanban/kanban-view-manager.js'; import {
KanbanSingleView,
materializeKanbanColumns,
} from '../view-presets/kanban/kanban-view-manager.js';
import type { KanbanCard } from '../view-presets/kanban/pc/card.js'; import type { KanbanCard } from '../view-presets/kanban/pc/card.js';
import { KanbanDragController } from '../view-presets/kanban/pc/controller/drag.js'; import { KanbanDragController } from '../view-presets/kanban/pc/controller/drag.js';
import type { KanbanGroup } from '../view-presets/kanban/pc/group.js'; import type { KanbanGroup } from '../view-presets/kanban/pc/group.js';
@@ -270,6 +274,73 @@ describe('kanban', () => {
}); });
}); });
describe('filtering', () => {
const sharedFilter: FilterGroup = {
type: 'group',
op: 'and',
conditions: [
{
type: 'filter',
left: {
type: 'ref',
name: 'status',
},
function: 'is',
args: [{ type: 'literal', value: 'Done' }],
},
],
};
const sharedTitleProperty = {
id: 'title',
cellGetOrCreate: () => ({
jsonValue$: {
value: 'Task 1',
},
}),
};
it('evaluates filters with hidden columns', () => {
const statusProperty = {
id: 'status',
cellGetOrCreate: () => ({
jsonValue$: {
value: 'Done',
},
}),
};
const view = {
filter$: { value: sharedFilter },
// Simulate status being hidden in current view.
properties$: { value: [sharedTitleProperty] },
propertiesRaw$: { value: [sharedTitleProperty, statusProperty] },
} as unknown as KanbanSingleView;
expect(KanbanSingleView.prototype.isShow.call(view, 'row-1')).toBe(true);
});
it('returns false when hidden filtered column does not match', () => {
const statusProperty = {
id: 'status',
cellGetOrCreate: () => ({
jsonValue$: {
value: 'In Progress',
},
}),
};
const view = {
filter$: { value: sharedFilter },
// Simulate status being hidden in current view.
properties$: { value: [sharedTitleProperty] },
propertiesRaw$: { value: [sharedTitleProperty, statusProperty] },
} as unknown as KanbanSingleView;
expect(KanbanSingleView.prototype.isShow.call(view, 'row-1')).toBe(false);
});
});
describe('drag indicator', () => { describe('drag indicator', () => {
it('shows drop preview when insert position exists', () => { it('shows drop preview when insert position exists', () => {
const controller = createDragController(); const controller = createDragController();

View File

@@ -1,15 +1,21 @@
import { describe, expect, test } from 'vitest'; import { describe, expect, test } from 'vitest';
import type { FilterGroup } from '../core/filter/types.js';
import { numberFormats } from '../property-presets/number/utils/formats.js'; import { numberFormats } from '../property-presets/number/utils/formats.js';
import { import {
formatNumber, formatNumber,
NumberFormatSchema, NumberFormatSchema,
parseNumber, parseNumber,
} from '../property-presets/number/utils/formatter.js'; } from '../property-presets/number/utils/formatter.js';
import { DEFAULT_COLUMN_WIDTH } from '../view-presets/table/consts.js';
import { mobileEffects } from '../view-presets/table/mobile/effect.js'; import { mobileEffects } from '../view-presets/table/mobile/effect.js';
import type { MobileTableGroup } from '../view-presets/table/mobile/group.js'; import type { MobileTableGroup } from '../view-presets/table/mobile/group.js';
import { pcEffects } from '../view-presets/table/pc/effect.js'; import { pcEffects } from '../view-presets/table/pc/effect.js';
import type { TableGroup } from '../view-presets/table/pc/group.js'; import type { TableGroup } from '../view-presets/table/pc/group.js';
import {
materializeTableColumns,
TableSingleView,
} from '../view-presets/table/table-view-manager.js';
/** @vitest-environment happy-dom */ /** @vitest-environment happy-dom */
@@ -41,6 +47,146 @@ describe('TableGroup', () => {
}); });
}); });
describe('table column materialization', () => {
test('appends missing properties while preserving existing order and state', () => {
const columns = [
{ id: 'status', width: 240, hide: true },
{ id: 'title', width: 320 },
];
const next = materializeTableColumns(columns, ['title', 'status', 'date']);
expect(next).toEqual([
{ id: 'status', width: 240, hide: true },
{ id: 'title', width: 320 },
{ id: 'date', width: DEFAULT_COLUMN_WIDTH },
]);
});
test('drops stale columns that no longer exist in data source', () => {
const columns = [
{ id: 'title', width: 320 },
{ id: 'removed', width: 200, hide: true },
];
const next = materializeTableColumns(columns, ['title']);
expect(next).toEqual([{ id: 'title', width: 320 }]);
});
test('returns original reference when columns are already materialized', () => {
const columns = [
{ id: 'title', width: 320 },
{ id: 'status', width: 240, hide: true },
];
const next = materializeTableColumns(columns, ['title', 'status']);
expect(next).toBe(columns);
});
test('supports type-aware default width when materializing missing columns', () => {
const next = materializeTableColumns([], ['title', 'status'], id =>
id === 'title' ? 260 : DEFAULT_COLUMN_WIDTH
);
expect(next).toEqual([
{ id: 'title', width: 260 },
{ id: 'status', width: DEFAULT_COLUMN_WIDTH },
]);
});
});
describe('table filtering', () => {
test('evaluates filters with hidden columns', () => {
const filter: FilterGroup = {
type: 'group',
op: 'and',
conditions: [
{
type: 'filter',
left: {
type: 'ref',
name: 'status',
},
function: 'is',
args: [{ type: 'literal', value: 'Done' }],
},
],
};
const titleProperty = {
id: 'title',
cellGetOrCreate: () => ({
jsonValue$: {
value: 'Task 1',
},
}),
};
const statusProperty = {
id: 'status',
cellGetOrCreate: () => ({
jsonValue$: {
value: 'Done',
},
}),
};
const view = {
filter$: { value: filter },
// Simulate status being hidden in current view.
properties$: { value: [titleProperty] },
propertiesRaw$: { value: [titleProperty, statusProperty] },
} as unknown as TableSingleView;
expect(TableSingleView.prototype.isShow.call(view, 'row-1')).toBe(true);
});
test('returns false when hidden filtered column does not match', () => {
const filter: FilterGroup = {
type: 'group',
op: 'and',
conditions: [
{
type: 'filter',
left: {
type: 'ref',
name: 'status',
},
function: 'is',
args: [{ type: 'literal', value: 'Done' }],
},
],
};
const titleProperty = {
id: 'title',
cellGetOrCreate: () => ({
jsonValue$: {
value: 'Task 1',
},
}),
};
const statusProperty = {
id: 'status',
cellGetOrCreate: () => ({
jsonValue$: {
value: 'In Progress',
},
}),
};
const view = {
filter$: { value: filter },
// Simulate status being hidden in current view.
properties$: { value: [titleProperty] },
propertiesRaw$: { value: [titleProperty, statusProperty] },
} as unknown as TableSingleView;
expect(TableSingleView.prototype.isShow.call(view, 'row-1')).toBe(false);
});
});
describe('number formatter', () => { describe('number formatter', () => {
test('number format menu should expose all schema formats', () => { test('number format menu should expose all schema formats', () => {
const menuFormats = numberFormats.map(format => format.type); const menuFormats = numberFormats.map(format => format.type);

View File

@@ -349,7 +349,7 @@ export class KanbanSingleView extends SingleViewBase<KanbanViewData> {
isShow(rowId: string): boolean { isShow(rowId: string): boolean {
if (this.filter$.value?.conditions.length) { if (this.filter$.value?.conditions.length) {
const rowMap = Object.fromEntries( const rowMap = Object.fromEntries(
this.properties$.value.map(column => [ this.propertiesRaw$.value.map(column => [
column.id, column.id,
column.cellGetOrCreate(rowId).jsonValue$.value, column.cellGetOrCreate(rowId).jsonValue$.value,
]) ])

View File

@@ -54,7 +54,9 @@ export class DatabaseCellContainer extends SignalWatcher(
const selectionView = this.selectionView; const selectionView = this.selectionView;
if (selectionView) { if (selectionView) {
const selection = selectionView.selection; const selection = selectionView.selection;
if (selection && this.isSelected(selection) && editing) { const shouldEnterEditMode =
editing && this.cell?.beforeEnterEditMode() !== false;
if (selection && this.isSelected(selection) && shouldEnterEditMode) {
selectionView.selection = TableViewAreaSelection.create({ selectionView.selection = TableViewAreaSelection.create({
groupKey: this.groupKey, groupKey: this.groupKey,
focus: { focus: {

View File

@@ -57,7 +57,9 @@ export class TableViewCellContainer extends SignalWatcher(
const selectionView = this.selectionController; const selectionView = this.selectionController;
if (selectionView) { if (selectionView) {
const selection = selectionView.selection; const selection = selectionView.selection;
if (selection && this.isSelected(selection) && editing) { const shouldEnterEditMode =
editing && this.cell?.beforeEnterEditMode() !== false;
if (selection && this.isSelected(selection) && shouldEnterEditMode) {
selectionView.selection = TableViewAreaSelection.create({ selectionView.selection = TableViewAreaSelection.create({
groupKey: this.groupKey, groupKey: this.groupKey,
focus: { focus: {

View File

@@ -26,6 +26,52 @@ import type { ViewManager } from '../../core/view-manager/view-manager.js';
import { DEFAULT_COLUMN_MIN_WIDTH, DEFAULT_COLUMN_WIDTH } from './consts.js'; import { DEFAULT_COLUMN_MIN_WIDTH, DEFAULT_COLUMN_WIDTH } from './consts.js';
import type { TableViewData } from './define.js'; import type { TableViewData } from './define.js';
export const materializeColumnsByPropertyIds = (
columns: TableColumnData[],
propertyIds: string[],
getDefaultWidth: (id: string) => number = () => DEFAULT_COLUMN_WIDTH
) => {
const needShow = new Set(propertyIds);
const orderedColumns: TableColumnData[] = [];
for (const column of columns) {
if (needShow.has(column.id)) {
orderedColumns.push(column);
needShow.delete(column.id);
}
}
for (const id of needShow) {
orderedColumns.push({ id, width: getDefaultWidth(id), hide: undefined });
}
return orderedColumns;
};
export const materializeTableColumns = (
columns: TableColumnData[],
propertyIds: string[],
getDefaultWidth?: (id: string) => number
) => {
const nextColumns = materializeColumnsByPropertyIds(
columns,
propertyIds,
getDefaultWidth
);
const unchanged =
columns.length === nextColumns.length &&
columns.every((column, index) => {
const nextColumn = nextColumns[index];
return (
nextColumn != null &&
column.id === nextColumn.id &&
column.hide === nextColumn.hide
);
});
return unchanged ? columns : nextColumns;
};
export class TableSingleView extends SingleViewBase<TableViewData> { export class TableSingleView extends SingleViewBase<TableViewData> {
propertiesRaw$ = computed(() => { propertiesRaw$ = computed(() => {
const needShow = new Set(this.dataSource.properties$.value); const needShow = new Set(this.dataSource.properties$.value);
@@ -220,14 +266,10 @@ export class TableSingleView extends SingleViewBase<TableViewData> {
return this.data$.value?.mode ?? 'table'; return this.data$.value?.mode ?? 'table';
} }
constructor(viewManager: ViewManager, viewId: string) {
super(viewManager, viewId);
}
isShow(rowId: string): boolean { isShow(rowId: string): boolean {
if (this.filter$.value?.conditions.length) { if (this.filter$.value?.conditions.length) {
const rowMap = Object.fromEntries( const rowMap = Object.fromEntries(
this.properties$.value.map(column => [ this.propertiesRaw$.value.map(column => [
column.id, column.id,
column.cellGetOrCreate(rowId).jsonValue$.value, column.cellGetOrCreate(rowId).jsonValue$.value,
]) ])
@@ -290,6 +332,33 @@ export class TableSingleView extends SingleViewBase<TableViewData> {
}); });
} }
); );
private materializeColumns() {
const data = this.data$.value;
if (!data) {
return;
}
const nextColumns = materializeTableColumns(
data.columns,
this.dataSource.properties$.value,
id => this.propertyGetOrCreate(id).width$.value
);
if (nextColumns === data.columns) {
return;
}
this.dataUpdate(() => ({ columns: nextColumns }));
}
constructor(viewManager: ViewManager, viewId: string) {
super(viewManager, viewId);
// Materialize view columns on view activation so newly added properties
// can participate in hide/order operations in table.
queueMicrotask(() => {
this.materializeColumns();
});
}
} }
type TableColumnData = TableViewData['columns'][number]; type TableColumnData = TableViewData['columns'][number];

View File

@@ -26,5 +26,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -42,5 +42,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -35,5 +35,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -26,7 +26,6 @@
"@preact/signals-core": "^1.8.0", "@preact/signals-core": "^1.8.0",
"@toeverything/theme": "^1.1.23", "@toeverything/theme": "^1.1.23",
"lit": "^3.2.0", "lit": "^3.2.0",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2", "rxjs": "^7.8.2",
"zod": "^3.25.76" "zod": "^3.25.76"
}, },
@@ -40,5 +39,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -28,7 +28,6 @@
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"lit": "^3.2.0", "lit": "^3.2.0",
"lodash-es": "^4.17.23", "lodash-es": "^4.17.23",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2", "rxjs": "^7.8.2",
"zod": "^3.25.76" "zod": "^3.25.76"
}, },
@@ -42,5 +41,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -27,7 +27,6 @@
"@toeverything/theme": "^1.1.23", "@toeverything/theme": "^1.1.23",
"@vanilla-extract/css": "^1.17.0", "@vanilla-extract/css": "^1.17.0",
"lit": "^3.2.0", "lit": "^3.2.0",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2", "rxjs": "^7.8.2",
"zod": "^3.25.76" "zod": "^3.25.76"
}, },
@@ -41,5 +40,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -27,7 +27,6 @@
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"lit": "^3.2.0", "lit": "^3.2.0",
"lodash-es": "^4.17.23", "lodash-es": "^4.17.23",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2", "rxjs": "^7.8.2",
"yjs": "^13.6.27", "yjs": "^13.6.27",
"zod": "^3.25.76" "zod": "^3.25.76"
@@ -43,5 +42,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -28,7 +28,6 @@
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"lit": "^3.2.0", "lit": "^3.2.0",
"lodash-es": "^4.17.23", "lodash-es": "^4.17.23",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2", "rxjs": "^7.8.2",
"yjs": "^13.6.27", "yjs": "^13.6.27",
"zod": "^3.25.76" "zod": "^3.25.76"
@@ -44,5 +43,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -29,7 +29,6 @@
"fractional-indexing": "^3.2.0", "fractional-indexing": "^3.2.0",
"lit": "^3.2.0", "lit": "^3.2.0",
"lodash-es": "^4.17.23", "lodash-es": "^4.17.23",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2", "rxjs": "^7.8.2",
"yjs": "^13.6.27", "yjs": "^13.6.27",
"zod": "^3.25.76" "zod": "^3.25.76"
@@ -48,5 +47,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -30,7 +30,6 @@
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"lit": "^3.2.0", "lit": "^3.2.0",
"lodash-es": "^4.17.23", "lodash-es": "^4.17.23",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2", "rxjs": "^7.8.2",
"yjs": "^13.6.27", "yjs": "^13.6.27",
"zod": "^3.25.76" "zod": "^3.25.76"
@@ -45,5 +44,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -34,7 +34,6 @@
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"lit": "^3.2.0", "lit": "^3.2.0",
"lodash-es": "^4.17.23", "lodash-es": "^4.17.23",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2", "rxjs": "^7.8.2",
"simple-xml-to-json": "^1.2.2", "simple-xml-to-json": "^1.2.2",
"yjs": "^13.6.27", "yjs": "^13.6.27",
@@ -51,5 +50,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -30,7 +30,6 @@
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"lit": "^3.2.0", "lit": "^3.2.0",
"lodash-es": "^4.17.23", "lodash-es": "^4.17.23",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2", "rxjs": "^7.8.2",
"yjs": "^13.6.27", "yjs": "^13.6.27",
"zod": "^3.25.76" "zod": "^3.25.76"
@@ -45,5 +44,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -27,7 +27,6 @@
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"lit": "^3.2.0", "lit": "^3.2.0",
"lodash-es": "^4.17.23", "lodash-es": "^4.17.23",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2", "rxjs": "^7.8.2",
"yjs": "^13.6.27", "yjs": "^13.6.27",
"zod": "^3.25.76" "zod": "^3.25.76"
@@ -45,5 +44,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -28,7 +28,6 @@
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"lit": "^3.2.0", "lit": "^3.2.0",
"lodash-es": "^4.17.23", "lodash-es": "^4.17.23",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2", "rxjs": "^7.8.2",
"yjs": "^13.6.27", "yjs": "^13.6.27",
"zod": "^3.25.76" "zod": "^3.25.76"
@@ -44,5 +43,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -29,7 +29,6 @@
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"lit": "^3.2.0", "lit": "^3.2.0",
"lodash-es": "^4.17.23", "lodash-es": "^4.17.23",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2", "rxjs": "^7.8.2",
"yjs": "^13.6.27", "yjs": "^13.6.27",
"zod": "^3.25.76" "zod": "^3.25.76"
@@ -44,5 +43,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -27,7 +27,6 @@
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"lit": "^3.2.0", "lit": "^3.2.0",
"lodash-es": "^4.17.23", "lodash-es": "^4.17.23",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2", "rxjs": "^7.8.2",
"yjs": "^13.6.27", "yjs": "^13.6.27",
"zod": "^3.25.76" "zod": "^3.25.76"
@@ -43,5 +42,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -25,5 +25,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -42,5 +42,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -47,5 +47,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -50,5 +50,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -44,5 +44,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -42,5 +42,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -56,5 +56,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -43,5 +43,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -30,5 +30,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -41,5 +41,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -39,7 +39,6 @@
"micromark-extension-gfm-table": "^2.1.0", "micromark-extension-gfm-table": "^2.1.0",
"micromark-extension-gfm-task-list-item": "^2.1.0", "micromark-extension-gfm-task-list-item": "^2.1.0",
"micromark-util-combine-extensions": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0",
"minimatch": "^10.1.1",
"pdfmake": "^0.2.20", "pdfmake": "^0.2.20",
"quick-lru": "^7.3.0", "quick-lru": "^7.3.0",
"rehype-parse": "^9.0.0", "rehype-parse": "^9.0.0",
@@ -77,5 +76,5 @@
"@types/pdfmake": "^0.2.12", "@types/pdfmake": "^0.2.12",
"vitest": "^3.2.4" "vitest": "^3.2.4"
}, },
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -21,12 +21,24 @@ const imageProxyMiddlewareBuilder = () => {
}; };
}; };
const IMAGE_PROXY_PATH = '/api/worker/image-proxy';
export const isImageProxyURL = (imageUrl: string) => {
try {
const url = new URL(imageUrl, globalThis.location.origin);
return url.pathname === IMAGE_PROXY_PATH && url.searchParams.has('url');
} catch {
return false;
}
};
const defaultImageProxyMiddlewarBuilder = imageProxyMiddlewareBuilder(); const defaultImageProxyMiddlewarBuilder = imageProxyMiddlewareBuilder();
export const setImageProxyMiddlewareURL = defaultImageProxyMiddlewarBuilder.set; export const setImageProxyMiddlewareURL = defaultImageProxyMiddlewarBuilder.set;
export const defaultImageProxyMiddleware = export const defaultImageProxyMiddleware: TransformerMiddleware = args => {
defaultImageProxyMiddlewarBuilder.get(); return defaultImageProxyMiddlewarBuilder.get()(args);
};
// TODO(@mirone): this should be configured when setup instead of runtime // TODO(@mirone): this should be configured when setup instead of runtime
export class ImageProxyService extends StoreExtension { export class ImageProxyService extends StoreExtension {
@@ -40,7 +52,7 @@ export class ImageProxyService extends StoreExtension {
} }
buildUrl(imageUrl: string) { buildUrl(imageUrl: string) {
if (imageUrl.startsWith(this.imageProxyURL)) { if (imageUrl.startsWith(this.imageProxyURL) || isImageProxyURL(imageUrl)) {
return imageUrl; return imageUrl;
} }

View File

@@ -1,5 +1,7 @@
import { css, unsafeCSS } from 'lit'; import { css, unsafeCSS } from 'lit';
import { unsafeCSSVarV2 } from '../theme/css-variables';
/** /**
* You should add a container before the scrollbar style to prevent the style pollution of the whole doc. * You should add a container before the scrollbar style to prevent the style pollution of the whole doc.
*/ */
@@ -28,7 +30,7 @@ export const scrollbarStyle = (container: string) => {
} }
${unsafeCSS(container)}::-webkit-scrollbar-thumb { ${unsafeCSS(container)}::-webkit-scrollbar-thumb {
border-radius: 2px; border-radius: 2px;
background-color: #b1b1b1; background-color: ${unsafeCSSVarV2('icon/secondary', '#b1b1b1')};
} }
${unsafeCSS(container)}::-webkit-scrollbar-corner { ${unsafeCSS(container)}::-webkit-scrollbar-corner {
display: none; display: none;

View File

@@ -31,7 +31,6 @@
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"lit": "^3.2.0", "lit": "^3.2.0",
"lodash-es": "^4.17.23", "lodash-es": "^4.17.23",
"minimatch": "^10.1.1",
"rxjs": "^7.8.2", "rxjs": "^7.8.2",
"zod": "^3.25.76" "zod": "^3.25.76"
}, },
@@ -45,5 +44,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -34,5 +34,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -36,5 +36,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -40,5 +40,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -38,5 +38,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -36,5 +36,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -34,5 +34,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -55,5 +55,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -43,5 +43,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -220,9 +220,7 @@ export class ImportDoc extends WithDisposable(LitElement) {
</header> </header>
<div> <div>
AFFiNE will gradually support more file formats for import. AFFiNE will gradually support more file formats for import.
<a <a href="https://affine.pro/redirect/discord" target="_blank"
href="https://community.affine.pro/c/feature-requests/import-export"
target="_blank"
>Provide feedback.</a >Provide feedback.</a
> >
</div> </div>

View File

@@ -37,5 +37,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -37,5 +37,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -35,5 +35,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -30,5 +30,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -36,5 +36,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -38,5 +38,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -20,6 +20,7 @@ import {
} from '@blocksuite/affine-shared/services'; } from '@blocksuite/affine-shared/services';
import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme'; import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
import { matchModels } from '@blocksuite/affine-shared/utils'; import { matchModels } from '@blocksuite/affine-shared/utils';
import { IS_MOBILE } from '@blocksuite/global/env';
import { import {
Bound, Bound,
getCommonBound, getCommonBound,
@@ -109,6 +110,17 @@ export class AffineToolbarWidget extends WidgetComponent {
} }
} }
editor-toolbar[data-mobile='true'] {
position: fixed;
top: auto;
left: 50%;
bottom: 16px;
transform: translateX(-50%);
max-width: calc(100vw - 32px);
overflow-x: auto;
touch-action: pan-x;
}
${unsafeCSS(darkToolbarStyles('editor-toolbar'))} ${unsafeCSS(darkToolbarStyles('editor-toolbar'))}
${unsafeCSS(lightToolbarStyles('editor-toolbar'))} ${unsafeCSS(lightToolbarStyles('editor-toolbar'))}
`; `;
@@ -268,10 +280,111 @@ export class AffineToolbarWidget extends WidgetComponent {
const { flags, flavour$, message$, placement$ } = toolbarRegistry; const { flags, flavour$, message$, placement$ } = toolbarRegistry;
const context = new ToolbarContext(std); const context = new ToolbarContext(std);
// TODO(@fundon): fix toolbar position shaking when the wheel scrolls const isNativeTextSelection = () => {
// document.body.append(toolbar); const dbSel = std.selection.find(DatabaseSelection);
const dbViewSel = dbSel?.viewSelection;
if (
dbViewSel &&
((dbViewSel.selectionType === 'area' && dbViewSel.isEditing) ||
(dbViewSel.selectionType === 'cell' && dbViewSel.isEditing))
) {
return true;
}
const tableViewSelection = std.selection.find(TableSelection)?.data;
return tableViewSelection?.type === 'area';
};
let updateMobilePosition: (() => void) | null = null;
if (IS_MOBILE) {
toolbar.dataset.mobile = 'true';
this.shadowRoot!.append(toolbar); this.shadowRoot!.append(toolbar);
// Position toolbar above virtual keyboard using Visual Viewport API
updateMobilePosition = () => {
const vv = window.visualViewport;
if (!vv) return;
const keyboardHeight = window.innerHeight - vv.height - vv.offsetTop;
toolbar.style.bottom = `${Math.max(16, keyboardHeight + 16)}px`;
};
if (window.visualViewport) {
disposables.addFromEvent(
window.visualViewport,
'resize',
updateMobilePosition
);
disposables.addFromEvent(
window.visualViewport,
'scroll',
updateMobilePosition
);
}
// Keep mobile selection in sync with toolbar flags. On some mobile browsers,
// long-press selection may skip the std selection stream intermittently.
const syncMobileTextSelection = () => {
if (!context.activated) {
flags.toggle(Flag.Text, false);
return;
}
if (isNativeTextSelection()) {
flags.toggle(Flag.Text, false);
return;
}
const selection = window.getSelection();
const hasSelection =
selection &&
selection.rangeCount > 0 &&
!selection.isCollapsed &&
selection.toString().length > 0;
const range = hasSelection ? selection.getRangeAt(0) : null;
const inEditor = Boolean(
range && host.contains(range.commonAncestorContainer)
);
batch(() => {
flags.toggle(Flag.Text, inEditor);
if (!inEditor || !range) return;
this.setReferenceElementWithRange(range);
sideOptions$.value = null;
flavour$.value = 'affine:note';
placement$.value = toolbarRegistry.getModulePlacement('affine:note');
flags.refresh(Flag.Text);
});
};
let selectionTimeout: ReturnType<typeof setTimeout> | null = null;
let touchTimeout: ReturnType<typeof setTimeout> | null = null;
const scheduleSyncMobileTextSelection = (delay: number) => {
if (selectionTimeout) clearTimeout(selectionTimeout);
selectionTimeout = setTimeout(syncMobileTextSelection, delay);
};
const scheduleTouchSync = (delay: number) => {
if (touchTimeout) clearTimeout(touchTimeout);
touchTimeout = setTimeout(syncMobileTextSelection, delay);
};
disposables.addFromEvent(document, 'selectionchange', () => {
scheduleSyncMobileTextSelection(50);
});
disposables.addFromEvent(host, 'touchend', () => {
scheduleTouchSync(100);
});
disposables.add(() => {
if (selectionTimeout) clearTimeout(selectionTimeout);
if (touchTimeout) clearTimeout(touchTimeout);
});
// Ensures a stable initial offset before the first viewport event arrives.
updateMobilePosition?.();
} else {
this.shadowRoot!.append(toolbar);
}
// Formatting // Formatting
// Selects text in note. // Selects text in note.
disposables.add( disposables.add(
@@ -305,30 +418,12 @@ export class AffineToolbarWidget extends WidgetComponent {
disposables.addFromEvent(document, 'selectionchange', () => { disposables.addFromEvent(document, 'selectionchange', () => {
const range = std.range.value ?? null; const range = std.range.value ?? null;
let activated = context.activated && Boolean(range && !range.collapsed); let activated = context.activated && Boolean(range && !range.collapsed);
let isNative = false;
if (activated) { if (activated) {
const result = std.selection.find(DatabaseSelection); activated = isNativeTextSelection();
const viewSelection = result?.viewSelection;
if (viewSelection) {
isNative =
(viewSelection.selectionType === 'area' &&
viewSelection.isEditing) ||
(viewSelection.selectionType === 'cell' && viewSelection.isEditing);
}
if (!isNative) {
const result = std.selection.find(TableSelection);
const viewSelection = result?.data;
if (viewSelection) {
isNative = viewSelection.type === 'area';
}
}
} }
batch(() => { batch(() => {
activated &&= isNative;
// Focues outside: `doc-title` // Focues outside: `doc-title`
if ( if (
flags.check(Flag.Text) && flags.check(Flag.Text) &&
@@ -662,6 +757,14 @@ export class AffineToolbarWidget extends WidgetComponent {
disposables.add( disposables.add(
effect(() => { effect(() => {
if (IS_MOBILE) {
const value = flags.value$.value;
if (!context.activated) return;
if (Flag.None === value || flags.contains(Flag.Hiding, value)) return;
updateMobilePosition?.();
return;
}
if (!abortController.signal.aborted) { if (!abortController.signal.aborted) {
abortController.abort(); abortController.abort();
} }

View File

@@ -35,5 +35,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -17,5 +17,5 @@
"dependencies": { "dependencies": {
"@blocksuite/affine": "workspace:*" "@blocksuite/affine": "workspace:*"
}, },
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -64,5 +64,5 @@
"devDependencies": { "devDependencies": {
"vitest": "^3.2.4" "vitest": "^3.2.4"
}, },
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -47,5 +47,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -0,0 +1,119 @@
import { describe, expect, test } from 'vitest';
import { bindKeymap } from '../event/keymap.js';
const createKeyboardEvent = (options: {
key: string;
keyCode: number;
altKey?: boolean;
ctrlKey?: boolean;
metaKey?: boolean;
shiftKey?: boolean;
}): KeyboardEvent => {
const event = new KeyboardEvent('keydown', {
key: options.key,
altKey: options.altKey ?? false,
ctrlKey: options.ctrlKey ?? false,
metaKey: options.metaKey ?? false,
shiftKey: options.shiftKey ?? false,
});
Object.defineProperty(event, 'keyCode', {
configurable: true,
get: () => options.keyCode,
});
Object.defineProperty(event, 'which', {
configurable: true,
get: () => options.keyCode,
});
return event;
};
const createCtx = (event: KeyboardEvent) => {
return {
get(name: string) {
if (name === 'keyboardState') {
return { raw: event };
}
return undefined;
},
} as any;
};
describe('bindKeymap', () => {
test('falls back to physical key for ctrl shortcuts on non-US layouts', () => {
let handled = false;
const handler = bindKeymap({
'Ctrl-f': () => {
handled = true;
return true;
},
});
const event = createKeyboardEvent({
key: 'а',
keyCode: 70,
ctrlKey: true,
});
expect(handler(createCtx(event))).toBe(true);
expect(handled).toBe(true);
});
test('does not fallback for Alt+locale-character letter input', () => {
let handled = false;
const handler = bindKeymap({
'Alt-s': () => {
handled = true;
return true;
},
});
const event = createKeyboardEvent({
key: 'ś',
keyCode: 83,
altKey: true,
});
expect(handler(createCtx(event))).toBe(false);
expect(handled).toBe(false);
});
test('keeps Alt+digit fallback for non-ASCII key outputs', () => {
let handled = false;
const handler = bindKeymap({
'Alt-0': () => {
handled = true;
return true;
},
});
const event = createKeyboardEvent({
key: 'º',
keyCode: 48,
altKey: true,
});
expect(handler(createCtx(event))).toBe(true);
expect(handled).toBe(true);
});
test('does not fallback on non-ASCII input without modifiers', () => {
let handled = false;
const handler = bindKeymap({
'[': () => {
handled = true;
return true;
},
});
const event = createKeyboardEvent({
key: 'х',
keyCode: 219,
});
expect(handler(createCtx(event))).toBe(false);
expect(handled).toBe(false);
});
});

View File

@@ -90,9 +90,21 @@ export function bindKeymap(
// Do NOT fallback when the key produces a non-ASCII character (e.g., Cyrillic 'х' on Russian keyboard), // Do NOT fallback when the key produces a non-ASCII character (e.g., Cyrillic 'х' on Russian keyboard),
// because the user intends to type that character, not trigger a shortcut bound to the physical key. // because the user intends to type that character, not trigger a shortcut bound to the physical key.
// See: https://github.com/toeverything/AFFiNE/issues/14059 // See: https://github.com/toeverything/AFFiNE/issues/14059
const hasModifier = event.shiftKey || event.altKey || event.metaKey; const hasModifier =
event.shiftKey || event.altKey || event.ctrlKey || event.metaKey;
const baseName = base[event.keyCode]; const baseName = base[event.keyCode];
if (hasModifier && baseName && baseName !== name) { const isSingleAscii = name.length === 1 && name.charCodeAt(0) <= 0x7e;
const isAltInputChar = event.altKey && !event.ctrlKey && !isSingleAscii;
// Keep supporting existing Alt+digit shortcuts (e.g. Alt-0/1/2 in edgeless)
// while preventing Alt-based locale input characters from triggering letter shortcuts.
const isDigitBaseKey =
baseName != null && baseName.length === 1 && /[0-9]/.test(baseName);
if (
hasModifier &&
baseName &&
baseName !== name &&
!(isAltInputChar && !isDigitBaseKey)
) {
const fromCode = map[modifiers(baseName, event)]; const fromCode = map[modifiers(baseName, event)];
if (fromCode && fromCode(ctx)) { if (fromCode && fromCode(ctx)) {
return true; return true;

View File

@@ -19,7 +19,7 @@
"lodash.clonedeep": "^4.5.0", "lodash.clonedeep": "^4.5.0",
"lodash.ismatch": "^4.4.0", "lodash.ismatch": "^4.4.0",
"lodash.merge": "^4.6.2", "lodash.merge": "^4.6.2",
"minimatch": "^10.1.1", "minimatch": "^10.2.2",
"nanoid": "^5.1.6", "nanoid": "^5.1.6",
"rxjs": "^7.8.2", "rxjs": "^7.8.2",
"y-protocols": "^1.0.6", "y-protocols": "^1.0.6",
@@ -42,5 +42,5 @@
"!dist/__tests__", "!dist/__tests__",
"shim.d.ts" "shim.d.ts"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -33,5 +33,5 @@
"!src/__tests__", "!src/__tests__",
"!dist/__tests__" "!dist/__tests__"
], ],
"version": "0.26.1" "version": "0.26.3"
} }

View File

@@ -46,5 +46,5 @@
"vite-plugin-wasm": "^3.5.0", "vite-plugin-wasm": "^3.5.0",
"vitest": "^3.2.4" "vitest": "^3.2.4"
}, },
"version": "0.26.1" "version": "0.26.3"
} }

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