Compare commits
193 Commits
v0.7.0-can
...
v0.8.0-can
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f33fb98912 | ||
|
|
4d254f3967 | ||
|
|
8833584756 | ||
|
|
582059f6d6 | ||
|
|
1484818974 | ||
|
|
1d41687786 | ||
|
|
401fb48b86 | ||
|
|
91619b87db | ||
|
|
844e73ca29 | ||
|
|
ee91964998 | ||
|
|
1eaf228a30 | ||
|
|
9639143df4 | ||
|
|
e9f4912665 | ||
|
|
ce21ea78eb | ||
|
|
13ac9d18af | ||
|
|
1a8f849693 | ||
|
|
f0cbbc3a84 | ||
|
|
4834f99da9 | ||
|
|
394469a807 | ||
|
|
b7be91e04d | ||
|
|
278ffa3372 | ||
|
|
601cbd83ff | ||
|
|
43b35a77bb | ||
|
|
f06efd4d02 | ||
|
|
5887071319 | ||
|
|
fa111db91b | ||
|
|
b6cdabff36 | ||
|
|
af314dabfb | ||
|
|
11f6273a3a | ||
|
|
4b51eb7e06 | ||
|
|
671129bc32 | ||
|
|
358c3a5bb2 | ||
|
|
7c4b8866d3 | ||
|
|
dafd5619e6 | ||
|
|
05144abd6a | ||
|
|
629a3d11c6 | ||
|
|
bbd49252cf | ||
|
|
7c3a7513e6 | ||
|
|
e180398637 | ||
|
|
cbd4420fe9 | ||
|
|
f7408dede9 | ||
|
|
c324ccc12a | ||
|
|
84bbc2d7ce | ||
|
|
e22d6924cd | ||
|
|
4472a2a0b0 | ||
|
|
4d6e28c725 | ||
|
|
881424d03a | ||
|
|
5842619206 | ||
|
|
73272e266b | ||
|
|
4e84b9a121 | ||
|
|
7d16a8348c | ||
|
|
4d1692db7b | ||
|
|
3009d729fa | ||
|
|
ceeabfeb00 | ||
|
|
bfa5180c1a | ||
|
|
6efe29f7ef | ||
|
|
7826ecfa58 | ||
|
|
d615ac23ea | ||
|
|
4a0089dbfc | ||
|
|
d101db2a39 | ||
|
|
b147624f1c | ||
|
|
ec05bd3f53 | ||
|
|
952283fe16 | ||
|
|
47c0b1f680 | ||
|
|
cdef5f5826 | ||
|
|
6d01495bc7 | ||
|
|
d9f323874d | ||
|
|
45ffad45b0 | ||
|
|
dd4612bcef | ||
|
|
a0ce75b1ae | ||
|
|
031eb35b86 | ||
|
|
5bc923d58e | ||
|
|
a74a549b8c | ||
|
|
50df137c7f | ||
|
|
7bf77b566d | ||
|
|
48350d7654 | ||
|
|
72c4aa0078 | ||
|
|
c3c40225d6 | ||
|
|
b4acbb90b7 | ||
|
|
d1b2018700 | ||
|
|
d087bad9de | ||
|
|
2da6c4a0ec | ||
|
|
ede6637a14 | ||
|
|
36bd0b02d0 | ||
|
|
3a92c4f798 | ||
|
|
97de0ef5b4 | ||
|
|
bbf5f0efe0 | ||
|
|
ea76936508 | ||
|
|
f076cb0ead | ||
|
|
0f230253a8 | ||
|
|
13d2dde501 | ||
|
|
65fc0ed59c | ||
|
|
7ec4b8fb8c | ||
|
|
6415f0093b | ||
|
|
5795020403 | ||
|
|
f8e49ee3be | ||
|
|
1012807c65 | ||
|
|
1c7c27712e | ||
|
|
7f28c78d8c | ||
|
|
4bb874756d | ||
|
|
0882d47dc9 | ||
|
|
0c16eb1189 | ||
|
|
f2ac4c7eda | ||
|
|
0d531782ca | ||
|
|
47ff376195 | ||
|
|
33cc9d25a1 | ||
|
|
58ceeb9c5f | ||
|
|
3c00b69805 | ||
|
|
2678ca9330 | ||
|
|
6f488d963b | ||
|
|
8face25bdf | ||
|
|
0e32803247 | ||
|
|
3282344d4a | ||
|
|
78d23d86f5 | ||
|
|
3a0797955c | ||
|
|
9449e66396 | ||
|
|
b6200ab56d | ||
|
|
ff23561e21 | ||
|
|
e718428d50 | ||
|
|
ea34d66e14 | ||
|
|
d3c719d89a | ||
|
|
dcd070b3e7 | ||
|
|
32c08e49c5 | ||
|
|
28a496bc67 | ||
|
|
4386894e8a | ||
|
|
33613c7041 | ||
|
|
db1b4d48b8 | ||
|
|
f007e2cecb | ||
|
|
7e4df4c3d1 | ||
|
|
03b98b433b | ||
|
|
1b17743ed3 | ||
|
|
03f12f6aa4 | ||
|
|
0176d66a94 | ||
|
|
f078154b9b | ||
|
|
35a4c63c27 | ||
|
|
70f3508005 | ||
|
|
16e22e614b | ||
|
|
c8b2728e27 | ||
|
|
452c780d40 | ||
|
|
9567471e7f | ||
|
|
4d4923cd37 | ||
|
|
e85404a9c5 | ||
|
|
1d43e46f99 | ||
|
|
5a8c1dcb57 | ||
|
|
9ffc523443 | ||
|
|
39693a19bd | ||
|
|
18fcaff5ee | ||
|
|
568d5e4cdf | ||
|
|
99c24b5cd8 | ||
|
|
a3087d14d8 | ||
|
|
cc7de52caf | ||
|
|
05865d51c6 | ||
|
|
45089e176f | ||
|
|
00a41b95b9 | ||
|
|
765efd19da | ||
|
|
ac59e28fcd | ||
|
|
77dab70ff7 | ||
|
|
0b66e911b1 | ||
|
|
6388a798c9 | ||
|
|
c45149b664 | ||
|
|
ce0c1c39e2 | ||
|
|
52809a2783 | ||
|
|
be3909370e | ||
|
|
f79733e5df | ||
|
|
2d95de06d6 | ||
|
|
97502231a3 | ||
|
|
d20a6d2677 | ||
|
|
9f43c0ddc8 | ||
|
|
4cb1bf6a9f | ||
|
|
d96263fde9 | ||
|
|
ed8b2d9927 | ||
|
|
7b3be389d4 | ||
|
|
68755f4303 | ||
|
|
0e1f712dcc | ||
|
|
0ab1cfdeb6 | ||
|
|
8185ee991b | ||
|
|
1001d7462a | ||
|
|
f9929ebd61 | ||
|
|
aa69a7cad2 | ||
|
|
4de063de98 | ||
|
|
d765d0350d | ||
|
|
d2459a5837 | ||
|
|
e1f604d857 | ||
|
|
af4e860176 | ||
|
|
a3d665503f | ||
|
|
b47fbde479 | ||
|
|
115f46a4fa | ||
|
|
b0f8486ef2 | ||
|
|
57c27e6a4b | ||
|
|
f591939a6a | ||
|
|
2d41cce90f | ||
|
|
3b1aff1db1 | ||
|
|
3a64b43032 |
@@ -1,641 +0,0 @@
|
||||
{
|
||||
"projectName": "AFFiNE",
|
||||
"projectOwner": "toeverything",
|
||||
"repoType": "github",
|
||||
"repoHost": "https://github.com",
|
||||
"files": [
|
||||
"README.md"
|
||||
],
|
||||
"imageSize": 50,
|
||||
"commit": false,
|
||||
"commitConvention": "angular",
|
||||
"contributorsPerLine": 7,
|
||||
"badgeTemplate": "\n[all-contributors-badge]: https://img.shields.io/badge/all_contributors-<%= contributors.length %>-orange.svg?style=flat-square\n",
|
||||
"contributors": [
|
||||
{
|
||||
"login": "doodlewind",
|
||||
"name": "Yifeng Wang",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/7312949?v=4",
|
||||
"profile": "https://github.com/doodlewind",
|
||||
"contributions": [
|
||||
"code",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "darkskygit",
|
||||
"name": "DarkSky",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/25152247?v=4",
|
||||
"profile": "https://darksky.eu.org/",
|
||||
"contributions": [
|
||||
"code",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "tzhangchi",
|
||||
"name": "Chi Zhang",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/5910926?v=4",
|
||||
"profile": "http://zhangchi.page/",
|
||||
"contributions": [
|
||||
"code",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "alt1o",
|
||||
"name": "wang xinglong",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/21084335?v=4",
|
||||
"profile": "https://github.com/alt1o",
|
||||
"contributions": [
|
||||
"code",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Brooooooklyn",
|
||||
"name": "LongYinan",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/3468483?v=4",
|
||||
"profile": "https://github.com/Brooooooklyn",
|
||||
"contributions": [
|
||||
"code",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "hwangdev97",
|
||||
"name": "Hwang",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/24713927?v=4",
|
||||
"profile": "https://github.com/hwangdev97",
|
||||
"contributions": [
|
||||
"code",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "kobeshanks",
|
||||
"name": "kobeshanks",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/82570088?v=4",
|
||||
"profile": "https://github.com/kobeshanks",
|
||||
"contributions": [
|
||||
"code",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "pengx17",
|
||||
"name": "Peng Xiao",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/584378?v=4",
|
||||
"profile": "https://pengx17.vercel.app/",
|
||||
"contributions": [
|
||||
"code",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Saul-Mirone",
|
||||
"name": "Mirone",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/10047788?v=4",
|
||||
"profile": "https://mirone.me/",
|
||||
"contributions": [
|
||||
"code",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "zqran",
|
||||
"name": "zqran",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/15389209?v=4",
|
||||
"profile": "https://github.com/zqran",
|
||||
"contributions": [
|
||||
"code",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "SuneBear",
|
||||
"name": "Shule Hsiung",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/7693264?v=4",
|
||||
"profile": "https://sunebear.com/",
|
||||
"contributions": [
|
||||
"code",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "fundon",
|
||||
"name": "Fangdun Tsai",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/27926?v=4",
|
||||
"profile": "https://fundon.viz.rs/",
|
||||
"contributions": [
|
||||
"code",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "lawvs",
|
||||
"name": "Whitewater",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/18554747?v=4",
|
||||
"profile": "https://lawvs.github.io/profile/",
|
||||
"contributions": [
|
||||
"code",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "zuoxiaodong0815",
|
||||
"name": "xiaodong zuo",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/53252747?v=4",
|
||||
"profile": "https://github.com/zuoxiaodong0815",
|
||||
"contributions": [
|
||||
"code",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Himself65",
|
||||
"name": "Himself65",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/14026360?v=4",
|
||||
"profile": "https://github.com/Himself65",
|
||||
"contributions": [
|
||||
"code",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "DiamondThree",
|
||||
"name": "DiamondThree",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/24630517?v=4",
|
||||
"profile": "https://github.com/DiamondThree",
|
||||
"contributions": [
|
||||
"code",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "QiShaoXuan",
|
||||
"name": "Qi",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/22772830?v=4",
|
||||
"profile": "https://github.com/QiShaoXuan",
|
||||
"contributions": [
|
||||
"code",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "colelawrence",
|
||||
"name": "Cole Lawrence",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/2925395?v=4",
|
||||
"profile": "https://colelawrence.com/",
|
||||
"contributions": [
|
||||
"code",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "linonetwo",
|
||||
"name": "lin onetwo",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/3746270?v=4",
|
||||
"profile": "https://onetwo.ren/wiki",
|
||||
"contributions": [
|
||||
"code",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "thorseraq",
|
||||
"name": "x1a0t",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/20554850?v=4",
|
||||
"profile": "https://github.com/thorseraq",
|
||||
"contributions": [
|
||||
"code",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "HeJiachen-PM",
|
||||
"name": "HeJiachen-PM",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/79301703?v=4",
|
||||
"profile": "https://github.com/HeJiachen-PM",
|
||||
"contributions": [
|
||||
"research",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "joebeijing",
|
||||
"name": "houjoe",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/22443345?v=4",
|
||||
"profile": "https://www.notion.so/houjoe/Joe-2a85f5be01004cd2b6a5ad26fbb948b1",
|
||||
"contributions": [
|
||||
"research",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Yipei-Operation",
|
||||
"name": "Yipei Wei",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/79373028?v=4",
|
||||
"profile": "https://github.com/Yipei-Operation",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "VelikaHF",
|
||||
"name": "Velika",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/121547898?v=4",
|
||||
"profile": "https://github.com/VelikaHF",
|
||||
"contributions": [
|
||||
"design"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Svaney-ssman",
|
||||
"name": "Svaney",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/110808979?v=4",
|
||||
"profile": "https://github.com/Svaney-ssman",
|
||||
"contributions": [
|
||||
"design"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "xell",
|
||||
"name": "Guozhu Liu",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/132558?v=4",
|
||||
"profile": "http://xell.me/",
|
||||
"contributions": [
|
||||
"design"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "fyZheng07",
|
||||
"name": "fyZheng07",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/63830919?v=4",
|
||||
"profile": "https://github.com/fyZheng07",
|
||||
"contributions": [
|
||||
"eventOrganizing",
|
||||
"userTesting"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "CJSS",
|
||||
"name": "CJSS",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/4605025?v=4",
|
||||
"profile": "https://github.com/CJSS",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "JimmFly",
|
||||
"name": "JimmFly",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/102217452?v=4",
|
||||
"profile": "https://github.com/JimmFly",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "mitsuhatu",
|
||||
"name": "mitsuhatu",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/110213079?v=4",
|
||||
"profile": "https://github.com/mitsuhatu",
|
||||
"contributions": [
|
||||
"code",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Austaras",
|
||||
"name": "Austaras",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/15013925?v=4",
|
||||
"profile": "https://shockwave.me/",
|
||||
"contributions": [
|
||||
"code",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "uptonking",
|
||||
"name": "Jin Yao",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/11391549?v=4",
|
||||
"profile": "https://github.com/uptonking",
|
||||
"contributions": [
|
||||
"code",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "CarlosZoft",
|
||||
"name": "Carlos Rafael ",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/62192072?v=4",
|
||||
"profile": "https://github.com/CarlosZoft",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "caleboleary",
|
||||
"name": "Caleb OLeary",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/12816579?v=4",
|
||||
"profile": "https://github.com/caleboleary",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "westongraham",
|
||||
"name": "Weston Graham",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/89493023?v=4",
|
||||
"profile": "https://github.com/westongraham",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "SaikaSakura",
|
||||
"name": "MingLIang Wang",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/11530942?v=4",
|
||||
"profile": "https://github.com/SaikaSakura",
|
||||
"contributions": [
|
||||
"code",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "fanjing22",
|
||||
"name": "fanjing22",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/109729699?v=4",
|
||||
"profile": "https://github.com/fanjing22",
|
||||
"contributions": [
|
||||
"design"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "pointmax",
|
||||
"name": "pointmax",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/49361135?v=4",
|
||||
"profile": "https://github.com/pointmax",
|
||||
"contributions": [
|
||||
"doc",
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "liby",
|
||||
"name": "Bryan Lee",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/38807139?v=4",
|
||||
"profile": "https://liby.github.io/notes",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "chenmoonmo",
|
||||
"name": "Simon Li",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/36295999?v=4",
|
||||
"profile": "https://github.com/chenmoonmo",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "githbq",
|
||||
"name": "Bob Hu",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/10009709?v=4",
|
||||
"profile": "https://github.com/githbq",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "lucky-chap",
|
||||
"name": "Quavo",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/67266933?v=4",
|
||||
"profile": "https://quavo.vercel.app/",
|
||||
"contributions": [
|
||||
"doc",
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "LuciNyan",
|
||||
"name": "子瞻 Luci",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/22126563?v=4",
|
||||
"profile": "https://github.com/LuciNyan",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "m1911star",
|
||||
"name": "Horus",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/4948120?v=4",
|
||||
"profile": "http://blog.ipili.me/",
|
||||
"contributions": [
|
||||
"code",
|
||||
"platform"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "fanshyiis",
|
||||
"name": "Super.x",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/15103283?v=4",
|
||||
"profile": "https://segmentfault.com/u/qzuser_584786517d31a",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "wangyu-1999",
|
||||
"name": "Wang Yu",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/80874770?v=4",
|
||||
"profile": "https://wangyu-1999.github.io/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "felixonmars",
|
||||
"name": "Felix Yan",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1006477?v=4",
|
||||
"profile": "https://felixc.at/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "lynettelopez",
|
||||
"name": "Lynette Lopez",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/32908859?v=4",
|
||||
"profile": "https://github.com/lynettelopez",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Zheaoli",
|
||||
"name": "Manjusaka",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/7054676?v=4",
|
||||
"profile": "http://manjusaka.itscoder.com/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "sudongyuer",
|
||||
"name": "Frozen FIsh",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/76603360?v=4",
|
||||
"profile": "https://juejin.cn/user/2867982785579102/posts?sort=popular",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "MuhammedFaraz",
|
||||
"name": "Mohammed Faraz",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/92734739?v=4",
|
||||
"profile": "https://github.com/MuhammedFaraz",
|
||||
"contributions": [
|
||||
"doc",
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Pranav4399",
|
||||
"name": "Pranav Sriram ",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/28348429?v=4",
|
||||
"profile": "https://pranavsriram.dev/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Reson-a",
|
||||
"name": "Reson-a",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/20806266?v=4",
|
||||
"profile": "https://github.com/Reson-a",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "hezhizhen",
|
||||
"name": "Zhizhen He",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/7611700?v=4",
|
||||
"profile": "https://t.me/littlepoint",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "AkaraChen",
|
||||
"name": "AkaraChen",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/85140972?v=4",
|
||||
"profile": "https://akr.moe/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "suyanhanx",
|
||||
"name": "Suyan",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/24221472?v=4",
|
||||
"profile": "https://github.com/suyanhanx",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "hehex9",
|
||||
"name": "hehe",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/9209882?v=4",
|
||||
"profile": "https://github.com/hehex9",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "albertodlc",
|
||||
"name": "Alberto de la Cruz",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/32411964?v=4",
|
||||
"profile": "https://github.com/albertodlc",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "AlessioGr",
|
||||
"name": "Alessio Gravili",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/70709113?v=4",
|
||||
"profile": "https://github.com/AlessioGr",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "lzlme",
|
||||
"name": "Zhilin Liu",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/117659326?v=4",
|
||||
"profile": "https://github.com/lzlme",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "suica",
|
||||
"name": "Sg",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/8041462?v=4",
|
||||
"profile": "https://github.com/suica",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "sinchang",
|
||||
"name": "Jeff Wen",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/3297859?v=4",
|
||||
"profile": "https://sinchang.me/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "m1212e",
|
||||
"name": "m1212e",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/14091540?v=4",
|
||||
"profile": "https://m1212e.github.io/portfolio/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "adityash1",
|
||||
"name": "Aditya Sharma",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/65771169?v=4",
|
||||
"profile": "https://adityash1.github.io/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "sheben404",
|
||||
"name": "Kehan Wang",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/61317160?v=4",
|
||||
"profile": "https://github.com/sheben404",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "VictorNanka",
|
||||
"name": "VictorNanka",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/30154366?v=4",
|
||||
"profile": "https://github.com/VictorNanka",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -24,7 +24,9 @@
|
||||
"debug",
|
||||
"storage",
|
||||
"infra",
|
||||
"plugin-infra"
|
||||
"plugin-cli",
|
||||
"sdk",
|
||||
"plugin"
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
@@ -9,3 +9,8 @@ lib
|
||||
.eslintrc.js
|
||||
packages/i18n/src/i18n-generated.ts
|
||||
e2e-dist-*
|
||||
static
|
||||
web-static
|
||||
public
|
||||
packages/sdk/src/*.d.ts
|
||||
packages/sdk/src/*.js
|
||||
|
||||
25
.eslintrc.js
@@ -26,6 +26,16 @@ 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 = [
|
||||
@@ -38,7 +48,8 @@ const allPackages = [
|
||||
'packages/i18n',
|
||||
'packages/jotai',
|
||||
'packages/native',
|
||||
'packages/plugin-infra',
|
||||
'packages/infra',
|
||||
'packages/sdk',
|
||||
'packages/templates',
|
||||
'packages/theme',
|
||||
'packages/workspace',
|
||||
@@ -144,6 +155,16 @@ 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'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -203,6 +224,7 @@ const config = {
|
||||
ignoreIIFE: false,
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/no-misused-promises': ['error'],
|
||||
},
|
||||
})),
|
||||
{
|
||||
@@ -228,6 +250,7 @@ const config = {
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/no-floating-promises': 0,
|
||||
'@typescript-eslint/no-misused-promises': 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
16
.github/actions/setup-maker/action.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
name: Setup maker
|
||||
description: 'Setup maker dmg for electron'
|
||||
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: 'Install @electron-forge/maker-dmg'
|
||||
if: runner.os == 'macos'
|
||||
shell: bash
|
||||
working-directory: ./apps/electron
|
||||
run: yarn add @electron-forge/maker-dmg --dev
|
||||
env:
|
||||
HUSKY: '0'
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1'
|
||||
ELECTRON_SKIP_BINARY_DOWNLOAD: '1'
|
||||
SENTRYCLI_SKIP_DOWNLOAD: '1'
|
||||
8
.github/actions/setup-node/action.yml
vendored
@@ -17,10 +17,6 @@ inputs:
|
||||
description: 'Download the Electron binary'
|
||||
required: false
|
||||
default: 'true'
|
||||
npm-token:
|
||||
description: 'The NPM token to use for private packages.'
|
||||
required: false
|
||||
default: ''
|
||||
hard-link-nm:
|
||||
description: 'set nmMode to hardlinks-local in .yarnrc.yml'
|
||||
required: false
|
||||
@@ -48,20 +44,20 @@ runs:
|
||||
shell: bash
|
||||
run: yarn install ${{ inputs.extra-flags }}
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ inputs.npm-token }}
|
||||
HUSKY: '0'
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1'
|
||||
ELECTRON_SKIP_BINARY_DOWNLOAD: '1'
|
||||
SENTRYCLI_SKIP_DOWNLOAD: '1'
|
||||
|
||||
- name: yarn install (try again)
|
||||
if: ${{ steps.install.outcome == 'failure' }}
|
||||
shell: bash
|
||||
run: yarn install ${{ inputs.extra-flags }}
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ inputs.npm-token }}
|
||||
HUSKY: '0'
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1'
|
||||
ELECTRON_SKIP_BINARY_DOWNLOAD: '1'
|
||||
SENTRYCLI_SKIP_DOWNLOAD: '1'
|
||||
|
||||
- name: Get installed Playwright version
|
||||
id: playwright-version
|
||||
|
||||
13
.github/actions/setup-sentry/action.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
name: Setup @sentry/cli
|
||||
description: 'Setup @sentry/cli'
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: 'Install @sentry/cli from brew'
|
||||
if: runner.os == 'macos'
|
||||
shell: bash
|
||||
run: brew install getsentry/tools/sentry-cli
|
||||
- name: 'Install @sentry/cli from npm'
|
||||
if: runner.os != 'macos'
|
||||
shell: bash
|
||||
run: sudo npm install -g @sentry/cli --unsafe-perm
|
||||
9
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: 'npm'
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: 'weekly'
|
||||
versioning-strategy: increase
|
||||
commit-message:
|
||||
prefix: 'chore'
|
||||
10
.github/labeler.yml
vendored
@@ -22,8 +22,14 @@ plugin:bookmark-block:
|
||||
plugin:copilot:
|
||||
- 'plugins/copilot/**/*'
|
||||
|
||||
mod:plugin-infra:
|
||||
- 'packages/plugin-infra/**/*'
|
||||
mod:infra:
|
||||
- 'packages/infra/**/*'
|
||||
|
||||
mod:sdk:
|
||||
- 'packages/sdk/**/*'
|
||||
|
||||
mod:plugin-cli:
|
||||
- 'packages/plugin-cli/**/*'
|
||||
|
||||
mod:workspace: 'packages/workspace/**/*'
|
||||
|
||||
|
||||
204
.github/workflows/build.yml
vendored
@@ -47,8 +47,6 @@ jobs:
|
||||
electron-install: false
|
||||
- name: Run i18n codegen
|
||||
run: yarn i18n-codegen gen
|
||||
- name: Run Type Check
|
||||
run: yarn typecheck
|
||||
- name: Run ESLint
|
||||
run: yarn lint:eslint --max-warnings=0
|
||||
- name: Run Prettier
|
||||
@@ -58,6 +56,40 @@ jobs:
|
||||
yarn lint:prettier
|
||||
- name: Run circular
|
||||
run: yarn circular
|
||||
- name: Run Type Check
|
||||
run: yarn typecheck
|
||||
|
||||
build-prototype:
|
||||
name: Build Prototype
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
electron-install: false
|
||||
- name: Build Prototype
|
||||
run: yarn nx build prototype
|
||||
- name: Upload prototype artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: prototype
|
||||
path: ./apps/prototype/dist
|
||||
if-no-files-found: error
|
||||
|
||||
build-server:
|
||||
name: Build Server
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
electron-install: false
|
||||
- name: Build Server
|
||||
run: yarn nx build @affine/server
|
||||
- name: Upload server dist
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
@@ -110,6 +142,8 @@ jobs:
|
||||
- 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
|
||||
@@ -119,10 +153,33 @@ jobs:
|
||||
path: ./apps/core/dist
|
||||
if-no-files-found: error
|
||||
|
||||
build-storage:
|
||||
name: Build Storage
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
- name: Setup Rust
|
||||
uses: ./.github/actions/setup-rust
|
||||
with:
|
||||
target: 'x86_64-unknown-linux-gnu'
|
||||
- name: Build Storage
|
||||
run: yarn build:storage
|
||||
- name: Upload storage.node
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: storage.node
|
||||
path: ./packages/storage/storage.node
|
||||
if-no-files-found: error
|
||||
|
||||
server-test:
|
||||
name: Server Test
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
needs: build-storage
|
||||
services:
|
||||
postgres:
|
||||
image: postgres
|
||||
@@ -158,24 +215,17 @@ jobs:
|
||||
working-directory: apps/server
|
||||
env:
|
||||
DATABASE_URL: postgresql://affine:affine@localhost:5432/affine
|
||||
- name: Setup Rust
|
||||
uses: ./.github/actions/setup-rust
|
||||
- name: Download storage.node
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
target: 'x86_64-unknown-linux-gnu'
|
||||
- name: Build Storage
|
||||
run: yarn build:storage
|
||||
name: storage.node
|
||||
path: ./apps/server
|
||||
- name: Run server tests
|
||||
run: yarn test:coverage
|
||||
working-directory: apps/server
|
||||
env:
|
||||
CARGO_TARGET_DIR: '${{ github.workspace }}/target'
|
||||
DATABASE_URL: postgresql://affine:affine@localhost:5432/affine
|
||||
- name: Upload storage.node
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: storage.node
|
||||
path: ./packages/storage/storage.node
|
||||
if-no-files-found: error
|
||||
- name: Upload server test coverage results
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
@@ -207,6 +257,91 @@ jobs:
|
||||
run: |
|
||||
yarn exec concurrently -k -s first -n "SB,TEST" -c "magenta,blue" "yarn exec serve ./storybook-static -l 6006" "yarn exec wait-on tcp:6006 && yarn test"
|
||||
|
||||
e2e-plugin-test:
|
||||
name: E2E Plugin Test
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
needs: build-core
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
playwright-install: true
|
||||
electron-install: false
|
||||
- name: Download core artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: core
|
||||
path: ./apps/core/dist
|
||||
- name: Run playwright tests
|
||||
run: yarn e2e --forbid-only
|
||||
working-directory: tests/affine-plugin
|
||||
env:
|
||||
COVERAGE: true
|
||||
- name: Collect code coverage report
|
||||
run: yarn exec nyc report -t .nyc_output --report-dir .coverage --reporter=lcov
|
||||
|
||||
- name: Upload e2e test coverage results
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ./.coverage/lcov.info
|
||||
flags: e2e-plugin-test
|
||||
name: affine
|
||||
fail_ci_if_error: false
|
||||
|
||||
- name: Upload test results
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: test-results-e2e-plugin
|
||||
path: ./test-results
|
||||
if-no-files-found: ignore
|
||||
|
||||
e2e-prototype-test:
|
||||
name: E2E Prototype Test
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
needs: build-prototype
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
playwright-install: true
|
||||
electron-install: false
|
||||
- name: Download prototype artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: prototype
|
||||
path: ./apps/prototype/dist
|
||||
- name: Run playwright tests
|
||||
run: yarn e2e --forbid-only
|
||||
working-directory: tests/affine-prototype
|
||||
env:
|
||||
COVERAGE: true
|
||||
|
||||
# - name: Collect code coverage report
|
||||
# run: yarn exec nyc report -t .nyc_output --report-dir .coverage --reporter=lcov
|
||||
|
||||
# - name: Upload e2e test coverage results
|
||||
# uses: codecov/codecov-action@v3
|
||||
# with:
|
||||
# token: ${{ secrets.CODECOV_TOKEN }}
|
||||
# files: ./.coverage/lcov.info
|
||||
# flags: e2etest-prototype
|
||||
# name: affine
|
||||
# fail_ci_if_error: false
|
||||
|
||||
- name: Upload test results
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: test-results-e2e-prototype
|
||||
path: ./test-results
|
||||
if-no-files-found: ignore
|
||||
|
||||
e2e-test:
|
||||
name: E2E Test
|
||||
runs-on: ubuntu-latest
|
||||
@@ -261,7 +396,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
needs: build-core
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
spec:
|
||||
- { package: 0.7.0-canary.18 }
|
||||
- { package: 0.8.0-canary.7 }
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
@@ -278,22 +417,18 @@ jobs:
|
||||
|
||||
- name: Unzip
|
||||
run: yarn unzip
|
||||
working-directory: ./tests/affine-legacy/0.7.0-canary.18
|
||||
working-directory: ./tests/affine-legacy/${{ matrix.spec.package }}
|
||||
|
||||
- name: Run legacy playwright tests
|
||||
- name: Run playwright tests
|
||||
run: yarn e2e --forbid-only
|
||||
working-directory: ./tests/affine-legacy/0.7.0-canary.18
|
||||
|
||||
- name: Run vitest
|
||||
run: yarn test
|
||||
working-directory: ./tests/affine-legacy/0.7.0-canary.18
|
||||
working-directory: ./tests/affine-legacy/${{ matrix.spec.package }}
|
||||
|
||||
- name: Upload test results
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: test-results-e2e-migration
|
||||
path: ./tests/affine-legacy/0.7.0-canary.18/test-results
|
||||
name: test-results-e2e-migration-${{ matrix.spec.package }}
|
||||
path: ./tests/affine-legacy/${{ matrix.spec.package }}/test-results
|
||||
if-no-files-found: ignore
|
||||
|
||||
desktop-test:
|
||||
@@ -338,6 +473,7 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
timeout-minutes: 10
|
||||
with:
|
||||
playwright-install: true
|
||||
hard-link-nm: false
|
||||
@@ -349,9 +485,8 @@ jobs:
|
||||
- name: Run unit tests
|
||||
if: ${{ matrix.spec.test }}
|
||||
shell: bash
|
||||
run: yarn nx test @affine/monorepo
|
||||
env:
|
||||
NATIVE_TEST: 'true'
|
||||
run: yarn vitest
|
||||
working-directory: ./apps/electron
|
||||
|
||||
- name: Download core artifact
|
||||
uses: actions/download-artifact@v3
|
||||
@@ -365,6 +500,12 @@ jobs:
|
||||
- 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
|
||||
@@ -379,12 +520,13 @@ jobs:
|
||||
|
||||
- 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: |
|
||||
./scripts/unzip-macos-arm64.sh
|
||||
yarn ts-node-esm ./scripts/macos-arm64-output-check.mts
|
||||
working-directory: apps/electron
|
||||
|
||||
@@ -436,11 +578,11 @@ jobs:
|
||||
build-docker:
|
||||
if: github.ref == 'refs/heads/master'
|
||||
name: Build Docker
|
||||
needs:
|
||||
- lint
|
||||
- desktop-test
|
||||
- server-test
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build-server
|
||||
- build-core
|
||||
- build-storage
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Download core artifact
|
||||
|
||||
2
.github/workflows/cancel.yml
vendored
@@ -14,5 +14,5 @@ jobs:
|
||||
- uses: styfle/cancel-workflow-action@0.11.0
|
||||
with:
|
||||
# See https://api.github.com/repos/toeverything/AFFiNE/actions/workflows
|
||||
workflow_id: 44038251, 61883931
|
||||
workflow_id: 44038251, 61883931, 65188160
|
||||
access_token: ${{ github.token }}
|
||||
|
||||
6
.github/workflows/nightly-build.yml
vendored
@@ -50,6 +50,8 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
- name: Setup @sentry/cli
|
||||
uses: ./.github/actions/setup-sentry
|
||||
- name: Replace Version
|
||||
run: ./scripts/set-version.sh ${{ needs.set-build-version.outputs.version }}
|
||||
- name: generate-assets
|
||||
@@ -110,7 +112,11 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
timeout-minutes: 10
|
||||
uses: ./.github/actions/setup-node
|
||||
- name: Setup Maker
|
||||
timeout-minutes: 10
|
||||
uses: ./.github/actions/setup-maker
|
||||
- name: Build AFFiNE native
|
||||
uses: ./.github/actions/build-rust
|
||||
with:
|
||||
|
||||
6
.github/workflows/release-desktop-app.yml
vendored
@@ -47,6 +47,8 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
- name: Setup @sentry/cli
|
||||
uses: ./.github/actions/setup-sentry
|
||||
- name: Get canary version
|
||||
id: get-canary-version
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
@@ -112,7 +114,11 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
timeout-minutes: 10
|
||||
uses: ./.github/actions/setup-node
|
||||
- name: Setup Maker
|
||||
timeout-minutes: 10
|
||||
uses: ./.github/actions/setup-maker
|
||||
- name: Build AFFiNE native
|
||||
uses: ./.github/actions/build-rust
|
||||
with:
|
||||
|
||||
54
README.md
@@ -14,21 +14,12 @@
|
||||
</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 -->
|
||||
|
||||
[?style=flat-square&logoColor=white&logo=affine>)](https://app.affine.pro)
|
||||
[](https://affine.pro/download)
|
||||
[](https://affine.pro/download)
|
||||
[](https://affine.pro/download)
|
||||
[](https://affine.pro/download)
|
||||
[](https://affine.pro/download)
|
||||
|
||||
[](https://github.com/toeverything/AFFiNE/releases/latest)
|
||||
[![stars-icon]](https://github.com/toeverything/AFFiNE)
|
||||
@@ -39,6 +30,7 @@ See https://github.com/all-?/all-contributors/issues/361#issuecomment-637166066
|
||||
[![React-version-icon]](https://reactjs.org/)
|
||||
[![blocksuite-icon]](https://github.com/toeverything/blocksuite)
|
||||
[![Rust-version-icon]](https://www.rust-lang.org/)
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2Ftoeverything%2FAFFiNE?ref=badge_shield)
|
||||
|
||||
</div>
|
||||
|
||||
@@ -122,28 +114,32 @@ If you have questions, you are welcome to contact us. One of the best places to
|
||||
|
||||
## Plugins
|
||||
|
||||
> Plugins are a way to extend the functionality of AFFiNE.
|
||||
> Plugins are a way to extend the functionality of AFFiNE. You can use plugins to add new blocks, new features, and even new ways to edit content.
|
||||
>
|
||||
> (Currently, plugins are under heavy development, and the SDK is not yet available.)
|
||||
> (Currently, the plugin system is under heavy development. You will see the plugin system in the canary release.)
|
||||
|
||||
| Name | |
|
||||
| ------------------------------------------------ | ----------------------------------------- |
|
||||
| [@affine/bookmark-block](plugins/bookmark-block) | A block for bookmarking a website |
|
||||
| [@affine/copilot](plugins/copilot) | AI Copilot that help you document writing |
|
||||
- [@affine/sdk](./packages/sdk) - SDK for developing plugins
|
||||
- [@affine/plugin-cli](./packages/plugin-cli) - CLI for developing plugins
|
||||
|
||||
| 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 |
|
||||
|
||||
## 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) - View layer support and web GUI framework.
|
||||
- [Rust](https://github.com/rust-lang/rust) - High performance language that extends the ability and availability of our real-time backend, OctoBase.
|
||||
- [yjs](https://github.com/yjs/yjs) - Fundamental support of CRDTs for our implementation on state management and data sync.
|
||||
- [electron](https://github.com/electron/electron) - Build cross-platform desktop apps with JavaScript, HTML, and CSS.
|
||||
- [React](https://github.com/facebook/react) - The library for web and native user interfaces.
|
||||
- [napi-rs](https://github.com/napi-rs/napi-rs) - A framework for building compiled Node.js add-ons in Rust via Node-API.
|
||||
- [Jotai](https://github.com/pmndrs/jotai) - Primitive and flexible state management for React.
|
||||
- [MUI](https://github.com/mui/material-ui) - Our most used graphic UI component library.
|
||||
- [async-call-rpc](https://github.com/Jack-Works/async-call-rpc) - A lightweight JSON RPC client & server.
|
||||
- [Vite](https://github.com/vitejs/vite) - Next generation frontend tooling.
|
||||
- Other upstream [dependencies](https://github.com/toeverything/AFFiNE/network/dependencies).
|
||||
|
||||
Thanks a lot to the community for providing such powerful and simple libraries, so that we can focus more on the implementation of the product logic, and we hope that in the future our projects will also provide a more easy-to-use knowledge base for everyone.
|
||||
@@ -167,15 +163,11 @@ Some amazing companies including AFFiNE are looking for developers! Are you inte
|
||||
|
||||
## Upgrading
|
||||
|
||||
For upgrading information please see our [update page].
|
||||
For upgrading information, please see our [update page].
|
||||
|
||||
## Feature Request
|
||||
|
||||
For feature request please see [community.affine.pro](https://community.affine.pro/c/feature-requests/).
|
||||
|
||||
## Is it awesome?
|
||||
|
||||
[These people] seem to like it.
|
||||
For feature request, please see [community.affine.pro](https://community.affine.pro/c/feature-requests/).
|
||||
|
||||
## Building
|
||||
|
||||
@@ -190,17 +182,19 @@ See [docs/contributing/tutorial.md](./docs/contributing/tutorial.md) for details
|
||||
|
||||
See [LICENSE] for details.
|
||||
|
||||
[all-contributors-badge]: https://img.shields.io/github/all-contributors/toeverything/AFFiNE/master?color=orange
|
||||
[license]: ./LICENSE
|
||||
[building.md]: ./docs/BUILDING.md
|
||||
[these people]: https://twitter.com/AffineOfficial/followers
|
||||
[update page]: https://affine.pro/blog?tag=Release%20Note
|
||||
[jobs available]: ./docs/jobs.md
|
||||
[latest packages]: https://github.com/toeverything/AFFiNE/pkgs/container/affine-self-hosted
|
||||
[contributor license agreement]: https://github.com/toeverything/affine/edit/master/.github/CLA.md
|
||||
[rust-version-icon]: https://img.shields.io/badge/Rust-1.70.0-dea584
|
||||
[rust-version-icon]: https://img.shields.io/badge/Rust-1.71.0-dea584
|
||||
[stars-icon]: https://img.shields.io/github/stars/toeverything/AFFiNE.svg?style=flat&logo=github&colorB=red&label=stars
|
||||
[codecov]: https://codecov.io/gh/toeverything/affine/branch/master/graphs/badge.svg?branch=master
|
||||
[node-version-icon]: https://img.shields.io/badge/node-%3E=18.16.1-success
|
||||
[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
|
||||
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2Ftoeverything%2FAFFiNE?ref=badge_large)
|
||||
|
||||
@@ -8,7 +8,7 @@ AFFiNE Developer Documentation using [waku](https://github.com/dai-shi/waku).
|
||||
|
||||
## electron
|
||||
|
||||
> `web` needs to be built before electron.
|
||||
> `core` needs to be built before electron.
|
||||
|
||||
AFFiNE Desktop (macOS, Linux and Windows Distribution) using [Electron](https://www.electronjs.org/).
|
||||
|
||||
@@ -20,6 +20,10 @@ Server using [Nest.js](https://nestjs.com/).
|
||||
|
||||
Storybook using [Storybook](https://storybook.js.org/).
|
||||
|
||||
## Core
|
||||
## prototype
|
||||
|
||||
AFFiNE Core Application using [React.js](https://reactjs.org/).
|
||||
AFFiNE Prototype using [React.js](https://reactjs.org/) + [Vite](https://vitejs.dev/).
|
||||
|
||||
## core
|
||||
|
||||
AFFiNE Core Application using [React.js](https://reactjs.org/) + [Webpack](https://webpack.js.org/).
|
||||
|
||||
@@ -8,9 +8,9 @@ export const productionCacheGroups = {
|
||||
test: /[\\/]node_modules[\\/]/,
|
||||
name(module: any) {
|
||||
// https://hackernoon.com/the-100-correct-way-to-split-your-chunks-with-webpack-f8a9df5b7758
|
||||
const name =
|
||||
module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)?.[1] ??
|
||||
'unknown';
|
||||
const name = module.context.match(
|
||||
/[\\/]node_modules[\\/](.*?)([\\/]|$)/
|
||||
)?.[1];
|
||||
return `npm-async-${name}`;
|
||||
},
|
||||
priority: Number.MAX_SAFE_INTEGER,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { join, resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { createRequire } from 'node:module';
|
||||
import HTMLPlugin from 'html-webpack-plugin';
|
||||
import type { Configuration as DevServerConfiguration } from 'webpack-dev-server';
|
||||
import { PerfseePlugin } from '@perfsee/webpack';
|
||||
import { sentryWebpackPlugin } from '@sentry/webpack-plugin';
|
||||
@@ -32,6 +31,7 @@ const OptimizeOptionOptions: (
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
minify: TerserPlugin.swcMinify,
|
||||
exclude: [/plugins\/.+\/.+\.js$/, /plugins\/.+\/.+\.mjs$/],
|
||||
parallel: true,
|
||||
extractComments: true,
|
||||
terserOptions: {
|
||||
@@ -73,12 +73,19 @@ export const createConfiguration: (
|
||||
) => webpack.Configuration = (buildFlags, runtimeConfig) => {
|
||||
let publicPath = process.env.PUBLIC_PATH ?? '/';
|
||||
|
||||
const blocksuiteBaseDir = buildFlags.localBlockSuite;
|
||||
|
||||
const cacheKey = computeCacheKey(buildFlags);
|
||||
|
||||
const config = {
|
||||
name: 'affine',
|
||||
// to set a correct base path for the source map
|
||||
context: projectRoot,
|
||||
experiments: {
|
||||
topLevelAwait: true,
|
||||
outputModule: false,
|
||||
syncWebAssembly: true,
|
||||
},
|
||||
output: {
|
||||
environment: {
|
||||
module: true,
|
||||
@@ -117,6 +124,60 @@ export const createConfiguration: (
|
||||
'.mjs': ['.mjs', '.mts'],
|
||||
},
|
||||
extensions: ['.js', '.ts', '.tsx'],
|
||||
fallback:
|
||||
blocksuiteBaseDir === undefined
|
||||
? undefined
|
||||
: {
|
||||
events: false,
|
||||
},
|
||||
alias:
|
||||
blocksuiteBaseDir === undefined
|
||||
? undefined
|
||||
: {
|
||||
yjs: require.resolve('yjs'),
|
||||
'@blocksuite/block-std': resolve(
|
||||
blocksuiteBaseDir,
|
||||
'packages',
|
||||
'block-std'
|
||||
),
|
||||
'@blocksuite/blocks': resolve(
|
||||
blocksuiteBaseDir,
|
||||
'packages',
|
||||
'blocks'
|
||||
),
|
||||
'@blocksuite/editor': resolve(
|
||||
blocksuiteBaseDir,
|
||||
'packages',
|
||||
'editor'
|
||||
),
|
||||
'@blocksuite/global': resolve(
|
||||
blocksuiteBaseDir,
|
||||
'packages',
|
||||
'global'
|
||||
),
|
||||
'@blocksuite/lit': resolve(blocksuiteBaseDir, 'packages', 'lit'),
|
||||
'@blocksuite/phasor': resolve(
|
||||
blocksuiteBaseDir,
|
||||
'packages',
|
||||
'phasor'
|
||||
),
|
||||
'@blocksuite/store/providers/broadcast-channel': resolve(
|
||||
blocksuiteBaseDir,
|
||||
'packages',
|
||||
'store',
|
||||
'src/providers/broadcast-channel'
|
||||
),
|
||||
'@blocksuite/store': resolve(
|
||||
blocksuiteBaseDir,
|
||||
'packages',
|
||||
'store'
|
||||
),
|
||||
'@blocksuite/virgo': resolve(
|
||||
blocksuiteBaseDir,
|
||||
'packages',
|
||||
'virgo'
|
||||
),
|
||||
},
|
||||
},
|
||||
|
||||
cache: {
|
||||
@@ -130,6 +191,10 @@ export const createConfiguration: (
|
||||
module: {
|
||||
parser: {
|
||||
javascript: {
|
||||
// Do not mock Node.js globals
|
||||
node: false,
|
||||
requireJs: false,
|
||||
import: true,
|
||||
// Treat as missing export as error
|
||||
strictExportPresence: true,
|
||||
},
|
||||
@@ -137,6 +202,20 @@ export const createConfiguration: (
|
||||
rules: [
|
||||
{
|
||||
test: /\.m?js?$/,
|
||||
enforce: 'pre',
|
||||
use: [
|
||||
{
|
||||
loader: require.resolve('source-map-loader'),
|
||||
options: {
|
||||
filterSourceMappingUrl: (
|
||||
_url: string,
|
||||
resourcePath: string
|
||||
) => {
|
||||
return resourcePath.includes('@blocksuite');
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
resolve: {
|
||||
fullySpecified: false,
|
||||
},
|
||||
@@ -145,8 +224,7 @@ export const createConfiguration: (
|
||||
oneOf: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
// Compile all ts files in the workspace
|
||||
include: resolve(rootPath, '..', '..'),
|
||||
exclude: /node_modules/,
|
||||
loader: require.resolve('swc-loader'),
|
||||
options: {
|
||||
// https://swc.rs/docs/configuring-swc/
|
||||
@@ -157,9 +235,10 @@ export const createConfiguration: (
|
||||
dynamicImport: true,
|
||||
topLevelAwait: false,
|
||||
tsx: true,
|
||||
decorators: true,
|
||||
},
|
||||
target: 'es2022',
|
||||
externalHelpers: true,
|
||||
externalHelpers: false,
|
||||
transform: {
|
||||
react: {
|
||||
runtime: 'automatic',
|
||||
@@ -169,6 +248,7 @@ export const createConfiguration: (
|
||||
emitFullSignatures: true,
|
||||
},
|
||||
},
|
||||
useDefineForClassFields: false,
|
||||
},
|
||||
experimental: {
|
||||
keepImportAssertions: true,
|
||||
@@ -252,14 +332,6 @@ export const createConfiguration: (
|
||||
ignoreOrder: true,
|
||||
}),
|
||||
]),
|
||||
new HTMLPlugin({
|
||||
template: join(rootPath, '.webpack', 'template.html'),
|
||||
inject: 'body',
|
||||
scriptLoading: 'defer',
|
||||
minify: false,
|
||||
chunks: ['index', 'plugin'],
|
||||
filename: 'index.html',
|
||||
}),
|
||||
new VanillaExtractPlugin(),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': JSON.stringify({}),
|
||||
@@ -269,7 +341,10 @@ export const createConfiguration: (
|
||||
}),
|
||||
new CopyPlugin({
|
||||
patterns: [
|
||||
{ from: resolve(rootPath, 'public'), to: resolve(rootPath, 'dist') },
|
||||
{
|
||||
from: resolve(rootPath, 'public'),
|
||||
to: resolve(rootPath, 'dist'),
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
|
||||
@@ -23,7 +23,7 @@ export function getRuntimeConfig(buildFlags: BuildFlags): RuntimeConfig {
|
||||
enableTestProperties: false,
|
||||
enableBroadcastChannelProvider: true,
|
||||
enableDebugPage: true,
|
||||
changelogUrl: 'https://affine.pro/blog/what-is-new-affine-0717',
|
||||
changelogUrl: 'https://affine.pro/blog/what-is-new-affine-0728',
|
||||
imageProxyUrl: 'https://workers.toeverything.workers.dev/proxy/image',
|
||||
enablePreloading: true,
|
||||
enableNewSettingModal: true,
|
||||
@@ -43,7 +43,7 @@ export function getRuntimeConfig(buildFlags: BuildFlags): RuntimeConfig {
|
||||
enableTestProperties: true,
|
||||
enableBroadcastChannelProvider: true,
|
||||
enableDebugPage: true,
|
||||
changelogUrl: 'https://affine.pro/blog/what-is-new-affine-0717',
|
||||
changelogUrl: 'https://github.com/toeverything/AFFiNE/releases',
|
||||
imageProxyUrl: 'https://workers.toeverything.workers.dev/proxy/image',
|
||||
enablePreloading: true,
|
||||
enableNewSettingModal: true,
|
||||
|
||||
@@ -7,5 +7,6 @@ export function computeCacheKey(buildFlags: BuildFlags) {
|
||||
buildFlags.mode,
|
||||
buildFlags.distribution,
|
||||
buildFlags.channel,
|
||||
...(buildFlags.localBlockSuite ? [buildFlags.localBlockSuite] : []),
|
||||
].join('-');
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { createConfiguration, rootPath } from './config.js';
|
||||
import { merge } from 'webpack-merge';
|
||||
import { resolve } from 'node:path';
|
||||
import { join, resolve } from 'node:path';
|
||||
import type { BuildFlags } from '@affine/cli/config';
|
||||
import { getRuntimeConfig } from './runtime-config.js';
|
||||
import HTMLPlugin from 'html-webpack-plugin';
|
||||
|
||||
export default async function (cli_env: any, _: any) {
|
||||
const flags: BuildFlags = JSON.parse(
|
||||
@@ -14,15 +15,49 @@ export default async function (cli_env: any, _: any) {
|
||||
const config = createConfiguration(flags, runtimeConfig);
|
||||
return merge(config, {
|
||||
entry: {
|
||||
index: {
|
||||
asyncChunks: false,
|
||||
import: resolve(rootPath, 'src/index.tsx'),
|
||||
'polyfill/intl-segmenter': {
|
||||
import: resolve(rootPath, 'src/polyfill/intl-segmenter.ts'),
|
||||
},
|
||||
'polyfill/ses': {
|
||||
import: resolve(rootPath, 'src/polyfill/ses.ts'),
|
||||
},
|
||||
plugin: {
|
||||
dependOn: ['index'],
|
||||
asyncChunks: true,
|
||||
dependOn: ['polyfill/intl-segmenter', 'polyfill/ses'],
|
||||
import: resolve(rootPath, 'src/bootstrap/register-plugins.ts'),
|
||||
},
|
||||
app: {
|
||||
chunkLoading: 'import',
|
||||
dependOn: ['polyfill/intl-segmenter', 'polyfill/ses', 'plugin'],
|
||||
import: resolve(rootPath, 'src/index.tsx'),
|
||||
},
|
||||
'_plugin/index.test': {
|
||||
chunkLoading: 'import',
|
||||
dependOn: ['polyfill/intl-segmenter', 'polyfill/ses', 'plugin'],
|
||||
import: resolve(rootPath, 'src/_plugin/index.test.tsx'),
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
new HTMLPlugin({
|
||||
template: join(rootPath, '.webpack', 'template.html'),
|
||||
inject: 'body',
|
||||
scriptLoading: 'module',
|
||||
minify: false,
|
||||
chunks: ['app', 'plugin', 'polyfill/intl-segmenter', 'polyfill/ses'],
|
||||
filename: 'index.html',
|
||||
}),
|
||||
new HTMLPlugin({
|
||||
template: join(rootPath, '.webpack', 'template.html'),
|
||||
inject: 'body',
|
||||
scriptLoading: 'module',
|
||||
minify: false,
|
||||
chunks: [
|
||||
'_plugin/index.test',
|
||||
'plugin',
|
||||
'polyfill/intl-segmenter',
|
||||
'polyfill/ses',
|
||||
],
|
||||
filename: '_plugin/index.html',
|
||||
}),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@affine/core",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"version": "0.7.0-canary.54",
|
||||
"version": "0.8.0-canary.21",
|
||||
"scripts": {
|
||||
"build": "yarn -T run build-core",
|
||||
"dev": "yarn -T run dev-core",
|
||||
@@ -11,7 +11,6 @@
|
||||
"dependencies": {
|
||||
"@affine-test/fixtures": "workspace:*",
|
||||
"@affine/component": "workspace:*",
|
||||
"@affine/copilot": "workspace:*",
|
||||
"@affine/debug": "workspace:*",
|
||||
"@affine/env": "workspace:*",
|
||||
"@affine/graphql": "workspace:*",
|
||||
@@ -19,29 +18,33 @@
|
||||
"@affine/jotai": "workspace:*",
|
||||
"@affine/templates": "workspace:*",
|
||||
"@affine/workspace": "workspace:*",
|
||||
"@blocksuite/block-std": "0.0.0-20230720073515-bea92e0f-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230720073515-bea92e0f-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230720073515-bea92e0f-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230720073515-bea92e0f-nightly",
|
||||
"@blocksuite/icons": "^2.1.27",
|
||||
"@blocksuite/lit": "0.0.0-20230720073515-bea92e0f-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230720073515-bea92e0f-nightly",
|
||||
"@blocksuite/block-std": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/icons": "^2.1.31",
|
||||
"@blocksuite/lit": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@dnd-kit/core": "^6.0.8",
|
||||
"@dnd-kit/sortable": "^7.0.2",
|
||||
"@emotion/cache": "^11.11.0",
|
||||
"@emotion/react": "^11.11.1",
|
||||
"@emotion/server": "^11.11.0",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@mui/material": "^5.14.2",
|
||||
"@mui/material": "^5.14.4",
|
||||
"@react-hookz/web": "^23.1.0",
|
||||
"@toeverything/components": "^0.0.10",
|
||||
"@types/lodash.throttle": "^4.1.7",
|
||||
"async-call-rpc": "^6.3.1",
|
||||
"cmdk": "^0.2.0",
|
||||
"css-spring": "^4.1.0",
|
||||
"cssnano": "^6.0.1",
|
||||
"graphql": "^16.7.1",
|
||||
"jotai": "^2.2.2",
|
||||
"jotai-devtools": "^0.6.0",
|
||||
"lit": "^2.7.6",
|
||||
"intl-segmenter-polyfill-rs": "^0.1.5",
|
||||
"jotai": "^2.3.1",
|
||||
"jotai-devtools": "^0.6.1",
|
||||
"lit": "^2.8.0",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"lottie-web": "^5.12.2",
|
||||
"mini-css-extract-plugin": "^2.7.6",
|
||||
"next-themes": "^0.2.1",
|
||||
@@ -50,29 +53,30 @@
|
||||
"react-dom": "18.2.0",
|
||||
"react-is": "18.2.0",
|
||||
"react-resizable-panels": "^0.0.54",
|
||||
"react-router-dom": "^6.14.2",
|
||||
"react-router-dom": "^6.15.0",
|
||||
"rxjs": "^7.8.1",
|
||||
"ses": "^0.18.5",
|
||||
"swr": "2.1.5",
|
||||
"ses": "^0.18.7",
|
||||
"swr": "2.2.1",
|
||||
"y-protocols": "^1.0.5",
|
||||
"yjs": "^13.6.7",
|
||||
"zod": "^3.21.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@perfsee/webpack": "^1.8.2",
|
||||
"@perfsee/webpack": "^1.8.4",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.10",
|
||||
"@sentry/webpack-plugin": "^2.5.0",
|
||||
"@sentry/webpack-plugin": "^2.6.2",
|
||||
"@svgr/webpack": "^8.0.1",
|
||||
"@swc/core": "^1.3.71",
|
||||
"@swc/core": "^1.3.76",
|
||||
"@types/webpack-env": "^1.18.1",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"css-loader": "^6.8.1",
|
||||
"express": "^4.18.2",
|
||||
"html-webpack-plugin": "^5.5.3",
|
||||
"raw-loader": "^4.0.2",
|
||||
"source-map-loader": "^4.0.1",
|
||||
"style-loader": "^3.3.3",
|
||||
"swc-loader": "^0.2.3",
|
||||
"swc-plugin-coverage-instrument": "^0.0.19",
|
||||
"swc-plugin-coverage-instrument": "^0.0.20",
|
||||
"thread-loader": "^4.0.2",
|
||||
"ts-node": "^10.9.1",
|
||||
"webpack": "^5.88.2",
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
{
|
||||
"name": "@affine/core",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"projectType": "application",
|
||||
"root": "apps/core",
|
||||
"sourceRoot": "apps/core/src",
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "nx:run-script",
|
||||
"dependsOn": ["^build"],
|
||||
"dependsOn": [
|
||||
{
|
||||
"projects": ["tag:plugin"],
|
||||
"target": "build",
|
||||
"params": "ignore"
|
||||
},
|
||||
"^build"
|
||||
],
|
||||
"inputs": [
|
||||
"{projectRoot}/.webpack/**/*",
|
||||
"{projectRoot}/**/*",
|
||||
"{projectRoot}/public/**/*",
|
||||
"{workspaceRoot}/packages/env/src/**/*",
|
||||
"{workspaceRoot}/packages/component/src/**/*",
|
||||
"{workspaceRoot}/packages/debug/src/**/*",
|
||||
"{workspaceRoot}/packages/graphql/src/**/*",
|
||||
@@ -45,7 +52,7 @@
|
||||
"options": {
|
||||
"script": "build"
|
||||
},
|
||||
"outputs": ["{projectRoot}/dist", "{projectRoot}/public/plugins"]
|
||||
"outputs": ["{projectRoot}/dist"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 132 KiB |
|
After Width: | Height: | Size: 280 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 330 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 723 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 224 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 971 KiB |
@@ -7,6 +7,9 @@ const PORT = process.env.PORT || 8080;
|
||||
app.use('/', express.static('dist'));
|
||||
|
||||
app.get('/*', (req, res) => {
|
||||
if (req.url.startsWith('/plugins')) {
|
||||
res.sendFile(req.url, { root: 'dist' });
|
||||
}
|
||||
res.sendFile('index.html', { root: 'dist' });
|
||||
});
|
||||
|
||||
|
||||
47
apps/core/src/_plugin/index.test.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { loadedPluginNameAtom, rootStore } from '@toeverything/infra/atom';
|
||||
import { use } from 'foxact/use';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { Provider } from 'jotai/react';
|
||||
import { StrictMode } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
import { _pluginNestedImportsMap } from '../bootstrap/plugins/setup';
|
||||
import { pluginRegisterPromise } from '../bootstrap/register-plugins';
|
||||
|
||||
async function main() {
|
||||
const { setup } = await import('../bootstrap/setup');
|
||||
await setup();
|
||||
const root = document.getElementById('app');
|
||||
assertExists(root);
|
||||
|
||||
const App = () => {
|
||||
use(pluginRegisterPromise);
|
||||
const plugins = useAtomValue(loadedPluginNameAtom);
|
||||
_pluginNestedImportsMap.forEach(value => {
|
||||
const exports = value.get('index.js');
|
||||
assertExists(exports);
|
||||
assertExists(exports?.get('entry'));
|
||||
});
|
||||
return (
|
||||
<div>
|
||||
<div data-plugins-load-status="success">
|
||||
Successfully loaded plugins:
|
||||
</div>
|
||||
{plugins.map(plugin => {
|
||||
return <div key={plugin}>{plugin}</div>;
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
createRoot(root).render(
|
||||
<StrictMode>
|
||||
<Provider store={rootStore}>
|
||||
<App />
|
||||
</Provider>
|
||||
</StrictMode>
|
||||
);
|
||||
}
|
||||
|
||||
await main();
|
||||
@@ -1,5 +1,5 @@
|
||||
import { DebugLogger } from '@affine/debug';
|
||||
import { initEmptyPage, initPageWithPreloading } from '@affine/env/blocksuite';
|
||||
import { initEmptyPage } from '@affine/env/blocksuite';
|
||||
import {
|
||||
DEFAULT_HELLO_WORLD_PAGE_ID_SUFFIX,
|
||||
DEFAULT_WORKSPACE_NAME,
|
||||
@@ -19,7 +19,8 @@ import {
|
||||
import { getOrCreateWorkspace } from '@affine/workspace/manager';
|
||||
import { createIndexedDBDownloadProvider } from '@affine/workspace/providers';
|
||||
import { nanoid } from '@blocksuite/store';
|
||||
import { useStaticBlockSuiteWorkspace } from '@toeverything/plugin-infra/__internal__/react';
|
||||
import { useStaticBlockSuiteWorkspace } from '@toeverything/infra/__internal__/react';
|
||||
import { buildShowcaseWorkspace } from '@toeverything/infra/blocksuite';
|
||||
|
||||
import {
|
||||
BlockSuitePageList,
|
||||
@@ -41,21 +42,18 @@ export const LocalAdapter: WorkspaceAdapter<WorkspaceFlavour.LOCAL> = {
|
||||
WorkspaceFlavour.LOCAL
|
||||
);
|
||||
blockSuiteWorkspace.meta.setName(DEFAULT_WORKSPACE_NAME);
|
||||
const page = blockSuiteWorkspace.createPage({
|
||||
id: `${blockSuiteWorkspace.id}-${DEFAULT_HELLO_WORLD_PAGE_ID_SUFFIX}`,
|
||||
});
|
||||
if (runtimeConfig.enablePreloading) {
|
||||
initPageWithPreloading(page).catch(err => {
|
||||
buildShowcaseWorkspace(blockSuiteWorkspace).catch(err => {
|
||||
logger.error('init page with preloading failed', err);
|
||||
});
|
||||
} else {
|
||||
const page = blockSuiteWorkspace.createPage({
|
||||
id: `${blockSuiteWorkspace.id}-${DEFAULT_HELLO_WORLD_PAGE_ID_SUFFIX}`,
|
||||
});
|
||||
initEmptyPage(page).catch(error => {
|
||||
logger.error('init page with empty failed', error);
|
||||
});
|
||||
}
|
||||
blockSuiteWorkspace.setPageMeta(page.id, {
|
||||
jumpOnce: true,
|
||||
});
|
||||
const provider = createIndexedDBDownloadProvider(
|
||||
blockSuiteWorkspace.id,
|
||||
blockSuiteWorkspace.doc,
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import '@affine/component/theme/global.css';
|
||||
import '@affine/component/theme/theme.css';
|
||||
import '@toeverything/components/style.css';
|
||||
|
||||
import { AffineContext } from '@affine/component/context';
|
||||
import { WorkspaceFallback } from '@affine/component/workspace';
|
||||
import { createI18n, setUpLanguage } from '@affine/i18n';
|
||||
import { CacheProvider } from '@emotion/react';
|
||||
import { use } from 'foxact/use';
|
||||
import type { PropsWithChildren, ReactElement } from 'react';
|
||||
import { lazy, memo, Suspense, useEffect } from 'react';
|
||||
import { lazy, memo, Suspense } from 'react';
|
||||
import { RouterProvider } from 'react-router-dom';
|
||||
|
||||
import { router } from './router';
|
||||
import createEmotionCache from './utils/create-emotion-cache';
|
||||
|
||||
const i18n = createI18n();
|
||||
const cache = createEmotionCache();
|
||||
|
||||
const DevTools = lazy(() =>
|
||||
@@ -32,14 +32,19 @@ const future = {
|
||||
v7_startTransition: true,
|
||||
} as const;
|
||||
|
||||
export const App = memo(function App() {
|
||||
useEffect(() => {
|
||||
async function loadLanguage() {
|
||||
if (environment.isBrowser) {
|
||||
const { createI18n, setUpLanguage } = await import('@affine/i18n');
|
||||
const i18n = createI18n();
|
||||
document.documentElement.lang = i18n.language;
|
||||
// todo(himself65): this is a hack, we should use a better way to set the language
|
||||
setUpLanguage(i18n)?.catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
}, []);
|
||||
await setUpLanguage(i18n);
|
||||
}
|
||||
}
|
||||
|
||||
const languageLoadingPromise = loadLanguage().catch(console.error);
|
||||
|
||||
export const App = memo(function App() {
|
||||
use(languageLoadingPromise);
|
||||
return (
|
||||
<CacheProvider value={cache}>
|
||||
<AffineContext>
|
||||
|
||||
@@ -70,12 +70,16 @@ export const guideOnboardingAtom = atom<
|
||||
}));
|
||||
}
|
||||
);
|
||||
|
||||
export const guideDownloadClientTipAtom = atom<
|
||||
Guide['downloadClientTip'],
|
||||
[open: boolean],
|
||||
void
|
||||
>(
|
||||
get => {
|
||||
if (environment.isDesktop) {
|
||||
return false;
|
||||
}
|
||||
return get(guidePrimitiveAtom).downloadClientTip;
|
||||
},
|
||||
(_, set, open) => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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';
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { PrimitiveAtom } from 'jotai';
|
||||
import { atom } from 'jotai';
|
||||
import { atomFamily, atomWithStorage } from 'jotai/utils';
|
||||
import type { AtomFamily } from 'jotai/vanilla/utils/atomFamily';
|
||||
|
||||
import type { CreateWorkspaceMode } from '../components/affine/create-workspace-modal';
|
||||
import type { SettingProps } from '../components/affine/setting-modal';
|
||||
@@ -59,19 +61,16 @@ const defaultPageSetting = {
|
||||
mode: 'page',
|
||||
} satisfies PageLocalSetting;
|
||||
|
||||
export const pageSettingFamily = atomFamily((pageId: string) =>
|
||||
export const pageSettingFamily: AtomFamily<
|
||||
string,
|
||||
PrimitiveAtom<PageLocalSetting>
|
||||
> = atomFamily((pageId: string) =>
|
||||
atom(
|
||||
get =>
|
||||
get(pageSettingsBaseAtom)[pageId] ?? {
|
||||
...defaultPageSetting,
|
||||
},
|
||||
(
|
||||
get,
|
||||
set,
|
||||
patch:
|
||||
| Partial<PageLocalSetting>
|
||||
| ((prevSetting: PageLocalSetting | undefined) => void)
|
||||
) => {
|
||||
(get, set, patch) => {
|
||||
set(recentPageSettingsBaseAtom, ids => {
|
||||
// pick 3 recent page ids
|
||||
return [...new Set([pageId, ...ids]).values()].slice(0, 3);
|
||||
@@ -93,7 +92,7 @@ export const pageSettingFamily = atomFamily((pageId: string) =>
|
||||
|
||||
export const setPageModeAtom = atom(
|
||||
void 0,
|
||||
(get, set, pageId: string, mode: PageMode) => {
|
||||
(_, set, pageId: string, mode: PageMode) => {
|
||||
set(pageSettingFamily(pageId), { mode });
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { currentPageIdAtom } from '@toeverything/plugin-infra/atom';
|
||||
import { currentPageIdAtom } from '@toeverything/infra/atom';
|
||||
import { atom } from 'jotai/vanilla';
|
||||
|
||||
import { pageSettingFamily } from './index';
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
import { migrateToSubdoc } from '@affine/env/blocksuite';
|
||||
import { setupGlobal } from '@affine/env/global';
|
||||
import type {
|
||||
LocalIndexedDBDownloadProvider,
|
||||
WorkspaceAdapter,
|
||||
} from '@affine/env/workspace';
|
||||
import { WorkspaceFlavour, WorkspaceVersion } from '@affine/env/workspace';
|
||||
import type { RootWorkspaceMetadata } from '@affine/workspace/atom';
|
||||
import {
|
||||
type RootWorkspaceMetadataV2,
|
||||
rootWorkspacesMetadataAtom,
|
||||
workspaceAdaptersAtom,
|
||||
} from '@affine/workspace/atom';
|
||||
import {
|
||||
migrateLocalBlobStorage,
|
||||
upgradeV1ToV2,
|
||||
} from '@affine/workspace/migration';
|
||||
import { createIndexedDBDownloadProvider } from '@affine/workspace/providers';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { rootStore } from '@toeverything/plugin-infra/atom';
|
||||
|
||||
import { WorkspaceAdapters } from '../adapters/workspace';
|
||||
|
||||
console.log('setup global');
|
||||
setupGlobal();
|
||||
|
||||
rootStore.set(
|
||||
workspaceAdaptersAtom,
|
||||
WorkspaceAdapters as Record<
|
||||
WorkspaceFlavour,
|
||||
WorkspaceAdapter<WorkspaceFlavour>
|
||||
>
|
||||
);
|
||||
|
||||
const value = localStorage.getItem('jotai-workspaces');
|
||||
if (value) {
|
||||
try {
|
||||
const metadata = JSON.parse(value) as RootWorkspaceMetadata[];
|
||||
const promises: Promise<void>[] = [];
|
||||
const newMetadata = [...metadata];
|
||||
metadata.forEach(oldMeta => {
|
||||
if (!('version' in oldMeta)) {
|
||||
const adapter = WorkspaceAdapters[oldMeta.flavour];
|
||||
assertExists(adapter);
|
||||
const upgrade = async () => {
|
||||
const workspace = await adapter.CRUD.get(oldMeta.id);
|
||||
if (!workspace) {
|
||||
console.warn('cannot find workspace', oldMeta.id);
|
||||
return;
|
||||
}
|
||||
if (workspace.flavour !== WorkspaceFlavour.LOCAL) {
|
||||
console.warn('not supported');
|
||||
return;
|
||||
}
|
||||
const doc = workspace.blockSuiteWorkspace.doc;
|
||||
const provider = createIndexedDBDownloadProvider(workspace.id, doc, {
|
||||
awareness: workspace.blockSuiteWorkspace.awarenessStore.awareness,
|
||||
}) as LocalIndexedDBDownloadProvider;
|
||||
provider.sync();
|
||||
await provider.whenReady;
|
||||
const newDoc = migrateToSubdoc(doc);
|
||||
if (doc === newDoc) {
|
||||
console.log('doc not changed');
|
||||
return;
|
||||
}
|
||||
const newWorkspace = upgradeV1ToV2(workspace);
|
||||
|
||||
const newId = await adapter.CRUD.create(
|
||||
newWorkspace.blockSuiteWorkspace
|
||||
);
|
||||
|
||||
await adapter.CRUD.delete(workspace as any);
|
||||
console.log('migrated', oldMeta.id, newId);
|
||||
const index = newMetadata.findIndex(meta => meta.id === oldMeta.id);
|
||||
newMetadata[index] = {
|
||||
...oldMeta,
|
||||
id: newId,
|
||||
version: WorkspaceVersion.SubDoc,
|
||||
};
|
||||
await migrateLocalBlobStorage(workspace.id, newId);
|
||||
};
|
||||
|
||||
// create a new workspace and push it to metadata
|
||||
promises.push(upgrade());
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(promises)
|
||||
.then(() => {
|
||||
console.log('migration done');
|
||||
})
|
||||
.catch(() => {
|
||||
console.error('migration failed');
|
||||
})
|
||||
.finally(() => {
|
||||
localStorage.setItem('jotai-workspaces', JSON.stringify(newMetadata));
|
||||
window.dispatchEvent(new CustomEvent('migration-done'));
|
||||
window.$migrationDone = true;
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('error when migrating data', e);
|
||||
}
|
||||
}
|
||||
|
||||
const createFirst = (): RootWorkspaceMetadataV2[] => {
|
||||
const Plugins = Object.values(WorkspaceAdapters).sort(
|
||||
(a, b) => a.loadPriority - b.loadPriority
|
||||
);
|
||||
|
||||
return Plugins.flatMap(Plugin => {
|
||||
return Plugin.Events['app:init']?.().map(
|
||||
id =>
|
||||
({
|
||||
id,
|
||||
flavour: Plugin.flavour,
|
||||
// new workspace should all support sub-doc feature
|
||||
version: WorkspaceVersion.SubDoc,
|
||||
}) satisfies RootWorkspaceMetadataV2
|
||||
);
|
||||
}).filter((ids): ids is RootWorkspaceMetadataV2 => !!ids);
|
||||
};
|
||||
|
||||
await rootStore
|
||||
.get(rootWorkspacesMetadataAtom)
|
||||
.then(meta => {
|
||||
if (meta.length === 0 && localStorage.getItem('is-first-open') === null) {
|
||||
const result = createFirst();
|
||||
console.info('create first workspace', result);
|
||||
localStorage.setItem('is-first-open', 'false');
|
||||
rootStore.set(rootWorkspacesMetadataAtom, result).catch(console.error);
|
||||
}
|
||||
})
|
||||
.catch(console.error);
|
||||
86
apps/core/src/bootstrap/plugins/endowments/fercher.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
export interface FetchOptions {
|
||||
fetch?: typeof fetch;
|
||||
signal?: AbortSignal;
|
||||
|
||||
normalizeURL?(url: string): string;
|
||||
|
||||
/**
|
||||
* Virtualize a url
|
||||
* @param url URL to be rewrite
|
||||
* @param direction Direction of this rewrite.
|
||||
* 'in' means the url is from the outside world and should be virtualized.
|
||||
* 'out' means the url is from the inside world and should be de-virtualized to fetch the real target.
|
||||
*/
|
||||
rewriteURL?(url: string, direction: 'in' | 'out'): string;
|
||||
|
||||
replaceRequest?(request: Request): Request | PromiseLike<Request>;
|
||||
|
||||
replaceResponse?(response: Response): Response | PromiseLike<Response>;
|
||||
|
||||
canConnect?(url: string): boolean | PromiseLike<boolean>;
|
||||
}
|
||||
|
||||
export function createFetch(options: FetchOptions) {
|
||||
const {
|
||||
fetch: _fetch = fetch,
|
||||
signal,
|
||||
rewriteURL,
|
||||
replaceRequest,
|
||||
replaceResponse,
|
||||
canConnect,
|
||||
normalizeURL,
|
||||
} = options;
|
||||
|
||||
return async function fetch(input: RequestInfo, init?: RequestInit) {
|
||||
let request = new Request(input, {
|
||||
...init,
|
||||
signal: getMergedSignal(init?.signal, signal) || null,
|
||||
});
|
||||
|
||||
if (normalizeURL) request = new Request(normalizeURL(request.url), request);
|
||||
if (canConnect && !(await canConnect(request.url)))
|
||||
throw new TypeError('Failed to fetch');
|
||||
if (rewriteURL)
|
||||
request = new Request(rewriteURL(request.url, 'out'), request);
|
||||
if (replaceRequest) request = await replaceRequest(request);
|
||||
|
||||
let response = await _fetch(request);
|
||||
|
||||
if (rewriteURL) {
|
||||
const { url, redirected, type } = response;
|
||||
// Note: Response constructor does not allow us to set the url of a response.
|
||||
// we have to define the own property on it. This is not a good simulation.
|
||||
// To prevent get the original url by Response.prototype.[[get url]].call(response)
|
||||
// we copy a response and set it's url to empty.
|
||||
response = new Response(response.body, response);
|
||||
Object.defineProperties(response, {
|
||||
url: { value: url, configurable: true },
|
||||
redirected: { value: redirected, configurable: true },
|
||||
type: { value: type, configurable: true },
|
||||
});
|
||||
Object.defineProperty(response, 'url', {
|
||||
configurable: true,
|
||||
value: rewriteURL(url, 'in'),
|
||||
});
|
||||
}
|
||||
if (replaceResponse) response = await replaceResponse(response);
|
||||
return response;
|
||||
};
|
||||
}
|
||||
|
||||
function getMergedSignal(
|
||||
signal: AbortSignal | undefined | null,
|
||||
signal2: AbortSignal | undefined | null
|
||||
) {
|
||||
if (!signal) return signal2;
|
||||
if (!signal2) return signal;
|
||||
|
||||
const abortController = new AbortController();
|
||||
signal.addEventListener('abort', () => abortController.abort(), {
|
||||
once: true,
|
||||
});
|
||||
signal2.addEventListener('abort', () => abortController.abort(), {
|
||||
once: true,
|
||||
});
|
||||
return abortController.signal;
|
||||
}
|
||||
110
apps/core/src/bootstrap/plugins/endowments/timer.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
type Handler = (...args: any[]) => void;
|
||||
|
||||
export interface Timers {
|
||||
setTimeout: (handler: Handler, timeout?: number, ...args: any[]) => number;
|
||||
clearTimeout: (handle: number) => void;
|
||||
setInterval: (handler: Handler, timeout?: number, ...args: any[]) => number;
|
||||
clearInterval: (handle: number) => void;
|
||||
requestAnimationFrame: (callback: Handler) => number;
|
||||
cancelAnimationFrame: (handle: number) => void;
|
||||
requestIdleCallback?: typeof window.requestIdleCallback | undefined;
|
||||
cancelIdleCallback?: typeof window.cancelIdleCallback | undefined;
|
||||
queueMicrotask: typeof window.queueMicrotask;
|
||||
}
|
||||
|
||||
export function createTimers(
|
||||
abortSignal: AbortSignal,
|
||||
originalTimes: Timers = {
|
||||
requestAnimationFrame,
|
||||
cancelAnimationFrame,
|
||||
requestIdleCallback:
|
||||
typeof requestIdleCallback === 'function'
|
||||
? requestIdleCallback
|
||||
: undefined,
|
||||
cancelIdleCallback:
|
||||
typeof cancelIdleCallback === 'function' ? cancelIdleCallback : undefined,
|
||||
setTimeout,
|
||||
clearTimeout,
|
||||
setInterval,
|
||||
clearInterval,
|
||||
queueMicrotask,
|
||||
}
|
||||
): Timers {
|
||||
const {
|
||||
requestAnimationFrame: _requestAnimationFrame,
|
||||
cancelAnimationFrame: _cancelAnimationFrame,
|
||||
setInterval: _setInterval,
|
||||
clearInterval: _clearInterval,
|
||||
setTimeout: _setTimeout,
|
||||
clearTimeout: _clearTimeout,
|
||||
cancelIdleCallback: _cancelIdleCallback,
|
||||
requestIdleCallback: _requestIdleCallback,
|
||||
queueMicrotask: _queueMicrotask,
|
||||
} = originalTimes;
|
||||
|
||||
const interval_timer_id: number[] = [];
|
||||
const idle_id: number[] = [];
|
||||
const raf_id: number[] = [];
|
||||
|
||||
abortSignal.addEventListener(
|
||||
'abort',
|
||||
() => {
|
||||
raf_id.forEach(_cancelAnimationFrame);
|
||||
interval_timer_id.forEach(_clearInterval);
|
||||
_cancelIdleCallback && idle_id.forEach(_cancelIdleCallback);
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
|
||||
return {
|
||||
// id is a positive number, it never repeats.
|
||||
requestAnimationFrame(callback) {
|
||||
raf_id[raf_id.length] = _requestAnimationFrame(callback);
|
||||
return raf_id.length;
|
||||
},
|
||||
cancelAnimationFrame(handle) {
|
||||
const id = raf_id[handle - 1];
|
||||
if (!id) return;
|
||||
_cancelAnimationFrame(id);
|
||||
},
|
||||
setInterval(handler, timeout) {
|
||||
interval_timer_id[interval_timer_id.length] = (_setInterval as any)(
|
||||
handler,
|
||||
timeout
|
||||
);
|
||||
return interval_timer_id.length;
|
||||
},
|
||||
clearInterval(id) {
|
||||
if (!id) return;
|
||||
const handle = interval_timer_id[id - 1];
|
||||
if (!handle) return;
|
||||
_clearInterval(handle);
|
||||
},
|
||||
setTimeout(handler, timeout) {
|
||||
idle_id[idle_id.length] = (_setTimeout as any)(handler, timeout);
|
||||
return idle_id.length;
|
||||
},
|
||||
clearTimeout(id) {
|
||||
if (!id) return;
|
||||
const handle = idle_id[id - 1];
|
||||
if (!handle) return;
|
||||
_clearTimeout(handle);
|
||||
},
|
||||
requestIdleCallback: _requestIdleCallback
|
||||
? function requestIdleCallback(callback, options) {
|
||||
idle_id[idle_id.length] = _requestIdleCallback(callback, options);
|
||||
return idle_id.length;
|
||||
}
|
||||
: undefined,
|
||||
cancelIdleCallback: _cancelIdleCallback
|
||||
? function cancelIdleCallback(handle) {
|
||||
const id = idle_id[handle - 1];
|
||||
if (!id) return;
|
||||
_cancelIdleCallback(id);
|
||||
}
|
||||
: undefined,
|
||||
queueMicrotask(callback) {
|
||||
_queueMicrotask(() => abortSignal.aborted || callback());
|
||||
},
|
||||
};
|
||||
}
|
||||
22
apps/core/src/bootstrap/plugins/setup-imports-map.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
type ExportsPromiseOrExports =
|
||||
| Promise<{ [key: string]: any }>
|
||||
| { [key: string]: any };
|
||||
|
||||
export async function setupImportsMap(
|
||||
map: Map<string, Map<string, any>>,
|
||||
imports: Record<string, ExportsPromiseOrExports>
|
||||
) {
|
||||
for (const [key, value] of Object.entries(imports)) {
|
||||
let module: { [key: string]: any };
|
||||
if (value instanceof Promise) {
|
||||
module = await value;
|
||||
} else {
|
||||
module = value;
|
||||
}
|
||||
const moduleMap = new Map();
|
||||
map.set(key, moduleMap);
|
||||
for (const [exportName, exportValue] of Object.entries(module)) {
|
||||
moduleMap.set(exportName, exportValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
547
apps/core/src/bootstrap/plugins/setup.ts
Normal file
@@ -0,0 +1,547 @@
|
||||
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,
|
||||
rootStore,
|
||||
} from '@toeverything/infra/atom';
|
||||
import { atom } from 'jotai';
|
||||
import { Provider } from 'jotai/react';
|
||||
import { createElement, type PropsWithChildren } from 'react';
|
||||
|
||||
import { createFetch } from './endowments/fercher';
|
||||
import { createTimers } from './endowments/timer';
|
||||
import { setupImportsMap } from './setup-imports-map';
|
||||
|
||||
const dynamicImportKey = '$h_import';
|
||||
|
||||
const permissionLogger = new DebugLogger('plugins:permission');
|
||||
const importLogger = new DebugLogger('plugins:import');
|
||||
|
||||
const pushLayoutAtom = atom<
|
||||
null,
|
||||
// fixme: check plugin name here
|
||||
[pluginName: string, create: (root: HTMLElement) => () => void],
|
||||
void
|
||||
>(null, (_, set, pluginName, callback) => {
|
||||
set(pluginWindowAtom, items => ({
|
||||
...items,
|
||||
[pluginName]: callback,
|
||||
}));
|
||||
set(contentLayoutAtom, layout => {
|
||||
if (layout === 'editor') {
|
||||
return {
|
||||
direction: 'horizontal',
|
||||
first: 'editor',
|
||||
second: pluginName,
|
||||
splitPercentage: 70,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...layout,
|
||||
direction: 'horizontal',
|
||||
first: 'editor',
|
||||
second: {
|
||||
direction: 'horizontal',
|
||||
// fixme: incorrect type here
|
||||
first: layout.second,
|
||||
second: pluginName,
|
||||
splitPercentage: 70,
|
||||
},
|
||||
} as 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 => {
|
||||
if (layout === 'editor') {
|
||||
return 'editor';
|
||||
} else {
|
||||
if (typeof layout === 'string') {
|
||||
return layout as ExpectedLayout;
|
||||
}
|
||||
if (layout.first === id) {
|
||||
return layout.second;
|
||||
} else if (layout.second === id) {
|
||||
return layout.first;
|
||||
} else {
|
||||
return removeLayout(layout.second);
|
||||
}
|
||||
}
|
||||
};
|
||||
set(contentLayoutAtom, layout => {
|
||||
if (layout === 'editor') {
|
||||
return 'editor';
|
||||
} else {
|
||||
if (typeof layout === 'string') {
|
||||
return layout as ExpectedLayout;
|
||||
}
|
||||
if (layout.first === id) {
|
||||
return layout.second as ExpectedLayout;
|
||||
} else if (layout.second === id) {
|
||||
return layout.first as ExpectedLayout;
|
||||
} else {
|
||||
return removeLayout(layout.second) as ExpectedLayout;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// module -> importName -> updater[]
|
||||
export const _rootImportsMap = new Map<string, Map<string, any>>();
|
||||
const rootImportsMapSetupPromise = setupImportsMap(_rootImportsMap, {
|
||||
react: import('react'),
|
||||
'react/jsx-runtime': import('react/jsx-runtime'),
|
||||
'react-dom': import('react-dom'),
|
||||
'react-dom/client': import('react-dom/client'),
|
||||
jotai: import('jotai'),
|
||||
'jotai/utils': import('jotai/utils'),
|
||||
swr: import('swr'),
|
||||
'@affine/component': import('@affine/component'),
|
||||
'@blocksuite/icons': import('@blocksuite/icons'),
|
||||
'@affine/sdk/entry': {
|
||||
rootStore: rootStore,
|
||||
currentWorkspaceAtom: currentWorkspaceAtom,
|
||||
currentPageAtom: currentPageAtom,
|
||||
pushLayoutAtom: pushLayoutAtom,
|
||||
deleteLayoutAtom: deleteLayoutAtom,
|
||||
},
|
||||
'@blocksuite/global/utils': import('@blocksuite/global/utils'),
|
||||
'@toeverything/infra/atom': import('@toeverything/infra/atom'),
|
||||
'@toeverything/components/button': import('@toeverything/components/button'),
|
||||
});
|
||||
|
||||
// pluginName -> module -> importName -> updater[]
|
||||
export const _pluginNestedImportsMap = new Map<
|
||||
string,
|
||||
Map<string, Map<string, any>>
|
||||
>();
|
||||
|
||||
const pluginImportsFunctionMap = new Map<string, (imports: any) => void>();
|
||||
export 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>
|
||||
>();
|
||||
|
||||
export 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>();
|
||||
|
||||
export 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;
|
||||
};
|
||||
|
||||
export 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 entryLogger = new DebugLogger('plugin:entry');
|
||||
|
||||
export const evaluatePluginEntry = (pluginName: string) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const currentImportMap = _pluginNestedImportsMap.get(pluginName)!;
|
||||
const 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);
|
||||
};
|
||||
@@ -1,200 +1,125 @@
|
||||
/// <reference types="@types/webpack-env" />
|
||||
import 'ses';
|
||||
|
||||
import * as AFFiNEComponent from '@affine/component';
|
||||
import * as BlockSuiteBlocksStd from '@blocksuite/blocks/std';
|
||||
import { DisposableGroup } from '@blocksuite/global/utils';
|
||||
import * as BlockSuiteGlobalUtils from '@blocksuite/global/utils';
|
||||
import * as Icons from '@blocksuite/icons';
|
||||
import * as Atom from '@toeverything/plugin-infra/atom';
|
||||
import { DebugLogger } from '@affine/debug';
|
||||
import {
|
||||
editorItemsAtom,
|
||||
headerItemsAtom,
|
||||
registeredPluginAtom,
|
||||
rootStore,
|
||||
windowItemsAtom,
|
||||
} from '@toeverything/plugin-infra/atom';
|
||||
import type {
|
||||
CallbackMap,
|
||||
PluginContext,
|
||||
} from '@toeverything/plugin-infra/entry';
|
||||
import * as Jotai from 'jotai';
|
||||
import { Provider } from 'jotai/react';
|
||||
import * as JotaiUtils from 'jotai/utils';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import * as React from 'react';
|
||||
import * as ReactJSXRuntime from 'react/jsx-runtime';
|
||||
import * as ReactDom from 'react-dom';
|
||||
import * as ReactDomClient from 'react-dom/client';
|
||||
builtinPluginPaths,
|
||||
enabledPluginAtom,
|
||||
invokeCleanup,
|
||||
pluginPackageJson,
|
||||
} from '@toeverything/infra/__internal__/plugin';
|
||||
import { loadedPluginNameAtom, rootStore } from '@toeverything/infra/atom';
|
||||
import { packageJsonOutputSchema } from '@toeverything/infra/type';
|
||||
import type { z } from 'zod';
|
||||
|
||||
if (!process.env.COVERAGE) {
|
||||
lockdown({
|
||||
evalTaming: 'unsafeEval',
|
||||
overrideTaming: 'severe',
|
||||
consoleTaming: 'unsafe',
|
||||
errorTaming: 'unsafe',
|
||||
errorTrapping: 'platform',
|
||||
unhandledRejectionTrapping: 'report',
|
||||
});
|
||||
import { evaluatePluginEntry, setupPluginCode } from './plugins/setup';
|
||||
|
||||
const logger = new DebugLogger('register-plugins');
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line no-var
|
||||
var __pluginPackageJson__: unknown[];
|
||||
}
|
||||
|
||||
const PluginProvider = ({ children }: PropsWithChildren) =>
|
||||
React.createElement(
|
||||
Provider,
|
||||
{
|
||||
store: rootStore,
|
||||
},
|
||||
children
|
||||
);
|
||||
Object.defineProperty(globalThis, '__pluginPackageJson__', {
|
||||
get() {
|
||||
return rootStore.get(pluginPackageJson);
|
||||
},
|
||||
});
|
||||
|
||||
const customRequire = (id: string) => {
|
||||
if (id === '@toeverything/plugin-infra/atom') {
|
||||
return Atom;
|
||||
}
|
||||
if (id === 'react') {
|
||||
return React;
|
||||
}
|
||||
if (id === 'react/jsx-runtime') {
|
||||
return ReactJSXRuntime;
|
||||
}
|
||||
if (id === 'react-dom') {
|
||||
return ReactDom;
|
||||
}
|
||||
if (id === 'react-dom/client') {
|
||||
return ReactDomClient;
|
||||
}
|
||||
if (id === '@blocksuite/icons') {
|
||||
return Icons;
|
||||
}
|
||||
if (id === '@affine/component') {
|
||||
return AFFiNEComponent;
|
||||
}
|
||||
if (id === '@blocksuite/blocks/std') {
|
||||
return BlockSuiteBlocksStd;
|
||||
}
|
||||
if (id === '@blocksuite/global/utils') {
|
||||
return BlockSuiteGlobalUtils;
|
||||
}
|
||||
if (id === 'jotai') {
|
||||
return Jotai;
|
||||
}
|
||||
if (id === 'jotai/utils') {
|
||||
return JotaiUtils;
|
||||
}
|
||||
throw new Error(`Cannot find module '${id}'`);
|
||||
};
|
||||
|
||||
const createGlobalThis = () => {
|
||||
return {
|
||||
process: Object.freeze({
|
||||
env: {
|
||||
NODE_ENV: process.env.NODE_ENV,
|
||||
},
|
||||
}),
|
||||
// UNSAFE: React will read `window` and `document`
|
||||
window,
|
||||
document,
|
||||
navigator,
|
||||
userAgent: navigator.userAgent,
|
||||
// todo: permission control
|
||||
fetch: globalThis.fetch,
|
||||
|
||||
// fixme: use our own db api
|
||||
indexedDB: globalThis.indexedDB,
|
||||
IDBRequest: globalThis.IDBRequest,
|
||||
IDBDatabase: globalThis.IDBDatabase,
|
||||
IDBCursorWithValue: globalThis.IDBCursorWithValue,
|
||||
IDBFactory: globalThis.IDBFactory,
|
||||
IDBKeyRange: globalThis.IDBKeyRange,
|
||||
IDBOpenDBRequest: globalThis.IDBOpenDBRequest,
|
||||
IDBTransaction: globalThis.IDBTransaction,
|
||||
IDBObjectStore: globalThis.IDBObjectStore,
|
||||
IDBIndex: globalThis.IDBIndex,
|
||||
IDBCursor: globalThis.IDBCursor,
|
||||
IDBVersionChangeEvent: globalThis.IDBVersionChangeEvent,
|
||||
|
||||
exports: {},
|
||||
console: globalThis.console,
|
||||
require: customRequire,
|
||||
};
|
||||
};
|
||||
|
||||
const group = new DisposableGroup();
|
||||
const pluginList = (await (
|
||||
await fetch(new URL(`./plugins/plugin-list.json`, window.location.origin))
|
||||
).json()) as { name: string; assets: string[]; release: boolean }[];
|
||||
|
||||
await Promise.all(
|
||||
pluginList.map(({ name: plugin, release, assets }) => {
|
||||
if (!release && process.env.NODE_ENV !== 'development') {
|
||||
return Promise.resolve();
|
||||
rootStore.sub(enabledPluginAtom, () => {
|
||||
const added = new Set<string>();
|
||||
const removed = new Set<string>();
|
||||
const enabledPlugin = new Set(rootStore.get(enabledPluginAtom));
|
||||
enabledPlugin.forEach(pluginName => {
|
||||
if (!enabledPluginSet.has(pluginName)) {
|
||||
added.add(pluginName);
|
||||
}
|
||||
const pluginCompartment = new Compartment(createGlobalThis(), {});
|
||||
const pluginGlobalThis = pluginCompartment.globalThis;
|
||||
const baseURL = new URL(`./plugins/${plugin}/`, window.location.origin);
|
||||
const entryURL = new URL('index.js', baseURL);
|
||||
rootStore.set(registeredPluginAtom, prev => [...prev, plugin]);
|
||||
return fetch(entryURL).then(async res => {
|
||||
if (assets.length > 0) {
|
||||
await Promise.all(
|
||||
assets.map(async asset => {
|
||||
if (asset.endsWith('.css')) {
|
||||
const res = await fetch(new URL(asset, baseURL));
|
||||
if (res.ok) {
|
||||
// todo: how to put css file into sandbox?
|
||||
return res.text().then(text => {
|
||||
const style = document.createElement('style');
|
||||
style.setAttribute('plugin-id', plugin);
|
||||
style.textContent = text;
|
||||
document.head.appendChild(style);
|
||||
});
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
const codeText = await res.text();
|
||||
pluginCompartment.evaluate(codeText, {
|
||||
__evadeHtmlCommentTest__: true,
|
||||
});
|
||||
pluginGlobalThis.__INTERNAL__ENTRY = {
|
||||
register: (part, callback) => {
|
||||
if (part === 'headerItem') {
|
||||
rootStore.set(headerItemsAtom, items => ({
|
||||
...items,
|
||||
[plugin]: callback as CallbackMap['headerItem'],
|
||||
}));
|
||||
} else if (part === 'editor') {
|
||||
rootStore.set(editorItemsAtom, items => ({
|
||||
...items,
|
||||
[plugin]: callback as CallbackMap['editor'],
|
||||
}));
|
||||
} else if (part === 'window') {
|
||||
rootStore.set(windowItemsAtom, items => ({
|
||||
...items,
|
||||
[plugin]: callback as CallbackMap['window'],
|
||||
}));
|
||||
} else {
|
||||
throw new Error(`Unknown part: ${part}`);
|
||||
}
|
||||
},
|
||||
utils: {
|
||||
PluginProvider,
|
||||
},
|
||||
} satisfies PluginContext;
|
||||
const dispose = pluginCompartment.evaluate(
|
||||
'exports.entry(__INTERNAL__ENTRY)'
|
||||
);
|
||||
if (typeof dispose !== 'function') {
|
||||
throw new Error('Plugin entry must return a function');
|
||||
}
|
||||
pluginGlobalThis.__INTERNAL__ENTRY = undefined;
|
||||
group.add(dispose);
|
||||
});
|
||||
})
|
||||
);
|
||||
});
|
||||
enabledPluginSet.forEach(pluginName => {
|
||||
if (!enabledPlugin.has(pluginName)) {
|
||||
removed.add(pluginName);
|
||||
}
|
||||
});
|
||||
// update plugins
|
||||
enabledPluginSet.clear();
|
||||
enabledPlugin.forEach(pluginName => {
|
||||
enabledPluginSet.add(pluginName);
|
||||
});
|
||||
added.forEach(pluginName => {
|
||||
evaluatePluginEntry(pluginName);
|
||||
});
|
||||
removed.forEach(pluginName => {
|
||||
invokeCleanup(pluginName);
|
||||
});
|
||||
});
|
||||
const enabledPluginSet = new Set(rootStore.get(enabledPluginAtom));
|
||||
const loadedAssets = new Set<string>();
|
||||
|
||||
console.log('register plugins finished');
|
||||
// we will load all plugins in parallel from builtinPlugins
|
||||
export const pluginRegisterPromise = 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) => {
|
||||
// todo(himself65): add assets into shadow dom
|
||||
if (loadedAssets.has(asset)) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
if (asset.endsWith('.css')) {
|
||||
loadedAssets.add(asset);
|
||||
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');
|
||||
});
|
||||
|
||||
188
apps/core/src/bootstrap/setup.ts
Normal file
@@ -0,0 +1,188 @@
|
||||
import {
|
||||
migrateDatabaseBlockTo3,
|
||||
migrateToSubdoc,
|
||||
} from '@affine/env/blocksuite';
|
||||
import { setupGlobal } from '@affine/env/global';
|
||||
import type {
|
||||
LocalIndexedDBDownloadProvider,
|
||||
WorkspaceAdapter,
|
||||
} from '@affine/env/workspace';
|
||||
import { WorkspaceFlavour, WorkspaceVersion } from '@affine/env/workspace';
|
||||
import type { RootWorkspaceMetadata } from '@affine/workspace/atom';
|
||||
import {
|
||||
type RootWorkspaceMetadataV2,
|
||||
rootWorkspacesMetadataAtom,
|
||||
workspaceAdaptersAtom,
|
||||
} from '@affine/workspace/atom';
|
||||
import { globalBlockSuiteSchema } from '@affine/workspace/manager';
|
||||
import {
|
||||
migrateLocalBlobStorage,
|
||||
upgradeV1ToV2,
|
||||
} from '@affine/workspace/migration';
|
||||
import { createIndexedDBDownloadProvider } from '@affine/workspace/providers';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { rootStore } from '@toeverything/infra/atom';
|
||||
|
||||
import { WorkspaceAdapters } from '../adapters/workspace';
|
||||
|
||||
async function tryMigration() {
|
||||
const value = localStorage.getItem('jotai-workspaces');
|
||||
if (value) {
|
||||
try {
|
||||
const metadata = JSON.parse(value) as RootWorkspaceMetadata[];
|
||||
const promises: Promise<void>[] = [];
|
||||
const newMetadata = [...metadata];
|
||||
metadata.forEach(oldMeta => {
|
||||
if (!('version' in oldMeta)) {
|
||||
const adapter = WorkspaceAdapters[oldMeta.flavour];
|
||||
assertExists(adapter);
|
||||
const upgrade = async () => {
|
||||
if (oldMeta.flavour !== WorkspaceFlavour.LOCAL) {
|
||||
console.warn('not supported');
|
||||
return;
|
||||
}
|
||||
const workspace = await adapter.CRUD.get(oldMeta.id);
|
||||
if (!workspace) {
|
||||
console.warn('cannot find workspace', oldMeta.id);
|
||||
return;
|
||||
}
|
||||
const doc = workspace.blockSuiteWorkspace.doc;
|
||||
const provider = createIndexedDBDownloadProvider(
|
||||
workspace.id,
|
||||
doc,
|
||||
{
|
||||
awareness:
|
||||
workspace.blockSuiteWorkspace.awarenessStore.awareness,
|
||||
}
|
||||
) as LocalIndexedDBDownloadProvider;
|
||||
provider.sync();
|
||||
await provider.whenReady;
|
||||
const newDoc = migrateToSubdoc(doc);
|
||||
if (doc === newDoc) {
|
||||
console.log('doc not changed');
|
||||
return;
|
||||
}
|
||||
const newWorkspace = upgradeV1ToV2(workspace);
|
||||
await migrateDatabaseBlockTo3(
|
||||
newWorkspace.blockSuiteWorkspace.doc,
|
||||
globalBlockSuiteSchema
|
||||
);
|
||||
|
||||
const newId = await adapter.CRUD.create(
|
||||
newWorkspace.blockSuiteWorkspace
|
||||
);
|
||||
|
||||
await adapter.CRUD.delete(workspace as any);
|
||||
console.log('migrated', oldMeta.id, newId);
|
||||
const index = newMetadata.findIndex(meta => meta.id === oldMeta.id);
|
||||
newMetadata[index] = {
|
||||
...oldMeta,
|
||||
id: newId,
|
||||
version: WorkspaceVersion.DatabaseV3,
|
||||
};
|
||||
await migrateLocalBlobStorage(workspace.id, newId);
|
||||
console.log('migrate to v2');
|
||||
};
|
||||
|
||||
// create a new workspace and push it to metadata
|
||||
promises.push(upgrade());
|
||||
} else if (oldMeta.version < WorkspaceVersion.DatabaseV3) {
|
||||
const adapter = WorkspaceAdapters[oldMeta.flavour];
|
||||
assertExists(adapter);
|
||||
promises.push(
|
||||
(async () => {
|
||||
if (oldMeta.flavour !== WorkspaceFlavour.LOCAL) {
|
||||
console.warn('not supported');
|
||||
return;
|
||||
}
|
||||
const workspace = await adapter.CRUD.get(oldMeta.id);
|
||||
if (workspace) {
|
||||
const provider = createIndexedDBDownloadProvider(
|
||||
workspace.id,
|
||||
workspace.blockSuiteWorkspace.doc,
|
||||
{
|
||||
awareness:
|
||||
workspace.blockSuiteWorkspace.awarenessStore.awareness,
|
||||
}
|
||||
) as LocalIndexedDBDownloadProvider;
|
||||
provider.sync();
|
||||
await provider.whenReady;
|
||||
await migrateDatabaseBlockTo3(
|
||||
workspace.blockSuiteWorkspace.doc,
|
||||
globalBlockSuiteSchema
|
||||
);
|
||||
}
|
||||
const index = newMetadata.findIndex(
|
||||
meta => meta.id === oldMeta.id
|
||||
);
|
||||
newMetadata[index] = {
|
||||
...oldMeta,
|
||||
version: WorkspaceVersion.DatabaseV3,
|
||||
};
|
||||
console.log('migrate to v3');
|
||||
})()
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(promises)
|
||||
.then(() => {
|
||||
console.log('migration done');
|
||||
})
|
||||
.catch(e => {
|
||||
console.error('migration failed', e);
|
||||
})
|
||||
.finally(() => {
|
||||
localStorage.setItem('jotai-workspaces', JSON.stringify(newMetadata));
|
||||
window.dispatchEvent(new CustomEvent('migration-done'));
|
||||
window.$migrationDone = true;
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('error when migrating data', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createFirstAppData() {
|
||||
const createFirst = (): RootWorkspaceMetadataV2[] => {
|
||||
const Plugins = Object.values(WorkspaceAdapters).sort(
|
||||
(a, b) => a.loadPriority - b.loadPriority
|
||||
);
|
||||
|
||||
return Plugins.flatMap(Plugin => {
|
||||
return Plugin.Events['app:init']?.().map(
|
||||
id =>
|
||||
<RootWorkspaceMetadataV2>{
|
||||
id,
|
||||
flavour: Plugin.flavour,
|
||||
version: WorkspaceVersion.DatabaseV3,
|
||||
}
|
||||
);
|
||||
}).filter((ids): ids is RootWorkspaceMetadataV2 => !!ids);
|
||||
};
|
||||
if (localStorage.getItem('is-first-open') !== null) {
|
||||
return;
|
||||
}
|
||||
const result = createFirst();
|
||||
console.info('create first workspace', result);
|
||||
localStorage.setItem('is-first-open', 'false');
|
||||
rootStore.set(rootWorkspacesMetadataAtom, result);
|
||||
}
|
||||
|
||||
export async function setup() {
|
||||
rootStore.set(
|
||||
workspaceAdaptersAtom,
|
||||
WorkspaceAdapters as Record<
|
||||
WorkspaceFlavour,
|
||||
WorkspaceAdapter<WorkspaceFlavour>
|
||||
>
|
||||
);
|
||||
|
||||
console.log('setup global');
|
||||
setupGlobal();
|
||||
|
||||
createFirstAppData();
|
||||
await tryMigration();
|
||||
await rootStore.get(rootWorkspacesMetadataAtom);
|
||||
console.log('setup done');
|
||||
}
|
||||
@@ -3,7 +3,6 @@ import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { getOrCreateWorkspace } from '@affine/workspace/manager';
|
||||
import type { EditorContainer } from '@blocksuite/editor';
|
||||
import type { Page } from '@blocksuite/store';
|
||||
import type React from 'react';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { BlockSuiteEditor } from '../../blocksuite/block-suite-editor';
|
||||
@@ -15,7 +14,7 @@ const blockSuiteWorkspace = getOrCreateWorkspace(
|
||||
|
||||
const page = blockSuiteWorkspace.createPage({ id: 'page0' });
|
||||
|
||||
const Editor: React.FC = () => {
|
||||
const Editor = () => {
|
||||
const onLoad = useCallback((page: Page, editor: EditorContainer) => {
|
||||
// @ts-expect-error
|
||||
globalThis.page = page;
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
currentPageIdAtom,
|
||||
currentWorkspaceIdAtom,
|
||||
rootStore,
|
||||
} from '@toeverything/plugin-infra/atom';
|
||||
} from '@toeverything/infra/atom';
|
||||
import { useAtomValue } from 'jotai/react';
|
||||
import { Provider } from 'jotai/react';
|
||||
import type { ErrorInfo, ReactElement, ReactNode } from 'react';
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {
|
||||
Button,
|
||||
Input,
|
||||
Modal,
|
||||
ModalCloseButton,
|
||||
@@ -10,6 +9,11 @@ import {
|
||||
import { DebugLogger } from '@affine/debug';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { HelpIcon } from '@blocksuite/icons';
|
||||
import { Button } from '@toeverything/components/button';
|
||||
import type {
|
||||
LoadDBFileResult,
|
||||
SelectDBFileLocationResult,
|
||||
} from '@toeverything/infra/type';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import type { KeyboardEvent } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
@@ -134,12 +138,12 @@ const SetDBLocationContent = ({
|
||||
}
|
||||
setOpening(true);
|
||||
(async function () {
|
||||
const result = await window.apis?.dialog.selectDBFileLocation();
|
||||
const result: SelectDBFileLocationResult =
|
||||
await window.apis?.dialog.selectDBFileLocation();
|
||||
setOpening(false);
|
||||
if (result?.filePath) {
|
||||
onConfirmLocation(result.filePath);
|
||||
} else if (result?.error) {
|
||||
// @ts-expect-error: result.error is dynamic so the type is unknown
|
||||
toast(t[result.error]());
|
||||
}
|
||||
})().catch(err => {
|
||||
@@ -267,13 +271,12 @@ export const CreateWorkspaceModal = ({
|
||||
}
|
||||
logger.info('load db file');
|
||||
setStep(undefined);
|
||||
const result = await window.apis.dialog.loadDBFile();
|
||||
const result: LoadDBFileResult = await window.apis.dialog.loadDBFile();
|
||||
if (result.workspaceId && !canceled) {
|
||||
setAddedId(result.workspaceId);
|
||||
setStep('set-syncing-mode');
|
||||
} else if (result.error || result.canceled) {
|
||||
if (result.error) {
|
||||
// @ts-expect-error: result.error is dynamic so the type is unknown
|
||||
toast(t[result.error]());
|
||||
}
|
||||
onClose();
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { IconButton, Modal, ModalWrapper } from '@affine/component';
|
||||
import { Modal, ModalWrapper, Wrapper } from '@affine/component';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { CloseIcon } from '@blocksuite/icons';
|
||||
import type React from 'react';
|
||||
import { Button, IconButton } from '@toeverything/components/button';
|
||||
|
||||
import { Content, ContentTitle, Header, StyleButton, StyleTips } from './style';
|
||||
import { Content, ContentTitle, Header, StyleTips } from './style';
|
||||
|
||||
interface EnableAffineCloudModalProps {
|
||||
open: boolean;
|
||||
@@ -11,11 +11,11 @@ interface EnableAffineCloudModalProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const EnableAffineCloudModal: React.FC<EnableAffineCloudModalProps> = ({
|
||||
export const EnableAffineCloudModal = ({
|
||||
onConfirm,
|
||||
open,
|
||||
onClose,
|
||||
}) => {
|
||||
}: EnableAffineCloudModalProps) => {
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
return (
|
||||
@@ -30,19 +30,22 @@ export const EnableAffineCloudModal: React.FC<EnableAffineCloudModalProps> = ({
|
||||
<ContentTitle>{t['Enable AFFiNE Cloud']()}?</ContentTitle>
|
||||
<StyleTips>{t['Enable AFFiNE Cloud Description']()}</StyleTips>
|
||||
{/* <StyleTips>{t('Retain cached cloud data')}</StyleTips> */}
|
||||
<div>
|
||||
<StyleButton
|
||||
<Wrapper width={284} margin="auto">
|
||||
<Button
|
||||
data-testid="confirm-enable-affine-cloud-button"
|
||||
shape="round"
|
||||
type="primary"
|
||||
block
|
||||
onClick={onConfirm}
|
||||
style={{
|
||||
marginBottom: '16px',
|
||||
}}
|
||||
>
|
||||
{t['Sign in and Enable']()}
|
||||
</StyleButton>
|
||||
<StyleButton shape="round" onClick={onClose}>
|
||||
</Button>
|
||||
<Button onClick={onClose} block>
|
||||
{t['Not now']()}
|
||||
</StyleButton>
|
||||
</div>
|
||||
</Button>
|
||||
</Wrapper>
|
||||
</Content>
|
||||
</ModalWrapper>
|
||||
</Modal>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Button, styled } from '@affine/component';
|
||||
import { styled } from '@affine/component';
|
||||
|
||||
export const Header = styled('div')({
|
||||
height: '44px',
|
||||
@@ -29,12 +29,3 @@ export const StyleTips = styled('div')(() => {
|
||||
marginTop: '12px',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyleButton = styled(Button)(() => {
|
||||
return {
|
||||
width: '284px',
|
||||
display: 'block',
|
||||
margin: 'auto',
|
||||
marginTop: '16px',
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
import {
|
||||
type ButtonProps,
|
||||
Menu,
|
||||
MenuItem,
|
||||
MenuTrigger,
|
||||
styled,
|
||||
} from '@affine/component';
|
||||
import { Menu, MenuItem, MenuTrigger, styled } from '@affine/component';
|
||||
import { LOCALES } from '@affine/i18n';
|
||||
import { useI18N } from '@affine/i18n';
|
||||
import type { FC, ReactElement } from 'react';
|
||||
import type { ButtonProps } from '@toeverything/components/button';
|
||||
import type { ReactElement } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export const StyledListItem = styled(MenuItem)(() => ({
|
||||
@@ -16,9 +11,11 @@ export const StyledListItem = styled(MenuItem)(() => ({
|
||||
textTransform: 'capitalize',
|
||||
}));
|
||||
|
||||
const LanguageMenuContent: FC<{
|
||||
interface LanguageMenuContentProps {
|
||||
currentLanguage?: string;
|
||||
}> = ({ currentLanguage }) => {
|
||||
}
|
||||
|
||||
const LanguageMenuContent = ({ currentLanguage }: LanguageMenuContentProps) => {
|
||||
const i18n = useI18N();
|
||||
const changeLanguage = useCallback(
|
||||
(event: string) => {
|
||||
@@ -26,6 +23,7 @@ const LanguageMenuContent: FC<{
|
||||
},
|
||||
[i18n]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{LOCALES.map(option => {
|
||||
@@ -47,9 +45,12 @@ const LanguageMenuContent: FC<{
|
||||
</>
|
||||
);
|
||||
};
|
||||
export const LanguageMenu: FC<{ triggerProps?: ButtonProps }> = ({
|
||||
triggerProps,
|
||||
}) => {
|
||||
|
||||
interface LanguageMenuProps {
|
||||
triggerProps?: ButtonProps;
|
||||
}
|
||||
|
||||
export const LanguageMenu = ({ triggerProps }: LanguageMenuProps) => {
|
||||
const i18n = useI18N();
|
||||
|
||||
const currentLanguage = LOCALES.find(item => item.tag === i18n.language);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Button, Input, Modal, ModalCloseButton } from '@affine/component';
|
||||
import { Input, Modal, ModalCloseButton } from '@affine/component';
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { Trans } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { Button } from '@toeverything/components/button';
|
||||
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
@@ -92,15 +93,15 @@ export const WorkspaceDeleteModal = ({
|
||||
/>
|
||||
</StyledInputContent>
|
||||
<StyledButtonContent>
|
||||
<Button shape="circle" onClick={onClose}>
|
||||
<Button onClick={onClose} size="large">
|
||||
{t['Cancel']()}
|
||||
</Button>
|
||||
<Button
|
||||
data-testid="delete-workspace-confirm-button"
|
||||
disabled={!allowDelete}
|
||||
onClick={handleDelete}
|
||||
size="large"
|
||||
type="error"
|
||||
shape="circle"
|
||||
style={{ marginLeft: '24px' }}
|
||||
>
|
||||
{t['Delete']()}
|
||||
|
||||
@@ -5,7 +5,7 @@ export const StyledModalWrapper = styled('div')(() => {
|
||||
position: 'relative',
|
||||
padding: '0px',
|
||||
width: '560px',
|
||||
background: 'var(--affine-white)',
|
||||
background: 'var(--affine-background-overlay-panel-color)',
|
||||
borderRadius: '12px',
|
||||
// height: '312px',
|
||||
};
|
||||
|
||||
@@ -1,23 +1,29 @@
|
||||
import { SettingRow } from '@affine/component/setting-components';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { ArrowRightSmallIcon } from '@blocksuite/icons';
|
||||
import { type FC, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import type { AffineOfficialWorkspace } from '../../../../shared';
|
||||
import type { WorkspaceSettingDetailProps } from '../index';
|
||||
import { WorkspaceDeleteModal } from './delete';
|
||||
import { WorkspaceLeave } from './leave';
|
||||
|
||||
export const DeleteLeaveWorkspace: FC<{
|
||||
interface DeleteLeaveWorkspaceProps {
|
||||
workspace: AffineOfficialWorkspace;
|
||||
onDeleteWorkspace: WorkspaceSettingDetailProps['onDeleteWorkspace'];
|
||||
}> = ({ workspace, onDeleteWorkspace }) => {
|
||||
}
|
||||
|
||||
export const DeleteLeaveWorkspace = ({
|
||||
workspace,
|
||||
onDeleteWorkspace,
|
||||
}: DeleteLeaveWorkspaceProps) => {
|
||||
const t = useAFFiNEI18N();
|
||||
// fixme: cloud regression
|
||||
const isOwner = true;
|
||||
|
||||
const [showDelete, setShowDelete] = useState(false);
|
||||
const [showLeave, setShowLeave] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingRow
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Modal } from '@affine/component';
|
||||
import { ModalCloseButton } from '@affine/component';
|
||||
import { Button } from '@affine/component';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { Button } from '@toeverything/components/button';
|
||||
|
||||
import {
|
||||
StyledButtonContent,
|
||||
@@ -38,7 +38,6 @@ export const WorkspaceLeave = ({ open, onClose }: WorkspaceDeleteProps) => {
|
||||
<Button
|
||||
onClick={handleLeave}
|
||||
type="error"
|
||||
shape="circle"
|
||||
style={{ marginLeft: '24px' }}
|
||||
>
|
||||
{t['Leave']()}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { Button, toast } from '@affine/component';
|
||||
import { toast } from '@affine/component';
|
||||
import { SettingRow } from '@affine/component/setting-components';
|
||||
import { isDesktop } from '@affine/env/constant';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { type FC, useCallback } from 'react';
|
||||
import { Button } from '@toeverything/components/button';
|
||||
import type { SaveDBFileResult } from '@toeverything/infra/type';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import type { AffineOfficialWorkspace } from '../../../shared';
|
||||
|
||||
@@ -27,21 +29,25 @@ async function syncBlobsToSqliteDb(workspace: AffineOfficialWorkspace) {
|
||||
}
|
||||
}
|
||||
|
||||
export const ExportPanel: FC<{
|
||||
interface ExportPanelProps {
|
||||
workspace: AffineOfficialWorkspace;
|
||||
}> = ({ workspace }) => {
|
||||
}
|
||||
|
||||
export const ExportPanel = ({ workspace }: ExportPanelProps) => {
|
||||
const workspaceId = workspace.id;
|
||||
const t = useAFFiNEI18N();
|
||||
const onExport = useCallback(async () => {
|
||||
await syncBlobsToSqliteDb(workspace);
|
||||
const result = await window.apis?.dialog.saveDBFileAs(workspaceId);
|
||||
const result: SaveDBFileResult = await window.apis?.dialog.saveDBFileAs(
|
||||
workspaceId
|
||||
);
|
||||
if (result?.error) {
|
||||
// @ts-expect-error: result.error is dynamic
|
||||
toast(t[result.error]());
|
||||
} else if (!result?.canceled) {
|
||||
toast(t['Export success']());
|
||||
}
|
||||
}, [t, workspace, workspaceId]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingRow name={t['Export']()} desc={t['Export Description']()}>
|
||||
|
||||
@@ -9,7 +9,7 @@ import type {
|
||||
} from '@affine/env/workspace';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
|
||||
import { type FC, useMemo } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { useWorkspace } from '../../../hooks/use-workspace';
|
||||
import { DeleteLeaveWorkspace } from './delete-leave-workspace';
|
||||
@@ -18,7 +18,7 @@ import { ProfilePanel } from './profile';
|
||||
import { PublishPanel } from './publish';
|
||||
import { StoragePanel } from './storage';
|
||||
|
||||
export type WorkspaceSettingDetailProps = {
|
||||
export interface WorkspaceSettingDetailProps {
|
||||
workspaceId: string;
|
||||
onDeleteWorkspace: (id: string) => Promise<void>;
|
||||
onTransferWorkspace: <
|
||||
@@ -29,13 +29,13 @@ export type WorkspaceSettingDetailProps = {
|
||||
to: To,
|
||||
workspace: WorkspaceRegistry[From]
|
||||
) => void;
|
||||
};
|
||||
}
|
||||
|
||||
export const WorkspaceSettingDetail: FC<WorkspaceSettingDetailProps> = ({
|
||||
export const WorkspaceSettingDetail = ({
|
||||
workspaceId,
|
||||
onDeleteWorkspace,
|
||||
...props
|
||||
}) => {
|
||||
}: WorkspaceSettingDetailProps) => {
|
||||
const t = useAFFiNEI18N();
|
||||
const workspace = useWorkspace(workspaceId);
|
||||
const [name] = useBlockSuiteWorkspaceName(workspace.blockSuiteWorkspace);
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { IconButton, Input, toast } from '@affine/component';
|
||||
import { Input, toast } from '@affine/component';
|
||||
import { WorkspaceAvatar } from '@affine/component/workspace-avatar';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { DoneIcon } from '@blocksuite/icons';
|
||||
import { IconButton } from '@toeverything/components/button';
|
||||
import { useBlockSuiteWorkspaceAvatarUrl } from '@toeverything/hooks/use-block-suite-workspace-avatar-url';
|
||||
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
|
||||
import { type FC, useCallback, useState } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
import type { AffineOfficialWorkspace } from '../../../shared';
|
||||
import { Upload } from '../../pure/file-upload';
|
||||
@@ -29,9 +30,11 @@ const CameraIcon = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export const ProfilePanel: FC<{
|
||||
interface ProfilePanelProps {
|
||||
workspace: AffineOfficialWorkspace;
|
||||
}> = ({ workspace }) => {
|
||||
}
|
||||
|
||||
export const ProfilePanel = ({ workspace }: ProfilePanelProps) => {
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
const [, update] = useBlockSuiteWorkspaceAvatarUrl(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Button, FlexWrapper, Switch, Tooltip } from '@affine/component';
|
||||
import { FlexWrapper, Switch, Tooltip } from '@affine/component';
|
||||
import { SettingRow } from '@affine/component/setting-components';
|
||||
import { Unreachable } from '@affine/env/constant';
|
||||
import type {
|
||||
@@ -7,8 +7,8 @@ import type {
|
||||
} from '@affine/env/workspace';
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { Button } from '@toeverything/components/button';
|
||||
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
|
||||
import type { FC } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import type { AffineOfficialWorkspace } from '../../../shared';
|
||||
@@ -18,26 +18,20 @@ import { TmpDisableAffineCloudModal } from '../tmp-disable-affine-cloud-modal';
|
||||
import type { WorkspaceSettingDetailProps } from './index';
|
||||
import * as style from './style.css';
|
||||
|
||||
export type PublishPanelProps = Omit<
|
||||
WorkspaceSettingDetailProps,
|
||||
'workspaceId'
|
||||
> & {
|
||||
export interface PublishPanelProps
|
||||
extends Omit<WorkspaceSettingDetailProps, 'workspaceId'> {
|
||||
workspace: AffineOfficialWorkspace;
|
||||
};
|
||||
export type PublishPanelLocalProps = Omit<
|
||||
WorkspaceSettingDetailProps,
|
||||
'workspaceId'
|
||||
> & {
|
||||
}
|
||||
export interface PublishPanelLocalProps
|
||||
extends Omit<WorkspaceSettingDetailProps, 'workspaceId'> {
|
||||
workspace: LocalWorkspace;
|
||||
};
|
||||
export type PublishPanelAffineProps = Omit<
|
||||
WorkspaceSettingDetailProps,
|
||||
'workspaceId'
|
||||
> & {
|
||||
}
|
||||
export interface PublishPanelAffineProps
|
||||
extends Omit<WorkspaceSettingDetailProps, 'workspaceId'> {
|
||||
workspace: AffineCloudWorkspace;
|
||||
};
|
||||
}
|
||||
|
||||
const PublishPanelAffine: FC<PublishPanelAffineProps> = props => {
|
||||
const PublishPanelAffine = (props: PublishPanelAffineProps) => {
|
||||
const { workspace } = props;
|
||||
const t = useAFFiNEI18N();
|
||||
// const toggleWorkspacePublish = useToggleWorkspacePublish(workspace);
|
||||
@@ -57,6 +51,7 @@ const PublishPanelAffine: FC<PublishPanelAffineProps> = props => {
|
||||
await navigator.clipboard.writeText(shareUrl);
|
||||
toast(t['Copied link to clipboard']());
|
||||
}, [shareUrl, t]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingRow
|
||||
@@ -90,10 +85,13 @@ const PublishPanelAffine: FC<PublishPanelAffineProps> = props => {
|
||||
);
|
||||
};
|
||||
|
||||
const FakePublishPanelAffine: FC<{
|
||||
interface FakePublishPanelAffineProps {
|
||||
workspace: AffineOfficialWorkspace;
|
||||
}> = () => {
|
||||
}
|
||||
|
||||
const FakePublishPanelAffine = (_props: FakePublishPanelAffineProps) => {
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
content={t['com.affine.settings.workspace.publish.local-tooltip']()}
|
||||
@@ -107,10 +105,11 @@ const FakePublishPanelAffine: FC<{
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
const PublishPanelLocal: FC<PublishPanelLocalProps> = ({
|
||||
|
||||
const PublishPanelLocal = ({
|
||||
workspace,
|
||||
onTransferWorkspace,
|
||||
}) => {
|
||||
}: PublishPanelLocalProps) => {
|
||||
const t = useAFFiNEI18N();
|
||||
const [name] = useBlockSuiteWorkspaceName(workspace.blockSuiteWorkspace);
|
||||
|
||||
@@ -166,7 +165,7 @@ const PublishPanelLocal: FC<PublishPanelLocalProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
export const PublishPanel: FC<PublishPanelProps> = props => {
|
||||
export const PublishPanel = (props: PublishPanelProps) => {
|
||||
if (props.workspace.flavour === WorkspaceFlavour.AFFINE_CLOUD) {
|
||||
return <PublishPanelAffine {...props} workspace={props.workspace} />;
|
||||
} else if (props.workspace.flavour === WorkspaceFlavour.LOCAL) {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { Button, FlexWrapper, toast, Tooltip } from '@affine/component';
|
||||
import { FlexWrapper, toast, Tooltip } from '@affine/component';
|
||||
import { SettingRow } from '@affine/component/setting-components';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { Button } from '@toeverything/components/button';
|
||||
import type { MoveDBFileResult } from '@toeverything/infra/type';
|
||||
import { useMemo } from 'react';
|
||||
import { type FC, useCallback, useEffect, useState } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import type { AffineOfficialWorkspace } from '../../../shared';
|
||||
import * as style from './style.css';
|
||||
@@ -30,9 +32,11 @@ const useDBFileSecondaryPath = (workspaceId: string) => {
|
||||
return path;
|
||||
};
|
||||
|
||||
export const StoragePanel: FC<{
|
||||
interface StoragePanelProps {
|
||||
workspace: AffineOfficialWorkspace;
|
||||
}> = ({ workspace }) => {
|
||||
}
|
||||
|
||||
export const StoragePanel = ({ workspace }: StoragePanelProps) => {
|
||||
const workspaceId = workspace.id;
|
||||
const t = useAFFiNEI18N();
|
||||
const secondaryPath = useDBFileSecondaryPath(workspaceId);
|
||||
@@ -51,11 +55,10 @@ export const StoragePanel: FC<{
|
||||
setMoveToInProgress(true);
|
||||
window.apis?.dialog
|
||||
.moveDBFile(workspaceId)
|
||||
.then(result => {
|
||||
.then((result: MoveDBFileResult) => {
|
||||
if (!result?.error && !result?.canceled) {
|
||||
toast(t['Move folder success']());
|
||||
} else if (result?.error) {
|
||||
// @ts-expect-error: result.error is dynamic
|
||||
toast(t[result.error]());
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { TourModal } from '@affine/component/tour-modal';
|
||||
import { useAtom } from 'jotai';
|
||||
import type { FC } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
|
||||
import { openOnboardingModalAtom } from '../../atoms';
|
||||
import { guideOnboardingAtom } from '../../atoms/guide';
|
||||
|
||||
export const OnboardingModal: FC = memo(function OnboardingModal() {
|
||||
export const OnboardingModal = memo(function OnboardingModal() {
|
||||
const [open, setOpen] = useAtom(openOnboardingModalAtom);
|
||||
const [guideOpen, setShowOnboarding] = useAtom(guideOnboardingAtom);
|
||||
const onCloseTourModal = useCallback(() => {
|
||||
|
||||
@@ -61,10 +61,7 @@ export const AboutAffine = () => {
|
||||
desc={t['View the AFFiNE Changelog.']()}
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
window.open(
|
||||
'https://affine.pro/blog/what-is-new-affine-0717',
|
||||
'_blank'
|
||||
);
|
||||
window.open(runtimeConfig.changelogUrl, '_blank');
|
||||
}}
|
||||
>
|
||||
<ArrowRightSmallIcon />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Menu, MenuItem, MenuTrigger } from '@affine/component';
|
||||
import dayjs from 'dayjs';
|
||||
import { type FC, useCallback } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import {
|
||||
dateFormatOptions,
|
||||
@@ -8,10 +8,15 @@ import {
|
||||
useAppSetting,
|
||||
} from '../../../../../atoms/settings';
|
||||
|
||||
const DateFormatMenuContent: FC<{
|
||||
interface DateFormatMenuContentProps {
|
||||
currentOption: DateFormats;
|
||||
onSelect: (option: DateFormats) => void;
|
||||
}> = ({ onSelect, currentOption }) => {
|
||||
}
|
||||
|
||||
const DateFormatMenuContent = ({
|
||||
onSelect,
|
||||
currentOption,
|
||||
}: DateFormatMenuContentProps) => {
|
||||
return (
|
||||
<>
|
||||
{dateFormatOptions.map(option => {
|
||||
@@ -30,6 +35,7 @@ const DateFormatMenuContent: FC<{
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const DateFormatSetting = () => {
|
||||
const [appearanceSettings, setAppSettings] = useAppSetting();
|
||||
const handleSelect = useCallback(
|
||||
@@ -38,6 +44,7 @@ export const DateFormatSetting = () => {
|
||||
},
|
||||
[setAppSettings]
|
||||
);
|
||||
|
||||
return (
|
||||
<Menu
|
||||
content={
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import {
|
||||
AiIcon,
|
||||
AppearanceIcon,
|
||||
InformationIcon,
|
||||
KeyboardIcon,
|
||||
PluginIcon,
|
||||
} from '@blocksuite/icons';
|
||||
import type { FC, SVGProps } from 'react';
|
||||
import type { ReactElement, SVGProps } from 'react';
|
||||
|
||||
import { AboutAffine } from './about';
|
||||
import { AppearanceSettings } from './appearance';
|
||||
@@ -18,15 +18,18 @@ export type GeneralSettingKeys =
|
||||
| 'plugins'
|
||||
| 'about';
|
||||
|
||||
export type GeneralSettingList = {
|
||||
interface GeneralSettingListItem {
|
||||
key: GeneralSettingKeys;
|
||||
title: string;
|
||||
icon: FC<SVGProps<SVGSVGElement>>;
|
||||
icon: (props: SVGProps<SVGSVGElement>) => ReactElement;
|
||||
testId: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export type GeneralSettingList = GeneralSettingListItem[];
|
||||
|
||||
export const useGeneralSettingList = (): GeneralSettingList => {
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
return [
|
||||
{
|
||||
key: 'appearance',
|
||||
@@ -43,7 +46,7 @@ export const useGeneralSettingList = (): GeneralSettingList => {
|
||||
{
|
||||
key: 'plugins',
|
||||
title: 'Plugins',
|
||||
icon: AiIcon,
|
||||
icon: PluginIcon,
|
||||
testId: 'plugins-panel-trigger',
|
||||
},
|
||||
{
|
||||
@@ -55,11 +58,11 @@ export const useGeneralSettingList = (): GeneralSettingList => {
|
||||
];
|
||||
};
|
||||
|
||||
export const GeneralSetting = ({
|
||||
generalKey,
|
||||
}: {
|
||||
interface GeneralSettingProps {
|
||||
generalKey: GeneralSettingKeys;
|
||||
}) => {
|
||||
}
|
||||
|
||||
export const GeneralSetting = ({ generalKey }: GeneralSettingProps) => {
|
||||
switch (generalKey) {
|
||||
case 'shortcuts':
|
||||
return <Shortcuts />;
|
||||
|
||||
@@ -1,23 +1,96 @@
|
||||
import {
|
||||
SettingHeader,
|
||||
SettingWrapper,
|
||||
} from '@affine/component/setting-components';
|
||||
import { Switch } from '@affine/component';
|
||||
import { SettingHeader } from '@affine/component/setting-components';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { registeredPluginAtom } from '@toeverything/plugin-infra/atom';
|
||||
import { useAtomValue } from 'jotai';
|
||||
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 allowedPlugins = useAtomValue(registeredPluginAtom);
|
||||
const loadedPlugins = useAtomValue(loadedPluginNameAtom);
|
||||
return (
|
||||
<>
|
||||
<SettingHeader
|
||||
title={'Plugins'}
|
||||
subtitle={allowedPlugins.length === 0 && t['None yet']()}
|
||||
subtitle={loadedPlugins.length === 0 && t['None yet']()}
|
||||
data-testid="plugins-title"
|
||||
/>
|
||||
{allowedPlugins.map(plugin => (
|
||||
<SettingWrapper key={plugin} title={plugin}></SettingWrapper>
|
||||
{useAtomValue(pluginPackageJson).map(json => (
|
||||
<PluginItem json={json} key={json.name} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
export const settingWrapper = style({
|
||||
export const settingWrapperStyle = style({
|
||||
flexGrow: 1,
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
minWidth: '150px',
|
||||
maxWidth: '250px',
|
||||
});
|
||||
|
||||
export const pluginItemStyle = style({
|
||||
borderBottom: '1px solid var(--affine-border-color)',
|
||||
transition: '0.3s',
|
||||
padding: '24px 8px',
|
||||
fontSize: 'var(--affine-font-sm)',
|
||||
});
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import {
|
||||
SettingModal as SettingModalBase,
|
||||
type SettingModalProps,
|
||||
type SettingModalProps as SettingModalBaseProps,
|
||||
} from '@affine/component/setting-components';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { ContactWithUsIcon } from '@blocksuite/icons';
|
||||
import type React from 'react';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { AccountSetting } from './account-setting';
|
||||
@@ -18,21 +17,25 @@ import { settingContent } from './style.css';
|
||||
import { WorkspaceSetting } from './workspace-setting';
|
||||
|
||||
type ActiveTab = GeneralSettingKeys | 'workspace' | 'account';
|
||||
export type SettingProps = {
|
||||
|
||||
export interface SettingProps {
|
||||
activeTab: ActiveTab;
|
||||
workspaceId: string | null;
|
||||
onSettingClick: (params: {
|
||||
activeTab: ActiveTab;
|
||||
workspaceId: string | null;
|
||||
}) => void;
|
||||
};
|
||||
export const SettingModal: React.FC<SettingModalProps & SettingProps> = ({
|
||||
}
|
||||
|
||||
type SettingModalProps = SettingModalBaseProps & SettingProps;
|
||||
|
||||
export const SettingModal = ({
|
||||
open,
|
||||
setOpen,
|
||||
activeTab = 'appearance',
|
||||
workspaceId = null,
|
||||
onSettingClick,
|
||||
}) => {
|
||||
}: SettingModalProps) => {
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
const generalSettingList = useGeneralSettingList();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Tooltip } from '@affine/component';
|
||||
import { ScrollableContainer, Tooltip } from '@affine/component';
|
||||
import {
|
||||
WorkspaceListItemSkeleton,
|
||||
WorkspaceListSkeleton,
|
||||
@@ -8,10 +8,9 @@ import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import type { RootWorkspaceMetadata } from '@affine/workspace/atom';
|
||||
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
||||
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
|
||||
import { useStaticBlockSuiteWorkspace } from '@toeverything/plugin-infra/__internal__/react';
|
||||
import { useStaticBlockSuiteWorkspace } from '@toeverything/infra/__internal__/react';
|
||||
import clsx from 'clsx';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import type { FC } from 'react';
|
||||
import { Suspense } from 'react';
|
||||
|
||||
import { useCurrentWorkspace } from '../../../../hooks/current/use-current-workspace';
|
||||
@@ -28,21 +27,24 @@ import {
|
||||
sidebarTitle,
|
||||
} from './style.css';
|
||||
|
||||
export const SettingSidebar: FC<{
|
||||
interface SettingSidebarProps {
|
||||
generalSettingList: GeneralSettingList;
|
||||
onGeneralSettingClick: (key: GeneralSettingKeys) => void;
|
||||
onWorkspaceSettingClick: (workspaceId: string) => void;
|
||||
selectedWorkspaceId: string | null;
|
||||
selectedGeneralKey: string | null;
|
||||
onAccountSettingClick: () => void;
|
||||
}> = ({
|
||||
}
|
||||
|
||||
export const SettingSidebar = ({
|
||||
generalSettingList,
|
||||
onGeneralSettingClick,
|
||||
onWorkspaceSettingClick,
|
||||
selectedWorkspaceId,
|
||||
selectedGeneralKey,
|
||||
}) => {
|
||||
}: SettingSidebarProps) => {
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
return (
|
||||
<div className={settingSlideBar} data-testid="settings-sidebar">
|
||||
<div className={sidebarTitle}>{t['Settings']()}</div>
|
||||
@@ -76,20 +78,27 @@ export const SettingSidebar: FC<{
|
||||
</div>
|
||||
<div className={clsx(sidebarItemsWrapper, 'scroll')}>
|
||||
<Suspense fallback={<WorkspaceListSkeleton />}>
|
||||
<WorkspaceList
|
||||
onWorkspaceSettingClick={onWorkspaceSettingClick}
|
||||
selectedWorkspaceId={selectedWorkspaceId}
|
||||
/>
|
||||
<ScrollableContainer>
|
||||
<WorkspaceList
|
||||
onWorkspaceSettingClick={onWorkspaceSettingClick}
|
||||
selectedWorkspaceId={selectedWorkspaceId}
|
||||
/>
|
||||
</ScrollableContainer>
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const WorkspaceList: FC<{
|
||||
interface WorkspaceListProps {
|
||||
onWorkspaceSettingClick: (workspaceId: string) => void;
|
||||
selectedWorkspaceId: string | null;
|
||||
}> = ({ onWorkspaceSettingClick, selectedWorkspaceId }) => {
|
||||
}
|
||||
|
||||
export const WorkspaceList = ({
|
||||
onWorkspaceSettingClick,
|
||||
selectedWorkspaceId,
|
||||
}: WorkspaceListProps) => {
|
||||
const workspaces = useAtomValue(rootWorkspacesMetadataAtom);
|
||||
const [currentWorkspace] = useCurrentWorkspace();
|
||||
return (
|
||||
@@ -112,19 +121,22 @@ export const WorkspaceList: FC<{
|
||||
);
|
||||
};
|
||||
|
||||
interface WorkspaceListItemProps {
|
||||
meta: RootWorkspaceMetadata;
|
||||
onClick: () => void;
|
||||
isCurrent: boolean;
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
const WorkspaceListItem = ({
|
||||
meta,
|
||||
onClick,
|
||||
isCurrent,
|
||||
isActive,
|
||||
}: {
|
||||
meta: RootWorkspaceMetadata;
|
||||
onClick: () => void;
|
||||
isCurrent: boolean;
|
||||
isActive: boolean;
|
||||
}) => {
|
||||
}: WorkspaceListItemProps) => {
|
||||
const workspace = useStaticBlockSuiteWorkspace(meta.id);
|
||||
const [workspaceName] = useBlockSuiteWorkspaceName(workspace);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(sidebarSelectItem, { active: isActive })}
|
||||
|
||||
@@ -4,7 +4,7 @@ export const settingSlideBar = style({
|
||||
width: '25%',
|
||||
maxWidth: '242px',
|
||||
background: 'var(--affine-background-secondary-color)',
|
||||
padding: '20px 16px',
|
||||
padding: '20px 0px',
|
||||
height: '100%',
|
||||
flexShrink: 0,
|
||||
display: 'flex',
|
||||
@@ -15,14 +15,14 @@ export const sidebarTitle = style({
|
||||
fontSize: 'var(--affine-font-h-6)',
|
||||
fontWeight: '600',
|
||||
lineHeight: 'var(--affine-line-height)',
|
||||
paddingLeft: '8px',
|
||||
padding: '0px 16px 0px 24px',
|
||||
});
|
||||
|
||||
export const sidebarSubtitle = style({
|
||||
fontSize: 'var(--affine-font-sm)',
|
||||
lineHeight: 'var(--affine-line-height)',
|
||||
color: 'var(--affine-text-secondary-color)',
|
||||
paddingLeft: '8px',
|
||||
padding: '0px 16px 0px 24px',
|
||||
marginTop: '20px',
|
||||
marginBottom: '4px',
|
||||
display: 'flex',
|
||||
@@ -34,7 +34,7 @@ export const sidebarItemsWrapper = style({
|
||||
selectors: {
|
||||
'&.scroll': {
|
||||
flexGrow: 1,
|
||||
overflowY: 'auto',
|
||||
overflowY: 'hidden',
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -42,9 +42,9 @@ export const sidebarItemsWrapper = style({
|
||||
export const sidebarSelectItem = style({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: '0 8px',
|
||||
margin: '0px 16px 4px 16px',
|
||||
padding: '0px 8px',
|
||||
height: '30px',
|
||||
marginBottom: '4px',
|
||||
fontSize: 'var(--affine-font-sm)',
|
||||
borderRadius: '8px',
|
||||
cursor: 'pointer',
|
||||
|
||||
@@ -32,11 +32,12 @@ globalStyle(`${settingContent} .footer`, {
|
||||
|
||||
globalStyle(`${settingContent} .footer a`, {
|
||||
color: 'var(--affine-text-primary-color)',
|
||||
lineHeight: 'normal',
|
||||
});
|
||||
|
||||
globalStyle(`${settingContent} .footer > svg`, {
|
||||
fontSize: 'var(--affine-font-base)',
|
||||
color: 'var(--affine-icon-color)',
|
||||
marginRight: '12px',
|
||||
marginTop: '2px',
|
||||
marginTop: '1px',
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { WorkspaceDetailSkeleton } from '@affine/component/setting-components';
|
||||
import { usePassiveWorkspaceEffect } from '@toeverything/plugin-infra/__internal__/react';
|
||||
import { usePassiveWorkspaceEffect } from '@toeverything/infra/__internal__/react';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { Suspense, useCallback } from 'react';
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Empty, IconButton, Modal, ModalWrapper } from '@affine/component';
|
||||
import { Empty, Modal, ModalWrapper } from '@affine/component';
|
||||
import { Trans } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { CloseIcon } from '@blocksuite/icons';
|
||||
import type React from 'react';
|
||||
import { IconButton } from '@toeverything/components/button';
|
||||
|
||||
import {
|
||||
Content,
|
||||
@@ -19,10 +19,12 @@ interface TmpDisableAffineCloudModalProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const TmpDisableAffineCloudModal: React.FC<
|
||||
TmpDisableAffineCloudModalProps
|
||||
> = ({ open, onClose }) => {
|
||||
export const TmpDisableAffineCloudModal = ({
|
||||
open,
|
||||
onClose,
|
||||
}: TmpDisableAffineCloudModalProps) => {
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
return (
|
||||
<Modal
|
||||
data-testid="disable-affine-cloud-modal"
|
||||
@@ -66,7 +68,7 @@ export const TmpDisableAffineCloudModal: React.FC<
|
||||
/>
|
||||
</StyleImage>
|
||||
<StyleButtonContainer>
|
||||
<StyleButton shape="round" type="primary" onClick={onClose}>
|
||||
<StyleButton type="primary" onClick={onClose}>
|
||||
{t['Got it']()}
|
||||
</StyleButton>
|
||||
</StyleButtonContainer>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Button, displayFlex, styled } from '@affine/component';
|
||||
import { displayFlex, styled } from '@affine/component';
|
||||
import { Button } from '@toeverything/components/button';
|
||||
|
||||
export const Header = styled('div')({
|
||||
height: '44px',
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
import { IconButton, Modal, ModalWrapper } from '@affine/component';
|
||||
import { Modal, ModalWrapper } from '@affine/component';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { CloseIcon } from '@blocksuite/icons';
|
||||
import type React from 'react';
|
||||
import { IconButton } from '@toeverything/components/button';
|
||||
|
||||
import { Content, ContentTitle, Header, StyleButton, StyleTips } from './style';
|
||||
|
||||
export type TransformWorkspaceToAffineModalProps = {
|
||||
export interface TransformWorkspaceToAffineModalProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onConform: () => void;
|
||||
};
|
||||
}
|
||||
|
||||
export const TransformWorkspaceToAffineModal: React.FC<
|
||||
TransformWorkspaceToAffineModalProps
|
||||
> = ({ open, onClose, onConform }) => {
|
||||
export const TransformWorkspaceToAffineModal = ({
|
||||
open,
|
||||
onClose,
|
||||
onConform,
|
||||
}: TransformWorkspaceToAffineModalProps) => {
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
return (
|
||||
@@ -35,7 +37,6 @@ export const TransformWorkspaceToAffineModal: React.FC<
|
||||
<div>
|
||||
<StyleButton
|
||||
data-testid="confirm-enable-cloud-button"
|
||||
shape="round"
|
||||
type="primary"
|
||||
onClick={onConform}
|
||||
>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Button, styled } from '@affine/component';
|
||||
import { styled } from '@affine/component';
|
||||
import { Button } from '@toeverything/components/button';
|
||||
|
||||
export const Header = styled('div')({
|
||||
height: '44px',
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import {
|
||||
useBlockSuitePageMeta,
|
||||
usePageMetaHelper,
|
||||
} from '@toeverything/hooks/use-block-suite-page-meta';
|
||||
import {
|
||||
type FocusEvent,
|
||||
type InputHTMLAttributes,
|
||||
type KeyboardEvent,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import type { AffineOfficialWorkspace } from '../../../shared';
|
||||
import { EditorModeSwitch } from '../block-suite-mode-switch';
|
||||
import { PageMenu } from './operation-menu';
|
||||
import * as styles from './styles.css';
|
||||
|
||||
export interface BlockSuiteHeaderTitleProps {
|
||||
workspace: AffineOfficialWorkspace;
|
||||
pageId: string;
|
||||
}
|
||||
|
||||
const EditableTitle = ({
|
||||
value,
|
||||
onFocus: propsOnFocus,
|
||||
...inputProps
|
||||
}: InputHTMLAttributes<HTMLInputElement>) => {
|
||||
const onFocus = useCallback(
|
||||
(e: FocusEvent<HTMLInputElement>) => {
|
||||
e.target.select();
|
||||
propsOnFocus?.(e);
|
||||
},
|
||||
[propsOnFocus]
|
||||
);
|
||||
return (
|
||||
<div className={styles.headerTitleContainer}>
|
||||
<input
|
||||
className={styles.titleInput}
|
||||
autoFocus={true}
|
||||
value={value}
|
||||
type="text"
|
||||
data-testid="title-content"
|
||||
onFocus={onFocus}
|
||||
{...inputProps}
|
||||
/>
|
||||
<span className={styles.shadowTitle}>{value}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const StableTitle = ({
|
||||
workspace,
|
||||
pageId,
|
||||
onRename,
|
||||
}: BlockSuiteHeaderTitleProps & {
|
||||
onRename?: () => void;
|
||||
}) => {
|
||||
const currentPage = workspace.blockSuiteWorkspace.getPage(pageId);
|
||||
const pageMeta = useBlockSuitePageMeta(workspace.blockSuiteWorkspace).find(
|
||||
meta => meta.id === currentPage?.id
|
||||
);
|
||||
|
||||
const title = pageMeta?.title;
|
||||
|
||||
return (
|
||||
<div className={styles.headerTitleContainer}>
|
||||
<EditorModeSwitch
|
||||
blockSuiteWorkspace={workspace.blockSuiteWorkspace}
|
||||
pageId={pageId}
|
||||
style={{
|
||||
marginRight: '12px',
|
||||
}}
|
||||
/>
|
||||
<span
|
||||
data-testid="title-edit-button"
|
||||
className={styles.titleEditButton}
|
||||
onClick={onRename}
|
||||
>
|
||||
{title || 'Untitled'}
|
||||
</span>
|
||||
<PageMenu rename={onRename} pageId={pageId} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const BlockSuiteTitleWithRename = (props: BlockSuiteHeaderTitleProps) => {
|
||||
const { workspace, pageId } = props;
|
||||
const currentPage = workspace.blockSuiteWorkspace.getPage(pageId);
|
||||
const pageMeta = useBlockSuitePageMeta(workspace.blockSuiteWorkspace).find(
|
||||
meta => meta.id === currentPage?.id
|
||||
);
|
||||
const pageTitleMeta = usePageMetaHelper(workspace.blockSuiteWorkspace);
|
||||
|
||||
const [isEditable, setIsEditable] = useState(false);
|
||||
const [title, setPageTitle] = useState(pageMeta?.title || 'Untitled');
|
||||
|
||||
const onRename = useCallback(() => {
|
||||
setIsEditable(true);
|
||||
}, []);
|
||||
|
||||
const onBlur = useCallback(() => {
|
||||
setIsEditable(false);
|
||||
if (!currentPage?.id) {
|
||||
return;
|
||||
}
|
||||
pageTitleMeta.setPageTitle(currentPage.id, title);
|
||||
}, [currentPage?.id, pageTitleMeta, title]);
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(e: KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === 'Enter' || e.key === 'Escape') {
|
||||
onBlur();
|
||||
}
|
||||
},
|
||||
[onBlur]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setPageTitle(pageMeta?.title || '');
|
||||
}, [pageMeta?.title]);
|
||||
|
||||
if (isEditable) {
|
||||
return (
|
||||
<EditableTitle
|
||||
onBlur={onBlur}
|
||||
value={title}
|
||||
onKeyDown={handleKeyDown}
|
||||
onChange={e => {
|
||||
const value = e.target.value;
|
||||
setPageTitle(value);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return <StableTitle {...props} onRename={onRename} />;
|
||||
};
|
||||
|
||||
export const BlockSuiteHeaderTitle = (props: BlockSuiteHeaderTitleProps) => {
|
||||
if (props.workspace.flavour === WorkspaceFlavour.PUBLIC) {
|
||||
return <StableTitle {...props} />;
|
||||
}
|
||||
return <BlockSuiteTitleWithRename {...props} />;
|
||||
};
|
||||
|
||||
BlockSuiteHeaderTitle.displayName = 'BlockSuiteHeaderTitle';
|
||||
@@ -0,0 +1,204 @@
|
||||
import { FlexWrapper, Menu, MenuItem } from '@affine/component';
|
||||
import { Export, MoveToTrash } from '@affine/component/page-list';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import {
|
||||
DuplicateIcon,
|
||||
EdgelessIcon,
|
||||
EditIcon,
|
||||
FavoritedIcon,
|
||||
FavoriteIcon,
|
||||
ImportIcon,
|
||||
PageIcon,
|
||||
} from '@blocksuite/icons';
|
||||
import type { PageMeta } from '@blocksuite/store';
|
||||
import { Divider } from '@toeverything/components/divider';
|
||||
import {
|
||||
useBlockSuitePageMeta,
|
||||
usePageMetaHelper,
|
||||
} from '@toeverything/hooks/use-block-suite-page-meta';
|
||||
import { useBlockSuiteWorkspaceHelper } from '@toeverything/hooks/use-block-suite-workspace-helper';
|
||||
import { useAtom, useSetAtom } from 'jotai';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { applyUpdate, encodeStateAsUpdate } from 'yjs';
|
||||
|
||||
import { pageSettingFamily, setPageModeAtom } from '../../../atoms';
|
||||
import { useBlockSuiteMetaHelper } from '../../../hooks/affine/use-block-suite-meta-helper';
|
||||
import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace';
|
||||
import { useNavigateHelper } from '../../../hooks/use-navigate-helper';
|
||||
import { toast } from '../../../utils';
|
||||
import { HeaderDropDownButton } from '../../pure/header-drop-down-button';
|
||||
import { usePageHelper } from '../block-suite-page-list/utils';
|
||||
|
||||
type PageMenuProps = {
|
||||
rename?: () => void;
|
||||
pageId: string;
|
||||
};
|
||||
|
||||
export const PageMenu = ({ rename, pageId }: PageMenuProps) => {
|
||||
const t = useAFFiNEI18N();
|
||||
// fixme(himself65): remove these hooks ASAP
|
||||
const [workspace] = useCurrentWorkspace();
|
||||
|
||||
const blockSuiteWorkspace = workspace.blockSuiteWorkspace;
|
||||
const pageMeta = useBlockSuitePageMeta(blockSuiteWorkspace).find(
|
||||
meta => meta.id === pageId
|
||||
) as PageMeta;
|
||||
const [setting, setSetting] = useAtom(pageSettingFamily(pageId));
|
||||
const mode = setting?.mode ?? 'page';
|
||||
|
||||
const favorite = pageMeta.favorite ?? false;
|
||||
const { setPageMeta, setPageTitle } = usePageMetaHelper(blockSuiteWorkspace);
|
||||
const [openConfirm, setOpenConfirm] = useState(false);
|
||||
const { removeToTrash } = useBlockSuiteMetaHelper(blockSuiteWorkspace);
|
||||
const { importFile } = usePageHelper(blockSuiteWorkspace);
|
||||
const handleFavorite = useCallback(() => {
|
||||
setPageMeta(pageId, { favorite: !favorite });
|
||||
toast(favorite ? t['Removed from Favorites']() : t['Added to Favorites']());
|
||||
}, [favorite, pageId, setPageMeta, t]);
|
||||
const handleSwitchMode = useCallback(() => {
|
||||
setSetting(setting => ({
|
||||
mode: setting?.mode === 'page' ? 'edgeless' : 'page',
|
||||
}));
|
||||
toast(
|
||||
mode === 'page'
|
||||
? t['com.affine.edgelessMode']()
|
||||
: t['com.affine.pageMode']()
|
||||
);
|
||||
}, [mode, setSetting, t]);
|
||||
const handleOnConfirm = useCallback(() => {
|
||||
removeToTrash(pageId);
|
||||
toast(t['Moved to Trash']());
|
||||
setOpenConfirm(false);
|
||||
}, [pageId, removeToTrash, t]);
|
||||
const menuItemStyle = {
|
||||
padding: '4px 12px',
|
||||
};
|
||||
const { openPage } = useNavigateHelper();
|
||||
const { createPage } = useBlockSuiteWorkspaceHelper(blockSuiteWorkspace);
|
||||
const setPageMode = useSetAtom(setPageModeAtom);
|
||||
const duplicate = useCallback(async () => {
|
||||
const currentPage = blockSuiteWorkspace.getPage(pageId);
|
||||
assertExists(currentPage);
|
||||
const currentPageMeta = currentPage.meta;
|
||||
const newPage = createPage();
|
||||
await newPage.waitForLoaded();
|
||||
const update = encodeStateAsUpdate(currentPage.spaceDoc);
|
||||
applyUpdate(newPage.spaceDoc, update);
|
||||
setPageMeta(newPage.id, {
|
||||
tags: currentPageMeta.tags,
|
||||
favorite: currentPageMeta.favorite,
|
||||
});
|
||||
setPageMode(newPage.id, mode);
|
||||
setPageTitle(newPage.id, `${currentPageMeta.title}(1)`);
|
||||
openPage(blockSuiteWorkspace.id, newPage.id);
|
||||
}, [
|
||||
blockSuiteWorkspace,
|
||||
createPage,
|
||||
mode,
|
||||
openPage,
|
||||
pageId,
|
||||
setPageMeta,
|
||||
setPageMode,
|
||||
setPageTitle,
|
||||
]);
|
||||
const EditMenu = (
|
||||
<>
|
||||
<MenuItem
|
||||
icon={<EditIcon />}
|
||||
data-testid="editor-option-menu-rename"
|
||||
onClick={rename}
|
||||
style={menuItemStyle}
|
||||
>
|
||||
{t['Rename']()}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={mode === 'page' ? <EdgelessIcon /> : <PageIcon />}
|
||||
data-testid="editor-option-menu-edgeless"
|
||||
onClick={handleSwitchMode}
|
||||
style={menuItemStyle}
|
||||
>
|
||||
{t['Convert to ']()}
|
||||
{mode === 'page' ? t['Edgeless']() : t['Page']()}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
data-testid="editor-option-menu-favorite"
|
||||
onClick={handleFavorite}
|
||||
style={menuItemStyle}
|
||||
icon={
|
||||
favorite ? (
|
||||
<FavoritedIcon style={{ color: 'var(--affine-primary-color)' }} />
|
||||
) : (
|
||||
<FavoriteIcon />
|
||||
)
|
||||
}
|
||||
>
|
||||
{favorite ? t['Remove from favorites']() : t['Add to Favorites']()}
|
||||
</MenuItem>
|
||||
{/* {TODO: add tag and duplicate function support} */}
|
||||
{/* <MenuItem
|
||||
icon={<TagsIcon />}
|
||||
data-testid="editor-option-menu-add-tag"
|
||||
onClick={() => {}}
|
||||
style={menuItemStyle}
|
||||
>
|
||||
{t['com.affine.header.option.add-tag']()}
|
||||
</MenuItem> */}
|
||||
<Divider />
|
||||
<MenuItem
|
||||
icon={<DuplicateIcon />}
|
||||
data-testid="editor-option-menu-duplicate"
|
||||
onClick={duplicate}
|
||||
style={menuItemStyle}
|
||||
>
|
||||
{t['com.affine.header.option.duplicate']()}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={<ImportIcon />}
|
||||
data-testid="editor-option-menu-import"
|
||||
onClick={importFile}
|
||||
style={menuItemStyle}
|
||||
>
|
||||
{t['Import']()}
|
||||
</MenuItem>
|
||||
<Export />
|
||||
<Divider />
|
||||
<MoveToTrash
|
||||
data-testid="editor-option-menu-delete"
|
||||
onItemClick={() => {
|
||||
setOpenConfirm(true);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<FlexWrapper alignItems="center" justifyContent="center">
|
||||
<Menu
|
||||
content={EditMenu}
|
||||
placement="bottom-end"
|
||||
disablePortal={true}
|
||||
trigger="click"
|
||||
menuStyles={{
|
||||
borderRadius: '8px',
|
||||
padding: '8px',
|
||||
background: 'var(--affine-background-overlay-panel-color)',
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<HeaderDropDownButton />
|
||||
</div>
|
||||
</Menu>
|
||||
<MoveToTrash.ConfirmModal
|
||||
open={openConfirm}
|
||||
title={pageMeta.title}
|
||||
onConfirm={handleOnConfirm}
|
||||
onCancel={() => {
|
||||
setOpenConfirm(false);
|
||||
}}
|
||||
/>
|
||||
</FlexWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
import { 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({
|
||||
flexGrow: 1,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
});
|
||||
|
||||
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',
|
||||
});
|
||||
@@ -0,0 +1,919 @@
|
||||
{
|
||||
"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": []
|
||||
}
|
||||
@@ -6,9 +6,9 @@ import { useAtom } from 'jotai';
|
||||
import type { CSSProperties } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { pageSettingFamily } from '../../../../atoms';
|
||||
import type { BlockSuiteWorkspace } from '../../../../shared';
|
||||
import { toast } from '../../../../utils';
|
||||
import { pageSettingFamily } from '../../../atoms';
|
||||
import type { BlockSuiteWorkspace } from '../../../shared';
|
||||
import { toast } from '../../../utils';
|
||||
import { StyledEditorModeSwitch, StyledKeyboardItem } from './style';
|
||||
import { EdgelessSwitchItem, PageSwitchItem } from './switch-items';
|
||||
|
||||
@@ -5,14 +5,15 @@ export const StyledEditorModeSwitch = styled('div')<{
|
||||
showAlone?: boolean;
|
||||
}>(({ switchLeft, showAlone }) => {
|
||||
return {
|
||||
width: showAlone ? '40px' : '78px',
|
||||
maxWidth: showAlone ? '40px' : '70px',
|
||||
gap: '8px',
|
||||
height: '32px',
|
||||
background: showAlone
|
||||
? 'transparent'
|
||||
: 'var(--affine-background-secondary-color)',
|
||||
borderRadius: '12px',
|
||||
...displayFlex('space-between', 'center'),
|
||||
padding: '0 8px',
|
||||
padding: '4px 4px',
|
||||
position: 'relative',
|
||||
|
||||
'::after': {
|
||||
@@ -25,7 +26,7 @@ export const StyledEditorModeSwitch = styled('div')<{
|
||||
borderRadius: '8px',
|
||||
zIndex: 1,
|
||||
position: 'absolute',
|
||||
transform: `translateX(${switchLeft ? '0' : '38px'})`,
|
||||
transform: `translateX(${switchLeft ? '0' : '32px'})`,
|
||||
transition: 'all .15s',
|
||||
},
|
||||
};
|
||||
@@ -54,7 +55,7 @@ export const StyledSwitchItem = styled('button')<{
|
||||
zIndex: 2,
|
||||
fontSize: '20px',
|
||||
path: {
|
||||
fill: 'currentColor',
|
||||
stroke: 'currentColor',
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -35,7 +35,7 @@ const HoverAnimateController = ({
|
||||
>
|
||||
{cloneElement(children, {
|
||||
isStopped: !startAnimate,
|
||||
speed: 5,
|
||||
speed: 1,
|
||||
width: 20,
|
||||
height: 20,
|
||||
})}
|
||||
@@ -11,8 +11,7 @@ import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-
|
||||
import { useBlockSuitePagePreview } from '@toeverything/hooks/use-block-suite-page-preview';
|
||||
import { useBlockSuiteWorkspacePage } from '@toeverything/hooks/use-block-suite-workspace-page';
|
||||
import { useAtom, useAtomValue } from 'jotai';
|
||||
import type React from 'react';
|
||||
import { Suspense, useMemo } from 'react';
|
||||
import { Suspense, useCallback, useMemo } from 'react';
|
||||
|
||||
import { allPageModeSelectAtom } from '../../../atoms';
|
||||
import { useBlockSuiteMetaHelper } from '../../../hooks/affine/use-block-suite-meta-helper';
|
||||
@@ -23,13 +22,13 @@ import { filterPage } from '../../../utils/filter';
|
||||
import { emptyDescButton, emptyDescKbd, pageListEmptyStyle } from './index.css';
|
||||
import { usePageHelper } from './utils';
|
||||
|
||||
export type BlockSuitePageListProps = {
|
||||
export interface BlockSuitePageListProps {
|
||||
blockSuiteWorkspace: BlockSuiteWorkspace;
|
||||
listType: 'all' | 'trash' | 'shared' | 'public';
|
||||
isPublic?: true;
|
||||
isPublic?: boolean;
|
||||
onOpenPage: (pageId: string, newTab?: boolean) => void;
|
||||
collection?: Collection;
|
||||
};
|
||||
}
|
||||
|
||||
const filter = {
|
||||
all: (pageMeta: PageMeta) => !pageMeta.trash,
|
||||
@@ -41,13 +40,12 @@ const filter = {
|
||||
shared: (pageMeta: PageMeta) => pageMeta.isPublic && !pageMeta.trash,
|
||||
};
|
||||
|
||||
const PagePreviewInner = ({
|
||||
workspace,
|
||||
pageId,
|
||||
}: {
|
||||
interface PagePreviewInnerProps {
|
||||
workspace: Workspace;
|
||||
pageId: string;
|
||||
}) => {
|
||||
}
|
||||
|
||||
const PagePreviewInner = ({ workspace, pageId }: PagePreviewInnerProps) => {
|
||||
const page = useBlockSuiteWorkspacePage(workspace, pageId);
|
||||
assertExists(page);
|
||||
const previewAtom = useBlockSuitePagePreview(page);
|
||||
@@ -55,13 +53,12 @@ const PagePreviewInner = ({
|
||||
return preview;
|
||||
};
|
||||
|
||||
const PagePreview = ({
|
||||
workspace,
|
||||
pageId,
|
||||
}: {
|
||||
interface PagePreviewProps {
|
||||
workspace: Workspace;
|
||||
pageId: string;
|
||||
}) => {
|
||||
}
|
||||
|
||||
const PagePreview = ({ workspace, pageId }: PagePreviewProps) => {
|
||||
return (
|
||||
<Suspense>
|
||||
<PagePreviewInner workspace={workspace} pageId={pageId} />
|
||||
@@ -69,17 +66,23 @@ const PagePreview = ({
|
||||
);
|
||||
};
|
||||
|
||||
const PageListEmpty = (props: {
|
||||
createPage?: () => void;
|
||||
interface PageListEmptyProps {
|
||||
createPage?: ReturnType<typeof usePageHelper>['createPage'];
|
||||
listType: BlockSuitePageListProps['listType'];
|
||||
}) => {
|
||||
}
|
||||
|
||||
const PageListEmpty = (props: PageListEmptyProps) => {
|
||||
const { listType, createPage } = props;
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
const onCreatePage = useCallback(() => {
|
||||
createPage?.();
|
||||
}, [createPage]);
|
||||
|
||||
const getEmptyDescription = () => {
|
||||
if (listType === 'all') {
|
||||
const CreateNewPageButton = () => (
|
||||
<button className={emptyDescButton} onClick={createPage}>
|
||||
const createNewPageButton = (
|
||||
<button className={emptyDescButton} onClick={onCreatePage}>
|
||||
New Page
|
||||
</button>
|
||||
);
|
||||
@@ -87,7 +90,7 @@ const PageListEmpty = (props: {
|
||||
const shortcut = environment.isMacOs ? '⌘ + N' : 'Ctrl + N';
|
||||
return (
|
||||
<Trans i18nKey="emptyAllPagesClient">
|
||||
Click on the <CreateNewPageButton /> button Or press
|
||||
Click on the {createNewPageButton} button Or press
|
||||
<kbd className={emptyDescKbd}>{{ shortcut } as any}</kbd> to create
|
||||
your first page.
|
||||
</Trans>
|
||||
@@ -96,7 +99,7 @@ const PageListEmpty = (props: {
|
||||
return (
|
||||
<Trans i18nKey="emptyAllPages">
|
||||
Click on the
|
||||
<CreateNewPageButton />
|
||||
{createNewPageButton}
|
||||
button to create your first page.
|
||||
</Trans>
|
||||
);
|
||||
@@ -120,13 +123,13 @@ const PageListEmpty = (props: {
|
||||
);
|
||||
};
|
||||
|
||||
export const BlockSuitePageList: React.FC<BlockSuitePageListProps> = ({
|
||||
export const BlockSuitePageList = ({
|
||||
blockSuiteWorkspace,
|
||||
onOpenPage,
|
||||
listType,
|
||||
isPublic = false,
|
||||
collection,
|
||||
}) => {
|
||||
}: BlockSuitePageListProps) => {
|
||||
const pageMetas = useBlockSuitePageMeta(blockSuiteWorkspace);
|
||||
const {
|
||||
toggleFavorite,
|
||||
@@ -258,6 +261,7 @@ export const BlockSuitePageList: React.FC<BlockSuitePageListProps> = ({
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<PageList
|
||||
workspaceId={blockSuiteWorkspace.id}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import { DownloadTips } from '@affine/component/affine-banner';
|
||||
import { isDesktop } from '@affine/env/constant';
|
||||
|
||||
export const DownloadClientTip = ({
|
||||
show,
|
||||
onClose,
|
||||
}: {
|
||||
// const [showDownloadClientTips, setShowDownloadClientTips] = useAtom(
|
||||
// guideDownloadClientTipAtom
|
||||
// );
|
||||
// const onCloseDownloadClient = useCallback(() => {
|
||||
// setShowDownloadClientTips(false);
|
||||
// }, [setShowDownloadClientTips]);
|
||||
|
||||
// if (!showDownloadClientTips || isDesktop) {
|
||||
// return <></>;
|
||||
// }
|
||||
|
||||
show: boolean;
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
if (!show || isDesktop) {
|
||||
return null;
|
||||
}
|
||||
return <DownloadTips onClose={onClose} />;
|
||||
};
|
||||
export default DownloadClientTip;
|
||||
@@ -1,669 +0,0 @@
|
||||
{
|
||||
"v": "5.9.0",
|
||||
"fr": 29.9700012207031,
|
||||
"ip": 0,
|
||||
"op": 60.0000024438501,
|
||||
"w": 500,
|
||||
"h": 500,
|
||||
"nm": "edgeless-hover",
|
||||
"ddd": 0,
|
||||
"assets": [],
|
||||
"layers": [
|
||||
{
|
||||
"ddd": 0,
|
||||
"ind": 1,
|
||||
"ty": 4,
|
||||
"nm": "Layer 3/paper-edgeless-icons Outlines",
|
||||
"sr": 1,
|
||||
"ks": {
|
||||
"o": { "a": 0, "k": 100, "ix": 11 },
|
||||
"r": { "a": 0, "k": 0, "ix": 10 },
|
||||
"p": { "a": 0, "k": [250, 250, 0], "ix": 2, "l": 2 },
|
||||
"a": { "a": 0, "k": [183.5, 183.5, 0], "ix": 1, "l": 2 },
|
||||
"s": { "a": 0, "k": [100, 100, 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, -5.604],
|
||||
[0, 0],
|
||||
[-5.614, 0],
|
||||
[0, 0],
|
||||
[0, 5.605],
|
||||
[0, 0],
|
||||
[5.615, 0]
|
||||
],
|
||||
"o": [
|
||||
[-5.614, 0],
|
||||
[0, 0],
|
||||
[0, 5.605],
|
||||
[0, 0],
|
||||
[5.615, 0],
|
||||
[0, 0],
|
||||
[0, -5.604],
|
||||
[0, 0]
|
||||
],
|
||||
"v": [
|
||||
[-30.525, -40.7],
|
||||
[-40.699, -30.525],
|
||||
[-40.699, 30.524],
|
||||
[-30.525, 40.699],
|
||||
[30.525, 40.699],
|
||||
[40.699, 30.524],
|
||||
[40.699, -30.525],
|
||||
[30.525, -40.7]
|
||||
],
|
||||
"c": true
|
||||
},
|
||||
"ix": 2
|
||||
},
|
||||
"nm": "Path 1",
|
||||
"mn": "ADBE Vector Shape - Group",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ind": 1,
|
||||
"ty": "sh",
|
||||
"ix": 2,
|
||||
"ks": {
|
||||
"a": 0,
|
||||
"k": {
|
||||
"i": [
|
||||
[22.447, 0],
|
||||
[0, 0],
|
||||
[0, 22.437],
|
||||
[0, 0],
|
||||
[-22.446, 0],
|
||||
[0, 0],
|
||||
[0, -22.437],
|
||||
[0, 0]
|
||||
],
|
||||
"o": [
|
||||
[0, 0],
|
||||
[-22.446, 0],
|
||||
[0, 0],
|
||||
[0, -22.437],
|
||||
[0, 0],
|
||||
[22.447, 0],
|
||||
[0, 0],
|
||||
[0, 22.437]
|
||||
],
|
||||
"v": [
|
||||
[30.525, 71.224],
|
||||
[-30.525, 71.224],
|
||||
[-71.225, 30.524],
|
||||
[-71.225, -30.525],
|
||||
[-30.525, -71.225],
|
||||
[30.525, -71.225],
|
||||
[71.224, -30.525],
|
||||
[71.224, 30.524]
|
||||
],
|
||||
"c": true
|
||||
},
|
||||
"ix": 2
|
||||
},
|
||||
"nm": "Path 2",
|
||||
"mn": "ADBE Vector Shape - Group",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "mm",
|
||||
"mm": 1,
|
||||
"nm": "Merge Paths 1",
|
||||
"mn": "ADBE Vector Filter - Merge",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "fl",
|
||||
"c": {
|
||||
"a": 0,
|
||||
"k": [0.466666696586, 0.458823559331, 0.490196108351, 1],
|
||||
"ix": 4
|
||||
},
|
||||
"o": { "a": 0, "k": 100, "ix": 5 },
|
||||
"r": 1,
|
||||
"bm": 0,
|
||||
"nm": "Fill 1",
|
||||
"mn": "ADBE Vector Graphic - Fill",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "tr",
|
||||
"p": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"i": { "x": 0.833, "y": 0.833 },
|
||||
"o": { "x": 0.167, "y": 0.167 },
|
||||
"t": 0,
|
||||
"s": [295.322, 295.323],
|
||||
"to": [0, 0],
|
||||
"ti": [0, 0]
|
||||
},
|
||||
{
|
||||
"i": { "x": 0.833, "y": 0.833 },
|
||||
"o": { "x": 0.167, "y": 0.167 },
|
||||
"t": 30,
|
||||
"s": [315.322, 315.323],
|
||||
"to": [0, 0],
|
||||
"ti": [0, 0]
|
||||
},
|
||||
{ "t": 58.0000023623884, "s": [295.322, 295.323] }
|
||||
],
|
||||
"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": "Transform"
|
||||
}
|
||||
],
|
||||
"nm": "Group 1",
|
||||
"np": 4,
|
||||
"cix": 2,
|
||||
"bm": 0,
|
||||
"ix": 1,
|
||||
"mn": "ADBE Vector Group",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "gr",
|
||||
"it": [
|
||||
{
|
||||
"ind": 0,
|
||||
"ty": "sh",
|
||||
"ix": 1,
|
||||
"ks": {
|
||||
"a": 0,
|
||||
"k": {
|
||||
"i": [
|
||||
[8.426, 0],
|
||||
[0, 8.426],
|
||||
[0, 0],
|
||||
[-8.426, 0],
|
||||
[0, -8.426],
|
||||
[0, 0]
|
||||
],
|
||||
"o": [
|
||||
[-8.426, 0],
|
||||
[0, 0],
|
||||
[0, -8.426],
|
||||
[8.426, 0],
|
||||
[0, 0],
|
||||
[0, 8.426]
|
||||
],
|
||||
"v": [
|
||||
[0, 30.525],
|
||||
[-15.262, 15.263],
|
||||
[-15.262, -15.262],
|
||||
[0, -30.525],
|
||||
[15.262, -15.262],
|
||||
[15.262, 15.263]
|
||||
],
|
||||
"c": true
|
||||
},
|
||||
"ix": 2
|
||||
},
|
||||
"nm": "Path 1",
|
||||
"mn": "ADBE Vector Shape - Group",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "fl",
|
||||
"c": {
|
||||
"a": 0,
|
||||
"k": [0.466666696586, 0.458823559331, 0.490196108351, 1],
|
||||
"ix": 4
|
||||
},
|
||||
"o": { "a": 0, "k": 100, "ix": 5 },
|
||||
"r": 1,
|
||||
"bm": 0,
|
||||
"nm": "Fill 1",
|
||||
"mn": "ADBE Vector Graphic - Fill",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "tr",
|
||||
"p": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"i": { "x": 0.833, "y": 0.833 },
|
||||
"o": { "x": 0.167, "y": 0.167 },
|
||||
"t": 0,
|
||||
"s": [76.561, 183.399],
|
||||
"to": [0, 0],
|
||||
"ti": [0, 0]
|
||||
},
|
||||
{
|
||||
"i": { "x": 0.833, "y": 0.833 },
|
||||
"o": { "x": 0.167, "y": 0.167 },
|
||||
"t": 30,
|
||||
"s": [56.561, 183.399],
|
||||
"to": [0, 0],
|
||||
"ti": [0, 0]
|
||||
},
|
||||
{ "t": 58.0000023623884, "s": [76.561, 183.399] }
|
||||
],
|
||||
"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": "Transform"
|
||||
}
|
||||
],
|
||||
"nm": "Group 2",
|
||||
"np": 2,
|
||||
"cix": 2,
|
||||
"bm": 0,
|
||||
"ix": 2,
|
||||
"mn": "ADBE Vector Group",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "gr",
|
||||
"it": [
|
||||
{
|
||||
"ind": 0,
|
||||
"ty": "sh",
|
||||
"ix": 1,
|
||||
"ks": {
|
||||
"a": 0,
|
||||
"k": {
|
||||
"i": [
|
||||
[8.426, 0],
|
||||
[0, 8.426],
|
||||
[5.395, 13.057],
|
||||
[9.956, 9.956],
|
||||
[13.037, 5.406],
|
||||
[14.1, 0],
|
||||
[0, 8.426],
|
||||
[-8.426, 0],
|
||||
[-16.753, -6.955],
|
||||
[-12.808, -12.818],
|
||||
[-6.955, -16.773],
|
||||
[0, -18.105]
|
||||
],
|
||||
"o": [
|
||||
[-8.426, 0],
|
||||
[0, -14.09],
|
||||
[-5.416, -13.016],
|
||||
[-9.957, -9.956],
|
||||
[-13.026, -5.385],
|
||||
[-8.426, 0],
|
||||
[0, -8.426],
|
||||
[18.134, 0],
|
||||
[16.763, 6.936],
|
||||
[12.788, 12.778],
|
||||
[6.936, 16.773],
|
||||
[0, 8.426]
|
||||
],
|
||||
"v": [
|
||||
[61.049, 76.312],
|
||||
[45.788, 61.05],
|
||||
[37.66, 20.151],
|
||||
[14.497, -14.487],
|
||||
[-20.161, -37.659],
|
||||
[-61.049, -45.787],
|
||||
[-76.311, -61.049],
|
||||
[-61.049, -76.312],
|
||||
[-8.475, -65.839],
|
||||
[36.09, -36.069],
|
||||
[65.858, 8.486],
|
||||
[76.311, 61.05]
|
||||
],
|
||||
"c": true
|
||||
},
|
||||
"ix": 2
|
||||
},
|
||||
"nm": "Path 1",
|
||||
"mn": "ADBE Vector Shape - Group",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "fl",
|
||||
"c": {
|
||||
"a": 0,
|
||||
"k": [0.466666696586, 0.458823559331, 0.490196108351, 1],
|
||||
"ix": 4
|
||||
},
|
||||
"o": { "a": 0, "k": 100, "ix": 5 },
|
||||
"r": 1,
|
||||
"bm": 0,
|
||||
"nm": "Fill 1",
|
||||
"mn": "ADBE Vector Graphic - Fill",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "tr",
|
||||
"p": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"i": { "x": 0.833, "y": 0.833 },
|
||||
"o": { "x": 0.167, "y": 0.167 },
|
||||
"t": 0,
|
||||
"s": [254.447, 112.349],
|
||||
"to": [0, 0],
|
||||
"ti": [0, 0]
|
||||
},
|
||||
{
|
||||
"i": { "x": 0.833, "y": 0.833 },
|
||||
"o": { "x": 0.167, "y": 0.167 },
|
||||
"t": 30,
|
||||
"s": [274.447, 92.349],
|
||||
"to": [0, 0],
|
||||
"ti": [0, 0]
|
||||
},
|
||||
{ "t": 58.0000023623884, "s": [254.447, 112.349] }
|
||||
],
|
||||
"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": "Transform"
|
||||
}
|
||||
],
|
||||
"nm": "Group 3",
|
||||
"np": 2,
|
||||
"cix": 2,
|
||||
"bm": 0,
|
||||
"ix": 3,
|
||||
"mn": "ADBE Vector Group",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "gr",
|
||||
"it": [
|
||||
{
|
||||
"ind": 0,
|
||||
"ty": "sh",
|
||||
"ix": 1,
|
||||
"ks": {
|
||||
"a": 0,
|
||||
"k": {
|
||||
"i": [
|
||||
[22.446, 0],
|
||||
[0, -22.437],
|
||||
[-22.446, 0],
|
||||
[0, 22.436]
|
||||
],
|
||||
"o": [
|
||||
[-22.446, 0],
|
||||
[0, 22.436],
|
||||
[22.446, 0],
|
||||
[0, -22.437]
|
||||
],
|
||||
"v": [
|
||||
[0, -40.7],
|
||||
[-40.7, 0],
|
||||
[0, 40.699],
|
||||
[40.7, 0]
|
||||
],
|
||||
"c": true
|
||||
},
|
||||
"ix": 2
|
||||
},
|
||||
"nm": "Path 1",
|
||||
"mn": "ADBE Vector Shape - Group",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ind": 1,
|
||||
"ty": "sh",
|
||||
"ix": 2,
|
||||
"ks": {
|
||||
"a": 0,
|
||||
"k": {
|
||||
"i": [
|
||||
[39.269, 0],
|
||||
[0, 39.268],
|
||||
[-39.269, 0],
|
||||
[0, -39.269]
|
||||
],
|
||||
"o": [
|
||||
[-39.269, 0],
|
||||
[0, -39.269],
|
||||
[39.269, 0],
|
||||
[0, 39.268]
|
||||
],
|
||||
"v": [
|
||||
[0, 71.224],
|
||||
[-71.224, 0],
|
||||
[0, -71.225],
|
||||
[71.224, 0]
|
||||
],
|
||||
"c": true
|
||||
},
|
||||
"ix": 2
|
||||
},
|
||||
"nm": "Path 2",
|
||||
"mn": "ADBE Vector Shape - Group",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "mm",
|
||||
"mm": 1,
|
||||
"nm": "Merge Paths 1",
|
||||
"mn": "ADBE Vector Filter - Merge",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "fl",
|
||||
"c": {
|
||||
"a": 0,
|
||||
"k": [0.466666696586, 0.458823559331, 0.490196108351, 1],
|
||||
"ix": 4
|
||||
},
|
||||
"o": { "a": 0, "k": 100, "ix": 5 },
|
||||
"r": 1,
|
||||
"bm": 0,
|
||||
"nm": "Fill 1",
|
||||
"mn": "ADBE Vector Graphic - Fill",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "tr",
|
||||
"p": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"i": { "x": 0.833, "y": 0.833 },
|
||||
"o": { "x": 0.167, "y": 0.167 },
|
||||
"t": 0,
|
||||
"s": [71.474, 295.323],
|
||||
"to": [0, 0],
|
||||
"ti": [0, 0]
|
||||
},
|
||||
{
|
||||
"i": { "x": 0.833, "y": 0.833 },
|
||||
"o": { "x": 0.167, "y": 0.167 },
|
||||
"t": 30,
|
||||
"s": [51.474, 315.323],
|
||||
"to": [0, 0],
|
||||
"ti": [0, 0]
|
||||
},
|
||||
{ "t": 58.0000023623884, "s": [71.474, 295.323] }
|
||||
],
|
||||
"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": "Transform"
|
||||
}
|
||||
],
|
||||
"nm": "Group 4",
|
||||
"np": 4,
|
||||
"cix": 2,
|
||||
"bm": 0,
|
||||
"ix": 4,
|
||||
"mn": "ADBE Vector Group",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "gr",
|
||||
"it": [
|
||||
{
|
||||
"ind": 0,
|
||||
"ty": "sh",
|
||||
"ix": 1,
|
||||
"ks": {
|
||||
"a": 0,
|
||||
"k": {
|
||||
"i": [
|
||||
[22.446, 0],
|
||||
[0, -22.437],
|
||||
[-22.446, 0],
|
||||
[0, 22.436]
|
||||
],
|
||||
"o": [
|
||||
[-22.446, 0],
|
||||
[0, 22.436],
|
||||
[22.446, 0],
|
||||
[0, -22.437]
|
||||
],
|
||||
"v": [
|
||||
[0, -40.7],
|
||||
[-40.7, 0.001],
|
||||
[0, 40.7],
|
||||
[40.7, 0.001]
|
||||
],
|
||||
"c": true
|
||||
},
|
||||
"ix": 2
|
||||
},
|
||||
"nm": "Path 1",
|
||||
"mn": "ADBE Vector Shape - Group",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ind": 1,
|
||||
"ty": "sh",
|
||||
"ix": 2,
|
||||
"ks": {
|
||||
"a": 0,
|
||||
"k": {
|
||||
"i": [
|
||||
[39.269, 0],
|
||||
[0, 39.268],
|
||||
[-39.269, 0],
|
||||
[0, -39.269]
|
||||
],
|
||||
"o": [
|
||||
[-39.269, 0],
|
||||
[0, -39.269],
|
||||
[39.269, 0],
|
||||
[0, 39.268]
|
||||
],
|
||||
"v": [
|
||||
[0, 71.225],
|
||||
[-71.224, 0.001],
|
||||
[0, -71.225],
|
||||
[71.224, 0.001]
|
||||
],
|
||||
"c": true
|
||||
},
|
||||
"ix": 2
|
||||
},
|
||||
"nm": "Path 2",
|
||||
"mn": "ADBE Vector Shape - Group",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "mm",
|
||||
"mm": 1,
|
||||
"nm": "Merge Paths 1",
|
||||
"mn": "ADBE Vector Filter - Merge",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "fl",
|
||||
"c": {
|
||||
"a": 0,
|
||||
"k": [0.466666696586, 0.458823559331, 0.490196108351, 1],
|
||||
"ix": 4
|
||||
},
|
||||
"o": { "a": 0, "k": 100, "ix": 5 },
|
||||
"r": 1,
|
||||
"bm": 0,
|
||||
"nm": "Fill 1",
|
||||
"mn": "ADBE Vector Graphic - Fill",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "tr",
|
||||
"p": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"i": { "x": 0.833, "y": 0.833 },
|
||||
"o": { "x": 0.167, "y": 0.167 },
|
||||
"t": 0,
|
||||
"s": [71.474, 71.475],
|
||||
"to": [0, 0],
|
||||
"ti": [0, 0]
|
||||
},
|
||||
{
|
||||
"i": { "x": 0.833, "y": 0.833 },
|
||||
"o": { "x": 0.167, "y": 0.167 },
|
||||
"t": 30,
|
||||
"s": [51.474, 51.475],
|
||||
"to": [0, 0],
|
||||
"ti": [0, 0]
|
||||
},
|
||||
{ "t": 58.0000023623884, "s": [71.474, 71.475] }
|
||||
],
|
||||
"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": "Transform"
|
||||
}
|
||||
],
|
||||
"nm": "Group 5",
|
||||
"np": 4,
|
||||
"cix": 2,
|
||||
"bm": 0,
|
||||
"ix": 5,
|
||||
"mn": "ADBE Vector Group",
|
||||
"hd": false
|
||||
}
|
||||
],
|
||||
"ip": 0,
|
||||
"op": 60.0000024438501,
|
||||
"st": 0,
|
||||
"bm": 0
|
||||
}
|
||||
],
|
||||
"markers": []
|
||||
}
|
||||
@@ -1,521 +0,0 @@
|
||||
{
|
||||
"v": "5.9.0",
|
||||
"fr": 29.9700012207031,
|
||||
"ip": 0,
|
||||
"op": 60.0000024438501,
|
||||
"w": 500,
|
||||
"h": 500,
|
||||
"nm": "page-hover",
|
||||
"ddd": 0,
|
||||
"assets": [],
|
||||
"layers": [
|
||||
{
|
||||
"ddd": 0,
|
||||
"ind": 1,
|
||||
"ty": 4,
|
||||
"nm": "Layer 1/paper-edgeless-icons Outlines",
|
||||
"sr": 1,
|
||||
"ks": {
|
||||
"o": { "a": 0, "k": 100, "ix": 11 },
|
||||
"r": { "a": 0, "k": 0, "ix": 10 },
|
||||
"p": { "a": 0, "k": [250, 250, 0], "ix": 2, "l": 2 },
|
||||
"a": { "a": 0, "k": [183, 183, 0], "ix": 1, "l": 2 },
|
||||
"s": { "a": 0, "k": [100, 100, 100], "ix": 6, "l": 2 }
|
||||
},
|
||||
"ao": 0,
|
||||
"shapes": [
|
||||
{
|
||||
"ty": "gr",
|
||||
"it": [
|
||||
{
|
||||
"ind": 0,
|
||||
"ty": "sh",
|
||||
"ix": 1,
|
||||
"ks": {
|
||||
"a": 0,
|
||||
"k": {
|
||||
"i": [
|
||||
[22.557, 0],
|
||||
[0, 0],
|
||||
[0, 8.639],
|
||||
[-8.64, 0],
|
||||
[0, 0],
|
||||
[-2.201, 1.651],
|
||||
[0, 9.536],
|
||||
[0, 0],
|
||||
[-8.64, 0],
|
||||
[0, -8.64],
|
||||
[0, 0],
|
||||
[16.892, -16.892]
|
||||
],
|
||||
"o": [
|
||||
[0, 0],
|
||||
[-8.64, 0],
|
||||
[0, -8.64],
|
||||
[0, 0],
|
||||
[17.442, 0],
|
||||
[10.412, -10.453],
|
||||
[0, 0],
|
||||
[0, -8.64],
|
||||
[8.639, 0],
|
||||
[0, 0],
|
||||
[0, 18.155],
|
||||
[-9.027, 8.987]
|
||||
],
|
||||
"v": [
|
||||
[46.947, 182.579],
|
||||
[-109.545, 182.579],
|
||||
[-125.194, 166.93],
|
||||
[-109.545, 151.281],
|
||||
[46.947, 151.281],
|
||||
[78.001, 145.086],
|
||||
[93.895, 114.766],
|
||||
[93.895, -166.93],
|
||||
[109.545, -182.579],
|
||||
[125.194, -166.93],
|
||||
[125.194, 114.766],
|
||||
[99.743, 167.561]
|
||||
],
|
||||
"c": true
|
||||
},
|
||||
"ix": 2
|
||||
},
|
||||
"nm": "Path 1",
|
||||
"mn": "ADBE Vector Shape - Group",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "fl",
|
||||
"c": {
|
||||
"a": 0,
|
||||
"k": [0.466666696586, 0.458823559331, 0.490196108351, 1],
|
||||
"ix": 4
|
||||
},
|
||||
"o": { "a": 0, "k": 100, "ix": 5 },
|
||||
"r": 1,
|
||||
"bm": 0,
|
||||
"nm": "Fill 1",
|
||||
"mn": "ADBE Vector Graphic - Fill",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "tr",
|
||||
"p": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"i": { "x": 0.833, "y": 0.833 },
|
||||
"o": { "x": 0.167, "y": 0.167 },
|
||||
"t": 0,
|
||||
"s": [240.204, 182.829],
|
||||
"to": [0, 0],
|
||||
"ti": [0, 0]
|
||||
},
|
||||
{
|
||||
"i": { "x": 0.833, "y": 0.833 },
|
||||
"o": { "x": 0.167, "y": 0.167 },
|
||||
"t": 30,
|
||||
"s": [260.204, 202.829],
|
||||
"to": [0, 0],
|
||||
"ti": [0, 0]
|
||||
},
|
||||
{ "t": 58.0000023623884, "s": [240.204, 182.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": "Transform"
|
||||
}
|
||||
],
|
||||
"nm": "Group 1",
|
||||
"np": 2,
|
||||
"cix": 2,
|
||||
"bm": 0,
|
||||
"ix": 1,
|
||||
"mn": "ADBE Vector Group",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "gr",
|
||||
"it": [
|
||||
{
|
||||
"ind": 0,
|
||||
"ty": "sh",
|
||||
"ix": 1,
|
||||
"ks": {
|
||||
"a": 0,
|
||||
"k": {
|
||||
"i": [
|
||||
[8.64, 0],
|
||||
[0, 8.639],
|
||||
[0, 0],
|
||||
[-16.892, 16.882],
|
||||
[-22.598, 0],
|
||||
[0, 0],
|
||||
[0, -8.64],
|
||||
[8.639, 0],
|
||||
[0, 0],
|
||||
[2.171, -1.65],
|
||||
[0, -9.546],
|
||||
[0, 0]
|
||||
],
|
||||
"o": [
|
||||
[-8.64, 0],
|
||||
[0, 0],
|
||||
[0, -18.145],
|
||||
[8.976, -8.986],
|
||||
[0, 0],
|
||||
[8.639, 0],
|
||||
[0, 8.64],
|
||||
[0, 0],
|
||||
[-17.453, 0],
|
||||
[-10.443, 10.484],
|
||||
[0, 0],
|
||||
[0, 8.639]
|
||||
],
|
||||
"v": [
|
||||
[-109.545, 182.579],
|
||||
[-125.194, 166.93],
|
||||
[-125.194, -114.766],
|
||||
[-99.743, -167.562],
|
||||
[-46.948, -182.579],
|
||||
[109.545, -182.579],
|
||||
[125.194, -166.93],
|
||||
[109.545, -151.281],
|
||||
[-46.948, -151.281],
|
||||
[-77.972, -145.107],
|
||||
[-93.896, -114.766],
|
||||
[-93.896, 166.93]
|
||||
],
|
||||
"c": true
|
||||
},
|
||||
"ix": 2
|
||||
},
|
||||
"nm": "Path 1",
|
||||
"mn": "ADBE Vector Shape - Group",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "fl",
|
||||
"c": {
|
||||
"a": 0,
|
||||
"k": [0.466666696586, 0.458823559331, 0.490196108351, 1],
|
||||
"ix": 4
|
||||
},
|
||||
"o": { "a": 0, "k": 100, "ix": 5 },
|
||||
"r": 1,
|
||||
"bm": 0,
|
||||
"nm": "Fill 1",
|
||||
"mn": "ADBE Vector Graphic - Fill",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "tr",
|
||||
"p": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"i": { "x": 0.833, "y": 0.833 },
|
||||
"o": { "x": 0.167, "y": 0.167 },
|
||||
"t": 0,
|
||||
"s": [125.443, 182.829],
|
||||
"to": [0, 0],
|
||||
"ti": [0, 0]
|
||||
},
|
||||
{
|
||||
"i": { "x": 0.833, "y": 0.833 },
|
||||
"o": { "x": 0.167, "y": 0.167 },
|
||||
"t": 30,
|
||||
"s": [105.443, 162.829],
|
||||
"to": [0, 0],
|
||||
"ti": [0, 0]
|
||||
},
|
||||
{ "t": 58.0000023623884, "s": [125.443, 182.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": "Transform"
|
||||
}
|
||||
],
|
||||
"nm": "Group 2",
|
||||
"np": 2,
|
||||
"cix": 2,
|
||||
"bm": 0,
|
||||
"ix": 2,
|
||||
"mn": "ADBE Vector Group",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "gr",
|
||||
"it": [
|
||||
{
|
||||
"ind": 0,
|
||||
"ty": "sh",
|
||||
"ix": 1,
|
||||
"ks": {
|
||||
"a": 0,
|
||||
"k": {
|
||||
"i": [
|
||||
[8.64, 0],
|
||||
[0, 0],
|
||||
[0, 8.639],
|
||||
[-8.64, 0],
|
||||
[0, 0],
|
||||
[0, -8.64]
|
||||
],
|
||||
"o": [
|
||||
[0, 0],
|
||||
[-8.64, 0],
|
||||
[0, -8.64],
|
||||
[0, 0],
|
||||
[8.64, 0],
|
||||
[0, 8.639]
|
||||
],
|
||||
"v": [
|
||||
[67.813, 15.649],
|
||||
[-67.813, 15.649],
|
||||
[-83.463, 0],
|
||||
[-67.813, -15.649],
|
||||
[67.813, -15.649],
|
||||
[83.462, 0]
|
||||
],
|
||||
"c": true
|
||||
},
|
||||
"ix": 2
|
||||
},
|
||||
"nm": "Path 1",
|
||||
"mn": "ADBE Vector Shape - Group",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "fl",
|
||||
"c": {
|
||||
"a": 0,
|
||||
"k": [0.466666696586, 0.458823559331, 0.490196108351, 1],
|
||||
"ix": 4
|
||||
},
|
||||
"o": { "a": 0, "k": 100, "ix": 5 },
|
||||
"r": 1,
|
||||
"bm": 0,
|
||||
"nm": "Fill 1",
|
||||
"mn": "ADBE Vector Graphic - Fill",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "tr",
|
||||
"p": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"i": { "x": 0.833, "y": 0.833 },
|
||||
"o": { "x": 0.167, "y": 0.167 },
|
||||
"t": 0,
|
||||
"s": [182.824, 271.513],
|
||||
"to": [0, 0],
|
||||
"ti": [0, 0]
|
||||
},
|
||||
{
|
||||
"i": { "x": 0.833, "y": 0.833 },
|
||||
"o": { "x": 0.167, "y": 0.167 },
|
||||
"t": 30,
|
||||
"s": [212.824, 281.513],
|
||||
"to": [0, 0],
|
||||
"ti": [0, 0]
|
||||
},
|
||||
{ "t": 58.0000023623884, "s": [182.824, 271.513] }
|
||||
],
|
||||
"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": "Transform"
|
||||
}
|
||||
],
|
||||
"nm": "Group 3",
|
||||
"np": 2,
|
||||
"cix": 2,
|
||||
"bm": 0,
|
||||
"ix": 3,
|
||||
"mn": "ADBE Vector Group",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "gr",
|
||||
"it": [
|
||||
{
|
||||
"ind": 0,
|
||||
"ty": "sh",
|
||||
"ix": 1,
|
||||
"ks": {
|
||||
"a": 0,
|
||||
"k": {
|
||||
"i": [
|
||||
[0, 0],
|
||||
[0, -5.757],
|
||||
[0, 0],
|
||||
[-5.756, 0],
|
||||
[0, 0],
|
||||
[0, 5.756],
|
||||
[0, 0],
|
||||
[5.747, 0]
|
||||
],
|
||||
"o": [
|
||||
[-5.756, 0],
|
||||
[0, 0],
|
||||
[0, 5.756],
|
||||
[0, 0],
|
||||
[5.747, 0],
|
||||
[0, 0],
|
||||
[0, -5.757],
|
||||
[0, 0]
|
||||
],
|
||||
"v": [
|
||||
[-41.732, -31.294],
|
||||
[-52.165, -20.86],
|
||||
[-52.165, 20.87],
|
||||
[-41.732, 31.303],
|
||||
[41.73, 31.303],
|
||||
[52.163, 20.87],
|
||||
[52.163, -20.86],
|
||||
[41.73, -31.294]
|
||||
],
|
||||
"c": true
|
||||
},
|
||||
"ix": 2
|
||||
},
|
||||
"nm": "Path 1",
|
||||
"mn": "ADBE Vector Shape - Group",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ind": 1,
|
||||
"ty": "sh",
|
||||
"ix": 2,
|
||||
"ks": {
|
||||
"a": 0,
|
||||
"k": {
|
||||
"i": [
|
||||
[23.006, 0],
|
||||
[0, 0],
|
||||
[0, 23.015],
|
||||
[0, 0],
|
||||
[-23.015, 0],
|
||||
[0, 0],
|
||||
[0, -23.016],
|
||||
[0, 0]
|
||||
],
|
||||
"o": [
|
||||
[0, 0],
|
||||
[-23.015, 0],
|
||||
[0, 0],
|
||||
[0, -23.016],
|
||||
[0, 0],
|
||||
[23.006, 0],
|
||||
[0, 0],
|
||||
[0, 23.015]
|
||||
],
|
||||
"v": [
|
||||
[41.73, 62.592],
|
||||
[-41.732, 62.592],
|
||||
[-83.463, 20.87],
|
||||
[-83.463, -20.86],
|
||||
[-41.732, -62.592],
|
||||
[41.73, -62.592],
|
||||
[83.463, -20.86],
|
||||
[83.463, 20.87]
|
||||
],
|
||||
"c": true
|
||||
},
|
||||
"ix": 2
|
||||
},
|
||||
"nm": "Path 2",
|
||||
"mn": "ADBE Vector Shape - Group",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "mm",
|
||||
"mm": 1,
|
||||
"nm": "Merge Paths 1",
|
||||
"mn": "ADBE Vector Filter - Merge",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "fl",
|
||||
"c": {
|
||||
"a": 0,
|
||||
"k": [0.466666696586, 0.458823559331, 0.490196108351, 1],
|
||||
"ix": 4
|
||||
},
|
||||
"o": { "a": 0, "k": 100, "ix": 5 },
|
||||
"r": 1,
|
||||
"bm": 0,
|
||||
"nm": "Fill 1",
|
||||
"mn": "ADBE Vector Graphic - Fill",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "tr",
|
||||
"p": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"i": { "x": 0.833, "y": 0.833 },
|
||||
"o": { "x": 0.167, "y": 0.167 },
|
||||
"t": 0,
|
||||
"s": [182.824, 141.088],
|
||||
"to": [0, 0],
|
||||
"ti": [0, 0]
|
||||
},
|
||||
{
|
||||
"i": { "x": 0.833, "y": 0.833 },
|
||||
"o": { "x": 0.167, "y": 0.167 },
|
||||
"t": 30,
|
||||
"s": [152.824, 131.088],
|
||||
"to": [0, 0],
|
||||
"ti": [0, 0]
|
||||
},
|
||||
{ "t": 58.0000023623884, "s": [182.824, 141.088] }
|
||||
],
|
||||
"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": "Transform"
|
||||
}
|
||||
],
|
||||
"nm": "Group 4",
|
||||
"np": 4,
|
||||
"cix": 2,
|
||||
"bm": 0,
|
||||
"ix": 4,
|
||||
"mn": "ADBE Vector Group",
|
||||
"hd": false
|
||||
}
|
||||
],
|
||||
"ip": 0,
|
||||
"op": 60.0000024438501,
|
||||
"st": 0,
|
||||
"bm": 0
|
||||
}
|
||||
],
|
||||
"markers": []
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
// fixme(himself65): refactor this file
|
||||
import { FlexWrapper, IconButton, Menu, MenuItem } from '@affine/component';
|
||||
import { Export, MoveToTrash } from '@affine/component/page-list';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import {
|
||||
EdgelessIcon,
|
||||
FavoritedIcon,
|
||||
FavoriteIcon,
|
||||
MoreVerticalIcon,
|
||||
PageIcon,
|
||||
} from '@blocksuite/icons';
|
||||
import {
|
||||
useBlockSuitePageMeta,
|
||||
usePageMetaHelper,
|
||||
} from '@toeverything/hooks/use-block-suite-page-meta';
|
||||
import { currentPageIdAtom } from '@toeverything/plugin-infra/atom';
|
||||
import { useAtom, useAtomValue } from 'jotai';
|
||||
import { useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { pageSettingFamily } from '../../../../atoms';
|
||||
import { useBlockSuiteMetaHelper } from '../../../../hooks/affine/use-block-suite-meta-helper';
|
||||
import { useCurrentWorkspace } from '../../../../hooks/current/use-current-workspace';
|
||||
import { toast } from '../../../../utils';
|
||||
import { MenuThemeModeSwitch } from '../header-right-items/theme-mode-switch';
|
||||
import * as styles from '../styles.css';
|
||||
import { LanguageMenu } from './language-menu';
|
||||
const CommonMenu = () => {
|
||||
const content = (
|
||||
<div
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<MenuThemeModeSwitch />
|
||||
<LanguageMenu />
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<FlexWrapper alignItems="center" justifyContent="center">
|
||||
<Menu
|
||||
content={content}
|
||||
placement="bottom"
|
||||
disablePortal={true}
|
||||
trigger="click"
|
||||
>
|
||||
<IconButton data-testid="editor-option-menu">
|
||||
<MoreVerticalIcon />
|
||||
</IconButton>
|
||||
</Menu>
|
||||
</FlexWrapper>
|
||||
);
|
||||
};
|
||||
const PageMenu = () => {
|
||||
const t = useAFFiNEI18N();
|
||||
// fixme(himself65): remove these hooks ASAP
|
||||
const [workspace] = useCurrentWorkspace();
|
||||
const pageId = useAtomValue(currentPageIdAtom);
|
||||
assertExists(workspace);
|
||||
assertExists(pageId);
|
||||
const blockSuiteWorkspace = workspace.blockSuiteWorkspace;
|
||||
const pageMeta = useBlockSuitePageMeta(blockSuiteWorkspace).find(
|
||||
meta => meta.id === pageId
|
||||
);
|
||||
assertExists(pageMeta);
|
||||
const [setting, setSetting] = useAtom(pageSettingFamily(pageId));
|
||||
const mode = setting?.mode ?? 'page';
|
||||
|
||||
const favorite = pageMeta.favorite ?? false;
|
||||
const { setPageMeta } = usePageMetaHelper(blockSuiteWorkspace);
|
||||
const [openConfirm, setOpenConfirm] = useState(false);
|
||||
const { removeToTrash } = useBlockSuiteMetaHelper(blockSuiteWorkspace);
|
||||
const EditMenu = (
|
||||
<>
|
||||
<>
|
||||
<MenuItem
|
||||
data-testid="editor-option-menu-favorite"
|
||||
onClick={() => {
|
||||
setPageMeta(pageId, { favorite: !favorite });
|
||||
toast(
|
||||
favorite
|
||||
? t['Removed from Favorites']()
|
||||
: t['Added to Favorites']()
|
||||
);
|
||||
}}
|
||||
icon={
|
||||
favorite ? (
|
||||
<FavoritedIcon style={{ color: 'var(--affine-primary-color)' }} />
|
||||
) : (
|
||||
<FavoriteIcon />
|
||||
)
|
||||
}
|
||||
>
|
||||
{favorite ? t['Remove from favorites']() : t['Add to Favorites']()}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={mode === 'page' ? <EdgelessIcon /> : <PageIcon />}
|
||||
data-testid="editor-option-menu-edgeless"
|
||||
onClick={() => {
|
||||
setSetting(setting => ({
|
||||
mode: setting?.mode === 'page' ? 'edgeless' : 'page',
|
||||
}));
|
||||
}}
|
||||
>
|
||||
{t['Convert to ']()}
|
||||
{mode === 'page' ? t['Edgeless']() : t['Page']()}
|
||||
</MenuItem>
|
||||
<Export />
|
||||
<MoveToTrash
|
||||
data-testid="editor-option-menu-delete"
|
||||
onItemClick={() => {
|
||||
setOpenConfirm(true);
|
||||
}}
|
||||
/>
|
||||
<div className={styles.horizontalDividerContainer}>
|
||||
<div className={styles.horizontalDivider} />
|
||||
</div>
|
||||
</>
|
||||
|
||||
<div
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<MenuThemeModeSwitch />
|
||||
<LanguageMenu />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<FlexWrapper alignItems="center" justifyContent="center">
|
||||
<Menu
|
||||
content={EditMenu}
|
||||
placement="bottom-end"
|
||||
disablePortal={true}
|
||||
trigger="click"
|
||||
>
|
||||
<IconButton data-testid="editor-option-menu">
|
||||
<MoreVerticalIcon />
|
||||
</IconButton>
|
||||
</Menu>
|
||||
<MoveToTrash.ConfirmModal
|
||||
open={openConfirm}
|
||||
title={pageMeta.title}
|
||||
onConfirm={() => {
|
||||
removeToTrash(pageMeta.id);
|
||||
toast(t['Moved to Trash']());
|
||||
setOpenConfirm(false);
|
||||
}}
|
||||
onCancel={() => {
|
||||
setOpenConfirm(false);
|
||||
}}
|
||||
/>
|
||||
</FlexWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export const EditorOptionMenu = () => {
|
||||
const { pageId } = useParams();
|
||||
return pageId ? <PageMenu /> : <CommonMenu />;
|
||||
};
|
||||
@@ -1,135 +0,0 @@
|
||||
import { Button, displayFlex, Menu, MenuItem, styled } from '@affine/component';
|
||||
import { LOCALES } from '@affine/i18n';
|
||||
import { useI18N } from '@affine/i18n';
|
||||
import { ArrowDownSmallIcon, LanguageIcon } from '@blocksuite/icons';
|
||||
import type { FC, ReactElement } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
const LanguageMenuContent: FC = () => {
|
||||
const i18n = useI18N();
|
||||
const changeLanguage = useCallback(
|
||||
(event: string) => {
|
||||
i18n.changeLanguage(event).catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
},
|
||||
[i18n]
|
||||
);
|
||||
return (
|
||||
<>
|
||||
{LOCALES.map(option => {
|
||||
return (
|
||||
<StyledListItem
|
||||
key={option.name}
|
||||
title={option.name}
|
||||
onClick={() => {
|
||||
changeLanguage(option.tag);
|
||||
}}
|
||||
>
|
||||
{option.originalName}
|
||||
</StyledListItem>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export const LanguageMenu: React.FC = () => {
|
||||
const i18n = useI18N();
|
||||
|
||||
const currentLanguage = LOCALES.find(item => item.tag === i18n.language);
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledIconContainer>
|
||||
<LanguageIcon />
|
||||
</StyledIconContainer>
|
||||
<StyledButtonContainer>
|
||||
<Menu
|
||||
content={(<LanguageMenuContent />) as ReactElement}
|
||||
placement="bottom"
|
||||
trigger="click"
|
||||
disablePortal={true}
|
||||
>
|
||||
<StyledButton
|
||||
type="plain"
|
||||
icon={
|
||||
<StyledArrowDownContainer>
|
||||
<ArrowDownSmallIcon />
|
||||
</StyledArrowDownContainer>
|
||||
}
|
||||
iconPosition="end"
|
||||
data-testid="language-menu-button"
|
||||
>
|
||||
<StyledCurrentLanguage>
|
||||
{currentLanguage?.originalName}
|
||||
</StyledCurrentLanguage>
|
||||
</StyledButton>
|
||||
</Menu>
|
||||
</StyledButtonContainer>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const StyledListItem = styled(MenuItem)(() => ({
|
||||
width: '132px',
|
||||
height: '38px',
|
||||
fontSize: 'var(--affine-font-base)',
|
||||
textTransform: 'capitalize',
|
||||
}));
|
||||
|
||||
const StyledContainer = styled('div')(() => {
|
||||
return {
|
||||
width: '100%',
|
||||
height: '48px',
|
||||
backgroundColor: 'transparent',
|
||||
...displayFlex('flex-start', 'center'),
|
||||
padding: '0 14px',
|
||||
};
|
||||
});
|
||||
const StyledIconContainer = styled('div')(() => {
|
||||
return {
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
color: 'var(--affine-icon-color)',
|
||||
fontSize: '20px',
|
||||
...displayFlex('flex-start', 'center'),
|
||||
};
|
||||
});
|
||||
const StyledButtonContainer = styled('div')(() => {
|
||||
return {
|
||||
width: '100%',
|
||||
height: '32px',
|
||||
borderRadius: '4px',
|
||||
border: `1px solid var(--affine-border-color)`,
|
||||
backgroundColor: 'transparent',
|
||||
...displayFlex('flex-start', 'center'),
|
||||
marginLeft: '12px',
|
||||
};
|
||||
});
|
||||
const StyledButton = styled(Button)(() => {
|
||||
return {
|
||||
width: '100%',
|
||||
height: '32px',
|
||||
borderRadius: '4px',
|
||||
backgroundColor: 'transparent',
|
||||
...displayFlex('space-between', 'center'),
|
||||
textTransform: 'capitalize',
|
||||
padding: '0',
|
||||
};
|
||||
});
|
||||
const StyledArrowDownContainer = styled('div')(() => {
|
||||
return {
|
||||
height: '32px',
|
||||
borderLeft: `1px solid var(--affine-border-color)`,
|
||||
backgroundColor: 'transparent',
|
||||
...displayFlex('flex-start', 'center'),
|
||||
padding: '4px 6px',
|
||||
fontSize: '24px',
|
||||
};
|
||||
});
|
||||
const StyledCurrentLanguage = styled('div')(() => {
|
||||
return {
|
||||
marginLeft: '12px',
|
||||
color: 'var(--affine-text-color)',
|
||||
};
|
||||
});
|
||||
@@ -1,44 +0,0 @@
|
||||
import type { CSSProperties, DOMAttributes } from 'react';
|
||||
type IconProps = {
|
||||
style?: CSSProperties;
|
||||
} & DOMAttributes<SVGElement>;
|
||||
|
||||
export const MoonIcon = ({ style = {}, ...props }: IconProps) => {
|
||||
return (
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
style={style}
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M9.54893 3.31407C9.33328 3.08158 9.27962 2.74521 9.41255 2.45912C9.54547 2.17302 9.83936 1.99233 10.1595 1.99986C13.4456 2.07712 16.5114 4.08044 17.7359 7.29071C19.3437 11.5057 17.1672 16.2024 12.8744 17.781C9.60251 18.9843 6.04745 18.0285 3.82974 15.6428C3.61375 15.4104 3.55978 15.0739 3.69257 14.7876C3.82537 14.5014 4.11931 14.3205 4.43962 14.3279C5.27228 14.3474 6.12412 14.2171 6.94979 13.9135C10.415 12.6391 12.172 8.84782 10.8741 5.44537C10.5657 4.63692 10.1061 3.91474 9.54893 3.31407Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const SunIcon = ({ style = {}, ...props }: IconProps) => {
|
||||
return (
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="currentColor"
|
||||
style={style}
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M10.8002 2.5002C10.8002 2.05837 10.442 1.7002 10.0002 1.7002C9.55837 1.7002 9.2002 2.05837 9.2002 2.5002V3.33353C9.2002 3.77536 9.55837 4.13353 10.0002 4.13353C10.442 4.13353 10.8002 3.77536 10.8002 3.33353V2.5002ZM5.14921 4.01784C4.83679 3.70542 4.33026 3.70542 4.01784 4.01784C3.70542 4.33026 3.70542 4.83679 4.01784 5.14921L4.69627 5.82764C5.00869 6.14006 5.51522 6.14006 5.82764 5.82764C6.14006 5.51522 6.14006 5.00869 5.82764 4.69627L5.14921 4.01784ZM15.9825 5.1492C16.2949 4.83678 16.2949 4.33025 15.9825 4.01783C15.6701 3.70542 15.1636 3.70543 14.8511 4.01785L14.1727 4.69628C13.8603 5.00871 13.8603 5.51524 14.1728 5.82765C14.4852 6.14007 14.9917 6.14006 15.3041 5.82763L15.9825 5.1492ZM10.0002 5.86686C7.71742 5.86686 5.86686 7.71742 5.86686 10.0002C5.86686 12.283 7.71742 14.1335 10.0002 14.1335C12.283 14.1335 14.1335 12.283 14.1335 10.0002C14.1335 7.71742 12.283 5.86686 10.0002 5.86686ZM2.5002 9.2002C2.05837 9.2002 1.7002 9.55837 1.7002 10.0002C1.7002 10.442 2.05837 10.8002 2.5002 10.8002H3.33353C3.77536 10.8002 4.13353 10.442 4.13353 10.0002C4.13353 9.55837 3.77536 9.2002 3.33353 9.2002H2.5002ZM16.6669 9.2002C16.225 9.2002 15.8669 9.55837 15.8669 10.0002C15.8669 10.442 16.225 10.8002 16.6669 10.8002H17.5002C17.942 10.8002 18.3002 10.442 18.3002 10.0002C18.3002 9.55837 17.942 9.2002 17.5002 9.2002H16.6669ZM5.82623 15.309C6.13943 14.9973 6.14069 14.4908 5.82906 14.1776C5.51742 13.8644 5.01089 13.8631 4.69769 14.1748L4.01926 14.8498C3.70606 15.1615 3.70479 15.668 4.01643 15.9812C4.32807 16.2944 4.8346 16.2956 5.1478 15.984L5.82623 15.309ZM15.3027 14.1748C14.9895 13.8631 14.483 13.8644 14.1713 14.1776C13.8597 14.4908 13.861 14.9973 14.1742 15.3089L14.8526 15.984C15.1658 16.2956 15.6723 16.2944 15.9839 15.9812C16.2956 15.668 16.2943 15.1615 15.9811 14.8498L15.3027 14.1748ZM10.8002 16.6669C10.8002 16.225 10.442 15.8669 10.0002 15.8669C9.55837 15.8669 9.2002 16.225 9.2002 16.6669V17.5002C9.2002 17.942 9.55837 18.3002 10.0002 18.3002C10.442 18.3002 10.8002 17.942 10.8002 17.5002V16.6669Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -1,61 +0,0 @@
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { DarkModeIcon, LightModeIcon } from '@blocksuite/icons';
|
||||
import { useTheme } from 'next-themes';
|
||||
|
||||
import {
|
||||
StyledSwitchItem,
|
||||
StyledThemeButton,
|
||||
StyledThemeButtonContainer,
|
||||
StyledThemeModeContainer,
|
||||
StyledThemeModeSwitch,
|
||||
StyledVerticalDivider,
|
||||
} from './style';
|
||||
|
||||
export const MenuThemeModeSwitch = () => {
|
||||
const { setTheme, resolvedTheme, theme } = useTheme();
|
||||
const t = useAFFiNEI18N();
|
||||
return (
|
||||
<StyledThemeModeContainer>
|
||||
<StyledThemeModeSwitch data-testid="change-theme-container" inMenu={true}>
|
||||
<StyledSwitchItem active={resolvedTheme === 'light'} inMenu={true}>
|
||||
<LightModeIcon />
|
||||
</StyledSwitchItem>
|
||||
<StyledSwitchItem active={resolvedTheme === 'dark'} inMenu={true}>
|
||||
<DarkModeIcon />
|
||||
</StyledSwitchItem>
|
||||
</StyledThemeModeSwitch>
|
||||
<StyledThemeButtonContainer>
|
||||
<StyledThemeButton
|
||||
data-testid="change-theme-light"
|
||||
active={theme === 'light'}
|
||||
onClick={() => {
|
||||
setTheme('light');
|
||||
}}
|
||||
>
|
||||
{t['light']()}
|
||||
</StyledThemeButton>
|
||||
<StyledVerticalDivider />
|
||||
<StyledThemeButton
|
||||
data-testid="change-theme-dark"
|
||||
active={theme === 'dark'}
|
||||
onClick={() => {
|
||||
setTheme('dark');
|
||||
}}
|
||||
>
|
||||
{t['dark']()}
|
||||
</StyledThemeButton>
|
||||
<StyledVerticalDivider />
|
||||
<StyledThemeButton
|
||||
active={theme === 'system'}
|
||||
onClick={() => {
|
||||
setTheme('system');
|
||||
}}
|
||||
>
|
||||
{t['system']()}
|
||||
</StyledThemeButton>
|
||||
</StyledThemeButtonContainer>
|
||||
</StyledThemeModeContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default MenuThemeModeSwitch;
|
||||
@@ -1,120 +0,0 @@
|
||||
import { css, displayFlex, keyframes, styled } from '@affine/component';
|
||||
// @ts-expect-error: no types for css-spring
|
||||
import spring, { toString } from 'css-spring';
|
||||
|
||||
const ANIMATE_DURATION = 400;
|
||||
export const StyledThemeModeContainer = styled('div')(() => {
|
||||
return {
|
||||
width: '100%',
|
||||
height: '48px',
|
||||
borderRadius: '6px',
|
||||
backgroundColor: 'transparent',
|
||||
color: 'var(--affine-icon-color)',
|
||||
fontSize: '16px',
|
||||
...displayFlex('flex-start', 'center'),
|
||||
padding: '0 14px',
|
||||
};
|
||||
});
|
||||
export const StyledThemeButtonContainer = styled('div')(() => {
|
||||
return {
|
||||
height: '32px',
|
||||
border: `1px solid var(--affine-border-color)`,
|
||||
borderRadius: '4px',
|
||||
cursor: 'pointer',
|
||||
...displayFlex('space-evenly', 'center'),
|
||||
flexGrow: 1,
|
||||
marginLeft: '12px',
|
||||
};
|
||||
});
|
||||
export const StyledThemeButton = styled('button')<{
|
||||
active: boolean;
|
||||
}>(({ active }) => {
|
||||
return {
|
||||
padding: '0 8px',
|
||||
height: '100%',
|
||||
flex: 1,
|
||||
cursor: 'pointer',
|
||||
color: active ? 'var(--affine-primary-color)' : 'var(--affine-icon-color)',
|
||||
whiteSpace: 'nowrap',
|
||||
};
|
||||
});
|
||||
export const StyledVerticalDivider = styled('div')(() => {
|
||||
return {
|
||||
width: '1px',
|
||||
height: '100%',
|
||||
borderLeft: `1px solid var(--affine-border-color)`,
|
||||
};
|
||||
});
|
||||
export const StyledThemeModeSwitch = styled('button')<{
|
||||
inMenu?: boolean;
|
||||
}>(({ inMenu }) => {
|
||||
return {
|
||||
width: inMenu ? '20px' : '32px',
|
||||
height: inMenu ? '20px' : '32px',
|
||||
borderRadius: '6px',
|
||||
overflow: 'hidden',
|
||||
WebkitAppRegion: 'no-drag',
|
||||
backgroundColor: 'transparent',
|
||||
position: 'relative',
|
||||
color: 'var(--affine-icon-color)',
|
||||
fontSize: inMenu ? '20px' : '24px',
|
||||
};
|
||||
});
|
||||
export const StyledSwitchItem = styled('div')<{
|
||||
active: boolean;
|
||||
isHover?: boolean;
|
||||
inMenu?: boolean;
|
||||
}>(({ active, isHover, inMenu }) => {
|
||||
const activeRaiseAnimate = toString(
|
||||
spring({ top: '0' }, { top: '-100%' }, { preset: 'gentle' })
|
||||
);
|
||||
const raiseAnimate = toString(
|
||||
spring({ top: '100%' }, { top: '0' }, { preset: 'gentle' })
|
||||
);
|
||||
const activeDeclineAnimate = toString(
|
||||
spring({ top: '-100%' }, { top: '0' }, { preset: 'gentle' })
|
||||
);
|
||||
const declineAnimate = toString(
|
||||
spring({ top: '0' }, { top: '100%' }, { preset: 'gentle' })
|
||||
);
|
||||
|
||||
const activeStyle = active
|
||||
? {
|
||||
color: 'var(--affine-icon-color)',
|
||||
top: '0',
|
||||
animation: css`
|
||||
${keyframes`${
|
||||
isHover ? activeRaiseAnimate : activeDeclineAnimate
|
||||
}`} ${ANIMATE_DURATION}ms forwards
|
||||
`,
|
||||
animationDirection: isHover ? 'normal' : 'alternate',
|
||||
}
|
||||
: {
|
||||
top: '100%',
|
||||
color: 'var(--affine-primary-color)',
|
||||
backgroundColor: 'var(--affine-hover-color)',
|
||||
animation: css`
|
||||
${keyframes`${
|
||||
isHover ? raiseAnimate : declineAnimate
|
||||
}`} ${ANIMATE_DURATION}ms forwards
|
||||
`,
|
||||
animationDirection: isHover ? 'normal' : 'alternate',
|
||||
};
|
||||
return css`
|
||||
${css(displayFlex('center', 'center'))}
|
||||
width:${inMenu ? '20px' : '32px'} ;
|
||||
height: ${inMenu ? '20px' : '32px'} ;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
cursor: pointer;
|
||||
color: ${activeStyle.color}
|
||||
top: ${activeStyle.top};
|
||||
background-color: ${activeStyle.backgroundColor};
|
||||
animation: ${activeStyle.animation};
|
||||
animation-direction: ${activeStyle.animationDirection};
|
||||
//svg {
|
||||
// width: 24px;
|
||||
// height: 24px;
|
||||
//},
|
||||
`;
|
||||
});
|
||||