mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-04 08:38:34 +00:00
Compare commits
43 Commits
darksky/na
...
v0.10.3-be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc279d062b | ||
|
|
47d5f9e1c2 | ||
|
|
a226eb8d5f | ||
|
|
908c4e1a6f | ||
|
|
1d0bcc80a0 | ||
|
|
50010bd824 | ||
|
|
c0ede1326d | ||
|
|
89197bacef | ||
|
|
f97d323ab5 | ||
|
|
2acb219dcc | ||
|
|
992ed89a89 | ||
|
|
d272d7922d | ||
|
|
c1cd1713b9 | ||
|
|
b20e91bee0 | ||
|
|
9a4e5ec8c3 | ||
|
|
2019838ae7 | ||
|
|
30ff25f400 | ||
|
|
e766208c18 | ||
|
|
8742f28148 | ||
|
|
cd291bb60e | ||
|
|
62c0efcfd1 | ||
|
|
87248b3337 | ||
|
|
00c940f7df | ||
|
|
931b459fbd | ||
|
|
51e71f4a0a | ||
|
|
9b631f2328 | ||
|
|
01f481a9b6 | ||
|
|
0177ab5c87 | ||
|
|
4db35d341c | ||
|
|
3c4a803c97 | ||
|
|
05154dc7ca | ||
|
|
c90b477f60 | ||
|
|
6f18ddbe85 | ||
|
|
dde779a71d | ||
|
|
bd9f66fbc7 | ||
|
|
92f1f40bfa | ||
|
|
48dc1049b3 | ||
|
|
9add530370 | ||
|
|
b77460d871 | ||
|
|
42db41776b | ||
|
|
075439c74f | ||
|
|
fc6c553ece | ||
|
|
59cb3d5df1 |
6
.github/workflows/deploy.yml
vendored
6
.github/workflows/deploy.yml
vendored
@@ -35,7 +35,7 @@ jobs:
|
||||
build-core:
|
||||
name: Build @affine/core
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
environment: ${{ github.event.inputs.flavor }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
@@ -52,6 +52,10 @@ jobs:
|
||||
SHOULD_REPORT_TRACE: true
|
||||
TRACE_REPORT_ENDPOINT: ${{ secrets.TRACE_REPORT_ENDPOINT }}
|
||||
CAPTCHA_SITE_KEY: ${{ secrets.CAPTCHA_SITE_KEY }}
|
||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||
- name: Upload core artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
|
||||
2
.github/workflows/nightly-build.yml
vendored
2
.github/workflows/nightly-build.yml
vendored
@@ -70,8 +70,8 @@ jobs:
|
||||
env:
|
||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
|
||||
NEXT_PUBLIC_SENTRY_DSN: ${{ secrets.NEXT_PUBLIC_SENTRY_DSN }}
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||
RELEASE_VERSION: ${{ needs.set-build-version.outputs.version }}
|
||||
|
||||
- name: Upload core artifact
|
||||
|
||||
2
.github/workflows/release-desktop-app.yml
vendored
2
.github/workflows/release-desktop-app.yml
vendored
@@ -40,6 +40,7 @@ env:
|
||||
jobs:
|
||||
before-make:
|
||||
runs-on: ubuntu-latest
|
||||
environment: ${{ github.event.inputs.build-type || (github.ref_type == 'tag' && contains(github.ref, 'canary') && 'canary') }}
|
||||
outputs:
|
||||
RELEASE_VERSION: ${{ steps.get-canary-version.outputs.RELEASE_VERSION }}
|
||||
steps:
|
||||
@@ -65,6 +66,7 @@ jobs:
|
||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||
RELEASE_VERSION: ${{ github.event.inputs.version || steps.get-canary-version.outputs.RELEASE_VERSION }}
|
||||
SKIP_PLUGIN_BUILD: 'true'
|
||||
|
||||
|
||||
144
Cargo.lock
generated
144
Cargo.lock
generated
@@ -83,15 +83,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.6"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a"
|
||||
checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -241,16 +240,6 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atomic-write-file"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c232177ba50b16fe7a4588495bd474a62a9e45a8e4ca6fd7d0b7ac29d164631e"
|
||||
dependencies = [
|
||||
"nix",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
@@ -952,7 +941,7 @@ version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
|
||||
dependencies = [
|
||||
"ahash 0.8.6",
|
||||
"ahash 0.8.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -961,7 +950,7 @@ version = "0.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156"
|
||||
dependencies = [
|
||||
"ahash 0.8.6",
|
||||
"ahash 0.8.3",
|
||||
"allocator-api2",
|
||||
]
|
||||
|
||||
@@ -1123,7 +1112,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "jwst-codec"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/toeverything/OctoBase.git?rev=49a6b7a#49a6b7af25ce1fe54e8383e10980e9536821d286"
|
||||
source = "git+https://github.com/toeverything/OctoBase.git?rev=aad9e5b#aad9e5b7e9d6f479e6cf7555f5845bbbaaadbc66"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"bitvec",
|
||||
@@ -1144,7 +1133,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "jwst-core"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/toeverything/OctoBase.git?rev=49a6b7a#49a6b7af25ce1fe54e8383e10980e9536821d286"
|
||||
source = "git+https://github.com/toeverything/OctoBase.git?rev=aad9e5b#aad9e5b7e9d6f479e6cf7555f5845bbbaaadbc66"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"base64",
|
||||
@@ -1162,7 +1151,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "jwst-logger"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/toeverything/OctoBase.git?rev=49a6b7a#49a6b7af25ce1fe54e8383e10980e9536821d286"
|
||||
source = "git+https://github.com/toeverything/OctoBase.git?rev=aad9e5b#aad9e5b7e9d6f479e6cf7555f5845bbbaaadbc66"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"nu-ansi-term 0.49.0",
|
||||
@@ -1175,7 +1164,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "jwst-storage"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/toeverything/OctoBase.git?rev=49a6b7a#49a6b7af25ce1fe54e8383e10980e9536821d286"
|
||||
source = "git+https://github.com/toeverything/OctoBase.git?rev=aad9e5b#aad9e5b7e9d6f479e6cf7555f5845bbbaaadbc66"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -1200,7 +1189,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "jwst-storage-migration"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/toeverything/OctoBase.git?rev=49a6b7a#49a6b7af25ce1fe54e8383e10980e9536821d286"
|
||||
source = "git+https://github.com/toeverything/OctoBase.git?rev=aad9e5b#aad9e5b7e9d6f479e6cf7555f5845bbbaaadbc66"
|
||||
dependencies = [
|
||||
"sea-orm-migration",
|
||||
"tokio",
|
||||
@@ -1268,9 +1257,9 @@ checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
|
||||
|
||||
[[package]]
|
||||
name = "libsqlite3-sys"
|
||||
version = "0.27.0"
|
||||
version = "0.26.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716"
|
||||
checksum = "afc22eff61b133b115c6e8c74e818c628d6d5e7a502afea6f64dee076dd94326"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
@@ -1348,15 +1337,6 @@ version = "2.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
@@ -1395,9 +1375,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "napi"
|
||||
version = "2.14.1"
|
||||
version = "2.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1133249c46e92da921bafc8aba4912bf84d6c475f7625183772ed2d0844dc3a7"
|
||||
checksum = "f9d90182620f32fe34b6ac9b52cba898af26e94c7f5abc01eb4094c417ae2e6c"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.4.1",
|
||||
@@ -1419,9 +1399,9 @@ checksum = "d4b4532cf86bfef556348ac65e561e3123879f0e7566cca6d43a6ff5326f13df"
|
||||
|
||||
[[package]]
|
||||
name = "napi-derive"
|
||||
version = "2.14.2"
|
||||
version = "2.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0cca5738c6e81eb5ffd2c8ff2b4f05ece9c4c60c7e2b36cec6524492cf7f330"
|
||||
checksum = "3619fa472d23cd5af94d63a2bae454a77a8863251f40230fbf59ce20eafa8a86"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"convert_case",
|
||||
@@ -1433,9 +1413,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "napi-derive-backend"
|
||||
version = "1.0.55"
|
||||
version = "1.0.54"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35960e5f33228192a9b661447d0dfe8f5a3790ff5b4058c4d67680ded4f65b91"
|
||||
checksum = "ecd3ea4b54020c73d591a49cd192f6334c5f37f71a63ead54dbc851fa991ef00"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"once_cell",
|
||||
@@ -1455,19 +1435,6 @@ dependencies = [
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.26.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"memoffset",
|
||||
"pin-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "no-std-compat"
|
||||
version = "0.4.1"
|
||||
@@ -2325,18 +2292,18 @@ checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.193"
|
||||
version = "1.0.192"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
|
||||
checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.193"
|
||||
version = "1.0.192"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
|
||||
checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2483,9 +2450,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx"
|
||||
version = "0.7.3"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dba03c279da73694ef99763320dea58b51095dfe87d001b1d4b5fe78ba8763cf"
|
||||
checksum = "0e50c216e3624ec8e7ecd14c6a6a6370aad6ee5d8cfc3ab30b5162eeeef2ed33"
|
||||
dependencies = [
|
||||
"sqlx-core",
|
||||
"sqlx-macros",
|
||||
@@ -2496,11 +2463,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-core"
|
||||
version = "0.7.3"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d84b0a3c3739e220d94b3239fd69fb1f74bc36e16643423bd99de3b43c21bfbd"
|
||||
checksum = "8d6753e460c998bbd4cd8c6f0ed9a64346fcca0723d6e75e52fdc351c5d2169d"
|
||||
dependencies = [
|
||||
"ahash 0.8.6",
|
||||
"ahash 0.8.3",
|
||||
"atoi",
|
||||
"bigdecimal",
|
||||
"byteorder",
|
||||
@@ -2544,9 +2511,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-macros"
|
||||
version = "0.7.3"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89961c00dc4d7dffb7aee214964b065072bff69e36ddb9e2c107541f75e4f2a5"
|
||||
checksum = "9a793bb3ba331ec8359c1853bd39eed32cdd7baaf22c35ccf5c92a7e8d1189ec"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2557,11 +2524,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-macros-core"
|
||||
version = "0.7.3"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0bd4519486723648186a08785143599760f7cc81c52334a55d6a83ea1e20841"
|
||||
checksum = "0a4ee1e104e00dedb6aa5ffdd1343107b0a4702e862a84320ee7cc74782d96fc"
|
||||
dependencies = [
|
||||
"atomic-write-file",
|
||||
"dotenvy",
|
||||
"either",
|
||||
"heck",
|
||||
@@ -2584,9 +2550,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-mysql"
|
||||
version = "0.7.3"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e37195395df71fd068f6e2082247891bc11e3289624bbc776a0cdfa1ca7f1ea4"
|
||||
checksum = "864b869fdf56263f4c95c45483191ea0af340f9f3e3e7b4d57a61c7c87a970db"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"base64",
|
||||
@@ -2631,9 +2597,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-postgres"
|
||||
version = "0.7.3"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6ac0ac3b7ccd10cc96c7ab29791a7dd236bd94021f31eec7ba3d46a74aa1c24"
|
||||
checksum = "eb7ae0e6a97fb3ba33b23ac2671a5ce6e3cabe003f451abd5a56e7951d975624"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"base64",
|
||||
@@ -2676,9 +2642,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-sqlite"
|
||||
version = "0.7.3"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "210976b7d948c7ba9fced8ca835b11cbb2d677c59c79de41ac0d397e14547490"
|
||||
checksum = "d59dc83cf45d89c555a577694534fcd1b55c545a816c816ce51f20bbe56a4f3f"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"chrono",
|
||||
@@ -2696,7 +2662,6 @@ dependencies = [
|
||||
"time",
|
||||
"tracing",
|
||||
"url",
|
||||
"urlencoding",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
@@ -3059,12 +3024,6 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urlencoding"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.1"
|
||||
@@ -3073,9 +3032,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.6.1"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560"
|
||||
checksum = "c58fe91d841bc04822c9801002db4ea904b9e4b8e6bbad25127b46eff8dc516b"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"rand",
|
||||
@@ -3182,9 +3141,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.25.3"
|
||||
version = "0.24.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10"
|
||||
checksum = "b291546d5d9d1eab74f069c77749f2cb8504a12caa20f0f2de93ddbf6f411888"
|
||||
dependencies = [
|
||||
"rustls-webpki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "whoami"
|
||||
@@ -3316,26 +3278,6 @@ dependencies = [
|
||||
"tap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.7.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e97e415490559a91254a2979b4829267a57d2fcd741a98eee8b722fb57289aa0"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.7.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd7e48ccf166952882ca8bd778a43502c64f33bf94c12ebe2a7f08e5a0f6689f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.38",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.6.0"
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
# Issues Triaging
|
||||
|
||||
When we receive your issue, we will first triaging it. Triaging an issue usually takes around one business day but may take longer. Goal of triaging is to provide you with a clear understanding of what will happen to your issue. For example, after your feature request was triaged you know whether we plan to tackle the issue or whether we'll wait to hear what the broader community thinks about this request.
|
||||
|
||||
Here are issue states and their descriptions:
|
||||
|
||||
| State | Description |
|
||||
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Untriaged | The team has not yet reviewed the issue. We usually do it within one business day. |
|
||||
| As designed | The behavior described in the issue is intentional. If you find it seriously disruptive or if we’ve misunderstood you, please let us know in the issue’s comments section. |
|
||||
| Blocked | We can’t work on this issue until another one (linked) is resolved. |
|
||||
| Can’t Reproduce | We have been unable to reproduce the issue on our side. It could be flaky or fixed already, or we may not have had all the details we needed. If you’re still experiencing the issue and have any further details, please share them. |
|
||||
| Duplicate | The issue is the same (or has the same cause) as another one (linked). |
|
||||
| Fixed | If the issue was a bug, it’s been fixed; if it was a missing feature, it’s been implemented. |
|
||||
| Fixed In Branch | If the issue was a bug, it’s been fixed; if it was a missing feature, it’s been implemented; the changes are now in a separate branch and haven’t been merged into the default branch yet. |
|
||||
| In Progress | We’re currently working on the issue. |
|
||||
| Incomplete | Unfortunately we don’t have enough information to proceed. If you’re willing to share any further details about the issue, please do so in the comments. |
|
||||
| Obsolete | The part of the product that was causing this issue has been removed or significantly reworked since it was created. |
|
||||
| Upvoting | We are currently evaluating demand for the issue and checking whether it requires complicated or risky changes. Please leave a vote or comment if you think it should be prioritized. |
|
||||
| Open | We want to implement the fix or feature in the near future. We can’t promise it will appear in the next public release, but it’s on our short list. |
|
||||
| Shelved | We have reviewed the issue and decided that, even though it has merit, we cannot currently include it in our near-term plan. |
|
||||
| Third Party Problem | The issue is caused by a third party. We've done our best to inform them about it. |
|
||||
| To be Discussed | We need some time to discuss the issue. |
|
||||
| To Reproduce | We will try to find the steps needed to reproduce the issue on our side. |
|
||||
| Under Investigation | We’ve triaged the issue, but now we need to investigate it more thoroughly. This may require processing additional information like logs or dumps. |
|
||||
| Waiting for Info | We’ve requested additional information from the person who created the issue and are waiting for them to get back to us. |
|
||||
| Declined | We’ve reviewed the suggestion and, while we appreciate its value, we unfortunately do not have the resources to implement it. |
|
||||
| Answered | The issue actually turned out to be a question or a misunderstanding, and it has been answered or resolved. |
|
||||
2
nx.json
2
nx.json
@@ -56,7 +56,7 @@
|
||||
"env": "SENTRY_AUTH_TOKEN"
|
||||
},
|
||||
{
|
||||
"env": "NEXT_PUBLIC_SENTRY_DSN"
|
||||
"env": "SENTRY_DSN"
|
||||
},
|
||||
{
|
||||
"env": "DISTRIBUTION"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@affine/monorepo",
|
||||
"version": "0.10.3-canary.2",
|
||||
"version": "0.10.3-beta.5",
|
||||
"private": true,
|
||||
"author": "toeverything",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "optimized_blobs" ADD COLUMN "deleted_at" TIMESTAMPTZ(6);
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@affine/server",
|
||||
"private": true,
|
||||
"version": "0.10.3-canary.2",
|
||||
"version": "0.10.3-beta.5",
|
||||
"description": "Affine Node.js server",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
|
||||
@@ -178,15 +178,13 @@ model Blob {
|
||||
}
|
||||
|
||||
model OptimizedBlob {
|
||||
id Int @id @default(autoincrement()) @db.Integer
|
||||
hash String @db.VarChar
|
||||
workspaceId String @map("workspace_id") @db.VarChar
|
||||
params String @db.VarChar
|
||||
blob Bytes @db.ByteA
|
||||
id Int @id @default(autoincrement()) @db.Integer
|
||||
hash String @db.VarChar
|
||||
workspaceId String @map("workspace_id") @db.VarChar
|
||||
params String @db.VarChar
|
||||
blob Bytes @db.ByteA
|
||||
length BigInt
|
||||
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6)
|
||||
// not for keeping, but for snapshot history
|
||||
deletedAt DateTime? @map("deleted_at") @db.Timestamptz(6)
|
||||
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6)
|
||||
|
||||
@@unique([workspaceId, hash, params])
|
||||
@@map("optimized_blobs")
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { Field, ObjectType, Query } from '@nestjs/graphql';
|
||||
|
||||
import { SERVER_FLAVOR } from '../modules';
|
||||
|
||||
@ObjectType()
|
||||
export class ServerConfigType {
|
||||
@Field({ description: 'server version' })
|
||||
version!: string;
|
||||
|
||||
@Field({ description: 'server flavor' })
|
||||
flavor!: string;
|
||||
}
|
||||
|
||||
export class ServerConfigResolver {
|
||||
@Query(() => ServerConfigType, {
|
||||
description: 'server config',
|
||||
})
|
||||
serverConfig(): ServerConfigType {
|
||||
return {
|
||||
version: AFFiNE.version,
|
||||
flavor: SERVER_FLAVOR || 'allinone',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@Module({
|
||||
providers: [ServerConfigResolver],
|
||||
})
|
||||
export class ServerConfigModule {}
|
||||
@@ -89,7 +89,7 @@ export class DocHistoryManager {
|
||||
workspaceId,
|
||||
id,
|
||||
timestamp: {
|
||||
lt: before,
|
||||
lte: before,
|
||||
},
|
||||
// only include the ones has not expired
|
||||
expiredAt: {
|
||||
|
||||
@@ -3,10 +3,9 @@ import { EventEmitterModule } from '@nestjs/event-emitter';
|
||||
import { ScheduleModule } from '@nestjs/schedule';
|
||||
|
||||
import { GqlModule } from '../graphql.module';
|
||||
import { ServerConfigModule } from './config';
|
||||
import { AuthModule } from './auth';
|
||||
import { DocModule } from './doc';
|
||||
import { PaymentModule } from './payment';
|
||||
import { SelfHostedModule } from './self-hosted';
|
||||
import { SyncModule } from './sync';
|
||||
import { UsersModule } from './users';
|
||||
import { WorkspaceModule } from './workspaces';
|
||||
@@ -23,25 +22,13 @@ switch (SERVER_FLAVOR) {
|
||||
case 'sync':
|
||||
BusinessModules.push(SyncModule, DocModule.forSync());
|
||||
break;
|
||||
case 'selfhosted':
|
||||
BusinessModules.push(
|
||||
ServerConfigModule,
|
||||
SelfHostedModule,
|
||||
ScheduleModule.forRoot(),
|
||||
GqlModule,
|
||||
WorkspaceModule,
|
||||
UsersModule,
|
||||
SyncModule,
|
||||
DocModule.forRoot()
|
||||
);
|
||||
break;
|
||||
case 'graphql':
|
||||
BusinessModules.push(
|
||||
ServerConfigModule,
|
||||
ScheduleModule.forRoot(),
|
||||
GqlModule,
|
||||
WorkspaceModule,
|
||||
UsersModule,
|
||||
AuthModule,
|
||||
DocModule.forRoot(),
|
||||
PaymentModule
|
||||
);
|
||||
@@ -49,11 +36,11 @@ switch (SERVER_FLAVOR) {
|
||||
case 'allinone':
|
||||
default:
|
||||
BusinessModules.push(
|
||||
ServerConfigModule,
|
||||
ScheduleModule.forRoot(),
|
||||
GqlModule,
|
||||
WorkspaceModule,
|
||||
UsersModule,
|
||||
AuthModule,
|
||||
SyncModule,
|
||||
DocModule.forRoot(),
|
||||
PaymentModule
|
||||
@@ -61,4 +48,4 @@ switch (SERVER_FLAVOR) {
|
||||
break;
|
||||
}
|
||||
|
||||
export { BusinessModules, SERVER_FLAVOR };
|
||||
export { BusinessModules };
|
||||
|
||||
@@ -52,7 +52,7 @@ class SubscriptionPrice {
|
||||
}
|
||||
|
||||
@ObjectType('UserSubscription')
|
||||
export class UserSubscriptionType implements Partial<UserSubscription> {
|
||||
class UserSubscriptionType implements Partial<UserSubscription> {
|
||||
@Field({ name: 'id' })
|
||||
stripeSubscriptionId!: string;
|
||||
|
||||
|
||||
@@ -30,7 +30,6 @@ export enum SubscriptionPlan {
|
||||
Pro = 'pro',
|
||||
Team = 'team',
|
||||
Enterprise = 'enterprise',
|
||||
SelfHosted = 'selfhosted',
|
||||
}
|
||||
|
||||
export function encodeLookupKey(
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ResolveField, Resolver } from '@nestjs/graphql';
|
||||
|
||||
import { UserSubscriptionType } from './payment/resolver';
|
||||
import {
|
||||
SubscriptionPlan,
|
||||
SubscriptionRecurring,
|
||||
SubscriptionStatus,
|
||||
} from './payment/service';
|
||||
import { UserType } from './users';
|
||||
|
||||
const YEAR = 1000 * 60 * 60 * 24 * 30 * 12;
|
||||
|
||||
@Resolver(() => UserType)
|
||||
export class SelfHostedDummyResolver {
|
||||
private readonly start = new Date();
|
||||
private readonly end = new Date(Number(this.start) + YEAR);
|
||||
constructor() {}
|
||||
|
||||
@ResolveField(() => UserSubscriptionType)
|
||||
async subscription() {
|
||||
return {
|
||||
stripeSubscriptionId: 'dummy',
|
||||
plan: SubscriptionPlan.SelfHosted,
|
||||
recurring: SubscriptionRecurring.Yearly,
|
||||
status: SubscriptionStatus.Active,
|
||||
start: this.start,
|
||||
end: this.end,
|
||||
createdAt: this.start,
|
||||
updatedAt: this.start,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@Module({
|
||||
providers: [SelfHostedDummyResolver],
|
||||
})
|
||||
export class SelfHostedModule {}
|
||||
@@ -2,14 +2,6 @@
|
||||
# THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY)
|
||||
# ------------------------------------------------------
|
||||
|
||||
type ServerConfigType {
|
||||
"""server version"""
|
||||
version: String!
|
||||
|
||||
"""server flavor"""
|
||||
flavor: String!
|
||||
}
|
||||
|
||||
type UserType {
|
||||
id: ID!
|
||||
|
||||
@@ -81,7 +73,6 @@ enum SubscriptionPlan {
|
||||
Pro
|
||||
Team
|
||||
Enterprise
|
||||
SelfHosted
|
||||
}
|
||||
|
||||
type UserSubscription {
|
||||
@@ -249,9 +240,6 @@ type DocHistoryType {
|
||||
}
|
||||
|
||||
type Query {
|
||||
"""server config"""
|
||||
serverConfig: ServerConfigType!
|
||||
|
||||
"""Get is owner of workspace"""
|
||||
isOwner(workspaceId: String!): Boolean!
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@ crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
chrono = "0.4"
|
||||
jwst-codec = { git = "https://github.com/toeverything/OctoBase.git", rev = "49a6b7a" }
|
||||
jwst-core = { git = "https://github.com/toeverything/OctoBase.git", rev = "49a6b7a" }
|
||||
jwst-storage = { git = "https://github.com/toeverything/OctoBase.git", rev = "49a6b7a" }
|
||||
jwst-codec = { git = "https://github.com/toeverything/OctoBase.git", rev = "aad9e5b" }
|
||||
jwst-core = { git = "https://github.com/toeverything/OctoBase.git", rev = "aad9e5b" }
|
||||
jwst-storage = { git = "https://github.com/toeverything/OctoBase.git", rev = "aad9e5b" }
|
||||
napi = { version = "2", default-features = false, features = [
|
||||
"napi5",
|
||||
"async",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@affine/storage",
|
||||
"version": "0.10.3-canary.2",
|
||||
"version": "0.10.3-beta.5",
|
||||
"engines": {
|
||||
"node": ">= 10.16.0 < 11 || >= 11.8.0"
|
||||
},
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0"
|
||||
},
|
||||
"version": "0.10.3-canary.2"
|
||||
"version": "0.10.3-beta.5"
|
||||
}
|
||||
|
||||
@@ -9,5 +9,5 @@
|
||||
"@types/debug": "^4.1.9",
|
||||
"vitest": "0.34.6"
|
||||
},
|
||||
"version": "0.10.3-canary.2"
|
||||
"version": "0.10.3-beta.5"
|
||||
}
|
||||
|
||||
6
packages/common/env/package.json
vendored
6
packages/common/env/package.json
vendored
@@ -3,8 +3,8 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@blocksuite/global": "0.0.0-20231124123613-7c06e95d-nightly",
|
||||
"@blocksuite/store": "0.0.0-20231124123613-7c06e95d-nightly",
|
||||
"@blocksuite/global": "0.0.0-20231122113751-6bf81eb3-nightly",
|
||||
"@blocksuite/store": "0.0.0-20231122113751-6bf81eb3-nightly",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"vitest": "0.34.6",
|
||||
@@ -27,5 +27,5 @@
|
||||
"dependencies": {
|
||||
"lit": "^3.0.2"
|
||||
},
|
||||
"version": "0.10.3-canary.2"
|
||||
"version": "0.10.3-beta.5"
|
||||
}
|
||||
|
||||
1
packages/common/env/src/global.ts
vendored
1
packages/common/env/src/global.ts
vendored
@@ -32,7 +32,6 @@ export const runtimeFlagsSchema = z.object({
|
||||
enableCaptcha: z.boolean(),
|
||||
enableEnhanceShareMode: z.boolean(),
|
||||
enablePayment: z.boolean(),
|
||||
enablePageHistory: z.boolean(),
|
||||
// this is for the electron app
|
||||
serverUrlPrefix: z.string(),
|
||||
enableMoveDatabase: z.boolean(),
|
||||
|
||||
@@ -55,9 +55,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@affine/sdk": "workspace:*",
|
||||
"@blocksuite/blocks": "0.0.0-20231124123613-7c06e95d-nightly",
|
||||
"@blocksuite/global": "0.0.0-20231124123613-7c06e95d-nightly",
|
||||
"@blocksuite/store": "0.0.0-20231124123613-7c06e95d-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20231122113751-6bf81eb3-nightly",
|
||||
"@blocksuite/global": "0.0.0-20231122113751-6bf81eb3-nightly",
|
||||
"@blocksuite/store": "0.0.0-20231122113751-6bf81eb3-nightly",
|
||||
"jotai": "^2.5.1",
|
||||
"jotai-effect": "^0.2.3",
|
||||
"tinykeys": "^2.1.0",
|
||||
@@ -66,8 +66,8 @@
|
||||
"devDependencies": {
|
||||
"@affine-test/fixtures": "workspace:*",
|
||||
"@affine/templates": "workspace:*",
|
||||
"@blocksuite/editor": "0.0.0-20231124123613-7c06e95d-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20231124123613-7c06e95d-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20231122113751-6bf81eb3-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20231122113751-6bf81eb3-nightly",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"async-call-rpc": "^6.3.1",
|
||||
"electron": "link:../../frontend/electron/node_modules/electron",
|
||||
@@ -107,5 +107,5 @@
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"version": "0.10.3-canary.2"
|
||||
"version": "0.10.3-beta.5"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { Page, PageMeta, Workspace } from '@blocksuite/store';
|
||||
import type { createStore, WritableAtom } from 'jotai/vanilla';
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
import { checkWorkspaceCompatibility, MigrationPoint } from '..';
|
||||
import { migratePages } from '../migration/blocksuite';
|
||||
|
||||
export async function initEmptyPage(page: Page, title?: string) {
|
||||
@@ -244,46 +245,48 @@ export async function buildShowcaseWorkspace(
|
||||
{} as Record<string, string>
|
||||
);
|
||||
});
|
||||
await Promise.all(
|
||||
data.map(async ([id, promise, newId]) => {
|
||||
const { default: template } = await promise;
|
||||
let json = JSON.stringify(template);
|
||||
Object.entries(idMap).forEach(([oldId, newId]) => {
|
||||
json = json.replaceAll(oldId, newId);
|
||||
});
|
||||
json = JSON.parse(json);
|
||||
await workspace
|
||||
.importPageSnapshot(structuredClone(json), newId)
|
||||
.catch(error => {
|
||||
console.error('error importing page', id, error);
|
||||
});
|
||||
const page = workspace.getPage(newId);
|
||||
assertExists(page);
|
||||
await page.load();
|
||||
workspace.schema.upgradePage(
|
||||
0,
|
||||
{
|
||||
'affine:note': 1,
|
||||
'affine:bookmark': 1,
|
||||
'affine:database': 2,
|
||||
'affine:divider': 1,
|
||||
'affine:image': 1,
|
||||
'affine:list': 1,
|
||||
'affine:code': 1,
|
||||
'affine:page': 2,
|
||||
'affine:paragraph': 1,
|
||||
'affine:surface': 3,
|
||||
},
|
||||
page.spaceDoc
|
||||
);
|
||||
|
||||
// The showcase building will create multiple pages once, and may skip the version writing.
|
||||
// https://github.com/toeverything/blocksuite/blob/master/packages/store/src/workspace/page.ts#L662
|
||||
if (!workspace.meta.blockVersions) {
|
||||
await migratePages(workspace.doc, workspace.schema);
|
||||
}
|
||||
})
|
||||
);
|
||||
// Import page one by one to prevent workspace meta race condition problem.
|
||||
for (const [id, promise, newId] of data) {
|
||||
const { default: template } = await promise;
|
||||
let json = JSON.stringify(template);
|
||||
Object.entries(idMap).forEach(([oldId, newId]) => {
|
||||
json = json.replaceAll(oldId, newId);
|
||||
});
|
||||
json = JSON.parse(json);
|
||||
await workspace
|
||||
.importPageSnapshot(structuredClone(json), newId)
|
||||
.catch(error => {
|
||||
console.error('error importing page', id, error);
|
||||
});
|
||||
const page = workspace.getPage(newId);
|
||||
assertExists(page);
|
||||
await page.load();
|
||||
workspace.schema.upgradePage(
|
||||
0,
|
||||
{
|
||||
'affine:note': 1,
|
||||
'affine:bookmark': 1,
|
||||
'affine:database': 2,
|
||||
'affine:divider': 1,
|
||||
'affine:image': 1,
|
||||
'affine:list': 1,
|
||||
'affine:code': 1,
|
||||
'affine:page': 2,
|
||||
'affine:paragraph': 1,
|
||||
'affine:surface': 3,
|
||||
},
|
||||
page.spaceDoc
|
||||
);
|
||||
}
|
||||
|
||||
// The showcase building will create multiple pages once, and may skip the version writing.
|
||||
// https://github.com/toeverything/blocksuite/blob/master/packages/store/src/workspace/page.ts#L662
|
||||
const compatibilityResult = checkWorkspaceCompatibility(workspace);
|
||||
if (compatibilityResult === MigrationPoint.BlockVersion) {
|
||||
await migratePages(workspace.doc, workspace.schema);
|
||||
}
|
||||
|
||||
Object.entries(pageMetas).forEach(([oldId, meta]) => {
|
||||
const newId = idMap[oldId];
|
||||
workspace.setPageMeta(newId, meta);
|
||||
|
||||
@@ -20,13 +20,18 @@ export async function migratePages(
|
||||
const meta = rootDoc.getMap('meta') as YMap<unknown>;
|
||||
const versions = meta.get('blockVersions') as YMap<number>;
|
||||
const oldVersions = versions?.toJSON() ?? {};
|
||||
|
||||
spaces.forEach((space: YDoc) => {
|
||||
try {
|
||||
schema.upgradePage(0, oldVersions, space);
|
||||
} catch (e) {
|
||||
console.error(`page ${space.guid} upgrade failed`, e);
|
||||
}
|
||||
schema.upgradePage(0, oldVersions, space);
|
||||
});
|
||||
schema.upgradeWorkspace(rootDoc);
|
||||
|
||||
// Hard code to upgrade page version to 2.
|
||||
// Let e2e to ensure the data version is correct.
|
||||
const pageVersion = meta.get('pageVersion');
|
||||
if (typeof pageVersion !== 'number' || pageVersion < 2) {
|
||||
meta.set('pageVersion', 2);
|
||||
}
|
||||
|
||||
const newVersions = getLatestVersions(schema);
|
||||
meta.set('blockVersions', new YMap(Object.entries(newVersions)));
|
||||
|
||||
@@ -43,3 +43,25 @@ export function guidCompatibilityFix(rootDoc: YDoc) {
|
||||
});
|
||||
return changed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hard code to fix workspace version to be compatible with legacy data.
|
||||
* Let e2e to ensure the data version is correct.
|
||||
*/
|
||||
export function fixWorkspaceVersion(rootDoc: YDoc) {
|
||||
const meta = rootDoc.getMap('meta') as YMap<unknown>;
|
||||
|
||||
/**
|
||||
* It doesn't matter to upgrade workspace version from 1 or undefined to 2.
|
||||
* Blocksuite just set the value, do nothing else.
|
||||
*/
|
||||
const workspaceVersion = meta.get('workspaceVersion');
|
||||
if (typeof workspaceVersion !== 'number' || workspaceVersion < 2) {
|
||||
meta.set('workspaceVersion', 2);
|
||||
|
||||
const pageVersion = meta.get('pageVersion');
|
||||
if (typeof pageVersion !== 'number') {
|
||||
meta.set('pageVersion', 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,19 +58,16 @@ export function checkWorkspaceCompatibility(
|
||||
return MigrationPoint.SubDoc;
|
||||
}
|
||||
|
||||
// Sometimes, blocksuite will not write blockVersions to meta.
|
||||
// Just fix it when user open the workspace.
|
||||
const blockVersions = workspace.meta.blockVersions;
|
||||
if (!blockVersions) {
|
||||
const hasVersion = workspace.meta.hasVersion;
|
||||
if (!hasVersion) {
|
||||
return MigrationPoint.BlockVersion;
|
||||
}
|
||||
|
||||
// From v2, we depend on blocksuite to check and migrate data.
|
||||
for (const [flavour, version] of Object.entries(blockVersions)) {
|
||||
const schema = workspace.schema.flavourSchemaMap.get(flavour);
|
||||
if (schema?.version !== version) {
|
||||
return MigrationPoint.BlockVersion;
|
||||
}
|
||||
try {
|
||||
workspace.meta.validateVersion(workspace);
|
||||
} catch (e) {
|
||||
console.info('validateVersion error', e);
|
||||
return MigrationPoint.BlockVersion;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@affine/sdk",
|
||||
"version": "0.10.3-canary.2",
|
||||
"version": "0.10.3-beta.5",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
@@ -22,11 +22,11 @@
|
||||
"dist"
|
||||
],
|
||||
"dependencies": {
|
||||
"@blocksuite/block-std": "0.0.0-20231124123613-7c06e95d-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20231124123613-7c06e95d-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20231124123613-7c06e95d-nightly",
|
||||
"@blocksuite/global": "0.0.0-20231124123613-7c06e95d-nightly",
|
||||
"@blocksuite/store": "0.0.0-20231124123613-7c06e95d-nightly",
|
||||
"@blocksuite/block-std": "0.0.0-20231122113751-6bf81eb3-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20231122113751-6bf81eb3-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20231122113751-6bf81eb3-nightly",
|
||||
"@blocksuite/global": "0.0.0-20231122113751-6bf81eb3-nightly",
|
||||
"@blocksuite/store": "0.0.0-20231122113751-6bf81eb3-nightly",
|
||||
"jotai": "^2.5.1",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@toeverything/y-indexeddb",
|
||||
"type": "module",
|
||||
"version": "0.10.3-canary.2",
|
||||
"version": "0.10.3-beta.5",
|
||||
"description": "IndexedDB database adapter for Yjs",
|
||||
"repository": "toeverything/AFFiNE",
|
||||
"author": "toeverything",
|
||||
@@ -37,8 +37,8 @@
|
||||
"y-provider": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@blocksuite/blocks": "0.0.0-20231124123613-7c06e95d-nightly",
|
||||
"@blocksuite/store": "0.0.0-20231124123613-7c06e95d-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20231122113751-6bf81eb3-nightly",
|
||||
"@blocksuite/store": "0.0.0-20231122113751-6bf81eb3-nightly",
|
||||
"fake-indexeddb": "^5.0.0",
|
||||
"vite": "^4.4.11",
|
||||
"vite-plugin-dts": "3.6.0",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "y-provider",
|
||||
"type": "module",
|
||||
"version": "0.10.3-canary.2",
|
||||
"version": "0.10.3-beta.5",
|
||||
"description": "Yjs provider protocol for multi document support",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
@@ -24,7 +24,7 @@
|
||||
"build": "vite build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@blocksuite/store": "0.0.0-20231124123613-7c06e95d-nightly",
|
||||
"@blocksuite/store": "0.0.0-20231122113751-6bf81eb3-nightly",
|
||||
"vite": "^4.4.11",
|
||||
"vite-plugin-dts": "3.6.0",
|
||||
"vitest": "0.34.6",
|
||||
|
||||
@@ -64,12 +64,12 @@
|
||||
"uuid": "^9.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@blocksuite/blocks": "0.0.0-20231124123613-7c06e95d-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20231124123613-7c06e95d-nightly",
|
||||
"@blocksuite/global": "0.0.0-20231124123613-7c06e95d-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20231122113751-6bf81eb3-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20231122113751-6bf81eb3-nightly",
|
||||
"@blocksuite/global": "0.0.0-20231122113751-6bf81eb3-nightly",
|
||||
"@blocksuite/icons": "2.1.36",
|
||||
"@blocksuite/lit": "0.0.0-20231124123613-7c06e95d-nightly",
|
||||
"@blocksuite/store": "0.0.0-20231124123613-7c06e95d-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20231122113751-6bf81eb3-nightly",
|
||||
"@blocksuite/store": "0.0.0-20231122113751-6bf81eb3-nightly",
|
||||
"@storybook/jest": "^0.2.3",
|
||||
"@storybook/testing-library": "^0.2.2",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
@@ -85,5 +85,5 @@
|
||||
"vitest": "0.34.6",
|
||||
"yjs": "^13.6.10"
|
||||
},
|
||||
"version": "0.10.3-canary.2"
|
||||
"version": "0.10.3-beta.5"
|
||||
}
|
||||
|
||||
@@ -7,14 +7,11 @@ import type { CSSProperties, ReactElement } from 'react';
|
||||
import {
|
||||
memo,
|
||||
Suspense,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import type { FallbackProps } from 'react-error-boundary';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
|
||||
import { Skeleton } from '../../ui/skeleton';
|
||||
import {
|
||||
@@ -77,6 +74,63 @@ const useBlockElementById = (
|
||||
return blockElement;
|
||||
};
|
||||
|
||||
/**
|
||||
* TODO: Define error to unexpected state together in the future.
|
||||
*/
|
||||
export class NoPageRootError extends Error {
|
||||
constructor(public page: Page) {
|
||||
super('Page root not found when render editor!');
|
||||
|
||||
// Log info to let sentry collect more message
|
||||
const spaceVectors = Array.from(page.doc.spaces.entries()).map(
|
||||
([pageId, doc]) => `${pageId} > ${doc.guid}`
|
||||
);
|
||||
const blocks = page.doc.getMap('blocks');
|
||||
console.info(
|
||||
'NoPageRootError current data: %s',
|
||||
JSON.stringify({
|
||||
expectPageId: page.id,
|
||||
expectGuid: page.spaceDoc.guid,
|
||||
spaceVectors,
|
||||
blockSize: blocks.size,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Defined async cache to support suspense, instead of reflect symbol to provider persistent error cache.
|
||||
*/
|
||||
const PAGE_LOAD_KEY = Symbol('PAGE_LOAD');
|
||||
const PAGE_ROOT_KEY = Symbol('PAGE_ROOT');
|
||||
function usePageRoot(page: Page) {
|
||||
let load$ = Reflect.get(page, PAGE_LOAD_KEY);
|
||||
if (!load$) {
|
||||
load$ = page.load();
|
||||
Reflect.set(page, PAGE_LOAD_KEY, load$);
|
||||
}
|
||||
use(load$);
|
||||
|
||||
if (!page.root) {
|
||||
let root$: Promise<void> | undefined = Reflect.get(page, PAGE_ROOT_KEY);
|
||||
if (!root$) {
|
||||
root$ = new Promise((resolve, reject) => {
|
||||
const disposable = page.slots.rootAdded.once(() => {
|
||||
resolve();
|
||||
});
|
||||
window.setTimeout(() => {
|
||||
disposable.dispose();
|
||||
reject(new NoPageRootError(page));
|
||||
}, 20 * 1000);
|
||||
});
|
||||
Reflect.set(page, PAGE_ROOT_KEY, root$);
|
||||
}
|
||||
use(root$);
|
||||
}
|
||||
|
||||
return page.root;
|
||||
}
|
||||
|
||||
const BlockSuiteEditorImpl = ({
|
||||
mode,
|
||||
page,
|
||||
@@ -86,9 +140,8 @@ const BlockSuiteEditorImpl = ({
|
||||
onModeChange,
|
||||
style,
|
||||
}: EditorProps): ReactElement => {
|
||||
if (!page.loaded) {
|
||||
use(page.waitForLoaded());
|
||||
}
|
||||
usePageRoot(page);
|
||||
|
||||
assertExists(page, 'page should not be null');
|
||||
const editorRef = useRef<EditorContainer | null>(null);
|
||||
if (editorRef.current === null) {
|
||||
@@ -176,27 +229,7 @@ const BlockSuiteEditorImpl = ({
|
||||
);
|
||||
};
|
||||
|
||||
const BlockSuiteErrorFallback = (
|
||||
props: FallbackProps & ErrorBoundaryProps
|
||||
): ReactElement => {
|
||||
return (
|
||||
<div>
|
||||
<h1>Sorry.. there was an error</h1>
|
||||
<div>{props.error.message}</div>
|
||||
<button
|
||||
data-testid="error-fallback-reset-button"
|
||||
onClick={() => {
|
||||
props.onReset?.();
|
||||
props.resetErrorBoundary();
|
||||
}}
|
||||
>
|
||||
Try again
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const BlockSuiteFallback = memo(function BlockSuiteFallback() {
|
||||
export const EditorLoading = memo(function EditorLoading() {
|
||||
return (
|
||||
<div className={blockSuiteEditorStyle}>
|
||||
<Skeleton
|
||||
@@ -210,21 +243,12 @@ export const BlockSuiteFallback = memo(function BlockSuiteFallback() {
|
||||
});
|
||||
|
||||
export const BlockSuiteEditor = memo(function BlockSuiteEditor(
|
||||
props: EditorProps & ErrorBoundaryProps
|
||||
props: EditorProps
|
||||
): ReactElement {
|
||||
return (
|
||||
<ErrorBoundary
|
||||
fallbackRender={useCallback(
|
||||
(fallbackProps: FallbackProps) => (
|
||||
<BlockSuiteErrorFallback {...fallbackProps} onReset={props.onReset} />
|
||||
),
|
||||
[props.onReset]
|
||||
)}
|
||||
>
|
||||
<Suspense fallback={<BlockSuiteFallback />}>
|
||||
<BlockSuiteEditorImpl key={props.page.id} {...props} />
|
||||
</Suspense>
|
||||
</ErrorBoundary>
|
||||
<Suspense fallback={<EditorLoading />}>
|
||||
<BlockSuiteEditorImpl key={props.page.id} {...props} />
|
||||
</Suspense>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BlockSuiteFallback } from '../block-suite-editor';
|
||||
import { EditorLoading } from '../block-suite-editor';
|
||||
import {
|
||||
pageDetailSkeletonStyle,
|
||||
pageDetailSkeletonTitleStyle,
|
||||
@@ -8,7 +8,7 @@ export const PageDetailSkeleton = () => {
|
||||
return (
|
||||
<div className={pageDetailSkeletonStyle}>
|
||||
<div className={pageDetailSkeletonTitleStyle} />
|
||||
<BlockSuiteFallback />
|
||||
<EditorLoading />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { createVar, style } from '@vanilla-extract/css';
|
||||
|
||||
export const hoverMaxWidth = createVar();
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
export const root = style({
|
||||
position: 'relative',
|
||||
@@ -17,8 +15,7 @@ export const tagsContainer = style({
|
||||
export const tagsScrollContainer = style([
|
||||
tagsContainer,
|
||||
{
|
||||
overflowX: 'hidden',
|
||||
position: 'relative',
|
||||
overflow: 'auto',
|
||||
height: '100%',
|
||||
gap: '8px',
|
||||
},
|
||||
@@ -44,7 +41,7 @@ export const innerContainer = style({
|
||||
transition: 'all 0.2s 0.3s ease-in-out',
|
||||
selectors: {
|
||||
[`${root}:hover &`]: {
|
||||
maxWidth: hoverMaxWidth,
|
||||
maxWidth: 'var(--hover-max-width)',
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -69,16 +66,6 @@ export const innerBackdrop = style({
|
||||
|
||||
export const tag = style({
|
||||
height: '20px',
|
||||
display: 'flex',
|
||||
minWidth: 0,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
':last-child': {
|
||||
minWidth: 'max-content',
|
||||
},
|
||||
});
|
||||
|
||||
export const tagInnerWrapper = style({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
@@ -87,7 +74,7 @@ export const tagInnerWrapper = style({
|
||||
});
|
||||
|
||||
export const tagSticky = style([
|
||||
tagInnerWrapper,
|
||||
tag,
|
||||
{
|
||||
fontSize: 'var(--affine-font-xs)',
|
||||
borderRadius: '10px',
|
||||
@@ -95,8 +82,10 @@ export const tagSticky = style([
|
||||
border: '1px solid var(--affine-border-color)',
|
||||
background: 'var(--affine-background-primary-color)',
|
||||
maxWidth: '128px',
|
||||
position: 'sticky',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
left: 0,
|
||||
},
|
||||
]);
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import type { Tag } from '@affine/env/filter';
|
||||
import { MoreHorizontalIcon } from '@blocksuite/icons';
|
||||
import { Menu } from '@toeverything/components/menu';
|
||||
import { assignInlineVars } from '@vanilla-extract/dynamic';
|
||||
import clsx from 'clsx';
|
||||
import { useMemo } from 'react';
|
||||
import { useEffect, useMemo, useRef } from 'react';
|
||||
|
||||
import * as styles from './page-tags.css';
|
||||
import { stopPropagation } from './utils';
|
||||
@@ -43,22 +42,18 @@ const TagItem = ({ tag, idx, mode, style }: TagItemProps) => {
|
||||
return (
|
||||
<div
|
||||
data-testid="page-tag"
|
||||
className={styles.tag}
|
||||
className={mode === 'sticky' ? styles.tagSticky : styles.tagListItem}
|
||||
data-idx={idx}
|
||||
title={tag.value}
|
||||
style={style}
|
||||
>
|
||||
<div
|
||||
className={mode === 'sticky' ? styles.tagSticky : styles.tagListItem}
|
||||
>
|
||||
<div
|
||||
className={styles.tagIndicator}
|
||||
style={{
|
||||
backgroundColor: tagColorMap(tag.color),
|
||||
}}
|
||||
/>
|
||||
<div className={styles.tagLabel}>{tag.value}</div>
|
||||
</div>
|
||||
className={styles.tagIndicator}
|
||||
style={{
|
||||
backgroundColor: tagColorMap(tag.color),
|
||||
}}
|
||||
/>
|
||||
<div className={styles.tagLabel}>{tag.value}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -74,6 +69,26 @@ export const PageTags = ({
|
||||
? widthOnHover
|
||||
: `${widthOnHover}px`
|
||||
: 'auto';
|
||||
const tagsContainerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (tagsContainerRef.current) {
|
||||
const tagsContainer = tagsContainerRef.current;
|
||||
const listener = () => {
|
||||
// on mouseleave, reset scroll position to the hoverExpandDirection
|
||||
tagsContainer.scrollTo({
|
||||
left: hoverExpandDirection === 'left' ? Number.MAX_SAFE_INTEGER : 0,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
};
|
||||
listener();
|
||||
tagsContainerRef.current.addEventListener('mouseleave', listener);
|
||||
return () => {
|
||||
tagsContainer.removeEventListener('mouseleave', listener);
|
||||
};
|
||||
}
|
||||
return;
|
||||
}, [hoverExpandDirection]);
|
||||
|
||||
const tagsInPopover = useMemo(() => {
|
||||
const lastTags = tags.slice(maxItems);
|
||||
@@ -92,17 +107,36 @@ export const PageTags = ({
|
||||
// sort tags by length
|
||||
nTags.sort((a, b) => a.value.length - b.value.length);
|
||||
|
||||
const tagRightCharLength = nTags.reduceRight<number[]>(
|
||||
(acc, tag) => {
|
||||
const curr = acc[0] + Math.min(tag.value.length, 10);
|
||||
return [curr, ...acc];
|
||||
},
|
||||
[0]
|
||||
);
|
||||
|
||||
tagRightCharLength.shift();
|
||||
|
||||
return nTags.map((tag, idx) => (
|
||||
<TagItem key={tag.id} tag={tag} idx={idx} mode="sticky" />
|
||||
<TagItem
|
||||
key={tag.id}
|
||||
tag={tag}
|
||||
idx={idx}
|
||||
mode="sticky"
|
||||
style={{
|
||||
right: `calc(${tagRightCharLength[idx]}em)`,
|
||||
}}
|
||||
/>
|
||||
));
|
||||
}, [maxItems, tags]);
|
||||
return (
|
||||
<div
|
||||
data-testid="page-tags"
|
||||
className={styles.root}
|
||||
style={assignInlineVars({
|
||||
[styles.hoverMaxWidth]: sanitizedWidthOnHover,
|
||||
})}
|
||||
style={{
|
||||
// @ts-expect-error it's fine
|
||||
'--hover-max-width': sanitizedWidthOnHover,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
@@ -112,7 +146,9 @@ export const PageTags = ({
|
||||
className={clsx(styles.innerContainer)}
|
||||
>
|
||||
<div className={styles.innerBackdrop} />
|
||||
<div className={styles.tagsScrollContainer}>{tagsNormal}</div>
|
||||
<div className={styles.tagsScrollContainer} ref={tagsContainerRef}>
|
||||
{tagsNormal}
|
||||
</div>
|
||||
{maxItems && tags.length > maxItems ? (
|
||||
<Menu
|
||||
items={tagsInPopover}
|
||||
|
||||
@@ -11,7 +11,6 @@ import * as styles from './share.css';
|
||||
export interface StorageProgressProgress {
|
||||
max: number;
|
||||
value: number;
|
||||
upgradable?: boolean;
|
||||
onUpgrade: () => void;
|
||||
plan: SubscriptionPlan;
|
||||
}
|
||||
@@ -24,7 +23,6 @@ enum ButtonType {
|
||||
export const StorageProgress = ({
|
||||
max: upperLimit,
|
||||
value,
|
||||
upgradable = true,
|
||||
onUpgrade,
|
||||
plan,
|
||||
}: StorageProgressProgress) => {
|
||||
@@ -65,24 +63,22 @@ export const StorageProgress = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{upgradable ? (
|
||||
<Tooltip
|
||||
options={{ hidden: percent < 100 }}
|
||||
content={t['com.affine.storage.maximum-tips']()}
|
||||
>
|
||||
<span tabIndex={0}>
|
||||
<Button
|
||||
type={buttonType}
|
||||
onClick={onUpgrade}
|
||||
className={styles.storageButton}
|
||||
>
|
||||
{plan === 'Free'
|
||||
? t['com.affine.storage.upgrade']()
|
||||
: t['com.affine.storage.change-plan']()}
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
<Tooltip
|
||||
options={{ hidden: percent < 100 }}
|
||||
content={t['com.affine.storage.maximum-tips']()}
|
||||
>
|
||||
<span tabIndex={0}>
|
||||
<Button
|
||||
type={buttonType}
|
||||
onClick={onUpgrade}
|
||||
className={styles.storageButton}
|
||||
>
|
||||
{plan === 'Free'
|
||||
? t['com.affine.storage.upgrade']()
|
||||
: t['com.affine.storage.change-plan']()}
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -351,6 +351,8 @@ export const createConfiguration: (
|
||||
'process.env.CAPTCHA_SITE_KEY': JSON.stringify(
|
||||
process.env.CAPTCHA_SITE_KEY
|
||||
),
|
||||
'process.env.SENTRY_DSN': JSON.stringify(process.env.SENTRY_DSN),
|
||||
'process.env.BUILD_TYPE': JSON.stringify(process.env.BUILD_TYPE),
|
||||
runtimeConfig: JSON.stringify(runtimeConfig),
|
||||
}),
|
||||
new CopyPlugin({
|
||||
|
||||
@@ -33,7 +33,6 @@ export function getRuntimeConfig(buildFlags: BuildFlags): RuntimeConfig {
|
||||
enableCaptcha: true,
|
||||
enableEnhanceShareMode: false,
|
||||
enablePayment: true,
|
||||
enablePageHistory: false,
|
||||
serverUrlPrefix: 'https://insider.affine.pro', // Let insider be stable environment temporarily.
|
||||
editorFlags,
|
||||
appVersion: packageJson.version,
|
||||
@@ -42,7 +41,6 @@ export function getRuntimeConfig(buildFlags: BuildFlags): RuntimeConfig {
|
||||
get beta() {
|
||||
return {
|
||||
...this.stable,
|
||||
enablePageHistory: false,
|
||||
serverUrlPrefix: 'https://insider.affine.pro',
|
||||
};
|
||||
},
|
||||
@@ -77,7 +75,6 @@ export function getRuntimeConfig(buildFlags: BuildFlags): RuntimeConfig {
|
||||
enableCaptcha: true,
|
||||
enableEnhanceShareMode: false,
|
||||
enablePayment: true,
|
||||
enablePageHistory: true,
|
||||
serverUrlPrefix: 'https://affine.fail',
|
||||
editorFlags,
|
||||
appVersion: packageJson.version,
|
||||
@@ -145,11 +142,6 @@ export function getRuntimeConfig(buildFlags: BuildFlags): RuntimeConfig {
|
||||
: buildFlags.mode === 'development'
|
||||
? true
|
||||
: currentBuildPreset.enablePayment,
|
||||
enablePageHistory: process.env.ENABLE_PAGE_HISTORY
|
||||
? process.env.ENABLE_PAGE_HISTORY === 'true'
|
||||
: buildFlags.mode === 'development'
|
||||
? true
|
||||
: currentBuildPreset.enablePageHistory,
|
||||
};
|
||||
|
||||
if (buildFlags.mode === 'development') {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@affine/core",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"version": "0.10.3-canary.2",
|
||||
"version": "0.10.3-beta.5",
|
||||
"scripts": {
|
||||
"build": "yarn -T run build-core",
|
||||
"dev": "yarn -T run dev-core",
|
||||
@@ -25,14 +25,14 @@
|
||||
"@affine/i18n": "workspace:*",
|
||||
"@affine/templates": "workspace:*",
|
||||
"@affine/workspace": "workspace:*",
|
||||
"@blocksuite/block-std": "0.0.0-20231124123613-7c06e95d-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20231124123613-7c06e95d-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20231124123613-7c06e95d-nightly",
|
||||
"@blocksuite/global": "0.0.0-20231124123613-7c06e95d-nightly",
|
||||
"@blocksuite/block-std": "0.0.0-20231122113751-6bf81eb3-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20231122113751-6bf81eb3-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20231122113751-6bf81eb3-nightly",
|
||||
"@blocksuite/global": "0.0.0-20231122113751-6bf81eb3-nightly",
|
||||
"@blocksuite/icons": "2.1.36",
|
||||
"@blocksuite/lit": "0.0.0-20231124123613-7c06e95d-nightly",
|
||||
"@blocksuite/store": "0.0.0-20231124123613-7c06e95d-nightly",
|
||||
"@blocksuite/virgo": "0.0.0-20231124123613-7c06e95d-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20231122113751-6bf81eb3-nightly",
|
||||
"@blocksuite/store": "0.0.0-20231122113751-6bf81eb3-nightly",
|
||||
"@blocksuite/virgo": "0.0.0-20231122113751-6bf81eb3-nightly",
|
||||
"@dnd-kit/core": "^6.0.8",
|
||||
"@dnd-kit/sortable": "^8.0.0",
|
||||
"@emotion/cache": "^11.11.0",
|
||||
@@ -45,6 +45,8 @@
|
||||
"@radix-ui/react-scroll-area": "^1.0.5",
|
||||
"@radix-ui/react-select": "^2.0.0",
|
||||
"@react-hookz/web": "^23.1.0",
|
||||
"@sentry/integrations": "^7.83.0",
|
||||
"@sentry/react": "^7.83.0",
|
||||
"@toeverything/components": "^0.0.46",
|
||||
"@toeverything/theme": "^0.7.20",
|
||||
"@vanilla-extract/css": "^1.13.0",
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"env": "SENTRY_AUTH_TOKEN"
|
||||
},
|
||||
{
|
||||
"env": "NEXT_PUBLIC_SENTRY_DSN"
|
||||
"env": "SENTRY_DSN"
|
||||
},
|
||||
{
|
||||
"env": "DISTRIBUTION"
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import { atom } from 'jotai';
|
||||
|
||||
// make page history controllable by atom to make it easier to use in CMDK
|
||||
export const pageHistoryModalAtom = atom({
|
||||
open: false,
|
||||
pageId: '',
|
||||
});
|
||||
@@ -6,7 +6,15 @@ import {
|
||||
rootWorkspacesMetadataAtom,
|
||||
workspaceAdaptersAtom,
|
||||
} from '@affine/workspace/atom';
|
||||
import * as Sentry from '@sentry/react';
|
||||
import type { createStore } from 'jotai/vanilla';
|
||||
import { useEffect } from 'react';
|
||||
import {
|
||||
createRoutesFromChildren,
|
||||
matchRoutes,
|
||||
useLocation,
|
||||
useNavigationType,
|
||||
} from 'react-router-dom';
|
||||
|
||||
import { WorkspaceAdapters } from '../adapters/workspace';
|
||||
import { performanceLogger } from '../shared';
|
||||
@@ -51,6 +59,31 @@ export async function setup(store: ReturnType<typeof createStore>) {
|
||||
performanceSetupLogger.info('setup global');
|
||||
setupGlobal();
|
||||
|
||||
if (window.SENTRY_RELEASE) {
|
||||
// https://docs.sentry.io/platforms/javascript/guides/react/#configure
|
||||
Sentry.init({
|
||||
dsn: process.env.SENTRY_DSN,
|
||||
environment: process.env.BUILD_TYPE ?? 'development',
|
||||
integrations: [
|
||||
new Sentry.BrowserTracing({
|
||||
routingInstrumentation: Sentry.reactRouterV6Instrumentation(
|
||||
useEffect,
|
||||
useLocation,
|
||||
useNavigationType,
|
||||
createRoutesFromChildren,
|
||||
matchRoutes
|
||||
),
|
||||
}),
|
||||
new Sentry.Replay(),
|
||||
new Sentry.BrowserTracing(),
|
||||
],
|
||||
});
|
||||
Sentry.setTags({
|
||||
appVersion: runtimeConfig.appVersion,
|
||||
editorVersion: runtimeConfig.editorVersion,
|
||||
});
|
||||
}
|
||||
|
||||
performanceSetupLogger.info('get root workspace meta');
|
||||
// do not read `rootWorkspacesMetadataAtom` before migration
|
||||
await store.get(rootWorkspacesMetadataAtom);
|
||||
|
||||
@@ -1,191 +0,0 @@
|
||||
import type {
|
||||
QueryParamError,
|
||||
Unreachable,
|
||||
WorkspaceNotFoundError,
|
||||
} from '@affine/env/constant';
|
||||
import { PageNotFoundError } from '@affine/env/constant';
|
||||
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
||||
import { Button } from '@toeverything/components/button';
|
||||
import {
|
||||
currentPageIdAtom,
|
||||
currentWorkspaceIdAtom,
|
||||
getCurrentStore,
|
||||
} from '@toeverything/infra/atom';
|
||||
import { useAtomValue } from 'jotai/react';
|
||||
import { Provider } from 'jotai/react';
|
||||
import type { ErrorInfo, ReactElement, ReactNode } from 'react';
|
||||
import type React from 'react';
|
||||
import { Component, useEffect } from 'react';
|
||||
import { useLocation, useParams } from 'react-router-dom';
|
||||
|
||||
import {
|
||||
RecoverableError,
|
||||
type SessionFetchErrorRightAfterLoginOrSignUp,
|
||||
} from '../../unexpected-application-state/errors';
|
||||
import {
|
||||
errorDescription,
|
||||
errorDetailStyle,
|
||||
errorDivider,
|
||||
errorImage,
|
||||
errorLayout,
|
||||
errorRetryButton,
|
||||
errorTitle,
|
||||
} from './affine-error-boundary.css';
|
||||
import errorBackground from './error-status.assets.svg';
|
||||
|
||||
export type AffineErrorBoundaryProps = React.PropsWithChildren & {
|
||||
height?: number | string;
|
||||
};
|
||||
|
||||
type AffineError =
|
||||
| QueryParamError
|
||||
| Unreachable
|
||||
| WorkspaceNotFoundError
|
||||
| PageNotFoundError
|
||||
| Error
|
||||
| SessionFetchErrorRightAfterLoginOrSignUp;
|
||||
|
||||
interface AffineErrorBoundaryState {
|
||||
error: AffineError | null;
|
||||
canRetryRecoveredError: boolean;
|
||||
}
|
||||
|
||||
export const DumpInfo = () => {
|
||||
const location = useLocation();
|
||||
const metadata = useAtomValue(rootWorkspacesMetadataAtom);
|
||||
const currentWorkspaceId = useAtomValue(currentWorkspaceIdAtom);
|
||||
const currentPageId = useAtomValue(currentPageIdAtom);
|
||||
const path = location.pathname;
|
||||
const query = useParams();
|
||||
useEffect(() => {
|
||||
console.info('DumpInfo', {
|
||||
path,
|
||||
query,
|
||||
currentWorkspaceId,
|
||||
currentPageId,
|
||||
metadata,
|
||||
});
|
||||
}, [path, query, currentWorkspaceId, currentPageId, metadata]);
|
||||
return null;
|
||||
};
|
||||
|
||||
export class AffineErrorBoundary extends Component<
|
||||
AffineErrorBoundaryProps,
|
||||
AffineErrorBoundaryState
|
||||
> {
|
||||
override state: AffineErrorBoundaryState = {
|
||||
error: null,
|
||||
canRetryRecoveredError: true,
|
||||
};
|
||||
|
||||
private readonly handleRecoverableRetry = () => {
|
||||
if (this.state.error instanceof RecoverableError) {
|
||||
if (this.state.error.canRetry()) {
|
||||
this.state.error.retry();
|
||||
this.setState({
|
||||
error: null,
|
||||
canRetryRecoveredError: this.state.error.canRetry(),
|
||||
});
|
||||
} else {
|
||||
document.location.reload();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private readonly handleRefresh = () => {
|
||||
this.setState({ error: null });
|
||||
};
|
||||
|
||||
static getDerivedStateFromError(
|
||||
error: AffineError
|
||||
): AffineErrorBoundaryState {
|
||||
return {
|
||||
error,
|
||||
canRetryRecoveredError:
|
||||
error instanceof RecoverableError ? error.canRetry() : true,
|
||||
};
|
||||
}
|
||||
|
||||
override componentDidCatch(error: AffineError, errorInfo: ErrorInfo) {
|
||||
console.error('Uncaught error:', error, errorInfo);
|
||||
}
|
||||
|
||||
override render(): ReactNode {
|
||||
if (this.state.error) {
|
||||
let errorDetail: ReactElement | null = null;
|
||||
const error = this.state.error;
|
||||
if (error instanceof PageNotFoundError) {
|
||||
errorDetail = (
|
||||
<>
|
||||
<h1>Sorry.. there was an error</h1>
|
||||
<>
|
||||
<span> Page error </span>
|
||||
<span>
|
||||
Cannot find page {error.pageId} in workspace{' '}
|
||||
{error.workspace.id}
|
||||
</span>
|
||||
</>
|
||||
</>
|
||||
);
|
||||
} else if (error instanceof RecoverableError) {
|
||||
const retryButtonDesc = this.state.canRetryRecoveredError
|
||||
? 'Refetch'
|
||||
: 'Reload';
|
||||
errorDetail = (
|
||||
<>
|
||||
<h1 className={errorTitle}>Sorry.. there was an error</h1>
|
||||
<span className={errorDescription}> {error.message} </span>
|
||||
<span className={errorDescription}>
|
||||
If you are still experiencing this issue, please{' '}
|
||||
<a
|
||||
style={{ color: 'var(--affine-primary-color)' }}
|
||||
href="https://community.affine.pro"
|
||||
target="__blank"
|
||||
>
|
||||
contact us through the community.
|
||||
</a>
|
||||
</span>
|
||||
<Button
|
||||
className={errorRetryButton}
|
||||
onClick={this.handleRecoverableRetry}
|
||||
type="primary"
|
||||
>
|
||||
{retryButtonDesc}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
errorDetail = (
|
||||
<>
|
||||
<h1 className={errorTitle}>Sorry.. there was an error</h1>
|
||||
<code className={errorDescription}>
|
||||
{error.message ?? error.toString()}
|
||||
</code>
|
||||
<Button
|
||||
onClick={this.handleRefresh}
|
||||
className={errorRetryButton}
|
||||
type="primary"
|
||||
>
|
||||
Refresh
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className={errorLayout} style={{ height: this.props.height }}>
|
||||
<div className={errorDetailStyle}>{errorDetail}</div>
|
||||
<span className={errorDivider} />
|
||||
<div
|
||||
className={errorImage}
|
||||
style={{ backgroundImage: `url(${errorBackground})` }}
|
||||
/>
|
||||
<Provider key="JotaiProvider" store={getCurrentStore()}>
|
||||
<DumpInfo />
|
||||
</Provider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
export const viewport = style({
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
});
|
||||
@@ -0,0 +1,53 @@
|
||||
import { getCurrentStore } from '@toeverything/infra/atom';
|
||||
import { Provider } from 'jotai/react';
|
||||
import type { FC } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import * as styles from './affine-error-fallback.css';
|
||||
import {
|
||||
ERROR_REFLECT_KEY,
|
||||
type FallbackProps,
|
||||
} from './error-basic/fallback-creator';
|
||||
import { DumpInfo } from './error-basic/info-logger';
|
||||
import { AnyErrorFallback } from './error-fallbacks/any-error-fallback';
|
||||
import { NoPageRootFallback } from './error-fallbacks/no-page-root-fallback';
|
||||
import { PageNotFoundDetail } from './error-fallbacks/page-not-found-fallback';
|
||||
import { RecoverableErrorFallback } from './error-fallbacks/recoverable-error-fallback';
|
||||
|
||||
/**
|
||||
* Register all fallback components here.
|
||||
* If have new one just add it to the set.
|
||||
*/
|
||||
const fallbacks = new Set([
|
||||
PageNotFoundDetail,
|
||||
RecoverableErrorFallback,
|
||||
NoPageRootFallback,
|
||||
]);
|
||||
|
||||
function getErrorFallbackComponent(error: any): FC<FallbackProps> {
|
||||
for (const Component of fallbacks) {
|
||||
const ErrorConstructor = Reflect.get(Component, ERROR_REFLECT_KEY);
|
||||
if (ErrorConstructor && error instanceof ErrorConstructor) {
|
||||
return Component as FC<FallbackProps>;
|
||||
}
|
||||
}
|
||||
return AnyErrorFallback;
|
||||
}
|
||||
|
||||
export interface AffineErrorFallbackProps extends FallbackProps {
|
||||
height?: number | string;
|
||||
}
|
||||
|
||||
export const AffineErrorFallback: FC<AffineErrorFallbackProps> = props => {
|
||||
const { error, resetError, height } = props;
|
||||
const Component = useMemo(() => getErrorFallbackComponent(error), [error]);
|
||||
|
||||
return (
|
||||
<div className={styles.viewport} style={{ height }}>
|
||||
<Component error={error} resetError={resetError} />
|
||||
<Provider key="JotaiProvider" store={getCurrentStore()}>
|
||||
<DumpInfo error={error} />
|
||||
</Provider>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
@@ -0,0 +1,43 @@
|
||||
<svg width="490" height="242" viewBox="0 0 490 242" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16.5098 155.545C16.4625 163.027 18.0723 169.655 21.3393 175.432C24.6064 181.208 29.1282 185.73 34.9047 188.997C40.6812 192.264 47.3337 193.898 54.8621 193.898C63.2428 193.898 70.6765 191.838 77.1632 187.719C83.65 183.599 88.7399 177.989 92.4331 170.886C96.1263 163.784 97.9965 155.735 98.0439 146.739C98.0912 137.364 96.1026 129.196 92.078 122.236C88.1007 115.228 82.7977 109.807 76.1689 105.972C69.5875 102.089 62.3905 100.148 54.578 100.148C48.7541 100.148 43.4274 101.166 38.5979 103.202C33.7683 105.19 29.5306 107.866 25.8848 111.227H25.3166L32.703 51H93.2143" stroke="black" stroke-width="2"/>
|
||||
<ellipse cx="94.4026" cy="50.8894" rx="3.89381" ry="3.88938" fill="#121212"/>
|
||||
<ellipse cx="16.4026" cy="155.889" rx="3.89381" ry="3.88938" fill="#121212"/>
|
||||
<ellipse cx="32.4026" cy="50.8894" rx="3.89381" ry="3.88938" fill="#121212"/>
|
||||
<ellipse cx="25.4026" cy="110.889" rx="3.89381" ry="3.88938" fill="#121212"/>
|
||||
<path d="M151.592 59.1235L275.951 183.342" stroke="#E3E2E4"/>
|
||||
<rect x="151.626" y="59.1582" width="124.291" height="124.149" stroke="#E3E2E4"/>
|
||||
<path d="M275.573 121.465C276.129 117.824 276.417 114.095 276.417 110.299C276.417 69.7024 243.469 36.792 202.826 36.792C162.183 36.792 129.235 69.7024 129.235 110.299C129.235 150.897 162.183 183.807 202.826 183.807C206.465 183.807 210.041 183.543 213.539 183.034" stroke="#E3E2E4"/>
|
||||
<path d="M213.539 182.964C217.184 183.519 220.917 183.807 224.717 183.807C265.36 183.807 298.308 150.897 298.308 110.299C298.308 69.7024 265.36 36.792 224.717 36.792C184.074 36.792 151.126 69.7024 151.126 110.299C151.126 114.095 151.414 117.824 151.97 121.465" stroke="#E3E2E4"/>
|
||||
<path d="M151.434 121.465C150.924 124.958 150.66 128.531 150.66 132.166C150.66 172.763 183.608 205.673 224.251 205.673C264.894 205.673 297.842 172.763 297.842 132.166C297.842 91.5686 264.894 58.6582 224.251 58.6582C220.613 58.6582 217.036 58.922 213.539 59.4314" stroke="#E3E2E4"/>
|
||||
<path d="M213.539 59.4314C210.041 58.922 206.465 58.6582 202.826 58.6582C162.183 58.6582 129.235 91.5686 129.235 132.166C129.235 172.763 162.183 205.673 202.826 205.673C243.469 205.673 276.417 172.763 276.417 132.166C276.417 128.531 276.153 124.958 275.643 121.465" stroke="#E3E2E4"/>
|
||||
<path d="M275.951 59.1235L151.592 183.342" stroke="#E3E2E4"/>
|
||||
<path d="M151.126 121.465H275.951" stroke="#E3E2E4"/>
|
||||
<path d="M213.539 58.6582V183.807" stroke="#E3E2E4"/>
|
||||
<ellipse cx="275.951" cy="121.465" rx="3.72614" ry="3.7219" fill="#121212"/>
|
||||
<ellipse cx="275.951" cy="59.1233" rx="3.72614" ry="3.7219" fill="#121212"/>
|
||||
<ellipse cx="275.951" cy="183.807" rx="3.72614" ry="3.7219" fill="#121212"/>
|
||||
<ellipse cx="151.592" cy="183.807" rx="3.72613" ry="3.7219" fill="#121212"/>
|
||||
<ellipse cx="213.539" cy="121.465" rx="3.72613" ry="3.7219" fill="#121212"/>
|
||||
<ellipse cx="213.539" cy="59.1233" rx="3.72613" ry="3.7219" fill="#121212"/>
|
||||
<ellipse cx="151.592" cy="121.465" rx="3.72613" ry="3.7219" fill="#121212"/>
|
||||
<ellipse cx="151.592" cy="59.1233" rx="3.72613" ry="3.7219" fill="#121212"/>
|
||||
<ellipse cx="213.539" cy="183.807" rx="3.72613" ry="3.7219" fill="#121212"/>
|
||||
<path d="M338.583 59.1235L462.943 183.342" stroke="#E3E2E4"/>
|
||||
<rect x="338.617" y="59.1582" width="124.291" height="124.149" stroke="#E3E2E4"/>
|
||||
<path d="M462.565 121.465C463.12 117.824 463.408 114.095 463.408 110.299C463.408 69.7024 430.46 36.792 389.817 36.792C349.174 36.792 316.226 69.7024 316.226 110.299C316.226 150.897 349.174 183.807 389.817 183.807C393.456 183.807 397.033 183.543 400.53 183.034" stroke="#E3E2E4"/>
|
||||
<path d="M400.53 182.964C404.175 183.519 407.908 183.807 411.708 183.807C452.351 183.807 485.299 150.897 485.299 110.299C485.299 69.7024 452.351 36.792 411.708 36.792C371.065 36.792 338.117 69.7024 338.117 110.299C338.117 114.095 338.405 117.824 338.961 121.465" stroke="#E3E2E4"/>
|
||||
<path d="M338.425 121.465C337.915 124.958 337.651 128.531 337.651 132.166C337.651 172.763 370.599 205.673 411.242 205.673C451.886 205.673 484.833 172.763 484.833 132.166C484.833 91.5686 451.886 58.6582 411.242 58.6582C407.604 58.6582 404.027 58.922 400.53 59.4314" stroke="#E3E2E4"/>
|
||||
<path d="M400.53 59.4314C397.033 58.922 393.456 58.6582 389.817 58.6582C349.174 58.6582 316.226 91.5686 316.226 132.166C316.226 172.763 349.174 205.673 389.817 205.673C430.46 205.673 463.408 172.763 463.408 132.166C463.408 128.531 463.144 124.958 462.634 121.465" stroke="#E3E2E4"/>
|
||||
<path d="M462.943 59.1235L338.583 183.342" stroke="#E3E2E4"/>
|
||||
<path d="M338.117 121.465H462.943" stroke="#E3E2E4"/>
|
||||
<path d="M400.53 58.6582V183.807" stroke="#E3E2E4"/>
|
||||
<ellipse cx="462.942" cy="121.465" rx="3.72614" ry="3.7219" fill="#121212"/>
|
||||
<ellipse cx="462.942" cy="59.1233" rx="3.72614" ry="3.7219" fill="#121212"/>
|
||||
<ellipse cx="462.942" cy="183.807" rx="3.72614" ry="3.7219" fill="#121212"/>
|
||||
<ellipse cx="338.583" cy="183.807" rx="3.72613" ry="3.7219" fill="#121212"/>
|
||||
<ellipse cx="400.53" cy="121.465" rx="3.72613" ry="3.7219" fill="#121212"/>
|
||||
<ellipse cx="400.53" cy="59.1233" rx="3.72613" ry="3.7219" fill="#121212"/>
|
||||
<ellipse cx="338.583" cy="121.465" rx="3.72613" ry="3.7219" fill="#121212"/>
|
||||
<ellipse cx="338.583" cy="59.1233" rx="3.72613" ry="3.7219" fill="#121212"/>
|
||||
<ellipse cx="400.53" cy="183.807" rx="3.72613" ry="3.7219" fill="#121212"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.3 KiB |
@@ -6,6 +6,7 @@ export const errorLayout = style({
|
||||
alignItems: 'center',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
gap: '20px',
|
||||
});
|
||||
|
||||
export const errorDetailStyle = style({
|
||||
@@ -24,15 +25,15 @@ export const errorImage = style({
|
||||
height: '178px',
|
||||
maxWidth: '400px',
|
||||
flexGrow: 1,
|
||||
backgroundSize: 'cover',
|
||||
});
|
||||
|
||||
export const errorDescription = style({
|
||||
marginTop: '24px',
|
||||
});
|
||||
|
||||
export const errorRetryButton = style({
|
||||
export const errorFooter = style({
|
||||
marginTop: '24px',
|
||||
width: '94px',
|
||||
});
|
||||
|
||||
export const errorDivider = style({
|
||||
@@ -0,0 +1,106 @@
|
||||
import { Trans } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { Button } from '@toeverything/components/button';
|
||||
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
|
||||
import {
|
||||
type FC,
|
||||
type PropsWithChildren,
|
||||
type ReactNode,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import imageUrlFor404 from '../error-assets/404-status.assets.svg';
|
||||
import imageUrlFor500 from '../error-assets/500-status.assets.svg';
|
||||
import * as styles from './error-detail.css';
|
||||
|
||||
export enum ErrorStatus {
|
||||
NotFound = 404,
|
||||
Unexpected = 500,
|
||||
}
|
||||
|
||||
export interface ErrorDetailProps extends PropsWithChildren {
|
||||
status?: ErrorStatus;
|
||||
direction?: 'column' | 'row';
|
||||
title: string;
|
||||
description: ReactNode | Array<ReactNode>;
|
||||
buttonText?: string;
|
||||
onButtonClick?: () => void | Promise<void>;
|
||||
resetError?: () => void;
|
||||
withoutImage?: boolean;
|
||||
}
|
||||
|
||||
const imageMap = new Map([
|
||||
[ErrorStatus.NotFound, imageUrlFor404],
|
||||
[ErrorStatus.Unexpected, imageUrlFor500],
|
||||
]);
|
||||
|
||||
/**
|
||||
* TODO: Unify with NotFoundPage.
|
||||
*/
|
||||
export const ErrorDetail: FC<ErrorDetailProps> = props => {
|
||||
const {
|
||||
status = ErrorStatus.Unexpected,
|
||||
direction = 'row',
|
||||
description,
|
||||
onButtonClick,
|
||||
resetError,
|
||||
withoutImage,
|
||||
} = props;
|
||||
const descriptions = Array.isArray(description) ? description : [description];
|
||||
const [isBtnLoading, setBtnLoading] = useState(false);
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
const onBtnClick = useAsyncCallback(async () => {
|
||||
try {
|
||||
setBtnLoading(true);
|
||||
await onButtonClick?.();
|
||||
resetError?.(); // Only reset when retry success.
|
||||
} finally {
|
||||
setBtnLoading(false);
|
||||
}
|
||||
}, [onButtonClick, resetError]);
|
||||
|
||||
return (
|
||||
<div className={styles.errorLayout} style={{ flexDirection: direction }}>
|
||||
<div className={styles.errorDetailStyle}>
|
||||
<h1 className={styles.errorTitle}>{props.title}</h1>
|
||||
{descriptions.map((item, i) => (
|
||||
<p key={i} className={styles.errorDescription}>
|
||||
{item}
|
||||
</p>
|
||||
))}
|
||||
<div className={styles.errorFooter}>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={onBtnClick}
|
||||
loading={isBtnLoading}
|
||||
size="extraLarge"
|
||||
>
|
||||
{props.buttonText ?? t['com.affine.error.retry']()}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{withoutImage ? null : (
|
||||
<div
|
||||
className={styles.errorImage}
|
||||
style={{ backgroundImage: `url(${imageMap.get(status)})` }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export function ContactUS() {
|
||||
return (
|
||||
<Trans>
|
||||
If you are still experiencing this issue, please{' '}
|
||||
<a
|
||||
style={{ color: 'var(--affine-primary-color)' }}
|
||||
href="https://community.affine.pro"
|
||||
target="__blank"
|
||||
>
|
||||
contact us through the community.
|
||||
</a>
|
||||
</Trans>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import type { FC } from 'react';
|
||||
|
||||
export interface FallbackProps<T extends Error = Error> {
|
||||
error: T;
|
||||
resetError: () => void;
|
||||
}
|
||||
|
||||
export const ERROR_REFLECT_KEY = Symbol('ERROR_REFLECT_KEY');
|
||||
|
||||
export function createErrorFallback<T extends Error>(
|
||||
ErrorConstructor: abstract new (...args: any[]) => T,
|
||||
Component: FC<FallbackProps<T>>
|
||||
): FC<FallbackProps<T>> {
|
||||
Reflect.set(Component, ERROR_REFLECT_KEY, ErrorConstructor);
|
||||
return Component;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
||||
import {
|
||||
currentPageIdAtom,
|
||||
currentWorkspaceIdAtom,
|
||||
} from '@toeverything/infra/atom';
|
||||
import { useAtomValue } from 'jotai/react';
|
||||
import { useEffect } from 'react';
|
||||
import { useLocation, useParams } from 'react-router-dom';
|
||||
|
||||
export interface DumpInfoProps {
|
||||
error: any;
|
||||
}
|
||||
|
||||
export const DumpInfo = (_props: DumpInfoProps) => {
|
||||
const location = useLocation();
|
||||
const metadata = useAtomValue(rootWorkspacesMetadataAtom);
|
||||
const currentWorkspaceId = useAtomValue(currentWorkspaceIdAtom);
|
||||
const currentPageId = useAtomValue(currentPageIdAtom);
|
||||
const path = location.pathname;
|
||||
const query = useParams();
|
||||
useEffect(() => {
|
||||
console.info('DumpInfo', {
|
||||
path,
|
||||
query,
|
||||
currentWorkspaceId,
|
||||
currentPageId,
|
||||
metadata,
|
||||
});
|
||||
}, [path, query, currentWorkspaceId, currentPageId, metadata]);
|
||||
return null;
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { type FC, useCallback } from 'react';
|
||||
|
||||
import { ErrorDetail } from '../error-basic/error-detail';
|
||||
import type { FallbackProps } from '../error-basic/fallback-creator';
|
||||
|
||||
/**
|
||||
* TODO: Support reload and retry two reset actions in page error and area error.
|
||||
*/
|
||||
export const AnyErrorFallback: FC<FallbackProps> = props => {
|
||||
const { error } = props;
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
const reloadPage = useCallback(() => {
|
||||
document.location.reload();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ErrorDetail
|
||||
title={t['com.affine.error.unexpected-error.title']()}
|
||||
resetError={reloadPage}
|
||||
buttonText={t['com.affine.error.reload']()}
|
||||
description={error.message ?? error.toString()}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
import { NoPageRootError } from '@affine/component/block-suite-editor';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
|
||||
import { ContactUS, ErrorDetail } from '../error-basic/error-detail';
|
||||
import { createErrorFallback } from '../error-basic/fallback-creator';
|
||||
|
||||
export const NoPageRootFallback = createErrorFallback(
|
||||
NoPageRootError,
|
||||
props => {
|
||||
const { resetError } = props;
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
return (
|
||||
<ErrorDetail
|
||||
title={t['com.affine.error.no-page-root.title']()}
|
||||
description={<ContactUS />}
|
||||
resetError={resetError}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,30 @@
|
||||
import { PageNotFoundError } from '@affine/env/constant';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import {
|
||||
RouteLogic,
|
||||
useNavigateHelper,
|
||||
} from '../../../../hooks/use-navigate-helper';
|
||||
import { ErrorDetail, ErrorStatus } from '../error-basic/error-detail';
|
||||
import { createErrorFallback } from '../error-basic/fallback-creator';
|
||||
|
||||
export const PageNotFoundDetail = createErrorFallback(PageNotFoundError, () => {
|
||||
const t = useAFFiNEI18N();
|
||||
const { jumpToIndex } = useNavigateHelper();
|
||||
|
||||
const onBtnClick = useCallback(
|
||||
() => jumpToIndex(RouteLogic.REPLACE),
|
||||
[jumpToIndex]
|
||||
);
|
||||
|
||||
return (
|
||||
<ErrorDetail
|
||||
title={t['com.affine.notFoundPage.title']()}
|
||||
description={t['404.hint']()}
|
||||
buttonText={t['404.back']()}
|
||||
onButtonClick={onBtnClick}
|
||||
status={ErrorStatus.NotFound}
|
||||
/>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { RecoverableError } from '../../../../unexpected-application-state/errors';
|
||||
import { ContactUS, ErrorDetail } from '../error-basic/error-detail';
|
||||
import { createErrorFallback } from '../error-basic/fallback-creator';
|
||||
|
||||
export const RecoverableErrorFallback = createErrorFallback(
|
||||
RecoverableError,
|
||||
props => {
|
||||
const { error, resetError } = props;
|
||||
const t = useAFFiNEI18N();
|
||||
const [count, rerender] = useState(0);
|
||||
|
||||
const canRetry = error.canRetry();
|
||||
const buttonDesc = useMemo(() => {
|
||||
if (canRetry) {
|
||||
return t['com.affine.error.refetch']();
|
||||
}
|
||||
return t['com.affine.error.reload']();
|
||||
}, [canRetry, t]);
|
||||
const onRetry = useCallback(async () => {
|
||||
if (canRetry) {
|
||||
rerender(count + 1);
|
||||
await error.retry();
|
||||
} else {
|
||||
document.location.reload();
|
||||
}
|
||||
}, [error, count, canRetry]);
|
||||
|
||||
return (
|
||||
<ErrorDetail
|
||||
title={t['com.affine.error.unexpected-error.title']()}
|
||||
resetError={resetError}
|
||||
buttonText={buttonDesc}
|
||||
onButtonClick={onRetry}
|
||||
description={[error.message, <ContactUS key="contact-us" />]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,25 @@
|
||||
<svg width="402" height="178" viewBox="0 0 402 178" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M93.7434 129.308H1L71.7965 15.1021V167.142" stroke="#121212" stroke-width="2" stroke-linecap="square" stroke-linejoin="bevel" />
|
||||
<ellipse cx="71.4426" cy="14.7483" rx="3.89381" ry="3.88938" fill="#121212" />
|
||||
<ellipse cx="93.3894" cy="129.308" rx="3.89381" ry="3.88938" fill="#121212" />
|
||||
<path d="M140.357 27.1235L264.717 151.342" stroke="#E3E2E4" />
|
||||
<rect x="140.392" y="27.1582" width="124.291" height="124.149" stroke="#E3E2E4" />
|
||||
<path d="M264.339 89.4652C264.895 85.8242 265.183 82.0954 265.183 78.2995C265.183 37.7024 232.235 4.79199 191.592 4.79199C150.948 4.79199 118 37.7024 118 78.2995C118 118.897 150.948 151.807 191.592 151.807C195.23 151.807 198.807 151.543 202.304 151.034" stroke="#E3E2E4" />
|
||||
<path d="M202.304 150.964C205.949 151.519 209.682 151.807 213.483 151.807C254.126 151.807 287.074 118.897 287.074 78.2995C287.074 37.7024 254.126 4.79199 213.483 4.79199C172.839 4.79199 139.892 37.7024 139.892 78.2995C139.892 82.0955 140.18 85.8242 140.735 89.4652" stroke="#E3E2E4" />
|
||||
<path d="M140.2 89.4652C139.69 92.9584 139.426 96.5312 139.426 100.166C139.426 140.763 172.374 173.673 213.017 173.673C253.66 173.673 286.608 140.763 286.608 100.166C286.608 59.5686 253.66 26.6582 213.017 26.6582C209.378 26.6582 205.801 26.922 202.304 27.4314" stroke="#E3E2E4" />
|
||||
<path d="M202.304 27.4314C198.807 26.922 195.23 26.6582 191.592 26.6582C150.948 26.6582 118 59.5686 118 100.166C118 140.763 150.948 173.673 191.592 173.673C232.235 173.673 265.183 140.763 265.183 100.166C265.183 96.5312 264.919 92.9584 264.409 89.4652" stroke="#E3E2E4" />
|
||||
<path d="M264.717 27.1235L140.357 151.342" stroke="#E3E2E4" />
|
||||
<path d="M139.892 89.4653H264.717" stroke="#E3E2E4" />
|
||||
<path d="M202.304 26.6582V151.807" stroke="#E3E2E4" />
|
||||
<ellipse cx="264.717" cy="89.4651" rx="3.72614" ry="3.7219" fill="#121212" />
|
||||
<ellipse cx="264.717" cy="27.1233" rx="3.72614" ry="3.7219" fill="#121212" />
|
||||
<ellipse cx="264.717" cy="151.807" rx="3.72614" ry="3.7219" fill="#121212" />
|
||||
<ellipse cx="140.357" cy="151.807" rx="3.72613" ry="3.7219" fill="#121212" />
|
||||
<ellipse cx="202.304" cy="89.4651" rx="3.72613" ry="3.7219" fill="#121212" />
|
||||
<ellipse cx="202.304" cy="27.1233" rx="3.72613" ry="3.7219" fill="#121212" />
|
||||
<ellipse cx="140.357" cy="89.4651" rx="3.72613" ry="3.7219" fill="#121212" />
|
||||
<ellipse cx="140.357" cy="27.1233" rx="3.72613" ry="3.7219" fill="#121212" />
|
||||
<ellipse cx="202.304" cy="151.807" rx="3.72613" ry="3.7219" fill="#121212" />
|
||||
<path d="M401 127.187H308.257L379.053 12.9805V165.02" stroke="#121212" stroke-width="2" stroke-linecap="square" stroke-linejoin="bevel" />
|
||||
<ellipse cx="379.407" cy="127.187" rx="3.89381" ry="3.88938" fill="#121212" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
@@ -0,0 +1,34 @@
|
||||
import { ErrorBoundary } from '@sentry/react';
|
||||
import type { FC, PropsWithChildren } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { AffineErrorFallback } from './affine-error-fallback';
|
||||
import { type FallbackProps } from './error-basic/fallback-creator';
|
||||
|
||||
export { type FallbackProps } from './error-basic/fallback-creator';
|
||||
|
||||
export interface AffineErrorBoundaryProps extends PropsWithChildren {
|
||||
height?: number | string;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Unify with SWRErrorBoundary
|
||||
*/
|
||||
export const AffineErrorBoundary: FC<AffineErrorBoundaryProps> = props => {
|
||||
const fallbackRender = useCallback(
|
||||
(fallbackProps: FallbackProps) => {
|
||||
return <AffineErrorFallback {...fallbackProps} height={props.height} />;
|
||||
},
|
||||
[props.height]
|
||||
);
|
||||
|
||||
const onError = useCallback((error: Error, componentStack: string) => {
|
||||
console.error('Uncaught error:', error, componentStack);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ErrorBoundary fallback={fallbackRender} onError={onError}>
|
||||
{props.children}
|
||||
</ErrorBoundary>
|
||||
);
|
||||
};
|
||||
@@ -1,11 +0,0 @@
|
||||
import type { ReactElement } from 'react';
|
||||
import type { FallbackProps } from 'react-error-boundary';
|
||||
|
||||
export const AnyErrorBoundary = (props: FallbackProps): ReactElement => {
|
||||
return (
|
||||
<div>
|
||||
<p>Something went wrong:</p>
|
||||
<p>{props.error.toString()}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -28,11 +28,6 @@ const UserPlanButtonWithData = () => {
|
||||
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
if (plan === SubscriptionPlan.SelfHosted) {
|
||||
// Self hosted version doesn't have a payment apis.
|
||||
return <div className={styles.userPlanButton}>{plan}</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip content={t['com.affine.payment.tag-tooltips']()} side="top">
|
||||
<div className={styles.userPlanButton} onClick={handleClick}>
|
||||
|
||||
@@ -7,7 +7,6 @@ import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { useSelfHosted } from '../../../hooks/affine/use-server-flavor';
|
||||
import { useWorkspace } from '../../../hooks/use-workspace';
|
||||
import { DeleteLeaveWorkspace } from './delete-leave-workspace';
|
||||
import { ExportPanel } from './export';
|
||||
@@ -21,7 +20,6 @@ import type { WorkspaceSettingDetailProps } from './types';
|
||||
export const WorkspaceSettingDetail = (props: WorkspaceSettingDetailProps) => {
|
||||
const { workspaceId } = props;
|
||||
const t = useAFFiNEI18N();
|
||||
const isSelfHosted = useSelfHosted();
|
||||
const workspace = useWorkspace(workspaceId);
|
||||
const [name] = useBlockSuiteWorkspaceName(workspace.blockSuiteWorkspace);
|
||||
|
||||
@@ -58,11 +56,7 @@ export const WorkspaceSettingDetail = (props: WorkspaceSettingDetailProps) => {
|
||||
</SettingWrapper>
|
||||
<SettingWrapper title={t['com.affine.brand.affineCloud']()}>
|
||||
<PublishPanel workspace={workspace} {...props} />
|
||||
<MembersPanel
|
||||
workspace={workspace}
|
||||
upgradable={!isSelfHosted}
|
||||
{...props}
|
||||
/>
|
||||
<MembersPanel workspace={workspace} {...props} />
|
||||
</SettingWrapper>
|
||||
{storageAndExportSetting}
|
||||
<SettingWrapper>
|
||||
|
||||
@@ -29,7 +29,6 @@ import {
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
|
||||
import { openSettingModalAtom } from '../../../atoms';
|
||||
import type { CheckedUser } from '../../../hooks/affine/use-current-user';
|
||||
@@ -39,7 +38,7 @@ import { useMemberCount } from '../../../hooks/affine/use-member-count';
|
||||
import { type Member, useMembers } from '../../../hooks/affine/use-members';
|
||||
import { useRevokeMemberPermission } from '../../../hooks/affine/use-revoke-member-permission';
|
||||
import { useUserSubscription } from '../../../hooks/use-subscription';
|
||||
import { AnyErrorBoundary } from '../any-error-boundary';
|
||||
import { AffineErrorBoundary } from '../affine-error-boundary';
|
||||
import * as style from './style.css';
|
||||
import type { WorkspaceSettingDetailProps } from './types';
|
||||
|
||||
@@ -51,7 +50,6 @@ enum MemberLimitCount {
|
||||
|
||||
const COUNT_PER_PAGE = 8;
|
||||
export interface MembersPanelProps extends WorkspaceSettingDetailProps {
|
||||
upgradable: boolean;
|
||||
workspace: AffineOfficialWorkspace;
|
||||
}
|
||||
type OnRevoke = (memberId: string) => void;
|
||||
@@ -71,7 +69,6 @@ const MembersPanelLocal = () => {
|
||||
export const CloudWorkspaceMembersPanel = ({
|
||||
workspace,
|
||||
isOwner,
|
||||
upgradable,
|
||||
}: MembersPanelProps) => {
|
||||
const workspaceId = workspace.id;
|
||||
const memberCount = useMemberCount(workspaceId);
|
||||
@@ -167,20 +164,16 @@ export const CloudWorkspaceMembersPanel = ({
|
||||
planName: plan,
|
||||
memberLimit,
|
||||
})}
|
||||
{upgradable ? (
|
||||
<>
|
||||
,
|
||||
<div className={style.goUpgradeWrapper} onClick={handleUpgrade}>
|
||||
<span className={style.goUpgrade}>
|
||||
{t['com.affine.payment.member.description.go-upgrade']()}
|
||||
</span>
|
||||
<ArrowRightBigIcon className={style.arrowRight} />
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
,
|
||||
<div className={style.goUpgradeWrapper} onClick={handleUpgrade}>
|
||||
<span className={style.goUpgrade}>
|
||||
{t['com.affine.payment.member.description.go-upgrade']()}
|
||||
</span>
|
||||
<ArrowRightBigIcon className={style.arrowRight} />
|
||||
</div>
|
||||
</span>
|
||||
);
|
||||
}, [handleUpgrade, memberLimit, plan, t, upgradable]);
|
||||
}, [handleUpgrade, memberLimit, plan, t]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -362,10 +355,10 @@ export const MembersPanel = (props: MembersPanelProps): ReactElement | null => {
|
||||
return <MembersPanelLocal />;
|
||||
}
|
||||
return (
|
||||
<ErrorBoundary FallbackComponent={AnyErrorBoundary}>
|
||||
<AffineErrorBoundary>
|
||||
<Suspense>
|
||||
<CloudWorkspaceMembersPanel {...props} />
|
||||
</Suspense>
|
||||
</ErrorBoundary>
|
||||
</AffineErrorBoundary>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,251 +0,0 @@
|
||||
import { DebugLogger } from '@affine/debug';
|
||||
import {
|
||||
fetchWithTraceReport,
|
||||
type ListHistoryQuery,
|
||||
listHistoryQuery,
|
||||
recoverDocMutation,
|
||||
} from '@affine/graphql';
|
||||
import {
|
||||
useMutateQueryResource,
|
||||
useMutation,
|
||||
useQueryInfinite,
|
||||
} from '@affine/workspace/affine/gql';
|
||||
import { createAffineCloudBlobEngine } from '@affine/workspace/blob';
|
||||
import { globalBlockSuiteSchema } from '@affine/workspace/manager';
|
||||
import { assertEquals } from '@blocksuite/global/utils';
|
||||
import { Workspace } from '@blocksuite/store';
|
||||
import { usePageMetaHelper } from '@toeverything/hooks/use-block-suite-page-meta';
|
||||
import { useBlockSuiteWorkspacePage } from '@toeverything/hooks/use-block-suite-workspace-page';
|
||||
import { revertUpdate } from '@toeverything/y-indexeddb';
|
||||
import { useMemo } from 'react';
|
||||
import useSWRImmutable from 'swr/immutable';
|
||||
import { applyUpdate } from 'yjs';
|
||||
|
||||
const logger = new DebugLogger('page-history');
|
||||
|
||||
type DocHistory = ListHistoryQuery['workspace']['histories'][number];
|
||||
|
||||
export const usePageSnapshotList = (workspaceId: string, pageDocId: string) => {
|
||||
const pageSize = 10;
|
||||
const { data, loadingMore, loadMore } = useQueryInfinite({
|
||||
query: listHistoryQuery,
|
||||
getVariables: (_, previousPageData) => {
|
||||
// use the timestamp of the last history as the cursor
|
||||
const before = previousPageData?.workspace.histories.at(-1)?.timestamp;
|
||||
const vars = {
|
||||
pageDocId: pageDocId,
|
||||
workspaceId: workspaceId,
|
||||
before: before,
|
||||
take: pageSize,
|
||||
};
|
||||
|
||||
return vars;
|
||||
},
|
||||
});
|
||||
|
||||
const shouldLoadMore = useMemo(() => {
|
||||
if (!data) {
|
||||
return false;
|
||||
}
|
||||
const lastPage = data.at(-1);
|
||||
if (!lastPage) {
|
||||
return false;
|
||||
}
|
||||
return lastPage.workspace.histories.length === pageSize;
|
||||
}, [data]);
|
||||
|
||||
const histories = useMemo(() => {
|
||||
if (!data) {
|
||||
return [];
|
||||
}
|
||||
return data.flatMap(page => page.workspace.histories);
|
||||
}, [data]);
|
||||
|
||||
return [
|
||||
histories,
|
||||
shouldLoadMore ? loadMore : undefined,
|
||||
loadingMore,
|
||||
] as const;
|
||||
};
|
||||
|
||||
const snapshotFetcher = async (
|
||||
[workspaceId, pageDocId, ts]: [
|
||||
workspaceId: string,
|
||||
pageDocId: string,
|
||||
ts: string,
|
||||
] // timestamp is the key to the history/snapshot
|
||||
) => {
|
||||
if (!ts) {
|
||||
return null;
|
||||
}
|
||||
const res = await fetchWithTraceReport(
|
||||
runtimeConfig.serverUrlPrefix +
|
||||
`/api/workspaces/${workspaceId}/docs/${pageDocId}/histories/${ts}`,
|
||||
{
|
||||
priority: 'high',
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error('Failed to fetch snapshot');
|
||||
}
|
||||
|
||||
const snapshot = await res.arrayBuffer();
|
||||
if (!snapshot) {
|
||||
throw new Error('Invalid snapshot');
|
||||
}
|
||||
return snapshot;
|
||||
};
|
||||
|
||||
// attach the Page shown in the modal to a temporary workspace
|
||||
// so that we do not need to worry about providers etc
|
||||
// todo: fix references to the page (the referenced page will shown as deleted)
|
||||
// if we simply clone the current workspace, it maybe time consuming right?
|
||||
const workspaceMap = new Map<string, Workspace>();
|
||||
|
||||
// assume the workspace is a cloud workspace since the history feature is only enabled for cloud workspace
|
||||
const getOrCreateWorkspace = (workspaceId: string) => {
|
||||
let workspace = workspaceMap.get(workspaceId);
|
||||
if (!workspace) {
|
||||
const blobEngine = createAffineCloudBlobEngine(workspaceId);
|
||||
workspace = new Workspace({
|
||||
id: workspaceId,
|
||||
providerCreators: [],
|
||||
blobStorages: [
|
||||
() => ({
|
||||
crud: {
|
||||
async get(key) {
|
||||
return (await blobEngine.get(key)) ?? null;
|
||||
},
|
||||
async set(key, value) {
|
||||
await blobEngine.set(key, value);
|
||||
return key;
|
||||
},
|
||||
async delete(key) {
|
||||
return blobEngine.delete(key);
|
||||
},
|
||||
async list() {
|
||||
return blobEngine.list();
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
schema: globalBlockSuiteSchema,
|
||||
});
|
||||
workspaceMap.set(workspaceId, workspace);
|
||||
}
|
||||
return workspace;
|
||||
};
|
||||
|
||||
// workspace id + page id + timestamp -> snapshot (update binary)
|
||||
export const usePageHistory = (
|
||||
workspaceId: string,
|
||||
pageDocId: string,
|
||||
ts?: string
|
||||
) => {
|
||||
// snapshot should be immutable. so we use swr immutable to disable revalidation
|
||||
const { data } = useSWRImmutable<ArrayBuffer | null>(
|
||||
[workspaceId, pageDocId, ts],
|
||||
{
|
||||
fetcher: snapshotFetcher,
|
||||
suspense: false,
|
||||
}
|
||||
);
|
||||
return data ?? undefined;
|
||||
};
|
||||
|
||||
// workspace id + page id + timestamp + snapshot -> Page (to be used for rendering in blocksuite editor)
|
||||
export const useSnapshotPage = (
|
||||
workspaceId: string,
|
||||
pageDocId: string,
|
||||
ts?: string,
|
||||
snapshot?: ArrayBuffer
|
||||
) => {
|
||||
const page = useMemo(() => {
|
||||
if (!ts) {
|
||||
return null;
|
||||
}
|
||||
const pageId = pageDocId + '-' + ts;
|
||||
const historyShellWorkspace = getOrCreateWorkspace(workspaceId);
|
||||
let page = historyShellWorkspace.getPage(pageId);
|
||||
if (!page && snapshot) {
|
||||
page = historyShellWorkspace.createPage({
|
||||
id: pageId,
|
||||
});
|
||||
page.awarenessStore.setReadonly(page, true);
|
||||
const spaceDoc = page.spaceDoc;
|
||||
page
|
||||
.load(() => applyUpdate(spaceDoc, new Uint8Array(snapshot)))
|
||||
.catch(console.error); // must load before applyUpdate
|
||||
}
|
||||
return page;
|
||||
}, [pageDocId, snapshot, ts, workspaceId]);
|
||||
|
||||
return page;
|
||||
};
|
||||
|
||||
export const historyListGroupByDay = (histories: DocHistory[]) => {
|
||||
const map = new Map<string, DocHistory[]>();
|
||||
for (const history of histories) {
|
||||
const day = new Date(history.timestamp).toLocaleDateString();
|
||||
const list = map.get(day) ?? [];
|
||||
list.push(history);
|
||||
map.set(day, list);
|
||||
}
|
||||
return [...map.entries()];
|
||||
};
|
||||
|
||||
export const useRestorePage = (workspace: Workspace, pageId: string) => {
|
||||
const page = useBlockSuiteWorkspacePage(workspace, pageId);
|
||||
const mutateQueryResource = useMutateQueryResource();
|
||||
const { trigger: recover, isMutating } = useMutation({
|
||||
mutation: recoverDocMutation,
|
||||
});
|
||||
const { getPageMeta, setPageTitle } = usePageMetaHelper(workspace);
|
||||
|
||||
const onRestore = useMemo(() => {
|
||||
return async (version: string, update: Uint8Array) => {
|
||||
if (!page) {
|
||||
return;
|
||||
}
|
||||
const pageDocId = page.spaceDoc.guid;
|
||||
revertUpdate(page.spaceDoc, update, key => {
|
||||
assertEquals(key, 'blocks'); // only expect this value is 'blocks'
|
||||
return 'Map';
|
||||
});
|
||||
|
||||
// should also update the page title, since it may be changed in the history
|
||||
const title = page.meta.title;
|
||||
|
||||
if (getPageMeta(pageDocId)?.title !== title) {
|
||||
setPageTitle(pageDocId, title);
|
||||
}
|
||||
|
||||
await recover({
|
||||
docId: pageDocId,
|
||||
timestamp: version,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
|
||||
await mutateQueryResource(listHistoryQuery, vars => {
|
||||
return (
|
||||
vars.pageDocId === pageDocId && vars.workspaceId === workspace.id
|
||||
);
|
||||
});
|
||||
|
||||
logger.info('Page restored', pageDocId, version);
|
||||
};
|
||||
}, [
|
||||
getPageMeta,
|
||||
mutateQueryResource,
|
||||
page,
|
||||
recover,
|
||||
setPageTitle,
|
||||
workspace.id,
|
||||
]);
|
||||
|
||||
return {
|
||||
onRestore,
|
||||
isMutating,
|
||||
};
|
||||
};
|
||||
@@ -1,119 +0,0 @@
|
||||
export const EmptyHistoryShape = () => (
|
||||
<svg
|
||||
width="200"
|
||||
height="174"
|
||||
viewBox="0 0 200 174"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect
|
||||
x="51.724"
|
||||
y="38.4615"
|
||||
width="96.5517"
|
||||
height="96.5517"
|
||||
stroke="var(--affine-text-disable-color)"
|
||||
/>
|
||||
<path
|
||||
d="M51.8339 86.7374L99.9999 38.5714L148.166 86.7374L99.9999 134.903L51.8339 86.7374Z"
|
||||
stroke="var(--affine-text-disable-color)"
|
||||
/>
|
||||
<path
|
||||
d="M99.6052 38.1966C107.662 33.4757 117.043 30.7695 127.055 30.7695C157.087 30.7695 181.432 55.1148 181.432 85.1462C181.432 107.547 167.887 126.783 148.541 135.113"
|
||||
stroke="var(--affine-text-disable-color)"
|
||||
/>
|
||||
<path
|
||||
d="M148.375 86.4722C153.096 94.5291 155.802 103.91 155.802 113.922C155.802 143.954 131.457 168.299 101.426 168.299C79.0254 168.299 59.7886 154.754 51.4587 135.408"
|
||||
stroke="var(--affine-text-disable-color)"
|
||||
/>
|
||||
<path
|
||||
d="M100.394 135.113C92.3373 139.834 82.9568 142.54 72.9441 142.54C42.9127 142.54 18.5675 118.195 18.5675 88.1634C18.5675 65.763 32.1124 46.5261 51.4587 38.1963"
|
||||
stroke="var(--affine-text-disable-color)"
|
||||
/>
|
||||
<path
|
||||
d="M51.4585 87.1318C46.7377 79.0749 44.0315 69.6943 44.0315 59.6817C44.0315 29.6503 68.3767 5.30503 98.4081 5.30503C120.809 5.30503 140.045 18.8499 148.375 38.1963"
|
||||
stroke="var(--affine-text-disable-color)"
|
||||
/>
|
||||
<path
|
||||
d="M51.4587 38.1963L148.541 135.279"
|
||||
stroke="var(--affine-text-disable-color)"
|
||||
/>
|
||||
<path
|
||||
d="M148.541 38.1963L51.4587 135.279"
|
||||
stroke="var(--affine-text-disable-color)"
|
||||
/>
|
||||
<path
|
||||
d="M99.9998 38.1963V135.279"
|
||||
stroke="var(--affine-text-disable-color)"
|
||||
/>
|
||||
<path
|
||||
d="M148.541 86.7378L51.4588 86.7378"
|
||||
stroke="var(--affine-text-disable-color)"
|
||||
/>
|
||||
<ellipse
|
||||
cx="148.275"
|
||||
cy="38.4617"
|
||||
rx="3.97878"
|
||||
ry="3.97878"
|
||||
fill="var(--affine-text-primary-color)"
|
||||
/>
|
||||
<ellipse
|
||||
cx="148.275"
|
||||
cy="135.013"
|
||||
rx="3.97878"
|
||||
ry="3.97878"
|
||||
fill="var(--affine-text-primary-color)"
|
||||
/>
|
||||
<ellipse
|
||||
cx="148.275"
|
||||
cy="86.7376"
|
||||
rx="3.97878"
|
||||
ry="3.97878"
|
||||
fill="var(--affine-text-primary-color)"
|
||||
/>
|
||||
<ellipse
|
||||
cx="51.7241"
|
||||
cy="38.4617"
|
||||
rx="3.97878"
|
||||
ry="3.97878"
|
||||
fill="var(--affine-text-primary-color)"
|
||||
/>
|
||||
<ellipse
|
||||
cx="51.7241"
|
||||
cy="135.013"
|
||||
rx="3.97878"
|
||||
ry="3.97878"
|
||||
fill="var(--affine-text-primary-color)"
|
||||
/>
|
||||
<ellipse
|
||||
cx="51.7241"
|
||||
cy="86.7376"
|
||||
rx="3.97878"
|
||||
ry="3.97878"
|
||||
fill="var(--affine-text-primary-color)"
|
||||
/>
|
||||
<ellipse
|
||||
cx="100"
|
||||
cy="38.4617"
|
||||
rx="3.97878"
|
||||
ry="3.97878"
|
||||
transform="rotate(-90 100 38.4617)"
|
||||
fill="var(--affine-text-primary-color)"
|
||||
/>
|
||||
<ellipse
|
||||
cx="100"
|
||||
cy="86.2073"
|
||||
rx="3.97878"
|
||||
ry="3.97878"
|
||||
transform="rotate(-90 100 86.2073)"
|
||||
fill="var(--affine-text-primary-color)"
|
||||
/>
|
||||
<ellipse
|
||||
cx="100"
|
||||
cy="135.013"
|
||||
rx="3.97878"
|
||||
ry="3.97878"
|
||||
transform="rotate(-90 100 135.013)"
|
||||
fill="var(--affine-text-primary-color)"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
@@ -1,446 +0,0 @@
|
||||
import { Scrollable } from '@affine/component';
|
||||
import {
|
||||
BlockSuiteEditor,
|
||||
BlockSuiteFallback,
|
||||
} from '@affine/component/block-suite-editor';
|
||||
import type { PageMode } from '@affine/core/atoms';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import type { Workspace } from '@blocksuite/store';
|
||||
import type { DialogContentProps } from '@radix-ui/react-dialog';
|
||||
import { Button } from '@toeverything/components/button';
|
||||
import { ConfirmModal, Modal } from '@toeverything/components/modal';
|
||||
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
|
||||
import { useAtom, useAtomValue } from 'jotai';
|
||||
import {
|
||||
type PropsWithChildren,
|
||||
Suspense,
|
||||
useCallback,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import { currentModeAtom } from '../../../atoms/mode';
|
||||
import { pageHistoryModalAtom } from '../../../atoms/page-history';
|
||||
import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace';
|
||||
import { StyledEditorModeSwitch } from '../../blocksuite/block-suite-mode-switch/style';
|
||||
import {
|
||||
EdgelessSwitchItem,
|
||||
PageSwitchItem,
|
||||
} from '../../blocksuite/block-suite-mode-switch/switch-items';
|
||||
import { AffineErrorBoundary } from '../affine-error-boundary';
|
||||
import {
|
||||
historyListGroupByDay,
|
||||
usePageHistory,
|
||||
usePageSnapshotList,
|
||||
useRestorePage,
|
||||
useSnapshotPage,
|
||||
} from './data';
|
||||
import { EmptyHistoryShape } from './empty-history-shape';
|
||||
import * as styles from './styles.css';
|
||||
|
||||
export interface PageHistoryModalProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
workspace: Workspace;
|
||||
pageId: string;
|
||||
}
|
||||
|
||||
const contentOptions: DialogContentProps = {
|
||||
['data-testid' as string]: 'page-history-modal',
|
||||
onPointerDownOutside: e => {
|
||||
e.preventDefault();
|
||||
},
|
||||
style: {
|
||||
padding: 0,
|
||||
maxWidth: 944,
|
||||
backgroundColor: 'var(--affine-background-primary-color)',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
};
|
||||
|
||||
const ModalContainer = ({
|
||||
onOpenChange,
|
||||
open,
|
||||
children,
|
||||
}: PropsWithChildren<{
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}>) => {
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
onOpenChange={onOpenChange}
|
||||
width="calc(100% - 64px)"
|
||||
height="80%"
|
||||
withoutCloseButton
|
||||
contentOptions={contentOptions}
|
||||
>
|
||||
<AffineErrorBoundary>{children}</AffineErrorBoundary>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const localTimeFormatter = new Intl.DateTimeFormat('en', {
|
||||
timeStyle: 'short',
|
||||
});
|
||||
|
||||
const timestampToLocalTime = (ts: string) => {
|
||||
return localTimeFormatter.format(new Date(ts));
|
||||
};
|
||||
|
||||
interface HistoryEditorPreviewProps {
|
||||
workspaceId: string;
|
||||
pageDocId: string;
|
||||
ts?: string;
|
||||
snapshot?: ArrayBuffer;
|
||||
mode: PageMode;
|
||||
onModeChange: (mode: PageMode) => void;
|
||||
title: string;
|
||||
}
|
||||
|
||||
const HistoryEditorPreview = ({
|
||||
ts,
|
||||
snapshot,
|
||||
onModeChange,
|
||||
mode,
|
||||
workspaceId,
|
||||
pageDocId,
|
||||
title,
|
||||
}: HistoryEditorPreviewProps) => {
|
||||
const onSwitchToPageMode = useCallback(() => {
|
||||
onModeChange('page');
|
||||
}, [onModeChange]);
|
||||
const onSwitchToEdgelessMode = useCallback(() => {
|
||||
onModeChange('edgeless');
|
||||
}, [onModeChange]);
|
||||
const page = useSnapshotPage(workspaceId, pageDocId, ts, snapshot);
|
||||
|
||||
return (
|
||||
<div className={styles.previewWrapper}>
|
||||
<div className={styles.previewHeader}>
|
||||
<StyledEditorModeSwitch switchLeft={mode === 'page'}>
|
||||
<PageSwitchItem
|
||||
data-testid="switch-page-mode-button"
|
||||
active={mode === 'page'}
|
||||
onClick={onSwitchToPageMode}
|
||||
/>
|
||||
<EdgelessSwitchItem
|
||||
data-testid="switch-edgeless-mode-button"
|
||||
active={mode === 'edgeless'}
|
||||
onClick={onSwitchToEdgelessMode}
|
||||
/>
|
||||
</StyledEditorModeSwitch>
|
||||
|
||||
<div className={styles.previewHeaderTitle}>{title}</div>
|
||||
|
||||
<div className={styles.previewHeaderTimestamp}>
|
||||
{ts ? timestampToLocalTime(ts) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{page ? (
|
||||
<BlockSuiteEditor
|
||||
className={styles.editor}
|
||||
mode={mode}
|
||||
page={page}
|
||||
onModeChange={onModeChange}
|
||||
/>
|
||||
) : (
|
||||
<BlockSuiteFallback />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const PageHistoryList = ({
|
||||
pageDocId,
|
||||
workspaceId,
|
||||
activeVersion,
|
||||
onVersionChange,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
pageDocId: string;
|
||||
activeVersion?: string;
|
||||
onVersionChange: (version: string) => void;
|
||||
}) => {
|
||||
const [historyList, loadMore, loadingMore] = usePageSnapshotList(
|
||||
workspaceId,
|
||||
pageDocId
|
||||
);
|
||||
const historyListByDay = useMemo(() => {
|
||||
return historyListGroupByDay(historyList);
|
||||
}, [historyList]);
|
||||
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (historyList.length > 0 && !activeVersion) {
|
||||
onVersionChange(historyList[0].timestamp);
|
||||
}
|
||||
}, [activeVersion, historyList, onVersionChange]);
|
||||
|
||||
return (
|
||||
<div className={styles.historyList}>
|
||||
<div className={styles.historyListHeader}>
|
||||
{t['com.affine.history.version-history']()}
|
||||
</div>
|
||||
<Scrollable.Root className={styles.historyListScrollable}>
|
||||
<Scrollable.Viewport className={styles.historyListScrollableInner}>
|
||||
{historyListByDay.map(([day, list]) => {
|
||||
return (
|
||||
<div key={day} className={styles.historyItemGroup}>
|
||||
<div className={styles.historyItemGroupTitle}>{day}</div>
|
||||
{list.map(history => (
|
||||
<div
|
||||
className={styles.historyItem}
|
||||
key={history.timestamp}
|
||||
data-testid="version-history-item"
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
onVersionChange(history.timestamp);
|
||||
}}
|
||||
data-active={activeVersion === history.timestamp}
|
||||
>
|
||||
<button>{timestampToLocalTime(history.timestamp)}</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{loadMore ? (
|
||||
<Button
|
||||
type="plain"
|
||||
loading={loadingMore}
|
||||
disabled={loadingMore}
|
||||
className={styles.historyItemLoadMore}
|
||||
onClick={loadMore}
|
||||
>
|
||||
Load More
|
||||
</Button>
|
||||
) : null}
|
||||
</Scrollable.Viewport>
|
||||
<Scrollable.Scrollbar />
|
||||
</Scrollable.Root>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface ConfirmRestoreModalProps {
|
||||
open: boolean;
|
||||
onConfirm: (res: boolean) => void;
|
||||
isMutating: boolean;
|
||||
}
|
||||
|
||||
const ConfirmRestoreModal = ({
|
||||
isMutating,
|
||||
open,
|
||||
onConfirm,
|
||||
}: ConfirmRestoreModalProps) => {
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
const handleConfirm = useCallback(() => {
|
||||
onConfirm(true);
|
||||
}, [onConfirm]);
|
||||
|
||||
const handleCancel = useCallback(() => {
|
||||
onConfirm(false);
|
||||
}, [onConfirm]);
|
||||
|
||||
return (
|
||||
<ConfirmModal
|
||||
open={open}
|
||||
onOpenChange={handleCancel}
|
||||
title={t['com.affine.history.restore-current-version']()}
|
||||
description={t['com.affine.history.confirm-restore-modal.hint']()}
|
||||
cancelText={t['Cancel']()}
|
||||
contentOptions={{
|
||||
['data-testid' as string]: 'confirm-restore-history-modal',
|
||||
style: {
|
||||
padding: '20px 26px',
|
||||
},
|
||||
}}
|
||||
confirmButtonOptions={{
|
||||
loading: isMutating,
|
||||
type: 'primary',
|
||||
['data-testid' as string]: 'confirm-restore-history-button',
|
||||
children: t['com.affine.history.confirm-restore-modal.restore'](),
|
||||
}}
|
||||
onConfirm={handleConfirm}
|
||||
></ConfirmModal>
|
||||
);
|
||||
};
|
||||
|
||||
const EmptyHistoryPrompt = () => {
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.emptyHistoryPrompt}
|
||||
data-testid="empty-history-prompt"
|
||||
>
|
||||
<EmptyHistoryShape />
|
||||
<div className={styles.emptyHistoryPromptTitle}>
|
||||
{t['com.affine.history.empty-prompt.title']()}
|
||||
</div>
|
||||
<div className={styles.emptyHistoryPromptDescription}>
|
||||
{t['com.affine.history.empty-prompt.description']()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const PageHistoryManager = ({
|
||||
workspace,
|
||||
pageId,
|
||||
onClose,
|
||||
}: {
|
||||
workspace: Workspace;
|
||||
pageId: string;
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
const workspaceId = workspace.id;
|
||||
const [activeVersion, setActiveVersion] = useState<string>();
|
||||
|
||||
const pageDocId = useMemo(() => {
|
||||
return workspace.getPage(pageId)?.spaceDoc.guid ?? pageId;
|
||||
}, [pageId, workspace]);
|
||||
|
||||
const snapshot = usePageHistory(workspaceId, pageDocId, activeVersion);
|
||||
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
const { onRestore, isMutating } = useRestorePage(workspace, pageId);
|
||||
|
||||
const handleRestore = useMemo(
|
||||
() => async () => {
|
||||
if (!activeVersion || !snapshot) {
|
||||
return;
|
||||
}
|
||||
await onRestore(activeVersion, new Uint8Array(snapshot));
|
||||
// close the modal after restore
|
||||
onClose();
|
||||
},
|
||||
[activeVersion, onClose, onRestore, snapshot]
|
||||
);
|
||||
|
||||
const defaultPreviewPageMode = useAtomValue(currentModeAtom);
|
||||
const [mode, setMode] = useState<PageMode>(defaultPreviewPageMode);
|
||||
|
||||
const title = useMemo(
|
||||
() => workspace.getPage(pageId)?.meta.title || t['Untitled'](),
|
||||
[pageId, t, workspace]
|
||||
);
|
||||
|
||||
const [showRestoreConfirmModal, setShowRestoreConfirmModal] = useState(false);
|
||||
|
||||
const showRestoreConfirm = useCallback(() => {
|
||||
setShowRestoreConfirmModal(true);
|
||||
}, []);
|
||||
|
||||
const onConfirmRestore = useAsyncCallback(
|
||||
async res => {
|
||||
if (res) {
|
||||
await handleRestore();
|
||||
}
|
||||
setShowRestoreConfirmModal(false);
|
||||
},
|
||||
[handleRestore]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
<div className={styles.modalContent} data-empty={!activeVersion}>
|
||||
<HistoryEditorPreview
|
||||
workspaceId={workspaceId}
|
||||
pageDocId={pageDocId}
|
||||
ts={activeVersion}
|
||||
snapshot={snapshot}
|
||||
mode={mode}
|
||||
onModeChange={setMode}
|
||||
title={title}
|
||||
/>
|
||||
|
||||
<PageHistoryList
|
||||
workspaceId={workspaceId}
|
||||
pageDocId={pageDocId}
|
||||
activeVersion={activeVersion}
|
||||
onVersionChange={setActiveVersion}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{!activeVersion ? (
|
||||
<div className={styles.modalContent}>
|
||||
<EmptyHistoryPrompt />
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className={styles.historyFooter}>
|
||||
<Button type="plain" onClick={onClose}>
|
||||
{t['com.affine.history.back-to-page']()}
|
||||
</Button>
|
||||
<div className={styles.spacer} />
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={showRestoreConfirm}
|
||||
disabled={isMutating || !activeVersion}
|
||||
>
|
||||
{t['com.affine.history.restore-current-version']()}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<ConfirmRestoreModal
|
||||
open={showRestoreConfirmModal}
|
||||
isMutating={isMutating}
|
||||
onConfirm={onConfirmRestore}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const PageHistoryModal = ({
|
||||
onOpenChange,
|
||||
open,
|
||||
pageId,
|
||||
workspace,
|
||||
}: PageHistoryModalProps) => {
|
||||
const onClose = useCallback(() => {
|
||||
onOpenChange(false);
|
||||
}, [onOpenChange]);
|
||||
|
||||
return (
|
||||
<ModalContainer onOpenChange={onOpenChange} open={open}>
|
||||
<Suspense fallback={<BlockSuiteFallback />}>
|
||||
<PageHistoryManager
|
||||
onClose={onClose}
|
||||
pageId={pageId}
|
||||
workspace={workspace}
|
||||
/>
|
||||
</Suspense>
|
||||
</ModalContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export const GlobalPageHistoryModal = () => {
|
||||
const [{ open, pageId }, setState] = useAtom(pageHistoryModalAtom);
|
||||
const [workspace] = useCurrentWorkspace();
|
||||
|
||||
const handleOpenChange = useCallback(
|
||||
(open: boolean) => {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
open,
|
||||
}));
|
||||
},
|
||||
[setState]
|
||||
);
|
||||
|
||||
return (
|
||||
<PageHistoryModal
|
||||
open={open}
|
||||
onOpenChange={handleOpenChange}
|
||||
pageId={pageId}
|
||||
workspace={workspace.blockSuiteWorkspace}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export * from './history-modal';
|
||||
@@ -1,185 +0,0 @@
|
||||
import { createVar, globalStyle, style } from '@vanilla-extract/css';
|
||||
|
||||
const headerHeight = createVar('header-height');
|
||||
const footerHeight = createVar('footer-height');
|
||||
const historyListWidth = createVar('history-list-width');
|
||||
|
||||
export const root = style({
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
vars: {
|
||||
[headerHeight]: '52px',
|
||||
[footerHeight]: '68px',
|
||||
[historyListWidth]: '160px',
|
||||
},
|
||||
});
|
||||
|
||||
export const modalContent = style({
|
||||
display: 'flex',
|
||||
height: `calc(100% - ${footerHeight})`,
|
||||
width: '100%',
|
||||
position: 'absolute',
|
||||
selectors: {
|
||||
'&[data-empty="true"]': {
|
||||
opacity: 0,
|
||||
pointerEvents: 'none',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const previewWrapper = style({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
height: '100%',
|
||||
width: `calc(100% - ${historyListWidth})`,
|
||||
backgroundColor: 'var(--affine-background-secondary-color)',
|
||||
});
|
||||
|
||||
export const previewHeader = style({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: headerHeight,
|
||||
borderBottom: '1px solid var(--affine-border-color)',
|
||||
backgroundColor: 'var(--affine-background-primary-color)',
|
||||
padding: '0 12px',
|
||||
flexShrink: 0,
|
||||
gap: 12,
|
||||
});
|
||||
|
||||
export const previewHeaderTitle = style({
|
||||
fontSize: 'var(--affine-font-xs)',
|
||||
fontWeight: 600,
|
||||
maxWidth: 400, // better responsiveness
|
||||
});
|
||||
|
||||
export const previewHeaderTimestamp = style({
|
||||
color: 'var(--affine-text-secondary-color)',
|
||||
backgroundColor: 'var(--affine-background-secondary-color)',
|
||||
padding: '0 10px',
|
||||
borderRadius: 4,
|
||||
fontSize: 'var(--affine-font-xs)',
|
||||
});
|
||||
|
||||
export const editor = style({
|
||||
height: '100%',
|
||||
flexGrow: 1,
|
||||
overflow: 'hidden',
|
||||
});
|
||||
|
||||
export const historyList = style({
|
||||
overflow: 'hidden',
|
||||
height: '100%',
|
||||
width: historyListWidth,
|
||||
flexShrink: 0,
|
||||
borderLeft: '1px solid var(--affine-border-color)',
|
||||
});
|
||||
|
||||
export const historyListScrollable = style({
|
||||
height: `calc(100% - ${headerHeight})`,
|
||||
});
|
||||
|
||||
export const historyListScrollableInner = style({
|
||||
display: 'flex',
|
||||
gap: 16,
|
||||
flexDirection: 'column',
|
||||
});
|
||||
|
||||
export const historyListHeader = style({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
height: 52,
|
||||
borderBottom: '1px solid var(--affine-border-color)',
|
||||
fontWeight: 'bold',
|
||||
flexShrink: 0,
|
||||
padding: '0 12px',
|
||||
});
|
||||
|
||||
export const historyItemGroup = style({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
rowGap: 6,
|
||||
});
|
||||
|
||||
export const historyItemGroupTitle = style({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: '12px',
|
||||
fontWeight: 'bold',
|
||||
backgroundColor: 'var(--affine-background-primary-color)',
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
});
|
||||
|
||||
export const historyItem = style({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: '0 12px',
|
||||
height: 32,
|
||||
cursor: 'pointer',
|
||||
selectors: {
|
||||
'&:hover, &[data-active=true]': {
|
||||
backgroundColor: 'var(--affine-hover-color)',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const historyItemLoadMore = style([
|
||||
historyItem,
|
||||
{
|
||||
cursor: 'pointer',
|
||||
color: 'var(--affine-text-secondary-color)',
|
||||
flexShrink: 0,
|
||||
borderRadius: 0,
|
||||
selectors: {
|
||||
'&:hover': {
|
||||
backgroundColor: 'var(--affine-hover-color)',
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
globalStyle(`${historyItem} button`, {
|
||||
color: 'inherit',
|
||||
});
|
||||
|
||||
export const historyFooter = style({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: 68,
|
||||
borderTop: '1px solid var(--affine-border-color)',
|
||||
padding: '0 24px',
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
});
|
||||
|
||||
export const spacer = style({
|
||||
flexGrow: 1,
|
||||
});
|
||||
|
||||
export const emptyHistoryPrompt = style({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
zIndex: 1,
|
||||
gap: 20,
|
||||
});
|
||||
|
||||
export const emptyHistoryPromptTitle = style({
|
||||
fontWeight: 600,
|
||||
fontSize: 'var(--affine-font-h-5)',
|
||||
});
|
||||
|
||||
export const emptyHistoryPromptDescription = style({
|
||||
width: 320,
|
||||
textAlign: 'center',
|
||||
fontSize: 'var(--affine-font-xs)',
|
||||
color: 'var(--affine-text-secondary-color)',
|
||||
});
|
||||
@@ -35,7 +35,6 @@ import {
|
||||
openSignOutModalAtom,
|
||||
} from '../../../../atoms';
|
||||
import { useCurrentUser } from '../../../../hooks/affine/use-current-user';
|
||||
import { useSelfHosted } from '../../../../hooks/affine/use-server-flavor';
|
||||
import { useUserSubscription } from '../../../../hooks/use-subscription';
|
||||
import { Upload } from '../../../pure/file-upload';
|
||||
import * as style from './style.css';
|
||||
@@ -168,7 +167,6 @@ export const AvatarAndName = () => {
|
||||
|
||||
const StoragePanel = () => {
|
||||
const t = useAFFiNEI18N();
|
||||
const isSelfHosted = useSelfHosted();
|
||||
|
||||
const { data } = useQuery({
|
||||
query: allBlobSizesQuery,
|
||||
@@ -177,7 +175,6 @@ const StoragePanel = () => {
|
||||
const [subscription] = useUserSubscription();
|
||||
const plan = subscription?.plan ?? SubscriptionPlan.Free;
|
||||
|
||||
// TODO(@JimmFly): get limit from user usage query directly after #4720 is merged
|
||||
const maxLimit = useMemo(() => {
|
||||
return bytes.parse(plan === SubscriptionPlan.Free ? '10GB' : '100GB');
|
||||
}, [plan]);
|
||||
@@ -202,7 +199,6 @@ const StoragePanel = () => {
|
||||
plan={plan}
|
||||
value={data.collectAllBlobSizes.size}
|
||||
onUpgrade={onUpgrade}
|
||||
upgradable={!isSelfHosted}
|
||||
/>
|
||||
</SettingRow>
|
||||
);
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
import type { ReactElement, SVGProps } from 'react';
|
||||
|
||||
import { useCurrentLoginStatus } from '../../../../hooks/affine/use-current-login-status';
|
||||
import { useSelfHosted } from '../../../../hooks/affine/use-server-flavor';
|
||||
import { AboutAffine } from './about';
|
||||
import { AppearanceSettings } from './appearance';
|
||||
import { BillingSettings } from './billing';
|
||||
@@ -37,7 +36,6 @@ export type GeneralSettingList = GeneralSettingListItem[];
|
||||
export const useGeneralSettingList = (): GeneralSettingList => {
|
||||
const t = useAFFiNEI18N();
|
||||
const status = useCurrentLoginStatus();
|
||||
const isSelfHosted = useSelfHosted();
|
||||
|
||||
const settings: GeneralSettingListItem[] = [
|
||||
{
|
||||
@@ -52,6 +50,13 @@ export const useGeneralSettingList = (): GeneralSettingList => {
|
||||
icon: KeyboardIcon,
|
||||
testId: 'shortcuts-panel-trigger',
|
||||
},
|
||||
{
|
||||
key: 'plans',
|
||||
title: t['com.affine.payment.title'](),
|
||||
icon: UpgradeIcon,
|
||||
testId: 'plans-panel-trigger',
|
||||
},
|
||||
|
||||
{
|
||||
key: 'plugins',
|
||||
title: 'Plugins',
|
||||
@@ -66,21 +71,13 @@ export const useGeneralSettingList = (): GeneralSettingList => {
|
||||
},
|
||||
];
|
||||
|
||||
if (!isSelfHosted) {
|
||||
if (status === 'authenticated') {
|
||||
settings.splice(3, 0, {
|
||||
key: 'plans',
|
||||
title: t['com.affine.payment.title'](),
|
||||
icon: UpgradeIcon,
|
||||
testId: 'plans-panel-trigger',
|
||||
key: 'billing',
|
||||
title: t['com.affine.payment.billing-setting.title'](),
|
||||
icon: PaymentIcon,
|
||||
testId: 'billing-panel-trigger',
|
||||
});
|
||||
if (status === 'authenticated') {
|
||||
settings.splice(3, 0, {
|
||||
key: 'billing',
|
||||
title: t['com.affine.payment.billing-setting.title'](),
|
||||
icon: PaymentIcon,
|
||||
testId: 'billing-panel-trigger',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return settings;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { FlexWrapper } from '@affine/component';
|
||||
import { Export, MoveToTrash } from '@affine/component/page-list';
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import {
|
||||
@@ -9,7 +8,6 @@ import {
|
||||
EditIcon,
|
||||
FavoritedIcon,
|
||||
FavoriteIcon,
|
||||
HistoryIcon,
|
||||
ImportIcon,
|
||||
PageIcon,
|
||||
} from '@blocksuite/icons';
|
||||
@@ -27,7 +25,7 @@ import {
|
||||
} from '@toeverything/hooks/use-block-suite-page-meta';
|
||||
import { useBlockSuiteWorkspaceHelper } from '@toeverything/hooks/use-block-suite-workspace-helper';
|
||||
import { useAtomValue, useSetAtom } from 'jotai';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { applyUpdate, encodeStateAsUpdate } from 'yjs';
|
||||
|
||||
import { setPageModeAtom } from '../../../atoms';
|
||||
@@ -38,7 +36,6 @@ import { useTrashModalHelper } from '../../../hooks/affine/use-trash-modal-helpe
|
||||
import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace';
|
||||
import { useNavigateHelper } from '../../../hooks/use-navigate-helper';
|
||||
import { toast } from '../../../utils';
|
||||
import { PageHistoryModal } from '../../affine/page-history-modal/history-modal';
|
||||
import { HeaderDropDownButton } from '../../pure/header-drop-down-button';
|
||||
import { usePageHelper } from '../block-suite-page-list/utils';
|
||||
|
||||
@@ -71,12 +68,6 @@ export const PageMenu = ({ rename, pageId }: PageMenuProps) => {
|
||||
const { createPage } = useBlockSuiteWorkspaceHelper(blockSuiteWorkspace);
|
||||
const { setTrashModal } = useTrashModalHelper(blockSuiteWorkspace);
|
||||
|
||||
const [historyModalOpen, setHistoryModalOpen] = useState(false);
|
||||
|
||||
const openHistoryModal = useCallback(() => {
|
||||
setHistoryModalOpen(true);
|
||||
}, []);
|
||||
|
||||
const handleOpenTrashModal = useCallback(() => {
|
||||
setTrashModal({
|
||||
open: true,
|
||||
@@ -216,23 +207,6 @@ export const PageMenu = ({ rename, pageId }: PageMenuProps) => {
|
||||
>
|
||||
{t['Import']()}
|
||||
</MenuItem>
|
||||
|
||||
{workspace.flavour === WorkspaceFlavour.AFFINE_CLOUD &&
|
||||
runtimeConfig.enablePageHistory ? (
|
||||
<MenuItem
|
||||
preFix={
|
||||
<MenuIcon>
|
||||
<HistoryIcon />
|
||||
</MenuIcon>
|
||||
}
|
||||
data-testid="editor-option-menu-history"
|
||||
onSelect={openHistoryModal}
|
||||
style={menuItemStyle}
|
||||
>
|
||||
{t['com.affine.history.view-history-version']()}
|
||||
</MenuItem>
|
||||
) : null}
|
||||
|
||||
<Export exportHandler={exportHandler} />
|
||||
<MenuSeparator />
|
||||
<MoveToTrash
|
||||
@@ -254,14 +228,6 @@ export const PageMenu = ({ rename, pageId }: PageMenuProps) => {
|
||||
>
|
||||
<HeaderDropDownButton />
|
||||
</Menu>
|
||||
{workspace.flavour === WorkspaceFlavour.AFFINE_CLOUD ? (
|
||||
<PageHistoryModal
|
||||
workspace={workspace.blockSuiteWorkspace}
|
||||
open={historyModalOpen}
|
||||
pageId={pageId}
|
||||
onOpenChange={setHistoryModalOpen}
|
||||
/>
|
||||
) : null}
|
||||
</FlexWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -30,8 +30,8 @@ import { useAppSettingHelper } from '../../hooks/affine/use-app-setting-helper';
|
||||
import { useDeleteCollectionInfo } from '../../hooks/affine/use-delete-collection-info';
|
||||
import { useGeneralShortcuts } from '../../hooks/affine/use-shortcuts';
|
||||
import { useTrashModalHelper } from '../../hooks/affine/use-trash-modal-helper';
|
||||
import { useRegisterBrowserHistoryCommands } from '../../hooks/use-browser-history-commands';
|
||||
import { useNavigateHelper } from '../../hooks/use-navigate-helper';
|
||||
import { useRegisterBlocksuiteEditorCommands } from '../../hooks/use-shortcut-commands';
|
||||
import type { AllWorkspace } from '../../shared';
|
||||
import { CollectionsList } from '../pure/workspace-slider-bar/collections';
|
||||
import { AddCollectionButton } from '../pure/workspace-slider-bar/collections/add-collection-button';
|
||||
@@ -169,7 +169,7 @@ export const RootAppSidebar = ({
|
||||
const closeUserWorkspaceList = useCallback(() => {
|
||||
setOpenUserWorkspaceList(false);
|
||||
}, [setOpenUserWorkspaceList]);
|
||||
useRegisterBrowserHistoryCommands(router.back, router.forward);
|
||||
useRegisterBlocksuiteEditorCommands(router.back, router.forward);
|
||||
const userInfo = useDeleteCollectionInfo();
|
||||
return (
|
||||
<AppSidebar
|
||||
|
||||
@@ -19,6 +19,7 @@ export const upgradeTips = style({
|
||||
fontStyle: 'normal',
|
||||
fontWeight: '400',
|
||||
lineHeight: '20px',
|
||||
textAlign: 'center',
|
||||
});
|
||||
|
||||
const rotate = keyframes({
|
||||
|
||||
@@ -44,7 +44,7 @@ interface WorkspaceUpgradeProps {
|
||||
export const WorkspaceUpgrade = function WorkspaceUpgrade(
|
||||
props: WorkspaceUpgradeProps
|
||||
) {
|
||||
const [upgradeState, , upgradeWorkspace, newWorkspaceId] =
|
||||
const [upgradeState, error, upgradeWorkspace, newWorkspaceId] =
|
||||
useUpgradeWorkspace(props.migration);
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
@@ -75,7 +75,7 @@ export const WorkspaceUpgrade = function WorkspaceUpgrade(
|
||||
<div className={styles.upgradeBox}>
|
||||
<AffineShapeIcon width={180} height={180} />
|
||||
<p className={styles.upgradeTips}>
|
||||
{t[UPGRADE_TIPS_KEYS[upgradeState]]()}
|
||||
{error ? error.message : t[UPGRADE_TIPS_KEYS[upgradeState]]()}
|
||||
</p>
|
||||
<Button
|
||||
data-testid="upgrade-workspace-button"
|
||||
|
||||
@@ -4,7 +4,6 @@ import { useSWRConfig } from 'swr';
|
||||
export function useMutateCloud() {
|
||||
const { mutate } = useSWRConfig();
|
||||
return useCallback(async () => {
|
||||
// todo: should not mutate all graphql cache
|
||||
return mutate(key => {
|
||||
if (Array.isArray(key)) {
|
||||
return key[0] === 'cloud';
|
||||
|
||||
@@ -1,29 +1,25 @@
|
||||
import { toast } from '@affine/component';
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { EdgelessIcon, HistoryIcon, PageIcon } from '@blocksuite/icons';
|
||||
import { EdgelessIcon, PageIcon } from '@blocksuite/icons';
|
||||
import type { Workspace } from '@blocksuite/store';
|
||||
import { usePageMetaHelper } from '@toeverything/hooks/use-block-suite-page-meta';
|
||||
import {
|
||||
PreconditionStrategy,
|
||||
registerAffineCommand,
|
||||
} from '@toeverything/infra/command';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
|
||||
import { pageHistoryModalAtom } from '../../atoms/page-history';
|
||||
import { useCurrentWorkspace } from '../current/use-current-workspace';
|
||||
import { useBlockSuiteMetaHelper } from './use-block-suite-meta-helper';
|
||||
import { useExportPage } from './use-export-page';
|
||||
import { useTrashModalHelper } from './use-trash-modal-helper';
|
||||
|
||||
export function useRegisterBlocksuiteEditorCommands(
|
||||
blockSuiteWorkspace: Workspace,
|
||||
pageId: string,
|
||||
mode: 'page' | 'edgeless'
|
||||
) {
|
||||
const t = useAFFiNEI18N();
|
||||
const [workspace] = useCurrentWorkspace();
|
||||
const blockSuiteWorkspace = workspace.blockSuiteWorkspace;
|
||||
const { getPageMeta } = usePageMetaHelper(blockSuiteWorkspace);
|
||||
const currentPage = blockSuiteWorkspace.getPage(pageId);
|
||||
assertExists(currentPage);
|
||||
@@ -32,15 +28,6 @@ export function useRegisterBlocksuiteEditorCommands(
|
||||
const favorite = pageMeta.favorite ?? false;
|
||||
const trash = pageMeta.trash ?? false;
|
||||
|
||||
const setPageHistoryModalState = useSetAtom(pageHistoryModalAtom);
|
||||
|
||||
const openHistoryModal = useCallback(() => {
|
||||
setPageHistoryModalState(() => ({
|
||||
pageId,
|
||||
open: true,
|
||||
}));
|
||||
}, [pageId, setPageHistoryModalState]);
|
||||
|
||||
const { togglePageMode, toggleFavorite, restoreFromTrash } =
|
||||
useBlockSuiteMetaHelper(blockSuiteWorkspace);
|
||||
const exportHandler = useExportPage(currentPage);
|
||||
@@ -53,14 +40,12 @@ export function useRegisterBlocksuiteEditorCommands(
|
||||
});
|
||||
}, [pageId, pageMeta.title, setTrashModal]);
|
||||
|
||||
const isCloudWorkspace = workspace.flavour === WorkspaceFlavour.AFFINE_CLOUD;
|
||||
|
||||
useEffect(() => {
|
||||
const unsubs: Array<() => void> = [];
|
||||
const preconditionStrategy = () =>
|
||||
PreconditionStrategy.InPaperOrEdgeless && !trash;
|
||||
|
||||
// TODO: add back when edgeless presentation is ready
|
||||
//TODO: add back when edgeless presentation is ready
|
||||
|
||||
// this is pretty hack and easy to break. need a better way to communicate with blocksuite editor
|
||||
// unsubs.push(
|
||||
@@ -204,20 +189,6 @@ export function useRegisterBlocksuiteEditorCommands(
|
||||
})
|
||||
);
|
||||
|
||||
if (runtimeConfig.enablePageHistory && isCloudWorkspace) {
|
||||
unsubs.push(
|
||||
registerAffineCommand({
|
||||
id: `editor:${mode}-page-history`,
|
||||
category: `editor:${mode}`,
|
||||
icon: <HistoryIcon />,
|
||||
label: t['com.affine.cmdk.affine.editor.reveal-page-history-modal'](),
|
||||
run() {
|
||||
openHistoryModal();
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return () => {
|
||||
unsubs.forEach(unsub => unsub());
|
||||
};
|
||||
@@ -227,12 +198,11 @@ export function useRegisterBlocksuiteEditorCommands(
|
||||
onClickDelete,
|
||||
exportHandler,
|
||||
pageId,
|
||||
pageMeta.title,
|
||||
restoreFromTrash,
|
||||
t,
|
||||
toggleFavorite,
|
||||
togglePageMode,
|
||||
trash,
|
||||
isCloudWorkspace,
|
||||
openHistoryModal,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import { serverConfigQuery } from '@affine/graphql';
|
||||
import { useQuery } from '@affine/workspace/affine/gql';
|
||||
import type { BareFetcher, Middleware } from 'swr';
|
||||
|
||||
const wrappedFetcher = (fetcher: BareFetcher<any> | null, ...args: any[]) =>
|
||||
fetcher?.(...args).catch(() => null);
|
||||
|
||||
const errorHandler: Middleware = useSWRNext => (key, fetcher, config) => {
|
||||
return useSWRNext(key, wrappedFetcher.bind(null, fetcher), config);
|
||||
};
|
||||
|
||||
export const useServerFlavor = () => {
|
||||
const { data: config, error } = useQuery(
|
||||
{ query: serverConfigQuery },
|
||||
{
|
||||
use: [errorHandler],
|
||||
revalidateOnFocus: false,
|
||||
revalidateOnMount: false,
|
||||
revalidateIfStale: false,
|
||||
}
|
||||
);
|
||||
|
||||
if (error || !config) {
|
||||
return 'local';
|
||||
}
|
||||
|
||||
return config.serverConfig.flavor;
|
||||
};
|
||||
|
||||
export const useSelfHosted = () => {
|
||||
const serverFlavor = useServerFlavor();
|
||||
|
||||
return ['local', 'selfhosted'].includes(serverFlavor);
|
||||
};
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
} from '@toeverything/infra/command';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export function useRegisterBrowserHistoryCommands(
|
||||
export function useRegisterBlocksuiteEditorCommands(
|
||||
back: () => unknown,
|
||||
forward: () => unknown
|
||||
) {
|
||||
@@ -2,8 +2,6 @@ import { type SubscriptionQuery, subscriptionQuery } from '@affine/graphql';
|
||||
import { useQuery } from '@affine/workspace/affine/gql';
|
||||
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
|
||||
|
||||
import { useSelfHosted } from './affine/use-server-flavor';
|
||||
|
||||
export type Subscription = NonNullable<
|
||||
NonNullable<SubscriptionQuery['currentUser']>['subscription']
|
||||
>;
|
||||
@@ -14,7 +12,6 @@ const selector = (data: SubscriptionQuery) =>
|
||||
data.currentUser?.subscription ?? null;
|
||||
|
||||
export const useUserSubscription = () => {
|
||||
const isSelfHosted = useSelfHosted();
|
||||
const { data, mutate } = useQuery({
|
||||
query: subscriptionQuery,
|
||||
});
|
||||
@@ -39,9 +36,5 @@ export const useUserSubscription = () => {
|
||||
[mutate]
|
||||
);
|
||||
|
||||
if (isSelfHosted) {
|
||||
return [selector(data), () => {}] as const;
|
||||
}
|
||||
|
||||
return [selector(data), set] as const;
|
||||
};
|
||||
|
||||
@@ -24,7 +24,6 @@ import { setPageModeAtom } from '../../atoms';
|
||||
import { collectionsCRUDAtom } from '../../atoms/collections';
|
||||
import { currentModeAtom } from '../../atoms/mode';
|
||||
import { AffineErrorBoundary } from '../../components/affine/affine-error-boundary';
|
||||
import { GlobalPageHistoryModal } from '../../components/affine/page-history-modal';
|
||||
import { WorkspaceHeader } from '../../components/workspace-header';
|
||||
import { useRegisterBlocksuiteEditorCommands } from '../../hooks/affine/use-register-blocksuite-editor-commands';
|
||||
import { useCurrentSyncEngineStatus } from '../../hooks/current/use-current-sync-engine';
|
||||
@@ -42,7 +41,7 @@ const DetailPageImpl = (): ReactElement => {
|
||||
const { setTemporaryFilter } = useCollectionManager(collectionsCRUDAtom);
|
||||
const mode = useAtomValue(currentModeAtom);
|
||||
const setPageMode = useSetAtom(setPageModeAtom);
|
||||
useRegisterBlocksuiteEditorCommands(currentPageId, mode);
|
||||
useRegisterBlocksuiteEditorCommands(blockSuiteWorkspace, currentPageId, mode);
|
||||
const onLoad = useCallback(
|
||||
(page: Page, editor: EditorContainer) => {
|
||||
try {
|
||||
@@ -102,8 +101,6 @@ const DetailPageImpl = (): ReactElement => {
|
||||
currentPageId={currentPageId}
|
||||
onLoadEditor={onLoad}
|
||||
/>
|
||||
|
||||
<GlobalPageHistoryModal />
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -113,7 +110,6 @@ export const DetailPage = (): ReactElement => {
|
||||
const currentSyncEngineStatus = useCurrentSyncEngineStatus();
|
||||
const currentPageId = useAtomValue(currentPageIdAtom);
|
||||
const [page, setPage] = useState<Page | null>(null);
|
||||
const [pageLoaded, setPageLoaded] = useState<boolean>(false);
|
||||
|
||||
// load page by current page id
|
||||
useEffect(() => {
|
||||
@@ -158,30 +154,7 @@ export const DetailPage = (): ReactElement => {
|
||||
return;
|
||||
}, [currentSyncEngineStatus, navigate, page]);
|
||||
|
||||
// wait for page to be loaded
|
||||
useEffect(() => {
|
||||
if (page) {
|
||||
if (!page.isEmpty) {
|
||||
setPageLoaded(true);
|
||||
} else {
|
||||
setPageLoaded(false);
|
||||
// call waitForLoaded to trigger load
|
||||
page
|
||||
.load(() => {})
|
||||
.catch(() => {
|
||||
// do nothing
|
||||
});
|
||||
return page.slots.ready.on(() => {
|
||||
setPageLoaded(true);
|
||||
}).dispose;
|
||||
}
|
||||
} else {
|
||||
setPageLoaded(false);
|
||||
}
|
||||
return;
|
||||
}, [page]);
|
||||
|
||||
if (!currentPageId || !page || !pageLoaded) {
|
||||
if (!currentPageId || !page) {
|
||||
return <PageDetailSkeleton key="current-page-is-null" />;
|
||||
}
|
||||
|
||||
@@ -191,7 +164,8 @@ export const DetailPage = (): ReactElement => {
|
||||
});
|
||||
}
|
||||
|
||||
return <DetailPageImpl />;
|
||||
// Add a key to force rerender when page changed, to avoid some lifecycle issues.
|
||||
return <DetailPageImpl key={currentPageId} />;
|
||||
};
|
||||
|
||||
export const loader: LoaderFunction = async () => {
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
import type { MigrationPoint } from '@toeverything/infra/blocksuite';
|
||||
import {
|
||||
checkWorkspaceCompatibility,
|
||||
fixWorkspaceVersion,
|
||||
guidCompatibilityFix,
|
||||
} from '@toeverything/infra/blocksuite';
|
||||
import { useSetAtom } from 'jotai';
|
||||
@@ -54,6 +55,7 @@ export const loader: LoaderFunction = async args => {
|
||||
workspaceLoaderLogger.info('workspace loaded');
|
||||
|
||||
guidCompatibilityFix(workspace.doc);
|
||||
fixWorkspaceVersion(workspace.doc);
|
||||
return checkWorkspaceCompatibility(workspace);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as Sentry from '@sentry/react';
|
||||
import type { RouteObject } from 'react-router-dom';
|
||||
import { createBrowserRouter } from 'react-router-dom';
|
||||
import { createBrowserRouter as reactRouterCreateBrowserRouter } from 'react-router-dom';
|
||||
|
||||
export const routes = [
|
||||
{
|
||||
@@ -70,6 +71,9 @@ export const routes = [
|
||||
},
|
||||
] satisfies [RouteObject, ...RouteObject[]];
|
||||
|
||||
const createBrowserRouter = Sentry.wrapCreateBrowserRouter(
|
||||
reactRouterCreateBrowserRouter
|
||||
);
|
||||
export const router = createBrowserRouter(routes, {
|
||||
future: {
|
||||
v7_normalizeFormMethod: true,
|
||||
|
||||
@@ -5,7 +5,7 @@ export abstract class RecoverableError extends Error {
|
||||
return this.ttl > 0;
|
||||
}
|
||||
|
||||
abstract retry(): void;
|
||||
abstract retry(): void | Promise<void>;
|
||||
}
|
||||
|
||||
// the first session request failed after login or signup succeed.
|
||||
@@ -24,8 +24,6 @@ export class SessionFetchErrorRightAfterLoginOrSignUp extends RecoverableError {
|
||||
}
|
||||
try {
|
||||
this.onRetry();
|
||||
} catch (e) {
|
||||
console.error('Retry error', e);
|
||||
} finally {
|
||||
this.ttl--;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@affine/electron",
|
||||
"private": true,
|
||||
"version": "0.10.3-canary.2",
|
||||
"version": "0.10.3-beta.5",
|
||||
"author": "toeverything",
|
||||
"repository": {
|
||||
"url": "https://github.com/toeverything/AFFiNE",
|
||||
@@ -32,10 +32,10 @@
|
||||
"@affine/sdk": "workspace:*",
|
||||
"@affine/templates": "workspace:*",
|
||||
"@affine/vue-hello-world-plugin": "workspace:*",
|
||||
"@blocksuite/blocks": "0.0.0-20231124123613-7c06e95d-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20231124123613-7c06e95d-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20231124123613-7c06e95d-nightly",
|
||||
"@blocksuite/store": "0.0.0-20231124123613-7c06e95d-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20231122113751-6bf81eb3-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20231122113751-6bf81eb3-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20231122113751-6bf81eb3-nightly",
|
||||
"@blocksuite/store": "0.0.0-20231122113751-6bf81eb3-nightly",
|
||||
"@electron-forge/cli": "^7.1.0",
|
||||
"@electron-forge/core": "^7.1.0",
|
||||
"@electron-forge/core-utils": "^7.1.0",
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { spawn } from 'node:child_process';
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
import type { ChildProcessWithoutNullStreams } from 'child_process';
|
||||
import type { BuildContext } from 'esbuild';
|
||||
import * as esbuild from 'esbuild';
|
||||
|
||||
import { config, electronDir, rootDir } from './common';
|
||||
import { config, electronDir } from './common';
|
||||
|
||||
// this means we don't spawn electron windows, mainly for testing
|
||||
const watchMode = process.argv.includes('--watch');
|
||||
@@ -30,10 +29,7 @@ function spawnOrReloadElectron() {
|
||||
spawnProcess = null;
|
||||
}
|
||||
|
||||
const ext = process.platform === 'win32' ? '.cmd' : '';
|
||||
const exe = resolve(rootDir, 'node_modules', '.bin', `electron${ext}`);
|
||||
|
||||
spawnProcess = spawn(exe, ['.'], {
|
||||
spawnProcess = spawn('electron', ['.'], {
|
||||
cwd: electronDir,
|
||||
env: process.env,
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@affine/graphql",
|
||||
"version": "0.10.3-canary.2",
|
||||
"version": "0.10.3-beta.5",
|
||||
"description": "Autogenerated GraphQL client for affine.pro",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
query listHistory(
|
||||
$workspaceId: String!
|
||||
$pageDocId: String!
|
||||
$take: Int
|
||||
$before: DateTime
|
||||
) {
|
||||
workspace(id: $workspaceId) {
|
||||
histories(guid: $pageDocId, take: $take, before: $before) {
|
||||
id
|
||||
timestamp
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -362,22 +362,6 @@ query getWorkspaces {
|
||||
}`,
|
||||
};
|
||||
|
||||
export const listHistoryQuery = {
|
||||
id: 'listHistoryQuery' as const,
|
||||
operationName: 'listHistory',
|
||||
definitionName: 'workspace',
|
||||
containsFile: false,
|
||||
query: `
|
||||
query listHistory($workspaceId: String!, $pageDocId: String!, $take: Int, $before: DateTime) {
|
||||
workspace(id: $workspaceId) {
|
||||
histories(guid: $pageDocId, take: $take, before: $before) {
|
||||
id
|
||||
timestamp
|
||||
}
|
||||
}
|
||||
}`,
|
||||
};
|
||||
|
||||
export const getInvoicesCountQuery = {
|
||||
id: 'getInvoicesCountQuery' as const,
|
||||
operationName: 'getInvoicesCount',
|
||||
@@ -461,17 +445,6 @@ mutation publishPage($workspaceId: String!, $pageId: String!, $mode: PublicPageM
|
||||
}`,
|
||||
};
|
||||
|
||||
export const recoverDocMutation = {
|
||||
id: 'recoverDocMutation' as const,
|
||||
operationName: 'recoverDoc',
|
||||
definitionName: 'recoverDoc',
|
||||
containsFile: false,
|
||||
query: `
|
||||
mutation recoverDoc($workspaceId: String!, $docId: String!, $timestamp: DateTime!) {
|
||||
recoverDoc(workspaceId: $workspaceId, guid: $docId, timestamp: $timestamp)
|
||||
}`,
|
||||
};
|
||||
|
||||
export const removeAvatarMutation = {
|
||||
id: 'removeAvatarMutation' as const,
|
||||
operationName: 'removeAvatar',
|
||||
@@ -572,20 +545,6 @@ mutation sendVerifyChangeEmail($token: String!, $email: String!, $callbackUrl: S
|
||||
}`,
|
||||
};
|
||||
|
||||
export const serverConfigQuery = {
|
||||
id: 'serverConfigQuery' as const,
|
||||
operationName: 'serverConfig',
|
||||
definitionName: 'serverConfig',
|
||||
containsFile: false,
|
||||
query: `
|
||||
query serverConfig {
|
||||
serverConfig {
|
||||
version
|
||||
flavor
|
||||
}
|
||||
}`,
|
||||
};
|
||||
|
||||
export const setWorkspacePublicByIdMutation = {
|
||||
id: 'setWorkspacePublicByIdMutation' as const,
|
||||
operationName: 'setWorkspacePublicById',
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
mutation recoverDoc(
|
||||
$workspaceId: String!
|
||||
$docId: String!
|
||||
$timestamp: DateTime!
|
||||
) {
|
||||
recoverDoc(workspaceId: $workspaceId, guid: $docId, timestamp: $timestamp)
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
query serverConfig {
|
||||
serverConfig {
|
||||
version
|
||||
flavor
|
||||
}
|
||||
}
|
||||
@@ -62,7 +62,6 @@ export enum SubscriptionPlan {
|
||||
Enterprise = 'Enterprise',
|
||||
Free = 'Free',
|
||||
Pro = 'Pro',
|
||||
SelfHosted = 'SelfHosted',
|
||||
Team = 'Team',
|
||||
}
|
||||
|
||||
@@ -373,25 +372,6 @@ export type GetWorkspacesQuery = {
|
||||
workspaces: Array<{ __typename?: 'WorkspaceType'; id: string }>;
|
||||
};
|
||||
|
||||
export type ListHistoryQueryVariables = Exact<{
|
||||
workspaceId: Scalars['String']['input'];
|
||||
pageDocId: Scalars['String']['input'];
|
||||
take: InputMaybe<Scalars['Int']['input']>;
|
||||
before: InputMaybe<Scalars['DateTime']['input']>;
|
||||
}>;
|
||||
|
||||
export type ListHistoryQuery = {
|
||||
__typename?: 'Query';
|
||||
workspace: {
|
||||
__typename?: 'WorkspaceType';
|
||||
histories: Array<{
|
||||
__typename?: 'DocHistoryType';
|
||||
id: string;
|
||||
timestamp: string;
|
||||
}>;
|
||||
};
|
||||
};
|
||||
|
||||
export type GetInvoicesCountQueryVariables = Exact<{ [key: string]: never }>;
|
||||
|
||||
export type GetInvoicesCountQuery = {
|
||||
@@ -464,17 +444,6 @@ export type PublishPageMutation = {
|
||||
};
|
||||
};
|
||||
|
||||
export type RecoverDocMutationVariables = Exact<{
|
||||
workspaceId: Scalars['String']['input'];
|
||||
docId: Scalars['String']['input'];
|
||||
timestamp: Scalars['DateTime']['input'];
|
||||
}>;
|
||||
|
||||
export type RecoverDocMutation = {
|
||||
__typename?: 'Mutation';
|
||||
recoverDoc: string;
|
||||
};
|
||||
|
||||
export type RemoveAvatarMutationVariables = Exact<{ [key: string]: never }>;
|
||||
|
||||
export type RemoveAvatarMutation = {
|
||||
@@ -564,17 +533,6 @@ export type SendVerifyChangeEmailMutation = {
|
||||
sendVerifyChangeEmail: boolean;
|
||||
};
|
||||
|
||||
export type ServerConfigQueryVariables = Exact<{ [key: string]: never }>;
|
||||
|
||||
export type ServerConfigQuery = {
|
||||
__typename?: 'Query';
|
||||
serverConfig: {
|
||||
__typename?: 'ServerConfigType';
|
||||
version: string;
|
||||
flavor: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type SetWorkspacePublicByIdMutationVariables = Exact<{
|
||||
id: Scalars['ID']['input'];
|
||||
public: Scalars['Boolean']['input'];
|
||||
@@ -759,11 +717,6 @@ export type Queries =
|
||||
variables: GetWorkspacesQueryVariables;
|
||||
response: GetWorkspacesQuery;
|
||||
}
|
||||
| {
|
||||
name: 'listHistoryQuery';
|
||||
variables: ListHistoryQueryVariables;
|
||||
response: ListHistoryQuery;
|
||||
}
|
||||
| {
|
||||
name: 'getInvoicesCountQuery';
|
||||
variables: GetInvoicesCountQueryVariables;
|
||||
@@ -779,11 +732,6 @@ export type Queries =
|
||||
variables: PricesQueryVariables;
|
||||
response: PricesQuery;
|
||||
}
|
||||
| {
|
||||
name: 'serverConfigQuery';
|
||||
variables: ServerConfigQueryVariables;
|
||||
response: ServerConfigQuery;
|
||||
}
|
||||
| {
|
||||
name: 'subscriptionQuery';
|
||||
variables: SubscriptionQueryVariables;
|
||||
@@ -851,11 +799,6 @@ export type Mutations =
|
||||
variables: PublishPageMutationVariables;
|
||||
response: PublishPageMutation;
|
||||
}
|
||||
| {
|
||||
name: 'recoverDocMutation';
|
||||
variables: RecoverDocMutationVariables;
|
||||
response: RecoverDocMutation;
|
||||
}
|
||||
| {
|
||||
name: 'removeAvatarMutation';
|
||||
variables: RemoveAvatarMutationVariables;
|
||||
|
||||
@@ -18,12 +18,12 @@
|
||||
"devDependencies": {
|
||||
"@affine/debug": "workspace:*",
|
||||
"@affine/env": "workspace:*",
|
||||
"@blocksuite/block-std": "0.0.0-20231124123613-7c06e95d-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20231124123613-7c06e95d-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20231124123613-7c06e95d-nightly",
|
||||
"@blocksuite/global": "0.0.0-20231124123613-7c06e95d-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20231124123613-7c06e95d-nightly",
|
||||
"@blocksuite/store": "0.0.0-20231124123613-7c06e95d-nightly",
|
||||
"@blocksuite/block-std": "0.0.0-20231122113751-6bf81eb3-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20231122113751-6bf81eb3-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20231122113751-6bf81eb3-nightly",
|
||||
"@blocksuite/global": "0.0.0-20231122113751-6bf81eb3-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20231122113751-6bf81eb3-nightly",
|
||||
"@blocksuite/store": "0.0.0-20231122113751-6bf81eb3-nightly",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@types/image-blob-reduce": "^4.1.3",
|
||||
"@types/lodash.debounce": "^4.0.7",
|
||||
@@ -62,5 +62,5 @@
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"version": "0.10.3-canary.2"
|
||||
"version": "0.10.3-beta.5"
|
||||
}
|
||||
|
||||
@@ -36,5 +36,5 @@
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.2.2"
|
||||
},
|
||||
"version": "0.10.3-canary.2"
|
||||
"version": "0.10.3-beta.5"
|
||||
}
|
||||
|
||||
@@ -518,7 +518,6 @@
|
||||
"com.affine.cmdk.affine.switch-state.on": "ON",
|
||||
"com.affine.cmdk.affine.translucent-ui-on-the-sidebar.to": "Change Translucent UI On The Sidebar to",
|
||||
"com.affine.cmdk.affine.whats-new": "What's New",
|
||||
"com.affine.cmdk.affine.editor.reveal-page-history-modal": "Reveal Page History Modal",
|
||||
"com.affine.cmdk.placeholder": "Type a command or search anything...",
|
||||
"com.affine.collection-bar.action.tooltip.delete": "Delete",
|
||||
"com.affine.collection-bar.action.tooltip.edit": "Edit",
|
||||
@@ -677,7 +676,7 @@
|
||||
"com.affine.new_edgeless": "New Edgeless",
|
||||
"com.affine.new_import": "Import",
|
||||
"com.affine.notFoundPage.backButton": "Back Home",
|
||||
"com.affine.notFoundPage.title": "404 - Page Not Found",
|
||||
"com.affine.notFoundPage.title": "Page Not Found",
|
||||
"com.affine.onboarding.title1": "Hyper merged whiteboard and docs",
|
||||
"com.affine.onboarding.title2": "Intuitive & robust block-based editing",
|
||||
"com.affine.onboarding.videoDescription1": "Easily switch between Page mode for structured document creation and Whiteboard mode for the freeform visual expression of creative ideas.",
|
||||
@@ -951,6 +950,13 @@
|
||||
"com.affine.workspaceType.offline": "Available Offline",
|
||||
"com.affine.write_with_a_blank_page": "Write with a blank page",
|
||||
"com.affine.yesterday": "Yesterday",
|
||||
"com.affine.error.retry": "Refresh",
|
||||
"com.affine.error.refetch": "Refetch",
|
||||
"com.affine.error.reload": "Reload",
|
||||
"com.affine.error.page-not-found.title": "Refresh",
|
||||
"com.affine.error.unexpected-error.title": "Something is wrong...",
|
||||
"com.affine.error.contact.description": "If you are still experiencing this issue, please <1>contact us through the community.</1>",
|
||||
"com.affine.error.no-page-root.title": "Page content is missing",
|
||||
"core": "core",
|
||||
"dark": "Dark",
|
||||
"emptyAllPages": "Click on the <1>$t(New Page)</1> button to create your first page.",
|
||||
@@ -973,13 +979,5 @@
|
||||
"system": "System",
|
||||
"upgradeBrowser": "Please upgrade to the latest version of Chrome for the best experience.",
|
||||
"will be moved to Trash": "{{title}} will be moved to Trash",
|
||||
"will delete member": "will delete member",
|
||||
"com.affine.history.version-history": "Version History",
|
||||
"com.affine.history.view-history-version": "View History Version",
|
||||
"com.affine.history.restore-current-version": "Restore current version",
|
||||
"com.affine.history.back-to-page": "Back to page",
|
||||
"com.affine.history.empty-prompt.title": "Empty",
|
||||
"com.affine.history.empty-prompt.description": "This document is such a spring chicken, it hasn't sprouted a single historical sprig yet!",
|
||||
"com.affine.history.confirm-restore-modal.restore": "Restore",
|
||||
"com.affine.history.confirm-restore-modal.hint": "You are about to restore the current version of the page to the latest version available. This action will overwrite any changes made prior to the latest version."
|
||||
"will delete member": "will delete member"
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ rand = "0.8"
|
||||
serde = "1"
|
||||
serde_json = "1"
|
||||
sha3 = "0.10"
|
||||
sqlx = { version = "0.7.3", default-features = false, features = [
|
||||
sqlx = { version = "0.7.2", default-features = false, features = [
|
||||
"sqlite",
|
||||
"migrate",
|
||||
"runtime-tokio",
|
||||
@@ -44,7 +44,7 @@ uuid = { version = "1", default-features = false, features = [
|
||||
affine_schema = { path = "./schema" }
|
||||
dotenv = "0.15"
|
||||
napi-build = "2"
|
||||
sqlx = { version = "0.7.3", default-features = false, features = [
|
||||
sqlx = { version = "0.7.2", default-features = false, features = [
|
||||
"sqlite",
|
||||
"runtime-tokio",
|
||||
"tls-rustls",
|
||||
|
||||
@@ -58,5 +58,5 @@
|
||||
"test": "ava",
|
||||
"version": "napi version"
|
||||
},
|
||||
"version": "0.10.3-canary.2"
|
||||
"version": "0.10.3-beta.5"
|
||||
}
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"./v1/*.json": "./v1/*.json",
|
||||
"./preloading.json": "./preloading.json"
|
||||
},
|
||||
"version": "0.10.3-canary.2"
|
||||
"version": "0.10.3-beta.5"
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"exports": {
|
||||
"./atom": "./src/atom.ts",
|
||||
"./manager": "./src/manager/index.ts",
|
||||
"./blob": "./src/blob/index.ts",
|
||||
"./local/crud": "./src/local/crud.ts",
|
||||
"./affine/*": "./src/affine/*.ts",
|
||||
"./providers": "./src/providers/index.ts"
|
||||
@@ -49,5 +48,5 @@
|
||||
"vitest": "0.34.6",
|
||||
"ws": "^8.14.2"
|
||||
},
|
||||
"version": "0.10.3-canary.2"
|
||||
"version": "0.10.3-beta.5"
|
||||
}
|
||||
|
||||
@@ -5,15 +5,12 @@ import type {
|
||||
QueryOptions,
|
||||
QueryResponse,
|
||||
QueryVariables,
|
||||
RecursiveMaybeFields,
|
||||
} from '@affine/graphql';
|
||||
import { gqlFetcherFactory } from '@affine/graphql';
|
||||
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
|
||||
import type { GraphQLError } from 'graphql';
|
||||
import { useMemo } from 'react';
|
||||
import type { Key, SWRConfiguration, SWRResponse } from 'swr';
|
||||
import useSWR, { useSWRConfig } from 'swr';
|
||||
import useSWRInfinite from 'swr/infinite';
|
||||
import useSWR from 'swr';
|
||||
import type {
|
||||
SWRMutationConfiguration,
|
||||
SWRMutationResponse,
|
||||
@@ -89,63 +86,6 @@ export function useQuery<Query extends GraphQLQuery>(
|
||||
);
|
||||
}
|
||||
|
||||
export function useQueryInfinite<Query extends GraphQLQuery>(
|
||||
options: Omit<QueryOptions<Query>, 'variables'> & {
|
||||
getVariables: (
|
||||
pageIndex: number,
|
||||
previousPageData: QueryResponse<Query>
|
||||
) => QueryOptions<Query>['variables'];
|
||||
},
|
||||
config?: Omit<
|
||||
SWRConfiguration<
|
||||
QueryResponse<Query>,
|
||||
GraphQLError | GraphQLError[],
|
||||
typeof fetcher<Query>
|
||||
>,
|
||||
'fetcher'
|
||||
>
|
||||
) {
|
||||
const configWithSuspense: SWRConfiguration = useMemo(
|
||||
() => ({
|
||||
suspense: true,
|
||||
...config,
|
||||
}),
|
||||
[config]
|
||||
);
|
||||
|
||||
const { data, setSize, size, error } = useSWRInfinite<
|
||||
QueryResponse<Query>,
|
||||
GraphQLError | GraphQLError[]
|
||||
>(
|
||||
(pageIndex: number, previousPageData: QueryResponse<Query>) => [
|
||||
'cloud',
|
||||
options.query.id,
|
||||
options.getVariables(pageIndex, previousPageData),
|
||||
],
|
||||
async ([_, __, variables]) => {
|
||||
const params = { ...options, variables } as QueryOptions<Query>;
|
||||
return fetcher(params);
|
||||
},
|
||||
configWithSuspense
|
||||
);
|
||||
|
||||
const loadingMore = size > 0 && data && !data[size - 1];
|
||||
|
||||
// todo: find a generic way to know whether or not there are more items to load
|
||||
const loadMore = useAsyncCallback(async () => {
|
||||
if (loadingMore) {
|
||||
return;
|
||||
}
|
||||
await setSize(size => size + 1);
|
||||
}, [loadingMore, setSize]);
|
||||
return {
|
||||
data,
|
||||
error,
|
||||
loadingMore,
|
||||
loadMore,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* A useSWRMutation wrapper for sending graphql mutations
|
||||
*
|
||||
@@ -198,32 +138,3 @@ export function useMutation(
|
||||
}
|
||||
|
||||
export const gql = fetcher;
|
||||
|
||||
// use this to revalidate all queries that match the filter
|
||||
export const useMutateQueryResource = () => {
|
||||
const { mutate } = useSWRConfig();
|
||||
const revalidateResource = useMemo(
|
||||
() =>
|
||||
<Q extends GraphQLQuery>(
|
||||
query: Q,
|
||||
varsFilter: (
|
||||
vars: RecursiveMaybeFields<QueryVariables<Q>>
|
||||
) => boolean = _vars => true
|
||||
) => {
|
||||
return mutate(key => {
|
||||
const res =
|
||||
Array.isArray(key) &&
|
||||
key[0] === 'cloud' &&
|
||||
key[1] === query.id &&
|
||||
varsFilter(key[2]);
|
||||
if (res) {
|
||||
console.debug('revalidate resource', key);
|
||||
}
|
||||
return res;
|
||||
});
|
||||
},
|
||||
[mutate]
|
||||
);
|
||||
|
||||
return revalidateResource;
|
||||
};
|
||||
|
||||
@@ -24,13 +24,13 @@ export function createAffineAwarenessProvider(
|
||||
const socket = getIoManager().socket('/');
|
||||
|
||||
const awarenessBroadcast = ({
|
||||
workspaceId,
|
||||
workspaceId: remoteWorkspaceId,
|
||||
awarenessUpdate,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
awarenessUpdate: string;
|
||||
}) => {
|
||||
if (workspaceId !== workspaceId) {
|
||||
if (remoteWorkspaceId !== workspaceId) {
|
||||
return;
|
||||
}
|
||||
applyAwarenessUpdate(
|
||||
|
||||
@@ -38,5 +38,5 @@
|
||||
"react": "*",
|
||||
"react-dom": "*"
|
||||
},
|
||||
"version": "0.10.3-canary.2"
|
||||
"version": "0.10.3-beta.5"
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user