Compare commits

..

231 Commits

Author SHA1 Message Date
Alex Yang
9d85318082 v0.7.3 2023-08-22 15:26:08 -05:00
Alex Yang
d2316cfdb3 ci: add setup-maker 2023-08-22 15:26:08 -05:00
Peng Xiao
113e4d860c build: sign windows app (#3809) 2023-08-22 15:26:08 -05:00
Alex Yang
27e1a7c171 v0.7.2 2023-08-11 02:10:44 -04:00
Alex Yang
42b90e1d8d fix: remove usage of mergeUpdates (#3687) 2023-08-11 02:09:33 -04:00
Alex Yang
52f01a71d2 v0.7.1 2023-07-30 19:24:31 -07:00
Alex Yang
ab6774bef9 fix(core): migration (#3322) 2023-07-31 10:19:18 +08:00
Alex Yang
7930671a51 v0.7.0 2023-07-27 21:27:37 -07:00
Alex Yang
3acd546337 v0.7.0-beta.1 2023-07-18 13:02:18 +08:00
Alex Yang
3bcf30348a build: fix nx inputs
(cherry picked from commit 41edacfc81)
2023-07-18 13:00:33 +08:00
JimmFly
74909d4fd5 fix: temporarily handle all page scroll bar styles (#3269) 2023-07-17 16:12:53 +08:00
Camol
691eb3cb2d feat(i18n): support i18n in app version (#3263) 2023-07-17 16:12:51 +08:00
Peng Xiao
6ddc56632e fix: disable updater button when app updating (#3268)
(cherry picked from commit 81bad608bc)
2023-07-17 16:12:45 +08:00
Alex Yang
5c183c8353 fix(plugin-infra): react as peer dependency (#3260)
(cherry picked from commit eeed398155)
2023-07-17 15:49:01 +08:00
xiaodong zuo
5e62fec49c chore: update blocksuite version (#3261)
(cherry picked from commit f173c8b183)
2023-07-17 15:49:00 +08:00
Alex Yang
8f590ff360 fix: first workspace not found (#3258)
(cherry picked from commit 071d582250)
2023-07-17 13:32:16 +08:00
JimmFly
24bb109ec3 chore: upadete onboarding video and changlog link (#3255)
(cherry picked from commit e8f8bd21cf)
2023-07-17 13:32:16 +08:00
Alex Yang
efdf6c2b81 refactor: use useCallback (#3254)
(cherry picked from commit c0749fbb9f)
2023-07-17 13:32:16 +08:00
Si Yang
b40051e4a4 docs: update building-desktop-client-app.md (#3248) 2023-07-17 11:24:10 +08:00
Alex Yang
02506bcda4 fix(plugin-infra): dependencies (#3252) 2023-07-17 11:24:10 +08:00
Alex Yang
58dc0f2b2f chore: bump version (#3250)
Co-authored-by: Alex Yang <himself65@Alexs-MacBook-Pro.local>
(cherry picked from commit 202e9b8fe3)
2023-07-16 23:30:59 +08:00
angle
5a648baff4 fix: pwa icon (#3246)
(cherry picked from commit ce23817c11)
2023-07-16 21:44:49 +08:00
Alex Yang
5ec3279910 fix: create first workspace logic (#3241)
(cherry picked from commit c49cf1c53c)
2023-07-16 21:44:49 +08:00
Alex Yang
e29db64e7f fix: migration logic (#3238)
(cherry picked from commit 1bc427e7a6)
2023-07-16 21:44:49 +08:00
Alex Yang
f8ec1ff6d7 fix: remove hello-world page (#3234)
(cherry picked from commit ea592eb150)
2023-07-16 21:44:49 +08:00
Alex Yang
e1a62c1f03 refactor: simplify code (#3231)
(cherry picked from commit 5864f8cb9a)
2023-07-16 21:44:49 +08:00
Alex Yang
27fa5654af revert: use stable react (#3228) 2023-07-14 14:03:43 +08:00
Peng Xiao
0a7852b1eb fix: internal build updater (#3229) 2023-07-14 14:03:43 +08:00
Alex Yang
0ca9f2cc6d fix(plugin-infra): dependencies (#3226) 2023-07-14 11:30:55 +08:00
Alex Yang
4750e2535f chore: bump version (#3223) 2023-07-14 00:46:42 +08:00
Alex Yang
9ff483cd4e refactor(plugin-infra): workspace loading (#3222) 2023-07-14 00:46:28 +08:00
3720
c38dd1cc5e fix: can't go back to all pages by click All Pages button (#3219) 2023-07-14 00:46:28 +08:00
Alex Yang
735d91a003 refactor: mode atom (#3221) 2023-07-14 00:46:28 +08:00
Alex Yang
02946a0f4f test: add workspace ref in e2e (#3217) 2023-07-14 00:46:28 +08:00
Alex Yang
22aa7ff25f refactor(web): move current atoms into plugin infra (#3220) 2023-07-14 00:46:27 +08:00
Alex Yang
912cd7fb8b refactor: move test utils to package (#3206) 2023-07-14 00:46:24 +08:00
3720
559d9bb9a8 fix: collections should be unique for workspaces (#3213) 2023-07-13 16:52:07 +08:00
Alex Yang
944f4ee895 fix: fullscreen settting not working (#3215)
Co-authored-by: QiShaoXuan <qishaoxuan777@gmail.com>
2023-07-13 16:52:00 +08:00
xiaodong zuo
94d518e09a chore: update blocksuite version (#3209)
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-07-13 16:52:00 +08:00
Alex Yang
b9dff62f1f fix: page id could be invalid (#3204) 2023-07-13 16:52:00 +08:00
Alex Yang
1bc8d6a0b8 fix: block hub might not work (#3199) 2023-07-13 02:16:55 +08:00
Alex Yang
04ade02254 chore: bump version (#3197) 2023-07-13 02:09:10 +08:00
Alex Yang
655b6d5399 chore: bump version (#3196) 2023-07-13 02:09:10 +08:00
Peng Xiao
9df1eac0b8 fix: iconUrl for windows build (#3194) 2023-07-13 02:09:10 +08:00
Alex Yang
e58b21c599 fix: first page id conflict (#3192) 2023-07-13 02:09:10 +08:00
Alex Yang
c1e53b90a2 fix: unexpected jump 404 page (#3190) 2023-07-13 02:09:10 +08:00
JimmFly
1fecb22627 style: update delete button style (#3180) 2023-07-13 02:09:10 +08:00
Peng Xiao
770bde8088 fix: add default fonts (#3185)
(cherry picked from commit bd42380f8a)
2023-07-12 17:21:21 +08:00
xiaodong zuo
bb7ed6869e fix: enhancing the security of image proxy (#3176)
(cherry picked from commit 30dee18835)
2023-07-12 17:21:21 +08:00
Alex Yang
ac55ae467c chore: bump version (#3179)
(cherry picked from commit e51c98c1dd)
2023-07-12 17:20:58 +08:00
Alex Yang
626e9f96e9 feat: display app version in setting panel (#3170)
(cherry picked from commit bbb1387469)
2023-07-12 17:20:58 +08:00
xiaodong zuo
0efcaa1b6b fix: the image lost after exporting (#3150)
Co-authored-by: Alex Yang <himself65@outlook.com>
(cherry picked from commit 4f88774999)
2023-07-12 17:20:57 +08:00
Alex Yang
8a1f022c49 feat: add suspense to workspace settings (#3167)
Co-authored-by: Qi <474021214@qq.com>
(cherry picked from commit 3968deb6d4)
2023-07-12 17:20:57 +08:00
Alex Yang
7faa909ab1 fix: jump to index page after deletion (#3169)
(cherry picked from commit 37c8465af8)
2023-07-12 17:20:57 +08:00
Peng Xiao
c9a7744270 fix: settings style update (#3161)
(cherry picked from commit d88a21d24a)
2023-07-12 17:20:56 +08:00
3720
2c9c4c2cd3 fix: some typo and i18n (#3155)
(cherry picked from commit 6ad2d106bc)
2023-07-12 17:20:56 +08:00
Alex Yang
b0bb925ef6 refactor: remove unused code (#3149)
(cherry picked from commit 8c1fcee135)
2023-07-12 17:20:56 +08:00
Peng Xiao
272835cfba fix: updater not working (#3144)
(cherry picked from commit 0514da9759)
2023-07-12 17:20:55 +08:00
JimmFly
0c66780fda style: modify the style of community item (#3143)
(cherry picked from commit b2fed03f30)
2023-07-12 17:20:55 +08:00
Alex Yang
74e6fd8d4a fix: remove workspace not working (#3140)
(cherry picked from commit ddb2931f38)
2023-07-11 12:58:01 +08:00
Alex Yang
6815110727 chore: bump version (#3138)
(cherry picked from commit acf17ebace)
2023-07-11 12:58:01 +08:00
Alex Yang
7bda0f87e6 revert: restrict node version
(cherry picked from commit 01de2ae714)
2023-07-11 12:57:57 +08:00
Qi
38521dc8b2 fix: font style setting only control editor's font (#3117)
Co-authored-by: Alex Yang <himself65@outlook.com>
(cherry picked from commit cfa18d1bc3)
2023-07-11 12:57:57 +08:00
Alex Yang
1effd9b22e chore: bump version (#3131)
(cherry picked from commit 127c63601e)
2023-07-11 12:57:57 +08:00
LongYinan
d7a7c6aa50 fix: add semver into server dependencies
(cherry picked from commit f079b0b49a)
2023-07-11 12:57:56 +08:00
Alex Yang
4de1baf2eb refactor: follow correct react rules (#3119)
(cherry picked from commit 6caf934d47)
2023-07-11 12:57:55 +08:00
Qi
d66f7da3c0 feat: modify setting modal entry in quick search modal (#3089)
(cherry picked from commit 2f910fbad0)
2023-07-11 12:57:54 +08:00
Peng Xiao
9b948613dc fix: add DB migration to add workspace (#3115)
(cherry picked from commit dac4e390aa)
2023-07-11 12:57:54 +08:00
JimmFly
77200ed8fc style: change switch tip color (#3123)
(cherry picked from commit 812e0e9c9a)
2023-07-11 12:57:52 +08:00
Alex Yang
d42c53c3da chore: restrict node version (#3120)
(cherry picked from commit 05291a8a36)
2023-07-11 12:57:52 +08:00
JimmFly
28316861e3 test: fix incorrect day suffix (#3121)
(cherry picked from commit 8bcc4d6a57)
2023-07-11 12:57:51 +08:00
danielchim
9bc42c7eca fix: page mode shortcut (#3097)
(cherry picked from commit e06d5e1c8d)
2023-07-11 12:57:51 +08:00
Alex Yang
994a5d9b54 feat: improve error log message (#3112)
(cherry picked from commit 1c8895f23f)
2023-07-11 12:57:50 +08:00
Alex Yang
0ddbe89d88 refactor(hooks): reduce null types (#3111)
(cherry picked from commit 8b5d997322)
2023-07-11 12:57:50 +08:00
Peng Xiao
fdffac6911 fix: disable move db by default (#3105)
Co-authored-by: Alex Yang <himself65@outlook.com>
(cherry picked from commit 33644a68b2)
2023-07-11 12:57:50 +08:00
mon-jai
72466d0768 fix: sidebar noise background on Windows (#3107)
(cherry picked from commit bc85ad5b65)
2023-07-11 12:57:50 +08:00
Alex Yang
4a982b3bcf fix: init workspace before loaded (#3104)
(cherry picked from commit 3c5ccd7231)
2023-07-11 12:57:44 +08:00
Alex Yang
348c56ba30 chore: remove unused code (#3102)
(cherry picked from commit da140b0b85)
2023-07-11 12:57:43 +08:00
Alex Yang
83d92af07d test: fix flaky (#3100)
(cherry picked from commit c4d53d59b5)
2023-07-11 12:57:43 +08:00
boomlion8
b4397eb64f fix: color of UI in dark mode (#3081)
Co-authored-by: boomlion8 <201116201@manit.ac.in>
Co-authored-by: Alex Yang <himself65@outlook.com>
(cherry picked from commit a48726d088)
2023-07-11 12:57:43 +08:00
Alex Yang
2ae7bea5b1 feat: improve workspace hook (#3099)
(cherry picked from commit b49306607b)
2023-07-11 12:57:42 +08:00
Alex Yang
1908d1a213 refactor: lazy load workspaces (#3091)
(cherry picked from commit 283f0cd263)
2023-07-11 12:57:33 +08:00
JimmFly
e6530f5017 chore: add new item for share component (#3084)
(cherry picked from commit 66152401be)
2023-07-11 12:57:33 +08:00
Qi
9ef492f5f8 feat: add font style setting (#3092)
(cherry picked from commit b12412a3c1)
2023-07-11 12:57:32 +08:00
Peng Xiao
a41d9c0ac2 fix: a possible issue on electron flaky test (#3094)
(cherry picked from commit ce1e8d868c)
2023-07-11 12:57:32 +08:00
Alex Yang
d4dd352ebe perf: reduce unused provider connection (#3090)
(cherry picked from commit 3294043180)
2023-07-11 12:57:31 +08:00
Alex Yang
c23d31f912 ci: fix nx.yml (#3086)
(cherry picked from commit 152fbaabda)
2023-07-11 12:57:31 +08:00
JimmFly
94b5563a0e style: adjust settings style (#3083)
(cherry picked from commit 5756bdf8d7)
2023-07-11 12:57:31 +08:00
Alex Yang
446d2d3370 chore: bump version (#3078)
(cherry picked from commit 80ee33fd3e)
2023-07-11 12:57:30 +08:00
Alex Yang
c8492d2014 test: image preview e2e (#3080)
Co-authored-by: danielchim <kahungchim@gmail.com>
(cherry picked from commit 955d80e2c1)
2023-07-11 12:57:30 +08:00
Alex Yang
1a223a99d1 build: fix nx inputs (#3079)
(cherry picked from commit 67fe7f04da)
2023-07-11 12:57:29 +08:00
Alex Yang
b8a2438679 test: upgrade playwright (#3077)
(cherry picked from commit 6395521f09)
2023-07-11 12:57:29 +08:00
Alex Yang
f38978e7c9 fix: cleanup workspace when switch setting panel (#3072)
(cherry picked from commit 822078e640)
2023-07-11 12:57:28 +08:00
Alex Yang
44dbe39001 refactor: block-hub in tool wrapper (#3073)
(cherry picked from commit fafd93f7dc)
2023-07-11 12:57:28 +08:00
Peng Xiao
424580971e fix: workspace storage settings issues (#3055)
(cherry picked from commit 00ce086e79)
2023-07-11 12:57:27 +08:00
Alex Yang
6d552ce85e fix(web): setting panel refresh (#3070)
(cherry picked from commit 28653d6892)
2023-07-11 12:57:27 +08:00
Alex Yang
031dd6d4ea fix(web): fetch hello-world from local (#3062)
(cherry picked from commit e30c67482f)
2023-07-11 12:57:27 +08:00
Pratik Kumar
d2503d46eb fix(component): new page button in all page (#3053)
(cherry picked from commit bda28e0404)
2023-07-11 12:57:26 +08:00
Alex Yang
1d701b9a8e fix(component): image preview fallback (#3058)
(cherry picked from commit ce63364299)
2023-07-11 12:57:26 +08:00
JimmFly
29c3744c53 chore: update communities link and icon (#3052)
(cherry picked from commit f468dff6aa)
2023-07-11 12:57:25 +08:00
Peng Xiao
794f19637d fix: menu item click area (#3051)
(cherry picked from commit fab03006e8)
2023-07-11 12:57:25 +08:00
JimmFly
822c01c360 fix: date-picker hidden in update collection (#3045)
(cherry picked from commit 8a565b8633)
2023-07-11 12:57:24 +08:00
Pratik Kumar
76da19bac6 fix(component): all page list UI padding (#3046)
Co-authored-by: Alex Yang <himself65@outlook.com>
(cherry picked from commit 95c2e20cb5)
2023-07-11 12:57:13 +08:00
JimmFly
c53638c08d chore: temporary fix sync script error (#3044)
(cherry picked from commit 2e0f410978)
2023-07-11 12:57:12 +08:00
Alex Yang
aa31043871 chore: bump version (#3041)
(cherry picked from commit fa1cd87348)
2023-07-11 12:57:11 +08:00
Alex Yang
e2d6d5548f fix: workspace name should change in the setting panel (#3039)
(cherry picked from commit e95d28e136)
2023-07-11 12:57:11 +08:00
Qi
8f1bfa46a9 fix: a series of setting issues (#3032)
(cherry picked from commit 87ba71e77e)
2023-07-11 12:57:10 +08:00
Peng Xiao
0a607e0450 fix: delete workspace in settings (#3030)
(cherry picked from commit dec0c0d3d1)
2023-07-11 12:57:10 +08:00
Peng Xiao
e41abe4cfe fix: updater issues (#3027)
(cherry picked from commit 776172bc88)
2023-07-11 12:57:09 +08:00
Alex Yang
5a846755a8 build: remove legacy cloud config (#3024)
(cherry picked from commit 70ac31b907)
2023-07-05 16:05:52 +08:00
Alex Yang
e99891f22d chore: bump version (#3023)
(cherry picked from commit cff9fd1ead)
2023-07-05 16:05:52 +08:00
Alex Yang
eaf7965e79 docs: update README.md
(cherry picked from commit 319febb00d)
2023-07-05 16:05:52 +08:00
3720
1d2b4c6057 fix: tags does not exist (#3020)
(cherry picked from commit 72fa2da2d3)
2023-07-05 16:05:52 +08:00
Alex Yang
e609ab201f feat: update server login feature (#3004)
Co-authored-by: LongYinan <lynweklm@gmail.com>
(cherry picked from commit 3084c427f1)
2023-07-05 16:05:52 +08:00
3720
43e996355a fix: flaky tests (#3019)
(cherry picked from commit 9cd1f013f8)
2023-07-05 16:05:51 +08:00
Alex Yang
d41c7fb899 chore: bump version (#3016) 2023-07-05 02:25:39 +08:00
Peng Xiao
093851ec0c fix: sqlite provider import sub doc db file (#2991)
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-07-05 02:25:39 +08:00
Alex Yang
c5f5e606b0 chore: update pre-commit (#3017) 2023-07-05 02:25:38 +08:00
JimmFly
b45c8b582e chore: update setting text (#3000) 2023-07-05 02:25:38 +08:00
Alex Yang
d94ad8caac refactor: simplify code (#3015) 2023-07-05 02:25:38 +08:00
Alex Yang
8dfc95d002 ci: check macOS arm64 bundle output (#3012) 2023-07-05 02:25:38 +08:00
Alex Yang
466b1bb173 feat(electron): move preload to infra (#3011) 2023-07-05 02:25:38 +08:00
Alex Yang
1a93429ffa chore: bump nx (#3014) 2023-07-05 02:24:15 +08:00
Fangdun Tsai
e5f150f093 chore(electron): renaming clipboard api (#3005) 2023-07-04 21:52:58 +08:00
Alex Yang
e6d709a8a0 fix: bookmark block output missing (#3010) 2023-07-04 21:52:58 +08:00
Qi
708e361264 feat: modify setting modal (#3008) 2023-07-04 21:52:58 +08:00
3720
34ad5cdaef feat: add tags support (#2988)
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-07-04 16:16:34 +08:00
Alex Yang
65c6479ee4 refactor: input component (#2999) 2023-07-04 16:16:33 +08:00
Alex Yang
394d535906 chore: bump version (#2998) 2023-07-04 16:16:33 +08:00
ShortCipher5
70cf3176b7 docs: update README.md (#2997) 2023-07-04 16:16:33 +08:00
Alex Yang
c11fe97b47 test: use static server (#2996) 2023-07-04 16:16:33 +08:00
Alex Yang
d5926fce8c chore: add circular check (#2995) 2023-07-04 16:16:32 +08:00
Alex Yang
9ba6539b5b build: enable next server (#2992) 2023-07-04 16:16:32 +08:00
danielchim
8ae091b8c3 feat: page mode shortcut (#2985) 2023-07-04 01:17:56 +08:00
Alex Yang
d04585f630 chore: bump version (#2989) 2023-07-04 01:17:44 +08:00
Alex Yang
dedb4ba833 refactor: remove legacy cloud (#2987) 2023-07-04 01:17:43 +08:00
xiaodong zuo
1887a36ca5 fix: dark mode export PDF leaves margin and notification (#2978) 2023-07-04 01:17:43 +08:00
LongYinan
4787fef74a ci: reduce yarn cache (#2983) 2023-07-04 01:17:43 +08:00
Peng Xiao
5be4873d36 fix: potential updater issue (#2973) 2023-07-04 01:17:42 +08:00
Alex Yang
0e342999fd feat: remove old setting page by default (#2980) 2023-07-04 01:17:42 +08:00
JimmFly
f16f02b6b0 chore: add import to sidebar (#2981) 2023-07-04 01:17:42 +08:00
Peng Xiao
3a3053fb49 fix: some potential tests issue (#2982) 2023-07-04 01:17:42 +08:00
Peng Xiao
425439ec42 fix: potential race condition on app load when migration (#2977)
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-07-04 01:17:42 +08:00
Alex Yang
2a4a2db6f6 fix(web): hydration issue (#2974) 2023-07-04 01:17:42 +08:00
JimmFly
562013e3bd style: adjust icon button hover color (#2976) 2023-07-04 01:17:41 +08:00
Alex Yang
1f7c1ef91e chore: add affine-cloud build config (#2971) 2023-07-04 01:17:41 +08:00
Alex Yang
1a80c95ae3 build: update build config (#2967) 2023-07-04 01:17:41 +08:00
Alex Yang
bbb97d06da chore: update 'lint-staged' rules (#2969) 2023-07-04 01:17:41 +08:00
JimmFly
20ce8c0cd5 style: adjust copilot chat style (#2915) 2023-07-04 01:17:41 +08:00
JimmFly
940bf15ac4 i18n: update translation resources (#2968) 2023-07-04 01:17:41 +08:00
Qi
5fe8fae6ae feat: modify preloading data (#2947) 2023-07-03 12:07:12 +08:00
Alex Yang
826281453f chore: bump version 2023-07-03 12:07:09 +08:00
Alex Yang
979ef24c39 chore: bump version (#2960) 2023-07-03 12:07:06 +08:00
Alex Yang
fad07b2cab chore: replace 'eslint-plugin-import' (#2957) 2023-07-03 12:07:06 +08:00
Alex Yang
e147487d9c ci: update labeler.yml 2023-07-03 12:07:06 +08:00
Alex Yang
1e15ae5766 ci: use yarn run test 2023-07-03 12:07:06 +08:00
Alex Yang
5d5110126e fix: remove export script 2023-07-03 12:07:02 +08:00
Alex Yang
b467ab1d76 build: update nx.json 2023-07-03 12:06:58 +08:00
Alex Yang
635ec0a401 chore: improve ci build speed (#2953) 2023-07-03 12:06:58 +08:00
Alex Yang
578f92e376 build: update nx.json 2023-07-03 12:06:57 +08:00
Alex Yang
00604bd4b0 build: fix nx.json (#2951) 2023-07-03 12:06:57 +08:00
Alex Yang
8f09df655c chore: bump version (#2950) 2023-07-03 12:06:56 +08:00
Alex Yang
fbc196e828 build: skip type check in next.js build (#2952) 2023-07-03 12:06:56 +08:00
Alex Yang
d120f3ada8 build: enhance nx build (#2948) 2023-07-03 12:06:56 +08:00
3720
d24009f494 chore: adjust icon and style (#2949) 2023-07-03 12:06:55 +08:00
Alex Yang
721e5e4dd9 docs: update react badge 2023-07-03 12:06:55 +08:00
Alex Yang
612fbb4f97 v0.7.0-beta.0 2023-06-30 17:11:46 +08:00
Alex Yang
bba27fb02e ci: fix build desktop 2023-06-30 17:06:44 +08:00
Alex Yang
92e5b3d937 ci: update paths-ignore 2023-06-30 16:47:07 +08:00
Alex Yang
014566fcc9 ci: chmod 777 on output directory 2023-06-30 16:40:47 +08:00
Alex Yang
3fbeefe329 chore: bump blocksuite to '0.0.0-20230630081054-55a25248-nightly' 2023-06-30 16:17:43 +08:00
regischen
ce09a7c09f fix(web): migrate connector (#2941)
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-06-30 16:17:34 +08:00
Alex Yang
4757a95528 build(electron): use nx (#2942) 2023-06-30 16:17:34 +08:00
Peng Xiao
5e4cd0f81d fix: disable sqlite blob storage (#2943) 2023-06-30 16:17:34 +08:00
Alex Yang
536bf01dd9 fix(cli): run dev-web crash 2023-06-30 16:17:33 +08:00
Alex Yang
99a60c37d0 ci: cancel previous build (#2794) 2023-06-30 16:17:33 +08:00
Alex Yang
bfe6f0b4dd refactor(cli): use typescript (#2938) 2023-06-30 16:17:33 +08:00
Alex Yang
aac0d24aed ci: build infra code before build layers 2023-06-30 16:17:33 +08:00
3720
c60648ce9b feat: support for view management (#2892) 2023-06-30 16:17:32 +08:00
Fangdun Tsai
48e33e2d73 feat: expose clipboard apis (#2932) 2023-06-30 16:17:32 +08:00
Alex Yang
3236a91d0b chore: tag deprecated files (#2936) 2023-06-30 16:17:24 +08:00
Alex Yang
d790ead446 feat: special ip address 'localhost' (#2935) 2023-06-30 16:17:24 +08:00
Alex Yang
12ed9db207 fix(web): disable notification center (#2934) 2023-06-30 12:03:16 +08:00
Alex Yang
5a4bb70869 chore: bump blocksuite to 0.0.0-20230629103121-76e6587d-nightly (#2931) 2023-06-30 10:19:07 +08:00
DarkSky
de52651c7f ci: make helm release only on bump version (#2928) 2023-06-30 10:19:06 +08:00
Ikko Eltociear Ashimine
ce45c15d13 refactor: fix typo in notification-center/index.tsx (#2929) 2023-06-30 10:17:27 +08:00
Hyden Liu
e65cae121e fix: z-index on app sidebar (#2761)
Co-authored-by: Himself65 <himself65@outlook.com>
2023-06-30 10:17:27 +08:00
xiaodong zuo
51b6f5a105 feat: export page as file (#2923) 2023-06-30 10:17:26 +08:00
Alex Yang
bfbb7e9e6b fix(electron): window only ui (#2926) 2023-06-30 10:17:26 +08:00
LongYinan
03dea53b30 build: affine Node.js server charts (#2895) 2023-06-30 10:17:25 +08:00
Qi
0bdecc776d feat: add and modify test case for new settings modal (#2925) 2023-06-30 10:17:23 +08:00
Alex Yang
b4389b7380 docs: set nodejs version to 18.16.1 2023-06-30 10:17:23 +08:00
Alex Yang
d796fc2ab0 v0.7.0-canary.24 2023-06-30 10:17:22 +08:00
JimmFly
a8dc00b406 chore: update changelog link and remove obsolete changelog components (#2918) 2023-06-30 10:17:18 +08:00
Alex Yang
76039d8d3e chore: bump blocksuite to 0.0.0-20230629084521-542de4e8-nightly (#2921) 2023-06-30 10:17:16 +08:00
Alex Yang
b6b5b58889 ci: allow codecov upload failure (#2922) 2023-06-30 10:17:15 +08:00
Qi
6f5f0ab986 fix: a serise of ui issues of new setting (#2920)
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-06-30 10:17:13 +08:00
Alex Yang
f7b07f4216 refactor: rootWorkspacesMetadataAtom loading logic (#2882) 2023-06-30 10:17:11 +08:00
DarkSky
df7f782e05 fix: incorrect formatting (#2917) 2023-06-30 10:17:11 +08:00
JimmFly
3fb30a6cce chore: update translation (#2916)
Co-authored-by: zuozijian3720 <zuozijian1994@gmail.com>
2023-06-30 10:17:09 +08:00
Alex Yang
2c5fa4f008 docs: add apps/README.md 2023-06-30 10:17:08 +08:00
Alex Yang
4433a72b96 ci: split migration test 2023-06-30 10:17:08 +08:00
Alex Yang
1853dd9895 test: migration test in real world (#2885) 2023-06-30 10:17:07 +08:00
Alex Yang
d98586773c ci: add prettier format check (#2908) 2023-06-30 10:17:05 +08:00
liuyi
bf30185c65 feat(storage): binding jwst storage to node (#2808) 2023-06-30 10:17:04 +08:00
Alex Yang
09e38e4a91 build: disable sqlite provider in canary 2023-06-30 10:17:04 +08:00
Peng Xiao
97f41ab620 fix: updater color updates (#2913) 2023-06-30 10:17:03 +08:00
DarkSky
0cbeff6f61 feat: add deployment guide & fix pod label (#2912) 2023-06-30 10:17:03 +08:00
Alex Yang
f3ed31b4b7 ci: fix output variable 2023-06-30 10:17:01 +08:00
Alex Yang
020f86bcc1 v0.7.0-canary.23 2023-06-30 10:17:00 +08:00
Alex Yang
997cf3bd92 ci: use production environment 2023-06-30 10:16:59 +08:00
Alex Yang
af399adf14 ci: fix package version output 2023-06-30 10:16:59 +08:00
Alex Yang
255e53673e v0.7.0-canary.22 2023-06-30 10:16:57 +08:00
Alex Yang
3ba1b3fc61 ci: fix tag version 2023-06-30 10:16:57 +08:00
Alex Yang
135eb07f9e v0.7.0-canary.21 2023-06-30 10:16:57 +08:00
Alex Yang
3362069ae8 ci: automatically build canary release (#2911) 2023-06-30 10:16:56 +08:00
Alex Yang
fe7400abe1 ci: add codeql check to merge group (#2909) 2023-06-30 10:16:56 +08:00
Qi
4cca8a16ab feat: migrate workspace setting with new design to setting modal (#2900)
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-06-30 10:16:52 +08:00
Alex Yang
5a3e5a1565 chore: bump typescript version (#2906) 2023-06-30 10:16:52 +08:00
Alex Yang
87063d20ce chore: codesandbox setup (#2907) 2023-06-30 10:16:52 +08:00
DarkSky
7b6728e8a9 docs: improve helm ci & document (#2902) 2023-06-30 10:16:50 +08:00
Alex Yang
1bff46d5f0 refactor(storybook): move to apps folder (#2901) 2023-06-30 10:16:48 +08:00
Alex Yang
81bac496b4 feat: add rule 'sonarjs/no-identical-functions' (#2905) 2023-06-30 10:16:48 +08:00
Alex Yang
b31aa44d40 chore: update pre-commit hook (#2904) 2023-06-30 10:16:46 +08:00
Alex Yang
bff8ca6178 refactor: environment setup (#2898)
Co-authored-by: Simon He <57086651+Simon-He95@users.noreply.github.com>
2023-06-30 10:16:45 +08:00
Alex Yang
be33cfd174 fix(web): bypass adapter list error (#2903) 2023-06-30 10:16:44 +08:00
Alex Yang
0eb11d14ad test(server): watch mode (#2893) 2023-06-30 10:16:44 +08:00
Alex Yang
b0d8c8e14f ci: enable merge group (#2899) 2023-06-30 10:16:44 +08:00
Alex Yang
72378abe56 feat: update migration test page (#2871) 2023-06-30 10:16:44 +08:00
Alex Yang
fc36aac0fb build: unify build flags (#2891) 2023-06-30 10:16:44 +08:00
Alex Yang
e7f4d82881 build: fix i18n output (#2896) 2023-06-30 10:16:44 +08:00
759 changed files with 27425 additions and 42087 deletions

641
.all-contributorsrc Normal file
View File

@@ -0,0 +1,641 @@
{
"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"
]
}
]
}

View File

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

View File

@@ -10,7 +10,7 @@
"tasks": {
"start-web": {
"name": "Start Web",
"command": "yarn dev-core",
"command": "yarn nx dev @affine/web --port 8080",
"runAtStart": true,
"preview": {
"port": 8080

View File

@@ -7,7 +7,7 @@
[
"electron",
"server",
"core",
"web",
"docs",
"storybook",
"component",
@@ -21,13 +21,10 @@
"native",
"templates",
"y-indexeddb",
"y-provider",
"debug",
"storage",
"infra",
"plugin-cli",
"sdk",
"plugin"
"plugin-infra"
]
]
}

View File

@@ -1,11 +0,0 @@
ENABLE_PLUGIN=
ENABLE_TEST_PROPERTIES=
ENABLE_BC_PROVIDER=
CHANGELOG_URL=
ENABLE_PRELOADING=
ENABLE_NEW_SETTING_MODAL=
ENABLE_SQLITE_PROVIDER=
ENABLE_NEW_SETTING_UNSTABLE_API=
ENABLE_NOTIFICATION_CENTER=
ENABLE_CLOUD=
ENABLE_MOVE_DATABASE=

View File

@@ -9,8 +9,3 @@ 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

@@ -26,16 +26,6 @@ const createPattern = packageName => [
message: "Import from '@blocksuite/global/utils'",
importNames: ['assertExists', 'assertEquals'],
},
{
group: ['react-router-dom'],
message: 'Use `useNavigateHelper` instead',
importNames: ['useNavigate'],
},
{
group: ['yjs'],
message: 'Do not use this API because it has a bug',
importNames: ['mergeUpdates'],
},
];
const allPackages = [
@@ -48,8 +38,7 @@ const allPackages = [
'packages/i18n',
'packages/jotai',
'packages/native',
'packages/infra',
'packages/sdk',
'packages/plugin-infra',
'packages/templates',
'packages/theme',
'packages/workspace',
@@ -155,16 +144,6 @@ const config = {
message: "Import from '@blocksuite/global/utils'",
importNames: ['assertExists', 'assertEquals'],
},
{
group: ['react-router-dom'],
message: 'Use `useNavigateHelper` instead',
importNames: ['useNavigate'],
},
{
group: ['yjs'],
message: 'Do not use this API because it has a bug',
importNames: ['mergeUpdates'],
},
],
},
],
@@ -224,7 +203,6 @@ const config = {
ignoreIIFE: false,
},
],
'@typescript-eslint/no-misused-promises': ['error'],
},
})),
{
@@ -250,7 +228,6 @@ const config = {
},
],
'@typescript-eslint/no-floating-promises': 0,
'@typescript-eslint/no-misused-promises': 0,
},
},
],

View File

@@ -21,8 +21,8 @@ body:
label: Distribution version
description: What version of AFFiNE are you using?
options:
- macOS x64 (Intel)
- macOS ARM 64 (Apple Silicon)
- macOS x64
- macOS ARM 64
- Windows x64
- Linux
- Web (app.affine.pro)

View File

@@ -17,6 +17,10 @@ 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
@@ -44,20 +48,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
@@ -121,7 +125,3 @@ runs:
- name: Build Infra
shell: bash
run: yarn run build:infra
- name: Build Plugins
shell: bash
run: yarn run build:plugins

View File

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

View File

@@ -1,6 +1,6 @@
FROM openresty/openresty:1.21.4.1-0-buster
WORKDIR /app
COPY ./apps/core/dist ./dist
COPY ./apps/web/out ./dist
COPY ./.github/deployment/front/nginx.conf /usr/local/openresty/nginx/conf/nginx.conf
COPY ./.github/deployment/front/affine.nginx.conf /etc/nginx/conf.d/affine.nginx.conf

12
.github/labeler.yml vendored
View File

@@ -22,14 +22,8 @@ plugin:bookmark-block:
plugin:copilot:
- 'plugins/copilot/**/*'
mod:infra:
- 'packages/infra/**/*'
mod:sdk:
- 'packages/sdk/**/*'
mod:plugin-cli:
- 'packages/plugin-cli/**/*'
mod:plugin-infra:
- 'packages/plugin-infra/**/*'
mod:workspace: 'packages/workspace/**/*'
@@ -59,7 +53,7 @@ rust:
package:y-indexeddb: 'packages/y-indexeddb/**/*'
app:core: 'apps/core/**/*'
app:web: 'apps/web/**/*'
app:electron: 'apps/electron/**/*'

View File

@@ -1,177 +0,0 @@
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,7 +30,6 @@ 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 }}
@@ -48,6 +47,8 @@ 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
@@ -57,40 +58,6 @@ 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:
@@ -134,8 +101,8 @@ jobs:
path: ./apps/storybook/storybook-static
if-no-files-found: error
build-storage:
name: Build Storage
build-web:
name: Build @affine/web
runs-on: ubuntu-latest
environment: development
@@ -143,24 +110,19 @@ jobs:
- 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
- name: Build Web
run: yarn nx build @affine/web
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: storage.node
path: ./packages/storage/storage.node
name: next-js-static
path: ./apps/web/out
if-no-files-found: error
server-test:
name: Server Test
runs-on: ubuntu-latest
environment: development
needs: build-storage
services:
postgres:
image: postgres
@@ -196,17 +158,24 @@ jobs:
working-directory: apps/server
env:
DATABASE_URL: postgresql://affine:affine@localhost:5432/affine
- name: Download storage.node
uses: actions/download-artifact@v3
- name: Setup Rust
uses: ./.github/actions/setup-rust
with:
name: storage.node
path: ./apps/server
target: 'x86_64-unknown-linux-gnu'
- name: Build Storage
run: yarn build:storage
- 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:
@@ -216,10 +185,11 @@ jobs:
name: affine
fail_ci_if_error: false
e2e-plugin-test:
name: E2E Plugin Test
storybook-test:
name: Storybook Test
runs-on: ubuntu-latest
environment: development
needs: [build-storybook]
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
@@ -227,73 +197,15 @@ jobs:
with:
playwright-install: true
electron-install: false
- 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
- name: Download storybook artifact
uses: actions/download-artifact@v3
with:
name: prototype
path: ./apps/prototype/dist
- name: Run playwright tests
run: yarn e2e --forbid-only
working-directory: tests/affine-prototype
env:
COVERAGE: true
# - name: Collect code coverage report
# run: yarn exec nyc report -t .nyc_output --report-dir .coverage --reporter=lcov
# - name: Upload e2e test coverage results
# uses: codecov/codecov-action@v3
# with:
# token: ${{ secrets.CODECOV_TOKEN }}
# files: ./.coverage/lcov.info
# flags: e2etest-prototype
# name: affine
# fail_ci_if_error: false
- name: Upload test results
if: ${{ failure() }}
uses: actions/upload-artifact@v3
with:
name: test-results-e2e-prototype
path: ./test-results
if-no-files-found: ignore
name: storybook
path: ./apps/storybook/storybook-static
- name: Run storybook tests
working-directory: ./apps/storybook
run: |
yarn exec concurrently -k -s first -n "SB,TEST" -c "magenta,blue" "yarn exec serve ./storybook-static -l 6006" "yarn exec wait-on tcp:6006 && yarn test"
e2e-test:
name: E2E Test
@@ -303,6 +215,8 @@ jobs:
matrix:
shard: [1, 2, 3, 4, 5]
environment: development
needs: build-web
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
@@ -310,6 +224,11 @@ jobs:
with:
playwright-install: true
electron-install: false
- name: Download artifact
uses: actions/download-artifact@v3
with:
name: next-js-static
path: ./apps/web/out
- name: Run playwright tests
run: yarn e2e --forbid-only --shard=${{ matrix.shard }}/${{ strategy.job-total }}
@@ -341,11 +260,8 @@ jobs:
name: E2E Migration Test
runs-on: ubuntu-latest
environment: development
strategy:
matrix:
spec:
- { package: 0.7.0-canary.18 }
- { package: 0.8.0-canary.7 }
needs: [build-web]
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
@@ -354,20 +270,144 @@ jobs:
playwright-install: true
electron-install: false
- name: Download next static
uses: actions/download-artifact@v3
with:
name: next-js-static
path: ./apps/web/out
- name: Unzip
run: yarn unzip
working-directory: ./tests/affine-legacy/${{ matrix.spec.package }}
working-directory: ./tests/affine-legacy/0.7.0-canary.18
- name: Run playwright tests
- name: Run legacy playwright tests
run: yarn e2e --forbid-only
working-directory: ./tests/affine-legacy/${{ matrix.spec.package }}
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
- name: Upload test results
if: ${{ failure() }}
uses: actions/upload-artifact@v3
with:
name: test-results-e2e-migration-${{ matrix.spec.package }}
path: ./tests/affine-legacy/${{ matrix.spec.package }}/test-results
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-web]
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: ./.github/actions/setup-node
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 nx test @affine/monorepo
env:
NATIVE_TEST: 'true'
- name: Download static resource artifact
uses: actions/download-artifact@v3
with:
name: next-js-static
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
if-no-files-found: ignore
unit-test:
@@ -392,3 +432,79 @@ 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 next static
uses: actions/download-artifact@v3
with:
name: next-js-static
path: ./apps/web/out
- 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, 65188160, 66789140
workflow_id: 44038251, 61883931
access_token: ${{ github.token }}

View File

@@ -50,8 +50,6 @@ 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
@@ -64,36 +62,43 @@ jobs:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
RELEASE_VERSION: ${{ needs.set-build-version.outputs.version }}
- name: Upload core artifact
- name: Upload Artifact (web-static)
uses: actions/upload-artifact@v3
with:
name: core
name: before-make-web-static
path: apps/electron/resources/web-static
make-distribution:
environment: production
strategy:
# all combinations: macos-latest x64, macos-latest arm64, ubuntu-latest x64
# For windows, we need a separate approach
# all combinations: macos-latest x64, macos-latest arm64, windows-latest x64, ubuntu-latest x64
matrix:
spec:
- 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 }}
- {
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 }}
needs:
- before-make
- set-build-version
@@ -105,11 +110,7 @@ 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:
@@ -119,7 +120,7 @@ jobs:
run: ./scripts/set-version.sh ${{ needs.set-build-version.outputs.version }}
- uses: actions/download-artifact@v3
with:
name: core
name: before-make-web-static
path: apps/electron/resources/web-static
- name: Build Plugins
@@ -158,7 +159,6 @@ jobs:
run: |
mkdir -p builds
mv apps/electron/out/*/make/zip/linux/x64/*.zip ./builds/affine-${{ env.BUILD_TYPE }}-linux-x64.zip
mv apps/electron/out/*/make/AppImage/x64/*.AppImage ./builds/affine-${{ env.BUILD_TYPE }}-linux-x64.AppImage
- name: Upload Artifact
uses: actions/upload-artifact@v3

View File

@@ -1,38 +0,0 @@
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

@@ -360,6 +360,7 @@ jobs:
./*.zip
./*.dmg
./*.exe
./RELEASES
./*.AppImage
./*.apk
./*.yml

View File

@@ -5,13 +5,6 @@ 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
@@ -24,142 +17,3 @@ 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

1
.gitignore vendored
View File

@@ -13,7 +13,6 @@
/out-tsc
.nyc_output
.coverage
.swc
# dependencies
node_modules

View File

@@ -12,5 +12,3 @@ tests/affine-legacy/0.7.0-canary.18/static
.github/helm
_next
storybook-static
web-static
public

View File

@@ -14,6 +14,15 @@
</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)
@@ -30,7 +39,6 @@
[![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>
@@ -114,32 +122,28 @@ 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. You can use plugins to add new blocks, new features, and even new ways to edit content.
> Plugins are a way to extend the functionality of AFFiNE.
>
> (Currently, the plugin system is under heavy development. You will see the plugin system in the canary release.)
> (Currently, plugins are under heavy development, and the SDK is not yet available.)
- [@affine/sdk](./packages/sdk) - SDK for developing plugins
- [@affine/plugin-cli](./packages/plugin-cli) - CLI for developing plugins
| Name | |
| ------------------------------------------------ | ----------------------------------------- |
| [@affine/bookmark-block](plugins/bookmark-block) | A block for bookmarking a website |
| [@affine/copilot](plugins/copilot) | AI Copilot that help you document writing |
| 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
## Thanks
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) - 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.
- [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.
- [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.
@@ -163,11 +167,15 @@ 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/).
For feature request please see [community.affine.pro](https://community.affine.pro/c/feature-requests/).
## Is it awesome?
[These people] seem to like it.
## Building
@@ -178,29 +186,21 @@ See [BUILDING.md] for instructions on how to build AFFiNE from source code.
We welcome contributions from everyone.
See [docs/contributing/tutorial.md](./docs/contributing/tutorial.md) for details.
## Thanks
<a href="https://www.chromatic.com/"><img src="https://user-images.githubusercontent.com/321738/84662277-e3db4f80-af1b-11ea-88f5-91d67a5e59f6.png" width="153" height="30" alt="Chromatic" /></a>
Thanks to [Chromatic](https://www.chromatic.com/) for providing the visual testing platform that helps us review UI changes and catch visual regressions.
## License
See [LICENSE] for details.
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Ftoeverything%2FAFFiNE.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Ftoeverything%2FAFFiNE?ref=badge_large)
[all-contributors-badge]: https://img.shields.io/github/contributors/toeverything/AFFiNE
[license]: ./LICENSE
[building.md]: ./docs/BUILDING.md
[these people]: https://twitter.com/AffineOfficial/followers
[update page]: https://affine.pro/blog?tag=Release%20Note
[jobs available]: ./docs/jobs.md
[latest packages]: https://github.com/toeverything/AFFiNE/pkgs/container/affine-self-hosted
[contributor license agreement]: https://github.com/toeverything/affine/edit/master/.github/CLA.md
[rust-version-icon]: https://img.shields.io/badge/Rust-1.71.0-dea584
[rust-version-icon]: https://img.shields.io/badge/Rust-1.70.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
[typescript-version-icon]: https://img.shields.io/github/package-json/dependency-version/toeverything/affine/dev/typescript
[react-version-icon]: https://img.shields.io/github/package-json/dependency-version/toeverything/AFFiNE/react?filename=apps%2Fcore%2Fpackage.json&color=rgb(97%2C228%2C251)
[blocksuite-icon]: https://img.shields.io/github/package-json/dependency-version/toeverything/AFFiNE/@blocksuite/store?color=6880ff&filename=apps%2Fcore%2Fpackage.json&label=blocksuite
[react-version-icon]: https://img.shields.io/github/package-json/dependency-version/toeverything/AFFiNE/react?filename=apps%2Fweb%2Fpackage.json&color=rgb(97%2C228%2C251)
[blocksuite-icon]: https://img.shields.io/github/package-json/dependency-version/toeverything/AFFiNE/@blocksuite/store?color=6880ff&filename=apps%2Fweb%2Fpackage.json&label=blocksuite

View File

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

View File

@@ -1,82 +0,0 @@
function testPackageName(regexp: RegExp): (module: any) => boolean {
return (module: any) =>
module.nameForCondition && regexp.test(module.nameForCondition());
}
export const productionCacheGroups = {
asyncVendor: {
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];
return `npm-async-${name}`;
},
priority: Number.MAX_SAFE_INTEGER,
chunks: 'async' as const,
},
mui: {
name: `npm-mui`,
test: testPackageName(/[\\/]node_modules[\\/](mui|@mui)[\\/]/),
priority: 200,
enforce: true,
},
blocksuite: {
name: `npm-blocksuite`,
test: testPackageName(/[\\/]node_modules[\\/](@blocksuite)[\\/]/),
priority: 200,
enforce: true,
},
react: {
name: `npm-react`,
test: testPackageName(
/[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/
),
priority: 200,
enforce: true,
},
jotai: {
name: `npm-jotai`,
test: testPackageName(/[\\/]node_modules[\\/](jotai)[\\/]/),
priority: 200,
enforce: true,
},
rxjs: {
name: `npm-rxjs`,
test: testPackageName(/[\\/]node_modules[\\/]rxjs[\\/]/),
priority: 200,
enforce: true,
},
lodash: {
name: `npm-lodash`,
test: testPackageName(/[\\/]node_modules[\\/]lodash[\\/]/),
priority: 200,
enforce: true,
},
emotion: {
name: `npm-emotion`,
test: testPackageName(/[\\/]node_modules[\\/](@emotion)[\\/]/),
priority: 200,
enforce: true,
},
vendor: {
name: 'vendor',
test: /[\\/]node_modules[\\/]/,
priority: 190,
enforce: true,
},
styles: {
name: 'styles',
test: (module: any) =>
module.nameForCondition &&
/\.css$/.test(module.nameForCondition()) &&
!/^javascript/.test(module.type),
chunks: 'all' as const,
minSize: 1,
minChunks: 1,
reuseExistingChunk: true,
priority: 1000,
enforce: true,
},
};

View File

@@ -1,421 +0,0 @@
import { join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { createRequire } from 'node:module';
import type { Configuration as DevServerConfiguration } from 'webpack-dev-server';
import { PerfseePlugin } from '@perfsee/webpack';
import { sentryWebpackPlugin } from '@sentry/webpack-plugin';
import CopyPlugin from 'copy-webpack-plugin';
import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin';
import TerserPlugin from 'terser-webpack-plugin';
import webpack from 'webpack';
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import { productionCacheGroups } from './cache-group.js';
import type { BuildFlags } from '@affine/cli/config';
import { projectRoot } from '@affine/cli/config';
import { VanillaExtractPlugin } from '@vanilla-extract/webpack-plugin';
import { computeCacheKey } from './utils.js';
import type { RuntimeConfig } from '@affine/env/global';
const IN_CI = !!process.env.CI;
export const rootPath = fileURLToPath(new URL('..', import.meta.url));
const require = createRequire(rootPath);
const OptimizeOptionOptions: (
buildFlags: BuildFlags
) => webpack.Configuration['optimization'] = buildFlags => ({
minimize: buildFlags.mode === 'production',
minimizer: [
new TerserPlugin({
minify: TerserPlugin.swcMinify,
exclude: [/plugins\/.+\/.+\.js$/, /plugins\/.+\/.+\.mjs$/],
parallel: true,
extractComments: true,
terserOptions: {
ecma: 2020,
compress: {
unused: true,
},
mangle: true,
},
}),
],
removeEmptyChunks: true,
providedExports: true,
usedExports: true,
sideEffects: true,
removeAvailableModules: true,
runtimeChunk: {
name: 'runtime',
},
splitChunks: {
chunks: 'all',
minSize: 1,
minChunks: 1,
maxInitialRequests: Number.MAX_SAFE_INTEGER,
maxAsyncRequests: Number.MAX_SAFE_INTEGER,
cacheGroups:
buildFlags.mode === 'production'
? productionCacheGroups
: {
default: false,
vendors: false,
},
},
});
export const createConfiguration: (
buildFlags: BuildFlags,
runtimeConfig: RuntimeConfig
) => 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,
dynamicImport: true,
},
filename:
buildFlags.mode === 'production'
? 'js/[name]-[contenthash:8].js'
: 'js/[name].js',
// In some cases webpack will emit files starts with "_" which is reserved in web extension.
chunkFilename: 'js/chunk.[name].js',
assetModuleFilename: 'assets/[contenthash:8][ext][query]',
devtoolModuleFilenameTemplate: 'webpack://[namespace]/[resource-path]',
hotUpdateChunkFilename: 'hot/[id].[fullhash].js',
hotUpdateMainFilename: 'hot/[runtime].[fullhash].json',
path: join(rootPath, 'dist'),
clean: buildFlags.mode === 'production',
globalObject: 'globalThis',
publicPath,
},
target: ['web', 'es2022'],
mode: buildFlags.mode,
devtool:
buildFlags.mode === 'production'
? buildFlags.distribution === 'desktop'
? 'nosources-source-map'
: 'source-map'
: 'eval-cheap-module-source-map',
resolve: {
symlinks: true,
extensionAlias: {
'.js': ['.js', '.tsx', '.ts'],
'.mjs': ['.mjs', '.mts'],
},
extensions: ['.js', '.ts', '.tsx'],
fallback:
blocksuiteBaseDir === undefined
? undefined
: {
events: false,
},
alias:
blocksuiteBaseDir === undefined
? undefined
: {
yjs: require.resolve('yjs'),
'@blocksuite/block-std': resolve(
blocksuiteBaseDir,
'packages',
'block-std'
),
'@blocksuite/blocks': resolve(
blocksuiteBaseDir,
'packages',
'blocks'
),
'@blocksuite/editor': resolve(
blocksuiteBaseDir,
'packages',
'editor'
),
'@blocksuite/global': resolve(
blocksuiteBaseDir,
'packages',
'global'
),
'@blocksuite/lit': resolve(blocksuiteBaseDir, 'packages', 'lit'),
'@blocksuite/phasor': resolve(
blocksuiteBaseDir,
'packages',
'phasor'
),
'@blocksuite/store/providers/broadcast-channel': resolve(
blocksuiteBaseDir,
'packages',
'store',
'src/providers/broadcast-channel'
),
'@blocksuite/store': resolve(
blocksuiteBaseDir,
'packages',
'store'
),
'@blocksuite/virgo': resolve(
blocksuiteBaseDir,
'packages',
'virgo'
),
},
},
cache: {
type: 'filesystem',
buildDependencies: {
config: [fileURLToPath(import.meta.url)],
},
version: cacheKey,
},
module: {
parser: {
javascript: {
// Do not mock Node.js globals
node: false,
requireJs: false,
import: true,
// Treat as missing export as error
strictExportPresence: true,
},
},
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,
},
},
{
oneOf: [
{
test: /\.tsx?$/,
exclude: /node_modules/,
loader: require.resolve('swc-loader'),
options: {
// https://swc.rs/docs/configuring-swc/
jsc: {
preserveAllComments: true,
parser: {
syntax: 'typescript',
dynamicImport: true,
topLevelAwait: false,
tsx: true,
decorators: true,
},
target: 'es2022',
externalHelpers: false,
transform: {
react: {
runtime: 'automatic',
refresh: buildFlags.mode === 'development' && {
refreshReg: '$RefreshReg$',
refreshSig: '$RefreshSig$',
emitFullSignatures: true,
},
},
useDefineForClassFields: false,
},
experimental: {
keepImportAssertions: true,
plugins: [
buildFlags.coverage && [
'swc-plugin-coverage-instrument',
{},
],
].filter(Boolean),
},
},
},
},
{
test: /\.svg$/,
use: [
'thread-loader',
{
loader: '@svgr/webpack',
options: {
icon: true,
},
},
],
exclude: [/node_modules/],
},
{
test: /\.(png|jpg|gif|svg|webp)$/,
type: 'asset/resource',
},
{
test: /\.(ttf|eot|woff|woff2)$/,
type: 'asset/resource',
},
{
test: /\.txt$/,
loader: 'raw-loader',
},
{
test: /\.css$/,
use: [
buildFlags.mode === 'development'
? 'style-loader'
: MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
url: false,
sourceMap: false,
modules: false,
import: true,
importLoaders: 1,
},
},
{
loader: 'postcss-loader',
options: {
postcssOptions: {
config: resolve(
rootPath,
'.webpack',
'postcss.config.cjs'
),
},
},
},
],
},
],
},
],
},
plugins: [
...(IN_CI ? [] : [new webpack.ProgressPlugin({ percentBy: 'entries' })]),
...(buildFlags.mode === 'development'
? [new ReactRefreshWebpackPlugin({ overlay: false, esModule: true })]
: [
new MiniCssExtractPlugin({
filename: `[name].[contenthash:8].css`,
ignoreOrder: true,
}),
]),
new VanillaExtractPlugin(),
new webpack.DefinePlugin({
'process.env': JSON.stringify({}),
'process.env.COVERAGE': JSON.stringify(!!buildFlags.coverage),
'process.env.NODE_ENV': JSON.stringify(buildFlags.mode),
runtimeConfig: JSON.stringify(runtimeConfig),
}),
new CopyPlugin({
patterns: [
{
from: resolve(rootPath, 'public'),
to: resolve(rootPath, 'dist'),
},
],
}),
],
optimization: OptimizeOptionOptions(buildFlags),
devServer: {
hot: 'only',
liveReload: true,
client: undefined,
historyApiFallback: true,
static: {
directory: resolve(rootPath, 'public'),
publicPath: '/',
watch: true,
},
} as DevServerConfiguration,
} satisfies webpack.Configuration;
if (buildFlags.mode === 'production' && process.env.PERFSEE_TOKEN) {
config.devtool = 'hidden-nosources-source-map';
config.plugins.push(
new PerfseePlugin({
project: 'affine-toeverything',
})
);
}
if (buildFlags.mode === 'development') {
config.optimization = {
...config.optimization,
minimize: false,
runtimeChunk: false,
splitChunks: {
maxInitialRequests: Infinity,
chunks: 'all',
cacheGroups: {
defaultVendors: {
test: `[\\/]node_modules[\\/](?!.*vanilla-extract)`,
priority: -10,
reuseExistingChunk: true,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
styles: {
name: 'styles',
type: 'css/mini-extract',
chunks: 'all',
enforce: true,
},
},
},
};
}
if (
process.env.SENTRY_AUTH_TOKEN &&
process.env.SENTRY_ORG &&
process.env.SENTRY_PROJECT
) {
config.plugins.push(
sentryWebpackPlugin({
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
authToken: process.env.SENTRY_AUTH_TOKEN,
})
);
}
return config;
};

View File

@@ -1,20 +0,0 @@
const cssnano = require('cssnano');
module.exports = function (context) {
const plugins = [
cssnano({
preset: [
'default',
{
convertValues: false,
},
],
}),
];
return {
from: context.from,
plugins,
to: context.to,
};
};

View File

@@ -1,122 +0,0 @@
import type { BlockSuiteFeatureFlags, RuntimeConfig } from '@affine/env/global';
import type { BuildFlags } from '@affine/cli/config';
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
const packageJson = require('../package.json');
const editorFlags: BlockSuiteFeatureFlags = {
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 {
const buildPreset: Record<string, RuntimeConfig> = {
stable: {
enablePlugin: false,
enableTestProperties: false,
enableBroadcastChannelProvider: true,
enableDebugPage: true,
changelogUrl: 'https://affine.pro/blog/affine-080-launch-week-day5',
imageProxyUrl: 'https://workers.toeverything.workers.dev/proxy/image',
enablePreloading: true,
enableNewSettingModal: true,
enableNewSettingUnstableApi: false,
enableSQLiteProvider: true,
enableMoveDatabase: false,
enableNotificationCenter: false,
enableCloud: false,
serverAPI: 'https://localhost:3010',
editorFlags,
appVersion: packageJson.version,
editorVersion: packageJson.dependencies['@blocksuite/editor'],
},
// canary will be aggressive and enable all features
canary: {
enablePlugin: true,
enableTestProperties: true,
enableBroadcastChannelProvider: true,
enableDebugPage: true,
changelogUrl: 'https://github.com/toeverything/AFFiNE/releases',
imageProxyUrl: 'https://workers.toeverything.workers.dev/proxy/image',
enablePreloading: true,
enableNewSettingModal: true,
enableNewSettingUnstableApi: false,
enableSQLiteProvider: true,
enableMoveDatabase: false,
enableNotificationCenter: true,
enableCloud: false,
serverAPI: 'https://localhost:3010',
editorFlags,
appVersion: packageJson.version,
editorVersion: packageJson.dependencies['@blocksuite/editor'],
},
};
// beta and internal versions are the same as stable
buildPreset.beta = buildPreset.stable;
buildPreset.internal = buildPreset.stable;
const currentBuild = buildFlags.channel;
if (!(currentBuild in buildPreset)) {
throw new Error(`BUILD_TYPE ${currentBuild} is not supported`);
}
const currentBuildPreset = buildPreset[currentBuild];
const environmentPreset = {
enablePlugin: process.env.ENABLE_PLUGIN
? process.env.ENABLE_PLUGIN === 'true'
: currentBuildPreset.enablePlugin,
enableTestProperties: process.env.ENABLE_TEST_PROPERTIES
? process.env.ENABLE_TEST_PROPERTIES === 'true'
: currentBuildPreset.enableTestProperties,
enableBroadcastChannelProvider: process.env.ENABLE_BC_PROVIDER
? process.env.ENABLE_BC_PROVIDER !== 'false'
: currentBuildPreset.enableBroadcastChannelProvider,
changelogUrl: process.env.CHANGELOG_URL ?? currentBuildPreset.changelogUrl,
enablePreloading: process.env.ENABLE_PRELOADING
? process.env.ENABLE_PRELOADING === 'true'
: currentBuildPreset.enablePreloading,
enableNewSettingModal: process.env.ENABLE_NEW_SETTING_MODAL
? process.env.ENABLE_NEW_SETTING_MODAL === 'true'
: currentBuildPreset.enableNewSettingModal,
enableSQLiteProvider: process.env.ENABLE_SQLITE_PROVIDER
? process.env.ENABLE_SQLITE_PROVIDER === 'true'
: currentBuildPreset.enableSQLiteProvider,
enableNewSettingUnstableApi: process.env.ENABLE_NEW_SETTING_UNSTABLE_API
? process.env.ENABLE_NEW_SETTING_UNSTABLE_API === 'true'
: currentBuildPreset.enableNewSettingUnstableApi,
enableNotificationCenter: process.env.ENABLE_NOTIFICATION_CENTER
? process.env.ENABLE_NOTIFICATION_CENTER === 'true'
: currentBuildPreset.enableNotificationCenter,
enableCloud: process.env.ENABLE_CLOUD
? process.env.ENABLE_CLOUD === 'true'
: currentBuildPreset.enableCloud,
enableMoveDatabase: process.env.ENABLE_MOVE_DATABASE
? process.env.ENABLE_MOVE_DATABASE === 'true'
: currentBuildPreset.enableMoveDatabase,
};
return {
...currentBuildPreset,
// environment preset will overwrite current build preset
// this environment variable is for debug proposes only
// do not put them into CI
...(process.env.CI ? {} : environmentPreset),
};
}

View File

@@ -1,45 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1"
/>
<title>AFFiNE</title>
<meta name="theme-color" content="#fafafa" />
<link rel="manifest" href="/manifest.json" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="icon" sizes="192x192" href="/chrome-192x192.png" />
<meta name="emotion-insertion-point" content="" />
<meta property="description" content="{description}" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:url" content="https://app.affine.pro/" />
<meta
name="twitter:title"
content="AFFiNEThere can be more than Notion and Miro."
/>
<meta name="twitter:description" content="{description}" />
<meta name="twitter:site" content="@AffineOfficial" />
<meta name="twitter:image" content="https://affine.pro/og.jpeg" />
<meta
property="og:title"
content="AFFiNEThere can be more than Notion and Miro."
/>
<meta property="og:type" content="website" />
<meta
property="og:description"
content="There can be more than Notion and Miro. AFFiNE is a next-gen knowledge base that brings planning, sorting and creating all together."
/>
<meta property="og:url" content="https://app.affine.pro/" />
<meta property="og:image" content="https://affine.pro/og.jpeg" />
<link
data-react-helmet="true"
rel="shortcut icon"
href="https://affine.pro/favicon.ico"
/>
</head>
<body>
<div id="app"></div>
</body>
</html>

View File

@@ -1,12 +0,0 @@
import type { BuildFlags } from '@affine/cli/config';
export function computeCacheKey(buildFlags: BuildFlags) {
return [
'1',
'node' + process.version,
buildFlags.mode,
buildFlags.distribution,
buildFlags.channel,
...(buildFlags.localBlockSuite ? [buildFlags.localBlockSuite] : []),
].join('-');
}

View File

@@ -1,63 +0,0 @@
import { createConfiguration, rootPath } from './config.js';
import { merge } from 'webpack-merge';
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(
Buffer.from(cli_env.flags, 'hex').toString('utf-8')
);
console.log('build flags', flags);
const runtimeConfig = getRuntimeConfig(flags);
console.log('runtime config', runtimeConfig);
const config = createConfiguration(flags, runtimeConfig);
return merge(config, {
entry: {
'polyfill/intl-segmenter': {
import: resolve(rootPath, 'src/polyfill/intl-segmenter.ts'),
},
'polyfill/ses': {
import: resolve(rootPath, 'src/polyfill/ses.ts'),
},
plugin: {
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

@@ -1,92 +0,0 @@
{
"name": "@affine/core",
"type": "module",
"private": true,
"version": "0.8.0",
"scripts": {
"build": "yarn -T run build-core",
"dev": "yarn -T run dev-core",
"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/debug": "workspace:*",
"@affine/env": "workspace:*",
"@affine/graphql": "workspace:*",
"@affine/i18n": "workspace:*",
"@affine/jotai": "workspace:*",
"@affine/templates": "workspace:*",
"@affine/workspace": "workspace:*",
"@blocksuite/block-std": "0.0.0-20230822230555-98129627-nightly",
"@blocksuite/blocks": "0.0.0-20230822230555-98129627-nightly",
"@blocksuite/editor": "0.0.0-20230822230555-98129627-nightly",
"@blocksuite/global": "0.0.0-20230822230555-98129627-nightly",
"@blocksuite/icons": "^2.1.31",
"@blocksuite/lit": "0.0.0-20230822230555-98129627-nightly",
"@blocksuite/store": "0.0.0-20230822230555-98129627-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.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.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",
"postcss-loader": "^7.3.3",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-is": "18.2.0",
"react-resizable-panels": "^0.0.55",
"react-router-dom": "^6.15.0",
"rxjs": "^7.8.1",
"ses": "^0.18.7",
"swr": "2.2.1",
"y-protocols": "^1.0.5",
"yjs": "^13.6.7",
"zod": "^3.22.1"
},
"devDependencies": {
"@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.20",
"thread-loader": "^4.0.2",
"webpack": "^5.88.2",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1",
"webpack-merge": "^5.9.0"
}
}

View File

@@ -1,58 +0,0 @@
{
"name": "@affine/core",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"targets": {
"build": {
"executor": "nx:run-script",
"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/**/*",
"{workspaceRoot}/packages/hooks/src/**/*",
"{workspaceRoot}/packages/jotai/src/**/*",
"{workspaceRoot}/packages/templates/src/**/*",
"{workspaceRoot}/packages/workspace/src/**/*",
{
"env": "BUILD_TYPE"
},
{
"env": "PERFSEE_TOKEN"
},
{
"env": "SENTRY_ORG"
},
{
"env": "SENTRY_PROJECT"
},
{
"env": "SENTRY_AUTH_TOKEN"
},
{
"env": "NEXT_PUBLIC_SENTRY_DSN"
},
{
"env": "DISTRIBUTION"
},
{
"env": "COVERAGE"
}
],
"options": {
"script": "build"
},
"outputs": ["{projectRoot}/dist"]
}
}
}

View File

@@ -1,4 +0,0 @@
*.js
*.map
plugins

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 280 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 330 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 723 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 971 KiB

View File

@@ -1,53 +0,0 @@
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,62 +0,0 @@
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 { 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 } from 'react';
import { RouterProvider } from 'react-router-dom';
import { router } from './router';
import createEmotionCache from './utils/create-emotion-cache';
const cache = createEmotionCache();
const DevTools = lazy(() =>
import('jotai-devtools').then(m => ({ default: m.DevTools }))
);
const DebugProvider = ({ children }: PropsWithChildren): ReactElement => {
return (
<>
<Suspense>{process.env.DEBUG_JOTAI === 'true' && <DevTools />}</Suspense>
{children}
</>
);
};
const future = {
v7_startTransition: true,
} as const;
async function loadLanguage() {
if (environment.isBrowser) {
const { createI18n, setUpLanguage } = await import('@affine/i18n');
const i18n = createI18n();
document.documentElement.lang = i18n.language;
await setUpLanguage(i18n);
}
}
const languageLoadingPromise = loadLanguage().catch(console.error);
export const App = memo(function App() {
use(languageLoadingPromise);
return (
<CacheProvider value={cache}>
<AffineContext store={getCurrentStore()}>
<DebugProvider>
<RouterProvider
fallbackElement={<WorkspaceFallback key="RouterFallback" />}
router={router}
future={future}
/>
</DebugProvider>
</AffineContext>
</CacheProvider>
);
});

View File

@@ -1,106 +0,0 @@
import { useAtom } from 'jotai';
import { atomWithStorage, createJSONStorage } from 'jotai/utils';
import { useCallback } from 'react';
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
import { useNavigate } from 'react-router-dom';
import { router } from '../router';
export type History = {
stack: string[];
current: number;
skip: boolean;
};
export const MAX_HISTORY = 50;
const historyBaseAtom = atomWithStorage<History>(
'router-history',
{
stack: [],
current: 0,
skip: false,
},
createJSONStorage(() => sessionStorage)
);
historyBaseAtom.onMount = set => {
const unsubscribe = router.subscribe(state => {
set(prev => {
const url = state.location.pathname;
// if stack top is the same as current, skip
if (prev.stack[prev.current] === url) {
return prev;
}
if (prev.skip) {
return {
stack: [...prev.stack],
current: prev.current,
skip: false,
};
} else {
if (prev.current < prev.stack.length - 1) {
const newStack = prev.stack.slice(0, prev.current);
newStack.push(url);
if (newStack.length > MAX_HISTORY) {
newStack.shift();
}
return {
stack: newStack,
current: newStack.length - 1,
skip: false,
};
} else {
const newStack = [...prev.stack, url];
if (newStack.length > MAX_HISTORY) {
newStack.shift();
}
return {
stack: newStack,
current: newStack.length - 1,
skip: false,
};
}
}
});
});
return () => {
unsubscribe();
};
};
export function useHistoryAtom() {
const navigate = useNavigate();
const [base, setBase] = useAtom(historyBaseAtom);
return [
base,
useCallback(
(forward: boolean) => {
setBase(prev => {
if (forward) {
const target = Math.min(prev.stack.length - 1, prev.current + 1);
const url = prev.stack[target];
navigate(url);
return {
...prev,
current: target,
skip: true,
};
} else {
const target = Math.max(0, prev.current - 1);
const url = prev.stack[target];
navigate(url);
return {
...prev,
current: target,
skip: true,
};
}
});
},
[setBase, navigate]
),
] as const;
}

View File

@@ -1,86 +0,0 @@
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

@@ -1,110 +0,0 @@
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

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

View File

@@ -1,580 +0,0 @@
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,134 +0,0 @@
import { DebugLogger } from '@affine/debug';
import {
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';
import { createSetup } from './plugins/setup';
const logger = new DebugLogger('register-plugins');
declare global {
// eslint-disable-next-line no-var
var __pluginPackageJson__: unknown[];
}
Object.defineProperty(globalThis, '__pluginPackageJson__', {
get() {
return getCurrentStore().get(pluginPackageJson);
},
});
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);
}
});
enabledPluginSet.forEach(pluginName => {
if (!enabledPlugin.has(pluginName)) {
removed.add(pluginName);
}
});
// update plugins
enabledPluginSet.clear();
enabledPlugin.forEach(pluginName => {
enabledPluginSet.add(pluginName);
});
added.forEach(pluginName => {
evaluatePluginEntry(pluginName);
});
removed.forEach(pluginName => {
invokeCleanup(pluginName);
});
});
const enabledPluginSet = new Set(rootStore.get(enabledPluginAtom));
const loadedAssets = new Set<string>();
// we will load all plugins in parallel from builtinPlugins
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

@@ -1,59 +0,0 @@
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 { Button } from '@toeverything/components/button';
import type { SaveDBFileResult } from '@toeverything/infra/type';
import { useCallback } from 'react';
import type { AffineOfficialWorkspace } from '../../../shared';
async function syncBlobsToSqliteDb(workspace: AffineOfficialWorkspace) {
if (window.apis && isDesktop) {
const bs = workspace.blockSuiteWorkspace.blobs;
const blobsInDb = await window.apis.db.getBlobKeys(workspace.id);
const blobsInStorage = await bs.list();
const blobsToSync = blobsInStorage.filter(
blob => !blobsInDb.includes(blob)
);
await Promise.all(
blobsToSync.map(async blobKey => {
const blob = await bs.get(blobKey);
if (blob) {
const bin = new Uint8Array(await blob.arrayBuffer());
await window.apis.db.addBlob(workspace.id, blobKey, bin);
}
})
);
}
}
interface ExportPanelProps {
workspace: AffineOfficialWorkspace;
}
export const ExportPanel = ({ workspace }: ExportPanelProps) => {
const workspaceId = workspace.id;
const t = useAFFiNEI18N();
const onExport = useCallback(async () => {
await syncBlobsToSqliteDb(workspace);
const result: SaveDBFileResult =
await window.apis?.dialog.saveDBFileAs(workspaceId);
if (result?.error) {
toast(t[result.error]());
} else if (!result?.canceled) {
toast(t['Export success']());
}
}, [t, workspace, workspaceId]);
return (
<>
<SettingRow name={t['Export']()} desc={t['Export Description']()}>
<Button data-testid="export-affine-backup" onClick={onExport}>
{t['Export']()}
</Button>
</SettingRow>
</>
);
};

View File

@@ -1,89 +0,0 @@
import { FlexWrapper, Input, toast, Wrapper } from '@affine/component';
import { WorkspaceAvatar } from '@affine/component/workspace-avatar';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
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 { useCallback, useState } from 'react';
import type { AffineOfficialWorkspace } from '../../../shared';
import { Upload } from '../../pure/file-upload';
import * as style from './style.css';
interface ProfilePanelProps {
workspace: AffineOfficialWorkspace;
}
export const ProfilePanel = ({ workspace }: ProfilePanelProps) => {
const t = useAFFiNEI18N();
const [, update] = useBlockSuiteWorkspaceAvatarUrl(
workspace.blockSuiteWorkspace
);
const [name, setName] = useBlockSuiteWorkspaceName(
workspace.blockSuiteWorkspace
);
const [input, setInput] = useState<string>(name);
const handleUpdateWorkspaceName = useCallback(
(name: string) => {
setName(name);
toast(t['Update workspace name success']());
},
[setName, t]
);
return (
<div className={style.profileWrapper}>
<div className={style.avatarWrapper}>
<Upload
accept="image/gif,image/jpeg,image/jpg,image/png,image/svg"
fileChange={update}
data-testid="upload-avatar"
>
<>
<div className="camera-icon-wrapper">
<CameraIcon />
</div>
<WorkspaceAvatar
size={56}
workspace={workspace.blockSuiteWorkspace}
/>
</>
</Upload>
</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,97 +0,0 @@
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 {
addCleanup,
enabledPluginAtom,
pluginPackageJson,
pluginSettingAtom,
} from '@toeverything/infra/__internal__/plugin';
import { loadedPluginNameAtom } from '@toeverything/infra/atom';
import type { packageJsonOutputSchema } from '@toeverything/infra/type';
import { useAtom, useAtomValue } from 'jotai/react';
import { startTransition, useCallback, useMemo } from 'react';
import type { z } from 'zod';
import { 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
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 loadedPlugins = useAtomValue(loadedPluginNameAtom);
return (
<>
<SettingHeader
title={'Plugins'}
subtitle={loadedPlugins.length === 0 && t['None yet']()}
data-testid="plugins-title"
/>
{useAtomValue(pluginPackageJson).map(json => (
<PluginItem json={json} key={json.name} />
))}
</>
);
};

View File

@@ -1,16 +0,0 @@
import { style } from '@vanilla-extract/css';
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

@@ -1,62 +0,0 @@
import { SettingHeader } from '@affine/component/setting-components';
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 { 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 markdownShortcutsInfo = useMarkdownShortcuts();
const pageShortcutsInfo = usePageShortcuts();
const edgelessShortcutsInfo = useEdgelessShortcuts();
const generalShortcutsInfo = useGeneralShortcuts();
return (
<>
<SettingHeader
title={t['Keyboard Shortcuts']()}
subtitle={t['Check Keyboard Shortcuts quickly']()}
data-testid="keyboard-shortcuts-title"
/>
<ShortcutsPanel shortcutsInfo={generalShortcutsInfo} />
<ShortcutsPanel shortcutsInfo={pageShortcutsInfo} />
<ShortcutsPanel shortcutsInfo={edgelessShortcutsInfo} />
<ShortcutsPanel shortcutsInfo={markdownShortcutsInfo} />
</>
);
};

View File

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

View File

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

View File

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

View File

@@ -1,919 +0,0 @@
{
"v": "5.12.1",
"fr": 120,
"ip": 0,
"op": 76,
"w": 240,
"h": 240,
"nm": "Edgeless",
"ddd": 0,
"assets": [],
"layers": [
{
"ddd": 0,
"ind": 1,
"ty": 4,
"nm": "“图层 2”轮廓",
"sr": 1,
"ks": {
"o": {
"a": 0,
"k": 100,
"ix": 11
},
"r": {
"a": 0,
"k": 0,
"ix": 10
},
"p": {
"a": 0,
"k": [97.5, 138, 0],
"ix": 2,
"l": 2
},
"a": {
"a": 0,
"k": [10.35, 13.5, 0],
"ix": 1,
"l": 2
},
"s": {
"a": 1,
"k": [
{
"i": {
"x": [0.772, 0.772, 0.667],
"y": [1, 1, 1.219]
},
"o": {
"x": [0.462, 0.462, 0.333],
"y": [0, 0, 0]
},
"t": 5,
"s": [1100, 1100, 100]
},
{
"i": {
"x": [0.562, 0.562, 0.667],
"y": [1, 1, 1]
},
"o": {
"x": [0.455, 0.455, 0.333],
"y": [0, 0, -0.238]
},
"t": 26.562,
"s": [1070, 1070, 100]
},
{
"t": 50,
"s": [1100, 1100, 100]
}
],
"ix": 6,
"l": 2
}
},
"ao": 0,
"shapes": [
{
"ty": "gr",
"it": [
{
"ind": 0,
"ty": "sh",
"ix": 1,
"ks": {
"a": 0,
"k": {
"i": [
[0, 0],
[1.369, 1.18],
[1.875, 0]
],
"o": [
[0, -1.885],
[-2.094, -1.571],
[0, 0]
],
"v": [
[3.665, 3.299],
[1.57, -1.728],
[-3.665, -3.299]
],
"c": false
},
"ix": 2
},
"nm": "路径 1",
"mn": "ADBE Vector Shape - Group",
"hd": false
},
{
"ty": "st",
"c": {
"a": 0,
"k": [0.466666696586, 0.458823559331, 0.490196108351, 1],
"ix": 3
},
"o": {
"a": 0,
"k": 100,
"ix": 4
},
"w": {
"a": 0,
"k": 1.5,
"ix": 5
},
"lc": 1,
"lj": 1,
"ml": 4,
"bm": 0,
"nm": "描边 1",
"mn": "ADBE Vector Graphic - Stroke",
"hd": false
},
{
"ty": "tr",
"p": {
"a": 0,
"k": [13.626, 10.441],
"ix": 2
},
"a": {
"a": 0,
"k": [0, 0],
"ix": 1
},
"s": {
"a": 0,
"k": [100, 100],
"ix": 3
},
"r": {
"a": 0,
"k": 0,
"ix": 6
},
"o": {
"a": 0,
"k": 100,
"ix": 7
},
"sk": {
"a": 0,
"k": 0,
"ix": 4
},
"sa": {
"a": 0,
"k": 0,
"ix": 5
},
"nm": "变换"
}
],
"nm": "组 1",
"np": 2,
"cix": 2,
"bm": 0,
"ix": 1,
"mn": "ADBE Vector Group",
"hd": false
}
],
"ip": 0,
"op": 240,
"st": 0,
"ct": 1,
"bm": 0
},
{
"ddd": 0,
"ind": 2,
"ty": 4,
"nm": "“图层 3”轮廓",
"sr": 1,
"ks": {
"o": {
"a": 0,
"k": 100,
"ix": 11
},
"r": {
"a": 0,
"k": 0,
"ix": 10
},
"p": {
"a": 0,
"k": [69, 119, 0],
"ix": 2,
"l": 2
},
"a": {
"a": 0,
"k": [6.9, 11.9, 0],
"ix": 1,
"l": 2
},
"s": {
"a": 0,
"k": [1000, 1000, 100],
"ix": 6,
"l": 2
}
},
"ao": 0,
"shapes": [
{
"ty": "gr",
"it": [
{
"ind": 0,
"ty": "sh",
"ix": 1,
"ks": {
"a": 0,
"k": {
"i": [
[0, 0],
[0, 0]
],
"o": [
[0, 0],
[0, 0]
],
"v": [
[6.818, 9.76],
[6.818, 13.949]
],
"c": false
},
"ix": 2
},
"nm": "路径 1",
"mn": "ADBE Vector Shape - Group",
"hd": false
},
{
"ty": "st",
"c": {
"a": 0,
"k": [0.466666696586, 0.458823559331, 0.490196108351, 1],
"ix": 3
},
"o": {
"a": 0,
"k": 100,
"ix": 4
},
"w": {
"a": 0,
"k": 1.5,
"ix": 5
},
"lc": 1,
"lj": 1,
"ml": 4,
"bm": 0,
"nm": "描边 1",
"mn": "ADBE Vector Graphic - Stroke",
"hd": false
},
{
"ty": "tr",
"p": {
"a": 0,
"k": [0, 0],
"ix": 2
},
"a": {
"a": 0,
"k": [0, 0],
"ix": 1
},
"s": {
"a": 0,
"k": [100, 100],
"ix": 3
},
"r": {
"a": 0,
"k": 0,
"ix": 6
},
"o": {
"a": 0,
"k": 100,
"ix": 7
},
"sk": {
"a": 0,
"k": 0,
"ix": 4
},
"sa": {
"a": 0,
"k": 0,
"ix": 5
},
"nm": "变换"
}
],
"nm": "组 1",
"np": 2,
"cix": 2,
"bm": 0,
"ix": 1,
"mn": "ADBE Vector Group",
"hd": false
}
],
"ip": 0,
"op": 240,
"st": 0,
"ct": 1,
"bm": 0
},
{
"ddd": 0,
"ind": 3,
"ty": 4,
"nm": "“图层 4”轮廓",
"sr": 1,
"ks": {
"o": {
"a": 0,
"k": 100,
"ix": 11
},
"r": {
"a": 1,
"k": [
{
"i": {
"x": [0.363],
"y": [1]
},
"o": {
"x": [0.675],
"y": [-0.111]
},
"t": 5,
"s": [0]
},
{
"t": 50,
"s": [90]
}
],
"ix": 10
},
"p": {
"a": 1,
"k": [
{
"i": {
"x": 0.363,
"y": 1
},
"o": {
"x": 0.675,
"y": 0
},
"t": 5,
"s": [173.5, 171, 0],
"to": [0, -1.333, 0],
"ti": [0, 0, 0]
},
{
"i": {
"x": 0.667,
"y": 1
},
"o": {
"x": 0.647,
"y": 0
},
"t": 26.562,
"s": [173.5, 163, 0],
"to": [0, 0, 0],
"ti": [0, -1.333, 0]
},
{
"t": 50,
"s": [173.5, 171, 0]
}
],
"ix": 2,
"l": 2
},
"a": {
"a": 0,
"k": [17.35, 16.9, 0],
"ix": 1,
"l": 2
},
"s": {
"a": 0,
"k": [1000, 1000, 100],
"ix": 6,
"l": 2
}
},
"ao": 0,
"shapes": [
{
"ty": "gr",
"it": [
{
"ind": 0,
"ty": "sh",
"ix": 1,
"ks": {
"a": 0,
"k": {
"i": [
[0, 0],
[0, 0],
[0, 0],
[0, 0]
],
"o": [
[0, 0],
[0, 0],
[0, 0],
[0, 0]
],
"v": [
[-2.357, -2.357],
[2.357, -2.357],
[2.357, 2.357],
[-2.357, 2.357]
],
"c": true
},
"ix": 2
},
"nm": "路径 1",
"mn": "ADBE Vector Shape - Group",
"hd": false
},
{
"ty": "st",
"c": {
"a": 0,
"k": [0.466666696586, 0.458823559331, 0.490196108351, 1],
"ix": 3
},
"o": {
"a": 0,
"k": 100,
"ix": 4
},
"w": {
"a": 0,
"k": 1.5,
"ix": 5
},
"lc": 1,
"lj": 2,
"bm": 0,
"nm": "描边 1",
"mn": "ADBE Vector Graphic - Stroke",
"hd": false
},
{
"ty": "tr",
"p": {
"a": 0,
"k": [17.344, 16.829],
"ix": 2
},
"a": {
"a": 0,
"k": [0, 0],
"ix": 1
},
"s": {
"a": 0,
"k": [100, 100],
"ix": 3
},
"r": {
"a": 0,
"k": 0,
"ix": 6
},
"o": {
"a": 0,
"k": 100,
"ix": 7
},
"sk": {
"a": 0,
"k": 0,
"ix": 4
},
"sa": {
"a": 0,
"k": 0,
"ix": 5
},
"nm": "变换"
}
],
"nm": "组 1",
"np": 2,
"cix": 2,
"bm": 0,
"ix": 1,
"mn": "ADBE Vector Group",
"hd": false
}
],
"ip": 0,
"op": 240,
"st": 0,
"ct": 1,
"bm": 0
},
{
"ddd": 0,
"ind": 4,
"ty": 4,
"nm": "“图层 5”轮廓",
"sr": 1,
"ks": {
"o": {
"a": 0,
"k": 100,
"ix": 11
},
"r": {
"a": 0,
"k": 0,
"ix": 10
},
"p": {
"a": 1,
"k": [
{
"i": {
"x": 0.363,
"y": 1
},
"o": {
"x": 0.675,
"y": 0
},
"t": 5,
"s": [68.5, 170, 0],
"to": [0, -1.333, 0],
"ti": [0, 0, 0]
},
{
"i": {
"x": 0.667,
"y": 1
},
"o": {
"x": 0.647,
"y": 0
},
"t": 26.562,
"s": [68.5, 162, 0],
"to": [0, 0, 0],
"ti": [0, -1.333, 0]
},
{
"t": 50,
"s": [68.5, 170, 0]
}
],
"ix": 2,
"l": 2
},
"a": {
"a": 0,
"k": [6.85, 17.05, 0],
"ix": 1,
"l": 2
},
"s": {
"a": 0,
"k": [1000, 1000, 100],
"ix": 6,
"l": 2
}
},
"ao": 0,
"shapes": [
{
"ty": "gr",
"it": [
{
"ind": 0,
"ty": "sh",
"ix": 1,
"ks": {
"a": 0,
"k": {
"i": [
[-1.446, 0],
[0, -1.446],
[1.446, 0],
[0, 1.446]
],
"o": [
[1.446, 0],
[0, 1.446],
[-1.446, 0],
[0, -1.446]
],
"v": [
[0, -2.618],
[2.618, 0],
[0, 2.618],
[-2.618, 0]
],
"c": true
},
"ix": 2
},
"nm": "路径 1",
"mn": "ADBE Vector Shape - Group",
"hd": false
},
{
"ty": "st",
"c": {
"a": 0,
"k": [0.466666696586, 0.458823559331, 0.490196108351, 1],
"ix": 3
},
"o": {
"a": 0,
"k": 100,
"ix": 4
},
"w": {
"a": 0,
"k": 1.5,
"ix": 5
},
"lc": 1,
"lj": 1,
"ml": 4,
"bm": 0,
"nm": "描边 1",
"mn": "ADBE Vector Graphic - Stroke",
"hd": false
},
{
"ty": "tr",
"p": {
"a": 0,
"k": [6.818, 17.091],
"ix": 2
},
"a": {
"a": 0,
"k": [0, 0],
"ix": 1
},
"s": {
"a": 0,
"k": [100, 100],
"ix": 3
},
"r": {
"a": 0,
"k": 0,
"ix": 6
},
"o": {
"a": 0,
"k": 100,
"ix": 7
},
"sk": {
"a": 0,
"k": 0,
"ix": 4
},
"sa": {
"a": 0,
"k": 0,
"ix": 5
},
"nm": "变换"
}
],
"nm": "组 1",
"np": 2,
"cix": 2,
"bm": 0,
"ix": 1,
"mn": "ADBE Vector Group",
"hd": false
}
],
"ip": 0,
"op": 240,
"st": 0,
"ct": 1,
"bm": 0
},
{
"ddd": 0,
"ind": 5,
"ty": 4,
"nm": "“图层 6”轮廓",
"sr": 1,
"ks": {
"o": {
"a": 0,
"k": 100,
"ix": 11
},
"r": {
"a": 0,
"k": 0,
"ix": 10
},
"p": {
"a": 1,
"k": [
{
"i": {
"x": 0.363,
"y": 1
},
"o": {
"x": 0.675,
"y": 0
},
"t": 5,
"s": [68, 65, 0],
"to": [0, 1.333, 0],
"ti": [0, 0, 0]
},
{
"i": {
"x": 0.667,
"y": 1
},
"o": {
"x": 0.647,
"y": 0
},
"t": 26.562,
"s": [68, 73, 0],
"to": [0, 0, 0],
"ti": [0, 1.333, 0]
},
{
"t": 50,
"s": [68, 65, 0]
}
],
"ix": 2,
"l": 2
},
"a": {
"a": 0,
"k": [6.8, 6.6, 0],
"ix": 1,
"l": 2
},
"s": {
"a": 0,
"k": [1000, 1000, 100],
"ix": 6,
"l": 2
}
},
"ao": 0,
"shapes": [
{
"ty": "gr",
"it": [
{
"ind": 0,
"ty": "sh",
"ix": 1,
"ks": {
"a": 0,
"k": {
"i": [
[-1.446, 0],
[0, -1.446],
[1.446, 0],
[0, 1.446]
],
"o": [
[1.446, 0],
[0, 1.446],
[-1.446, 0],
[0, -1.446]
],
"v": [
[0, -2.618],
[2.618, 0],
[0, 2.618],
[-2.618, 0]
],
"c": true
},
"ix": 2
},
"nm": "路径 1",
"mn": "ADBE Vector Shape - Group",
"hd": false
},
{
"ty": "st",
"c": {
"a": 0,
"k": [0.466666696586, 0.458823559331, 0.490196108351, 1],
"ix": 3
},
"o": {
"a": 0,
"k": 100,
"ix": 4
},
"w": {
"a": 0,
"k": 1.5,
"ix": 5
},
"lc": 1,
"lj": 1,
"ml": 4,
"bm": 0,
"nm": "描边 1",
"mn": "ADBE Vector Graphic - Stroke",
"hd": false
},
{
"ty": "tr",
"p": {
"a": 0,
"k": [6.818, 6.618],
"ix": 2
},
"a": {
"a": 0,
"k": [0, 0],
"ix": 1
},
"s": {
"a": 0,
"k": [100, 100],
"ix": 3
},
"r": {
"a": 0,
"k": 0,
"ix": 6
},
"o": {
"a": 0,
"k": 100,
"ix": 7
},
"sk": {
"a": 0,
"k": 0,
"ix": 4
},
"sa": {
"a": 0,
"k": 0,
"ix": 5
},
"nm": "变换"
}
],
"nm": "组 1",
"np": 2,
"cix": 2,
"bm": 0,
"ix": 1,
"mn": "ADBE Vector Group",
"hd": false
}
],
"ip": 0,
"op": 240,
"st": 0,
"ct": 1,
"bm": 0
}
],
"markers": []
}

View File

@@ -1,63 +0,0 @@
import { toast } from '@affine/component';
import { initEmptyPage } from '@affine/env/blocksuite';
import { WorkspaceSubPath } from '@affine/env/workspace';
import { useBlockSuiteWorkspaceHelper } from '@toeverything/hooks/use-block-suite-workspace-helper';
import { useAtomValue, useSetAtom } from 'jotai';
import { useCallback } from 'react';
import { pageSettingsAtom, setPageModeAtom } from '../../../atoms';
import { useNavigateHelper } from '../../../hooks/use-navigate-helper';
import type { BlockSuiteWorkspace } from '../../../shared';
export const usePageHelper = (blockSuiteWorkspace: BlockSuiteWorkspace) => {
const { openPage, jumpToSubPath } = useNavigateHelper();
const { createPage } = useBlockSuiteWorkspaceHelper(blockSuiteWorkspace);
const pageSettings = useAtomValue(pageSettingsAtom);
const isPreferredEdgeless = useCallback(
(pageId: string) => pageSettings[pageId]?.mode === 'edgeless',
[pageSettings]
);
const setPageMode = useSetAtom(setPageModeAtom);
const createPageAndOpen = useCallback(
(id?: string, mode?: 'page' | 'edgeless') => {
const page = createPage(id);
initEmptyPage(page); // we don't need to wait it to be loaded right?
setPageMode(page.id, mode || 'page');
openPage(blockSuiteWorkspace.id, page.id);
},
[blockSuiteWorkspace.id, createPage, openPage, setPageMode]
);
const createEdgelessAndOpen = useCallback(
(id?: string) => {
return createPageAndOpen(id, 'edgeless');
},
[createPageAndOpen]
);
const importFileAndOpen = useCallback(async () => {
const { showImportModal } = await import('@blocksuite/blocks');
const onSuccess = (pageIds: string[], isWorkspaceFile: boolean) => {
toast(
`Successfully imported ${pageIds.length} Page${
pageIds.length > 1 ? 's' : ''
}.`
);
if (isWorkspaceFile) {
jumpToSubPath(blockSuiteWorkspace.id, WorkspaceSubPath.ALL);
return;
}
if (pageIds.length === 0) {
return;
}
const pageId = pageIds[0];
openPage(blockSuiteWorkspace.id, pageId);
};
showImportModal({ workspace: blockSuiteWorkspace, onSuccess });
}, [blockSuiteWorkspace, openPage, jumpToSubPath]);
return {
createPage: createPageAndOpen,
createEdgeless: createEdgelessAndOpen,
importFile: importFileAndOpen,
isPreferredEdgeless: isPreferredEdgeless,
};
};

View File

@@ -1,260 +0,0 @@
import './page-detail-editor.css';
import { PageNotFoundError } from '@affine/env/constant';
import type { LayoutNode } from '@affine/sdk//entry';
import { rootBlockHubAtom } from '@affine/workspace/atom';
import type { EditorContainer } from '@blocksuite/editor';
import { assertExists } from '@blocksuite/global/utils';
import type { Page, Workspace } from '@blocksuite/store';
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
import { useBlockSuiteWorkspacePage } from '@toeverything/hooks/use-block-suite-workspace-page';
import {
addCleanup,
pluginEditorAtom,
pluginWindowAtom,
} from '@toeverything/infra/__internal__/plugin';
import { contentLayoutAtom, getCurrentStore } from '@toeverything/infra/atom';
import clsx from 'clsx';
import { useAtomValue, useSetAtom } from 'jotai';
import type { CSSProperties, ReactElement } from 'react';
import {
memo,
startTransition,
Suspense,
useCallback,
useEffect,
useMemo,
useRef,
} from 'react';
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
import { pageSettingFamily } from '../atoms';
import { fontStyleOptions, useAppSetting } from '../atoms/settings';
import { BlockSuiteEditor as Editor } from './blocksuite/block-suite-editor';
import * as styles from './page-detail-editor.css';
import { pluginContainer } from './page-detail-editor.css';
import { TrashButtonGroup } from './pure/trash-button-group';
export interface PageDetailEditorProps {
isPublic?: boolean;
workspace: Workspace;
pageId: string;
onInit: (page: Page, editor: Readonly<EditorContainer>) => void;
onLoad?: (page: Page, editor: EditorContainer) => () => void;
}
const EditorWrapper = memo(function EditorWrapper({
workspace,
pageId,
onInit,
onLoad,
isPublic,
}: PageDetailEditorProps) {
const page = useBlockSuiteWorkspacePage(workspace, pageId);
if (!page) {
throw new PageNotFoundError(workspace, pageId);
}
const meta = useBlockSuitePageMeta(workspace).find(
meta => meta.id === pageId
);
const pageSettingAtom = pageSettingFamily(pageId);
const pageSetting = useAtomValue(pageSettingAtom);
const currentMode = pageSetting?.mode ?? 'page';
const setBlockHub = useSetAtom(rootBlockHubAtom);
const [appSettings] = useAppSetting();
assertExists(meta);
const value = useMemo(() => {
const fontStyle = fontStyleOptions.find(
option => option.key === appSettings.fontStyle
);
assertExists(fontStyle);
return fontStyle.value;
}, [appSettings.fontStyle]);
return (
<>
<Editor
className={clsx(styles.editor, {
'full-screen': appSettings.fullWidthLayout,
})}
style={
{
'--affine-font-family': value,
} as CSSProperties
}
mode={isPublic ? 'page' : currentMode}
page={page}
onInit={useCallback(
(page: Page, editor: Readonly<EditorContainer>) => {
onInit(page, editor);
},
[onInit]
)}
setBlockHub={setBlockHub}
onLoad={useCallback(
(page: Page, editor: EditorContainer) => {
page.workspace.setPageMeta(page.id, {
updatedDate: Date.now(),
});
localStorage.setItem('last_page_id', page.id);
let dispose = () => {};
if (onLoad) {
dispose = onLoad(page, editor);
}
const rootStore = getCurrentStore();
const editorItems = rootStore.get(pluginEditorAtom);
let disposes: (() => void)[] = [];
const renderTimeout = window.setTimeout(() => {
disposes = Object.entries(editorItems).map(([id, editorItem]) => {
const div = document.createElement('div');
div.setAttribute('plugin-id', id);
const cleanup = editorItem(div, editor);
assertExists(parent);
document.body.appendChild(div);
return () => {
cleanup();
document.body.removeChild(div);
};
});
});
return () => {
dispose();
clearTimeout(renderTimeout);
window.setTimeout(() => {
disposes.forEach(dispose => dispose());
});
};
},
[onLoad]
)}
/>
{meta.trash && <TrashButtonGroup />}
</>
);
});
interface PluginContentAdapterProps {
windowItem: (div: HTMLDivElement) => () => void;
pluginName: string;
}
const PluginContentAdapter = memo<PluginContentAdapterProps>(
function PluginContentAdapter({ windowItem, pluginName }) {
const rootRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
const abortController = new AbortController();
const root = rootRef.current;
if (root) {
startTransition(() => {
if (abortController.signal.aborted) {
return;
}
const div = document.createElement('div');
const cleanup = windowItem(div);
root.appendChild(div);
if (abortController.signal.aborted) {
cleanup();
root.removeChild(div);
} else {
const cl = () => {
cleanup();
root.removeChild(div);
};
const dispose = addCleanup(pluginName, cl);
abortController.signal.addEventListener('abort', () => {
window.setTimeout(() => {
dispose();
cl();
});
});
}
});
return () => {
abortController.abort();
};
}
return;
}, [pluginName, windowItem]);
return <div className={pluginContainer} ref={rootRef} />;
}
);
interface LayoutPanelProps {
node: LayoutNode;
editorProps: PageDetailEditorProps;
}
const LayoutPanel = memo(function LayoutPanel(
props: LayoutPanelProps
): ReactElement {
const node = props.node;
const windowItems = useAtomValue(pluginWindowAtom);
if (typeof node === 'string') {
if (node === 'editor') {
return <EditorWrapper {...props.editorProps} />;
} else {
const windowItem = windowItems[node];
return <PluginContentAdapter pluginName={node} windowItem={windowItem} />;
}
} else {
return (
<PanelGroup direction={node.direction}>
<Panel
defaultSize={node.splitPercentage}
style={{
maxWidth: node.maxWidth?.[0],
}}
>
<Suspense>
<LayoutPanel node={node.first} editorProps={props.editorProps} />
</Suspense>
</Panel>
<PanelResizeHandle />
<Panel
defaultSize={100 - node.splitPercentage}
style={{
overflow: 'scroll',
maxWidth: node.maxWidth?.[1],
}}
>
<Suspense>
<LayoutPanel node={node.second} editorProps={props.editorProps} />
</Suspense>
</Panel>
</PanelGroup>
);
}
});
export const PageDetailEditor = (props: PageDetailEditorProps) => {
const { workspace, pageId } = props;
const page = useBlockSuiteWorkspacePage(workspace, pageId);
if (!page) {
throw new PageNotFoundError(workspace, pageId);
}
const layout = useAtomValue(contentLayoutAtom);
if (layout === 'editor') {
return (
<Suspense>
<PanelGroup direction="horizontal">
<Panel>
<EditorWrapper {...props} />
</Panel>
</PanelGroup>
</Suspense>
);
}
return (
<>
<Suspense>
<LayoutPanel node={layout} editorProps={props} />
</Suspense>
</>
);
};

View File

@@ -1,178 +0,0 @@
import { Wrapper } from '@affine/component';
import {
appSidebarFloatingAtom,
appSidebarOpenAtom,
SidebarSwitch,
} from '@affine/component/app-sidebar';
import { isDesktop } from '@affine/env/constant';
import clsx from 'clsx';
import { useAtomValue } from 'jotai';
import debounce from 'lodash.debounce';
import type { MutableRefObject, ReactNode } from 'react';
import { useEffect, useRef, useState } from 'react';
import * as style from './style.css';
import { TopTip } from './top-tip';
import { WindowsAppControls } from './windows-app-controls';
interface HeaderPros {
left?: ReactNode;
right?: ReactNode;
center?: ReactNode;
}
const useIsTinyScreen = ({
mainContainer,
leftStatic,
leftSlot,
centerDom,
rightStatic,
rightSlot,
}: {
mainContainer: HTMLElement;
leftStatic: MutableRefObject<HTMLElement | null>;
leftSlot: MutableRefObject<HTMLElement | null>[];
centerDom: MutableRefObject<HTMLElement | null>;
rightStatic: MutableRefObject<HTMLElement | null>;
rightSlot: MutableRefObject<HTMLElement | null>[];
}) => {
const [isTinyScreen, setIsTinyScreen] = useState(false);
useEffect(() => {
const handleResize = debounce(() => {
if (!centerDom.current) {
return;
}
const leftStaticWidth = leftStatic.current?.clientWidth || 0;
const leftSlotWidth = leftSlot.reduce((accWidth, dom) => {
return accWidth + (dom.current?.clientWidth || 0);
}, 0);
const rightStaticWidth = rightStatic.current?.clientWidth || 0;
const rightSlotWidth = rightSlot.reduce((accWidth, dom) => {
return accWidth + (dom.current?.clientWidth || 0);
}, 0);
if (!leftSlotWidth && !rightSlotWidth) {
if (isTinyScreen) {
setIsTinyScreen(false);
}
return;
}
const containerRect = mainContainer.getBoundingClientRect();
const centerRect = centerDom.current.getBoundingClientRect();
if (
leftStaticWidth + leftSlotWidth + containerRect.left >=
centerRect.left ||
containerRect.right - centerRect.right <=
rightSlotWidth + rightStaticWidth
) {
setIsTinyScreen(true);
} else {
setIsTinyScreen(false);
}
}, 100);
handleResize();
const resizeObserver = new ResizeObserver(() => {
handleResize();
});
resizeObserver.observe(mainContainer);
return () => {
resizeObserver.disconnect();
};
}, [
centerDom,
isTinyScreen,
leftSlot,
leftStatic,
mainContainer,
rightSlot,
rightStatic,
]);
return isTinyScreen;
};
// The Header component is used to solve the following problems
// 1. Manage layout issues independently of page or business logic
// 2. Dynamic centered middle element (relative to the main-container), when the middle element is detected to collide with the two elements, the line wrapping process is performed
export const Header = ({ left, center, right }: HeaderPros) => {
const sidebarSwitchRef = useRef<HTMLDivElement | null>(null);
const leftSlotRef = useRef<HTMLDivElement | null>(null);
const centerSlotRef = useRef<HTMLDivElement | null>(null);
const rightSlotRef = useRef<HTMLDivElement | null>(null);
const windowControlsRef = useRef<HTMLDivElement | null>(null);
const isTinyScreen = useIsTinyScreen({
mainContainer: document.querySelector('.main-container') || document.body,
leftStatic: sidebarSwitchRef,
leftSlot: [leftSlotRef],
centerDom: centerSlotRef,
rightSlot: [rightSlotRef],
rightStatic: windowControlsRef,
});
const isWindowsDesktop = globalThis.platform === 'win32' && isDesktop;
const open = useAtomValue(appSidebarOpenAtom);
const appSidebarFloating = useAtomValue(appSidebarFloatingAtom);
return (
<>
<TopTip />
<div
className={style.header}
// data-has-warning={showWarning}
data-open={open}
data-sidebar-floating={appSidebarFloating}
data-testid="header"
>
<div
className={clsx(style.headerSideContainer, {
block: isTinyScreen,
})}
>
<div className={clsx(style.headerItem, 'top-item')}>
<div ref={sidebarSwitchRef}>
{!open && (
<Wrapper marginRight={20}>
<SidebarSwitch />
</Wrapper>
)}
</div>
</div>
<div className={clsx(style.headerItem, 'left')}>
<div ref={leftSlotRef}>{left}</div>
</div>
</div>
<div
className={clsx({
[style.headerCenter]: center,
'is-window': isWindowsDesktop,
})}
ref={centerSlotRef}
>
{center}
</div>
<div
className={clsx(style.headerSideContainer, 'right', {
block: isTinyScreen,
})}
>
<div className={clsx(style.headerItem, 'top-item')}>
<div ref={windowControlsRef}>
{isWindowsDesktop ? <WindowsAppControls /> : null}
</div>
</div>
<div className={clsx(style.headerItem, 'right')}>
<div ref={rightSlotRef}>{right}</div>
</div>
</div>
</div>
</>
);
};

View File

@@ -1,112 +0,0 @@
import type { ComplexStyleRule } from '@vanilla-extract/css';
import { style } from '@vanilla-extract/css';
export const header = style({
display: 'flex',
justifyContent: 'space-between',
position: 'relative',
padding: '0 16px',
minHeight: '52px',
borderBottom: '1px solid var(--affine-border-color)',
zIndex: 2,
selectors: {
'&[data-sidebar-floating="false"]': {
WebkitAppRegion: 'drag',
},
},
'@media': {
print: {
display: 'none',
},
},
':has([data-popper-placement])': {
WebkitAppRegion: 'no-drag',
},
} as ComplexStyleRule);
export const headerItem = style({
minHeight: '32px',
display: 'flex',
alignItems: 'center',
flexShrink: 0,
selectors: {
'&.top-item': {
height: '52px',
},
'&.left': {
justifyContent: 'left',
},
'&.right': {
justifyContent: 'right',
},
},
});
export const headerCenter = style({
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '52px',
flexShrink: 0,
maxWidth: '60%',
minWidth: '300px',
position: 'absolute',
transform: 'translateX(-50%)',
left: '50%',
zIndex: 1,
selectors: {
'&.is-window': {
maxWidth: '50%',
},
'&.shadow': {
position: 'static',
visibility: 'hidden',
},
},
});
export const headerSideContainer = style({
display: 'flex',
flexShrink: 0,
alignItems: 'center',
selectors: {
'&.right': {
flexDirection: 'row-reverse',
},
'&.block': {
display: 'block',
paddingBottom: '10px',
},
},
});
export const windowAppControlsWrapper = style({
display: 'flex',
marginLeft: '20px',
// header padding right
transform: 'translateX(16px)',
});
export const windowAppControl = style({
WebkitAppRegion: 'no-drag',
cursor: 'pointer',
display: 'inline-flex',
width: '52px',
height: '52px',
alignItems: 'center',
justifyContent: 'center',
borderRadius: '0',
selectors: {
'&[data-type="close"]': {
width: '56px',
paddingRight: '5px',
},
'&[data-type="close"]:hover': {
background: 'var(--affine-windows-close-button)',
color: 'var(--affine-pure-white)',
},
'&:hover': {
background: 'var(--affine-hover-color)',
},
},
} as ComplexStyleRule);

View File

@@ -1,51 +0,0 @@
import { CloseIcon, MinusIcon, RoundedRectangleIcon } from '@blocksuite/icons';
import { useCallback } from 'react';
import * as style from './style.css';
export const WindowsAppControls = () => {
const handleMinimizeApp = useCallback(() => {
window.apis?.ui.handleMinimizeApp().catch(err => {
console.error(err);
});
}, []);
const handleMaximizeApp = useCallback(() => {
window.apis?.ui.handleMaximizeApp().catch(err => {
console.error(err);
});
}, []);
const handleCloseApp = useCallback(() => {
window.apis?.ui.handleCloseApp().catch(err => {
console.error(err);
});
}, []);
return (
<div
data-platform-target="win32"
className={style.windowAppControlsWrapper}
>
<button
data-type="minimize"
className={style.windowAppControl}
onClick={handleMinimizeApp}
>
<MinusIcon />
</button>
<button
data-type="maximize"
className={style.windowAppControl}
onClick={handleMaximizeApp}
>
<RoundedRectangleIcon />
</button>
<button
data-type="close"
className={style.windowAppControl}
onClick={handleCloseApp}
>
<CloseIcon />
</button>
</div>
);
};

View File

@@ -1,44 +0,0 @@
import {
addCleanup,
pluginHeaderItemAtom,
} from '@toeverything/infra/__internal__/plugin';
import { useAtomValue } from 'jotai';
import { startTransition, useCallback, useRef } from 'react';
import * as styles from './styles.css';
export const PluginHeader = () => {
const headerItem = useAtomValue(pluginHeaderItemAtom);
const pluginsRef = useRef<string[]>([]);
return (
<div
className={styles.pluginHeaderItems}
ref={useCallback(
(root: HTMLDivElement | null) => {
if (root) {
Object.entries(headerItem).forEach(([pluginName, create]) => {
if (pluginsRef.current.includes(pluginName)) {
return;
}
pluginsRef.current.push(pluginName);
const div = document.createElement('div');
div.setAttribute('plugin-id', pluginName);
startTransition(() => {
const cleanup = create(div);
root.appendChild(div);
addCleanup(pluginName, () => {
pluginsRef.current = pluginsRef.current.filter(
name => name !== pluginName
);
root.removeChild(div);
cleanup();
});
});
});
}
},
[headerItem]
)}
/>
);
};

View File

@@ -1,8 +0,0 @@
import { style } from '@vanilla-extract/css';
export const pluginHeaderItems = style({
display: 'flex',
gap: '12px',
alignItems: 'center',
height: '100%',
});

View File

@@ -1,101 +0,0 @@
import {
ModalCloseButton,
MuiClickAwayListener,
MuiSlide,
} from '@affine/component';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import {
type ShortcutsInfo,
useEdgelessShortcuts,
useGeneralShortcuts,
useMarkdownShortcuts,
usePageShortcuts,
} from '../../../hooks/affine/use-shortcuts';
import { KeyboardIcon } from './icons';
import * as styles from './style.css';
// import {
// StyledListItem,
// StyledModalHeader,
// StyledShortcutsModal,
// StyledSubTitle,
// StyledTitle,
// } from './style';
type ModalProps = {
open: boolean;
onClose: () => void;
};
const ShortcutsPanel = ({
shortcutsInfo,
}: {
shortcutsInfo: ShortcutsInfo;
}) => {
return (
<>
<div className={styles.subtitle}>{shortcutsInfo.title}</div>
{Object.entries(shortcutsInfo.shortcuts).map(([title, shortcuts]) => {
return (
<div className={styles.listItem} key={title}>
<span>{title}</span>
<div className={styles.keyContainer}>
{shortcuts.map(key => {
return (
<span className={styles.key} key={key}>
{key}
</span>
);
})}
</div>
</div>
);
})}
</>
);
};
export const ShortcutsModal = ({ open, onClose }: ModalProps) => {
const t = useAFFiNEI18N();
const markdownShortcutsInfo = useMarkdownShortcuts();
const pageShortcutsInfo = usePageShortcuts();
const edgelessShortcutsInfo = useEdgelessShortcuts();
const generalShortcutsInfo = useGeneralShortcuts();
return (
<MuiSlide direction="left" in={open} mountOnEnter unmountOnExit>
<div className={styles.shortcutsModal} data-testid="shortcuts-modal">
<MuiClickAwayListener
onClickAway={() => {
onClose();
}}
>
<div>
<div
className={styles.modalHeader}
style={{ marginBottom: '-28px' }}
>
<div className={styles.title}>
<KeyboardIcon />
{t['Shortcuts']()}
</div>
<ModalCloseButton
top={6}
right={6}
onClick={() => {
onClose();
}}
/>
</div>
<ShortcutsPanel shortcutsInfo={generalShortcutsInfo} />
<ShortcutsPanel shortcutsInfo={pageShortcutsInfo} />
<ShortcutsPanel shortcutsInfo={edgelessShortcutsInfo} />
<ShortcutsPanel shortcutsInfo={markdownShortcutsInfo} />
</div>
</MuiClickAwayListener>
</div>
</MuiSlide>
);
};

View File

@@ -1,89 +0,0 @@
import { globalStyle, style } from '@vanilla-extract/css';
export const shortcutsModal = style({
width: '288px',
height: '74vh',
paddingBottom: '28px',
backgroundColor: 'var(--affine-white)',
boxShadow: 'var(--affine-popover-shadow)',
borderRadius: `var(--affine-popover-radius)`,
overflow: 'auto',
position: 'fixed',
right: '12px',
top: '0',
bottom: '0',
margin: 'auto',
zIndex: 'var(--affine-z-index-modal)',
});
// export const shortcutsModal = style({
// color: 'var(--affine-text-primary-color)',
// fontWeight: '500',
// fontSize: 'var(--affine-font-sm)',
// height: '44px',
// display: 'flex',
// justifyContent: 'center',
// alignItems: 'center',
// svg: {
// width: '20px',
// marginRight: '14px',
// color: 'var(--affine-primary-color)',
// },
// });
export const title = style({
color: 'var(--affine-text-primary-color)',
fontWeight: '500',
fontSize: 'var(--affine-font-sm)',
height: '44px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
});
globalStyle(`${title} svg`, {
width: '20px',
marginRight: '14px',
color: 'var(--affine-primary-color)',
});
export const subtitle = style({
fontWeight: '500',
fontSize: 'var(--affine-font-sm)',
height: '34px',
lineHeight: '36px',
marginTop: '28px',
padding: '0 16px',
});
export const modalHeader = style({
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
paddingTop: '8px 4px 0 4px',
width: '100%',
padding: '8px 16px 0 16px',
position: 'sticky',
left: '0',
top: '0',
background: 'var(--affine-white)',
transition: 'background-color 0.5s',
});
export const listItem = style({
height: '34px',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
fontSize: 'var(--affine-font-sm)',
padding: '0 16px',
});
export const keyContainer = style({
display: 'flex',
});
export const key = style({
selectors: {
'&:not(:last-child)::after': {
content: '+',
margin: '0 4px',
},
},
});

View File

@@ -1,81 +0,0 @@
import { Confirm } from '@affine/component';
import { WorkspaceSubPath } from '@affine/env/workspace';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { assertExists } from '@blocksuite/global/utils';
import { Button } from '@toeverything/components/button';
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
import { currentPageIdAtom } from '@toeverything/infra/atom';
import { useAtomValue } from 'jotai';
import { useCallback, useState } from 'react';
import { useBlockSuiteMetaHelper } from '../../../hooks/affine/use-block-suite-meta-helper';
import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace';
import { useNavigateHelper } from '../../../hooks/use-navigate-helper';
import { toast } from '../../../utils';
import { buttonContainer, group } from './styles.css';
export const TrashButtonGroup = () => {
// fixme(himself65): remove these hooks ASAP
const [workspace] = useCurrentWorkspace();
const pageId = useAtomValue(currentPageIdAtom);
assertExists(workspace);
assertExists(pageId);
const blockSuiteWorkspace = workspace.blockSuiteWorkspace;
const pageMeta = useBlockSuitePageMeta(blockSuiteWorkspace).find(
meta => meta.id === pageId
);
assertExists(pageMeta);
const t = useAFFiNEI18N();
const { jumpToSubPath } = useNavigateHelper();
const { restoreFromTrash } = useBlockSuiteMetaHelper(blockSuiteWorkspace);
const [open, setOpen] = useState(false);
return (
<div className={group}>
<div className={buttonContainer}>
<Button
type="primary"
onClick={() => {
restoreFromTrash(pageId);
toast(t['restored']({ title: pageMeta.title || 'Untitled' }));
}}
size="large"
>
{t['Restore it']()}
</Button>
</div>
<div className={buttonContainer}>
<Button
type="error"
onClick={() => {
setOpen(true);
}}
size="large"
>
{t['Delete permanently']()}
</Button>
</div>
<Confirm
title={t['TrashButtonGroupTitle']()}
content={t['TrashButtonGroupDescription']()}
confirmText={t['Delete']()}
confirmType="error"
open={open}
onConfirm={useCallback(() => {
jumpToSubPath(workspace.id, WorkspaceSubPath.ALL);
blockSuiteWorkspace.removePage(pageId);
toast(t['Permanently deleted']());
}, [blockSuiteWorkspace, jumpToSubPath, pageId, workspace.id, t])}
onCancel={() => {
setOpen(false);
}}
onClose={() => {
setOpen(false);
}}
/>
</div>
);
};
export default TrashButtonGroup;

View File

@@ -1,16 +0,0 @@
import { style } from '@vanilla-extract/css';
export const group = style({
width: '100%',
position: 'absolute',
bottom: '100px',
left: '0',
display: 'flex',
gap: '24px',
justifyContent: 'center',
zIndex: 2,
});
export const buttonContainer = style({
boxShadow: 'var(--affine-float-button-shadow-2)',
borderRadius: '8px',
});

View File

@@ -1,290 +0,0 @@
import { Menu, MenuItem } from '@affine/component';
import { WorkspaceList } from '@affine/component/workspace-list';
import type {
AffineCloudWorkspace,
LocalWorkspace,
} from '@affine/env/workspace';
import { WorkspaceFlavour } from '@affine/env/workspace';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import type { RootWorkspaceMetadata } from '@affine/workspace/atom';
import {
CloudWorkspaceIcon,
ImportIcon,
MoreHorizontalIcon,
PlusIcon,
} from '@blocksuite/icons';
import type { DragEndEvent } from '@dnd-kit/core';
import { Popover } from '@mui/material';
import { IconButton } from '@toeverything/components/button';
import { Divider } from '@toeverything/components/divider';
import { useSetAtom } from 'jotai';
import { useCallback } from 'react';
import { openDisableCloudAlertModalAtom } from '../../../atoms';
import type { AllWorkspace } from '../../../shared';
import {
StyledCreateWorkspaceCardPill,
StyledCreateWorkspaceCardPillContent,
StyledCreateWorkspaceCardPillIcon,
StyledImportWorkspaceCardPill,
StyledModalBody,
StyledModalContent,
StyledModalFooterContent,
StyledModalHeader,
StyledModalHeaderContent,
StyledModalHeaderLeft,
StyledModalTitle,
StyledOperationWrapper,
StyledSignInCardPill,
StyledSignInCardPillTextCotainer,
StyledSignInCardPillTextPrimary,
StyledSignInCardPillTextSecondary,
} from './styles';
interface WorkspaceModalProps {
disabled?: boolean;
workspaces: RootWorkspaceMetadata[];
currentWorkspaceId: AllWorkspace['id'] | null;
open: boolean;
onClose: () => void;
onClickWorkspace: (workspace: RootWorkspaceMetadata['id']) => void;
onClickWorkspaceSetting: (workspace: RootWorkspaceMetadata['id']) => void;
onNewWorkspace: () => void;
onAddWorkspace: () => void;
onMoveWorkspace: (activeId: string, overId: string) => void;
}
const AccountMenu = () => {
const t = useAFFiNEI18N();
return (
<div>
<div>Unlimted</div>
<Divider size="thinner" dividerColor="var(--affine-border-color)" />
<MenuItem icon={<ImportIcon />} data-testid="editor-option-menu-import">
{t['com.affine.workspace.cloud.join']()}
</MenuItem>
<MenuItem icon={<ImportIcon />} data-testid="editor-option-menu-import">
{t['com.affine.workspace.cloud.account.settings']()}
</MenuItem>
<Divider size="thinner" dividerColor="var(--affine-border-color)" />
<MenuItem icon={<ImportIcon />} data-testid="editor-option-menu-import">
{t['com.affine.workspace.cloud.account.logout']()}
</MenuItem>
</div>
);
};
const CloudWorkSpaceList = ({
disabled,
workspaces,
onClickWorkspace,
onClickWorkspaceSetting,
currentWorkspaceId,
onMoveWorkspace,
}: WorkspaceModalProps) => {
const t = useAFFiNEI18N();
return (
<>
<StyledModalHeader>
<StyledModalHeaderLeft>
<StyledModalTitle>
{t['com.affine.workspace.cloud.sync']()}
</StyledModalTitle>
</StyledModalHeaderLeft>
<StyledOperationWrapper>
<Menu
placement="bottom-end"
trigger={['click']}
content={<AccountMenu />}
zIndex={1000}
>
<IconButton
data-testid="previous-image-button"
icon={<MoreHorizontalIcon />}
type="plain"
/>
</Menu>
</StyledOperationWrapper>
</StyledModalHeader>
<StyledModalContent>
<WorkspaceList
disabled={disabled}
items={
workspaces.filter(
({ flavour }) => flavour !== WorkspaceFlavour.PUBLIC
) as (AffineCloudWorkspace | LocalWorkspace)[]
}
currentWorkspaceId={currentWorkspaceId}
onClick={onClickWorkspace}
onSettingClick={onClickWorkspaceSetting}
onDragEnd={useCallback(
(event: DragEndEvent) => {
const { active, over } = event;
if (active.id !== over?.id) {
onMoveWorkspace(active.id as string, over?.id as string);
}
},
[onMoveWorkspace]
)}
/>
<Divider size="thinner" dividerColor="var(--affine-border-color)" />
</StyledModalContent>
</>
);
};
export const WorkspaceListModal = ({
disabled,
open,
onClose,
workspaces,
onClickWorkspace,
onClickWorkspaceSetting,
onNewWorkspace,
onAddWorkspace,
currentWorkspaceId,
onMoveWorkspace,
}: WorkspaceModalProps) => {
const t = useAFFiNEI18N();
const setOpen = useSetAtom(openDisableCloudAlertModalAtom);
// TODO: AFFiNE Cloud support
const isLoggedIn = false;
const anchorEl = document.getElementById('current-workspace');
return (
<Popover
sx={{
color: 'success.main',
zIndex: 999,
'& .MuiPopover-paper': {
borderRadius: '8px',
display: 'flex',
flexDirection: 'column',
boxShadow: 'var(--affine-shadow-2)',
backgroundColor: 'var(--affine-background-overlay-panel-color)',
},
maxHeight: '90vh',
}}
open={open}
anchorEl={anchorEl}
onClose={onClose}
>
<StyledModalHeaderContent>
<StyledSignInCardPill>
<MenuItem
style={{
height: 'auto',
padding: '8px 12px',
}}
onClick={async () => {
if (!runtimeConfig.enableCloud) {
setOpen(true);
}
}}
data-testid="cloud-signin-button"
>
<StyledCreateWorkspaceCardPillContent>
<StyledCreateWorkspaceCardPillIcon>
<CloudWorkspaceIcon />
</StyledCreateWorkspaceCardPillIcon>
<StyledSignInCardPillTextCotainer>
<StyledSignInCardPillTextPrimary>
{t['com.affine.workspace.cloud.auth']()}
</StyledSignInCardPillTextPrimary>
<StyledSignInCardPillTextSecondary>
Sync with AFFiNE Cloud
</StyledSignInCardPillTextSecondary>
</StyledSignInCardPillTextCotainer>
</StyledCreateWorkspaceCardPillContent>
</MenuItem>
</StyledSignInCardPill>
<Divider size="thinner" dividerColor="var(--affine-border-color)" />
</StyledModalHeaderContent>
<StyledModalBody>
{isLoggedIn ? (
<CloudWorkSpaceList
disabled={disabled}
open={open}
onClose={onClose}
workspaces={workspaces}
onClickWorkspace={onClickWorkspace}
onClickWorkspaceSetting={onClickWorkspaceSetting}
onNewWorkspace={onNewWorkspace}
onAddWorkspace={onAddWorkspace}
currentWorkspaceId={currentWorkspaceId}
onMoveWorkspace={onMoveWorkspace}
/>
) : null}
<StyledModalHeader>
<StyledModalTitle>{t['Local Workspace']()}</StyledModalTitle>
</StyledModalHeader>
<StyledModalContent>
<WorkspaceList
disabled={disabled}
items={
workspaces.filter(
({ flavour }) => flavour !== WorkspaceFlavour.PUBLIC
) as (AffineCloudWorkspace | LocalWorkspace)[]
}
currentWorkspaceId={currentWorkspaceId}
onClick={onClickWorkspace}
onSettingClick={onClickWorkspaceSetting}
onDragEnd={useCallback(
(event: DragEndEvent) => {
const { active, over } = event;
if (active.id !== over?.id) {
onMoveWorkspace(active.id as string, over?.id as string);
}
},
[onMoveWorkspace]
)}
/>
</StyledModalContent>
{runtimeConfig.enableSQLiteProvider && environment.isDesktop ? (
<StyledImportWorkspaceCardPill>
<MenuItem
onClick={onAddWorkspace}
data-testid="add-workspace"
style={{
height: 'auto',
padding: '8px 12px',
}}
>
<StyledCreateWorkspaceCardPillContent>
<StyledCreateWorkspaceCardPillIcon>
<ImportIcon />
</StyledCreateWorkspaceCardPillIcon>
<div>
<p>{t['com.affine.workspace.local.import']()}</p>
</div>
</StyledCreateWorkspaceCardPillContent>
</MenuItem>
</StyledImportWorkspaceCardPill>
) : null}
</StyledModalBody>
<StyledModalFooterContent>
<StyledCreateWorkspaceCardPill>
<MenuItem
style={{
height: 'auto',
padding: '8px 12px',
}}
onClick={onNewWorkspace}
data-testid="new-workspace"
>
<StyledCreateWorkspaceCardPillContent>
<StyledCreateWorkspaceCardPillIcon>
<PlusIcon />
</StyledCreateWorkspaceCardPillIcon>
<div>
<p>{t['New Workspace']()}</p>
</div>
</StyledCreateWorkspaceCardPillContent>
</MenuItem>
</StyledCreateWorkspaceCardPill>
</StyledModalFooterContent>
</Popover>
);
};

View File

@@ -1,30 +0,0 @@
import { RadioButton, RadioButtonGroup } from '@affine/component';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { useAtom } from 'jotai';
import { allPageModeSelectAtom } from '../../../atoms';
export const WorkspaceModeFilterTab = () => {
const t = useAFFiNEI18N();
const [value, setMode] = useAtom(allPageModeSelectAtom);
const handleValueChange = (value: string) => {
if (value !== 'all' && value !== 'page' && value !== 'edgeless') {
throw new Error('Invalid value for page mode option');
}
setMode(value);
};
return (
<RadioButtonGroup
width={300}
defaultValue={value}
onValueChange={handleValueChange}
>
<RadioButton value="all" style={{ textTransform: 'capitalize' }}>
{t['all']()}
</RadioButton>
<RadioButton value="page">{t['Page']()}</RadioButton>
<RadioButton value="edgeless">{t['Edgeless']()}</RadioButton>
</RadioButtonGroup>
);
};

View File

@@ -1,59 +0,0 @@
import {
EditCollectionModel,
useCollectionManager,
} from '@affine/component/page-list';
import type { Collection } from '@affine/env/filter';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { PlusIcon } from '@blocksuite/icons';
import type { Workspace } from '@blocksuite/store';
import { uuidv4 } from '@blocksuite/store';
import { IconButton } from '@toeverything/components/button';
import { useCallback, useState } from 'react';
import { useGetPageInfoById } from '../../../../hooks/use-get-page-info';
type AddCollectionButtonProps = {
workspace: Workspace;
};
export const AddCollectionButton = ({
workspace,
}: AddCollectionButtonProps) => {
const getPageInfo = useGetPageInfoById(workspace);
const setting = useCollectionManager(workspace.id);
const t = useAFFiNEI18N();
const [show, showUpdateCollection] = useState(false);
const [defaultCollection, setDefaultCollection] = useState<Collection>();
const handleClick = useCallback(() => {
showUpdateCollection(true);
setDefaultCollection({
id: uuidv4(),
name: '',
pinned: true,
filterList: [],
workspaceId: workspace.id,
});
}, [showUpdateCollection, workspace.id]);
return (
<>
<IconButton
data-testid="slider-bar-add-collection-button"
onClick={handleClick}
size="small"
>
<PlusIcon />
</IconButton>
<EditCollectionModel
propertiesMeta={workspace.meta.properties}
getPageInfo={getPageInfo}
onConfirm={setting.saveCollection}
open={show}
onClose={() => showUpdateCollection(false)}
title={t['Save As New Collection']()}
init={defaultCollection}
/>
</>
);
};

View File

@@ -1,134 +0,0 @@
import {
CollectionList,
FilterList,
SaveCollectionButton,
useCollectionManager,
} from '@affine/component/page-list';
import type { Collection } from '@affine/env/filter';
import type { PropertiesMeta } from '@affine/env/filter';
import type {
WorkspaceFlavour,
WorkspaceHeaderProps,
} from '@affine/env/workspace';
import { WorkspaceSubPath } from '@affine/env/workspace';
import { useCallback } from 'react';
import { useGetPageInfoById } from '../hooks/use-get-page-info';
import { useWorkspace } from '../hooks/use-workspace';
import { BlockSuiteHeaderTitle } from './blocksuite/block-suite-header-title';
import { filterContainerStyle } from './filter-container.css';
import { Header } from './pure/header';
import { PluginHeader } from './pure/plugin-header';
import { WorkspaceModeFilterTab } from './pure/workspace-mode-filter-tab';
const FilterContainer = ({ workspaceId }: { workspaceId: string }) => {
const currentWorkspace = useWorkspace(workspaceId);
const setting = useCollectionManager(workspaceId);
const saveToCollection = useCallback(
async (collection: Collection) => {
await setting.saveCollection(collection);
setting.selectCollection(collection.id);
},
[setting]
);
const getPageInfoById = useGetPageInfoById(
currentWorkspace.blockSuiteWorkspace
);
if (!setting.isDefault || !setting.currentCollection.filterList.length) {
return null;
}
return (
<div className={filterContainerStyle}>
<div style={{ flex: 1 }}>
<FilterList
propertiesMeta={currentWorkspace.blockSuiteWorkspace.meta.properties}
value={setting.currentCollection.filterList}
onChange={filterList => {
return setting.updateCollection({
...setting.currentCollection,
filterList,
});
}}
/>
</div>
<div>
{setting.currentCollection.filterList.length > 0 ? (
<SaveCollectionButton
propertiesMeta={
currentWorkspace.blockSuiteWorkspace.meta
.properties as PropertiesMeta
}
getPageInfo={getPageInfoById}
onConfirm={saveToCollection}
filterList={setting.currentCollection.filterList}
workspaceId={workspaceId}
></SaveCollectionButton>
) : null}
</div>
</div>
);
};
export function WorkspaceHeader({
currentWorkspaceId,
currentEntry,
}: WorkspaceHeaderProps<WorkspaceFlavour>) {
const setting = useCollectionManager(currentWorkspaceId);
const currentWorkspace = useWorkspace(currentWorkspaceId);
const getPageInfoById = useGetPageInfoById(
currentWorkspace.blockSuiteWorkspace
);
// route in all page
if (
'subPath' in currentEntry &&
currentEntry.subPath === WorkspaceSubPath.ALL
) {
return (
<>
<Header
left={
<CollectionList
setting={setting}
getPageInfo={getPageInfoById}
propertiesMeta={
currentWorkspace.blockSuiteWorkspace.meta.properties
}
/>
}
center={<WorkspaceModeFilterTab />}
/>
{<FilterContainer workspaceId={currentWorkspaceId} />}
</>
);
}
// route in shared or trash
if (
'subPath' in currentEntry &&
(currentEntry.subPath === WorkspaceSubPath.SHARED ||
currentEntry.subPath === WorkspaceSubPath.TRASH)
) {
return <Header center={<WorkspaceModeFilterTab />} />;
}
// route in edit page
if ('pageId' in currentEntry) {
return (
<Header
center={
<BlockSuiteHeaderTitle
workspace={currentWorkspace}
pageId={currentEntry.pageId}
/>
}
right={<PluginHeader />}
/>
);
}
return null;
}

View File

@@ -1,262 +0,0 @@
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { useMemo } from 'react';
interface ShortcutMap {
[x: string]: string[];
}
export interface ShortcutsInfo {
title: string;
shortcuts: ShortcutMap;
}
export const useWinGeneralKeyboardShortcuts = (): ShortcutMap => {
const t = useAFFiNEI18N();
return useMemo(
() => ({
[t['Cancel']()]: ['ESC'],
[t['Quick Search']()]: ['Ctrl', 'K'],
[t['New Page']()]: ['Ctrl', 'N'],
// not implement yet
// [t['Append to Daily Note']()]: 'Ctrl + Alt + A',
[t['Expand/Collapse Sidebar']()]: ['Ctrl', '/'],
// not implement yet
// [t['Go Back']()]: 'Ctrl + [',
// [t['Go Forward']()]: 'Ctrl + ]',
}),
[t]
);
};
export const useMacGeneralKeyboardShortcuts = (): ShortcutMap => {
const t = useAFFiNEI18N();
return useMemo(
() => ({
[t['Cancel']()]: ['ESC'],
[t['Quick Search']()]: ['⌘', 'K'],
[t['New Page']()]: ['⌘', 'N'],
// not implement yet
// [t['Append to Daily Note']()]: '⌘ + ⌥ + A',
[t['Expand/Collapse Sidebar']()]: ['⌘', '/'],
// not implement yet
// [t['Go Back']()]: '⌘ + [',
// [t['Go Forward']()]: '⌘ + ]',
}),
[t]
);
};
export const useMacEdgelessKeyboardShortcuts = (): ShortcutMap => {
const t = useAFFiNEI18N();
return useMemo(
() => ({
[t['Select All']()]: ['⌘', 'A'],
[t['Undo']()]: ['⌘', 'Z'],
[t['Redo']()]: ['⌘', '⇧', 'Z'],
[t['Zoom in']()]: ['⌘', '+'],
[t['Zoom out']()]: ['⌘', '-'],
[t['Zoom to 100%']()]: ['⌘', '0'],
[t['Zoom to fit']()]: ['⌘', '1'],
[t['Select']()]: ['V'],
[t['Text']()]: ['T'],
[t['Shape']()]: ['S'],
[t['Image']()]: ['I'],
[t['Straight Connector']()]: ['L'],
[t['Elbowed Connector']()]: ['X'],
// not implement yet
// [t['Curve Connector']()]: 'C',
[t['Pen']()]: ['P'],
[t['Hand']()]: ['H'],
[t['Note']()]: ['N'],
// not implement yet
// [t['Group']()]: '⌘ + G',
// [t['Ungroup']()]: '⌘ + ⇧ + G',
}),
[t]
);
};
export const useWinEdgelessKeyboardShortcuts = (): ShortcutMap => {
const t = useAFFiNEI18N();
return useMemo(
() => ({
[t['Select All']()]: ['Ctrl', 'A'],
[t['Undo']()]: ['Ctrl', 'Z'],
[t['Redo']()]: ['Ctrl', 'Y/Ctrl', 'Shift', 'Z'],
[t['Zoom in']()]: ['Ctrl', '+'],
[t['Zoom out']()]: ['Ctrl', '-'],
[t['Zoom to 100%']()]: ['Ctrl', '0'],
[t['Zoom to fit']()]: ['Ctrl', '1'],
[t['Select']()]: ['V'],
[t['Text']()]: ['T'],
[t['Shape']()]: ['S'],
[t['Image']()]: ['I'],
[t['Straight Connector']()]: ['L'],
[t['Elbowed Connector']()]: ['X'],
// not implement yet
// [t['Curve Connector']()]: 'C',
[t['Pen']()]: ['P'],
[t['Hand']()]: ['H'],
[t['Note']()]: ['N'],
[t['Switch']()]: ['Alt ', ''],
// not implement yet
// [t['Group']()]: 'Ctrl + G',
// [t['Ungroup']()]: 'Ctrl + Shift + G',
}),
[t]
);
};
export const useMacPageKeyboardShortcuts = (): ShortcutMap => {
const t = useAFFiNEI18N();
return useMemo(
() => ({
[t['Undo']()]: ['⌘', 'Z'],
[t['Redo']()]: ['⌘', '⇧', 'Z'],
[t['Bold']()]: ['⌘', 'B'],
[t['Italic']()]: ['⌘', 'I'],
[t['Underline']()]: ['⌘', 'U'],
[t['Strikethrough']()]: ['⌘', '⇧', 'S'],
[t['Inline code']()]: ['⌘', 'E'],
[t['Code block']()]: ['⌘', '⌥', 'C'],
[t['Link']()]: ['⌘', 'K'],
[t['Quick search']()]: ['⌘', 'K'],
[t['Body text']()]: ['⌘', '⌥', '0'],
[t['Heading']({ number: '1' })]: ['⌘', '⌥', '1'],
[t['Heading']({ number: '2' })]: ['⌘', '⌥', '2'],
[t['Heading']({ number: '3' })]: ['⌘', '⌥', '3'],
[t['Heading']({ number: '4' })]: ['⌘', '⌥', '4'],
[t['Heading']({ number: '5' })]: ['⌘', '⌥', '5'],
[t['Heading']({ number: '6' })]: ['⌘', '⌥', '6'],
[t['Increase indent']()]: ['Tab'],
[t['Reduce indent']()]: ['⇧', 'Tab'],
[t['Group as Database']()]: ['⌘', 'G'],
[t['Switch']()]: ['⌥', 'S'],
// not implement yet
// [t['Move Up']()]: '⌘ + ⌥ + ↑',
// [t['Move Down']()]: '⌘ + ⌥ + ↓',
}),
[t]
);
};
export const useMacMarkdownShortcuts = (): ShortcutMap => {
const t = useAFFiNEI18N();
return useMemo(
() => ({
[t['Bold']()]: ['**Text**'],
[t['Italic']()]: ['*Text*'],
[t['Underline']()]: ['~Text~'],
[t['Strikethrough']()]: ['~~Text~~'],
[t['Divider']()]: ['***'],
[t['Inline code']()]: ['`Text` '],
[t['Code block']()]: ['``` Space'],
[t['Heading']({ number: '1' })]: ['# Text'],
[t['Heading']({ number: '2' })]: ['## Text'],
[t['Heading']({ number: '3' })]: ['### Text'],
[t['Heading']({ number: '4' })]: ['#### Text'],
[t['Heading']({ number: '5' })]: ['##### Text'],
[t['Heading']({ number: '6' })]: ['###### Text'],
}),
[t]
);
};
export const useWinPageKeyboardShortcuts = (): ShortcutMap => {
const t = useAFFiNEI18N();
return useMemo(
() => ({
[t['Undo']()]: ['Ctrl', 'Z'],
[t['Redo']()]: ['Ctrl', 'Y'],
[t['Bold']()]: ['Ctrl', 'B'],
[t['Italic']()]: ['Ctrl', 'I'],
[t['Underline']()]: ['Ctrl', 'U'],
[t['Strikethrough']()]: ['Ctrl', 'Shift', 'S'],
[t['Inline code']()]: [' Ctrl', 'E'],
[t['Code block']()]: ['Ctrl', 'Alt', 'C'],
[t['Link']()]: ['Ctr', 'K'],
[t['Quick search']()]: ['Ctrl', 'K'],
[t['Body text']()]: ['Ctrl', 'Shift', '0'],
[t['Heading']({ number: '1' })]: ['Ctrl', 'Shift', '1'],
[t['Heading']({ number: '2' })]: ['Ctrl', 'Shift', '2'],
[t['Heading']({ number: '3' })]: ['Ctrl', 'Shift', '3'],
[t['Heading']({ number: '4' })]: ['Ctrl', 'Shift', '4'],
[t['Heading']({ number: '5' })]: ['Ctrl', 'Shift', '5'],
[t['Heading']({ number: '6' })]: ['Ctrl', 'Shift', '6'],
[t['Increase indent']()]: ['Tab'],
[t['Reduce indent']()]: ['Shift+Tab'],
[t['Group as Database']()]: ['Ctrl + G'],
['Switch']: ['Alt + S'],
// not implement yet
// [t['Move Up']()]: 'Ctrl + Alt + ↑',
// [t['Move Down']()]: 'Ctrl + Alt + ↓',
}),
[t]
);
};
export const useWinMarkdownShortcuts = (): ShortcutMap => {
const t = useAFFiNEI18N();
return useMemo(
() => ({
[t['Bold']()]: ['**Text** '],
[t['Italic']()]: ['*Text* '],
[t['Underline']()]: ['~Text~ '],
[t['Strikethrough']()]: ['~~Text~~ '],
[t['Divider']()]: ['***'],
[t['Inline code']()]: ['`Text` '],
[t['Code block']()]: ['``` Text'],
[t['Heading']({ number: '1' })]: ['# Text'],
[t['Heading']({ number: '2' })]: ['## Text'],
[t['Heading']({ number: '3' })]: ['### Text'],
[t['Heading']({ number: '4' })]: ['#### Text'],
[t['Heading']({ number: '5' })]: ['##### Text'],
[t['Heading']({ number: '6' })]: ['###### Text'],
}),
[t]
);
};
export const useMarkdownShortcuts = (): ShortcutsInfo => {
const t = useAFFiNEI18N();
const macMarkdownShortcuts = useMacMarkdownShortcuts();
const winMarkdownShortcuts = useWinMarkdownShortcuts();
const isMac = environment.isBrowser && environment.isMacOs;
return {
title: t['Markdown Syntax'](),
shortcuts: isMac ? macMarkdownShortcuts : winMarkdownShortcuts,
};
};
export const usePageShortcuts = (): ShortcutsInfo => {
const t = useAFFiNEI18N();
const macPageShortcuts = useMacPageKeyboardShortcuts();
const winPageShortcuts = useWinPageKeyboardShortcuts();
const isMac = environment.isBrowser && environment.isMacOs;
return {
title: t['Page'](),
shortcuts: isMac ? macPageShortcuts : winPageShortcuts,
};
};
export const useEdgelessShortcuts = (): ShortcutsInfo => {
const t = useAFFiNEI18N();
const macEdgelessShortcuts = useMacEdgelessKeyboardShortcuts();
const winEdgelessShortcuts = useWinEdgelessKeyboardShortcuts();
const isMac = environment.isBrowser && environment.isMacOs;
return {
title: t['Edgeless'](),
shortcuts: isMac ? macEdgelessShortcuts : winEdgelessShortcuts,
};
};
export const useGeneralShortcuts = (): ShortcutsInfo => {
const t = useAFFiNEI18N();
const macGeneralShortcuts = useMacGeneralKeyboardShortcuts();
const winGeneralShortcuts = useWinGeneralKeyboardShortcuts();
const isMac = environment.isBrowser && environment.isMacOs;
return {
title: t['General'](),
shortcuts: isMac ? macGeneralShortcuts : winGeneralShortcuts,
};
};

View File

@@ -1,4 +0,0 @@
// todo
export function useLocationTitle(): string {
return 'AFFiNE';
}

View File

@@ -1,90 +0,0 @@
import type { WorkspaceSubPath } from '@affine/env/workspace';
import { useCallback } from 'react';
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
import { useLocation, useNavigate } from 'react-router-dom';
export enum RouteLogic {
REPLACE = 'replace',
PUSH = 'push',
}
export function useNavigateHelper() {
const location = useLocation();
const navigate = useNavigate();
const jumpToPage = useCallback(
(
workspaceId: string,
pageId: string,
logic: RouteLogic = RouteLogic.PUSH
) => {
return navigate(`/workspace/${workspaceId}/${pageId}`, {
replace: logic === RouteLogic.REPLACE,
});
},
[navigate]
);
const jumpToPublicWorkspacePage = useCallback(
(
workspaceId: string,
pageId: string,
logic: RouteLogic = RouteLogic.PUSH
) => {
return navigate(`/public-workspace/${workspaceId}/${pageId}`, {
replace: logic === RouteLogic.REPLACE,
});
},
[navigate]
);
const jumpToSubPath = useCallback(
(
workspaceId: string,
subPath: WorkspaceSubPath,
logic: RouteLogic = RouteLogic.PUSH
) => {
return navigate(`/workspace/${workspaceId}/${subPath}`, {
replace: logic === RouteLogic.REPLACE,
});
},
[navigate]
);
const openPage = useCallback(
(workspaceId: string, pageId: string) => {
const isPublicWorkspace =
location.pathname.indexOf('/public-workspace') === 0;
if (isPublicWorkspace) {
return jumpToPublicWorkspacePage(workspaceId, pageId);
} else {
return jumpToPage(workspaceId, pageId);
}
},
[jumpToPage, jumpToPublicWorkspacePage, location.pathname]
);
const jumpToIndex = useCallback(
(logic: RouteLogic = RouteLogic.PUSH) => {
return navigate('/', {
replace: logic === RouteLogic.REPLACE,
});
},
[navigate]
);
const jumpTo404 = useCallback(
(logic: RouteLogic = RouteLogic.PUSH) => {
return navigate('/404', {
replace: logic === RouteLogic.REPLACE,
});
},
[navigate]
);
return {
jumpToPage,
jumpToPublicWorkspacePage,
jumpToSubPath,
jumpToIndex,
jumpTo404,
openPage,
};
}

View File

@@ -1,29 +0,0 @@
import { WorkspaceFallback } from '@affine/component/workspace';
import { assertExists } from '@blocksuite/global/utils';
import { getCurrentStore } from '@toeverything/infra/atom';
import { StrictMode, Suspense } from 'react';
import { createRoot } from 'react-dom/client';
import { bootstrapPluginSystem } from './bootstrap/register-plugins';
async function main() {
const { setup } = await import('./bootstrap/setup');
const rootStore = getCurrentStore();
await setup(rootStore);
bootstrapPluginSystem(rootStore).catch(err => {
console.error('Failed to bootstrap plugin system', err);
});
const { App } = await import('./app');
const root = document.getElementById('app');
assertExists(root);
createRoot(root).render(
<StrictMode>
<Suspense fallback={<WorkspaceFallback key="AppLoading" />}>
<App />
</Suspense>
</StrictMode>
);
}
await main();

View File

@@ -1,59 +0,0 @@
import { DebugLogger } from '@affine/debug';
import { DEFAULT_HELLO_WORLD_PAGE_ID_SUFFIX } from '@affine/env/constant';
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
import { getWorkspace } from '@toeverything/infra/__internal__/workspace';
import { getCurrentStore } from '@toeverything/infra/atom';
import { lazy } from 'react';
import type { LoaderFunction } from 'react-router-dom';
import { redirect } from 'react-router-dom';
const AllWorkspaceModals = lazy(() =>
import('../providers/modal-provider').then(({ AllWorkspaceModals }) => ({
default: AllWorkspaceModals,
}))
);
const logger = new DebugLogger('index-page');
export const loader: LoaderFunction = async () => {
const rootStore = getCurrentStore();
const meta = await rootStore.get(rootWorkspacesMetadataAtom);
const lastId = localStorage.getItem('last_workspace_id');
const lastPageId = localStorage.getItem('last_page_id');
const target = (lastId && meta.find(({ id }) => id === lastId)) || meta.at(0);
if (target) {
const targetWorkspace = getWorkspace(target.id);
const nonTrashPages = targetWorkspace.meta.pageMetas.filter(
({ trash }) => !trash
);
const helloWorldPage = nonTrashPages.find(
({ id, jumpOnce }) =>
id.endsWith(DEFAULT_HELLO_WORLD_PAGE_ID_SUFFIX) && jumpOnce
)?.id;
const pageId =
nonTrashPages.find(({ id }) => id === lastPageId)?.id ??
nonTrashPages.at(0)?.id;
if (helloWorldPage) {
logger.debug(
'Found target workspace. Jump to hello world page',
helloWorldPage
);
return redirect(`/workspace/${targetWorkspace.id}/${helloWorldPage}`);
} else if (pageId) {
logger.debug('Found target workspace. Jump to page', pageId);
return redirect(`/workspace/${targetWorkspace.id}/${pageId}`);
} else {
logger.debug('Found target workspace. Jump to all page');
return redirect(`/workspace/${targetWorkspace.id}/all`);
}
}
return null;
};
export const Component = () => {
return (
<>
<AllWorkspaceModals />
</>
);
};

View File

@@ -1,70 +0,0 @@
import { useCollectionManager } from '@affine/component/page-list';
import { DEFAULT_HELLO_WORLD_PAGE_ID_SUFFIX } from '@affine/env/constant';
import { WorkspaceSubPath } from '@affine/env/workspace';
import { assertExists } from '@blocksuite/global/utils';
import { getActiveBlockSuiteWorkspaceAtom } from '@toeverything/infra/__internal__/workspace';
import { getCurrentStore } from '@toeverything/infra/atom';
import { useCallback } from 'react';
import type { LoaderFunction } from 'react-router-dom';
import { redirect } from 'react-router-dom';
import { getUIAdapter } from '../../adapters/workspace';
import { useCurrentWorkspace } from '../../hooks/current/use-current-workspace';
import { useNavigateHelper } from '../../hooks/use-navigate-helper';
export const loader: LoaderFunction = async args => {
const rootStore = getCurrentStore();
const workspaceId = args.params.workspaceId;
assertExists(workspaceId);
const workspaceAtom = getActiveBlockSuiteWorkspaceAtom(workspaceId);
const workspace = await rootStore.get(workspaceAtom);
for (const pageId of workspace.pages.keys()) {
if (pageId.endsWith(DEFAULT_HELLO_WORLD_PAGE_ID_SUFFIX)) {
const page = workspace.getPage(pageId);
if (page && page.meta.jumpOnce) {
workspace.meta.setPageMeta(page.id, {
jumpOnce: false,
});
return redirect(`/workspace/${workspace.id}/${page.id}`);
}
}
}
return null;
};
export const AllPage = () => {
const { jumpToPage } = useNavigateHelper();
const [currentWorkspace] = useCurrentWorkspace();
const setting = useCollectionManager(currentWorkspace.id);
const onClickPage = useCallback(
(pageId: string, newTab?: boolean) => {
assertExists(currentWorkspace);
if (newTab) {
window.open(`/workspace/${currentWorkspace?.id}/${pageId}`, '_blank');
} else {
jumpToPage(currentWorkspace.id, pageId);
}
},
[currentWorkspace, jumpToPage]
);
const { PageList, Header } = getUIAdapter(currentWorkspace.flavour);
return (
<>
<Header
currentWorkspaceId={currentWorkspace.id}
currentEntry={{
subPath: WorkspaceSubPath.ALL,
}}
/>
<PageList
collection={setting.currentCollection}
onOpenPage={onClickPage}
blockSuiteWorkspace={currentWorkspace.blockSuiteWorkspace}
/>
</>
);
};
export const Component = () => {
return <AllPage />;
};

View File

@@ -1,127 +0,0 @@
import { PageDetailSkeleton } from '@affine/component/page-detail-skeleton';
import {
createTagFilter,
useCollectionManager,
} from '@affine/component/page-list';
import { WorkspaceSubPath } from '@affine/env/workspace';
import type { EditorContainer } from '@blocksuite/editor';
import { assertExists } from '@blocksuite/global/utils';
import type { Page } from '@blocksuite/store';
import {
contentLayoutAtom,
currentPageIdAtom,
currentWorkspaceAtom,
currentWorkspaceIdAtom,
getCurrentStore,
} from '@toeverything/infra/atom';
import { useAtomValue, useSetAtom } from 'jotai';
import { type ReactElement, useCallback } from 'react';
import type { LoaderFunction } from 'react-router-dom';
import { redirect } from 'react-router-dom';
import { getUIAdapter } from '../../adapters/workspace';
import { setPageModeAtom } from '../../atoms';
import { currentModeAtom } from '../../atoms/mode';
import { useCurrentWorkspace } from '../../hooks/current/use-current-workspace';
import { useNavigateHelper } from '../../hooks/use-navigate-helper';
const DetailPageImpl = (): ReactElement => {
const { openPage, jumpToSubPath } = useNavigateHelper();
const currentPageId = useAtomValue(currentPageIdAtom);
const [currentWorkspace] = useCurrentWorkspace();
assertExists(currentWorkspace);
assertExists(currentPageId);
const blockSuiteWorkspace = currentWorkspace.blockSuiteWorkspace;
const collectionManager = useCollectionManager(currentWorkspace.id);
const mode = useAtomValue(currentModeAtom);
const setPageMode = useSetAtom(setPageModeAtom);
const onLoad = useCallback(
(_: Page, editor: EditorContainer) => {
setPageMode(currentPageId, mode);
const dispose = editor.slots.pageLinkClicked.on(({ pageId }) => {
return openPage(blockSuiteWorkspace.id, pageId);
});
const disposeTagClick = editor.slots.tagClicked.on(async ({ tagId }) => {
jumpToSubPath(currentWorkspace.id, WorkspaceSubPath.ALL);
collectionManager.backToAll();
collectionManager.setTemporaryFilter([createTagFilter(tagId)]);
});
return () => {
dispose.dispose();
disposeTagClick.dispose();
};
},
[
blockSuiteWorkspace.id,
collectionManager,
currentPageId,
currentWorkspace.id,
jumpToSubPath,
mode,
openPage,
setPageMode,
]
);
const { PageDetail, Header } = getUIAdapter(currentWorkspace.flavour);
return (
<>
<Header
currentWorkspaceId={currentWorkspace.id}
currentEntry={{
pageId: currentPageId,
}}
/>
<PageDetail
currentWorkspaceId={currentWorkspace.id}
currentPageId={currentPageId}
onLoadEditor={onLoad}
/>
</>
);
};
export const DetailPage = (): ReactElement => {
const [currentWorkspace] = useCurrentWorkspace();
const currentPageId = useAtomValue(currentPageIdAtom);
const page = currentPageId
? currentWorkspace.blockSuiteWorkspace.getPage(currentPageId)
: null;
if (!currentPageId || !page) {
return <PageDetailSkeleton key="current-page-is-null" />;
}
return <DetailPageImpl />;
};
export const loader: LoaderFunction = async args => {
const rootStore = getCurrentStore();
rootStore.set(contentLayoutAtom, 'editor');
if (args.params.workspaceId) {
localStorage.setItem('last_workspace_id', args.params.workspaceId);
rootStore.set(currentWorkspaceIdAtom, args.params.workspaceId);
}
if (args.params.pageId) {
const pageId = args.params.pageId;
localStorage.setItem('last_page_id', pageId);
const currentWorkspace = await rootStore.get(currentWorkspaceAtom);
const page = currentWorkspace.getPage(pageId);
if (!page) {
return redirect('/404');
}
if (page.meta.jumpOnce) {
currentWorkspace.setPageMeta(page.id, {
jumpOnce: false,
});
}
rootStore.set(currentPageIdAtom, pageId);
} else {
return redirect('/404');
}
return null;
};
export const Component = () => {
return <DetailPage />;
};

View File

@@ -1,34 +0,0 @@
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
import {
currentPageIdAtom,
currentWorkspaceIdAtom,
getCurrentStore,
} from '@toeverything/infra/atom';
import type { ReactElement } from 'react';
import { type LoaderFunction, Outlet, redirect } from 'react-router-dom';
import { WorkspaceLayout } from '../../layouts/workspace-layout';
export const loader: LoaderFunction = async args => {
const rootStore = getCurrentStore();
const meta = await rootStore.get(rootWorkspacesMetadataAtom);
if (!meta.some(({ id }) => id === args.params.workspaceId)) {
return redirect('/404');
}
if (args.params.workspaceId) {
localStorage.setItem('last_workspace_id', args.params.workspaceId);
rootStore.set(currentWorkspaceIdAtom, args.params.workspaceId);
}
if (!args.params.pageId) {
rootStore.set(currentPageIdAtom, null);
}
return null;
};
export const Component = (): ReactElement => {
return (
<WorkspaceLayout>
<Outlet />
</WorkspaceLayout>
);
};

View File

@@ -1,47 +0,0 @@
import { WorkspaceSubPath } from '@affine/env/workspace';
import { assertExists } from '@blocksuite/global/utils';
import { useCallback } from 'react';
import { getUIAdapter } from '../../adapters/workspace';
import { BlockSuitePageList } from '../../components/blocksuite/block-suite-page-list';
import { useCurrentWorkspace } from '../../hooks/current/use-current-workspace';
import { useNavigateHelper } from '../../hooks/use-navigate-helper';
export const TrashPage = () => {
const { jumpToPage } = useNavigateHelper();
const [currentWorkspace] = useCurrentWorkspace();
const onClickPage = useCallback(
(pageId: string, newTab?: boolean) => {
assertExists(currentWorkspace);
if (newTab) {
window.open(`/workspace/${currentWorkspace?.id}/${pageId}`, '_blank');
} else {
jumpToPage(currentWorkspace.id, pageId);
}
},
[currentWorkspace, jumpToPage]
);
// todo(himself65): refactor to plugin
const blockSuiteWorkspace = currentWorkspace.blockSuiteWorkspace;
assertExists(blockSuiteWorkspace);
const { Header } = getUIAdapter(currentWorkspace.flavour);
return (
<>
<Header
currentWorkspaceId={currentWorkspace.id}
currentEntry={{
subPath: WorkspaceSubPath.TRASH,
}}
/>
<BlockSuitePageList
blockSuiteWorkspace={blockSuiteWorkspace}
onOpenPage={onClickPage}
listType="trash"
/>
</>
);
};
export const Component = () => {
return <TrashPage />;
};

View File

@@ -1,11 +0,0 @@
if (Intl.Segmenter === undefined) {
await import('intl-segmenter-polyfill-rs').then(({ Segmenter }) => {
Object.defineProperty(Intl, 'Segmenter', {
value: Segmenter,
configurable: true,
writable: true,
});
});
}
export {};

View File

@@ -1,19 +0,0 @@
import 'ses';
if (!process.env.COVERAGE) {
lockdown({
evalTaming: 'unsafeEval',
overrideTaming: 'severe',
consoleTaming: 'unsafe',
errorTaming: 'unsafe',
errorTrapping: 'platform',
unhandledRejectionTrapping: 'report',
});
console.log('SES lockdown complete');
} else {
Object.defineProperty(globalThis, 'harden', {
value: (x: any) => Object.freeze(x),
writable: false,
});
}

View File

@@ -1,41 +0,0 @@
import type { RouteObject } from 'react-router-dom';
import { createBrowserRouter } from 'react-router-dom';
export const routes = [
{
path: '/',
lazy: () => import('./pages/index'),
},
{
path: '/workspace/:workspaceId',
lazy: () => import('./pages/workspace/index'),
children: [
{
path: 'all',
lazy: () => import('./pages/workspace/all-page'),
},
{
path: 'trash',
lazy: () => import('./pages/workspace/trash-page'),
},
{
path: ':pageId',
lazy: () => import('./pages/workspace/detail-page'),
},
],
},
{
path: '/404',
lazy: () => import('./pages/404'),
},
{
path: '*',
lazy: () => import('./pages/404'),
},
] satisfies [RouteObject, ...RouteObject[]];
export const router = createBrowserRouter(routes, {
future: {
v7_normalizeFormMethod: true,
},
});

View File

@@ -1,47 +0,0 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "lib",
"typeRoots": ["../../node_modules", "../../node_modules/@types"],
"types": ["webpack-env", "ses", "affine__env"]
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.json"],
"exclude": ["node_modules"],
"references": [
{
"path": "../../packages/component"
},
{
"path": "../../packages/debug"
},
{
"path": "../../packages/env"
},
{
"path": "../../packages/graphql"
},
{
"path": "../../packages/hooks"
},
{
"path": "../../packages/i18n"
},
{
"path": "../../packages/jotai"
},
{
"path": "../../packages/workspace"
},
{
"path": "../../plugins/copilot"
},
{
"path": "./tsconfig.node.json"
},
{
"path": "./tsconfig.server.json"
}
]
}

View File

@@ -1,20 +0,0 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"composite": true,
"target": "ESNext",
"module": "ESNext",
"verbatimModuleSyntax": false,
"resolveJsonModule": true,
"moduleResolution": "Node16",
"allowSyntheticDefaultImports": true,
"outDir": "./lib/.webpack",
"rootDir": "."
},
"include": [".webpack/*.ts"],
"references": [
{
"path": "../../packages/cli"
}
]
}

View File

@@ -4,7 +4,7 @@ export default defineRouter(
async id => {
switch (id) {
case 'index': {
const { default: AppCreator } = await import('./app.js');
const { default: AppCreator } = await import('./src/app.js');
return AppCreator(id);
}
default:

View File

@@ -29,7 +29,7 @@
<div class="spinner"></div>
</div>
<!--/placeholder1-->
<script src="./index.tsx" defer type="module"></script>
<script src="./src/index.tsx" defer type="module"></script>
<!--placeholder2-->
<!--/placeholder2-->
</body>

View File

@@ -1,6 +1,6 @@
{
"name": "@affine/docs",
"version": "0.8.0",
"version": "0.7.3",
"type": "module",
"private": true,
"scripts": {
@@ -10,26 +10,26 @@
},
"dependencies": {
"@affine/component": "workspace:*",
"@blocksuite/block-std": "0.0.0-20230822230555-98129627-nightly",
"@blocksuite/blocks": "0.0.0-20230822230555-98129627-nightly",
"@blocksuite/editor": "0.0.0-20230822230555-98129627-nightly",
"@blocksuite/global": "0.0.0-20230822230555-98129627-nightly",
"@blocksuite/lit": "0.0.0-20230822230555-98129627-nightly",
"@blocksuite/store": "0.0.0-20230822230555-98129627-nightly",
"@blocksuite/block-std": "0.0.0-20230717055529-79180930-nightly",
"@blocksuite/blocks": "0.0.0-20230717055529-79180930-nightly",
"@blocksuite/editor": "0.0.0-20230717055529-79180930-nightly",
"@blocksuite/global": "0.0.0-20230717055529-79180930-nightly",
"@blocksuite/lit": "0.0.0-20230717055529-79180930-nightly",
"@blocksuite/store": "0.0.0-20230717055529-79180930-nightly",
"express": "^4.18.2",
"jotai": "^2.3.1",
"react": "18.3.0-canary-7118f5dd7-20230705",
"react-dom": "18.3.0-canary-7118f5dd7-20230705",
"react-server-dom-webpack": "18.3.0-canary-7118f5dd7-20230705",
"waku": "0.14.0"
"jotai": "^2.2.2",
"react": "18.3.0-canary-1fdacbefd-20230630",
"react-dom": "18.3.0-canary-1fdacbefd-20230630",
"react-server-dom-webpack": "18.3.0-canary-1fdacbefd-20230630",
"waku": "0.12.1"
},
"devDependencies": {
"@types/react": "^18.2.20",
"@types/react-dom": "^18.2.7",
"@types/react": "^18.2.14",
"@types/react-dom": "^18.2.6",
"@vanilla-extract/css": "^1.12.0",
"@vanilla-extract/vite-plugin": "^3.8.2",
"autoprefixer": "^10.4.15",
"tailwindcss": "^3.3.3",
"autoprefixer": "^10.4.14",
"tailwindcss": "^3.3.2",
"typescript": "^5.1.6"
}
}

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