Compare commits

..

290 Commits

Author SHA1 Message Date
Alex Yang
a79284b65e v0.8.0-canary.34 2023-08-24 11:30:29 -05:00
JimmFly
40fbf1bd41 chore: update preload page (#3943) 2023-08-24 11:21:17 -05:00
JimmFly
59e54917af chore: bump version (#3942) 2023-08-24 10:05:09 +00:00
Peng Xiao
765e160ba2 fix: add missing matrix value (#3937) 2023-08-24 07:26:49 +00:00
Peng Xiao
efb6de3435 fix: incorrect workflow file (#3935) 2023-08-24 06:52:13 +00:00
Peng Xiao
fb6eada410 fix: disable windows signing for nightly (#3933) 2023-08-24 05:11:20 +00:00
Peng Xiao
4247a5d0d9 fix: remove use of glob (#3932) 2023-08-24 04:34:36 +00:00
Alex Yang
e1816c165b v0.8.0-canary.33 2023-08-23 19:09:40 -05:00
Alex Yang
873d40d1c3 fix: add missing package (#3927) 2023-08-23 19:08:53 -05:00
Alex Yang
52f736fb77 v0.8.0-canary.32 2023-08-23 16:53:32 -05:00
Alex Yang
dd60106b5d Revert "ci: retry install three times (#3924)"
This reverts commit 4a003878e2.
2023-08-23 16:52:34 -05:00
Alex Yang
340e10f765 v0.8.0-canary.31 2023-08-23 15:01:51 -05:00
Alex Yang
301f3219e5 refactor: add content to be selectable and remove swipe gesture (#3923)
Co-authored-by: JimmFly <yangjinfei001@gmail.com>
2023-08-23 13:22:15 -05:00
Alex Yang
4a003878e2 ci: retry install three times (#3924) 2023-08-23 13:22:04 -05:00
Alex Yang
834771878e fix(workspace): remove item not in the adapter (#3922) 2023-08-23 17:51:17 +00:00
Alex Yang
ede61387c6 chore: bump version (#3919) 2023-08-23 10:53:44 -05:00
Garfield Lee
4c5b4f03e8 chore: modify code style (#3914) 2023-08-23 14:45:57 +00:00
Alex Yang
e397f18316 chore(storybook): remove cache (#3917) 2023-08-23 09:50:25 -05:00
JimmFly
a600626b83 chore: update preloading page (#3909) 2023-08-23 09:22:22 -05:00
Peng Xiao
213f42a8b6 fix: support windows auto update (#3911) 2023-08-23 09:14:37 -05:00
Hongtao Lye
9ea66a1658 fix: debug local blocksuite (#3915) 2023-08-23 09:08:37 -05:00
Alex Yang
1f03ece1a4 fix(storybook): lazy load app (#3905) 2023-08-22 21:56:47 -05:00
Alex Yang
2b71c69732 v0.8.0-canary.30 2023-08-22 19:41:29 -05:00
Alex Yang
3b6e145b23 fix(core): search feature not working (#3902) 2023-08-22 19:38:02 -05:00
Alex Yang
c7a4805e5c fix(y-provider): syncing status (#3903) 2023-08-22 19:18:35 -05:00
fourdim
bf062fb6d4 fix: make media print overflow visible (#3893) 2023-08-22 18:49:05 -05:00
Alex Yang
4da4583668 chore: bump version (#3901) 2023-08-22 15:55:01 -05:00
Alex Yang
9efec5b233 v0.8.0-canary.29 2023-08-22 11:00:12 -05:00
Peng Xiao
7d6e91f56e build: sign windows app (#3809) 2023-08-22 10:16:26 -05:00
Alex Yang
507b5dcfb3 test: loose cmdk result check (#3888) 2023-08-21 23:17:45 -05:00
Alex Yang
8ec005f7de chore: bump version (#3885) 2023-08-21 19:29:47 -05:00
Alex Yang
b5afbe385f test: fix flaky title insert (#3884) 2023-08-21 19:19:59 -05:00
Noothan am
2a5ef04397 fix(core): add toast message (#3847) 2023-08-21 23:57:11 +00:00
Alex Yang
58184679ca fix(cli): read environment variable (#3883) 2023-08-21 18:59:01 -05:00
Alex Yang
bf00299bc7 ci: do not build core in e2e test (#3882) 2023-08-21 18:36:39 -05:00
Camol
fc9981335b fix: timers type in browser env (#3875) 2023-08-21 14:14:25 -05:00
danielchim
eda5ff4d3f feat: e2e for recent search list (#3872) 2023-08-21 12:01:53 -05:00
KaranPant
54d74f6f0b fix: recent pages list doesn't update (#3848)
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-08-21 06:29:52 +00:00
dependabot[bot]
c689c08b9a chore: bump @storybook/jest from 0.1.0 to 0.2.1 (#3859)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-21 05:15:44 +00:00
Alex Yang
0e99f25fea chore: bump version (#3865) 2023-08-21 00:28:47 -05:00
danielchim
a6d5bde059 fix: remove tooltip (#3862) 2023-08-20 23:36:37 -05:00
Alex Yang
b22563b3b8 v0.8.0-canary.28 2023-08-20 20:33:38 -05:00
xiaodong zuo
72de11b8ca chore: bump blocksuite version (#3852) 2023-08-20 20:31:39 -05:00
xiaodong zuo
cae6133f7e fix: jump to the correct url after importing notion (#3844) 2023-08-20 22:13:01 +00:00
Alex Yang
a348df4c47 ci: add cancel id 2023-08-20 15:35:03 -05:00
Alex Yang
940dbbe9c3 ci: split desktop test (#3849) 2023-08-20 14:29:22 -05:00
Alex Yang
8a42592ff7 v0.8.0-canary.27 2023-08-19 13:57:20 -05:00
Alex Yang
956cde308e feat(storybook): avoid refresh (#3841) 2023-08-19 12:30:24 -05:00
Alex Yang
37c1d9bab1 fix(infra): dynamic import (#3842) 2023-08-19 12:30:11 -05:00
Alex Yang
e6cd193bf4 feat: run app in closure (#3790) 2023-08-18 14:50:35 -05:00
Peng Xiao
bd826bb7f9 fix: reference page crash for deleted items (#3835) 2023-08-18 18:52:09 +00:00
Peng Xiao
ba676eb937 fix: page blink issue on navigation (#3833) 2023-08-18 16:45:21 +00:00
Peng Xiao
0ae6c977aa fix: workaround for fullscreen mode (#3829) 2023-08-18 11:26:21 -05:00
JimmFly
e389bf902f chore: change divider style (#3826) 2023-08-18 02:31:10 -05:00
Alex Yang
f6311e73cc chore: bump version 2023-08-18 00:40:12 -05:00
Alex Yang
55c512942d chore: update changelog url (#3823) 2023-08-17 23:44:26 -05:00
Alex Yang
71cf36a300 fix: cleanup editor layout (#3822) 2023-08-17 23:37:56 -05:00
Peng Xiao
e4e17ff606 fix: disable updater for internal (#3819) 2023-08-17 22:21:30 -05:00
Alex Yang
81afecdb0e v0.8.0-canary.26 2023-08-17 21:33:59 -05:00
Alex Yang
f1cb2fc6d6 chore: bump version (#3816) 2023-08-17 21:33:34 -05:00
Alex Yang
96b64e1c78 chore: bump version (#3815) 2023-08-17 21:01:49 -05:00
JimmFly
4d58f2b4c7 fix: wrong cascading relationship (#3800) 2023-08-17 19:59:37 -05:00
fourdim
ab9452969b chore: update the top tip (#3797)
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-08-17 19:59:08 -05:00
JimmFly
aea508573b chore: adjust preloading tags (#3803) 2023-08-17 19:58:50 -05:00
Peng Xiao
068c697be9 fix: app sidebar ui issues (#3783)
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-08-17 18:36:17 +00:00
Qi
7a31089c4b feat: modify shortcut key style (#3807) 2023-08-17 18:27:24 +00:00
Alex Yang
d50fcaa94e build: fix turbosnap rootDir 2023-08-17 13:03:08 -05:00
danielchim
7f8dfc17a0 fix: workspace dropdown fix (#3808) 2023-08-17 17:47:18 +00:00
Hongtao Lye
fb47a04f55 fix: toc tooltip (#3812) 2023-08-17 17:10:37 +00:00
Alex Yang
da3dd1e324 fix(core): cleanup layout when switch page (#3794) 2023-08-16 23:34:56 -05:00
Alex Yang
c3e465d644 fix(core): editor height incorrect (#3799) 2023-08-16 23:20:27 -05:00
Mirone
d8d6620c5f chore: bump blocksuite version (#3798) 2023-08-16 22:57:21 -05:00
Alex Yang
9853d0f6ef fix: disable unstable snapshot (#3791) 2023-08-16 21:44:48 -05:00
Alex Yang
9d723fd487 v0.8.0-canary.25 2023-08-16 21:34:42 -05:00
Alex Yang
ef7ad4f111 build: fix file ignore 2023-08-16 20:08:59 -05:00
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
Alex Yang
dcd070b3e7 v0.8.0-canary.8 2023-08-02 16:51:34 -07:00
Alex Yang
32c08e49c5 feat: migrate to database v3 (#3528) 2023-08-02 16:50:10 -07:00
Garfield Lee
28a496bc67 feat: update editor mode switch icons (#3526) 2023-08-02 10:08:45 -07:00
Alex Yang
4386894e8a v0.8.0-canary.7 2023-08-02 10:06:05 -07:00
Alex Yang
33613c7041 fix(electron): check bundle (#3527) 2023-08-02 15:56:00 +00:00
Garfield Lee
db1b4d48b8 docs: update docs for build plugins (#3525) 2023-08-02 08:32:22 -07:00
Alex Yang
f007e2cecb ci: fix setup maker (#3519) 2023-08-02 05:03:05 +00:00
Peng Xiao
7e4df4c3d1 fix: stackoverflow issue in empty page (#3518) 2023-08-02 04:29:49 +00:00
Alex Yang
03b98b433b fix: drag workspace (#3513) 2023-08-01 23:29:17 +00:00
Alex Yang
1b17743ed3 feat: custom maker dmg (#3501) 2023-08-01 19:20:29 +00:00
Alex Yang
03f12f6aa4 feat: add filter schema (#3479) 2023-08-01 19:13:24 +00:00
Alex Yang
0176d66a94 v0.8.0-canary.6 2023-08-01 11:50:32 -07:00
Alex Yang
f078154b9b chore: bump version (#3489) 2023-08-01 11:30:56 -07:00
Peng Xiao
35a4c63c27 fix: flaky tests (#3507) 2023-08-01 14:13:04 +00:00
JimmFly
70f3508005 feat: brand new version of icons (#3496) 2023-08-01 05:51:30 +00:00
Alex Yang
16e22e614b feat: init @affine/worker (#3495) 2023-08-01 05:39:37 +00:00
Chi Zhang
c8b2728e27 feat: add placeholder for OPENAI_API_KEY input (#3486) 2023-07-31 17:35:53 +00:00
Alex Yang
452c780d40 refactor(i18n): language setup (#3484) 2023-07-31 09:21:12 +00:00
JimmFly
9567471e7f chore: adjustment options menu (#3455) 2023-07-31 07:56:51 +00:00
Alex Yang
4d4923cd37 v0.8.0-canary.5 2023-07-31 00:54:03 -07:00
Alex Yang
e85404a9c5 build: enable plugin system in production (#3480) 2023-07-31 06:52:11 +00:00
Alex Yang
1d43e46f99 chore: add noUnusedLocals and noUnusedParameters rules (#3476)
Co-authored-by: LongYinan <lynweklm@gmail.com>
2023-07-31 05:47:37 +00:00
Peng Xiao
5a8c1dcb57 fix: flaky test (#3478) 2023-07-31 05:47:13 +00:00
Alex Yang
9ffc523443 v0.8.0-canary.4 2023-07-30 20:33:52 -07:00
Alex Yang
39693a19bd chore: bump version (#3471) 2023-07-31 02:59:54 +00:00
Alex Yang
18fcaff5ee feat(plugin-cli): add cli af (#3465) 2023-07-30 18:10:45 +00:00
Alex Yang
568d5e4cdf fix(core): lockdown twice 2023-07-30 08:09:05 -07:00
fourdim
99c24b5cd8 chore: add the missing d.ts file for y-indexeddb (#3467) 2023-07-30 13:19:59 +00:00
Alex Yang
a3087d14d8 chore: remove unused files (#3466) 2023-07-30 06:35:00 +00:00
Alex Yang
cc7de52caf build: improve webpack config (#3463) 2023-07-30 06:34:52 +00:00
Alex Yang
05865d51c6 docs: update plugins section 2023-07-29 19:49:58 -07:00
Alex Yang
45089e176f v0.8.0-canary.3 2023-07-29 19:40:22 -07:00
Alex Yang
00a41b95b9 feat(plugin-infra): esm simulation in browser (#3464) 2023-07-30 02:23:00 +00:00
Alex Yang
765efd19da v0.8.0-canary.2 2023-07-29 14:33:38 -07:00
Alex Yang
ac59e28fcd feat(plugin-infra): support worker thread in server side (#3462) 2023-07-29 20:57:23 +00:00
Alex Yang
77dab70ff7 feat(plugin-infra): init permission control (#3461) 2023-07-29 20:10:50 +00:00
Alex Yang
0b66e911b1 feat(plugin-infra): support esm bundler (#3460) 2023-07-29 19:07:32 +00:00
JimmFly
6388a798c9 style: adjust active slider bar collection item active style (#3458)
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-07-29 15:22:21 +00:00
Alex Yang
c45149b664 v0.8.0-canary.1 2023-07-29 08:23:38 -07:00
Alex Yang
ce0c1c39e2 feat: improve copilot plugin (#3459) 2023-07-29 07:37:01 +00:00
Alex Yang
52809a2783 refactor: image preview plugin (#3457) 2023-07-29 00:18:28 -07:00
Alex Yang
be3909370e refactor(plugin-infra): split functions (#3451) 2023-07-28 22:28:10 -07:00
Alex Yang
f79733e5df feat(plugin-infra): add package.json schema (#3456) 2023-07-29 05:07:25 +00:00
Alex Yang
2d95de06d6 docs: update rustc version 2023-07-28 21:36:43 -07:00
Alex Yang
97502231a3 v0.8.0-canary.0 2023-07-28 20:18:00 -07:00
Alex Yang
d20a6d2677 chore: bump version (#3449) 2023-07-29 02:53:29 +00:00
Alex Yang
9f43c0ddc8 refactor: plugin loading logic (#3448) 2023-07-29 02:43:52 +00:00
Peng Xiao
4cb1bf6a9f test: add test for sub doc (#3444) 2023-07-28 15:15:32 +00:00
JimmFly
d96263fde9 feat: add read only mode for page in trash (#3440) 2023-07-28 15:01:10 +00:00
JimmFly
ed8b2d9927 chore: update change log link (#3435) 2023-07-28 15:00:03 +00:00
Alex Yang
7b3be389d4 v0.7.0-canary.59 2023-07-27 22:03:23 -07:00
JimmFly
68755f4303 fix: bring back the lost WorkspaceDeleteModal style (#3434) 2023-07-27 21:32:46 -07:00
545 changed files with 35743 additions and 20094 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

@@ -21,10 +21,13 @@
"native",
"templates",
"y-indexeddb",
"y-provider",
"debug",
"storage",
"infra",
"plugin-infra"
"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,
},
},
],

16
.github/actions/setup-maker/action.yml vendored Normal file
View File

@@ -0,0 +1,16 @@
name: Setup maker
description: 'Setup maker dmg for electron'
runs:
using: 'composite'
steps:
- name: 'Install @electron-forge/maker-dmg'
if: runner.os == 'macos'
shell: bash
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
@@ -125,3 +121,7 @@ runs:
- name: Build Infra
shell: bash
run: yarn run build:infra
- name: Build Plugins
shell: bash
run: yarn run build:plugins

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'

10
.github/labeler.yml vendored
View File

@@ -22,8 +22,14 @@ 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/**/*'
mod:workspace: 'packages/workspace/**/*'

177
.github/workflows/build-desktop.yml vendored Normal file
View File

@@ -0,0 +1,177 @@
name: Build(Desktop) & Test
on:
push:
branches:
- master
- v[0-9]+.[0-9]+.x-staging
- v[0-9]+.[0-9]+.x
paths-ignore:
- README.md
- .github/**
- '!.github/workflows/build-desktop.yml'
- '!.github/actions/build-rust/action.yml'
- '!.github/actions/setup-node/action.yml'
pull_request:
merge_group:
branches:
- master
- v[0-9]+.[0-9]+.x-staging
- v[0-9]+.[0-9]+.x
paths-ignore:
- README.md
- .github/**
- '!.github/workflows/build-desktop.yml'
- '!.github/actions/build-rust/action.yml'
- '!.github/actions/setup-node/action.yml'
env:
DEBUG: napi:*
BUILD_TYPE: canary
APP_NAME: affine
COVERAGE: true
DISTRIBUTION: desktop
MACOSX_DEPLOYMENT_TARGET: '10.13'
NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
jobs:
build-core:
name: Build @affine/core
runs-on: ubuntu-latest
environment: development
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: ./.github/actions/setup-node
- name: Build Plugins
run: yarn run build:plugins
- name: Build Core
run: yarn nx build @affine/core
- name: Upload core artifact
uses: actions/upload-artifact@v3
with:
name: core
path: ./apps/core/dist
if-no-files-found: error
desktop-test:
name: Desktop Test
runs-on: ${{ matrix.spec.os }}
environment: development
strategy:
fail-fast: false
# all combinations: macos-latest x64, macos-latest arm64, windows-latest x64, ubuntu-latest x64
matrix:
spec:
- {
os: macos-latest,
platform: macos,
arch: x64,
target: x86_64-apple-darwin,
test: true,
}
- {
os: macos-latest,
platform: macos,
arch: arm64,
target: aarch64-apple-darwin,
test: false,
}
- {
os: ubuntu-latest,
platform: linux,
arch: x64,
target: x86_64-unknown-linux-gnu,
test: true,
}
- {
os: windows-latest,
platform: windows,
arch: x64,
target: x86_64-pc-windows-msvc,
test: true,
}
needs: build-core
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: ./.github/actions/setup-node
timeout-minutes: 10
with:
playwright-install: true
hard-link-nm: false
- name: Build AFFiNE native
uses: ./.github/actions/build-rust
with:
target: ${{ matrix.spec.target }}
nx_token: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
- name: Run unit tests
if: ${{ matrix.spec.test }}
shell: bash
run: yarn vitest
working-directory: ./apps/electron
- name: Download core artifact
uses: actions/download-artifact@v3
with:
name: core
path: apps/electron/resources/web-static
- name: Build Plugins
run: yarn run build:plugins
- name: Build Desktop Layers
run: yarn workspace @affine/electron build
- name: Upload desktop dist
uses: actions/upload-artifact@v3
with:
name: dist-${{ matrix.spec.platform }}-${{ matrix.spec.arch }}
path: ./apps/electron/dist
- name: Run desktop tests
if: ${{ matrix.spec.test && matrix.spec.os == 'ubuntu-latest' }}
run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn workspace @affine/electron test
env:
COVERAGE: true
- name: Run desktop tests
if: ${{ matrix.spec.test && matrix.spec.os != 'ubuntu-latest' }}
run: yarn workspace @affine/electron test
env:
COVERAGE: true
- name: Make bundle
if: ${{ matrix.spec.os == 'macos-latest' && matrix.spec.arch == 'arm64' }}
env:
SKIP_BUNDLE: true
run: yarn workspace @affine/electron make --platform=darwin --arch=arm64
- name: Bundle output check
if: ${{ matrix.spec.os == 'macos-latest' && matrix.spec.arch == 'arm64' }}
run: |
yarn ts-node-esm ./scripts/macos-arm64-output-check.mts
working-directory: apps/electron
- name: Collect code coverage report
if: ${{ matrix.spec.test }}
run: yarn exec nyc report -t .nyc_output --report-dir .coverage --reporter=lcov
- name: Upload e2e test coverage results
if: ${{ matrix.spec.test }}
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./.coverage/lcov.info
flags: e2etest-${{ matrix.spec.os }}-${{ matrix.spec.arch }}
name: affine
fail_ci_if_error: false
- name: Upload test results
if: ${{ failure() }}
uses: actions/upload-artifact@v3
with:
name: test-results-e2e-${{ matrix.spec.os }}-${{ matrix.spec.arch }}
path: ./test-results
if-no-files-found: ignore

View File

@@ -30,6 +30,7 @@ env:
BUILD_TYPE: canary
APP_NAME: affine
COVERAGE: true
DISTRIBUTION: browser
MACOSX_DEPLOYMENT_TARGET: '10.13'
NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
@@ -47,8 +48,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 +57,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:
@@ -101,8 +134,8 @@ jobs:
path: ./apps/storybook/storybook-static
if-no-files-found: error
build-core:
name: Build @affine/core
build-storage:
name: Build Storage
runs-on: ubuntu-latest
environment: development
@@ -110,19 +143,24 @@ jobs:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: ./.github/actions/setup-node
- name: Build Core
run: yarn nx build @affine/core
- name: Upload core artifact
- 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: core
path: ./apps/core/dist
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
@@ -158,24 +196,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:
@@ -185,11 +216,10 @@ jobs:
name: affine
fail_ci_if_error: false
storybook-test:
name: Storybook Test
e2e-plugin-test:
name: E2E Plugin Test
runs-on: ubuntu-latest
environment: development
needs: [build-storybook]
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
@@ -197,15 +227,73 @@ jobs:
with:
playwright-install: true
electron-install: false
- name: Download storybook artifact
- name: Run playwright tests
run: yarn e2e --forbid-only
working-directory: tests/affine-plugin
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: e2e-plugin-test
name: affine
fail_ci_if_error: false
- name: Upload test results
if: ${{ failure() }}
uses: actions/upload-artifact@v3
with:
name: test-results-e2e-plugin
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: 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"
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
@@ -215,8 +303,6 @@ jobs:
matrix:
shard: [1, 2, 3, 4, 5]
environment: development
needs: build-core
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
@@ -224,11 +310,6 @@ jobs:
with:
playwright-install: true
electron-install: false
- name: Download core artifact
uses: actions/download-artifact@v3
with:
name: core
path: ./apps/core/dist
- name: Run playwright tests
run: yarn e2e --forbid-only --shard=${{ matrix.shard }}/${{ strategy.job-total }}
@@ -260,8 +341,11 @@ jobs:
name: E2E Migration Test
runs-on: ubuntu-latest
environment: development
needs: build-core
strategy:
matrix:
spec:
- { package: 0.7.0-canary.18 }
- { package: 0.8.0-canary.7 }
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
@@ -270,144 +354,20 @@ jobs:
playwright-install: true
electron-install: false
- name: Download core artifact
uses: actions/download-artifact@v3
with:
name: core
path: ./apps/core/dist
- name: Unzip
run: yarn unzip
working-directory: ./tests/affine-legacy/0.7.0-canary.18
working-directory: ./tests/affine-legacy/${{ matrix.spec.package }}
- name: Run legacy playwright tests
- name: Run playwright tests
run: yarn e2e --forbid-only
working-directory: ./tests/affine-legacy/0.7.0-canary.18
- name: Run vitest
run: yarn test
working-directory: ./tests/affine-legacy/0.7.0-canary.18
working-directory: ./tests/affine-legacy/${{ matrix.spec.package }}
- name: Upload test results
if: ${{ failure() }}
uses: actions/upload-artifact@v3
with:
name: test-results-e2e-migration
path: ./tests/affine-legacy/0.7.0-canary.18/test-results
if-no-files-found: ignore
desktop-test:
name: Desktop Test
runs-on: ${{ matrix.spec.os }}
environment: development
strategy:
fail-fast: false
# all combinations: macos-latest x64, macos-latest arm64, windows-latest x64, ubuntu-latest x64
matrix:
spec:
- {
os: macos-latest,
platform: macos,
arch: x64,
target: x86_64-apple-darwin,
test: true,
}
- {
os: macos-latest,
platform: macos,
arch: arm64,
target: aarch64-apple-darwin,
test: false,
}
- {
os: ubuntu-latest,
platform: linux,
arch: x64,
target: x86_64-unknown-linux-gnu,
test: true,
}
- {
os: windows-latest,
platform: windows,
arch: x64,
target: x86_64-pc-windows-msvc,
test: true,
}
needs: build-core
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: ./.github/actions/setup-node
timeout-minutes: 10
with:
playwright-install: true
hard-link-nm: false
- name: Build AFFiNE native
uses: ./.github/actions/build-rust
with:
target: ${{ matrix.spec.target }}
nx_token: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
- name: Run unit tests
if: ${{ matrix.spec.test }}
shell: bash
run: yarn vitest
working-directory: ./apps/electron
- name: Download core artifact
uses: actions/download-artifact@v3
with:
name: core
path: apps/electron/resources/web-static
- name: Build Plugins
run: yarn run build:plugins
- name: Build Desktop Layers
run: yarn workspace @affine/electron build
- name: Run desktop tests
if: ${{ matrix.spec.test && matrix.spec.os == 'ubuntu-latest' }}
run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn workspace @affine/electron test
env:
COVERAGE: true
- name: Run desktop tests
if: ${{ matrix.spec.test && matrix.spec.os != 'ubuntu-latest' }}
run: yarn workspace @affine/electron test
env:
COVERAGE: true
- name: Make bundle
if: ${{ matrix.spec.os == 'macos-latest' && matrix.spec.arch == 'arm64' }}
run: yarn workspace @affine/electron make --platform=darwin --arch=arm64
- name: Bundle output check
if: ${{ matrix.spec.os == 'macos-latest' && matrix.spec.arch == 'arm64' }}
run: |
./scripts/unzip-macos-arm64.sh
yarn ts-node-esm ./scripts/macos-arm64-output-check.mts
working-directory: apps/electron
- name: Collect code coverage report
if: ${{ matrix.spec.test }}
run: yarn exec nyc report -t .nyc_output --report-dir .coverage --reporter=lcov
- name: Upload e2e test coverage results
if: ${{ matrix.spec.test }}
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./.coverage/lcov.info
flags: e2etest-${{ matrix.spec.os }}-${{ matrix.spec.arch }}
name: affine
fail_ci_if_error: false
- name: Upload test results
if: ${{ failure() }}
uses: actions/upload-artifact@v3
with:
name: test-results-e2e-${{ matrix.spec.os }}-${{ matrix.spec.arch }}
path: ./test-results
name: test-results-e2e-migration-${{ matrix.spec.package }}
path: ./tests/affine-legacy/${{ matrix.spec.package }}/test-results
if-no-files-found: ignore
unit-test:
@@ -432,79 +392,3 @@ jobs:
flags: unittest
name: affine
fail_ci_if_error: false
build-docker:
if: github.ref == 'refs/heads/master'
name: Build Docker
needs:
- lint
- desktop-test
- server-test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Download core artifact
uses: actions/download-artifact@v3
with:
name: core
path: ./apps/core/dist
- name: Download server dist
uses: actions/download-artifact@v3
with:
name: server-dist
path: ./apps/server/dist
- name: Download storage.node
uses: actions/download-artifact@v3
with:
name: storage.node
path: ./apps/server
- name: Setup Git short hash
run: |
echo "GIT_SHORT_HASH=$(git rev-parse --short HEAD)" >> "$GITHUB_ENV"
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
logout: false
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Build front Dockerfile
uses: docker/build-push-action@v4
with:
context: .
push: true
pull: true
platforms: linux/amd64,linux/arm64
provenance: true
file: .github/deployment/front/Dockerfile
tags: ghcr.io/toeverything/affine-front:${{ env.GIT_SHORT_HASH }},ghcr.io/toeverything/affine-front:latest
# setup node without cache configuration
# Prisma cache is not compatible with docker build cache
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
registry-url: https://npm.pkg.github.com
scope: '@toeverything'
- name: Install Node.js dependencies
run: yarn workspaces focus @affine/server --production
- name: Generate Prisma client
run: yarn workspace @affine/server prisma generate
- name: Build graphql Dockerfile
uses: docker/build-push-action@v4
with:
context: .
push: true
pull: true
platforms: linux/amd64,linux/arm64
provenance: true
file: .github/deployment/node/Dockerfile
tags: ghcr.io/toeverything/affine-graphql:${{ env.GIT_SHORT_HASH }},ghcr.io/toeverything/affine-graphql:latest

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, 66789140
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
@@ -71,34 +73,27 @@ jobs:
make-distribution:
environment: production
strategy:
# all combinations: macos-latest x64, macos-latest arm64, windows-latest x64, ubuntu-latest x64
# all combinations: macos-latest x64, macos-latest arm64, ubuntu-latest x64
# For windows, we need a separate approach
matrix:
spec:
- {
os: macos-latest,
platform: darwin,
arch: x64,
target: x86_64-apple-darwin,
}
- {
os: macos-latest,
platform: darwin,
arch: arm64,
target: aarch64-apple-darwin,
}
- {
os: ubuntu-latest,
platform: linux,
arch: x64,
target: x86_64-unknown-linux-gnu,
}
- {
os: windows-latest,
platform: win32,
arch: x64,
target: x86_64-pc-windows-msvc,
}
runs-on: ${{ matrix.spec.os }}
- runner: macos-latest
platform: darwin
arch: x64
target: x86_64-apple-darwin
- runner: macos-latest
platform: darwin
arch: arm64
target: aarch64-apple-darwin
- runner: ubuntu-latest
platform: linux
arch: x64
target: x86_64-unknown-linux-gnu
- runner: windows-latest
platform: win32
arch: x64
target: x86_64-pc-windows-msvc
runs-on: ${{ matrix.spec.runner }}
needs:
- before-make
- set-build-version
@@ -110,7 +105,11 @@ 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
with:

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' }}
@@ -75,34 +77,23 @@ jobs:
make-distribution:
environment: production
strategy:
# all combinations: macos-latest x64, macos-latest arm64, windows-latest x64, ubuntu-latest x64
# all combinations: macos-latest x64, macos-latest arm64, ubuntu-latest x64
# For windows, we need a separate approach
matrix:
spec:
- {
os: macos-latest,
platform: darwin,
arch: x64,
target: x86_64-apple-darwin,
}
- {
os: macos-latest,
platform: darwin,
arch: arm64,
target: aarch64-apple-darwin,
}
- {
os: ubuntu-latest,
platform: linux,
arch: x64,
target: x86_64-unknown-linux-gnu,
}
- {
os: windows-latest,
platform: win32,
arch: x64,
target: x86_64-pc-windows-msvc,
}
runs-on: ${{ matrix.spec.os }}
- runner: macos-latest
platform: darwin
arch: x64
target: x86_64-apple-darwin
- runner: macos-latest
platform: darwin
arch: arm64
target: aarch64-apple-darwin
- runner: ubuntu-latest
platform: linux
arch: x64
target: x86_64-unknown-linux-gnu
runs-on: ${{ matrix.spec.runner }}
needs: before-make
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
@@ -112,7 +103,11 @@ 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
with:
@@ -145,15 +140,6 @@ jobs:
mkdir -p builds
mv apps/electron/out/*/make/*.dmg ./builds/affine-${{ env.BUILD_TYPE }}-macos-${{ matrix.spec.arch }}.dmg
mv apps/electron/out/*/make/zip/darwin/${{ matrix.spec.arch }}/*.zip ./builds/affine-${{ env.BUILD_TYPE }}-macos-${{ matrix.spec.arch }}.zip
- name: Save artifacts (windows)
if: ${{ matrix.spec.platform == 'win32' }}
run: |
mkdir -p builds
mv apps/electron/out/*/make/zip/win32/x64/AFFiNE*-win32-x64-*.zip ./builds/affine-${{ env.BUILD_TYPE }}-windows-x64.zip
mv apps/electron/out/*/make/squirrel.windows/x64/*.exe ./builds/affine-${{ env.BUILD_TYPE }}-windows-x64.exe
mv apps/electron/out/*/make/squirrel.windows/x64/*.msi ./builds/affine-${{ env.BUILD_TYPE }}-windows-x64.msi
mv apps/electron/out/*/make/squirrel.windows/x64/*.nupkg ./builds/affine-${{ env.BUILD_TYPE }}-windows-x64.nupkg
- name: Save artifacts (linux)
if: ${{ matrix.spec.platform == 'linux' }}
run: |
@@ -167,8 +153,166 @@ jobs:
name: affine-${{ matrix.spec.platform }}-${{ matrix.spec.arch }}-builds
path: builds
package-distribution-windows:
environment: production
strategy:
# all combinations: macos-latest x64, macos-latest arm64, ubuntu-latest x64
# For windows, we need a separate approach
matrix:
spec:
- runner: windows-latest
platform: win32
arch: x64
target: x86_64-pc-windows-msvc
runs-on: ${{ matrix.spec.runner }}
needs: before-make
outputs:
FILES_TO_BE_SIGNED: ${{ steps.get_files_to_be_signed.outputs.FILES_TO_BE_SIGNED }}
env:
SKIP_GENERATE_ASSETS: 1
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
with:
target: ${{ matrix.spec.target }}
nx_token: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
- uses: actions/download-artifact@v3
with:
name: core
path: apps/electron/resources/web-static
- name: Build Plugins
run: yarn run build:plugins
- name: Build Desktop Layers
run: yarn workspace @affine/electron build
- name: package
run: yarn workspace @affine/electron package --platform=${{ matrix.spec.platform }} --arch=${{ matrix.spec.arch }}
- name: get all files to be signed
id: get_files_to_be_signed
run: |
Set-Variable -Name FILES_TO_BE_SIGNED -Value ((Get-ChildItem -Path apps/electron/out -Recurse -File | Where-Object { $_.Extension -in @(".exe", ".node", ".dll", ".msi") } | ForEach-Object { '"' + $_.FullName.Replace((Get-Location).Path + '\apps\electron\out\', '') + '"' }) -join ' ')
"FILES_TO_BE_SIGNED=$FILES_TO_BE_SIGNED" >> $env:GITHUB_OUTPUT
echo $FILES_TO_BE_SIGNED
- name: Zip artifacts for faster upload
run: Compress-Archive -CompressionLevel Fastest -Path apps/electron/out/* -DestinationPath archive.zip
- name: Save packaged artifacts for signing
uses: actions/upload-artifact@v3
with:
name: packaged-${{ matrix.spec.platform }}-${{ matrix.spec.arch }}
path: |
archive.zip
!**/*.map
sign-packaged-artifacts-windows:
needs: package-distribution-windows
uses: ./.github/workflows/windows-signer.yml
with:
files: ${{ needs.package-distribution-windows.outputs.FILES_TO_BE_SIGNED }}
artifact-name: packaged-win32-x64
make-windows-installer:
environment: production
needs: sign-packaged-artifacts-windows
strategy:
# all combinations: macos-latest x64, macos-latest arm64, ubuntu-latest x64
# For windows, we need a separate approach
matrix:
spec:
- runner: windows-latest
platform: win32
arch: x64
target: x86_64-pc-windows-msvc
runs-on: ${{ matrix.spec.runner }}
outputs:
FILES_TO_BE_SIGNED: ${{ steps.get_files_to_be_signed.outputs.FILES_TO_BE_SIGNED }}
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
timeout-minutes: 10
uses: ./.github/actions/setup-node
- name: Download and overwrite packaged artifacts
uses: actions/download-artifact@v3
with:
name: signed-packaged-${{ matrix.spec.platform }}-${{ matrix.spec.arch }}
path: .
- name: unzip file
run: Expand-Archive -Path signed.zip -DestinationPath apps/electron/out
- name: Make squirrel.windows installer
run: yarn workspace @affine/electron make-squirrel --platform=${{ matrix.spec.platform }} --arch=${{ matrix.spec.arch }}
- name: Zip artifacts for faster upload
run: Compress-Archive -CompressionLevel Fastest -Path apps/electron/out/${{ env.BUILD_TYPE }}/make/* -DestinationPath archive.zip
- name: get all files to be signed
id: get_files_to_be_signed
run: |
Set-Variable -Name FILES_TO_BE_SIGNED -Value ((Get-ChildItem -Path apps/electron/out/${{ env.BUILD_TYPE }}/make -Recurse -File | Where-Object { $_.Extension -in @(".exe", ".node", ".dll", ".msi") } | ForEach-Object { '"' + $_.FullName.Replace((Get-Location).Path + '\apps\electron\out\${{ env.BUILD_TYPE }}\make\', '') + '"' }) -join ' ')
"FILES_TO_BE_SIGNED=$FILES_TO_BE_SIGNED" >> $env:GITHUB_OUTPUT
echo $FILES_TO_BE_SIGNED
- name: Save installer for signing
uses: actions/upload-artifact@v3
with:
name: installer-${{ matrix.spec.platform }}-${{ matrix.spec.arch }}
path: archive.zip
sign-installer-artifacts-windows:
needs: make-windows-installer
uses: ./.github/workflows/windows-signer.yml
with:
files: ${{ needs.make-windows-installer.outputs.FILES_TO_BE_SIGNED }}
artifact-name: installer-win32-x64
finalize-installer-windows:
environment: production
needs: sign-installer-artifacts-windows
strategy:
# all combinations: macos-latest x64, macos-latest arm64, ubuntu-latest x64
# For windows, we need a separate approach
matrix:
spec:
- runner: windows-latest
platform: win32
arch: x64
target: x86_64-pc-windows-msvc
runs-on: ${{ matrix.spec.runner }}
steps:
- name: Download and overwrite installer artifacts
uses: actions/download-artifact@v3
with:
name: signed-installer-${{ matrix.spec.platform }}-${{ matrix.spec.arch }}
path: .
- name: unzip file
run: Expand-Archive -Path signed.zip -DestinationPath apps/electron/out/${{ env.BUILD_TYPE }}/make
- name: Save artifacts
run: |
mkdir -p builds
mv apps/electron/out/*/make/zip/win32/x64/AFFiNE*-win32-x64-*.zip ./builds/affine-${{ env.BUILD_TYPE }}-windows-x64.zip
mv apps/electron/out/*/make/squirrel.windows/x64/*.exe ./builds/affine-${{ env.BUILD_TYPE }}-windows-x64.exe
mv apps/electron/out/*/make/squirrel.windows/x64/*.msi ./builds/affine-${{ env.BUILD_TYPE }}-windows-x64.msi
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: affine-${{ matrix.spec.platform }}-${{ matrix.spec.arch }}-builds
path: builds
release:
needs: [before-make, make-distribution]
needs: [before-make, make-distribution, finalize-installer-windows]
runs-on: ubuntu-latest
steps:
@@ -216,8 +360,6 @@ jobs:
./*.zip
./*.dmg
./*.exe
./*.nupkg
./RELEASES
./*.AppImage
./*.apk
./*.yml

View File

@@ -5,6 +5,13 @@ on:
branches:
- master
env:
BUILD_TYPE: stable
APP_NAME: affine
COVERAGE: false
DISTRIBUTION: browser
NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
jobs:
release:
name: Try publishing npm@latest release
@@ -17,3 +24,142 @@ jobs:
run: ./scripts/publish.sh
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
build-core:
name: Build @affine/core
runs-on: ubuntu-latest
environment: development
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: ./.github/actions/setup-node
- name: Build Plugins
run: yarn run build:plugins
- name: Build Core
run: yarn nx build @affine/core
- name: Upload core artifact
uses: actions/upload-artifact@v3
with:
name: core
path: ./apps/core/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:
name: server-dist
path: ./apps/server/dist
if-no-files-found: error
build-storage:
name: Build Storage
runs-on: ubuntu-latest
env:
RUSTFLAGS: '-C debuginfo=1'
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
build-docker:
if: github.ref == 'refs/heads/master'
name: Build Docker
runs-on: ubuntu-latest
needs:
- build-server
- build-core
- build-storage
steps:
- uses: actions/checkout@v3
- name: Download core artifact
uses: actions/download-artifact@v3
with:
name: core
path: ./apps/core/dist
- name: Download server dist
uses: actions/download-artifact@v3
with:
name: server-dist
path: ./apps/server/dist
- name: Download storage.node
uses: actions/download-artifact@v3
with:
name: storage.node
path: ./apps/server
- name: Setup Git short hash
run: |
echo "GIT_SHORT_HASH=$(git rev-parse --short HEAD)" >> "$GITHUB_ENV"
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
logout: false
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Build front Dockerfile
uses: docker/build-push-action@v4
with:
context: .
push: true
pull: true
platforms: linux/amd64,linux/arm64
provenance: true
file: .github/deployment/front/Dockerfile
tags: ghcr.io/toeverything/affine-front:${{ env.GIT_SHORT_HASH }},ghcr.io/toeverything/affine-front:latest
# setup node without cache configuration
# Prisma cache is not compatible with docker build cache
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
registry-url: https://npm.pkg.github.com
scope: '@toeverything'
- name: Install Node.js dependencies
run: yarn workspaces focus @affine/server --production
- name: Generate Prisma client
run: yarn workspace @affine/server prisma generate
- name: Build graphql Dockerfile
uses: docker/build-push-action@v4
with:
context: .
push: true
pull: true
platforms: linux/amd64,linux/arm64
provenance: true
file: .github/deployment/node/Dockerfile
tags: ghcr.io/toeverything/affine-graphql:${{ env.GIT_SHORT_HASH }},ghcr.io/toeverything/affine-graphql:latest

42
.github/workflows/windows-signer.yml vendored Normal file
View File

@@ -0,0 +1,42 @@
name: Windows Signer
on:
workflow_call:
inputs:
artifact-name:
required: true
type: string
files:
required: true
type: string
jobs:
sign:
runs-on: [self-hosted, win-signer]
env:
ARCHIVE_DIR: ${{ github.run_id }}-${{ github.run_attempt }}-${{ inputs.artifact-name }}
steps:
- uses: actions/download-artifact@v3
with:
name: ${{ inputs.artifact-name }}
path: ${{ env.ARCHIVE_DIR }}
- name: unzip file
shell: cmd
# 7za is pre-installed on the signer machine
run: |
cd ${{ env.ARCHIVE_DIR }}
md out
7za x archive.zip -y -oout
- name: sign
shell: cmd
run: |
cd ${{ env.ARCHIVE_DIR }}/out
signtool sign /tr http://timestamp.sectigo.com /td sha256 /fd sha256 /a ${{ inputs.files }}
- name: zip file
shell: cmd
run: |
cd ${{ env.ARCHIVE_DIR }}
7za a signed.zip .\out\*
- name: upload
uses: actions/upload-artifact@v3
with:
name: signed-${{ inputs.artifact-name }}
path: ${{ env.ARCHIVE_DIR }}/signed.zip

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,28 +114,32 @@ 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.)
| Name | |
| ------------------------------------ | ----------------------------------------- |
| [@affine/bookmark](plugins/bookmark) | A block for bookmarking a website |
| [@affine/copilot](plugins/copilot) | AI Copilot that help you document writing |
- [@affine/sdk](./packages/sdk) - SDK for developing plugins
- [@affine/plugin-cli](./packages/plugin-cli) - CLI for developing plugins
## Thanks
| Official Plugin | Description |
| ----------------------------------------------------- | ----------------------------------------- |
| [@affine/bookmark-plugin](plugins/bookmark) | A block for bookmarking a website |
| [@affine/copilot-plugin](plugins/copilot) | AI Copilot that help you document writing |
| [@affine/image-preview-plugin](plugins/image-preview) | Component for previewing an image |
## 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.
@@ -167,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
@@ -186,18 +178,26 @@ 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
[contributor license agreement]: https://github.com/toeverything/affine/edit/master/.github/CLA.md
[rust-version-icon]: https://img.shields.io/badge/Rust-1.70.0-dea584
[rust-version-icon]: https://img.shields.io/badge/Rust-1.71.0-dea584
[stars-icon]: https://img.shields.io/github/stars/toeverything/AFFiNE.svg?style=flat&logo=github&colorB=red&label=stars
[codecov]: https://codecov.io/gh/toeverything/affine/branch/master/graphs/badge.svg?branch=master
[node-version-icon]: https://img.shields.io/badge/node-%3E=18.16.1-success

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

@@ -8,9 +8,9 @@ export const productionCacheGroups = {
test: /[\\/]node_modules[\\/]/,
name(module: any) {
// https://hackernoon.com/the-100-correct-way-to-split-your-chunks-with-webpack-f8a9df5b7758
const name =
module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)?.[1] ??
'unknown';
const name = module.context.match(
/[\\/]node_modules[\\/](.*?)([\\/]|$)/
)?.[1];
return `npm-async-${name}`;
},
priority: Number.MAX_SAFE_INTEGER,

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';
@@ -32,6 +31,7 @@ const OptimizeOptionOptions: (
minimizer: [
new TerserPlugin({
minify: TerserPlugin.swcMinify,
exclude: [/plugins\/.+\/.+\.js$/, /plugins\/.+\/.+\.mjs$/],
parallel: true,
extractComments: true,
terserOptions: {
@@ -73,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,
@@ -117,6 +124,72 @@ 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',
'src'
),
'@blocksuite/blocks': resolve(
blocksuiteBaseDir,
'packages',
'blocks',
'src'
),
'@blocksuite/editor': resolve(
blocksuiteBaseDir,
'packages',
'editor',
'src'
),
'@blocksuite/global': resolve(
blocksuiteBaseDir,
'packages',
'global',
'src'
),
'@blocksuite/lit': resolve(
blocksuiteBaseDir,
'packages',
'lit',
'src'
),
'@blocksuite/phasor': resolve(
blocksuiteBaseDir,
'packages',
'phasor',
'src'
),
'@blocksuite/store/providers/broadcast-channel': resolve(
blocksuiteBaseDir,
'packages',
'store',
'src/providers/broadcast-channel'
),
'@blocksuite/store': resolve(
blocksuiteBaseDir,
'packages',
'store',
'src'
),
'@blocksuite/virgo': resolve(
blocksuiteBaseDir,
'packages',
'virgo',
'src'
),
},
},
cache: {
@@ -130,6 +203,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,
},
@@ -137,6 +214,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,
},
@@ -145,8 +236,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/
@@ -157,9 +247,10 @@ export const createConfiguration: (
dynamicImport: true,
topLevelAwait: false,
tsx: true,
decorators: true,
},
target: 'es2022',
externalHelpers: true,
externalHelpers: false,
transform: {
react: {
runtime: 'automatic',
@@ -169,6 +260,7 @@ export const createConfiguration: (
emitFullSignatures: true,
},
},
useDefineForClassFields: false,
},
experimental: {
keepImportAssertions: true,
@@ -252,14 +344,6 @@ export const createConfiguration: (
ignoreOrder: true,
}),
]),
new HTMLPlugin({
template: join(rootPath, '.webpack', 'template.html'),
inject: 'body',
scriptLoading: 'defer',
minify: false,
chunks: ['index', 'plugin'],
filename: 'index.html',
}),
new VanillaExtractPlugin(),
new webpack.DefinePlugin({
'process.env': JSON.stringify({}),
@@ -269,7 +353,10 @@ export const createConfiguration: (
}),
new CopyPlugin({
patterns: [
{ from: resolve(rootPath, 'public'), to: resolve(rootPath, 'dist') },
{
from: resolve(rootPath, 'public'),
to: resolve(rootPath, 'dist'),
},
],
}),
],

View File

@@ -6,14 +6,21 @@ const require = createRequire(import.meta.url);
const packageJson = require('../package.json');
const editorFlags: BlockSuiteFeatureFlags = {
enable_database: true,
enable_slash_menu: true,
enable_edgeless_toolbar: true,
enable_block_hub: true,
enable_drag_handle: true,
enable_block_hub: true,
enable_surface: true,
enable_edgeless_toolbar: true,
enable_slash_menu: true,
enable_database: true,
enable_database_filter: false,
enable_data_view: false,
enable_page_tags: false,
enable_toggle_block: false,
enable_linked_page: true,
enable_bookmark_operation: false,
enable_note_index: false,
enable_attachment_block: true,
};
export function getRuntimeConfig(buildFlags: BuildFlags): RuntimeConfig {
@@ -23,7 +30,7 @@ export function getRuntimeConfig(buildFlags: BuildFlags): RuntimeConfig {
enableTestProperties: false,
enableBroadcastChannelProvider: true,
enableDebugPage: true,
changelogUrl: 'https://affine.pro/blog/what-is-new-affine-0717',
changelogUrl: 'https://affine.pro/blog/what-is-new-affine-0818',
imageProxyUrl: 'https://workers.toeverything.workers.dev/proxy/image',
enablePreloading: true,
enableNewSettingModal: true,
@@ -43,7 +50,7 @@ export function getRuntimeConfig(buildFlags: BuildFlags): RuntimeConfig {
enableTestProperties: true,
enableBroadcastChannelProvider: true,
enableDebugPage: true,
changelogUrl: 'https://affine.pro/blog/what-is-new-affine-0717',
changelogUrl: 'https://github.com/toeverything/AFFiNE/releases',
imageProxyUrl: 'https://workers.toeverything.workers.dev/proxy/image',
enablePreloading: true,
enableNewSettingModal: true,

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,15 +15,49 @@ export default async function (cli_env: any, _: any) {
const config = createConfiguration(flags, runtimeConfig);
return merge(config, {
entry: {
index: {
asyncChunks: false,
import: resolve(rootPath, 'src/index.tsx'),
'polyfill/intl-segmenter': {
import: resolve(rootPath, 'src/polyfill/intl-segmenter.ts'),
},
'polyfill/ses': {
import: resolve(rootPath, 'src/polyfill/ses.ts'),
},
plugin: {
dependOn: ['index'],
asyncChunks: true,
dependOn: ['polyfill/intl-segmenter', 'polyfill/ses'],
import: resolve(rootPath, 'src/bootstrap/register-plugins.ts'),
},
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,16 +2,21 @@
"name": "@affine/core",
"type": "module",
"private": true,
"version": "0.7.0-canary.58",
"version": "0.8.0-canary.34",
"scripts": {
"build": "yarn -T run build-core",
"dev": "yarn -T run dev-core",
"static-server": "ts-node-esm ./server.mts"
"static-server": "yarn -T run dev-core --static"
},
"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:*",
"@affine/copilot": "workspace:*",
"@affine/debug": "workspace:*",
"@affine/env": "workspace:*",
"@affine/graphql": "workspace:*",
@@ -19,29 +24,32 @@
"@affine/jotai": "workspace:*",
"@affine/templates": "workspace:*",
"@affine/workspace": "workspace:*",
"@blocksuite/block-std": "0.0.0-20230720073515-bea92e0f-nightly",
"@blocksuite/blocks": "0.0.0-20230720073515-bea92e0f-nightly",
"@blocksuite/editor": "0.0.0-20230720073515-bea92e0f-nightly",
"@blocksuite/global": "0.0.0-20230720073515-bea92e0f-nightly",
"@blocksuite/icons": "^2.1.27",
"@blocksuite/lit": "0.0.0-20230720073515-bea92e0f-nightly",
"@blocksuite/store": "0.0.0-20230720073515-bea92e0f-nightly",
"@blocksuite/block-std": "0.0.0-20230824081236-b25a3da9-nightly",
"@blocksuite/blocks": "0.0.0-20230824081236-b25a3da9-nightly",
"@blocksuite/editor": "0.0.0-20230824081236-b25a3da9-nightly",
"@blocksuite/global": "0.0.0-20230824081236-b25a3da9-nightly",
"@blocksuite/icons": "^2.1.31",
"@blocksuite/lit": "0.0.0-20230824081236-b25a3da9-nightly",
"@blocksuite/store": "0.0.0-20230824081236-b25a3da9-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.12",
"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",
@@ -49,32 +57,33 @@
"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",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1",

View File

@@ -1,15 +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/**/*",
"{workspaceRoot}/packages/env/src/**/*",
"{workspaceRoot}/packages/component/src/**/*",
"{workspaceRoot}/packages/debug/src/**/*",
"{workspaceRoot}/packages/graphql/src/**/*",
@@ -45,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: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

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: 4.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 523 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 819 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 585 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: 4.2 MiB

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

@@ -1,15 +0,0 @@
// static server for web app
import express from 'express';
const app = express();
const PORT = process.env.PORT || 8080;
app.use('/', express.static('dist'));
app.get('/*', (req, res) => {
res.sendFile('index.html', { root: 'dist' });
});
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});

View File

@@ -0,0 +1,53 @@
import { assertExists } from '@blocksuite/global/utils';
import {
getCurrentStore,
loadedPluginNameAtom,
} 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 { createSetup } from '../bootstrap/plugins/setup';
import { bootstrapPluginSystem } from '../bootstrap/register-plugins';
async function main() {
const { setup } = await import('../bootstrap/setup');
const rootStore = getCurrentStore();
await setup(rootStore);
const { _pluginNestedImportsMap } = createSetup(rootStore);
const pluginRegisterPromise = bootstrapPluginSystem(rootStore);
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,12 @@ 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 { getCurrentStore } from '@toeverything/infra/atom';
import { buildShowcaseWorkspace } from '@toeverything/infra/blocksuite';
import { useCallback } from 'react';
import { setPageModeAtom } from '../../atoms';
import {
BlockSuitePageList,
NewWorkspaceSettingDetail,
@@ -41,21 +45,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: getCurrentStore(),
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,
@@ -86,7 +92,7 @@ export const LocalAdapter: WorkspaceAdapter<WorkspaceFlavour.LOCAL> = {
<>
<PageDetailEditor
pageId={currentPageId}
onInit={initEmptyPage}
onInit={useCallback(async page => initEmptyPage(page), [])}
onLoad={onLoadEditor}
workspace={workspace}
/>

View File

@@ -1,18 +1,19 @@
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 { getCurrentStore } from '@toeverything/infra/atom';
import { use } from 'foxact/use';
import type { PropsWithChildren, ReactElement } from 'react';
import { lazy, memo, Suspense, useEffect } from 'react';
import { lazy, memo, Suspense } from 'react';
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(() =>
@@ -32,17 +33,22 @@ const future = {
v7_startTransition: true,
} as const;
export const App = memo(function App() {
useEffect(() => {
async function loadLanguage() {
if (environment.isBrowser) {
const { createI18n, setUpLanguage } = await import('@affine/i18n');
const i18n = createI18n();
document.documentElement.lang = i18n.language;
// todo(himself65): this is a hack, we should use a better way to set the language
setUpLanguage(i18n)?.catch(error => {
console.error(error);
});
}, []);
await setUpLanguage(i18n);
}
}
const languageLoadingPromise = loadLanguage().catch(console.error);
export const App = memo(function App() {
use(languageLoadingPromise);
return (
<CacheProvider value={cache}>
<AffineContext>
<AffineContext store={getCurrentStore()}>
<DebugProvider>
<RouterProvider
fallbackElement={<WorkspaceFallback key="RouterFallback" />}

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);
@@ -93,7 +92,7 @@ export const pageSettingFamily = atomFamily((pageId: string) =>
export const setPageModeAtom = atom(
void 0,
(get, set, pageId: string, mode: PageMode) => {
(_, set, pageId: string, mode: PageMode) => {
set(pageSettingFamily(pageId), { mode });
}
);

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,133 +0,0 @@
import { 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);
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.SubDoc,
};
await migrateLocalBlobStorage(workspace.id, newId);
};
// create a new workspace and push it to metadata
promises.push(upgrade());
}
});
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 =>
({
id,
flavour: Plugin.flavour,
// new workspace should all support sub-doc feature
version: WorkspaceVersion.SubDoc,
}) satisfies RootWorkspaceMetadataV2
);
}).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);
}
})
.catch(console.error);

View File

@@ -0,0 +1,86 @@
export interface FetchOptions {
fetch?: typeof fetch;
signal?: AbortSignal;
normalizeURL?(url: string): string;
/**
* Virtualize a url
* @param url URL to be rewrite
* @param direction Direction of this rewrite.
* 'in' means the url is from the outside world and should be virtualized.
* 'out' means the url is from the inside world and should be de-virtualized to fetch the real target.
*/
rewriteURL?(url: string, direction: 'in' | 'out'): string;
replaceRequest?(request: Request): Request | PromiseLike<Request>;
replaceResponse?(response: Response): Response | PromiseLike<Response>;
canConnect?(url: string): boolean | PromiseLike<boolean>;
}
export function createFetch(options: FetchOptions) {
const {
fetch: _fetch = fetch,
signal,
rewriteURL,
replaceRequest,
replaceResponse,
canConnect,
normalizeURL,
} = options;
return async function fetch(input: RequestInfo, init?: RequestInit) {
let request = new Request(input, {
...init,
signal: getMergedSignal(init?.signal, signal) || null,
});
if (normalizeURL) request = new Request(normalizeURL(request.url), request);
if (canConnect && !(await canConnect(request.url)))
throw new TypeError('Failed to fetch');
if (rewriteURL)
request = new Request(rewriteURL(request.url, 'out'), request);
if (replaceRequest) request = await replaceRequest(request);
let response = await _fetch(request);
if (rewriteURL) {
const { url, redirected, type } = response;
// Note: Response constructor does not allow us to set the url of a response.
// we have to define the own property on it. This is not a good simulation.
// To prevent get the original url by Response.prototype.[[get url]].call(response)
// we copy a response and set it's url to empty.
response = new Response(response.body, response);
Object.defineProperties(response, {
url: { value: url, configurable: true },
redirected: { value: redirected, configurable: true },
type: { value: type, configurable: true },
});
Object.defineProperty(response, 'url', {
configurable: true,
value: rewriteURL(url, 'in'),
});
}
if (replaceResponse) response = await replaceResponse(response);
return response;
};
}
function getMergedSignal(
signal: AbortSignal | undefined | null,
signal2: AbortSignal | undefined | null
) {
if (!signal) return signal2;
if (!signal2) return signal;
const abortController = new AbortController();
signal.addEventListener('abort', () => abortController.abort(), {
once: true,
});
signal2.addEventListener('abort', () => abortController.abort(), {
once: true,
});
return abortController.signal;
}

View File

@@ -0,0 +1,110 @@
type Handler = (...args: any[]) => void;
export interface Timers {
setTimeout: (handler: Handler, timeout?: number, ...args: any[]) => number;
clearTimeout: (handle: number) => void;
setInterval: (handler: Handler, timeout?: number, ...args: any[]) => number;
clearInterval: (handle: number) => void;
requestAnimationFrame: (callback: Handler) => number;
cancelAnimationFrame: (handle: number) => void;
requestIdleCallback?: typeof window.requestIdleCallback | undefined;
cancelIdleCallback?: typeof window.cancelIdleCallback | undefined;
queueMicrotask: typeof window.queueMicrotask;
}
export function createTimers(
abortSignal: AbortSignal,
originalTimes: Timers = {
requestAnimationFrame,
cancelAnimationFrame,
requestIdleCallback:
typeof requestIdleCallback === 'function'
? requestIdleCallback
: undefined,
cancelIdleCallback:
typeof cancelIdleCallback === 'function' ? cancelIdleCallback : undefined,
setTimeout,
clearTimeout,
setInterval,
clearInterval,
queueMicrotask,
}
): Timers {
const {
requestAnimationFrame: _requestAnimationFrame,
cancelAnimationFrame: _cancelAnimationFrame,
setInterval: _setInterval,
clearInterval: _clearInterval,
setTimeout: _setTimeout,
clearTimeout: _clearTimeout,
cancelIdleCallback: _cancelIdleCallback,
requestIdleCallback: _requestIdleCallback,
queueMicrotask: _queueMicrotask,
} = originalTimes;
const interval_timer_id: number[] = [];
const idle_id: number[] = [];
const raf_id: number[] = [];
abortSignal.addEventListener(
'abort',
() => {
raf_id.forEach(_cancelAnimationFrame);
interval_timer_id.forEach(_clearInterval);
_cancelIdleCallback && idle_id.forEach(_cancelIdleCallback);
},
{ once: true }
);
return {
// id is a positive number, it never repeats.
requestAnimationFrame(callback) {
raf_id[raf_id.length] = _requestAnimationFrame(callback);
return raf_id.length;
},
cancelAnimationFrame(handle) {
const id = raf_id[handle - 1];
if (!id) return;
_cancelAnimationFrame(id);
},
setInterval(handler, timeout) {
interval_timer_id[interval_timer_id.length] = (_setInterval as any)(
handler,
timeout
);
return interval_timer_id.length;
},
clearInterval(id) {
if (!id) return;
const handle = interval_timer_id[id - 1];
if (!handle) return;
_clearInterval(handle);
},
setTimeout(handler, timeout) {
idle_id[idle_id.length] = (_setTimeout as any)(handler, timeout);
return idle_id.length;
},
clearTimeout(id) {
if (!id) return;
const handle = idle_id[id - 1];
if (!handle) return;
_clearTimeout(handle);
},
requestIdleCallback: _requestIdleCallback
? function requestIdleCallback(callback, options) {
idle_id[idle_id.length] = _requestIdleCallback(callback, options);
return idle_id.length;
}
: undefined,
cancelIdleCallback: _cancelIdleCallback
? function cancelIdleCallback(handle) {
const id = idle_id[handle - 1];
if (!id) return;
_cancelIdleCallback(id);
}
: undefined,
queueMicrotask(callback) {
_queueMicrotask(() => abortSignal.aborted || callback());
},
};
}

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

@@ -0,0 +1,580 @@
import { DebugLogger } from '@affine/debug';
import type {
CallbackMap,
ExpectedLayout,
LayoutNode,
PluginContext,
} 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,
} from '@toeverything/infra/atom';
import { atom } from 'jotai';
import { Provider } from 'jotai/react';
import type { createStore } from 'jotai/vanilla';
import { createElement, type PropsWithChildren } from 'react';
import { createFetch } from './endowments/fercher';
import { createTimers } from './endowments/timer';
import { setupImportsMap } from './setup-imports-map';
// DO NOT REMOVE INVISIBLE CHARACTERS
const dynamicImportKey = '$h_import';
const permissionLogger = new DebugLogger('plugins:permission');
const importLogger = new DebugLogger('plugins:import');
const entryLogger = new DebugLogger('plugins:entry');
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;
}
});
});
const setupWeakMap = new WeakMap<
ReturnType<typeof createStore>,
ReturnType<typeof createSetupImpl>
>();
export function createSetup(rootStore: ReturnType<typeof createStore>) {
if (setupWeakMap.has(rootStore)) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return setupWeakMap.get(rootStore)!;
}
const setup = createSetupImpl(rootStore);
setupWeakMap.set(rootStore, setup);
return setup;
}
function createSetupImpl(rootStore: ReturnType<typeof createStore>) {
// clean up plugin windows when switching to other pages
rootStore.sub(currentPageAtom, () => {
rootStore.set(contentLayoutAtom, 'editor');
});
// module -> importName -> updater[]
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,
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>>
>();
const pluginImportsFunctionMap = new Map<string, (imports: any) => void>();
const createImports = (pluginName: string) => {
if (pluginImportsFunctionMap.has(pluginName)) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return pluginImportsFunctionMap.get(pluginName)!;
}
const imports = (
newUpdaters: [string, [string, ((val: any) => void)[]][]][]
) => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const currentImportMap = _pluginNestedImportsMap.get(pluginName)!;
importLogger.debug('currentImportMap', pluginName, currentImportMap);
for (const [module, moduleUpdaters] of newUpdaters) {
importLogger.debug('imports module', module, moduleUpdaters);
let moduleImports = _rootImportsMap.get(module);
if (!moduleImports) {
moduleImports = currentImportMap.get(module);
}
if (moduleImports) {
for (const [importName, importUpdaters] of moduleUpdaters) {
const updateImport = (value: any) => {
for (const importUpdater of importUpdaters) {
importUpdater(value);
}
};
if (moduleImports.has(importName)) {
const val = moduleImports.get(importName);
updateImport(val);
}
}
} else {
console.error(
'cannot find module in plugin import map',
module,
currentImportMap,
_pluginNestedImportsMap
);
}
}
};
pluginImportsFunctionMap.set(pluginName, imports);
return imports;
};
const abortController = new AbortController();
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<
string,
(moduleName: string) => Promise<any>
>();
const createOrGetDynamicImport = (baseUrl: string, pluginName: string) => {
if (dynamicImportMap.has(pluginName)) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return dynamicImportMap.get(pluginName)!;
}
const dynamicImport = async (moduleName: string): Promise<any> => {
const codeUrl = `${baseUrl}/${moduleName}`;
const analysisUrl = `${baseUrl}/${moduleName}.json`;
const response = await fetch(codeUrl);
const analysisResponse = await fetch(analysisUrl);
const analysis = await analysisResponse.json();
const exports = analysis.exports as string[];
const code = await response.text();
const moduleCompartment = new Compartment(
createOrGetGlobalThis(
pluginName,
// use singleton here to avoid infinite loop
createOrGetDynamicImport(pluginName, baseUrl)
)
);
const entryPoint = moduleCompartment.evaluate(code, {
__evadeHtmlCommentTest__: true,
});
const moduleExports = {} as Record<string, any>;
const setVarProxy = new Proxy(
{},
{
get(_, p: string): any {
return (newValue: any) => {
moduleExports[p] = newValue;
};
},
}
);
entryPoint({
imports: createImports(pluginName),
liveVar: setVarProxy,
onceVar: setVarProxy,
});
importLogger.debug('import', moduleName, exports, moduleExports);
return moduleExports;
};
dynamicImportMap.set(pluginName, dynamicImport);
return dynamicImport;
};
const globalThisMap = new Map<string, any>();
const createOrGetGlobalThis = (
pluginName: string,
dynamicImport: (moduleName: string) => Promise<any>
) => {
if (globalThisMap.has(pluginName)) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return globalThisMap.get(pluginName)!;
}
const pluginGlobalThis = Object.assign(
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,
},
}),
// dynamic import function
[dynamicImportKey]: dynamicImport,
// UNSAFE: React will read `window` and `document`
window: new Proxy(
{},
{
get(_, key) {
permissionLogger.debug(`${pluginName} is accessing window`, key);
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`,
key,
args
);
return result.apply(window, args);
};
}
permissionLogger.debug('window', key, result);
return result;
},
}
),
document: new Proxy(
{},
{
get(_, key) {
permissionLogger.debug(
`${pluginName} is accessing document`,
key
);
if (sharedGlobalThis[key]) return sharedGlobalThis[key];
const result = Reflect.get(document, key);
if (typeof result === 'function') {
return function (...args: any[]) {
permissionLogger.debug(
`${pluginName} is calling window`,
key,
args
);
return result.apply(document, args);
};
}
permissionLogger.debug('document', key, result);
return result;
},
}
),
navigator: {
userAgent: navigator.userAgent,
},
MouseEvent: globalThis.MouseEvent,
KeyboardEvent: globalThis.KeyboardEvent,
CustomEvent: globalThis.CustomEvent,
// copilot uses these
Date: globalThis.Date,
Math: globalThis.Math,
URL: globalThis.URL,
URLSearchParams: globalThis.URLSearchParams,
Headers: globalThis.Headers,
TextEncoder: globalThis.TextEncoder,
TextDecoder: globalThis.TextDecoder,
Request: globalThis.Request,
// image-preview uses these
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,
IDBDatabase: globalThis.IDBDatabase,
IDBCursorWithValue: globalThis.IDBCursorWithValue,
IDBFactory: globalThis.IDBFactory,
IDBKeyRange: globalThis.IDBKeyRange,
IDBOpenDBRequest: globalThis.IDBOpenDBRequest,
IDBTransaction: globalThis.IDBTransaction,
IDBObjectStore: globalThis.IDBObjectStore,
IDBIndex: globalThis.IDBIndex,
IDBCursor: globalThis.IDBCursor,
IDBVersionChangeEvent: globalThis.IDBVersionChangeEvent,
}
);
pluginGlobalThis.global = pluginGlobalThis;
globalThisMap.set(pluginName, pluginGlobalThis);
return pluginGlobalThis;
};
const setupPluginCode = async (
baseUrl: string,
pluginName: string,
filename: string
) => {
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 isMissingPackage = (name: string) =>
_rootImportsMap.has(name) && !currentImportMap.has(name);
const bundleAnalysis = await fetch(`${baseUrl}/${filename}.json`).then(
res => res.json()
);
const moduleExports = bundleAnalysis.exports as Record<string, [string]>;
const moduleImports = bundleAnalysis.imports as string[];
const moduleReexports = bundleAnalysis.reexports as Record<
string,
[localName: string, exportedName: string][]
>;
await Promise.all(
moduleImports.map(name => {
if (isMissingPackage(name)) {
return Promise.resolve();
} else {
importLogger.debug('missing package', name);
return setupPluginCode(baseUrl, pluginName, name);
}
})
);
const code = await fetch(
`${baseUrl}/${filename.replace(/^\.\//, '')}`
).then(res => res.text());
importLogger.debug('evaluating', filename);
const moduleCompartment = new Compartment(
createOrGetGlobalThis(
pluginName,
// use singleton here to avoid infinite loop
createOrGetDynamicImport(baseUrl, pluginName)
)
);
const entryPoint = moduleCompartment.evaluate(code, {
__evadeHtmlCommentTest__: true,
});
const moduleExportsMap = new Map<string, any>();
const setVarProxy = new Proxy(
{},
{
get(_, p: string): any {
return (newValue: any) => {
moduleExportsMap.set(p, newValue);
};
},
}
);
currentImportMap.set(filename, moduleExportsMap);
entryPoint({
imports: createImports(pluginName),
liveVar: setVarProxy,
onceVar: setVarProxy,
});
for (const [newExport, [originalExport]] of Object.entries(moduleExports)) {
if (newExport === originalExport) continue;
const value = moduleExportsMap.get(originalExport);
moduleExportsMap.set(newExport, value);
moduleExportsMap.delete(originalExport);
}
for (const [name, reexports] of Object.entries(moduleReexports)) {
const targetExports = currentImportMap.get(filename);
const moduleExports = currentImportMap.get(name);
assertExists(targetExports);
assertExists(moduleExports);
for (const [exportedName, localName] of reexports) {
const exportedValue: any = moduleExports.get(exportedName);
assertExists(exportedValue);
targetExports.set(localName, exportedValue);
}
}
};
const PluginProvider = ({ children }: PropsWithChildren) =>
createElement(
Provider,
{
store: rootStore,
},
children
);
const evaluatePluginEntry = (pluginName: string) => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const currentImportMap = _pluginNestedImportsMap.get(pluginName)!;
const pluginExports = currentImportMap.get('index.js');
assertExists(pluginExports);
const entryFunction = pluginExports.get('entry');
const cleanup = entryFunction(<PluginContext>{
register: (part, callback) => {
entryLogger.info(`Registering ${pluginName} to ${part}`);
if (part === 'headerItem') {
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(pluginEditorAtom, items => ({
...items,
[pluginName]: callback as CallbackMap['editor'],
}));
addCleanup(pluginName, () => {
rootStore.set(pluginEditorAtom, items => {
const newItems = { ...items };
delete newItems[pluginName];
return newItems;
});
});
} else if (part === 'setting') {
rootStore.set(pluginSettingAtom, items => ({
...items,
[pluginName]: callback as CallbackMap['setting'],
}));
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}`);
}
},
utils: {
PluginProvider,
},
});
if (typeof cleanup !== 'function') {
throw new Error('Plugin entry must return a function');
}
addCleanup(pluginName, cleanup);
};
return {
_rootImportsMap,
_pluginNestedImportsMap,
createImports,
createOrGetDynamicImport,
setupPluginCode,
evaluatePluginEntry,
createOrGetGlobalThis,
};
}

View File

@@ -1,236 +1,134 @@
/// <reference types="@types/webpack-env" />
import 'ses';
import * as AFFiNEComponent from '@affine/component';
import { FormatQuickBar } from '@blocksuite/blocks';
import * as BlockSuiteBlocksStd from '@blocksuite/blocks/std';
import { DisposableGroup } from '@blocksuite/global/utils';
import * as BlockSuiteGlobalUtils from '@blocksuite/global/utils';
import * as Icons from '@blocksuite/icons';
import * as Atom from '@toeverything/plugin-infra/atom';
import { DebugLogger } from '@affine/debug';
import {
editorItemsAtom,
headerItemsAtom,
registeredPluginAtom,
rootStore,
settingItemsAtom,
windowItemsAtom,
} from '@toeverything/plugin-infra/atom';
import type {
CallbackMap,
PluginContext,
} from '@toeverything/plugin-infra/entry';
import * as Jotai from 'jotai';
import { Provider } from 'jotai/react';
import * as JotaiUtils from 'jotai/utils';
import type { PropsWithChildren } from 'react';
import * as React from 'react';
import * as ReactJSXRuntime from 'react/jsx-runtime';
import * as ReactDom from 'react-dom';
import * as ReactDomClient from 'react-dom/client';
builtinPluginPaths,
enabledPluginAtom,
invokeCleanup,
pluginPackageJson,
} from '@toeverything/infra/__internal__/plugin';
import {
getCurrentStore,
loadedPluginNameAtom,
} from '@toeverything/infra/atom';
import { packageJsonOutputSchema } from '@toeverything/infra/type';
import type { z } from 'zod';
if (!process.env.COVERAGE) {
lockdown({
evalTaming: 'unsafeEval',
overrideTaming: 'severe',
consoleTaming: 'unsafe',
errorTaming: 'unsafe',
errorTrapping: 'platform',
unhandledRejectionTrapping: 'report',
});
import { createSetup } from './plugins/setup';
const logger = new DebugLogger('register-plugins');
declare global {
// eslint-disable-next-line no-var
var __pluginPackageJson__: unknown[];
}
const PluginProvider = ({ children }: PropsWithChildren) =>
React.createElement(
Provider,
{
store: rootStore,
},
children
);
Object.defineProperty(globalThis, '__pluginPackageJson__', {
get() {
return getCurrentStore().get(pluginPackageJson);
},
});
const customRequire = (id: string) => {
if (id === '@toeverything/plugin-infra/atom') {
return Atom;
}
if (id === 'react') {
return React;
}
if (id === 'react/jsx-runtime') {
return ReactJSXRuntime;
}
if (id === 'react-dom') {
return ReactDom;
}
if (id === 'react-dom/client') {
return ReactDomClient;
}
if (id === '@blocksuite/icons') {
return Icons;
}
if (id === '@affine/component') {
return AFFiNEComponent;
}
if (id === '@blocksuite/blocks/std') {
return BlockSuiteBlocksStd;
}
if (id === '@blocksuite/global/utils') {
return BlockSuiteGlobalUtils;
}
if (id === 'jotai') {
return Jotai;
}
if (id === 'jotai/utils') {
return JotaiUtils;
}
throw new Error(`Cannot find module '${id}'`);
};
const createGlobalThis = () => {
return {
process: Object.freeze({
env: {
NODE_ENV: process.env.NODE_ENV,
},
}),
// UNSAFE: React will read `window` and `document`
window,
document,
navigator,
userAgent: navigator.userAgent,
// todo(himself65): permission control
fetch: function (input: RequestInfo, init?: RequestInit) {
return globalThis.fetch(input, init);
},
setTimeout: function (callback: () => void, timeout: number) {
return globalThis.setTimeout(callback, timeout);
},
clearTimeout: function (id: number) {
return globalThis.clearTimeout(id);
},
// copilot uses these
crypto: globalThis.crypto,
CustomEvent: globalThis.CustomEvent,
Date: globalThis.Date,
Math: globalThis.Math,
URL: globalThis.URL,
URLSearchParams: globalThis.URLSearchParams,
Headers: globalThis.Headers,
TextEncoder: globalThis.TextEncoder,
TextDecoder: globalThis.TextDecoder,
Request: globalThis.Request,
Error: globalThis.Error,
// fixme: use our own db api
indexedDB: globalThis.indexedDB,
IDBRequest: globalThis.IDBRequest,
IDBDatabase: globalThis.IDBDatabase,
IDBCursorWithValue: globalThis.IDBCursorWithValue,
IDBFactory: globalThis.IDBFactory,
IDBKeyRange: globalThis.IDBKeyRange,
IDBOpenDBRequest: globalThis.IDBOpenDBRequest,
IDBTransaction: globalThis.IDBTransaction,
IDBObjectStore: globalThis.IDBObjectStore,
IDBIndex: globalThis.IDBIndex,
IDBCursor: globalThis.IDBCursor,
IDBVersionChangeEvent: globalThis.IDBVersionChangeEvent,
exports: {},
console: globalThis.console,
require: customRequire,
};
};
const group = new DisposableGroup();
const pluginList = (await (
await fetch(new URL(`./plugins/plugin-list.json`, window.location.origin))
).json()) as { name: string; assets: string[]; release: boolean }[];
await Promise.all(
pluginList.map(({ name: plugin, release, assets }) => {
if (!release && process.env.NODE_ENV !== 'development') {
return Promise.resolve();
}
const pluginCompartment = new Compartment(createGlobalThis(), {});
const pluginGlobalThis = pluginCompartment.globalThis;
const baseURL = new URL(`./plugins/${plugin}/`, window.location.origin);
const entryURL = new URL('index.js', baseURL);
rootStore.set(registeredPluginAtom, prev => [...prev, plugin]);
return fetch(entryURL).then(async res => {
if (assets.length > 0) {
await Promise.all(
assets.map(async asset => {
if (asset.endsWith('.css')) {
const res = await fetch(new URL(asset, baseURL));
if (res.ok) {
// todo: how to put css file into sandbox?
return res.text().then(text => {
const style = document.createElement('style');
style.setAttribute('plugin-id', plugin);
style.textContent = text;
document.head.appendChild(style);
});
}
return null;
} else {
return Promise.resolve();
}
})
);
export async function bootstrapPluginSystem(
rootStore: ReturnType<typeof getCurrentStore>
) {
const { evaluatePluginEntry, setupPluginCode } = createSetup(rootStore);
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);
}
const codeText = await res.text();
pluginCompartment.evaluate(codeText, {
__evadeHtmlCommentTest__: true,
});
pluginGlobalThis.__INTERNAL__ENTRY = {
register: (part, callback) => {
if (part === 'headerItem') {
rootStore.set(headerItemsAtom, items => ({
...items,
[plugin]: callback as CallbackMap['headerItem'],
}));
} else if (part === 'editor') {
rootStore.set(editorItemsAtom, items => ({
...items,
[plugin]: callback as CallbackMap['editor'],
}));
} else if (part === 'window') {
rootStore.set(windowItemsAtom, items => ({
...items,
[plugin]: callback as CallbackMap['window'],
}));
} else if (part === 'setting') {
console.log('setting');
rootStore.set(settingItemsAtom, items => ({
...items,
[plugin]: callback as CallbackMap['setting'],
}));
} else if (part === 'formatBar') {
console.log('1');
FormatQuickBar.customElements.push((page, getBlockRange) => {
console.log('2');
const div = document.createElement('div');
(callback as CallbackMap['formatBar'])(div, page, getBlockRange);
return div;
});
} else {
throw new Error(`Unknown part: ${part}`);
}
},
utils: {
PluginProvider,
},
} satisfies PluginContext;
const dispose = pluginCompartment.evaluate(
'exports.entry(__INTERNAL__ENTRY)'
);
if (typeof dispose !== 'function') {
throw new Error('Plugin entry must return a function');
}
pluginGlobalThis.__INTERNAL__ENTRY = undefined;
group.add(dispose);
});
})
);
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>();
console.log('register plugins finished');
// we will load all plugins in parallel from builtinPlugins
return Promise.all(
[...builtinPluginPaths].map(url => {
return fetch(`${url}/package.json`)
.then(async res => {
const packageJson = (await res.json()) as z.infer<
typeof packageJsonOutputSchema
>;
packageJsonOutputSchema.parse(packageJson);
const {
name: pluginName,
affinePlugin: {
release,
entry: { core },
assets,
},
} = packageJson;
rootStore.set(pluginPackageJson, json => [...json, packageJson]);
logger.debug(`registering plugin ${pluginName}`);
logger.debug(`package.json: ${packageJson}`);
if (!release && !runtimeConfig.enablePlugin) {
return Promise.resolve();
}
const baseURL = url;
const entryURL = `${baseURL}/${core}`;
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?
return res.text().then(text => {
const style = document.createElement('style');
style.setAttribute('plugin-id', pluginName);
style.textContent = text;
document.head.appendChild(style);
});
}
return null;
} else {
return Promise.resolve();
}
})
);
}
if (!enabledPluginSet.has(pluginName)) {
logger.debug(`plugin ${pluginName} is not enabled`);
} else {
logger.debug(`plugin ${pluginName} is enabled`);
evaluatePluginEntry(pluginName);
}
});
})
.catch(e => {
console.error(`error when fetch plugin from ${url}`, e);
});
})
).then(() => {
console.info('All plugins loaded');
});
}

View File

@@ -0,0 +1,188 @@
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 type { createStore } from 'jotai/vanilla';
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(store: ReturnType<typeof createStore>) {
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');
store.set(rootWorkspacesMetadataAtom, result);
}
export async function setup(store: ReturnType<typeof createStore>) {
store.set(
workspaceAdaptersAtom,
WorkspaceAdapters as Record<
WorkspaceFlavour,
WorkspaceAdapter<WorkspaceFlavour>
>
);
console.log('setup global');
setupGlobal();
createFirstAppData(store);
await tryMigration();
await store.get(rootWorkspacesMetadataAtom);
console.log('setup done');
}

View File

@@ -1,40 +0,0 @@
import { initEmptyPage } from '@affine/env/blocksuite';
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';
const blockSuiteWorkspace = getOrCreateWorkspace(
'test',
WorkspaceFlavour.LOCAL
);
const page = blockSuiteWorkspace.createPage({ id: 'page0' });
const Editor: React.FC = () => {
const onLoad = useCallback((page: Page, editor: EditorContainer) => {
// @ts-expect-error
globalThis.page = page;
// @ts-expect-error
globalThis.editor = editor;
return () => void 0;
}, []);
if (!page) {
return <>loading...</>;
}
return (
<BlockSuiteEditor
page={page}
mode="page"
onInit={initEmptyPage}
onLoad={onLoad}
/>
);
};
export default Editor;

View File

@@ -8,8 +8,8 @@ import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
import {
currentPageIdAtom,
currentWorkspaceIdAtom,
rootStore,
} from '@toeverything/plugin-infra/atom';
getCurrentStore,
} from '@toeverything/infra/atom';
import { useAtomValue } from 'jotai/react';
import { Provider } from 'jotai/react';
import type { ErrorInfo, ReactElement, ReactNode } from 'react';
@@ -102,7 +102,7 @@ export class AffineErrorBoundary extends Component<
return (
<>
{errorDetail}
<Provider key="JotaiProvider" store={rootStore}>
<Provider key="JotaiProvider" store={getCurrentStore()}>
<DumpInfo />
</Provider>
</>

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';
@@ -66,7 +70,7 @@ const NameWorkspaceContent = ({
<Input
ref={ref => {
if (ref) {
setTimeout(() => ref.focus(), 0);
window.setTimeout(() => ref.focus(), 0);
}
}}
data-testid="create-workspace-input"
@@ -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';
@@ -81,7 +82,7 @@ export const WorkspaceDeleteModal = ({
<Input
ref={ref => {
if (ref) {
setTimeout(() => ref.focus(), 0);
window.setTimeout(() => ref.focus(), 0);
}
}}
onChange={setDeleteStr}
@@ -92,15 +93,15 @@ export const WorkspaceDeleteModal = ({
/>
</StyledInputContent>
<StyledButtonContent>
<Button shape="circle" onClick={onClose}>
<Button onClick={onClose} size="large">
{t['Cancel']()}
</Button>
<Button
data-testid="delete-workspace-confirm-button"
disabled={!allowDelete}
onClick={handleDelete}
size="large"
type="error"
shape="circle"
style={{ marginLeft: '24px' }}
>
{t['Delete']()}

View File

@@ -5,7 +5,7 @@ export const StyledModalWrapper = styled('div')(() => {
position: 'relative',
padding: '0px',
width: '560px',
background: 'var(--affine-white)',
background: 'var(--affine-background-overlay-panel-color)',
borderRadius: '12px',
// height: '312px',
};

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,13 +58,10 @@ 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(
'https://affine.pro/blog/what-is-new-affine-0717',
'_blank'
);
window.open(runtimeConfig.changelogUrl, '_blank');
}}
>
<ArrowRightSmallIcon />

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,51 +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';
const PluginSettingWrapper: FC<{
id: string;
title?: ReactNode;
}> = ({ title, id }) => {
const Setting = useAtomValue(settingItemsAtom)[id];
const disposeRef = useRef<(() => void) | null>(null);
import { pluginItemStyle } from './style.css';
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 => (
<PluginSettingWrapper key={plugin} id={plugin} title={plugin} />
{useAtomValue(pluginPackageJson).map(json => (
<PluginItem json={json} key={json.name} />
))}
</>
);

View File

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

View File

@@ -3,20 +3,48 @@ import { SettingWrapper } from '@affine/component/setting-components';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import {
type ShortcutsInfo,
useEdgelessShortcuts,
useGeneralShortcuts,
useMarkdownShortcuts,
usePageShortcuts,
} from '../../../../../hooks/affine/use-shortcuts';
import { shortcutRow } from './style.css';
import { shortcutKey, shortcutKeyContainer, shortcutRow } from './style.css';
const ShortcutsPanel = ({
shortcutsInfo,
}: {
shortcutsInfo: ShortcutsInfo;
}) => {
return (
<SettingWrapper title={shortcutsInfo.title}>
{Object.entries(shortcutsInfo.shortcuts).map(([title, shortcuts]) => {
return (
<div key={title} className={shortcutRow}>
<span>{title}</span>
<div className={shortcutKeyContainer}>
{shortcuts.map(key => {
return (
<span className={shortcutKey} key={key}>
{key}
</span>
);
})}
</div>
</div>
);
})}
</SettingWrapper>
);
};
export const Shortcuts = () => {
const t = useAFFiNEI18N();
const markdownShortcuts = useMarkdownShortcuts();
const pageShortcuts = usePageShortcuts();
const edgelessShortcuts = useEdgelessShortcuts();
const generalShortcuts = useGeneralShortcuts();
const markdownShortcutsInfo = useMarkdownShortcuts();
const pageShortcutsInfo = usePageShortcuts();
const edgelessShortcutsInfo = useEdgelessShortcuts();
const generalShortcutsInfo = useGeneralShortcuts();
return (
<>
@@ -25,46 +53,10 @@ export const Shortcuts = () => {
subtitle={t['Check Keyboard Shortcuts quickly']()}
data-testid="keyboard-shortcuts-title"
/>
<SettingWrapper title={t['General']()}>
{Object.entries(generalShortcuts).map(([title, shortcuts]) => {
return (
<div key={title} className={shortcutRow}>
<span>{title}</span>
<span className="shortcut">{shortcuts}</span>
</div>
);
})}
</SettingWrapper>
<SettingWrapper title={t['Page']()}>
{Object.entries(pageShortcuts).map(([title, shortcuts]) => {
return (
<div key={title} className={shortcutRow}>
<span>{title}</span>
<span className="shortcut">{shortcuts}</span>
</div>
);
})}
</SettingWrapper>
<SettingWrapper title={t['Edgeless']()}>
{Object.entries(edgelessShortcuts).map(([title, shortcuts]) => {
return (
<div key={title} className={shortcutRow}>
<span>{title}</span>
<span className="shortcut">{shortcuts}</span>
</div>
);
})}
</SettingWrapper>
<SettingWrapper title={t['Markdown Syntax']()}>
{Object.entries(markdownShortcuts).map(([title, shortcuts]) => {
return (
<div key={title} className={shortcutRow}>
<span>{title}</span>
<span className="shortcut">{shortcuts}</span>
</div>
);
})}
</SettingWrapper>
<ShortcutsPanel shortcutsInfo={generalShortcutsInfo} />
<ShortcutsPanel shortcutsInfo={pageShortcutsInfo} />
<ShortcutsPanel shortcutsInfo={edgelessShortcutsInfo} />
<ShortcutsPanel shortcutsInfo={markdownShortcutsInfo} />
</>
);
};

View File

@@ -1,4 +1,4 @@
import { globalStyle, style } from '@vanilla-extract/css';
import { style } from '@vanilla-extract/css';
export const shortcutRow = style({
height: '32px',
@@ -14,8 +14,25 @@ export const shortcutRow = style({
},
});
globalStyle(`${shortcutRow} .shortcut`, {
border: '1px solid var(--affine-border-color)',
borderRadius: '8px',
padding: '4px 18px',
export const shortcutKeyContainer = style({
display: 'flex',
});
export const shortcutKey = style({
minWidth: '24px',
height: '20px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
padding: '0 6px',
border: '1px solid var(--affine-border-color)',
borderRadius: '4px',
background: 'var(--affine-background-tertiary-color)',
boxShadow:
'0px 6px 4px 0px rgba(255, 255, 255, 0.24) inset, 0px 0px 0px 0.5px rgba(0, 0, 0, 0.10) inset',
fontSize: 'var(--affine-font-xs)',
selectors: {
'&:not(:last-of-type)': {
marginRight: '2px',
},
},
});

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',

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