Compare commits

..

163 Commits

Author SHA1 Message Date
Alex Yang
c59d1e67ab v0.8.0-canary.24 2023-08-16 17:42:16 -05:00
danielchim
9ab9c0c70d feat: new workspace switch dropdown design (#3700)
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-08-16 17:18:43 -05:00
Alex Yang
f369ca39f7 fix(core): correct the suspense behavior (#3789) 2023-08-16 16:42:35 -05:00
Rohit Yadav
804b8f38b8 fix(core): unused z-index (#3781)
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-08-16 16:25:51 -05:00
Alex Yang
dd23917e3e docs: rename to upstreams section 2023-08-16 16:09:29 -05:00
Alex Yang
b604d9b47e docs: update README.md 2023-08-16 16:06:37 -05:00
Alex Yang
1e5a4a6849 feat(storybook): improve code (#3786) 2023-08-16 15:07:55 -05:00
LongYinan
64656c3c98 fix(native): static link msvc runtime on Windows (#3773)
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-08-16 14:55:37 -05:00
Alex Yang
61ba85e1f3 chore: bump version (#3784) 2023-08-16 14:53:33 -05:00
Peng Xiao
61ffc4220c fix: ignore some files to be bundled (#3770) 2023-08-16 18:29:47 +00:00
danielchim
866408015e fix: tooltip arrow (#3769)
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-08-16 17:48:13 +00:00
Alex Yang
651e815b42 chore: bump version (#3771)
Co-authored-by: Mirone <Saul-Mirone@outlook.com>
2023-08-16 12:02:07 -05:00
Alex Yang
645a300112 ci: add environment 2023-08-16 11:31:13 -05:00
Peng Xiao
e0a3c7f2bc fix: disable secondary db test (#3774) 2023-08-17 00:11:04 +08:00
Alex Yang
3dbefda6ed feat(storybook): import plugins (#3768) 2023-08-16 03:01:14 -05:00
Alex Yang
73eddc2386 v0.8.0-canary.23 2023-08-16 02:43:10 -05:00
Alex Yang
6f9dfcc3c1 feat: add outline plugin (#3624)
Co-authored-by: codert <codert.sn@gmail.com>
2023-08-16 02:34:26 -05:00
Alex Yang
93d352f3d8 ci: checkout pull request ref 2023-08-16 00:54:57 -05:00
Alex Yang
7546b080ea ci: add name 2023-08-16 00:38:45 -05:00
Alex Yang
6988b6f034 ci: publish storybook on push to master 2023-08-16 00:37:53 -05:00
Alex Yang
de2cb1a3bc ci: add publish-storybook.yml 2023-08-16 00:36:24 -05:00
KaranPant
08f01ea1b3 fix: add min height to footer (#3717) 2023-08-16 03:03:30 +00:00
Alex Yang
0df30e43c6 feat(storybook): add not found page (#3767) 2023-08-15 16:58:14 -05:00
Alex Yang
67b33d9b8f feat(storybook): preview app (#3765) 2023-08-15 15:34:02 -05:00
Alex Yang
42dfd0a4bb fix(core): default page mode (#3745) 2023-08-15 14:49:53 -05:00
Alex Yang
25052220a4 feat: add chromatic (#3764) 2023-08-15 14:32:24 -05:00
Qi
48e96cd399 fix: wrong style of cancel button in create workspace modal (#3761) 2023-08-15 12:44:03 -05:00
JimmFly
ca016f1dd1 chore: adjust preloading page (#3753) 2023-08-15 05:53:53 +00:00
Qi
a4fe7dd119 fix: ui issues (#3755) 2023-08-15 05:53:19 +00:00
JimmFly
8d2df468ee chore: update en.json (#3754) 2023-08-15 05:08:48 +00:00
Peng Xiao
2830cb19fe fix: show recursive items (#3750) 2023-08-15 04:01:46 +00:00
Alex Yang
8487b2c4af fix(electron): type on handlers (#3747) 2023-08-15 01:06:25 +00:00
Alex Yang
720a90fe93 v0.8.0-canary.22 2023-08-14 20:37:04 -04:00
Alex Yang
623fa87d5c fix(core): first page (#3744) 2023-08-14 23:16:46 +00:00
Alex Yang
4ad50bf8cf docs: update badge in README.md (#3743) 2023-08-14 16:41:37 -04:00
Alex Yang
efd02a015a chore: bump version (#3742) 2023-08-14 19:23:19 +00:00
Qi
75a2bbdfac fix: ui issues (#3738)
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-08-14 18:48:18 +00:00
Quincy Qiu
52102ee792 fix(plugin): allow multiple loads assets (#3741) 2023-08-14 18:44:52 +00:00
Qi
58dae07b5f fix: error style of empty page (#3733) 2023-08-14 18:28:39 +00:00
Qi
d0e33c748b fix: shaky header (#3727) 2023-08-14 18:03:14 +00:00
Peng Xiao
08da58aa1e fix: allow multiple versions to be installed on windows (#3740) 2023-08-14 15:40:00 +00:00
JimmFly
1072db632e chore: adjust translation (#3734) 2023-08-14 14:34:42 +00:00
Alex Yang
f33fb98912 v0.8.0-canary.21 2023-08-13 22:06:36 -04:00
JimmFly
4d254f3967 fix: get back the missing scrollbar (#3718) 2023-08-13 13:36:29 +00:00
Alex Yang
8833584756 v0.8.0-canary.20 2023-08-11 21:26:29 -04:00
Alex Yang
582059f6d6 fix(electron): download tip (#3711) 2023-08-12 01:11:19 +00:00
Alex Yang
1484818974 v0.8.0-canary.19 2023-08-11 16:42:24 -04:00
Alex Yang
1d41687786 chore: bump version (#3710) 2023-08-11 16:42:00 -04:00
Qi
401fb48b86 feat: refator header (#3685)
Co-authored-by: JimmFly <yangjinfei001@gmail.com>
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-08-11 16:27:24 -04:00
Alex Yang
91619b87db chore: bump version (#3706) 2023-08-11 15:36:10 -04:00
Alex Yang
844e73ca29 docs: remove .all-contributorsrc (#3704) 2023-08-11 14:25:03 -04:00
fourdim
ee91964998 feat: add Intl.Segmenter polyfill (#3688) 2023-08-11 17:14:09 +00:00
Peng Xiao
1eaf228a30 fix: page references in list/database (#3702) 2023-08-11 16:20:33 +00:00
Alex Yang
9639143df4 chore: prohibit using mergeUpdates (#3701) 2023-08-11 15:55:17 +00:00
Garfield Lee
e9f4912665 refactor: remove React.FC (#3694) 2023-08-11 14:58:44 +00:00
Garfield Lee
ce21ea78eb fix: remove invisible unicode for dynamicImportKey (#3695) 2023-08-11 14:58:15 +00:00
xiaodong zuo
13ac9d18af fix: update blocksuite version (#3693) 2023-08-11 14:24:42 +00:00
JimmFly
1a8f849693 feat: add duplicate feature (#3675)
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-08-11 13:58:59 +00:00
JimmFly
f0cbbc3a84 fix: test case (#3686) 2023-08-11 02:59:06 -04:00
Alex Yang
4834f99da9 chore: bump version (#3677) 2023-08-11 02:05:49 +00:00
dependabot[bot]
394469a807 chore: bump ses from 0.18.5 to 0.18.7 (#3630)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-08-10 22:02:21 -04:00
Alex Yang
b7be91e04d v0.8.0-canary.18 2023-08-10 21:25:51 -04:00
Alex Yang
278ffa3372 fix: upload blobs (#3676) 2023-08-10 21:25:22 -04:00
Alex Yang
601cbd83ff v0.8.0-canary.17 2023-08-10 15:59:59 -04:00
Alex Yang
43b35a77bb feat: new preload pages (#3674) 2023-08-10 15:56:16 -04:00
Mirone
f06efd4d02 chore: remove dependency of blocksuite/blocks/std (#3667) 2023-08-10 16:36:28 +00:00
dependabot[bot]
5887071319 chore: bump @storybook/test-runner from 0.11.0 to 0.13.0 (#3673)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-08-10 15:45:41 +00:00
dependabot[bot]
fa111db91b chore: bump lib0 from 0.2.78 to 0.2.80 (#3670)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-10 15:35:15 +00:00
Alex Yang
b6cdabff36 chore: bump version (#3672) 2023-08-10 15:21:11 +00:00
dependabot[bot]
af314dabfb chore: bump @clack/core from 0.3.2 to 0.3.3 (#3671)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-08-10 15:05:32 +00:00
dependabot[bot]
11f6273a3a chore: bump nodemon from 2.0.22 to 3.0.1 (#3657)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-08-10 14:51:20 +00:00
dependabot[bot]
4b51eb7e06 chore: bump eslint-config-prettier from 8.8.0 to 9.0.0 (#3655)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-08-10 14:06:46 +00:00
Elisha Frain
671129bc32 chore: make dev:electron work (#3644)
Co-authored-by: Peng Xiao <pengxiao@outlook.com>
2023-08-10 13:58:46 +00:00
Garfield Lee
358c3a5bb2 feat: add no match route (#3659) 2023-08-10 13:43:51 +00:00
Alex Yang
7c4b8866d3 ci: dependabot.yml update weekly 2023-08-10 09:52:11 -04:00
Alex Yang
dafd5619e6 feat: support get datasource status (#3645) 2023-08-10 05:05:34 +00:00
Alex Yang
05144abd6a v0.8.0-canary.16 2023-08-10 00:34:30 -04:00
Mirone
629a3d11c6 chore: bump blocksuite version (#3654) 2023-08-10 00:19:24 -04:00
Alex Yang
bbd49252cf v0.8.0-canary.15 2023-08-09 17:29:27 -04:00
dependabot[bot]
7c3a7513e6 chore: bump storybook-dark-mode from 3.0.0 to 3.0.1 (#3632)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-09 12:37:40 +00:00
JimmFly
e180398637 chore: bump icon version and change plugins icon (#3640) 2023-08-09 11:41:25 +00:00
Garfield Lee
cbd4420fe9 fix: help island icons height (#3639) 2023-08-09 11:40:59 +00:00
dependabot[bot]
f7408dede9 chore: bump @vitejs/plugin-react from 4.0.3 to 4.0.4 (#3631)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-09 03:53:19 +00:00
Alex Yang
c324ccc12a chore: bump version (#3634) 2023-08-09 00:13:48 -04:00
Alex Yang
84bbc2d7ce chore: bump version (#3627) 2023-08-09 03:04:42 +00:00
dependabot[bot]
e22d6924cd chore: bump vite from 4.4.7 to 4.4.9 (#3607)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-08-09 01:10:54 +00:00
Alex Yang
4472a2a0b0 fix: page validation logic (#3626) 2023-08-09 00:37:53 +00:00
Alex Yang
4d6e28c725 v0.8.0-canary.14 2023-08-08 17:01:22 -04:00
Alex Yang
881424d03a chore: bump version (#3623) 2023-08-08 16:57:22 -04:00
JimmFly
5842619206 refactor: optimize the use of notification center (#3621) 2023-08-08 19:00:44 +00:00
Prajna
73272e266b fix: collection-list pin icon not changed (#3625) 2023-08-08 17:54:20 +00:00
JimmFly
4e84b9a121 refactor: header options menu (#3615) 2023-08-08 17:14:24 +00:00
Camol
7d16a8348c chore: add @types/addfine__env to the devDep (#3616) 2023-08-08 15:46:23 +00:00
LIUCHAO
4d1692db7b docs: fix typo (#3618) 2023-08-08 03:25:56 -04:00
Garfield Lee
3009d729fa refactor: remove React.FC for affine core (#3617) 2023-08-08 06:54:31 +00:00
dependabot[bot]
ceeabfeb00 chore: bump eslint and @types/eslint (#3611)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-08 05:35:10 +00:00
dependabot[bot]
bfa5180c1a chore: bump marked from 5.1.2 to 7.0.1 (#3613)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-08 05:12:52 +00:00
Qi
6efe29f7ef feat: replace button from @toeverything/components (#3608) 2023-08-08 04:38:02 +00:00
fourdim
7826ecfa58 fix: cache key on blocksuite local debug (#3610) 2023-08-08 04:37:49 +00:00
dependabot[bot]
d615ac23ea chore: bump jest-mock from 29.6.1 to 29.6.2 (#3612)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-08 04:36:48 +00:00
Alex Yang
4a0089dbfc v0.8.0-canary.13 2023-08-07 23:02:09 -04:00
Alex Yang
d101db2a39 chore: bump version (#3606) 2023-08-07 23:01:47 -04:00
Alex Yang
b147624f1c feat: support enable/disable plugin (#3605) 2023-08-08 00:58:31 +00:00
fourdim
ec05bd3f53 feat: add local blocksuite debug support (#3591)
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-08-07 17:44:31 +00:00
dependabot[bot]
952283fe16 chore: bump eslint-plugin-sonarjs from 0.19.0 to 0.20.0 (#3597)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-07 17:32:48 +00:00
Garfield Lee
47c0b1f680 fix: correct contact with us icon alignment (#3600) 2023-08-07 17:30:48 +00:00
dependabot[bot]
cdef5f5826 chore: bump prisma from 5.0.0 to 5.1.1 (#3596)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-07 17:11:00 +00:00
Alex Yang
6d01495bc7 feat(infra): lazy load plugin modules (#3604) 2023-08-07 17:06:44 +00:00
JimmFly
d9f323874d style: adjust page list header style (#3599) 2023-08-07 16:03:54 +00:00
Peng Xiao
45ffad45b0 fix: eslint stack overflow issue (#3601) 2023-08-07 07:00:57 +00:00
dependabot[bot]
dd4612bcef chore: bump vite-plugin-dts from 3.3.1 to 3.5.1 (#3598)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-07 04:20:55 +00:00
Alex Yang
a0ce75b1ae v0.8.0-canary.12 2023-08-06 20:55:41 -04:00
Alex Yang
031eb35b86 chore: bump version (#3594) 2023-08-06 20:54:28 -04:00
Pratik Kumar
5bc923d58e feat(cli): build infra and plugins before dev-core (#3470) 2023-08-06 17:02:30 +00:00
Alex Yang
a74a549b8c docs: update README.md 2023-08-06 12:47:24 -04:00
Alex Yang
50df137c7f docs: update README.md 2023-08-06 01:10:56 -04:00
Alex Yang
7bf77b566d feat(plugin): add vue example (#3592) 2023-08-05 23:59:14 -04:00
Zeb Wu
48350d7654 fix: disable button when value is empty (#3590)
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-08-05 11:27:14 +00:00
dependabot[bot]
72c4aa0078 chore: bump @nestjs/common from 10.1.2 to 10.1.3 (#3586)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-08-05 04:55:01 +00:00
dependabot[bot]
c3c40225d6 chore: bump @typescript-eslint/parser from 6.2.0 to 6.2.1 (#3589)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-08-05 04:50:05 +00:00
dependabot[bot]
b4acbb90b7 chore: bump electron from 25.3.2 to 25.4.0 (#3585)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-05 03:20:08 +00:00
dependabot[bot]
d1b2018700 chore: bump @perfsee/sdk from 1.8.3 to 1.8.5 (#3582)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-05 01:43:21 +00:00
dependabot[bot]
d087bad9de chore: bump happy-dom from 10.5.2 to 10.8.0 (#3584)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-05 01:19:25 +00:00
dependabot[bot]
2da6c4a0ec chore: bump jotai-devtools from 0.6.0 to 0.6.1 (#3583)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-05 01:18:45 +00:00
Alex Yang
ede6637a14 ci: fix dependabot.yml 2023-08-04 17:54:02 -07:00
Alex Yang
36bd0b02d0 v0.8.0-canary.11 2023-08-04 17:34:31 -07:00
JimmFly
3a92c4f798 feat: modify sidebar floating logic and header responsive style (#3550) 2023-08-05 00:15:17 +00:00
Alex Yang
97de0ef5b4 build: use tsconfig bundler (#3581) 2023-08-05 00:00:36 +00:00
Alex Yang
bbf5f0efe0 chore: bump version (#3567) 2023-08-04 23:55:28 +00:00
Alex Yang
ea76936508 feat: update 404 page (#3580)
Co-authored-by: QiShaoXuan <qishaoxuan777@gmail.com>
2023-08-04 23:11:30 +00:00
Alex Yang
f076cb0ead docs: fix all-contributors count 2023-08-04 13:48:43 -07:00
fossabot
0f230253a8 docs: add license scan report and status (#3576)
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-08-04 13:44:15 -07:00
Alex Yang
13d2dde501 fix: only run migration in local workspace (#3570) 2023-08-04 16:09:55 +00:00
Garfield Lee
65fc0ed59c refactor: remove React.FC for component package (#3575) 2023-08-04 15:00:28 +00:00
Chi Zhang
7ec4b8fb8c Update README.md (#3578) 2023-08-04 14:46:10 +00:00
Peng Xiao
6415f0093b fix: optimize types for infra/electron (#3574) 2023-08-04 09:23:56 +00:00
LongYinan
5795020403 style: add no-misused-promises rule (#3547)
Co-authored-by: Peng Xiao <pengxiao@outlook.com>
2023-08-04 08:08:10 +00:00
Alex Yang
f8e49ee3be v0.8.0-canary.10 2023-08-03 20:08:52 -07:00
Pratik Kumar
1012807c65 fix: added scrollbar at the correct position (#3506)
Co-authored-by: JimmFly <yangjinfei001@gmail.com>
2023-08-04 02:50:22 +00:00
Alex Yang
1c7c27712e ci: update cancel.yml 2023-08-03 19:11:32 -07:00
Alex Yang
7f28c78d8c ci: skip build in the non-darwin platform 2023-08-03 18:12:00 -07:00
Alex Yang
4bb874756d refactor: lazy download macos maker (#3564) 2023-08-03 17:58:42 -07:00
Alex Yang
0882d47dc9 v0.8.0-canary.9 2023-08-03 16:23:02 -07:00
Alex Yang
0c16eb1189 build: improve webpack config (#3561) 2023-08-03 23:05:46 +00:00
Alex Yang
f2ac4c7eda fix(core): editor wrapper css (#3563) 2023-08-03 21:40:43 +00:00
Alex Yang
0d531782ca ci: add dependabot.yml (#3562) 2023-08-03 12:24:23 -07:00
Alex Yang
47ff376195 ci: improve download @sentry/cli (#3560) 2023-08-03 17:26:30 +00:00
Alex Yang
33cc9d25a1 fix(core): use download atom (#3558) 2023-08-03 17:13:44 +00:00
Alex Yang
58ceeb9c5f chore: ignore output files (#3557) 2023-08-03 16:24:58 +00:00
Camol
3c00b69805 chore: remove repeated inreferences (#3551) 2023-08-03 16:11:51 +00:00
Chi Zhang
2678ca9330 feat: should hide downloadtip when it had been closed (#3555) 2023-08-03 23:50:08 +08:00
Peng Xiao
6f488d963b fix: a possible double connect issue (#3552) 2023-08-03 13:45:00 +00:00
JimmFly
8face25bdf fix: scrollbar position offset (#3538) 2023-08-03 08:52:04 +00:00
Alex Yang
0e32803247 refactor: merge plugin-infra into infra (#3540) 2023-08-03 08:48:59 +00:00
Pratik Kumar
3282344d4a fix: padding in the Switch button of Page/Edgeless (#3542) 2023-08-03 08:38:00 +00:00
Garfield Lee
78d23d86f5 feat: add tooltips for collection bar action buttons (#3545)
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-08-03 08:26:17 +00:00
Garfield Lee
3a0797955c fix: editor-mode-switch animation should only run once (#3543)
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-08-03 08:24:10 +00:00
Alex Yang
9449e66396 ci: fix upload artifact 2023-08-03 01:11:32 -07:00
Alex Yang
b6200ab56d ci: build storage 2023-08-03 00:32:07 -07:00
Alex Yang
ff23561e21 ci: fix needs 2023-08-03 00:25:31 -07:00
Alex Yang
e718428d50 ci: fix server build (#3541) 2023-08-03 07:10:02 +00:00
Alex Yang
ea34d66e14 feat: add @affine/sdk (#3536) 2023-08-03 04:47:05 +00:00
Alex Yang
d3c719d89a test: add test case for plugin bootstrap (#3529) 2023-08-03 01:48:35 +00:00
448 changed files with 26886 additions and 17178 deletions

View File

@@ -1,641 +0,0 @@
{
"projectName": "AFFiNE",
"projectOwner": "toeverything",
"repoType": "github",
"repoHost": "https://github.com",
"files": [
"README.md"
],
"imageSize": 50,
"commit": false,
"commitConvention": "angular",
"contributorsPerLine": 7,
"badgeTemplate": "\n[all-contributors-badge]: https://img.shields.io/badge/all_contributors-<%= contributors.length %>-orange.svg?style=flat-square\n",
"contributors": [
{
"login": "doodlewind",
"name": "Yifeng Wang",
"avatar_url": "https://avatars.githubusercontent.com/u/7312949?v=4",
"profile": "https://github.com/doodlewind",
"contributions": [
"code",
"doc"
]
},
{
"login": "darkskygit",
"name": "DarkSky",
"avatar_url": "https://avatars.githubusercontent.com/u/25152247?v=4",
"profile": "https://darksky.eu.org/",
"contributions": [
"code",
"doc"
]
},
{
"login": "tzhangchi",
"name": "Chi Zhang",
"avatar_url": "https://avatars.githubusercontent.com/u/5910926?v=4",
"profile": "http://zhangchi.page/",
"contributions": [
"code",
"doc"
]
},
{
"login": "alt1o",
"name": "wang xinglong",
"avatar_url": "https://avatars.githubusercontent.com/u/21084335?v=4",
"profile": "https://github.com/alt1o",
"contributions": [
"code",
"doc"
]
},
{
"login": "Brooooooklyn",
"name": "LongYinan",
"avatar_url": "https://avatars.githubusercontent.com/u/3468483?v=4",
"profile": "https://github.com/Brooooooklyn",
"contributions": [
"code",
"doc"
]
},
{
"login": "hwangdev97",
"name": "Hwang",
"avatar_url": "https://avatars.githubusercontent.com/u/24713927?v=4",
"profile": "https://github.com/hwangdev97",
"contributions": [
"code",
"doc"
]
},
{
"login": "kobeshanks",
"name": "kobeshanks",
"avatar_url": "https://avatars.githubusercontent.com/u/82570088?v=4",
"profile": "https://github.com/kobeshanks",
"contributions": [
"code",
"doc"
]
},
{
"login": "pengx17",
"name": "Peng Xiao",
"avatar_url": "https://avatars.githubusercontent.com/u/584378?v=4",
"profile": "https://pengx17.vercel.app/",
"contributions": [
"code",
"doc"
]
},
{
"login": "Saul-Mirone",
"name": "Mirone",
"avatar_url": "https://avatars.githubusercontent.com/u/10047788?v=4",
"profile": "https://mirone.me/",
"contributions": [
"code",
"doc"
]
},
{
"login": "zqran",
"name": "zqran",
"avatar_url": "https://avatars.githubusercontent.com/u/15389209?v=4",
"profile": "https://github.com/zqran",
"contributions": [
"code",
"doc"
]
},
{
"login": "SuneBear",
"name": "Shule Hsiung",
"avatar_url": "https://avatars.githubusercontent.com/u/7693264?v=4",
"profile": "https://sunebear.com/",
"contributions": [
"code",
"doc"
]
},
{
"login": "fundon",
"name": "Fangdun Tsai",
"avatar_url": "https://avatars.githubusercontent.com/u/27926?v=4",
"profile": "https://fundon.viz.rs/",
"contributions": [
"code",
"doc"
]
},
{
"login": "lawvs",
"name": "Whitewater",
"avatar_url": "https://avatars.githubusercontent.com/u/18554747?v=4",
"profile": "https://lawvs.github.io/profile/",
"contributions": [
"code",
"doc"
]
},
{
"login": "zuoxiaodong0815",
"name": "xiaodong zuo",
"avatar_url": "https://avatars.githubusercontent.com/u/53252747?v=4",
"profile": "https://github.com/zuoxiaodong0815",
"contributions": [
"code",
"doc"
]
},
{
"login": "Himself65",
"name": "Himself65",
"avatar_url": "https://avatars.githubusercontent.com/u/14026360?v=4",
"profile": "https://github.com/Himself65",
"contributions": [
"code",
"doc"
]
},
{
"login": "DiamondThree",
"name": "DiamondThree",
"avatar_url": "https://avatars.githubusercontent.com/u/24630517?v=4",
"profile": "https://github.com/DiamondThree",
"contributions": [
"code",
"doc"
]
},
{
"login": "QiShaoXuan",
"name": "Qi",
"avatar_url": "https://avatars.githubusercontent.com/u/22772830?v=4",
"profile": "https://github.com/QiShaoXuan",
"contributions": [
"code",
"doc"
]
},
{
"login": "colelawrence",
"name": "Cole Lawrence",
"avatar_url": "https://avatars.githubusercontent.com/u/2925395?v=4",
"profile": "https://colelawrence.com/",
"contributions": [
"code",
"doc"
]
},
{
"login": "linonetwo",
"name": "lin onetwo",
"avatar_url": "https://avatars.githubusercontent.com/u/3746270?v=4",
"profile": "https://onetwo.ren/wiki",
"contributions": [
"code",
"doc"
]
},
{
"login": "thorseraq",
"name": "x1a0t",
"avatar_url": "https://avatars.githubusercontent.com/u/20554850?v=4",
"profile": "https://github.com/thorseraq",
"contributions": [
"code",
"doc"
]
},
{
"login": "HeJiachen-PM",
"name": "HeJiachen-PM",
"avatar_url": "https://avatars.githubusercontent.com/u/79301703?v=4",
"profile": "https://github.com/HeJiachen-PM",
"contributions": [
"research",
"doc"
]
},
{
"login": "joebeijing",
"name": "houjoe",
"avatar_url": "https://avatars.githubusercontent.com/u/22443345?v=4",
"profile": "https://www.notion.so/houjoe/Joe-2a85f5be01004cd2b6a5ad26fbb948b1",
"contributions": [
"research",
"doc"
]
},
{
"login": "Yipei-Operation",
"name": "Yipei Wei",
"avatar_url": "https://avatars.githubusercontent.com/u/79373028?v=4",
"profile": "https://github.com/Yipei-Operation",
"contributions": [
"doc"
]
},
{
"login": "VelikaHF",
"name": "Velika",
"avatar_url": "https://avatars.githubusercontent.com/u/121547898?v=4",
"profile": "https://github.com/VelikaHF",
"contributions": [
"design"
]
},
{
"login": "Svaney-ssman",
"name": "Svaney",
"avatar_url": "https://avatars.githubusercontent.com/u/110808979?v=4",
"profile": "https://github.com/Svaney-ssman",
"contributions": [
"design"
]
},
{
"login": "xell",
"name": "Guozhu Liu",
"avatar_url": "https://avatars.githubusercontent.com/u/132558?v=4",
"profile": "http://xell.me/",
"contributions": [
"design"
]
},
{
"login": "fyZheng07",
"name": "fyZheng07",
"avatar_url": "https://avatars.githubusercontent.com/u/63830919?v=4",
"profile": "https://github.com/fyZheng07",
"contributions": [
"eventOrganizing",
"userTesting"
]
},
{
"login": "CJSS",
"name": "CJSS",
"avatar_url": "https://avatars.githubusercontent.com/u/4605025?v=4",
"profile": "https://github.com/CJSS",
"contributions": [
"doc"
]
},
{
"login": "JimmFly",
"name": "JimmFly",
"avatar_url": "https://avatars.githubusercontent.com/u/102217452?v=4",
"profile": "https://github.com/JimmFly",
"contributions": [
"code"
]
},
{
"login": "mitsuhatu",
"name": "mitsuhatu",
"avatar_url": "https://avatars.githubusercontent.com/u/110213079?v=4",
"profile": "https://github.com/mitsuhatu",
"contributions": [
"code",
"doc"
]
},
{
"login": "Austaras",
"name": "Austaras",
"avatar_url": "https://avatars.githubusercontent.com/u/15013925?v=4",
"profile": "https://shockwave.me/",
"contributions": [
"code",
"doc"
]
},
{
"login": "uptonking",
"name": "Jin Yao",
"avatar_url": "https://avatars.githubusercontent.com/u/11391549?v=4",
"profile": "https://github.com/uptonking",
"contributions": [
"code",
"doc"
]
},
{
"login": "CarlosZoft",
"name": "Carlos Rafael ",
"avatar_url": "https://avatars.githubusercontent.com/u/62192072?v=4",
"profile": "https://github.com/CarlosZoft",
"contributions": [
"code"
]
},
{
"login": "caleboleary",
"name": "Caleb OLeary",
"avatar_url": "https://avatars.githubusercontent.com/u/12816579?v=4",
"profile": "https://github.com/caleboleary",
"contributions": [
"code"
]
},
{
"login": "westongraham",
"name": "Weston Graham",
"avatar_url": "https://avatars.githubusercontent.com/u/89493023?v=4",
"profile": "https://github.com/westongraham",
"contributions": [
"doc"
]
},
{
"login": "SaikaSakura",
"name": "MingLIang Wang",
"avatar_url": "https://avatars.githubusercontent.com/u/11530942?v=4",
"profile": "https://github.com/SaikaSakura",
"contributions": [
"code",
"doc"
]
},
{
"login": "fanjing22",
"name": "fanjing22",
"avatar_url": "https://avatars.githubusercontent.com/u/109729699?v=4",
"profile": "https://github.com/fanjing22",
"contributions": [
"design"
]
},
{
"login": "pointmax",
"name": "pointmax",
"avatar_url": "https://avatars.githubusercontent.com/u/49361135?v=4",
"profile": "https://github.com/pointmax",
"contributions": [
"doc",
"code"
]
},
{
"login": "liby",
"name": "Bryan Lee",
"avatar_url": "https://avatars.githubusercontent.com/u/38807139?v=4",
"profile": "https://liby.github.io/notes",
"contributions": [
"code"
]
},
{
"login": "chenmoonmo",
"name": "Simon Li",
"avatar_url": "https://avatars.githubusercontent.com/u/36295999?v=4",
"profile": "https://github.com/chenmoonmo",
"contributions": [
"code"
]
},
{
"login": "githbq",
"name": "Bob Hu",
"avatar_url": "https://avatars.githubusercontent.com/u/10009709?v=4",
"profile": "https://github.com/githbq",
"contributions": [
"code"
]
},
{
"login": "lucky-chap",
"name": "Quavo",
"avatar_url": "https://avatars.githubusercontent.com/u/67266933?v=4",
"profile": "https://quavo.vercel.app/",
"contributions": [
"doc",
"code"
]
},
{
"login": "LuciNyan",
"name": "子瞻 Luci",
"avatar_url": "https://avatars.githubusercontent.com/u/22126563?v=4",
"profile": "https://github.com/LuciNyan",
"contributions": [
"code"
]
},
{
"login": "m1911star",
"name": "Horus",
"avatar_url": "https://avatars.githubusercontent.com/u/4948120?v=4",
"profile": "http://blog.ipili.me/",
"contributions": [
"code",
"platform"
]
},
{
"login": "fanshyiis",
"name": "Super.x",
"avatar_url": "https://avatars.githubusercontent.com/u/15103283?v=4",
"profile": "https://segmentfault.com/u/qzuser_584786517d31a",
"contributions": [
"code"
]
},
{
"login": "wangyu-1999",
"name": "Wang Yu",
"avatar_url": "https://avatars.githubusercontent.com/u/80874770?v=4",
"profile": "https://wangyu-1999.github.io/",
"contributions": [
"code"
]
},
{
"login": "felixonmars",
"name": "Felix Yan",
"avatar_url": "https://avatars.githubusercontent.com/u/1006477?v=4",
"profile": "https://felixc.at/",
"contributions": [
"code"
]
},
{
"login": "lynettelopez",
"name": "Lynette Lopez",
"avatar_url": "https://avatars.githubusercontent.com/u/32908859?v=4",
"profile": "https://github.com/lynettelopez",
"contributions": [
"code"
]
},
{
"login": "Zheaoli",
"name": "Manjusaka",
"avatar_url": "https://avatars.githubusercontent.com/u/7054676?v=4",
"profile": "http://manjusaka.itscoder.com/",
"contributions": [
"code"
]
},
{
"login": "sudongyuer",
"name": "Frozen FIsh",
"avatar_url": "https://avatars.githubusercontent.com/u/76603360?v=4",
"profile": "https://juejin.cn/user/2867982785579102/posts?sort=popular",
"contributions": [
"code"
]
},
{
"login": "MuhammedFaraz",
"name": "Mohammed Faraz",
"avatar_url": "https://avatars.githubusercontent.com/u/92734739?v=4",
"profile": "https://github.com/MuhammedFaraz",
"contributions": [
"doc",
"code"
]
},
{
"login": "Pranav4399",
"name": "Pranav Sriram ",
"avatar_url": "https://avatars.githubusercontent.com/u/28348429?v=4",
"profile": "https://pranavsriram.dev/",
"contributions": [
"code"
]
},
{
"login": "Reson-a",
"name": "Reson-a",
"avatar_url": "https://avatars.githubusercontent.com/u/20806266?v=4",
"profile": "https://github.com/Reson-a",
"contributions": [
"code"
]
},
{
"login": "hezhizhen",
"name": "Zhizhen He",
"avatar_url": "https://avatars.githubusercontent.com/u/7611700?v=4",
"profile": "https://t.me/littlepoint",
"contributions": [
"code"
]
},
{
"login": "AkaraChen",
"name": "AkaraChen",
"avatar_url": "https://avatars.githubusercontent.com/u/85140972?v=4",
"profile": "https://akr.moe/",
"contributions": [
"code"
]
},
{
"login": "suyanhanx",
"name": "Suyan",
"avatar_url": "https://avatars.githubusercontent.com/u/24221472?v=4",
"profile": "https://github.com/suyanhanx",
"contributions": [
"code"
]
},
{
"login": "hehex9",
"name": "hehe",
"avatar_url": "https://avatars.githubusercontent.com/u/9209882?v=4",
"profile": "https://github.com/hehex9",
"contributions": [
"code"
]
},
{
"login": "albertodlc",
"name": "Alberto de la Cruz",
"avatar_url": "https://avatars.githubusercontent.com/u/32411964?v=4",
"profile": "https://github.com/albertodlc",
"contributions": [
"code"
]
},
{
"login": "AlessioGr",
"name": "Alessio Gravili",
"avatar_url": "https://avatars.githubusercontent.com/u/70709113?v=4",
"profile": "https://github.com/AlessioGr",
"contributions": [
"code"
]
},
{
"login": "lzlme",
"name": "Zhilin Liu",
"avatar_url": "https://avatars.githubusercontent.com/u/117659326?v=4",
"profile": "https://github.com/lzlme",
"contributions": [
"code"
]
},
{
"login": "suica",
"name": "Sg",
"avatar_url": "https://avatars.githubusercontent.com/u/8041462?v=4",
"profile": "https://github.com/suica",
"contributions": [
"code"
]
},
{
"login": "sinchang",
"name": "Jeff Wen",
"avatar_url": "https://avatars.githubusercontent.com/u/3297859?v=4",
"profile": "https://sinchang.me/",
"contributions": [
"code"
]
},
{
"login": "m1212e",
"name": "m1212e",
"avatar_url": "https://avatars.githubusercontent.com/u/14091540?v=4",
"profile": "https://m1212e.github.io/portfolio/",
"contributions": [
"code"
]
},
{
"login": "adityash1",
"name": "Aditya Sharma",
"avatar_url": "https://avatars.githubusercontent.com/u/65771169?v=4",
"profile": "https://adityash1.github.io/",
"contributions": [
"code"
]
},
{
"login": "sheben404",
"name": "Kehan Wang",
"avatar_url": "https://avatars.githubusercontent.com/u/61317160?v=4",
"profile": "https://github.com/sheben404",
"contributions": [
"code"
]
},
{
"login": "VictorNanka",
"name": "VictorNanka",
"avatar_url": "https://avatars.githubusercontent.com/u/30154366?v=4",
"profile": "https://github.com/VictorNanka",
"contributions": [
"code"
]
}
]
}

2
.cargo/config.toml Normal file
View File

@@ -0,0 +1,2 @@
[target.x86_64-pc-windows-msvc]
rustflags = ["-C", "target-feature=+crt-static"]

View File

@@ -24,8 +24,9 @@
"debug",
"storage",
"infra",
"plugin-infra",
"plugin-cli"
"plugin-cli",
"sdk",
"plugin"
]
]
}

View File

@@ -9,3 +9,8 @@ lib
.eslintrc.js
packages/i18n/src/i18n-generated.ts
e2e-dist-*
static
web-static
public
packages/sdk/src/*.d.ts
packages/sdk/src/*.js

View File

@@ -22,7 +22,7 @@ const createPattern = packageName => [
allowTypeImports: false,
},
{
group: ['@blocksuite /store'],
group: ['@blocksuite/store'],
message: "Import from '@blocksuite/global/utils'",
importNames: ['assertExists', 'assertEquals'],
},
@@ -31,6 +31,11 @@ const createPattern = packageName => [
message: 'Use `useNavigateHelper` instead',
importNames: ['useNavigate'],
},
{
group: ['yjs'],
message: 'Do not use this API because it has a bug',
importNames: ['mergeUpdates'],
},
];
const allPackages = [
@@ -43,7 +48,8 @@ const allPackages = [
'packages/i18n',
'packages/jotai',
'packages/native',
'packages/plugin-infra',
'packages/infra',
'packages/sdk',
'packages/templates',
'packages/theme',
'packages/workspace',
@@ -154,6 +160,11 @@ const config = {
message: 'Use `useNavigateHelper` instead',
importNames: ['useNavigate'],
},
{
group: ['yjs'],
message: 'Do not use this API because it has a bug',
importNames: ['mergeUpdates'],
},
],
},
],
@@ -213,6 +224,7 @@ const config = {
ignoreIIFE: false,
},
],
'@typescript-eslint/no-misused-promises': ['error'],
},
})),
{
@@ -238,6 +250,7 @@ const config = {
},
],
'@typescript-eslint/no-floating-promises': 0,
'@typescript-eslint/no-misused-promises': 0,
},
},
],

View File

@@ -1,12 +1,16 @@
name: Setup maker
description: 'Setup maker dmg for electron'
runs:
using: 'composite'
steps:
- name: 'Install create-dmg from brew'
- name: 'Install @electron-forge/maker-dmg'
if: runner.os == 'macos'
shell: bash
run: brew install create-dmg
- name: 'Build @affine/makder-dmg'
shell: bash
run: yarn nx build @affine/maker-dmg
working-directory: ./apps/electron
run: yarn add @electron-forge/maker-dmg --dev
env:
HUSKY: '0'
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1'
ELECTRON_SKIP_BINARY_DOWNLOAD: '1'
SENTRYCLI_SKIP_DOWNLOAD: '1'

View File

@@ -17,10 +17,6 @@ inputs:
description: 'Download the Electron binary'
required: false
default: 'true'
npm-token:
description: 'The NPM token to use for private packages.'
required: false
default: ''
hard-link-nm:
description: 'set nmMode to hardlinks-local in .yarnrc.yml'
required: false
@@ -48,20 +44,20 @@ runs:
shell: bash
run: yarn install ${{ inputs.extra-flags }}
env:
NODE_AUTH_TOKEN: ${{ inputs.npm-token }}
HUSKY: '0'
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1'
ELECTRON_SKIP_BINARY_DOWNLOAD: '1'
SENTRYCLI_SKIP_DOWNLOAD: '1'
- name: yarn install (try again)
if: ${{ steps.install.outcome == 'failure' }}
shell: bash
run: yarn install ${{ inputs.extra-flags }}
env:
NODE_AUTH_TOKEN: ${{ inputs.npm-token }}
HUSKY: '0'
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1'
ELECTRON_SKIP_BINARY_DOWNLOAD: '1'
SENTRYCLI_SKIP_DOWNLOAD: '1'
- name: Get installed Playwright version
id: playwright-version

13
.github/actions/setup-sentry/action.yml vendored Normal file
View File

@@ -0,0 +1,13 @@
name: Setup @sentry/cli
description: 'Setup @sentry/cli'
runs:
using: 'composite'
steps:
- name: 'Install @sentry/cli from brew'
if: runner.os == 'macos'
shell: bash
run: brew install getsentry/tools/sentry-cli
- name: 'Install @sentry/cli from npm'
if: runner.os != 'macos'
shell: bash
run: sudo npm install -g @sentry/cli --unsafe-perm

9
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,9 @@
version: 2
updates:
- package-ecosystem: 'npm'
directory: '/'
schedule:
interval: 'weekly'
versioning-strategy: increase
commit-message:
prefix: 'chore'

7
.github/labeler.yml vendored
View File

@@ -22,8 +22,11 @@ plugin:bookmark-block:
plugin:copilot:
- 'plugins/copilot/**/*'
mod:plugin-infra:
- 'packages/plugin-infra/**/*'
mod:infra:
- 'packages/infra/**/*'
mod:sdk:
- 'packages/sdk/**/*'
mod:plugin-cli:
- 'packages/plugin-cli/**/*'

View File

@@ -47,8 +47,6 @@ jobs:
electron-install: false
- name: Run i18n codegen
run: yarn i18n-codegen gen
- name: Run Type Check
run: yarn typecheck
- name: Run ESLint
run: yarn lint:eslint --max-warnings=0
- name: Run Prettier
@@ -58,6 +56,40 @@ jobs:
yarn lint:prettier
- name: Run circular
run: yarn circular
- name: Run Type Check
run: yarn typecheck
build-prototype:
name: Build Prototype
runs-on: ubuntu-latest
environment: development
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
electron-install: false
- name: Build Prototype
run: yarn nx build prototype
- name: Upload prototype artifact
uses: actions/upload-artifact@v3
with:
name: prototype
path: ./apps/prototype/dist
if-no-files-found: error
build-server:
name: Build Server
runs-on: ubuntu-latest
environment: development
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
electron-install: false
- name: Build Server
run: yarn nx build @affine/server
- name: Upload server dist
uses: actions/upload-artifact@v3
with:
@@ -121,10 +153,33 @@ jobs:
path: ./apps/core/dist
if-no-files-found: error
build-storage:
name: Build Storage
runs-on: ubuntu-latest
environment: development
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: ./.github/actions/setup-node
- name: Setup Rust
uses: ./.github/actions/setup-rust
with:
target: 'x86_64-unknown-linux-gnu'
- name: Build Storage
run: yarn build:storage
- name: Upload storage.node
uses: actions/upload-artifact@v3
with:
name: storage.node
path: ./packages/storage/storage.node
if-no-files-found: error
server-test:
name: Server Test
runs-on: ubuntu-latest
environment: development
needs: build-storage
services:
postgres:
image: postgres
@@ -160,24 +215,17 @@ jobs:
working-directory: apps/server
env:
DATABASE_URL: postgresql://affine:affine@localhost:5432/affine
- name: Setup Rust
uses: ./.github/actions/setup-rust
- name: Download storage.node
uses: actions/download-artifact@v3
with:
target: 'x86_64-unknown-linux-gnu'
- name: Build Storage
run: yarn build:storage
name: storage.node
path: ./apps/server
- name: Run server tests
run: yarn test:coverage
working-directory: apps/server
env:
CARGO_TARGET_DIR: '${{ github.workspace }}/target'
DATABASE_URL: postgresql://affine:affine@localhost:5432/affine
- name: Upload storage.node
uses: actions/upload-artifact@v3
with:
name: storage.node
path: ./packages/storage/storage.node
if-no-files-found: error
- name: Upload server test coverage results
uses: codecov/codecov-action@v3
with:
@@ -187,28 +235,6 @@ jobs:
name: affine
fail_ci_if_error: false
storybook-test:
name: Storybook Test
runs-on: ubuntu-latest
environment: development
needs: [build-storybook]
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
playwright-install: true
electron-install: false
- name: Download storybook artifact
uses: actions/download-artifact@v3
with:
name: storybook
path: ./apps/storybook/storybook-static
- name: Run storybook tests
working-directory: ./apps/storybook
run: |
yarn exec concurrently -k -s first -n "SB,TEST" -c "magenta,blue" "yarn exec serve ./storybook-static -l 6006" "yarn exec wait-on tcp:6006 && yarn test"
e2e-plugin-test:
name: E2E Plugin Test
runs-on: ubuntu-latest
@@ -251,6 +277,49 @@ jobs:
path: ./test-results
if-no-files-found: ignore
e2e-prototype-test:
name: E2E Prototype Test
runs-on: ubuntu-latest
environment: development
needs: build-prototype
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
playwright-install: true
electron-install: false
- name: Download prototype artifact
uses: actions/download-artifact@v3
with:
name: prototype
path: ./apps/prototype/dist
- name: Run playwright tests
run: yarn e2e --forbid-only
working-directory: tests/affine-prototype
env:
COVERAGE: true
# - name: Collect code coverage report
# run: yarn exec nyc report -t .nyc_output --report-dir .coverage --reporter=lcov
# - name: Upload e2e test coverage results
# uses: codecov/codecov-action@v3
# with:
# token: ${{ secrets.CODECOV_TOKEN }}
# files: ./.coverage/lcov.info
# flags: e2etest-prototype
# name: affine
# fail_ci_if_error: false
- name: Upload test results
if: ${{ failure() }}
uses: actions/upload-artifact@v3
with:
name: test-results-e2e-prototype
path: ./test-results
if-no-files-found: ignore
e2e-test:
name: E2E Test
runs-on: ubuntu-latest
@@ -386,8 +455,6 @@ jobs:
with:
playwright-install: true
hard-link-nm: false
- name: Setup Maker
uses: ./.github/actions/setup-maker
- name: Build AFFiNE native
uses: ./.github/actions/build-rust
with:
@@ -489,11 +556,11 @@ jobs:
build-docker:
if: github.ref == 'refs/heads/master'
name: Build Docker
needs:
- lint
- desktop-test
- server-test
runs-on: ubuntu-latest
needs:
- build-server
- build-core
- build-storage
steps:
- uses: actions/checkout@v3
- name: Download core artifact

View File

@@ -14,5 +14,5 @@ jobs:
- uses: styfle/cancel-workflow-action@0.11.0
with:
# See https://api.github.com/repos/toeverything/AFFiNE/actions/workflows
workflow_id: 44038251, 61883931
workflow_id: 44038251, 61883931, 65188160
access_token: ${{ github.token }}

View File

@@ -50,6 +50,8 @@ jobs:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: ./.github/actions/setup-node
- name: Setup @sentry/cli
uses: ./.github/actions/setup-sentry
- name: Replace Version
run: ./scripts/set-version.sh ${{ needs.set-build-version.outputs.version }}
- name: generate-assets
@@ -110,8 +112,10 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
timeout-minutes: 10
uses: ./.github/actions/setup-node
- name: Setup Maker
timeout-minutes: 10
uses: ./.github/actions/setup-maker
- name: Build AFFiNE native
uses: ./.github/actions/build-rust

38
.github/workflows/publish-storybook.yml vendored Normal file
View File

@@ -0,0 +1,38 @@
name: Publish Storybook
on:
push:
branches:
- master
pull_request_target:
branches:
- master
paths-ignore:
- README.md
- .github/**
- '!.github/workflows/publish-storybook.yml'
jobs:
publish-storybook:
name: Publish Storybook
runs-on: ubuntu-latest
environment: development
steps:
- uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.merge_commit_sha }}
# This is required to fetch all commits for chromatic
fetch-depth: 0
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
electron-install: false
- name: Build Plugins
run: yarn run build:plugins
- name: Publish to Chromatic
uses: chromaui/action@v1
with:
workingDir: apps/storybook
buildScriptName: build
onlyChanged: true
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}

View File

@@ -47,6 +47,8 @@ jobs:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: ./.github/actions/setup-node
- name: Setup @sentry/cli
uses: ./.github/actions/setup-sentry
- name: Get canary version
id: get-canary-version
if: ${{ github.ref_type == 'tag' }}
@@ -112,8 +114,10 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
timeout-minutes: 10
uses: ./.github/actions/setup-node
- name: Setup Maker
timeout-minutes: 10
uses: ./.github/actions/setup-maker
- name: Build AFFiNE native
uses: ./.github/actions/build-rust

View File

@@ -14,15 +14,6 @@
</div>
<div align="center">
<!--
Make New Badge Pattern badges inline
See https://github.com/all-?/all-contributors/issues/361#issuecomment-637166066
-->
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[all-contributors-badge]: https://img.shields.io/badge/all_contributors-66-orange.svg?style=flat-square
<!-- ALL-CONTRIBUTORS-BADGE:END -->
[![AFFiNE Web](<https://img.shields.io/badge/-Try%20It%20Online%20%E2%86%92-rgb(84,56,255)?style=flat-square&logoColor=white&logo=affine>)](https://app.affine.pro)
[![AFFiNE macOS M1/M2 Chip](https://img.shields.io/badge/-macOS_M_Chip%20%E2%86%92-black?style=flat-square&logo=apple&logoColor=white)](https://affine.pro/download)
@@ -39,6 +30,7 @@ See https://github.com/all-?/all-contributors/issues/361#issuecomment-637166066
[![React-version-icon]](https://reactjs.org/)
[![blocksuite-icon]](https://github.com/toeverything/blocksuite)
[![Rust-version-icon]](https://www.rust-lang.org/)
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Ftoeverything%2FAFFiNE.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Ftoeverything%2FAFFiNE?ref=badge_shield)
</div>
@@ -122,9 +114,12 @@ If you have questions, you are welcome to contact us. One of the best places to
## Plugins
> Plugins are a way to extend the functionality of AFFiNE.
> Plugins are a way to extend the functionality of AFFiNE. You can use plugins to add new blocks, new features, and even new ways to edit content.
>
> (Currently, plugins are under heavy development, and the SDK is not yet available.)
> (Currently, the plugin system is under heavy development. You will see the plugin system in the canary release.)
- [@affine/sdk](./packages/sdk) - SDK for developing plugins
- [@affine/plugin-cli](./packages/plugin-cli) - CLI for developing plugins
| Official Plugin | Description |
| ----------------------------------------------------- | ----------------------------------------- |
@@ -132,19 +127,19 @@ If you have questions, you are welcome to contact us. One of the best places to
| [@affine/copilot-plugin](plugins/copilot) | AI Copilot that help you document writing |
| [@affine/image-preview-plugin](plugins/image-preview) | Component for previewing an image |
## Thanks
## Upstreams
We would also like to give thanks to open-source projects that make AFFiNE possible:
- [BlockSuite](https://github.com/toeverything/BlockSuite) - 💠 BlockSuite is the open-source collaborative editor project behind AFFiNE.
- [blocksuite](https://github.com/toeverything/BlockSuite) - 💠 BlockSuite is the open-source collaborative editor project behind AFFiNE.
- [OctoBase](https://github.com/toeverything/OctoBase) - 🐙 OctoBase is the open-source database behind AFFiNE, local-first, yet collaborative. A light-weight, scalable, data engine written in Rust.
- [Yjs](https://github.com/yjs/yjs) - Fundamental support of CRDTs for our implementation on state management and data sync.
- [Electron](https://github.com/electron/electron) - Build cross-platform desktop apps with JavaScript, HTML, and CSS.
- [React](https://github.com/facebook/react) - View layer support and web GUI framework.
- [Rust](https://github.com/rust-lang/rust) - High performance language that extends the ability and availability of our real-time backend, OctoBase.
- [yjs](https://github.com/yjs/yjs) - Fundamental support of CRDTs for our implementation on state management and data sync.
- [electron](https://github.com/electron/electron) - Build cross-platform desktop apps with JavaScript, HTML, and CSS.
- [React](https://github.com/facebook/react) - The library for web and native user interfaces.
- [napi-rs](https://github.com/napi-rs/napi-rs) - A framework for building compiled Node.js add-ons in Rust via Node-API.
- [Jotai](https://github.com/pmndrs/jotai) - Primitive and flexible state management for React.
- [MUI](https://github.com/mui/material-ui) - Our most used graphic UI component library.
- [async-call-rpc](https://github.com/Jack-Works/async-call-rpc) - A lightweight JSON RPC client & server.
- [Vite](https://github.com/vitejs/vite) - Next generation frontend tooling.
- Other upstream [dependencies](https://github.com/toeverything/AFFiNE/network/dependencies).
Thanks a lot to the community for providing such powerful and simple libraries, so that we can focus more on the implementation of the product logic, and we hope that in the future our projects will also provide a more easy-to-use knowledge base for everyone.
@@ -168,15 +163,11 @@ Some amazing companies including AFFiNE are looking for developers! Are you inte
## Upgrading
For upgrading information please see our [update page].
For upgrading information, please see our [update page].
## Feature Request
For feature request please see [community.affine.pro](https://community.affine.pro/c/feature-requests/).
## Is it awesome?
[These people] seem to like it.
For feature request, please see [community.affine.pro](https://community.affine.pro/c/feature-requests/).
## Building
@@ -187,13 +178,21 @@ See [BUILDING.md] for instructions on how to build AFFiNE from source code.
We welcome contributions from everyone.
See [docs/contributing/tutorial.md](./docs/contributing/tutorial.md) for details.
## Thanks
<a href="https://www.chromatic.com/"><img src="https://user-images.githubusercontent.com/321738/84662277-e3db4f80-af1b-11ea-88f5-91d67a5e59f6.png" width="153" height="30" alt="Chromatic" /></a>
Thanks to [Chromatic](https://www.chromatic.com/) for providing the visual testing platform that helps us review UI changes and catch visual regressions.
## License
See [LICENSE] for details.
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Ftoeverything%2FAFFiNE.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Ftoeverything%2FAFFiNE?ref=badge_large)
[all-contributors-badge]: https://img.shields.io/github/contributors/toeverything/AFFiNE
[license]: ./LICENSE
[building.md]: ./docs/BUILDING.md
[these people]: https://twitter.com/AffineOfficial/followers
[update page]: https://affine.pro/blog?tag=Release%20Note
[jobs available]: ./docs/jobs.md
[latest packages]: https://github.com/toeverything/AFFiNE/pkgs/container/affine-self-hosted

View File

@@ -8,7 +8,7 @@ AFFiNE Developer Documentation using [waku](https://github.com/dai-shi/waku).
## electron
> `web` needs to be built before electron.
> `core` needs to be built before electron.
AFFiNE Desktop (macOS, Linux and Windows Distribution) using [Electron](https://www.electronjs.org/).
@@ -20,6 +20,10 @@ Server using [Nest.js](https://nestjs.com/).
Storybook using [Storybook](https://storybook.js.org/).
## Core
## prototype
AFFiNE Core Application using [React.js](https://reactjs.org/).
AFFiNE Prototype using [React.js](https://reactjs.org/) + [Vite](https://vitejs.dev/).
## core
AFFiNE Core Application using [React.js](https://reactjs.org/) + [Webpack](https://webpack.js.org/).

View File

@@ -1,7 +1,6 @@
import { join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { createRequire } from 'node:module';
import HTMLPlugin from 'html-webpack-plugin';
import type { Configuration as DevServerConfiguration } from 'webpack-dev-server';
import { PerfseePlugin } from '@perfsee/webpack';
import { sentryWebpackPlugin } from '@sentry/webpack-plugin';
@@ -74,12 +73,19 @@ export const createConfiguration: (
) => webpack.Configuration = (buildFlags, runtimeConfig) => {
let publicPath = process.env.PUBLIC_PATH ?? '/';
const blocksuiteBaseDir = buildFlags.localBlockSuite;
const cacheKey = computeCacheKey(buildFlags);
const config = {
name: 'affine',
// to set a correct base path for the source map
context: projectRoot,
experiments: {
topLevelAwait: true,
outputModule: false,
syncWebAssembly: true,
},
output: {
environment: {
module: true,
@@ -118,6 +124,60 @@ export const createConfiguration: (
'.mjs': ['.mjs', '.mts'],
},
extensions: ['.js', '.ts', '.tsx'],
fallback:
blocksuiteBaseDir === undefined
? undefined
: {
events: false,
},
alias:
blocksuiteBaseDir === undefined
? undefined
: {
yjs: require.resolve('yjs'),
'@blocksuite/block-std': resolve(
blocksuiteBaseDir,
'packages',
'block-std'
),
'@blocksuite/blocks': resolve(
blocksuiteBaseDir,
'packages',
'blocks'
),
'@blocksuite/editor': resolve(
blocksuiteBaseDir,
'packages',
'editor'
),
'@blocksuite/global': resolve(
blocksuiteBaseDir,
'packages',
'global'
),
'@blocksuite/lit': resolve(blocksuiteBaseDir, 'packages', 'lit'),
'@blocksuite/phasor': resolve(
blocksuiteBaseDir,
'packages',
'phasor'
),
'@blocksuite/store/providers/broadcast-channel': resolve(
blocksuiteBaseDir,
'packages',
'store',
'src/providers/broadcast-channel'
),
'@blocksuite/store': resolve(
blocksuiteBaseDir,
'packages',
'store'
),
'@blocksuite/virgo': resolve(
blocksuiteBaseDir,
'packages',
'virgo'
),
},
},
cache: {
@@ -131,6 +191,10 @@ export const createConfiguration: (
module: {
parser: {
javascript: {
// Do not mock Node.js globals
node: false,
requireJs: false,
import: true,
// Treat as missing export as error
strictExportPresence: true,
},
@@ -138,6 +202,20 @@ export const createConfiguration: (
rules: [
{
test: /\.m?js?$/,
enforce: 'pre',
use: [
{
loader: require.resolve('source-map-loader'),
options: {
filterSourceMappingUrl: (
_url: string,
resourcePath: string
) => {
return resourcePath.includes('@blocksuite');
},
},
},
],
resolve: {
fullySpecified: false,
},
@@ -146,8 +224,7 @@ export const createConfiguration: (
oneOf: [
{
test: /\.tsx?$/,
// Compile all ts files in the workspace
include: resolve(rootPath, '..', '..'),
exclude: /node_modules/,
loader: require.resolve('swc-loader'),
options: {
// https://swc.rs/docs/configuring-swc/
@@ -158,9 +235,10 @@ export const createConfiguration: (
dynamicImport: true,
topLevelAwait: false,
tsx: true,
decorators: true,
},
target: 'es2022',
externalHelpers: true,
externalHelpers: false,
transform: {
react: {
runtime: 'automatic',
@@ -170,6 +248,7 @@ export const createConfiguration: (
emitFullSignatures: true,
},
},
useDefineForClassFields: false,
},
experimental: {
keepImportAssertions: true,
@@ -253,14 +332,6 @@ export const createConfiguration: (
ignoreOrder: true,
}),
]),
new HTMLPlugin({
template: join(rootPath, '.webpack', 'template.html'),
inject: 'body',
scriptLoading: 'module',
minify: false,
chunks: ['index', 'plugin', 'polyfill-ses'],
filename: 'index.html',
}),
new VanillaExtractPlugin(),
new webpack.DefinePlugin({
'process.env': JSON.stringify({}),

View File

@@ -7,5 +7,6 @@ export function computeCacheKey(buildFlags: BuildFlags) {
buildFlags.mode,
buildFlags.distribution,
buildFlags.channel,
...(buildFlags.localBlockSuite ? [buildFlags.localBlockSuite] : []),
].join('-');
}

View File

@@ -1,8 +1,9 @@
import { createConfiguration, rootPath } from './config.js';
import { merge } from 'webpack-merge';
import { resolve } from 'node:path';
import { join, resolve } from 'node:path';
import type { BuildFlags } from '@affine/cli/config';
import { getRuntimeConfig } from './runtime-config.js';
import HTMLPlugin from 'html-webpack-plugin';
export default async function (cli_env: any, _: any) {
const flags: BuildFlags = JSON.parse(
@@ -14,20 +15,49 @@ export default async function (cli_env: any, _: any) {
const config = createConfiguration(flags, runtimeConfig);
return merge(config, {
entry: {
'polyfill-ses': {
asyncChunks: false,
'polyfill/intl-segmenter': {
import: resolve(rootPath, 'src/polyfill/intl-segmenter.ts'),
},
'polyfill/ses': {
import: resolve(rootPath, 'src/polyfill/ses.ts'),
},
plugin: {
asyncChunks: true,
dependOn: ['polyfill-ses'],
dependOn: ['polyfill/intl-segmenter', 'polyfill/ses'],
import: resolve(rootPath, 'src/bootstrap/register-plugins.ts'),
},
index: {
asyncChunks: false,
dependOn: ['polyfill-ses', 'plugin'],
app: {
chunkLoading: 'import',
dependOn: ['polyfill/intl-segmenter', 'polyfill/ses', 'plugin'],
import: resolve(rootPath, 'src/index.tsx'),
},
'_plugin/index.test': {
chunkLoading: 'import',
dependOn: ['polyfill/intl-segmenter', 'polyfill/ses', 'plugin'],
import: resolve(rootPath, 'src/_plugin/index.test.tsx'),
},
},
plugins: [
new HTMLPlugin({
template: join(rootPath, '.webpack', 'template.html'),
inject: 'body',
scriptLoading: 'module',
minify: false,
chunks: ['app', 'plugin', 'polyfill/intl-segmenter', 'polyfill/ses'],
filename: 'index.html',
}),
new HTMLPlugin({
template: join(rootPath, '.webpack', 'template.html'),
inject: 'body',
scriptLoading: 'module',
minify: false,
chunks: [
'_plugin/index.test',
'plugin',
'polyfill/intl-segmenter',
'polyfill/ses',
],
filename: '_plugin/index.html',
}),
],
});
}

View File

@@ -2,12 +2,18 @@
"name": "@affine/core",
"type": "module",
"private": true,
"version": "0.8.0-canary.8",
"version": "0.8.0-canary.24",
"scripts": {
"build": "yarn -T run build-core",
"dev": "yarn -T run dev-core",
"static-server": "ts-node-esm ./server.mts"
},
"exports": {
"./app": "./src/app.tsx",
"./router": "./src/router.ts",
"./bootstrap/setup": "./src/bootstrap/setup.ts",
"./bootstrap/register-plugins": "./src/bootstrap/register-plugins.ts"
},
"dependencies": {
"@affine-test/fixtures": "workspace:*",
"@affine/component": "workspace:*",
@@ -18,29 +24,32 @@
"@affine/jotai": "workspace:*",
"@affine/templates": "workspace:*",
"@affine/workspace": "workspace:*",
"@blocksuite/block-std": "0.0.0-20230802200139-381599c0-nightly",
"@blocksuite/blocks": "0.0.0-20230802200139-381599c0-nightly",
"@blocksuite/editor": "0.0.0-20230802200139-381599c0-nightly",
"@blocksuite/global": "0.0.0-20230802200139-381599c0-nightly",
"@blocksuite/icons": "^2.1.29",
"@blocksuite/lit": "0.0.0-20230802200139-381599c0-nightly",
"@blocksuite/store": "0.0.0-20230802200139-381599c0-nightly",
"@blocksuite/block-std": "0.0.0-20230816084758-3be96d22-nightly",
"@blocksuite/blocks": "0.0.0-20230816084758-3be96d22-nightly",
"@blocksuite/editor": "0.0.0-20230816084758-3be96d22-nightly",
"@blocksuite/global": "0.0.0-20230816084758-3be96d22-nightly",
"@blocksuite/icons": "^2.1.31",
"@blocksuite/lit": "0.0.0-20230816084758-3be96d22-nightly",
"@blocksuite/store": "0.0.0-20230816084758-3be96d22-nightly",
"@dnd-kit/core": "^6.0.8",
"@dnd-kit/sortable": "^7.0.2",
"@emotion/cache": "^11.11.0",
"@emotion/react": "^11.11.1",
"@emotion/server": "^11.11.0",
"@emotion/styled": "^11.11.0",
"@mui/material": "^5.14.2",
"@mui/material": "^5.14.5",
"@react-hookz/web": "^23.1.0",
"@toeverything/components": "^0.0.11",
"async-call-rpc": "^6.3.1",
"cmdk": "^0.2.0",
"css-spring": "^4.1.0",
"cssnano": "^6.0.1",
"graphql": "^16.7.1",
"jotai": "^2.2.2",
"jotai-devtools": "^0.6.0",
"lit": "^2.7.6",
"graphql": "^16.8.0",
"intl-segmenter-polyfill-rs": "^0.1.5",
"jotai": "^2.3.1",
"jotai-devtools": "^0.6.1",
"lit": "^2.8.0",
"lodash.debounce": "^4.0.8",
"lottie-web": "^5.12.2",
"mini-css-extract-plugin": "^2.7.6",
"next-themes": "^0.2.1",
@@ -48,30 +57,32 @@
"react": "18.2.0",
"react-dom": "18.2.0",
"react-is": "18.2.0",
"react-resizable-panels": "^0.0.54",
"react-router-dom": "^6.14.2",
"react-resizable-panels": "^0.0.55",
"react-router-dom": "^6.15.0",
"rxjs": "^7.8.1",
"ses": "^0.18.5",
"swr": "2.1.5",
"ses": "^0.18.7",
"swr": "2.2.1",
"y-protocols": "^1.0.5",
"yjs": "^13.6.7",
"zod": "^3.21.4"
"zod": "^3.22.1"
},
"devDependencies": {
"@perfsee/webpack": "^1.8.2",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.10",
"@sentry/webpack-plugin": "^2.5.0",
"@svgr/webpack": "^8.0.1",
"@swc/core": "^1.3.71",
"@perfsee/webpack": "^1.8.4",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.11",
"@sentry/webpack-plugin": "^2.6.2",
"@svgr/webpack": "^8.1.0",
"@swc/core": "^1.3.77",
"@types/lodash.debounce": "^4.0.7",
"@types/webpack-env": "^1.18.1",
"copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.8.1",
"express": "^4.18.2",
"html-webpack-plugin": "^5.5.3",
"raw-loader": "^4.0.2",
"source-map-loader": "^4.0.1",
"style-loader": "^3.3.3",
"swc-loader": "^0.2.3",
"swc-plugin-coverage-instrument": "^0.0.19",
"swc-plugin-coverage-instrument": "^0.0.20",
"thread-loader": "^4.0.2",
"ts-node": "^10.9.1",
"webpack": "^5.88.2",

View File

@@ -1,16 +1,22 @@
{
"name": "@affine/core",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"projectType": "application",
"root": "apps/core",
"sourceRoot": "apps/core/src",
"targets": {
"build": {
"executor": "nx:run-script",
"dependsOn": ["^build"],
"dependsOn": [
{
"projects": ["tag:plugin"],
"target": "build",
"params": "ignore"
},
"^build"
],
"inputs": [
"{projectRoot}/.webpack/**/*",
"{projectRoot}/**/*",
"{projectRoot}/public/plugins/**/*",
"{projectRoot}/public/**/*",
"{workspaceRoot}/packages/env/src/**/*",
"{workspaceRoot}/packages/component/src/**/*",
"{workspaceRoot}/packages/debug/src/**/*",
"{workspaceRoot}/packages/graphql/src/**/*",
@@ -46,7 +52,7 @@
"options": {
"script": "build"
},
"outputs": ["{projectRoot}/dist", "{projectRoot}/public/plugins"]
"outputs": ["{projectRoot}/dist"]
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 723 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 971 KiB

View File

@@ -6,7 +6,10 @@ const PORT = process.env.PORT || 8080;
app.use('/', express.static('dist'));
app.get('/*', (_, res) => {
app.get('/*', (req, res) => {
if (req.url.startsWith('/plugins')) {
res.sendFile(req.url, { root: 'dist' });
}
res.sendFile('index.html', { root: 'dist' });
});

View File

@@ -0,0 +1,47 @@
import { assertExists } from '@blocksuite/global/utils';
import { loadedPluginNameAtom, rootStore } from '@toeverything/infra/atom';
import { use } from 'foxact/use';
import { useAtomValue } from 'jotai';
import { Provider } from 'jotai/react';
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { _pluginNestedImportsMap } from '../bootstrap/plugins/setup';
import { pluginRegisterPromise } from '../bootstrap/register-plugins';
async function main() {
const { setup } = await import('../bootstrap/setup');
await setup();
const root = document.getElementById('app');
assertExists(root);
const App = () => {
use(pluginRegisterPromise);
const plugins = useAtomValue(loadedPluginNameAtom);
_pluginNestedImportsMap.forEach(value => {
const exports = value.get('index.js');
assertExists(exports);
assertExists(exports?.get('entry'));
});
return (
<div>
<div data-plugins-load-status="success">
Successfully loaded plugins:
</div>
{plugins.map(plugin => {
return <div key={plugin}>{plugin}</div>;
})}
</div>
);
};
createRoot(root).render(
<StrictMode>
<Provider store={rootStore}>
<App />
</Provider>
</StrictMode>
);
}
await main();

View File

@@ -1,5 +1,5 @@
import { DebugLogger } from '@affine/debug';
import { initEmptyPage, initPageWithPreloading } from '@affine/env/blocksuite';
import { initEmptyPage } from '@affine/env/blocksuite';
import {
DEFAULT_HELLO_WORLD_PAGE_ID_SUFFIX,
DEFAULT_WORKSPACE_NAME,
@@ -19,8 +19,11 @@ import {
import { getOrCreateWorkspace } from '@affine/workspace/manager';
import { createIndexedDBDownloadProvider } from '@affine/workspace/providers';
import { nanoid } from '@blocksuite/store';
import { useStaticBlockSuiteWorkspace } from '@toeverything/plugin-infra/__internal__/react';
import { useStaticBlockSuiteWorkspace } from '@toeverything/infra/__internal__/react';
import { rootStore } from '@toeverything/infra/atom';
import { buildShowcaseWorkspace } from '@toeverything/infra/blocksuite';
import { setPageModeAtom } from '../../atoms';
import {
BlockSuitePageList,
NewWorkspaceSettingDetail,
@@ -41,21 +44,23 @@ export const LocalAdapter: WorkspaceAdapter<WorkspaceFlavour.LOCAL> = {
WorkspaceFlavour.LOCAL
);
blockSuiteWorkspace.meta.setName(DEFAULT_WORKSPACE_NAME);
const page = blockSuiteWorkspace.createPage({
id: `${blockSuiteWorkspace.id}-${DEFAULT_HELLO_WORLD_PAGE_ID_SUFFIX}`,
});
if (runtimeConfig.enablePreloading) {
initPageWithPreloading(page).catch(err => {
buildShowcaseWorkspace(blockSuiteWorkspace, {
store: rootStore,
atoms: {
pageMode: setPageModeAtom,
},
}).catch(err => {
logger.error('init page with preloading failed', err);
});
} else {
const page = blockSuiteWorkspace.createPage({
id: `${blockSuiteWorkspace.id}-${DEFAULT_HELLO_WORLD_PAGE_ID_SUFFIX}`,
});
initEmptyPage(page).catch(error => {
logger.error('init page with empty failed', error);
});
}
blockSuiteWorkspace.setPageMeta(page.id, {
jumpOnce: true,
});
const provider = createIndexedDBDownloadProvider(
blockSuiteWorkspace.id,
blockSuiteWorkspace.doc,

View File

@@ -1,9 +1,9 @@
import '@affine/component/theme/global.css';
import '@affine/component/theme/theme.css';
import '@toeverything/components/style.css';
import { AffineContext } from '@affine/component/context';
import { WorkspaceFallback } from '@affine/component/workspace';
import { createI18n, setUpLanguage } from '@affine/i18n';
import { CacheProvider } from '@emotion/react';
import { use } from 'foxact/use';
import type { PropsWithChildren, ReactElement } from 'react';
@@ -13,7 +13,6 @@ import { RouterProvider } from 'react-router-dom';
import { router } from './router';
import createEmotionCache from './utils/create-emotion-cache';
const i18n = createI18n();
const cache = createEmotionCache();
const DevTools = lazy(() =>
@@ -33,18 +32,16 @@ const future = {
v7_startTransition: true,
} as const;
const languageLoadingPromise = new Promise<void>(resolve => {
async function loadLanguage() {
if (environment.isBrowser) {
const { createI18n, setUpLanguage } = await import('@affine/i18n');
const i18n = createI18n();
document.documentElement.lang = i18n.language;
setUpLanguage(i18n)
.then(() => resolve())
.catch(error => {
console.error(error);
});
} else {
resolve();
await setUpLanguage(i18n);
}
});
}
const languageLoadingPromise = loadLanguage().catch(console.error);
export const App = memo(function App() {
use(languageLoadingPromise);

View File

@@ -70,12 +70,16 @@ export const guideOnboardingAtom = atom<
}));
}
);
export const guideDownloadClientTipAtom = atom<
Guide['downloadClientTip'],
[open: boolean],
void
>(
get => {
if (environment.isDesktop) {
return false;
}
return get(guidePrimitiveAtom).downloadClientTip;
},
(_, set, open) => {

View File

@@ -1,5 +1,7 @@
import type { PrimitiveAtom } from 'jotai';
import { atom } from 'jotai';
import { atomFamily, atomWithStorage } from 'jotai/utils';
import type { AtomFamily } from 'jotai/vanilla/utils/atomFamily';
import type { CreateWorkspaceMode } from '../components/affine/create-workspace-modal';
import type { SettingProps } from '../components/affine/setting-modal';
@@ -59,19 +61,16 @@ const defaultPageSetting = {
mode: 'page',
} satisfies PageLocalSetting;
export const pageSettingFamily = atomFamily((pageId: string) =>
export const pageSettingFamily: AtomFamily<
string,
PrimitiveAtom<PageLocalSetting>
> = atomFamily((pageId: string) =>
atom(
get =>
get(pageSettingsBaseAtom)[pageId] ?? {
...defaultPageSetting,
},
(
get,
set,
patch:
| Partial<PageLocalSetting>
| ((prevSetting: PageLocalSetting | undefined) => void)
) => {
(get, set, patch) => {
set(recentPageSettingsBaseAtom, ids => {
// pick 3 recent page ids
return [...new Set([pageId, ...ids]).values()].slice(0, 3);

View File

@@ -1,4 +1,4 @@
import { currentPageIdAtom } from '@toeverything/plugin-infra/atom';
import { currentPageIdAtom } from '@toeverything/infra/atom';
import { atom } from 'jotai/vanilla';
import { pageSettingFamily } from './index';

View File

@@ -1,164 +0,0 @@
import {
migrateDatabaseBlockTo3,
migrateToSubdoc,
} from '@affine/env/blocksuite';
import { setupGlobal } from '@affine/env/global';
import type {
LocalIndexedDBDownloadProvider,
WorkspaceAdapter,
} from '@affine/env/workspace';
import { WorkspaceFlavour, WorkspaceVersion } from '@affine/env/workspace';
import type { RootWorkspaceMetadata } from '@affine/workspace/atom';
import {
type RootWorkspaceMetadataV2,
rootWorkspacesMetadataAtom,
workspaceAdaptersAtom,
} from '@affine/workspace/atom';
import {
migrateLocalBlobStorage,
upgradeV1ToV2,
} from '@affine/workspace/migration';
import { createIndexedDBDownloadProvider } from '@affine/workspace/providers';
import { assertExists } from '@blocksuite/global/utils';
import { rootStore } from '@toeverything/plugin-infra/atom';
import { WorkspaceAdapters } from '../adapters/workspace';
console.log('setup global');
setupGlobal();
rootStore.set(
workspaceAdaptersAtom,
WorkspaceAdapters as Record<
WorkspaceFlavour,
WorkspaceAdapter<WorkspaceFlavour>
>
);
const value = localStorage.getItem('jotai-workspaces');
if (value) {
try {
const metadata = JSON.parse(value) as RootWorkspaceMetadata[];
const promises: Promise<void>[] = [];
const newMetadata = [...metadata];
metadata.forEach(oldMeta => {
if (!('version' in oldMeta)) {
const adapter = WorkspaceAdapters[oldMeta.flavour];
assertExists(adapter);
const upgrade = async () => {
const workspace = await adapter.CRUD.get(oldMeta.id);
if (!workspace) {
console.warn('cannot find workspace', oldMeta.id);
return;
}
if (workspace.flavour !== WorkspaceFlavour.LOCAL) {
console.warn('not supported');
return;
}
const doc = workspace.blockSuiteWorkspace.doc;
const provider = createIndexedDBDownloadProvider(workspace.id, doc, {
awareness: workspace.blockSuiteWorkspace.awarenessStore.awareness,
}) as LocalIndexedDBDownloadProvider;
provider.sync();
await provider.whenReady;
const newDoc = migrateToSubdoc(doc);
if (doc === newDoc) {
console.log('doc not changed');
return;
}
const newWorkspace = upgradeV1ToV2(workspace);
await migrateDatabaseBlockTo3(newWorkspace.blockSuiteWorkspace.doc);
const newId = await adapter.CRUD.create(
newWorkspace.blockSuiteWorkspace
);
await adapter.CRUD.delete(workspace as any);
console.log('migrated', oldMeta.id, newId);
const index = newMetadata.findIndex(meta => meta.id === oldMeta.id);
newMetadata[index] = {
...oldMeta,
id: newId,
version: WorkspaceVersion.DatabaseV3,
};
await migrateLocalBlobStorage(workspace.id, newId);
console.log('migrate to v2');
};
// create a new workspace and push it to metadata
promises.push(upgrade());
} else if (oldMeta.version < WorkspaceVersion.DatabaseV3) {
const adapter = WorkspaceAdapters[oldMeta.flavour];
assertExists(adapter);
promises.push(
(async () => {
const workspace = await adapter.CRUD.get(oldMeta.id);
if (workspace) {
const provider = createIndexedDBDownloadProvider(
workspace.id,
workspace.blockSuiteWorkspace.doc,
{
awareness:
workspace.blockSuiteWorkspace.awarenessStore.awareness,
}
) as LocalIndexedDBDownloadProvider;
provider.sync();
await provider.whenReady;
await migrateDatabaseBlockTo3(workspace.blockSuiteWorkspace.doc);
}
const index = newMetadata.findIndex(meta => meta.id === oldMeta.id);
console.log('migrate to v3');
newMetadata[index] = {
...oldMeta,
version: WorkspaceVersion.DatabaseV3,
};
})()
);
}
});
await Promise.all(promises)
.then(() => {
console.log('migration done');
})
.catch(() => {
console.error('migration failed');
})
.finally(() => {
localStorage.setItem('jotai-workspaces', JSON.stringify(newMetadata));
window.dispatchEvent(new CustomEvent('migration-done'));
window.$migrationDone = true;
});
} catch (e) {
console.error('error when migrating data', e);
}
}
const createFirst = (): RootWorkspaceMetadataV2[] => {
const Plugins = Object.values(WorkspaceAdapters).sort(
(a, b) => a.loadPriority - b.loadPriority
);
return Plugins.flatMap(Plugin => {
return Plugin.Events['app:init']?.().map(
id =>
<RootWorkspaceMetadataV2>{
id,
flavour: Plugin.flavour,
version: WorkspaceVersion.DatabaseV3,
}
);
}).filter((ids): ids is RootWorkspaceMetadataV2 => !!ids);
};
await rootStore
.get(rootWorkspacesMetadataAtom)
.then(meta => {
if (meta.length === 0 && localStorage.getItem('is-first-open') === null) {
const result = createFirst();
console.info('create first workspace', result);
localStorage.setItem('is-first-open', 'false');
rootStore.set(rootWorkspacesMetadataAtom, result);
}
})
.catch(console.error);

View File

@@ -0,0 +1,22 @@
type ExportsPromiseOrExports =
| Promise<{ [key: string]: any }>
| { [key: string]: any };
export async function setupImportsMap(
map: Map<string, Map<string, any>>,
imports: Record<string, ExportsPromiseOrExports>
) {
for (const [key, value] of Object.entries(imports)) {
let module: { [key: string]: any };
if (value instanceof Promise) {
module = await value;
} else {
module = value;
}
const moduleMap = new Map();
map.set(key, moduleMap);
for (const [exportName, exportValue] of Object.entries(module)) {
moduleMap.set(exportName, exportValue);
}
}
}

View File

@@ -1,79 +1,141 @@
import * as AFFiNEComponent from '@affine/component';
import { DebugLogger } from '@affine/debug';
import { FormatQuickBar } from '@blocksuite/blocks';
import * as BlockSuiteBlocksStd from '@blocksuite/blocks/std';
import * as BlockSuiteGlobalUtils from '@blocksuite/global/utils';
import { assertExists } from '@blocksuite/global/utils';
import { DisposableGroup } from '@blocksuite/global/utils';
import * as Icons from '@blocksuite/icons';
import * as Atom from '@toeverything/plugin-infra/atom';
import {
editorItemsAtom,
headerItemsAtom,
rootStore,
settingItemsAtom,
windowItemsAtom,
} from '@toeverything/plugin-infra/atom';
import type {
CallbackMap,
ExpectedLayout,
LayoutNode,
PluginContext,
} from '@toeverything/plugin-infra/entry';
import * as Jotai from 'jotai/index';
} from '@affine/sdk/entry';
import { AffineFormatBarWidget } from '@blocksuite/blocks';
import { assertExists } from '@blocksuite/global/utils';
import {
addCleanup,
pluginEditorAtom,
pluginHeaderItemAtom,
pluginSettingAtom,
pluginWindowAtom,
} from '@toeverything/infra/__internal__/plugin';
import {
contentLayoutAtom,
currentPageAtom,
currentWorkspaceAtom,
rootStore,
} from '@toeverything/infra/atom';
import { atom } from 'jotai';
import { Provider } from 'jotai/react';
import * as JotaiUtils from 'jotai/utils';
import * as React from 'react';
import { createElement, type PropsWithChildren } from 'react';
import * as ReactJSXRuntime from 'react/jsx-runtime';
import * as ReactDom from 'react-dom';
import * as ReactDomClient from 'react-dom/client';
import * as SWR from 'swr';
import { createFetch } from './endowments/fercher';
import { createTimers } from './endowments/timer';
import { setupImportsMap } from './setup-imports-map';
const dynamicImportKey = '$h_import';
const dynamicImportKey = '$h_import';
const permissionLogger = new DebugLogger('plugins:permission');
const importLogger = new DebugLogger('plugins:import');
const setupRootImportsMap = () => {
rootImportsMap.set('react', new Map(Object.entries(React)));
rootImportsMap.set(
'react/jsx-runtime',
new Map(Object.entries(ReactJSXRuntime))
);
rootImportsMap.set('react-dom', new Map(Object.entries(ReactDom)));
rootImportsMap.set(
'react-dom/client',
new Map(Object.entries(ReactDomClient))
);
rootImportsMap.set('@blocksuite/icons', new Map(Object.entries(Icons)));
rootImportsMap.set(
'@affine/component',
new Map(Object.entries(AFFiNEComponent))
);
rootImportsMap.set(
'@blocksuite/blocks/std',
new Map(Object.entries(BlockSuiteBlocksStd))
);
rootImportsMap.set(
'@blocksuite/global/utils',
new Map(Object.entries(BlockSuiteGlobalUtils))
);
rootImportsMap.set('jotai', new Map(Object.entries(Jotai)));
rootImportsMap.set('jotai/utils', new Map(Object.entries(JotaiUtils)));
rootImportsMap.set(
'@toeverything/plugin-infra/atom',
new Map(Object.entries(Atom))
);
rootImportsMap.set('swr', new Map(Object.entries(SWR)));
};
const pushLayoutAtom = atom<
null,
// fixme: check plugin name here
[
pluginName: string,
create: (root: HTMLElement) => () => void,
options: { maxWidth: (number | undefined)[] } | undefined,
],
void
>(null, (_, set, pluginName, callback, options) => {
set(pluginWindowAtom, items => ({
...items,
[pluginName]: callback,
}));
set(contentLayoutAtom, layout => {
if (layout === 'editor') {
return {
direction: 'horizontal',
first: 'editor',
second: pluginName,
splitPercentage: 70,
maxWidth: options?.maxWidth,
};
} else {
return {
direction: 'horizontal',
first: 'editor',
splitPercentage: 70,
second: {
direction: 'horizontal',
first: pluginName,
second: layout.second,
splitPercentage: 50,
},
} satisfies ExpectedLayout;
}
});
addCleanup(pluginName, () => {
set(deleteLayoutAtom, pluginName);
});
});
const deleteLayoutAtom = atom<null, [string], void>(null, (_, set, id) => {
set(pluginWindowAtom, items => {
const newItems = { ...items };
delete newItems[id];
return newItems;
});
const removeLayout = (layout: LayoutNode): LayoutNode | string => {
if (typeof layout === 'string') {
return layout;
}
if (layout.first === id) {
return layout.second;
} else if (layout.second === id) {
return layout.first;
} else {
return {
...layout,
second: removeLayout(layout.second),
};
}
};
set(contentLayoutAtom, layout => {
if (layout === 'editor') {
return 'editor';
} else {
return removeLayout(layout) as ExpectedLayout;
}
});
});
// module -> importName -> updater[]
const rootImportsMap = new Map<string, Map<string, any>>();
setupRootImportsMap();
export const _rootImportsMap = new Map<string, Map<string, any>>();
const rootImportsMapSetupPromise = setupImportsMap(_rootImportsMap, {
react: import('react'),
'react/jsx-runtime': import('react/jsx-runtime'),
'react-dom': import('react-dom'),
'react-dom/client': import('react-dom/client'),
jotai: import('jotai'),
'jotai/utils': import('jotai/utils'),
swr: import('swr'),
'@affine/component': import('@affine/component'),
'@blocksuite/icons': import('@blocksuite/icons'),
'@blocksuite/blocks': import('@blocksuite/blocks'),
'@affine/sdk/entry': {
rootStore: rootStore,
currentWorkspaceAtom: currentWorkspaceAtom,
currentPageAtom: currentPageAtom,
pushLayoutAtom: pushLayoutAtom,
deleteLayoutAtom: deleteLayoutAtom,
},
'@blocksuite/global/utils': import('@blocksuite/global/utils'),
'@toeverything/infra/atom': import('@toeverything/infra/atom'),
'@toeverything/components/button': import('@toeverything/components/button'),
});
// pluginName -> module -> importName -> updater[]
const pluginNestedImportsMap = new Map<string, Map<string, Map<string, any>>>();
export const _pluginNestedImportsMap = new Map<
string,
Map<string, Map<string, any>>
>();
const pluginImportsFunctionMap = new Map<string, (imports: any) => void>();
export const createImports = (pluginName: string) => {
@@ -85,12 +147,12 @@ export const createImports = (pluginName: string) => {
newUpdaters: [string, [string, ((val: any) => void)[]][]][]
) => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const currentImportMap = pluginNestedImportsMap.get(pluginName)!;
console.log('currentImportMap', pluginName, currentImportMap);
const currentImportMap = _pluginNestedImportsMap.get(pluginName)!;
importLogger.debug('currentImportMap', pluginName, currentImportMap);
for (const [module, moduleUpdaters] of newUpdaters) {
console.log('imports module', module, moduleUpdaters);
let moduleImports = rootImportsMap.get(module);
importLogger.debug('imports module', module, moduleUpdaters);
let moduleImports = _rootImportsMap.get(module);
if (!moduleImports) {
moduleImports = currentImportMap.get(module);
}
@@ -107,11 +169,11 @@ export const createImports = (pluginName: string) => {
}
}
} else {
console.log(
console.error(
'cannot find module in plugin import map',
module,
currentImportMap,
pluginNestedImportsMap
_pluginNestedImportsMap
);
}
}
@@ -126,7 +188,15 @@ const pluginFetch = createFetch({});
const timer = createTimers(abortController.signal);
const sharedGlobalThis = Object.assign(Object.create(null), timer, {
Object: globalThis.Object,
fetch: pluginFetch,
ReadableStream: globalThis.ReadableStream,
Symbol: globalThis.Symbol,
Error: globalThis.Error,
TypeError: globalThis.TypeError,
RangeError: globalThis.RangeError,
console: globalThis.console,
crypto: globalThis.crypto,
});
const dynamicImportMap = new Map<
@@ -197,6 +267,7 @@ export const createOrGetGlobalThis = (
Object.create(null),
sharedGlobalThis,
{
// fixme: vite build output bundle will have this, we should remove it
process: Object.freeze({
env: {
NODE_ENV: process.env.NODE_ENV,
@@ -213,6 +284,9 @@ export const createOrGetGlobalThis = (
if (sharedGlobalThis[key]) return sharedGlobalThis[key];
const result = Reflect.get(window, key);
if (typeof result === 'function') {
if (result === ShadowRoot) {
return result;
}
return function (...args: any[]) {
permissionLogger.debug(
`${pluginName} is calling window`,
@@ -253,15 +327,11 @@ export const createOrGetGlobalThis = (
userAgent: navigator.userAgent,
},
// safe to use for all plugins
Error: globalThis.Error,
TypeError: globalThis.TypeError,
RangeError: globalThis.RangeError,
console: globalThis.console,
crypto: globalThis.crypto,
MouseEvent: globalThis.MouseEvent,
KeyboardEvent: globalThis.KeyboardEvent,
CustomEvent: globalThis.CustomEvent,
// copilot uses these
CustomEvent: globalThis.CustomEvent,
Date: globalThis.Date,
Math: globalThis.Math,
URL: globalThis.URL,
@@ -275,6 +345,10 @@ export const createOrGetGlobalThis = (
Blob: globalThis.Blob,
ClipboardItem: globalThis.ClipboardItem,
// vue uses these
Element: globalThis.Element,
SVGElement: globalThis.SVGElement,
// fixme: use our own db api
indexedDB: globalThis.indexedDB,
IDBRequest: globalThis.IDBRequest,
@@ -290,6 +364,7 @@ export const createOrGetGlobalThis = (
IDBVersionChangeEvent: globalThis.IDBVersionChangeEvent,
}
);
pluginGlobalThis.global = pluginGlobalThis;
globalThisMap.set(pluginName, pluginGlobalThis);
return pluginGlobalThis;
};
@@ -299,13 +374,14 @@ export const setupPluginCode = async (
pluginName: string,
filename: string
) => {
if (!pluginNestedImportsMap.has(pluginName)) {
pluginNestedImportsMap.set(pluginName, new Map());
await rootImportsMapSetupPromise;
if (!_pluginNestedImportsMap.has(pluginName)) {
_pluginNestedImportsMap.set(pluginName, new Map());
}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const currentImportMap = pluginNestedImportsMap.get(pluginName)!;
const currentImportMap = _pluginNestedImportsMap.get(pluginName)!;
const isMissingPackage = (name: string) =>
rootImportsMap.has(name) && !currentImportMap.has(name);
_rootImportsMap.has(name) && !currentImportMap.has(name);
const bundleAnalysis = await fetch(`${baseUrl}/${filename}.json`).then(res =>
res.json()
@@ -321,7 +397,7 @@ export const setupPluginCode = async (
if (isMissingPackage(name)) {
return Promise.resolve();
} else {
console.log('missing package', name);
importLogger.debug('missing package', name);
return setupPluginCode(baseUrl, pluginName, name);
}
})
@@ -329,7 +405,7 @@ export const setupPluginCode = async (
const code = await fetch(`${baseUrl}/${filename.replace(/^\.\//, '')}`).then(
res => res.text()
);
console.log('evaluating', filename);
importLogger.debug('evaluating', filename);
const moduleCompartment = new Compartment(
createOrGetGlobalThis(
pluginName,
@@ -358,7 +434,6 @@ export const setupPluginCode = async (
onceVar: setVarProxy,
});
console.log('module exports alias', moduleExports);
for (const [newExport, [originalExport]] of Object.entries(moduleExports)) {
if (newExport === originalExport) continue;
const value = moduleExportsMap.get(originalExport);
@@ -366,7 +441,6 @@ export const setupPluginCode = async (
moduleExportsMap.delete(originalExport);
}
console.log('module re-exports', moduleReexports);
for (const [name, reexports] of Object.entries(moduleReexports)) {
const targetExports = currentImportMap.get(filename);
const moduleExports = currentImportMap.get(name);
@@ -374,7 +448,6 @@ export const setupPluginCode = async (
assertExists(moduleExports);
for (const [exportedName, localName] of reexports) {
const exportedValue: any = moduleExports.get(exportedName);
console.log('re-export', name, localName, exportedName, exportedValue);
assertExists(exportedValue);
targetExports.set(localName, exportedValue);
}
@@ -390,12 +463,11 @@ const PluginProvider = ({ children }: PropsWithChildren) =>
children
);
const group = new DisposableGroup();
const entryLogger = new DebugLogger('plugin:entry');
export const evaluatePluginEntry = (pluginName: string) => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const currentImportMap = pluginNestedImportsMap.get(pluginName)!;
const currentImportMap = _pluginNestedImportsMap.get(pluginName)!;
const pluginExports = currentImportMap.get('index.js');
assertExists(pluginExports);
const entryFunction = pluginExports.get('entry');
@@ -403,31 +475,59 @@ export const evaluatePluginEntry = (pluginName: string) => {
register: (part, callback) => {
entryLogger.info(`Registering ${pluginName} to ${part}`);
if (part === 'headerItem') {
rootStore.set(headerItemsAtom, items => ({
rootStore.set(pluginHeaderItemAtom, items => ({
...items,
[pluginName]: callback as CallbackMap['headerItem'],
}));
addCleanup(pluginName, () => {
rootStore.set(pluginHeaderItemAtom, items => {
const newItems = { ...items };
delete newItems[pluginName];
return newItems;
});
});
} else if (part === 'editor') {
rootStore.set(editorItemsAtom, items => ({
rootStore.set(pluginEditorAtom, items => ({
...items,
[pluginName]: callback as CallbackMap['editor'],
}));
} else if (part === 'window') {
rootStore.set(windowItemsAtom, items => ({
...items,
[pluginName]: callback as CallbackMap['window'],
}));
addCleanup(pluginName, () => {
rootStore.set(pluginEditorAtom, items => {
const newItems = { ...items };
delete newItems[pluginName];
return newItems;
});
});
} else if (part === 'setting') {
rootStore.set(settingItemsAtom, items => ({
rootStore.set(pluginSettingAtom, items => ({
...items,
[pluginName]: callback as CallbackMap['setting'],
}));
} else if (part === 'formatBar') {
FormatQuickBar.customElements.push((page, getBlockRange) => {
const div = document.createElement('div');
(callback as CallbackMap['formatBar'])(div, page, getBlockRange);
return div;
addCleanup(pluginName, () => {
rootStore.set(pluginSettingAtom, items => {
const newItems = { ...items };
delete newItems[pluginName];
return newItems;
});
});
} else if (part === 'formatBar') {
const register = (widget: AffineFormatBarWidget) => {
const div = document.createElement('div');
const root = widget.root;
const cleanup = (callback as CallbackMap['formatBar'])(
div,
widget.page,
() => {
return root.selectionManager.value;
}
);
addCleanup(pluginName, () => {
AffineFormatBarWidget.customElements.delete(register);
cleanup();
});
return div;
};
AffineFormatBarWidget.customElements.add(register);
} else {
throw new Error(`Unknown part: ${part}`);
}
@@ -439,5 +539,5 @@ export const evaluatePluginEntry = (pluginName: string) => {
if (typeof cleanup !== 'function') {
throw new Error('Plugin entry must return a function');
}
group.add(cleanup);
addCleanup(pluginName, cleanup);
};

View File

@@ -1,18 +1,16 @@
import { DebugLogger } from '@affine/debug';
import {
registeredPluginAtom,
rootStore,
} from '@toeverything/plugin-infra/atom';
builtinPluginPaths,
enabledPluginAtom,
invokeCleanup,
pluginPackageJson,
} from '@toeverything/infra/__internal__/plugin';
import { loadedPluginNameAtom, rootStore } from '@toeverything/infra/atom';
import { packageJsonOutputSchema } from '@toeverything/infra/type';
import type { z } from 'zod';
import { evaluatePluginEntry, setupPluginCode } from './plugins/setup';
const builtinPluginUrl = new Set([
'/plugins/bookmark',
'/plugins/copilot',
'/plugins/hello-world',
'/plugins/image-preview',
]);
const logger = new DebugLogger('register-plugins');
declare global {
@@ -20,13 +18,50 @@ declare global {
var __pluginPackageJson__: unknown[];
}
globalThis.__pluginPackageJson__ = [];
Object.defineProperty(globalThis, '__pluginPackageJson__', {
get() {
return rootStore.get(pluginPackageJson);
},
});
Promise.all(
[...builtinPluginUrl].map(url => {
rootStore.sub(enabledPluginAtom, () => {
const added = new Set<string>();
const removed = new Set<string>();
const enabledPlugin = new Set(rootStore.get(enabledPluginAtom));
enabledPlugin.forEach(pluginName => {
if (!enabledPluginSet.has(pluginName)) {
added.add(pluginName);
}
});
enabledPluginSet.forEach(pluginName => {
if (!enabledPlugin.has(pluginName)) {
removed.add(pluginName);
}
});
// update plugins
enabledPluginSet.clear();
enabledPlugin.forEach(pluginName => {
enabledPluginSet.add(pluginName);
});
added.forEach(pluginName => {
evaluatePluginEntry(pluginName);
});
removed.forEach(pluginName => {
invokeCleanup(pluginName);
});
});
const enabledPluginSet = new Set(rootStore.get(enabledPluginAtom));
const loadedAssets = new Set<string>();
// we will load all plugins in parallel from builtinPlugins
export const pluginRegisterPromise = Promise.all(
[...builtinPluginPaths].map(url => {
return fetch(`${url}/package.json`)
.then(async res => {
const packageJson = await res.json();
const packageJson = (await res.json()) as z.infer<
typeof packageJsonOutputSchema
>;
packageJsonOutputSchema.parse(packageJson);
const {
name: pluginName,
affinePlugin: {
@@ -35,7 +70,7 @@ Promise.all(
assets,
},
} = packageJson;
globalThis.__pluginPackageJson__.push(packageJson);
rootStore.set(pluginPackageJson, json => [...json, packageJson]);
logger.debug(`registering plugin ${pluginName}`);
logger.debug(`package.json: ${packageJson}`);
if (!release && !runtimeConfig.enablePlugin) {
@@ -43,14 +78,20 @@ Promise.all(
}
const baseURL = url;
const entryURL = `${baseURL}/${core}`;
rootStore.set(registeredPluginAtom, prev => [...prev, pluginName]);
rootStore.set(loadedPluginNameAtom, prev => [...prev, pluginName]);
await setupPluginCode(baseURL, pluginName, core);
console.log(`prepareImports for ${pluginName} done`);
await fetch(entryURL).then(async () => {
if (assets.length > 0) {
await Promise.all(
assets.map(async (asset: string) => {
const loadedAssetName = `${pluginName}_${asset}`;
// todo(himself65): add assets into shadow dom
if (loadedAssets.has(loadedAssetName)) {
return Promise.resolve();
}
if (asset.endsWith('.css')) {
loadedAssets.add(loadedAssetName);
const res = await fetch(`${baseURL}/${asset}`);
if (res.ok) {
// todo: how to put css file into sandbox?
@@ -68,7 +109,12 @@ Promise.all(
})
);
}
evaluatePluginEntry(pluginName);
if (!enabledPluginSet.has(pluginName)) {
logger.debug(`plugin ${pluginName} is not enabled`);
} else {
logger.debug(`plugin ${pluginName} is enabled`);
evaluatePluginEntry(pluginName);
}
});
})
.catch(e => {

View File

@@ -0,0 +1,195 @@
import {
migrateDatabaseBlockTo3,
migrateToSubdoc,
} from '@affine/env/blocksuite';
import { setupGlobal } from '@affine/env/global';
import type {
LocalIndexedDBDownloadProvider,
WorkspaceAdapter,
} from '@affine/env/workspace';
import { WorkspaceFlavour, WorkspaceVersion } from '@affine/env/workspace';
import type { RootWorkspaceMetadata } from '@affine/workspace/atom';
import {
type RootWorkspaceMetadataV2,
rootWorkspacesMetadataAtom,
workspaceAdaptersAtom,
} from '@affine/workspace/atom';
import { globalBlockSuiteSchema } from '@affine/workspace/manager';
import {
migrateLocalBlobStorage,
upgradeV1ToV2,
} from '@affine/workspace/migration';
import { createIndexedDBDownloadProvider } from '@affine/workspace/providers';
import { assertExists } from '@blocksuite/global/utils';
import { rootStore } from '@toeverything/infra/atom';
import { WorkspaceAdapters } from '../adapters/workspace';
async function tryMigration() {
const value = localStorage.getItem('jotai-workspaces');
if (value) {
try {
const metadata = JSON.parse(value) as RootWorkspaceMetadata[];
const promises: Promise<void>[] = [];
const newMetadata = [...metadata];
metadata.forEach(oldMeta => {
if (!('version' in oldMeta)) {
const adapter = WorkspaceAdapters[oldMeta.flavour];
assertExists(adapter);
const upgrade = async () => {
if (oldMeta.flavour !== WorkspaceFlavour.LOCAL) {
console.warn('not supported');
return;
}
const workspace = await adapter.CRUD.get(oldMeta.id);
if (!workspace) {
console.warn('cannot find workspace', oldMeta.id);
return;
}
const doc = workspace.blockSuiteWorkspace.doc;
const provider = createIndexedDBDownloadProvider(
workspace.id,
doc,
{
awareness:
workspace.blockSuiteWorkspace.awarenessStore.awareness,
}
) as LocalIndexedDBDownloadProvider;
provider.sync();
await provider.whenReady;
const newDoc = migrateToSubdoc(doc);
if (doc === newDoc) {
console.log('doc not changed');
return;
}
const newWorkspace = upgradeV1ToV2(workspace);
await migrateDatabaseBlockTo3(
newWorkspace.blockSuiteWorkspace.doc,
globalBlockSuiteSchema
);
const newId = await adapter.CRUD.create(
newWorkspace.blockSuiteWorkspace
);
await adapter.CRUD.delete(workspace as any);
console.log('migrated', oldMeta.id, newId);
const index = newMetadata.findIndex(meta => meta.id === oldMeta.id);
newMetadata[index] = {
...oldMeta,
id: newId,
version: WorkspaceVersion.DatabaseV3,
};
await migrateLocalBlobStorage(workspace.id, newId);
console.log('migrate to v2');
};
// create a new workspace and push it to metadata
promises.push(upgrade());
} else if (oldMeta.version < WorkspaceVersion.DatabaseV3) {
const adapter = WorkspaceAdapters[oldMeta.flavour];
assertExists(adapter);
promises.push(
(async () => {
if (oldMeta.flavour !== WorkspaceFlavour.LOCAL) {
console.warn('not supported');
return;
}
const workspace = await adapter.CRUD.get(oldMeta.id);
if (workspace) {
const provider = createIndexedDBDownloadProvider(
workspace.id,
workspace.blockSuiteWorkspace.doc,
{
awareness:
workspace.blockSuiteWorkspace.awarenessStore.awareness,
}
) as LocalIndexedDBDownloadProvider;
provider.sync();
await provider.whenReady;
await migrateDatabaseBlockTo3(
workspace.blockSuiteWorkspace.doc,
globalBlockSuiteSchema
);
}
const index = newMetadata.findIndex(
meta => meta.id === oldMeta.id
);
newMetadata[index] = {
...oldMeta,
version: WorkspaceVersion.DatabaseV3,
};
console.log('migrate to v3');
})()
);
}
});
await Promise.all(promises)
.then(() => {
console.log('migration done');
})
.catch(e => {
console.error('migration failed', e);
})
.finally(() => {
localStorage.setItem('jotai-workspaces', JSON.stringify(newMetadata));
window.dispatchEvent(new CustomEvent('migration-done'));
window.$migrationDone = true;
});
} catch (e) {
console.error('error when migrating data', e);
}
}
}
function createFirstAppData() {
const createFirst = (): RootWorkspaceMetadataV2[] => {
const Plugins = Object.values(WorkspaceAdapters).sort(
(a, b) => a.loadPriority - b.loadPriority
);
return Plugins.flatMap(Plugin => {
return Plugin.Events['app:init']?.().map(
id =>
<RootWorkspaceMetadataV2>{
id,
flavour: Plugin.flavour,
version: WorkspaceVersion.DatabaseV3,
}
);
}).filter((ids): ids is RootWorkspaceMetadataV2 => !!ids);
};
if (localStorage.getItem('is-first-open') !== null) {
return;
}
const result = createFirst();
console.info('create first workspace', result);
localStorage.setItem('is-first-open', 'false');
rootStore.set(rootWorkspacesMetadataAtom, result);
}
let isSetup = false;
export async function setup() {
if (isSetup) {
console.warn('already setup');
return;
}
isSetup = true;
rootStore.set(
workspaceAdaptersAtom,
WorkspaceAdapters as Record<
WorkspaceFlavour,
WorkspaceAdapter<WorkspaceFlavour>
>
);
console.log('setup global');
setupGlobal();
createFirstAppData();
await tryMigration();
await rootStore.get(rootWorkspacesMetadataAtom);
console.log('setup done');
}

View File

@@ -3,7 +3,6 @@ import { WorkspaceFlavour } from '@affine/env/workspace';
import { getOrCreateWorkspace } from '@affine/workspace/manager';
import type { EditorContainer } from '@blocksuite/editor';
import type { Page } from '@blocksuite/store';
import type React from 'react';
import { useCallback } from 'react';
import { BlockSuiteEditor } from '../../blocksuite/block-suite-editor';
@@ -15,7 +14,7 @@ const blockSuiteWorkspace = getOrCreateWorkspace(
const page = blockSuiteWorkspace.createPage({ id: 'page0' });
const Editor: React.FC = () => {
const Editor = () => {
const onLoad = useCallback((page: Page, editor: EditorContainer) => {
// @ts-expect-error
globalThis.page = page;

View File

@@ -9,7 +9,7 @@ import {
currentPageIdAtom,
currentWorkspaceIdAtom,
rootStore,
} from '@toeverything/plugin-infra/atom';
} from '@toeverything/infra/atom';
import { useAtomValue } from 'jotai/react';
import { Provider } from 'jotai/react';
import type { ErrorInfo, ReactElement, ReactNode } from 'react';

View File

@@ -1,5 +1,4 @@
import {
Button,
Input,
Modal,
ModalCloseButton,
@@ -10,6 +9,11 @@ import {
import { DebugLogger } from '@affine/debug';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { HelpIcon } from '@blocksuite/icons';
import { Button } from '@toeverything/components/button';
import type {
LoadDBFileResult,
SelectDBFileLocationResult,
} from '@toeverything/infra/type';
import { useSetAtom } from 'jotai';
import type { KeyboardEvent } from 'react';
import { useEffect } from 'react';
@@ -77,11 +81,7 @@ const NameWorkspaceContent = ({
onChange={setWorkspaceName}
/>
<div className={style.buttonGroup}>
<Button
data-testid="create-workspace-close-button"
type="primary"
onClick={onClose}
>
<Button data-testid="create-workspace-close-button" onClick={onClose}>
{t.Cancel()}
</Button>
<Button
@@ -134,12 +134,12 @@ const SetDBLocationContent = ({
}
setOpening(true);
(async function () {
const result = await window.apis?.dialog.selectDBFileLocation();
const result: SelectDBFileLocationResult =
await window.apis?.dialog.selectDBFileLocation();
setOpening(false);
if (result?.filePath) {
onConfirmLocation(result.filePath);
} else if (result?.error) {
// @ts-expect-error: result.error is dynamic so the type is unknown
toast(t[result.error]());
}
})().catch(err => {
@@ -267,13 +267,12 @@ export const CreateWorkspaceModal = ({
}
logger.info('load db file');
setStep(undefined);
const result = await window.apis.dialog.loadDBFile();
const result: LoadDBFileResult = await window.apis.dialog.loadDBFile();
if (result.workspaceId && !canceled) {
setAddedId(result.workspaceId);
setStep('set-syncing-mode');
} else if (result.error || result.canceled) {
if (result.error) {
// @ts-expect-error: result.error is dynamic so the type is unknown
toast(t[result.error]());
}
onClose();

View File

@@ -1,9 +1,9 @@
import { IconButton, Modal, ModalWrapper } from '@affine/component';
import { Modal, ModalWrapper, Wrapper } from '@affine/component';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { CloseIcon } from '@blocksuite/icons';
import type React from 'react';
import { Button, IconButton } from '@toeverything/components/button';
import { Content, ContentTitle, Header, StyleButton, StyleTips } from './style';
import { Content, ContentTitle, Header, StyleTips } from './style';
interface EnableAffineCloudModalProps {
open: boolean;
@@ -11,11 +11,11 @@ interface EnableAffineCloudModalProps {
onClose: () => void;
}
export const EnableAffineCloudModal: React.FC<EnableAffineCloudModalProps> = ({
export const EnableAffineCloudModal = ({
onConfirm,
open,
onClose,
}) => {
}: EnableAffineCloudModalProps) => {
const t = useAFFiNEI18N();
return (
@@ -30,19 +30,22 @@ export const EnableAffineCloudModal: React.FC<EnableAffineCloudModalProps> = ({
<ContentTitle>{t['Enable AFFiNE Cloud']()}?</ContentTitle>
<StyleTips>{t['Enable AFFiNE Cloud Description']()}</StyleTips>
{/* <StyleTips>{t('Retain cached cloud data')}</StyleTips> */}
<div>
<StyleButton
<Wrapper width={284} margin="auto">
<Button
data-testid="confirm-enable-affine-cloud-button"
shape="round"
type="primary"
block
onClick={onConfirm}
style={{
marginBottom: '16px',
}}
>
{t['Sign in and Enable']()}
</StyleButton>
<StyleButton shape="round" onClick={onClose}>
</Button>
<Button onClick={onClose} block>
{t['Not now']()}
</StyleButton>
</div>
</Button>
</Wrapper>
</Content>
</ModalWrapper>
</Modal>

View File

@@ -1,4 +1,4 @@
import { Button, styled } from '@affine/component';
import { styled } from '@affine/component';
export const Header = styled('div')({
height: '44px',
@@ -29,12 +29,3 @@ export const StyleTips = styled('div')(() => {
marginTop: '12px',
};
});
export const StyleButton = styled(Button)(() => {
return {
width: '284px',
display: 'block',
margin: 'auto',
marginTop: '16px',
};
});

View File

@@ -1,24 +1,26 @@
import {
type ButtonProps,
Menu,
MenuItem,
type MenuProps,
MenuTrigger,
styled,
} from '@affine/component';
import { LOCALES } from '@affine/i18n';
import { useI18N } from '@affine/i18n';
import type { FC, ReactElement } from 'react';
import type { ButtonProps } from '@toeverything/components/button';
import type { ReactElement } from 'react';
import { useCallback } from 'react';
export const StyledListItem = styled(MenuItem)(() => ({
width: '132px',
height: '38px',
textTransform: 'capitalize',
}));
const LanguageMenuContent: FC<{
interface LanguageMenuContentProps {
currentLanguage?: string;
}> = ({ currentLanguage }) => {
}
const LanguageMenuContent = ({ currentLanguage }: LanguageMenuContentProps) => {
const i18n = useI18N();
const changeLanguage = useCallback(
(event: string) => {
@@ -26,6 +28,7 @@ const LanguageMenuContent: FC<{
},
[i18n]
);
return (
<>
{LOCALES.map(option => {
@@ -47,9 +50,15 @@ const LanguageMenuContent: FC<{
</>
);
};
export const LanguageMenu: FC<{ triggerProps?: ButtonProps }> = ({
interface LanguageMenuProps extends Omit<MenuProps, 'children'> {
triggerProps?: ButtonProps;
}
export const LanguageMenu = ({
triggerProps,
}) => {
...menuProps
}: LanguageMenuProps) => {
const i18n = useI18N();
const currentLanguage = LOCALES.find(item => item.tag === i18n.language);
@@ -66,6 +75,7 @@ export const LanguageMenu: FC<{ triggerProps?: ButtonProps }> = ({
placement="bottom-end"
trigger="click"
disablePortal={true}
{...menuProps}
>
<MenuTrigger
data-testid="language-menu-button"

View File

@@ -1,7 +1,8 @@
import { Button, Input, Modal, ModalCloseButton } from '@affine/component';
import { Input, Modal, ModalCloseButton } from '@affine/component';
import { WorkspaceFlavour } from '@affine/env/workspace';
import { Trans } from '@affine/i18n';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { Button } from '@toeverything/components/button';
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
import { useCallback, useState } from 'react';

View File

@@ -1,34 +1,40 @@
import { SettingRow } from '@affine/component/setting-components';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { ArrowRightSmallIcon } from '@blocksuite/icons';
import { type FC, useState } from 'react';
import { useState } from 'react';
import type { AffineOfficialWorkspace } from '../../../../shared';
import type { WorkspaceSettingDetailProps } from '../index';
import { WorkspaceDeleteModal } from './delete';
import { WorkspaceLeave } from './leave';
export const DeleteLeaveWorkspace: FC<{
interface DeleteLeaveWorkspaceProps {
workspace: AffineOfficialWorkspace;
onDeleteWorkspace: WorkspaceSettingDetailProps['onDeleteWorkspace'];
}> = ({ workspace, onDeleteWorkspace }) => {
}
export const DeleteLeaveWorkspace = ({
workspace,
onDeleteWorkspace,
}: DeleteLeaveWorkspaceProps) => {
const t = useAFFiNEI18N();
// fixme: cloud regression
const isOwner = true;
const [showDelete, setShowDelete] = useState(false);
const [showLeave, setShowLeave] = useState(false);
return (
<>
<SettingRow
name={
<span style={{ color: 'var(--affine-error-color)' }}>
{isOwner
? t['com.affine.settings.workspace.remove']()
? t['com.affine.settings.remove-workspace']()
: t['Leave Workspace']()}
</span>
}
desc={t['com.affine.settings.workspace.remove.message']()}
desc={t['com.affine.settings.remove-workspace-description']()}
style={{ cursor: 'pointer' }}
onClick={() => {
setShowDelete(true);

View File

@@ -1,7 +1,7 @@
import { Modal } from '@affine/component';
import { ModalCloseButton } from '@affine/component';
import { Button } from '@affine/component';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { Button } from '@toeverything/components/button';
import {
StyledButtonContent,
@@ -38,7 +38,6 @@ export const WorkspaceLeave = ({ open, onClose }: WorkspaceDeleteProps) => {
<Button
onClick={handleLeave}
type="error"
shape="circle"
style={{ marginLeft: '24px' }}
>
{t['Leave']()}

View File

@@ -1,8 +1,10 @@
import { Button, toast } from '@affine/component';
import { toast } from '@affine/component';
import { SettingRow } from '@affine/component/setting-components';
import { isDesktop } from '@affine/env/constant';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { type FC, useCallback } from 'react';
import { Button } from '@toeverything/components/button';
import type { SaveDBFileResult } from '@toeverything/infra/type';
import { useCallback } from 'react';
import type { AffineOfficialWorkspace } from '../../../shared';
@@ -27,21 +29,24 @@ async function syncBlobsToSqliteDb(workspace: AffineOfficialWorkspace) {
}
}
export const ExportPanel: FC<{
interface ExportPanelProps {
workspace: AffineOfficialWorkspace;
}> = ({ workspace }) => {
}
export const ExportPanel = ({ workspace }: ExportPanelProps) => {
const workspaceId = workspace.id;
const t = useAFFiNEI18N();
const onExport = useCallback(async () => {
await syncBlobsToSqliteDb(workspace);
const result = await window.apis?.dialog.saveDBFileAs(workspaceId);
const result: SaveDBFileResult =
await window.apis?.dialog.saveDBFileAs(workspaceId);
if (result?.error) {
// @ts-expect-error: result.error is dynamic
toast(t[result.error]());
} else if (!result?.canceled) {
toast(t['Export success']());
}
}, [t, workspace, workspaceId]);
return (
<>
<SettingRow name={t['Export']()} desc={t['Export Description']()}>

View File

@@ -9,7 +9,7 @@ import type {
} from '@affine/env/workspace';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
import { type FC, useMemo } from 'react';
import { useMemo } from 'react';
import { useWorkspace } from '../../../hooks/use-workspace';
import { DeleteLeaveWorkspace } from './delete-leave-workspace';
@@ -18,7 +18,7 @@ import { ProfilePanel } from './profile';
import { PublishPanel } from './publish';
import { StoragePanel } from './storage';
export type WorkspaceSettingDetailProps = {
export interface WorkspaceSettingDetailProps {
workspaceId: string;
onDeleteWorkspace: (id: string) => Promise<void>;
onTransferWorkspace: <
@@ -29,13 +29,13 @@ export type WorkspaceSettingDetailProps = {
to: To,
workspace: WorkspaceRegistry[From]
) => void;
};
}
export const WorkspaceSettingDetail: FC<WorkspaceSettingDetailProps> = ({
export const WorkspaceSettingDetail = ({
workspaceId,
onDeleteWorkspace,
...props
}) => {
}: WorkspaceSettingDetailProps) => {
const t = useAFFiNEI18N();
const workspace = useWorkspace(workspaceId);
const [name] = useBlockSuiteWorkspaceName(workspace.blockSuiteWorkspace);
@@ -59,14 +59,12 @@ export const WorkspaceSettingDetail: FC<WorkspaceSettingDetailProps> = ({
<>
<SettingHeader
title={t[`Workspace Settings with name`]({ name })}
subtitle={t['You can customize your workspace here.']()}
subtitle={t['com.affine.settings.workspace.description']()}
/>
<SettingWrapper title={t['Info']()}>
<SettingRow
name={t['Workspace Profile']()}
desc={t[
'Only an owner can edit the the Workspace avatar and name.Changes will be shown for everyone.'
]()}
desc={t['com.affine.settings.workspace.not-owner']()}
spreadCol={false}
>
<ProfilePanel workspace={workspace} />

View File

@@ -1,37 +1,21 @@
import { IconButton, Input, toast } from '@affine/component';
import { FlexWrapper, Input, toast, Wrapper } from '@affine/component';
import { WorkspaceAvatar } from '@affine/component/workspace-avatar';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { DoneIcon } from '@blocksuite/icons';
import { CameraIcon, DoneIcon } from '@blocksuite/icons';
import { IconButton } from '@toeverything/components/button';
import { useBlockSuiteWorkspaceAvatarUrl } from '@toeverything/hooks/use-block-suite-workspace-avatar-url';
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
import { type FC, useCallback, useState } from 'react';
import { useCallback, useState } from 'react';
import type { AffineOfficialWorkspace } from '../../../shared';
import { Upload } from '../../pure/file-upload';
import * as style from './style.css';
const CameraIcon = () => {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M10.6236 4.25001C10.635 4.25001 10.6467 4.25002 10.6584 4.25002H13.3416C13.3533 4.25002 13.365 4.25001 13.3764 4.25001C13.5609 4.24995 13.7105 4.2499 13.8543 4.26611C14.5981 4.34997 15.2693 4.75627 15.6826 5.38026C15.7624 5.50084 15.83 5.63398 15.9121 5.79586C15.9173 5.80613 15.9226 5.81652 15.9279 5.82703C15.9538 5.87792 15.9679 5.90562 15.9789 5.9261C15.9832 5.9341 15.9857 5.93861 15.9869 5.94065C16.0076 5.97069 16.0435 5.99406 16.0878 5.99905L16.0849 5.99877C16.0849 5.99877 16.0907 5.99918 16.1047 5.99947C16.1286 5.99998 16.1604 6.00002 16.2181 6.00002L17.185 6.00001C17.6577 6 18.0566 5.99999 18.3833 6.02627C18.7252 6.05377 19.0531 6.11364 19.3656 6.27035C19.8402 6.50842 20.2283 6.88944 20.4723 7.36077C20.6336 7.67233 20.6951 7.99944 20.7232 8.33858C20.75 8.66166 20.75 9.05554 20.75 9.51992V16.2301C20.75 16.6945 20.75 17.0884 20.7232 17.4114C20.6951 17.7506 20.6336 18.0777 20.4723 18.3893C20.2283 18.8606 19.8402 19.2416 19.3656 19.4797C19.0531 19.6364 18.7252 19.6963 18.3833 19.7238C18.0566 19.75 17.6578 19.75 17.185 19.75H6.81497C6.34225 19.75 5.9434 19.75 5.61668 19.7238C5.27477 19.6963 4.94688 19.6364 4.63444 19.4797C4.15978 19.2416 3.77167 18.8606 3.52771 18.3893C3.36644 18.0777 3.30494 17.7506 3.27679 17.4114C3.24998 17.0884 3.24999 16.6945 3.25 16.2302V9.51987C3.24999 9.05551 3.24998 8.66164 3.27679 8.33858C3.30494 7.99944 3.36644 7.67233 3.52771 7.36077C3.77167 6.88944 4.15978 6.50842 4.63444 6.27035C4.94688 6.11364 5.27477 6.05377 5.61668 6.02627C5.9434 5.99999 6.34225 6 6.81498 6.00001L7.78191 6.00002C7.83959 6.00002 7.87142 5.99998 7.8953 5.99947C7.90607 5.99924 7.91176 5.99897 7.91398 5.99884C7.95747 5.99343 7.99267 5.9703 8.01312 5.94066C8.01429 5.93863 8.01684 5.93412 8.02113 5.9261C8.0321 5.90561 8.04622 5.87791 8.07206 5.82703C8.07739 5.81653 8.08266 5.80615 8.08787 5.79588C8.17004 5.63397 8.23759 5.50086 8.31745 5.38026C8.73067 4.75627 9.40192 4.34997 10.1457 4.26611C10.2895 4.2499 10.4391 4.24995 10.6236 4.25001ZM10.6584 5.75002C10.422 5.75002 10.3627 5.75114 10.3138 5.75666C10.0055 5.79142 9.73316 5.95919 9.56809 6.20845C9.54218 6.24758 9.51544 6.29761 9.40943 6.50633C9.40611 6.51287 9.40274 6.5195 9.39934 6.52622C9.36115 6.60161 9.31758 6.68761 9.26505 6.76694C8.9964 7.17261 8.56105 7.4354 8.08026 7.48961C7.98625 7.50021 7.89021 7.50011 7.80434 7.50003C7.79678 7.50002 7.7893 7.50002 7.78191 7.50002H6.84445C6.33444 7.50002 5.99634 7.50058 5.73693 7.52144C5.48594 7.54163 5.37478 7.57713 5.30693 7.61115C5.11257 7.70864 4.95675 7.86306 4.85983 8.05029C4.82733 8.11308 4.79194 8.21816 4.77165 8.46266C4.7506 8.71626 4.75 9.0474 4.75 9.55001V16.2C4.75 16.7026 4.7506 17.0338 4.77165 17.2874C4.79194 17.5319 4.82733 17.6369 4.85983 17.6997C4.95675 17.887 5.11257 18.0414 5.30693 18.1389C5.37478 18.1729 5.48594 18.2084 5.73693 18.2286C5.99634 18.2494 6.33444 18.25 6.84445 18.25H17.1556C17.6656 18.25 18.0037 18.2494 18.2631 18.2286C18.5141 18.2084 18.6252 18.1729 18.6931 18.1389C18.8874 18.0414 19.0433 17.887 19.1402 17.6997C19.1727 17.6369 19.2081 17.5319 19.2283 17.2874C19.2494 17.0338 19.25 16.7026 19.25 16.2V9.55001C19.25 9.0474 19.2494 8.71626 19.2283 8.46266C19.2081 8.21816 19.1727 8.11308 19.1402 8.05029C19.0433 7.86306 18.8874 7.70864 18.6931 7.61115C18.6252 7.57713 18.5141 7.54163 18.2631 7.52144C18.0037 7.50058 17.6656 7.50002 17.1556 7.50002H16.2181C16.2107 7.50002 16.2032 7.50002 16.1957 7.50003C16.1098 7.50011 16.0138 7.50021 15.9197 7.48961C15.4389 7.4354 15.0036 7.17261 14.735 6.76694C14.6824 6.68761 14.6389 6.60163 14.6007 6.52622C14.5973 6.5195 14.5939 6.51287 14.5906 6.50633C14.4846 6.29763 14.4578 6.24758 14.4319 6.20846C14.2668 5.95919 13.9945 5.79142 13.6862 5.75666C13.6373 5.75114 13.578 5.75002 13.3416 5.75002H10.6584ZM12 11C10.9303 11 10.0833 11.8506 10.0833 12.875C10.0833 13.8995 10.9303 14.75 12 14.75C13.0697 14.75 13.9167 13.8995 13.9167 12.875C13.9167 11.8506 13.0697 11 12 11ZM8.58333 12.875C8.58333 11 10.1242 9.50002 12 9.50002C13.8758 9.50002 15.4167 11 15.4167 12.875C15.4167 14.7501 13.8758 16.25 12 16.25C10.1242 16.25 8.58333 14.7501 8.58333 12.875Z"
fill="white"
/>
</svg>
);
};
export const ProfilePanel: FC<{
interface ProfilePanelProps {
workspace: AffineOfficialWorkspace;
}> = ({ workspace }) => {
}
export const ProfilePanel = ({ workspace }: ProfilePanelProps) => {
const t = useAFFiNEI18N();
const [, update] = useBlockSuiteWorkspaceAvatarUrl(
@@ -71,32 +55,35 @@ export const ProfilePanel: FC<{
</>
</Upload>
</div>
<div className={style.profileHandlerWrapper}>
<Input
width={280}
height={32}
defaultValue={input}
data-testid="workspace-name-input"
placeholder={t['Workspace Name']()}
maxLength={64}
minLength={0}
onChange={setInput}
/>
{input === workspace.blockSuiteWorkspace.meta.name ? null : (
<IconButton
data-testid="save-workspace-name"
onClick={() => {
handleUpdateWorkspaceName(input);
}}
active={true}
style={{
marginLeft: '12px',
}}
>
<DoneIcon />
</IconButton>
)}
</div>
<Wrapper marginLeft={20}>
<div className={style.label}>{t['Workspace Name']()}</div>
<FlexWrapper alignItems="center" flexGrow="1">
<Input
width={280}
height={32}
defaultValue={input}
data-testid="workspace-name-input"
placeholder={t['Workspace Name']()}
maxLength={64}
minLength={0}
onChange={setInput}
/>
{input === workspace.blockSuiteWorkspace.meta.name ? null : (
<IconButton
data-testid="save-workspace-name"
onClick={() => {
handleUpdateWorkspaceName(input);
}}
active={true}
style={{
marginLeft: '12px',
}}
>
<DoneIcon />
</IconButton>
)}
</FlexWrapper>
</Wrapper>
</div>
);
};

View File

@@ -1,4 +1,4 @@
import { Button, FlexWrapper, Switch, Tooltip } from '@affine/component';
import { FlexWrapper, Switch, Tooltip } from '@affine/component';
import { SettingRow } from '@affine/component/setting-components';
import { Unreachable } from '@affine/env/constant';
import type {
@@ -7,8 +7,8 @@ import type {
} from '@affine/env/workspace';
import { WorkspaceFlavour } from '@affine/env/workspace';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { Button } from '@toeverything/components/button';
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
import type { FC } from 'react';
import { useCallback, useEffect, useState } from 'react';
import type { AffineOfficialWorkspace } from '../../../shared';
@@ -18,26 +18,20 @@ import { TmpDisableAffineCloudModal } from '../tmp-disable-affine-cloud-modal';
import type { WorkspaceSettingDetailProps } from './index';
import * as style from './style.css';
export type PublishPanelProps = Omit<
WorkspaceSettingDetailProps,
'workspaceId'
> & {
export interface PublishPanelProps
extends Omit<WorkspaceSettingDetailProps, 'workspaceId'> {
workspace: AffineOfficialWorkspace;
};
export type PublishPanelLocalProps = Omit<
WorkspaceSettingDetailProps,
'workspaceId'
> & {
}
export interface PublishPanelLocalProps
extends Omit<WorkspaceSettingDetailProps, 'workspaceId'> {
workspace: LocalWorkspace;
};
export type PublishPanelAffineProps = Omit<
WorkspaceSettingDetailProps,
'workspaceId'
> & {
}
export interface PublishPanelAffineProps
extends Omit<WorkspaceSettingDetailProps, 'workspaceId'> {
workspace: AffineCloudWorkspace;
};
}
const PublishPanelAffine: FC<PublishPanelAffineProps> = props => {
const PublishPanelAffine = (props: PublishPanelAffineProps) => {
const { workspace } = props;
const t = useAFFiNEI18N();
// const toggleWorkspacePublish = useToggleWorkspacePublish(workspace);
@@ -57,6 +51,7 @@ const PublishPanelAffine: FC<PublishPanelAffineProps> = props => {
await navigator.clipboard.writeText(shareUrl);
toast(t['Copied link to clipboard']());
}, [shareUrl, t]);
return (
<>
<SettingRow
@@ -90,13 +85,16 @@ const PublishPanelAffine: FC<PublishPanelAffineProps> = props => {
);
};
const FakePublishPanelAffine: FC<{
interface FakePublishPanelAffineProps {
workspace: AffineOfficialWorkspace;
}> = () => {
}
const FakePublishPanelAffine = (_props: FakePublishPanelAffineProps) => {
const t = useAFFiNEI18N();
return (
<Tooltip
content={t['com.affine.settings.workspace.publish.local-tooltip']()}
content={t['com.affine.settings.workspace.publish-tooltip']()}
placement="top"
>
<div className={style.fakeWrapper}>
@@ -107,10 +105,11 @@ const FakePublishPanelAffine: FC<{
</Tooltip>
);
};
const PublishPanelLocal: FC<PublishPanelLocalProps> = ({
const PublishPanelLocal = ({
workspace,
onTransferWorkspace,
}) => {
}: PublishPanelLocalProps) => {
const t = useAFFiNEI18N();
const [name] = useBlockSuiteWorkspaceName(workspace.blockSuiteWorkspace);
@@ -166,7 +165,7 @@ const PublishPanelLocal: FC<PublishPanelLocalProps> = ({
);
};
export const PublishPanel: FC<PublishPanelProps> = props => {
export const PublishPanel = (props: PublishPanelProps) => {
if (props.workspace.flavour === WorkspaceFlavour.AFFINE_CLOUD) {
return <PublishPanelAffine {...props} workspace={props.workspace} />;
} else if (props.workspace.flavour === WorkspaceFlavour.LOCAL) {

View File

@@ -1,8 +1,10 @@
import { Button, FlexWrapper, toast, Tooltip } from '@affine/component';
import { FlexWrapper, toast, Tooltip } from '@affine/component';
import { SettingRow } from '@affine/component/setting-components';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { Button } from '@toeverything/components/button';
import type { MoveDBFileResult } from '@toeverything/infra/type';
import { useMemo } from 'react';
import { type FC, useCallback, useEffect, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import type { AffineOfficialWorkspace } from '../../../shared';
import * as style from './style.css';
@@ -30,9 +32,11 @@ const useDBFileSecondaryPath = (workspaceId: string) => {
return path;
};
export const StoragePanel: FC<{
interface StoragePanelProps {
workspace: AffineOfficialWorkspace;
}> = ({ workspace }) => {
}
export const StoragePanel = ({ workspace }: StoragePanelProps) => {
const workspaceId = workspace.id;
const t = useAFFiNEI18N();
const secondaryPath = useDBFileSecondaryPath(workspaceId);
@@ -51,11 +55,10 @@ export const StoragePanel: FC<{
setMoveToInProgress(true);
window.apis?.dialog
.moveDBFile(workspaceId)
.then(result => {
.then((result: MoveDBFileResult) => {
if (!result?.error && !result?.canceled) {
toast(t['Move folder success']());
} else if (result?.error) {
// @ts-expect-error: result.error is dynamic
toast(t[result.error]());
}
})

View File

@@ -5,12 +5,6 @@ export const profileWrapper = style({
alignItems: 'flex-end',
marginTop: '12px',
});
export const profileHandlerWrapper = style({
flexGrow: '1',
display: 'flex',
alignItems: 'center',
marginLeft: '20px',
});
export const avatarWrapper = style({
width: '56px',
@@ -39,6 +33,8 @@ globalStyle(`${avatarWrapper} .camera-icon-wrapper`, {
alignItems: 'center',
backgroundColor: 'rgba(60, 61, 63, 0.5)',
zIndex: '1',
color: 'var(--affine-white)',
fontSize: '24px',
});
export const urlButton = style({
@@ -71,3 +67,9 @@ export const fakeWrapper = style({
},
},
});
export const label = style({
fontSize: 'var(--affine-font-xs)',
color: 'var(--affine-text-secondary-color)',
marginBottom: '5px',
});

View File

@@ -1,12 +1,11 @@
import { TourModal } from '@affine/component/tour-modal';
import { useAtom } from 'jotai';
import type { FC } from 'react';
import { memo, useCallback } from 'react';
import { openOnboardingModalAtom } from '../../atoms';
import { guideOnboardingAtom } from '../../atoms/guide';
export const OnboardingModal: FC = memo(function OnboardingModal() {
export const OnboardingModal = memo(function OnboardingModal() {
const [open, setOpen] = useAtom(openOnboardingModalAtom);
const [guideOpen, setShowOnboarding] = useAtom(guideOnboardingAtom);
const onCloseTourModal = useCallback(() => {

View File

@@ -58,7 +58,7 @@ export const AboutAffine = () => {
</SettingRow>
<SettingRow
name={t[`Discover what's new`]()}
desc={t['View the AFFiNE Changelog.']()}
desc={t['Changelog description']()}
style={{ cursor: 'pointer' }}
onClick={() => {
window.open(runtimeConfig.changelogUrl, '_blank');

View File

@@ -1,6 +1,6 @@
import { Menu, MenuItem, MenuTrigger } from '@affine/component';
import dayjs from 'dayjs';
import { type FC, useCallback } from 'react';
import { useCallback } from 'react';
import {
dateFormatOptions,
@@ -8,10 +8,15 @@ import {
useAppSetting,
} from '../../../../../atoms/settings';
const DateFormatMenuContent: FC<{
interface DateFormatMenuContentProps {
currentOption: DateFormats;
onSelect: (option: DateFormats) => void;
}> = ({ onSelect, currentOption }) => {
}
const DateFormatMenuContent = ({
onSelect,
currentOption,
}: DateFormatMenuContentProps) => {
return (
<>
{dateFormatOptions.map(option => {
@@ -30,6 +35,7 @@ const DateFormatMenuContent: FC<{
</>
);
};
export const DateFormatSetting = () => {
const [appearanceSettings, setAppSettings] = useAppSetting();
const handleSelect = useCallback(
@@ -38,6 +44,7 @@ export const DateFormatSetting = () => {
},
[setAppSettings]
);
return (
<Menu
content={

View File

@@ -35,10 +35,10 @@ export const ThemeSettings = () => {
<RadioButton value="system" data-testid="system-theme-trigger">
{t['system']()}
</RadioButton>
<RadioButton bold={true} value="light" data-testid="light-theme-trigger">
<RadioButton value="light" data-testid="light-theme-trigger">
{t['light']()}
</RadioButton>
<RadioButton bold={true} value="dark" data-testid="dark-theme-trigger">
<RadioButton value="dark" data-testid="dark-theme-trigger">
{t['dark']()}
</RadioButton>
</RadioButtonGroup>
@@ -63,7 +63,6 @@ const FontFamilySettings = () => {
return (
<RadioButton
key={key}
bold={true}
value={key}
data-testid="system-font-style-trigger"
style={{
@@ -110,16 +109,28 @@ export const AppearanceSettings = () => {
</SettingRow>
<SettingRow
name={t['Display Language']()}
desc={t['Select the language for the interface.']()}
desc={t['com.affine.settings.appearance.language-description']()}
>
<div className={settingWrapper}>
<LanguageMenu />
<LanguageMenu
triggerContainerStyle={{ width: '100%' }}
triggerProps={{
style: {
width: '100%',
justifyContent: 'space-between',
fontWeight: 600,
padding: '0 10px',
},
}}
/>
</div>
</SettingRow>
{environment.isDesktop ? (
<SettingRow
name={t['Client Border Style']()}
desc={t['Customize the appearance of the client.']()}
desc={t[
'com.affine.settings.appearance.border-style-description'
]()}
>
<Switch
checked={appSettings.clientBorder}
@@ -130,7 +141,7 @@ export const AppearanceSettings = () => {
<SettingRow
name={t['Full width Layout']()}
desc={t['Maximum display of content within a page.']()}
desc={t['com.affine.settings.appearance.full-width-description']()}
>
<Switch
data-testid="full-width-layout-trigger"
@@ -141,7 +152,9 @@ export const AppearanceSettings = () => {
{runtimeConfig.enableNewSettingUnstableApi && environment.isDesktop ? (
<SettingRow
name={t['Window frame style']()}
desc={t['Customize appearance of Windows Client.']()}
desc={t[
'com.affine.settings.appearance.window-frame-description'
]()}
>
<RadioButtonGroup
className={settingWrapper}
@@ -166,7 +179,7 @@ export const AppearanceSettings = () => {
<SettingWrapper title={t['Date']()}>
<SettingRow
name={t['Date Format']()}
desc={t['Customize your date style.']()}
desc={t['com.affine.settings.appearance.date-format-description']()}
>
<div className={settingWrapper}>
<DateFormatSetting />
@@ -174,7 +187,7 @@ export const AppearanceSettings = () => {
</SettingRow>
<SettingRow
name={t['Start Week On Monday']()}
desc={t['By default, the week starts on Sunday.']()}
desc={t['com.affine.settings.appearance.start-week-description']()}
>
<Switch
checked={appSettings.startWeekOnMonday}
@@ -187,8 +200,8 @@ export const AppearanceSettings = () => {
{environment.isDesktop ? (
<SettingWrapper title={t['Sidebar']()}>
<SettingRow
name={t['com.affine.settings.appearance.sidebar.noise']()}
desc={t['com.affine.settings.appearance.sidebar.noise.message']()}
name={t['com.affine.settings.noise-style']()}
desc={t['com.affine.settings.noise-style-description']()}
>
<Switch
checked={appSettings.enableNoisyBackground}
@@ -198,10 +211,8 @@ export const AppearanceSettings = () => {
/>
</SettingRow>
<SettingRow
name={t['com.affine.settings.appearance.sidebar.translucent']()}
desc={t[
'com.affine.settings.appearance.sidebar.translucent.message'
]()}
name={t['com.affine.settings.translucent-style']()}
desc={t['com.affine.settings.translucent-style-description']()}
>
<Switch
checked={appSettings.enableBlurBackground}

View File

@@ -1,11 +1,11 @@
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import {
AiIcon,
AppearanceIcon,
InformationIcon,
KeyboardIcon,
PluginIcon,
} from '@blocksuite/icons';
import type { FC, SVGProps } from 'react';
import type { ReactElement, SVGProps } from 'react';
import { AboutAffine } from './about';
import { AppearanceSettings } from './appearance';
@@ -18,15 +18,18 @@ export type GeneralSettingKeys =
| 'plugins'
| 'about';
export type GeneralSettingList = {
interface GeneralSettingListItem {
key: GeneralSettingKeys;
title: string;
icon: FC<SVGProps<SVGSVGElement>>;
icon: (props: SVGProps<SVGSVGElement>) => ReactElement;
testId: string;
}[];
}
export type GeneralSettingList = GeneralSettingListItem[];
export const useGeneralSettingList = (): GeneralSettingList => {
const t = useAFFiNEI18N();
return [
{
key: 'appearance',
@@ -43,7 +46,7 @@ export const useGeneralSettingList = (): GeneralSettingList => {
{
key: 'plugins',
title: 'Plugins',
icon: AiIcon,
icon: PluginIcon,
testId: 'plugins-panel-trigger',
},
{
@@ -55,11 +58,11 @@ export const useGeneralSettingList = (): GeneralSettingList => {
];
};
export const GeneralSetting = ({
generalKey,
}: {
interface GeneralSettingProps {
generalKey: GeneralSettingKeys;
}) => {
}
export const GeneralSetting = ({ generalKey }: GeneralSettingProps) => {
switch (generalKey) {
case 'shortcuts':
return <Shortcuts />;

View File

@@ -1,55 +1,96 @@
import { Switch } from '@affine/component';
import { SettingHeader } from '@affine/component/setting-components';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import type { CallbackMap } from '@affine/sdk/entry';
import {
registeredPluginAtom,
settingItemsAtom,
} from '@toeverything/plugin-infra/atom';
import { useAtomValue } from 'jotai';
import type { FC, ReactNode } from 'react';
import { useRef } from 'react';
addCleanup,
enabledPluginAtom,
pluginPackageJson,
pluginSettingAtom,
} from '@toeverything/infra/__internal__/plugin';
import { loadedPluginNameAtom } from '@toeverything/infra/atom';
import type { packageJsonOutputSchema } from '@toeverything/infra/type';
import { useAtom, useAtomValue } from 'jotai/react';
import { startTransition, useCallback, useMemo } from 'react';
import type { z } from 'zod';
import { pluginItem } from './style.css';
import { pluginItemStyle } from './style.css';
const PluginSettingWrapper: FC<{
id: string;
title?: ReactNode;
}> = ({ title, id }) => {
const Setting = useAtomValue(settingItemsAtom)[id];
const disposeRef = useRef<(() => void) | null>(null);
type PluginItemProps = {
json: z.infer<typeof packageJsonOutputSchema>;
};
type PluginSettingDetailProps = {
pluginName: string;
create: CallbackMap['setting'];
};
const PluginSettingDetail = ({
pluginName,
create,
}: PluginSettingDetailProps) => {
return (
<div>
{title ? <div className="title">{title}</div> : null}
<div
ref={ref => {
if (ref && Setting) {
setTimeout(() => {
disposeRef.current = Setting(ref);
});
} else if (ref === null) {
setTimeout(() => {
disposeRef.current?.();
});
<div
ref={useCallback(
(ref: HTMLDivElement | null) => {
if (ref) {
const cleanup = create(ref);
addCleanup(pluginName, cleanup);
}
}}
/>
},
[pluginName, create]
)}
/>
);
};
const PluginItem = ({ json }: PluginItemProps) => {
const [plugins, setEnabledPlugins] = useAtom(enabledPluginAtom);
const checked = useMemo(
() => plugins.includes(json.name),
[json.name, plugins]
);
const create = useAtomValue(pluginSettingAtom)[json.name];
return (
<div className={pluginItemStyle} key={json.name}>
<div>
{json.name}
<Switch
checked={checked}
onChange={useCallback(
(checked: boolean) => {
startTransition(() => {
setEnabledPlugins(plugins => {
if (checked) {
return [...plugins, json.name];
} else {
return plugins.filter(plugin => plugin !== json.name);
}
});
});
},
[json.name, setEnabledPlugins]
)}
/>
</div>
<div>{json.description}</div>
{create && <PluginSettingDetail pluginName={json.name} create={create} />}
</div>
);
};
export const Plugins = () => {
const t = useAFFiNEI18N();
const allowedPlugins = useAtomValue(registeredPluginAtom);
const loadedPlugins = useAtomValue(loadedPluginNameAtom);
return (
<>
<SettingHeader
title={'Plugins'}
subtitle={allowedPlugins.length === 0 && t['None yet']()}
subtitle={loadedPlugins.length === 0 && t['None yet']()}
data-testid="plugins-title"
/>
{allowedPlugins.map(plugin => (
<div className={pluginItem} key={plugin}>
<PluginSettingWrapper key={plugin} id={plugin} title={plugin} />
</div>
{useAtomValue(pluginPackageJson).map(json => (
<PluginItem json={json} key={json.name} />
))}
</>
);

View File

@@ -1,6 +1,6 @@
import { style } from '@vanilla-extract/css';
export const settingWrapper = style({
export const settingWrapperStyle = style({
flexGrow: 1,
display: 'flex',
justifyContent: 'flex-end',
@@ -8,7 +8,7 @@ export const settingWrapper = style({
maxWidth: '250px',
});
export const pluginItem = style({
export const pluginItemStyle = style({
borderBottom: '1px solid var(--affine-border-color)',
transition: '0.3s',
padding: '24px 8px',

View File

@@ -1,11 +1,11 @@
import {
SettingModal as SettingModalBase,
type SettingModalProps,
type SettingModalProps as SettingModalBaseProps,
WorkspaceDetailSkeleton,
} from '@affine/component/setting-components';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { ContactWithUsIcon } from '@blocksuite/icons';
import type React from 'react';
import { useCallback } from 'react';
import { Suspense, useCallback } from 'react';
import { AccountSetting } from './account-setting';
import {
@@ -14,25 +14,29 @@ import {
useGeneralSettingList,
} from './general-setting';
import { SettingSidebar } from './setting-sidebar';
import { settingContent } from './style.css';
import { footerIconWrapper, settingContent } from './style.css';
import { WorkspaceSetting } from './workspace-setting';
type ActiveTab = GeneralSettingKeys | 'workspace' | 'account';
export type SettingProps = {
export interface SettingProps {
activeTab: ActiveTab;
workspaceId: string | null;
onSettingClick: (params: {
activeTab: ActiveTab;
workspaceId: string | null;
}) => void;
};
export const SettingModal: React.FC<SettingModalProps & SettingProps> = ({
}
type SettingModalProps = SettingModalBaseProps & SettingProps;
export const SettingModal = ({
open,
setOpen,
activeTab = 'appearance',
workspaceId = null,
onSettingClick,
}) => {
}: SettingModalProps) => {
const t = useAFFiNEI18N();
const generalSettingList = useGeneralSettingList();
@@ -74,7 +78,9 @@ export const SettingModal: React.FC<SettingModalProps & SettingProps> = ({
<div className="wrapper">
<div className="content">
{activeTab === 'workspace' && workspaceId ? (
<WorkspaceSetting key={workspaceId} workspaceId={workspaceId} />
<Suspense fallback={<WorkspaceDetailSkeleton />}>
<WorkspaceSetting key={workspaceId} workspaceId={workspaceId} />
</Suspense>
) : null}
{generalSettingList.find(v => v.key === activeTab) ? (
<GeneralSetting generalKey={activeTab as GeneralSettingKeys} />
@@ -82,15 +88,15 @@ export const SettingModal: React.FC<SettingModalProps & SettingProps> = ({
{activeTab === 'account' ? <AccountSetting /> : null}
</div>
<div className="footer">
<ContactWithUsIcon />
<div className={footerIconWrapper}>
<ContactWithUsIcon />
</div>
<a
href="https://community.affine.pro/home"
target="_blank"
rel="noreferrer"
>
{t[
'Need more customization options? You can suggest them to us in the community.'
]()}
{t['com.affine.settings.suggestion']()}
</a>
</div>
</div>

View File

@@ -1,4 +1,4 @@
import { Tooltip } from '@affine/component';
import { ScrollableContainer, Tooltip } from '@affine/component';
import {
WorkspaceListItemSkeleton,
WorkspaceListSkeleton,
@@ -8,10 +8,9 @@ import { useAFFiNEI18N } from '@affine/i18n/hooks';
import type { RootWorkspaceMetadata } from '@affine/workspace/atom';
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
import { useStaticBlockSuiteWorkspace } from '@toeverything/plugin-infra/__internal__/react';
import { useStaticBlockSuiteWorkspace } from '@toeverything/infra/__internal__/react';
import clsx from 'clsx';
import { useAtomValue } from 'jotai';
import type { FC } from 'react';
import { Suspense } from 'react';
import { useCurrentWorkspace } from '../../../../hooks/current/use-current-workspace';
@@ -28,21 +27,24 @@ import {
sidebarTitle,
} from './style.css';
export const SettingSidebar: FC<{
interface SettingSidebarProps {
generalSettingList: GeneralSettingList;
onGeneralSettingClick: (key: GeneralSettingKeys) => void;
onWorkspaceSettingClick: (workspaceId: string) => void;
selectedWorkspaceId: string | null;
selectedGeneralKey: string | null;
onAccountSettingClick: () => void;
}> = ({
}
export const SettingSidebar = ({
generalSettingList,
onGeneralSettingClick,
onWorkspaceSettingClick,
selectedWorkspaceId,
selectedGeneralKey,
}) => {
}: SettingSidebarProps) => {
const t = useAFFiNEI18N();
return (
<div className={settingSlideBar} data-testid="settings-sidebar">
<div className={sidebarTitle}>{t['Settings']()}</div>
@@ -76,20 +78,27 @@ export const SettingSidebar: FC<{
</div>
<div className={clsx(sidebarItemsWrapper, 'scroll')}>
<Suspense fallback={<WorkspaceListSkeleton />}>
<WorkspaceList
onWorkspaceSettingClick={onWorkspaceSettingClick}
selectedWorkspaceId={selectedWorkspaceId}
/>
<ScrollableContainer>
<WorkspaceList
onWorkspaceSettingClick={onWorkspaceSettingClick}
selectedWorkspaceId={selectedWorkspaceId}
/>
</ScrollableContainer>
</Suspense>
</div>
</div>
);
};
export const WorkspaceList: FC<{
interface WorkspaceListProps {
onWorkspaceSettingClick: (workspaceId: string) => void;
selectedWorkspaceId: string | null;
}> = ({ onWorkspaceSettingClick, selectedWorkspaceId }) => {
}
export const WorkspaceList = ({
onWorkspaceSettingClick,
selectedWorkspaceId,
}: WorkspaceListProps) => {
const workspaces = useAtomValue(rootWorkspacesMetadataAtom);
const [currentWorkspace] = useCurrentWorkspace();
return (
@@ -112,19 +121,22 @@ export const WorkspaceList: FC<{
);
};
interface WorkspaceListItemProps {
meta: RootWorkspaceMetadata;
onClick: () => void;
isCurrent: boolean;
isActive: boolean;
}
const WorkspaceListItem = ({
meta,
onClick,
isCurrent,
isActive,
}: {
meta: RootWorkspaceMetadata;
onClick: () => void;
isCurrent: boolean;
isActive: boolean;
}) => {
}: WorkspaceListItemProps) => {
const workspace = useStaticBlockSuiteWorkspace(meta.id);
const [workspaceName] = useBlockSuiteWorkspaceName(workspace);
return (
<div
className={clsx(sidebarSelectItem, { active: isActive })}

View File

@@ -4,7 +4,7 @@ export const settingSlideBar = style({
width: '25%',
maxWidth: '242px',
background: 'var(--affine-background-secondary-color)',
padding: '20px 16px',
padding: '20px 0px',
height: '100%',
flexShrink: 0,
display: 'flex',
@@ -15,14 +15,14 @@ export const sidebarTitle = style({
fontSize: 'var(--affine-font-h-6)',
fontWeight: '600',
lineHeight: 'var(--affine-line-height)',
paddingLeft: '8px',
padding: '0px 16px 0px 24px',
});
export const sidebarSubtitle = style({
fontSize: 'var(--affine-font-sm)',
lineHeight: 'var(--affine-line-height)',
color: 'var(--affine-text-secondary-color)',
paddingLeft: '8px',
padding: '0px 16px 0px 24px',
marginTop: '20px',
marginBottom: '4px',
display: 'flex',
@@ -34,7 +34,7 @@ export const sidebarItemsWrapper = style({
selectors: {
'&.scroll': {
flexGrow: 1,
overflowY: 'auto',
overflowY: 'hidden',
},
},
});
@@ -42,9 +42,9 @@ export const sidebarItemsWrapper = style({
export const sidebarSelectItem = style({
display: 'flex',
alignItems: 'center',
padding: '0 8px',
margin: '0px 16px 4px 16px',
padding: '0px 8px',
height: '30px',
marginBottom: '4px',
fontSize: 'var(--affine-font-sm)',
borderRadius: '8px',
cursor: 'pointer',

View File

@@ -28,15 +28,18 @@ globalStyle(`${settingContent} .footer`, {
marginTop: '-80px',
fontSize: 'var(--affine-font-sm)',
display: 'flex',
minHeight: '100px',
});
globalStyle(`${settingContent} .footer a`, {
color: 'var(--affine-text-primary-color)',
lineHeight: 'normal',
});
globalStyle(`${settingContent} .footer > svg`, {
export const footerIconWrapper = style({
fontSize: 'var(--affine-font-base)',
color: 'var(--affine-icon-color)',
marginRight: '12px',
marginTop: '2px',
height: '19px',
display: 'flex',
alignItems: 'center',
});

View File

@@ -1,7 +1,6 @@
import { WorkspaceDetailSkeleton } from '@affine/component/setting-components';
import { usePassiveWorkspaceEffect } from '@toeverything/plugin-infra/__internal__/react';
import { usePassiveWorkspaceEffect } from '@toeverything/infra/__internal__/react';
import { useSetAtom } from 'jotai';
import { Suspense, useCallback } from 'react';
import { useCallback } from 'react';
import { getUIAdapter } from '../../../../adapters/workspace';
import { openSettingModalAtom } from '../../../../atoms';
@@ -33,12 +32,10 @@ export const WorkspaceSetting = ({ workspaceId }: { workspaceId: string }) => {
const onTransformWorkspace = useOnTransformWorkspace();
return (
<Suspense fallback={<WorkspaceDetailSkeleton />}>
<NewSettingsDetail
onTransformWorkspace={onTransformWorkspace}
onDeleteWorkspace={onDeleteWorkspace}
currentWorkspaceId={workspaceId}
/>
</Suspense>
<NewSettingsDetail
onTransformWorkspace={onTransformWorkspace}
onDeleteWorkspace={onDeleteWorkspace}
currentWorkspaceId={workspaceId}
/>
);
};

View File

@@ -1,8 +1,8 @@
import { Empty, IconButton, Modal, ModalWrapper } from '@affine/component';
import { Empty, Modal, ModalWrapper } from '@affine/component';
import { Trans } from '@affine/i18n';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { CloseIcon } from '@blocksuite/icons';
import type React from 'react';
import { IconButton } from '@toeverything/components/button';
import {
Content,
@@ -19,10 +19,12 @@ interface TmpDisableAffineCloudModalProps {
onClose: () => void;
}
export const TmpDisableAffineCloudModal: React.FC<
TmpDisableAffineCloudModalProps
> = ({ open, onClose }) => {
export const TmpDisableAffineCloudModal = ({
open,
onClose,
}: TmpDisableAffineCloudModalProps) => {
const t = useAFFiNEI18N();
return (
<Modal
data-testid="disable-affine-cloud-modal"
@@ -66,7 +68,7 @@ export const TmpDisableAffineCloudModal: React.FC<
/>
</StyleImage>
<StyleButtonContainer>
<StyleButton shape="round" type="primary" onClick={onClose}>
<StyleButton type="primary" onClick={onClose}>
{t['Got it']()}
</StyleButton>
</StyleButtonContainer>

View File

@@ -1,4 +1,5 @@
import { Button, displayFlex, styled } from '@affine/component';
import { displayFlex, styled } from '@affine/component';
import { Button } from '@toeverything/components/button';
export const Header = styled('div')({
height: '44px',

View File

@@ -1,19 +1,21 @@
import { IconButton, Modal, ModalWrapper } from '@affine/component';
import { Modal, ModalWrapper } from '@affine/component';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { CloseIcon } from '@blocksuite/icons';
import type React from 'react';
import { IconButton } from '@toeverything/components/button';
import { Content, ContentTitle, Header, StyleButton, StyleTips } from './style';
export type TransformWorkspaceToAffineModalProps = {
export interface TransformWorkspaceToAffineModalProps {
open: boolean;
onClose: () => void;
onConform: () => void;
};
}
export const TransformWorkspaceToAffineModal: React.FC<
TransformWorkspaceToAffineModalProps
> = ({ open, onClose, onConform }) => {
export const TransformWorkspaceToAffineModal = ({
open,
onClose,
onConform,
}: TransformWorkspaceToAffineModalProps) => {
const t = useAFFiNEI18N();
return (
@@ -35,7 +37,6 @@ export const TransformWorkspaceToAffineModal: React.FC<
<div>
<StyleButton
data-testid="confirm-enable-cloud-button"
shape="round"
type="primary"
onClick={onConform}
>

View File

@@ -1,4 +1,5 @@
import { Button, styled } from '@affine/component';
import { styled } from '@affine/component';
import { Button } from '@toeverything/components/button';
export const Header = styled('div')({
height: '44px',

View File

@@ -0,0 +1,148 @@
import { WorkspaceFlavour } from '@affine/env/workspace';
import {
useBlockSuitePageMeta,
usePageMetaHelper,
} from '@toeverything/hooks/use-block-suite-page-meta';
import {
type FocusEvent,
type InputHTMLAttributes,
type KeyboardEvent,
useCallback,
useEffect,
useState,
} from 'react';
import type { AffineOfficialWorkspace } from '../../../shared';
import { EditorModeSwitch } from '../block-suite-mode-switch';
import { PageMenu } from './operation-menu';
import * as styles from './styles.css';
export interface BlockSuiteHeaderTitleProps {
workspace: AffineOfficialWorkspace;
pageId: string;
}
const EditableTitle = ({
value,
onFocus: propsOnFocus,
...inputProps
}: InputHTMLAttributes<HTMLInputElement>) => {
const onFocus = useCallback(
(e: FocusEvent<HTMLInputElement>) => {
e.target.select();
propsOnFocus?.(e);
},
[propsOnFocus]
);
return (
<div className={styles.headerTitleContainer}>
<input
className={styles.titleInput}
autoFocus={true}
value={value}
type="text"
data-testid="title-content"
onFocus={onFocus}
{...inputProps}
/>
<span className={styles.shadowTitle}>{value}</span>
</div>
);
};
const StableTitle = ({
workspace,
pageId,
onRename,
}: BlockSuiteHeaderTitleProps & {
onRename?: () => void;
}) => {
const currentPage = workspace.blockSuiteWorkspace.getPage(pageId);
const pageMeta = useBlockSuitePageMeta(workspace.blockSuiteWorkspace).find(
meta => meta.id === currentPage?.id
);
const title = pageMeta?.title;
return (
<div className={styles.headerTitleContainer}>
<EditorModeSwitch
blockSuiteWorkspace={workspace.blockSuiteWorkspace}
pageId={pageId}
style={{
marginRight: '12px',
}}
/>
<span
data-testid="title-edit-button"
className={styles.titleEditButton}
onClick={onRename}
>
{title || 'Untitled'}
</span>
<PageMenu rename={onRename} pageId={pageId} />
</div>
);
};
const BlockSuiteTitleWithRename = (props: BlockSuiteHeaderTitleProps) => {
const { workspace, pageId } = props;
const currentPage = workspace.blockSuiteWorkspace.getPage(pageId);
const pageMeta = useBlockSuitePageMeta(workspace.blockSuiteWorkspace).find(
meta => meta.id === currentPage?.id
);
const pageTitleMeta = usePageMetaHelper(workspace.blockSuiteWorkspace);
const [isEditable, setIsEditable] = useState(false);
const [title, setPageTitle] = useState(pageMeta?.title || 'Untitled');
const onRename = useCallback(() => {
setIsEditable(true);
}, []);
const onBlur = useCallback(() => {
setIsEditable(false);
if (!currentPage?.id) {
return;
}
pageTitleMeta.setPageTitle(currentPage.id, title);
}, [currentPage?.id, pageTitleMeta, title]);
const handleKeyDown = useCallback(
(e: KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter' || e.key === 'Escape') {
onBlur();
}
},
[onBlur]
);
useEffect(() => {
setPageTitle(pageMeta?.title || '');
}, [pageMeta?.title]);
if (isEditable) {
return (
<EditableTitle
onBlur={onBlur}
value={title}
onKeyDown={handleKeyDown}
onChange={e => {
const value = e.target.value;
setPageTitle(value);
}}
/>
);
}
return <StableTitle {...props} onRename={onRename} />;
};
export const BlockSuiteHeaderTitle = (props: BlockSuiteHeaderTitleProps) => {
if (props.workspace.flavour === WorkspaceFlavour.PUBLIC) {
return <StableTitle {...props} />;
}
return <BlockSuiteTitleWithRename {...props} />;
};
BlockSuiteHeaderTitle.displayName = 'BlockSuiteHeaderTitle';

View File

@@ -0,0 +1,204 @@
import { FlexWrapper, Menu, MenuItem } from '@affine/component';
import { Export, MoveToTrash } from '@affine/component/page-list';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { assertExists } from '@blocksuite/global/utils';
import {
DuplicateIcon,
EdgelessIcon,
EditIcon,
FavoritedIcon,
FavoriteIcon,
ImportIcon,
PageIcon,
} from '@blocksuite/icons';
import type { PageMeta } from '@blocksuite/store';
import { Divider } from '@toeverything/components/divider';
import {
useBlockSuitePageMeta,
usePageMetaHelper,
} from '@toeverything/hooks/use-block-suite-page-meta';
import { useBlockSuiteWorkspaceHelper } from '@toeverything/hooks/use-block-suite-workspace-helper';
import { useAtom, useSetAtom } from 'jotai';
import { useCallback, useState } from 'react';
import { applyUpdate, encodeStateAsUpdate } from 'yjs';
import { pageSettingFamily, setPageModeAtom } from '../../../atoms';
import { useBlockSuiteMetaHelper } from '../../../hooks/affine/use-block-suite-meta-helper';
import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace';
import { useNavigateHelper } from '../../../hooks/use-navigate-helper';
import { toast } from '../../../utils';
import { HeaderDropDownButton } from '../../pure/header-drop-down-button';
import { usePageHelper } from '../block-suite-page-list/utils';
type PageMenuProps = {
rename?: () => void;
pageId: string;
};
export const PageMenu = ({ rename, pageId }: PageMenuProps) => {
const t = useAFFiNEI18N();
// fixme(himself65): remove these hooks ASAP
const [workspace] = useCurrentWorkspace();
const blockSuiteWorkspace = workspace.blockSuiteWorkspace;
const pageMeta = useBlockSuitePageMeta(blockSuiteWorkspace).find(
meta => meta.id === pageId
) as PageMeta;
const [setting, setSetting] = useAtom(pageSettingFamily(pageId));
const mode = setting?.mode ?? 'page';
const favorite = pageMeta.favorite ?? false;
const { setPageMeta, setPageTitle } = usePageMetaHelper(blockSuiteWorkspace);
const [openConfirm, setOpenConfirm] = useState(false);
const { removeToTrash } = useBlockSuiteMetaHelper(blockSuiteWorkspace);
const { importFile } = usePageHelper(blockSuiteWorkspace);
const handleFavorite = useCallback(() => {
setPageMeta(pageId, { favorite: !favorite });
toast(favorite ? t['Removed from Favorites']() : t['Added to Favorites']());
}, [favorite, pageId, setPageMeta, t]);
const handleSwitchMode = useCallback(() => {
setSetting(setting => ({
mode: setting?.mode === 'page' ? 'edgeless' : 'page',
}));
toast(
mode === 'page'
? t['com.affine.edgelessMode']()
: t['com.affine.pageMode']()
);
}, [mode, setSetting, t]);
const handleOnConfirm = useCallback(() => {
removeToTrash(pageId);
toast(t['Moved to Trash']());
setOpenConfirm(false);
}, [pageId, removeToTrash, t]);
const menuItemStyle = {
padding: '4px 12px',
};
const { openPage } = useNavigateHelper();
const { createPage } = useBlockSuiteWorkspaceHelper(blockSuiteWorkspace);
const setPageMode = useSetAtom(setPageModeAtom);
const duplicate = useCallback(async () => {
const currentPage = blockSuiteWorkspace.getPage(pageId);
assertExists(currentPage);
const currentPageMeta = currentPage.meta;
const newPage = createPage();
await newPage.waitForLoaded();
const update = encodeStateAsUpdate(currentPage.spaceDoc);
applyUpdate(newPage.spaceDoc, update);
setPageMeta(newPage.id, {
tags: currentPageMeta.tags,
favorite: currentPageMeta.favorite,
});
setPageMode(newPage.id, mode);
setPageTitle(newPage.id, `${currentPageMeta.title}(1)`);
openPage(blockSuiteWorkspace.id, newPage.id);
}, [
blockSuiteWorkspace,
createPage,
mode,
openPage,
pageId,
setPageMeta,
setPageMode,
setPageTitle,
]);
const EditMenu = (
<>
<MenuItem
icon={<EditIcon />}
data-testid="editor-option-menu-rename"
onClick={rename}
style={menuItemStyle}
>
{t['Rename']()}
</MenuItem>
<MenuItem
icon={mode === 'page' ? <EdgelessIcon /> : <PageIcon />}
data-testid="editor-option-menu-edgeless"
onClick={handleSwitchMode}
style={menuItemStyle}
>
{t['Convert to ']()}
{mode === 'page' ? t['Edgeless']() : t['Page']()}
</MenuItem>
<MenuItem
data-testid="editor-option-menu-favorite"
onClick={handleFavorite}
style={menuItemStyle}
icon={
favorite ? (
<FavoritedIcon style={{ color: 'var(--affine-primary-color)' }} />
) : (
<FavoriteIcon />
)
}
>
{favorite ? t['Remove from favorites']() : t['Add to Favorites']()}
</MenuItem>
{/* {TODO: add tag and duplicate function support} */}
{/* <MenuItem
icon={<TagsIcon />}
data-testid="editor-option-menu-add-tag"
onClick={() => {}}
style={menuItemStyle}
>
{t['com.affine.header.option.add-tag']()}
</MenuItem> */}
<Divider />
<MenuItem
icon={<DuplicateIcon />}
data-testid="editor-option-menu-duplicate"
onClick={duplicate}
style={menuItemStyle}
>
{t['com.affine.header.option.duplicate']()}
</MenuItem>
<MenuItem
icon={<ImportIcon />}
data-testid="editor-option-menu-import"
onClick={importFile}
style={menuItemStyle}
>
{t['Import']()}
</MenuItem>
<Export />
<Divider />
<MoveToTrash
data-testid="editor-option-menu-delete"
onItemClick={() => {
setOpenConfirm(true);
}}
/>
</>
);
return (
<>
<FlexWrapper alignItems="center" justifyContent="center">
<Menu
content={EditMenu}
placement="bottom-end"
disablePortal={true}
trigger="click"
menuStyles={{
borderRadius: '8px',
padding: '8px',
background: 'var(--affine-background-overlay-panel-color)',
}}
>
<div>
<HeaderDropDownButton />
</div>
</Menu>
<MoveToTrash.ConfirmModal
open={openConfirm}
title={pageMeta.title}
onConfirm={handleOnConfirm}
onCancel={() => {
setOpenConfirm(false);
}}
/>
</FlexWrapper>
</>
);
};

View File

@@ -0,0 +1,31 @@
import { type ComplexStyleRule, style } from '@vanilla-extract/css';
export const headerTitleContainer = style({
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
flexGrow: 1,
position: 'relative',
width: '100%',
});
export const titleEditButton = style({
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
WebkitAppRegion: 'no-drag',
} as ComplexStyleRule);
export const titleInput = style({
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
margin: 'auto',
width: '100%',
height: '100%',
});
export const shadowTitle = style({
visibility: 'hidden',
});

View File

@@ -6,9 +6,9 @@ import { useAtom } from 'jotai';
import type { CSSProperties } from 'react';
import { useEffect } from 'react';
import { pageSettingFamily } from '../../../../atoms';
import type { BlockSuiteWorkspace } from '../../../../shared';
import { toast } from '../../../../utils';
import { pageSettingFamily } from '../../../atoms';
import type { BlockSuiteWorkspace } from '../../../shared';
import { toast } from '../../../utils';
import { StyledEditorModeSwitch, StyledKeyboardItem } from './style';
import { EdgelessSwitchItem, PageSwitchItem } from './switch-items';

View File

@@ -5,14 +5,15 @@ export const StyledEditorModeSwitch = styled('div')<{
showAlone?: boolean;
}>(({ switchLeft, showAlone }) => {
return {
width: showAlone ? '40px' : '78px',
maxWidth: showAlone ? '40px' : '70px',
gap: '8px',
height: '32px',
background: showAlone
? 'transparent'
: 'var(--affine-background-secondary-color)',
borderRadius: '12px',
...displayFlex('space-between', 'center'),
padding: '0 8px',
padding: '4px 4px',
position: 'relative',
'::after': {
@@ -25,7 +26,7 @@ export const StyledEditorModeSwitch = styled('div')<{
borderRadius: '8px',
zIndex: 1,
position: 'absolute',
transform: `translateX(${switchLeft ? '0' : '38px'})`,
transform: `translateX(${switchLeft ? '0' : '32px'})`,
transition: 'all .15s',
},
};

View File

@@ -3,6 +3,8 @@ import type { HTMLAttributes } from 'react';
import type React from 'react';
import { cloneElement, useState } from 'react';
import edgelessHover from './animation-data/edgeless-hover.json';
import pageHover from './animation-data/page-hover.json';
import { StyledSwitchItem } from './style';
type HoverAnimateControllerProps = {
@@ -50,9 +52,9 @@ export const PageSwitchItem = (
<HoverAnimateController {...props}>
<InternalLottie
options={{
loop: true,
loop: false,
autoplay: false,
animationData: require('./animation-data/page-hover.json'),
animationData: pageHover,
rendererSettings: {
preserveAspectRatio: 'xMidYMid slice',
},
@@ -69,9 +71,9 @@ export const EdgelessSwitchItem = (
<HoverAnimateController {...props}>
<InternalLottie
options={{
loop: true,
loop: false,
autoplay: false,
animationData: require('./animation-data/edgeless-hover.json'),
animationData: edgelessHover,
rendererSettings: {
preserveAspectRatio: 'xMidYMid slice',
},

View File

@@ -11,7 +11,6 @@ import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-
import { useBlockSuitePagePreview } from '@toeverything/hooks/use-block-suite-page-preview';
import { useBlockSuiteWorkspacePage } from '@toeverything/hooks/use-block-suite-workspace-page';
import { useAtom, useAtomValue } from 'jotai';
import type React from 'react';
import { Suspense, useCallback, useMemo } from 'react';
import { allPageModeSelectAtom } from '../../../atoms';
@@ -23,13 +22,13 @@ import { filterPage } from '../../../utils/filter';
import { emptyDescButton, emptyDescKbd, pageListEmptyStyle } from './index.css';
import { usePageHelper } from './utils';
export type BlockSuitePageListProps = {
export interface BlockSuitePageListProps {
blockSuiteWorkspace: BlockSuiteWorkspace;
listType: 'all' | 'trash' | 'shared' | 'public';
isPublic?: true;
isPublic?: boolean;
onOpenPage: (pageId: string, newTab?: boolean) => void;
collection?: Collection;
};
}
const filter = {
all: (pageMeta: PageMeta) => !pageMeta.trash,
@@ -41,13 +40,12 @@ const filter = {
shared: (pageMeta: PageMeta) => pageMeta.isPublic && !pageMeta.trash,
};
const PagePreviewInner = ({
workspace,
pageId,
}: {
interface PagePreviewInnerProps {
workspace: Workspace;
pageId: string;
}) => {
}
const PagePreviewInner = ({ workspace, pageId }: PagePreviewInnerProps) => {
const page = useBlockSuiteWorkspacePage(workspace, pageId);
assertExists(page);
const previewAtom = useBlockSuitePagePreview(page);
@@ -55,13 +53,12 @@ const PagePreviewInner = ({
return preview;
};
const PagePreview = ({
workspace,
pageId,
}: {
interface PagePreviewProps {
workspace: Workspace;
pageId: string;
}) => {
}
const PagePreview = ({ workspace, pageId }: PagePreviewProps) => {
return (
<Suspense>
<PagePreviewInner workspace={workspace} pageId={pageId} />
@@ -69,10 +66,12 @@ const PagePreview = ({
);
};
const PageListEmpty = (props: {
interface PageListEmptyProps {
createPage?: ReturnType<typeof usePageHelper>['createPage'];
listType: BlockSuitePageListProps['listType'];
}) => {
}
const PageListEmpty = (props: PageListEmptyProps) => {
const { listType, createPage } = props;
const t = useAFFiNEI18N();
@@ -124,13 +123,13 @@ const PageListEmpty = (props: {
);
};
export const BlockSuitePageList: React.FC<BlockSuitePageListProps> = ({
export const BlockSuitePageList = ({
blockSuiteWorkspace,
onOpenPage,
listType,
isPublic = false,
collection,
}) => {
}: BlockSuitePageListProps) => {
const pageMetas = useBlockSuitePageMeta(blockSuiteWorkspace);
const {
toggleFavorite,
@@ -262,6 +261,7 @@ export const BlockSuitePageList: React.FC<BlockSuitePageListProps> = ({
},
};
});
return (
<PageList
workspaceId={blockSuiteWorkspace.id}

View File

@@ -1,27 +0,0 @@
import { DownloadTips } from '@affine/component/affine-banner';
import { isDesktop } from '@affine/env/constant';
export const DownloadClientTip = ({
show,
onClose,
}: {
// const [showDownloadClientTips, setShowDownloadClientTips] = useAtom(
// guideDownloadClientTipAtom
// );
// const onCloseDownloadClient = useCallback(() => {
// setShowDownloadClientTips(false);
// }, [setShowDownloadClientTips]);
// if (!showDownloadClientTips || isDesktop) {
// return <></>;
// }
show: boolean;
onClose: () => void;
}) => {
if (!show || isDesktop) {
return null;
}
return <DownloadTips onClose={onClose} />;
};
export default DownloadClientTip;

View File

@@ -1,154 +0,0 @@
// fixme(himself65): refactor this file
import { FlexWrapper, IconButton, Menu, MenuItem } from '@affine/component';
import { Export, MoveToTrash } from '@affine/component/page-list';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { assertExists } from '@blocksuite/global/utils';
import {
EdgelessIcon,
FavoritedIcon,
FavoriteIcon,
MoreVerticalIcon,
PageIcon,
} from '@blocksuite/icons';
import {
useBlockSuitePageMeta,
usePageMetaHelper,
} from '@toeverything/hooks/use-block-suite-page-meta';
import { currentPageIdAtom } from '@toeverything/plugin-infra/atom';
import { useAtom, useAtomValue } from 'jotai';
import { useCallback, useState } from 'react';
import { useParams } from 'react-router-dom';
import { pageSettingFamily } from '../../../../atoms';
import { useBlockSuiteMetaHelper } from '../../../../hooks/affine/use-block-suite-meta-helper';
import { useCurrentWorkspace } from '../../../../hooks/current/use-current-workspace';
import { toast } from '../../../../utils';
import { MenuThemeModeSwitch } from '../header-right-items/theme-mode-switch';
import { LanguageMenu } from './language-menu';
const CommonMenu = () => {
const content = (
<div
onClick={e => {
e.stopPropagation();
}}
>
<MenuThemeModeSwitch />
<LanguageMenu />
</div>
);
return (
<FlexWrapper alignItems="center" justifyContent="center">
<Menu
content={content}
placement="bottom"
disablePortal={true}
trigger="click"
>
<IconButton data-testid="editor-option-menu">
<MoreVerticalIcon />
</IconButton>
</Menu>
</FlexWrapper>
);
};
const PageMenu = () => {
const t = useAFFiNEI18N();
// fixme(himself65): remove these hooks ASAP
const [workspace] = useCurrentWorkspace();
const pageId = useAtomValue(currentPageIdAtom);
assertExists(workspace);
assertExists(pageId);
const blockSuiteWorkspace = workspace.blockSuiteWorkspace;
const pageMeta = useBlockSuitePageMeta(blockSuiteWorkspace).find(
meta => meta.id === pageId
);
assertExists(pageMeta);
const [setting, setSetting] = useAtom(pageSettingFamily(pageId));
const mode = setting?.mode ?? 'page';
const favorite = pageMeta.favorite ?? false;
const { setPageMeta } = usePageMetaHelper(blockSuiteWorkspace);
const [openConfirm, setOpenConfirm] = useState(false);
const { removeToTrash } = useBlockSuiteMetaHelper(blockSuiteWorkspace);
const handleFavorite = useCallback(() => {
setPageMeta(pageId, { favorite: !favorite });
toast(favorite ? t['Removed from Favorites']() : t['Added to Favorites']());
}, [favorite, pageId, setPageMeta, t]);
const handleSwitchMode = useCallback(() => {
setSetting(setting => ({
mode: setting?.mode === 'page' ? 'edgeless' : 'page',
}));
toast(
mode === 'page'
? t['com.affine.edgelessMode']()
: t['com.affine.pageMode']()
);
}, [mode, setSetting, t]);
const handleOnConfirm = useCallback(() => {
removeToTrash(pageMeta.id);
toast(t['Moved to Trash']());
setOpenConfirm(false);
}, [pageMeta.id, removeToTrash, t]);
const EditMenu = (
<>
<MenuItem
data-testid="editor-option-menu-favorite"
onClick={handleFavorite}
icon={
favorite ? (
<FavoritedIcon style={{ color: 'var(--affine-primary-color)' }} />
) : (
<FavoriteIcon />
)
}
>
{favorite ? t['Remove from favorites']() : t['Add to Favorites']()}
</MenuItem>
<MenuItem
icon={mode === 'page' ? <EdgelessIcon /> : <PageIcon />}
data-testid="editor-option-menu-edgeless"
onClick={handleSwitchMode}
>
{t['Convert to ']()}
{mode === 'page' ? t['Edgeless']() : t['Page']()}
</MenuItem>
<Export />
<MoveToTrash
data-testid="editor-option-menu-delete"
onItemClick={() => {
setOpenConfirm(true);
}}
/>
</>
);
return (
<>
<FlexWrapper alignItems="center" justifyContent="center">
<Menu
content={EditMenu}
placement="bottom-end"
disablePortal={true}
trigger="click"
>
<IconButton data-testid="editor-option-menu">
<MoreVerticalIcon />
</IconButton>
</Menu>
<MoveToTrash.ConfirmModal
open={openConfirm}
title={pageMeta.title}
onConfirm={handleOnConfirm}
onCancel={() => {
setOpenConfirm(false);
}}
/>
</FlexWrapper>
</>
);
};
export const EditorOptionMenu = () => {
const { pageId } = useParams();
return pageId ? <PageMenu /> : <CommonMenu />;
};

View File

@@ -1,135 +0,0 @@
import { Button, displayFlex, Menu, MenuItem, styled } from '@affine/component';
import { LOCALES } from '@affine/i18n';
import { useI18N } from '@affine/i18n';
import { ArrowDownSmallIcon, LanguageIcon } from '@blocksuite/icons';
import type { FC, ReactElement } from 'react';
import { useCallback } from 'react';
const LanguageMenuContent: FC = () => {
const i18n = useI18N();
const changeLanguage = useCallback(
(event: string) => {
i18n.changeLanguage(event).catch(err => {
console.error(err);
});
},
[i18n]
);
return (
<>
{LOCALES.map(option => {
return (
<StyledListItem
key={option.name}
title={option.name}
onClick={() => {
changeLanguage(option.tag);
}}
>
{option.originalName}
</StyledListItem>
);
})}
</>
);
};
export const LanguageMenu: React.FC = () => {
const i18n = useI18N();
const currentLanguage = LOCALES.find(item => item.tag === i18n.language);
return (
<StyledContainer>
<StyledIconContainer>
<LanguageIcon />
</StyledIconContainer>
<StyledButtonContainer>
<Menu
content={(<LanguageMenuContent />) as ReactElement}
placement="bottom"
trigger="click"
disablePortal={true}
>
<StyledButton
type="plain"
icon={
<StyledArrowDownContainer>
<ArrowDownSmallIcon />
</StyledArrowDownContainer>
}
iconPosition="end"
data-testid="language-menu-button"
>
<StyledCurrentLanguage>
{currentLanguage?.originalName}
</StyledCurrentLanguage>
</StyledButton>
</Menu>
</StyledButtonContainer>
</StyledContainer>
);
};
const StyledListItem = styled(MenuItem)(() => ({
width: '132px',
height: '38px',
fontSize: 'var(--affine-font-base)',
textTransform: 'capitalize',
}));
const StyledContainer = styled('div')(() => {
return {
width: '100%',
height: '48px',
backgroundColor: 'transparent',
...displayFlex('flex-start', 'center'),
padding: '0 14px',
};
});
const StyledIconContainer = styled('div')(() => {
return {
width: '20px',
height: '20px',
color: 'var(--affine-icon-color)',
fontSize: '20px',
...displayFlex('flex-start', 'center'),
};
});
const StyledButtonContainer = styled('div')(() => {
return {
width: '100%',
height: '32px',
borderRadius: '4px',
border: `1px solid var(--affine-border-color)`,
backgroundColor: 'transparent',
...displayFlex('flex-start', 'center'),
marginLeft: '12px',
};
});
const StyledButton = styled(Button)(() => {
return {
width: '100%',
height: '32px',
borderRadius: '4px',
backgroundColor: 'transparent',
...displayFlex('space-between', 'center'),
textTransform: 'capitalize',
padding: '0',
};
});
const StyledArrowDownContainer = styled('div')(() => {
return {
height: '32px',
borderLeft: `1px solid var(--affine-border-color)`,
backgroundColor: 'transparent',
...displayFlex('flex-start', 'center'),
padding: '4px 6px',
fontSize: '24px',
};
});
const StyledCurrentLanguage = styled('div')(() => {
return {
marginLeft: '12px',
color: 'var(--affine-text-color)',
};
});

View File

@@ -1,44 +0,0 @@
import type { CSSProperties, DOMAttributes } from 'react';
type IconProps = {
style?: CSSProperties;
} & DOMAttributes<SVGElement>;
export const MoonIcon = ({ style = {}, ...props }: IconProps) => {
return (
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
style={style}
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M9.54893 3.31407C9.33328 3.08158 9.27962 2.74521 9.41255 2.45912C9.54547 2.17302 9.83936 1.99233 10.1595 1.99986C13.4456 2.07712 16.5114 4.08044 17.7359 7.29071C19.3437 11.5057 17.1672 16.2024 12.8744 17.781C9.60251 18.9843 6.04745 18.0285 3.82974 15.6428C3.61375 15.4104 3.55978 15.0739 3.69257 14.7876C3.82537 14.5014 4.11931 14.3205 4.43962 14.3279C5.27228 14.3474 6.12412 14.2171 6.94979 13.9135C10.415 12.6391 12.172 8.84782 10.8741 5.44537C10.5657 4.63692 10.1061 3.91474 9.54893 3.31407Z"
/>
</svg>
);
};
export const SunIcon = ({ style = {}, ...props }: IconProps) => {
return (
<svg
width="20"
height="20"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
style={style}
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M10.8002 2.5002C10.8002 2.05837 10.442 1.7002 10.0002 1.7002C9.55837 1.7002 9.2002 2.05837 9.2002 2.5002V3.33353C9.2002 3.77536 9.55837 4.13353 10.0002 4.13353C10.442 4.13353 10.8002 3.77536 10.8002 3.33353V2.5002ZM5.14921 4.01784C4.83679 3.70542 4.33026 3.70542 4.01784 4.01784C3.70542 4.33026 3.70542 4.83679 4.01784 5.14921L4.69627 5.82764C5.00869 6.14006 5.51522 6.14006 5.82764 5.82764C6.14006 5.51522 6.14006 5.00869 5.82764 4.69627L5.14921 4.01784ZM15.9825 5.1492C16.2949 4.83678 16.2949 4.33025 15.9825 4.01783C15.6701 3.70542 15.1636 3.70543 14.8511 4.01785L14.1727 4.69628C13.8603 5.00871 13.8603 5.51524 14.1728 5.82765C14.4852 6.14007 14.9917 6.14006 15.3041 5.82763L15.9825 5.1492ZM10.0002 5.86686C7.71742 5.86686 5.86686 7.71742 5.86686 10.0002C5.86686 12.283 7.71742 14.1335 10.0002 14.1335C12.283 14.1335 14.1335 12.283 14.1335 10.0002C14.1335 7.71742 12.283 5.86686 10.0002 5.86686ZM2.5002 9.2002C2.05837 9.2002 1.7002 9.55837 1.7002 10.0002C1.7002 10.442 2.05837 10.8002 2.5002 10.8002H3.33353C3.77536 10.8002 4.13353 10.442 4.13353 10.0002C4.13353 9.55837 3.77536 9.2002 3.33353 9.2002H2.5002ZM16.6669 9.2002C16.225 9.2002 15.8669 9.55837 15.8669 10.0002C15.8669 10.442 16.225 10.8002 16.6669 10.8002H17.5002C17.942 10.8002 18.3002 10.442 18.3002 10.0002C18.3002 9.55837 17.942 9.2002 17.5002 9.2002H16.6669ZM5.82623 15.309C6.13943 14.9973 6.14069 14.4908 5.82906 14.1776C5.51742 13.8644 5.01089 13.8631 4.69769 14.1748L4.01926 14.8498C3.70606 15.1615 3.70479 15.668 4.01643 15.9812C4.32807 16.2944 4.8346 16.2956 5.1478 15.984L5.82623 15.309ZM15.3027 14.1748C14.9895 13.8631 14.483 13.8644 14.1713 14.1776C13.8597 14.4908 13.861 14.9973 14.1742 15.3089L14.8526 15.984C15.1658 16.2956 15.6723 16.2944 15.9839 15.9812C16.2956 15.668 16.2943 15.1615 15.9811 14.8498L15.3027 14.1748ZM10.8002 16.6669C10.8002 16.225 10.442 15.8669 10.0002 15.8669C9.55837 15.8669 9.2002 16.225 9.2002 16.6669V17.5002C9.2002 17.942 9.55837 18.3002 10.0002 18.3002C10.442 18.3002 10.8002 17.942 10.8002 17.5002V16.6669Z"
/>
</svg>
);
};

View File

@@ -1,61 +0,0 @@
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { DarkModeIcon, LightModeIcon } from '@blocksuite/icons';
import { useTheme } from 'next-themes';
import {
StyledSwitchItem,
StyledThemeButton,
StyledThemeButtonContainer,
StyledThemeModeContainer,
StyledThemeModeSwitch,
StyledVerticalDivider,
} from './style';
export const MenuThemeModeSwitch = () => {
const { setTheme, resolvedTheme, theme } = useTheme();
const t = useAFFiNEI18N();
return (
<StyledThemeModeContainer>
<StyledThemeModeSwitch data-testid="change-theme-container" inMenu={true}>
<StyledSwitchItem active={resolvedTheme === 'light'} inMenu={true}>
<LightModeIcon />
</StyledSwitchItem>
<StyledSwitchItem active={resolvedTheme === 'dark'} inMenu={true}>
<DarkModeIcon />
</StyledSwitchItem>
</StyledThemeModeSwitch>
<StyledThemeButtonContainer>
<StyledThemeButton
data-testid="change-theme-light"
active={theme === 'light'}
onClick={() => {
setTheme('light');
}}
>
{t['light']()}
</StyledThemeButton>
<StyledVerticalDivider />
<StyledThemeButton
data-testid="change-theme-dark"
active={theme === 'dark'}
onClick={() => {
setTheme('dark');
}}
>
{t['dark']()}
</StyledThemeButton>
<StyledVerticalDivider />
<StyledThemeButton
active={theme === 'system'}
onClick={() => {
setTheme('system');
}}
>
{t['system']()}
</StyledThemeButton>
</StyledThemeButtonContainer>
</StyledThemeModeContainer>
);
};
export default MenuThemeModeSwitch;

View File

@@ -1,120 +0,0 @@
import { css, displayFlex, keyframes, styled } from '@affine/component';
// @ts-expect-error: no types for css-spring
import spring, { toString } from 'css-spring';
const ANIMATE_DURATION = 400;
export const StyledThemeModeContainer = styled('div')(() => {
return {
width: '100%',
height: '48px',
borderRadius: '6px',
backgroundColor: 'transparent',
color: 'var(--affine-icon-color)',
fontSize: '16px',
...displayFlex('flex-start', 'center'),
padding: '0 14px',
};
});
export const StyledThemeButtonContainer = styled('div')(() => {
return {
height: '32px',
border: `1px solid var(--affine-border-color)`,
borderRadius: '4px',
cursor: 'pointer',
...displayFlex('space-evenly', 'center'),
flexGrow: 1,
marginLeft: '12px',
};
});
export const StyledThemeButton = styled('button')<{
active: boolean;
}>(({ active }) => {
return {
padding: '0 8px',
height: '100%',
flex: 1,
cursor: 'pointer',
color: active ? 'var(--affine-primary-color)' : 'var(--affine-icon-color)',
whiteSpace: 'nowrap',
};
});
export const StyledVerticalDivider = styled('div')(() => {
return {
width: '1px',
height: '100%',
borderLeft: `1px solid var(--affine-border-color)`,
};
});
export const StyledThemeModeSwitch = styled('button')<{
inMenu?: boolean;
}>(({ inMenu }) => {
return {
width: inMenu ? '20px' : '32px',
height: inMenu ? '20px' : '32px',
borderRadius: '6px',
overflow: 'hidden',
WebkitAppRegion: 'no-drag',
backgroundColor: 'transparent',
position: 'relative',
color: 'var(--affine-icon-color)',
fontSize: inMenu ? '20px' : '24px',
};
});
export const StyledSwitchItem = styled('div')<{
active: boolean;
isHover?: boolean;
inMenu?: boolean;
}>(({ active, isHover, inMenu }) => {
const activeRaiseAnimate = toString(
spring({ top: '0' }, { top: '-100%' }, { preset: 'gentle' })
);
const raiseAnimate = toString(
spring({ top: '100%' }, { top: '0' }, { preset: 'gentle' })
);
const activeDeclineAnimate = toString(
spring({ top: '-100%' }, { top: '0' }, { preset: 'gentle' })
);
const declineAnimate = toString(
spring({ top: '0' }, { top: '100%' }, { preset: 'gentle' })
);
const activeStyle = active
? {
color: 'var(--affine-icon-color)',
top: '0',
animation: css`
${keyframes`${
isHover ? activeRaiseAnimate : activeDeclineAnimate
}`} ${ANIMATE_DURATION}ms forwards
`,
animationDirection: isHover ? 'normal' : 'alternate',
}
: {
top: '100%',
color: 'var(--affine-primary-color)',
backgroundColor: 'var(--affine-hover-color)',
animation: css`
${keyframes`${
isHover ? raiseAnimate : declineAnimate
}`} ${ANIMATE_DURATION}ms forwards
`,
animationDirection: isHover ? 'normal' : 'alternate',
};
return css`
${css(displayFlex('center', 'center'))}
width:${inMenu ? '20px' : '32px'} ;
height: ${inMenu ? '20px' : '32px'} ;
position: absolute;
left: 0;
cursor: pointer;
color: ${activeStyle.color}
top: ${activeStyle.top};
background-color: ${activeStyle.backgroundColor};
animation: ${activeStyle.animation};
animation-direction: ${activeStyle.animationDirection};
//svg {
// width: 24px;
// height: 24px;
//},
`;
});

View File

@@ -1,101 +0,0 @@
import { Menu, MenuItem } from '@affine/component';
import { Logo1Icon, SignOutIcon } from '@blocksuite/icons';
import type { CSSProperties } from 'react';
import { forwardRef } from 'react';
const EditMenu = (
<MenuItem data-testid="editor-option-menu-favorite" icon={<SignOutIcon />}>
Sign Out
</MenuItem>
);
export const UserAvatar = () => {
// fixme: cloud regression
const user: any = null;
return (
<Menu
width={276}
content={EditMenu}
placement="bottom"
disablePortal={true}
trigger="click"
>
{user ? (
<WorkspaceAvatar
size={24}
name={user.name}
avatar={user.avatar_url}
></WorkspaceAvatar>
) : (
<WorkspaceAvatar size={24}></WorkspaceAvatar>
)}
</Menu>
);
};
interface WorkspaceAvatarProps {
size: number;
name?: string;
avatar?: string;
style?: CSSProperties;
}
export const WorkspaceAvatar = forwardRef<HTMLDivElement, WorkspaceAvatarProps>(
function WorkspaceAvatar(props, ref) {
const size = props.size || 20;
const sizeStr = size + 'px';
return (
<>
{props.avatar ? (
<div
style={{
...props.style,
width: sizeStr,
height: sizeStr,
color: '#fff',
borderRadius: '50%',
overflow: 'hidden',
display: 'inline-block',
verticalAlign: 'middle',
}}
ref={ref}
>
<picture>
<img
style={{ width: sizeStr, height: sizeStr }}
src={props.avatar}
alt=""
referrerPolicy="no-referrer"
/>
</picture>
</div>
) : (
<div
style={{
...props.style,
width: sizeStr,
height: sizeStr,
border: '1px solid #fff',
color: '#fff',
fontSize: Math.ceil(0.5 * size) + 'px',
borderRadius: '50%',
textAlign: 'center',
lineHeight: size + 'px',
display: 'inline-block',
verticalAlign: 'middle',
}}
ref={ref}
>
{props.name ? (
props.name.substring(0, 1)
) : (
<Logo1Icon fontSize={24} color={'#5438FF'} />
)}
</div>
)}
</>
);
}
);
export default UserAvatar;

View File

@@ -1,232 +0,0 @@
import { BrowserWarning } from '@affine/component/affine-banner';
import {
appSidebarFloatingAtom,
appSidebarOpenAtom,
} from '@affine/component/app-sidebar';
import { SidebarSwitch } from '@affine/component/app-sidebar/sidebar-header';
import { isDesktop } from '@affine/env/constant';
import { CloseIcon, MinusIcon, RoundedRectangleIcon } from '@blocksuite/icons';
import type { Page } from '@blocksuite/store';
import { headerItemsAtom } from '@toeverything/plugin-infra/atom';
import { useAtomValue } from 'jotai';
import type { FC, HTMLAttributes, PropsWithChildren, ReactNode } from 'react';
import {
forwardRef,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { currentModeAtom } from '../../../atoms/mode';
import type { AffineOfficialWorkspace } from '../../../shared';
import DownloadClientTip from './download-tips';
import { EditorOptionMenu } from './header-right-items/editor-option-menu';
import * as styles from './styles.css';
import { OSWarningMessage, shouldShowWarning } from './utils';
export type BaseHeaderProps<
Workspace extends AffineOfficialWorkspace = AffineOfficialWorkspace,
> = {
workspace: Workspace;
currentPage: Page | null;
isPublic: boolean;
leftSlot?: ReactNode;
};
export enum HeaderRightItemName {
EditorOptionMenu = 'editorOptionMenu',
// some windows only items
WindowsAppControls = 'windowsAppControls',
}
type HeaderItem = {
Component: FC<BaseHeaderProps>;
// todo: public workspace should be one of the flavour
availableWhen: (
workspace: AffineOfficialWorkspace,
currentPage: Page | null,
status: {
isPublic: boolean;
}
) => boolean;
};
const HeaderRightItems: Record<HeaderRightItemName, HeaderItem> = {
[HeaderRightItemName.EditorOptionMenu]: {
Component: EditorOptionMenu,
availableWhen: (_, currentPage, { isPublic }) => {
return (
!isPublic && currentPage?.meta.trash !== true && currentPage !== null
);
},
},
[HeaderRightItemName.WindowsAppControls]: {
Component: () => {
const handleMinimizeApp = useCallback(() => {
window.apis?.ui.handleMinimizeApp().catch(err => {
console.error(err);
});
}, []);
const handleMaximizeApp = useCallback(() => {
window.apis?.ui.handleMaximizeApp().catch(err => {
console.error(err);
});
}, []);
const handleCloseApp = useCallback(() => {
window.apis?.ui.handleCloseApp().catch(err => {
console.error(err);
});
}, []);
return (
<div
data-platform-target="win32"
className={styles.windowAppControlsWrapper}
>
<button
data-type="minimize"
className={styles.windowAppControl}
onClick={handleMinimizeApp}
>
<MinusIcon />
</button>
<button
data-type="maximize"
className={styles.windowAppControl}
onClick={handleMaximizeApp}
>
<RoundedRectangleIcon />
</button>
<button
data-type="close"
className={styles.windowAppControl}
onClick={handleCloseApp}
>
<CloseIcon />
</button>
</div>
);
},
availableWhen: () => {
return isDesktop && globalThis.platform === 'win32';
},
},
};
export type HeaderProps = BaseHeaderProps;
const PluginHeader = () => {
const rootRef = useRef<HTMLDivElement>(null);
const headerItems = useAtomValue(headerItemsAtom);
useEffect(() => {
const root = rootRef.current;
if (!root) {
return;
}
let disposes: (() => void)[] = [];
const renderTimeout = setTimeout(() => {
disposes = Object.entries(headerItems).map(([id, headerItem]) => {
const div = document.createElement('div');
div.setAttribute('plugin-id', id);
const cleanup = headerItem(div);
root.appendChild(div);
return () => {
cleanup();
root.removeChild(div);
};
});
});
return () => {
clearTimeout(renderTimeout);
setTimeout(() => {
disposes.forEach(dispose => dispose());
});
};
}, [headerItems]);
return <div className={styles.pluginHeaderItems} ref={rootRef} />;
};
export const Header = forwardRef<
HTMLDivElement,
PropsWithChildren<HeaderProps> & HTMLAttributes<HTMLDivElement>
>((props, ref) => {
const [showWarning, setShowWarning] = useState(false);
const [showDownloadTip, setShowDownloadTip] = useState(true);
// const [shouldShowGuideDownloadClientTip] = useAtom(
// guideDownloadClientTipAtom
// );
useEffect(() => {
setShowWarning(shouldShowWarning());
}, []);
const open = useAtomValue(appSidebarOpenAtom);
const appSidebarFloating = useAtomValue(appSidebarFloatingAtom);
const mode = useAtomValue(currentModeAtom);
return (
<div
className={styles.headerContainer}
ref={ref}
data-has-warning={showWarning}
data-open={open}
data-sidebar-floating={appSidebarFloating}
>
{showDownloadTip ? (
<DownloadClientTip
show={showDownloadTip}
onClose={() => setShowDownloadTip(false)}
/>
) : (
<BrowserWarning
show={showWarning}
message={<OSWarningMessage />}
onClose={() => {
setShowWarning(false);
}}
/>
)}
<div
className={styles.header}
data-has-warning={showWarning}
data-testid="editor-header-items"
data-is-edgeless={mode === 'edgeless'}
>
<div className={styles.headerLeftSide}>
{!open && <SidebarSwitch />}
{props.leftSlot}
</div>
{props.children}
<div className={styles.headerRightSide}>
<PluginHeader />
{useMemo(() => {
return Object.entries(HeaderRightItems).map(
([name, { availableWhen, Component }]) => {
if (
availableWhen(props.workspace, props.currentPage, {
isPublic: props.isPublic,
})
) {
return (
<Component
workspace={props.workspace}
currentPage={props.currentPage}
isPublic={props.isPublic}
key={name}
/>
);
}
return null;
}
);
}, [props])}
</div>
</div>
</div>
);
});
Header.displayName = 'Header';

View File

@@ -1,114 +0,0 @@
import { Button } from '@affine/component';
import { assertExists } from '@blocksuite/global/utils';
import {
useBlockSuitePageMeta,
usePageMetaHelper,
} from '@toeverything/hooks/use-block-suite-page-meta';
import { useSetAtom } from 'jotai';
import type {
FC,
HTMLAttributes,
PropsWithChildren,
ReactElement,
} from 'react';
import { useCallback, useRef, useState } from 'react';
import { openQuickSearchModalAtom } from '../../../atoms';
import { QuickSearchButton } from '../../pure/quick-search-button';
import { EditorModeSwitch } from './editor-mode-switch';
import type { BaseHeaderProps } from './header';
import { Header } from './header';
import * as styles from './styles.css';
export type WorkspaceHeaderProps = BaseHeaderProps;
export const BlockSuiteEditorHeader: FC<
PropsWithChildren<WorkspaceHeaderProps> & HTMLAttributes<HTMLDivElement>
> = (props): ReactElement => {
const { workspace, currentPage, children, isPublic } = props;
// fixme(himself65): remove this atom and move it to props
const setOpenQuickSearch = useSetAtom(openQuickSearchModalAtom);
const pageMeta = useBlockSuitePageMeta(workspace.blockSuiteWorkspace).find(
meta => meta.id === currentPage?.id
);
const pageTitleMeta = usePageMetaHelper(workspace.blockSuiteWorkspace);
const [isEditable, setIsEditable] = useState(false);
const inputRef = useRef<HTMLInputElement>(null);
const handleClick = useCallback(() => {
if (isEditable) {
setIsEditable(!isEditable);
const value = inputRef.current?.value;
if (value !== pageMeta?.title && currentPage) {
pageTitleMeta.setPageTitle(currentPage?.id, value || '');
}
} else {
setIsEditable(!isEditable);
}
}, [currentPage, isEditable, pageMeta?.title, pageTitleMeta]);
const headerRef = useRef<HTMLDivElement>(null);
assertExists(pageMeta);
const title = pageMeta?.title;
return (
<Header ref={headerRef} {...props}>
{children}
{!isPublic && currentPage && (
<div className={styles.titleContainer}>
<div className={styles.titleWrapper}>
<div className={styles.switchWrapper}>
<EditorModeSwitch
blockSuiteWorkspace={workspace.blockSuiteWorkspace}
pageId={currentPage.id}
style={{
marginRight: '12px',
}}
/>
</div>
<div>
{isEditable ? (
<div>
<input
autoFocus={true}
className={styles.title}
type="text"
data-testid="title-content"
defaultValue={pageMeta?.title}
onBlur={handleClick}
ref={inputRef}
/>
<Button
onClick={handleClick}
data-testid="save-edit-button"
style={{
marginLeft: '12px',
}}
>
Save
</Button>
</div>
) : (
<span
data-testid="title-edit-button"
onClick={handleClick}
style={{ cursor: 'pointer' }}
>
{title || 'Untitled'}
</span>
)}
</div>
<div className={styles.searchArrowWrapper}>
<QuickSearchButton
onClick={() => {
setOpenQuickSearch(true);
}}
/>
</div>
</div>
</div>
)}
</Header>
);
};
BlockSuiteEditorHeader.displayName = 'BlockSuiteEditorHeader';

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