Compare commits
87 Commits
v0.8.0-can
...
v0.8.0-can
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c59d1e67ab | ||
|
|
9ab9c0c70d | ||
|
|
f369ca39f7 | ||
|
|
804b8f38b8 | ||
|
|
dd23917e3e | ||
|
|
b604d9b47e | ||
|
|
1e5a4a6849 | ||
|
|
64656c3c98 | ||
|
|
61ba85e1f3 | ||
|
|
61ffc4220c | ||
|
|
866408015e | ||
|
|
651e815b42 | ||
|
|
645a300112 | ||
|
|
e0a3c7f2bc | ||
|
|
3dbefda6ed | ||
|
|
73eddc2386 | ||
|
|
6f9dfcc3c1 | ||
|
|
93d352f3d8 | ||
|
|
7546b080ea | ||
|
|
6988b6f034 | ||
|
|
de2cb1a3bc | ||
|
|
08f01ea1b3 | ||
|
|
0df30e43c6 | ||
|
|
67b33d9b8f | ||
|
|
42dfd0a4bb | ||
|
|
25052220a4 | ||
|
|
48e96cd399 | ||
|
|
ca016f1dd1 | ||
|
|
a4fe7dd119 | ||
|
|
8d2df468ee | ||
|
|
2830cb19fe | ||
|
|
8487b2c4af | ||
|
|
720a90fe93 | ||
|
|
623fa87d5c | ||
|
|
4ad50bf8cf | ||
|
|
efd02a015a | ||
|
|
75a2bbdfac | ||
|
|
52102ee792 | ||
|
|
58dae07b5f | ||
|
|
d0e33c748b | ||
|
|
08da58aa1e | ||
|
|
1072db632e | ||
|
|
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 |
@@ -1,641 +0,0 @@
|
|||||||
{
|
|
||||||
"projectName": "AFFiNE",
|
|
||||||
"projectOwner": "toeverything",
|
|
||||||
"repoType": "github",
|
|
||||||
"repoHost": "https://github.com",
|
|
||||||
"files": [
|
|
||||||
"README.md"
|
|
||||||
],
|
|
||||||
"imageSize": 50,
|
|
||||||
"commit": false,
|
|
||||||
"commitConvention": "angular",
|
|
||||||
"contributorsPerLine": 7,
|
|
||||||
"badgeTemplate": "\n[all-contributors-badge]: https://img.shields.io/badge/all_contributors-<%= contributors.length %>-orange.svg?style=flat-square\n",
|
|
||||||
"contributors": [
|
|
||||||
{
|
|
||||||
"login": "doodlewind",
|
|
||||||
"name": "Yifeng Wang",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/7312949?v=4",
|
|
||||||
"profile": "https://github.com/doodlewind",
|
|
||||||
"contributions": [
|
|
||||||
"code",
|
|
||||||
"doc"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "darkskygit",
|
|
||||||
"name": "DarkSky",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/25152247?v=4",
|
|
||||||
"profile": "https://darksky.eu.org/",
|
|
||||||
"contributions": [
|
|
||||||
"code",
|
|
||||||
"doc"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "tzhangchi",
|
|
||||||
"name": "Chi Zhang",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/5910926?v=4",
|
|
||||||
"profile": "http://zhangchi.page/",
|
|
||||||
"contributions": [
|
|
||||||
"code",
|
|
||||||
"doc"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "alt1o",
|
|
||||||
"name": "wang xinglong",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/21084335?v=4",
|
|
||||||
"profile": "https://github.com/alt1o",
|
|
||||||
"contributions": [
|
|
||||||
"code",
|
|
||||||
"doc"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "Brooooooklyn",
|
|
||||||
"name": "LongYinan",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/3468483?v=4",
|
|
||||||
"profile": "https://github.com/Brooooooklyn",
|
|
||||||
"contributions": [
|
|
||||||
"code",
|
|
||||||
"doc"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "hwangdev97",
|
|
||||||
"name": "Hwang",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/24713927?v=4",
|
|
||||||
"profile": "https://github.com/hwangdev97",
|
|
||||||
"contributions": [
|
|
||||||
"code",
|
|
||||||
"doc"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "kobeshanks",
|
|
||||||
"name": "kobeshanks",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/82570088?v=4",
|
|
||||||
"profile": "https://github.com/kobeshanks",
|
|
||||||
"contributions": [
|
|
||||||
"code",
|
|
||||||
"doc"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "pengx17",
|
|
||||||
"name": "Peng Xiao",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/584378?v=4",
|
|
||||||
"profile": "https://pengx17.vercel.app/",
|
|
||||||
"contributions": [
|
|
||||||
"code",
|
|
||||||
"doc"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "Saul-Mirone",
|
|
||||||
"name": "Mirone",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/10047788?v=4",
|
|
||||||
"profile": "https://mirone.me/",
|
|
||||||
"contributions": [
|
|
||||||
"code",
|
|
||||||
"doc"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "zqran",
|
|
||||||
"name": "zqran",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/15389209?v=4",
|
|
||||||
"profile": "https://github.com/zqran",
|
|
||||||
"contributions": [
|
|
||||||
"code",
|
|
||||||
"doc"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "SuneBear",
|
|
||||||
"name": "Shule Hsiung",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/7693264?v=4",
|
|
||||||
"profile": "https://sunebear.com/",
|
|
||||||
"contributions": [
|
|
||||||
"code",
|
|
||||||
"doc"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "fundon",
|
|
||||||
"name": "Fangdun Tsai",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/27926?v=4",
|
|
||||||
"profile": "https://fundon.viz.rs/",
|
|
||||||
"contributions": [
|
|
||||||
"code",
|
|
||||||
"doc"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "lawvs",
|
|
||||||
"name": "Whitewater",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/18554747?v=4",
|
|
||||||
"profile": "https://lawvs.github.io/profile/",
|
|
||||||
"contributions": [
|
|
||||||
"code",
|
|
||||||
"doc"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "zuoxiaodong0815",
|
|
||||||
"name": "xiaodong zuo",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/53252747?v=4",
|
|
||||||
"profile": "https://github.com/zuoxiaodong0815",
|
|
||||||
"contributions": [
|
|
||||||
"code",
|
|
||||||
"doc"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "Himself65",
|
|
||||||
"name": "Himself65",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/14026360?v=4",
|
|
||||||
"profile": "https://github.com/Himself65",
|
|
||||||
"contributions": [
|
|
||||||
"code",
|
|
||||||
"doc"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "DiamondThree",
|
|
||||||
"name": "DiamondThree",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/24630517?v=4",
|
|
||||||
"profile": "https://github.com/DiamondThree",
|
|
||||||
"contributions": [
|
|
||||||
"code",
|
|
||||||
"doc"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "QiShaoXuan",
|
|
||||||
"name": "Qi",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/22772830?v=4",
|
|
||||||
"profile": "https://github.com/QiShaoXuan",
|
|
||||||
"contributions": [
|
|
||||||
"code",
|
|
||||||
"doc"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "colelawrence",
|
|
||||||
"name": "Cole Lawrence",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/2925395?v=4",
|
|
||||||
"profile": "https://colelawrence.com/",
|
|
||||||
"contributions": [
|
|
||||||
"code",
|
|
||||||
"doc"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "linonetwo",
|
|
||||||
"name": "lin onetwo",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/3746270?v=4",
|
|
||||||
"profile": "https://onetwo.ren/wiki",
|
|
||||||
"contributions": [
|
|
||||||
"code",
|
|
||||||
"doc"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "thorseraq",
|
|
||||||
"name": "x1a0t",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/20554850?v=4",
|
|
||||||
"profile": "https://github.com/thorseraq",
|
|
||||||
"contributions": [
|
|
||||||
"code",
|
|
||||||
"doc"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "HeJiachen-PM",
|
|
||||||
"name": "HeJiachen-PM",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/79301703?v=4",
|
|
||||||
"profile": "https://github.com/HeJiachen-PM",
|
|
||||||
"contributions": [
|
|
||||||
"research",
|
|
||||||
"doc"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "joebeijing",
|
|
||||||
"name": "houjoe",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/22443345?v=4",
|
|
||||||
"profile": "https://www.notion.so/houjoe/Joe-2a85f5be01004cd2b6a5ad26fbb948b1",
|
|
||||||
"contributions": [
|
|
||||||
"research",
|
|
||||||
"doc"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "Yipei-Operation",
|
|
||||||
"name": "Yipei Wei",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/79373028?v=4",
|
|
||||||
"profile": "https://github.com/Yipei-Operation",
|
|
||||||
"contributions": [
|
|
||||||
"doc"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "VelikaHF",
|
|
||||||
"name": "Velika",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/121547898?v=4",
|
|
||||||
"profile": "https://github.com/VelikaHF",
|
|
||||||
"contributions": [
|
|
||||||
"design"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "Svaney-ssman",
|
|
||||||
"name": "Svaney",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/110808979?v=4",
|
|
||||||
"profile": "https://github.com/Svaney-ssman",
|
|
||||||
"contributions": [
|
|
||||||
"design"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "xell",
|
|
||||||
"name": "Guozhu Liu",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/132558?v=4",
|
|
||||||
"profile": "http://xell.me/",
|
|
||||||
"contributions": [
|
|
||||||
"design"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "fyZheng07",
|
|
||||||
"name": "fyZheng07",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/63830919?v=4",
|
|
||||||
"profile": "https://github.com/fyZheng07",
|
|
||||||
"contributions": [
|
|
||||||
"eventOrganizing",
|
|
||||||
"userTesting"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "CJSS",
|
|
||||||
"name": "CJSS",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/4605025?v=4",
|
|
||||||
"profile": "https://github.com/CJSS",
|
|
||||||
"contributions": [
|
|
||||||
"doc"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "JimmFly",
|
|
||||||
"name": "JimmFly",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/102217452?v=4",
|
|
||||||
"profile": "https://github.com/JimmFly",
|
|
||||||
"contributions": [
|
|
||||||
"code"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "mitsuhatu",
|
|
||||||
"name": "mitsuhatu",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/110213079?v=4",
|
|
||||||
"profile": "https://github.com/mitsuhatu",
|
|
||||||
"contributions": [
|
|
||||||
"code",
|
|
||||||
"doc"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "Austaras",
|
|
||||||
"name": "Austaras",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/15013925?v=4",
|
|
||||||
"profile": "https://shockwave.me/",
|
|
||||||
"contributions": [
|
|
||||||
"code",
|
|
||||||
"doc"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "uptonking",
|
|
||||||
"name": "Jin Yao",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/11391549?v=4",
|
|
||||||
"profile": "https://github.com/uptonking",
|
|
||||||
"contributions": [
|
|
||||||
"code",
|
|
||||||
"doc"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "CarlosZoft",
|
|
||||||
"name": "Carlos Rafael ",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/62192072?v=4",
|
|
||||||
"profile": "https://github.com/CarlosZoft",
|
|
||||||
"contributions": [
|
|
||||||
"code"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "caleboleary",
|
|
||||||
"name": "Caleb OLeary",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/12816579?v=4",
|
|
||||||
"profile": "https://github.com/caleboleary",
|
|
||||||
"contributions": [
|
|
||||||
"code"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "westongraham",
|
|
||||||
"name": "Weston Graham",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/89493023?v=4",
|
|
||||||
"profile": "https://github.com/westongraham",
|
|
||||||
"contributions": [
|
|
||||||
"doc"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "SaikaSakura",
|
|
||||||
"name": "MingLIang Wang",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/11530942?v=4",
|
|
||||||
"profile": "https://github.com/SaikaSakura",
|
|
||||||
"contributions": [
|
|
||||||
"code",
|
|
||||||
"doc"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "fanjing22",
|
|
||||||
"name": "fanjing22",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/109729699?v=4",
|
|
||||||
"profile": "https://github.com/fanjing22",
|
|
||||||
"contributions": [
|
|
||||||
"design"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "pointmax",
|
|
||||||
"name": "pointmax",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/49361135?v=4",
|
|
||||||
"profile": "https://github.com/pointmax",
|
|
||||||
"contributions": [
|
|
||||||
"doc",
|
|
||||||
"code"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "liby",
|
|
||||||
"name": "Bryan Lee",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/38807139?v=4",
|
|
||||||
"profile": "https://liby.github.io/notes",
|
|
||||||
"contributions": [
|
|
||||||
"code"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "chenmoonmo",
|
|
||||||
"name": "Simon Li",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/36295999?v=4",
|
|
||||||
"profile": "https://github.com/chenmoonmo",
|
|
||||||
"contributions": [
|
|
||||||
"code"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "githbq",
|
|
||||||
"name": "Bob Hu",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/10009709?v=4",
|
|
||||||
"profile": "https://github.com/githbq",
|
|
||||||
"contributions": [
|
|
||||||
"code"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "lucky-chap",
|
|
||||||
"name": "Quavo",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/67266933?v=4",
|
|
||||||
"profile": "https://quavo.vercel.app/",
|
|
||||||
"contributions": [
|
|
||||||
"doc",
|
|
||||||
"code"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "LuciNyan",
|
|
||||||
"name": "子瞻 Luci",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/22126563?v=4",
|
|
||||||
"profile": "https://github.com/LuciNyan",
|
|
||||||
"contributions": [
|
|
||||||
"code"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "m1911star",
|
|
||||||
"name": "Horus",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/4948120?v=4",
|
|
||||||
"profile": "http://blog.ipili.me/",
|
|
||||||
"contributions": [
|
|
||||||
"code",
|
|
||||||
"platform"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "fanshyiis",
|
|
||||||
"name": "Super.x",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/15103283?v=4",
|
|
||||||
"profile": "https://segmentfault.com/u/qzuser_584786517d31a",
|
|
||||||
"contributions": [
|
|
||||||
"code"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "wangyu-1999",
|
|
||||||
"name": "Wang Yu",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/80874770?v=4",
|
|
||||||
"profile": "https://wangyu-1999.github.io/",
|
|
||||||
"contributions": [
|
|
||||||
"code"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "felixonmars",
|
|
||||||
"name": "Felix Yan",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/1006477?v=4",
|
|
||||||
"profile": "https://felixc.at/",
|
|
||||||
"contributions": [
|
|
||||||
"code"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "lynettelopez",
|
|
||||||
"name": "Lynette Lopez",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/32908859?v=4",
|
|
||||||
"profile": "https://github.com/lynettelopez",
|
|
||||||
"contributions": [
|
|
||||||
"code"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "Zheaoli",
|
|
||||||
"name": "Manjusaka",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/7054676?v=4",
|
|
||||||
"profile": "http://manjusaka.itscoder.com/",
|
|
||||||
"contributions": [
|
|
||||||
"code"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "sudongyuer",
|
|
||||||
"name": "Frozen FIsh",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/76603360?v=4",
|
|
||||||
"profile": "https://juejin.cn/user/2867982785579102/posts?sort=popular",
|
|
||||||
"contributions": [
|
|
||||||
"code"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "MuhammedFaraz",
|
|
||||||
"name": "Mohammed Faraz",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/92734739?v=4",
|
|
||||||
"profile": "https://github.com/MuhammedFaraz",
|
|
||||||
"contributions": [
|
|
||||||
"doc",
|
|
||||||
"code"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "Pranav4399",
|
|
||||||
"name": "Pranav Sriram ",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/28348429?v=4",
|
|
||||||
"profile": "https://pranavsriram.dev/",
|
|
||||||
"contributions": [
|
|
||||||
"code"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "Reson-a",
|
|
||||||
"name": "Reson-a",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/20806266?v=4",
|
|
||||||
"profile": "https://github.com/Reson-a",
|
|
||||||
"contributions": [
|
|
||||||
"code"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "hezhizhen",
|
|
||||||
"name": "Zhizhen He",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/7611700?v=4",
|
|
||||||
"profile": "https://t.me/littlepoint",
|
|
||||||
"contributions": [
|
|
||||||
"code"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "AkaraChen",
|
|
||||||
"name": "AkaraChen",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/85140972?v=4",
|
|
||||||
"profile": "https://akr.moe/",
|
|
||||||
"contributions": [
|
|
||||||
"code"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "suyanhanx",
|
|
||||||
"name": "Suyan",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/24221472?v=4",
|
|
||||||
"profile": "https://github.com/suyanhanx",
|
|
||||||
"contributions": [
|
|
||||||
"code"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "hehex9",
|
|
||||||
"name": "hehe",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/9209882?v=4",
|
|
||||||
"profile": "https://github.com/hehex9",
|
|
||||||
"contributions": [
|
|
||||||
"code"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "albertodlc",
|
|
||||||
"name": "Alberto de la Cruz",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/32411964?v=4",
|
|
||||||
"profile": "https://github.com/albertodlc",
|
|
||||||
"contributions": [
|
|
||||||
"code"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "AlessioGr",
|
|
||||||
"name": "Alessio Gravili",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/70709113?v=4",
|
|
||||||
"profile": "https://github.com/AlessioGr",
|
|
||||||
"contributions": [
|
|
||||||
"code"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "lzlme",
|
|
||||||
"name": "Zhilin Liu",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/117659326?v=4",
|
|
||||||
"profile": "https://github.com/lzlme",
|
|
||||||
"contributions": [
|
|
||||||
"code"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "suica",
|
|
||||||
"name": "Sg",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/8041462?v=4",
|
|
||||||
"profile": "https://github.com/suica",
|
|
||||||
"contributions": [
|
|
||||||
"code"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "sinchang",
|
|
||||||
"name": "Jeff Wen",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/3297859?v=4",
|
|
||||||
"profile": "https://sinchang.me/",
|
|
||||||
"contributions": [
|
|
||||||
"code"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "m1212e",
|
|
||||||
"name": "m1212e",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/14091540?v=4",
|
|
||||||
"profile": "https://m1212e.github.io/portfolio/",
|
|
||||||
"contributions": [
|
|
||||||
"code"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "adityash1",
|
|
||||||
"name": "Aditya Sharma",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/65771169?v=4",
|
|
||||||
"profile": "https://adityash1.github.io/",
|
|
||||||
"contributions": [
|
|
||||||
"code"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "sheben404",
|
|
||||||
"name": "Kehan Wang",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/61317160?v=4",
|
|
||||||
"profile": "https://github.com/sheben404",
|
|
||||||
"contributions": [
|
|
||||||
"code"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "VictorNanka",
|
|
||||||
"name": "VictorNanka",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/30154366?v=4",
|
|
||||||
"profile": "https://github.com/VictorNanka",
|
|
||||||
"contributions": [
|
|
||||||
"code"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
2
.cargo/config.toml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[target.x86_64-pc-windows-msvc]
|
||||||
|
rustflags = ["-C", "target-feature=+crt-static"]
|
||||||
10
.eslintrc.js
@@ -31,6 +31,11 @@ const createPattern = packageName => [
|
|||||||
message: 'Use `useNavigateHelper` instead',
|
message: 'Use `useNavigateHelper` instead',
|
||||||
importNames: ['useNavigate'],
|
importNames: ['useNavigate'],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
group: ['yjs'],
|
||||||
|
message: 'Do not use this API because it has a bug',
|
||||||
|
importNames: ['mergeUpdates'],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const allPackages = [
|
const allPackages = [
|
||||||
@@ -155,6 +160,11 @@ const config = {
|
|||||||
message: 'Use `useNavigateHelper` instead',
|
message: 'Use `useNavigateHelper` instead',
|
||||||
importNames: ['useNavigate'],
|
importNames: ['useNavigate'],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
group: ['yjs'],
|
||||||
|
message: 'Do not use this API because it has a bug',
|
||||||
|
importNames: ['mergeUpdates'],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
2
.github/dependabot.yml
vendored
@@ -3,7 +3,7 @@ updates:
|
|||||||
- package-ecosystem: 'npm'
|
- package-ecosystem: 'npm'
|
||||||
directory: '/'
|
directory: '/'
|
||||||
schedule:
|
schedule:
|
||||||
interval: 'daily'
|
interval: 'weekly'
|
||||||
versioning-strategy: increase
|
versioning-strategy: increase
|
||||||
commit-message:
|
commit-message:
|
||||||
prefix: 'chore'
|
prefix: 'chore'
|
||||||
|
|||||||
84
.github/workflows/build.yml
vendored
@@ -59,6 +59,25 @@ jobs:
|
|||||||
- name: Run Type Check
|
- name: Run Type Check
|
||||||
run: yarn typecheck
|
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:
|
build-server:
|
||||||
name: Build Server
|
name: Build Server
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -216,28 +235,6 @@ jobs:
|
|||||||
name: affine
|
name: affine
|
||||||
fail_ci_if_error: false
|
fail_ci_if_error: false
|
||||||
|
|
||||||
storybook-test:
|
|
||||||
name: Storybook Test
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
environment: development
|
|
||||||
needs: [build-storybook]
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: Setup Node.js
|
|
||||||
uses: ./.github/actions/setup-node
|
|
||||||
with:
|
|
||||||
playwright-install: true
|
|
||||||
electron-install: false
|
|
||||||
- name: Download storybook artifact
|
|
||||||
uses: actions/download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: storybook
|
|
||||||
path: ./apps/storybook/storybook-static
|
|
||||||
- name: Run storybook tests
|
|
||||||
working-directory: ./apps/storybook
|
|
||||||
run: |
|
|
||||||
yarn exec concurrently -k -s first -n "SB,TEST" -c "magenta,blue" "yarn exec serve ./storybook-static -l 6006" "yarn exec wait-on tcp:6006 && yarn test"
|
|
||||||
|
|
||||||
e2e-plugin-test:
|
e2e-plugin-test:
|
||||||
name: E2E Plugin Test
|
name: E2E Plugin Test
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -280,6 +277,49 @@ jobs:
|
|||||||
path: ./test-results
|
path: ./test-results
|
||||||
if-no-files-found: ignore
|
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:
|
e2e-test:
|
||||||
name: E2E Test
|
name: E2E Test
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
38
.github/workflows/publish-storybook.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
name: Publish Storybook
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
pull_request_target:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
paths-ignore:
|
||||||
|
- README.md
|
||||||
|
- .github/**
|
||||||
|
- '!.github/workflows/publish-storybook.yml'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish-storybook:
|
||||||
|
name: Publish Storybook
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment: development
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.pull_request.merge_commit_sha }}
|
||||||
|
# This is required to fetch all commits for chromatic
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: ./.github/actions/setup-node
|
||||||
|
with:
|
||||||
|
electron-install: false
|
||||||
|
- name: Build Plugins
|
||||||
|
run: yarn run build:plugins
|
||||||
|
- name: Publish to Chromatic
|
||||||
|
uses: chromaui/action@v1
|
||||||
|
with:
|
||||||
|
workingDir: apps/storybook
|
||||||
|
buildScriptName: build
|
||||||
|
onlyChanged: true
|
||||||
|
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
|
||||||
14
README.md
@@ -127,7 +127,7 @@ If you have questions, you are welcome to contact us. One of the best places to
|
|||||||
| [@affine/copilot-plugin](plugins/copilot) | AI Copilot that help you document writing |
|
| [@affine/copilot-plugin](plugins/copilot) | AI Copilot that help you document writing |
|
||||||
| [@affine/image-preview-plugin](plugins/image-preview) | Component for previewing an image |
|
| [@affine/image-preview-plugin](plugins/image-preview) | Component for previewing an image |
|
||||||
|
|
||||||
## Thanks
|
## Upstreams
|
||||||
|
|
||||||
We would also like to give thanks to open-source projects that make AFFiNE possible:
|
We would also like to give thanks to open-source projects that make AFFiNE possible:
|
||||||
|
|
||||||
@@ -178,11 +178,19 @@ See [BUILDING.md] for instructions on how to build AFFiNE from source code.
|
|||||||
We welcome contributions from everyone.
|
We welcome contributions from everyone.
|
||||||
See [docs/contributing/tutorial.md](./docs/contributing/tutorial.md) for details.
|
See [docs/contributing/tutorial.md](./docs/contributing/tutorial.md) for details.
|
||||||
|
|
||||||
|
## Thanks
|
||||||
|
|
||||||
|
<a href="https://www.chromatic.com/"><img src="https://user-images.githubusercontent.com/321738/84662277-e3db4f80-af1b-11ea-88f5-91d67a5e59f6.png" width="153" height="30" alt="Chromatic" /></a>
|
||||||
|
|
||||||
|
Thanks to [Chromatic](https://www.chromatic.com/) for providing the visual testing platform that helps us review UI changes and catch visual regressions.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
See [LICENSE] for details.
|
See [LICENSE] for details.
|
||||||
|
|
||||||
[all-contributors-badge]: https://img.shields.io/github/all-contributors/toeverything/AFFiNE/master?color=orange
|
[](https://app.fossa.com/projects/git%2Bgithub.com%2Ftoeverything%2FAFFiNE?ref=badge_large)
|
||||||
|
|
||||||
|
[all-contributors-badge]: https://img.shields.io/github/contributors/toeverything/AFFiNE
|
||||||
[license]: ./LICENSE
|
[license]: ./LICENSE
|
||||||
[building.md]: ./docs/BUILDING.md
|
[building.md]: ./docs/BUILDING.md
|
||||||
[update page]: https://affine.pro/blog?tag=Release%20Note
|
[update page]: https://affine.pro/blog?tag=Release%20Note
|
||||||
@@ -196,5 +204,3 @@ See [LICENSE] for details.
|
|||||||
[typescript-version-icon]: https://img.shields.io/github/package-json/dependency-version/toeverything/affine/dev/typescript
|
[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)
|
[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
|
[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
|
## 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/).
|
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/).
|
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/).
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ export const createConfiguration: (
|
|||||||
experiments: {
|
experiments: {
|
||||||
topLevelAwait: true,
|
topLevelAwait: true,
|
||||||
outputModule: false,
|
outputModule: false,
|
||||||
|
syncWebAssembly: true,
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
environment: {
|
environment: {
|
||||||
|
|||||||
@@ -15,21 +15,24 @@ export default async function (cli_env: any, _: any) {
|
|||||||
const config = createConfiguration(flags, runtimeConfig);
|
const config = createConfiguration(flags, runtimeConfig);
|
||||||
return merge(config, {
|
return merge(config, {
|
||||||
entry: {
|
entry: {
|
||||||
|
'polyfill/intl-segmenter': {
|
||||||
|
import: resolve(rootPath, 'src/polyfill/intl-segmenter.ts'),
|
||||||
|
},
|
||||||
'polyfill/ses': {
|
'polyfill/ses': {
|
||||||
import: resolve(rootPath, 'src/polyfill/ses.ts'),
|
import: resolve(rootPath, 'src/polyfill/ses.ts'),
|
||||||
},
|
},
|
||||||
plugin: {
|
plugin: {
|
||||||
dependOn: ['polyfill/ses'],
|
dependOn: ['polyfill/intl-segmenter', 'polyfill/ses'],
|
||||||
import: resolve(rootPath, 'src/bootstrap/register-plugins.ts'),
|
import: resolve(rootPath, 'src/bootstrap/register-plugins.ts'),
|
||||||
},
|
},
|
||||||
app: {
|
app: {
|
||||||
chunkLoading: 'import',
|
chunkLoading: 'import',
|
||||||
dependOn: ['polyfill/ses', 'plugin'],
|
dependOn: ['polyfill/intl-segmenter', 'polyfill/ses', 'plugin'],
|
||||||
import: resolve(rootPath, 'src/index.tsx'),
|
import: resolve(rootPath, 'src/index.tsx'),
|
||||||
},
|
},
|
||||||
'_plugin/index.test': {
|
'_plugin/index.test': {
|
||||||
chunkLoading: 'import',
|
chunkLoading: 'import',
|
||||||
dependOn: ['polyfill/ses', 'plugin'],
|
dependOn: ['polyfill/intl-segmenter', 'polyfill/ses', 'plugin'],
|
||||||
import: resolve(rootPath, 'src/_plugin/index.test.tsx'),
|
import: resolve(rootPath, 'src/_plugin/index.test.tsx'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -39,7 +42,7 @@ export default async function (cli_env: any, _: any) {
|
|||||||
inject: 'body',
|
inject: 'body',
|
||||||
scriptLoading: 'module',
|
scriptLoading: 'module',
|
||||||
minify: false,
|
minify: false,
|
||||||
chunks: ['app', 'plugin', 'polyfill/ses'],
|
chunks: ['app', 'plugin', 'polyfill/intl-segmenter', 'polyfill/ses'],
|
||||||
filename: 'index.html',
|
filename: 'index.html',
|
||||||
}),
|
}),
|
||||||
new HTMLPlugin({
|
new HTMLPlugin({
|
||||||
@@ -47,7 +50,12 @@ export default async function (cli_env: any, _: any) {
|
|||||||
inject: 'body',
|
inject: 'body',
|
||||||
scriptLoading: 'module',
|
scriptLoading: 'module',
|
||||||
minify: false,
|
minify: false,
|
||||||
chunks: ['_plugin/index.test', 'plugin', 'polyfill/ses'],
|
chunks: [
|
||||||
|
'_plugin/index.test',
|
||||||
|
'plugin',
|
||||||
|
'polyfill/intl-segmenter',
|
||||||
|
'polyfill/ses',
|
||||||
|
],
|
||||||
filename: '_plugin/index.html',
|
filename: '_plugin/index.html',
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -2,12 +2,18 @@
|
|||||||
"name": "@affine/core",
|
"name": "@affine/core",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.8.0-canary.14",
|
"version": "0.8.0-canary.24",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "yarn -T run build-core",
|
"build": "yarn -T run build-core",
|
||||||
"dev": "yarn -T run dev-core",
|
"dev": "yarn -T run dev-core",
|
||||||
"static-server": "ts-node-esm ./server.mts"
|
"static-server": "ts-node-esm ./server.mts"
|
||||||
},
|
},
|
||||||
|
"exports": {
|
||||||
|
"./app": "./src/app.tsx",
|
||||||
|
"./router": "./src/router.ts",
|
||||||
|
"./bootstrap/setup": "./src/bootstrap/setup.ts",
|
||||||
|
"./bootstrap/register-plugins": "./src/bootstrap/register-plugins.ts"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@affine-test/fixtures": "workspace:*",
|
"@affine-test/fixtures": "workspace:*",
|
||||||
"@affine/component": "workspace:*",
|
"@affine/component": "workspace:*",
|
||||||
@@ -18,30 +24,32 @@
|
|||||||
"@affine/jotai": "workspace:*",
|
"@affine/jotai": "workspace:*",
|
||||||
"@affine/templates": "workspace:*",
|
"@affine/templates": "workspace:*",
|
||||||
"@affine/workspace": "workspace:*",
|
"@affine/workspace": "workspace:*",
|
||||||
"@blocksuite/block-std": "0.0.0-20230808131052-ef3445a9-nightly",
|
"@blocksuite/block-std": "0.0.0-20230816084758-3be96d22-nightly",
|
||||||
"@blocksuite/blocks": "0.0.0-20230808131052-ef3445a9-nightly",
|
"@blocksuite/blocks": "0.0.0-20230816084758-3be96d22-nightly",
|
||||||
"@blocksuite/editor": "0.0.0-20230808131052-ef3445a9-nightly",
|
"@blocksuite/editor": "0.0.0-20230816084758-3be96d22-nightly",
|
||||||
"@blocksuite/global": "0.0.0-20230808131052-ef3445a9-nightly",
|
"@blocksuite/global": "0.0.0-20230816084758-3be96d22-nightly",
|
||||||
"@blocksuite/icons": "^2.1.30",
|
"@blocksuite/icons": "^2.1.31",
|
||||||
"@blocksuite/lit": "0.0.0-20230808131052-ef3445a9-nightly",
|
"@blocksuite/lit": "0.0.0-20230816084758-3be96d22-nightly",
|
||||||
"@blocksuite/store": "0.0.0-20230808131052-ef3445a9-nightly",
|
"@blocksuite/store": "0.0.0-20230816084758-3be96d22-nightly",
|
||||||
"@dnd-kit/core": "^6.0.8",
|
"@dnd-kit/core": "^6.0.8",
|
||||||
"@dnd-kit/sortable": "^7.0.2",
|
"@dnd-kit/sortable": "^7.0.2",
|
||||||
"@emotion/cache": "^11.11.0",
|
"@emotion/cache": "^11.11.0",
|
||||||
"@emotion/react": "^11.11.1",
|
"@emotion/react": "^11.11.1",
|
||||||
"@emotion/server": "^11.11.0",
|
"@emotion/server": "^11.11.0",
|
||||||
"@emotion/styled": "^11.11.0",
|
"@emotion/styled": "^11.11.0",
|
||||||
"@mui/material": "^5.14.2",
|
"@mui/material": "^5.14.5",
|
||||||
"@react-hookz/web": "^23.1.0",
|
"@react-hookz/web": "^23.1.0",
|
||||||
"@toeverything/components": "^0.0.10",
|
"@toeverything/components": "^0.0.11",
|
||||||
"async-call-rpc": "^6.3.1",
|
"async-call-rpc": "^6.3.1",
|
||||||
"cmdk": "^0.2.0",
|
"cmdk": "^0.2.0",
|
||||||
"css-spring": "^4.1.0",
|
"css-spring": "^4.1.0",
|
||||||
"cssnano": "^6.0.1",
|
"cssnano": "^6.0.1",
|
||||||
"graphql": "^16.7.1",
|
"graphql": "^16.8.0",
|
||||||
"jotai": "^2.2.2",
|
"intl-segmenter-polyfill-rs": "^0.1.5",
|
||||||
|
"jotai": "^2.3.1",
|
||||||
"jotai-devtools": "^0.6.1",
|
"jotai-devtools": "^0.6.1",
|
||||||
"lit": "^2.7.6",
|
"lit": "^2.8.0",
|
||||||
|
"lodash.debounce": "^4.0.8",
|
||||||
"lottie-web": "^5.12.2",
|
"lottie-web": "^5.12.2",
|
||||||
"mini-css-extract-plugin": "^2.7.6",
|
"mini-css-extract-plugin": "^2.7.6",
|
||||||
"next-themes": "^0.2.1",
|
"next-themes": "^0.2.1",
|
||||||
@@ -49,21 +57,22 @@
|
|||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-is": "18.2.0",
|
"react-is": "18.2.0",
|
||||||
"react-resizable-panels": "^0.0.54",
|
"react-resizable-panels": "^0.0.55",
|
||||||
"react-router-dom": "^6.14.2",
|
"react-router-dom": "^6.15.0",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
"ses": "^0.18.5",
|
"ses": "^0.18.7",
|
||||||
"swr": "2.1.5",
|
"swr": "2.2.1",
|
||||||
"y-protocols": "^1.0.5",
|
"y-protocols": "^1.0.5",
|
||||||
"yjs": "^13.6.7",
|
"yjs": "^13.6.7",
|
||||||
"zod": "^3.21.4"
|
"zod": "^3.22.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@perfsee/webpack": "^1.8.2",
|
"@perfsee/webpack": "^1.8.4",
|
||||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.10",
|
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.11",
|
||||||
"@sentry/webpack-plugin": "^2.5.0",
|
"@sentry/webpack-plugin": "^2.6.2",
|
||||||
"@svgr/webpack": "^8.0.1",
|
"@svgr/webpack": "^8.1.0",
|
||||||
"@swc/core": "^1.3.74",
|
"@swc/core": "^1.3.77",
|
||||||
|
"@types/lodash.debounce": "^4.0.7",
|
||||||
"@types/webpack-env": "^1.18.1",
|
"@types/webpack-env": "^1.18.1",
|
||||||
"copy-webpack-plugin": "^11.0.0",
|
"copy-webpack-plugin": "^11.0.0",
|
||||||
"css-loader": "^6.8.1",
|
"css-loader": "^6.8.1",
|
||||||
@@ -73,7 +82,7 @@
|
|||||||
"source-map-loader": "^4.0.1",
|
"source-map-loader": "^4.0.1",
|
||||||
"style-loader": "^3.3.3",
|
"style-loader": "^3.3.3",
|
||||||
"swc-loader": "^0.2.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",
|
"thread-loader": "^4.0.2",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"webpack": "^5.88.2",
|
"webpack": "^5.88.2",
|
||||||
|
|||||||
|
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 |
@@ -1,5 +1,5 @@
|
|||||||
import { DebugLogger } from '@affine/debug';
|
import { DebugLogger } from '@affine/debug';
|
||||||
import { initEmptyPage, initPageWithPreloading } from '@affine/env/blocksuite';
|
import { initEmptyPage } from '@affine/env/blocksuite';
|
||||||
import {
|
import {
|
||||||
DEFAULT_HELLO_WORLD_PAGE_ID_SUFFIX,
|
DEFAULT_HELLO_WORLD_PAGE_ID_SUFFIX,
|
||||||
DEFAULT_WORKSPACE_NAME,
|
DEFAULT_WORKSPACE_NAME,
|
||||||
@@ -20,7 +20,10 @@ import { getOrCreateWorkspace } from '@affine/workspace/manager';
|
|||||||
import { createIndexedDBDownloadProvider } from '@affine/workspace/providers';
|
import { createIndexedDBDownloadProvider } from '@affine/workspace/providers';
|
||||||
import { nanoid } from '@blocksuite/store';
|
import { nanoid } from '@blocksuite/store';
|
||||||
import { useStaticBlockSuiteWorkspace } from '@toeverything/infra/__internal__/react';
|
import { useStaticBlockSuiteWorkspace } from '@toeverything/infra/__internal__/react';
|
||||||
|
import { rootStore } from '@toeverything/infra/atom';
|
||||||
|
import { buildShowcaseWorkspace } from '@toeverything/infra/blocksuite';
|
||||||
|
|
||||||
|
import { setPageModeAtom } from '../../atoms';
|
||||||
import {
|
import {
|
||||||
BlockSuitePageList,
|
BlockSuitePageList,
|
||||||
NewWorkspaceSettingDetail,
|
NewWorkspaceSettingDetail,
|
||||||
@@ -41,21 +44,23 @@ export const LocalAdapter: WorkspaceAdapter<WorkspaceFlavour.LOCAL> = {
|
|||||||
WorkspaceFlavour.LOCAL
|
WorkspaceFlavour.LOCAL
|
||||||
);
|
);
|
||||||
blockSuiteWorkspace.meta.setName(DEFAULT_WORKSPACE_NAME);
|
blockSuiteWorkspace.meta.setName(DEFAULT_WORKSPACE_NAME);
|
||||||
const page = blockSuiteWorkspace.createPage({
|
|
||||||
id: `${blockSuiteWorkspace.id}-${DEFAULT_HELLO_WORLD_PAGE_ID_SUFFIX}`,
|
|
||||||
});
|
|
||||||
if (runtimeConfig.enablePreloading) {
|
if (runtimeConfig.enablePreloading) {
|
||||||
initPageWithPreloading(page).catch(err => {
|
buildShowcaseWorkspace(blockSuiteWorkspace, {
|
||||||
|
store: rootStore,
|
||||||
|
atoms: {
|
||||||
|
pageMode: setPageModeAtom,
|
||||||
|
},
|
||||||
|
}).catch(err => {
|
||||||
logger.error('init page with preloading failed', err);
|
logger.error('init page with preloading failed', err);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
const page = blockSuiteWorkspace.createPage({
|
||||||
|
id: `${blockSuiteWorkspace.id}-${DEFAULT_HELLO_WORLD_PAGE_ID_SUFFIX}`,
|
||||||
|
});
|
||||||
initEmptyPage(page).catch(error => {
|
initEmptyPage(page).catch(error => {
|
||||||
logger.error('init page with empty failed', error);
|
logger.error('init page with empty failed', error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
blockSuiteWorkspace.setPageMeta(page.id, {
|
|
||||||
jumpOnce: true,
|
|
||||||
});
|
|
||||||
const provider = createIndexedDBDownloadProvider(
|
const provider = createIndexedDBDownloadProvider(
|
||||||
blockSuiteWorkspace.id,
|
blockSuiteWorkspace.id,
|
||||||
blockSuiteWorkspace.doc,
|
blockSuiteWorkspace.doc,
|
||||||
|
|||||||
@@ -70,12 +70,16 @@ export const guideOnboardingAtom = atom<
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export const guideDownloadClientTipAtom = atom<
|
export const guideDownloadClientTipAtom = atom<
|
||||||
Guide['downloadClientTip'],
|
Guide['downloadClientTip'],
|
||||||
[open: boolean],
|
[open: boolean],
|
||||||
void
|
void
|
||||||
>(
|
>(
|
||||||
get => {
|
get => {
|
||||||
|
if (environment.isDesktop) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return get(guidePrimitiveAtom).downloadClientTip;
|
return get(guidePrimitiveAtom).downloadClientTip;
|
||||||
},
|
},
|
||||||
(_, set, open) => {
|
(_, set, open) => {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import { createFetch } from './endowments/fercher';
|
|||||||
import { createTimers } from './endowments/timer';
|
import { createTimers } from './endowments/timer';
|
||||||
import { setupImportsMap } from './setup-imports-map';
|
import { setupImportsMap } from './setup-imports-map';
|
||||||
|
|
||||||
const dynamicImportKey = '$h_import';
|
const dynamicImportKey = '$h_import';
|
||||||
|
|
||||||
const permissionLogger = new DebugLogger('plugins:permission');
|
const permissionLogger = new DebugLogger('plugins:permission');
|
||||||
const importLogger = new DebugLogger('plugins:import');
|
const importLogger = new DebugLogger('plugins:import');
|
||||||
@@ -36,9 +36,13 @@ const importLogger = new DebugLogger('plugins:import');
|
|||||||
const pushLayoutAtom = atom<
|
const pushLayoutAtom = atom<
|
||||||
null,
|
null,
|
||||||
// fixme: check plugin name here
|
// fixme: check plugin name here
|
||||||
[pluginName: string, create: (root: HTMLElement) => () => void],
|
[
|
||||||
|
pluginName: string,
|
||||||
|
create: (root: HTMLElement) => () => void,
|
||||||
|
options: { maxWidth: (number | undefined)[] } | undefined,
|
||||||
|
],
|
||||||
void
|
void
|
||||||
>(null, (_, set, pluginName, callback) => {
|
>(null, (_, set, pluginName, callback, options) => {
|
||||||
set(pluginWindowAtom, items => ({
|
set(pluginWindowAtom, items => ({
|
||||||
...items,
|
...items,
|
||||||
[pluginName]: callback,
|
[pluginName]: callback,
|
||||||
@@ -50,20 +54,20 @@ const pushLayoutAtom = atom<
|
|||||||
first: 'editor',
|
first: 'editor',
|
||||||
second: pluginName,
|
second: pluginName,
|
||||||
splitPercentage: 70,
|
splitPercentage: 70,
|
||||||
|
maxWidth: options?.maxWidth,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
...layout,
|
|
||||||
direction: 'horizontal',
|
direction: 'horizontal',
|
||||||
first: 'editor',
|
first: 'editor',
|
||||||
|
splitPercentage: 70,
|
||||||
second: {
|
second: {
|
||||||
direction: 'horizontal',
|
direction: 'horizontal',
|
||||||
// fixme: incorrect type here
|
first: pluginName,
|
||||||
first: layout.second,
|
second: layout.second,
|
||||||
second: pluginName,
|
splitPercentage: 50,
|
||||||
splitPercentage: 70,
|
|
||||||
},
|
},
|
||||||
} as ExpectedLayout;
|
} satisfies ExpectedLayout;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
addCleanup(pluginName, () => {
|
addCleanup(pluginName, () => {
|
||||||
@@ -77,36 +81,27 @@ const deleteLayoutAtom = atom<null, [string], void>(null, (_, set, id) => {
|
|||||||
delete newItems[id];
|
delete newItems[id];
|
||||||
return newItems;
|
return newItems;
|
||||||
});
|
});
|
||||||
const removeLayout = (layout: LayoutNode): LayoutNode => {
|
const removeLayout = (layout: LayoutNode): LayoutNode | string => {
|
||||||
if (layout === 'editor') {
|
if (typeof layout === 'string') {
|
||||||
return 'editor';
|
return layout;
|
||||||
|
}
|
||||||
|
if (layout.first === id) {
|
||||||
|
return layout.second;
|
||||||
|
} else if (layout.second === id) {
|
||||||
|
return layout.first;
|
||||||
} else {
|
} else {
|
||||||
if (typeof layout === 'string') {
|
return {
|
||||||
return layout as ExpectedLayout;
|
...layout,
|
||||||
}
|
second: removeLayout(layout.second),
|
||||||
if (layout.first === id) {
|
};
|
||||||
return layout.second;
|
|
||||||
} else if (layout.second === id) {
|
|
||||||
return layout.first;
|
|
||||||
} else {
|
|
||||||
return removeLayout(layout.second);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
set(contentLayoutAtom, layout => {
|
set(contentLayoutAtom, layout => {
|
||||||
if (layout === 'editor') {
|
if (layout === 'editor') {
|
||||||
return 'editor';
|
return 'editor';
|
||||||
} else {
|
} else {
|
||||||
if (typeof layout === 'string') {
|
return removeLayout(layout) as ExpectedLayout;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -123,6 +118,7 @@ const rootImportsMapSetupPromise = setupImportsMap(_rootImportsMap, {
|
|||||||
swr: import('swr'),
|
swr: import('swr'),
|
||||||
'@affine/component': import('@affine/component'),
|
'@affine/component': import('@affine/component'),
|
||||||
'@blocksuite/icons': import('@blocksuite/icons'),
|
'@blocksuite/icons': import('@blocksuite/icons'),
|
||||||
|
'@blocksuite/blocks': import('@blocksuite/blocks'),
|
||||||
'@affine/sdk/entry': {
|
'@affine/sdk/entry': {
|
||||||
rootStore: rootStore,
|
rootStore: rootStore,
|
||||||
currentWorkspaceAtom: currentWorkspaceAtom,
|
currentWorkspaceAtom: currentWorkspaceAtom,
|
||||||
@@ -130,7 +126,6 @@ const rootImportsMapSetupPromise = setupImportsMap(_rootImportsMap, {
|
|||||||
pushLayoutAtom: pushLayoutAtom,
|
pushLayoutAtom: pushLayoutAtom,
|
||||||
deleteLayoutAtom: deleteLayoutAtom,
|
deleteLayoutAtom: deleteLayoutAtom,
|
||||||
},
|
},
|
||||||
'@blocksuite/blocks/std': import('@blocksuite/blocks/std'),
|
|
||||||
'@blocksuite/global/utils': import('@blocksuite/global/utils'),
|
'@blocksuite/global/utils': import('@blocksuite/global/utils'),
|
||||||
'@toeverything/infra/atom': import('@toeverything/infra/atom'),
|
'@toeverything/infra/atom': import('@toeverything/infra/atom'),
|
||||||
'@toeverything/components/button': import('@toeverything/components/button'),
|
'@toeverything/components/button': import('@toeverything/components/button'),
|
||||||
@@ -195,6 +190,7 @@ const timer = createTimers(abortController.signal);
|
|||||||
const sharedGlobalThis = Object.assign(Object.create(null), timer, {
|
const sharedGlobalThis = Object.assign(Object.create(null), timer, {
|
||||||
Object: globalThis.Object,
|
Object: globalThis.Object,
|
||||||
fetch: pluginFetch,
|
fetch: pluginFetch,
|
||||||
|
ReadableStream: globalThis.ReadableStream,
|
||||||
Symbol: globalThis.Symbol,
|
Symbol: globalThis.Symbol,
|
||||||
Error: globalThis.Error,
|
Error: globalThis.Error,
|
||||||
TypeError: globalThis.TypeError,
|
TypeError: globalThis.TypeError,
|
||||||
|
|||||||
@@ -85,12 +85,13 @@ export const pluginRegisterPromise = Promise.all(
|
|||||||
if (assets.length > 0) {
|
if (assets.length > 0) {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
assets.map(async (asset: string) => {
|
assets.map(async (asset: string) => {
|
||||||
|
const loadedAssetName = `${pluginName}_${asset}`;
|
||||||
// todo(himself65): add assets into shadow dom
|
// todo(himself65): add assets into shadow dom
|
||||||
if (loadedAssets.has(asset)) {
|
if (loadedAssets.has(loadedAssetName)) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
if (asset.endsWith('.css')) {
|
if (asset.endsWith('.css')) {
|
||||||
loadedAssets.add(asset);
|
loadedAssets.add(loadedAssetName);
|
||||||
const res = await fetch(`${baseURL}/${asset}`);
|
const res = await fetch(`${baseURL}/${asset}`);
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
// todo: how to put css file into sandbox?
|
// todo: how to put css file into sandbox?
|
||||||
|
|||||||
@@ -169,7 +169,14 @@ function createFirstAppData() {
|
|||||||
rootStore.set(rootWorkspacesMetadataAtom, result);
|
rootStore.set(rootWorkspacesMetadataAtom, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let isSetup = false;
|
||||||
|
|
||||||
export async function setup() {
|
export async function setup() {
|
||||||
|
if (isSetup) {
|
||||||
|
console.warn('already setup');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
isSetup = true;
|
||||||
rootStore.set(
|
rootStore.set(
|
||||||
workspaceAdaptersAtom,
|
workspaceAdaptersAtom,
|
||||||
WorkspaceAdapters as Record<
|
WorkspaceAdapters as Record<
|
||||||
|
|||||||
@@ -81,11 +81,7 @@ const NameWorkspaceContent = ({
|
|||||||
onChange={setWorkspaceName}
|
onChange={setWorkspaceName}
|
||||||
/>
|
/>
|
||||||
<div className={style.buttonGroup}>
|
<div className={style.buttonGroup}>
|
||||||
<Button
|
<Button data-testid="create-workspace-close-button" onClick={onClose}>
|
||||||
data-testid="create-workspace-close-button"
|
|
||||||
type="primary"
|
|
||||||
onClick={onClose}
|
|
||||||
>
|
|
||||||
{t.Cancel()}
|
{t.Cancel()}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
import { Menu, MenuItem, MenuTrigger, styled } from '@affine/component';
|
import {
|
||||||
|
Menu,
|
||||||
|
MenuItem,
|
||||||
|
type MenuProps,
|
||||||
|
MenuTrigger,
|
||||||
|
styled,
|
||||||
|
} from '@affine/component';
|
||||||
import { LOCALES } from '@affine/i18n';
|
import { LOCALES } from '@affine/i18n';
|
||||||
import { useI18N } from '@affine/i18n';
|
import { useI18N } from '@affine/i18n';
|
||||||
import type { ButtonProps } from '@toeverything/components/button';
|
import type { ButtonProps } from '@toeverything/components/button';
|
||||||
@@ -6,7 +12,6 @@ import type { ReactElement } from 'react';
|
|||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
export const StyledListItem = styled(MenuItem)(() => ({
|
export const StyledListItem = styled(MenuItem)(() => ({
|
||||||
width: '132px',
|
|
||||||
height: '38px',
|
height: '38px',
|
||||||
textTransform: 'capitalize',
|
textTransform: 'capitalize',
|
||||||
}));
|
}));
|
||||||
@@ -46,11 +51,14 @@ const LanguageMenuContent = ({ currentLanguage }: LanguageMenuContentProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
interface LanguageMenuProps {
|
interface LanguageMenuProps extends Omit<MenuProps, 'children'> {
|
||||||
triggerProps?: ButtonProps;
|
triggerProps?: ButtonProps;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LanguageMenu = ({ triggerProps }: LanguageMenuProps) => {
|
export const LanguageMenu = ({
|
||||||
|
triggerProps,
|
||||||
|
...menuProps
|
||||||
|
}: LanguageMenuProps) => {
|
||||||
const i18n = useI18N();
|
const i18n = useI18N();
|
||||||
|
|
||||||
const currentLanguage = LOCALES.find(item => item.tag === i18n.language);
|
const currentLanguage = LOCALES.find(item => item.tag === i18n.language);
|
||||||
@@ -67,6 +75,7 @@ export const LanguageMenu = ({ triggerProps }: LanguageMenuProps) => {
|
|||||||
placement="bottom-end"
|
placement="bottom-end"
|
||||||
trigger="click"
|
trigger="click"
|
||||||
disablePortal={true}
|
disablePortal={true}
|
||||||
|
{...menuProps}
|
||||||
>
|
>
|
||||||
<MenuTrigger
|
<MenuTrigger
|
||||||
data-testid="language-menu-button"
|
data-testid="language-menu-button"
|
||||||
|
|||||||
@@ -30,11 +30,11 @@ export const DeleteLeaveWorkspace = ({
|
|||||||
name={
|
name={
|
||||||
<span style={{ color: 'var(--affine-error-color)' }}>
|
<span style={{ color: 'var(--affine-error-color)' }}>
|
||||||
{isOwner
|
{isOwner
|
||||||
? t['com.affine.settings.workspace.remove']()
|
? t['com.affine.settings.remove-workspace']()
|
||||||
: t['Leave Workspace']()}
|
: t['Leave Workspace']()}
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
desc={t['com.affine.settings.workspace.remove.message']()}
|
desc={t['com.affine.settings.remove-workspace-description']()}
|
||||||
style={{ cursor: 'pointer' }}
|
style={{ cursor: 'pointer' }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowDelete(true);
|
setShowDelete(true);
|
||||||
|
|||||||
@@ -38,9 +38,8 @@ export const ExportPanel = ({ workspace }: ExportPanelProps) => {
|
|||||||
const t = useAFFiNEI18N();
|
const t = useAFFiNEI18N();
|
||||||
const onExport = useCallback(async () => {
|
const onExport = useCallback(async () => {
|
||||||
await syncBlobsToSqliteDb(workspace);
|
await syncBlobsToSqliteDb(workspace);
|
||||||
const result: SaveDBFileResult = await window.apis?.dialog.saveDBFileAs(
|
const result: SaveDBFileResult =
|
||||||
workspaceId
|
await window.apis?.dialog.saveDBFileAs(workspaceId);
|
||||||
);
|
|
||||||
if (result?.error) {
|
if (result?.error) {
|
||||||
toast(t[result.error]());
|
toast(t[result.error]());
|
||||||
} else if (!result?.canceled) {
|
} else if (!result?.canceled) {
|
||||||
|
|||||||
@@ -59,14 +59,12 @@ export const WorkspaceSettingDetail = ({
|
|||||||
<>
|
<>
|
||||||
<SettingHeader
|
<SettingHeader
|
||||||
title={t[`Workspace Settings with name`]({ name })}
|
title={t[`Workspace Settings with name`]({ name })}
|
||||||
subtitle={t['You can customize your workspace here.']()}
|
subtitle={t['com.affine.settings.workspace.description']()}
|
||||||
/>
|
/>
|
||||||
<SettingWrapper title={t['Info']()}>
|
<SettingWrapper title={t['Info']()}>
|
||||||
<SettingRow
|
<SettingRow
|
||||||
name={t['Workspace Profile']()}
|
name={t['Workspace Profile']()}
|
||||||
desc={t[
|
desc={t['com.affine.settings.workspace.not-owner']()}
|
||||||
'Only an owner can edit the the Workspace avatar and name.Changes will be shown for everyone.'
|
|
||||||
]()}
|
|
||||||
spreadCol={false}
|
spreadCol={false}
|
||||||
>
|
>
|
||||||
<ProfilePanel workspace={workspace} />
|
<ProfilePanel workspace={workspace} />
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Input, toast } from '@affine/component';
|
import { FlexWrapper, Input, toast, Wrapper } from '@affine/component';
|
||||||
import { WorkspaceAvatar } from '@affine/component/workspace-avatar';
|
import { WorkspaceAvatar } from '@affine/component/workspace-avatar';
|
||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import { DoneIcon } from '@blocksuite/icons';
|
import { CameraIcon, DoneIcon } from '@blocksuite/icons';
|
||||||
import { IconButton } from '@toeverything/components/button';
|
import { IconButton } from '@toeverything/components/button';
|
||||||
import { useBlockSuiteWorkspaceAvatarUrl } from '@toeverything/hooks/use-block-suite-workspace-avatar-url';
|
import { useBlockSuiteWorkspaceAvatarUrl } from '@toeverything/hooks/use-block-suite-workspace-avatar-url';
|
||||||
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
|
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
|
||||||
@@ -11,25 +11,6 @@ import type { AffineOfficialWorkspace } from '../../../shared';
|
|||||||
import { Upload } from '../../pure/file-upload';
|
import { Upload } from '../../pure/file-upload';
|
||||||
import * as style from './style.css';
|
import * as style from './style.css';
|
||||||
|
|
||||||
const CameraIcon = () => {
|
|
||||||
return (
|
|
||||||
<svg
|
|
||||||
width="24"
|
|
||||||
height="24"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fillRule="evenodd"
|
|
||||||
clipRule="evenodd"
|
|
||||||
d="M10.6236 4.25001C10.635 4.25001 10.6467 4.25002 10.6584 4.25002H13.3416C13.3533 4.25002 13.365 4.25001 13.3764 4.25001C13.5609 4.24995 13.7105 4.2499 13.8543 4.26611C14.5981 4.34997 15.2693 4.75627 15.6826 5.38026C15.7624 5.50084 15.83 5.63398 15.9121 5.79586C15.9173 5.80613 15.9226 5.81652 15.9279 5.82703C15.9538 5.87792 15.9679 5.90562 15.9789 5.9261C15.9832 5.9341 15.9857 5.93861 15.9869 5.94065C16.0076 5.97069 16.0435 5.99406 16.0878 5.99905L16.0849 5.99877C16.0849 5.99877 16.0907 5.99918 16.1047 5.99947C16.1286 5.99998 16.1604 6.00002 16.2181 6.00002L17.185 6.00001C17.6577 6 18.0566 5.99999 18.3833 6.02627C18.7252 6.05377 19.0531 6.11364 19.3656 6.27035C19.8402 6.50842 20.2283 6.88944 20.4723 7.36077C20.6336 7.67233 20.6951 7.99944 20.7232 8.33858C20.75 8.66166 20.75 9.05554 20.75 9.51992V16.2301C20.75 16.6945 20.75 17.0884 20.7232 17.4114C20.6951 17.7506 20.6336 18.0777 20.4723 18.3893C20.2283 18.8606 19.8402 19.2416 19.3656 19.4797C19.0531 19.6364 18.7252 19.6963 18.3833 19.7238C18.0566 19.75 17.6578 19.75 17.185 19.75H6.81497C6.34225 19.75 5.9434 19.75 5.61668 19.7238C5.27477 19.6963 4.94688 19.6364 4.63444 19.4797C4.15978 19.2416 3.77167 18.8606 3.52771 18.3893C3.36644 18.0777 3.30494 17.7506 3.27679 17.4114C3.24998 17.0884 3.24999 16.6945 3.25 16.2302V9.51987C3.24999 9.05551 3.24998 8.66164 3.27679 8.33858C3.30494 7.99944 3.36644 7.67233 3.52771 7.36077C3.77167 6.88944 4.15978 6.50842 4.63444 6.27035C4.94688 6.11364 5.27477 6.05377 5.61668 6.02627C5.9434 5.99999 6.34225 6 6.81498 6.00001L7.78191 6.00002C7.83959 6.00002 7.87142 5.99998 7.8953 5.99947C7.90607 5.99924 7.91176 5.99897 7.91398 5.99884C7.95747 5.99343 7.99267 5.9703 8.01312 5.94066C8.01429 5.93863 8.01684 5.93412 8.02113 5.9261C8.0321 5.90561 8.04622 5.87791 8.07206 5.82703C8.07739 5.81653 8.08266 5.80615 8.08787 5.79588C8.17004 5.63397 8.23759 5.50086 8.31745 5.38026C8.73067 4.75627 9.40192 4.34997 10.1457 4.26611C10.2895 4.2499 10.4391 4.24995 10.6236 4.25001ZM10.6584 5.75002C10.422 5.75002 10.3627 5.75114 10.3138 5.75666C10.0055 5.79142 9.73316 5.95919 9.56809 6.20845C9.54218 6.24758 9.51544 6.29761 9.40943 6.50633C9.40611 6.51287 9.40274 6.5195 9.39934 6.52622C9.36115 6.60161 9.31758 6.68761 9.26505 6.76694C8.9964 7.17261 8.56105 7.4354 8.08026 7.48961C7.98625 7.50021 7.89021 7.50011 7.80434 7.50003C7.79678 7.50002 7.7893 7.50002 7.78191 7.50002H6.84445C6.33444 7.50002 5.99634 7.50058 5.73693 7.52144C5.48594 7.54163 5.37478 7.57713 5.30693 7.61115C5.11257 7.70864 4.95675 7.86306 4.85983 8.05029C4.82733 8.11308 4.79194 8.21816 4.77165 8.46266C4.7506 8.71626 4.75 9.0474 4.75 9.55001V16.2C4.75 16.7026 4.7506 17.0338 4.77165 17.2874C4.79194 17.5319 4.82733 17.6369 4.85983 17.6997C4.95675 17.887 5.11257 18.0414 5.30693 18.1389C5.37478 18.1729 5.48594 18.2084 5.73693 18.2286C5.99634 18.2494 6.33444 18.25 6.84445 18.25H17.1556C17.6656 18.25 18.0037 18.2494 18.2631 18.2286C18.5141 18.2084 18.6252 18.1729 18.6931 18.1389C18.8874 18.0414 19.0433 17.887 19.1402 17.6997C19.1727 17.6369 19.2081 17.5319 19.2283 17.2874C19.2494 17.0338 19.25 16.7026 19.25 16.2V9.55001C19.25 9.0474 19.2494 8.71626 19.2283 8.46266C19.2081 8.21816 19.1727 8.11308 19.1402 8.05029C19.0433 7.86306 18.8874 7.70864 18.6931 7.61115C18.6252 7.57713 18.5141 7.54163 18.2631 7.52144C18.0037 7.50058 17.6656 7.50002 17.1556 7.50002H16.2181C16.2107 7.50002 16.2032 7.50002 16.1957 7.50003C16.1098 7.50011 16.0138 7.50021 15.9197 7.48961C15.4389 7.4354 15.0036 7.17261 14.735 6.76694C14.6824 6.68761 14.6389 6.60163 14.6007 6.52622C14.5973 6.5195 14.5939 6.51287 14.5906 6.50633C14.4846 6.29763 14.4578 6.24758 14.4319 6.20846C14.2668 5.95919 13.9945 5.79142 13.6862 5.75666C13.6373 5.75114 13.578 5.75002 13.3416 5.75002H10.6584ZM12 11C10.9303 11 10.0833 11.8506 10.0833 12.875C10.0833 13.8995 10.9303 14.75 12 14.75C13.0697 14.75 13.9167 13.8995 13.9167 12.875C13.9167 11.8506 13.0697 11 12 11ZM8.58333 12.875C8.58333 11 10.1242 9.50002 12 9.50002C13.8758 9.50002 15.4167 11 15.4167 12.875C15.4167 14.7501 13.8758 16.25 12 16.25C10.1242 16.25 8.58333 14.7501 8.58333 12.875Z"
|
|
||||||
fill="white"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface ProfilePanelProps {
|
interface ProfilePanelProps {
|
||||||
workspace: AffineOfficialWorkspace;
|
workspace: AffineOfficialWorkspace;
|
||||||
}
|
}
|
||||||
@@ -74,32 +55,35 @@ export const ProfilePanel = ({ workspace }: ProfilePanelProps) => {
|
|||||||
</>
|
</>
|
||||||
</Upload>
|
</Upload>
|
||||||
</div>
|
</div>
|
||||||
<div className={style.profileHandlerWrapper}>
|
<Wrapper marginLeft={20}>
|
||||||
<Input
|
<div className={style.label}>{t['Workspace Name']()}</div>
|
||||||
width={280}
|
<FlexWrapper alignItems="center" flexGrow="1">
|
||||||
height={32}
|
<Input
|
||||||
defaultValue={input}
|
width={280}
|
||||||
data-testid="workspace-name-input"
|
height={32}
|
||||||
placeholder={t['Workspace Name']()}
|
defaultValue={input}
|
||||||
maxLength={64}
|
data-testid="workspace-name-input"
|
||||||
minLength={0}
|
placeholder={t['Workspace Name']()}
|
||||||
onChange={setInput}
|
maxLength={64}
|
||||||
/>
|
minLength={0}
|
||||||
{input === workspace.blockSuiteWorkspace.meta.name ? null : (
|
onChange={setInput}
|
||||||
<IconButton
|
/>
|
||||||
data-testid="save-workspace-name"
|
{input === workspace.blockSuiteWorkspace.meta.name ? null : (
|
||||||
onClick={() => {
|
<IconButton
|
||||||
handleUpdateWorkspaceName(input);
|
data-testid="save-workspace-name"
|
||||||
}}
|
onClick={() => {
|
||||||
active={true}
|
handleUpdateWorkspaceName(input);
|
||||||
style={{
|
}}
|
||||||
marginLeft: '12px',
|
active={true}
|
||||||
}}
|
style={{
|
||||||
>
|
marginLeft: '12px',
|
||||||
<DoneIcon />
|
}}
|
||||||
</IconButton>
|
>
|
||||||
)}
|
<DoneIcon />
|
||||||
</div>
|
</IconButton>
|
||||||
|
)}
|
||||||
|
</FlexWrapper>
|
||||||
|
</Wrapper>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ const FakePublishPanelAffine = (_props: FakePublishPanelAffineProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
content={t['com.affine.settings.workspace.publish.local-tooltip']()}
|
content={t['com.affine.settings.workspace.publish-tooltip']()}
|
||||||
placement="top"
|
placement="top"
|
||||||
>
|
>
|
||||||
<div className={style.fakeWrapper}>
|
<div className={style.fakeWrapper}>
|
||||||
|
|||||||
@@ -5,12 +5,6 @@ export const profileWrapper = style({
|
|||||||
alignItems: 'flex-end',
|
alignItems: 'flex-end',
|
||||||
marginTop: '12px',
|
marginTop: '12px',
|
||||||
});
|
});
|
||||||
export const profileHandlerWrapper = style({
|
|
||||||
flexGrow: '1',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginLeft: '20px',
|
|
||||||
});
|
|
||||||
|
|
||||||
export const avatarWrapper = style({
|
export const avatarWrapper = style({
|
||||||
width: '56px',
|
width: '56px',
|
||||||
@@ -39,6 +33,8 @@ globalStyle(`${avatarWrapper} .camera-icon-wrapper`, {
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
backgroundColor: 'rgba(60, 61, 63, 0.5)',
|
backgroundColor: 'rgba(60, 61, 63, 0.5)',
|
||||||
zIndex: '1',
|
zIndex: '1',
|
||||||
|
color: 'var(--affine-white)',
|
||||||
|
fontSize: '24px',
|
||||||
});
|
});
|
||||||
|
|
||||||
export const urlButton = style({
|
export const urlButton = style({
|
||||||
@@ -71,3 +67,9 @@ export const fakeWrapper = style({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const label = style({
|
||||||
|
fontSize: 'var(--affine-font-xs)',
|
||||||
|
color: 'var(--affine-text-secondary-color)',
|
||||||
|
marginBottom: '5px',
|
||||||
|
});
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export const AboutAffine = () => {
|
|||||||
</SettingRow>
|
</SettingRow>
|
||||||
<SettingRow
|
<SettingRow
|
||||||
name={t[`Discover what's new`]()}
|
name={t[`Discover what's new`]()}
|
||||||
desc={t['View the AFFiNE Changelog.']()}
|
desc={t['Changelog description']()}
|
||||||
style={{ cursor: 'pointer' }}
|
style={{ cursor: 'pointer' }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
window.open(runtimeConfig.changelogUrl, '_blank');
|
window.open(runtimeConfig.changelogUrl, '_blank');
|
||||||
|
|||||||
@@ -35,10 +35,10 @@ export const ThemeSettings = () => {
|
|||||||
<RadioButton value="system" data-testid="system-theme-trigger">
|
<RadioButton value="system" data-testid="system-theme-trigger">
|
||||||
{t['system']()}
|
{t['system']()}
|
||||||
</RadioButton>
|
</RadioButton>
|
||||||
<RadioButton bold={true} value="light" data-testid="light-theme-trigger">
|
<RadioButton value="light" data-testid="light-theme-trigger">
|
||||||
{t['light']()}
|
{t['light']()}
|
||||||
</RadioButton>
|
</RadioButton>
|
||||||
<RadioButton bold={true} value="dark" data-testid="dark-theme-trigger">
|
<RadioButton value="dark" data-testid="dark-theme-trigger">
|
||||||
{t['dark']()}
|
{t['dark']()}
|
||||||
</RadioButton>
|
</RadioButton>
|
||||||
</RadioButtonGroup>
|
</RadioButtonGroup>
|
||||||
@@ -63,7 +63,6 @@ const FontFamilySettings = () => {
|
|||||||
return (
|
return (
|
||||||
<RadioButton
|
<RadioButton
|
||||||
key={key}
|
key={key}
|
||||||
bold={true}
|
|
||||||
value={key}
|
value={key}
|
||||||
data-testid="system-font-style-trigger"
|
data-testid="system-font-style-trigger"
|
||||||
style={{
|
style={{
|
||||||
@@ -110,16 +109,28 @@ export const AppearanceSettings = () => {
|
|||||||
</SettingRow>
|
</SettingRow>
|
||||||
<SettingRow
|
<SettingRow
|
||||||
name={t['Display Language']()}
|
name={t['Display Language']()}
|
||||||
desc={t['Select the language for the interface.']()}
|
desc={t['com.affine.settings.appearance.language-description']()}
|
||||||
>
|
>
|
||||||
<div className={settingWrapper}>
|
<div className={settingWrapper}>
|
||||||
<LanguageMenu />
|
<LanguageMenu
|
||||||
|
triggerContainerStyle={{ width: '100%' }}
|
||||||
|
triggerProps={{
|
||||||
|
style: {
|
||||||
|
width: '100%',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
fontWeight: 600,
|
||||||
|
padding: '0 10px',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
{environment.isDesktop ? (
|
{environment.isDesktop ? (
|
||||||
<SettingRow
|
<SettingRow
|
||||||
name={t['Client Border Style']()}
|
name={t['Client Border Style']()}
|
||||||
desc={t['Customize the appearance of the client.']()}
|
desc={t[
|
||||||
|
'com.affine.settings.appearance.border-style-description'
|
||||||
|
]()}
|
||||||
>
|
>
|
||||||
<Switch
|
<Switch
|
||||||
checked={appSettings.clientBorder}
|
checked={appSettings.clientBorder}
|
||||||
@@ -130,7 +141,7 @@ export const AppearanceSettings = () => {
|
|||||||
|
|
||||||
<SettingRow
|
<SettingRow
|
||||||
name={t['Full width Layout']()}
|
name={t['Full width Layout']()}
|
||||||
desc={t['Maximum display of content within a page.']()}
|
desc={t['com.affine.settings.appearance.full-width-description']()}
|
||||||
>
|
>
|
||||||
<Switch
|
<Switch
|
||||||
data-testid="full-width-layout-trigger"
|
data-testid="full-width-layout-trigger"
|
||||||
@@ -141,7 +152,9 @@ export const AppearanceSettings = () => {
|
|||||||
{runtimeConfig.enableNewSettingUnstableApi && environment.isDesktop ? (
|
{runtimeConfig.enableNewSettingUnstableApi && environment.isDesktop ? (
|
||||||
<SettingRow
|
<SettingRow
|
||||||
name={t['Window frame style']()}
|
name={t['Window frame style']()}
|
||||||
desc={t['Customize appearance of Windows Client.']()}
|
desc={t[
|
||||||
|
'com.affine.settings.appearance.window-frame-description'
|
||||||
|
]()}
|
||||||
>
|
>
|
||||||
<RadioButtonGroup
|
<RadioButtonGroup
|
||||||
className={settingWrapper}
|
className={settingWrapper}
|
||||||
@@ -166,7 +179,7 @@ export const AppearanceSettings = () => {
|
|||||||
<SettingWrapper title={t['Date']()}>
|
<SettingWrapper title={t['Date']()}>
|
||||||
<SettingRow
|
<SettingRow
|
||||||
name={t['Date Format']()}
|
name={t['Date Format']()}
|
||||||
desc={t['Customize your date style.']()}
|
desc={t['com.affine.settings.appearance.date-format-description']()}
|
||||||
>
|
>
|
||||||
<div className={settingWrapper}>
|
<div className={settingWrapper}>
|
||||||
<DateFormatSetting />
|
<DateFormatSetting />
|
||||||
@@ -174,7 +187,7 @@ export const AppearanceSettings = () => {
|
|||||||
</SettingRow>
|
</SettingRow>
|
||||||
<SettingRow
|
<SettingRow
|
||||||
name={t['Start Week On Monday']()}
|
name={t['Start Week On Monday']()}
|
||||||
desc={t['By default, the week starts on Sunday.']()}
|
desc={t['com.affine.settings.appearance.start-week-description']()}
|
||||||
>
|
>
|
||||||
<Switch
|
<Switch
|
||||||
checked={appSettings.startWeekOnMonday}
|
checked={appSettings.startWeekOnMonday}
|
||||||
@@ -187,8 +200,8 @@ export const AppearanceSettings = () => {
|
|||||||
{environment.isDesktop ? (
|
{environment.isDesktop ? (
|
||||||
<SettingWrapper title={t['Sidebar']()}>
|
<SettingWrapper title={t['Sidebar']()}>
|
||||||
<SettingRow
|
<SettingRow
|
||||||
name={t['com.affine.settings.appearance.sidebar.noise']()}
|
name={t['com.affine.settings.noise-style']()}
|
||||||
desc={t['com.affine.settings.appearance.sidebar.noise.message']()}
|
desc={t['com.affine.settings.noise-style-description']()}
|
||||||
>
|
>
|
||||||
<Switch
|
<Switch
|
||||||
checked={appSettings.enableNoisyBackground}
|
checked={appSettings.enableNoisyBackground}
|
||||||
@@ -198,10 +211,8 @@ export const AppearanceSettings = () => {
|
|||||||
/>
|
/>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
<SettingRow
|
<SettingRow
|
||||||
name={t['com.affine.settings.appearance.sidebar.translucent']()}
|
name={t['com.affine.settings.translucent-style']()}
|
||||||
desc={t[
|
desc={t['com.affine.settings.translucent-style-description']()}
|
||||||
'com.affine.settings.appearance.sidebar.translucent.message'
|
|
||||||
]()}
|
|
||||||
>
|
>
|
||||||
<Switch
|
<Switch
|
||||||
checked={appSettings.enableBlurBackground}
|
checked={appSettings.enableBlurBackground}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import {
|
import {
|
||||||
AiIcon,
|
|
||||||
AppearanceIcon,
|
AppearanceIcon,
|
||||||
InformationIcon,
|
InformationIcon,
|
||||||
KeyboardIcon,
|
KeyboardIcon,
|
||||||
|
PluginIcon,
|
||||||
} from '@blocksuite/icons';
|
} from '@blocksuite/icons';
|
||||||
import type { ReactElement, SVGProps } from 'react';
|
import type { ReactElement, SVGProps } from 'react';
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ export const useGeneralSettingList = (): GeneralSettingList => {
|
|||||||
{
|
{
|
||||||
key: 'plugins',
|
key: 'plugins',
|
||||||
title: 'Plugins',
|
title: 'Plugins',
|
||||||
icon: AiIcon,
|
icon: PluginIcon,
|
||||||
testId: 'plugins-panel-trigger',
|
testId: 'plugins-panel-trigger',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import {
|
import {
|
||||||
SettingModal as SettingModalBase,
|
SettingModal as SettingModalBase,
|
||||||
type SettingModalProps as SettingModalBaseProps,
|
type SettingModalProps as SettingModalBaseProps,
|
||||||
|
WorkspaceDetailSkeleton,
|
||||||
} from '@affine/component/setting-components';
|
} from '@affine/component/setting-components';
|
||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import { ContactWithUsIcon } from '@blocksuite/icons';
|
import { ContactWithUsIcon } from '@blocksuite/icons';
|
||||||
import { useCallback } from 'react';
|
import { Suspense, useCallback } from 'react';
|
||||||
|
|
||||||
import { AccountSetting } from './account-setting';
|
import { AccountSetting } from './account-setting';
|
||||||
import {
|
import {
|
||||||
@@ -13,7 +14,7 @@ import {
|
|||||||
useGeneralSettingList,
|
useGeneralSettingList,
|
||||||
} from './general-setting';
|
} from './general-setting';
|
||||||
import { SettingSidebar } from './setting-sidebar';
|
import { SettingSidebar } from './setting-sidebar';
|
||||||
import { settingContent } from './style.css';
|
import { footerIconWrapper, settingContent } from './style.css';
|
||||||
import { WorkspaceSetting } from './workspace-setting';
|
import { WorkspaceSetting } from './workspace-setting';
|
||||||
|
|
||||||
type ActiveTab = GeneralSettingKeys | 'workspace' | 'account';
|
type ActiveTab = GeneralSettingKeys | 'workspace' | 'account';
|
||||||
@@ -77,7 +78,9 @@ export const SettingModal = ({
|
|||||||
<div className="wrapper">
|
<div className="wrapper">
|
||||||
<div className="content">
|
<div className="content">
|
||||||
{activeTab === 'workspace' && workspaceId ? (
|
{activeTab === 'workspace' && workspaceId ? (
|
||||||
<WorkspaceSetting key={workspaceId} workspaceId={workspaceId} />
|
<Suspense fallback={<WorkspaceDetailSkeleton />}>
|
||||||
|
<WorkspaceSetting key={workspaceId} workspaceId={workspaceId} />
|
||||||
|
</Suspense>
|
||||||
) : null}
|
) : null}
|
||||||
{generalSettingList.find(v => v.key === activeTab) ? (
|
{generalSettingList.find(v => v.key === activeTab) ? (
|
||||||
<GeneralSetting generalKey={activeTab as GeneralSettingKeys} />
|
<GeneralSetting generalKey={activeTab as GeneralSettingKeys} />
|
||||||
@@ -85,15 +88,15 @@ export const SettingModal = ({
|
|||||||
{activeTab === 'account' ? <AccountSetting /> : null}
|
{activeTab === 'account' ? <AccountSetting /> : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="footer">
|
<div className="footer">
|
||||||
<ContactWithUsIcon />
|
<div className={footerIconWrapper}>
|
||||||
|
<ContactWithUsIcon />
|
||||||
|
</div>
|
||||||
<a
|
<a
|
||||||
href="https://community.affine.pro/home"
|
href="https://community.affine.pro/home"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
{t[
|
{t['com.affine.settings.suggestion']()}
|
||||||
'Need more customization options? You can suggest them to us in the community.'
|
|
||||||
]()}
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -28,16 +28,18 @@ globalStyle(`${settingContent} .footer`, {
|
|||||||
marginTop: '-80px',
|
marginTop: '-80px',
|
||||||
fontSize: 'var(--affine-font-sm)',
|
fontSize: 'var(--affine-font-sm)',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
minHeight: '100px',
|
||||||
});
|
});
|
||||||
|
|
||||||
globalStyle(`${settingContent} .footer a`, {
|
globalStyle(`${settingContent} .footer a`, {
|
||||||
color: 'var(--affine-text-primary-color)',
|
color: 'var(--affine-text-primary-color)',
|
||||||
lineHeight: 'normal',
|
lineHeight: 'normal',
|
||||||
});
|
});
|
||||||
|
export const footerIconWrapper = style({
|
||||||
globalStyle(`${settingContent} .footer > svg`, {
|
|
||||||
fontSize: 'var(--affine-font-base)',
|
fontSize: 'var(--affine-font-base)',
|
||||||
color: 'var(--affine-icon-color)',
|
color: 'var(--affine-icon-color)',
|
||||||
marginRight: '12px',
|
marginRight: '12px',
|
||||||
marginTop: '1px',
|
height: '19px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { WorkspaceDetailSkeleton } from '@affine/component/setting-components';
|
|
||||||
import { usePassiveWorkspaceEffect } from '@toeverything/infra/__internal__/react';
|
import { usePassiveWorkspaceEffect } from '@toeverything/infra/__internal__/react';
|
||||||
import { useSetAtom } from 'jotai';
|
import { useSetAtom } from 'jotai';
|
||||||
import { Suspense, useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
import { getUIAdapter } from '../../../../adapters/workspace';
|
import { getUIAdapter } from '../../../../adapters/workspace';
|
||||||
import { openSettingModalAtom } from '../../../../atoms';
|
import { openSettingModalAtom } from '../../../../atoms';
|
||||||
@@ -33,12 +32,10 @@ export const WorkspaceSetting = ({ workspaceId }: { workspaceId: string }) => {
|
|||||||
const onTransformWorkspace = useOnTransformWorkspace();
|
const onTransformWorkspace = useOnTransformWorkspace();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Suspense fallback={<WorkspaceDetailSkeleton />}>
|
<NewSettingsDetail
|
||||||
<NewSettingsDetail
|
onTransformWorkspace={onTransformWorkspace}
|
||||||
onTransformWorkspace={onTransformWorkspace}
|
onDeleteWorkspace={onDeleteWorkspace}
|
||||||
onDeleteWorkspace={onDeleteWorkspace}
|
currentWorkspaceId={workspaceId}
|
||||||
currentWorkspaceId={workspaceId}
|
/>
|
||||||
/>
|
|
||||||
</Suspense>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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';
|
||||||
@@ -1,84 +1,54 @@
|
|||||||
// fixme(himself65): refactor this file
|
|
||||||
import { FlexWrapper, Menu, MenuItem } from '@affine/component';
|
import { FlexWrapper, Menu, MenuItem } from '@affine/component';
|
||||||
import { Export, MoveToTrash } from '@affine/component/page-list';
|
import { Export, MoveToTrash } from '@affine/component/page-list';
|
||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import { assertExists } from '@blocksuite/global/utils';
|
import { assertExists } from '@blocksuite/global/utils';
|
||||||
import {
|
import {
|
||||||
|
DuplicateIcon,
|
||||||
EdgelessIcon,
|
EdgelessIcon,
|
||||||
EditIcon,
|
EditIcon,
|
||||||
FavoritedIcon,
|
FavoritedIcon,
|
||||||
FavoriteIcon,
|
FavoriteIcon,
|
||||||
ImportIcon,
|
ImportIcon,
|
||||||
MoreVerticalIcon,
|
|
||||||
PageIcon,
|
PageIcon,
|
||||||
} from '@blocksuite/icons';
|
} from '@blocksuite/icons';
|
||||||
import { IconButton } from '@toeverything/components/button';
|
import type { PageMeta } from '@blocksuite/store';
|
||||||
import { Divider } from '@toeverything/components/divider';
|
import { Divider } from '@toeverything/components/divider';
|
||||||
import {
|
import {
|
||||||
useBlockSuitePageMeta,
|
useBlockSuitePageMeta,
|
||||||
usePageMetaHelper,
|
usePageMetaHelper,
|
||||||
} from '@toeverything/hooks/use-block-suite-page-meta';
|
} from '@toeverything/hooks/use-block-suite-page-meta';
|
||||||
import { currentPageIdAtom } from '@toeverything/infra/atom';
|
import { useBlockSuiteWorkspaceHelper } from '@toeverything/hooks/use-block-suite-workspace-helper';
|
||||||
import { useAtom, useAtomValue } from 'jotai';
|
import { useAtom, useSetAtom } from 'jotai';
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { applyUpdate, encodeStateAsUpdate } from 'yjs';
|
||||||
|
|
||||||
import { pageSettingFamily } from '../../../../atoms';
|
import { pageSettingFamily, setPageModeAtom } from '../../../atoms';
|
||||||
import { useBlockSuiteMetaHelper } from '../../../../hooks/affine/use-block-suite-meta-helper';
|
import { useBlockSuiteMetaHelper } from '../../../hooks/affine/use-block-suite-meta-helper';
|
||||||
import { useCurrentWorkspace } from '../../../../hooks/current/use-current-workspace';
|
import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace';
|
||||||
import { toast } from '../../../../utils';
|
import { useNavigateHelper } from '../../../hooks/use-navigate-helper';
|
||||||
import { HeaderDropDownButton } from '../../../pure/header-drop-down-button';
|
import { toast } from '../../../utils';
|
||||||
import { usePageHelper } from '../../block-suite-page-list/utils';
|
import { HeaderDropDownButton } from '../../pure/header-drop-down-button';
|
||||||
import { LanguageMenu } from './language-menu';
|
import { usePageHelper } from '../block-suite-page-list/utils';
|
||||||
import { MenuThemeModeSwitch } from './theme-mode-switch';
|
|
||||||
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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
type PageMenuProps = {
|
type PageMenuProps = {
|
||||||
rename?: () => void;
|
rename?: () => void;
|
||||||
|
pageId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PageMenu = ({ rename }: PageMenuProps) => {
|
export const PageMenu = ({ rename, pageId }: PageMenuProps) => {
|
||||||
const t = useAFFiNEI18N();
|
const t = useAFFiNEI18N();
|
||||||
// fixme(himself65): remove these hooks ASAP
|
// fixme(himself65): remove these hooks ASAP
|
||||||
const [workspace] = useCurrentWorkspace();
|
const [workspace] = useCurrentWorkspace();
|
||||||
const pageId = useAtomValue(currentPageIdAtom);
|
|
||||||
assertExists(workspace);
|
|
||||||
assertExists(pageId);
|
|
||||||
const blockSuiteWorkspace = workspace.blockSuiteWorkspace;
|
const blockSuiteWorkspace = workspace.blockSuiteWorkspace;
|
||||||
const pageMeta = useBlockSuitePageMeta(blockSuiteWorkspace).find(
|
const pageMeta = useBlockSuitePageMeta(blockSuiteWorkspace).find(
|
||||||
meta => meta.id === pageId
|
meta => meta.id === pageId
|
||||||
);
|
) as PageMeta;
|
||||||
assertExists(pageMeta);
|
|
||||||
const [setting, setSetting] = useAtom(pageSettingFamily(pageId));
|
const [setting, setSetting] = useAtom(pageSettingFamily(pageId));
|
||||||
const mode = setting?.mode ?? 'page';
|
const mode = setting?.mode ?? 'page';
|
||||||
|
|
||||||
const favorite = pageMeta.favorite ?? false;
|
const favorite = pageMeta.favorite ?? false;
|
||||||
const { setPageMeta } = usePageMetaHelper(blockSuiteWorkspace);
|
const { setPageMeta, setPageTitle } = usePageMetaHelper(blockSuiteWorkspace);
|
||||||
const [openConfirm, setOpenConfirm] = useState(false);
|
const [openConfirm, setOpenConfirm] = useState(false);
|
||||||
const { removeToTrash } = useBlockSuiteMetaHelper(blockSuiteWorkspace);
|
const { removeToTrash } = useBlockSuiteMetaHelper(blockSuiteWorkspace);
|
||||||
const { importFile } = usePageHelper(blockSuiteWorkspace);
|
const { importFile } = usePageHelper(blockSuiteWorkspace);
|
||||||
@@ -97,13 +67,41 @@ export const PageMenu = ({ rename }: PageMenuProps) => {
|
|||||||
);
|
);
|
||||||
}, [mode, setSetting, t]);
|
}, [mode, setSetting, t]);
|
||||||
const handleOnConfirm = useCallback(() => {
|
const handleOnConfirm = useCallback(() => {
|
||||||
removeToTrash(pageMeta.id);
|
removeToTrash(pageId);
|
||||||
toast(t['Moved to Trash']());
|
toast(t['Moved to Trash']());
|
||||||
setOpenConfirm(false);
|
setOpenConfirm(false);
|
||||||
}, [pageMeta.id, removeToTrash, t]);
|
}, [pageId, removeToTrash, t]);
|
||||||
const menuItemStyle = {
|
const menuItemStyle = {
|
||||||
padding: '4px 12px',
|
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 = (
|
const EditMenu = (
|
||||||
<>
|
<>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
@@ -147,14 +145,14 @@ export const PageMenu = ({ rename }: PageMenuProps) => {
|
|||||||
{t['com.affine.header.option.add-tag']()}
|
{t['com.affine.header.option.add-tag']()}
|
||||||
</MenuItem> */}
|
</MenuItem> */}
|
||||||
<Divider />
|
<Divider />
|
||||||
{/* <MenuItem
|
<MenuItem
|
||||||
icon={<DuplicateIcon />}
|
icon={<DuplicateIcon />}
|
||||||
data-testid="editor-option-menu-duplicate"
|
data-testid="editor-option-menu-duplicate"
|
||||||
onClick={() => {}}
|
onClick={duplicate}
|
||||||
style={menuItemStyle}
|
style={menuItemStyle}
|
||||||
>
|
>
|
||||||
{t['com.affine.header.option.duplicate']()}
|
{t['com.affine.header.option.duplicate']()}
|
||||||
</MenuItem> */}
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon={<ImportIcon />}
|
icon={<ImportIcon />}
|
||||||
data-testid="editor-option-menu-import"
|
data-testid="editor-option-menu-import"
|
||||||
@@ -204,7 +202,3 @@ export const PageMenu = ({ rename }: PageMenuProps) => {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export const EditorOptionMenu = () => {
|
|
||||||
const { pageId } = useParams();
|
|
||||||
return pageId ? <PageMenu /> : <CommonMenu />;
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { type ComplexStyleRule, style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
export const headerTitleContainer = style({
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
flexGrow: 1,
|
||||||
|
position: 'relative',
|
||||||
|
width: '100%',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const titleEditButton = style({
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
WebkitAppRegion: 'no-drag',
|
||||||
|
} as ComplexStyleRule);
|
||||||
|
|
||||||
|
export const titleInput = style({
|
||||||
|
position: 'absolute',
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
margin: 'auto',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
});
|
||||||
|
export const shadowTitle = style({
|
||||||
|
visibility: 'hidden',
|
||||||
|
});
|
||||||
@@ -6,9 +6,9 @@ import { useAtom } from 'jotai';
|
|||||||
import type { CSSProperties } from 'react';
|
import type { CSSProperties } from 'react';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
import { pageSettingFamily } from '../../../../atoms';
|
import { pageSettingFamily } from '../../../atoms';
|
||||||
import type { BlockSuiteWorkspace } from '../../../../shared';
|
import type { BlockSuiteWorkspace } from '../../../shared';
|
||||||
import { toast } from '../../../../utils';
|
import { toast } from '../../../utils';
|
||||||
import { StyledEditorModeSwitch, StyledKeyboardItem } from './style';
|
import { StyledEditorModeSwitch, StyledKeyboardItem } from './style';
|
||||||
import { EdgelessSwitchItem, PageSwitchItem } from './switch-items';
|
import { EdgelessSwitchItem, PageSwitchItem } from './switch-items';
|
||||||
|
|
||||||
@@ -3,6 +3,8 @@ import type { HTMLAttributes } from 'react';
|
|||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
import { cloneElement, useState } from 'react';
|
import { cloneElement, useState } from 'react';
|
||||||
|
|
||||||
|
import edgelessHover from './animation-data/edgeless-hover.json';
|
||||||
|
import pageHover from './animation-data/page-hover.json';
|
||||||
import { StyledSwitchItem } from './style';
|
import { StyledSwitchItem } from './style';
|
||||||
|
|
||||||
type HoverAnimateControllerProps = {
|
type HoverAnimateControllerProps = {
|
||||||
@@ -52,7 +54,7 @@ export const PageSwitchItem = (
|
|||||||
options={{
|
options={{
|
||||||
loop: false,
|
loop: false,
|
||||||
autoplay: false,
|
autoplay: false,
|
||||||
animationData: require('./animation-data/page-hover.json'),
|
animationData: pageHover,
|
||||||
rendererSettings: {
|
rendererSettings: {
|
||||||
preserveAspectRatio: 'xMidYMid slice',
|
preserveAspectRatio: 'xMidYMid slice',
|
||||||
},
|
},
|
||||||
@@ -71,7 +73,7 @@ export const EdgelessSwitchItem = (
|
|||||||
options={{
|
options={{
|
||||||
loop: false,
|
loop: false,
|
||||||
autoplay: false,
|
autoplay: false,
|
||||||
animationData: require('./animation-data/edgeless-hover.json'),
|
animationData: edgelessHover,
|
||||||
rendererSettings: {
|
rendererSettings: {
|
||||||
preserveAspectRatio: 'xMidYMid slice',
|
preserveAspectRatio: 'xMidYMid slice',
|
||||||
},
|
},
|
||||||
@@ -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,143 +0,0 @@
|
|||||||
import { displayFlex, Menu, MenuItem, styled } from '@affine/component';
|
|
||||||
import { LOCALES } from '@affine/i18n';
|
|
||||||
import { useI18N } from '@affine/i18n';
|
|
||||||
import { ArrowDownSmallIcon, LanguageIcon } from '@blocksuite/icons';
|
|
||||||
import { Button } from '@toeverything/components/button';
|
|
||||||
import type { ReactElement } from 'react';
|
|
||||||
import { useCallback } from 'react';
|
|
||||||
|
|
||||||
const LanguageMenuContent = () => {
|
|
||||||
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 = () => {
|
|
||||||
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;
|
|
||||||
//},
|
|
||||||
`;
|
|
||||||
});
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
import { Menu, MenuItem } from '@affine/component';
|
|
||||||
import { Logo1Icon, SignOutIcon } from '@blocksuite/icons';
|
|
||||||
import type { CSSProperties } from 'react';
|
|
||||||
import { forwardRef } from 'react';
|
|
||||||
|
|
||||||
const EditMenu = (
|
|
||||||
<MenuItem data-testid="editor-option-menu-favorite" icon={<SignOutIcon />}>
|
|
||||||
Sign Out
|
|
||||||
</MenuItem>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const UserAvatar = () => {
|
|
||||||
// fixme: cloud regression
|
|
||||||
const user: any = null;
|
|
||||||
return (
|
|
||||||
<Menu
|
|
||||||
width={276}
|
|
||||||
content={EditMenu}
|
|
||||||
placement="bottom"
|
|
||||||
disablePortal={true}
|
|
||||||
trigger="click"
|
|
||||||
>
|
|
||||||
{user ? (
|
|
||||||
<WorkspaceAvatar
|
|
||||||
size={24}
|
|
||||||
name={user.name}
|
|
||||||
avatar={user.avatar_url}
|
|
||||||
></WorkspaceAvatar>
|
|
||||||
) : (
|
|
||||||
<WorkspaceAvatar size={24}></WorkspaceAvatar>
|
|
||||||
)}
|
|
||||||
</Menu>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface WorkspaceAvatarProps {
|
|
||||||
size: number;
|
|
||||||
name?: string;
|
|
||||||
avatar?: string;
|
|
||||||
style?: CSSProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const WorkspaceAvatar = forwardRef<HTMLDivElement, WorkspaceAvatarProps>(
|
|
||||||
function WorkspaceAvatar(props, ref) {
|
|
||||||
const size = props.size || 20;
|
|
||||||
const sizeStr = size + 'px';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{props.avatar ? (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
...props.style,
|
|
||||||
width: sizeStr,
|
|
||||||
height: sizeStr,
|
|
||||||
color: '#fff',
|
|
||||||
borderRadius: '50%',
|
|
||||||
overflow: 'hidden',
|
|
||||||
display: 'inline-block',
|
|
||||||
verticalAlign: 'middle',
|
|
||||||
}}
|
|
||||||
ref={ref}
|
|
||||||
>
|
|
||||||
<picture>
|
|
||||||
<img
|
|
||||||
style={{ width: sizeStr, height: sizeStr }}
|
|
||||||
src={props.avatar}
|
|
||||||
alt=""
|
|
||||||
referrerPolicy="no-referrer"
|
|
||||||
/>
|
|
||||||
</picture>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
...props.style,
|
|
||||||
width: sizeStr,
|
|
||||||
height: sizeStr,
|
|
||||||
border: '1px solid #fff',
|
|
||||||
color: '#fff',
|
|
||||||
fontSize: Math.ceil(0.5 * size) + 'px',
|
|
||||||
borderRadius: '50%',
|
|
||||||
textAlign: 'center',
|
|
||||||
lineHeight: size + 'px',
|
|
||||||
display: 'inline-block',
|
|
||||||
verticalAlign: 'middle',
|
|
||||||
}}
|
|
||||||
ref={ref}
|
|
||||||
>
|
|
||||||
{props.name ? (
|
|
||||||
props.name.substring(0, 1)
|
|
||||||
) : (
|
|
||||||
<Logo1Icon fontSize={24} color={'#5438FF'} />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
export default UserAvatar;
|
|
||||||
@@ -1,258 +0,0 @@
|
|||||||
import { BrowserWarning } from '@affine/component/affine-banner';
|
|
||||||
import {
|
|
||||||
appSidebarFloatingAtom,
|
|
||||||
appSidebarOpenAtom,
|
|
||||||
} from '@affine/component/app-sidebar';
|
|
||||||
import { SidebarSwitch } from '@affine/component/app-sidebar/sidebar-header';
|
|
||||||
import { isDesktop } from '@affine/env/constant';
|
|
||||||
import { CloseIcon, MinusIcon, RoundedRectangleIcon } from '@blocksuite/icons';
|
|
||||||
import type { Page } from '@blocksuite/store';
|
|
||||||
import {
|
|
||||||
addCleanup,
|
|
||||||
pluginHeaderItemAtom,
|
|
||||||
} from '@toeverything/infra/__internal__/plugin';
|
|
||||||
import clsx from 'clsx';
|
|
||||||
import { useAtom, useAtomValue } from 'jotai';
|
|
||||||
import type { HTMLAttributes, ReactElement, ReactNode } from 'react';
|
|
||||||
import {
|
|
||||||
forwardRef,
|
|
||||||
startTransition,
|
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useMemo,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
} from 'react';
|
|
||||||
|
|
||||||
import { guideDownloadClientTipAtom } from '../../../atoms/guide';
|
|
||||||
import { currentModeAtom } from '../../../atoms/mode';
|
|
||||||
import type { AffineOfficialWorkspace } from '../../../shared';
|
|
||||||
import DownloadClientTip from './download-tips';
|
|
||||||
import { EditorOptionMenu } from './header-right-items/editor-option-menu';
|
|
||||||
import * as styles from './styles.css';
|
|
||||||
import { OSWarningMessage, shouldShowWarning } from './utils';
|
|
||||||
|
|
||||||
export interface BaseHeaderProps<
|
|
||||||
Workspace extends AffineOfficialWorkspace = AffineOfficialWorkspace,
|
|
||||||
> {
|
|
||||||
workspace: Workspace;
|
|
||||||
currentPage: Page | null;
|
|
||||||
isPublic: boolean;
|
|
||||||
leftSlot?: ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum HeaderRightItemName {
|
|
||||||
EditorOptionMenu = 'editorOptionMenu',
|
|
||||||
}
|
|
||||||
|
|
||||||
interface HeaderItem {
|
|
||||||
Component: (props: BaseHeaderProps) => ReactElement;
|
|
||||||
// todo: public workspace should be one of the flavour
|
|
||||||
availableWhen: (
|
|
||||||
workspace: AffineOfficialWorkspace,
|
|
||||||
currentPage: Page | null,
|
|
||||||
status: {
|
|
||||||
isPublic: boolean;
|
|
||||||
}
|
|
||||||
) => boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const HeaderRightItems: Record<HeaderRightItemName, HeaderItem> = {
|
|
||||||
[HeaderRightItemName.EditorOptionMenu]: {
|
|
||||||
Component: EditorOptionMenu,
|
|
||||||
availableWhen: () => {
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const WindowsAppControls = () => {
|
|
||||||
const handleMinimizeApp = useCallback(() => {
|
|
||||||
window.apis?.ui.handleMinimizeApp().catch(err => {
|
|
||||||
console.error(err);
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
const handleMaximizeApp = useCallback(() => {
|
|
||||||
window.apis?.ui.handleMaximizeApp().catch(err => {
|
|
||||||
console.error(err);
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
const handleCloseApp = useCallback(() => {
|
|
||||||
window.apis?.ui.handleCloseApp().catch(err => {
|
|
||||||
console.error(err);
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-platform-target="win32"
|
|
||||||
className={styles.windowAppControlsWrapper}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
data-type="minimize"
|
|
||||||
className={styles.windowAppControl}
|
|
||||||
onClick={handleMinimizeApp}
|
|
||||||
>
|
|
||||||
<MinusIcon />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
data-type="maximize"
|
|
||||||
className={styles.windowAppControl}
|
|
||||||
onClick={handleMaximizeApp}
|
|
||||||
>
|
|
||||||
<RoundedRectangleIcon />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
data-type="close"
|
|
||||||
className={styles.windowAppControl}
|
|
||||||
onClick={handleCloseApp}
|
|
||||||
>
|
|
||||||
<CloseIcon />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const PluginHeader = () => {
|
|
||||||
const headerItem = useAtomValue(pluginHeaderItemAtom);
|
|
||||||
const pluginsRef = useRef<string[]>([]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={styles.pluginHeaderItems}
|
|
||||||
ref={useCallback(
|
|
||||||
(root: HTMLDivElement | null) => {
|
|
||||||
if (root) {
|
|
||||||
Object.entries(headerItem).forEach(([pluginName, create]) => {
|
|
||||||
if (pluginsRef.current.includes(pluginName)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
pluginsRef.current.push(pluginName);
|
|
||||||
const div = document.createElement('div');
|
|
||||||
div.setAttribute('plugin-id', pluginName);
|
|
||||||
startTransition(() => {
|
|
||||||
const cleanup = create(div);
|
|
||||||
root.appendChild(div);
|
|
||||||
addCleanup(pluginName, () => {
|
|
||||||
pluginsRef.current = pluginsRef.current.filter(
|
|
||||||
name => name !== pluginName
|
|
||||||
);
|
|
||||||
root.removeChild(div);
|
|
||||||
cleanup();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[headerItem]
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface HeaderProps
|
|
||||||
extends BaseHeaderProps,
|
|
||||||
HTMLAttributes<HTMLDivElement> {
|
|
||||||
children?: ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Header = forwardRef<HTMLDivElement, HeaderProps>((props, ref) => {
|
|
||||||
const [showWarning, setShowWarning] = useState(false);
|
|
||||||
const [showDownloadTip, setShowDownloadTip] = useAtom(
|
|
||||||
guideDownloadClientTipAtom
|
|
||||||
);
|
|
||||||
useEffect(() => {
|
|
||||||
setShowWarning(shouldShowWarning());
|
|
||||||
}, []);
|
|
||||||
const open = useAtomValue(appSidebarOpenAtom);
|
|
||||||
const appSidebarFloating = useAtomValue(appSidebarFloatingAtom);
|
|
||||||
|
|
||||||
const mode = useAtomValue(currentModeAtom);
|
|
||||||
const isWindowsDesktop = globalThis.platform === 'win32' && isDesktop;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={styles.headerContainer}
|
|
||||||
ref={ref}
|
|
||||||
data-has-warning={showWarning}
|
|
||||||
data-open={open}
|
|
||||||
data-sidebar-floating={appSidebarFloating}
|
|
||||||
>
|
|
||||||
{showDownloadTip ? (
|
|
||||||
<DownloadClientTip
|
|
||||||
show={showDownloadTip}
|
|
||||||
onClose={() => {
|
|
||||||
setShowDownloadTip(false);
|
|
||||||
localStorage.setItem('affine-is-dt-hide', '1');
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<BrowserWarning
|
|
||||||
show={showWarning}
|
|
||||||
message={<OSWarningMessage />}
|
|
||||||
onClose={() => {
|
|
||||||
setShowWarning(false);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<div
|
|
||||||
className={styles.header}
|
|
||||||
data-has-warning={showWarning}
|
|
||||||
data-testid="editor-header-items"
|
|
||||||
data-is-edgeless={mode === 'edgeless'}
|
|
||||||
data-is-page-list={props.currentPage === null}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={clsx(styles.headerLeftSide, {
|
|
||||||
[styles.headerLeftSideColumn]:
|
|
||||||
isWindowsDesktop || props.currentPage === null,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<div>{!open && <SidebarSwitch />}</div>
|
|
||||||
<div
|
|
||||||
className={clsx(styles.headerLeftSideItem, {
|
|
||||||
[styles.headerLeftSideOpen]: open,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{props.leftSlot}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{props.children}
|
|
||||||
<div
|
|
||||||
className={clsx(styles.headerRightSide, {
|
|
||||||
[styles.headerRightSideWindow]: isWindowsDesktop,
|
|
||||||
[styles.headerRightSideColumn]:
|
|
||||||
isWindowsDesktop || props.currentPage === null,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<PluginHeader />
|
|
||||||
{useMemo(() => {
|
|
||||||
return Object.entries(HeaderRightItems).map(
|
|
||||||
([name, { availableWhen, Component }]) => {
|
|
||||||
if (
|
|
||||||
availableWhen(props.workspace, props.currentPage, {
|
|
||||||
isPublic: props.isPublic,
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
return (
|
|
||||||
<Component
|
|
||||||
workspace={props.workspace}
|
|
||||||
currentPage={props.currentPage}
|
|
||||||
isPublic={props.isPublic}
|
|
||||||
key={name}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}, [props])}
|
|
||||||
</div>
|
|
||||||
{isWindowsDesktop ? <WindowsAppControls /> : null}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
Header.displayName = 'Header';
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
import { assertExists } from '@blocksuite/global/utils';
|
|
||||||
import {
|
|
||||||
useBlockSuitePageMeta,
|
|
||||||
usePageMetaHelper,
|
|
||||||
} from '@toeverything/hooks/use-block-suite-page-meta';
|
|
||||||
import type { HTMLAttributes, ReactElement, ReactNode } from 'react';
|
|
||||||
import { useCallback, useRef, useState } from 'react';
|
|
||||||
|
|
||||||
import { EditorModeSwitch } from './editor-mode-switch';
|
|
||||||
import type { BaseHeaderProps } from './header';
|
|
||||||
import { Header } from './header';
|
|
||||||
import { PageMenu } from './header-right-items/editor-option-menu';
|
|
||||||
import * as styles from './styles.css';
|
|
||||||
|
|
||||||
export interface WorkspaceHeaderProps
|
|
||||||
extends BaseHeaderProps,
|
|
||||||
HTMLAttributes<HTMLDivElement> {
|
|
||||||
children?: ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const BlockSuiteEditorHeader = (
|
|
||||||
props: WorkspaceHeaderProps
|
|
||||||
): ReactElement => {
|
|
||||||
const { workspace, currentPage, children, isPublic } = props;
|
|
||||||
// fixme(himself65): remove this atom and move it to props
|
|
||||||
const pageMeta = useBlockSuitePageMeta(workspace.blockSuiteWorkspace).find(
|
|
||||||
meta => meta.id === currentPage?.id
|
|
||||||
);
|
|
||||||
const pageTitleMeta = usePageMetaHelper(workspace.blockSuiteWorkspace);
|
|
||||||
const [isEditable, setIsEditable] = useState(false);
|
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
|
||||||
const handleClick = useCallback(() => {
|
|
||||||
if (isEditable) {
|
|
||||||
setIsEditable(!isEditable);
|
|
||||||
const value = inputRef.current?.value;
|
|
||||||
if (value !== pageMeta?.title && currentPage) {
|
|
||||||
pageTitleMeta.setPageTitle(currentPage?.id, value || '');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setIsEditable(!isEditable);
|
|
||||||
}
|
|
||||||
}, [currentPage, isEditable, pageMeta?.title, pageTitleMeta]);
|
|
||||||
const handleKeyDown = useCallback(
|
|
||||||
(e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
||||||
if (e.key === 'Enter' || e.key === 'Escape') {
|
|
||||||
handleClick();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[handleClick]
|
|
||||||
);
|
|
||||||
const headerRef = useRef<HTMLDivElement>(null);
|
|
||||||
assertExists(pageMeta);
|
|
||||||
const title = pageMeta?.title;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Header ref={headerRef} {...props}>
|
|
||||||
{children}
|
|
||||||
{!isPublic && currentPage && (
|
|
||||||
<div className={styles.titleContainer}>
|
|
||||||
<div className={styles.titleWrapper}>
|
|
||||||
<div className={styles.switchWrapper}>
|
|
||||||
<EditorModeSwitch
|
|
||||||
blockSuiteWorkspace={workspace.blockSuiteWorkspace}
|
|
||||||
pageId={currentPage.id}
|
|
||||||
style={{
|
|
||||||
marginRight: '12px',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className={styles.pageTitle}>
|
|
||||||
{isEditable ? (
|
|
||||||
<div>
|
|
||||||
<input
|
|
||||||
autoFocus={true}
|
|
||||||
className={styles.title}
|
|
||||||
type="text"
|
|
||||||
data-testid="title-content"
|
|
||||||
defaultValue={pageMeta?.title}
|
|
||||||
onBlur={handleClick}
|
|
||||||
ref={inputRef}
|
|
||||||
onKeyDown={handleKeyDown}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<span data-testid="title-edit-button" onClick={handleClick}>
|
|
||||||
{title || 'Untitled'}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className={styles.searchArrowWrapper}>
|
|
||||||
<PageMenu rename={handleClick} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Header>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
BlockSuiteEditorHeader.displayName = 'BlockSuiteEditorHeader';
|
|
||||||
@@ -1,315 +0,0 @@
|
|||||||
import type { ComplexStyleRule } from '@vanilla-extract/css';
|
|
||||||
import { createContainer, style } from '@vanilla-extract/css';
|
|
||||||
|
|
||||||
export const headerVanillaContainer = createContainer();
|
|
||||||
|
|
||||||
export const headerContainer = style({
|
|
||||||
height: 'auto',
|
|
||||||
flexShrink: 0,
|
|
||||||
position: 'sticky',
|
|
||||||
top: 0,
|
|
||||||
background: 'var(--affine-background-primary-color)',
|
|
||||||
zIndex: 'var(--affine-z-index-popover)',
|
|
||||||
selectors: {
|
|
||||||
'&[data-has-warning="true"]': {
|
|
||||||
height: '96px',
|
|
||||||
},
|
|
||||||
'&[data-sidebar-floating="false"]': {
|
|
||||||
WebkitAppRegion: 'drag',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'@media': {
|
|
||||||
print: {
|
|
||||||
display: 'none',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
':has([data-popper-placement])': {
|
|
||||||
WebkitAppRegion: 'no-drag',
|
|
||||||
},
|
|
||||||
} as ComplexStyleRule);
|
|
||||||
|
|
||||||
export const header = style({
|
|
||||||
containerName: headerVanillaContainer,
|
|
||||||
containerType: 'inline-size',
|
|
||||||
flexShrink: 0,
|
|
||||||
minHeight: '52px',
|
|
||||||
width: '100%',
|
|
||||||
padding: '8px 20px',
|
|
||||||
display: 'grid',
|
|
||||||
gridTemplateColumns: '1fr auto 1fr',
|
|
||||||
alignItems: 'center',
|
|
||||||
background: 'var(--affine-background-primary-color)',
|
|
||||||
zIndex: 99,
|
|
||||||
position: 'relative',
|
|
||||||
selectors: {
|
|
||||||
'&[data-is-page-list="true"], &[data-is-edgeless="true"]': {
|
|
||||||
borderBottom: `1px solid var(--affine-border-color)`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'@container': {
|
|
||||||
[`${headerVanillaContainer} (max-width: 900px)`]: {
|
|
||||||
alignItems: 'start',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const titleContainer = style({
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
alignContent: 'unset',
|
|
||||||
fontSize: 'var(--affine-font-base)',
|
|
||||||
['WebkitAppRegion' as string]: 'no-drag',
|
|
||||||
'@container': {
|
|
||||||
[`${headerVanillaContainer} (max-width: 900px)`]: {
|
|
||||||
alignItems: 'start',
|
|
||||||
paddingTop: '2px',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const title = style({
|
|
||||||
maxWidth: '620px',
|
|
||||||
transition: 'max-width .15s',
|
|
||||||
userSelect: 'none',
|
|
||||||
overflow: 'hidden',
|
|
||||||
textOverflow: 'ellipsis',
|
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
'@media': {
|
|
||||||
'(max-width: 768px)': {
|
|
||||||
selectors: {
|
|
||||||
'&[data-open="true"]': {
|
|
||||||
WebkitAppRegion: 'no-drag',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as ComplexStyleRule);
|
|
||||||
export const pageTitle = style({
|
|
||||||
maxWidth: '600px',
|
|
||||||
overflow: 'hidden',
|
|
||||||
textOverflow: 'ellipsis',
|
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
transition: 'width .15s',
|
|
||||||
cursor: 'pointer',
|
|
||||||
'@container': {
|
|
||||||
[`${headerVanillaContainer} (max-width: 1920px)`]: {
|
|
||||||
maxWidth: '800px',
|
|
||||||
},
|
|
||||||
[`${headerVanillaContainer} (max-width: 1300px)`]: {
|
|
||||||
maxWidth: '400px',
|
|
||||||
},
|
|
||||||
[`${headerVanillaContainer} (max-width: 768px)`]: {
|
|
||||||
maxWidth: '220px',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
export const titleWrapper = style({
|
|
||||||
position: 'relative',
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
});
|
|
||||||
export const headerLeftSide = style({
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
transition: 'all .15s',
|
|
||||||
});
|
|
||||||
export const headerLeftSideColumn = style({
|
|
||||||
'@container': {
|
|
||||||
[`${headerVanillaContainer} (max-width: 900px)`]: {
|
|
||||||
flexDirection: 'column',
|
|
||||||
alignItems: 'flex-start',
|
|
||||||
height: '68px',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
export const headerLeftSideItem = style({
|
|
||||||
'@container': {
|
|
||||||
[`${headerVanillaContainer} (max-width: 900px)`]: {
|
|
||||||
position: 'absolute',
|
|
||||||
left: '0',
|
|
||||||
bottom: '8px',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
export const headerLeftSideOpen = style({
|
|
||||||
'@container': {
|
|
||||||
[`${headerVanillaContainer} (max-width: 900px)`]: {
|
|
||||||
marginLeft: '20px',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
export const headerRightSide = style({
|
|
||||||
height: '100%',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: '12px',
|
|
||||||
zIndex: 1,
|
|
||||||
marginLeft: '20px',
|
|
||||||
justifyContent: 'flex-end',
|
|
||||||
transition: 'all .15s',
|
|
||||||
});
|
|
||||||
|
|
||||||
export const headerRightSideColumn = style({
|
|
||||||
'@container': {
|
|
||||||
[`${headerVanillaContainer} (max-width: 900px)`]: {
|
|
||||||
position: 'absolute',
|
|
||||||
height: 'auto',
|
|
||||||
right: '0',
|
|
||||||
bottom: '8px',
|
|
||||||
marginRight: '18px',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
export const headerRightSideWindow = style({
|
|
||||||
marginRight: '140px',
|
|
||||||
});
|
|
||||||
export const browserWarning = style({
|
|
||||||
backgroundColor: 'var(--affine-background-warning-color)',
|
|
||||||
color: 'var(--affine-warning-color)',
|
|
||||||
height: '36px',
|
|
||||||
fontSize: 'var(--affine-font-sm)',
|
|
||||||
display: 'none',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
selectors: {
|
|
||||||
'&[data-show="true"]': {
|
|
||||||
display: 'flex',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const closeButton = style({
|
|
||||||
width: '36px',
|
|
||||||
height: '36px',
|
|
||||||
color: 'var(--affine-icon-color)',
|
|
||||||
cursor: 'pointer',
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
position: 'absolute',
|
|
||||||
right: '15px',
|
|
||||||
top: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const switchWrapper = style({
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
});
|
|
||||||
|
|
||||||
export const searchArrowWrapper = style({
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginLeft: '4px',
|
|
||||||
});
|
|
||||||
|
|
||||||
export const pageListTitleWrapper = style({
|
|
||||||
fontSize: 'var(--affine-font-base)',
|
|
||||||
color: 'var(--affine-text-primary-color)',
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
});
|
|
||||||
export const allPageListTitleWrapper = style({
|
|
||||||
fontSize: 'var(--affine-font-base)',
|
|
||||||
color: 'var(--affine-text-primary-color)',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
'@container': {
|
|
||||||
[`${headerVanillaContainer} (max-width: 900px)`]: {
|
|
||||||
alignItems: 'flex-start',
|
|
||||||
marginTop: '8px',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
export const pageListTitleIcon = style({
|
|
||||||
fontSize: '20px',
|
|
||||||
height: '1em',
|
|
||||||
marginRight: '12px',
|
|
||||||
});
|
|
||||||
|
|
||||||
export const quickSearchTipButton = style({
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginTop: '12px',
|
|
||||||
color: '#FFFFFF',
|
|
||||||
width: '48px',
|
|
||||||
height: ' 26px',
|
|
||||||
fontSize: 'var(--affine-font-sm)',
|
|
||||||
lineHeight: '22px',
|
|
||||||
background: 'var(--affine-primary-color)',
|
|
||||||
borderRadius: '8px',
|
|
||||||
textAlign: 'center',
|
|
||||||
cursor: 'pointer',
|
|
||||||
});
|
|
||||||
|
|
||||||
export const quickSearchTipContent = style({
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'flex-end',
|
|
||||||
flexDirection: 'column',
|
|
||||||
});
|
|
||||||
|
|
||||||
export const horizontalDivider = style({
|
|
||||||
width: '100%',
|
|
||||||
borderTop: `1px solid var(--affine-border-color)`,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const horizontalDividerContainer = style({
|
|
||||||
width: '100%',
|
|
||||||
padding: '14px',
|
|
||||||
});
|
|
||||||
|
|
||||||
export const windowAppControlsWrapper = style({
|
|
||||||
display: 'flex',
|
|
||||||
gap: '2px',
|
|
||||||
transform: 'translateX(8px)',
|
|
||||||
height: '100%',
|
|
||||||
position: 'absolute',
|
|
||||||
right: '14px',
|
|
||||||
});
|
|
||||||
|
|
||||||
export const windowAppControl = style({
|
|
||||||
WebkitAppRegion: 'no-drag',
|
|
||||||
cursor: 'pointer',
|
|
||||||
display: 'inline-flex',
|
|
||||||
width: '51px',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
borderRadius: '0',
|
|
||||||
selectors: {
|
|
||||||
'&[data-type="close"]': {
|
|
||||||
width: '56px',
|
|
||||||
paddingRight: '5px',
|
|
||||||
marginRight: '-12px',
|
|
||||||
},
|
|
||||||
'&[data-type="close"]:hover': {
|
|
||||||
background: 'var(--affine-windows-close-button)',
|
|
||||||
color: 'var(--affine-pure-white)',
|
|
||||||
},
|
|
||||||
'&:hover': {
|
|
||||||
background: 'var(--affine-hover-color)',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'@container': {
|
|
||||||
[`${headerVanillaContainer} (max-width: 900px)`]: {
|
|
||||||
height: '50px',
|
|
||||||
paddingTop: '0',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as ComplexStyleRule);
|
|
||||||
|
|
||||||
export const pluginHeaderItems = style({
|
|
||||||
display: 'flex',
|
|
||||||
gap: '12px',
|
|
||||||
alignItems: 'center',
|
|
||||||
height: '100%',
|
|
||||||
});
|
|
||||||
@@ -17,15 +17,15 @@ import { contentLayoutAtom, rootStore } from '@toeverything/infra/atom';
|
|||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { useAtomValue, useSetAtom } from 'jotai';
|
import { useAtomValue, useSetAtom } from 'jotai';
|
||||||
import type { CSSProperties, ReactElement } from 'react';
|
import type { CSSProperties, ReactElement } from 'react';
|
||||||
import { memo, Suspense, useCallback, useMemo } from 'react';
|
import { memo, startTransition, Suspense, useCallback, useMemo } from 'react';
|
||||||
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
|
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
|
||||||
|
|
||||||
import { pageSettingFamily } from '../atoms';
|
import { pageSettingFamily } from '../atoms';
|
||||||
import { fontStyleOptions, useAppSetting } from '../atoms/settings';
|
import { fontStyleOptions, useAppSetting } from '../atoms/settings';
|
||||||
import { BlockSuiteEditor as Editor } from './blocksuite/block-suite-editor';
|
import { BlockSuiteEditor as Editor } from './blocksuite/block-suite-editor';
|
||||||
import { TrashButtonGroup } from './blocksuite/workspace-header/header-right-items/trash-button-group';
|
|
||||||
import * as styles from './page-detail-editor.css';
|
import * as styles from './page-detail-editor.css';
|
||||||
import { pluginContainer } from './page-detail-editor.css';
|
import { pluginContainer } from './page-detail-editor.css';
|
||||||
|
import { TrashButtonGroup } from './pure/trash-button-group';
|
||||||
|
|
||||||
export interface PageDetailEditorProps {
|
export interface PageDetailEditorProps {
|
||||||
isPublic?: boolean;
|
isPublic?: boolean;
|
||||||
@@ -76,7 +76,6 @@ const EditorWrapper = memo(function EditorWrapper({
|
|||||||
'--affine-font-family': value,
|
'--affine-font-family': value,
|
||||||
} as CSSProperties
|
} as CSSProperties
|
||||||
}
|
}
|
||||||
key={`${workspace.id}-${pageId}`}
|
|
||||||
mode={isPublic ? 'page' : currentMode}
|
mode={isPublic ? 'page' : currentMode}
|
||||||
page={page}
|
page={page}
|
||||||
onInit={useCallback(
|
onInit={useCallback(
|
||||||
@@ -141,12 +140,14 @@ const PluginContentAdapter = memo<PluginContentAdapterProps>(
|
|||||||
ref={useCallback(
|
ref={useCallback(
|
||||||
(ref: HTMLDivElement | null) => {
|
(ref: HTMLDivElement | null) => {
|
||||||
if (ref) {
|
if (ref) {
|
||||||
const div = document.createElement('div');
|
startTransition(() => {
|
||||||
const cleanup = windowItem(div);
|
const div = document.createElement('div');
|
||||||
ref.appendChild(div);
|
const cleanup = windowItem(div);
|
||||||
addCleanup(pluginName, () => {
|
ref.appendChild(div);
|
||||||
cleanup();
|
addCleanup(pluginName, () => {
|
||||||
ref.removeChild(div);
|
cleanup();
|
||||||
|
ref.removeChild(div);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -182,7 +183,12 @@ const LayoutPanel = memo(function LayoutPanel(
|
|||||||
}}
|
}}
|
||||||
direction={node.direction}
|
direction={node.direction}
|
||||||
>
|
>
|
||||||
<Panel defaultSize={node.splitPercentage}>
|
<Panel
|
||||||
|
defaultSize={node.splitPercentage}
|
||||||
|
style={{
|
||||||
|
maxWidth: node.maxWidth?.[0],
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<LayoutPanel node={node.first} editorProps={props.editorProps} />
|
<LayoutPanel node={node.first} editorProps={props.editorProps} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
@@ -192,6 +198,7 @@ const LayoutPanel = memo(function LayoutPanel(
|
|||||||
defaultSize={100 - node.splitPercentage}
|
defaultSize={100 - node.splitPercentage}
|
||||||
style={{
|
style={{
|
||||||
overflow: 'scroll',
|
overflow: 'scroll',
|
||||||
|
maxWidth: node.maxWidth?.[1],
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Suspense>
|
<Suspense>
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import { CloudWorkspaceIcon } from '@blocksuite/icons';
|
import { CloudWorkspaceIcon } from '@blocksuite/icons';
|
||||||
|
import { Button } from '@toeverything/components/button';
|
||||||
import { useSetAtom } from 'jotai';
|
import { useSetAtom } from 'jotai';
|
||||||
import { type CSSProperties, forwardRef } from 'react';
|
import { type CSSProperties, forwardRef } from 'react';
|
||||||
|
|
||||||
import { openDisableCloudAlertModalAtom } from '../../../atoms';
|
import { openDisableCloudAlertModalAtom } from '../../../atoms';
|
||||||
import { stringToColour } from '../../../utils';
|
import { stringToColour } from '../../../utils';
|
||||||
import { StyledFooter, StyledSignInButton } from './styles';
|
import { StyledFooter } from './styles';
|
||||||
|
|
||||||
export const Footer = () => {
|
export const Footer = () => {
|
||||||
const t = useAFFiNEI18N();
|
const t = useAFFiNEI18N();
|
||||||
@@ -13,13 +14,13 @@ export const Footer = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledFooter data-testid="workspace-list-modal-footer">
|
<StyledFooter data-testid="workspace-list-modal-footer">
|
||||||
<StyledSignInButton
|
<Button
|
||||||
data-testid="sign-in-button"
|
data-testid="sign-in-button"
|
||||||
type="plain"
|
type="plain"
|
||||||
icon={
|
icon={
|
||||||
<div className="circle">
|
<CloudWorkspaceIcon
|
||||||
<CloudWorkspaceIcon />
|
style={{ color: 'var(--affine-primary-color)' }}
|
||||||
</div>
|
/>
|
||||||
}
|
}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
if (!runtimeConfig.enableCloud) {
|
if (!runtimeConfig.enableCloud) {
|
||||||
@@ -28,7 +29,7 @@ export const Footer = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t['Sign in']()}
|
{t['Sign in']()}
|
||||||
</StyledSignInButton>
|
</Button>
|
||||||
</StyledFooter>
|
</StyledFooter>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,10 +1,4 @@
|
|||||||
import {
|
import { displayFlex, styled, textEllipsis } from '@affine/component';
|
||||||
displayFlex,
|
|
||||||
displayInlineFlex,
|
|
||||||
styled,
|
|
||||||
textEllipsis,
|
|
||||||
} from '@affine/component';
|
|
||||||
import { Button } from '@toeverything/components/button';
|
|
||||||
export const StyleWorkspaceInfo = styled('div')(() => {
|
export const StyleWorkspaceInfo = styled('div')(() => {
|
||||||
return {
|
return {
|
||||||
marginLeft: '15px',
|
marginLeft: '15px',
|
||||||
@@ -116,21 +110,3 @@ export const StyledModalHeader = styled('div')(() => {
|
|||||||
...displayFlex('space-between', 'center'),
|
...displayFlex('space-between', 'center'),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
export const StyledSignInButton = styled(Button)(() => {
|
|
||||||
return {
|
|
||||||
fontWeight: 600,
|
|
||||||
paddingLeft: 0,
|
|
||||||
'.circle': {
|
|
||||||
width: '40px',
|
|
||||||
height: '40px',
|
|
||||||
borderRadius: '20px',
|
|
||||||
backgroundColor: 'var(--affine-hover-color)',
|
|
||||||
color: 'var(--affine-primary-color)',
|
|
||||||
fontSize: '24px',
|
|
||||||
flexShrink: 0,
|
|
||||||
marginRight: '16px',
|
|
||||||
...displayInlineFlex('center', 'center'),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|||||||
178
apps/core/src/components/pure/header/index.tsx
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
import { Wrapper } from '@affine/component';
|
||||||
|
import {
|
||||||
|
appSidebarFloatingAtom,
|
||||||
|
appSidebarOpenAtom,
|
||||||
|
SidebarSwitch,
|
||||||
|
} from '@affine/component/app-sidebar';
|
||||||
|
import { isDesktop } from '@affine/env/constant';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import { useAtomValue } from 'jotai';
|
||||||
|
import debounce from 'lodash.debounce';
|
||||||
|
import type { MutableRefObject, ReactNode } from 'react';
|
||||||
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
import * as style from './style.css';
|
||||||
|
import { TopTip } from './top-tip';
|
||||||
|
import { WindowsAppControls } from './windows-app-controls';
|
||||||
|
interface HeaderPros {
|
||||||
|
left?: ReactNode;
|
||||||
|
right?: ReactNode;
|
||||||
|
center?: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useIsTinyScreen = ({
|
||||||
|
mainContainer,
|
||||||
|
leftStatic,
|
||||||
|
leftSlot,
|
||||||
|
centerDom,
|
||||||
|
rightStatic,
|
||||||
|
rightSlot,
|
||||||
|
}: {
|
||||||
|
mainContainer: HTMLElement;
|
||||||
|
leftStatic: MutableRefObject<HTMLElement | null>;
|
||||||
|
leftSlot: MutableRefObject<HTMLElement | null>[];
|
||||||
|
centerDom: MutableRefObject<HTMLElement | null>;
|
||||||
|
rightStatic: MutableRefObject<HTMLElement | null>;
|
||||||
|
rightSlot: MutableRefObject<HTMLElement | null>[];
|
||||||
|
}) => {
|
||||||
|
const [isTinyScreen, setIsTinyScreen] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleResize = debounce(() => {
|
||||||
|
if (!centerDom.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const leftStaticWidth = leftStatic.current?.clientWidth || 0;
|
||||||
|
const leftSlotWidth = leftSlot.reduce((accWidth, dom) => {
|
||||||
|
return accWidth + (dom.current?.clientWidth || 0);
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
const rightStaticWidth = rightStatic.current?.clientWidth || 0;
|
||||||
|
|
||||||
|
const rightSlotWidth = rightSlot.reduce((accWidth, dom) => {
|
||||||
|
return accWidth + (dom.current?.clientWidth || 0);
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
if (!leftSlotWidth && !rightSlotWidth) {
|
||||||
|
if (isTinyScreen) {
|
||||||
|
setIsTinyScreen(false);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const containerRect = mainContainer.getBoundingClientRect();
|
||||||
|
const centerRect = centerDom.current.getBoundingClientRect();
|
||||||
|
|
||||||
|
if (
|
||||||
|
leftStaticWidth + leftSlotWidth + containerRect.left >=
|
||||||
|
centerRect.left ||
|
||||||
|
containerRect.right - centerRect.right <=
|
||||||
|
rightSlotWidth + rightStaticWidth
|
||||||
|
) {
|
||||||
|
setIsTinyScreen(true);
|
||||||
|
} else {
|
||||||
|
setIsTinyScreen(false);
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
handleResize();
|
||||||
|
|
||||||
|
const resizeObserver = new ResizeObserver(() => {
|
||||||
|
handleResize();
|
||||||
|
});
|
||||||
|
|
||||||
|
resizeObserver.observe(mainContainer);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
resizeObserver.disconnect();
|
||||||
|
};
|
||||||
|
}, [
|
||||||
|
centerDom,
|
||||||
|
isTinyScreen,
|
||||||
|
leftSlot,
|
||||||
|
leftStatic,
|
||||||
|
mainContainer,
|
||||||
|
rightSlot,
|
||||||
|
rightStatic,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return isTinyScreen;
|
||||||
|
};
|
||||||
|
|
||||||
|
// The Header component is used to solve the following problems
|
||||||
|
// 1. Manage layout issues independently of page or business logic
|
||||||
|
// 2. Dynamic centered middle element (relative to the main-container), when the middle element is detected to collide with the two elements, the line wrapping process is performed
|
||||||
|
export const Header = ({ left, center, right }: HeaderPros) => {
|
||||||
|
const sidebarSwitchRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const leftSlotRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const centerSlotRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const rightSlotRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const windowControlsRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
|
const isTinyScreen = useIsTinyScreen({
|
||||||
|
mainContainer: document.querySelector('.main-container') || document.body,
|
||||||
|
leftStatic: sidebarSwitchRef,
|
||||||
|
leftSlot: [leftSlotRef],
|
||||||
|
centerDom: centerSlotRef,
|
||||||
|
rightSlot: [rightSlotRef],
|
||||||
|
rightStatic: windowControlsRef,
|
||||||
|
});
|
||||||
|
|
||||||
|
const isWindowsDesktop = globalThis.platform === 'win32' && isDesktop;
|
||||||
|
const open = useAtomValue(appSidebarOpenAtom);
|
||||||
|
const appSidebarFloating = useAtomValue(appSidebarFloatingAtom);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<TopTip />
|
||||||
|
<div
|
||||||
|
className={style.header}
|
||||||
|
// data-has-warning={showWarning}
|
||||||
|
data-open={open}
|
||||||
|
data-sidebar-floating={appSidebarFloating}
|
||||||
|
data-testid="header"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={clsx(style.headerSideContainer, {
|
||||||
|
block: isTinyScreen,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div className={clsx(style.headerItem, 'top-item')}>
|
||||||
|
<div ref={sidebarSwitchRef}>
|
||||||
|
{!open && (
|
||||||
|
<Wrapper marginRight={20}>
|
||||||
|
<SidebarSwitch />
|
||||||
|
</Wrapper>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={clsx(style.headerItem, 'left')}>
|
||||||
|
<div ref={leftSlotRef}>{left}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={clsx({
|
||||||
|
[style.headerCenter]: center,
|
||||||
|
'is-window': isWindowsDesktop,
|
||||||
|
})}
|
||||||
|
ref={centerSlotRef}
|
||||||
|
>
|
||||||
|
{center}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={clsx(style.headerSideContainer, 'right', {
|
||||||
|
block: isTinyScreen,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div className={clsx(style.headerItem, 'top-item')}>
|
||||||
|
<div ref={windowControlsRef}>
|
||||||
|
{isWindowsDesktop ? <WindowsAppControls /> : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={clsx(style.headerItem, 'right')}>
|
||||||
|
<div ref={rightSlotRef}>{right}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
111
apps/core/src/components/pure/header/style.css.tsx
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
import type { ComplexStyleRule } from '@vanilla-extract/css';
|
||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
export const header = style({
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
position: 'relative',
|
||||||
|
padding: '0 16px',
|
||||||
|
minHeight: '52px',
|
||||||
|
borderBottom: '1px solid var(--affine-border-color)',
|
||||||
|
selectors: {
|
||||||
|
'&[data-sidebar-floating="false"]': {
|
||||||
|
WebkitAppRegion: 'drag',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'@media': {
|
||||||
|
print: {
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
':has([data-popper-placement])': {
|
||||||
|
WebkitAppRegion: 'no-drag',
|
||||||
|
},
|
||||||
|
} as ComplexStyleRule);
|
||||||
|
|
||||||
|
export const headerItem = style({
|
||||||
|
minHeight: '32px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
flexShrink: 0,
|
||||||
|
selectors: {
|
||||||
|
'&.top-item': {
|
||||||
|
height: '52px',
|
||||||
|
},
|
||||||
|
'&.left': {
|
||||||
|
justifyContent: 'left',
|
||||||
|
},
|
||||||
|
'&.right': {
|
||||||
|
justifyContent: 'right',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const headerCenter = style({
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
height: '52px',
|
||||||
|
flexShrink: 0,
|
||||||
|
maxWidth: '60%',
|
||||||
|
minWidth: '300px',
|
||||||
|
position: 'absolute',
|
||||||
|
transform: 'translateX(-50%)',
|
||||||
|
left: '50%',
|
||||||
|
zIndex: 1,
|
||||||
|
selectors: {
|
||||||
|
'&.is-window': {
|
||||||
|
maxWidth: '50%',
|
||||||
|
},
|
||||||
|
'&.shadow': {
|
||||||
|
position: 'static',
|
||||||
|
visibility: 'hidden',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const headerSideContainer = style({
|
||||||
|
display: 'flex',
|
||||||
|
flexShrink: 0,
|
||||||
|
alignItems: 'center',
|
||||||
|
selectors: {
|
||||||
|
'&.right': {
|
||||||
|
flexDirection: 'row-reverse',
|
||||||
|
},
|
||||||
|
'&.block': {
|
||||||
|
display: 'block',
|
||||||
|
paddingBottom: '10px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const windowAppControlsWrapper = style({
|
||||||
|
display: 'flex',
|
||||||
|
marginLeft: '20px',
|
||||||
|
// header padding right
|
||||||
|
transform: 'translateX(16px)',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const windowAppControl = style({
|
||||||
|
WebkitAppRegion: 'no-drag',
|
||||||
|
cursor: 'pointer',
|
||||||
|
display: 'inline-flex',
|
||||||
|
width: '52px',
|
||||||
|
height: '52px',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
borderRadius: '0',
|
||||||
|
selectors: {
|
||||||
|
'&[data-type="close"]': {
|
||||||
|
width: '56px',
|
||||||
|
paddingRight: '5px',
|
||||||
|
},
|
||||||
|
'&[data-type="close"]:hover': {
|
||||||
|
background: 'var(--affine-windows-close-button)',
|
||||||
|
color: 'var(--affine-pure-white)',
|
||||||
|
},
|
||||||
|
'&:hover': {
|
||||||
|
background: 'var(--affine-hover-color)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as ComplexStyleRule);
|
||||||
@@ -1,11 +1,16 @@
|
|||||||
|
import { BrowserWarning } from '@affine/component/affine-banner';
|
||||||
|
import { DownloadTips } from '@affine/component/affine-banner';
|
||||||
import { isDesktop } from '@affine/env/constant';
|
import { isDesktop } from '@affine/env/constant';
|
||||||
import { Trans } from '@affine/i18n';
|
import { Trans } from '@affine/i18n';
|
||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
|
import { useAtom } from 'jotai';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { guideDownloadClientTipAtom } from '../../../atoms/guide';
|
||||||
|
|
||||||
const minimumChromeVersion = 102;
|
const minimumChromeVersion = 102;
|
||||||
|
|
||||||
export const shouldShowWarning = () => {
|
const shouldShowWarning = () => {
|
||||||
if (isDesktop) {
|
if (isDesktop) {
|
||||||
// even though desktop has compatibility issues,
|
// even though desktop has compatibility issues,
|
||||||
// we don't want to show the warning
|
// we don't want to show the warning
|
||||||
@@ -22,7 +27,7 @@ export const shouldShowWarning = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const OSWarningMessage = () => {
|
const OSWarningMessage = () => {
|
||||||
const t = useAFFiNEI18N();
|
const t = useAFFiNEI18N();
|
||||||
const [notChrome, setNotChrome] = useState(false);
|
const [notChrome, setNotChrome] = useState(false);
|
||||||
const [notGoodVersion, setNotGoodVersion] = useState(false);
|
const [notGoodVersion, setNotGoodVersion] = useState(false);
|
||||||
@@ -49,3 +54,34 @@ export const OSWarningMessage = () => {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
export const TopTip = () => {
|
||||||
|
const [showWarning, setShowWarning] = useState(false);
|
||||||
|
const [showDownloadTip, setShowDownloadTip] = useAtom(
|
||||||
|
guideDownloadClientTipAtom
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setShowWarning(shouldShowWarning());
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (showDownloadTip && isDesktop) {
|
||||||
|
return (
|
||||||
|
<DownloadTips
|
||||||
|
onClose={() => {
|
||||||
|
setShowDownloadTip(false);
|
||||||
|
localStorage.setItem('affine-is-dt-hide', '1');
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BrowserWarning
|
||||||
|
show={showWarning}
|
||||||
|
message={<OSWarningMessage />}
|
||||||
|
onClose={() => {
|
||||||
|
setShowWarning(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import { CloseIcon, MinusIcon, RoundedRectangleIcon } from '@blocksuite/icons';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
import * as style from './style.css';
|
||||||
|
|
||||||
|
export const WindowsAppControls = () => {
|
||||||
|
const handleMinimizeApp = useCallback(() => {
|
||||||
|
window.apis?.ui.handleMinimizeApp().catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
const handleMaximizeApp = useCallback(() => {
|
||||||
|
window.apis?.ui.handleMaximizeApp().catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
const handleCloseApp = useCallback(() => {
|
||||||
|
window.apis?.ui.handleCloseApp().catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-platform-target="win32"
|
||||||
|
className={style.windowAppControlsWrapper}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
data-type="minimize"
|
||||||
|
className={style.windowAppControl}
|
||||||
|
onClick={handleMinimizeApp}
|
||||||
|
>
|
||||||
|
<MinusIcon />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
data-type="maximize"
|
||||||
|
className={style.windowAppControl}
|
||||||
|
onClick={handleMaximizeApp}
|
||||||
|
>
|
||||||
|
<RoundedRectangleIcon />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
data-type="close"
|
||||||
|
className={style.windowAppControl}
|
||||||
|
onClick={handleCloseApp}
|
||||||
|
>
|
||||||
|
<CloseIcon />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -57,10 +57,14 @@ export const HelpIsland = ({
|
|||||||
inEdgelessPage={mode === 'edgeless'}
|
inEdgelessPage={mode === 'edgeless'}
|
||||||
>
|
>
|
||||||
<StyledAnimateWrapper
|
<StyledAnimateWrapper
|
||||||
style={{ height: spread ? `${showList.length * 44}px` : 0 }}
|
style={{ height: spread ? `${showList.length * 40 + 4}px` : 0 }}
|
||||||
>
|
>
|
||||||
{showList.includes('whatNew') && (
|
{showList.includes('whatNew') && (
|
||||||
<Tooltip content={t["Discover what's new!"]()} placement="left-end">
|
<Tooltip
|
||||||
|
content={t["Discover what's new!"]()}
|
||||||
|
placement="left-end"
|
||||||
|
showArrow={true}
|
||||||
|
>
|
||||||
<StyledIconWrapper
|
<StyledIconWrapper
|
||||||
data-testid="right-bottom-change-log-icon"
|
data-testid="right-bottom-change-log-icon"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -72,7 +76,11 @@ export const HelpIsland = ({
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
{showList.includes('contact') && (
|
{showList.includes('contact') && (
|
||||||
<Tooltip content={t['Contact Us']()} placement="left-end">
|
<Tooltip
|
||||||
|
content={t['Contact Us']()}
|
||||||
|
placement="left-end"
|
||||||
|
showArrow={true}
|
||||||
|
>
|
||||||
<StyledIconWrapper
|
<StyledIconWrapper
|
||||||
data-testid="right-bottom-contact-us-icon"
|
data-testid="right-bottom-contact-us-icon"
|
||||||
onClick={openAbout}
|
onClick={openAbout}
|
||||||
@@ -82,7 +90,11 @@ export const HelpIsland = ({
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
{showList.includes('shortcuts') && (
|
{showList.includes('shortcuts') && (
|
||||||
<Tooltip content={t['Keyboard Shortcuts']()} placement="left-end">
|
<Tooltip
|
||||||
|
content={t['Keyboard Shortcuts']()}
|
||||||
|
placement="left-end"
|
||||||
|
showArrow={true}
|
||||||
|
>
|
||||||
<StyledIconWrapper
|
<StyledIconWrapper
|
||||||
data-testid="shortcuts-icon"
|
data-testid="shortcuts-icon"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -98,6 +110,7 @@ export const HelpIsland = ({
|
|||||||
<Tooltip
|
<Tooltip
|
||||||
content={t['com.affine.helpIsland.gettingStarted']()}
|
content={t['com.affine.helpIsland.gettingStarted']()}
|
||||||
placement="left-end"
|
placement="left-end"
|
||||||
|
showArrow={true}
|
||||||
>
|
>
|
||||||
<StyledIconWrapper
|
<StyledIconWrapper
|
||||||
data-testid="easy-guide"
|
data-testid="easy-guide"
|
||||||
@@ -112,7 +125,11 @@ export const HelpIsland = ({
|
|||||||
)}
|
)}
|
||||||
</StyledAnimateWrapper>
|
</StyledAnimateWrapper>
|
||||||
|
|
||||||
<Tooltip content={t['Help and Feedback']()} placement="left-end">
|
<Tooltip
|
||||||
|
content={t['Help and Feedback']()}
|
||||||
|
placement={'left-end'}
|
||||||
|
showArrow={true}
|
||||||
|
>
|
||||||
<MuiFade in={!spread} data-testid="faq-icon">
|
<MuiFade in={!spread} data-testid="faq-icon">
|
||||||
<StyledTriggerWrapper>
|
<StyledTriggerWrapper>
|
||||||
<HelpIcon />
|
<HelpIcon />
|
||||||
|
|||||||
44
apps/core/src/components/pure/plugin-header/index.tsx
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import {
|
||||||
|
addCleanup,
|
||||||
|
pluginHeaderItemAtom,
|
||||||
|
} from '@toeverything/infra/__internal__/plugin';
|
||||||
|
import { useAtomValue } from 'jotai';
|
||||||
|
import { startTransition, useCallback, useRef } from 'react';
|
||||||
|
|
||||||
|
import * as styles from './styles.css';
|
||||||
|
export const PluginHeader = () => {
|
||||||
|
const headerItem = useAtomValue(pluginHeaderItemAtom);
|
||||||
|
const pluginsRef = useRef<string[]>([]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={styles.pluginHeaderItems}
|
||||||
|
ref={useCallback(
|
||||||
|
(root: HTMLDivElement | null) => {
|
||||||
|
if (root) {
|
||||||
|
Object.entries(headerItem).forEach(([pluginName, create]) => {
|
||||||
|
if (pluginsRef.current.includes(pluginName)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pluginsRef.current.push(pluginName);
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.setAttribute('plugin-id', pluginName);
|
||||||
|
startTransition(() => {
|
||||||
|
const cleanup = create(div);
|
||||||
|
root.appendChild(div);
|
||||||
|
addCleanup(pluginName, () => {
|
||||||
|
pluginsRef.current = pluginsRef.current.filter(
|
||||||
|
name => name !== pluginName
|
||||||
|
);
|
||||||
|
root.removeChild(div);
|
||||||
|
cleanup();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[headerItem]
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
export const pluginHeaderItems = style({
|
||||||
|
display: 'flex',
|
||||||
|
gap: '12px',
|
||||||
|
alignItems: 'center',
|
||||||
|
height: '100%',
|
||||||
|
});
|
||||||
@@ -8,10 +8,11 @@ import { currentPageIdAtom } from '@toeverything/infra/atom';
|
|||||||
import { useAtomValue } from 'jotai';
|
import { useAtomValue } from 'jotai';
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
|
|
||||||
import { useBlockSuiteMetaHelper } from '../../../../hooks/affine/use-block-suite-meta-helper';
|
import { useBlockSuiteMetaHelper } from '../../../hooks/affine/use-block-suite-meta-helper';
|
||||||
import { useCurrentWorkspace } from '../../../../hooks/current/use-current-workspace';
|
import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace';
|
||||||
import { useNavigateHelper } from '../../../../hooks/use-navigate-helper';
|
import { useNavigateHelper } from '../../../hooks/use-navigate-helper';
|
||||||
import { buttonContainer, group } from './styles.css';
|
import { buttonContainer, group } from './styles.css';
|
||||||
|
|
||||||
export const TrashButtonGroup = () => {
|
export const TrashButtonGroup = () => {
|
||||||
// fixme(himself65): remove these hooks ASAP
|
// fixme(himself65): remove these hooks ASAP
|
||||||
const [workspace] = useCurrentWorkspace();
|
const [workspace] = useCurrentWorkspace();
|
||||||
@@ -33,7 +34,7 @@ export const TrashButtonGroup = () => {
|
|||||||
<div className={group}>
|
<div className={group}>
|
||||||
<div className={buttonContainer}>
|
<div className={buttonContainer}>
|
||||||
<Button
|
<Button
|
||||||
type="processing"
|
type="primary"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
restoreFromTrash(pageId);
|
restoreFromTrash(pageId);
|
||||||
}}
|
}}
|
||||||
@@ -1,11 +1,4 @@
|
|||||||
import {
|
import { Menu, MenuItem, Tooltip } from '@affine/component';
|
||||||
Menu,
|
|
||||||
MenuItem,
|
|
||||||
Modal,
|
|
||||||
ModalCloseButton,
|
|
||||||
ModalWrapper,
|
|
||||||
Tooltip,
|
|
||||||
} from '@affine/component';
|
|
||||||
import { ScrollableContainer } from '@affine/component';
|
import { ScrollableContainer } from '@affine/component';
|
||||||
import { WorkspaceList } from '@affine/component/workspace-list';
|
import { WorkspaceList } from '@affine/component/workspace-list';
|
||||||
import type {
|
import type {
|
||||||
@@ -15,28 +8,38 @@ import type {
|
|||||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import type { RootWorkspaceMetadata } from '@affine/workspace/atom';
|
import type { RootWorkspaceMetadata } from '@affine/workspace/atom';
|
||||||
import { HelpIcon, ImportIcon, PlusIcon } from '@blocksuite/icons';
|
|
||||||
import type { DragEndEvent } from '@dnd-kit/core';
|
|
||||||
import { useCallback, useRef } from 'react';
|
|
||||||
|
|
||||||
import type { AllWorkspace } from '../../../shared';
|
|
||||||
import { Footer } from '../footer';
|
|
||||||
import {
|
import {
|
||||||
StyledCreateWorkspaceCard,
|
CloudWorkspaceIcon,
|
||||||
|
HelpIcon,
|
||||||
|
ImportIcon,
|
||||||
|
MoreHorizontalIcon,
|
||||||
|
PlusIcon,
|
||||||
|
} from '@blocksuite/icons';
|
||||||
|
import type { DragEndEvent } from '@dnd-kit/core';
|
||||||
|
import { Popover } from '@mui/material';
|
||||||
|
import { IconButton } from '@toeverything/components/button';
|
||||||
|
import { Divider } from '@toeverything/components/divider';
|
||||||
|
import { useSetAtom } from 'jotai';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
import { openDisableCloudAlertModalAtom } from '../../../atoms';
|
||||||
|
import type { AllWorkspace } from '../../../shared';
|
||||||
|
import {
|
||||||
StyledCreateWorkspaceCardPill,
|
StyledCreateWorkspaceCardPill,
|
||||||
StyledCreateWorkspaceCardPillContainer,
|
|
||||||
StyledCreateWorkspaceCardPillContent,
|
StyledCreateWorkspaceCardPillContent,
|
||||||
StyledCreateWorkspaceCardPillIcon,
|
StyledCreateWorkspaceCardPillIcon,
|
||||||
StyledCreateWorkspaceCardPillTextSecondary,
|
|
||||||
StyledHelperContainer,
|
StyledHelperContainer,
|
||||||
|
StyledImportWorkspaceCardPill,
|
||||||
|
StyledModalBody,
|
||||||
StyledModalContent,
|
StyledModalContent,
|
||||||
StyledModalHeader,
|
StyledModalHeader,
|
||||||
StyledModalHeaderLeft,
|
StyledModalHeaderLeft,
|
||||||
StyledModalTitle,
|
StyledModalTitle,
|
||||||
StyledOperationWrapper,
|
StyledOperationWrapper,
|
||||||
StyleWorkspaceAdd,
|
StyledSignInCardPill,
|
||||||
StyleWorkspaceInfo,
|
StyledSignInCardPillTextCotainer,
|
||||||
StyleWorkspaceTitle,
|
StyledSignInCardPillTextPrimary,
|
||||||
|
StyledSignInCardPillTextSecondary,
|
||||||
} from './styles';
|
} from './styles';
|
||||||
|
|
||||||
interface WorkspaceModalProps {
|
interface WorkspaceModalProps {
|
||||||
@@ -52,105 +55,94 @@ interface WorkspaceModalProps {
|
|||||||
onMoveWorkspace: (activeId: string, overId: string) => void;
|
onMoveWorkspace: (activeId: string, overId: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CreateWorkspaceCard = ({
|
const AccountMenu = () => {
|
||||||
onNewWorkspace,
|
|
||||||
onAddWorkspace,
|
|
||||||
}: {
|
|
||||||
onNewWorkspace: () => void;
|
|
||||||
onAddWorkspace: () => void;
|
|
||||||
}) => {
|
|
||||||
const t = useAFFiNEI18N();
|
const t = useAFFiNEI18N();
|
||||||
const anchorEL = useRef<HTMLDivElement>(null);
|
return (
|
||||||
|
<div>
|
||||||
|
<div>Unlimted</div>
|
||||||
|
<Divider></Divider>
|
||||||
|
<MenuItem icon={<ImportIcon />} data-testid="editor-option-menu-import">
|
||||||
|
{t['com.affine.workspace.cloud.join']()}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem icon={<ImportIcon />} data-testid="editor-option-menu-import">
|
||||||
|
{t['com.affine.workspace.cloud.account.settings']()}
|
||||||
|
</MenuItem>
|
||||||
|
<Divider></Divider>
|
||||||
|
<MenuItem icon={<ImportIcon />} data-testid="editor-option-menu-import">
|
||||||
|
{t['com.affine.workspace.cloud.account.logout']()}
|
||||||
|
</MenuItem>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
if (runtimeConfig.enableSQLiteProvider && environment.isDesktop) {
|
const CloudWorkSpaceList = ({
|
||||||
return (
|
disabled,
|
||||||
<Menu
|
workspaces,
|
||||||
placement="auto"
|
onClickWorkspace,
|
||||||
trigger={['click']}
|
onClickWorkspaceSetting,
|
||||||
zIndex={1000}
|
currentWorkspaceId,
|
||||||
content={
|
onMoveWorkspace,
|
||||||
<StyledCreateWorkspaceCardPillContainer>
|
}: WorkspaceModalProps) => {
|
||||||
<StyledCreateWorkspaceCardPill>
|
const t = useAFFiNEI18N();
|
||||||
<MenuItem
|
|
||||||
style={{
|
|
||||||
height: 'auto',
|
|
||||||
padding: '8px 12px',
|
|
||||||
}}
|
|
||||||
onClick={onNewWorkspace}
|
|
||||||
data-testid="new-workspace"
|
|
||||||
>
|
|
||||||
<StyledCreateWorkspaceCardPillContent>
|
|
||||||
<div>
|
|
||||||
<p>{t['New Workspace']()}</p>
|
|
||||||
<StyledCreateWorkspaceCardPillTextSecondary>
|
|
||||||
<p>{t['Create your own workspace']()}</p>
|
|
||||||
</StyledCreateWorkspaceCardPillTextSecondary>
|
|
||||||
</div>
|
|
||||||
<StyledCreateWorkspaceCardPillIcon>
|
|
||||||
<PlusIcon />
|
|
||||||
</StyledCreateWorkspaceCardPillIcon>
|
|
||||||
</StyledCreateWorkspaceCardPillContent>
|
|
||||||
</MenuItem>
|
|
||||||
</StyledCreateWorkspaceCardPill>
|
|
||||||
<StyledCreateWorkspaceCardPill>
|
|
||||||
<MenuItem
|
|
||||||
onClick={onAddWorkspace}
|
|
||||||
data-testid="add-workspace"
|
|
||||||
style={{
|
|
||||||
height: 'auto',
|
|
||||||
padding: '8px 12px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<StyledCreateWorkspaceCardPillContent>
|
|
||||||
<div>
|
|
||||||
<p>{t['Add Workspace']()}</p>
|
|
||||||
<StyledCreateWorkspaceCardPillTextSecondary>
|
|
||||||
<p>{t['Add Workspace Hint']()}</p>
|
|
||||||
</StyledCreateWorkspaceCardPillTextSecondary>
|
|
||||||
</div>
|
|
||||||
<StyledCreateWorkspaceCardPillIcon>
|
|
||||||
<ImportIcon />
|
|
||||||
</StyledCreateWorkspaceCardPillIcon>
|
|
||||||
</StyledCreateWorkspaceCardPillContent>
|
|
||||||
</MenuItem>
|
|
||||||
</StyledCreateWorkspaceCardPill>
|
|
||||||
</StyledCreateWorkspaceCardPillContainer>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<StyledCreateWorkspaceCard
|
|
||||||
ref={anchorEL}
|
|
||||||
data-testid="add-or-new-workspace"
|
|
||||||
>
|
|
||||||
<StyleWorkspaceAdd className="add-icon">
|
|
||||||
<PlusIcon />
|
|
||||||
</StyleWorkspaceAdd>
|
|
||||||
|
|
||||||
<StyleWorkspaceInfo>
|
return (
|
||||||
<StyleWorkspaceTitle>{t['New Workspace']()}</StyleWorkspaceTitle>
|
<>
|
||||||
<p>{t['Create Or Import']()}</p>
|
<StyledModalHeader>
|
||||||
</StyleWorkspaceInfo>
|
<StyledModalHeaderLeft>
|
||||||
</StyledCreateWorkspaceCard>
|
<StyledModalTitle>
|
||||||
</Menu>
|
{t['com.affine.workspace.cloud.sync']()}
|
||||||
);
|
</StyledModalTitle>
|
||||||
} else {
|
<Tooltip
|
||||||
return (
|
content={t['Workspace description']()}
|
||||||
<div>
|
placement="top-start"
|
||||||
<StyledCreateWorkspaceCard
|
disablePortal={true}
|
||||||
onClick={onNewWorkspace}
|
>
|
||||||
data-testid="new-workspace"
|
<StyledHelperContainer>
|
||||||
>
|
<HelpIcon />
|
||||||
<StyleWorkspaceAdd className="add-icon">
|
</StyledHelperContainer>
|
||||||
<PlusIcon />
|
</Tooltip>
|
||||||
</StyleWorkspaceAdd>
|
</StyledModalHeaderLeft>
|
||||||
|
|
||||||
<StyleWorkspaceInfo>
|
<StyledOperationWrapper>
|
||||||
<StyleWorkspaceTitle>{t['New Workspace']()}</StyleWorkspaceTitle>
|
<Menu
|
||||||
<p>{t['Create Or Import']()}</p>
|
placement="bottom-end"
|
||||||
</StyleWorkspaceInfo>
|
trigger={['click']}
|
||||||
</StyledCreateWorkspaceCard>
|
content={<AccountMenu />}
|
||||||
</div>
|
zIndex={1000}
|
||||||
);
|
>
|
||||||
}
|
<IconButton
|
||||||
|
data-testid="previous-image-button"
|
||||||
|
icon={<MoreHorizontalIcon />}
|
||||||
|
type="plain"
|
||||||
|
/>
|
||||||
|
</Menu>
|
||||||
|
</StyledOperationWrapper>
|
||||||
|
</StyledModalHeader>
|
||||||
|
<StyledModalContent>
|
||||||
|
<WorkspaceList
|
||||||
|
disabled={disabled}
|
||||||
|
items={
|
||||||
|
workspaces.filter(
|
||||||
|
({ flavour }) => flavour !== WorkspaceFlavour.PUBLIC
|
||||||
|
) as (AffineCloudWorkspace | LocalWorkspace)[]
|
||||||
|
}
|
||||||
|
currentWorkspaceId={currentWorkspaceId}
|
||||||
|
onClick={onClickWorkspace}
|
||||||
|
onSettingClick={onClickWorkspaceSetting}
|
||||||
|
onDragEnd={useCallback(
|
||||||
|
(event: DragEndEvent) => {
|
||||||
|
const { active, over } = event;
|
||||||
|
if (active.id !== over?.id) {
|
||||||
|
onMoveWorkspace(active.id as string, over?.id as string);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[onMoveWorkspace]
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Divider />
|
||||||
|
</StyledModalContent>
|
||||||
|
</>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WorkspaceListModal = ({
|
export const WorkspaceListModal = ({
|
||||||
@@ -166,19 +158,77 @@ export const WorkspaceListModal = ({
|
|||||||
onMoveWorkspace,
|
onMoveWorkspace,
|
||||||
}: WorkspaceModalProps) => {
|
}: WorkspaceModalProps) => {
|
||||||
const t = useAFFiNEI18N();
|
const t = useAFFiNEI18N();
|
||||||
|
const setOpen = useSetAtom(openDisableCloudAlertModalAtom);
|
||||||
|
// TODO: AFFiNE Cloud support
|
||||||
|
const isLoggedIn = false;
|
||||||
|
const anchorEl = document.getElementById('current-workspace');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal open={open} onClose={onClose}>
|
<Popover
|
||||||
<ModalWrapper
|
sx={{
|
||||||
width={720}
|
color: 'success.main',
|
||||||
height={690}
|
zIndex: 999,
|
||||||
style={{
|
'& .MuiPopover-paper': {
|
||||||
display: 'flex',
|
borderRadius: '8px',
|
||||||
flexDirection: 'column',
|
boxShadow: 'var(--affine-shadow-1)',
|
||||||
}}
|
},
|
||||||
>
|
}}
|
||||||
<StyledModalHeader>
|
open={open}
|
||||||
<StyledModalHeaderLeft>
|
anchorEl={anchorEl}
|
||||||
<StyledModalTitle>{t['My Workspaces']()}</StyledModalTitle>
|
onClose={onClose}
|
||||||
|
>
|
||||||
|
<StyledModalBody>
|
||||||
|
<ScrollableContainer>
|
||||||
|
{isLoggedIn ? (
|
||||||
|
<CloudWorkSpaceList
|
||||||
|
disabled={disabled}
|
||||||
|
open={open}
|
||||||
|
onClose={onClose}
|
||||||
|
workspaces={workspaces}
|
||||||
|
onClickWorkspace={onClickWorkspace}
|
||||||
|
onClickWorkspaceSetting={onClickWorkspaceSetting}
|
||||||
|
onNewWorkspace={onNewWorkspace}
|
||||||
|
onAddWorkspace={onAddWorkspace}
|
||||||
|
currentWorkspaceId={currentWorkspaceId}
|
||||||
|
onMoveWorkspace={onMoveWorkspace}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<StyledModalContent>
|
||||||
|
<StyledSignInCardPill>
|
||||||
|
<MenuItem
|
||||||
|
style={{
|
||||||
|
height: 'auto',
|
||||||
|
padding: '8px 12px',
|
||||||
|
}}
|
||||||
|
onClick={async () => {
|
||||||
|
if (!runtimeConfig.enableCloud) {
|
||||||
|
setOpen(true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
data-testid="cloud-signin-button"
|
||||||
|
>
|
||||||
|
<StyledCreateWorkspaceCardPillContent>
|
||||||
|
<StyledCreateWorkspaceCardPillIcon>
|
||||||
|
<CloudWorkspaceIcon />
|
||||||
|
</StyledCreateWorkspaceCardPillIcon>
|
||||||
|
<StyledSignInCardPillTextCotainer>
|
||||||
|
<StyledSignInCardPillTextPrimary>
|
||||||
|
{t['com.affine.workspace.cloud.auth']()}
|
||||||
|
</StyledSignInCardPillTextPrimary>
|
||||||
|
<StyledSignInCardPillTextSecondary>
|
||||||
|
Sync with AFFiNE Cloud
|
||||||
|
</StyledSignInCardPillTextSecondary>
|
||||||
|
</StyledSignInCardPillTextCotainer>
|
||||||
|
</StyledCreateWorkspaceCardPillContent>
|
||||||
|
</MenuItem>
|
||||||
|
</StyledSignInCardPill>
|
||||||
|
<Divider />
|
||||||
|
</StyledModalContent>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<StyledModalHeader>
|
||||||
|
<StyledModalTitle>{t['Local Workspace']()}</StyledModalTitle>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
content={t['Workspace description']()}
|
content={t['Workspace description']()}
|
||||||
placement="top-start"
|
placement="top-start"
|
||||||
@@ -188,19 +238,7 @@ export const WorkspaceListModal = ({
|
|||||||
<HelpIcon />
|
<HelpIcon />
|
||||||
</StyledHelperContainer>
|
</StyledHelperContainer>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</StyledModalHeaderLeft>
|
</StyledModalHeader>
|
||||||
|
|
||||||
<StyledOperationWrapper>
|
|
||||||
<ModalCloseButton
|
|
||||||
data-testid="close-workspace-modal"
|
|
||||||
onClick={() => {
|
|
||||||
onClose();
|
|
||||||
}}
|
|
||||||
absolute={false}
|
|
||||||
/>
|
|
||||||
</StyledOperationWrapper>
|
|
||||||
</StyledModalHeader>
|
|
||||||
<ScrollableContainer>
|
|
||||||
<StyledModalContent>
|
<StyledModalContent>
|
||||||
<WorkspaceList
|
<WorkspaceList
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
@@ -222,14 +260,51 @@ export const WorkspaceListModal = ({
|
|||||||
[onMoveWorkspace]
|
[onMoveWorkspace]
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<CreateWorkspaceCard
|
</StyledModalContent>
|
||||||
onNewWorkspace={onNewWorkspace}
|
<StyledModalContent>
|
||||||
onAddWorkspace={onAddWorkspace}
|
{runtimeConfig.enableSQLiteProvider && environment.isDesktop ? (
|
||||||
/>
|
<StyledImportWorkspaceCardPill>
|
||||||
|
<MenuItem
|
||||||
|
onClick={onAddWorkspace}
|
||||||
|
data-testid="add-workspace"
|
||||||
|
style={{
|
||||||
|
height: 'auto',
|
||||||
|
padding: '8px 12px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<StyledCreateWorkspaceCardPillContent>
|
||||||
|
<StyledCreateWorkspaceCardPillIcon>
|
||||||
|
<ImportIcon />
|
||||||
|
</StyledCreateWorkspaceCardPillIcon>
|
||||||
|
<div>
|
||||||
|
<p>{t['com.affine.workspace.local.import']()}</p>
|
||||||
|
</div>
|
||||||
|
</StyledCreateWorkspaceCardPillContent>
|
||||||
|
</MenuItem>
|
||||||
|
</StyledImportWorkspaceCardPill>
|
||||||
|
) : null}
|
||||||
|
<StyledCreateWorkspaceCardPill>
|
||||||
|
<MenuItem
|
||||||
|
style={{
|
||||||
|
height: 'auto',
|
||||||
|
padding: '8px 12px',
|
||||||
|
}}
|
||||||
|
onClick={onNewWorkspace}
|
||||||
|
data-testid="new-workspace"
|
||||||
|
>
|
||||||
|
<StyledCreateWorkspaceCardPillContent>
|
||||||
|
<StyledCreateWorkspaceCardPillIcon>
|
||||||
|
<PlusIcon />
|
||||||
|
</StyledCreateWorkspaceCardPillIcon>
|
||||||
|
<div>
|
||||||
|
<p>{t['New Workspace']()}</p>
|
||||||
|
</div>
|
||||||
|
</StyledCreateWorkspaceCardPillContent>
|
||||||
|
</MenuItem>
|
||||||
|
</StyledCreateWorkspaceCardPill>
|
||||||
</StyledModalContent>
|
</StyledModalContent>
|
||||||
</ScrollableContainer>
|
</ScrollableContainer>
|
||||||
<Footer />
|
</StyledModalBody>
|
||||||
</ModalWrapper>
|
</Popover>
|
||||||
</Modal>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -81,11 +81,33 @@ export const StyledCreateWorkspaceCardPillContainer = styled('div')(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const StyledCreateWorkspaceCardPill = styled('div')(() => {
|
export const StyledCreateWorkspaceCardPill = styled('div')(() => {
|
||||||
|
return {
|
||||||
|
borderRadius: '8px',
|
||||||
|
display: 'flex',
|
||||||
|
width: '100%',
|
||||||
|
height: '58px',
|
||||||
|
background: 'var(--affine-background-primary-color)',
|
||||||
|
border: `1px solid var(--affine-border-color)`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export const StyledSignInCardPill = styled('div')(() => {
|
||||||
|
return {
|
||||||
|
borderRadius: '8px',
|
||||||
|
display: 'flex',
|
||||||
|
width: '100%',
|
||||||
|
height: '58px',
|
||||||
|
marginBottom: '6px',
|
||||||
|
background: 'var(--affine-background-primary-color)',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export const StyledImportWorkspaceCardPill = styled('div')(() => {
|
||||||
return {
|
return {
|
||||||
borderRadius: '5px',
|
borderRadius: '5px',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
boxShadow: '0px 0px 6px 0px rgba(0, 0, 0, 0.1)',
|
width: '100%',
|
||||||
background: 'var(--affine-background-primary-color)',
|
marginBottom: '12px',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -94,7 +116,6 @@ export const StyledCreateWorkspaceCardPillContent = styled('div')(() => {
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
gap: '12px',
|
gap: '12px',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'space-between',
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -106,13 +127,30 @@ export const StyledCreateWorkspaceCardPillIcon = styled('div')(() => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
export const StyledCreateWorkspaceCardPillTextSecondary = styled('div')(() => {
|
export const StyledSignInCardPillTextCotainer = styled('div')(() => {
|
||||||
|
return {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export const StyledSignInCardPillTextSecondary = styled('div')(() => {
|
||||||
return {
|
return {
|
||||||
fontSize: '12px',
|
fontSize: '12px',
|
||||||
color: 'var(--affine-text-secondary-color)',
|
color: 'var(--affine-text-secondary-color)',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const StyledSignInCardPillTextPrimary = styled('div')(() => {
|
||||||
|
return {
|
||||||
|
fontSize: 'var(--affine-font-base)',
|
||||||
|
fontWeight: 600,
|
||||||
|
lineHeight: '24px',
|
||||||
|
maxWidth: '200px',
|
||||||
|
...textEllipsis(1),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
export const StyledModalHeaderLeft = styled('div')(() => {
|
export const StyledModalHeaderLeft = styled('div')(() => {
|
||||||
return { ...displayFlex('flex-start', 'center') };
|
return { ...displayFlex('flex-start', 'center') };
|
||||||
});
|
});
|
||||||
@@ -120,6 +158,7 @@ export const StyledModalTitle = styled('div')(() => {
|
|||||||
return {
|
return {
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
fontSize: 'var(--affine-font-h6)',
|
fontSize: 'var(--affine-font-h6)',
|
||||||
|
color: 'var(--affine-text-primary-color)',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -134,10 +173,9 @@ export const StyledHelperContainer = styled('div')(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const StyledModalContent = styled('div')({
|
export const StyledModalContent = styled('div')({
|
||||||
height: '540px',
|
|
||||||
padding: '8px 40px',
|
|
||||||
...displayFlex('space-between', 'flex-start', 'flex-start'),
|
...displayFlex('space-between', 'flex-start', 'flex-start'),
|
||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
|
flexDirection: 'column',
|
||||||
});
|
});
|
||||||
export const StyledOperationWrapper = styled('div')(() => {
|
export const StyledOperationWrapper = styled('div')(() => {
|
||||||
return {
|
return {
|
||||||
@@ -162,11 +200,22 @@ export const StyleWorkspaceAdd = styled('div')(() => {
|
|||||||
export const StyledModalHeader = styled('div')(() => {
|
export const StyledModalHeader = styled('div')(() => {
|
||||||
return {
|
return {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
marginTop: '10px',
|
|
||||||
left: 0,
|
left: 0,
|
||||||
top: 0,
|
top: 0,
|
||||||
borderRadius: '24px 24px 0 0',
|
borderRadius: '24px 24px 0 0',
|
||||||
padding: '10px 40px',
|
padding: '12px 14px',
|
||||||
...displayFlex('space-between', 'center'),
|
...displayFlex('space-between', 'center'),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const StyledModalBody = styled('div')(() => {
|
||||||
|
return {
|
||||||
|
borderRadius: '8px',
|
||||||
|
padding: '12px',
|
||||||
|
display: 'inline-flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
gap: '12px',
|
||||||
|
background: 'var(--affine-white)',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { RadioButton, RadioButtonGroup } from '@affine/component';
|
||||||
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
|
import { useAtom } from 'jotai';
|
||||||
|
|
||||||
|
import { allPageModeSelectAtom } from '../../../atoms';
|
||||||
|
|
||||||
|
export const WorkspaceModeFilterTab = () => {
|
||||||
|
const t = useAFFiNEI18N();
|
||||||
|
const [value, setMode] = useAtom(allPageModeSelectAtom);
|
||||||
|
const handleValueChange = (value: string) => {
|
||||||
|
if (value !== 'all' && value !== 'page' && value !== 'edgeless') {
|
||||||
|
throw new Error('Invalid value for page mode option');
|
||||||
|
}
|
||||||
|
setMode(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RadioButtonGroup
|
||||||
|
width={300}
|
||||||
|
defaultValue={value}
|
||||||
|
onValueChange={handleValueChange}
|
||||||
|
>
|
||||||
|
<RadioButton value="all" style={{ textTransform: 'capitalize' }}>
|
||||||
|
{t['all']()}
|
||||||
|
</RadioButton>
|
||||||
|
<RadioButton value="page">{t['Page']()}</RadioButton>
|
||||||
|
<RadioButton value="edgeless">{t['Edgeless']()}</RadioButton>
|
||||||
|
</RadioButtonGroup>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -4,7 +4,6 @@ import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-
|
|||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
import { useCurrentWorkspace } from '../../../../hooks/current/use-current-workspace';
|
|
||||||
import type { AllWorkspace } from '../../../../shared';
|
import type { AllWorkspace } from '../../../../shared';
|
||||||
import { workspaceAvatarStyle } from './index.css';
|
import { workspaceAvatarStyle } from './index.css';
|
||||||
import {
|
import {
|
||||||
@@ -28,12 +27,12 @@ export const WorkspaceSelector = ({
|
|||||||
onClick,
|
onClick,
|
||||||
}: WorkspaceSelectorProps) => {
|
}: WorkspaceSelectorProps) => {
|
||||||
const [name] = useBlockSuiteWorkspaceName(
|
const [name] = useBlockSuiteWorkspaceName(
|
||||||
currentWorkspace?.blockSuiteWorkspace
|
currentWorkspace.blockSuiteWorkspace
|
||||||
);
|
);
|
||||||
const [workspace] = useCurrentWorkspace();
|
|
||||||
|
|
||||||
// Open dialog when `Enter` or `Space` pressed
|
// Open dialog when `Enter` or `Space` pressed
|
||||||
// TODO-Doma Refactor with `@radix-ui/react-dialog` or other libraries that handle these out of the box and be accessible by default
|
// TODO-Doma Refactor with `@radix-ui/react-dialog` or other libraries that handle these out of the box and be accessible by default
|
||||||
|
// TODO: Delete this?
|
||||||
const handleKeyDown = useCallback(
|
const handleKeyDown = useCallback(
|
||||||
(e: React.KeyboardEvent) => {
|
(e: React.KeyboardEvent) => {
|
||||||
if (e.key === 'Enter' || e.key === ' ') {
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
@@ -52,27 +51,26 @@ export const WorkspaceSelector = ({
|
|||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
data-testid="current-workspace"
|
data-testid="current-workspace"
|
||||||
|
id="current-workspace"
|
||||||
>
|
>
|
||||||
<WorkspaceAvatar
|
<WorkspaceAvatar
|
||||||
data-testid="workspace-avatar"
|
data-testid="workspace-avatar"
|
||||||
className={workspaceAvatarStyle}
|
className={workspaceAvatarStyle}
|
||||||
size={40}
|
size={40}
|
||||||
workspace={currentWorkspace?.blockSuiteWorkspace ?? null}
|
workspace={currentWorkspace.blockSuiteWorkspace}
|
||||||
/>
|
/>
|
||||||
<StyledSelectorWrapper>
|
<StyledSelectorWrapper>
|
||||||
<StyledWorkspaceName data-testid="workspace-name">
|
<StyledWorkspaceName data-testid="workspace-name">
|
||||||
{name}
|
{name}
|
||||||
</StyledWorkspaceName>
|
</StyledWorkspaceName>
|
||||||
{workspace && (
|
<StyledWorkspaceStatus>
|
||||||
<StyledWorkspaceStatus>
|
{currentWorkspace.flavour === 'local' ? (
|
||||||
{workspace.flavour === 'local' ? (
|
<LocalWorkspaceIcon />
|
||||||
<LocalWorkspaceIcon />
|
) : (
|
||||||
) : (
|
<CloudWorkspaceIcon />
|
||||||
<CloudWorkspaceIcon />
|
)}
|
||||||
)}
|
{currentWorkspace.flavour === 'local' ? 'Local' : 'AFFiNE Cloud'}
|
||||||
{workspace.flavour === 'local' ? 'Local' : 'AFFiNE Cloud'}
|
</StyledWorkspaceStatus>
|
||||||
</StyledWorkspaceStatus>
|
|
||||||
)}
|
|
||||||
</StyledSelectorWrapper>
|
</StyledSelectorWrapper>
|
||||||
</StyledSelectorContainer>
|
</StyledSelectorContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -257,6 +257,7 @@ export const CollectionsList = ({ workspace }: CollectionsListProps) => {
|
|||||||
() => savedCollections.filter(v => v.pinned),
|
() => savedCollections.filter(v => v.pinned),
|
||||||
[savedCollections]
|
[savedCollections]
|
||||||
);
|
);
|
||||||
|
const t = useAFFiNEI18N();
|
||||||
if (pinedCollections.length === 0) {
|
if (pinedCollections.length === 0) {
|
||||||
return (
|
return (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
@@ -264,7 +265,7 @@ export const CollectionsList = ({ workspace }: CollectionsListProps) => {
|
|||||||
icon={<InformationIcon />}
|
icon={<InformationIcon />}
|
||||||
disabled
|
disabled
|
||||||
>
|
>
|
||||||
<span>Create a collection</span>
|
<span>{t['Create a collection']()}</span>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,14 +29,8 @@ export const ReferencePage = ({
|
|||||||
const icon = setting?.mode === 'edgeless' ? <EdgelessIcon /> : <PageIcon />;
|
const icon = setting?.mode === 'edgeless' ? <EdgelessIcon /> : <PageIcon />;
|
||||||
const references = useBlockSuitePageReferences(workspace, pageId);
|
const references = useBlockSuitePageReferences(workspace, pageId);
|
||||||
const referencesToShow = useMemo(() => {
|
const referencesToShow = useMemo(() => {
|
||||||
return [
|
return [...new Set(references.filter(ref => !metaMapping[ref]?.trash))];
|
||||||
...new Set(
|
}, [references, metaMapping]);
|
||||||
references.filter(
|
|
||||||
ref => !parentIds.has(ref) && !metaMapping[ref]?.trash
|
|
||||||
)
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}, [references, parentIds, metaMapping]);
|
|
||||||
const [collapsed, setCollapsed] = useState(true);
|
const [collapsed, setCollapsed] = useState(true);
|
||||||
const collapsible = referencesToShow.length > 0;
|
const collapsible = referencesToShow.length > 0;
|
||||||
const nestedItem = parentIds.size > 0;
|
const nestedItem = parentIds.size > 0;
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
import { RadioButton, RadioButtonGroup } from '@affine/component';
|
|
||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
|
||||||
import { useAtom } from 'jotai';
|
|
||||||
import type { ReactNode } from 'react';
|
|
||||||
import type React from 'react';
|
|
||||||
|
|
||||||
import { allPageModeSelectAtom } from '../../../atoms';
|
|
||||||
import type { HeaderProps } from '../../blocksuite/workspace-header/header';
|
|
||||||
import { Header } from '../../blocksuite/workspace-header/header';
|
|
||||||
import * as styles from '../../blocksuite/workspace-header/styles.css';
|
|
||||||
|
|
||||||
export interface WorkspaceTitleProps
|
|
||||||
extends React.PropsWithChildren<HeaderProps> {
|
|
||||||
icon?: ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const WorkspaceModeFilterTab = ({ ...props }: WorkspaceTitleProps) => {
|
|
||||||
const t = useAFFiNEI18N();
|
|
||||||
const [value, setMode] = useAtom(allPageModeSelectAtom);
|
|
||||||
const handleValueChange = (value: string) => {
|
|
||||||
if (value !== 'all' && value !== 'page' && value !== 'edgeless') {
|
|
||||||
throw new Error('Invalid value for page mode option');
|
|
||||||
}
|
|
||||||
setMode(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Header {...props}>
|
|
||||||
<div className={styles.allPageListTitleWrapper}>
|
|
||||||
<RadioButtonGroup
|
|
||||||
width={300}
|
|
||||||
defaultValue={value}
|
|
||||||
onValueChange={handleValueChange}
|
|
||||||
>
|
|
||||||
<RadioButton value="all" style={{ textTransform: 'capitalize' }}>
|
|
||||||
{t['all']()}
|
|
||||||
</RadioButton>
|
|
||||||
<RadioButton value="page">{t['Page']()}</RadioButton>
|
|
||||||
<RadioButton value="edgeless">{t['Edgeless']()}</RadioButton>
|
|
||||||
</RadioButtonGroup>
|
|
||||||
</div>
|
|
||||||
</Header>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -115,7 +115,7 @@ export const RootAppSidebar = ({
|
|||||||
|
|
||||||
const [sidebarOpen, setSidebarOpen] = useAtom(appSidebarOpenAtom);
|
const [sidebarOpen, setSidebarOpen] = useAtom(appSidebarOpenAtom);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isDesktop && typeof sidebarOpen === 'boolean') {
|
if (isDesktop) {
|
||||||
window.apis?.ui.handleSidebarVisibilityChange(sidebarOpen).catch(err => {
|
window.apis?.ui.handleSidebarVisibilityChange(sidebarOpen).catch(err => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,22 +5,25 @@ import {
|
|||||||
useCollectionManager,
|
useCollectionManager,
|
||||||
} from '@affine/component/page-list';
|
} from '@affine/component/page-list';
|
||||||
import type { Collection } from '@affine/env/filter';
|
import type { Collection } from '@affine/env/filter';
|
||||||
import type { WorkspaceHeaderProps } from '@affine/env/workspace';
|
import type { PropertiesMeta } from '@affine/env/filter';
|
||||||
import { WorkspaceFlavour, WorkspaceSubPath } from '@affine/env/workspace';
|
import type {
|
||||||
import type { ReactElement } from 'react';
|
WorkspaceFlavour,
|
||||||
|
WorkspaceHeaderProps,
|
||||||
|
} from '@affine/env/workspace';
|
||||||
|
import { WorkspaceSubPath } from '@affine/env/workspace';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
import { useGetPageInfoById } from '../hooks/use-get-page-info';
|
import { useGetPageInfoById } from '../hooks/use-get-page-info';
|
||||||
import { useWorkspace } from '../hooks/use-workspace';
|
import { useWorkspace } from '../hooks/use-workspace';
|
||||||
import { BlockSuiteEditorHeader } from './blocksuite/workspace-header';
|
import { BlockSuiteHeaderTitle } from './blocksuite/block-suite-header-title';
|
||||||
import { filterContainerStyle } from './filter-container.css';
|
import { filterContainerStyle } from './filter-container.css';
|
||||||
import { WorkspaceModeFilterTab } from './pure/workspace-title';
|
import { Header } from './pure/header';
|
||||||
|
import { PluginHeader } from './pure/plugin-header';
|
||||||
|
import { WorkspaceModeFilterTab } from './pure/workspace-mode-filter-tab';
|
||||||
|
|
||||||
export function WorkspaceHeader({
|
const FilterContainer = ({ workspaceId }: { workspaceId: string }) => {
|
||||||
currentWorkspaceId,
|
const currentWorkspace = useWorkspace(workspaceId);
|
||||||
currentEntry,
|
const setting = useCollectionManager(workspaceId);
|
||||||
}: WorkspaceHeaderProps<WorkspaceFlavour>): ReactElement {
|
|
||||||
const setting = useCollectionManager(currentWorkspaceId);
|
|
||||||
const saveToCollection = useCallback(
|
const saveToCollection = useCallback(
|
||||||
async (collection: Collection) => {
|
async (collection: Collection) => {
|
||||||
await setting.saveCollection(collection);
|
await setting.saveCollection(collection);
|
||||||
@@ -28,86 +31,105 @@ export function WorkspaceHeader({
|
|||||||
},
|
},
|
||||||
[setting]
|
[setting]
|
||||||
);
|
);
|
||||||
|
const getPageInfoById = useGetPageInfoById(
|
||||||
|
currentWorkspace.blockSuiteWorkspace
|
||||||
|
);
|
||||||
|
if (!setting.isDefault || !setting.currentCollection.filterList.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={filterContainerStyle}>
|
||||||
|
<div style={{ flex: 1 }}>
|
||||||
|
<FilterList
|
||||||
|
propertiesMeta={currentWorkspace.blockSuiteWorkspace.meta.properties}
|
||||||
|
value={setting.currentCollection.filterList}
|
||||||
|
onChange={filterList => {
|
||||||
|
return setting.updateCollection({
|
||||||
|
...setting.currentCollection,
|
||||||
|
filterList,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{setting.currentCollection.filterList.length > 0 ? (
|
||||||
|
<SaveCollectionButton
|
||||||
|
propertiesMeta={
|
||||||
|
currentWorkspace.blockSuiteWorkspace.meta
|
||||||
|
.properties as PropertiesMeta
|
||||||
|
}
|
||||||
|
getPageInfo={getPageInfoById}
|
||||||
|
onConfirm={saveToCollection}
|
||||||
|
filterList={setting.currentCollection.filterList}
|
||||||
|
workspaceId={workspaceId}
|
||||||
|
></SaveCollectionButton>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export function WorkspaceHeader({
|
||||||
|
currentWorkspaceId,
|
||||||
|
currentEntry,
|
||||||
|
}: WorkspaceHeaderProps<WorkspaceFlavour>) {
|
||||||
|
const setting = useCollectionManager(currentWorkspaceId);
|
||||||
|
|
||||||
const currentWorkspace = useWorkspace(currentWorkspaceId);
|
const currentWorkspace = useWorkspace(currentWorkspaceId);
|
||||||
|
|
||||||
const getPageInfoById = useGetPageInfoById(
|
const getPageInfoById = useGetPageInfoById(
|
||||||
currentWorkspace.blockSuiteWorkspace
|
currentWorkspace.blockSuiteWorkspace
|
||||||
);
|
);
|
||||||
if ('subPath' in currentEntry) {
|
|
||||||
if (currentEntry.subPath === WorkspaceSubPath.ALL) {
|
// route in all page
|
||||||
const leftSlot = (
|
if (
|
||||||
<CollectionList
|
'subPath' in currentEntry &&
|
||||||
setting={setting}
|
currentEntry.subPath === WorkspaceSubPath.ALL
|
||||||
getPageInfo={getPageInfoById}
|
) {
|
||||||
propertiesMeta={currentWorkspace.blockSuiteWorkspace.meta.properties}
|
|
||||||
></CollectionList>
|
|
||||||
);
|
|
||||||
const filterContainer =
|
|
||||||
setting.isDefault && setting.currentCollection.filterList.length > 0 ? (
|
|
||||||
<div className={filterContainerStyle}>
|
|
||||||
<div style={{ flex: 1 }}>
|
|
||||||
<FilterList
|
|
||||||
propertiesMeta={
|
|
||||||
currentWorkspace.blockSuiteWorkspace.meta.properties
|
|
||||||
}
|
|
||||||
value={setting.currentCollection.filterList}
|
|
||||||
onChange={filterList => {
|
|
||||||
return setting.updateCollection({
|
|
||||||
...setting.currentCollection,
|
|
||||||
filterList,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{setting.currentCollection.filterList.length > 0 ? (
|
|
||||||
<SaveCollectionButton
|
|
||||||
propertiesMeta={
|
|
||||||
currentWorkspace.blockSuiteWorkspace.meta.properties
|
|
||||||
}
|
|
||||||
getPageInfo={getPageInfoById}
|
|
||||||
onConfirm={saveToCollection}
|
|
||||||
filterList={setting.currentCollection.filterList}
|
|
||||||
workspaceId={currentWorkspaceId}
|
|
||||||
></SaveCollectionButton>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : null;
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<WorkspaceModeFilterTab
|
|
||||||
workspace={currentWorkspace}
|
|
||||||
currentPage={null}
|
|
||||||
isPublic={false}
|
|
||||||
leftSlot={leftSlot}
|
|
||||||
/>
|
|
||||||
{filterContainer}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
} else if (
|
|
||||||
currentEntry.subPath === WorkspaceSubPath.SHARED ||
|
|
||||||
currentEntry.subPath === WorkspaceSubPath.TRASH
|
|
||||||
) {
|
|
||||||
return (
|
|
||||||
<WorkspaceModeFilterTab
|
|
||||||
workspace={currentWorkspace}
|
|
||||||
currentPage={null}
|
|
||||||
isPublic={false}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else if ('pageId' in currentEntry) {
|
|
||||||
const pageId = currentEntry.pageId;
|
|
||||||
const isPublic = currentWorkspace.flavour === WorkspaceFlavour.PUBLIC;
|
|
||||||
return (
|
return (
|
||||||
<BlockSuiteEditorHeader
|
<>
|
||||||
isPublic={isPublic}
|
<Header
|
||||||
workspace={currentWorkspace}
|
left={
|
||||||
currentPage={currentWorkspace.blockSuiteWorkspace.getPage(pageId)}
|
<CollectionList
|
||||||
|
setting={setting}
|
||||||
|
getPageInfo={getPageInfoById}
|
||||||
|
propertiesMeta={
|
||||||
|
currentWorkspace.blockSuiteWorkspace.meta.properties
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
center={<WorkspaceModeFilterTab />}
|
||||||
|
right={<PluginHeader />}
|
||||||
|
/>
|
||||||
|
{<FilterContainer workspaceId={currentWorkspaceId} />}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// route in shared or trash
|
||||||
|
if (
|
||||||
|
'subPath' in currentEntry &&
|
||||||
|
(currentEntry.subPath === WorkspaceSubPath.SHARED ||
|
||||||
|
currentEntry.subPath === WorkspaceSubPath.TRASH)
|
||||||
|
) {
|
||||||
|
return <Header center={<WorkspaceModeFilterTab />} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
// route in edit page
|
||||||
|
if ('pageId' in currentEntry) {
|
||||||
|
return (
|
||||||
|
<Header
|
||||||
|
center={
|
||||||
|
<BlockSuiteHeaderTitle
|
||||||
|
workspace={currentWorkspace}
|
||||||
|
pageId={currentEntry.pageId}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
right={<PluginHeader />}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return <></>;
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,4 @@
|
|||||||
import type { WorkspaceSubPath } from '@affine/env/workspace';
|
import type { WorkspaceSubPath } from '@affine/env/workspace';
|
||||||
import {
|
|
||||||
currentPageIdAtom,
|
|
||||||
currentWorkspaceIdAtom,
|
|
||||||
} from '@toeverything/infra/atom';
|
|
||||||
import { useSetAtom } from 'jotai';
|
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
||||||
import { useLocation, useNavigate } from 'react-router-dom';
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
@@ -16,8 +11,6 @@ export enum RouteLogic {
|
|||||||
export function useNavigateHelper() {
|
export function useNavigateHelper() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const setWorkspaceId = useSetAtom(currentWorkspaceIdAtom);
|
|
||||||
const setCurrentPageId = useSetAtom(currentPageIdAtom);
|
|
||||||
|
|
||||||
const jumpToPage = useCallback(
|
const jumpToPage = useCallback(
|
||||||
(
|
(
|
||||||
@@ -25,13 +18,11 @@ export function useNavigateHelper() {
|
|||||||
pageId: string,
|
pageId: string,
|
||||||
logic: RouteLogic = RouteLogic.PUSH
|
logic: RouteLogic = RouteLogic.PUSH
|
||||||
) => {
|
) => {
|
||||||
setWorkspaceId(workspaceId);
|
|
||||||
setCurrentPageId(pageId);
|
|
||||||
return navigate(`/workspace/${workspaceId}/${pageId}`, {
|
return navigate(`/workspace/${workspaceId}/${pageId}`, {
|
||||||
replace: logic === RouteLogic.REPLACE,
|
replace: logic === RouteLogic.REPLACE,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[navigate, setCurrentPageId, setWorkspaceId]
|
[navigate]
|
||||||
);
|
);
|
||||||
const jumpToPublicWorkspacePage = useCallback(
|
const jumpToPublicWorkspacePage = useCallback(
|
||||||
(
|
(
|
||||||
@@ -39,13 +30,11 @@ export function useNavigateHelper() {
|
|||||||
pageId: string,
|
pageId: string,
|
||||||
logic: RouteLogic = RouteLogic.PUSH
|
logic: RouteLogic = RouteLogic.PUSH
|
||||||
) => {
|
) => {
|
||||||
setWorkspaceId(workspaceId);
|
|
||||||
setCurrentPageId(pageId);
|
|
||||||
return navigate(`/public-workspace/${workspaceId}/${pageId}`, {
|
return navigate(`/public-workspace/${workspaceId}/${pageId}`, {
|
||||||
replace: logic === RouteLogic.REPLACE,
|
replace: logic === RouteLogic.REPLACE,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[navigate, setCurrentPageId, setWorkspaceId]
|
[navigate]
|
||||||
);
|
);
|
||||||
const jumpToSubPath = useCallback(
|
const jumpToSubPath = useCallback(
|
||||||
(
|
(
|
||||||
@@ -53,18 +42,14 @@ export function useNavigateHelper() {
|
|||||||
subPath: WorkspaceSubPath,
|
subPath: WorkspaceSubPath,
|
||||||
logic: RouteLogic = RouteLogic.PUSH
|
logic: RouteLogic = RouteLogic.PUSH
|
||||||
) => {
|
) => {
|
||||||
setWorkspaceId(workspaceId);
|
|
||||||
setCurrentPageId(null);
|
|
||||||
return navigate(`/workspace/${workspaceId}/${subPath}`, {
|
return navigate(`/workspace/${workspaceId}/${subPath}`, {
|
||||||
replace: logic === RouteLogic.REPLACE,
|
replace: logic === RouteLogic.REPLACE,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[navigate, setCurrentPageId, setWorkspaceId]
|
[navigate]
|
||||||
);
|
);
|
||||||
const openPage = useCallback(
|
const openPage = useCallback(
|
||||||
(workspaceId: string, pageId: string) => {
|
(workspaceId: string, pageId: string) => {
|
||||||
setWorkspaceId(workspaceId);
|
|
||||||
setCurrentPageId(pageId);
|
|
||||||
const isPublicWorkspace =
|
const isPublicWorkspace =
|
||||||
location.pathname.indexOf('/public-workspace') === 0;
|
location.pathname.indexOf('/public-workspace') === 0;
|
||||||
if (isPublicWorkspace) {
|
if (isPublicWorkspace) {
|
||||||
@@ -73,35 +58,25 @@ export function useNavigateHelper() {
|
|||||||
return jumpToPage(workspaceId, pageId);
|
return jumpToPage(workspaceId, pageId);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[jumpToPage, jumpToPublicWorkspacePage, location.pathname]
|
||||||
jumpToPage,
|
|
||||||
jumpToPublicWorkspacePage,
|
|
||||||
location.pathname,
|
|
||||||
setCurrentPageId,
|
|
||||||
setWorkspaceId,
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const jumpToIndex = useCallback(
|
const jumpToIndex = useCallback(
|
||||||
(logic: RouteLogic = RouteLogic.PUSH) => {
|
(logic: RouteLogic = RouteLogic.PUSH) => {
|
||||||
setWorkspaceId(null);
|
|
||||||
setCurrentPageId(null);
|
|
||||||
return navigate('/', {
|
return navigate('/', {
|
||||||
replace: logic === RouteLogic.REPLACE,
|
replace: logic === RouteLogic.REPLACE,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[navigate, setCurrentPageId, setWorkspaceId]
|
[navigate]
|
||||||
);
|
);
|
||||||
|
|
||||||
const jumpTo404 = useCallback(
|
const jumpTo404 = useCallback(
|
||||||
(logic: RouteLogic = RouteLogic.PUSH) => {
|
(logic: RouteLogic = RouteLogic.PUSH) => {
|
||||||
setWorkspaceId(null);
|
|
||||||
setCurrentPageId(null);
|
|
||||||
return navigate('/404', {
|
return navigate('/404', {
|
||||||
replace: logic === RouteLogic.REPLACE,
|
replace: logic === RouteLogic.REPLACE,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[navigate, setCurrentPageId, setWorkspaceId]
|
[navigate]
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import { DebugLogger } from '@affine/debug';
|
import { DebugLogger } from '@affine/debug';
|
||||||
import { initEmptyPage, initPageWithPreloading } from '@affine/env/blocksuite';
|
|
||||||
import { DEFAULT_HELLO_WORLD_PAGE_ID_SUFFIX } from '@affine/env/constant';
|
|
||||||
import { WorkspaceFlavour, WorkspaceVersion } from '@affine/env/workspace';
|
import { WorkspaceFlavour, WorkspaceVersion } from '@affine/env/workspace';
|
||||||
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
||||||
import { saveWorkspaceToLocalStorage } from '@affine/workspace/local/crud';
|
import { saveWorkspaceToLocalStorage } from '@affine/workspace/local/crud';
|
||||||
import { getOrCreateWorkspace } from '@affine/workspace/manager';
|
import { getOrCreateWorkspace } from '@affine/workspace/manager';
|
||||||
import { assertEquals } from '@blocksuite/global/utils';
|
|
||||||
import { nanoid } from '@blocksuite/store';
|
import { nanoid } from '@blocksuite/store';
|
||||||
import { getWorkspace } from '@toeverything/infra/__internal__/workspace';
|
import { getWorkspace } from '@toeverything/infra/__internal__/workspace';
|
||||||
|
import { rootStore } from '@toeverything/infra/atom';
|
||||||
|
import { buildShowcaseWorkspace } from '@toeverything/infra/blocksuite';
|
||||||
import { useAtomValue, useSetAtom } from 'jotai';
|
import { useAtomValue, useSetAtom } from 'jotai';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
import { LocalAdapter } from '../adapters/local';
|
import { LocalAdapter } from '../adapters/local';
|
||||||
import { WorkspaceAdapters } from '../adapters/workspace';
|
import { WorkspaceAdapters } from '../adapters/workspace';
|
||||||
|
import { setPageModeAtom } from '../atoms';
|
||||||
|
|
||||||
const logger = new DebugLogger('use-workspaces');
|
const logger = new DebugLogger('use-workspaces');
|
||||||
|
|
||||||
@@ -54,22 +54,11 @@ export function useAppHelper() {
|
|||||||
id,
|
id,
|
||||||
WorkspaceFlavour.LOCAL
|
WorkspaceFlavour.LOCAL
|
||||||
);
|
);
|
||||||
const pageId = `${blockSuiteWorkspace.id}-${DEFAULT_HELLO_WORLD_PAGE_ID_SUFFIX}`;
|
await buildShowcaseWorkspace(blockSuiteWorkspace, {
|
||||||
const page = blockSuiteWorkspace.createPage({
|
store: rootStore,
|
||||||
id: pageId,
|
atoms: {
|
||||||
});
|
pageMode: setPageModeAtom,
|
||||||
assertEquals(page.id, pageId);
|
},
|
||||||
if (runtimeConfig.enablePreloading) {
|
|
||||||
await initPageWithPreloading(page).catch(error => {
|
|
||||||
console.error('import error:', error);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await initEmptyPage(page).catch(error => {
|
|
||||||
console.error('init empty page error', error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
blockSuiteWorkspace.setPageMeta(page.id, {
|
|
||||||
jumpOnce: true,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
set(workspaces => [
|
set(workspaces => [
|
||||||
|
|||||||
@@ -89,15 +89,6 @@ export const QuickSearch = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
declare global {
|
|
||||||
// eslint-disable-next-line no-var
|
|
||||||
var HALTING_PROBLEM_TIMEOUT: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (globalThis.HALTING_PROBLEM_TIMEOUT === undefined) {
|
|
||||||
globalThis.HALTING_PROBLEM_TIMEOUT = 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
const showList: IslandItemNames[] = environment.isDesktop
|
const showList: IslandItemNames[] = environment.isDesktop
|
||||||
? ['whatNew', 'contact', 'guide']
|
? ['whatNew', 'contact', 'guide']
|
||||||
: ['whatNew', 'contact'];
|
: ['whatNew', 'contact'];
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { DebugLogger } from '@affine/debug';
|
import { DebugLogger } from '@affine/debug';
|
||||||
|
import { DEFAULT_HELLO_WORLD_PAGE_ID_SUFFIX } from '@affine/env/constant';
|
||||||
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
||||||
import { getWorkspace } from '@toeverything/infra/__internal__/workspace';
|
import { getWorkspace } from '@toeverything/infra/__internal__/workspace';
|
||||||
import { rootStore } from '@toeverything/infra/atom';
|
import { rootStore } from '@toeverything/infra/atom';
|
||||||
@@ -24,10 +25,20 @@ export const loader: LoaderFunction = async () => {
|
|||||||
const nonTrashPages = targetWorkspace.meta.pageMetas.filter(
|
const nonTrashPages = targetWorkspace.meta.pageMetas.filter(
|
||||||
({ trash }) => !trash
|
({ trash }) => !trash
|
||||||
);
|
);
|
||||||
|
const helloWorldPage = nonTrashPages.find(
|
||||||
|
({ id, jumpOnce }) =>
|
||||||
|
id.endsWith(DEFAULT_HELLO_WORLD_PAGE_ID_SUFFIX) && jumpOnce
|
||||||
|
)?.id;
|
||||||
const pageId =
|
const pageId =
|
||||||
nonTrashPages.find(({ id }) => id === lastPageId)?.id ??
|
nonTrashPages.find(({ id }) => id === lastPageId)?.id ??
|
||||||
nonTrashPages.at(0)?.id;
|
nonTrashPages.at(0)?.id;
|
||||||
if (pageId) {
|
if (helloWorldPage) {
|
||||||
|
logger.debug(
|
||||||
|
'Found target workspace. Jump to hello world page',
|
||||||
|
helloWorldPage
|
||||||
|
);
|
||||||
|
return redirect(`/workspace/${targetWorkspace.id}/${helloWorldPage}`);
|
||||||
|
} else if (pageId) {
|
||||||
logger.debug('Found target workspace. Jump to page', pageId);
|
logger.debug('Found target workspace. Jump to page', pageId);
|
||||||
return redirect(`/workspace/${targetWorkspace.id}/${pageId}`);
|
return redirect(`/workspace/${targetWorkspace.id}/${pageId}`);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -3,9 +3,8 @@ import { DEFAULT_HELLO_WORLD_PAGE_ID_SUFFIX } from '@affine/env/constant';
|
|||||||
import { WorkspaceSubPath } from '@affine/env/workspace';
|
import { WorkspaceSubPath } from '@affine/env/workspace';
|
||||||
import { assertExists } from '@blocksuite/global/utils';
|
import { assertExists } from '@blocksuite/global/utils';
|
||||||
import { getActiveBlockSuiteWorkspaceAtom } from '@toeverything/infra/__internal__/workspace';
|
import { getActiveBlockSuiteWorkspaceAtom } from '@toeverything/infra/__internal__/workspace';
|
||||||
import { currentPageIdAtom, rootStore } from '@toeverything/infra/atom';
|
import { rootStore } from '@toeverything/infra/atom';
|
||||||
import { useAtom } from 'jotai/react';
|
import { useCallback } from 'react';
|
||||||
import { useCallback, useEffect } from 'react';
|
|
||||||
import type { LoaderFunction } from 'react-router-dom';
|
import type { LoaderFunction } from 'react-router-dom';
|
||||||
import { redirect } from 'react-router-dom';
|
import { redirect } from 'react-router-dom';
|
||||||
|
|
||||||
@@ -18,21 +17,22 @@ export const loader: LoaderFunction = async args => {
|
|||||||
assertExists(workspaceId);
|
assertExists(workspaceId);
|
||||||
const workspaceAtom = getActiveBlockSuiteWorkspaceAtom(workspaceId);
|
const workspaceAtom = getActiveBlockSuiteWorkspaceAtom(workspaceId);
|
||||||
const workspace = await rootStore.get(workspaceAtom);
|
const workspace = await rootStore.get(workspaceAtom);
|
||||||
const page = workspace.getPage(
|
for (const pageId of workspace.pages.keys()) {
|
||||||
`${workspace.id}-${DEFAULT_HELLO_WORLD_PAGE_ID_SUFFIX}`
|
if (pageId.endsWith(DEFAULT_HELLO_WORLD_PAGE_ID_SUFFIX)) {
|
||||||
);
|
const page = workspace.getPage(pageId);
|
||||||
if (page && page.meta.jumpOnce) {
|
if (page && page.meta.jumpOnce) {
|
||||||
workspace.meta.setPageMeta(page.id, {
|
workspace.meta.setPageMeta(page.id, {
|
||||||
jumpOnce: false,
|
jumpOnce: false,
|
||||||
});
|
});
|
||||||
return redirect(`/workspace/${workspace.id}/${page.id}`);
|
return redirect(`/workspace/${workspace.id}/${page.id}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AllPage = () => {
|
export const AllPage = () => {
|
||||||
const { jumpToPage } = useNavigateHelper();
|
const { jumpToPage } = useNavigateHelper();
|
||||||
const [currentPageId, setCurrentPageId] = useAtom(currentPageIdAtom);
|
|
||||||
const [currentWorkspace] = useCurrentWorkspace();
|
const [currentWorkspace] = useCurrentWorkspace();
|
||||||
const setting = useCollectionManager(currentWorkspace.id);
|
const setting = useCollectionManager(currentWorkspace.id);
|
||||||
const onClickPage = useCallback(
|
const onClickPage = useCallback(
|
||||||
@@ -46,18 +46,6 @@ export const AllPage = () => {
|
|||||||
},
|
},
|
||||||
[currentWorkspace, jumpToPage]
|
[currentWorkspace, jumpToPage]
|
||||||
);
|
);
|
||||||
useEffect(() => {
|
|
||||||
const page = currentWorkspace.blockSuiteWorkspace.getPage(
|
|
||||||
`${currentWorkspace.blockSuiteWorkspace.id}-${DEFAULT_HELLO_WORLD_PAGE_ID_SUFFIX}`
|
|
||||||
);
|
|
||||||
if (page && page.meta.jumpOnce) {
|
|
||||||
currentWorkspace.blockSuiteWorkspace.meta.setPageMeta(page.id, {
|
|
||||||
jumpOnce: false,
|
|
||||||
});
|
|
||||||
setCurrentPageId(currentPageId);
|
|
||||||
jumpToPage(currentWorkspace.id, page.id);
|
|
||||||
}
|
|
||||||
}, [currentPageId, currentWorkspace, jumpToPage, setCurrentPageId]);
|
|
||||||
const { PageList, Header } = getUIAdapter(currentWorkspace.flavour);
|
const { PageList, Header } = getUIAdapter(currentWorkspace.flavour);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -7,12 +7,16 @@ import { WorkspaceSubPath } from '@affine/env/workspace';
|
|||||||
import type { EditorContainer } from '@blocksuite/editor';
|
import type { EditorContainer } from '@blocksuite/editor';
|
||||||
import { assertExists } from '@blocksuite/global/utils';
|
import { assertExists } from '@blocksuite/global/utils';
|
||||||
import type { Page } from '@blocksuite/store';
|
import type { Page } from '@blocksuite/store';
|
||||||
import { currentPageIdAtom, rootStore } from '@toeverything/infra/atom';
|
import {
|
||||||
|
currentPageIdAtom,
|
||||||
|
currentWorkspaceAtom,
|
||||||
|
currentWorkspaceIdAtom,
|
||||||
|
rootStore,
|
||||||
|
} from '@toeverything/infra/atom';
|
||||||
import { useAtomValue } from 'jotai';
|
import { useAtomValue } from 'jotai';
|
||||||
import { useAtom } from 'jotai/react';
|
import { type ReactElement, useCallback } from 'react';
|
||||||
import { type ReactElement, useCallback, useEffect } from 'react';
|
|
||||||
import type { LoaderFunction } from 'react-router-dom';
|
import type { LoaderFunction } from 'react-router-dom';
|
||||||
import { useLocation, useParams } from 'react-router-dom';
|
import { redirect } from 'react-router-dom';
|
||||||
|
|
||||||
import { getUIAdapter } from '../../adapters/workspace';
|
import { getUIAdapter } from '../../adapters/workspace';
|
||||||
import { useCurrentWorkspace } from '../../hooks/current/use-current-workspace';
|
import { useCurrentWorkspace } from '../../hooks/current/use-current-workspace';
|
||||||
@@ -32,7 +36,7 @@ const DetailPageImpl = (): ReactElement => {
|
|||||||
return openPage(blockSuiteWorkspace.id, pageId);
|
return openPage(blockSuiteWorkspace.id, pageId);
|
||||||
});
|
});
|
||||||
const disposeTagClick = editor.slots.tagClicked.on(async ({ tagId }) => {
|
const disposeTagClick = editor.slots.tagClicked.on(async ({ tagId }) => {
|
||||||
await jumpToSubPath(currentWorkspace.id, WorkspaceSubPath.ALL);
|
jumpToSubPath(currentWorkspace.id, WorkspaceSubPath.ALL);
|
||||||
collectionManager.backToAll();
|
collectionManager.backToAll();
|
||||||
collectionManager.setTemporaryFilter([createTagFilter(tagId)]);
|
collectionManager.setTemporaryFilter([createTagFilter(tagId)]);
|
||||||
});
|
});
|
||||||
@@ -69,61 +73,39 @@ const DetailPageImpl = (): ReactElement => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const DetailPage = (): ReactElement => {
|
export const DetailPage = (): ReactElement => {
|
||||||
const { workspaceId, pageId } = useParams();
|
|
||||||
const location = useLocation();
|
|
||||||
const { jumpTo404 } = useNavigateHelper();
|
|
||||||
const [currentWorkspace] = useCurrentWorkspace();
|
const [currentWorkspace] = useCurrentWorkspace();
|
||||||
const [currentPageId, setCurrentPageId] = useAtom(currentPageIdAtom);
|
const currentPageId = useAtomValue(currentPageIdAtom);
|
||||||
const page = currentPageId
|
const page = currentPageId
|
||||||
? currentWorkspace.blockSuiteWorkspace.getPage(currentPageId)
|
? currentWorkspace.blockSuiteWorkspace.getPage(currentPageId)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
//#region check if page is valid
|
|
||||||
useEffect(() => {
|
|
||||||
// if the workspace changed, ignore the page check
|
|
||||||
if (currentWorkspace.id !== workspaceId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (typeof pageId === 'string' && currentPageId) {
|
|
||||||
if (currentPageId !== pageId) {
|
|
||||||
setCurrentPageId(pageId);
|
|
||||||
} else {
|
|
||||||
const page =
|
|
||||||
currentWorkspace.blockSuiteWorkspace.getPage(currentPageId);
|
|
||||||
if (!page) {
|
|
||||||
jumpTo404();
|
|
||||||
} else {
|
|
||||||
// fixme: cleanup jumpOnce in the right time
|
|
||||||
if (page.meta.jumpOnce) {
|
|
||||||
currentWorkspace.blockSuiteWorkspace.setPageMeta(currentPageId, {
|
|
||||||
jumpOnce: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
currentPageId,
|
|
||||||
currentWorkspace.blockSuiteWorkspace,
|
|
||||||
currentWorkspace.id,
|
|
||||||
jumpTo404,
|
|
||||||
location.pathname,
|
|
||||||
pageId,
|
|
||||||
setCurrentPageId,
|
|
||||||
workspaceId,
|
|
||||||
]);
|
|
||||||
//#endregion
|
|
||||||
|
|
||||||
if (!currentPageId || !page) {
|
if (!currentPageId || !page) {
|
||||||
return <PageDetailSkeleton key="current-page-is-null" />;
|
return <PageDetailSkeleton key="current-page-is-null" />;
|
||||||
}
|
}
|
||||||
return <DetailPageImpl />;
|
return <DetailPageImpl />;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const loader: LoaderFunction = args => {
|
export const loader: LoaderFunction = async args => {
|
||||||
|
if (args.params.workspaceId) {
|
||||||
|
localStorage.setItem('last_workspace_id', args.params.workspaceId);
|
||||||
|
rootStore.set(currentWorkspaceIdAtom, args.params.workspaceId);
|
||||||
|
}
|
||||||
if (args.params.pageId) {
|
if (args.params.pageId) {
|
||||||
localStorage.setItem('last_page_id', args.params.pageId);
|
const pageId = args.params.pageId;
|
||||||
rootStore.set(currentPageIdAtom, args.params.pageId);
|
localStorage.setItem('last_page_id', pageId);
|
||||||
|
const currentWorkspace = await rootStore.get(currentWorkspaceAtom);
|
||||||
|
const page = currentWorkspace.getPage(pageId);
|
||||||
|
if (!page) {
|
||||||
|
return redirect('/404');
|
||||||
|
}
|
||||||
|
if (page.meta.jumpOnce) {
|
||||||
|
currentWorkspace.setPageMeta(page.id, {
|
||||||
|
jumpOnce: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
rootStore.set(currentPageIdAtom, pageId);
|
||||||
|
} else {
|
||||||
|
return redirect('/404');
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
||||||
import { currentWorkspaceIdAtom, rootStore } from '@toeverything/infra/atom';
|
import {
|
||||||
|
currentPageIdAtom,
|
||||||
|
currentWorkspaceIdAtom,
|
||||||
|
rootStore,
|
||||||
|
} from '@toeverything/infra/atom';
|
||||||
import type { ReactElement } from 'react';
|
import type { ReactElement } from 'react';
|
||||||
import { type LoaderFunction, Outlet, redirect } from 'react-router-dom';
|
import { type LoaderFunction, Outlet, redirect } from 'react-router-dom';
|
||||||
|
|
||||||
@@ -14,6 +18,9 @@ export const loader: LoaderFunction = async args => {
|
|||||||
localStorage.setItem('last_workspace_id', args.params.workspaceId);
|
localStorage.setItem('last_workspace_id', args.params.workspaceId);
|
||||||
rootStore.set(currentWorkspaceIdAtom, args.params.workspaceId);
|
rootStore.set(currentWorkspaceIdAtom, args.params.workspaceId);
|
||||||
}
|
}
|
||||||
|
if (!args.params.pageId) {
|
||||||
|
rootStore.set(currentPageIdAtom, null);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
11
apps/core/src/polyfill/intl-segmenter.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
if (Intl.Segmenter === undefined) {
|
||||||
|
await import('intl-segmenter-polyfill-rs').then(({ Segmenter }) => {
|
||||||
|
Object.defineProperty(Intl, 'Segmenter', {
|
||||||
|
value: Segmenter,
|
||||||
|
configurable: true,
|
||||||
|
writable: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export {};
|
||||||
@@ -126,6 +126,7 @@ export const AllWorkspaceModals = (): ReactElement => {
|
|||||||
);
|
);
|
||||||
const setCurrentPageId = useSetAtom(currentPageIdAtom);
|
const setCurrentPageId = useSetAtom(currentPageIdAtom);
|
||||||
const [isPending, startTransition] = useTransition();
|
const [isPending, startTransition] = useTransition();
|
||||||
|
const [, startCloseTransition] = useTransition();
|
||||||
const [, setOpenSettingModalAtom] = useAtom(openSettingModalAtom);
|
const [, setOpenSettingModalAtom] = useAtom(openSettingModalAtom);
|
||||||
|
|
||||||
const handleOpenSettingModal = useCallback(
|
const handleOpenSettingModal = useCallback(
|
||||||
@@ -153,7 +154,9 @@ export const AllWorkspaceModals = (): ReactElement => {
|
|||||||
isOpenCreateWorkspaceModal === false
|
isOpenCreateWorkspaceModal === false
|
||||||
}
|
}
|
||||||
onClose={useCallback(() => {
|
onClose={useCallback(() => {
|
||||||
setOpenWorkspacesModal(false);
|
startCloseTransition(() => {
|
||||||
|
setOpenWorkspacesModal(false);
|
||||||
|
});
|
||||||
}, [setOpenWorkspacesModal])}
|
}, [setOpenWorkspacesModal])}
|
||||||
onMoveWorkspace={useCallback(
|
onMoveWorkspace={useCallback(
|
||||||
(activeId, overId) => {
|
(activeId, overId) => {
|
||||||
@@ -169,10 +172,12 @@ export const AllWorkspaceModals = (): ReactElement => {
|
|||||||
)}
|
)}
|
||||||
onClickWorkspace={useCallback(
|
onClickWorkspace={useCallback(
|
||||||
workspaceId => {
|
workspaceId => {
|
||||||
setOpenWorkspacesModal(false);
|
startCloseTransition(() => {
|
||||||
setCurrentWorkspaceId(workspaceId);
|
setOpenWorkspacesModal(false);
|
||||||
setCurrentPageId(null);
|
setCurrentWorkspaceId(workspaceId);
|
||||||
jumpToSubPath(workspaceId, WorkspaceSubPath.ALL);
|
setCurrentPageId(null);
|
||||||
|
jumpToSubPath(workspaceId, WorkspaceSubPath.ALL);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
jumpToSubPath,
|
jumpToSubPath,
|
||||||
|
|||||||
@@ -1,37 +1,41 @@
|
|||||||
|
import type { RouteObject } from 'react-router-dom';
|
||||||
import { createBrowserRouter } from 'react-router-dom';
|
import { createBrowserRouter } from 'react-router-dom';
|
||||||
|
|
||||||
export const router = createBrowserRouter(
|
export const routes = [
|
||||||
[
|
|
||||||
{
|
|
||||||
path: '/',
|
|
||||||
lazy: () => import('./pages/index'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/workspace/:workspaceId',
|
|
||||||
lazy: () => import('./pages/workspace/index'),
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: 'all',
|
|
||||||
lazy: () => import('./pages/workspace/all-page'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'trash',
|
|
||||||
lazy: () => import('./pages/workspace/trash-page'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: ':pageId',
|
|
||||||
lazy: () => import('./pages/workspace/detail-page'),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/404',
|
|
||||||
lazy: () => import('./pages/404'),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
{
|
{
|
||||||
future: {
|
path: '/',
|
||||||
v7_normalizeFormMethod: true,
|
lazy: () => import('./pages/index'),
|
||||||
},
|
},
|
||||||
}
|
{
|
||||||
);
|
path: '/workspace/:workspaceId',
|
||||||
|
lazy: () => import('./pages/workspace/index'),
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'all',
|
||||||
|
lazy: () => import('./pages/workspace/all-page'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'trash',
|
||||||
|
lazy: () => import('./pages/workspace/trash-page'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: ':pageId',
|
||||||
|
lazy: () => import('./pages/workspace/detail-page'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/404',
|
||||||
|
lazy: () => import('./pages/404'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '*',
|
||||||
|
lazy: () => import('./pages/404'),
|
||||||
|
},
|
||||||
|
] satisfies [RouteObject, ...RouteObject[]];
|
||||||
|
|
||||||
|
export const router = createBrowserRouter(routes, {
|
||||||
|
future: {
|
||||||
|
v7_normalizeFormMethod: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ describe('GraphQL wrapper for SWR', () => {
|
|||||||
const el = await renderer.findByText('number: 1');
|
const el = await renderer.findByText('number: 1');
|
||||||
expect(el).toMatchInlineSnapshot(`
|
expect(el).toMatchInlineSnapshot(`
|
||||||
<div>
|
<div>
|
||||||
number:
|
number:${' '}
|
||||||
1
|
1
|
||||||
</div>
|
</div>
|
||||||
`);
|
`);
|
||||||
@@ -107,14 +107,7 @@ describe('GraphQL wrapper for SWR', () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<button
|
<button onClick={() => trigger()}>click</button>
|
||||||
onClick={() =>
|
|
||||||
// @ts-expect-error forgive the fake variables
|
|
||||||
trigger()
|
|
||||||
}
|
|
||||||
>
|
|
||||||
click
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ export function useMutation<Mutation extends GraphQLQuery>(
|
|||||||
): SWRMutationResponse<
|
): SWRMutationResponse<
|
||||||
QueryResponse<Mutation>,
|
QueryResponse<Mutation>,
|
||||||
GraphQLError | GraphQLError[],
|
GraphQLError | GraphQLError[],
|
||||||
|
string,
|
||||||
QueryVariables<Mutation>
|
QueryVariables<Mutation>
|
||||||
>;
|
>;
|
||||||
export function useMutation<Mutation extends GraphQLQuery>(
|
export function useMutation<Mutation extends GraphQLQuery>(
|
||||||
@@ -87,6 +88,7 @@ export function useMutation<Mutation extends GraphQLQuery>(
|
|||||||
SWRMutationConfiguration<
|
SWRMutationConfiguration<
|
||||||
QueryResponse<Mutation>,
|
QueryResponse<Mutation>,
|
||||||
GraphQLError | GraphQLError[],
|
GraphQLError | GraphQLError[],
|
||||||
|
string,
|
||||||
QueryVariables<Mutation>
|
QueryVariables<Mutation>
|
||||||
>,
|
>,
|
||||||
'fetcher'
|
'fetcher'
|
||||||
@@ -94,6 +96,7 @@ export function useMutation<Mutation extends GraphQLQuery>(
|
|||||||
): SWRMutationResponse<
|
): SWRMutationResponse<
|
||||||
QueryResponse<Mutation>,
|
QueryResponse<Mutation>,
|
||||||
GraphQLError | GraphQLError[],
|
GraphQLError | GraphQLError[],
|
||||||
|
string,
|
||||||
QueryVariables<Mutation>
|
QueryVariables<Mutation>
|
||||||
>;
|
>;
|
||||||
export function useMutation(
|
export function useMutation(
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"typeRoots": ["../../node_modules", "../../node_modules/@types"],
|
"typeRoots": ["../../node_modules", "../../node_modules/@types"],
|
||||||
"types": ["webpack-env", "ses", "affine__env"]
|
"types": ["webpack-env", "ses", "affine__env"]
|
||||||
},
|
},
|
||||||
"include": ["src/**/*.ts", "src/**/*.tsx"],
|
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.json"],
|
||||||
"exclude": ["node_modules"],
|
"exclude": ["node_modules"],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@affine/docs",
|
"name": "@affine/docs",
|
||||||
"version": "0.8.0-canary.14",
|
"version": "0.8.0-canary.24",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -10,25 +10,25 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@affine/component": "workspace:*",
|
"@affine/component": "workspace:*",
|
||||||
"@blocksuite/block-std": "0.0.0-20230808131052-ef3445a9-nightly",
|
"@blocksuite/block-std": "0.0.0-20230816084758-3be96d22-nightly",
|
||||||
"@blocksuite/blocks": "0.0.0-20230808131052-ef3445a9-nightly",
|
"@blocksuite/blocks": "0.0.0-20230816084758-3be96d22-nightly",
|
||||||
"@blocksuite/editor": "0.0.0-20230808131052-ef3445a9-nightly",
|
"@blocksuite/editor": "0.0.0-20230816084758-3be96d22-nightly",
|
||||||
"@blocksuite/global": "0.0.0-20230808131052-ef3445a9-nightly",
|
"@blocksuite/global": "0.0.0-20230816084758-3be96d22-nightly",
|
||||||
"@blocksuite/lit": "0.0.0-20230808131052-ef3445a9-nightly",
|
"@blocksuite/lit": "0.0.0-20230816084758-3be96d22-nightly",
|
||||||
"@blocksuite/store": "0.0.0-20230808131052-ef3445a9-nightly",
|
"@blocksuite/store": "0.0.0-20230816084758-3be96d22-nightly",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"jotai": "^2.2.2",
|
"jotai": "^2.3.1",
|
||||||
"react": "18.3.0-canary-1fdacbefd-20230630",
|
"react": "18.3.0-canary-7118f5dd7-20230705",
|
||||||
"react-dom": "18.3.0-canary-1fdacbefd-20230630",
|
"react-dom": "18.3.0-canary-7118f5dd7-20230705",
|
||||||
"react-server-dom-webpack": "18.3.0-canary-1fdacbefd-20230630",
|
"react-server-dom-webpack": "18.3.0-canary-7118f5dd7-20230705",
|
||||||
"waku": "0.14.0"
|
"waku": "0.14.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "^18.2.17",
|
"@types/react": "^18.2.20",
|
||||||
"@types/react-dom": "^18.2.7",
|
"@types/react-dom": "^18.2.7",
|
||||||
"@vanilla-extract/css": "^1.12.0",
|
"@vanilla-extract/css": "^1.12.0",
|
||||||
"@vanilla-extract/vite-plugin": "^3.8.2",
|
"@vanilla-extract/vite-plugin": "^3.8.2",
|
||||||
"autoprefixer": "^10.4.14",
|
"autoprefixer": "^10.4.15",
|
||||||
"tailwindcss": "^3.3.3",
|
"tailwindcss": "^3.3.3",
|
||||||
"typescript": "^5.1.6"
|
"typescript": "^5.1.6"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,8 @@ test.skip('move workspace db file', async ({ page, appInfo, workspace }) => {
|
|||||||
expect(files.some(f => f.endsWith('.affine'))).toBe(true);
|
expect(files.some(f => f.endsWith('.affine'))).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('export then add', async ({ page, appInfo, workspace }) => {
|
//TODO:fix test
|
||||||
|
test.fixme('export then add', async ({ page, appInfo, workspace }) => {
|
||||||
const w = await workspace.current();
|
const w = await workspace.current();
|
||||||
|
|
||||||
await page.focus('.affine-doc-page-block-title');
|
await page.focus('.affine-doc-page-block-title');
|
||||||
@@ -58,14 +59,13 @@ test('export then add', async ({ page, appInfo, workspace }) => {
|
|||||||
|
|
||||||
// goto workspace setting
|
// goto workspace setting
|
||||||
await page.getByTestId('workspace-list-item').click();
|
await page.getByTestId('workspace-list-item').click();
|
||||||
|
const input = page.getByTestId('workspace-name-input');
|
||||||
await page.waitForTimeout(500);
|
await expect(input).toBeVisible();
|
||||||
|
|
||||||
// change workspace name
|
// change workspace name
|
||||||
await page.getByTestId('workspace-name-input').fill(newWorkspaceName);
|
await input.fill(newWorkspaceName);
|
||||||
await page.getByTestId('save-workspace-name').click();
|
await page.getByTestId('save-workspace-name').click();
|
||||||
await page.waitForSelector('text="Update workspace name success"');
|
await page.waitForSelector('text="Update workspace name success"');
|
||||||
await page.waitForTimeout(500);
|
|
||||||
|
|
||||||
const tmpPath = path.join(appInfo.sessionData, w.id + '-tmp.db');
|
const tmpPath = path.join(appInfo.sessionData, w.id + '-tmp.db');
|
||||||
|
|
||||||
@@ -78,7 +78,7 @@ test('export then add', async ({ page, appInfo, workspace }) => {
|
|||||||
|
|
||||||
await page.getByTestId('export-affine-backup').click();
|
await page.getByTestId('export-affine-backup').click();
|
||||||
await page.waitForSelector('text="Export success"');
|
await page.waitForSelector('text="Export success"');
|
||||||
|
await page.waitForTimeout(1000);
|
||||||
expect(await fs.exists(tmpPath)).toBe(true);
|
expect(await fs.exists(tmpPath)).toBe(true);
|
||||||
|
|
||||||
await page.getByTestId('modal-close-button').click();
|
await page.getByTestId('modal-close-button').click();
|
||||||
|
|||||||