Compare commits

..

2 Commits

Author SHA1 Message Date
zzj3720 9bee2cb0fa fix(editor): improve string conversion logic for checkbox property
- Add a FALSE_VALUES set containing various falsy string representations

- Support Chinese negation terms like "否", "不", "错", etc.

- Optimize the implementation of cellFromString method
2025-02-26 00:11:36 +08:00
zzj3720 1addd17d64 fix(editor): table block supports parsing rich text 2025-02-25 18:52:13 +08:00
463 changed files with 7956 additions and 9728 deletions
+4 -6
View File
@@ -44,14 +44,12 @@ runs:
RUSTUP_HOME: ${{ env.DEV_DRIVE }}/.rustup
- name: Set CC
if: ${{ contains(inputs.target, 'linux') && inputs.no-build != 'true' }}
if: ${{ contains(inputs.target, 'linux') && inputs.package != '@affine/native' && inputs.no-build != 'true' }}
working-directory: ${{ env.DEV_DRIVE_WORKSPACE || github.workspace }}
shell: bash
# https://github.com/tree-sitter/tree-sitter/issues/4186
# pass -D_BSD_SOURCE to clang to fix the tree-sitter build issue
run: |
echo "CC=clang -D_BSD_SOURCE" >> "$GITHUB_ENV"
echo "TARGET_CC=clang -D_BSD_SOURCE" >> "$GITHUB_ENV"
echo "CC=clang" >> "$GITHUB_ENV"
echo "TARGET_CC=clang" >> "$GITHUB_ENV"
- name: Cache cargo
uses: Swatinem/rust-cache@v2
@@ -84,7 +82,7 @@ runs:
shell: bash
if: ${{ runner.os == 'Windows' && inputs.no-build != 'true' }}
run: |
yarn workspace ${{ inputs.package }} build --target ${{ inputs.target }}
yarn workspace ${{ inputs.package }} build --target ${{ inputs.target }} --use-napi-cross
env:
DEBUG: 'napi:*'
CARGO_HOME: ${{ env.DEV_DRIVE }}/.cargo
+8 -15
View File
@@ -25,7 +25,6 @@ const {
AFFINE_GOOGLE_CLIENT_ID,
AFFINE_GOOGLE_CLIENT_SECRET,
CLOUD_SQL_IAM_ACCOUNT,
APP_IAM_ACCOUNT,
GCLOUD_CONNECTION_NAME,
GCLOUD_CLOUD_SQL_INTERNAL_ENDPOINT,
REDIS_HOST,
@@ -100,22 +99,16 @@ const createHelmCommand = ({ isDryRun }) => {
`--set-string global.redis.password="${REDIS_PASSWORD}"`,
]
: [];
const serviceAnnotations = [
`--set-json web.serviceAccount.annotations="{ \\"iam.gke.io/gcp-service-account\\": \\"${APP_IAM_ACCOUNT}\\" }"`,
`--set-json graphql.serviceAccount.annotations="{ \\"iam.gke.io/gcp-service-account\\": \\"${APP_IAM_ACCOUNT}\\" }"`,
`--set-json sync.serviceAccount.annotations="{ \\"iam.gke.io/gcp-service-account\\": \\"${APP_IAM_ACCOUNT}\\" }"`,
`--set-json doc.serviceAccount.annotations="{ \\"iam.gke.io/gcp-service-account\\": \\"${APP_IAM_ACCOUNT}\\" }"`,
].concat(
const serviceAnnotations =
isProduction || isBeta || isInternal
? [
`--set-json web.service.annotations="{ \\"cloud.google.com/neg\\": \\"{\\\\\\"ingress\\\\\\": true}\\" }"`,
`--set-json graphql.service.annotations="{ \\"cloud.google.com/neg\\": \\"{\\\\\\"ingress\\\\\\": true}\\" }"`,
`--set-json sync.service.annotations="{ \\"cloud.google.com/neg\\": \\"{\\\\\\"ingress\\\\\\": true}\\" }"`,
`--set-json cloud-sql-proxy.serviceAccount.annotations="{ \\"iam.gke.io/gcp-service-account\\": \\"${CLOUD_SQL_IAM_ACCOUNT}\\" }"`,
`--set-json cloud-sql-proxy.nodeSelector="{ \\"iam.gke.io/gke-metadata-server-enabled\\": \\"true\\" }"`,
`--set-json web.service.annotations=\"{ \\"cloud.google.com/neg\\": \\"{\\\\\\"ingress\\\\\\": true}\\" }\"`,
`--set-json graphql.service.annotations=\"{ \\"cloud.google.com/neg\\": \\"{\\\\\\"ingress\\\\\\": true}\\" }\"`,
`--set-json sync.service.annotations=\"{ \\"cloud.google.com/neg\\": \\"{\\\\\\"ingress\\\\\\": true}\\" }\"`,
`--set-json cloud-sql-proxy.serviceAccount.annotations=\"{ \\"iam.gke.io/gcp-service-account\\": \\"${CLOUD_SQL_IAM_ACCOUNT}\\" }\"`,
`--set-json cloud-sql-proxy.nodeSelector=\"{ \\"iam.gke.io/gke-metadata-server-enabled\\": \\"true\\" }\"`,
]
: []
);
: [];
const cpu = cpuConfig[buildType];
const resources = cpu
@@ -143,7 +136,7 @@ const createHelmCommand = ({ isDryRun }) => {
`--namespace ${namespace}`,
`--set-string global.app.buildType="${buildType}"`,
`--set global.ingress.enabled=true`,
`--set-json global.ingress.annotations="{ \\"kubernetes.io/ingress.class\\": \\"gce\\", \\"kubernetes.io/ingress.allow-http\\": \\"true\\", \\"kubernetes.io/ingress.global-static-ip-name\\": \\"${STATIC_IP_NAME}\\" }"`,
`--set-json global.ingress.annotations=\"{ \\"kubernetes.io/ingress.class\\": \\"gce\\", \\"kubernetes.io/ingress.allow-http\\": \\"true\\", \\"kubernetes.io/ingress.global-static-ip-name\\": \\"${STATIC_IP_NAME}\\" }\"`,
`--set-string global.ingress.host="${host}"`,
`--set global.objectStorage.r2.enabled=true`,
`--set-string global.objectStorage.r2.accountId="${R2_ACCOUNT_ID}"`,
+1 -1
View File
@@ -26,7 +26,7 @@ podSecurityContext:
resources:
requests:
cpu: '1'
cpu: '2'
memory: 4Gi
probe:
@@ -36,7 +36,7 @@ spec:
- name: NODE_ENV
value: "{{ .Values.env }}"
- name: NODE_OPTIONS
value: "--max-old-space-size=2048"
value: "--max-old-space-size=4096"
- name: NO_COLOR
value: "1"
- name: DEPLOYMENT_TYPE
@@ -71,8 +71,8 @@ podSecurityContext:
resources:
requests:
cpu: '2'
memory: 2Gi
cpu: '4'
memory: 4Gi
probe:
initialDelaySeconds: 20
@@ -36,7 +36,7 @@ spec:
- name: NODE_ENV
value: "{{ .Values.env }}"
- name: NODE_OPTIONS
value: "--max-old-space-size=2048"
value: "--max-old-space-size=4096"
- name: NO_COLOR
value: "1"
- name: DEPLOYMENT_TYPE
@@ -27,8 +27,8 @@ podSecurityContext:
resources:
requests:
cpu: '1'
memory: 2Gi
cpu: '4'
memory: 4Gi
probe:
initialDelaySeconds: 20
+3 -3
View File
@@ -24,11 +24,11 @@ podSecurityContext:
resources:
limits:
cpu: '4'
memory: 8Gi
requests:
cpu: '2'
memory: 4Gi
requests:
cpu: '1'
memory: 2Gi
probe:
initialDelaySeconds: 20
-27
View File
@@ -309,10 +309,6 @@ jobs:
with:
workspace-copy: true
drive-size: 8GB
drive-format: NTFS
env-mapping: |
CARGO_HOME,{{ DEV_DRIVE }}/.cargo
RUSTUP_HOME,{{ DEV_DRIVE }}/.rustup
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
@@ -391,28 +387,6 @@ jobs:
path: dist.tar.gz
if-no-files-found: error
native-unit-test:
name: Native Unit Test
runs-on: ubuntu-latest
needs:
- optimize_ci
- build-native
if: needs.optimize_ci.outputs.skip == 'false'
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
extra-flags: workspaces focus @affine-tools/cli @affine/monorepo @affine/native
electron-install: false
- name: Download affine.linux-x64-gnu.node
uses: actions/download-artifact@v4
with:
name: affine.linux-x64-gnu.node
path: ./packages/frontend/native
- name: Unit Test
run: yarn affine @affine/native test
server-test:
name: Server Test
runs-on: ubuntu-latest
@@ -923,7 +897,6 @@ jobs:
- build-native
- build-server-native
- build-electron-renderer
- native-unit-test
- server-test
- rust-test
- copilot-api-test
-1
View File
@@ -116,7 +116,6 @@ jobs:
REDIS_HOST: ${{ secrets.REDIS_HOST }}
REDIS_PASSWORD: ${{ secrets.REDIS_PASSWORD }}
CLOUD_SQL_IAM_ACCOUNT: ${{ secrets.CLOUD_SQL_IAM_ACCOUNT }}
APP_IAM_ACCOUNT: ${{ secrets.APP_IAM_ACCOUNT }}
STRIPE_API_KEY: ${{ secrets.STRIPE_API_KEY }}
STRIPE_WEBHOOK_KEY: ${{ secrets.STRIPE_WEBHOOK_KEY }}
STATIC_IP_NAME: ${{ secrets.STATIC_IP_NAME }}
+1 -1
View File
@@ -53,7 +53,7 @@ jobs:
uses: actions/checkout@v4
with:
ref: l10n_crowdin_translations
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
+1 -1
View File
@@ -1,7 +1,7 @@
# we will make this file shared by prettier|eslint|oxlint
**/node_modules
.yarn
.github/helm
.github
.vscode
.yarnrc.yml
.docker
Generated
+92 -353
View File
@@ -44,7 +44,7 @@ dependencies = [
"sha3",
"strum_macros",
"text-splitter",
"thiserror 2.0.11",
"thiserror 1.0.69",
"tiktoken-rs",
"tree-sitter",
"tree-sitter-c",
@@ -69,8 +69,6 @@ dependencies = [
"core-foundation",
"coreaudio-rs",
"dispatch2",
"libc",
"mp3lame-encoder",
"napi",
"napi-build",
"napi-derive",
@@ -78,7 +76,6 @@ dependencies = [
"objc2-foundation",
"rubato",
"screencapturekit",
"symphonia",
"thiserror 2.0.11",
"uuid",
]
@@ -276,9 +273,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.96"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b964d184e89d9b6b67dd2715bc8e74cf3107fb2b529990c90cf517326150bf4"
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
[[package]]
name = "arbitrary"
@@ -289,12 +286,6 @@ dependencies = [
"derive_arbitrary",
]
[[package]]
name = "arrayvec"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
[[package]]
name = "async-compat"
version = "0.2.4"
@@ -347,15 +338,6 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "autotools"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef941527c41b0fc0dd48511a8154cd5fc7e29200a0ff8b7203c5d777dbc795cf"
dependencies = [
"cc",
]
[[package]]
name = "backtrace"
version = "0.3.74"
@@ -518,12 +500,6 @@ version = "3.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
[[package]]
name = "bytemuck"
version = "1.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3"
[[package]]
name = "byteorder"
version = "1.5.0"
@@ -576,9 +552,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cc"
version = "1.2.15"
version = "1.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c736e259eea577f443d5c86c304f9f4ae0295c43f3ba05c21f1d66b5f06001af"
checksum = "755717a7de9ec452bf7f3f1a3099085deabd7f2962b861dae91ecd7a365903d2"
dependencies = [
"shlex",
]
@@ -632,16 +608,16 @@ dependencies = [
[[package]]
name = "chrono"
version = "0.4.40"
version = "0.4.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"wasm-bindgen",
"windows-link",
"windows-targets 0.52.6",
]
[[package]]
@@ -684,9 +660,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.31"
version = "4.5.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767"
checksum = "3e77c3243bd94243c03672cb5154667347c457ca271254724f9f393aee1c05ff"
dependencies = [
"clap_builder",
"clap_derive",
@@ -694,9 +670,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.31"
version = "4.5.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863"
checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7"
dependencies = [
"anstream",
"anstyle",
@@ -909,9 +885,9 @@ dependencies = [
[[package]]
name = "criterion2"
version = "3.0.0"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b43b9cdbf592c78d882f2a3b9e6ebe8aedc749ef84915103a0248802ce2f6b3"
checksum = "09db22066fd79bd628faf416dac96e44054deb00531601bcc20c6d12506b3701"
dependencies = [
"anes",
"bpaf",
@@ -976,18 +952,9 @@ dependencies = [
[[package]]
name = "ctor"
version = "0.3.6"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21d960ecacd0a1bf55e73144b72de745e7bf275c7952c50e36e8af0a0cb7ab1f"
dependencies = [
"ctor-proc-macro",
]
[[package]]
name = "ctor-proc-macro"
version = "0.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c426d2ba3e525b39c1f0a9ba41b9fe61878dee11fa4e4a76b6ab440f46c5db5d"
checksum = "f06b1425736ba96096116f063c9d10be2352a7cde0cbea829a717008e114aec9"
[[package]]
name = "dashmap"
@@ -1132,9 +1099,9 @@ checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
[[package]]
name = "either"
version = "1.14.0"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7914353092ddf589ad78f25c5c1c21b7f80b0ff8621e7c814c3485b5306da9d"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
dependencies = [
"serde",
]
@@ -1150,9 +1117,9 @@ dependencies = [
[[package]]
name = "equivalent"
version = "1.0.2"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "errno"
@@ -1206,12 +1173,6 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "extended"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af9673d8203fcb076b19dfd17e38b3d4ae9f44959416ea532ce72415a6020365"
[[package]]
name = "fancy-regex"
version = "0.13.0"
@@ -1237,9 +1198,9 @@ checksum = "e7ef3d5e8ae27277c8285ac43ed153158178ef0f79567f32024ca8140a0c7cd8"
[[package]]
name = "flate2"
version = "1.1.0"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc"
checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c"
dependencies = [
"crc32fast",
"miniz_oxide",
@@ -1913,9 +1874,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.170"
version = "0.2.169"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828"
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
[[package]]
name = "libloading"
@@ -1962,9 +1923,9 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
[[package]]
name = "litemap"
version = "0.7.5"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856"
checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104"
[[package]]
name = "lock_api"
@@ -1978,9 +1939,9 @@ dependencies = [
[[package]]
name = "log"
version = "0.4.26"
version = "0.4.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
[[package]]
name = "loom"
@@ -2114,9 +2075,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.8.5"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5"
checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924"
dependencies = [
"adler2",
]
@@ -2132,27 +2093,6 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "mp3lame-encoder"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc8c8b5cdbe788ccd1098c3d3635298a011cffdebdd3460c9ca5060a7551557b"
dependencies = [
"libc",
"mp3lame-sys",
]
[[package]]
name = "mp3lame-sys"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acaec8842b2ebd61692a6c8c2b9f3edbf5c36e5e5c4677b5911430eaf859377c"
dependencies = [
"autotools",
"cc",
"libc",
]
[[package]]
name = "nanoid"
version = "0.4.0"
@@ -2164,9 +2104,9 @@ dependencies = [
[[package]]
name = "napi"
version = "3.0.0-alpha.31"
version = "3.0.0-alpha.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1911b4f0d33fbcb5f46ff68319ec053ab8a655f3a17440eae1246a23ba2ad78"
checksum = "0dd957e2cc4bd62b730b10ff1f35775f8a81dac84a3bfac273b0ec4336f53ab8"
dependencies = [
"anyhow",
"bitflags 2.8.0",
@@ -2180,15 +2120,15 @@ dependencies = [
[[package]]
name = "napi-build"
version = "2.1.5"
version = "2.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40685973218af4aa4b42486652692c294c44b5a67e4b2202df721c9063f2e51c"
checksum = "db836caddef23662b94e16bf1f26c40eceb09d6aee5d5b06a7ac199320b69b19"
[[package]]
name = "napi-derive"
version = "3.0.0-alpha.28"
version = "3.0.0-alpha.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8097918a9af1976700eac6944b120b65ad17bf6d38906703d2b68e17ee89256"
checksum = "a0f0b6f3f77925d8fd2030855af659ce428a7bb6e10e94852e226f509186ba7c"
dependencies = [
"convert_case 0.7.1",
"napi-derive-backend",
@@ -2199,9 +2139,9 @@ dependencies = [
[[package]]
name = "napi-derive-backend"
version = "2.0.0-alpha.27"
version = "2.0.0-alpha.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e5adc92fcdec3aa09f591bd2b139d7c669399f34b8211fe653641b52d40d3b3"
checksum = "c694bb49a2fa84dd9542d51eece39a57519f9cf1fc2deefa9d119ab8181e374d"
dependencies = [
"convert_case 0.7.1",
"proc-macro2",
@@ -2689,9 +2629,9 @@ dependencies = [
[[package]]
name = "pulldown-cmark"
version = "0.13.0"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e8bbe1a966bd2f362681a44f6edce3c2310ac21e4d5067a6e7ec396297a6ea0"
checksum = "f86ba2052aebccc42cbbb3ed234b8b13ce76f75c3551a303cb2bcffcff12bb14"
dependencies = [
"bitflags 2.8.0",
"memchr",
@@ -2731,8 +2671,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94"
dependencies = [
"rand_chacha 0.9.0",
"rand_core 0.9.2",
"zerocopy 0.8.21",
"rand_core 0.9.0",
"zerocopy 0.8.16",
]
[[package]]
@@ -2752,7 +2692,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core 0.9.2",
"rand_core 0.9.0",
]
[[package]]
@@ -2766,12 +2706,12 @@ dependencies = [
[[package]]
name = "rand_core"
version = "0.9.2"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a509b1a2ffbe92afab0e55c8fd99dea1c280e8171bd2d88682bb20bc41cbc2c"
checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff"
dependencies = [
"getrandom 0.3.1",
"zerocopy 0.8.21",
"zerocopy 0.8.16",
]
[[package]]
@@ -2834,9 +2774,9 @@ dependencies = [
[[package]]
name = "redox_syscall"
version = "0.5.9"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82b568323e98e49e2a0899dcee453dd679fae22d69adf9b11dd508d1549b7e2f"
checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834"
dependencies = [
"bitflags 2.8.0",
]
@@ -2887,14 +2827,15 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "ring"
version = "0.17.11"
version = "0.17.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da5349ae27d3887ca812fb375b45a4fbb36d8d12d2df394968cd86e35683fe73"
checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d"
dependencies = [
"cc",
"cfg-if",
"getrandom 0.2.15",
"libc",
"spin",
"untrusted",
"windows-sys 0.52.0",
]
@@ -3027,9 +2968,9 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.23.23"
version = "0.23.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395"
checksum = "9fb9263ab4eb695e42321db096e3b8fbd715a59b154d5c88d82db2175b681ba7"
dependencies = [
"once_cell",
"ring",
@@ -3145,18 +3086,18 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.218"
version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.218"
version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
dependencies = [
"proc-macro2",
"quote",
@@ -3165,9 +3106,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.139"
version = "1.0.138"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6"
checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949"
dependencies = [
"indexmap",
"itoa",
@@ -3277,9 +3218,9 @@ dependencies = [
[[package]]
name = "smallvec"
version = "1.14.0"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
dependencies = [
"serde",
]
@@ -3562,9 +3503,9 @@ dependencies = [
[[package]]
name = "string_cache_codegen"
version = "0.5.4"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0"
checksum = "244292f3441c89febe5b5bdfbb6863aeaf4f64da810ea3050fd927b27b8d92ce"
dependencies = [
"phf_generator 0.11.3",
"phf_shared 0.11.3",
@@ -3591,18 +3532,18 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "strum"
version = "0.27.1"
version = "0.26.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32"
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
dependencies = [
"strum_macros",
]
[[package]]
name = "strum_macros"
version = "0.27.1"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8"
checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be"
dependencies = [
"heck",
"proc-macro2",
@@ -3617,202 +3558,6 @@ version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "symphonia"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "815c942ae7ee74737bb00f965fa5b5a2ac2ce7b6c01c0cc169bbeaf7abd5f5a9"
dependencies = [
"lazy_static",
"symphonia-bundle-flac",
"symphonia-bundle-mp3",
"symphonia-codec-aac",
"symphonia-codec-adpcm",
"symphonia-codec-alac",
"symphonia-codec-pcm",
"symphonia-codec-vorbis",
"symphonia-core",
"symphonia-format-caf",
"symphonia-format-isomp4",
"symphonia-format-mkv",
"symphonia-format-ogg",
"symphonia-format-riff",
"symphonia-metadata",
]
[[package]]
name = "symphonia-bundle-flac"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72e34f34298a7308d4397a6c7fbf5b84c5d491231ce3dd379707ba673ab3bd97"
dependencies = [
"log",
"symphonia-core",
"symphonia-metadata",
"symphonia-utils-xiph",
]
[[package]]
name = "symphonia-bundle-mp3"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c01c2aae70f0f1fb096b6f0ff112a930b1fb3626178fba3ae68b09dce71706d4"
dependencies = [
"lazy_static",
"log",
"symphonia-core",
"symphonia-metadata",
]
[[package]]
name = "symphonia-codec-aac"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdbf25b545ad0d3ee3e891ea643ad115aff4ca92f6aec472086b957a58522f70"
dependencies = [
"lazy_static",
"log",
"symphonia-core",
]
[[package]]
name = "symphonia-codec-adpcm"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c94e1feac3327cd616e973d5be69ad36b3945f16b06f19c6773fc3ac0b426a0f"
dependencies = [
"log",
"symphonia-core",
]
[[package]]
name = "symphonia-codec-alac"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d8a6666649a08412906476a8b0efd9b9733e241180189e9f92b09c08d0e38f3"
dependencies = [
"log",
"symphonia-core",
]
[[package]]
name = "symphonia-codec-pcm"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f395a67057c2ebc5e84d7bb1be71cce1a7ba99f64e0f0f0e303a03f79116f89b"
dependencies = [
"log",
"symphonia-core",
]
[[package]]
name = "symphonia-codec-vorbis"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a98765fb46a0a6732b007f7e2870c2129b6f78d87db7987e6533c8f164a9f30"
dependencies = [
"log",
"symphonia-core",
"symphonia-utils-xiph",
]
[[package]]
name = "symphonia-core"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "798306779e3dc7d5231bd5691f5a813496dc79d3f56bf82e25789f2094e022c3"
dependencies = [
"arrayvec",
"bitflags 1.3.2",
"bytemuck",
"lazy_static",
"log",
"rustfft",
]
[[package]]
name = "symphonia-format-caf"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e43c99c696a388295a29fe71b133079f5d8b18041cf734c5459c35ad9097af50"
dependencies = [
"log",
"symphonia-core",
"symphonia-metadata",
]
[[package]]
name = "symphonia-format-isomp4"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abfdf178d697e50ce1e5d9b982ba1b94c47218e03ec35022d9f0e071a16dc844"
dependencies = [
"encoding_rs",
"log",
"symphonia-core",
"symphonia-metadata",
"symphonia-utils-xiph",
]
[[package]]
name = "symphonia-format-mkv"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bb43471a100f7882dc9937395bd5ebee8329298e766250b15b3875652fe3d6f"
dependencies = [
"lazy_static",
"log",
"symphonia-core",
"symphonia-metadata",
"symphonia-utils-xiph",
]
[[package]]
name = "symphonia-format-ogg"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ada3505789516bcf00fc1157c67729eded428b455c27ca370e41f4d785bfa931"
dependencies = [
"log",
"symphonia-core",
"symphonia-metadata",
"symphonia-utils-xiph",
]
[[package]]
name = "symphonia-format-riff"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f7be232f962f937f4b7115cbe62c330929345434c834359425e043bfd15f50"
dependencies = [
"extended",
"log",
"symphonia-core",
"symphonia-metadata",
]
[[package]]
name = "symphonia-metadata"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc622b9841a10089c5b18e99eb904f4341615d5aa55bbf4eedde1be721a4023c"
dependencies = [
"encoding_rs",
"lazy_static",
"log",
"symphonia-core",
]
[[package]]
name = "symphonia-utils-xiph"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "484472580fa49991afda5f6550ece662237b00c6f562c7d9638d1b086ed010fe"
dependencies = [
"symphonia-core",
"symphonia-metadata",
]
[[package]]
name = "syn"
version = "1.0.109"
@@ -3854,9 +3599,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "tempfile"
version = "3.17.1"
version = "3.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230"
checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91"
dependencies = [
"cfg-if",
"fastrand",
@@ -3879,9 +3624,9 @@ dependencies = [
[[package]]
name = "text-splitter"
version = "0.24.1"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "698b22fc8ce5bef13475143a43e87df82440e66b2a18d7655d1425dd36580a53"
checksum = "a5cb76f2930deed7b89fd345fff5361813fb8feb7b6f0b80d26c4aba391819dd"
dependencies = [
"ahash",
"auto_enums",
@@ -4167,9 +3912,9 @@ dependencies = [
[[package]]
name = "tree-sitter"
version = "0.25.2"
version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5168a515fe492af54c5cc8800ff8c840be09fa5168de45838afaecd3e008bce4"
checksum = "1a802c93485fb6781d27e27cb5927f6b00ff8d26b56c70af87267be7e99def97"
dependencies = [
"cc",
"regex",
@@ -4251,9 +3996,9 @@ dependencies = [
[[package]]
name = "tree-sitter-language"
version = "0.1.5"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4013970217383f67b18aef68f6fb2e8d409bc5755227092d32efb0422ba24b8"
checksum = "38eee4db33814de3d004de9d8d825627ed3320d0989cce0dea30efaf5be4736c"
[[package]]
name = "tree-sitter-python"
@@ -4306,9 +4051,9 @@ dependencies = [
[[package]]
name = "typenum"
version = "1.18.0"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "unicase"
@@ -4324,9 +4069,9 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
[[package]]
name = "unicode-ident"
version = "1.0.17"
version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe"
checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"
[[package]]
name = "unicode-normalization"
@@ -4506,9 +4251,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
version = "1.15.1"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0f540e3240398cce6128b64ba83fdbdd86129c16a3aa1a3a252efd66eb3d587"
checksum = "ced87ca4be083373936a67f8de945faa23b6b42384bd5b64434850802c6dccd0"
dependencies = [
"getrandom 0.3.1",
]
@@ -4801,12 +4546,6 @@ dependencies = [
"syn 2.0.98",
]
[[package]]
name = "windows-link"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3"
[[package]]
name = "windows-result"
version = "0.1.2"
@@ -4985,9 +4724,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
version = "0.7.3"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1"
checksum = "59690dea168f2198d1a3b0cac23b8063efcd11012f10ae4698f284808c8ef603"
dependencies = [
"memchr",
]
@@ -5100,11 +4839,11 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.8.21"
version = "0.8.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcf01143b2dd5d134f11f545cf9f1431b13b749695cb33bcce051e7568f99478"
checksum = "7b8c07a70861ce02bad1607b5753ecb2501f67847b9f9ada7c160fff0ec6300c"
dependencies = [
"zerocopy-derive 0.8.21",
"zerocopy-derive 0.8.16",
]
[[package]]
@@ -5120,9 +4859,9 @@ dependencies = [
[[package]]
name = "zerocopy-derive"
version = "0.8.21"
version = "0.8.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712c8386f4f4299382c9abee219bee7084f78fb939d88b6840fcc1320d5f6da2"
checksum = "5226bc9a9a9836e7428936cde76bb6b22feea1a8bfdbc0d241136e4d13417e25"
dependencies = [
"proc-macro2",
"quote",
@@ -5131,18 +4870,18 @@ dependencies = [
[[package]]
name = "zerofrom"
version = "0.1.6"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e"
dependencies = [
"zerofrom-derive",
]
[[package]]
name = "zerofrom-derive"
version = "0.1.6"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808"
dependencies = [
"proc-macro2",
"quote",
+38 -64
View File
@@ -8,72 +8,46 @@ members = [
"./packages/frontend/native/schema",
"./packages/frontend/native/sqlite_v1",
]
resolver = "3"
[workspace.package]
edition = "2024"
resolver = "2"
[workspace.dependencies]
affine_common = { path = "./packages/common/native" }
affine_nbstore = { path = "./packages/frontend/native/nbstore" }
anyhow = "1"
base64-simd = "0.8"
block2 = "0.6"
chrono = "0.4"
core-foundation = "0.10"
coreaudio-rs = "0.12"
criterion2 = { version = "3", default-features = false }
dispatch2 = "0.2"
docx-parser = { git = "https://github.com/toeverything/docx-parser" }
dotenvy = "0.15"
file-format = { version = "0.26", features = ["reader"] }
homedir = "0.3"
infer = { version = "0.19.0" }
libc = "0.2"
mimalloc = "0.1"
mp3lame-encoder = "0.2"
napi = { version = "3.0.0-alpha.31", features = ["async", "chrono_date", "error_anyhow", "napi9", "serde"] }
napi-build = { version = "2" }
napi-derive = { version = "3.0.0-alpha.28" }
notify = { version = "8", features = ["serde"] }
objc2 = "0.6"
objc2-foundation = "0.3"
once_cell = "1"
parking_lot = "0.12"
path-ext = "0.1.1"
pdf-extract = "0.8.2"
rand = "0.9"
rayon = "1.10"
readability = { version = "0.3.0", default-features = false }
rubato = "0.16"
screencapturekit = "0.3"
serde = "1"
serde_json = "1"
sha3 = "0.10"
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.24"
thiserror = "2"
tiktoken-rs = "0.6"
tokio = "1.37"
tree-sitter = { version = "0.25" }
tree-sitter-c = { version = "0.23" }
tree-sitter-c-sharp = { version = "0.23" }
tree-sitter-cpp = { version = "0.23" }
tree-sitter-go = { version = "0.23" }
tree-sitter-java = { version = "0.23" }
tree-sitter-javascript = { version = "0.23" }
tree-sitter-kotlin-ng = { version = "1.1" }
tree-sitter-python = { version = "0.23" }
tree-sitter-rust = { version = "0.23" }
tree-sitter-scala = { version = "0.23" }
tree-sitter-typescript = { version = "0.23" }
uniffi = "0.29"
url = { version = "2.5" }
uuid = "1.8"
v_htmlescape = "0.15"
y-octo = { git = "https://github.com/y-crdt/y-octo.git", branch = "main" }
affine_common = { path = "./packages/common/native" }
affine_nbstore = { path = "./packages/frontend/native/nbstore" }
anyhow = "1"
base64-simd = "0.8"
block2 = "0.6"
chrono = "0.4"
core-foundation = "0.10"
coreaudio-rs = "0.12"
criterion2 = { version = "2", default-features = false }
dispatch2 = "0.2"
dotenvy = "0.15"
file-format = { version = "0.26", features = ["reader"] }
homedir = "0.3"
mimalloc = "0.1"
napi = { version = "3.0.0-alpha.12", features = ["async", "chrono_date", "error_anyhow", "napi9", "serde"] }
napi-build = { version = "2" }
napi-derive = { version = "3.0.0-alpha.12" }
notify = { version = "8", features = ["serde"] }
objc2 = "0.6"
objc2-foundation = "0.3"
once_cell = "1"
parking_lot = "0.12"
rand = "0.9"
rayon = "1.10"
rubato = "0.16"
screencapturekit = "0.3"
serde = "1"
serde_json = "1"
sha3 = "0.10"
sqlx = { version = "0.8", default-features = false, features = ["chrono", "macros", "migrate", "runtime-tokio", "sqlite", "tls-rustls"] }
thiserror = "2"
tiktoken-rs = "0.6"
tokio = "1.37"
uniffi = "0.29"
uuid = "1.8"
v_htmlescape = "0.15"
y-octo = { git = "https://github.com/y-crdt/y-octo.git", branch = "main" }
[profile.dev.package.sqlx-macros]
opt-level = 3
-1
View File
@@ -160,7 +160,6 @@ We would also like to give thanks to open-source projects that make AFFiNE possi
- [Jotai](https://github.com/pmndrs/jotai) - Primitive and flexible state management for React.
- [async-call-rpc](https://github.com/Jack-Works/async-call-rpc) - A lightweight JSON RPC client & server.
- [Vite](https://github.com/vitejs/vite) - Next generation frontend tooling.
- [lame](https://lame.sourceforge.io/) - High quality MPEG Audio Layer III (MP3) encoder.
- Other upstream [dependencies](https://github.com/toeverything/AFFiNE/network/dependencies).
Thanks a lot to the community for providing such powerful and simple libraries, so that we can focus more on the implementation of the product logic, and we hope that in the future our projects will also provide a more easy-to-use knowledge base for everyone.
@@ -30,11 +30,7 @@ import { AttachmentEmbedProvider } from './embed.js';
import { styles } from './styles.js';
import { checkAttachmentBlob, downloadAttachmentBlob } from './utils.js';
@Peekable({
enableOn: ({ model }: AttachmentBlockComponent) => {
return model.type.endsWith('pdf');
},
})
@Peekable()
export class AttachmentBlockComponent extends CaptionedBlockComponent<AttachmentBlockModel> {
static override styles = styles;
+1 -1
View File
@@ -28,7 +28,7 @@
"@types/mdast": "^4.0.4",
"lit": "^3.2.0",
"minimatch": "^10.0.1",
"shiki": "^3.0.0",
"shiki": "^2.0.0",
"zod": "^3.23.8"
},
"exports": {
@@ -182,17 +182,17 @@ export class CodeBlockComponent extends CaptionedBlockComponent<
// TODO: move to service for better performance
this.bindHotKey({
Backspace: ctx => {
const event = ctx.get('defaultState').event;
const state = ctx.get('keyboardState');
const textSelection = selectionManager.find(TextSelection);
if (!textSelection) {
event.preventDefault();
state.raw.preventDefault();
return;
}
const from = textSelection.from;
if (from.index === 0 && from.length === 0) {
event.preventDefault();
state.raw.preventDefault();
selectionManager.setGroup('note', [
selectionManager.create(BlockSelection, { blockId: this.blockId }),
]);
@@ -215,7 +215,7 @@ export class CodeBlockComponent extends CaptionedBlockComponent<
index: index,
length: 0,
});
event.preventDefault();
state.raw.preventDefault();
return true;
}
@@ -8,6 +8,7 @@ import {
getMoreMenuConfig,
} from '@blocksuite/affine-components/toolbar';
import type { CodeBlockModel } from '@blocksuite/affine-model';
import { PAGE_HEADER_HEIGHT } from '@blocksuite/affine-shared/consts';
import {
BlockSelection,
TextSelection,
@@ -91,6 +92,7 @@ export class AffineCodeToolbarWidget extends WidgetComponent<
shift({
crossAxis: true,
padding: {
top: PAGE_HEADER_HEIGHT + 12,
bottom: 12,
right: 12,
},
@@ -1,4 +1,3 @@
import { affineTextStyles } from '@blocksuite/affine-components/rich-text';
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
import { ShadowlessElement } from '@blocksuite/block-std';
import { type DeltaInsert, ZERO_WIDTH_SPACE } from '@blocksuite/inline';
@@ -17,25 +16,7 @@ export class AffineCodeUnit extends ShadowlessElement {
}
override render() {
if (this.delta.attributes?.link && this.codeBlock) {
return html`<affine-link
.std=${this.codeBlock.std}
.delta=${this.delta}
></affine-link>`;
}
let style = this.delta.attributes
? affineTextStyles(this.delta.attributes)
: {};
if (this.delta.attributes?.code) {
style = {
...style,
'font-size': 'calc(var(--affine-font-base) - 3px)',
padding: '0px 4px 2px',
};
}
const plainContent = html`<span style=${styleMap(style)}
const plainContent = html`<span
><v-text .str=${this.delta.insert}></v-text
></span>`;
@@ -72,13 +53,12 @@ export class AffineCodeUnit extends ShadowlessElement {
endOffset - token.offset
);
return html`<span
return html`<v-text
.str=${content}
style=${styleMap({
color: token.color,
...style,
})}
><v-text .str=${content}></v-text
></span>`;
></v-text>`;
} else {
const firstToken = includedTokens[0];
const lastToken = includedTokens[includedTokens.length - 1];
@@ -99,7 +79,6 @@ export class AffineCodeUnit extends ShadowlessElement {
.str=${token.content}
style=${styleMap({
color: token.color,
...style,
})}
></v-text>`;
});
@@ -1,6 +1,10 @@
import { css } from 'lit';
export const codeBlockStyles = css`
affine-code {
position: relative;
}
.affine-code-block-container {
font-size: var(--affine-font-xs);
line-height: var(--affine-line-height);
@@ -5,11 +5,7 @@ import {
type InsertToPosition,
} from '@blocksuite/affine-shared/utils';
import type { DataViewDataType } from '@blocksuite/data-view';
import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} from '@blocksuite/store';
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
type Props = {
title: string;
@@ -97,6 +93,3 @@ export const DataViewBlockSchema = defineBlockSchema({
return new DataViewBlockModel();
},
});
export const DataViewBlockSchemaExtension =
BlockSchemaExtension(DataViewBlockSchema);
@@ -1,3 +1,4 @@
import { clamp } from '@blocksuite/affine-shared/utils';
import {
createPropertyConvert,
getTagColor,
@@ -5,7 +6,6 @@ import {
} from '@blocksuite/data-view';
import { presetPropertyConverts } from '@blocksuite/data-view/property-presets';
import { propertyModelPresets } from '@blocksuite/data-view/property-pure-presets';
import { clamp } from '@blocksuite/global/utils';
import { nanoid, Text } from '@blocksuite/store';
import { richTextPropertyModelConfig } from './rich-text/define.js';
+1 -1
View File
@@ -32,7 +32,7 @@
"zod": "^3.23.8"
},
"devDependencies": {
"vitest": "3.0.7"
"vitest": "3.0.6"
},
"exports": {
".": "./src/index.ts",
@@ -9,7 +9,6 @@ import {
EMBED_CARD_WIDTH,
} from '@blocksuite/affine-shared/consts';
import { DocModeProvider } from '@blocksuite/affine-shared/services';
import { findAncestorModel } from '@blocksuite/affine-shared/utils';
import type { BlockService } from '@blocksuite/block-std';
import type { GfxCompatibleProps } from '@blocksuite/block-std/gfx';
import type { BlockModel } from '@blocksuite/store';
@@ -58,15 +57,7 @@ export class EmbedBlockComponent<
) {
this.style.display = 'block';
const insideNote = findAncestorModel(
this.model,
m => m.flavour === 'affine:note'
);
if (
!insideNote &&
this.std.get(DocModeProvider).getEditorMode() === 'edgeless'
) {
if (this.std.get(DocModeProvider).getEditorMode() === 'edgeless') {
this.style.minWidth = `${EMBED_CARD_MIN_WIDTH}px`;
}
}
@@ -304,12 +304,11 @@ export function getDocContentWithMaxLength(doc: Store, maxlength = 500) {
export function getTitleFromSelectedModels(selectedModels: DraftModel[]) {
const firstBlock = selectedModels[0];
const isParagraph = (
model: DraftModel
): model is DraftModel<ParagraphBlockModel> =>
model.flavour === 'affine:paragraph';
if (isParagraph(firstBlock) && firstBlock.type.startsWith('h')) {
return firstBlock.text?.toString();
if (
matchModels(firstBlock, [ParagraphBlockModel]) &&
firstBlock.type.startsWith('h')
) {
return firstBlock.text.toString();
}
return undefined;
}
@@ -395,7 +394,7 @@ export async function convertSelectedBlocksToLinkedDoc(
'before'
);
// delete selected elements
models.forEach(model => doc.deleteBlock(model.id));
models.forEach(model => doc.deleteBlock(model));
return linkedDoc;
}
@@ -10,9 +10,13 @@ import { classMap } from 'lit/directives/class-map.js';
import { styleMap } from 'lit/directives/style-map.js';
import { EmbedBlockComponent } from '../common/embed-block-element.js';
import type { EmbedFigmaBlockService } from './embed-figma-service.js';
import { FigmaIcon, styles } from './styles.js';
export class EmbedFigmaBlockComponent extends EmbedBlockComponent<EmbedFigmaModel> {
export class EmbedFigmaBlockComponent extends EmbedBlockComponent<
EmbedFigmaModel,
EmbedFigmaBlockService
> {
static override styles = styles;
override _cardStyle: (typeof EmbedFigmaStyles)[number] = 'figma';
@@ -2,13 +2,22 @@ import {
EmbedFigmaBlockSchema,
EmbedFigmaStyles,
} from '@blocksuite/affine-model';
import { EmbedOptionConfig } from '@blocksuite/affine-shared/services';
import { EmbedOptionProvider } from '@blocksuite/affine-shared/services';
import { BlockService } from '@blocksuite/block-std';
import { figmaUrlRegex } from './embed-figma-model.js';
export const EmbedFigmaBlockOptionConfig = EmbedOptionConfig({
flavour: EmbedFigmaBlockSchema.model.flavour,
urlRegex: figmaUrlRegex,
styles: EmbedFigmaStyles,
viewType: 'embed',
});
export class EmbedFigmaBlockService extends BlockService {
static override readonly flavour = EmbedFigmaBlockSchema.model.flavour;
override mounted() {
super.mounted();
this.std.get(EmbedOptionProvider).registerEmbedBlockOptions({
flavour: this.flavour,
urlRegex: figmaUrlRegex,
styles: EmbedFigmaStyles,
viewType: 'embed',
});
}
}
@@ -3,15 +3,15 @@ import type { ExtensionType } from '@blocksuite/store';
import { literal } from 'lit/static-html.js';
import { EmbedFigmaBlockAdapterExtensions } from './adapters/extension.js';
import { EmbedFigmaBlockOptionConfig } from './embed-figma-service.js';
import { EmbedFigmaBlockService } from './embed-figma-service.js';
export const EmbedFigmaBlockSpec: ExtensionType[] = [
FlavourExtension('affine:embed-figma'),
EmbedFigmaBlockService,
BlockViewExtension('affine:embed-figma', model => {
return model.parent?.flavour === 'affine:surface'
? literal`affine-embed-edgeless-figma-block`
: literal`affine-embed-figma-block`;
}),
EmbedFigmaBlockAdapterExtensions,
EmbedFigmaBlockOptionConfig,
].flat();
@@ -4,7 +4,7 @@ import {
EmbedGithubStyles,
} from '@blocksuite/affine-model';
import {
EmbedOptionConfig,
EmbedOptionProvider,
LinkPreviewerService,
} from '@blocksuite/affine-shared/services';
import { BlockService } from '@blocksuite/block-std';
@@ -26,11 +26,15 @@ export class EmbedGithubBlockService extends BlockService {
signal
);
};
}
export const EmbedGithubBlockOptionConfig = EmbedOptionConfig({
flavour: EmbedGithubBlockSchema.model.flavour,
urlRegex: githubUrlRegex,
styles: EmbedGithubStyles,
viewType: 'card',
});
override mounted() {
super.mounted();
this.std.get(EmbedOptionProvider).registerEmbedBlockOptions({
flavour: this.flavour,
urlRegex: githubUrlRegex,
styles: EmbedGithubStyles,
viewType: 'card',
});
}
}
@@ -3,10 +3,7 @@ import type { ExtensionType } from '@blocksuite/store';
import { literal } from 'lit/static-html.js';
import { EmbedGithubBlockAdapterExtensions } from './adapters/extension.js';
import {
EmbedGithubBlockOptionConfig,
EmbedGithubBlockService,
} from './embed-github-service.js';
import { EmbedGithubBlockService } from './embed-github-service.js';
export const EmbedGithubBlockSpec: ExtensionType[] = [
FlavourExtension('affine:embed-github'),
@@ -17,5 +14,4 @@ export const EmbedGithubBlockSpec: ExtensionType[] = [
: literal`affine-embed-github-block`;
}),
EmbedGithubBlockAdapterExtensions,
EmbedGithubBlockOptionConfig,
].flat();
@@ -2,7 +2,6 @@ import { css, html } from 'lit';
export const styles = css`
.affine-embed-github-block {
container: affine-embed-github-block / inline-size;
box-sizing: border-box;
display: flex;
width: 100%;
@@ -25,7 +24,6 @@ export const styles = css`
padding: 12px;
border-radius: var(--1, 0px);
opacity: var(--add, 1);
overflow: hidden;
}
.affine-embed-github-content-title {
@@ -378,15 +376,6 @@ export const styles = css`
display: none;
}
}
@container affine-embed-github-block (width < 375px) {
.affine-embed-github-content {
width: 100%;
}
.affine-embed-github-banner {
display: none;
}
}
`;
export const GithubIcon = html`<svg
@@ -6,6 +6,7 @@ import {
EMBED_CARD_HEIGHT,
EMBED_CARD_WIDTH,
} from '@blocksuite/affine-shared/consts';
import { FeatureFlagService } from '@blocksuite/affine-shared/services';
import {
cloneReferenceInfoWithoutAliases,
isNewTabTrigger,
@@ -22,6 +23,14 @@ export class EmbedEdgelessLinkedDocBlockComponent extends toEdgelessEmbedBlock(
override convertToEmbed = () => {
const { id, doc, caption, xywh } = this.model;
// synced doc entry controlled by flag
const isSyncedDocEnabled = doc
.get(FeatureFlagService)
.getFlag('enable_synced_doc_block');
if (!isSyncedDocEnabled) {
return;
}
const style = 'syncedDoc';
const bound = Bound.deserialize(xywh);
bound.w = EMBED_CARD_WIDTH[style];
@@ -14,6 +14,7 @@ import {
import {
DocDisplayMetaProvider,
DocModeProvider,
FeatureFlagService,
OpenDocExtensionIdentifier,
type OpenDocMode,
ThemeProvider,
@@ -130,6 +131,14 @@ export class EmbedLinkedDocBlockComponent extends EmbedBlockComponent<EmbedLinke
const { doc, caption } = this.model;
// synced doc entry controlled by flag
const isSyncedDocEnabled = doc
.get(FeatureFlagService)
.getFlag('enable_synced_doc_block');
if (!isSyncedDocEnabled) {
return;
}
const parent = doc.getParent(this.model);
if (!parent) {
return;
@@ -3,7 +3,7 @@ import {
type EmbedLoomModel,
EmbedLoomStyles,
} from '@blocksuite/affine-model';
import { EmbedOptionConfig } from '@blocksuite/affine-shared/services';
import { EmbedOptionProvider } from '@blocksuite/affine-shared/services';
import { BlockService } from '@blocksuite/block-std';
import { loomUrlRegex } from './embed-loom-model.js';
@@ -15,11 +15,15 @@ export class EmbedLoomBlockService extends BlockService {
queryUrlData = (embedLoomModel: EmbedLoomModel, signal?: AbortSignal) => {
return queryEmbedLoomData(embedLoomModel, signal);
};
}
export const EmbedLoomBlockOptionConfig = EmbedOptionConfig({
flavour: EmbedLoomBlockSchema.model.flavour,
urlRegex: loomUrlRegex,
styles: EmbedLoomStyles,
viewType: 'embed',
});
override mounted() {
super.mounted();
this.std.get(EmbedOptionProvider).registerEmbedBlockOptions({
flavour: this.flavour,
urlRegex: loomUrlRegex,
styles: EmbedLoomStyles,
viewType: 'embed',
});
}
}
@@ -3,10 +3,7 @@ import type { ExtensionType } from '@blocksuite/store';
import { literal } from 'lit/static-html.js';
import { EmbedLoomBlockAdapterExtensions } from './adapters/extension.js';
import {
EmbedLoomBlockOptionConfig,
EmbedLoomBlockService,
} from './embed-loom-service.js';
import { EmbedLoomBlockService } from './embed-loom-service.js';
export const EmbedLoomBlockSpec: ExtensionType[] = [
FlavourExtension('affine:embed-loom'),
@@ -17,5 +14,4 @@ export const EmbedLoomBlockSpec: ExtensionType[] = [
: literal`affine-embed-loom-block`;
}),
EmbedLoomBlockAdapterExtensions,
EmbedLoomBlockOptionConfig,
].flat();
@@ -26,9 +26,9 @@ import {
} from '@blocksuite/affine-shared/utils';
import {
BlockSelection,
BlockServiceWatcher,
BlockStdScope,
type EditorHost,
LifeCycleWatcher,
} from '@blocksuite/block-std';
import {
GfxControllerIdentifier,
@@ -124,31 +124,27 @@ export class EmbedSyncedDocBlockComponent extends EmbedBlockComponent<EmbedSynce
this.std.getOptional(EditorSettingProvider) ??
signal(GeneralSettingSchema.parse({}));
class EmbedSyncedDocWatcher extends LifeCycleWatcher {
static override key = 'embed-synced-doc-watcher';
class EmbedSyncedDocWatcher extends BlockServiceWatcher {
static override readonly flavour = 'affine:embed-synced-doc';
override mounted(): void {
const { view } = this.std;
view.viewUpdated.on(payload => {
if (
payload.type !== 'block' ||
payload.view.model.flavour !== 'affine:embed-synced-doc'
) {
return;
}
const nextComponent = payload.view as EmbedSyncedDocBlockComponent;
if (payload.method === 'add') {
override mounted() {
const disposableGroup = this.blockService.disposables;
const slots = this.blockService.specSlots;
disposableGroup.add(
slots.viewConnected.on(({ component }) => {
const nextComponent = component as EmbedSyncedDocBlockComponent;
nextComponent.depth = nextDepth;
currentDisposables.add(() => {
nextComponent.depth = 0;
});
return;
}
if (payload.method === 'delete') {
})
);
disposableGroup.add(
slots.viewDisconnected.on(({ component }) => {
const nextComponent = component as EmbedSyncedDocBlockComponent;
nextComponent.depth = 0;
return;
}
});
})
);
}
}
@@ -235,7 +231,6 @@ export class EmbedSyncedDocBlockComponent extends EmbedBlockComponent<EmbedSynce
[theme]: true,
surface: false,
selected: this.selected$.value,
'show-hover-border': true,
})}
@click=${this._handleClick}
style=${containerStyleMap}
@@ -57,13 +57,10 @@ export const blockStyles = css`
}
.affine-embed-synced-doc-container {
border: 1px solid transparent;
border: 1px solid var(--affine-border-color);
border-radius: 8px;
overflow: hidden;
}
.affine-embed-synced-doc-container.show-hover-border:hover {
border-color: var(--affine-border-color);
}
.affine-embed-synced-doc-container.page {
display: block;
width: 100%;
@@ -154,12 +151,7 @@ export const blockStyles = css`
}
.affine-embed-synced-doc-container.surface {
border-color: var(--affine-border-color);
background: var(--affine-background-primary-color);
affine-preview-root {
padding: 0 24px;
}
}
.affine-embed-synced-doc-container
@@ -4,7 +4,7 @@ import {
EmbedYoutubeStyles,
} from '@blocksuite/affine-model';
import {
EmbedOptionConfig,
EmbedOptionProvider,
LinkPreviewerService,
} from '@blocksuite/affine-shared/services';
import { BlockService } from '@blocksuite/block-std';
@@ -25,11 +25,15 @@ export class EmbedYoutubeBlockService extends BlockService {
signal
);
};
}
export const EmbedYoutubeBlockOptionConfig = EmbedOptionConfig({
flavour: EmbedYoutubeBlockSchema.model.flavour,
urlRegex: youtubeUrlRegex,
styles: EmbedYoutubeStyles,
viewType: 'embed',
});
override mounted() {
super.mounted();
this.std.get(EmbedOptionProvider).registerEmbedBlockOptions({
flavour: this.flavour,
urlRegex: youtubeUrlRegex,
styles: EmbedYoutubeStyles,
viewType: 'embed',
});
}
}
@@ -3,10 +3,7 @@ import type { ExtensionType } from '@blocksuite/store';
import { literal } from 'lit/static-html.js';
import { EmbedYoutubeBlockAdapterExtensions } from './adapters/extension.js';
import {
EmbedYoutubeBlockOptionConfig,
EmbedYoutubeBlockService,
} from './embed-youtube-service.js';
import { EmbedYoutubeBlockService } from './embed-youtube-service.js';
export const EmbedYoutubeBlockSpec: ExtensionType[] = [
FlavourExtension('affine:embed-youtube'),
@@ -17,5 +14,4 @@ export const EmbedYoutubeBlockSpec: ExtensionType[] = [
: literal`affine-embed-youtube-block`;
}),
EmbedYoutubeBlockAdapterExtensions,
EmbedYoutubeBlockOptionConfig,
].flat();
+1 -1
View File
@@ -30,7 +30,7 @@
"zod": "^3.23.8"
},
"devDependencies": {
"vitest": "3.0.7"
"vitest": "3.0.6"
},
"exports": {
".": "./src/index.ts",
@@ -105,7 +105,7 @@ export const ListKeymapExtension = KeymapExtension(
const isStart = isCollapsed && text.from.index === 0;
if (!isStart) return false;
ctx.get('defaultState').event.preventDefault();
ctx.get('keyboardState').raw.preventDefault();
std.command
.chain()
.pipe(listToParagraphCommand, {
@@ -4,23 +4,14 @@ import {
TextSelection,
} from '@blocksuite/block-std';
/**
* Focus the end of the block
* @param focusBlock - The block to focus
* @param force - If set to true, the selection will be cleared.
* It is useful when the selection is same.
*/
export const focusBlockEnd: Command<{
focusBlock?: BlockComponent;
force?: boolean;
}> = (ctx, next) => {
const { focusBlock, force, std } = ctx;
const { focusBlock, std } = ctx;
if (!focusBlock || !focusBlock.model.text) return;
const { selection } = std;
if (force) selection.clear();
selection.setGroup('note', [
selection.create(TextSelection, {
from: {
@@ -2,11 +2,10 @@ import { EdgelessLegacySlotIdentifier } from '@blocksuite/affine-block-surface';
import type { DocTitle } from '@blocksuite/affine-components/doc-title';
import { NoteDisplayMode } from '@blocksuite/affine-model';
import { EDGELESS_BLOCK_CHILD_PADDING } from '@blocksuite/affine-shared/consts';
import { TelemetryProvider } from '@blocksuite/affine-shared/services';
import { stopPropagation } from '@blocksuite/affine-shared/utils';
import { toGfxBlockComponent } from '@blocksuite/block-std';
import { Bound } from '@blocksuite/global/utils';
import { html, nothing, type PropertyValues } from 'lit';
import { html, nothing } from 'lit';
import { query, state } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { ifDefined } from 'lit/directives/if-defined.js';
@@ -174,15 +173,6 @@ export class EdgelessNoteBlockComponent extends toGfxBlockComponent(
}
}
override updated(changedProperties: PropertyValues) {
if (changedProperties.has('_editing') && this._editing) {
this.std.getOptional(TelemetryProvider)?.track('EdgelessNoteEditing', {
page: 'edgeless',
segment: this.model.isPageBlock() ? 'page' : 'note',
});
}
}
override getRenderingRect() {
const { xywh, edgeless } = this.model;
const { collapse, scale = 1 } = edgeless;
@@ -9,7 +9,6 @@ import {
getSelectedModelsCommand,
} from '@blocksuite/affine-shared/commands';
import type { BlockStdScope } from '@blocksuite/block-std';
import { toDraftModel } from '@blocksuite/store';
export interface QuickActionConfig {
id: string;
@@ -46,9 +45,7 @@ export const quickActionConfig: QuickActionConfig[] = [
std.selection.clear();
const doc = std.store;
const autofill = getTitleFromSelectedModels(
selectedModels.map(toDraftModel)
);
const autofill = getTitleFromSelectedModels(selectedModels);
promptDocTitle(std, autofill)
.then(title => {
if (title === null) return;
@@ -42,7 +42,7 @@ export const ParagraphKeymapExtension = KeymapExtension(
const model = store.getBlock(text.from.blockId)?.model;
if (!model || !matchModels(model, [ParagraphBlockModel])) return;
const event = ctx.get('defaultState').event;
const event = ctx.get('keyboardState').raw;
event.preventDefault();
// When deleting at line start of a paragraph block,
@@ -64,6 +64,7 @@ import {
BlockSnapshotSchema,
fromJSON,
type SliceSnapshot,
Transformer,
} from '@blocksuite/store';
import DOMPurify from 'dompurify';
import * as Y from 'yjs';
@@ -372,7 +373,15 @@ export class EdgelessClipboardController extends PageClipboard {
if (mayBeSurfaceDataJson !== undefined) {
const elementsRawData = JSON.parse(mayBeSurfaceDataJson);
const { snapshot, blobs } = elementsRawData;
const job = this.std.store.getTransformer();
const job = new Transformer({
schema: this.std.workspace.schema,
blobCRUD: this.std.workspace.blobSync,
docCRUD: {
create: (id: string) => this.std.workspace.createDoc({ id }),
get: (id: string) => this.std.workspace.getDoc(id),
delete: (id: string) => this.std.workspace.removeDoc(id),
},
});
const map = job.assetsManager.getAssets();
decodeClipboardBlobs(blobs, map);
for (const blobId of map.keys()) {
@@ -1368,7 +1377,15 @@ export async function prepareClipboardData(
selectedAll: GfxModel[],
std: BlockStdScope
) {
const job = std.store.getTransformer();
const job = new Transformer({
schema: std.workspace.schema,
blobCRUD: std.workspace.blobSync,
docCRUD: {
create: (id: string) => std.workspace.createDoc({ id }),
get: (id: string) => std.workspace.getDoc(id),
delete: (id: string) => std.workspace.removeDoc(id),
},
});
const selected = await Promise.all(
selectedAll.map(async selected => {
const data = serializeElement(selected, selectedAll, job);
@@ -1,9 +1,9 @@
import type { FrameBlockModel } from '@blocksuite/affine-model';
import { SpecProvider } from '@blocksuite/affine-shared/utils';
import {
BlockServiceWatcher,
BlockStdScope,
type EditorHost,
LifeCycleWatcher,
ShadowlessElement,
} from '@blocksuite/block-std';
import { GfxControllerIdentifier } from '@blocksuite/block-std/gfx';
@@ -117,26 +117,22 @@ export class FramePreview extends WithDisposable(ShadowlessElement) {
private _initSpec() {
const refreshViewport = this._refreshViewport.bind(this);
class FramePreviewWatcher extends LifeCycleWatcher {
static override key = 'frame-preview-watcher';
class FramePreviewWatcher extends BlockServiceWatcher {
static override readonly flavour = 'affine:page';
override mounted() {
const { view } = this.std;
view.viewUpdated.on(payload => {
if (
payload.type !== 'block' ||
payload.method !== 'add' ||
payload.view.model.flavour !== 'affine:page'
) {
return;
}
const edgelessBlock =
payload.view as EdgelessRootPreviewBlockComponent;
edgelessBlock.editorViewportSelector = 'frame-preview-viewport';
edgelessBlock.service.viewport.sizeUpdated.once(() => {
refreshViewport();
});
});
const blockService = this.blockService;
blockService.disposables.add(
blockService.specSlots.viewConnected.on(({ component }) => {
const edgelessBlock =
component as EdgelessRootPreviewBlockComponent;
edgelessBlock.editorViewportSelector = 'frame-preview-viewport';
edgelessBlock.service.viewport.sizeUpdated.once(() => {
refreshViewport();
});
})
);
}
}
this._previewSpec.extend([FramePreviewWatcher]);
@@ -1,9 +1,5 @@
import type { Color, ColorScheme, Palette } from '@blocksuite/affine-model';
import {
DefaultTheme,
isTransparent,
resolveColor,
} from '@blocksuite/affine-model';
import { isTransparent, resolveColor } from '@blocksuite/affine-model';
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
import { ColorEvent } from '@blocksuite/affine-shared/utils';
import { css, html, LitElement, nothing, svg, type TemplateResult } from 'lit';
@@ -257,7 +253,7 @@ export class EdgelessColorPanel extends LitElement {
accessor openColorPicker!: (e: MouseEvent) => void;
@property({ type: Array })
accessor palettes: readonly Palette[] = DefaultTheme.Palettes;
accessor palettes: readonly Palette[] = [];
@property({ attribute: false })
accessor theme!: ColorScheme;
@@ -1,10 +1,9 @@
import { LINE_WIDTHS, LineWidth } from '@blocksuite/affine-model';
import { on, once } from '@blocksuite/affine-shared/utils';
import { clamp, on, once } from '@blocksuite/affine-shared/utils';
import { WithDisposable } from '@blocksuite/global/utils';
import { css, html, LitElement, nothing, type PropertyValues } from 'lit';
import { property, query } from 'lit/decorators.js';
import { repeat } from 'lit/directives/repeat.js';
import clamp from 'lodash-es/clamp';
interface Config {
width: number;
@@ -1,8 +1,7 @@
import { stopPropagation } from '@blocksuite/affine-shared/utils';
import { clamp, stopPropagation } from '@blocksuite/affine-shared/utils';
import { css, html, LitElement } from 'lit';
import { property } from 'lit/decorators.js';
import { repeat } from 'lit/directives/repeat.js';
import clamp from 'lodash-es/clamp';
const MIN_SCALE = 0;
const MAX_SCALE = 400;
@@ -1,9 +1,8 @@
import { stopPropagation } from '@blocksuite/affine-shared/utils';
import { clamp, stopPropagation } from '@blocksuite/affine-shared/utils';
import { DoneIcon } from '@blocksuite/icons/lit';
import { css, html, LitElement, nothing } from 'lit';
import { property } from 'lit/decorators.js';
import { repeat } from 'lit/directives/repeat.js';
import clamp from 'lodash-es/clamp';
const MIN_SIZE = 1;
const MAX_SIZE = 200;
@@ -1,4 +1,8 @@
import { type ColorScheme, type StrokeStyle } from '@blocksuite/affine-model';
import {
type ColorScheme,
DefaultTheme,
type StrokeStyle,
} from '@blocksuite/affine-model';
import type { ColorEvent } from '@blocksuite/affine-shared/utils';
import { WithDisposable } from '@blocksuite/global/utils';
import { css, html, LitElement } from 'lit';
@@ -40,6 +44,7 @@ export class StrokeStylePanel extends WithDisposable(LitElement) {
aria-label="Border colors"
.value=${this.strokeColor}
.theme=${this.theme}
.palettes=${DefaultTheme.Palettes}
.hollowCircle=${this.hollowCircle}
@select=${(e: ColorEvent) => this.setStrokeColor(e)}
>
@@ -35,6 +35,7 @@ import {
import { EMBED_CARD_HEIGHT } from '@blocksuite/affine-shared/consts';
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
import {
clamp,
getElementsWithoutGroup,
getSelectedRect,
requestThrottledConnectedFrame,
@@ -67,7 +68,6 @@ import { css, html, nothing } from 'lit';
import { state } from 'lit/decorators.js';
import { ifDefined } from 'lit/directives/if-defined.js';
import { styleMap } from 'lit/directives/style-map.js';
import clamp from 'lodash-es/clamp';
import type { EdgelessRootBlockComponent } from '../../edgeless-root-block.js';
import {
@@ -65,7 +65,7 @@ export class EdgelessBrushMenu extends EdgelessToolbarToolMixin(
class="one-way"
.value=${this._props$.value.color}
.theme=${this._theme$.value}
.palettes=${DefaultTheme.StrokeColorShortPalettes}
.palettes=${DefaultTheme.StrokeColorPalettes}
.hasTransparent=${!this.edgeless.doc
.get(FeatureFlagService)
.getFlag('enable_color_picker')}
@@ -133,7 +133,7 @@ export class EdgelessConnectorMenu extends EdgelessToolbarToolMixin(
class="one-way"
.value=${stroke}
.theme=${this._theme$.value}
.palettes=${DefaultTheme.StrokeColorShortPalettes}
.palettes=${DefaultTheme.StrokeColorPalettes}
.hasTransparent=${!this.edgeless.doc
.get(FeatureFlagService)
.getFlag('enable_color_picker')}
@@ -75,10 +75,9 @@ export class EdgelessShapeMenu extends SignalWatcher(
const filled = !isTransparent(value);
const fillColor = value;
const strokeColor = filled
? DefaultTheme.StrokeColorShortPalettes.find(
palette => palette.key === key
)?.value
: DefaultTheme.StrokeColorShortMap.Grey;
? DefaultTheme.StrokeColorPalettes.find(palette => palette.key === key)
?.value
: DefaultTheme.StrokeColorMap.Grey;
const { shapeName } = this._props$.value;
this.edgeless.std
@@ -174,7 +173,7 @@ export class EdgelessShapeMenu extends SignalWatcher(
class="one-way"
.value=${fillColor}
.theme=${this._theme$.value}
.palettes=${DefaultTheme.FillColorShortPalettes}
.palettes=${DefaultTheme.FillColorPalettes}
.hasTransparent=${!this.edgeless.doc
.get(FeatureFlagService)
.getFlag('enable_color_picker')}
@@ -33,7 +33,7 @@ export class EdgelessTextMenu extends EdgelessToolbarToolMixin(LitElement) {
class="one-way"
.value=${this.color}
.theme=${this._theme$.value}
.palettes=${DefaultTheme.StrokeColorShortPalettes}
.palettes=${DefaultTheme.StrokeColorPalettes}
@select=${(e: ColorEvent) => this.onChange({ color: e.detail })}
></edgeless-color-panel>
</div>
@@ -21,7 +21,7 @@ import { ShapeTool } from './gfx-tool/shape-tool.js';
import { TemplateTool } from './gfx-tool/template-tool.js';
import { TextTool } from './gfx-tool/text-tool.js';
import { EditPropsMiddlewareBuilder } from './middlewares/base.js';
import { SnapManager } from './utils/snap-manager.js';
import { EdgelessSnapManager } from './utils/snap-manager.js';
export const EdgelessToolExtension: ExtensionType[] = [
DefaultTool,
@@ -43,7 +43,7 @@ export const EdgelessBuiltInManager: ExtensionType[] = [
ConnectionOverlay,
FrameOverlay,
MindMapIndicatorOverlay,
SnapManager,
EdgelessSnapManager,
EdgelessFrameManager,
EditPropsMiddlewareBuilder,
];
@@ -447,7 +447,7 @@ export class EdgelessRootBlockComponent extends BlockComponent<
);
const zoom = normalizeWheelDeltaY(e.deltaY, viewport.zoom);
viewport.setZoom(zoom, new Point(baseX, baseY), true);
viewport.setZoom(zoom, new Point(baseX, baseY));
e.stopPropagation();
}
// pan
@@ -14,6 +14,7 @@ import {
MindmapElementModel,
RootBlockSchema,
} from '@blocksuite/affine-model';
import { clamp } from '@blocksuite/affine-shared/utils';
import type { BlockStdScope } from '@blocksuite/block-std';
import type {
GfxController,
@@ -34,7 +35,6 @@ import {
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import { Bound, getCommonBound } from '@blocksuite/global/utils';
import { effect } from '@preact/signals-core';
import clamp from 'lodash-es/clamp';
import { RootService } from '../root-service.js';
import { TemplateJob } from './services/template.js';
@@ -2,14 +2,11 @@ import { autoConnectWidget } from '@blocksuite/affine-widget-edgeless-auto-conne
import { frameTitleWidget } from '@blocksuite/affine-widget-frame-title';
import { edgelessRemoteSelectionWidget } from '@blocksuite/affine-widget-remote-selection';
import {
BlockServiceWatcher,
BlockViewExtension,
LifeCycleWatcher,
WidgetViewExtension,
} from '@blocksuite/block-std';
import {
GfxControllerIdentifier,
ToolController,
} from '@blocksuite/block-std/gfx';
import { ToolController } from '@blocksuite/block-std/gfx';
import type { ExtensionType } from '@blocksuite/store';
import { literal, unsafeStatic } from 'lit/static-html.js';
@@ -59,12 +56,17 @@ export const edgelessToolbarWidget = WidgetViewExtension(
literal`${unsafeStatic(EDGELESS_TOOLBAR_WIDGET)}`
);
class EdgelessLocker extends LifeCycleWatcher {
static override key = 'edgeless-locker';
class EdgelessLocker extends BlockServiceWatcher {
static override readonly flavour = 'affine:page';
override mounted() {
const { viewport } = this.std.get(GfxControllerIdentifier);
viewport.locked = true;
const service = this.blockService;
service.disposables.add(
service.specSlots.viewConnected.on(({ service }) => {
// Does not allow the user to move and zoom.
(service as EdgelessRootService).locked = true;
})
);
}
}
@@ -27,6 +27,7 @@ import {
TelemetryProvider,
} from '@blocksuite/affine-shared/services';
import {
clamp,
handleNativeRangeAtPoint,
resetNativeSelection,
} from '@blocksuite/affine-shared/utils';
@@ -51,14 +52,13 @@ import {
Vec,
} from '@blocksuite/global/utils';
import { effect } from '@preact/signals-core';
import clamp from 'lodash-es/clamp';
import type { EdgelessRootBlockComponent } from '../edgeless-root-block.js';
import { prepareCloneData } from '../utils/clone-utils.js';
import { isSingleMindMapNode } from '../utils/mindmap.js';
import { calPanDelta } from '../utils/panning-utils.js';
import { isCanvasElement, isEdgelessTextBlock } from '../utils/query.js';
import type { SnapManager } from '../utils/snap-manager.js';
import type { EdgelessSnapManager } from '../utils/snap-manager.js';
import {
addText,
mountConnectorLabelEditor,
@@ -277,7 +277,9 @@ export class DefaultTool extends BaseTool {
}
get snapOverlay() {
return this.std.get(OverlayIdentifier('snap-manager')) as SnapManager;
return this.std.get(
OverlayIdentifier('snap-manager')
) as EdgelessSnapManager;
}
private _addEmptyParagraphBlock(
@@ -578,7 +580,7 @@ export class DefaultTool extends BaseTool {
) {
const mindmap = this._toBeMoved[0].group as MindmapElementModel;
this._alignBound = this.snapOverlay.setMovingElements(this._toBeMoved, [
this._alignBound = this.snapOverlay.setupAlignables(this._toBeMoved, [
mindmap,
...(mindmap?.childElements || []),
]);
@@ -638,7 +640,7 @@ export class DefaultTool extends BaseTool {
);
}
this._alignBound = this.snapOverlay.setMovingElements(
this._alignBound = this.snapOverlay.setupAlignables(
this._toBeMoved
);
@@ -880,7 +882,7 @@ export class DefaultTool extends BaseTool {
if (this.edgelessSelectionManager.editing) return;
this._selectedBounds = [];
this.snapOverlay.clear();
this.snapOverlay.cleanupAlignables();
this.frameOverlay.clear();
this._toBeMoved = [];
this._selectedConnector = null;
@@ -16,7 +16,7 @@ import {
type DocSnapshot,
DocSnapshotSchema,
type SnapshotNode,
type Transformer,
Transformer,
} from '@blocksuite/store';
import type * as Y from 'yjs';
/**
@@ -90,7 +90,16 @@ export class TemplateJob {
type: TemplateType;
constructor({ model, type, middlewares }: TemplateJobConfig) {
this.job = model.doc.getTransformer();
this.job = new Transformer({
schema: model.doc.workspace.schema,
blobCRUD: model.doc.workspace.blobSync,
docCRUD: {
create: (id: string) => model.doc.workspace.createDoc({ id }),
get: (id: string) => model.doc.workspace.getDoc(id),
delete: (id: string) => model.doc.workspace.removeDoc(id),
},
middlewares: [],
});
this.model = model;
this.type = TEMPLATE_TYPES.includes(type as TemplateType)
? (type as TemplateType)
@@ -311,7 +320,8 @@ export class TemplateJob {
from: Record<string, Record<string, unknown>>,
to: Y.Map<Y.Map<unknown>>
) {
const schema = this.model.doc.schema.get('affine:surface');
const schema =
this.model.doc.workspace.schema.flavourSchemaMap.get('affine:surface');
const surfaceTransformer = schema?.transformer?.(
new Map()
) as SurfaceBlockTransformer;
@@ -19,7 +19,7 @@ import {
isGfxGroupCompatibleModel,
type SerializedElement,
} from '@blocksuite/block-std/gfx';
import type { BlockSnapshot, Transformer } from '@blocksuite/store';
import { type BlockSnapshot, Transformer } from '@blocksuite/store';
/**
* return all elements in the tree of the elements
@@ -40,7 +40,15 @@ export function getSortedCloneElements(elements: GfxModel[]) {
export function prepareCloneData(elements: GfxModel[], std: BlockStdScope) {
elements = sortEdgelessElements(elements);
const job = std.store.getTransformer();
const job = new Transformer({
schema: std.workspace.schema,
blobCRUD: std.workspace.blobSync,
docCRUD: {
create: (id: string) => std.workspace.createDoc({ id }),
get: (id: string) => std.workspace.getDoc(id),
delete: (id: string) => std.workspace.removeDoc(id),
},
});
const res = elements.map(element => {
const data = serializeElement(element, elements, job);
return data;
@@ -1,49 +1,30 @@
import { Overlay } from '@blocksuite/affine-block-surface';
import { ConnectorElementModel } from '@blocksuite/affine-model';
import type { GfxModel } from '@blocksuite/block-std/gfx';
import { almostEqual, Bound, Point } from '@blocksuite/global/utils';
import type {
SurfaceBlockComponent,
SurfaceBlockModel,
} from '@blocksuite/affine-block-surface';
import { getSurfaceBlock, Overlay } from '@blocksuite/affine-block-surface';
import type { ConnectorElementModel } from '@blocksuite/affine-model';
import type { GfxController, GfxModel } from '@blocksuite/block-std/gfx';
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import { Bound, Point } from '@blocksuite/global/utils';
import { isConnectable } from '../utils/query.js';
interface Distance {
horiz?: {
/**
* the minimum x moving distance to align with other bound
*/
distance: number;
/**
* the indices of the align position
*/
alignPositionIndices: number[];
};
vert?: {
/**
* the minimum y moving distance to align with other bound
*/
distance: number;
/**
* the indices of the align position
*/
alignPositionIndices: number[];
};
absXDistance: number;
absYDistance: number;
xDistance: number;
yDistance: number;
indexX: number;
indexY: number;
}
const ALIGN_THRESHOLD = 8;
const DISTRIBUTION_LINE_OFFSET = 1;
const ALIGN_THRESHOLD = 5;
export class SnapManager extends Overlay {
export class EdgelessSnapManager extends Overlay {
static override overlayName: string = 'snap-manager';
private _referenceBounds: {
vertical: Bound[];
horizontal: Bound[];
all: Bound[];
} = {
vertical: [],
horizontal: [],
all: [],
};
private _alignableBounds: Bound[] = [];
/**
* This variable contains reference lines that are
@@ -63,16 +44,28 @@ export class SnapManager extends Overlay {
*/
private _intraGraphicAlignLines: [Point, Point][] = [];
override clear() {
super.clear();
this._referenceBounds = {
vertical: [],
horizontal: [],
all: [],
};
cleanupAlignables = () => {
this._alignableBounds = [];
this._intraGraphicAlignLines = [];
this._distributedAlignLines = [];
// FIXME: not sure why renderer can be undefined sometimes
this._surface.renderer?.removeOverlay(this);
};
private get _surface() {
const surfaceModel = getSurfaceBlock(this.gfx.doc);
if (!surfaceModel) {
throw new BlockSuiteError(
ErrorCode.ValueNotExists,
'Surface block not found in doc when creating snap manager'
);
}
return this.gfx.std.view.getBlock(surfaceModel.id) as SurfaceBlockComponent;
}
constructor(gfx: GfxController) {
super(gfx);
}
private _alignDistributeHorizontally(
@@ -82,52 +75,31 @@ export class SnapManager extends Overlay {
viewport: { zoom: number }
) {
const wBoxes: Bound[] = [];
this._referenceBounds.horizontal.forEach(box => {
this._alignableBounds.forEach(box => {
if (box.isHorizontalCross(bound)) {
wBoxes.push(box);
}
});
wBoxes.sort((a, b) => a.center[0] - b.center[0]);
let dif = Infinity;
let min = Infinity;
let aveDis = Number.MAX_SAFE_INTEGER;
let curBound!: {
leftIdx: number;
rightIdx: number;
spacing: number;
points: [Point, Point][];
};
for (let i = 0; i < wBoxes.length; i++) {
for (let j = i + 1; j < wBoxes.length; j++) {
let lb = wBoxes[i],
rb = wBoxes[j];
// it means these bound need to be horizontally across
if (!lb.isHorizontalCross(rb) || lb.isIntersectWithBound(rb)) continue;
let switchFlag = false;
// exchange lb and rb to make sure lb is on the left of rb
if (!lb.isHorizontalCross(rb)) continue;
if (lb.isIntersectWithBound(rb)) continue;
if (rb.maxX < lb.minX) {
const temp = rb;
rb = lb;
lb = temp;
switchFlag = true;
}
/** align middle */
let _centerX = 0;
const updateDif = () => {
dif = Math.abs(bound.center[0] - _centerX);
const curAveDis =
(Math.abs(lb.center[0] - bound.center[0]) +
Math.abs(rb.center[0] - bound.center[0])) /
2;
if (
dif <= threshold &&
(dif < min || (almostEqual(dif, min) && curAveDis < aveDis))
) {
if (dif <= threshold && dif < min) {
min = dif;
aveDis = curAveDis;
rst.dx = _centerX - bound.center[0];
/**
* calculate points to draw
@@ -136,102 +108,37 @@ export class SnapManager extends Overlay {
(a, b) => a - b
);
const y = (ys[1] + ys[2]) / 2;
const offset = DISTRIBUTION_LINE_OFFSET / viewport.zoom;
const offset = 2 / viewport.zoom;
const xs = [
_centerX - bound.w / 2,
_centerX + bound.w / 2,
_centerX - bound.w / 2 - offset,
_centerX + bound.w / 2 + offset,
rb.minX,
rb.maxX,
lb.minX,
lb.maxX,
].sort((a, b) => a - b);
curBound = {
leftIdx: switchFlag ? j : i,
rightIdx: switchFlag ? i : j,
spacing: xs[2] - xs[1],
points: [
[new Point(xs[1] + offset, y), new Point(xs[2] - offset, y)],
[new Point(xs[3] + offset, y), new Point(xs[4] - offset, y)],
],
};
this._distributedAlignLines[0] = [
new Point(xs[1], y),
new Point(xs[2], y),
];
this._distributedAlignLines[1] = [
new Point(xs[3], y),
new Point(xs[4], y),
];
}
};
/**
* align between left and right bound
*/
if (lb.horizontalDistance(rb) > bound.w) {
_centerX = (lb.maxX + rb.minX) / 2;
updateDif();
}
/**
* align to the left bounds
*/
/** align left */
_centerX = lb.minX - (rb.minX - lb.maxX) - bound.w / 2;
updateDif();
/** align right */
_centerX = rb.minX - lb.maxX + rb.maxX + bound.w / 2;
updateDif();
}
}
// find the boxes that has same spacing
if (curBound) {
const { leftIdx, rightIdx, spacing, points } = curBound;
this._distributedAlignLines.push(...points);
{
let curLeftBound = wBoxes[leftIdx];
for (let i = leftIdx - 1; i >= 0; i--) {
if (almostEqual(wBoxes[i].maxX, curLeftBound.minX - spacing)) {
const targetBound = wBoxes[i];
const ys = [
targetBound.minY,
targetBound.maxY,
curLeftBound.minY,
curLeftBound.maxY,
].sort((a, b) => a - b);
const y = (ys[1] + ys[2]) / 2;
this._distributedAlignLines.push([
new Point(wBoxes[i].maxX, y),
new Point(curLeftBound.minX, y),
]);
curLeftBound = wBoxes[i];
}
}
}
{
let curRightBound = wBoxes[rightIdx];
for (let i = rightIdx + 1; i < wBoxes.length; i++) {
if (almostEqual(wBoxes[i].minX, curRightBound.maxX + spacing)) {
const targetBound = wBoxes[i];
const ys = [
targetBound.minY,
targetBound.maxY,
curRightBound.minY,
curRightBound.maxY,
].sort((a, b) => a - b);
const y = (ys[1] + ys[2]) / 2;
this._distributedAlignLines.push([
new Point(curRightBound.maxX, y),
new Point(wBoxes[i].minX, y),
]);
curRightBound = wBoxes[i];
}
}
}
}
}
private _alignDistributeVertically(
@@ -241,50 +148,29 @@ export class SnapManager extends Overlay {
viewport: { zoom: number }
) {
const hBoxes: Bound[] = [];
this._referenceBounds.vertical.forEach(box => {
this._alignableBounds.forEach(box => {
if (box.isVerticalCross(bound)) {
hBoxes.push(box);
}
});
hBoxes.sort((a, b) => a.center[0] - b.center[0]);
let dif = Infinity;
let min = Infinity;
let aveDis = Number.MAX_SAFE_INTEGER;
let curBound!: {
upperIdx: number;
lowerIdx: number;
spacing: number;
points: [Point, Point][];
};
for (let i = 0; i < hBoxes.length; i++) {
for (let j = i + 1; j < hBoxes.length; j++) {
let ub = hBoxes[i],
db = hBoxes[j];
if (!ub.isVerticalCross(db) || ub.isIntersectWithBound(db)) continue;
let switchFlag = false;
if (!ub.isVerticalCross(db)) continue;
if (ub.isIntersectWithBound(db)) continue;
if (db.maxY < ub.minX) {
const temp = ub;
ub = db;
db = temp;
switchFlag = true;
}
/** align middle */
let _centerY = 0;
const updateDiff = () => {
dif = Math.abs(bound.center[1] - _centerY);
const curAveDis =
(Math.abs(ub.center[1] - bound.center[1]) +
Math.abs(db.center[1] - bound.center[1])) /
2;
if (
dif <= threshold &&
(dif < min || (almostEqual(dif, min) && curAveDis < aveDis))
) {
if (dif <= threshold && dif < min) {
min = dif;
rst.dy = _centerY - bound.center[1];
/**
@@ -294,33 +180,29 @@ export class SnapManager extends Overlay {
(a, b) => a - b
);
const x = (xs[1] + xs[2]) / 2;
const offset = DISTRIBUTION_LINE_OFFSET / viewport.zoom;
const offset = 2 / viewport.zoom;
const ys = [
_centerY - bound.h / 2,
_centerY + bound.h / 2,
_centerY - bound.h / 2 - offset,
_centerY + bound.h / 2 + offset,
db.minY,
db.maxY,
ub.minY,
ub.maxY,
].sort((a, b) => a - b);
curBound = {
upperIdx: switchFlag ? j : i,
lowerIdx: switchFlag ? i : j,
spacing: ys[2] - ys[1],
points: [
[new Point(x, ys[1] + offset), new Point(x, ys[2] - offset)],
[new Point(x, ys[3] + offset), new Point(x, ys[4] - offset)],
],
};
this._distributedAlignLines[3] = [
new Point(x, ys[1]),
new Point(x, ys[2]),
];
this._distributedAlignLines[4] = [
new Point(x, ys[3]),
new Point(x, ys[4]),
];
}
};
if (ub.verticalDistance(db) > bound.h) {
_centerY = (ub.maxY + db.minY) / 2;
updateDiff();
}
/** align upper */
_centerY = ub.minY - (db.minY - ub.maxY) - bound.h / 2;
updateDiff();
@@ -329,61 +211,6 @@ export class SnapManager extends Overlay {
updateDiff();
}
}
// find the boxes that has same spacing
if (curBound) {
const { upperIdx, lowerIdx, spacing, points } = curBound;
this._distributedAlignLines.push(...points);
{
let curUpperBound = hBoxes[upperIdx];
for (let i = upperIdx - 1; i >= 0; i--) {
if (almostEqual(hBoxes[i].maxY, curUpperBound.minY - spacing)) {
const targetBound = hBoxes[i];
const xs = [
targetBound.minX,
targetBound.maxX,
curUpperBound.minX,
curUpperBound.maxX,
].sort((a, b) => a - b);
const x = (xs[1] + xs[2]) / 2;
this._distributedAlignLines.push([
new Point(x, hBoxes[i].maxY),
new Point(x, curUpperBound.minY),
]);
curUpperBound = hBoxes[i];
}
}
}
{
let curLowerBound = hBoxes[lowerIdx];
for (let i = lowerIdx + 1; i < hBoxes.length; i++) {
if (almostEqual(hBoxes[i].minY, curLowerBound.maxY + spacing)) {
const targetBound = hBoxes[i];
const xs = [
targetBound.minX,
targetBound.maxX,
curLowerBound.minX,
curLowerBound.maxX,
].sort((a, b) => a - b);
const x = (xs[1] + xs[2]) / 2;
this._distributedAlignLines.push([
new Point(x, curLowerBound.maxY),
new Point(x, hBoxes[i].minY),
]);
curLowerBound = hBoxes[i];
}
}
}
}
}
private _calculateClosestDistances(bound: Bound, other: Bound): Distance {
@@ -436,150 +263,94 @@ export class SnapManager extends Overlay {
const closestX = Math.min(...xDistancesAbs);
const closestY = Math.min(...yDistancesAbs);
const threshold = ALIGN_THRESHOLD / this.gfx.viewport.zoom;
const indexX = xDistancesAbs.indexOf(closestX);
const indexY = yDistancesAbs.indexOf(closestY);
// the x and y distances will be useful for locating the align point
return {
horiz:
closestX <= threshold
? {
distance: xDistances[xDistancesAbs.indexOf(closestX)],
get alignPositionIndices() {
const indices: number[] = [];
xDistancesAbs.forEach(
(val, idx) => almostEqual(val, closestX) && indices.push(idx)
);
return indices;
},
}
: undefined,
vert:
closestY <= threshold
? {
distance: yDistances[yDistancesAbs.indexOf(closestY)],
get alignPositionIndices() {
const indices: number[] = [];
yDistancesAbs.forEach(
(val, idx) => almostEqual(val, closestY) && indices.push(idx)
);
return indices;
},
}
: undefined,
absXDistance: closestX,
absYDistance: closestY,
xDistance: xDistances[indexX],
yDistance: yDistances[indexY],
indexX,
indexY,
};
}
/**
* Update horizontal moving distance `rst.dx` to align with other bound.
* Also, update the align points to draw.
* @param rst
* @param bound
* @param other
* @param distance
*/
private _draw() {
this._surface.refresh();
}
// Update X align point
private _updateXAlignPoint(
rst: { dx: number; dy: number },
bound: Bound,
other: Bound,
distance: Distance
) {
if (!distance.horiz) return;
const { distance: dx, alignPositionIndices: distanceIndices } =
distance.horiz;
const alignXPosition = [
const index = distance.indexX;
rst.dx = distance.xDistance;
const alignPointX = [
other.center[0],
other.minX,
other.maxX,
bound.minX + dx,
bound.minX + dx,
bound.maxX + dx,
bound.maxX + dx,
bound.minX + rst.dx,
bound.minX + rst.dx,
bound.maxX + rst.dx,
bound.maxX + rst.dx,
][index];
this._intraGraphicAlignLines[0] = [
new Point(alignPointX, bound.center[1]),
new Point(alignPointX, other.center[1]),
];
rst.dx = dx;
const dy = distance.vert?.distance ?? 0;
const top = Math.min(bound.minY + dy, other.minY);
const down = Math.max(bound.maxY + dy, other.maxY);
this._intraGraphicAlignLines.push(
...distanceIndices.map(
idx =>
[
new Point(alignXPosition[idx], top),
new Point(alignXPosition[idx], down),
] as [Point, Point]
)
);
}
/**
* Update vertical moving distance `rst.dy` to align with other bound.
* Also, update the align points to draw.
* @param rst
* @param bound
* @param other
* @param distance
*/
// Update Y align point
private _updateYAlignPoint(
rst: { dx: number; dy: number },
bound: Bound,
other: Bound,
distance: Distance
) {
if (!distance.vert) return;
const { distance: dy, alignPositionIndices } = distance.vert;
const alignXPosition = [
const index = distance.indexY;
rst.dy = distance.yDistance;
const alignPointY = [
other.center[1],
other.minY,
other.maxY,
bound.minY + dy,
bound.minY + dy,
bound.maxY + dy,
bound.maxY + dy,
bound.minY + rst.dy,
bound.minY + rst.dy,
bound.maxY + rst.dy,
bound.maxY + rst.dy,
][index];
this._intraGraphicAlignLines[1] = [
new Point(bound.center[0], alignPointY),
new Point(other.center[0], alignPointY),
];
rst.dy = dy;
const dx = distance.horiz?.distance ?? 0;
const left = Math.min(bound.minX + dx, other.minX);
const right = Math.max(bound.maxX + dx, other.maxX);
this._intraGraphicAlignLines.push(
...alignPositionIndices.map(
idx =>
[
new Point(left, alignXPosition[idx]),
new Point(right, alignXPosition[idx]),
] as [Point, Point]
)
);
}
align(bound: Bound): { dx: number; dy: number } {
const rst = { dx: 0, dy: 0 };
const threshold = ALIGN_THRESHOLD / this.gfx.viewport.zoom;
const threshold = ALIGN_THRESHOLD;
const { viewport } = this.gfx;
this._intraGraphicAlignLines = [];
this._distributedAlignLines = [];
for (const other of this._referenceBounds.all) {
for (const other of this._alignableBounds) {
const closestDistances = this._calculateClosestDistances(bound, other);
if (closestDistances.horiz) {
if (closestDistances.absXDistance < threshold) {
this._updateXAlignPoint(rst, bound, other, closestDistances);
}
if (closestDistances.vert) {
if (closestDistances.absYDistance < threshold) {
this._updateYAlignPoint(rst, bound, other, closestDistances);
}
}
// point align priority is higher than distribute align
// point align prority is higher than distribute align
if (rst.dx === 0) {
this._alignDistributeHorizontally(rst, bound, threshold, viewport);
}
@@ -587,9 +358,7 @@ export class SnapManager extends Overlay {
if (rst.dy === 0) {
this._alignDistributeVertically(rst, bound, threshold, viewport);
}
this._renderer?.refresh();
this._draw();
return rst;
}
@@ -600,9 +369,9 @@ export class SnapManager extends Overlay {
)
return;
const { viewport } = this.gfx;
const strokeWidth = 2 / viewport.zoom;
ctx.strokeStyle = '#8B5CF6';
const strokeWidth = 1 / viewport.zoom;
const offset = 5 / viewport.zoom;
ctx.strokeStyle = '#1672F3';
ctx.lineWidth = strokeWidth;
ctx.beginPath();
@@ -612,31 +381,30 @@ export class SnapManager extends Overlay {
const x = line[0].x;
const minY = Math.min(line[0].y, line[1].y);
const maxY = Math.max(line[0].y, line[1].y);
d = `M${x},${minY}L${x},${maxY}`;
d = `M${x},${minY - offset}L${x},${maxY}`;
} else {
const y = line[0].y;
const minX = Math.min(line[0].x, line[1].x);
const maxX = Math.max(line[0].x, line[1].x);
d = `M${minX},${y}L${maxX},${y}`;
d = `M${minX - offset},${y}L${maxX + offset},${y}`;
}
ctx.stroke(new Path2D(d));
});
ctx.strokeStyle = '#CC4187';
this._distributedAlignLines.forEach(line => {
const bar = 10 / viewport.zoom;
let d = '';
if (line[0].x === line[1].x) {
const x = line[0].x;
const minY = Math.min(line[0].y, line[1].y);
const maxY = Math.max(line[0].y, line[1].y);
const minY = Math.min(line[0].y, line[1].y) + offset;
const maxY = Math.max(line[0].y, line[1].y) - offset;
d = `M${x},${minY}L${x},${maxY}
M${x - bar},${minY}L${x + bar},${minY}
M${x - bar},${maxY}L${x + bar},${maxY} `;
} else {
const y = line[0].y;
const minX = Math.min(line[0].x, line[1].x);
const maxX = Math.max(line[0].x, line[1].x);
const minX = Math.min(line[0].x, line[1].x) + offset;
const maxX = Math.max(line[0].x, line[1].x) - offset;
d = `M${minX},${y}L${maxX},${y}
M${minX},${y - bar}L${minX},${y + bar}
M${maxX},${y - bar}L${maxX},${y + bar}`;
@@ -645,68 +413,42 @@ export class SnapManager extends Overlay {
});
}
setMovingElements(
movingElements: GfxModel[],
excludes: GfxModel[] = []
): Bound {
if (movingElements.length === 0) return new Bound();
setupAlignables(alignables: GfxModel[], exclude: GfxModel[] = []): Bound {
if (alignables.length === 0) return new Bound();
const skipped = new Set(movingElements);
excludes.forEach(e => skipped.add(e));
const connectors = alignables.filter(isConnectable).reduce((prev, el) => {
const connectors = (this.gfx.surface as SurfaceBlockModel).getConnectors(
el.id
);
const viewportBound = this.gfx.viewport.viewportBounds;
const movingBound = movingElements
.reduce(
(prev, element) => prev.unite(element.elementBound),
movingElements[0].elementBound
)
.expand(ALIGN_THRESHOLD * this.gfx.viewport.zoom);
const horizAreaBound = new Bound(
Math.min(movingBound.x, viewportBound.x),
movingBound.y,
Math.max(movingBound.w, viewportBound.w),
movingBound.h
);
const vertAreaBound = new Bound(
movingBound.x,
Math.min(movingBound.y, viewportBound.y),
movingBound.w,
Math.max(movingBound.h, viewportBound.h)
if (connectors.length > 0) {
prev = prev.concat(connectors);
}
return prev;
}, [] as ConnectorElementModel[]);
const { viewport } = this.gfx;
const viewportBounds = Bound.from(viewport.viewportBounds);
this._surface.renderer.addOverlay(this);
const canvasElements = this.gfx.layer.canvasElements;
const excludes = new Set([...alignables, ...exclude, ...connectors]);
this._alignableBounds = [];
([...this.gfx.layer.blocks, ...canvasElements] as GfxModel[]).forEach(
alignable => {
const bounds = alignable.elementBound;
if (
viewportBounds.isOverlapWithBound(bounds) &&
!excludes.has(alignable)
) {
this._alignableBounds.push(bounds);
}
}
);
const vertCandidates = this.gfx.grid.search(vertAreaBound, {
useSet: true,
});
const horizCandidates = this.gfx.grid.search(horizAreaBound, {
useSet: true,
});
const verticalBounds: Bound[] = [];
const horizBounds: Bound[] = [];
const allBounds: Bound[] = [];
vertCandidates.forEach(candidate => {
if (skipped.has(candidate) || candidate instanceof ConnectorElementModel)
return;
verticalBounds.push(candidate.elementBound);
allBounds.push(candidate.elementBound);
});
horizCandidates.forEach(candidate => {
if (skipped.has(candidate) || candidate instanceof ConnectorElementModel)
return;
horizBounds.push(candidate.elementBound);
allBounds.push(candidate.elementBound);
});
this._referenceBounds = {
horizontal: horizBounds,
vertical: verticalBounds,
all: allBounds,
};
return movingElements.reduce(
(prev, element) => prev.unite(element.elementBound),
Bound.deserialize(movingElements[0].xywh)
);
return alignables.reduce((prev, element) => {
const bounds = element.elementBound;
return prev.unite(bounds);
}, Bound.deserialize(alignables[0].xywh));
}
}
@@ -17,11 +17,10 @@ import {
type UIEventHandler,
} from '@blocksuite/block-std';
import { IS_MAC, IS_WINDOWS } from '@blocksuite/global/env';
import { toDraftModel } from '@blocksuite/store';
export class PageKeyboardManager {
private readonly _handleDelete: UIEventHandler = ctx => {
const event = ctx.get('defaultState').event;
const event = ctx.get('keyboardState').raw;
const blockSelections = this._currentSelection.filter(sel =>
sel.is(BlockSelection)
);
@@ -144,9 +143,7 @@ export class PageKeyboardManager {
}
const doc = rootComponent.host.doc;
const autofill = getTitleFromSelectedModels(
selectedModels.map(toDraftModel)
);
const autofill = getTitleFromSelectedModels(selectedModels);
promptDocTitle(rootComponent.std, autofill)
.then(title => {
if (title === null) return;
@@ -10,6 +10,7 @@ export class PreviewRootBlockComponent extends BlockComponent {
static override styles = css`
affine-preview-root {
display: block;
padding: 0 24px;
}
`;
@@ -8,21 +8,19 @@ import {
import { SpecProvider } from '@blocksuite/affine-shared/utils';
import { Container } from '@blocksuite/global/di';
import { sha } from '@blocksuite/global/utils';
import type { Schema, Store, Workspace } from '@blocksuite/store';
import type { Store, Workspace } from '@blocksuite/store';
import { extMimeMap, Transformer } from '@blocksuite/store';
import { createAssetsArchive, download, Unzip } from './utils.js';
type ImportHTMLToDocOptions = {
collection: Workspace;
schema: Schema;
html: string;
fileName?: string;
};
type ImportHTMLZipOptions = {
collection: Workspace;
schema: Schema;
imported: Blob;
};
@@ -43,10 +41,19 @@ function getProvider() {
*/
async function exportDoc(doc: Store) {
const provider = getProvider();
const job = doc.getTransformer([
docLinkBaseURLMiddleware(doc.workspace.id),
titleMiddleware(doc.workspace.meta.docMetas),
]);
const job = new Transformer({
schema: doc.schema,
blobCRUD: doc.blobSync,
docCRUD: {
create: (id: string) => doc.workspace.createDoc({ id }),
get: (id: string) => doc.workspace.getDoc(id),
delete: (id: string) => doc.workspace.removeDoc(id),
},
middlewares: [
docLinkBaseURLMiddleware(doc.workspace.id),
titleMiddleware(doc.workspace.meta.docMetas),
],
});
const snapshot = job.docToSnapshot(doc);
const adapter = new HtmlAdapter(job, provider);
if (!snapshot) {
@@ -80,20 +87,18 @@ async function exportDoc(doc: Store) {
*
* @param options - The import options.
* @param options.collection - The target doc collection.
* @param options.schema - The schema of the target doc collection.
* @param options.html - The HTML content to import.
* @param options.fileName - Optional filename for the imported doc.
* @returns A Promise that resolves to the ID of the newly created doc, or undefined if import fails.
*/
async function importHTMLToDoc({
collection,
schema,
html,
fileName,
}: ImportHTMLToDocOptions) {
const provider = getProvider();
const job = new Transformer({
schema,
schema: collection.schema,
blobCRUD: collection.blobSync,
docCRUD: {
create: (id: string) => collection.createDoc({ id }),
@@ -122,15 +127,10 @@ async function importHTMLToDoc({
*
* @param options - The import options.
* @param options.collection - The target doc collection.
* @param options.schema - The schema of the target doc collection.
* @param options.imported - The zip file as a Blob.
* @returns A Promise that resolves to an array of IDs of the newly created docs.
*/
async function importHTMLZip({
collection,
schema,
imported,
}: ImportHTMLZipOptions) {
async function importHTMLZip({ collection, imported }: ImportHTMLZipOptions) {
const provider = getProvider();
const unzip = new Unzip();
await unzip.load(imported);
@@ -161,7 +161,7 @@ async function importHTMLZip({
htmlBlobs.map(async ([fileName, blob]) => {
const fileNameWithoutExt = fileName.replace(/\.[^/.]+$/, '');
const job = new Transformer({
schema,
schema: collection.schema,
blobCRUD: collection.blobSync,
docCRUD: {
create: (id: string) => collection.createDoc({ id }),
@@ -9,7 +9,7 @@ import { SpecProvider } from '@blocksuite/affine-shared/utils';
import { Container } from '@blocksuite/global/di';
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import { assertExists, sha } from '@blocksuite/global/utils';
import type { Schema, Store, Workspace } from '@blocksuite/store';
import type { Store, Workspace } from '@blocksuite/store';
import { extMimeMap, Transformer } from '@blocksuite/store';
import { createAssetsArchive, download, Unzip } from './utils.js';
@@ -31,14 +31,12 @@ type ImportMarkdownToBlockOptions = {
type ImportMarkdownToDocOptions = {
collection: Workspace;
schema: Schema;
markdown: string;
fileName?: string;
};
type ImportMarkdownZipOptions = {
collection: Workspace;
schema: Schema;
imported: Blob;
};
@@ -49,10 +47,19 @@ type ImportMarkdownZipOptions = {
*/
async function exportDoc(doc: Store) {
const provider = getProvider();
const job = doc.getTransformer([
docLinkBaseURLMiddleware(doc.workspace.id),
titleMiddleware(doc.workspace.meta.docMetas),
]);
const job = new Transformer({
schema: doc.schema,
blobCRUD: doc.blobSync,
docCRUD: {
create: (id: string) => doc.workspace.createDoc({ id }),
get: (id: string) => doc.workspace.getDoc(id),
delete: (id: string) => doc.workspace.removeDoc(id),
},
middlewares: [
docLinkBaseURLMiddleware(doc.workspace.id),
titleMiddleware(doc.workspace.meta.docMetas),
],
});
const snapshot = job.docToSnapshot(doc);
const adapter = new MarkdownAdapter(job, provider);
@@ -100,10 +107,19 @@ async function importMarkdownToBlock({
blockId,
}: ImportMarkdownToBlockOptions) {
const provider = getProvider();
const job = doc.getTransformer([
defaultImageProxyMiddleware,
docLinkBaseURLMiddleware(doc.workspace.id),
]);
const job = new Transformer({
schema: doc.schema,
blobCRUD: doc.blobSync,
docCRUD: {
create: (id: string) => doc.workspace.createDoc({ id }),
get: (id: string) => doc.workspace.getDoc(id),
delete: (id: string) => doc.workspace.removeDoc(id),
},
middlewares: [
defaultImageProxyMiddleware,
docLinkBaseURLMiddleware(doc.workspace.id),
],
});
const adapter = new MarkdownAdapter(job, provider);
const snapshot = await adapter.toSliceSnapshot({
file: markdown,
@@ -127,20 +143,18 @@ async function importMarkdownToBlock({
* Imports Markdown content into a new doc within a collection.
* @param options Object containing import options
* @param options.collection The target doc collection
* @param options.schema The schema of the target doc collection
* @param options.markdown The Markdown content to import
* @param options.fileName Optional filename for the imported doc
* @returns A Promise that resolves to the ID of the newly created doc, or undefined if import fails
*/
async function importMarkdownToDoc({
collection,
schema,
markdown,
fileName,
}: ImportMarkdownToDocOptions) {
const provider = getProvider();
const job = new Transformer({
schema,
schema: collection.schema,
blobCRUD: collection.blobSync,
docCRUD: {
create: (id: string) => collection.createDoc({ id }),
@@ -168,13 +182,11 @@ async function importMarkdownToDoc({
* Imports a zip file containing Markdown files and assets into a collection.
* @param options Object containing import options
* @param options.collection The target doc collection
* @param options.schema The schema of the target doc collection
* @param options.imported The zip file as a Blob
* @returns A Promise that resolves to an array of IDs of the newly created docs
*/
async function importMarkdownZip({
collection,
schema,
imported,
}: ImportMarkdownZipOptions) {
const provider = getProvider();
@@ -207,7 +219,7 @@ async function importMarkdownZip({
markdownBlobs.map(async ([fileName, blob]) => {
const fileNameWithoutExt = fileName.replace(/\.[^/.]+$/, '');
const job = new Transformer({
schema,
schema: collection.schema,
blobCRUD: collection.blobSync,
docCRUD: {
create: (id: string) => collection.createDoc({ id }),
@@ -3,18 +3,12 @@ import { NotionHtmlAdapter } from '@blocksuite/affine-shared/adapters';
import { SpecProvider } from '@blocksuite/affine-shared/utils';
import { Container } from '@blocksuite/global/di';
import { sha } from '@blocksuite/global/utils';
import {
extMimeMap,
type Schema,
Transformer,
type Workspace,
} from '@blocksuite/store';
import { extMimeMap, Transformer, type Workspace } from '@blocksuite/store';
import { Unzip } from './utils.js';
type ImportNotionZipOptions = {
collection: Workspace;
schema: Schema;
imported: Blob;
};
@@ -32,7 +26,6 @@ function getProvider() {
*
* @param options - The options for importing.
* @param options.collection - The BlockSuite document collection.
* @param options.schema - The schema of the BlockSuite document collection.
* @param options.imported - The imported zip file as a Blob.
*
* @returns A promise that resolves to an object containing:
@@ -43,7 +36,6 @@ function getProvider() {
*/
async function importNotionZip({
collection,
schema,
imported,
}: ImportNotionZipOptions) {
const provider = getProvider();
@@ -125,7 +117,7 @@ async function importNotionZip({
}
const pagePromises = Array.from(pagePaths).map(async path => {
const job = new Transformer({
schema,
schema: collection.schema,
blobCRUD: collection.blobSync,
docCRUD: {
create: (id: string) => collection.createDoc({ id }),
@@ -3,19 +3,15 @@ import {
titleMiddleware,
} from '@blocksuite/affine-shared/adapters';
import { sha } from '@blocksuite/global/utils';
import type { DocSnapshot, Schema, Store, Workspace } from '@blocksuite/store';
import type { DocSnapshot, Store, Workspace } from '@blocksuite/store';
import { extMimeMap, getAssetName, Transformer } from '@blocksuite/store';
import { download, Unzip, Zip } from '../transformers/utils.js';
async function exportDocs(
collection: Workspace,
schema: Schema,
docs: Store[]
) {
async function exportDocs(collection: Workspace, docs: Store[]) {
const zip = new Zip();
const job = new Transformer({
schema,
schema: collection.schema,
blobCRUD: collection.blobSync,
docCRUD: {
create: (id: string) => collection.createDoc({ id }),
@@ -74,11 +70,7 @@ async function exportDocs(
return download(downloadBlob, `${collection.id}.bs.zip`);
}
async function importDocs(
collection: Workspace,
schema: Schema,
imported: Blob
) {
async function importDocs(collection: Workspace, imported: Blob) {
const unzip = new Unzip();
await unzip.load(imported);
@@ -106,7 +98,7 @@ async function importDocs(
}
const job = new Transformer({
schema,
schema: collection.schema,
blobCRUD: collection.blobSync,
docCRUD: {
create: (id: string) => collection.createDoc({ id }),
@@ -1,4 +1,4 @@
import clamp from 'lodash-es/clamp';
import { clamp } from '@blocksuite/affine-shared/utils';
type CollisionBox = {
/**
@@ -134,12 +134,13 @@ export class EdgelessChangeBrushButton extends WithDisposable(LitElement) {
return html`
<edgeless-color-picker-button
class="color"
.label="${'Color'}"
.label=${'Color'}
.pick=${this.pickColor}
.color=${selectedColor}
.colors=${colors}
.colorType=${type}
.theme=${colorScheme}
.palettes=${DefaultTheme.Palettes}
>
</edgeless-color-picker-button>
`;
@@ -158,6 +159,7 @@ export class EdgelessChangeBrushButton extends WithDisposable(LitElement) {
<edgeless-color-panel
.value=${selectedColor}
.theme=${colorScheme}
.palettes=${DefaultTheme.Palettes}
@select=${this._setBrushColor}
>
</edgeless-color-panel>
@@ -373,12 +373,13 @@ export class EdgelessChangeConnectorButton extends WithDisposable(LitElement) {
return html`
<edgeless-color-picker-button
class="stroke-color"
.label="${'Stroke style'}"
.label=${'Stroke style'}
.pick=${this.pickColor}
.color=${selectedColor}
.colors=${colors}
.colorType=${type}
.theme=${colorScheme}
.palettes=${DefaultTheme.Palettes}
.hollowCircle=${true}
>
<div
@@ -43,6 +43,7 @@ import {
import {
EmbedOptionProvider,
type EmbedOptions,
FeatureFlagService,
GenerateDocUrlProvider,
type GenerateDocUrlService,
type LinkEventType,
@@ -418,6 +419,16 @@ export class EdgelessChangeEmbedCardButton extends WithDisposable(LitElement) {
private get _canConvertToEmbedView() {
const block = this._blockComponent;
// synced doc entry controlled by awareness flag
if (!!block && isEmbedLinkedDocBlock(block.model)) {
const isSyncedDocEnabled = block.doc
.get(FeatureFlagService)
.getFlag('enable_synced_doc_block');
if (!isSyncedDocEnabled) {
return false;
}
}
return (
(block && 'convertToEmbed' in block) ||
this._embedOptions?.viewType === 'embed'
@@ -13,6 +13,7 @@ import { renderToolbarSeparator } from '@blocksuite/affine-components/toolbar';
import {
type ColorScheme,
DEFAULT_NOTE_HEIGHT,
DefaultTheme,
type FrameBlockModel,
NoteBlockModel,
NoteDisplayMode,
@@ -200,12 +201,13 @@ export class EdgelessChangeFrameButton extends WithDisposable(LitElement) {
return html`
<edgeless-color-picker-button
class="background"
.label="${'Background'}"
.label=${'Background'}
.pick=${this.pickColor}
.color=${background}
.colors=${colors}
.colorType=${type}
.theme=${colorScheme}
.palettes=${DefaultTheme.Palettes}
>
</edgeless-color-picker-button>
`;
@@ -227,6 +229,7 @@ export class EdgelessChangeFrameButton extends WithDisposable(LitElement) {
<edgeless-color-panel
.value=${background}
.theme=${colorScheme}
.palettes=${DefaultTheme.Palettes}
@select=${this._setFrameBackground}
>
</edgeless-color-panel>
@@ -338,6 +338,7 @@ export class EdgelessChangeShapeButton extends WithDisposable(LitElement) {
.colors=${colors}
.colorType=${type}
.theme=${colorScheme}
.palettes=${DefaultTheme.Palettes}
>
</edgeless-color-picker-button>
`;
@@ -361,6 +362,7 @@ export class EdgelessChangeShapeButton extends WithDisposable(LitElement) {
aria-label="Fill colors"
.value=${selectedFillColor}
.theme=${colorScheme}
.palettes=${DefaultTheme.Palettes}
@select=${this._setShapeFillColor}
>
</edgeless-color-panel>
@@ -388,6 +390,7 @@ export class EdgelessChangeShapeButton extends WithDisposable(LitElement) {
.colors=${colors}
.colorType=${type}
.theme=${colorScheme}
.palettes=${DefaultTheme.Palettes}
.hollowCircle=${true}
>
<div
@@ -450,8 +453,8 @@ export class EdgelessChangeShapeButton extends WithDisposable(LitElement) {
() => html`
<editor-icon-button
aria-label="Add text"
.tooltip="${'Add text'}"
.iconSize="${'20px'}"
.tooltip=${'Add text'}
.iconSize=${'20px'}
@click=${this._addText}
>
${AddTextIcon()}
@@ -462,7 +465,7 @@ export class EdgelessChangeShapeButton extends WithDisposable(LitElement) {
'menu',
() => html`
<edgeless-change-text-menu
.elementType="${'shape'}"
.elementType=${'shape'}
.elements=${elements}
.edgeless=${this.edgeless}
></edgeless-change-text-menu>
@@ -344,10 +344,6 @@ export class EdgelessChangeTextMenu extends WithDisposable(LitElement) {
matchFontFaces.length === 1 &&
matchFontFaces[0].style === selectedFontStyle &&
matchFontFaces[0].weight === selectedFontWeight;
const palettes =
this.elementType === 'shape'
? DefaultTheme.ShapeTextColorPalettes
: DefaultTheme.Palettes;
return join(
[
@@ -393,14 +389,14 @@ export class EdgelessChangeTextMenu extends WithDisposable(LitElement) {
return html`
<edgeless-color-picker-button
class="text-color"
.label="${'Text color'}"
.label=${'Text color'}
.pick=${this.pickColor}
.isText=${true}
.color=${selectedColor}
.colors=${colors}
.colorType=${type}
.theme=${colorScheme}
.palettes=${palettes}
.palettes=${DefaultTheme.Palettes}
>
</edgeless-color-picker-button>
`;
@@ -422,7 +418,7 @@ export class EdgelessChangeTextMenu extends WithDisposable(LitElement) {
<edgeless-color-panel
.value=${selectedColor}
.theme=${colorScheme}
.palettes=${palettes}
.palettes=${DefaultTheme.Palettes}
@select=${this._setTextColor}
></edgeless-color-panel>
</editor-menu-button>
@@ -44,6 +44,7 @@ import {
import {
EmbedOptionProvider,
type EmbedOptions,
FeatureFlagService,
GenerateDocUrlProvider,
type GenerateDocUrlService,
type LinkEventType,
@@ -235,6 +236,16 @@ export class EmbedCardToolbar extends WidgetComponent<
cloneGroups(BUILT_IN_GROUPS);
private get _canConvertToEmbedView() {
// synced doc entry controlled by awareness flag
if (this.focusModel && isEmbedLinkedDocBlock(this.focusModel)) {
const isSyncedDocEnabled = this.doc
.get(FeatureFlagService)
.getFlag('enable_synced_doc_block');
if (!isSyncedDocEnabled) {
return false;
}
}
if (!this.focusBlock) return false;
return (
@@ -64,7 +64,7 @@ import type {
import { tableViewMeta } from '@blocksuite/data-view/view-presets';
import { assertExists } from '@blocksuite/global/utils';
import { MoreVerticalIcon } from '@blocksuite/icons/lit';
import { Slice, toDraftModel } from '@blocksuite/store';
import { Slice } from '@blocksuite/store';
import { html, type TemplateResult } from 'lit';
import { FormatBarContext } from './context.js';
@@ -230,9 +230,7 @@ export function toolbarDefaultConfig(toolbar: AffineFormatBarWidget) {
host.selection.clear();
const doc = host.doc;
const autofill = getTitleFromSelectedModels(
selectedModels.map(toDraftModel)
);
const autofill = getTitleFromSelectedModels(selectedModels);
promptDocTitle(std, autofill)
.then(async title => {
if (title === null) return;
@@ -113,6 +113,12 @@ import {
export type KeyboardToolbarConfig = {
items: KeyboardToolbarItem[];
/**
* @description Whether to use the screen height as the keyboard height when the virtual keyboard API is not supported.
* It is useful when the app is running in a webview and the keyboard is not overlaid on the content.
* @default false
*/
useScreenHeight?: boolean;
};
export type KeyboardToolbarItem =
@@ -1100,4 +1106,5 @@ export const defaultKeyboardToolbarConfig: KeyboardToolbarConfig = {
},
},
],
useScreenHeight: false,
};
@@ -1,8 +1,8 @@
import { getDocTitleByEditorHost } from '@blocksuite/affine-components/doc-title';
import type { RootBlockModel } from '@blocksuite/affine-model';
import { FeatureFlagService } from '@blocksuite/affine-shared/services';
import { WidgetComponent } from '@blocksuite/block-std';
import { IS_MOBILE } from '@blocksuite/global/env';
import { assertType } from '@blocksuite/global/utils';
import { signal } from '@preact/signals-core';
import { html, nothing } from 'lit';
@@ -20,10 +20,10 @@ export class AffineKeyboardToolbarWidget extends WidgetComponent<
> {
private readonly _close = (blur: boolean) => {
if (blur) {
if (document.activeElement === this._docTitle?.inlineEditorContainer) {
this._docTitle?.inlineEditor?.setInlineRange(null);
if (document.activeElement === this._docTitle) {
this._docTitle?.blur();
} else if (document.activeElement === this.block.rootComponent) {
this.std.selection.clear();
this.block.rootComponent?.blur();
}
}
this._show$.value = false;
@@ -31,8 +31,12 @@ export class AffineKeyboardToolbarWidget extends WidgetComponent<
private readonly _show$ = signal(false);
private get _docTitle() {
return getDocTitleByEditorHost(this.std.host);
private get _docTitle(): HTMLDivElement | null {
const docTitle = this.std.host
.closest('.affine-page-viewport')
?.querySelector('doc-title rich-text .inline-editor');
assertType<HTMLDivElement | null>(docTitle);
return docTitle;
}
get config() {
@@ -57,11 +61,10 @@ export class AffineKeyboardToolbarWidget extends WidgetComponent<
}
if (this._docTitle) {
const { inlineEditorContainer } = this._docTitle;
this.disposables.addFromEvent(inlineEditorContainer, 'focus', () => {
this.disposables.addFromEvent(this._docTitle, 'focus', () => {
this._show$.value = true;
});
this.disposables.addFromEvent(inlineEditorContainer, 'blur', () => {
this.disposables.addFromEvent(this._docTitle, 'blur', () => {
this._show$.value = false;
});
}
@@ -1,5 +1,8 @@
import {
VirtualKeyboardController,
type VirtualKeyboardControllerConfig,
} from '@blocksuite/affine-components/virtual-keyboard';
import { getSelectedModelsCommand } from '@blocksuite/affine-shared/commands';
import { VirtualKeyboardProvider } from '@blocksuite/affine-shared/services';
import {
PropTypes,
requiredProperties,
@@ -14,21 +17,20 @@ import { repeat } from 'lit/directives/repeat.js';
import { styleMap } from 'lit/directives/style-map.js';
import { when } from 'lit/directives/when.js';
import { PageRootBlockComponent } from '../../page/page-root-block';
import { PageRootBlockComponent } from '../../page/page-root-block.js';
import type {
KeyboardIconType,
KeyboardToolbarConfig,
KeyboardToolbarContext,
KeyboardToolbarItem,
KeyboardToolPanelConfig,
} from './config';
import { PositionController } from './position-controller';
import { keyboardToolbarStyles } from './styles';
} from './config.js';
import { keyboardToolbarStyles, TOOLBAR_HEIGHT } from './styles.js';
import {
isKeyboardSubToolBarConfig,
isKeyboardToolBarActionItem,
isKeyboardToolPanelConfig,
} from './utils';
} from './utils.js';
export const AFFINE_KEYBOARD_TOOLBAR = 'affine-keyboard-toolbar';
@@ -41,28 +43,11 @@ export class AffineKeyboardToolbar extends SignalWatcher(
) {
static override styles = keyboardToolbarStyles;
/** This field records the panel static height same as the virtual keyboard height */
panelHeight$ = signal(0);
positionController = new PositionController(this);
get std() {
return this.rootComponent.std;
}
get keyboard() {
return this._context.std.get(VirtualKeyboardProvider);
}
get panelOpened() {
return this._currentPanelIndex$.value !== -1;
}
private readonly _closeToolPanel = () => {
if (!this.panelOpened) return;
if (!this._isPanelOpened) return;
this._currentPanelIndex$.value = -1;
this.keyboard.show();
this._keyboardController.show();
};
private readonly _currentPanelIndex$ = signal(-1);
@@ -70,7 +55,7 @@ export class AffineKeyboardToolbar extends SignalWatcher(
private readonly _goPrevToolbar = () => {
if (!this._isSubToolbarOpened) return;
if (this.panelOpened) this._closeToolPanel();
if (this._isPanelOpened) this._closeToolPanel();
this._path$.value = this._path$.value.slice(0, -1);
};
@@ -90,25 +75,31 @@ export class AffineKeyboardToolbar extends SignalWatcher(
this._closeToolPanel();
} else {
this._currentPanelIndex$.value = index;
this.keyboard.hide();
this._scrollCurrentBlockIntoView();
this._keyboardController.hide();
this.scrollCurrentBlockIntoView();
}
}
this._lastActiveItem$.value = item;
};
private readonly _keyboardController = new VirtualKeyboardController(this);
private readonly _lastActiveItem$ = signal<KeyboardToolbarItem | null>(null);
/** This field records the panel static height, which dose not aim to control the panel opening */
private readonly _panelHeight$ = signal(0);
private readonly _path$ = signal<number[]>([]);
private readonly _scrollCurrentBlockIntoView = () => {
this.std.command
private readonly scrollCurrentBlockIntoView = () => {
const { std } = this.rootComponent;
std.command
.chain()
.pipe(getSelectedModelsCommand)
.pipe(({ selectedModels }) => {
if (!selectedModels?.length) return;
const block = this.std.view.getBlock(selectedModels[0].id);
const block = std.view.getBlock(selectedModels[0].id);
if (!block) return;
const { y: y1 } = this.getBoundingClientRect();
@@ -127,7 +118,7 @@ export class AffineKeyboardToolbar extends SignalWatcher(
private get _context(): KeyboardToolbarContext {
return {
std: this.std,
std: this.rootComponent.std,
rootComponent: this.rootComponent,
closeToolbar: (blur = false) => {
this.close(blur);
@@ -139,7 +130,7 @@ export class AffineKeyboardToolbar extends SignalWatcher(
}
private get _currentPanelConfig(): KeyboardToolPanelConfig | null {
if (!this.panelOpened) return null;
if (!this._isPanelOpened) return null;
const result = this._currentToolbarItems[this._currentPanelIndex$.value];
@@ -148,7 +139,9 @@ export class AffineKeyboardToolbar extends SignalWatcher(
private get _currentToolbarItems(): KeyboardToolbarItem[] {
let items = this.config.items;
for (const index of this._path$.value) {
// eslint-disable-next-line @typescript-eslint/prefer-for-of
for (let i = 0; i < this._path$.value.length; i++) {
const index = this._path$.value[i];
if (isKeyboardSubToolBarConfig(items[index])) {
items = items[index].items;
} else {
@@ -163,10 +156,21 @@ export class AffineKeyboardToolbar extends SignalWatcher(
);
}
private get _isPanelOpened() {
return this._currentPanelIndex$.value !== -1;
}
private get _isSubToolbarOpened() {
return this._path$.value.length > 0;
}
get virtualKeyboardControllerConfig(): VirtualKeyboardControllerConfig {
return {
useScreenHeight: this.config.useScreenHeight ?? false,
inputElement: this.rootComponent,
};
}
private _renderIcon(icon: KeyboardIconType) {
return typeof icon === 'function' ? icon(this._context) : icon;
}
@@ -248,13 +252,35 @@ export class AffineKeyboardToolbar extends SignalWatcher(
e.preventDefault();
});
this.disposables.add(
effect(() => {
if (this._keyboardController.opened) {
this._panelHeight$.value = this._keyboardController.keyboardHeight;
} else if (this._isPanelOpened && this._panelHeight$.peek() === 0) {
this._panelHeight$.value = 260;
}
})
);
this.disposables.add(
effect(() => {
if (this._keyboardController.opened && !this.config.useScreenHeight) {
document.body.style.paddingBottom = `${this._keyboardController.keyboardHeight + TOOLBAR_HEIGHT}px`;
} else if (this._isPanelOpened) {
document.body.style.paddingBottom = `${this._panelHeight$.value + TOOLBAR_HEIGHT}px`;
} else {
document.body.style.paddingBottom = '';
}
})
);
this.disposables.add(
effect(() => {
const std = this.rootComponent.std;
std.selection.value;
// wait cursor updated
requestAnimationFrame(() => {
this._scrollCurrentBlockIntoView();
this.scrollCurrentBlockIntoView();
});
})
);
@@ -302,14 +328,24 @@ export class AffineKeyboardToolbar extends SignalWatcher(
);
}
override disconnectedCallback() {
super.disconnectedCallback();
document.body.style.paddingBottom = '';
}
override firstUpdated() {
// workaround for the virtual keyboard showing transition animation
setTimeout(() => {
this._scrollCurrentBlockIntoView();
this.scrollCurrentBlockIntoView();
}, 700);
}
override render() {
this.style.bottom =
this.config.useScreenHeight && this._keyboardController.opened
? `${-this._panelHeight$.value}px`
: '0px';
return html`
<div class="keyboard-toolbar">
${this._renderItems()}
@@ -319,7 +355,7 @@ export class AffineKeyboardToolbar extends SignalWatcher(
<affine-keyboard-tool-panel
.config=${this._currentPanelConfig}
.context=${this._context}
height=${this.panelHeight$.value}
height=${this._panelHeight$.value}
></affine-keyboard-tool-panel>
`;
}
@@ -1,58 +0,0 @@
import { type VirtualKeyboardProvider } from '@blocksuite/affine-shared/services';
import type { BlockStdScope, ShadowlessElement } from '@blocksuite/block-std';
import { DisposableGroup } from '@blocksuite/global/utils';
import { effect, type Signal } from '@preact/signals-core';
import type { ReactiveController, ReactiveControllerHost } from 'lit';
import { TOOLBAR_HEIGHT } from './styles';
/**
* This controller is used to control the keyboard toolbar position
*/
export class PositionController implements ReactiveController {
private readonly _disposables = new DisposableGroup();
host: ReactiveControllerHost &
ShadowlessElement & {
std: BlockStdScope;
panelHeight$: Signal<number>;
keyboard: VirtualKeyboardProvider;
panelOpened: boolean;
};
constructor(host: PositionController['host']) {
(this.host = host).addController(this);
}
hostConnected() {
const { keyboard, panelOpened } = this.host;
this._disposables.add(
effect(() => {
if (keyboard.visible$.value) {
this.host.panelHeight$.value = keyboard.height$.value;
}
})
);
this.host.style.bottom = '0px';
this._disposables.add(
effect(() => {
if (keyboard.visible$.value) {
document.body.style.paddingBottom = `${keyboard.height$.value + TOOLBAR_HEIGHT}px`;
} else if (panelOpened) {
document.body.style.paddingBottom = `${this.host.panelHeight$.peek() + TOOLBAR_HEIGHT}px`;
} else {
document.body.style.paddingBottom = '';
}
})
);
this._disposables.add(() => {
document.body.style.paddingBottom = '';
});
}
hostDisconnected() {
this._disposables.dispose();
}
}
@@ -55,15 +55,15 @@ export interface LinkedWidgetConfig {
*
* If the return value is not null, no action will be taken.
*/
autoFocusedItemKey?: (
autoFocusedItem?: (
menus: LinkedMenuGroup[],
query: string,
currentActiveKey: string | null,
editorHost: EditorHost,
inlineEditor: AffineInlineEditor
) => string | null;
) => LinkedMenuItem | null;
mobile: {
useScreenHeight?: boolean;
/**
* The linked doc menu widget will scroll the container to make sure the input cursor is visible in viewport.
* It accepts a selector string, HTMLElement or Window
@@ -101,6 +101,8 @@ export type LinkedMenuGroup = {
loading?: boolean | Signal<boolean>;
// copywriting when display quantity exceeds
overflowText?: string | Signal<string>;
// loading text
loadingText?: string | Signal<string>;
};
export type LinkedDocContext = {
@@ -231,7 +233,6 @@ export function createNewDocMenuGroup(
};
showImportModal({
collection: doc.workspace,
schema: doc.schema,
onSuccess,
onFail,
});
@@ -8,7 +8,7 @@ import {
} from '@blocksuite/affine-components/icons';
import { openFileOrFiles } from '@blocksuite/affine-shared/utils';
import { WithDisposable } from '@blocksuite/global/utils';
import type { Schema, Workspace } from '@blocksuite/store';
import type { Workspace } from '@blocksuite/store';
import { html, LitElement, type PropertyValues } from 'lit';
import { query, state } from 'lit/decorators.js';
@@ -31,7 +31,6 @@ export class ImportDoc extends WithDisposable(LitElement) {
constructor(
private readonly collection: Workspace,
private readonly schema: Schema,
private readonly onSuccess?: OnSuccessHandler,
private readonly onFail?: OnFailHandler,
private readonly abortController = new AbortController()
@@ -64,7 +63,6 @@ export class ImportDoc extends WithDisposable(LitElement) {
}
const pageId = await HtmlTransformer.importHTMLToDoc({
collection: this.collection,
schema: this.schema,
html: text,
fileName,
});
@@ -95,7 +93,6 @@ export class ImportDoc extends WithDisposable(LitElement) {
}
const pageId = await MarkdownTransformer.importMarkdownToDoc({
collection: this.collection,
schema: this.schema,
markdown: text,
fileName,
});
@@ -120,7 +117,6 @@ export class ImportDoc extends WithDisposable(LitElement) {
const { entryId, pageIds, isWorkspaceFile, hasMarkdown } =
await NotionHtmlTransformer.importNotionZip({
collection: this.collection,
schema: this.schema,
imported: file,
});
needLoading && this.abortController.abort();
@@ -1,4 +1,4 @@
import type { Schema, Workspace } from '@blocksuite/store';
import type { Workspace } from '@blocksuite/store';
import {
ImportDoc,
@@ -7,14 +7,12 @@ import {
} from './import-doc.js';
export function showImportModal({
schema,
collection,
onSuccess,
onFail,
container = document.body,
abortController = new AbortController(),
}: {
schema: Schema;
collection: Workspace;
onSuccess?: OnSuccessHandler;
onFail?: OnFailHandler;
@@ -24,7 +22,6 @@ export function showImportModal({
}) {
const importDoc = new ImportDoc(
collection,
schema,
onSuccess,
onFail,
abortController
@@ -20,8 +20,10 @@ import { choose } from 'lit/directives/choose.js';
import { repeat } from 'lit/directives/repeat.js';
import { styleMap } from 'lit/directives/style-map.js';
import type { PageRootBlockComponent } from '../../page/page-root-block.js';
import { RootBlockConfigExtension } from '../../root-config.js';
import {
type PageRootBlockComponent,
RootBlockConfigExtension,
} from '../../index.js';
import {
type AFFINE_LINKED_DOC_WIDGET,
getMenus,
@@ -214,6 +216,7 @@ export class AffineLinkedDocWidget extends WidgetComponent<
convertTriggerKey: true,
getMenus,
mobile: {
useScreenHeight: false,
scrollContainer: getViewportElement(this.std.host) ?? window,
scrollTopOffset: 46,
},
@@ -75,33 +75,23 @@ export class LinkedDocPopover extends SignalWatcher(
// need to rebind the effect because this._linkedDocGroup has changed.
this._menusItemsEffectCleanup = effect(() => {
this._updateAutoFocusedItem();
// wait for the next tick to ensure the items are rendered to DOM
setTimeout(() => {
this.scrollToFocusedItem();
});
});
};
private readonly _updateAutoFocusedItem = () => {
// Get the auto-focused item key from the config
const autoFocusedItemKey = this.context.config.autoFocusedItemKey?.(
if (!this._query) {
return;
}
const autoFocusedItem = this.context.config.autoFocusedItem?.(
this._linkedDocGroup,
this._query || '',
this._activatedItemKey,
this._query,
this.context.std.host,
this.context.inlineEditor
);
if (autoFocusedItemKey) {
this._activatedItemKey = autoFocusedItemKey;
return;
}
// If no auto-focused item key is returned from the config and no item is currently focused,
// focus the first item in the flattened action list
if (!this._activatedItemKey && this._flattenActionList.length > 0) {
this._activatedItemKey = this._flattenActionList[0].key;
if (autoFocusedItem) {
this._activatedItemIndex = this._flattenActionList.findIndex(
item => item.key === autoFocusedItem.key
);
}
};
@@ -136,9 +126,19 @@ export class LinkedDocPopover extends SignalWatcher(
let items = resolveSignal(group.items);
const isOverflow = !!group.maxDisplay && items.length > group.maxDisplay;
const isLoading = resolveSignal(group.loading);
items = isExpanded ? items : items.slice(0, group.maxDisplay);
if (isLoading) {
items = items.concat({
key: 'loading',
name: resolveSignal(group.loadingText) || 'loading',
icon: LoadingIcon,
action: () => {},
});
}
if (isOverflow && !isExpanded && group.maxDisplay) {
items = items.concat({
key: `${group.name} More`,
@@ -183,6 +183,7 @@ export class LinkedDocPopover extends SignalWatcher(
target: eventSource,
signal: keydownObserverAbortController.signal,
onInput: isComposition => {
this._activatedItemIndex = 0;
if (isComposition) {
this._updateLinkedDocGroup().catch(console.error);
} else {
@@ -192,6 +193,7 @@ export class LinkedDocPopover extends SignalWatcher(
}
},
onPaste: () => {
this._activatedItemIndex = 0;
setTimeout(() => {
this._updateLinkedDocGroup().catch(console.error);
}, 50);
@@ -204,18 +206,33 @@ export class LinkedDocPopover extends SignalWatcher(
if (curRange.index < this.context.startRange.index) {
this.context.close();
}
this._activatedItemIndex = 0;
this.context.inlineEditor.slots.renderComplete.once(
this._updateLinkedDocGroup
);
},
onMove: step => {
const itemLen = this._flattenActionList.length;
const nextIndex = (itemLen + this._activatedItemIndex + step) % itemLen;
const item = this._flattenActionList[nextIndex];
if (item) {
this._activatedItemKey = item.key;
this._activatedItemIndex =
(itemLen + this._activatedItemIndex + step) % itemLen;
// Scroll to the active item
const item = this._flattenActionList[this._activatedItemIndex];
const shadowRoot = this.shadowRoot;
if (!shadowRoot) {
console.warn('Failed to find the shadow root!', this);
return;
}
this.scrollToFocusedItem();
const ele = shadowRoot.querySelector(
`icon-button[data-id="${item.key}"]`
);
if (!ele) {
console.warn('Failed to find the active item!', item);
return;
}
ele.scrollIntoView({
block: 'nearest',
});
},
onConfirm: () => {
this._flattenActionList[this._activatedItemIndex]
@@ -244,29 +261,19 @@ export class LinkedDocPopover extends SignalWatcher(
visibility: 'hidden',
});
const actionGroups = this._actionGroup.map(group => {
// Check if the group is loading
const isLoading = resolveSignal(group.loading);
return {
...group,
isLoading,
};
});
// XXX This is a side effect
let accIdx = 0;
return html`<div class="linked-doc-popover" style="${style}">
${actionGroups
.filter(group => group.items.length || group.isLoading)
${this._actionGroup
.filter(group => group.items.length)
.map((group, idx) => {
return html`
<div class="divider" ?hidden=${idx === 0}></div>
<div class="group-title">
${group.name}
${group.isLoading
? html`<span class="loading-icon">${LoadingIcon}</span>`
: nothing}
</div>
<div class="group-title">${group.name}</div>
<div class="group" style=${group.styles ?? ''}>
${group.items.map(({ key, name, icon, action }) => {
accIdx++;
const curIdx = accIdx - 1;
const tooltip = this._showTooltip
? html`<affine-tooltip
tip-position=${'right'}
@@ -283,13 +290,13 @@ export class LinkedDocPopover extends SignalWatcher(
height="30px"
data-id=${key}
.text=${name}
hover=${this._activatedItemKey === key}
hover=${this._activatedItemIndex === curIdx}
@click=${() => {
action()?.catch(console.error);
}}
@mousemove=${() => {
// Use `mousemove` instead of `mouseover` to avoid navigate conflict with keyboard
this._activatedItemKey = key;
this._activatedItemIndex = curIdx;
// show tooltip whether text length overflows
for (const button of this.iconButtons.values()) {
if (button.dataset.id == key && button.textElement) {
@@ -341,40 +348,8 @@ export class LinkedDocPopover extends SignalWatcher(
}
}
private scrollToFocusedItem() {
const shadowRoot = this.shadowRoot;
if (!shadowRoot) {
return;
}
// If there's no active item key, don't try to scroll
if (!this._activatedItemKey) {
return;
}
const ele = shadowRoot.querySelector(
`icon-button[data-id="${this._activatedItemKey}"]`
);
// If the element doesn't exist, don't log a warning
if (!ele) {
return;
}
ele.scrollIntoView({
block: 'nearest',
});
}
get _activatedItemIndex() {
const index = this._flattenActionList.findIndex(
item => item.key === this._activatedItemKey
);
return index === -1 ? 0 : index;
}
@state()
private accessor _activatedItemKey: string | null = null;
private accessor _activatedItemIndex = 0;
@state()
private accessor _linkedDocGroup: LinkedMenuGroup[] = [];
@@ -2,7 +2,10 @@ import {
cleanSpecifiedTail,
getTextContentFromInlineRange,
} from '@blocksuite/affine-components/rich-text';
import { VirtualKeyboardProvider } from '@blocksuite/affine-shared/services';
import {
VirtualKeyboardController,
type VirtualKeyboardControllerConfig,
} from '@blocksuite/affine-components/virtual-keyboard';
import {
createKeydownObserver,
getViewportElement,
@@ -40,6 +43,8 @@ export class AffineMobileLinkedDocMenu extends SignalWatcher(
private _firstActionItem: LinkedMenuItem | null = null;
private readonly _keyboardController = new VirtualKeyboardController(this);
private readonly _linkedDocGroup$ = signal<LinkedMenuGroup[]>([]);
private readonly _renderGroup = (group: LinkedMenuGroup) => {
@@ -154,8 +159,11 @@ export class AffineMobileLinkedDocMenu extends SignalWatcher(
);
}
get keyboard() {
return this.context.std.get(VirtualKeyboardProvider);
get virtualKeyboardControllerConfig(): VirtualKeyboardControllerConfig {
return {
useScreenHeight: this.context.config.mobile.useScreenHeight ?? false,
inputElement: this.rootComponent,
};
}
override connectedCallback() {
@@ -222,8 +230,8 @@ export class AffineMobileLinkedDocMenu extends SignalWatcher(
}
override firstUpdated() {
if (!this.keyboard.visible$.value) {
this.keyboard.show();
if (!this._keyboardController.opened) {
this._keyboardController.show();
}
this._scrollInputToTop();
}
@@ -236,7 +244,11 @@ export class AffineMobileLinkedDocMenu extends SignalWatcher(
this._firstActionItem = resolveSignal(groups[0].items)[0];
this.style.bottom = `${this.keyboard.height$.value}px`;
this.style.bottom =
this.context.config.mobile.useScreenHeight &&
this._keyboardController.opened
? '0px'
: `max(0px, ${this._keyboardController.keyboardHeight}px)`;
return html`
${join(groups.map(this._renderGroup), html`<div class="divider"></div>`)}
@@ -51,18 +51,6 @@ export const linkedDocPopoverStyles = css`
align-items: center;
flex-shrink: 0;
font-weight: 500;
justify-content: space-between;
}
.linked-doc-popover .group-title .loading-icon {
display: flex;
align-items: center;
margin-left: 8px;
}
.linked-doc-popover .group-title .loading-icon svg {
width: 20px;
height: 20px;
}
.linked-doc-popover .divider {
@@ -423,9 +423,8 @@ export class SurfaceRefBlockComponent extends BlockComponent<SurfaceRefBlockMode
override mounted() {
const disposable = this.std.view.viewUpdated.on(payload => {
if (payload.type !== 'block') return;
if (
payload.method === 'add' &&
payload.type === 'add' &&
matchModels(payload.view.model, [RootBlockModel])
) {
disposable.dispose();
+1 -1
View File
@@ -34,7 +34,7 @@
},
"devDependencies": {
"@types/lodash.chunk": "^4.2.9",
"vitest": "3.0.7"
"vitest": "3.0.6"
},
"exports": {
".": "./src/index.ts",
+1 -5
View File
@@ -50,11 +50,7 @@ export {
} from './adapters/index.js';
export type { SurfaceContext } from './surface-block.js';
export { SurfaceBlockComponent } from './surface-block.js';
export {
SurfaceBlockModel,
SurfaceBlockSchema,
SurfaceBlockSchemaExtension,
} from './surface-model.js';
export { SurfaceBlockModel, SurfaceBlockSchema } from './surface-model.js';
export type { SurfaceBlockService } from './surface-service.js';
export {
EdgelessSurfaceBlockSpec,
@@ -5,7 +5,7 @@ import type {
import type { SurfaceBlockProps } from '@blocksuite/block-std/gfx';
import { SurfaceBlockModel as BaseSurfaceModel } from '@blocksuite/block-std/gfx';
import { DisposableGroup } from '@blocksuite/global/utils';
import { BlockSchemaExtension, defineBlockSchema } from '@blocksuite/store';
import { defineBlockSchema } from '@blocksuite/store';
import * as Y from 'yjs';
import { elementsCtorMap } from './element-model/index.js';
@@ -36,9 +36,6 @@ export const SurfaceBlockSchema = defineBlockSchema({
toModel: () => new SurfaceBlockModel(),
});
export const SurfaceBlockSchemaExtension =
BlockSchemaExtension(SurfaceBlockSchema);
export type SurfaceMiddleware = (surface: SurfaceBlockModel) => () => void;
export class SurfaceBlockModel extends BaseSurfaceModel {
@@ -246,7 +246,6 @@ export class AddButton extends SignalWatcher(
this.hoverColumnIndex$.value === this.columns$.value.length - 1;
const dragging = this.columnDragging$.value;
return html` <div
data-testid="add-column-button"
class="${classMap({
[addColumnButtonStyle]: true,
active: dragging,
@@ -270,7 +269,6 @@ export class AddButton extends SignalWatcher(
const hovered = this.hoverRowIndex$.value === this.rows$.value.length - 1;
const dragging = this.rowDragging$.value;
return html` <div
data-testid="add-row-button"
class="${classMap({
[addRowButtonStyle]: true,
active: dragging,
@@ -180,16 +180,14 @@ export class TableCell extends SignalWatcher(
name: 'Insert Left',
prefix: InsertLeftIcon(),
select: () => {
this.dataManager.insertColumn(
columnIndex > 0 ? columnIndex - 1 : undefined
);
this.dataManager.insertColumn(columnIndex - 1);
},
}),
menu.action({
name: 'Insert Right',
prefix: InsertRightIcon(),
select: () => {
this.dataManager.insertColumn(columnIndex);
this.dataManager.insertColumn(columnIndex + 1);
},
}),
menu.action({
@@ -306,16 +304,14 @@ export class TableCell extends SignalWatcher(
name: 'Insert Above',
prefix: InsertAboveIcon(),
select: () => {
this.dataManager.insertRow(
rowIndex > 0 ? rowIndex - 1 : undefined
);
this.dataManager.insertRow(rowIndex - 1);
},
}),
menu.action({
name: 'Insert Below',
prefix: InsertBelowIcon(),
select: () => {
this.dataManager.insertRow(rowIndex);
this.dataManager.insertRow(rowIndex + 1);
},
}),
menu.action({
@@ -458,7 +454,6 @@ export class TableCell extends SignalWatcher(
};
return html`<div class=${columnOptionsCellStyle}>
<div
data-testid="drag-column-handle"
data-drag-column-id=${column.columnId}
class=${classMap({
[columnOptionsStyle]: true,
@@ -483,7 +478,6 @@ export class TableCell extends SignalWatcher(
};
return html`<div class=${rowOptionsCellStyle}>
<div
data-testid="drag-row-handle"
data-drag-row-id=${row.rowId}
class=${classMap({
[rowOptionsStyle]: true,
+2 -1
View File
@@ -34,7 +34,7 @@
"lit-html": "^3.2.1",
"lodash.clonedeep": "^4.5.0",
"remark-math": "^6.0.0",
"shiki": "^3.0.0",
"shiki": "^2.0.0",
"yjs": "^13.6.21",
"zod": "^3.23.8"
},
@@ -55,6 +55,7 @@
"./date-picker": "./src/date-picker/index.ts",
"./drop-indicator": "./src/drop-indicator/index.ts",
"./filterable-list": "./src/filterable-list/index.ts",
"./virtual-keyboard": "./src/virtual-keyboard/index.ts",
"./toggle-button": "./src/toggle-button/index.ts",
"./toggle-switch": "./src/toggle-switch/index.ts",
"./notification": "./src/notification/index.ts",
@@ -1,5 +1,5 @@
import type { ColorScheme, Palette } from '@blocksuite/affine-model';
import { DefaultTheme, resolveColor } from '@blocksuite/affine-model';
import { resolveColor } from '@blocksuite/affine-model';
import type { ColorEvent } from '@blocksuite/affine-shared/utils';
import { WithDisposable } from '@blocksuite/global/utils';
import { html, LitElement } from 'lit';
@@ -188,7 +188,7 @@ export class EdgelessColorPickerButton extends WithDisposable(LitElement) {
accessor menuButton!: EditorMenuButton;
@property({ attribute: false })
accessor palettes: Palette[] = DefaultTheme.Palettes;
accessor palettes: Palette[] = [];
@property({ attribute: false })
accessor pick!: (event: PickColorEvent) => void;

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