Compare commits
36 Commits
v0.8.0-can
...
v0.8.0-can
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f33fb98912 | ||
|
|
4d254f3967 | ||
|
|
8833584756 | ||
|
|
582059f6d6 | ||
|
|
1484818974 | ||
|
|
1d41687786 | ||
|
|
401fb48b86 | ||
|
|
91619b87db | ||
|
|
844e73ca29 | ||
|
|
ee91964998 | ||
|
|
1eaf228a30 | ||
|
|
9639143df4 | ||
|
|
e9f4912665 | ||
|
|
ce21ea78eb | ||
|
|
13ac9d18af | ||
|
|
1a8f849693 | ||
|
|
f0cbbc3a84 | ||
|
|
4834f99da9 | ||
|
|
394469a807 | ||
|
|
b7be91e04d | ||
|
|
278ffa3372 | ||
|
|
601cbd83ff | ||
|
|
43b35a77bb | ||
|
|
f06efd4d02 | ||
|
|
5887071319 | ||
|
|
fa111db91b | ||
|
|
b6cdabff36 | ||
|
|
af314dabfb | ||
|
|
11f6273a3a | ||
|
|
4b51eb7e06 | ||
|
|
671129bc32 | ||
|
|
358c3a5bb2 | ||
|
|
7c4b8866d3 | ||
|
|
dafd5619e6 | ||
|
|
05144abd6a | ||
|
|
629a3d11c6 |
@@ -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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
10
.eslintrc.js
@@ -31,6 +31,11 @@ const createPattern = packageName => [
|
||||
message: 'Use `useNavigateHelper` instead',
|
||||
importNames: ['useNavigate'],
|
||||
},
|
||||
{
|
||||
group: ['yjs'],
|
||||
message: 'Do not use this API because it has a bug',
|
||||
importNames: ['mergeUpdates'],
|
||||
},
|
||||
];
|
||||
|
||||
const allPackages = [
|
||||
@@ -155,6 +160,11 @@ const config = {
|
||||
message: 'Use `useNavigateHelper` instead',
|
||||
importNames: ['useNavigate'],
|
||||
},
|
||||
{
|
||||
group: ['yjs'],
|
||||
message: 'Do not use this API because it has a bug',
|
||||
importNames: ['mergeUpdates'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
2
.github/dependabot.yml
vendored
@@ -3,7 +3,7 @@ updates:
|
||||
- package-ecosystem: 'npm'
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: 'daily'
|
||||
interval: 'weekly'
|
||||
versioning-strategy: increase
|
||||
commit-message:
|
||||
prefix: 'chore'
|
||||
|
||||
62
.github/workflows/build.yml
vendored
@@ -59,6 +59,25 @@ jobs:
|
||||
- name: Run Type Check
|
||||
run: yarn typecheck
|
||||
|
||||
build-prototype:
|
||||
name: Build Prototype
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
electron-install: false
|
||||
- name: Build Prototype
|
||||
run: yarn nx build prototype
|
||||
- name: Upload prototype artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: prototype
|
||||
path: ./apps/prototype/dist
|
||||
if-no-files-found: error
|
||||
|
||||
build-server:
|
||||
name: Build Server
|
||||
runs-on: ubuntu-latest
|
||||
@@ -280,6 +299,49 @@ jobs:
|
||||
path: ./test-results
|
||||
if-no-files-found: ignore
|
||||
|
||||
e2e-prototype-test:
|
||||
name: E2E Prototype Test
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
needs: build-prototype
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
playwright-install: true
|
||||
electron-install: false
|
||||
- name: Download prototype artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: prototype
|
||||
path: ./apps/prototype/dist
|
||||
- name: Run playwright tests
|
||||
run: yarn e2e --forbid-only
|
||||
working-directory: tests/affine-prototype
|
||||
env:
|
||||
COVERAGE: true
|
||||
|
||||
# - name: Collect code coverage report
|
||||
# run: yarn exec nyc report -t .nyc_output --report-dir .coverage --reporter=lcov
|
||||
|
||||
# - name: Upload e2e test coverage results
|
||||
# uses: codecov/codecov-action@v3
|
||||
# with:
|
||||
# token: ${{ secrets.CODECOV_TOKEN }}
|
||||
# files: ./.coverage/lcov.info
|
||||
# flags: e2etest-prototype
|
||||
# name: affine
|
||||
# fail_ci_if_error: false
|
||||
|
||||
- name: Upload test results
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: test-results-e2e-prototype
|
||||
path: ./test-results
|
||||
if-no-files-found: ignore
|
||||
|
||||
e2e-test:
|
||||
name: E2E Test
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -8,7 +8,7 @@ AFFiNE Developer Documentation using [waku](https://github.com/dai-shi/waku).
|
||||
|
||||
## electron
|
||||
|
||||
> `web` needs to be built before electron.
|
||||
> `core` needs to be built before electron.
|
||||
|
||||
AFFiNE Desktop (macOS, Linux and Windows Distribution) using [Electron](https://www.electronjs.org/).
|
||||
|
||||
@@ -20,6 +20,10 @@ Server using [Nest.js](https://nestjs.com/).
|
||||
|
||||
Storybook using [Storybook](https://storybook.js.org/).
|
||||
|
||||
## Core
|
||||
## prototype
|
||||
|
||||
AFFiNE Core Application using [React.js](https://reactjs.org/).
|
||||
AFFiNE Prototype using [React.js](https://reactjs.org/) + [Vite](https://vitejs.dev/).
|
||||
|
||||
## core
|
||||
|
||||
AFFiNE Core Application using [React.js](https://reactjs.org/) + [Webpack](https://webpack.js.org/).
|
||||
|
||||
@@ -84,6 +84,7 @@ export const createConfiguration: (
|
||||
experiments: {
|
||||
topLevelAwait: true,
|
||||
outputModule: false,
|
||||
syncWebAssembly: true,
|
||||
},
|
||||
output: {
|
||||
environment: {
|
||||
|
||||
@@ -15,21 +15,24 @@ export default async function (cli_env: any, _: any) {
|
||||
const config = createConfiguration(flags, runtimeConfig);
|
||||
return merge(config, {
|
||||
entry: {
|
||||
'polyfill/intl-segmenter': {
|
||||
import: resolve(rootPath, 'src/polyfill/intl-segmenter.ts'),
|
||||
},
|
||||
'polyfill/ses': {
|
||||
import: resolve(rootPath, 'src/polyfill/ses.ts'),
|
||||
},
|
||||
plugin: {
|
||||
dependOn: ['polyfill/ses'],
|
||||
dependOn: ['polyfill/intl-segmenter', 'polyfill/ses'],
|
||||
import: resolve(rootPath, 'src/bootstrap/register-plugins.ts'),
|
||||
},
|
||||
app: {
|
||||
chunkLoading: 'import',
|
||||
dependOn: ['polyfill/ses', 'plugin'],
|
||||
dependOn: ['polyfill/intl-segmenter', 'polyfill/ses', 'plugin'],
|
||||
import: resolve(rootPath, 'src/index.tsx'),
|
||||
},
|
||||
'_plugin/index.test': {
|
||||
chunkLoading: 'import',
|
||||
dependOn: ['polyfill/ses', 'plugin'],
|
||||
dependOn: ['polyfill/intl-segmenter', 'polyfill/ses', 'plugin'],
|
||||
import: resolve(rootPath, 'src/_plugin/index.test.tsx'),
|
||||
},
|
||||
},
|
||||
@@ -39,7 +42,7 @@ export default async function (cli_env: any, _: any) {
|
||||
inject: 'body',
|
||||
scriptLoading: 'module',
|
||||
minify: false,
|
||||
chunks: ['app', 'plugin', 'polyfill/ses'],
|
||||
chunks: ['app', 'plugin', 'polyfill/intl-segmenter', 'polyfill/ses'],
|
||||
filename: 'index.html',
|
||||
}),
|
||||
new HTMLPlugin({
|
||||
@@ -47,7 +50,12 @@ export default async function (cli_env: any, _: any) {
|
||||
inject: 'body',
|
||||
scriptLoading: 'module',
|
||||
minify: false,
|
||||
chunks: ['_plugin/index.test', 'plugin', 'polyfill/ses'],
|
||||
chunks: [
|
||||
'_plugin/index.test',
|
||||
'plugin',
|
||||
'polyfill/intl-segmenter',
|
||||
'polyfill/ses',
|
||||
],
|
||||
filename: '_plugin/index.html',
|
||||
}),
|
||||
],
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@affine/core",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"version": "0.8.0-canary.15",
|
||||
"version": "0.8.0-canary.21",
|
||||
"scripts": {
|
||||
"build": "yarn -T run build-core",
|
||||
"dev": "yarn -T run dev-core",
|
||||
@@ -18,30 +18,33 @@
|
||||
"@affine/jotai": "workspace:*",
|
||||
"@affine/templates": "workspace:*",
|
||||
"@affine/workspace": "workspace:*",
|
||||
"@blocksuite/block-std": "0.0.0-20230809030546-32e6e21d-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230809030546-32e6e21d-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230809030546-32e6e21d-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230809030546-32e6e21d-nightly",
|
||||
"@blocksuite/block-std": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/icons": "^2.1.31",
|
||||
"@blocksuite/lit": "0.0.0-20230809030546-32e6e21d-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230809030546-32e6e21d-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@dnd-kit/core": "^6.0.8",
|
||||
"@dnd-kit/sortable": "^7.0.2",
|
||||
"@emotion/cache": "^11.11.0",
|
||||
"@emotion/react": "^11.11.1",
|
||||
"@emotion/server": "^11.11.0",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@mui/material": "^5.14.2",
|
||||
"@mui/material": "^5.14.4",
|
||||
"@react-hookz/web": "^23.1.0",
|
||||
"@toeverything/components": "^0.0.10",
|
||||
"@types/lodash.throttle": "^4.1.7",
|
||||
"async-call-rpc": "^6.3.1",
|
||||
"cmdk": "^0.2.0",
|
||||
"css-spring": "^4.1.0",
|
||||
"cssnano": "^6.0.1",
|
||||
"graphql": "^16.7.1",
|
||||
"jotai": "^2.2.2",
|
||||
"intl-segmenter-polyfill-rs": "^0.1.5",
|
||||
"jotai": "^2.3.1",
|
||||
"jotai-devtools": "^0.6.1",
|
||||
"lit": "^2.7.6",
|
||||
"lit": "^2.8.0",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"lottie-web": "^5.12.2",
|
||||
"mini-css-extract-plugin": "^2.7.6",
|
||||
"next-themes": "^0.2.1",
|
||||
@@ -50,20 +53,20 @@
|
||||
"react-dom": "18.2.0",
|
||||
"react-is": "18.2.0",
|
||||
"react-resizable-panels": "^0.0.54",
|
||||
"react-router-dom": "^6.14.2",
|
||||
"react-router-dom": "^6.15.0",
|
||||
"rxjs": "^7.8.1",
|
||||
"ses": "^0.18.5",
|
||||
"swr": "2.1.5",
|
||||
"ses": "^0.18.7",
|
||||
"swr": "2.2.1",
|
||||
"y-protocols": "^1.0.5",
|
||||
"yjs": "^13.6.7",
|
||||
"zod": "^3.21.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@perfsee/webpack": "^1.8.2",
|
||||
"@perfsee/webpack": "^1.8.4",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.10",
|
||||
"@sentry/webpack-plugin": "^2.5.0",
|
||||
"@sentry/webpack-plugin": "^2.6.2",
|
||||
"@svgr/webpack": "^8.0.1",
|
||||
"@swc/core": "^1.3.74",
|
||||
"@swc/core": "^1.3.76",
|
||||
"@types/webpack-env": "^1.18.1",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"css-loader": "^6.8.1",
|
||||
@@ -73,7 +76,7 @@
|
||||
"source-map-loader": "^4.0.1",
|
||||
"style-loader": "^3.3.3",
|
||||
"swc-loader": "^0.2.3",
|
||||
"swc-plugin-coverage-instrument": "^0.0.19",
|
||||
"swc-plugin-coverage-instrument": "^0.0.20",
|
||||
"thread-loader": "^4.0.2",
|
||||
"ts-node": "^10.9.1",
|
||||
"webpack": "^5.88.2",
|
||||
|
||||
|
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 { initEmptyPage, initPageWithPreloading } from '@affine/env/blocksuite';
|
||||
import { initEmptyPage } from '@affine/env/blocksuite';
|
||||
import {
|
||||
DEFAULT_HELLO_WORLD_PAGE_ID_SUFFIX,
|
||||
DEFAULT_WORKSPACE_NAME,
|
||||
@@ -20,6 +20,7 @@ import { getOrCreateWorkspace } from '@affine/workspace/manager';
|
||||
import { createIndexedDBDownloadProvider } from '@affine/workspace/providers';
|
||||
import { nanoid } from '@blocksuite/store';
|
||||
import { useStaticBlockSuiteWorkspace } from '@toeverything/infra/__internal__/react';
|
||||
import { buildShowcaseWorkspace } from '@toeverything/infra/blocksuite';
|
||||
|
||||
import {
|
||||
BlockSuitePageList,
|
||||
@@ -41,21 +42,18 @@ export const LocalAdapter: WorkspaceAdapter<WorkspaceFlavour.LOCAL> = {
|
||||
WorkspaceFlavour.LOCAL
|
||||
);
|
||||
blockSuiteWorkspace.meta.setName(DEFAULT_WORKSPACE_NAME);
|
||||
const page = blockSuiteWorkspace.createPage({
|
||||
id: `${blockSuiteWorkspace.id}-${DEFAULT_HELLO_WORLD_PAGE_ID_SUFFIX}`,
|
||||
});
|
||||
if (runtimeConfig.enablePreloading) {
|
||||
initPageWithPreloading(page).catch(err => {
|
||||
buildShowcaseWorkspace(blockSuiteWorkspace).catch(err => {
|
||||
logger.error('init page with preloading failed', err);
|
||||
});
|
||||
} else {
|
||||
const page = blockSuiteWorkspace.createPage({
|
||||
id: `${blockSuiteWorkspace.id}-${DEFAULT_HELLO_WORLD_PAGE_ID_SUFFIX}`,
|
||||
});
|
||||
initEmptyPage(page).catch(error => {
|
||||
logger.error('init page with empty failed', error);
|
||||
});
|
||||
}
|
||||
blockSuiteWorkspace.setPageMeta(page.id, {
|
||||
jumpOnce: true,
|
||||
});
|
||||
const provider = createIndexedDBDownloadProvider(
|
||||
blockSuiteWorkspace.id,
|
||||
blockSuiteWorkspace.doc,
|
||||
|
||||
@@ -70,12 +70,16 @@ export const guideOnboardingAtom = atom<
|
||||
}));
|
||||
}
|
||||
);
|
||||
|
||||
export const guideDownloadClientTipAtom = atom<
|
||||
Guide['downloadClientTip'],
|
||||
[open: boolean],
|
||||
void
|
||||
>(
|
||||
get => {
|
||||
if (environment.isDesktop) {
|
||||
return false;
|
||||
}
|
||||
return get(guidePrimitiveAtom).downloadClientTip;
|
||||
},
|
||||
(_, set, open) => {
|
||||
|
||||
@@ -28,7 +28,7 @@ import { createFetch } from './endowments/fercher';
|
||||
import { createTimers } from './endowments/timer';
|
||||
import { setupImportsMap } from './setup-imports-map';
|
||||
|
||||
const dynamicImportKey = '$h_import';
|
||||
const dynamicImportKey = '$h_import';
|
||||
|
||||
const permissionLogger = new DebugLogger('plugins:permission');
|
||||
const importLogger = new DebugLogger('plugins:import');
|
||||
@@ -130,7 +130,6 @@ const rootImportsMapSetupPromise = setupImportsMap(_rootImportsMap, {
|
||||
pushLayoutAtom: pushLayoutAtom,
|
||||
deleteLayoutAtom: deleteLayoutAtom,
|
||||
},
|
||||
'@blocksuite/blocks/std': import('@blocksuite/blocks/std'),
|
||||
'@blocksuite/global/utils': import('@blocksuite/global/utils'),
|
||||
'@toeverything/infra/atom': import('@toeverything/infra/atom'),
|
||||
'@toeverything/components/button': import('@toeverything/components/button'),
|
||||
@@ -195,6 +194,7 @@ const timer = createTimers(abortController.signal);
|
||||
const sharedGlobalThis = Object.assign(Object.create(null), timer, {
|
||||
Object: globalThis.Object,
|
||||
fetch: pluginFetch,
|
||||
ReadableStream: globalThis.ReadableStream,
|
||||
Symbol: globalThis.Symbol,
|
||||
Error: globalThis.Error,
|
||||
TypeError: globalThis.TypeError,
|
||||
|
||||
@@ -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 { Export, MoveToTrash } from '@affine/component/page-list';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import {
|
||||
DuplicateIcon,
|
||||
EdgelessIcon,
|
||||
EditIcon,
|
||||
FavoritedIcon,
|
||||
FavoriteIcon,
|
||||
ImportIcon,
|
||||
MoreVerticalIcon,
|
||||
PageIcon,
|
||||
} from '@blocksuite/icons';
|
||||
import { IconButton } from '@toeverything/components/button';
|
||||
import type { PageMeta } from '@blocksuite/store';
|
||||
import { Divider } from '@toeverything/components/divider';
|
||||
import {
|
||||
useBlockSuitePageMeta,
|
||||
usePageMetaHelper,
|
||||
} from '@toeverything/hooks/use-block-suite-page-meta';
|
||||
import { currentPageIdAtom } from '@toeverything/infra/atom';
|
||||
import { useAtom, useAtomValue } from 'jotai';
|
||||
import { useBlockSuiteWorkspaceHelper } from '@toeverything/hooks/use-block-suite-workspace-helper';
|
||||
import { useAtom, useSetAtom } from 'jotai';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { applyUpdate, encodeStateAsUpdate } from 'yjs';
|
||||
|
||||
import { pageSettingFamily } from '../../../../atoms';
|
||||
import { useBlockSuiteMetaHelper } from '../../../../hooks/affine/use-block-suite-meta-helper';
|
||||
import { useCurrentWorkspace } from '../../../../hooks/current/use-current-workspace';
|
||||
import { toast } from '../../../../utils';
|
||||
import { HeaderDropDownButton } from '../../../pure/header-drop-down-button';
|
||||
import { usePageHelper } from '../../block-suite-page-list/utils';
|
||||
import { LanguageMenu } from './language-menu';
|
||||
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>
|
||||
);
|
||||
};
|
||||
import { pageSettingFamily, setPageModeAtom } from '../../../atoms';
|
||||
import { useBlockSuiteMetaHelper } from '../../../hooks/affine/use-block-suite-meta-helper';
|
||||
import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace';
|
||||
import { useNavigateHelper } from '../../../hooks/use-navigate-helper';
|
||||
import { toast } from '../../../utils';
|
||||
import { HeaderDropDownButton } from '../../pure/header-drop-down-button';
|
||||
import { usePageHelper } from '../block-suite-page-list/utils';
|
||||
|
||||
type PageMenuProps = {
|
||||
rename?: () => void;
|
||||
pageId: string;
|
||||
};
|
||||
|
||||
export const PageMenu = ({ rename }: PageMenuProps) => {
|
||||
export const PageMenu = ({ rename, pageId }: PageMenuProps) => {
|
||||
const t = useAFFiNEI18N();
|
||||
// fixme(himself65): remove these hooks ASAP
|
||||
const [workspace] = useCurrentWorkspace();
|
||||
const pageId = useAtomValue(currentPageIdAtom);
|
||||
assertExists(workspace);
|
||||
assertExists(pageId);
|
||||
|
||||
const blockSuiteWorkspace = workspace.blockSuiteWorkspace;
|
||||
const pageMeta = useBlockSuitePageMeta(blockSuiteWorkspace).find(
|
||||
meta => meta.id === pageId
|
||||
);
|
||||
assertExists(pageMeta);
|
||||
) as PageMeta;
|
||||
const [setting, setSetting] = useAtom(pageSettingFamily(pageId));
|
||||
const mode = setting?.mode ?? 'page';
|
||||
|
||||
const favorite = pageMeta.favorite ?? false;
|
||||
const { setPageMeta } = usePageMetaHelper(blockSuiteWorkspace);
|
||||
const { setPageMeta, setPageTitle } = usePageMetaHelper(blockSuiteWorkspace);
|
||||
const [openConfirm, setOpenConfirm] = useState(false);
|
||||
const { removeToTrash } = useBlockSuiteMetaHelper(blockSuiteWorkspace);
|
||||
const { importFile } = usePageHelper(blockSuiteWorkspace);
|
||||
@@ -97,13 +67,41 @@ export const PageMenu = ({ rename }: PageMenuProps) => {
|
||||
);
|
||||
}, [mode, setSetting, t]);
|
||||
const handleOnConfirm = useCallback(() => {
|
||||
removeToTrash(pageMeta.id);
|
||||
removeToTrash(pageId);
|
||||
toast(t['Moved to Trash']());
|
||||
setOpenConfirm(false);
|
||||
}, [pageMeta.id, removeToTrash, t]);
|
||||
}, [pageId, removeToTrash, t]);
|
||||
const menuItemStyle = {
|
||||
padding: '4px 12px',
|
||||
};
|
||||
const { openPage } = useNavigateHelper();
|
||||
const { createPage } = useBlockSuiteWorkspaceHelper(blockSuiteWorkspace);
|
||||
const setPageMode = useSetAtom(setPageModeAtom);
|
||||
const duplicate = useCallback(async () => {
|
||||
const currentPage = blockSuiteWorkspace.getPage(pageId);
|
||||
assertExists(currentPage);
|
||||
const currentPageMeta = currentPage.meta;
|
||||
const newPage = createPage();
|
||||
await newPage.waitForLoaded();
|
||||
const update = encodeStateAsUpdate(currentPage.spaceDoc);
|
||||
applyUpdate(newPage.spaceDoc, update);
|
||||
setPageMeta(newPage.id, {
|
||||
tags: currentPageMeta.tags,
|
||||
favorite: currentPageMeta.favorite,
|
||||
});
|
||||
setPageMode(newPage.id, mode);
|
||||
setPageTitle(newPage.id, `${currentPageMeta.title}(1)`);
|
||||
openPage(blockSuiteWorkspace.id, newPage.id);
|
||||
}, [
|
||||
blockSuiteWorkspace,
|
||||
createPage,
|
||||
mode,
|
||||
openPage,
|
||||
pageId,
|
||||
setPageMeta,
|
||||
setPageMode,
|
||||
setPageTitle,
|
||||
]);
|
||||
const EditMenu = (
|
||||
<>
|
||||
<MenuItem
|
||||
@@ -147,14 +145,14 @@ export const PageMenu = ({ rename }: PageMenuProps) => {
|
||||
{t['com.affine.header.option.add-tag']()}
|
||||
</MenuItem> */}
|
||||
<Divider />
|
||||
{/* <MenuItem
|
||||
<MenuItem
|
||||
icon={<DuplicateIcon />}
|
||||
data-testid="editor-option-menu-duplicate"
|
||||
onClick={() => {}}
|
||||
onClick={duplicate}
|
||||
style={menuItemStyle}
|
||||
>
|
||||
{t['com.affine.header.option.duplicate']()}
|
||||
</MenuItem> */}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={<ImportIcon />}
|
||||
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 { style } from '@vanilla-extract/css';
|
||||
|
||||
export const headerTitleContainer = style({
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
flexGrow: 1,
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
});
|
||||
|
||||
export const titleEditButton = style({
|
||||
flexGrow: 1,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
});
|
||||
|
||||
export const titleInput = style({
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
margin: 'auto',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
});
|
||||
export const shadowTitle = style({
|
||||
visibility: 'hidden',
|
||||
});
|
||||
@@ -6,9 +6,9 @@ import { useAtom } from 'jotai';
|
||||
import type { CSSProperties } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { pageSettingFamily } from '../../../../atoms';
|
||||
import type { BlockSuiteWorkspace } from '../../../../shared';
|
||||
import { toast } from '../../../../utils';
|
||||
import { pageSettingFamily } from '../../../atoms';
|
||||
import type { BlockSuiteWorkspace } from '../../../shared';
|
||||
import { toast } from '../../../utils';
|
||||
import { StyledEditorModeSwitch, StyledKeyboardItem } from './style';
|
||||
import { EdgelessSwitchItem, PageSwitchItem } from './switch-items';
|
||||
|
||||
@@ -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%',
|
||||
});
|
||||
@@ -23,9 +23,9 @@ import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
|
||||
import { pageSettingFamily } from '../atoms';
|
||||
import { fontStyleOptions, useAppSetting } from '../atoms/settings';
|
||||
import { BlockSuiteEditor as Editor } from './blocksuite/block-suite-editor';
|
||||
import { TrashButtonGroup } from './blocksuite/workspace-header/header-right-items/trash-button-group';
|
||||
import * as styles from './page-detail-editor.css';
|
||||
import { pluginContainer } from './page-detail-editor.css';
|
||||
import { TrashButtonGroup } from './pure/trash-button-group';
|
||||
|
||||
export interface PageDetailEditorProps {
|
||||
isPublic?: boolean;
|
||||
|
||||
150
apps/core/src/components/pure/header/index.tsx
Normal file
@@ -0,0 +1,150 @@
|
||||
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 throttle from 'lodash.throttle';
|
||||
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,
|
||||
leftDoms,
|
||||
centerDom,
|
||||
rightDoms,
|
||||
}: {
|
||||
mainContainer: HTMLElement;
|
||||
leftDoms: MutableRefObject<HTMLElement | null>[];
|
||||
centerDom: MutableRefObject<HTMLElement | null>;
|
||||
rightDoms: MutableRefObject<HTMLElement | null>[];
|
||||
}) => {
|
||||
const [isTinyScreen, setIsTinyScreen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = throttle(() => {
|
||||
if (!centerDom.current) {
|
||||
return;
|
||||
}
|
||||
const leftTotalWidth = leftDoms.reduce((accWidth, dom) => {
|
||||
return accWidth + (dom.current?.clientWidth || 0);
|
||||
}, 0);
|
||||
|
||||
const rightTotalWidth = rightDoms.reduce((accWidth, dom) => {
|
||||
return accWidth + (dom.current?.clientWidth || 0);
|
||||
}, 0);
|
||||
|
||||
const containerRect = mainContainer.getBoundingClientRect();
|
||||
const centerRect = centerDom.current.getBoundingClientRect();
|
||||
|
||||
const offset = isTinyScreen ? 50 : 0;
|
||||
if (
|
||||
leftTotalWidth + containerRect.left >= centerRect.left - offset ||
|
||||
containerRect.right - centerRect.right <= rightTotalWidth + offset
|
||||
) {
|
||||
setIsTinyScreen(true);
|
||||
} else {
|
||||
setIsTinyScreen(false);
|
||||
}
|
||||
}, 100);
|
||||
|
||||
handleResize();
|
||||
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
handleResize();
|
||||
});
|
||||
|
||||
resizeObserver.observe(mainContainer);
|
||||
}, [centerDom, isTinyScreen, leftDoms, mainContainer, rightDoms]);
|
||||
|
||||
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,
|
||||
leftDoms: [sidebarSwitchRef, leftSlotRef],
|
||||
centerDom: centerSlotRef,
|
||||
rightDoms: [rightSlotRef, 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,
|
||||
'has-min-width': !isTinyScreen,
|
||||
})}
|
||||
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%',
|
||||
position: 'absolute',
|
||||
transform: 'translateX(-50%)',
|
||||
left: '50%',
|
||||
zIndex: 1,
|
||||
selectors: {
|
||||
'&.is-window': {
|
||||
maxWidth: '50%',
|
||||
},
|
||||
'&.is-window.has-min-width': {
|
||||
minWidth: '400px',
|
||||
},
|
||||
'&.shadow': {
|
||||
position: 'static',
|
||||
visibility: 'hidden',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const headerSideContainer = style({
|
||||
display: 'flex',
|
||||
flexShrink: 0,
|
||||
alignItems: 'center',
|
||||
selectors: {
|
||||
'&.right': {
|
||||
flexDirection: 'row-reverse',
|
||||
},
|
||||
'&.block': {
|
||||
display: 'block',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const windowAppControlsWrapper = style({
|
||||
display: 'flex',
|
||||
marginLeft: '20px',
|
||||
});
|
||||
|
||||
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',
|
||||
marginRight: '-12px',
|
||||
},
|
||||
'&[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 { Trans } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { useAtom } from 'jotai';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { guideDownloadClientTipAtom } from '../../../atoms/guide';
|
||||
|
||||
const minimumChromeVersion = 102;
|
||||
|
||||
export const shouldShowWarning = () => {
|
||||
const shouldShowWarning = () => {
|
||||
if (isDesktop) {
|
||||
// even though desktop has compatibility issues,
|
||||
// we don't want to show the warning
|
||||
@@ -22,7 +27,7 @@ export const shouldShowWarning = () => {
|
||||
}
|
||||
};
|
||||
|
||||
export const OSWarningMessage = () => {
|
||||
const OSWarningMessage = () => {
|
||||
const t = useAFFiNEI18N();
|
||||
const [notChrome, setNotChrome] = useState(false);
|
||||
const [notGoodVersion, setNotGoodVersion] = useState(false);
|
||||
@@ -49,3 +54,34 @@ export const OSWarningMessage = () => {
|
||||
}
|
||||
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>
|
||||
);
|
||||
};
|
||||
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 { useCallback, useState } from 'react';
|
||||
|
||||
import { useBlockSuiteMetaHelper } from '../../../../hooks/affine/use-block-suite-meta-helper';
|
||||
import { useCurrentWorkspace } from '../../../../hooks/current/use-current-workspace';
|
||||
import { useNavigateHelper } from '../../../../hooks/use-navigate-helper';
|
||||
import { useBlockSuiteMetaHelper } from '../../../hooks/affine/use-block-suite-meta-helper';
|
||||
import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace';
|
||||
import { useNavigateHelper } from '../../../hooks/use-navigate-helper';
|
||||
import { buttonContainer, group } from './styles.css';
|
||||
|
||||
export const TrashButtonGroup = () => {
|
||||
// fixme(himself65): remove these hooks ASAP
|
||||
const [workspace] = useCurrentWorkspace();
|
||||
@@ -33,7 +34,7 @@ export const TrashButtonGroup = () => {
|
||||
<div className={group}>
|
||||
<div className={buttonContainer}>
|
||||
<Button
|
||||
type="processing"
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
restoreFromTrash(pageId);
|
||||
}}
|
||||
@@ -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 { useCallback } from 'react';
|
||||
|
||||
import { useCurrentWorkspace } from '../../../../hooks/current/use-current-workspace';
|
||||
import type { AllWorkspace } from '../../../../shared';
|
||||
import { workspaceAvatarStyle } from './index.css';
|
||||
import {
|
||||
@@ -28,9 +27,8 @@ export const WorkspaceSelector = ({
|
||||
onClick,
|
||||
}: WorkspaceSelectorProps) => {
|
||||
const [name] = useBlockSuiteWorkspaceName(
|
||||
currentWorkspace?.blockSuiteWorkspace
|
||||
currentWorkspace.blockSuiteWorkspace
|
||||
);
|
||||
const [workspace] = useCurrentWorkspace();
|
||||
|
||||
// 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
|
||||
@@ -57,22 +55,20 @@ export const WorkspaceSelector = ({
|
||||
data-testid="workspace-avatar"
|
||||
className={workspaceAvatarStyle}
|
||||
size={40}
|
||||
workspace={currentWorkspace?.blockSuiteWorkspace ?? null}
|
||||
workspace={currentWorkspace.blockSuiteWorkspace}
|
||||
/>
|
||||
<StyledSelectorWrapper>
|
||||
<StyledWorkspaceName data-testid="workspace-name">
|
||||
{name}
|
||||
</StyledWorkspaceName>
|
||||
{workspace && (
|
||||
<StyledWorkspaceStatus>
|
||||
{workspace.flavour === 'local' ? (
|
||||
<LocalWorkspaceIcon />
|
||||
) : (
|
||||
<CloudWorkspaceIcon />
|
||||
)}
|
||||
{workspace.flavour === 'local' ? 'Local' : 'AFFiNE Cloud'}
|
||||
</StyledWorkspaceStatus>
|
||||
)}
|
||||
<StyledWorkspaceStatus>
|
||||
{currentWorkspace.flavour === 'local' ? (
|
||||
<LocalWorkspaceIcon />
|
||||
) : (
|
||||
<CloudWorkspaceIcon />
|
||||
)}
|
||||
{currentWorkspace.flavour === 'local' ? 'Local' : 'AFFiNE Cloud'}
|
||||
</StyledWorkspaceStatus>
|
||||
</StyledSelectorWrapper>
|
||||
</StyledSelectorContainer>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -5,22 +5,25 @@ import {
|
||||
useCollectionManager,
|
||||
} from '@affine/component/page-list';
|
||||
import type { Collection } from '@affine/env/filter';
|
||||
import type { WorkspaceHeaderProps } from '@affine/env/workspace';
|
||||
import { WorkspaceFlavour, WorkspaceSubPath } from '@affine/env/workspace';
|
||||
import type { ReactElement } from 'react';
|
||||
import type { PropertiesMeta } from '@affine/env/filter';
|
||||
import type {
|
||||
WorkspaceFlavour,
|
||||
WorkspaceHeaderProps,
|
||||
} from '@affine/env/workspace';
|
||||
import { WorkspaceSubPath } from '@affine/env/workspace';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { useGetPageInfoById } from '../hooks/use-get-page-info';
|
||||
import { useWorkspace } from '../hooks/use-workspace';
|
||||
import { BlockSuiteEditorHeader } from './blocksuite/workspace-header';
|
||||
import { BlockSuiteHeaderTitle } from './blocksuite/block-suite-header-title';
|
||||
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({
|
||||
currentWorkspaceId,
|
||||
currentEntry,
|
||||
}: WorkspaceHeaderProps<WorkspaceFlavour>): ReactElement {
|
||||
const setting = useCollectionManager(currentWorkspaceId);
|
||||
const FilterContainer = ({ workspaceId }: { workspaceId: string }) => {
|
||||
const currentWorkspace = useWorkspace(workspaceId);
|
||||
const setting = useCollectionManager(workspaceId);
|
||||
const saveToCollection = useCallback(
|
||||
async (collection: Collection) => {
|
||||
await setting.saveCollection(collection);
|
||||
@@ -28,86 +31,105 @@ export function WorkspaceHeader({
|
||||
},
|
||||
[setting]
|
||||
);
|
||||
const getPageInfoById = useGetPageInfoById(
|
||||
currentWorkspace.blockSuiteWorkspace
|
||||
);
|
||||
if (!setting.isDefault || !setting.currentCollection.filterList.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={filterContainerStyle}>
|
||||
<div style={{ flex: 1 }}>
|
||||
<FilterList
|
||||
propertiesMeta={currentWorkspace.blockSuiteWorkspace.meta.properties}
|
||||
value={setting.currentCollection.filterList}
|
||||
onChange={filterList => {
|
||||
return setting.updateCollection({
|
||||
...setting.currentCollection,
|
||||
filterList,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
{setting.currentCollection.filterList.length > 0 ? (
|
||||
<SaveCollectionButton
|
||||
propertiesMeta={
|
||||
currentWorkspace.blockSuiteWorkspace.meta
|
||||
.properties as PropertiesMeta
|
||||
}
|
||||
getPageInfo={getPageInfoById}
|
||||
onConfirm={saveToCollection}
|
||||
filterList={setting.currentCollection.filterList}
|
||||
workspaceId={workspaceId}
|
||||
></SaveCollectionButton>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export function WorkspaceHeader({
|
||||
currentWorkspaceId,
|
||||
currentEntry,
|
||||
}: WorkspaceHeaderProps<WorkspaceFlavour>) {
|
||||
const setting = useCollectionManager(currentWorkspaceId);
|
||||
|
||||
const currentWorkspace = useWorkspace(currentWorkspaceId);
|
||||
|
||||
const getPageInfoById = useGetPageInfoById(
|
||||
currentWorkspace.blockSuiteWorkspace
|
||||
);
|
||||
if ('subPath' in currentEntry) {
|
||||
if (currentEntry.subPath === WorkspaceSubPath.ALL) {
|
||||
const leftSlot = (
|
||||
<CollectionList
|
||||
setting={setting}
|
||||
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;
|
||||
|
||||
// route in all page
|
||||
if (
|
||||
'subPath' in currentEntry &&
|
||||
currentEntry.subPath === WorkspaceSubPath.ALL
|
||||
) {
|
||||
return (
|
||||
<BlockSuiteEditorHeader
|
||||
isPublic={isPublic}
|
||||
workspace={currentWorkspace}
|
||||
currentPage={currentWorkspace.blockSuiteWorkspace.getPage(pageId)}
|
||||
<>
|
||||
<Header
|
||||
left={
|
||||
<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,13 +1,11 @@
|
||||
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 { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
||||
import { saveWorkspaceToLocalStorage } from '@affine/workspace/local/crud';
|
||||
import { getOrCreateWorkspace } from '@affine/workspace/manager';
|
||||
import { assertEquals } from '@blocksuite/global/utils';
|
||||
import { nanoid } from '@blocksuite/store';
|
||||
import { getWorkspace } from '@toeverything/infra/__internal__/workspace';
|
||||
import { buildShowcaseWorkspace } from '@toeverything/infra/blocksuite';
|
||||
import { useAtomValue, useSetAtom } from 'jotai';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
@@ -54,23 +52,7 @@ export function useAppHelper() {
|
||||
id,
|
||||
WorkspaceFlavour.LOCAL
|
||||
);
|
||||
const pageId = `${blockSuiteWorkspace.id}-${DEFAULT_HELLO_WORLD_PAGE_ID_SUFFIX}`;
|
||||
const page = blockSuiteWorkspace.createPage({
|
||||
id: pageId,
|
||||
});
|
||||
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,
|
||||
});
|
||||
await buildShowcaseWorkspace(blockSuiteWorkspace);
|
||||
}
|
||||
set(workspaces => [
|
||||
...workspaces,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { DebugLogger } from '@affine/debug';
|
||||
import { DEFAULT_HELLO_WORLD_PAGE_ID_SUFFIX } from '@affine/env/constant';
|
||||
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
||||
import { getWorkspace } from '@toeverything/infra/__internal__/workspace';
|
||||
import { rootStore } from '@toeverything/infra/atom';
|
||||
@@ -24,10 +25,20 @@ export const loader: LoaderFunction = async () => {
|
||||
const nonTrashPages = targetWorkspace.meta.pageMetas.filter(
|
||||
({ trash }) => !trash
|
||||
);
|
||||
const helloWorldPage = nonTrashPages.find(
|
||||
({ id, jumpOnce }) =>
|
||||
id.endsWith(DEFAULT_HELLO_WORLD_PAGE_ID_SUFFIX) && jumpOnce
|
||||
)?.id;
|
||||
const pageId =
|
||||
nonTrashPages.find(({ id }) => id === lastPageId)?.id ??
|
||||
nonTrashPages.at(0)?.id;
|
||||
if (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);
|
||||
return redirect(`/workspace/${targetWorkspace.id}/${pageId}`);
|
||||
} else {
|
||||
|
||||
@@ -3,9 +3,8 @@ import { DEFAULT_HELLO_WORLD_PAGE_ID_SUFFIX } from '@affine/env/constant';
|
||||
import { WorkspaceSubPath } from '@affine/env/workspace';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { getActiveBlockSuiteWorkspaceAtom } from '@toeverything/infra/__internal__/workspace';
|
||||
import { currentPageIdAtom, rootStore } from '@toeverything/infra/atom';
|
||||
import { useAtom } from 'jotai/react';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { rootStore } from '@toeverything/infra/atom';
|
||||
import { useCallback } from 'react';
|
||||
import type { LoaderFunction } from 'react-router-dom';
|
||||
import { redirect } from 'react-router-dom';
|
||||
|
||||
@@ -18,21 +17,22 @@ export const loader: LoaderFunction = async args => {
|
||||
assertExists(workspaceId);
|
||||
const workspaceAtom = getActiveBlockSuiteWorkspaceAtom(workspaceId);
|
||||
const workspace = await rootStore.get(workspaceAtom);
|
||||
const page = workspace.getPage(
|
||||
`${workspace.id}-${DEFAULT_HELLO_WORLD_PAGE_ID_SUFFIX}`
|
||||
);
|
||||
if (page && page.meta.jumpOnce) {
|
||||
workspace.meta.setPageMeta(page.id, {
|
||||
jumpOnce: false,
|
||||
});
|
||||
return redirect(`/workspace/${workspace.id}/${page.id}`);
|
||||
for (const pageId of workspace.pages.keys()) {
|
||||
if (pageId.endsWith(DEFAULT_HELLO_WORLD_PAGE_ID_SUFFIX)) {
|
||||
const page = workspace.getPage(pageId);
|
||||
if (page && page.meta.jumpOnce) {
|
||||
workspace.meta.setPageMeta(page.id, {
|
||||
jumpOnce: false,
|
||||
});
|
||||
return redirect(`/workspace/${workspace.id}/${page.id}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const AllPage = () => {
|
||||
const { jumpToPage } = useNavigateHelper();
|
||||
const [currentPageId, setCurrentPageId] = useAtom(currentPageIdAtom);
|
||||
const [currentWorkspace] = useCurrentWorkspace();
|
||||
const setting = useCollectionManager(currentWorkspace.id);
|
||||
const onClickPage = useCallback(
|
||||
@@ -46,18 +46,6 @@ export const AllPage = () => {
|
||||
},
|
||||
[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);
|
||||
return (
|
||||
<>
|
||||
|
||||
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 {};
|
||||
@@ -28,6 +28,10 @@ export const router = createBrowserRouter(
|
||||
path: '/404',
|
||||
lazy: () => import('./pages/404'),
|
||||
},
|
||||
{
|
||||
path: '*',
|
||||
lazy: () => import('./pages/404'),
|
||||
},
|
||||
],
|
||||
{
|
||||
future: {
|
||||
|
||||
@@ -62,7 +62,7 @@ describe('GraphQL wrapper for SWR', () => {
|
||||
const el = await renderer.findByText('number: 1');
|
||||
expect(el).toMatchInlineSnapshot(`
|
||||
<div>
|
||||
number:
|
||||
number:${' '}
|
||||
1
|
||||
</div>
|
||||
`);
|
||||
@@ -107,14 +107,7 @@ describe('GraphQL wrapper for SWR', () => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button
|
||||
onClick={() =>
|
||||
// @ts-expect-error forgive the fake variables
|
||||
trigger()
|
||||
}
|
||||
>
|
||||
click
|
||||
</button>
|
||||
<button onClick={() => trigger()}>click</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -79,6 +79,7 @@ export function useMutation<Mutation extends GraphQLQuery>(
|
||||
): SWRMutationResponse<
|
||||
QueryResponse<Mutation>,
|
||||
GraphQLError | GraphQLError[],
|
||||
string,
|
||||
QueryVariables<Mutation>
|
||||
>;
|
||||
export function useMutation<Mutation extends GraphQLQuery>(
|
||||
@@ -87,6 +88,7 @@ export function useMutation<Mutation extends GraphQLQuery>(
|
||||
SWRMutationConfiguration<
|
||||
QueryResponse<Mutation>,
|
||||
GraphQLError | GraphQLError[],
|
||||
string,
|
||||
QueryVariables<Mutation>
|
||||
>,
|
||||
'fetcher'
|
||||
@@ -94,6 +96,7 @@ export function useMutation<Mutation extends GraphQLQuery>(
|
||||
): SWRMutationResponse<
|
||||
QueryResponse<Mutation>,
|
||||
GraphQLError | GraphQLError[],
|
||||
string,
|
||||
QueryVariables<Mutation>
|
||||
>;
|
||||
export function useMutation(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@affine/docs",
|
||||
"version": "0.8.0-canary.15",
|
||||
"version": "0.8.0-canary.21",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@@ -10,21 +10,21 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@affine/component": "workspace:*",
|
||||
"@blocksuite/block-std": "0.0.0-20230809030546-32e6e21d-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230809030546-32e6e21d-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230809030546-32e6e21d-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230809030546-32e6e21d-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230809030546-32e6e21d-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230809030546-32e6e21d-nightly",
|
||||
"@blocksuite/block-std": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"express": "^4.18.2",
|
||||
"jotai": "^2.2.2",
|
||||
"react": "18.3.0-canary-1fdacbefd-20230630",
|
||||
"react-dom": "18.3.0-canary-1fdacbefd-20230630",
|
||||
"react-server-dom-webpack": "18.3.0-canary-1fdacbefd-20230630",
|
||||
"jotai": "^2.3.1",
|
||||
"react": "18.3.0-canary-7118f5dd7-20230705",
|
||||
"react-dom": "18.3.0-canary-7118f5dd7-20230705",
|
||||
"react-server-dom-webpack": "18.3.0-canary-7118f5dd7-20230705",
|
||||
"waku": "0.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.17",
|
||||
"@types/react": "^18.2.20",
|
||||
"@types/react-dom": "^18.2.7",
|
||||
"@vanilla-extract/css": "^1.12.0",
|
||||
"@vanilla-extract/vite-plugin": "^3.8.2",
|
||||
|
||||
@@ -43,7 +43,8 @@ test.skip('move workspace db file', async ({ page, appInfo, workspace }) => {
|
||||
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();
|
||||
|
||||
await page.focus('.affine-doc-page-block-title');
|
||||
@@ -58,14 +59,13 @@ test('export then add', async ({ page, appInfo, workspace }) => {
|
||||
|
||||
// goto workspace setting
|
||||
await page.getByTestId('workspace-list-item').click();
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
const input = page.getByTestId('workspace-name-input');
|
||||
await expect(input).toBeVisible();
|
||||
|
||||
// change workspace name
|
||||
await page.getByTestId('workspace-name-input').fill(newWorkspaceName);
|
||||
await input.fill(newWorkspaceName);
|
||||
await page.getByTestId('save-workspace-name').click();
|
||||
await page.waitForSelector('text="Update workspace name success"');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
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.waitForSelector('text="Export success"');
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
expect(await fs.exists(tmpPath)).toBe(true);
|
||||
|
||||
await page.getByTestId('modal-close-button').click();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@affine/electron",
|
||||
"private": true,
|
||||
"version": "0.8.0-canary.15",
|
||||
"version": "0.8.0-canary.21",
|
||||
"author": "affine",
|
||||
"repository": {
|
||||
"url": "https://github.com/toeverything/AFFiNE",
|
||||
@@ -28,10 +28,10 @@
|
||||
"@affine/env": "workspace:*",
|
||||
"@affine/native": "workspace:*",
|
||||
"@affine/sdk": "workspace:*",
|
||||
"@blocksuite/blocks": "0.0.0-20230809030546-32e6e21d-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230809030546-32e6e21d-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230809030546-32e6e21d-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230809030546-32e6e21d-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@electron-forge/cli": "^6.3.0",
|
||||
"@electron-forge/core": "^6.3.0",
|
||||
"@electron-forge/core-utils": "^6.3.0",
|
||||
@@ -44,17 +44,17 @@
|
||||
"@types/fs-extra": "^11.0.1",
|
||||
"@types/uuid": "^9.0.2",
|
||||
"cross-env": "7.0.3",
|
||||
"electron": "^25.4.0",
|
||||
"electron": "^25.5.0",
|
||||
"electron-log": "^5.0.0-beta.25",
|
||||
"electron-squirrel-startup": "1.0.0",
|
||||
"electron-window-state": "^5.0.3",
|
||||
"esbuild": "^0.18.15",
|
||||
"esbuild": "^0.18.20",
|
||||
"fs-extra": "^11.1.1",
|
||||
"jotai": "^2.2.2",
|
||||
"jotai": "^2.3.1",
|
||||
"ts-node": "^10.9.1",
|
||||
"undici": "^5.22.1",
|
||||
"undici": "^5.23.0",
|
||||
"uuid": "^9.0.0",
|
||||
"vitest": "^0.33.0",
|
||||
"vitest": "0.33.0",
|
||||
"which": "^3.0.1",
|
||||
"zx": "^7.2.3"
|
||||
},
|
||||
|
||||
@@ -2,7 +2,7 @@ import path from 'node:path';
|
||||
|
||||
import { SqliteConnection } from '@affine/native';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import * as Y from 'yjs';
|
||||
import { applyUpdate, Doc as YDoc } from 'yjs';
|
||||
|
||||
import { removeWithRetry } from '../../../../tests/utils';
|
||||
import { copyToTemp, migrateToSubdocAndReplaceDatabase } from '../migration';
|
||||
@@ -41,14 +41,14 @@ describe('migrateToSubdocAndReplaceDatabase', () => {
|
||||
expect(subdocUpdate).toBeDefined();
|
||||
|
||||
// apply updates
|
||||
const rootDoc = new Y.Doc();
|
||||
Y.applyUpdate(rootDoc, rootUpdate);
|
||||
const rootDoc = new YDoc();
|
||||
applyUpdate(rootDoc, rootUpdate);
|
||||
|
||||
// check if root doc has one subdoc
|
||||
expect(rootDoc.subdocs.size).toBe(1);
|
||||
|
||||
// populates subdoc
|
||||
Y.applyUpdate(rootDoc.subdocs.values().next().value, subdocUpdate);
|
||||
applyUpdate(rootDoc.subdocs.values().next().value, subdocUpdate);
|
||||
|
||||
// check if root doc's meta is correct
|
||||
const meta = rootDoc.getMap('meta').toJSON();
|
||||
@@ -59,9 +59,7 @@ describe('migrateToSubdocAndReplaceDatabase', () => {
|
||||
expect(pageMeta.title).toBe('Welcome to AFFiNEd');
|
||||
|
||||
// get the subdoc through id
|
||||
const subDoc = rootDoc
|
||||
.getMap('spaces')
|
||||
.get(`space:${pageMeta.id}`) as Y.Doc;
|
||||
const subDoc = rootDoc.getMap('spaces').get(`space:${pageMeta.id}`) as YDoc;
|
||||
expect(subDoc).toEqual(rootDoc.subdocs.values().next().value);
|
||||
|
||||
await db.close();
|
||||
|
||||
@@ -3,7 +3,7 @@ import path from 'node:path';
|
||||
import fs from 'fs-extra';
|
||||
import { v4 } from 'uuid';
|
||||
import { afterEach, expect, test, vi } from 'vitest';
|
||||
import * as Y from 'yjs';
|
||||
import { Doc as YDoc, encodeStateAsUpdate } from 'yjs';
|
||||
|
||||
import { removeWithRetry } from '../../../../tests/utils';
|
||||
import { dbSubjects } from '../subjects';
|
||||
@@ -21,18 +21,18 @@ afterEach(async () => {
|
||||
await removeWithRetry(tmpDir);
|
||||
});
|
||||
|
||||
let testYDoc: Y.Doc;
|
||||
let testYSubDoc: Y.Doc;
|
||||
let testYDoc: YDoc;
|
||||
let testYSubDoc: YDoc;
|
||||
|
||||
function getTestUpdates() {
|
||||
testYDoc = new Y.Doc();
|
||||
testYDoc = new YDoc();
|
||||
const yText = testYDoc.getText('test');
|
||||
yText.insert(0, 'hello');
|
||||
|
||||
testYSubDoc = new Y.Doc();
|
||||
testYSubDoc = new YDoc();
|
||||
testYDoc.getMap('subdocs').set('test-subdoc', testYSubDoc);
|
||||
|
||||
const updates = Y.encodeStateAsUpdate(testYDoc);
|
||||
const updates = encodeStateAsUpdate(testYDoc);
|
||||
|
||||
return updates;
|
||||
}
|
||||
@@ -41,7 +41,7 @@ function getTestSubDocUpdates() {
|
||||
const yText = testYSubDoc.getText('test');
|
||||
yText.insert(0, 'hello');
|
||||
|
||||
const updates = Y.encodeStateAsUpdate(testYSubDoc);
|
||||
const updates = encodeStateAsUpdate(testYSubDoc);
|
||||
|
||||
return updates;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import * as Y from 'yjs';
|
||||
import { applyUpdate, Doc as YDoc, encodeStateAsUpdate, transact } from 'yjs';
|
||||
|
||||
export function mergeUpdate(updates: Uint8Array[]) {
|
||||
const yDoc = new Y.Doc();
|
||||
Y.transact(yDoc, () => {
|
||||
const yDoc = new YDoc();
|
||||
transact(yDoc, () => {
|
||||
for (const update of updates) {
|
||||
Y.applyUpdate(yDoc, update);
|
||||
applyUpdate(yDoc, update);
|
||||
}
|
||||
});
|
||||
return Y.encodeStateAsUpdate(yDoc);
|
||||
return encodeStateAsUpdate(yDoc);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { migrateToSubdoc } from '@affine/env/blocksuite';
|
||||
import { SqliteConnection } from '@affine/native';
|
||||
import fs from 'fs-extra';
|
||||
import { nanoid } from 'nanoid';
|
||||
import * as Y from 'yjs';
|
||||
import { applyUpdate, Doc as YDoc, encodeStateAsUpdate } from 'yjs';
|
||||
|
||||
import { mainRPC } from '../main-rpc';
|
||||
|
||||
@@ -13,11 +13,11 @@ export const migrateToSubdocAndReplaceDatabase = async (path: string) => {
|
||||
await db.connect();
|
||||
|
||||
const rows = await db.getAllUpdates();
|
||||
const originalDoc = new Y.Doc();
|
||||
const originalDoc = new YDoc();
|
||||
|
||||
// 1. apply all updates to the root doc
|
||||
rows.forEach(row => {
|
||||
Y.applyUpdate(originalDoc, row.data);
|
||||
applyUpdate(originalDoc, row.data);
|
||||
});
|
||||
|
||||
// 2. migrate using migrateToSubdoc
|
||||
@@ -40,10 +40,10 @@ export const copyToTemp = async (path: string) => {
|
||||
|
||||
async function replaceRows(
|
||||
db: SqliteConnection,
|
||||
doc: Y.Doc,
|
||||
doc: YDoc,
|
||||
isRoot: boolean
|
||||
): Promise<void> {
|
||||
const migratedUpdates = Y.encodeStateAsUpdate(doc);
|
||||
const migratedUpdates = encodeStateAsUpdate(doc);
|
||||
const docId = isRoot ? undefined : doc.guid;
|
||||
const rows = [{ data: migratedUpdates, docId: docId }];
|
||||
await db.replaceUpdates(docId, rows);
|
||||
|
||||
@@ -2,7 +2,7 @@ import assert from 'node:assert';
|
||||
|
||||
import type { InsertRow } from '@affine/native';
|
||||
import { debounce } from 'lodash-es';
|
||||
import * as Y from 'yjs';
|
||||
import { applyUpdate, Doc as YDoc } from 'yjs';
|
||||
|
||||
import { logger } from '../logger';
|
||||
import type { YOrigin } from '../type';
|
||||
@@ -16,7 +16,7 @@ const FLUSH_MAX_WAIT_TIME = 10000;
|
||||
// todo: trim db when it is too big
|
||||
export class SecondaryWorkspaceSQLiteDB extends BaseSQLiteAdapter {
|
||||
role = 'secondary';
|
||||
yDoc = new Y.Doc();
|
||||
yDoc = new YDoc();
|
||||
firstConnected = false;
|
||||
destroyed = false;
|
||||
|
||||
@@ -165,7 +165,7 @@ export class SecondaryWorkspaceSQLiteDB extends BaseSQLiteAdapter {
|
||||
}
|
||||
};
|
||||
|
||||
const onSubdocs = ({ added }: { added: Set<Y.Doc> }) => {
|
||||
const onSubdocs = ({ added }: { added: Set<YDoc> }) => {
|
||||
added.forEach(subdoc => {
|
||||
this.setupListener(subdoc.guid);
|
||||
});
|
||||
@@ -214,7 +214,7 @@ export class SecondaryWorkspaceSQLiteDB extends BaseSQLiteAdapter {
|
||||
) => {
|
||||
const doc = this.getDoc(docId);
|
||||
if (doc) {
|
||||
Y.applyUpdate(this.yDoc, data, origin);
|
||||
applyUpdate(this.yDoc, data, origin);
|
||||
} else {
|
||||
logger.warn(
|
||||
'[SecondaryWorkspaceSQLiteDB] applyUpdate: doc not found',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { InsertRow } from '@affine/native';
|
||||
import { debounce } from 'lodash-es';
|
||||
import { Subject } from 'rxjs';
|
||||
import * as Y from 'yjs';
|
||||
import { applyUpdate, Doc as YDoc, encodeStateAsUpdate } from 'yjs';
|
||||
|
||||
import { logger } from '../logger';
|
||||
import type { YOrigin } from '../type';
|
||||
@@ -13,7 +13,7 @@ const TRIM_SIZE = 500;
|
||||
|
||||
export class WorkspaceSQLiteDB extends BaseSQLiteAdapter {
|
||||
role = 'primary';
|
||||
yDoc = new Y.Doc();
|
||||
yDoc = new YDoc();
|
||||
firstConnected = false;
|
||||
|
||||
update$ = new Subject<void>();
|
||||
@@ -78,7 +78,7 @@ export class WorkspaceSQLiteDB extends BaseSQLiteAdapter {
|
||||
doc.subdocs.forEach(subdoc => {
|
||||
this.setupListener(subdoc.guid);
|
||||
});
|
||||
const onSubdocs = ({ added }: { added: Set<Y.Doc> }) => {
|
||||
const onSubdocs = ({ added }: { added: Set<YDoc> }) => {
|
||||
logger.info('onSubdocs', this.workspaceId, docId, added);
|
||||
added.forEach(subdoc => {
|
||||
this.setupListener(subdoc.guid);
|
||||
@@ -126,7 +126,7 @@ export class WorkspaceSQLiteDB extends BaseSQLiteAdapter {
|
||||
getDocAsUpdates = (docId?: string) => {
|
||||
const doc = docId ? this.getDoc(docId) : this.yDoc;
|
||||
if (doc) {
|
||||
return Y.encodeStateAsUpdate(doc);
|
||||
return encodeStateAsUpdate(doc);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
@@ -144,7 +144,7 @@ export class WorkspaceSQLiteDB extends BaseSQLiteAdapter {
|
||||
// yjs-idb will always trim the db for the first time after DB is loaded
|
||||
const doc = this.getDoc(docId);
|
||||
if (doc) {
|
||||
Y.applyUpdate(doc, data, origin);
|
||||
applyUpdate(doc, data, origin);
|
||||
} else {
|
||||
logger.warn('[WorkspaceSQLiteDB] applyUpdate: doc not found', docId);
|
||||
}
|
||||
|
||||
5
apps/prototype/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# AFFiNE Prototype
|
||||
|
||||
> This is a prototype of the AFFiNE system to test the feasibility of the approach.
|
||||
>
|
||||
> It is not intended for production use.
|
||||
15
apps/prototype/index.html
Normal file
@@ -0,0 +1,15 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>AFFiNE Prototype</title>
|
||||
</head>
|
||||
<body>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="suite/provider-status.html">Provider status test</a>
|
||||
</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
40
apps/prototype/package.json
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "@affine/prototype",
|
||||
"private": true,
|
||||
"version": "0.8.0-canary.21",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --host --port 3003",
|
||||
"build": "tsc -b && vite build",
|
||||
"preview": "vite preview --host --port 3003"
|
||||
},
|
||||
"dependencies": {
|
||||
"@affine-test/fixtures": "workspace:*",
|
||||
"@affine/component": "workspace:*",
|
||||
"@affine/debug": "workspace:*",
|
||||
"@affine/env": "workspace:*",
|
||||
"@affine/graphql": "workspace:*",
|
||||
"@affine/i18n": "workspace:*",
|
||||
"@affine/jotai": "workspace:*",
|
||||
"@affine/templates": "workspace:*",
|
||||
"@affine/workspace": "workspace:*",
|
||||
"@blocksuite/block-std": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/icons": "^2.1.31",
|
||||
"@blocksuite/lit": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@toeverything/hooks": "workspace:*",
|
||||
"@toeverything/y-indexeddb": "workspace:*",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.20",
|
||||
"@types/react-dom": "^18.2.7",
|
||||
"@vitejs/plugin-react-swc": "^3.3.2",
|
||||
"typescript": "^5.1.6",
|
||||
"vite": "^4.4.9"
|
||||
}
|
||||
}
|
||||
16
apps/prototype/project.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "prototype",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"projectType": "application",
|
||||
"sourceRoot": "apps/prototype/src",
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "nx:run-script",
|
||||
"dependsOn": ["^build"],
|
||||
"options": {
|
||||
"script": "build"
|
||||
},
|
||||
"outputs": ["{projectRoot}/dist"]
|
||||
}
|
||||
}
|
||||
}
|
||||
57
apps/prototype/src/provider-status.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import type { LocalIndexedDBBackgroundProvider } from '@affine/env/workspace';
|
||||
import { createIndexedDBBackgroundProvider } from '@affine/workspace/providers';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { useDataSourceStatus } from '@toeverything/hooks/use-data-source-status';
|
||||
import React, { useCallback, useRef } from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import { Awareness } from 'y-protocols/awareness';
|
||||
import { Doc } from 'yjs';
|
||||
|
||||
const doc = new Doc();
|
||||
const map = doc.getMap();
|
||||
const awareness = new Awareness(doc);
|
||||
|
||||
const indexeddbProvider = createIndexedDBBackgroundProvider('test', doc, {
|
||||
awareness,
|
||||
}) as LocalIndexedDBBackgroundProvider;
|
||||
indexeddbProvider.connect();
|
||||
|
||||
const App = () => {
|
||||
const counterRef = useRef(0);
|
||||
const disposeRef = useRef<number>(0);
|
||||
const status = useDataSourceStatus(indexeddbProvider);
|
||||
return (
|
||||
<div>
|
||||
<button
|
||||
data-testid="start-button"
|
||||
onClick={useCallback(() => {
|
||||
disposeRef.current = setInterval(() => {
|
||||
const counter = counterRef.current;
|
||||
map.set('counter', counter + 1);
|
||||
counterRef.current = counter + 1;
|
||||
}, 0) as any;
|
||||
}, [])}
|
||||
>
|
||||
start writing
|
||||
</button>
|
||||
<button
|
||||
data-testid="stop-button"
|
||||
onClick={useCallback(() => {
|
||||
clearInterval(disposeRef.current);
|
||||
}, [])}
|
||||
>
|
||||
stop writing
|
||||
</button>
|
||||
<div data-testid="status">{status.type}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const root = document.getElementById('root');
|
||||
assertExists(root);
|
||||
|
||||
ReactDOM.createRoot(root).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
1
apps/prototype/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
12
apps/prototype/suite/provider-status.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Provider status test</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="../src/provider-status.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
40
apps/prototype/tsconfig.json
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "bundler",
|
||||
"outDir": "./lib"
|
||||
},
|
||||
"include": ["./src"],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../packages/component"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/debug"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/env"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/graphql"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/hooks"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/i18n"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/jotai"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/y-indexeddb"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/workspace"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
11
apps/prototype/tsconfig.node.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"outDir": "./lib",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
22
apps/prototype/vite.config.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
import react from '@vitejs/plugin-react-swc';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
build: {
|
||||
target: 'ES2022',
|
||||
sourcemap: true,
|
||||
rollupOptions: {
|
||||
input: {
|
||||
'suite/provider-status': resolve(
|
||||
__dirname,
|
||||
'suite',
|
||||
'provider-status.html'
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [react()],
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@affine/server",
|
||||
"private": true,
|
||||
"version": "0.8.0-canary.15",
|
||||
"version": "0.8.0-canary.21",
|
||||
"description": "Affine Node.js server",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
@@ -16,9 +16,9 @@
|
||||
"postinstall": "prisma generate"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/server": "^4.8.1",
|
||||
"@apollo/server": "^4.9.1",
|
||||
"@auth/prisma-adapter": "^1.0.1",
|
||||
"@aws-sdk/client-s3": "^3.378.0",
|
||||
"@aws-sdk/client-s3": "^3.388.0",
|
||||
"@nestjs/apollo": "^12.0.7",
|
||||
"@nestjs/common": "^10.1.3",
|
||||
"@nestjs/core": "^10.1.3",
|
||||
@@ -27,7 +27,7 @@
|
||||
"@node-rs/argon2": "^1.5.2",
|
||||
"@node-rs/crc32": "^1.7.2",
|
||||
"@node-rs/jsonwebtoken": "^0.2.3",
|
||||
"@prisma/client": "^5.0.0",
|
||||
"@prisma/client": "^5.1.1",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"dotenv": "^16.3.1",
|
||||
"express": "^4.18.2",
|
||||
@@ -35,8 +35,8 @@
|
||||
"graphql-type-json": "^0.3.2",
|
||||
"graphql-upload": "^16.0.2",
|
||||
"lodash-es": "^4.17.21",
|
||||
"next-auth": "^4.22.1",
|
||||
"nodemailer": "^6.9.3",
|
||||
"next-auth": "4.22.1",
|
||||
"nodemailer": "^6.9.4",
|
||||
"parse-duration": "^1.1.0",
|
||||
"prisma": "^5.1.1",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
@@ -50,11 +50,11 @@
|
||||
"@types/cookie-parser": "^1.4.3",
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/lodash-es": "^4.17.8",
|
||||
"@types/node": "^18.17.1",
|
||||
"@types/node": "^18.17.4",
|
||||
"@types/nodemailer": "^6.4.9",
|
||||
"@types/supertest": "^2.0.12",
|
||||
"c8": "^8.0.1",
|
||||
"nodemon": "^2.0.22",
|
||||
"nodemon": "^3.0.1",
|
||||
"supertest": "^6.3.3",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.1.6"
|
||||
|
||||
@@ -9,34 +9,34 @@
|
||||
"dependencies": {
|
||||
"@affine/component": "workspace:*",
|
||||
"@affine/i18n": "workspace:*",
|
||||
"@storybook/addon-actions": "^7.1.1",
|
||||
"@storybook/addon-essentials": "^7.1.1",
|
||||
"@storybook/addon-interactions": "^7.1.1",
|
||||
"@storybook/addon-links": "^7.1.1",
|
||||
"@storybook/addon-storysource": "^7.1.1",
|
||||
"@storybook/blocks": "^7.1.1",
|
||||
"@storybook/builder-vite": "^7.1.1",
|
||||
"@storybook/addon-actions": "^7.2.3",
|
||||
"@storybook/addon-essentials": "^7.2.3",
|
||||
"@storybook/addon-interactions": "^7.2.3",
|
||||
"@storybook/addon-links": "^7.2.3",
|
||||
"@storybook/addon-storysource": "^7.2.3",
|
||||
"@storybook/blocks": "^7.2.3",
|
||||
"@storybook/builder-vite": "^7.2.3",
|
||||
"@storybook/jest": "^0.1.0",
|
||||
"@storybook/react": "^7.1.1",
|
||||
"@storybook/react-vite": "^7.1.1",
|
||||
"@storybook/test-runner": "^0.11.0",
|
||||
"@storybook/react": "^7.2.3",
|
||||
"@storybook/react-vite": "^7.2.3",
|
||||
"@storybook/test-runner": "^0.13.0",
|
||||
"@storybook/testing-library": "^0.2.0",
|
||||
"@vitejs/plugin-react": "^4.0.4",
|
||||
"concurrently": "^8.2.0",
|
||||
"jest-mock": "^29.6.2",
|
||||
"serve": "^14.2.0",
|
||||
"storybook": "^7.1.1",
|
||||
"storybook": "^7.2.3",
|
||||
"storybook-dark-mode": "^3.0.1",
|
||||
"wait-on": "^7.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@blocksuite/block-std": "0.0.0-20230809030546-32e6e21d-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230809030546-32e6e21d-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230809030546-32e6e21d-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230809030546-32e6e21d-nightly",
|
||||
"@blocksuite/block-std": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/icons": "^2.1.31",
|
||||
"@blocksuite/lit": "0.0.0-20230809030546-32e6e21d-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230809030546-32e6e21d-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0"
|
||||
},
|
||||
@@ -48,5 +48,5 @@
|
||||
"@blocksuite/lit": "*",
|
||||
"@blocksuite/store": "*"
|
||||
},
|
||||
"version": "0.8.0-canary.15"
|
||||
"version": "0.8.0-canary.21"
|
||||
}
|
||||
|
||||
45
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@affine/monorepo",
|
||||
"version": "0.8.0-canary.15",
|
||||
"version": "0.8.0-canary.21",
|
||||
"private": true,
|
||||
"author": "toeverything",
|
||||
"license": "MPL-2.0",
|
||||
@@ -14,14 +14,15 @@
|
||||
"tests/kit",
|
||||
"tests/affine-legacy/*",
|
||||
"tests/affine-local",
|
||||
"tests/affine-plugin"
|
||||
"tests/affine-plugin",
|
||||
"tests/affine-prototype"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18.16.1 <19.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "dev-core",
|
||||
"dev:electron": "yarn workspace @affine/electron dev:app",
|
||||
"dev:electron": "yarn workspace @affine/electron dev",
|
||||
"build": "yarn nx build @affine/core",
|
||||
"build:electron": "yarn nx build @affine/electron",
|
||||
"build:storage": "yarn nx run-many -t build -p @affine/storage",
|
||||
@@ -60,40 +61,40 @@
|
||||
"@affine-test/kit": "workspace:*",
|
||||
"@affine/cli": "workspace:*",
|
||||
"@affine/plugin-cli": "workspace:*",
|
||||
"@commitlint/cli": "^17.6.7",
|
||||
"@commitlint/config-conventional": "^17.6.7",
|
||||
"@commitlint/cli": "^17.7.1",
|
||||
"@commitlint/config-conventional": "^17.7.0",
|
||||
"@faker-js/faker": "^8.0.2",
|
||||
"@istanbuljs/schema": "^0.1.3",
|
||||
"@magic-works/i18n-codegen": "^0.5.0",
|
||||
"@nx/vite": "16.6.0",
|
||||
"@perfsee/sdk": "^1.8.5",
|
||||
"@playwright/test": "^1.36.2",
|
||||
"@perfsee/sdk": "^1.9.0",
|
||||
"@playwright/test": "^1.37.0",
|
||||
"@taplo/cli": "^0.5.2",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@toeverything/infra": "workspace:*",
|
||||
"@types/affine__env": "workspace:*",
|
||||
"@types/eslint": "^8.44.2",
|
||||
"@types/node": "^18.17.1",
|
||||
"@typescript-eslint/eslint-plugin": "^6.2.1",
|
||||
"@typescript-eslint/parser": "^6.2.1",
|
||||
"@types/node": "^18.17.4",
|
||||
"@typescript-eslint/eslint-plugin": "^6.3.0",
|
||||
"@typescript-eslint/parser": "^6.3.0",
|
||||
"@vanilla-extract/vite-plugin": "^3.8.2",
|
||||
"@vanilla-extract/webpack-plugin": "^2.2.0",
|
||||
"@vitejs/plugin-react-swc": "^3.3.2",
|
||||
"@vitest/coverage-istanbul": "^0.33.0",
|
||||
"@vitest/ui": "^0.33.0",
|
||||
"eslint": "^8.46.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-plugin-i": "^2.27.5-3",
|
||||
"@vitest/coverage-istanbul": "0.33.0",
|
||||
"@vitest/ui": "0.33.0",
|
||||
"eslint": "^8.47.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-i": "^2.28.0",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"eslint-plugin-react": "^7.33.0",
|
||||
"eslint-plugin-react": "^7.33.1",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-simple-import-sort": "^10.0.0",
|
||||
"eslint-plugin-sonarjs": "^0.20.0",
|
||||
"eslint-plugin-unicorn": "^48.0.0",
|
||||
"eslint-plugin-unicorn": "^48.0.1",
|
||||
"eslint-plugin-unused-imports": "^3.0.0",
|
||||
"eslint-plugin-vue": "^9.16.1",
|
||||
"eslint-plugin-vue": "^9.17.0",
|
||||
"fake-indexeddb": "4.0.2",
|
||||
"happy-dom": "^10.8.0",
|
||||
"happy-dom": "^10.9.0",
|
||||
"husky": "^8.0.3",
|
||||
"lint-staged": "^13.2.3",
|
||||
"madge": "^6.1.0",
|
||||
@@ -102,7 +103,7 @@
|
||||
"nx": "16.6.0",
|
||||
"nx-cloud": "latest",
|
||||
"nyc": "^15.1.0",
|
||||
"prettier": "^3.0.0",
|
||||
"prettier": "^3.0.1",
|
||||
"semver": "^7.5.4",
|
||||
"serve": "^14.2.0",
|
||||
"ts-node": "^10.9.1",
|
||||
@@ -111,9 +112,9 @@
|
||||
"vite-plugin-istanbul": "^5.0.0",
|
||||
"vite-plugin-static-copy": "^0.17.0",
|
||||
"vite-tsconfig-paths": "^4.2.0",
|
||||
"vitest": "^0.33.0",
|
||||
"vitest": "0.33.0",
|
||||
"vitest-fetch-mock": "^0.2.2",
|
||||
"vitest-mock-extended": "^1.1.4"
|
||||
"vitest-mock-extended": "^1.2.0"
|
||||
},
|
||||
"packageManager": "yarn@3.6.0"
|
||||
}
|
||||
|
||||
16
packages/@types/env/__all.d.ts
vendored
@@ -52,3 +52,19 @@ declare global {
|
||||
// eslint-disable-next-line no-var
|
||||
var websocketPrefixUrl: string;
|
||||
}
|
||||
|
||||
declare module '@blocksuite/store' {
|
||||
interface PageMeta {
|
||||
favorite?: boolean;
|
||||
subpageIds: string[];
|
||||
// If a page remove to trash, and it is a subpage, it will remove from its parent `subpageIds`, 'trashRelate' is use for save it parent
|
||||
trashRelate?: string;
|
||||
trash?: boolean;
|
||||
trashDate?: number;
|
||||
updatedDate?: number;
|
||||
mode?: 'page' | 'edgeless';
|
||||
jumpOnce?: boolean;
|
||||
// todo: support `number` in the future
|
||||
isPublic?: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
2
packages/@types/env/package.json
vendored
@@ -7,5 +7,5 @@
|
||||
"@affine/env": "workspace:*",
|
||||
"@toeverything/infra": "workspace:*"
|
||||
},
|
||||
"version": "0.8.0-canary.15"
|
||||
"version": "0.8.0-canary.21"
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
"./config": "./src/config/index.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@clack/core": "^0.3.2",
|
||||
"@clack/prompts": "^0.6.3",
|
||||
"@clack/core": "^0.3.3",
|
||||
"@clack/prompts": "^0.7.0",
|
||||
"ts-node": "^10.9.1"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -20,5 +20,5 @@
|
||||
"peerDependencies": {
|
||||
"ts-node": "*"
|
||||
},
|
||||
"version": "0.8.0-canary.15"
|
||||
"version": "0.8.0-canary.21"
|
||||
}
|
||||
|
||||
@@ -26,9 +26,9 @@
|
||||
"@emotion/react": "^11.11.1",
|
||||
"@emotion/server": "^11.11.0",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@mui/base": "5.0.0-beta.8",
|
||||
"@mui/icons-material": "^5.14.1",
|
||||
"@mui/material": "^5.14.2",
|
||||
"@mui/base": "5.0.0-beta.10",
|
||||
"@mui/icons-material": "^5.14.3",
|
||||
"@mui/material": "^5.14.4",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@radix-ui/react-avatar": "^1.0.3",
|
||||
"@radix-ui/react-collapsible": "^1.0.3",
|
||||
@@ -36,28 +36,28 @@
|
||||
"@radix-ui/react-scroll-area": "^1.0.4",
|
||||
"@radix-ui/react-toast": "^1.1.4",
|
||||
"@toeverything/hooks": "workspace:*",
|
||||
"@toeverything/theme": "^0.7.11",
|
||||
"@toeverything/theme": "^0.7.12",
|
||||
"@vanilla-extract/dynamic": "^2.0.3",
|
||||
"clsx": "^2.0.0",
|
||||
"dayjs": "^1.11.9",
|
||||
"jotai": "^2.2.2",
|
||||
"lit": "^2.7.6",
|
||||
"jotai": "^2.3.1",
|
||||
"lit": "^2.8.0",
|
||||
"lottie-web": "^5.12.2",
|
||||
"react": "18.2.0",
|
||||
"react-datepicker": "^4.16.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-error-boundary": "^4.0.10",
|
||||
"react-error-boundary": "^4.0.11",
|
||||
"react-is": "^18.2.0",
|
||||
"rxjs": "^7.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@blocksuite/blocks": "0.0.0-20230809030546-32e6e21d-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230809030546-32e6e21d-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230809030546-32e6e21d-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/icons": "^2.1.31",
|
||||
"@blocksuite/lit": "0.0.0-20230809030546-32e6e21d-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230809030546-32e6e21d-nightly",
|
||||
"@types/react": "^18.2.17",
|
||||
"@blocksuite/lit": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@types/react": "^18.2.20",
|
||||
"@types/react-datepicker": "^4.15.0",
|
||||
"@types/react-dnd": "^3.0.2",
|
||||
"@types/react-dom": "^18.2.7",
|
||||
@@ -66,5 +66,5 @@
|
||||
"vite": "^4.4.9",
|
||||
"yjs": "^13.6.7"
|
||||
},
|
||||
"version": "0.8.0-canary.15"
|
||||
"version": "0.8.0-canary.21"
|
||||
}
|
||||
|
||||
@@ -184,7 +184,7 @@ export const BlockSuiteEditor = memo(function BlockSuiteEditor(
|
||||
)}
|
||||
>
|
||||
<Suspense fallback={<BlockSuiteFallback />}>
|
||||
<BlockSuiteEditorImpl {...props} />
|
||||
<BlockSuiteEditorImpl key={props.page.id} {...props} />
|
||||
</Suspense>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { FlexWrapper } from '@affine/component';
|
||||
import { EditCollectionModel } from '@affine/component/page-list';
|
||||
import type { Collection, Filter } from '@affine/env/filter';
|
||||
import type { PropertiesMeta } from '@affine/env/filter';
|
||||
@@ -6,13 +7,11 @@ import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { FilteredIcon, FolderIcon, ViewLayersIcon } from '@blocksuite/icons';
|
||||
import { Button } from '@toeverything/components/button';
|
||||
import clsx from 'clsx';
|
||||
import { useAtom } from 'jotai';
|
||||
import type { MouseEvent } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
import { MenuItem, Tooltip } from '../../..';
|
||||
import Menu from '../../../ui/menu/menu';
|
||||
import { appSidebarOpenAtom } from '../../app-sidebar';
|
||||
import { CreateFilterMenu } from '../filter/vars';
|
||||
import type { useCollectionManager } from '../use-collection-manager';
|
||||
import * as styles from './collection-list.css';
|
||||
@@ -106,7 +105,6 @@ export const CollectionList = ({
|
||||
propertiesMeta: PropertiesMeta;
|
||||
}) => {
|
||||
const t = useAFFiNEI18N();
|
||||
const [open] = useAtom(appSidebarOpenAtom);
|
||||
const [collection, setCollection] = useState<Collection>();
|
||||
const onChange = useCallback(
|
||||
(filterList: Filter[]) => {
|
||||
@@ -133,15 +131,7 @@ export const CollectionList = ({
|
||||
[closeUpdateCollectionModal, setting]
|
||||
);
|
||||
return (
|
||||
<div
|
||||
className={clsx({
|
||||
[styles.filterButtonCollapse]: !open,
|
||||
})}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<FlexWrapper alignItems="center">
|
||||
{setting.savedCollections.length > 0 && (
|
||||
<Menu
|
||||
trigger="click"
|
||||
@@ -176,8 +166,8 @@ export const CollectionList = ({
|
||||
}
|
||||
>
|
||||
<Button
|
||||
className={clsx(styles.viewButton)}
|
||||
data-testid="collection-select"
|
||||
style={{ marginRight: '20px' }}
|
||||
>
|
||||
<Tooltip
|
||||
content={setting.currentCollection.name}
|
||||
@@ -199,11 +189,7 @@ export const CollectionList = ({
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
icon={<FilteredIcon />}
|
||||
className={clsx(styles.filterButton)}
|
||||
data-testid="create-first-filter"
|
||||
>
|
||||
<Button icon={<FilteredIcon />} data-testid="create-first-filter">
|
||||
{t['com.affine.filter']()}
|
||||
</Button>
|
||||
</Menu>
|
||||
@@ -215,6 +201,6 @@ export const CollectionList = ({
|
||||
onClose={closeUpdateCollectionModal}
|
||||
onConfirm={onConfirm}
|
||||
></EditCollectionModel>
|
||||
</div>
|
||||
</FlexWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -190,11 +190,11 @@ input[type='number']::-webkit-outer-spin-button {
|
||||
width: 0; /* Chrome Safari */
|
||||
height: 0;
|
||||
}
|
||||
.affine-default-viewport::-webkit-scrollbar {
|
||||
.affine-doc-viewport::-webkit-scrollbar {
|
||||
width: 20px; /* Chrome Safari */
|
||||
height: 20px;
|
||||
}
|
||||
.affine-default-viewport::-webkit-scrollbar-thumb {
|
||||
.affine-doc-viewport::-webkit-scrollbar-thumb {
|
||||
border-radius: 12px;
|
||||
background-clip: padding-box;
|
||||
border-style: solid;
|
||||
@@ -202,10 +202,10 @@ input[type='number']::-webkit-outer-spin-button {
|
||||
border-color: transparent;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.affine-default-viewport:hover::-webkit-scrollbar-thumb {
|
||||
.affine-doc-viewport:hover::-webkit-scrollbar-thumb {
|
||||
background-color: var(--affine-divider-color);
|
||||
}
|
||||
.affine-default-viewport:hover::-webkit-scrollbar-thumb:hover {
|
||||
.affine-doc-viewport:hover::-webkit-scrollbar-thumb:hover {
|
||||
background-color: var(--affine-icon-color);
|
||||
border-width: 5px;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import ModalUnstyled from '@mui/base/Modal';
|
||||
import { Modal as ModalUnstyled } from '@mui/base/Modal';
|
||||
import type { CSSProperties } from 'react';
|
||||
|
||||
import { styled } from '../../styles';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import MuiClickAwayListener from '@mui/base/ClickAwayListener';
|
||||
import { ClickAwayListener as MuiClickAwayListener } from '@mui/base/ClickAwayListener';
|
||||
import MuiAvatar from '@mui/material/Avatar';
|
||||
import MuiBreadcrumbs from '@mui/material/Breadcrumbs';
|
||||
import MuiCollapse from '@mui/material/Collapse';
|
||||
@@ -6,6 +6,7 @@ import MuiFade from '@mui/material/Fade';
|
||||
import MuiGrow from '@mui/material/Grow';
|
||||
import MuiSkeleton from '@mui/material/Skeleton';
|
||||
import MuiSlide from '@mui/material/Slide';
|
||||
|
||||
export {
|
||||
MuiAvatar,
|
||||
MuiBreadcrumbs,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import ClickAwayListener from '@mui/base/ClickAwayListener';
|
||||
import PopperUnstyled from '@mui/base/Popper';
|
||||
import { ClickAwayListener } from '@mui/base/ClickAwayListener';
|
||||
import { Popper as PopperUnstyled } from '@mui/base/Popper';
|
||||
import Grow from '@mui/material/Grow';
|
||||
import type { CSSProperties, PointerEvent } from 'react';
|
||||
import {
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"devDependencies": {
|
||||
"@types/debug": "^4.1.8"
|
||||
},
|
||||
"version": "0.8.0-canary.15"
|
||||
"version": "0.8.0-canary.21"
|
||||
}
|
||||
|
||||
6
packages/env/package.json
vendored
@@ -5,7 +5,7 @@
|
||||
"main": "./src/index.ts",
|
||||
"module": "./src/index.ts",
|
||||
"devDependencies": {
|
||||
"@blocksuite/global": "0.0.0-20230809030546-32e6e21d-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"zod": "^3.21.4"
|
||||
@@ -25,7 +25,7 @@
|
||||
"@toeverything/infra": "workspace:*"
|
||||
},
|
||||
"dependencies": {
|
||||
"lit": "^2.7.6"
|
||||
"lit": "^2.8.0"
|
||||
},
|
||||
"version": "0.8.0-canary.15"
|
||||
"version": "0.8.0-canary.21"
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@ import { readFileSync } from 'fs';
|
||||
import { dirname, resolve } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import * as Y from 'yjs';
|
||||
import type { Array as YArray, Map as YMap } from 'yjs';
|
||||
import { applyUpdate, Doc } from 'yjs';
|
||||
|
||||
import { migrateToSubdoc } from '../blocksuite/index.js';
|
||||
|
||||
@@ -11,8 +12,8 @@ const fixturePath = resolve(
|
||||
'workspace.ydoc'
|
||||
);
|
||||
const yDocBuffer = readFileSync(fixturePath);
|
||||
const doc = new Y.Doc();
|
||||
Y.applyUpdate(doc, new Uint8Array(yDocBuffer));
|
||||
const doc = new Doc();
|
||||
applyUpdate(doc, new Uint8Array(yDocBuffer));
|
||||
const migratedDoc = migrateToSubdoc(doc);
|
||||
|
||||
describe('subdoc', () => {
|
||||
@@ -23,8 +24,8 @@ describe('subdoc', () => {
|
||||
for (let i = 0; i < length; i++) {
|
||||
binary[i] = (json as any)[i];
|
||||
}
|
||||
const doc = new Y.Doc();
|
||||
Y.applyUpdate(doc, binary);
|
||||
const doc = new Doc();
|
||||
applyUpdate(doc, binary);
|
||||
{
|
||||
// invoke data
|
||||
doc.getMap('space:hello-world');
|
||||
@@ -32,7 +33,7 @@ describe('subdoc', () => {
|
||||
}
|
||||
const blocks = doc.getMap('space:hello-world').toJSON();
|
||||
const newDoc = migrateToSubdoc(doc);
|
||||
const subDoc = newDoc.getMap('spaces').get('space:hello-world') as Y.Doc;
|
||||
const subDoc = newDoc.getMap('spaces').get('space:hello-world') as Doc;
|
||||
const data = (subDoc.toJSON() as any).blocks;
|
||||
Object.keys(data).forEach(id => {
|
||||
if (id === 'xyWNqindHH') {
|
||||
@@ -50,23 +51,23 @@ describe('subdoc', () => {
|
||||
|
||||
test('Test fixture should be set correctly', () => {
|
||||
const meta = doc.getMap('space:meta');
|
||||
const versions = meta.get('versions') as Y.Map<unknown>;
|
||||
const versions = meta.get('versions') as YMap<unknown>;
|
||||
expect(versions.get('affine:code')).toBeTypeOf('number');
|
||||
});
|
||||
|
||||
test('Meta data should be migrated correctly', () => {
|
||||
const originalMeta = doc.getMap('space:meta');
|
||||
const originalVersions = originalMeta.get('versions') as Y.Map<unknown>;
|
||||
const originalVersions = originalMeta.get('versions') as YMap<unknown>;
|
||||
|
||||
const meta = migratedDoc.getMap('meta');
|
||||
const blockVersions = meta.get('blockVersions') as Y.Map<unknown>;
|
||||
const blockVersions = meta.get('blockVersions') as YMap<unknown>;
|
||||
|
||||
expect(meta.get('workspaceVersion')).toBe(1);
|
||||
expect(blockVersions.get('affine:code')).toBe(
|
||||
originalVersions.get('affine:code')
|
||||
);
|
||||
expect((meta.get('pages') as Y.Array<unknown>).length).toBe(
|
||||
(originalMeta.get('pages') as Y.Array<unknown>).length
|
||||
expect((meta.get('pages') as YArray<unknown>).length).toBe(
|
||||
(originalMeta.get('pages') as YArray<unknown>).length
|
||||
);
|
||||
|
||||
expect(blockVersions.get('affine:embed')).toBeUndefined();
|
||||
|
||||
14
packages/env/src/blocksuite/index.ts
vendored
@@ -1,16 +1,8 @@
|
||||
import type { Page } from '@blocksuite/store';
|
||||
|
||||
export async function initPageWithPreloading(page: Page) {
|
||||
const workspace = page.workspace;
|
||||
const {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
data,
|
||||
} = await import('@affine/templates/preloading.json');
|
||||
await page.waitForLoaded();
|
||||
await workspace.importPageSnapshot(data['space:hello-world'], page.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export async function initEmptyPage(page: Page) {
|
||||
await page.waitForLoaded();
|
||||
const pageBlockId = page.addBlock('affine:page', {
|
||||
|
||||