mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-04 16:44:56 +00:00
Compare commits
2 Commits
v0.21.6
...
fix-toc-se
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5526696357 | ||
|
|
5be0292536 |
@@ -5,7 +5,7 @@ rustflags = ["-C", "target-feature=+crt-static"]
|
||||
[target.'cfg(target_os = "linux")']
|
||||
rustflags = ["-C", "link-args=-Wl,--warn-unresolved-symbols"]
|
||||
[target.'cfg(target_os = "macos")']
|
||||
rustflags = ["-C", "link-args=-Wl,-undefined,dynamic_lookup,-no_fixup_chains", "-C", "link-args=-all_load", "-C", "link-args=-weak_framework ScreenCaptureKit"]
|
||||
rustflags = ["-C", "link-args=-all_load", "-C", "link-args=-weak_framework ScreenCaptureKit"]
|
||||
# https://sourceware.org/bugzilla/show_bug.cgi?id=21032
|
||||
# https://sourceware.org/bugzilla/show_bug.cgi?id=21031
|
||||
# https://github.com/rust-lang/rust/issues/134820
|
||||
|
||||
@@ -44,7 +44,7 @@ services:
|
||||
|
||||
redis:
|
||||
image: redis
|
||||
container_name: affine_redis
|
||||
container_name: redis
|
||||
healthcheck:
|
||||
test: ['CMD', 'redis-cli', '--raw', 'incr', 'ping']
|
||||
interval: 10s
|
||||
@@ -54,7 +54,7 @@ services:
|
||||
|
||||
postgres:
|
||||
image: postgres:16
|
||||
container_name: affine_postgres
|
||||
container_name: postgres
|
||||
volumes:
|
||||
- ${DB_DATA_LOCATION}:/var/lib/postgresql/data
|
||||
environment:
|
||||
|
||||
@@ -200,6 +200,11 @@
|
||||
"type": "object",
|
||||
"description": "Configuration for mailer module",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"description": "Whether enabled mail service.\n@default false",
|
||||
"default": false
|
||||
},
|
||||
"SMTP.host": {
|
||||
"type": "string",
|
||||
"description": "Host of the email server (e.g. smtp.gmail.com)\n@default \"\"\n@environment `MAILER_HOST`",
|
||||
|
||||
39
.github/workflows/build-test.yml
vendored
39
.github/workflows/build-test.yml
vendored
@@ -218,43 +218,7 @@ jobs:
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-results-e2e-bs-${{ matrix.shard }}
|
||||
path: ./test-results
|
||||
if-no-files-found: ignore
|
||||
|
||||
e2e-blocksuite-cross-browser-test:
|
||||
name: E2E BlockSuite Cross Browser Test
|
||||
runs-on: ubuntu-latest
|
||||
needs: optimize_ci
|
||||
if: needs.optimize_ci.outputs.skip == 'false'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shard: [1, 2]
|
||||
browser: ['chromium', 'firefox', 'webkit']
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
playwright-install: true
|
||||
playwright-platform: ${{ matrix.browser }}
|
||||
electron-install: false
|
||||
full-cache: true
|
||||
|
||||
- name: Run playground build
|
||||
run: yarn workspace @blocksuite/playground build
|
||||
|
||||
- name: Run playwright tests
|
||||
env:
|
||||
BROWSER: ${{ matrix.browser }}
|
||||
run: yarn workspace @affine-test/blocksuite test "cross-platform/" --forbid-only --shard=${{ matrix.shard }}/${{ strategy.job-total }}
|
||||
|
||||
- name: Upload test results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-results-e2e-bs-cross-browser-${{ matrix.browser }}-${{ matrix.shard }}
|
||||
name: test-results-e2e-legacy-bs-${{ matrix.shard }}
|
||||
path: ./test-results
|
||||
if-no-files-found: ignore
|
||||
|
||||
@@ -1177,7 +1141,6 @@ jobs:
|
||||
- check-yarn-binary
|
||||
- e2e-test
|
||||
- e2e-blocksuite-test
|
||||
- e2e-blocksuite-cross-browser-test
|
||||
- e2e-mobile-test
|
||||
- unit-test
|
||||
- build-native
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -85,6 +85,3 @@ packages/frontend/core/public/static/templates
|
||||
af
|
||||
af.cmd
|
||||
*.resolved
|
||||
|
||||
# playwright
|
||||
storageState.json
|
||||
|
||||
5
.vscode/launch.template.json
vendored
5
.vscode/launch.template.json
vendored
@@ -29,7 +29,10 @@
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "Debug AFFiNE Web",
|
||||
"url": "http://localhost:8080"
|
||||
"url": "http://localhost:8080",
|
||||
"sourceMapPathOverrides": {
|
||||
"webpack://affine/blocksuite/*": "${workspaceFolder}/blocksuite/*"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -12,4 +12,4 @@ npmPublishAccess: public
|
||||
|
||||
npmPublishRegistry: "https://registry.npmjs.org"
|
||||
|
||||
yarnPath: .yarn/releases/yarn-4.9.1.cjs
|
||||
yarnPath: .yarn/releases/yarn-4.9.0.cjs
|
||||
|
||||
113
Cargo.lock
generated
113
Cargo.lock
generated
@@ -79,7 +79,6 @@ dependencies = [
|
||||
"block2",
|
||||
"core-foundation",
|
||||
"coreaudio-rs",
|
||||
"criterion2",
|
||||
"dispatch2",
|
||||
"libc",
|
||||
"napi",
|
||||
@@ -287,9 +286,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.98"
|
||||
version = "1.0.97"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
|
||||
checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f"
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
@@ -514,9 +513,9 @@ checksum = "4848ed5727d39a7573551c205bcb1ccd88c8cad4ed2c80f62e2316f208196b8d"
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.12.0"
|
||||
version = "1.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4"
|
||||
checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"regex-automata 0.4.9",
|
||||
@@ -602,9 +601,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.19"
|
||||
version = "1.2.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362"
|
||||
checksum = "525046617d8376e3db1deffb079e91cef90a89fc3ca5c185bbf8c9ecdd15cd5c"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
@@ -720,9 +719,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.36"
|
||||
version = "4.5.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2df961d8c8a0d08aa9945718ccf584145eee3f3aa06cddbeac12933781102e04"
|
||||
checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -730,9 +729,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.36"
|
||||
version = "4.5.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "132dbda40fb6753878316a489d5a1242a8ef2f0d9e47ba01c951ea8aa7d013a5"
|
||||
checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -1208,9 +1207,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.11"
|
||||
version = "0.3.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e"
|
||||
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.59.0",
|
||||
@@ -1358,9 +1357,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "four-char-code"
|
||||
version = "2.3.0"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42da99970737c0150e3c5cd1cdc510735a2511739f5c3aa3c6bfc9f31441488d"
|
||||
checksum = "c661315fd366b2a1f970df7b7cb1a28d2678d49ef4872f7dcc19b4a83150f20b"
|
||||
|
||||
[[package]]
|
||||
name = "fs-err"
|
||||
@@ -1530,9 +1529,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "2.6.0"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9"
|
||||
checksum = "7db2ff139bba50379da6aa0766b52fdcb62cb5b263009b09ed58ba604e14bbd1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crunchy",
|
||||
@@ -1845,9 +1844,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.9.0"
|
||||
version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
|
||||
checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.15.2",
|
||||
@@ -1976,9 +1975,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.172"
|
||||
version = "0.2.171"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
||||
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
@@ -1987,7 +1986,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-targets 0.52.6",
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2019,9 +2018,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.9.4"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
|
||||
checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413"
|
||||
|
||||
[[package]]
|
||||
name = "litemap"
|
||||
@@ -2185,9 +2184,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.8"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a"
|
||||
checksum = "ff70ce3e48ae43fa075863cef62e8b43b71a4f2382229920e0df362592919430"
|
||||
dependencies = [
|
||||
"adler2",
|
||||
]
|
||||
@@ -2561,7 +2560,7 @@ checksum = "1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42"
|
||||
[[package]]
|
||||
name = "pdf-extract"
|
||||
version = "0.8.2"
|
||||
source = "git+https://github.com/toeverything/pdf-extract?branch=darksky%2Fimprove-font-decoding#e74beed894e1b8dc228c2bf078ed92814b27759f"
|
||||
source = "git+https://github.com/toeverything/pdf-extract#49ef7d2aec5bb495467a40082cd9717e849ee29a"
|
||||
dependencies = [
|
||||
"adobe-cmap-parser",
|
||||
"cff-parser",
|
||||
@@ -2897,9 +2896,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.11"
|
||||
version = "0.5.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3"
|
||||
checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
]
|
||||
@@ -3090,9 +3089,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.23.26"
|
||||
version = "0.23.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0"
|
||||
checksum = "822ee9188ac4ec04a2f0531e55d035fb2de73f18b41a63c70c2712503b6fb13c"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"ring",
|
||||
@@ -3102,6 +3101,15 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pemfile"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
|
||||
dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pki-types"
|
||||
version = "1.11.0"
|
||||
@@ -3179,9 +3187,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "scroll_derive"
|
||||
version = "0.12.1"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1783eabc414609e28a5ba76aee5ddd52199f7107a0b24c2e9746a1ecc34a683d"
|
||||
checksum = "7f81c2fde025af7e69b1d1420531c8a8811ca898919db177141a85313b1cb932"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3331,9 +3339,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.0"
|
||||
version = "1.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9"
|
||||
checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@@ -3385,9 +3393,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx"
|
||||
version = "0.8.4"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14e22987355fbf8cfb813a0cf8cd97b1b4ec834b94dbd759a9e8679d41fabe83"
|
||||
checksum = "4410e73b3c0d8442c5f99b425d7a435b5ee0ae4167b3196771dd3f7a01be745f"
|
||||
dependencies = [
|
||||
"sqlx-core",
|
||||
"sqlx-macros",
|
||||
@@ -3398,11 +3406,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-core"
|
||||
version = "0.8.4"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55c4720d7d4cd3d5b00f61d03751c685ad09c33ae8290c8a2c11335e0604300b"
|
||||
checksum = "6a007b6936676aa9ab40207cde35daab0a04b823be8ae004368c0793b96a61e0"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"crc",
|
||||
@@ -3421,6 +3428,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"rustls",
|
||||
"rustls-pemfile",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
@@ -3435,9 +3443,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-macros"
|
||||
version = "0.8.4"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "175147fcb75f353ac7675509bc58abb2cb291caf0fd24a3623b8f7e3eb0a754b"
|
||||
checksum = "3112e2ad78643fef903618d78cf0aec1cb3134b019730edb039b69eaf531f310"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3448,9 +3456,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-macros-core"
|
||||
version = "0.8.4"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cde983058e53bfa75998e1982086c5efe3c370f3250bf0357e344fa3352e32b"
|
||||
checksum = "4e9f90acc5ab146a99bf5061a7eb4976b573f560bc898ef3bf8435448dd5e7ad"
|
||||
dependencies = [
|
||||
"dotenvy",
|
||||
"either",
|
||||
@@ -3474,9 +3482,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-mysql"
|
||||
version = "0.8.4"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "847d2e5393a4f39e47e4f36cab419709bc2b83cbe4223c60e86e1471655be333"
|
||||
checksum = "4560278f0e00ce64938540546f59f590d60beee33fffbd3b9cd47851e5fff233"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"base64 0.22.1",
|
||||
@@ -3517,9 +3525,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-postgres"
|
||||
version = "0.8.4"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc35947a541b9e0a2e3d85da444f1c4137c13040267141b208395a0d0ca4659f"
|
||||
checksum = "c5b98a57f363ed6764d5b3a12bfedf62f07aa16e1856a7ddc2a0bb190a959613"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"base64 0.22.1",
|
||||
@@ -3555,9 +3563,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-sqlite"
|
||||
version = "0.8.4"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c48291dac4e5ed32da0927a0b981788be65674aeb62666d19873ab4289febde"
|
||||
checksum = "f85ca71d3a5b24e64e1d08dd8fe36c6c95c339a896cc33068148906784620540"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"chrono",
|
||||
@@ -3573,7 +3581,6 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_urlencoded",
|
||||
"sqlx-core",
|
||||
"thiserror 2.0.12",
|
||||
"tracing",
|
||||
"url",
|
||||
]
|
||||
@@ -5051,9 +5058,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.6"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10"
|
||||
checksum = "0e97b544156e9bebe1a0ffbc03484fc1ffe3100cbce3ffb17eac35f7cdd7ab36"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
@@ -40,7 +40,7 @@ objc2-foundation = "0.3"
|
||||
once_cell = "1"
|
||||
parking_lot = "0.12"
|
||||
path-ext = "0.1.1"
|
||||
pdf-extract = { git = "https://github.com/toeverything/pdf-extract", branch = "darksky/improve-font-decoding" }
|
||||
pdf-extract = { git = "https://github.com/toeverything/pdf-extract" }
|
||||
rand = "0.9"
|
||||
rayon = "1.10"
|
||||
readability = { version = "0.3.0", default-features = false }
|
||||
|
||||
@@ -55,8 +55,6 @@
|
||||
"@blocksuite/affine-widget-edgeless-auto-connect": "workspace:*",
|
||||
"@blocksuite/affine-widget-edgeless-toolbar": "workspace:*",
|
||||
"@blocksuite/affine-widget-frame-title": "workspace:*",
|
||||
"@blocksuite/affine-widget-keyboard-toolbar": "workspace:*",
|
||||
"@blocksuite/affine-widget-linked-doc": "workspace:*",
|
||||
"@blocksuite/affine-widget-remote-selection": "workspace:*",
|
||||
"@blocksuite/affine-widget-scroll-anchoring": "workspace:*",
|
||||
"@blocksuite/affine-widget-slash-menu": "workspace:*",
|
||||
@@ -117,12 +115,10 @@
|
||||
"./widgets/edgeless-auto-connect": "./src/widgets/edgeless-auto-connect.ts",
|
||||
"./widgets/edgeless-toolbar": "./src/widgets/edgeless-toolbar.ts",
|
||||
"./widgets/frame-title": "./src/widgets/frame-title.ts",
|
||||
"./widgets/linked-doc": "./src/widgets/linked-doc.ts",
|
||||
"./widgets/remote-selection": "./src/widgets/remote-selection.ts",
|
||||
"./widgets/scroll-anchoring": "./src/widgets/scroll-anchoring.ts",
|
||||
"./widgets/slash-menu": "./src/widgets/slash-menu.ts",
|
||||
"./widgets/toolbar": "./src/widgets/toolbar.ts",
|
||||
"./widgets/keyboard-toolbar": "./src/widgets/keyboard-toolbar.ts",
|
||||
"./fragments/doc-title": "./src/fragments/doc-title.ts",
|
||||
"./fragments/frame-panel": "./src/fragments/frame-panel.ts",
|
||||
"./fragments/outline": "./src/fragments/outline.ts",
|
||||
|
||||
@@ -2448,262 +2448,203 @@ World!
|
||||
});
|
||||
|
||||
describe('markdown to snapshot', () => {
|
||||
describe('code', () => {
|
||||
test('markdown code block', async () => {
|
||||
const markdown = '```python\nimport this\n```\n';
|
||||
test('code', async () => {
|
||||
const markdown = '```python\nimport this\n```\n';
|
||||
|
||||
const blockSnapshot: BlockSnapshot = {
|
||||
type: 'block',
|
||||
id: 'matchesReplaceMap[0]',
|
||||
flavour: 'affine:note',
|
||||
props: {
|
||||
xywh: '[0,0,800,95]',
|
||||
background: DefaultTheme.noteBackgrounColor,
|
||||
index: 'a0',
|
||||
hidden: false,
|
||||
displayMode: NoteDisplayMode.DocAndEdgeless,
|
||||
const blockSnapshot: BlockSnapshot = {
|
||||
type: 'block',
|
||||
id: 'matchesReplaceMap[0]',
|
||||
flavour: 'affine:note',
|
||||
props: {
|
||||
xywh: '[0,0,800,95]',
|
||||
background: DefaultTheme.noteBackgrounColor,
|
||||
index: 'a0',
|
||||
hidden: false,
|
||||
displayMode: NoteDisplayMode.DocAndEdgeless,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
type: 'block',
|
||||
id: 'matchesReplaceMap[1]',
|
||||
flavour: 'affine:code',
|
||||
props: {
|
||||
language: 'python',
|
||||
wrap: false,
|
||||
text: {
|
||||
'$blocksuite:internal:text$': true,
|
||||
delta: [
|
||||
{
|
||||
insert: 'import this',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
children: [
|
||||
{
|
||||
type: 'block',
|
||||
id: 'matchesReplaceMap[1]',
|
||||
flavour: 'affine:code',
|
||||
props: {
|
||||
language: 'python',
|
||||
wrap: false,
|
||||
text: {
|
||||
'$blocksuite:internal:text$': true,
|
||||
delta: [
|
||||
{
|
||||
insert: 'import this',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
};
|
||||
],
|
||||
};
|
||||
|
||||
const mdAdapter = new MarkdownAdapter(createJob(), provider);
|
||||
const rawBlockSnapshot = await mdAdapter.toBlockSnapshot({
|
||||
file: markdown,
|
||||
});
|
||||
expect(nanoidReplacement(rawBlockSnapshot)).toEqual(blockSnapshot);
|
||||
const mdAdapter = new MarkdownAdapter(createJob(), provider);
|
||||
const rawBlockSnapshot = await mdAdapter.toBlockSnapshot({
|
||||
file: markdown,
|
||||
});
|
||||
expect(nanoidReplacement(rawBlockSnapshot)).toEqual(blockSnapshot);
|
||||
});
|
||||
|
||||
test('code with indentation 1 - slice', async () => {
|
||||
const markdown = '```python\n import this\n```';
|
||||
test('code with indentation 1 - slice', async () => {
|
||||
const markdown = '```python\n import this\n```';
|
||||
|
||||
const sliceSnapshot: SliceSnapshot = {
|
||||
type: 'slice',
|
||||
content: [
|
||||
{
|
||||
type: 'block',
|
||||
id: 'matchesReplaceMap[0]',
|
||||
flavour: 'affine:note',
|
||||
props: {
|
||||
xywh: '[0,0,800,95]',
|
||||
background: DefaultTheme.noteBackgrounColor,
|
||||
index: 'a0',
|
||||
hidden: false,
|
||||
displayMode: 'both',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
type: 'block',
|
||||
id: 'matchesReplaceMap[1]',
|
||||
flavour: 'affine:code',
|
||||
props: {
|
||||
language: 'python',
|
||||
wrap: false,
|
||||
text: {
|
||||
'$blocksuite:internal:text$': true,
|
||||
delta: [
|
||||
{
|
||||
insert: ' import this',
|
||||
},
|
||||
],
|
||||
},
|
||||
const sliceSnapshot: SliceSnapshot = {
|
||||
type: 'slice',
|
||||
content: [
|
||||
{
|
||||
type: 'block',
|
||||
id: 'matchesReplaceMap[0]',
|
||||
flavour: 'affine:note',
|
||||
props: {
|
||||
xywh: '[0,0,800,95]',
|
||||
background: DefaultTheme.noteBackgrounColor,
|
||||
index: 'a0',
|
||||
hidden: false,
|
||||
displayMode: 'both',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
type: 'block',
|
||||
id: 'matchesReplaceMap[1]',
|
||||
flavour: 'affine:code',
|
||||
props: {
|
||||
language: 'python',
|
||||
wrap: false,
|
||||
text: {
|
||||
'$blocksuite:internal:text$': true,
|
||||
delta: [
|
||||
{
|
||||
insert: ' import this',
|
||||
},
|
||||
],
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
workspaceId: '',
|
||||
pageId: '',
|
||||
};
|
||||
|
||||
const mdAdapter = new MarkdownAdapter(createJob(), provider);
|
||||
const rawSliceSnapshot = await mdAdapter.toSliceSnapshot({
|
||||
file: markdown,
|
||||
workspaceId: '',
|
||||
pageId: '',
|
||||
});
|
||||
expect(nanoidReplacement(rawSliceSnapshot!)).toEqual(sliceSnapshot);
|
||||
});
|
||||
|
||||
test('code with indentation 2 - slice', async () => {
|
||||
const markdown = '````python\n```python\n import this\n```\n````';
|
||||
|
||||
const sliceSnapshot: SliceSnapshot = {
|
||||
type: 'slice',
|
||||
content: [
|
||||
{
|
||||
type: 'block',
|
||||
id: 'matchesReplaceMap[0]',
|
||||
flavour: 'affine:note',
|
||||
props: {
|
||||
xywh: '[0,0,800,95]',
|
||||
background: DefaultTheme.noteBackgrounColor,
|
||||
index: 'a0',
|
||||
hidden: false,
|
||||
displayMode: 'both',
|
||||
children: [],
|
||||
},
|
||||
children: [
|
||||
{
|
||||
type: 'block',
|
||||
id: 'matchesReplaceMap[1]',
|
||||
flavour: 'affine:code',
|
||||
props: {
|
||||
language: 'python',
|
||||
wrap: false,
|
||||
text: {
|
||||
'$blocksuite:internal:text$': true,
|
||||
delta: [
|
||||
{
|
||||
insert: '```python\n import this\n```',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
workspaceId: '',
|
||||
pageId: '',
|
||||
};
|
||||
|
||||
const mdAdapter = new MarkdownAdapter(createJob(), provider);
|
||||
const rawSliceSnapshot = await mdAdapter.toSliceSnapshot({
|
||||
file: markdown,
|
||||
workspaceId: '',
|
||||
pageId: '',
|
||||
});
|
||||
expect(nanoidReplacement(rawSliceSnapshot!)).toEqual(sliceSnapshot);
|
||||
});
|
||||
|
||||
test('code with indentation 3 - slice', async () => {
|
||||
const markdown = '~~~~python\n````python\n import this\n````\n~~~~';
|
||||
|
||||
const sliceSnapshot: SliceSnapshot = {
|
||||
type: 'slice',
|
||||
content: [
|
||||
{
|
||||
type: 'block',
|
||||
id: 'matchesReplaceMap[0]',
|
||||
flavour: 'affine:note',
|
||||
props: {
|
||||
xywh: '[0,0,800,95]',
|
||||
background: DefaultTheme.noteBackgrounColor,
|
||||
index: 'a0',
|
||||
hidden: false,
|
||||
displayMode: 'both',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
type: 'block',
|
||||
id: 'matchesReplaceMap[1]',
|
||||
flavour: 'affine:code',
|
||||
props: {
|
||||
language: 'python',
|
||||
wrap: false,
|
||||
text: {
|
||||
'$blocksuite:internal:text$': true,
|
||||
delta: [
|
||||
{
|
||||
insert: '````python\n import this\n````',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
workspaceId: '',
|
||||
pageId: '',
|
||||
};
|
||||
|
||||
const mdAdapter = new MarkdownAdapter(createJob(), provider);
|
||||
const rawSliceSnapshot = await mdAdapter.toSliceSnapshot({
|
||||
file: markdown,
|
||||
workspaceId: '',
|
||||
pageId: '',
|
||||
});
|
||||
expect(nanoidReplacement(rawSliceSnapshot!)).toEqual(sliceSnapshot);
|
||||
});
|
||||
|
||||
test('html block import as code block', async () => {
|
||||
const markdown = `<div class="container">
|
||||
<header>
|
||||
<h1>Welcome to My Page</h1>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="#home">Home</a></li>
|
||||
<li><a href="#about">About</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
<main>
|
||||
<p>This is a sample HTML content</p>
|
||||
</main>
|
||||
</div>`;
|
||||
|
||||
const blockSnapshot: BlockSnapshot = {
|
||||
type: 'block',
|
||||
id: 'matchesReplaceMap[0]',
|
||||
flavour: 'affine:note',
|
||||
props: {
|
||||
xywh: '[0,0,800,95]',
|
||||
background: DefaultTheme.noteBackgrounColor,
|
||||
index: 'a0',
|
||||
hidden: false,
|
||||
displayMode: NoteDisplayMode.DocAndEdgeless,
|
||||
],
|
||||
},
|
||||
children: [
|
||||
{
|
||||
type: 'block',
|
||||
id: 'matchesReplaceMap[1]',
|
||||
flavour: 'affine:code',
|
||||
props: {
|
||||
language: 'html',
|
||||
wrap: false,
|
||||
text: {
|
||||
'$blocksuite:internal:text$': true,
|
||||
delta: [
|
||||
{
|
||||
insert:
|
||||
'<div class="container">\n <header>\n <h1>Welcome to My Page</h1>\n <nav>\n <ul>\n <li><a href="#home">Home</a></li>\n <li><a href="#about">About</a></li>\n </ul>\n </nav>\n </header>\n <main>\n <p>This is a sample HTML content</p>\n </main>\n</div>',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
};
|
||||
],
|
||||
workspaceId: '',
|
||||
pageId: '',
|
||||
};
|
||||
|
||||
const mdAdapter = new MarkdownAdapter(createJob(), provider);
|
||||
const rawBlockSnapshot = await mdAdapter.toBlockSnapshot({
|
||||
file: markdown,
|
||||
});
|
||||
expect(nanoidReplacement(rawBlockSnapshot)).toEqual(blockSnapshot);
|
||||
const mdAdapter = new MarkdownAdapter(createJob(), provider);
|
||||
const rawSliceSnapshot = await mdAdapter.toSliceSnapshot({
|
||||
file: markdown,
|
||||
workspaceId: '',
|
||||
pageId: '',
|
||||
});
|
||||
expect(nanoidReplacement(rawSliceSnapshot!)).toEqual(sliceSnapshot);
|
||||
});
|
||||
|
||||
test('code with indentation 2 - slice', async () => {
|
||||
const markdown = '````python\n```python\n import this\n```\n````';
|
||||
|
||||
const sliceSnapshot: SliceSnapshot = {
|
||||
type: 'slice',
|
||||
content: [
|
||||
{
|
||||
type: 'block',
|
||||
id: 'matchesReplaceMap[0]',
|
||||
flavour: 'affine:note',
|
||||
props: {
|
||||
xywh: '[0,0,800,95]',
|
||||
background: DefaultTheme.noteBackgrounColor,
|
||||
index: 'a0',
|
||||
hidden: false,
|
||||
displayMode: 'both',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
type: 'block',
|
||||
id: 'matchesReplaceMap[1]',
|
||||
flavour: 'affine:code',
|
||||
props: {
|
||||
language: 'python',
|
||||
wrap: false,
|
||||
text: {
|
||||
'$blocksuite:internal:text$': true,
|
||||
delta: [
|
||||
{
|
||||
insert: '```python\n import this\n```',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
workspaceId: '',
|
||||
pageId: '',
|
||||
};
|
||||
|
||||
const mdAdapter = new MarkdownAdapter(createJob(), provider);
|
||||
const rawSliceSnapshot = await mdAdapter.toSliceSnapshot({
|
||||
file: markdown,
|
||||
workspaceId: '',
|
||||
pageId: '',
|
||||
});
|
||||
expect(nanoidReplacement(rawSliceSnapshot!)).toEqual(sliceSnapshot);
|
||||
});
|
||||
|
||||
test('code with indentation 3 - slice', async () => {
|
||||
const markdown = '~~~~python\n````python\n import this\n````\n~~~~';
|
||||
|
||||
const sliceSnapshot: SliceSnapshot = {
|
||||
type: 'slice',
|
||||
content: [
|
||||
{
|
||||
type: 'block',
|
||||
id: 'matchesReplaceMap[0]',
|
||||
flavour: 'affine:note',
|
||||
props: {
|
||||
xywh: '[0,0,800,95]',
|
||||
background: DefaultTheme.noteBackgrounColor,
|
||||
index: 'a0',
|
||||
hidden: false,
|
||||
displayMode: 'both',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
type: 'block',
|
||||
id: 'matchesReplaceMap[1]',
|
||||
flavour: 'affine:code',
|
||||
props: {
|
||||
language: 'python',
|
||||
wrap: false,
|
||||
text: {
|
||||
'$blocksuite:internal:text$': true,
|
||||
delta: [
|
||||
{
|
||||
insert: '````python\n import this\n````',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
workspaceId: '',
|
||||
pageId: '',
|
||||
};
|
||||
|
||||
const mdAdapter = new MarkdownAdapter(createJob(), provider);
|
||||
const rawSliceSnapshot = await mdAdapter.toSliceSnapshot({
|
||||
file: markdown,
|
||||
workspaceId: '',
|
||||
pageId: '',
|
||||
});
|
||||
expect(nanoidReplacement(rawSliceSnapshot!)).toEqual(sliceSnapshot);
|
||||
});
|
||||
|
||||
test('paragraph', async () => {
|
||||
@@ -3697,6 +3638,48 @@ bbb
|
||||
expect(nanoidReplacement(rawBlockSnapshot)).toEqual(blockSnapshot);
|
||||
});
|
||||
|
||||
test('html tag', async () => {
|
||||
const markdown = `<aaa>\n`;
|
||||
|
||||
const blockSnapshot: BlockSnapshot = {
|
||||
type: 'block',
|
||||
id: 'matchesReplaceMap[0]',
|
||||
flavour: 'affine:note',
|
||||
props: {
|
||||
xywh: '[0,0,800,95]',
|
||||
background: DefaultTheme.noteBackgrounColor,
|
||||
index: 'a0',
|
||||
hidden: false,
|
||||
displayMode: NoteDisplayMode.DocAndEdgeless,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
type: 'block',
|
||||
id: 'matchesReplaceMap[1]',
|
||||
flavour: 'affine:paragraph',
|
||||
props: {
|
||||
text: {
|
||||
'$blocksuite:internal:text$': true,
|
||||
delta: [
|
||||
{
|
||||
insert: '<aaa>',
|
||||
},
|
||||
],
|
||||
},
|
||||
type: 'text',
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const mdAdapter = new MarkdownAdapter(createJob(), provider);
|
||||
const rawBlockSnapshot = await mdAdapter.toBlockSnapshot({
|
||||
file: markdown,
|
||||
});
|
||||
expect(nanoidReplacement(rawBlockSnapshot)).toEqual(blockSnapshot);
|
||||
});
|
||||
|
||||
describe('inline latex', () => {
|
||||
test.each([
|
||||
['dollar sign syntax', 'inline $E=mc^2$ latex\n'],
|
||||
@@ -4108,55 +4091,4 @@ hhh
|
||||
});
|
||||
expect(nanoidReplacement(rawBlockSnapshot)).toEqual(blockSnapshot);
|
||||
});
|
||||
|
||||
test('should not wrap url with angle brackets if it is not a url', async () => {
|
||||
const markdown = 'prompt: How many people will live in the world in 2040?';
|
||||
const sliceSnapshot: SliceSnapshot = {
|
||||
type: 'slice',
|
||||
content: [
|
||||
{
|
||||
type: 'block',
|
||||
id: 'matchesReplaceMap[0]',
|
||||
flavour: 'affine:note',
|
||||
props: {
|
||||
xywh: '[0,0,800,95]',
|
||||
background: DefaultTheme.noteBackgrounColor,
|
||||
index: 'a0',
|
||||
hidden: false,
|
||||
displayMode: NoteDisplayMode.DocAndEdgeless,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
type: 'block',
|
||||
id: 'matchesReplaceMap[1]',
|
||||
flavour: 'affine:paragraph',
|
||||
props: {
|
||||
type: 'text',
|
||||
text: {
|
||||
'$blocksuite:internal:text$': true,
|
||||
delta: [
|
||||
{
|
||||
insert:
|
||||
'prompt: How many people will live in the world in 2040?',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
workspaceId: '',
|
||||
pageId: '',
|
||||
};
|
||||
|
||||
const mdAdapter = new MarkdownAdapter(createJob(), provider);
|
||||
const rawSliceSnapshot = await mdAdapter.toSliceSnapshot({
|
||||
file: markdown,
|
||||
workspaceId: '',
|
||||
pageId: '',
|
||||
});
|
||||
expect(nanoidReplacement(rawSliceSnapshot!)).toEqual(sliceSnapshot);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export * from '@blocksuite/affine-widget-keyboard-toolbar';
|
||||
@@ -1 +0,0 @@
|
||||
export * from '@blocksuite/affine-widget-linked-doc';
|
||||
@@ -52,8 +52,6 @@
|
||||
{ "path": "../widgets/edgeless-auto-connect" },
|
||||
{ "path": "../widgets/edgeless-toolbar" },
|
||||
{ "path": "../widgets/frame-title" },
|
||||
{ "path": "../widgets/keyboard-toolbar" },
|
||||
{ "path": "../widgets/linked-doc" },
|
||||
{ "path": "../widgets/remote-selection" },
|
||||
{ "path": "../widgets/scroll-anchoring" },
|
||||
{ "path": "../widgets/slash-menu" },
|
||||
|
||||
@@ -28,7 +28,7 @@ import { checkAttachmentBlob, downloadAttachmentBlob } from './utils';
|
||||
|
||||
@Peekable({
|
||||
enableOn: ({ model }: AttachmentBlockComponent) => {
|
||||
return !model.doc.readonly && model.props.type.endsWith('pdf');
|
||||
return model.props.type.endsWith('pdf');
|
||||
},
|
||||
})
|
||||
export class AttachmentBlockComponent extends CaptionedBlockComponent<AttachmentBlockModel> {
|
||||
|
||||
@@ -146,20 +146,17 @@ const embedConfig: AttachmentEmbedConfig[] = [
|
||||
// More options: https://tinytip.co/tips/html-pdf-params/
|
||||
// https://chromium.googlesource.com/chromium/src/+/refs/tags/121.0.6153.1/chrome/browser/resources/pdf/open_pdf_params_parser.ts
|
||||
const parameters = '#toolbar=0';
|
||||
return html`
|
||||
<iframe
|
||||
style="width: 100%; color-scheme: auto;"
|
||||
height="480"
|
||||
src=${blobUrl + parameters}
|
||||
loading="lazy"
|
||||
scrolling="no"
|
||||
frameborder="no"
|
||||
allowTransparency
|
||||
allowfullscreen
|
||||
type="application/pdf"
|
||||
></iframe>
|
||||
<div class="affine-attachment-embed-event-mask"></div>
|
||||
`;
|
||||
return html`<iframe
|
||||
style="width: 100%; color-scheme: auto;"
|
||||
height="480"
|
||||
src=${blobUrl + parameters}
|
||||
loading="lazy"
|
||||
scrolling="no"
|
||||
frameborder="no"
|
||||
allowTransparency
|
||||
allowfullscreen
|
||||
type="application/pdf"
|
||||
></iframe>`;
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -136,9 +136,4 @@ export const styles = css`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.affine-attachment-embed-event-mask {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -81,10 +81,21 @@ export class BookmarkCard extends SignalWatcher(
|
||||
const theme = this.bookmark.std.get(ThemeProvider).theme;
|
||||
const { LoadingIcon, EmbedCardBannerIcon } = getEmbedCardIcons(theme);
|
||||
|
||||
const titleIconType =
|
||||
!icon?.split('.').pop() || icon?.split('.').pop() === 'svg'
|
||||
? 'svg+xml'
|
||||
: icon?.split('.').pop();
|
||||
|
||||
const titleIcon = this.loading
|
||||
? LoadingIcon
|
||||
: icon
|
||||
? html`<img src=${icon} alt="icon" />`
|
||||
? html`<object
|
||||
type="image/${titleIconType}"
|
||||
data=${icon}
|
||||
draggable="false"
|
||||
>
|
||||
${WebIcon16}
|
||||
</object>`
|
||||
: WebIcon16;
|
||||
|
||||
const descriptionText = this.loading
|
||||
@@ -97,7 +108,9 @@ export class BookmarkCard extends SignalWatcher(
|
||||
|
||||
const bannerImage =
|
||||
!this.loading && image
|
||||
? html`<img src=${image} alt="banner" />`
|
||||
? html`<object type="image/webp" data=${image} draggable="false">
|
||||
${EmbedCardBannerIcon}
|
||||
</object>`
|
||||
: EmbedCardBannerIcon;
|
||||
|
||||
return html`
|
||||
|
||||
@@ -21,21 +21,16 @@ export class CalloutBlockComponent extends CaptionedBlockComponent<CalloutBlockM
|
||||
|
||||
.affine-callout-block-container {
|
||||
display: flex;
|
||||
padding: 5px 10px;
|
||||
padding: 12px 16px;
|
||||
border-radius: 8px;
|
||||
background-color: ${unsafeCSSVarV2('block/callout/background/grey')};
|
||||
}
|
||||
|
||||
.affine-callout-emoji-container {
|
||||
margin-right: 10px;
|
||||
margin-top: 14px;
|
||||
margin-right: 12px;
|
||||
margin-top: 10px;
|
||||
user-select: none;
|
||||
font-size: 1.2em;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.affine-callout-emoji:hover {
|
||||
cursor: pointer;
|
||||
@@ -45,7 +40,6 @@ export class CalloutBlockComponent extends CaptionedBlockComponent<CalloutBlockM
|
||||
.affine-callout-children {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding-left: 10px;
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@blocksuite/affine-components": "workspace:*",
|
||||
"@blocksuite/affine-gfx-turbo-renderer": "workspace:*",
|
||||
"@blocksuite/affine-inline-latex": "workspace:*",
|
||||
"@blocksuite/affine-inline-link": "workspace:*",
|
||||
"@blocksuite/affine-inline-preset": "workspace:*",
|
||||
@@ -36,8 +35,7 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./turbo-painter": "./src/turbo/code-painter.worker.ts"
|
||||
"./effects": "./src/effects.ts"
|
||||
},
|
||||
"files": [
|
||||
"src",
|
||||
|
||||
@@ -3,51 +3,25 @@ import {
|
||||
BlockMarkdownAdapterExtension,
|
||||
type BlockMarkdownAdapterMatcher,
|
||||
CODE_BLOCK_WRAP_KEY,
|
||||
IN_PARAGRAPH_NODE_CONTEXT_KEY,
|
||||
type MarkdownAST,
|
||||
} from '@blocksuite/affine-shared/adapters';
|
||||
import type { DeltaInsert } from '@blocksuite/store';
|
||||
import { nanoid } from '@blocksuite/store';
|
||||
import type { Code, Html } from 'mdast';
|
||||
import type { Code } from 'mdast';
|
||||
|
||||
const isCodeNode = (node: MarkdownAST): node is Code => node.type === 'code';
|
||||
const isHtmlNode = (node: MarkdownAST): node is Html => node.type === 'html';
|
||||
|
||||
const isCodeOrHtmlNode = (node: MarkdownAST): node is Code | Html =>
|
||||
isCodeNode(node) || isHtmlNode(node);
|
||||
|
||||
export const codeBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMatcher = {
|
||||
flavour: CodeBlockSchema.model.flavour,
|
||||
toMatch: o => isCodeOrHtmlNode(o.node),
|
||||
toMatch: o => isCodeNode(o.node),
|
||||
fromMatch: o => o.node.flavour === 'affine:code',
|
||||
toBlockSnapshot: {
|
||||
enter: (o, context) => {
|
||||
if (!isCodeOrHtmlNode(o.node)) {
|
||||
if (!isCodeNode(o.node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { walkerContext, configs } = context;
|
||||
const wrap = configs.get(CODE_BLOCK_WRAP_KEY) === 'true';
|
||||
let language = 'plain text';
|
||||
switch (o.node.type) {
|
||||
case 'code': {
|
||||
if (o.node.lang) {
|
||||
language = o.node.lang;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'html': {
|
||||
const inParagraphNode = !!walkerContext.getGlobalContext(
|
||||
IN_PARAGRAPH_NODE_CONTEXT_KEY
|
||||
);
|
||||
// only handle top level html node
|
||||
if (inParagraphNode) {
|
||||
return;
|
||||
}
|
||||
language = 'html';
|
||||
break;
|
||||
}
|
||||
}
|
||||
walkerContext
|
||||
.openNode(
|
||||
{
|
||||
@@ -55,7 +29,7 @@ export const codeBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMatcher = {
|
||||
id: nanoid(),
|
||||
flavour: 'affine:code',
|
||||
props: {
|
||||
language,
|
||||
language: o.node.lang ?? 'Plain Text',
|
||||
wrap,
|
||||
text: {
|
||||
'$blocksuite:internal:text$': true,
|
||||
|
||||
@@ -2,7 +2,6 @@ import {
|
||||
type MarkdownAdapterPreprocessor,
|
||||
MarkdownPreprocessorExtension,
|
||||
} from '@blocksuite/affine-shared/adapters';
|
||||
import { isValidUrl } from '@blocksuite/affine-shared/utils';
|
||||
|
||||
const codePreprocessor: MarkdownAdapterPreprocessor = {
|
||||
name: 'code',
|
||||
@@ -54,9 +53,14 @@ const codePreprocessor: MarkdownAdapterPreprocessor = {
|
||||
//
|
||||
// eg. /MuawcBMT1Mzvoar09-_66?mode=page&blockIds=rL2_GXbtLU2SsJVfCSmh_
|
||||
// https://www.markdownguide.org/basic-syntax/#urls-and-email-addresses
|
||||
const valid = isValidUrl(trimmedLine);
|
||||
if (valid) {
|
||||
return `<${trimmedLine}>`;
|
||||
try {
|
||||
const valid =
|
||||
URL.canParse?.(trimmedLine) ?? Boolean(new URL(trimmedLine));
|
||||
if (valid) {
|
||||
return `<${trimmedLine}>`;
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,173 +0,0 @@
|
||||
import { deleteTextCommand } from '@blocksuite/affine-inline-preset';
|
||||
import {
|
||||
HtmlAdapter,
|
||||
pasteMiddleware,
|
||||
PlainTextAdapter,
|
||||
} from '@blocksuite/affine-shared/adapters';
|
||||
import {
|
||||
getBlockIndexCommand,
|
||||
getBlockSelectionsCommand,
|
||||
getTextSelectionCommand,
|
||||
} from '@blocksuite/affine-shared/commands';
|
||||
import { type Container, createIdentifier } from '@blocksuite/global/di';
|
||||
import { DisposableGroup } from '@blocksuite/global/disposable';
|
||||
import {
|
||||
type BlockStdScope,
|
||||
Clipboard,
|
||||
type ClipboardAdapterConfig,
|
||||
LifeCycleWatcher,
|
||||
LifeCycleWatcherIdentifier,
|
||||
StdIdentifier,
|
||||
type UIEventHandler,
|
||||
} from '@blocksuite/std';
|
||||
import type { ExtensionType } from '@blocksuite/store';
|
||||
|
||||
export const CodeClipboardAdapterConfigIdentifier =
|
||||
createIdentifier<ClipboardAdapterConfig>('code-clipboard-adapter-config');
|
||||
|
||||
export function CodeClipboardAdapterConfigExtension(
|
||||
config: ClipboardAdapterConfig
|
||||
): ExtensionType {
|
||||
return {
|
||||
setup: di => {
|
||||
di.addImpl(
|
||||
CodeClipboardAdapterConfigIdentifier(config.mimeType),
|
||||
() => config
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const PlainTextClipboardConfig = CodeClipboardAdapterConfigExtension({
|
||||
mimeType: 'text/plain',
|
||||
adapter: PlainTextAdapter,
|
||||
priority: 90,
|
||||
});
|
||||
|
||||
const HtmlClipboardConfig = CodeClipboardAdapterConfigExtension({
|
||||
mimeType: 'text/html',
|
||||
adapter: HtmlAdapter,
|
||||
priority: 80,
|
||||
});
|
||||
|
||||
export class CodeBlockClipboard extends Clipboard {
|
||||
static override readonly key = 'code-block-clipboard';
|
||||
|
||||
override get _adapters() {
|
||||
const adapterConfigs = this.std.provider.getAll(
|
||||
CodeClipboardAdapterConfigIdentifier
|
||||
);
|
||||
return Array.from(adapterConfigs.values());
|
||||
}
|
||||
}
|
||||
|
||||
export class CodeBlockClipboardController extends LifeCycleWatcher {
|
||||
static override key = 'code-block-clipboard-controller';
|
||||
|
||||
private readonly _disposables = new DisposableGroup();
|
||||
|
||||
constructor(
|
||||
std: BlockStdScope,
|
||||
readonly clipboard: CodeBlockClipboard
|
||||
) {
|
||||
super(std);
|
||||
}
|
||||
|
||||
static override setup(di: Container) {
|
||||
di.add(
|
||||
this as unknown as {
|
||||
new (
|
||||
std: BlockStdScope,
|
||||
clipboard: CodeBlockClipboard
|
||||
): CodeBlockClipboardController;
|
||||
},
|
||||
[StdIdentifier, CodeBlockClipboard]
|
||||
);
|
||||
di.addImpl(LifeCycleWatcherIdentifier(this.key), provider =>
|
||||
provider.get(this)
|
||||
);
|
||||
}
|
||||
|
||||
protected _init = () => {
|
||||
const paste = pasteMiddleware(this.std);
|
||||
this.clipboard.use(paste);
|
||||
|
||||
this._disposables.add({
|
||||
dispose: () => {
|
||||
this.clipboard.unuse(paste);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
onPaste: UIEventHandler = ctx => {
|
||||
const e = ctx.get('clipboardState').raw;
|
||||
e.preventDefault();
|
||||
|
||||
this.std.store.captureSync();
|
||||
this.std.command
|
||||
.chain()
|
||||
.try(cmd => [
|
||||
cmd.pipe(getTextSelectionCommand).pipe((ctx, next) => {
|
||||
const textSelection = ctx.currentTextSelection;
|
||||
if (!textSelection) return;
|
||||
const end = textSelection.to ?? textSelection.from;
|
||||
next({ currentSelectionPath: end.blockId });
|
||||
}),
|
||||
cmd.pipe(getBlockSelectionsCommand).pipe((ctx, next) => {
|
||||
const currentBlockSelections = ctx.currentBlockSelections;
|
||||
if (!currentBlockSelections) return;
|
||||
const blockSelection = currentBlockSelections.at(-1);
|
||||
if (!blockSelection) return;
|
||||
next({ currentSelectionPath: blockSelection.blockId });
|
||||
}),
|
||||
])
|
||||
.pipe(getBlockIndexCommand)
|
||||
.try(cmd => [cmd.pipe(getTextSelectionCommand).pipe(deleteTextCommand)])
|
||||
.pipe((ctx, next) => {
|
||||
if (!ctx.parentBlock) {
|
||||
return;
|
||||
}
|
||||
this.clipboard
|
||||
.paste(
|
||||
e,
|
||||
this.std.store,
|
||||
ctx.parentBlock.model.id,
|
||||
ctx.blockIndex ? ctx.blockIndex + 1 : 1
|
||||
)
|
||||
.catch(console.error);
|
||||
|
||||
return next();
|
||||
})
|
||||
.run();
|
||||
return true;
|
||||
};
|
||||
|
||||
override mounted() {
|
||||
this._init();
|
||||
|
||||
// add paste event listener for code block
|
||||
const subscription = this.std.view.viewUpdated.subscribe(
|
||||
({ type, method, view }) => {
|
||||
if (type !== 'block' || view.model.flavour !== 'affine:code') return;
|
||||
|
||||
if (method === 'add') {
|
||||
view.handleEvent('paste', this.onPaste);
|
||||
}
|
||||
}
|
||||
);
|
||||
this._disposables.add(subscription);
|
||||
}
|
||||
|
||||
override unmounted() {
|
||||
this._disposables.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export function getCodeClipboardExtensions(): ExtensionType[] {
|
||||
return [
|
||||
PlainTextClipboardConfig,
|
||||
HtmlClipboardConfig,
|
||||
CodeBlockClipboard,
|
||||
CodeBlockClipboardController,
|
||||
];
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import type { ExtensionType } from '@blocksuite/store';
|
||||
import { literal, unsafeStatic } from 'lit/static-html.js';
|
||||
|
||||
import { CodeBlockAdapterExtensions } from './adapters/extension.js';
|
||||
import { getCodeClipboardExtensions } from './clipboard/index.js';
|
||||
import {
|
||||
CodeBlockInlineManagerExtension,
|
||||
CodeBlockUnitSpecExtension,
|
||||
@@ -34,5 +33,4 @@ export const CodeBlockSpec: ExtensionType[] = [
|
||||
CodeBlockAdapterExtensions,
|
||||
SlashMenuConfigExtension('affine:code', codeSlashMenuConfig),
|
||||
CodeKeymapExtension,
|
||||
...getCodeClipboardExtensions(),
|
||||
].flat();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { affineTextStyles } from '@blocksuite/affine-shared/styles';
|
||||
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
|
||||
import { ShadowlessElement } from '@blocksuite/std';
|
||||
import { ZERO_WIDTH_FOR_EMPTY_LINE } from '@blocksuite/std/inline';
|
||||
import { ZERO_WIDTH_SPACE } from '@blocksuite/std/inline';
|
||||
import type { DeltaInsert } from '@blocksuite/store';
|
||||
import { html } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
@@ -111,7 +111,7 @@ export class AffineCodeUnit extends ShadowlessElement {
|
||||
|
||||
@property({ type: Object })
|
||||
accessor delta: DeltaInsert<AffineTextAttributes> = {
|
||||
insert: ZERO_WIDTH_FOR_EMPTY_LINE,
|
||||
insert: ZERO_WIDTH_SPACE,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
export * from './adapters';
|
||||
export * from './clipboard';
|
||||
export * from './code-block';
|
||||
export * from './code-block-config';
|
||||
export * from './code-block-spec';
|
||||
export * from './code-toolbar';
|
||||
export * from './turbo/code-layout-handler';
|
||||
export * from './turbo/code-painter.worker';
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
import type { Rect } from '@blocksuite/affine-gfx-turbo-renderer';
|
||||
import {
|
||||
BlockLayoutHandlerExtension,
|
||||
BlockLayoutHandlersIdentifier,
|
||||
} from '@blocksuite/affine-gfx-turbo-renderer';
|
||||
import type { Container } from '@blocksuite/global/di';
|
||||
import type { EditorHost, GfxBlockComponent } from '@blocksuite/std';
|
||||
import { clientToModelCoord, type ViewportRecord } from '@blocksuite/std/gfx';
|
||||
import type { BlockModel } from '@blocksuite/store';
|
||||
|
||||
import type { CodeLayout } from './code-painter.worker';
|
||||
|
||||
export class CodeLayoutHandlerExtension extends BlockLayoutHandlerExtension<CodeLayout> {
|
||||
readonly blockType = 'affine:code';
|
||||
|
||||
static override setup(di: Container) {
|
||||
di.addImpl(
|
||||
BlockLayoutHandlersIdentifier('code'),
|
||||
CodeLayoutHandlerExtension
|
||||
);
|
||||
}
|
||||
|
||||
override queryLayout(
|
||||
model: BlockModel,
|
||||
host: EditorHost,
|
||||
viewportRecord: ViewportRecord
|
||||
): CodeLayout | null {
|
||||
const component = host.std.view.getBlock(model.id) as GfxBlockComponent;
|
||||
if (!component) return null;
|
||||
|
||||
const codeBlockElement = component.querySelector(
|
||||
'.affine-code-block-container'
|
||||
);
|
||||
if (!codeBlockElement) return null;
|
||||
|
||||
const { zoom, viewScale } = viewportRecord;
|
||||
const codeLayout: CodeLayout = {
|
||||
type: 'affine:code',
|
||||
blockId: model.id,
|
||||
rect: { x: 0, y: 0, w: 0, h: 0 },
|
||||
};
|
||||
|
||||
// Get the bounding rect of the code block
|
||||
const clientRect = codeBlockElement.getBoundingClientRect();
|
||||
if (!clientRect) return null;
|
||||
|
||||
// Convert client coordinates to model coordinates
|
||||
const [modelX, modelY] = clientToModelCoord(viewportRecord, [
|
||||
clientRect.x,
|
||||
clientRect.y,
|
||||
]);
|
||||
|
||||
codeLayout.rect = {
|
||||
x: modelX,
|
||||
y: modelY,
|
||||
w: clientRect.width / zoom / viewScale,
|
||||
h: clientRect.height / zoom / viewScale,
|
||||
};
|
||||
|
||||
return codeLayout;
|
||||
}
|
||||
|
||||
calculateBound(layout: CodeLayout) {
|
||||
const rect: Rect = layout.rect;
|
||||
|
||||
return {
|
||||
rect,
|
||||
subRects: [rect],
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
import type {
|
||||
BlockLayout,
|
||||
BlockLayoutPainter,
|
||||
WorkerToHostMessage,
|
||||
} from '@blocksuite/affine-gfx-turbo-renderer';
|
||||
import { BlockLayoutPainterExtension } from '@blocksuite/affine-gfx-turbo-renderer/painter';
|
||||
|
||||
export interface CodeLayout extends BlockLayout {
|
||||
type: 'affine:code';
|
||||
}
|
||||
|
||||
function isCodeLayout(layout: BlockLayout): layout is CodeLayout {
|
||||
return layout.type === 'affine:code';
|
||||
}
|
||||
|
||||
class CodeLayoutPainter implements BlockLayoutPainter {
|
||||
paint(
|
||||
ctx: OffscreenCanvasRenderingContext2D,
|
||||
layout: BlockLayout,
|
||||
layoutBaseX: number,
|
||||
layoutBaseY: number
|
||||
): void {
|
||||
if (!isCodeLayout(layout)) {
|
||||
const message: WorkerToHostMessage = {
|
||||
type: 'paintError',
|
||||
error: 'Invalid layout format',
|
||||
blockType: 'affine:code',
|
||||
};
|
||||
self.postMessage(message);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the layout dimensions
|
||||
const x = layout.rect.x - layoutBaseX;
|
||||
const y = layout.rect.y - layoutBaseY;
|
||||
const width = layout.rect.w;
|
||||
const height = layout.rect.h;
|
||||
|
||||
// Simple white rectangle for now
|
||||
ctx.fillStyle = 'white';
|
||||
ctx.fillRect(x, y, width, height);
|
||||
|
||||
// Add a border to visualize the code block
|
||||
ctx.strokeStyle = 'rgba(0, 0, 0, 0.1)';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeRect(x, y, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
export const CodeLayoutPainterExtension = BlockLayoutPainterExtension(
|
||||
'affine:code',
|
||||
CodeLayoutPainter
|
||||
);
|
||||
@@ -8,7 +8,6 @@
|
||||
"include": ["./src"],
|
||||
"references": [
|
||||
{ "path": "../../components" },
|
||||
{ "path": "../../gfx/turbo-renderer" },
|
||||
{ "path": "../../inlines/latex" },
|
||||
{ "path": "../../inlines/link" },
|
||||
{ "path": "../../inlines/preset" },
|
||||
|
||||
@@ -259,7 +259,7 @@ export class EdgelessTextBlockComponent extends GfxBlockComponent<EdgelessTextBl
|
||||
};
|
||||
}
|
||||
|
||||
override onSelected(context: SelectedContext): void | boolean {
|
||||
override onSelected(context: SelectedContext) {
|
||||
const { selected, multiSelect, event: e } = context;
|
||||
const { editing } = this.gfx.selection;
|
||||
const alreadySelected = this.gfx.selection.has(this.model.id);
|
||||
@@ -318,7 +318,7 @@ export class EdgelessTextBlockComponent extends GfxBlockComponent<EdgelessTextBl
|
||||
})
|
||||
.catch(console.error);
|
||||
} else {
|
||||
return super.onSelected(context);
|
||||
super.onSelected(context);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import type {
|
||||
EmbedGithubStyles,
|
||||
} from '@blocksuite/affine-model';
|
||||
import { ThemeProvider } from '@blocksuite/affine-shared/services';
|
||||
import { BlockSelection, isGfxBlockComponent } from '@blocksuite/std';
|
||||
import { BlockSelection } from '@blocksuite/std';
|
||||
import { html, nothing } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
@@ -166,16 +166,16 @@ export class EmbedGithubBlockComponent extends EmbedBlockComponent<
|
||||
'affine-embed-github-block': true,
|
||||
loading,
|
||||
[style]: true,
|
||||
edgeless: isGfxBlockComponent(this),
|
||||
selected: this.selected$.value,
|
||||
})}
|
||||
style=${styleMap({
|
||||
// transform: `scale(${this._scale})`,
|
||||
transform: `scale(${this._scale})`,
|
||||
transformOrigin: '0 0 ',
|
||||
})}
|
||||
@click=${this._handleClick}
|
||||
@dblclick=${this._handleDoubleClick}
|
||||
>
|
||||
<div class="affine-embed-github-banner">${bannerImage}</div>
|
||||
<div class="affine-embed-github-content">
|
||||
<div class="affine-embed-github-content-title">
|
||||
<div class="affine-embed-github-content-title-icons">
|
||||
@@ -260,7 +260,6 @@ export class EmbedGithubBlockComponent extends EmbedBlockComponent<
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="affine-embed-github-banner">${bannerImage}</div>
|
||||
</div>
|
||||
`
|
||||
);
|
||||
|
||||
@@ -14,7 +14,6 @@ export const styles = css`
|
||||
opacity: var(--add, 1);
|
||||
background: var(--affine-background-primary-color);
|
||||
user-select: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.affine-embed-github-content {
|
||||
@@ -276,7 +275,7 @@ export const styles = css`
|
||||
}
|
||||
|
||||
.affine-embed-github-block.vertical {
|
||||
flex-direction: column-reverse;
|
||||
flex-direction: column;
|
||||
|
||||
.affine-embed-github-content {
|
||||
width: 100%;
|
||||
@@ -384,8 +383,7 @@ export const styles = css`
|
||||
.affine-embed-github-content {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.affine-embed-github-block:not(.edgeless) .affine-embed-github-banner {
|
||||
.affine-embed-github-banner {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,9 +209,9 @@ export class EmbedIframeErrorCard extends WithDisposable(LitElement) {
|
||||
});
|
||||
};
|
||||
|
||||
private readonly _handleRetry = async (e: MouseEvent) => {
|
||||
private readonly _handleRetry = (e: MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
const success = await this.onRetry();
|
||||
this.onRetry();
|
||||
|
||||
// track retry event
|
||||
this.telemetryService?.track('ReloadLink', {
|
||||
@@ -220,7 +220,6 @@ export class EmbedIframeErrorCard extends WithDisposable(LitElement) {
|
||||
segment: 'editor',
|
||||
module: 'embed block',
|
||||
control: 'reload button',
|
||||
result: success ? 'success' : 'failure',
|
||||
});
|
||||
};
|
||||
|
||||
@@ -302,7 +301,7 @@ export class EmbedIframeErrorCard extends WithDisposable(LitElement) {
|
||||
accessor error: Error | null = null;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor onRetry!: () => Promise<boolean>;
|
||||
accessor onRetry!: () => void;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor model!: EmbedIframeBlockModel;
|
||||
|
||||
@@ -78,7 +78,7 @@ export class EmbedIframeLinkEditPopup extends SignalWatcher(
|
||||
segment: 'editor',
|
||||
module: 'embed block',
|
||||
control: 'edit button',
|
||||
result: status,
|
||||
other: status,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -228,7 +228,7 @@ export class EmbedIframeLinkInputPopup extends EmbedIframeLinkInputBase {
|
||||
segment: this.options?.telemetrySegment ?? 'editor',
|
||||
module: 'embed block',
|
||||
control: 'confirm embed link',
|
||||
result: status,
|
||||
other: status,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -299,19 +299,12 @@ export const builtinToolbarConfig = {
|
||||
icon: ResetIcon(),
|
||||
run(ctx) {
|
||||
const component = ctx.getCurrentBlockByType(EmbedIframeBlockComponent);
|
||||
component
|
||||
?.refreshData()
|
||||
.then(success => {
|
||||
ctx.track('ReloadLink', {
|
||||
type: 'embed iframe block',
|
||||
page: 'doc editor',
|
||||
segment: 'doc',
|
||||
module: 'toolbar',
|
||||
control: 'reload link',
|
||||
result: success ? 'success' : 'failure',
|
||||
});
|
||||
})
|
||||
.catch(console.error);
|
||||
component?.refreshData().catch(console.error);
|
||||
|
||||
ctx.track('ReloadLink', {
|
||||
...trackBaseProps,
|
||||
control: 'reload link',
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -143,7 +143,7 @@ export class EmbedIframeBlockComponent extends CaptionedBlockComponent<EmbedIfra
|
||||
const { url } = this.model.props;
|
||||
if (!url) {
|
||||
this.status$.value = 'idle';
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
// set loading status
|
||||
@@ -188,13 +188,11 @@ export class EmbedIframeBlockComponent extends CaptionedBlockComponent<EmbedIfra
|
||||
|
||||
// set success status
|
||||
this.status$.value = 'success';
|
||||
return true;
|
||||
} catch (err) {
|
||||
// set error status
|
||||
this.status$.value = 'error';
|
||||
this.error$.value = err instanceof Error ? err : new Error(String(err));
|
||||
console.error('Failed to refresh iframe data:', err);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -286,7 +284,7 @@ export class EmbedIframeBlockComponent extends CaptionedBlockComponent<EmbedIfra
|
||||
};
|
||||
|
||||
private readonly _handleRetry = async () => {
|
||||
return await this.refreshData();
|
||||
await this.refreshData();
|
||||
};
|
||||
|
||||
private readonly _renderIframe = () => {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { EmbedLinkedDocBlockSchema } from '@blocksuite/affine-model';
|
||||
import { insertContent } from '@blocksuite/affine-rich-text';
|
||||
import {
|
||||
getInlineEditorByModel,
|
||||
insertContent,
|
||||
} from '@blocksuite/affine-rich-text';
|
||||
import { REFERENCE_NODE } from '@blocksuite/affine-shared/consts';
|
||||
import { createDefaultDoc } from '@blocksuite/affine-shared/utils';
|
||||
import {
|
||||
@@ -65,7 +68,22 @@ const linkedDocSlashMenuConfig: SlashMenuConfig = {
|
||||
if (!linkedDocWidget) return;
|
||||
// TODO(@L-Sun): make linked-doc-widget as extension
|
||||
// @ts-expect-error same as above
|
||||
linkedDocWidget.show({ addTriggerKey: true });
|
||||
const triggerKey = linkedDocWidget.config.triggerKeys[0];
|
||||
|
||||
insertContent(std, model, triggerKey);
|
||||
|
||||
const inlineEditor = getInlineEditorByModel(std, model);
|
||||
if (inlineEditor) {
|
||||
// Wait for range to be updated
|
||||
const subscription = inlineEditor.slots.inlineRangeSync.subscribe(
|
||||
() => {
|
||||
// TODO(@L-Sun): make linked-doc-widget as extension
|
||||
subscription.unsubscribe();
|
||||
// @ts-expect-error same as above
|
||||
linkedDocWidget.show({ addTriggerKey: true });
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -12,8 +12,6 @@ import {
|
||||
import {
|
||||
ActionPlacement,
|
||||
DocDisplayMetaProvider,
|
||||
EditorSettingProvider,
|
||||
FeatureFlagService,
|
||||
type LinkEventType,
|
||||
type OpenDocMode,
|
||||
type ToolbarAction,
|
||||
@@ -35,7 +33,7 @@ import {
|
||||
ExpandFullIcon,
|
||||
OpenInNewIcon,
|
||||
} from '@blocksuite/icons/lit';
|
||||
import { BlockFlavourIdentifier, isGfxBlockComponent } from '@blocksuite/std';
|
||||
import { BlockFlavourIdentifier } from '@blocksuite/std';
|
||||
import { type ExtensionType, Slice } from '@blocksuite/store';
|
||||
import { computed, signal } from '@preact/signals-core';
|
||||
import { html } from 'lit';
|
||||
@@ -215,20 +213,6 @@ const conversionsActionGroup = {
|
||||
},
|
||||
run(ctx) {
|
||||
const block = ctx.getCurrentBlockByType(EmbedLinkedDocBlockComponent);
|
||||
|
||||
if (
|
||||
ctx.std
|
||||
.get(FeatureFlagService)
|
||||
.getFlag('enable_embed_doc_with_alias') &&
|
||||
isGfxBlockComponent(block)
|
||||
) {
|
||||
const editorSetting = ctx.std.getOptional(EditorSettingProvider);
|
||||
editorSetting?.set?.(
|
||||
'docDropCanvasPreferView',
|
||||
'affine:embed-synced-doc'
|
||||
);
|
||||
}
|
||||
|
||||
block?.convertToEmbed();
|
||||
|
||||
ctx.track('SelectedView', {
|
||||
|
||||
@@ -3,8 +3,6 @@ import { EditorChevronDown } from '@blocksuite/affine-components/toolbar';
|
||||
import { EmbedSyncedDocModel } from '@blocksuite/affine-model';
|
||||
import {
|
||||
ActionPlacement,
|
||||
EditorSettingProvider,
|
||||
FeatureFlagService,
|
||||
type LinkEventType,
|
||||
type OpenDocMode,
|
||||
type ToolbarAction,
|
||||
@@ -23,7 +21,7 @@ import {
|
||||
ExpandFullIcon,
|
||||
OpenInNewIcon,
|
||||
} from '@blocksuite/icons/lit';
|
||||
import { BlockFlavourIdentifier, isGfxBlockComponent } from '@blocksuite/std';
|
||||
import { BlockFlavourIdentifier } from '@blocksuite/std';
|
||||
import { type ExtensionType, Slice } from '@blocksuite/store';
|
||||
import { computed, signal } from '@preact/signals-core';
|
||||
import { html } from 'lit';
|
||||
@@ -32,6 +30,7 @@ import { keyed } from 'lit/directives/keyed.js';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
|
||||
import { EmbedSyncedDocBlockComponent } from '../embed-synced-doc-block';
|
||||
|
||||
const trackBaseProps = {
|
||||
category: 'linked doc',
|
||||
type: 'embed view',
|
||||
@@ -143,19 +142,6 @@ const conversionsActionGroup = {
|
||||
label: 'Card view',
|
||||
run(ctx) {
|
||||
const block = ctx.getCurrentBlockByType(EmbedSyncedDocBlockComponent);
|
||||
if (
|
||||
ctx.std
|
||||
.get(FeatureFlagService)
|
||||
.getFlag('enable_embed_doc_with_alias') &&
|
||||
isGfxBlockComponent(block)
|
||||
) {
|
||||
const editorSetting = ctx.std.getOptional(EditorSettingProvider);
|
||||
editorSetting?.set?.(
|
||||
'docDropCanvasPreferView',
|
||||
'affine:embed-linked-doc'
|
||||
);
|
||||
}
|
||||
|
||||
block?.convertToCard();
|
||||
|
||||
ctx.track('SelectedView', {
|
||||
|
||||
@@ -117,9 +117,9 @@ export class EmbedSyncedDocBlockComponent extends EmbedBlockComponent<EmbedSynce
|
||||
const nextDepth = this.depth + 1;
|
||||
const previewSpecBuilder = SpecProvider._.getSpec(name);
|
||||
const currentDisposables = this.disposables;
|
||||
const editorSetting = this.std.getOptional(EditorSettingProvider) ?? {
|
||||
setting$: signal(GeneralSettingSchema.parse({})),
|
||||
};
|
||||
const editorSetting =
|
||||
this.std.getOptional(EditorSettingProvider) ??
|
||||
signal(GeneralSettingSchema.parse({}));
|
||||
|
||||
class EmbedSyncedDocWatcher extends LifeCycleWatcher {
|
||||
static override key = 'embed-synced-doc-watcher';
|
||||
|
||||
@@ -53,7 +53,7 @@ export class FrameBlockComponent extends GfxBlockComponent<FrameBlockModel> {
|
||||
};
|
||||
}
|
||||
|
||||
override onSelected(context: SelectedContext): boolean | void {
|
||||
override onSelected(context: SelectedContext): void {
|
||||
const { x, y } = context.position;
|
||||
|
||||
if (
|
||||
@@ -63,10 +63,10 @@ export class FrameBlockComponent extends GfxBlockComponent<FrameBlockModel> {
|
||||
// otherwise if the frame has title, then ignore it because in this case the frame cannot be selected by frame body
|
||||
this.model.props.title.length)
|
||||
) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
return super.onSelected(context);
|
||||
super.onSelected(context);
|
||||
}
|
||||
|
||||
override renderGfxBlock() {
|
||||
|
||||
@@ -80,7 +80,7 @@ const builtinSurfaceToolbarConfig = {
|
||||
|
||||
ctx.store.addBlock(
|
||||
SurfaceRefBlockSchema.model.flavour,
|
||||
{ reference: frameId, refFlavour: FrameBlockSchema.model.flavour },
|
||||
{ reference: frameId, refFlavour: NoteBlockSchema.model.flavour },
|
||||
lastNoteId
|
||||
);
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
"@blocksuite/affine-block-note": "workspace:*",
|
||||
"@blocksuite/affine-block-surface": "workspace:*",
|
||||
"@blocksuite/affine-components": "workspace:*",
|
||||
"@blocksuite/affine-gfx-turbo-renderer": "workspace:*",
|
||||
"@blocksuite/affine-model": "workspace:*",
|
||||
"@blocksuite/affine-shared": "workspace:*",
|
||||
"@blocksuite/affine-widget-slash-menu": "workspace:*",
|
||||
@@ -33,8 +32,7 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./turbo-painter": "./src/turbo/image-painter.worker.ts"
|
||||
"./effects": "./src/effects.ts"
|
||||
},
|
||||
"files": [
|
||||
"src",
|
||||
|
||||
@@ -7,7 +7,5 @@ export { ImageProxyService } from './image-proxy-service';
|
||||
export * from './image-service';
|
||||
export * from './image-spec';
|
||||
export * from './styles';
|
||||
export * from './turbo/image-layout-handler';
|
||||
export * from './turbo/image-painter.worker';
|
||||
export { addImages, downloadImageBlob, uploadBlobForImage } from './utils';
|
||||
export { ImageSelection } from '@blocksuite/affine-shared/selection';
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
import type { Rect } from '@blocksuite/affine-gfx-turbo-renderer';
|
||||
import {
|
||||
BlockLayoutHandlerExtension,
|
||||
BlockLayoutHandlersIdentifier,
|
||||
} from '@blocksuite/affine-gfx-turbo-renderer';
|
||||
import type { Container } from '@blocksuite/global/di';
|
||||
import type { EditorHost, GfxBlockComponent } from '@blocksuite/std';
|
||||
import { clientToModelCoord, type ViewportRecord } from '@blocksuite/std/gfx';
|
||||
import type { BlockModel } from '@blocksuite/store';
|
||||
|
||||
import type { ImageLayout } from './image-painter.worker';
|
||||
|
||||
export class ImageLayoutHandlerExtension extends BlockLayoutHandlerExtension<ImageLayout> {
|
||||
readonly blockType = 'affine:image';
|
||||
|
||||
static override setup(di: Container) {
|
||||
di.addImpl(
|
||||
BlockLayoutHandlersIdentifier('image'),
|
||||
ImageLayoutHandlerExtension
|
||||
);
|
||||
}
|
||||
|
||||
override queryLayout(
|
||||
model: BlockModel,
|
||||
host: EditorHost,
|
||||
viewportRecord: ViewportRecord
|
||||
): ImageLayout | null {
|
||||
const component = host.std.view.getBlock(model.id) as GfxBlockComponent;
|
||||
if (!component) return null;
|
||||
|
||||
const imageContainer = component.querySelector('.affine-image-container');
|
||||
if (!imageContainer) return null;
|
||||
|
||||
const resizableImg = component.querySelector(
|
||||
'.resizable-img'
|
||||
) as HTMLElement;
|
||||
if (!resizableImg) return null;
|
||||
|
||||
const { zoom, viewScale } = viewportRecord;
|
||||
const rect = resizableImg.getBoundingClientRect();
|
||||
|
||||
const [modelX, modelY] = clientToModelCoord(viewportRecord, [
|
||||
rect.x,
|
||||
rect.y,
|
||||
]);
|
||||
|
||||
const imageLayout: ImageLayout = {
|
||||
type: 'affine:image',
|
||||
blockId: model.id,
|
||||
rect: {
|
||||
x: modelX,
|
||||
y: modelY,
|
||||
w: rect.width / zoom / viewScale,
|
||||
h: rect.height / zoom / viewScale,
|
||||
},
|
||||
};
|
||||
|
||||
return imageLayout;
|
||||
}
|
||||
|
||||
calculateBound(layout: ImageLayout) {
|
||||
const rect: Rect = layout.rect;
|
||||
|
||||
return {
|
||||
rect,
|
||||
subRects: [rect],
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
import type {
|
||||
BlockLayout,
|
||||
BlockLayoutPainter,
|
||||
} from '@blocksuite/affine-gfx-turbo-renderer';
|
||||
import { BlockLayoutPainterExtension } from '@blocksuite/affine-gfx-turbo-renderer/painter';
|
||||
|
||||
export interface ImageLayout extends BlockLayout {
|
||||
type: 'affine:image';
|
||||
rect: {
|
||||
x: number;
|
||||
y: number;
|
||||
w: number;
|
||||
h: number;
|
||||
};
|
||||
}
|
||||
|
||||
function isImageLayout(layout: BlockLayout): layout is ImageLayout {
|
||||
return layout.type === 'affine:image';
|
||||
}
|
||||
|
||||
class ImageLayoutPainter implements BlockLayoutPainter {
|
||||
paint(
|
||||
ctx: OffscreenCanvasRenderingContext2D,
|
||||
layout: BlockLayout,
|
||||
layoutBaseX: number,
|
||||
layoutBaseY: number
|
||||
): void {
|
||||
if (!isImageLayout(layout)) {
|
||||
console.warn(
|
||||
'Expected image layout but received different format:',
|
||||
layout
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// For now, just paint a white rectangle
|
||||
const x = layout.rect.x - layoutBaseX;
|
||||
const y = layout.rect.y - layoutBaseY;
|
||||
const width = layout.rect.w;
|
||||
const height = layout.rect.h;
|
||||
|
||||
// Draw a white rectangle with border
|
||||
ctx.fillStyle = 'white';
|
||||
ctx.fillRect(x, y, width, height);
|
||||
|
||||
// Add a border
|
||||
ctx.strokeStyle = '#e0e0e0';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeRect(x, y, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
export const ImageLayoutPainterExtension = BlockLayoutPainterExtension(
|
||||
'affine:image',
|
||||
ImageLayoutPainter
|
||||
);
|
||||
@@ -10,7 +10,6 @@
|
||||
{ "path": "../note" },
|
||||
{ "path": "../surface" },
|
||||
{ "path": "../../components" },
|
||||
{ "path": "../../gfx/turbo-renderer" },
|
||||
{ "path": "../../model" },
|
||||
{ "path": "../../shared" },
|
||||
{ "path": "../../widgets/slash-menu" },
|
||||
|
||||
@@ -2,14 +2,18 @@ import { ParagraphBlockSchema } from '@blocksuite/affine-model';
|
||||
import {
|
||||
BlockMarkdownAdapterExtension,
|
||||
type BlockMarkdownAdapterMatcher,
|
||||
IN_PARAGRAPH_NODE_CONTEXT_KEY,
|
||||
type MarkdownAST,
|
||||
} from '@blocksuite/affine-shared/adapters';
|
||||
import type { DeltaInsert } from '@blocksuite/store';
|
||||
import { nanoid } from '@blocksuite/store';
|
||||
import type { Heading } from 'mdast';
|
||||
|
||||
const PARAGRAPH_MDAST_TYPE = new Set(['paragraph', 'heading', 'blockquote']);
|
||||
const PARAGRAPH_MDAST_TYPE = new Set([
|
||||
'paragraph',
|
||||
'html',
|
||||
'heading',
|
||||
'blockquote',
|
||||
]);
|
||||
|
||||
const isParagraphMDASTType = (node: MarkdownAST) =>
|
||||
PARAGRAPH_MDAST_TYPE.has(node.type);
|
||||
@@ -23,8 +27,32 @@ export const paragraphBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMatcher =
|
||||
enter: (o, context) => {
|
||||
const { walkerContext, deltaConverter } = context;
|
||||
switch (o.node.type) {
|
||||
case 'html': {
|
||||
walkerContext
|
||||
.openNode(
|
||||
{
|
||||
type: 'block',
|
||||
id: nanoid(),
|
||||
flavour: 'affine:paragraph',
|
||||
props: {
|
||||
type: 'text',
|
||||
text: {
|
||||
'$blocksuite:internal:text$': true,
|
||||
delta: [
|
||||
{
|
||||
insert: o.node.value,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
'children'
|
||||
)
|
||||
.closeNode();
|
||||
break;
|
||||
}
|
||||
case 'paragraph': {
|
||||
walkerContext.setGlobalContext(IN_PARAGRAPH_NODE_CONTEXT_KEY, true);
|
||||
walkerContext
|
||||
.openNode(
|
||||
{
|
||||
@@ -43,6 +71,7 @@ export const paragraphBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMatcher =
|
||||
'children'
|
||||
)
|
||||
.closeNode();
|
||||
walkerContext.skipAllChildren();
|
||||
break;
|
||||
}
|
||||
case 'heading': {
|
||||
@@ -90,12 +119,6 @@ export const paragraphBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMatcher =
|
||||
}
|
||||
}
|
||||
},
|
||||
leave: (o, context) => {
|
||||
if (o.node.type === 'paragraph') {
|
||||
const { walkerContext } = context;
|
||||
walkerContext.setGlobalContext(IN_PARAGRAPH_NODE_CONTEXT_KEY, false);
|
||||
}
|
||||
},
|
||||
},
|
||||
fromBlockSnapshot: {
|
||||
enter: (o, context) => {
|
||||
|
||||
@@ -46,8 +46,6 @@
|
||||
"@blocksuite/affine-widget-edgeless-auto-connect": "workspace:*",
|
||||
"@blocksuite/affine-widget-edgeless-toolbar": "workspace:*",
|
||||
"@blocksuite/affine-widget-frame-title": "workspace:*",
|
||||
"@blocksuite/affine-widget-keyboard-toolbar": "workspace:*",
|
||||
"@blocksuite/affine-widget-linked-doc": "workspace:*",
|
||||
"@blocksuite/affine-widget-remote-selection": "workspace:*",
|
||||
"@blocksuite/affine-widget-scroll-anchoring": "workspace:*",
|
||||
"@blocksuite/affine-widget-slash-menu": "workspace:*",
|
||||
|
||||
@@ -34,12 +34,6 @@ const NotionClipboardConfig = ClipboardAdapterConfigExtension({
|
||||
priority: 95,
|
||||
});
|
||||
|
||||
const HtmlClipboardConfig = ClipboardAdapterConfigExtension({
|
||||
mimeType: 'text/html',
|
||||
adapter: HtmlAdapter,
|
||||
priority: 90,
|
||||
});
|
||||
|
||||
const imageClipboardConfigs = [
|
||||
'image/apng',
|
||||
'image/avif',
|
||||
@@ -52,14 +46,20 @@ const imageClipboardConfigs = [
|
||||
return ClipboardAdapterConfigExtension({
|
||||
mimeType,
|
||||
adapter: ImageAdapter,
|
||||
priority: 80,
|
||||
priority: 85,
|
||||
});
|
||||
});
|
||||
|
||||
const PlainTextClipboardConfig = ClipboardAdapterConfigExtension({
|
||||
mimeType: 'text/plain',
|
||||
adapter: MixTextAdapter,
|
||||
priority: 70,
|
||||
priority: 80,
|
||||
});
|
||||
|
||||
const HtmlClipboardConfig = ClipboardAdapterConfigExtension({
|
||||
mimeType: 'text/html',
|
||||
adapter: HtmlAdapter,
|
||||
priority: 75,
|
||||
});
|
||||
|
||||
const AttachmentClipboardConfig = ClipboardAdapterConfigExtension({
|
||||
|
||||
@@ -33,7 +33,6 @@ import {
|
||||
ToolbarRegistryExtension,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import { dragHandleWidget } from '@blocksuite/affine-widget-drag-handle';
|
||||
import { linkedDocWidget } from '@blocksuite/affine-widget-linked-doc';
|
||||
import { docRemoteSelectionWidget } from '@blocksuite/affine-widget-remote-selection';
|
||||
import { scrollAnchoringWidget } from '@blocksuite/affine-widget-scroll-anchoring';
|
||||
import { SlashMenuExtension } from '@blocksuite/affine-widget-slash-menu';
|
||||
@@ -45,7 +44,7 @@ import { RootBlockAdapterExtensions } from '../adapters/extension';
|
||||
import { clipboardConfigs } from '../clipboard';
|
||||
import { builtinToolbarConfig } from '../configs/toolbar';
|
||||
import { fallbackKeymap } from '../keyboard/keymap';
|
||||
import { viewportOverlayWidget } from './widgets';
|
||||
import { linkedDocWidget, modalWidget, viewportOverlayWidget } from './widgets';
|
||||
|
||||
/**
|
||||
* Why do we add these extensions into CommonSpecs?
|
||||
@@ -84,6 +83,7 @@ export const CommonSpecs: ExtensionType[] = [
|
||||
...clipboardConfigs,
|
||||
...EdgelessElementViews,
|
||||
...EdgelessElementRendererExtension,
|
||||
modalWidget,
|
||||
SlashMenuExtension,
|
||||
linkedDocWidget,
|
||||
dragHandleWidget,
|
||||
|
||||
@@ -1,8 +1,20 @@
|
||||
import { WidgetViewExtension } from '@blocksuite/std';
|
||||
import { literal, unsafeStatic } from 'lit/static-html.js';
|
||||
|
||||
import { AFFINE_LINKED_DOC_WIDGET } from '../widgets/linked-doc/config.js';
|
||||
import { AFFINE_MODAL_WIDGET } from '../widgets/modal/modal.js';
|
||||
import { AFFINE_VIEWPORT_OVERLAY_WIDGET } from '../widgets/viewport-overlay/viewport-overlay.js';
|
||||
|
||||
export const modalWidget = WidgetViewExtension(
|
||||
'affine:page',
|
||||
AFFINE_MODAL_WIDGET,
|
||||
literal`${unsafeStatic(AFFINE_MODAL_WIDGET)}`
|
||||
);
|
||||
export const linkedDocWidget = WidgetViewExtension(
|
||||
'affine:page',
|
||||
AFFINE_LINKED_DOC_WIDGET,
|
||||
literal`${unsafeStatic(AFFINE_LINKED_DOC_WIDGET)}`
|
||||
);
|
||||
export const viewportOverlayWidget = WidgetViewExtension(
|
||||
'affine:page',
|
||||
AFFINE_VIEWPORT_OVERLAY_WIDGET,
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
} from '@blocksuite/affine-block-embed';
|
||||
import { updateBlockType } from '@blocksuite/affine-block-note';
|
||||
import { toast } from '@blocksuite/affine-components/toast';
|
||||
import { EditorChevronDown } from '@blocksuite/affine-components/toolbar';
|
||||
import {
|
||||
deleteTextCommand,
|
||||
formatBlockCommand,
|
||||
@@ -41,6 +40,7 @@ import { ActionPlacement } from '@blocksuite/affine-shared/services';
|
||||
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
|
||||
import { tableViewMeta } from '@blocksuite/data-view/view-presets';
|
||||
import {
|
||||
ArrowDownSmallIcon,
|
||||
CopyIcon,
|
||||
DatabaseTableViewIcon,
|
||||
DeleteIcon,
|
||||
@@ -94,7 +94,7 @@ const conversionsActionGroup = {
|
||||
aria-label="Conversions"
|
||||
.tooltip="${'Turn into'}"
|
||||
>
|
||||
${conversion.icon} ${EditorChevronDown}
|
||||
${conversion.icon} ${ArrowDownSmallIcon()}
|
||||
</editor-icon-button>
|
||||
`}
|
||||
>
|
||||
|
||||
@@ -88,9 +88,6 @@ export class EdgelessPageKeyboardManager extends PageKeyboardManager {
|
||||
p: () => {
|
||||
this._setEdgelessTool('brush');
|
||||
},
|
||||
'Shift-p': () => {
|
||||
this._setEdgelessTool('highlighter');
|
||||
},
|
||||
e: () => {
|
||||
this._setEdgelessTool('eraser');
|
||||
},
|
||||
|
||||
@@ -45,6 +45,7 @@ import { css, html } from 'lit';
|
||||
import { query } from 'lit/decorators.js';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
|
||||
import type { EdgelessRootBlockWidgetName } from '../types.js';
|
||||
import type { EdgelessSelectedRectWidget } from './components/rects/edgeless-selected-rect.js';
|
||||
import { EdgelessPageKeyboardManager } from './edgeless-keyboard.js';
|
||||
import type { EdgelessRootService } from './edgeless-root-service.js';
|
||||
@@ -52,7 +53,8 @@ import { isCanvasElement } from './utils/query.js';
|
||||
|
||||
export class EdgelessRootBlockComponent extends BlockComponent<
|
||||
RootBlockModel,
|
||||
EdgelessRootService
|
||||
EdgelessRootService,
|
||||
EdgelessRootBlockWidgetName
|
||||
> {
|
||||
static override styles = css`
|
||||
affine-edgeless-root {
|
||||
@@ -349,7 +351,7 @@ export class EdgelessRootBlockComponent extends BlockComponent<
|
||||
private _initWheelEvent() {
|
||||
this._disposables.add(
|
||||
this.dispatcher.add('wheel', ctx => {
|
||||
const config = this.std.getOptional(EditorSettingProvider)?.setting$;
|
||||
const config = this.std.getOptional(EditorSettingProvider);
|
||||
const state = ctx.get('defaultState');
|
||||
const e = state.event as WheelEvent;
|
||||
const edgelessScrollZoom = config?.peek().edgelessScrollZoom ?? false;
|
||||
|
||||
@@ -25,12 +25,14 @@ import { css, html } from 'lit';
|
||||
import { query, state } from 'lit/decorators.js';
|
||||
import { type StyleInfo, styleMap } from 'lit/directives/style-map.js';
|
||||
|
||||
import type { EdgelessRootBlockWidgetName } from '../types.js';
|
||||
import type { EdgelessRootService } from './edgeless-root-service.js';
|
||||
import { isCanvasElement } from './utils/query.js';
|
||||
|
||||
export class EdgelessRootPreviewBlockComponent extends BlockComponent<
|
||||
RootBlockModel,
|
||||
EdgelessRootService
|
||||
EdgelessRootService,
|
||||
EdgelessRootBlockWidgetName
|
||||
> {
|
||||
static override styles = css`
|
||||
affine-edgeless-root-preview {
|
||||
@@ -169,7 +171,7 @@ export class EdgelessRootPreviewBlockComponent extends BlockComponent<
|
||||
}
|
||||
|
||||
private get _disableScheduleUpdate() {
|
||||
const editorSetting = this.std.getOptional(EditorSettingProvider)?.setting$;
|
||||
const editorSetting = this.std.getOptional(EditorSettingProvider);
|
||||
|
||||
return editorSetting?.peek().edgelessDisableScheduleUpdate ?? false;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
type SurfaceBlockModel,
|
||||
type SurfaceContext,
|
||||
} from '@blocksuite/affine-block-surface';
|
||||
import { TemplateJob } from '@blocksuite/affine-gfx-template';
|
||||
import {
|
||||
type ConnectorElementModel,
|
||||
RootBlockSchema,
|
||||
@@ -38,6 +39,8 @@ export class EdgelessRootService extends RootService implements SurfaceContext {
|
||||
|
||||
private readonly _surface: SurfaceBlockModel;
|
||||
|
||||
TemplateJob = TemplateJob;
|
||||
|
||||
get blocks(): GfxBlockElementModel[] {
|
||||
return this.layer.blocks;
|
||||
}
|
||||
|
||||
@@ -7,8 +7,6 @@ import { effects as gfxShapeEffects } from '@blocksuite/affine-gfx-shape/effects
|
||||
import { effects as gfxTemplateEffects } from '@blocksuite/affine-gfx-template/effects';
|
||||
import { effects as gfxCanvasTextEffects } from '@blocksuite/affine-gfx-text/effects';
|
||||
import { effects as widgetEdgelessToolbarEffects } from '@blocksuite/affine-widget-edgeless-toolbar/effects';
|
||||
import { effects as widgetMobileToolbarEffects } from '@blocksuite/affine-widget-keyboard-toolbar/effects';
|
||||
import { effects as widgetLinkedDocEffects } from '@blocksuite/affine-widget-linked-doc/effects';
|
||||
|
||||
import { EdgelessAutoCompletePanel } from './edgeless/components/auto-complete/auto-complete-panel.js';
|
||||
import { EdgelessAutoComplete } from './edgeless/components/auto-complete/edgeless-auto-complete.js';
|
||||
@@ -29,6 +27,7 @@ import { ToolbarArrowUpIcon } from './edgeless/components/toolbar/common/toolbar
|
||||
import { EdgelessDefaultToolButton } from './edgeless/components/toolbar/default/default-tool-button.js';
|
||||
import { EdgelessLinkToolButton } from './edgeless/components/toolbar/link/link-tool-button.js';
|
||||
import {
|
||||
AffineModalWidget,
|
||||
EdgelessRootBlockComponent,
|
||||
EdgelessRootPreviewBlockComponent,
|
||||
PageRootBlockComponent,
|
||||
@@ -44,6 +43,11 @@ import {
|
||||
} from './widgets/edgeless-zoom-toolbar/index.js';
|
||||
import { ZoomBarToggleButton } from './widgets/edgeless-zoom-toolbar/zoom-bar-toggle-button.js';
|
||||
import { EdgelessZoomToolbar } from './widgets/edgeless-zoom-toolbar/zoom-toolbar.js';
|
||||
import { effects as widgetMobileToolbarEffects } from './widgets/keyboard-toolbar/effects.js';
|
||||
import { effects as widgetLinkedDocEffects } from './widgets/linked-doc/effects.js';
|
||||
import { Loader } from './widgets/linked-doc/import-doc/loader.js';
|
||||
import { AffineCustomModal } from './widgets/modal/custom-modal.js';
|
||||
import { AFFINE_MODAL_WIDGET } from './widgets/modal/modal.js';
|
||||
import {
|
||||
AFFINE_PAGE_DRAGGING_AREA_WIDGET,
|
||||
AffinePageDraggingAreaWidget,
|
||||
@@ -89,6 +93,7 @@ function registerGfxEffects() {
|
||||
}
|
||||
|
||||
function registerWidgets() {
|
||||
customElements.define(AFFINE_MODAL_WIDGET, AffineModalWidget);
|
||||
customElements.define(
|
||||
AFFINE_PAGE_DRAGGING_AREA_WIDGET,
|
||||
AffinePageDraggingAreaWidget
|
||||
@@ -119,6 +124,12 @@ function registerEdgelessToolbarComponents() {
|
||||
}
|
||||
|
||||
function registerMiscComponents() {
|
||||
// Modal and menu components
|
||||
customElements.define('affine-custom-modal', AffineCustomModal);
|
||||
|
||||
// Loading and preview components
|
||||
customElements.define('loader-element', Loader);
|
||||
|
||||
// Toolbar and UI components
|
||||
customElements.define('edgeless-zoom-toolbar', EdgelessZoomToolbar);
|
||||
customElements.define('zoom-bar-toggle-button', ZoomBarToggleButton);
|
||||
|
||||
@@ -8,7 +8,9 @@ export * from './page/page-root-block.js';
|
||||
export { PageRootService } from './page/page-root-service.js';
|
||||
export * from './page/page-root-spec.js';
|
||||
export * from './preview/preview-root-block.js';
|
||||
export * from './root-config.js';
|
||||
export { RootService } from './root-service.js';
|
||||
export * from './transformers/index.js';
|
||||
export * from './types.js';
|
||||
export * from './utils/index.js';
|
||||
export * from './widgets/index.js';
|
||||
|
||||
@@ -27,6 +27,7 @@ import { css, html } from 'lit';
|
||||
import { query } from 'lit/decorators.js';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
|
||||
import type { PageRootBlockWidgetName } from '../index.js';
|
||||
import { PageKeyboardManager } from '../keyboard/keyboard-manager.js';
|
||||
import type { PageRootService } from './page-root-service.js';
|
||||
|
||||
@@ -51,7 +52,8 @@ function testClickOnBlankArea(
|
||||
|
||||
export class PageRootBlockComponent extends BlockComponent<
|
||||
RootBlockModel,
|
||||
PageRootService
|
||||
PageRootService,
|
||||
PageRootBlockWidgetName
|
||||
> {
|
||||
static override styles = css`
|
||||
editor-host:has(> affine-page-root, * > affine-page-root) {
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
import { ViewportElementExtension } from '@blocksuite/affine-shared/services';
|
||||
import { keyboardToolbarWidget } from '@blocksuite/affine-widget-keyboard-toolbar';
|
||||
import { IS_MOBILE } from '@blocksuite/global/env';
|
||||
import { BlockViewExtension, WidgetViewExtension } from '@blocksuite/std';
|
||||
import type { ExtensionType } from '@blocksuite/store';
|
||||
import { literal, unsafeStatic } from 'lit/static-html.js';
|
||||
|
||||
import { PageClipboard } from '../clipboard/page-clipboard.js';
|
||||
import { CommonSpecs } from '../common-specs/index.js';
|
||||
import { AFFINE_KEYBOARD_TOOLBAR_WIDGET } from '../widgets/keyboard-toolbar/index.js';
|
||||
import { AFFINE_PAGE_DRAGGING_AREA_WIDGET } from '../widgets/page-dragging-area/page-dragging-area.js';
|
||||
import { PageRootService } from './page-root-service.js';
|
||||
|
||||
export const keyboardToolbarWidget = WidgetViewExtension(
|
||||
'affine:page',
|
||||
AFFINE_KEYBOARD_TOOLBAR_WIDGET,
|
||||
literal`${unsafeStatic(AFFINE_KEYBOARD_TOOLBAR_WIDGET)}`
|
||||
);
|
||||
|
||||
export const pageDraggingAreaWidget = WidgetViewExtension(
|
||||
'affine:page',
|
||||
AFFINE_PAGE_DRAGGING_AREA_WIDGET,
|
||||
@@ -26,7 +31,7 @@ const PageCommonExtension: ExtensionType[] = [
|
||||
export const PageRootBlockSpec: ExtensionType[] = [
|
||||
...PageCommonExtension,
|
||||
BlockViewExtension('affine:page', literal`affine-page-root`),
|
||||
IS_MOBILE ? [keyboardToolbarWidget] : [],
|
||||
keyboardToolbarWidget,
|
||||
PageClipboard,
|
||||
].flat();
|
||||
|
||||
|
||||
12
blocksuite/affine/blocks/root/src/root-config.ts
Normal file
12
blocksuite/affine/blocks/root/src/root-config.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { ConfigExtensionFactory } from '@blocksuite/std';
|
||||
|
||||
import type { KeyboardToolbarConfig } from './widgets/keyboard-toolbar/config.js';
|
||||
import type { LinkedWidgetConfig } from './widgets/linked-doc/index.js';
|
||||
|
||||
export interface RootBlockConfig {
|
||||
linkedWidget?: Partial<LinkedWidgetConfig>;
|
||||
keyboardToolbar?: Partial<KeyboardToolbarConfig>;
|
||||
}
|
||||
|
||||
export const RootBlockConfigExtension =
|
||||
ConfigExtensionFactory<RootBlockConfig>('affine:root-block');
|
||||
@@ -1,6 +1,48 @@
|
||||
import { RootBlockSchema } from '@blocksuite/affine-model';
|
||||
import {
|
||||
getBlockSelectionsCommand,
|
||||
getImageSelectionsCommand,
|
||||
getSelectedBlocksCommand,
|
||||
getTextSelectionCommand,
|
||||
} from '@blocksuite/affine-shared/commands';
|
||||
import type { BlockComponent } from '@blocksuite/std';
|
||||
import { BlockService } from '@blocksuite/std';
|
||||
|
||||
import type { RootBlockComponent } from './types.js';
|
||||
|
||||
export abstract class RootService extends BlockService {
|
||||
static override readonly flavour = RootBlockSchema.model.flavour;
|
||||
|
||||
get selectedBlocks() {
|
||||
let result: BlockComponent[] = [];
|
||||
this.std.command
|
||||
.chain()
|
||||
.tryAll(chain => [
|
||||
chain.pipe(getTextSelectionCommand),
|
||||
chain.pipe(getImageSelectionsCommand),
|
||||
chain.pipe(getBlockSelectionsCommand),
|
||||
])
|
||||
.pipe(getSelectedBlocksCommand)
|
||||
.pipe(({ selectedBlocks }) => {
|
||||
if (!selectedBlocks) return;
|
||||
result = selectedBlocks;
|
||||
})
|
||||
.run();
|
||||
return result;
|
||||
}
|
||||
|
||||
get selectedModels() {
|
||||
return this.selectedBlocks.map(block => block.model);
|
||||
}
|
||||
|
||||
get viewportElement() {
|
||||
const rootId = this.std.store.root?.id;
|
||||
if (!rootId) return null;
|
||||
const rootComponent = this.std.view.getBlock(
|
||||
rootId
|
||||
) as RootBlockComponent | null;
|
||||
if (!rootComponent) return null;
|
||||
const viewportElement = rootComponent.viewportElement;
|
||||
return viewportElement;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { sha } from '@blocksuite/global/utils';
|
||||
import type { DocSnapshot, Schema, Store, Workspace } from '@blocksuite/store';
|
||||
import { extMimeMap, getAssetName, Transformer } from '@blocksuite/store';
|
||||
|
||||
import { download, Unzip, Zip } from './utils.js';
|
||||
import { download, Unzip, Zip } from '../transformers/utils.js';
|
||||
|
||||
async function exportDocs(
|
||||
collection: Workspace,
|
||||
@@ -1,5 +1,40 @@
|
||||
import type { AFFINE_DRAG_HANDLE_WIDGET } from '@blocksuite/affine-widget-drag-handle';
|
||||
import type { AFFINE_FRAME_TITLE_WIDGET } from '@blocksuite/affine-widget-frame-title';
|
||||
import type {
|
||||
AFFINE_DOC_REMOTE_SELECTION_WIDGET,
|
||||
AFFINE_EDGELESS_REMOTE_SELECTION_WIDGET,
|
||||
} from '@blocksuite/affine-widget-remote-selection';
|
||||
import type { AFFINE_SLASH_MENU_WIDGET } from '@blocksuite/affine-widget-slash-menu';
|
||||
|
||||
import type { EdgelessRootBlockComponent } from './edgeless/edgeless-root-block.js';
|
||||
import type { PageRootBlockComponent } from './page/page-root-block.js';
|
||||
import type { AFFINE_EDGELESS_ZOOM_TOOLBAR_WIDGET } from './widgets/edgeless-zoom-toolbar/index.js';
|
||||
import type { AFFINE_KEYBOARD_TOOLBAR_WIDGET } from './widgets/index.js';
|
||||
import type { AFFINE_LINKED_DOC_WIDGET } from './widgets/linked-doc/config.js';
|
||||
import type { AFFINE_MODAL_WIDGET } from './widgets/modal/modal.js';
|
||||
import type { AFFINE_PAGE_DRAGGING_AREA_WIDGET } from './widgets/page-dragging-area/page-dragging-area.js';
|
||||
import type { AFFINE_VIEWPORT_OVERLAY_WIDGET } from './widgets/viewport-overlay/viewport-overlay.js';
|
||||
|
||||
export type PageRootBlockWidgetName =
|
||||
| typeof AFFINE_KEYBOARD_TOOLBAR_WIDGET
|
||||
| typeof AFFINE_MODAL_WIDGET
|
||||
| typeof AFFINE_SLASH_MENU_WIDGET
|
||||
| typeof AFFINE_LINKED_DOC_WIDGET
|
||||
| typeof AFFINE_PAGE_DRAGGING_AREA_WIDGET
|
||||
| typeof AFFINE_DRAG_HANDLE_WIDGET
|
||||
| typeof AFFINE_DOC_REMOTE_SELECTION_WIDGET
|
||||
| typeof AFFINE_VIEWPORT_OVERLAY_WIDGET;
|
||||
|
||||
export type EdgelessRootBlockWidgetName =
|
||||
| typeof AFFINE_MODAL_WIDGET
|
||||
| typeof AFFINE_SLASH_MENU_WIDGET
|
||||
| typeof AFFINE_LINKED_DOC_WIDGET
|
||||
| typeof AFFINE_DRAG_HANDLE_WIDGET
|
||||
| typeof AFFINE_DOC_REMOTE_SELECTION_WIDGET
|
||||
| typeof AFFINE_EDGELESS_REMOTE_SELECTION_WIDGET
|
||||
| typeof AFFINE_EDGELESS_ZOOM_TOOLBAR_WIDGET
|
||||
| typeof AFFINE_VIEWPORT_OVERLAY_WIDGET
|
||||
| typeof AFFINE_FRAME_TITLE_WIDGET;
|
||||
|
||||
export type RootBlockComponent =
|
||||
| PageRootBlockComponent
|
||||
|
||||
@@ -1,4 +1,18 @@
|
||||
export { AffineEdgelessZoomToolbarWidget } from './edgeless-zoom-toolbar/index.js';
|
||||
export * from './keyboard-toolbar/index.js';
|
||||
export {
|
||||
type LinkedMenuAction,
|
||||
type LinkedMenuGroup,
|
||||
type LinkedMenuItem,
|
||||
type LinkedWidgetConfig,
|
||||
LinkedWidgetUtils,
|
||||
} from './linked-doc/config.js';
|
||||
export {
|
||||
// It's used in the AFFiNE!
|
||||
showImportModal,
|
||||
} from './linked-doc/import-doc/index.js';
|
||||
export { AffineLinkedDocWidget } from './linked-doc/index.js';
|
||||
export { AffineModalWidget } from './modal/modal.js';
|
||||
export { AffinePageDraggingAreaWidget } from './page-dragging-area/page-dragging-area.js';
|
||||
export * from './viewport-overlay/viewport-overlay.js';
|
||||
export { AffineFrameTitleWidget } from '@blocksuite/affine-widget-frame-title';
|
||||
|
||||
@@ -34,7 +34,10 @@ import {
|
||||
toggleUnderline,
|
||||
} from '@blocksuite/affine-inline-preset';
|
||||
import type { FrameBlockModel } from '@blocksuite/affine-model';
|
||||
import { insertContent } from '@blocksuite/affine-rich-text';
|
||||
import {
|
||||
getInlineEditorByModel,
|
||||
insertContent,
|
||||
} from '@blocksuite/affine-rich-text';
|
||||
import {
|
||||
copySelectedModelsCommand,
|
||||
deleteSelectedModelsCommand,
|
||||
@@ -52,7 +55,6 @@ import {
|
||||
openFileOrFiles,
|
||||
type Signal,
|
||||
} from '@blocksuite/affine-shared/utils';
|
||||
import type { AffineLinkedDocWidget } from '@blocksuite/affine-widget-linked-doc';
|
||||
import { viewPresets } from '@blocksuite/data-view/view-presets';
|
||||
import { assertType } from '@blocksuite/global/utils';
|
||||
import {
|
||||
@@ -97,15 +99,13 @@ import {
|
||||
YesterdayIcon,
|
||||
YoutubeDuotoneIcon,
|
||||
} from '@blocksuite/icons/lit';
|
||||
import {
|
||||
type BlockComponent,
|
||||
type BlockStdScope,
|
||||
ConfigExtensionFactory,
|
||||
} from '@blocksuite/std';
|
||||
import type { BlockStdScope } from '@blocksuite/std';
|
||||
import { computed } from '@preact/signals-core';
|
||||
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||
import type { TemplateResult } from 'lit';
|
||||
|
||||
import type { PageRootBlockComponent } from '../../page/page-root-block.js';
|
||||
import type { AffineLinkedDocWidget } from '../linked-doc/index.js';
|
||||
import {
|
||||
FigmaDuotoneIcon,
|
||||
HeadingIcon,
|
||||
@@ -159,7 +159,7 @@ export type KeyboardSubToolbarConfig = {
|
||||
|
||||
export type KeyboardToolbarContext = {
|
||||
std: BlockStdScope;
|
||||
rootComponent: BlockComponent;
|
||||
rootComponent: PageRootBlockComponent;
|
||||
/**
|
||||
* Close tool bar, and blur the focus if blur is true, default is false
|
||||
*/
|
||||
@@ -348,11 +348,35 @@ const pageToolGroup: KeyboardToolPanelGroup = {
|
||||
);
|
||||
if (!linkedDocWidget) return;
|
||||
assertType<AffineLinkedDocWidget>(linkedDocWidget);
|
||||
linkedDocWidget.show({
|
||||
mode: 'mobile',
|
||||
addTriggerKey: true,
|
||||
});
|
||||
closeToolPanel();
|
||||
|
||||
const triggerKey = linkedDocWidget.config.triggerKeys[0];
|
||||
|
||||
std.command
|
||||
.chain()
|
||||
.pipe(getSelectedModelsCommand)
|
||||
.pipe(ctx => {
|
||||
const { selectedModels } = ctx;
|
||||
if (!selectedModels?.length) return;
|
||||
|
||||
const currentModel = selectedModels[0];
|
||||
insertContent(std, currentModel, triggerKey);
|
||||
|
||||
const inlineEditor = getInlineEditorByModel(std, currentModel);
|
||||
// Wait for range to be updated
|
||||
if (inlineEditor) {
|
||||
const subscription = inlineEditor.slots.inlineRangeSync.subscribe(
|
||||
() => {
|
||||
subscription.unsubscribe();
|
||||
linkedDocWidget.show({
|
||||
mode: 'mobile',
|
||||
addTriggerKey: true,
|
||||
});
|
||||
closeToolPanel();
|
||||
}
|
||||
);
|
||||
}
|
||||
})
|
||||
.run();
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -404,7 +428,8 @@ const contentMediaToolGroup: KeyboardToolPanelGroup = {
|
||||
{
|
||||
name: 'Attachment',
|
||||
icon: AttachmentIcon(),
|
||||
showWhen: () => false,
|
||||
showWhen: ({ std }) =>
|
||||
std.store.schema.flavourSchemaMap.has('affine:attachment'),
|
||||
action: async ({ std }) => {
|
||||
const [_, { selectedModels }] = std.command.exec(
|
||||
getSelectedModelsCommand
|
||||
@@ -1004,7 +1029,8 @@ export const defaultKeyboardToolbarConfig: KeyboardToolbarConfig = {
|
||||
{
|
||||
name: 'Attachment',
|
||||
icon: AttachmentIcon(),
|
||||
showWhen: () => false,
|
||||
showWhen: ({ std }) =>
|
||||
std.store.schema.flavourSchemaMap.has('affine:attachment'),
|
||||
action: async ({ std }) => {
|
||||
const [_, { selectedModels }] = std.command.exec(
|
||||
getSelectedModelsCommand
|
||||
@@ -1129,7 +1155,3 @@ export const defaultKeyboardToolbarConfig: KeyboardToolbarConfig = {
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const KeyboardToolbarConfigExtension = ConfigExtensionFactory<
|
||||
Partial<KeyboardToolbarConfig>
|
||||
>('affine:keyboard-toolbar');
|
||||
@@ -2,20 +2,18 @@ import { getDocTitleByEditorHost } from '@blocksuite/affine-fragment-doc-title';
|
||||
import type { RootBlockModel } from '@blocksuite/affine-model';
|
||||
import {
|
||||
FeatureFlagService,
|
||||
isVirtualKeyboardProviderWithAction,
|
||||
VirtualKeyboardProvider,
|
||||
type VirtualKeyboardProviderWithAction,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import { IS_MOBILE } from '@blocksuite/global/env';
|
||||
import { WidgetComponent, WidgetViewExtension } from '@blocksuite/std';
|
||||
import { WidgetComponent } from '@blocksuite/std';
|
||||
import { effect, signal } from '@preact/signals-core';
|
||||
import { html, nothing } from 'lit';
|
||||
import { literal, unsafeStatic } from 'lit/static-html.js';
|
||||
|
||||
import {
|
||||
defaultKeyboardToolbarConfig,
|
||||
KeyboardToolbarConfigExtension,
|
||||
} from './config.js';
|
||||
import { RootBlockConfigExtension } from '../../root-config.js';
|
||||
import { defaultKeyboardToolbarConfig } from './config.js';
|
||||
|
||||
export * from './config.js';
|
||||
|
||||
export const AFFINE_KEYBOARD_TOOLBAR_WIDGET = 'affine-keyboard-toolbar-widget';
|
||||
|
||||
@@ -36,10 +34,7 @@ export class AffineKeyboardToolbarWidget extends WidgetComponent<RootBlockModel>
|
||||
|
||||
private _initialInputMode: string = '';
|
||||
|
||||
get keyboard(): VirtualKeyboardProviderWithAction & { fallback?: boolean } {
|
||||
const provider = this.std.get(VirtualKeyboardProvider);
|
||||
if (isVirtualKeyboardProviderWithAction(provider)) return provider;
|
||||
|
||||
get keyboard(): VirtualKeyboardProviderWithAction {
|
||||
return {
|
||||
// fallback keyboard actions
|
||||
show: () => {
|
||||
@@ -54,7 +49,7 @@ export class AffineKeyboardToolbarWidget extends WidgetComponent<RootBlockModel>
|
||||
rootComponent.inputMode = 'none';
|
||||
}
|
||||
},
|
||||
...provider,
|
||||
...this.std.get(VirtualKeyboardProvider),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -65,7 +60,8 @@ export class AffineKeyboardToolbarWidget extends WidgetComponent<RootBlockModel>
|
||||
get config() {
|
||||
return {
|
||||
...defaultKeyboardToolbarConfig,
|
||||
...this.std.getOptional(KeyboardToolbarConfigExtension.identifier),
|
||||
...this.std.getOptional(RootBlockConfigExtension.identifier)
|
||||
?.keyboardToolbar,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -74,6 +70,10 @@ export class AffineKeyboardToolbarWidget extends WidgetComponent<RootBlockModel>
|
||||
|
||||
const rootComponent = this.block?.rootComponent;
|
||||
if (rootComponent) {
|
||||
this._initialInputMode = rootComponent.inputMode;
|
||||
this.disposables.add(() => {
|
||||
rootComponent.inputMode = this._initialInputMode;
|
||||
});
|
||||
this.disposables.addFromEvent(rootComponent, 'focus', () => {
|
||||
this._show$.value = true;
|
||||
});
|
||||
@@ -81,20 +81,14 @@ export class AffineKeyboardToolbarWidget extends WidgetComponent<RootBlockModel>
|
||||
this._show$.value = false;
|
||||
});
|
||||
|
||||
if (this.keyboard.fallback) {
|
||||
this._initialInputMode = rootComponent.inputMode;
|
||||
this.disposables.add(() => {
|
||||
rootComponent.inputMode = this._initialInputMode;
|
||||
});
|
||||
this.disposables.add(
|
||||
effect(() => {
|
||||
// recover input mode when keyboard toolbar is hidden
|
||||
if (!this._show$.value) {
|
||||
rootComponent.inputMode = this._initialInputMode;
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
this.disposables.add(
|
||||
effect(() => {
|
||||
// recover input mode when keyboard toolbar is hidden
|
||||
if (!this._show$.value) {
|
||||
rootComponent.inputMode = this._initialInputMode;
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (this._docTitle) {
|
||||
@@ -134,12 +128,6 @@ export class AffineKeyboardToolbarWidget extends WidgetComponent<RootBlockModel>
|
||||
}
|
||||
}
|
||||
|
||||
export const keyboardToolbarWidget = WidgetViewExtension(
|
||||
'affine:page',
|
||||
AFFINE_KEYBOARD_TOOLBAR_WIDGET,
|
||||
literal`${unsafeStatic(AFFINE_KEYBOARD_TOOLBAR_WIDGET)}`
|
||||
);
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
[AFFINE_KEYBOARD_TOOLBAR_WIDGET]: AffineKeyboardToolbarWidget;
|
||||
@@ -3,7 +3,6 @@ import { type VirtualKeyboardProviderWithAction } from '@blocksuite/affine-share
|
||||
import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
|
||||
import { ArrowLeftBigIcon, KeyboardIcon } from '@blocksuite/icons/lit';
|
||||
import {
|
||||
BlockComponent,
|
||||
PropTypes,
|
||||
requiredProperties,
|
||||
ShadowlessElement,
|
||||
@@ -15,6 +14,7 @@ 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 type {
|
||||
KeyboardIconType,
|
||||
KeyboardToolbarConfig,
|
||||
@@ -34,7 +34,7 @@ export const AFFINE_KEYBOARD_TOOLBAR = 'affine-keyboard-toolbar';
|
||||
|
||||
@requiredProperties({
|
||||
config: PropTypes.object,
|
||||
rootComponent: PropTypes.instanceOf(BlockComponent),
|
||||
rootComponent: PropTypes.instanceOf(PageRootBlockComponent),
|
||||
})
|
||||
export class AffineKeyboardToolbar extends SignalWatcher(
|
||||
WithDisposable(ShadowlessElement)
|
||||
@@ -55,8 +55,10 @@ export class AffineKeyboardToolbar extends SignalWatcher(
|
||||
}
|
||||
|
||||
private readonly _closeToolPanel = () => {
|
||||
if (!this.panelOpened) return;
|
||||
|
||||
this._currentPanelIndex$.value = -1;
|
||||
if (!this.keyboard.visible$.peek()) this.keyboard.show();
|
||||
this.keyboard.show();
|
||||
};
|
||||
|
||||
private readonly _currentPanelIndex$ = signal(-1);
|
||||
@@ -253,16 +255,6 @@ export class AffineKeyboardToolbar extends SignalWatcher(
|
||||
})
|
||||
);
|
||||
|
||||
this.disposables.add(
|
||||
effect(() => {
|
||||
// sometime the keyboard will auto show when user click into different paragraph in Android,
|
||||
// so we need to close the tool panel explicitly when the keyboard is visible
|
||||
if (this.keyboard.visible$.value) {
|
||||
this._closeToolPanel();
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
this._watchAutoShow();
|
||||
}
|
||||
|
||||
@@ -338,5 +330,5 @@ export class AffineKeyboardToolbar extends SignalWatcher(
|
||||
accessor config!: KeyboardToolbarConfig;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor rootComponent!: BlockComponent;
|
||||
accessor rootComponent!: PageRootBlockComponent;
|
||||
}
|
||||
@@ -16,12 +16,7 @@ import {
|
||||
isFuzzyMatch,
|
||||
type Signal,
|
||||
} from '@blocksuite/affine-shared/utils';
|
||||
import { IS_MOBILE } from '@blocksuite/global/env';
|
||||
import {
|
||||
type BlockStdScope,
|
||||
ConfigExtensionFactory,
|
||||
type EditorHost,
|
||||
} from '@blocksuite/std';
|
||||
import type { BlockStdScope, EditorHost } from '@blocksuite/std';
|
||||
import type { InlineRange } from '@blocksuite/std/inline';
|
||||
import type { TemplateResult } from 'lit';
|
||||
|
||||
@@ -112,7 +107,6 @@ export type LinkedDocContext = {
|
||||
std: BlockStdScope;
|
||||
inlineEditor: AffineInlineEditor;
|
||||
startRange: InlineRange;
|
||||
startNativeRange: Range;
|
||||
triggerKey: string;
|
||||
config: LinkedWidgetConfig;
|
||||
close: () => void;
|
||||
@@ -177,77 +171,73 @@ export function createNewDocMenuGroup(
|
||||
docName.slice(0, DISPLAY_NAME_LENGTH) +
|
||||
(docName.length > DISPLAY_NAME_LENGTH ? '..' : '');
|
||||
|
||||
const items: LinkedMenuItem[] = [
|
||||
{
|
||||
key: 'create',
|
||||
name: `Create "${displayDocName}" doc`,
|
||||
icon: NewDocIcon,
|
||||
action: () => {
|
||||
abort();
|
||||
const docName = query;
|
||||
const newDoc = createDefaultDoc(doc.workspace, {
|
||||
title: docName,
|
||||
});
|
||||
insertLinkedNode({
|
||||
inlineEditor,
|
||||
docId: newDoc.id,
|
||||
});
|
||||
const telemetryService = editorHost.std.getOptional(TelemetryProvider);
|
||||
telemetryService?.track('LinkedDocCreated', {
|
||||
control: 'new doc',
|
||||
module: 'inline @',
|
||||
type: 'doc',
|
||||
other: 'new doc',
|
||||
});
|
||||
telemetryService?.track('DocCreated', {
|
||||
control: 'new doc',
|
||||
module: 'inline @',
|
||||
type: 'doc',
|
||||
});
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
if (!IS_MOBILE) {
|
||||
items.push({
|
||||
key: 'import',
|
||||
name: 'Import',
|
||||
icon: ImportIcon,
|
||||
action: () => {
|
||||
abort();
|
||||
const onSuccess = (
|
||||
docIds: string[],
|
||||
options: {
|
||||
importedCount: number;
|
||||
}
|
||||
) => {
|
||||
toast(
|
||||
editorHost,
|
||||
`Successfully imported ${options.importedCount} Doc${options.importedCount > 1 ? 's' : ''}.`
|
||||
);
|
||||
for (const docId of docIds) {
|
||||
insertLinkedNode({
|
||||
inlineEditor,
|
||||
docId,
|
||||
});
|
||||
}
|
||||
};
|
||||
const onFail = (message: string) => {
|
||||
toast(editorHost, message);
|
||||
};
|
||||
showImportModal({
|
||||
collection: doc.workspace,
|
||||
schema: doc.schema,
|
||||
onSuccess,
|
||||
onFail,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
name: 'New Doc',
|
||||
items,
|
||||
items: [
|
||||
{
|
||||
key: 'create',
|
||||
name: `Create "${displayDocName}" doc`,
|
||||
icon: NewDocIcon,
|
||||
action: () => {
|
||||
abort();
|
||||
const docName = query;
|
||||
const newDoc = createDefaultDoc(doc.workspace, {
|
||||
title: docName,
|
||||
});
|
||||
insertLinkedNode({
|
||||
inlineEditor,
|
||||
docId: newDoc.id,
|
||||
});
|
||||
const telemetryService =
|
||||
editorHost.std.getOptional(TelemetryProvider);
|
||||
telemetryService?.track('LinkedDocCreated', {
|
||||
control: 'new doc',
|
||||
module: 'inline @',
|
||||
type: 'doc',
|
||||
other: 'new doc',
|
||||
});
|
||||
telemetryService?.track('DocCreated', {
|
||||
control: 'new doc',
|
||||
module: 'inline @',
|
||||
type: 'doc',
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'import',
|
||||
name: 'Import',
|
||||
icon: ImportIcon,
|
||||
action: () => {
|
||||
abort();
|
||||
const onSuccess = (
|
||||
docIds: string[],
|
||||
options: {
|
||||
importedCount: number;
|
||||
}
|
||||
) => {
|
||||
toast(
|
||||
editorHost,
|
||||
`Successfully imported ${options.importedCount} Doc${options.importedCount > 1 ? 's' : ''}.`
|
||||
);
|
||||
for (const docId of docIds) {
|
||||
insertLinkedNode({
|
||||
inlineEditor,
|
||||
docId,
|
||||
});
|
||||
}
|
||||
};
|
||||
const onFail = (message: string) => {
|
||||
toast(editorHost, message);
|
||||
};
|
||||
showImportModal({
|
||||
collection: doc.workspace,
|
||||
schema: doc.schema,
|
||||
onSuccess,
|
||||
onFail,
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -270,7 +260,3 @@ export const LinkedWidgetUtils = {
|
||||
};
|
||||
|
||||
export const AFFINE_LINKED_DOC_WIDGET = 'affine-linked-doc-widget';
|
||||
|
||||
export const LinkedWidgetConfigExtension = ConfigExtensionFactory<
|
||||
Partial<LinkedWidgetConfig>
|
||||
>('affine:widget-linked-doc');
|
||||
@@ -1,6 +1,5 @@
|
||||
import { AFFINE_LINKED_DOC_WIDGET } from './config.js';
|
||||
import { ImportDoc } from './import-doc/import-doc.js';
|
||||
import { Loader } from './import-doc/loader.js';
|
||||
import { AffineLinkedDocWidget } from './index.js';
|
||||
import { LinkedDocPopover } from './linked-doc-popover.js';
|
||||
import { AffineMobileLinkedDocMenu } from './mobile-linked-doc-menu.js';
|
||||
@@ -9,9 +8,9 @@ export function effects() {
|
||||
customElements.define('affine-linked-doc-popover', LinkedDocPopover);
|
||||
customElements.define(AFFINE_LINKED_DOC_WIDGET, AffineLinkedDocWidget);
|
||||
customElements.define('import-doc', ImportDoc);
|
||||
|
||||
customElements.define(
|
||||
'affine-mobile-linked-doc-menu',
|
||||
AffineMobileLinkedDocMenu
|
||||
);
|
||||
customElements.define('loader-element', Loader);
|
||||
}
|
||||
@@ -12,9 +12,9 @@ import type { Schema, Workspace } from '@blocksuite/store';
|
||||
import { html, LitElement, type PropertyValues } from 'lit';
|
||||
import { query, state } from 'lit/decorators.js';
|
||||
|
||||
import { HtmlTransformer } from '../transformers/html.js';
|
||||
import { MarkdownTransformer } from '../transformers/markdown.js';
|
||||
import { NotionHtmlTransformer } from '../transformers/notion-html.js';
|
||||
import { HtmlTransformer } from '../../../transformers/html.js';
|
||||
import { MarkdownTransformer } from '../../../transformers/markdown.js';
|
||||
import { NotionHtmlTransformer } from '../../../transformers/notion-html.js';
|
||||
import { styles } from './styles.js';
|
||||
|
||||
export type OnSuccessHandler = (
|
||||
@@ -7,11 +7,7 @@ import { FeatureFlagService } from '@blocksuite/affine-shared/services';
|
||||
import { getViewportElement } from '@blocksuite/affine-shared/utils';
|
||||
import { IS_MOBILE } from '@blocksuite/global/env';
|
||||
import type { BlockComponent } from '@blocksuite/std';
|
||||
import {
|
||||
BLOCK_ID_ATTR,
|
||||
WidgetComponent,
|
||||
WidgetViewExtension,
|
||||
} from '@blocksuite/std';
|
||||
import { BLOCK_ID_ATTR, WidgetComponent } from '@blocksuite/std';
|
||||
import { GfxControllerIdentifier } from '@blocksuite/std/gfx';
|
||||
import {
|
||||
INLINE_ROOT_ATTR,
|
||||
@@ -23,18 +19,22 @@ import { html, nothing } from 'lit';
|
||||
import { choose } from 'lit/directives/choose.js';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
import { literal, unsafeStatic } from 'lit/static-html.js';
|
||||
|
||||
import type { PageRootBlockComponent } from '../../page/page-root-block.js';
|
||||
import { RootBlockConfigExtension } from '../../root-config.js';
|
||||
import {
|
||||
AFFINE_LINKED_DOC_WIDGET,
|
||||
type AFFINE_LINKED_DOC_WIDGET,
|
||||
getMenus,
|
||||
type LinkedDocContext,
|
||||
type LinkedWidgetConfig,
|
||||
LinkedWidgetConfigExtension,
|
||||
} from './config.js';
|
||||
import { linkedDocWidgetStyles } from './styles.js';
|
||||
export { type LinkedWidgetConfig } from './config.js';
|
||||
|
||||
export class AffineLinkedDocWidget extends WidgetComponent<RootBlockModel> {
|
||||
export class AffineLinkedDocWidget extends WidgetComponent<
|
||||
RootBlockModel,
|
||||
PageRootBlockComponent
|
||||
> {
|
||||
static override styles = linkedDocWidgetStyles;
|
||||
|
||||
private _context: LinkedDocContext | null = null;
|
||||
@@ -43,19 +43,6 @@ export class AffineLinkedDocWidget extends WidgetComponent<RootBlockModel> {
|
||||
|
||||
private readonly _mode$ = signal<'desktop' | 'mobile' | 'none'>('none');
|
||||
|
||||
private _addTriggerKey(inlineEditor: InlineEditor, triggerKey: string) {
|
||||
const inlineRange = inlineEditor.getInlineRange();
|
||||
if (!inlineRange) return;
|
||||
inlineEditor.insertText(
|
||||
{ index: inlineRange.index, length: 0 },
|
||||
triggerKey
|
||||
);
|
||||
inlineEditor.setInlineRange({
|
||||
index: inlineRange.index + triggerKey.length,
|
||||
length: 0,
|
||||
});
|
||||
}
|
||||
|
||||
private _updateInputRects() {
|
||||
if (!this._context) return;
|
||||
const { inlineEditor, startRange, triggerKey } = this._context;
|
||||
@@ -230,7 +217,8 @@ export class AffineLinkedDocWidget extends WidgetComponent<RootBlockModel> {
|
||||
scrollContainer: getViewportElement(this.std.host) ?? window,
|
||||
scrollTopOffset: 46,
|
||||
},
|
||||
...this.std.getOptional(LinkedWidgetConfigExtension.identifier),
|
||||
...this.std.getOptional(RootBlockConfigExtension.identifier)
|
||||
?.linkedWidget,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -271,30 +259,27 @@ export class AffineLinkedDocWidget extends WidgetComponent<RootBlockModel> {
|
||||
inlineEditor = props.inlineEditor;
|
||||
}
|
||||
|
||||
const inlineRange = inlineEditor.getInlineRange();
|
||||
if (!inlineRange) return;
|
||||
|
||||
if (addTriggerKey) {
|
||||
this._addTriggerKey(inlineEditor, primaryTriggerKey);
|
||||
// we need to wait the range sync to get the correct startNativeRange
|
||||
const subscription = inlineEditor.slots.inlineRangeSync.subscribe(() => {
|
||||
this.show({ ...props, addTriggerKey: false });
|
||||
subscription.unsubscribe();
|
||||
inlineEditor.insertText(
|
||||
{ index: inlineRange.index, length: 0 },
|
||||
primaryTriggerKey
|
||||
);
|
||||
inlineEditor.setInlineRange({
|
||||
index: inlineRange.index + primaryTriggerKey.length,
|
||||
length: 0,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const startRange = inlineEditor.getInlineRange();
|
||||
if (!startRange) return;
|
||||
|
||||
const startNativeRange = inlineEditor.getNativeRange();
|
||||
if (!startNativeRange) return;
|
||||
|
||||
const disposable = inlineEditor.slots.renderComplete.subscribe(() => {
|
||||
this._updateInputRects();
|
||||
});
|
||||
this._context = {
|
||||
std: this.std,
|
||||
inlineEditor,
|
||||
startRange,
|
||||
startNativeRange,
|
||||
startRange: inlineRange,
|
||||
triggerKey: primaryTriggerKey,
|
||||
config: this.config,
|
||||
close: () => {
|
||||
@@ -331,12 +316,6 @@ export class AffineLinkedDocWidget extends WidgetComponent<RootBlockModel> {
|
||||
}
|
||||
}
|
||||
|
||||
export const linkedDocWidget = WidgetViewExtension(
|
||||
'affine:page',
|
||||
AFFINE_LINKED_DOC_WIDGET,
|
||||
literal`${unsafeStatic(AFFINE_LINKED_DOC_WIDGET)}`
|
||||
);
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
[AFFINE_LINKED_DOC_WIDGET]: AffineLinkedDocWidget;
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
import { unsafeCSSVar } from '@blocksuite/affine-shared/theme';
|
||||
import {
|
||||
createKeydownObserver,
|
||||
getCurrentNativeRange,
|
||||
getPopperPosition,
|
||||
getViewportElement,
|
||||
} from '@blocksuite/affine-shared/utils';
|
||||
@@ -159,15 +160,11 @@ export class LinkedDocPopover extends SignalWatcher(
|
||||
|
||||
// init
|
||||
this._updateLinkedDocGroup().catch(console.error);
|
||||
this._disposables.addFromEvent(this, 'pointerdown', e => {
|
||||
this._disposables.addFromEvent(this, 'mousedown', e => {
|
||||
// Prevent input from losing focus
|
||||
e.preventDefault();
|
||||
});
|
||||
this._disposables.addFromEvent(this, 'mousedown', e => {
|
||||
// Prevent input from losing focus in electron
|
||||
e.preventDefault();
|
||||
});
|
||||
this._disposables.addFromEvent(window, 'pointerdown', e => {
|
||||
this._disposables.addFromEvent(window, 'mousedown', e => {
|
||||
if (e.target === this) return;
|
||||
// We don't clear the query when clicking outside the popover
|
||||
this.context.close();
|
||||
@@ -249,7 +246,6 @@ export class LinkedDocPopover extends SignalWatcher(
|
||||
override disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this._menusItemsEffectCleanup();
|
||||
this._updateLinkedDocGroupAbortController?.abort();
|
||||
}
|
||||
|
||||
override render() {
|
||||
@@ -284,7 +280,7 @@ export class LinkedDocPopover extends SignalWatcher(
|
||||
return html`
|
||||
<div class="divider" ?hidden=${idx === 0}></div>
|
||||
<div class="group-title">
|
||||
<div class="group-title-text">${group.name}</div>
|
||||
${group.name}
|
||||
${group.isLoading
|
||||
? html`<span class="loading-icon">${LoadingIcon}</span>`
|
||||
: nothing}
|
||||
@@ -342,8 +338,11 @@ export class LinkedDocPopover extends SignalWatcher(
|
||||
|
||||
override willUpdate() {
|
||||
if (!this.hasUpdated) {
|
||||
const curRange = getCurrentNativeRange();
|
||||
if (!curRange) return;
|
||||
|
||||
const updatePosition = throttle(() => {
|
||||
this._position = getPopperPosition(this, this.context.startNativeRange);
|
||||
this._position = getPopperPosition(this, curRange);
|
||||
}, 10);
|
||||
|
||||
this.disposables.addFromEvent(window, 'resize', updatePosition);
|
||||
@@ -3,7 +3,10 @@ import {
|
||||
getTextContentFromInlineRange,
|
||||
} from '@blocksuite/affine-rich-text';
|
||||
import { VirtualKeyboardProvider } from '@blocksuite/affine-shared/services';
|
||||
import { getViewportElement } from '@blocksuite/affine-shared/utils';
|
||||
import {
|
||||
createKeydownObserver,
|
||||
getViewportElement,
|
||||
} from '@blocksuite/affine-shared/utils';
|
||||
import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
|
||||
import { MoreHorizontalIcon } from '@blocksuite/icons/lit';
|
||||
import { PropTypes, requiredProperties } from '@blocksuite/std';
|
||||
@@ -13,6 +16,7 @@ import { property } from 'lit/decorators.js';
|
||||
import { join } from 'lit/directives/join.js';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
|
||||
import { PageRootBlockComponent } from '../../index.js';
|
||||
import type {
|
||||
LinkedDocContext,
|
||||
LinkedMenuGroup,
|
||||
@@ -25,6 +29,7 @@ export const AFFINE_MOBILE_LINKED_DOC_MENU = 'affine-mobile-linked-doc-menu';
|
||||
|
||||
@requiredProperties({
|
||||
context: PropTypes.object,
|
||||
rootComponent: PropTypes.instanceOf(PageRootBlockComponent),
|
||||
})
|
||||
export class AffineMobileLinkedDocMenu extends SignalWatcher(
|
||||
WithDisposable(LitElement)
|
||||
@@ -33,6 +38,8 @@ export class AffineMobileLinkedDocMenu extends SignalWatcher(
|
||||
|
||||
private readonly _expand = new Set<string>();
|
||||
|
||||
private _firstActionItem: LinkedMenuItem | null = null;
|
||||
|
||||
private readonly _linkedDocGroup$ = signal<LinkedMenuGroup[]>([]);
|
||||
|
||||
private readonly _renderGroup = (group: LinkedMenuGroup) => {
|
||||
@@ -182,14 +189,42 @@ export class AffineMobileLinkedDocMenu extends SignalWatcher(
|
||||
const keydownObserverAbortController = new AbortController();
|
||||
this._disposables.add(() => keydownObserverAbortController.abort());
|
||||
|
||||
// we need use beforeinput because the event.key of keypress event usually is `Unidentified` in Android
|
||||
this.disposables.addFromEvent(eventSource, 'beforeinput', () => {
|
||||
const curRange = inlineEditor.getInlineRange();
|
||||
if (curRange && curRange.index < this.context.startRange.index) {
|
||||
createKeydownObserver({
|
||||
target: eventSource,
|
||||
signal: keydownObserverAbortController.signal,
|
||||
onInput: isComposition => {
|
||||
if (isComposition) {
|
||||
this._updateLinkedDocGroup().catch(console.error);
|
||||
} else {
|
||||
const subscription = inlineEditor.slots.renderComplete.subscribe(
|
||||
() => {
|
||||
subscription.unsubscribe();
|
||||
this._updateLinkedDocGroup().catch(console.error);
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
onDelete: () => {
|
||||
const subscription = inlineEditor.slots.renderComplete.subscribe(
|
||||
() => {
|
||||
subscription.unsubscribe();
|
||||
const curRange = inlineEditor.getInlineRange();
|
||||
|
||||
if (!this.context.startRange || !curRange) return;
|
||||
|
||||
if (curRange.index < this.context.startRange.index) {
|
||||
this.context.close();
|
||||
}
|
||||
this._updateLinkedDocGroup().catch(console.error);
|
||||
}
|
||||
);
|
||||
},
|
||||
onConfirm: () => {
|
||||
this._firstActionItem?.action()?.catch(console.error);
|
||||
},
|
||||
onAbort: () => {
|
||||
this.context.close();
|
||||
return;
|
||||
}
|
||||
this._updateLinkedDocGroup().catch(console.error);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -204,6 +239,8 @@ export class AffineMobileLinkedDocMenu extends SignalWatcher(
|
||||
return nothing;
|
||||
}
|
||||
|
||||
this._firstActionItem = resolveSignal(groups[0].items)[0];
|
||||
|
||||
this.style.bottom = `${this.keyboard.height$.value}px`;
|
||||
|
||||
return html`
|
||||
@@ -213,4 +250,7 @@ export class AffineMobileLinkedDocMenu extends SignalWatcher(
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor context!: LinkedDocContext;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor rootComponent!: PageRootBlockComponent;
|
||||
}
|
||||
@@ -52,13 +52,6 @@ export const linkedDocPopoverStyles = css`
|
||||
flex-shrink: 0;
|
||||
font-weight: 500;
|
||||
justify-content: space-between;
|
||||
max-width: 240px;
|
||||
}
|
||||
|
||||
.linked-doc-popover .group-title .group-title-text {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.linked-doc-popover .group-title .loading-icon {
|
||||
144
blocksuite/affine/blocks/root/src/widgets/modal/custom-modal.ts
Normal file
144
blocksuite/affine/blocks/root/src/widgets/modal/custom-modal.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
import { css, html, LitElement, nothing } from 'lit';
|
||||
import { ref } from 'lit/directives/ref.js';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
|
||||
type ModalButton = {
|
||||
text: string;
|
||||
type?: 'primary';
|
||||
onClick: () => Promise<void> | void;
|
||||
};
|
||||
|
||||
type ModalOptions = {
|
||||
footer: null | ModalButton[];
|
||||
};
|
||||
|
||||
export class AffineCustomModal extends LitElement {
|
||||
static override styles = css`
|
||||
:host {
|
||||
z-index: calc(var(--affine-z-index-modal) + 3);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.modal-background {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
background-color: var(--affine-background-modal-color);
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.modal-window {
|
||||
width: 70%;
|
||||
min-width: 500px;
|
||||
height: 80%;
|
||||
overflow-y: scroll;
|
||||
background-color: var(--affine-background-overlay-panel-color);
|
||||
border-radius: 12px;
|
||||
box-shadow: var(--affine-shadow-3);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.modal-main {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 20px;
|
||||
padding: 24px;
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.modal-footer .button {
|
||||
align-items: center;
|
||||
background: var(--affine-white);
|
||||
border: 1px solid;
|
||||
border-color: var(--affine-border-color);
|
||||
border-radius: 8px;
|
||||
color: var(--affine-text-primary-color);
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
font-size: var(--affine-font-sm);
|
||||
font-weight: 500;
|
||||
justify-content: center;
|
||||
outline: 0;
|
||||
padding: 12px 18px;
|
||||
touch-action: manipulation;
|
||||
transition: all 0.3s;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.modal-footer .primary {
|
||||
background: var(--affine-primary-color);
|
||||
border-color: var(--affine-black-10);
|
||||
box-shadow: var(--affine-button-inner-shadow);
|
||||
color: var(--affine-pure-white);
|
||||
}
|
||||
`;
|
||||
|
||||
onOpen!: (div: HTMLDivElement) => void;
|
||||
|
||||
options!: ModalOptions;
|
||||
|
||||
close() {
|
||||
this.remove();
|
||||
}
|
||||
|
||||
modalRef(modal: Element | undefined) {
|
||||
if (modal) this.onOpen?.(modal as HTMLDivElement);
|
||||
}
|
||||
|
||||
override render() {
|
||||
const { options } = this;
|
||||
|
||||
return html`<div class="modal-background">
|
||||
<div class="modal-window">
|
||||
<div class="modal-main" ${ref(this.modalRef)}></div>
|
||||
<div class="modal-footer">
|
||||
${options.footer
|
||||
? repeat(
|
||||
options.footer,
|
||||
button => button.text,
|
||||
button => html`
|
||||
<button
|
||||
class="button ${button.type ?? ''}"
|
||||
@click=${button.onClick}
|
||||
>
|
||||
${button.text}
|
||||
</button>
|
||||
`
|
||||
)
|
||||
: nothing}
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
type CreateModalOption = ModalOptions & {
|
||||
entry: (div: HTMLDivElement) => void;
|
||||
};
|
||||
|
||||
export function createCustomModal(
|
||||
options: CreateModalOption,
|
||||
container: HTMLElement = document.body
|
||||
) {
|
||||
const modal = new AffineCustomModal();
|
||||
|
||||
modal.onOpen = options.entry;
|
||||
modal.options = options;
|
||||
|
||||
container.append(modal);
|
||||
|
||||
return modal;
|
||||
}
|
||||
22
blocksuite/affine/blocks/root/src/widgets/modal/modal.ts
Normal file
22
blocksuite/affine/blocks/root/src/widgets/modal/modal.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { WidgetComponent } from '@blocksuite/std';
|
||||
import { nothing } from 'lit';
|
||||
|
||||
import { createCustomModal } from './custom-modal.js';
|
||||
|
||||
export const AFFINE_MODAL_WIDGET = 'affine-modal-widget';
|
||||
|
||||
export class AffineModalWidget extends WidgetComponent {
|
||||
open(options: Parameters<typeof createCustomModal>[0]) {
|
||||
return createCustomModal(options, this.ownerDocument.body);
|
||||
}
|
||||
|
||||
override render() {
|
||||
return nothing;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
[AFFINE_MODAL_WIDGET]: AffineModalWidget;
|
||||
}
|
||||
}
|
||||
@@ -43,8 +43,6 @@
|
||||
{ "path": "../../widgets/edgeless-auto-connect" },
|
||||
{ "path": "../../widgets/edgeless-toolbar" },
|
||||
{ "path": "../../widgets/frame-title" },
|
||||
{ "path": "../../widgets/keyboard-toolbar" },
|
||||
{ "path": "../../widgets/linked-doc" },
|
||||
{ "path": "../../widgets/remote-selection" },
|
||||
{ "path": "../../widgets/scroll-anchoring" },
|
||||
{ "path": "../../widgets/slash-menu" },
|
||||
|
||||
@@ -321,7 +321,7 @@ export const calcCustomButtonStyle = (
|
||||
return { '--b': b, '--c': c };
|
||||
}
|
||||
|
||||
if (color.startsWith('--')) {
|
||||
if (color.startsWith('---')) {
|
||||
if (!color.endsWith('transparent')) {
|
||||
b = 'var(--affine-background-overlay-panel-color)';
|
||||
c = keepColor(
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
import { repeat } from 'lit-html/directives/repeat.js';
|
||||
|
||||
export class TooltipContentWithShortcut extends LitElement {
|
||||
static override styles = css`
|
||||
@@ -10,10 +9,6 @@ export class TooltipContentWithShortcut extends LitElement {
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.tooltip__shortcuts {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
}
|
||||
.tooltip__shortcut {
|
||||
font-size: 12px;
|
||||
position: relative;
|
||||
@@ -33,30 +28,19 @@ export class TooltipContentWithShortcut extends LitElement {
|
||||
opacity: 0.2;
|
||||
}
|
||||
.tooltip__label {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
white-space: pre;
|
||||
}
|
||||
`;
|
||||
|
||||
get shortcuts() {
|
||||
let shortcut = this.shortcut;
|
||||
if (!shortcut) return [];
|
||||
return shortcut.split(' ');
|
||||
}
|
||||
|
||||
override render() {
|
||||
const { tip, shortcuts, postfix } = this;
|
||||
const { tip, shortcut, postfix } = this;
|
||||
|
||||
return html`
|
||||
<div class="tooltip-with-shortcut">
|
||||
<span class="tooltip__label">${tip}</span>
|
||||
<div class="tooltip__shortcuts">
|
||||
${repeat(
|
||||
shortcuts,
|
||||
shortcut => html`<span class="tooltip__shortcut">${shortcut}</span>`
|
||||
)}
|
||||
</div>
|
||||
${shortcut
|
||||
? html`<span class="tooltip__shortcut">${shortcut}</span>`
|
||||
: ''}
|
||||
${postfix ? html`<span class="tooltip__postfix">${postfix}</span>` : ''}
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -26,10 +26,10 @@ export const cardPreview = style({
|
||||
borderRadius: '4px',
|
||||
cursor: 'default',
|
||||
userSelect: 'none',
|
||||
':hover': {
|
||||
background: cssVarV2('layer/background/hoverOverlay'),
|
||||
},
|
||||
selectors: {
|
||||
[`${outlineCard}[data-sortable="true"] &:hover`]: {
|
||||
background: cssVarV2('layer/background/hoverOverlay'),
|
||||
},
|
||||
[`${outlineCard}[data-status="selected"] &`]: {
|
||||
background: cssVarV2('layer/background/hoverOverlay'),
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { cssVar } from '@toeverything/theme';
|
||||
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||
import { globalStyle, style } from '@vanilla-extract/css';
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
export const outlineBlockPreview = style({
|
||||
fontFamily: cssVar('fontFamily'),
|
||||
@@ -108,11 +108,6 @@ export const linkedDocPreviewUnavailable = style({
|
||||
color: cssVarV2('text/disable'),
|
||||
});
|
||||
|
||||
export const linkedDocPreviewAvailable = style({});
|
||||
globalStyle(`${linkedDocPreviewAvailable} > svg`, {
|
||||
marginBottom: '0.1em',
|
||||
});
|
||||
|
||||
export const linkedDocTextUnavailable = style({
|
||||
color: cssVarV2('text/disable'),
|
||||
textDecoration: 'line-through',
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user